diff options
author | polwex <polwex@sortug.com> | 2025-10-06 22:16:33 +0700 |
---|---|---|
committer | polwex <polwex@sortug.com> | 2025-10-06 22:16:33 +0700 |
commit | da03400d5857aab4c809c34f0416d1c09c4fed12 (patch) | |
tree | e44d94e3ceeafc108e1290a1de2e5c9c12012d48 /vere | |
parent | 64b132efc5ad870677ac974334b30fdbc4afafd3 (diff) |
pretty good pretty good
Diffstat (limited to 'vere')
-rw-r--r-- | vere/C_INSIGHTS.md | 51 | ||||
-rw-r--r-- | vere/build.zig | 9 | ||||
-rw-r--r-- | vere/pkg/noun/nock.c | 83 | ||||
-rw-r--r-- | vere/pkg/vere/ivory_boot_test.c | 72 | ||||
-rw-r--r-- | vere/pkg/vere/solid_boot_test.c | 19 |
5 files changed, 197 insertions, 37 deletions
diff --git a/vere/C_INSIGHTS.md b/vere/C_INSIGHTS.md new file mode 100644 index 0000000..016ee83 --- /dev/null +++ b/vere/C_INSIGHTS.md @@ -0,0 +1,51 @@ +## on nock.c +CLAUDE: +The key is that call_count is static (shared across ALL function calls), while my_call is local (each function call gets its own copy on the stack). + + Here's what happens without my_call: + + 1. Call #0 ENTERS → logs "ENTER call #0", call_count becomes 1 + 2. Call #1 ENTERS (recursive) → logs "ENTER call #1", call_count becomes 2 + 3. Call #2 ENTERS (recursive) → logs "ENTER call #2", call_count becomes 3 + 4. Call #2 EXITS → logs "EXIT call #2" using (call_count-1 = 2) ✓ Correct! + 5. Call #1 EXITS → logs "EXIT call #1" using (call_count-1 = 2) ✗ Wrong! Should be 1 + 6. Call #0 EXITS → logs "EXIT call #0" using (call_count-1 = 2) ✗ Wrong! Should be 0 + + The problem: by the time call #0 exits, call_count has already been incremented by all the recursive calls. + + With my_call (a local variable): + + 1. Call #0: my_call=0 (saved on stack), call_count++ + 2. Call #1: my_call=1 (saved on stack), call_count++ + 3. Call #2: my_call=2 (saved on stack), call_count++ + 4. Call #2 EXITS → uses its own my_call=2 ✓ + 5. Call #1 EXITS → uses its own my_call=1 ✓ + 6. Call #0 EXITS → uses its own my_call=0 ✓ + + Each recursive call preserves its own my_call value on the stack, so the EXIT log always matches the ENTER log! + + This is a classic recursion pattern in C: use static variables for things you want to share across calls (like a counter), but capture their values in local variables if you need them to survive recursion. + + + ## after first round on vere + CLAUDE: + Current Logging Features + + The C Nock interpreter now logs: + - Call number: Unique ID for each Nock evaluation (call #0, #1, etc.) + - Recursion depth: Indented to show call hierarchy + - Opcode: The Nock rule being executed (0-12) or CELL for distribution + - Subject type: Whether the subject (bus) is a cell or atom + - Mug: Hash of the subject/result for tracking identical nouns + - Return value type: Cell or atom with its mug + + Example Pattern from the Log + + >>> ENTER call #8 depth=5 opcode=8 bus=cell[mug=0x5cc2e3ff] + >>> ENTER call #9 depth=6 opcode=8 bus=cell[mug=0x528e9b65] + <<< EXIT call #9 depth=6 returns=cell[mug=0x6d2bc01f] + >>> ENTER call #10 depth=6 opcode=8 bus=cell[mug=0x479b42af] + <<< EXIT call #10 depth=6 returns=cell[mug=0x6d2bc01f] + <<< EXIT call #8 depth=5 returns=cell[mug=0x6d2bc01f] + + This shows: opcode 8 (Nock Eight = extend subject) calling itself recursively twice, both returning the same noun (mug 0x6d2bc01f). diff --git a/vere/build.zig b/vere/build.zig index 707ca2b..72baf8e 100644 --- a/vere/build.zig +++ b/vere/build.zig @@ -638,6 +638,11 @@ fn buildBinary( .deps = vere_test_deps, }, .{ + .name = "ivory-boot-test", + .file = "pkg/vere/ivory_boot_test.c", + .deps = vere_test_deps, + }, + .{ .name = "newt-test", .file = "pkg/vere/newt_tests.c", .deps = vere_test_deps, @@ -700,7 +705,7 @@ fn buildBinary( test_exe.addLibraryPath(.{ .cwd_relative = "/opt/homebrew/opt/llvm@18/lib/clang/18/lib/darwin", }); - if (cfg.asan) test_exe.linkSystemLibrary("clang_rt.asan_osx_dynamic"); + if (cfg.asan) test_exe.linkSystemLibrary("clang_rt.asan_osx_dynamic"); if (cfg.ubsan) test_exe.linkSystemLibrary("clang_rt.ubsan_osx_dynamic"); } @@ -719,7 +724,7 @@ fn buildBinary( }); const exe_install = b.addInstallArtifact(test_exe, .{}); const run_unit_tests = b.addRunArtifact(test_exe); - if ( t.os.tag.isDarwin() and (cfg.asan or cfg.ubsan) ) { + if (t.os.tag.isDarwin() and (cfg.asan or cfg.ubsan)) { // disable libmalloc warnings run_unit_tests.setEnvironmentVariable("MallocNanoZone", "0"); } diff --git a/vere/pkg/noun/nock.c b/vere/pkg/noun/nock.c index 959ec96..eef211b 100644 --- a/vere/pkg/noun/nock.c +++ b/vere/pkg/noun/nock.c @@ -2919,28 +2919,6 @@ u3n_burn(u3p(u3n_prog) pog_p, u3_noun bus) static u3_noun _n_burn_on(u3_noun bus, u3_noun fol) { - static c3_w burn_count = 0; - if ( burn_count < 500 ) { // Increased to 500 to capture solid pill boot - u3_noun hib = u3h(fol); - if ( c3y == u3du(hib) ) { - u3l_log("[C-Burn:%u] cell-cell formula", burn_count); - } else { - u3l_log("[C-Burn:%u] opcode %u", burn_count, hib); - // For call #1, dump more details - if ( burn_count == 1 ) { - u3_noun gal = u3t(fol); - u3l_log("[C-Burn:1-DEBUG] gal is %s", (c3y == u3du(gal)) ? "cell" : "atom"); - if ( c3y == u3du(gal) ) { - u3_noun gal_h = u3h(gal); - u3l_log("[C-Burn:1-DEBUG] head(gal) is %s, val=%u", - (c3y == u3du(gal_h)) ? "cell" : "atom", - (c3y == u3du(gal_h)) ? u3h(gal_h) : gal_h); - } - } - } - burn_count++; - } - u3n_prog* pog_u = _n_find(u3_nul, fol); u3z(fol); @@ -2954,12 +2932,46 @@ u3n_nock_on(u3_noun bus, u3_noun fol) { u3_noun pro; - static c3_w call_count = 0; - if ( call_count < 100 ) { // Increased to 100 - u3l_log(">>> u3n_nock_on call #%u <<<", call_count); + // Static variables persist across all calls; local variables are per-call + static c3_w call_count = 0; // Global counter incremented on each call + static c3_w depth = 0; // Current recursion depth + + // Capture current call_count in a local variable so it survives recursion. + // Without this, when we log EXIT, call_count will have been incremented + // by all the recursive calls that happened in between, giving wrong numbers. + c3_w my_call = call_count; + c3_o should_log = (call_count < 100) ? c3y : c3n; + + if ( c3y == should_log ) { + u3_noun hib = u3h(fol); // Get the opcode (head of formula) + + // Build indentation string based on recursion depth for readability + const char* indent = ""; + if ( depth == 0 ) indent = ""; + else if ( depth == 1 ) indent = " "; + else if ( depth == 2 ) indent = " "; + else if ( depth == 3 ) indent = " "; + else if ( depth == 4 ) indent = " "; + else if ( depth >= 5 ) indent = " "; + + // Log entry with opcode. CELL means distribution (implicit cons). + if ( c3y == u3du(hib) ) { + u3l_log("%s>>> ENTER call #%u depth=%u opcode=CELL bus=%s[mug=0x%x]", + indent, my_call, depth, + (c3y == u3du(bus)) ? "cell" : "atom", + u3r_mug(bus)); + } else { + // Opcodes 0-12 are the Nock rules (0=slot, 1=const, 2=eval, etc.) + u3l_log("%s>>> ENTER call #%u depth=%u opcode=%u bus=%s[mug=0x%x]", + indent, my_call, depth, hib, + (c3y == u3du(bus)) ? "cell" : "atom", + u3r_mug(bus)); + } call_count++; } + depth++; + u3t_on(noc_o); #if 0 pro = _n_nock_on(bus, fol); @@ -2968,6 +2980,27 @@ u3n_nock_on(u3_noun bus, u3_noun fol) #endif u3t_off(noc_o); + // Restore depth before logging exit (so depth matches the ENTER log) + depth--; + + if ( c3y == should_log ) { + // Rebuild indentation to match the ENTER log + const char* indent = ""; + if ( depth == 0 ) indent = ""; + else if ( depth == 1 ) indent = " "; + else if ( depth == 2 ) indent = " "; + else if ( depth == 3 ) indent = " "; + else if ( depth == 4 ) indent = " "; + else if ( depth >= 5 ) indent = " "; + + // Log exit with the result. Use my_call (not call_count) to match ENTER. + // The mug (hash) helps track if the same noun is returned multiple times. + u3l_log("%s<<< EXIT call #%u depth=%u returns=%s[mug=0x%x]", + indent, my_call, depth, + (c3y == u3du(pro)) ? "cell" : "atom", + u3r_mug(pro)); + } + return pro; } diff --git a/vere/pkg/vere/ivory_boot_test.c b/vere/pkg/vere/ivory_boot_test.c new file mode 100644 index 0000000..0bd968b --- /dev/null +++ b/vere/pkg/vere/ivory_boot_test.c @@ -0,0 +1,72 @@ +/// @file + +// #include "ivory.h" +#include "noun.h" +#include "ur/ur.h" +#include "vere.h" + +/* _setup(): prepare for tests. +*/ +static void +_setup(void) +{ + u3C.wag_w |= u3o_hashless; + u3m_boot_lite(1 << 28); // 256MB loom for solid pill + + // Load OCaml's ivory.pill file instead of embedded + u3l_log("Loading ivory.pill from OCaml directory..."); + u3_noun ivory_jammed = u3m_file("/home/y/code/urbit/vere/ocaml/ivory.pill"); + u3l_log("Cuing ivory.pill..."); + u3_noun pil = u3ke_cue(ivory_jammed); + // Don't free ivory_jammed - it's managed by u3m_file + + u3l_log("ivory_pil is_atom: %u", u3a_is_atom(pil)); + + // Boot with ivory pill + u3l_log("Booting with ivory.pill from OCaml..."); + if ( c3n == u3v_boot_lite(pil) ) { + printf("*** fail: ivory boot failed\n"); + exit(1); + } + u3l_log("✓ Ivory boot completed!"); +} + +/* _test_lily(): test small noun parsing. +*/ +static void +_test_lily() +{ + c3_l lit_l; + c3_w big_w[] = {0, 0, 1}; + u3_noun big = u3i_words(3, big_w); + u3_noun cod = u3dc("scot", c3__uv, big); + + if ( c3y == u3v_lily(c3__uv, cod, &lit_l) ) { + printf("*** fail _test_lily-1\n"); + exit(1); + } + cod = u3dc("scot", c3__ud, 0x7fffffff); + if ( (c3n == u3v_lily(c3__ud, cod, &lit_l)) || + (0x7fffffff != lit_l) ) { + printf("*** fail _test_lily-2a\n"); + exit(1); + } + cod = u3dc("scot", c3__ux, u3i_word(0x80000000)); + if ( c3y == u3v_lily(c3__ux, cod, &lit_l) ) { + printf("*** fail _test_lily-2b\n"); + exit(1); + } +} + +/* main(): run all test cases. +*/ +int +main(int argc, char* argv[]) +{ + _setup(); + + _test_lily(); + + fprintf(stderr, "test boot: ok\n"); + return 0; +} diff --git a/vere/pkg/vere/solid_boot_test.c b/vere/pkg/vere/solid_boot_test.c index 1d5c11d..26a0f07 100644 --- a/vere/pkg/vere/solid_boot_test.c +++ b/vere/pkg/vere/solid_boot_test.c @@ -258,22 +258,21 @@ _setup(void) u3C.wag_w |= u3o_hashless; u3m_boot_lite(1 << 28); // 256MB loom for solid pill - // Load OCaml's ivory.pill file instead of embedded - u3l_log("Loading ivory.pill from OCaml directory..."); - u3_noun ivory_jammed = u3m_file("/home/y/code/urbit/vere/ocaml/ivory.pill"); - u3l_log("Cuing ivory.pill..."); - u3_noun pil = u3ke_cue(ivory_jammed); + u3l_log("Loading solid.pill from OCaml directory..."); + u3_noun solid_jammed = u3m_file("/home/y/code/urbit/vere/ocaml/solid.pill"); + u3l_log("Cuing solid.pill..."); + u3_noun pil = u3ke_cue(solid_jammed); // Don't free ivory_jammed - it's managed by u3m_file - u3l_log("ivory_pil is_atom: %u", u3a_is_atom(pil)); + u3l_log("solid_pil is_atom: %u", u3a_is_atom(pil)); - // Boot with ivory pill - u3l_log("Booting with ivory.pill from OCaml..."); + // Boot with solid pill + u3l_log("Booting with solid.pill from OCaml..."); if ( c3n == u3v_boot_lite(pil) ) { - printf("*** fail: ivory boot failed\n"); + printf("*** fail: solid boot failed\n"); exit(1); } - u3l_log("✓ Ivory boot completed!"); + u3l_log("✓ solid boot completed!"); } /* _test_lily(): test small noun parsing. |