1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
//! One tclrs interpreter behind every `Tcl_Interp *` Tk holds.
//!
//! Phase 1's host answered questions about data structures; it had no
//! evaluator, and the run stopped the moment Tk asked for one
//! (`tk9.0.4/generic/tkOption.c:1592`). This module is the other half of the
//! join: a [`HostInterp`] — the 32 bytes Tk's `Tcl_InitStubs` reads, plus the
//! slot state behind it — is paired here with a real [`crate::runtime::Interp`],
//! and a script Tk hands over is compiled by this crate's own parser and
//! compiler and run on fusevm.
//!
//! # Why a registry rather than a field
//!
//! `Host` is the struct the stub bodies operate on and it is shared with the
//! rest of the phase's work. Keeping the pairing here, keyed by the address of
//! the `Host`, means the evaluator can be added without changing that struct's
//! shape — and the shape is what several files agree on.
//!
//! # The second interpreter
//!
//! Tk asks for one: `Tcl_CreateInterp` at
//! `tk9.0.4/generic/tkOption.c:1497`, used to hold the option database while it
//! is parsed, then `Tcl_DeleteInterp` at 1499. It has to be *independent* — a
//! variable set in it must not be visible in the first — which is exactly what
//! a second [`crate::runtime::Interp`] is, since an interpreter owns its
//! globals map and its chunk cache and shares neither.
//!
//! # Which interpreter a running script belongs to
//!
//! Evaluation re-enters: Tk calls `Tcl_EvalEx`, the script calls a command Tk
//! registered, that command calls `Tcl_EvalEx` again. So "the interpreter this
//! evaluation belongs to" is a stack, not a variable, and it is per thread
//! because a VM run never leaves the thread that started it. [`Scope`] pushes on
//! construction and pops on drop, so an evaluation that fails still unwinds the
//! stack correctly.
use RefCell;
use c_void;
use ptr;
use Mutex;
use ;
use crateShared;
/// The pairing of one `Host` with one tclrs interpreter.
///
/// Keyed by the `Host`'s address rather than the `HostInterp`'s: the slots that
/// take a `Tcl_Interp *` reach the `Host` through it, and the `Host` is what
/// `Tcl_DeleteInterp` frees.
/// Every live pairing. A `Mutex` rather than a thread-local because a `Host` is
/// process-wide state — `Tcl_GetThreadData` and the registered `Tcl_ObjType`s
/// already live on the primary one — while the *current* interpreter is not.
static INTERPS: = new;
/// Whether any Tk interpreter has been created in this process.
///
/// Read by the compiler ([`super::dispatch::may_exist`]) to decide whether an
/// unknown command name is worth lowering as a run-time lookup. False in every
/// process that never loaded Tk, which is what keeps an ordinary script's
/// lowering identical to what it was.
/// The tclrs interpreter behind `host`, creating one on first use.
///
/// A `Shared` is an `Arc` over the interpreter's state, so this hands back a
/// handle rather than a borrow — which is what makes re-entrant evaluation
/// sound: no lock of this registry is held while a script runs.
pub
/// Make `shared` the interpreter behind `host`, replacing whatever
/// [`shared_for`] created.
///
/// What a session opened by `tclrs --tk` does with the interpreter the script
/// is about to run in: a callback Tk evaluates — a `-command` body, a `bind`
/// script, an `after` script — then runs against the same variables and
/// procedures the script that registered it has, rather than in an interpreter
/// of the host's own that shares nothing with it. See
/// [`crate::tk::session::open`].
pub
/// Forget the interpreter behind `host`. Called by `Tcl_DeleteInterp`; the
/// state itself goes when the last `Shared` handle does.
thread_local!
/// The interpreter an evaluation is running in, for as long as it runs.
///
/// Held by value on the stack of whichever slot started the evaluation, so the
/// pop happens on every path out of it — including the one where the script
/// raised an error.
;
/// The innermost interpreter an evaluation is running in on this thread, or the
/// primary one when nothing is running inside a `Tcl_Eval*` call.
///
/// The fallback is what makes a Tk command reachable from a script this process
/// started itself — `tclrs script.tcl` after `Tk_Init` — rather than only from a
/// script Tk asked to have evaluated. Null when no host has been built at all.
/// The `Host` behind a `Tcl_Interp *`, or null.
///
/// # Safety
/// `interp` is either null or a pointer this crate handed to Tk.
pub unsafe