Repository navigation
contrib.host.tcl: the #904 bidirectional tier - #3493
Merged
Merged
Conversation
A persistent VM per interpreter, Aether callbacks registered as Tcl
commands, global setters, typed arguments and returns, and typed eval
results, in the shape Lua and Duktape already have, plus a TCL_TFLOAT
tag. Particular to Tcl:
- The eval compiles the script whole (Tcl_EvalObjEx). Tcl checks its
limits only in compiled code, and a top-level `while 1 {incr i}` run
command by command (Tcl_EvalEx) never reached a check.
- Two guards: the command limit, and a time limit for loops that run no
commands (`while 1 {}`). Both are per outermost eval; a callback's
nested eval runs inside them rather than renewing them.
- A callback may evaluate on its own VM: at global level, with the outer
call's arguments kept, and with `return`, `return -code error`, deep
returns and stray `break` read as Tcl reads them at the top.
- A VM is refused from any thread but its creator's, without touching Tcl.
- Reference counts go through Tcl_DbIncrRefCount/DecrRefCount; the one
field read directly, Tcl_Obj.typePtr, sits where 8.6 and 9.0 both put it.
Tests: the co-located std.spec (contrib-host-check) and a C harness in
tests/integration/host_tcl_bidirectional/. 17 planted defects, 16 caught;
the survivor is equivalent to the original.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
contrib.host.tclgets the #904 bidirectional tier that Lua and Duktape already have:@c_callbacks registered as Tcl commands;It ticks Tcl off
contrib/host/TODO.md. It is also groundwork for the sandboxed-child design: there, a guest calls the host and the host calls back into the guest, and that is what nested evals here test.API
tcl.vm_new/vm_free/vm_register/vm_set_global_str|int|strlist/vm_eval/vm_result_type|int|double|str|bool/vm_pop_result/vm_set_instruction_limit/vm_set_time_limit. Inside a callback:tcl.arg_count|type|int|double|str|bool,tcl.push_int|double|str|bool|nilandtcl.raise_error.The tags are
TCL_TNIL…TCL_TOTHERas in Lua and Duktape, plusTCL_TFLOAT.A command is registered under the name it is given, unchanged.
"app.add"is called asapp.add 2 3, and"app::add"lands in the namespace::app.What is particular to Tcl
Tcl_EvalObjEx). Tcl checks its limits only inside compiled code, every few dozen instructions. Run as a string (Tcl_EvalEx), a top-levelwhile 1 {incr i}executes a command at a time, and each pass of its body is a compiled run too short to reach a check. So the command limit never fired. Plain Tcl, without the bridge, hangs the same way.vm_set_instruction_limitcounts Tcl commands, so it cannot stop a loop that runs none, such aswhile 1 {}, which compiles to jumps.vm_set_time_limitcan. Both apply per outermost eval, and a script cannotcatcheither one. An eval nested in a callback runs inside the outer budget rather than renewing it.returninto success and a straybreakinto an error. For a nested eval the bridge does that, reading the return options.return -code error,return -level 2and codes Tcl doesn't know come out as Tcl reports them at the top.vm_*call from another thread is refused without touching Tcl:vm_evalreturns -1, and the error result says why.expr {2+3}is an int, but the literal5inset x 5is a string. The readers convert either way.push_nilreturns the empty string, which reads asTCL_TNIL.push_boolreturns 1 or 0.Tcl_DbIncrRefCount/Tcl_DbDecrRefCount. TheTcl_IncrRefCountmacros write a field that 9.0 widened.Tcl_Obj.typePtr. It is three pointers in on both versions, and a_Static_assertholds the bridge to that.Tests
contrib/host/tcl/test_host_tcl.ae: a co-located std.spec run bymake contrib-host-check, 3 cases.tests/integration/host_tcl_bidirectional/: a C harness, shaped like Duktape's, covering:Tcl_EvalEx)info level1while 1 {host.nest_small}never ends (timeout)breaksucceedsreturn -level 2One more mutant survived: treating every non-ok code as an error. It is equivalent to the original, because by that line the code has already been reduced to ok or error. Two of the mutants above, built into the archive, also fail the Aether spec: the raise case fails 3 assertions, and the eval-as-string case hangs.
Checked
make contrib-host-checkpasses;tclisOKandPASS (built + ran), and its spec gives 3 passing.ae fmt --check,check-docs,check-changelog,check-tests,check-warnings,check-raw-calls,check-extern-widthsandcheck-standalonepass.Not run: the full
make testandmake test-ae, since the runtime is untouched and the new test was run on its own. Tcl 9.0 is also untested; this machine has 8.6.13.🤖 Generated with Claude Code