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
172
173
174
175
176
177
178
179
180
181
182
183
184
//! Calling a command Tk registered, from a script tclrs compiled.
//!
//! # The problem this solves
//!
//! tclrs resolves a command name while compiling. `Compiler::dispatch` matches
//! the name against the builtins, the procedures the script defined, the
//! coroutines it created and the functions an inline `rust { … }` block
//! exported, and a name that matches none of them is `invalid command name`.
//! That is the whole reason a Tcl script lowers to straight-line bytecode with
//! no dispatch table in it, and it is what keeps a counted loop inside fusevm's
//! tracing JIT.
//!
//! Tk does not fit that. `button`, `pack`, `wm`, `canvas`, `bind` and the rest
//! are registered with `Tcl_CreateObjCommand` *while `Tk_Init` runs*
//! (`tk9.0.4/generic/tkWindow.c:1004-1096`), which is long after this crate
//! finished compiling whatever script asked for Tk. No amount of compile-time
//! knowledge can name them.
//!
//! # The shape of the answer
//!
//! One extension op, [`crate::compiler::ext::DYN_CALL`], shared with the other
//! kind of name this frontend cannot resolve while compiling — a procedure
//! defined by a `proc` that is not at a script's top level (see
//! [`crate::procs`]). The op is one run-time lookup over two tables, procedures
//! first; this module owns the second one.
//!
//! A *Tk* name reaches it under exactly one condition: the name matched nothing
//! at compile time *and* a Tk interpreter exists in this process. Both halves
//! matter.
//!
//! * A process that never loaded Tk never emits it for an unknown name, so
//! every script that compiled before this existed compiles to the same
//! bytecode now — including `bench/counted_loop_proc.tcl`, whose trace
//! eligibility is what the tiers report measures.
//! * A name that *is* a builtin never reaches it, so no builtin becomes
//! dynamic and no hot loop grows an extension op it did not have.
//!
//! When the op runs and finds nothing registered under the name in either
//! table, it raises the error the compiler would have deferred — same wording,
//! same script line — which is why the fallback is not a second, weaker
//! diagnosis.
//!
//! # What "calling a C command" means
//!
//! A `Tcl_ObjCmdProc` is
//! `int (*)(void *clientData, Tcl_Interp *, int objc, Tcl_Obj *const *objv)`
//! (`generic/tcl.h:587-588`); the `2` variant differs only in taking `objc` as
//! a `Tcl_Size` (`generic/tcl.h:590-591`), which on this platform is
//! `ptrdiff_t` (`generic/tcl.h:332`). By Tcl's convention `objv[0]` is the
//! command name as it was invoked and `objc` counts it, so a command with two
//! arguments is called with `objc == 3`.
//!
//! The values are built as `Tcl_Obj`s of this host's own making, retained
//! across the call so a command that keeps one keeps something live, and
//! released afterwards. The command's answer is its return code plus whatever
//! it left in the interpreter result, which is read back out and becomes the
//! value of the Tcl command that called it.
use ;
use Value;
use ;
use host;
use interp;
use crateto_tcl_string;
/// Whether a Tk interpreter exists in this process at all.
///
/// The compile-time half of the condition above. False in every build without
/// the feature, and false in a `--features tk` build until something has
/// actually created a host.
/// Whether a name written in a script could reach *this* table: a Tk
/// interpreter exists, and the name is not one of the list commands.
///
/// It was the compiler's gate for lowering a name as a run-time lookup. It is
/// not any more: a lookup also finds a procedure another chunk defined, so
/// `crate::compiler` lowers every name no module claims that way in both feature
/// sets, and whether a Tk interpreter exists is decided when the call runs
/// (`invoke` answers `invalid command name` when none does). What is left here
/// is the question this module can answer — would Tk's table be consulted for
/// this name — which `tests/tk_cold_lowering.rs` asks of a process that has never
/// built a host.
///
/// The list commands are excluded by name rather than by trying them and catching
/// the refusal, because a refusal is not always "unknown": `llength` with three
/// arguments refuses too, and that one has to stay a `wrong # args` on
/// `llength`, not become a lookup for a Tk command called `llength`.
/// `cmd_list::COMMANDS` is exactly the set `cmd_list::compile` accepts —
/// `names::tests::every_offered_command_is_known_to_the_compiler` is what keeps
/// that true.
/// `int (*)(void *clientData, Tcl_Interp *, int objc, Tcl_Obj *const *objv)` —
/// `generic/tcl.h:587-588`.
type ObjCmdProc =
unsafe extern "C" fn ;
/// `int (*)(void *clientData, Tcl_Interp *, Tcl_Size objc, Tcl_Obj *const
/// *objv)` — `generic/tcl.h:590-591`. `Tcl_Size` is `ptrdiff_t`
/// (`generic/tcl.h:332`), so the third argument is wider than the `1` variant's
/// and the two signatures are *not* interchangeable: calling a `2` procedure
/// through the `1` type leaves the upper half of `objc` undefined.
type ObjCmdProc2 =
unsafe extern "C" fn ;
/// Call the registered command `name` with `words` as `objv`, returning its
/// completion code. The interpreter result is left as the command left it.
///
/// # Safety
/// `interp_ptr` is a `Tcl_Interp *` this crate handed to Tk, and every entry of
/// `words` is a live `Tcl_Obj`.
pub unsafe
/// Call the registered command `name` with tclrs values, and give back what it
/// left as the interpreter result.
///
/// The fallback half of [`crate::compiler::ext::DYN_CALL`], reached once the
/// run-time procedure table has answered that it knows no such name.
///
/// The words are `Tcl_Obj`s built here: retained before the call because a
/// command may keep one (`Tcl_IncrRefCount` is a macro over `refCount`,
/// `generic/tcl.h:2517-2519`, so a command that keeps one has already written
/// to it by the time this returns), released after.
pub