feat(v4): every word but the ten is called, so it can be checked

Plan 2026-10-20 (ENGINE step 4), Task A2, for #189. Captain Bob,
2026-11-13: "The ten"; "Accept it".

- compile.f18 (COMPILE,): a word is copied in line only when it is flagged
  in-line and compile-only (24) -- the ten that work on the return stack,
  >R R> R@ I J LEAVE and the later UNLOOP 2>R 2R> 2R@. Every other word,
  in-line ones too, is a call the hook at `call` sees.
- EXECUTE calls the xt: `call xt` (opcode 3, slot 0) and a ; in (EXEC),
  run. INTERPRET runs a word by (GO): `call xt` and `jump (IL)` in
  (RUNX), entered by a jump, at the old return-stack depth.
- host_map.h: (EXEC) BUF0_W-22, (RUNX) BUF0_W-115, from what was free.
- Its cost, accepted: one return entry at the bottom of a chain (30 deep
  from the prompt, not 31) and one per explicit EXECUTE while its word
  runs; the hosted boot +2.4% (2.917 s to 2.987 s), instructions +0.4%.
- Tests first (logs/20261113-045122/a2): inline_words_are_called,
  residue_stays_inline, execute_passes_the_hook, rstack_kept_across_call,
  abandoned_definition_not_called -- 3 of 1096 failed before. The depth
  checks moved to the new figures; SEE shows the calls; two layout checks
  use in-line test words; 2>R 2R@ 2R> checked in test_host_quit.
- DECOMPOSITION.md EXECUTE, the plan's A2 and rulings, FABRIC-3 XLVIII.22.

