lua_types/upval.rs
1//! `UpVal` — closure upvalues. PORT_STRATEGY §3.8.
2
3use std::cell::{Cell, Ref, RefCell};
4use crate::StackIdx;
5use crate::value::LuaValue;
6
7/// Discriminator state for an upvalue: either still pointing at a thread's
8/// stack slot, or owning the value after close.
9///
10/// Retained as the public read-side enum for out-of-crate consumers that
11/// pattern-match through `UpVal::slot()`. The canonical storage on `UpVal`
12/// is now a `Cell`-tagged shape; the `RefCell<UpValState>` mirror is
13/// updated in lockstep so existing `slot()` callers keep working.
14#[derive(Debug, Clone)]
15pub enum UpValState {
16 Open {
17 thread_id: usize,
18 idx: StackIdx,
19 },
20 Closed(LuaValue),
21}
22
23/// A closure upvalue. Open upvalues point at a slot on a thread's stack
24/// (referred to by index, since the stack reallocates). Closed upvalues
25/// own the value.
26///
27/// Canonical state lives in two `Cell` fields (the tag and the open payload)
28/// plus a `RefCell<LuaValue>` holding the closed payload. The `state`
29/// `RefCell<UpValState>` mirror is kept consistent so cold consumers that
30/// still call `slot()` see the correct view. The split lets
31/// `state.rs::upvalue_get` / `upvalue_set` short-circuit the Open path with
32/// zero `RefCell` borrow overhead, which is the dominant cost in
33/// fibonacci-class recursion benchmarks.
34#[derive(Debug)]
35pub struct UpVal {
36 open_thread_id: Cell<i64>,
37 open_idx: Cell<u32>,
38 closed_value: RefCell<LuaValue>,
39 pub state: RefCell<UpValState>,
40}
41
42/// Sentinel placed in `open_thread_id` once the upvalue has been closed.
43/// Valid thread ids are non-negative (the main thread is id 0), so -1 is
44/// unambiguous.
45const CLOSED_TAG: i64 = -1;
46
47impl UpVal {
48 pub fn open(thread_id: usize, idx: StackIdx) -> Self {
49 UpVal {
50 open_thread_id: Cell::new(thread_id as i64),
51 open_idx: Cell::new(idx.0),
52 closed_value: RefCell::new(LuaValue::Nil),
53 state: RefCell::new(UpValState::Open { thread_id, idx }),
54 }
55 }
56
57 pub fn closed(v: LuaValue) -> Self {
58 UpVal {
59 open_thread_id: Cell::new(CLOSED_TAG),
60 open_idx: Cell::new(0),
61 closed_value: RefCell::new(v.clone()),
62 state: RefCell::new(UpValState::Closed(v)),
63 }
64 }
65
66 /// Backwards-compat handle on the full `UpValState`. Out-of-crate code
67 /// matches against this through `Ref::deref`. Hot-path callers should
68 /// use `try_open_payload` / `closed_value` instead.
69 pub fn slot(&self) -> Ref<'_, UpValState> { self.state.borrow() }
70
71 pub fn is_open(&self) -> bool { self.open_thread_id.get() >= 0 }
72 pub fn is_closed(&self) -> bool { self.open_thread_id.get() < 0 }
73
74 /// Zero-`RefCell` fast path used by `upvalue_get` / `upvalue_set`.
75 /// Returns `Some((thread_id, idx))` when the upvalue is still open,
76 /// `None` otherwise.
77 #[inline(always)]
78 pub fn try_open_payload(&self) -> Option<(usize, StackIdx)> {
79 let tid = self.open_thread_id.get();
80 if tid < 0 {
81 None
82 } else {
83 Some((tid as usize, StackIdx(self.open_idx.get())))
84 }
85 }
86
87 /// Borrows the closed-side value. Callers must have confirmed the
88 /// upvalue is closed (`try_open_payload` returned `None`).
89 #[inline(always)]
90 pub fn closed_value(&self) -> Ref<'_, LuaValue> { self.closed_value.borrow() }
91
92 pub fn close_with(&self, v: LuaValue) {
93 self.open_thread_id.set(CLOSED_TAG);
94 self.open_idx.set(0);
95 *self.closed_value.borrow_mut() = v.clone();
96 *self.state.borrow_mut() = UpValState::Closed(v);
97 }
98
99 pub fn set_closed_value(&self, v: LuaValue) {
100 self.open_thread_id.set(CLOSED_TAG);
101 self.open_idx.set(0);
102 *self.closed_value.borrow_mut() = v.clone();
103 *self.state.borrow_mut() = UpValState::Closed(v);
104 }
105
106 pub fn try_closed_value(&self) -> Option<std::cell::Ref<'_, LuaValue>> {
107 if self.is_closed() {
108 self.closed_value.try_borrow().ok()
109 } else {
110 None
111 }
112 }
113}
114
115// ──────────────────────────────────────────────────────────────────────────────
116// PORT STATUS
117// source: src/lfunc.h, src/lfunc.c (UpVal struct)
118// target_crate: lua-types
119// confidence: high
120// todos: 0
121// port_notes: 0
122// unsafe_blocks: 0
123// notes: UpVal + UpValState (Open/Closed). C uses a TValue* that switches
124// between stack-pointing (open) and embedded (closed) via union; we
125// use an enum with the equivalent two states. Canonical storage is
126// Cell-tagged (open_thread_id, open_idx, closed_value) so hot-path
127// upvalue_get/_set skip RefCell borrow guards on the Open branch.
128// The RefCell<UpValState> mirror is updated in lockstep so existing
129// out-of-crate slot() consumers (api.rs, debug.rs, coro_lib.rs,
130// func.rs) keep working without migration.
131// ──────────────────────────────────────────────────────────────────────────────