Suite: test, test-32 99 programs 0 failures; v4-checks 79 under the
sanitizers 0 failures, the hosted builds alike; hosted POST 539/539.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
This commit is contained in:
Claude
2026-11-13 05:30:10 +00:00
parent bacb7b0616
commit 892e3889f5
14 changed files with 197 additions and 38 deletions
+73 -5
View File
@@ -67,6 +67,37 @@ static void new_session(void)
* starts with the canary alone; `depth` extra marked cells go under it and
* `rdepth` under the return address, for the headroom tests. True when
* INTERPRET returns. */
/* The hook at `call` (plan 2026-10-20, Task A2; #189): while `watching`, every call's target is
* recorded, as the kernel will see it. */
static int watching;
static v4_cell called[4096];
static unsigned ncalled;
static void record_call(void *ctx, v4_node *nd, v4_cell target)
{
(void)ctx; (void)nd;
if (ncalled < sizeof called / sizeof called[0]) called[ncalled] = target;
ncalled++;
}
static unsigned times_called(v4_cell xt)
{
unsigned k, c = 0;
for (k = 0; k < ncalled && k < sizeof called / sizeof called[0]; k++) if (called[k] == xt) c++;
return c;
}
static int called_in_order(const char *const *names, unsigned count) /* names, in this order, among the calls */
{
unsigned k, i = 0;
for (k = 0; k < ncalled && k < sizeof called / sizeof called[0] && i < count; k++)
if (called[k] == v4_text_word(&tx, names[i])) i++;
return i == count;
}
static unsigned calls_from(v4_cell lo) /* how many calls went to an address at or above lo */
{
unsigned k, c = 0;
for (k = 0; k < ncalled && k < sizeof called / sizeof called[0]; k++) if (called[k] >= lo) c++;
return c;
}
static int interpret_with(const char *line, unsigned depth, unsigned rdepth)
{
unsigned len = (unsigned)strlen(line), i;
@@ -77,6 +108,7 @@ static int interpret_with(const char *line, unsigned depth, unsigned rdepth)
v4_dstack_reset(&n.ds);
v4_rstack_reset(&n.rs);
v4_exec_reset(&es);
if (watching) { es.on_call = record_call; es.on_call_ctx = 0; ncalled = 0; }
v4_heat_reset(&h);
for (i = 0; i < depth; i++) v4_dstack_push(&n.ds, (v4_cell)(0x5A000000 + i));
v4_dstack_push(&n.ds, CANARY);
@@ -203,8 +235,12 @@ int main(void)
}
/* two in-line words of the test's own, to exercise the in-liner: one with
* two literals in one instruction word, one with a nop in it */
CHECK(v4_text_assemble(&tx, "header TWOLIT inline : TWOLIT 3 + 100 + ;\n"
"header HASNOP inline : HASNOP dup nop drop 7 ;\n"), "the test's in-line words: %s", v4_text_error(&tx));
CHECK(v4_text_assemble(&tx, "header TWOLIT inline compile-only : TWOLIT 3 + 100 + ;\n"
"header HASNOP inline compile-only : HASNOP dup nop drop 7 ;\n"
/* and two that stay in line (A2), so the control structures' code can be
* compared word for word without calls in it */
"header 1-I inline compile-only : 1-I -1 + ;\n"
"header DUPI inline compile-only : DUPI dup ;\n"), "the test's in-line words: %s", v4_text_error(&tx));
CHECK(v4_text_finish(&tx), "everything is defined: %s", v4_text_error(&tx));
CHECK(v4_text_here(&tx) < DICT_W, "code stays below the dictionary space");
printf(" capsule: %ld words\n", (long)v4_text_here(&tx) - 16);
@@ -237,16 +273,48 @@ int main(void)
}
/* ---- what was compiled, word for word ---- */
/* ---- A2 (plan 2026-10-20; #189; Captain Bob, 2026-11-13: "The ten"): every word is called, so
* the hook at `call` sees it -- but the ten that work on the return stack (in line and
* compile-only: >R R> R@ I J LEAVE UNLOOP 2>R 2R> 2R@), which a call would bury ---- */
new_session();
CHECK(ok(": T DUP OVER + SWAP DROP 5 + ;") && same_code(": T dup over + over push push drop pop pop drop 5 + ;"), "in-line words are copied in line");
CHECK(ok(": T DUP OVER + SWAP DROP 5 + ;"), "a definition of in-line words");
watching = 1;
{
static const char *const order[] = { "DUP", "OVER", "PLUS", "SWAP'", "DROP", "PLUS" };
CHECK(one("3 T") == 11 && called_in_order(order, 6) && times_called(v4_text_word(&tx, "DUP")) == 1
&& times_called(v4_text_word(&tx, "PLUS")) == 2,
"inline_words_are_called: the hook sees DUP OVER + SWAP DROP + called, in order (DUP %u, + %u times)",
times_called(v4_text_word(&tx, "DUP")), times_called(v4_text_word(&tx, "PLUS")));
}
watching = 0;
new_session();
CHECK(ok(": T 1 >R R> ;") && same_code(": T 1 push pop ;"), "residue_stays_inline: >R R> are copied in line");
new_session();
CHECK(ok(": T 4 0 DO I LOOP + + + ;") && one("T") == 6, "residue_stays_inline: I in a loop, still in line");
new_session();
CHECK(ok(": T 3 0 DO I 1 = IF UNLOOP 7 EXIT THEN LOOP 9 ;") && one("T") == 7, "residue_stays_inline: UNLOOP then EXIT leaves the word");
new_session();
watching = 1;
CHECK(ok(": T 5 ' DUP EXECUTE + 1+ ;") && one("T") == 11 && times_called(v4_text_word(&tx, "DUP")) == 1,
"execute_passes_the_hook: ' DUP EXECUTE inside a definition is seen once (%u), and the definition goes on after",
times_called(v4_text_word(&tx, "DUP")));
CHECK(one("6 ' DUP EXECUTE +") == 12 && times_called(v4_text_word(&tx, "DUP")) >= 1, "and at the prompt");
watching = 0;
new_session();
CHECK(ok(": T 7 >R 1 DUP + R> + ;") && one("T") == 9, "rstack_kept_across_call: a call does not disturb what >R put there");
new_session();
watching = 1;
CHECK(abandoned(": T 1 DUP NOSUCH") && calls_from(DICT_W) == 0,
"abandoned_definition_not_called: the hook sees no call into a definition that was abandoned (%u)", calls_from(DICT_W));
watching = 0;
new_session();
CHECK(ok(": T IF 1 THEN ;") && same_code(": T if L1 drop 1 jump L2 L1: drop L2: ;"), "IF THEN, as section 2 gives it");
new_session();
CHECK(ok(": T IF 1 ELSE 2 THEN ;") && same_code(": T if L1 drop 1 jump L2 L1: drop 2 L2: ;"), "IF ELSE THEN, as section 2 gives it");
new_session();
CHECK(ok(": T BEGIN 1- DUP UNTIL ;") && same_code(": T jump L0 LD: drop L0: -1 + dup if LD drop ;"), "BEGIN UNTIL");
CHECK(ok(": T BEGIN 1-I DUPI UNTIL ;") && same_code(": T jump L0 LD: drop L0: -1 + dup if LD drop ;"), "BEGIN UNTIL");
new_session();
CHECK(ok(": T BEGIN DUP WHILE 1- REPEAT ;") && same_code(": T jump L0 LD: drop L0: dup if LX drop -1 + jump L0 LX: drop ;"), "BEGIN WHILE REPEAT");
CHECK(ok(": T BEGIN DUPI WHILE 1-I REPEAT ;") && same_code(": T jump L0 LD: drop L0: dup if LX drop -1 + jump L0 LX: drop ;"), "BEGIN WHILE REPEAT");
new_session();
CHECK(ok(": T DO I LOOP ;")
&& same_code(": T over push push drop BODY: pop dup push"