Skip to content

Context finds functions and globals by name and by mangled hash through sealed perfect-hash tables; standalone exes and AOT contexts adopt emitter-sealed constant data - #3973

Merged
borisbat merged 1 commit into
masterfrom
bbatkin/context-name-lookup
Sep 8, 2026
Merged

Context finds functions and globals by name and by mangled hash through sealed perfect-hash tables; standalone exes and AOT contexts adopt emitter-sealed constant data#3973
borisbat merged 1 commit into
masterfrom
bbatkin/context-name-lookup

Conversation

@borisbat

@borisbat borisbat commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Rebuild required: Context loses the public members tabMnLookup and tabGMnLookup, jit_register_standalone_variable changes signature, and FillFunction no longer registers functions - every external module, standalone context and -exe artifact is regenerated or rebuilt against this daslang.

Why. Context::findFunction, findFunctions, findFunction(name, isUnique) and findVariable walked the whole function or global table with a strcmp per entry. An editor that calls invoke_in_context by name for every scene node on a program with several thousand functions spent 96% of its frame time there.

What changes.

  • Each Context finds a function or a global through one sealed NameLookup: two perfect hashes (mangled-name hash to entry, plain-name hash to chain head), same-name chains in function-index order, no strcmp at lookup.
  • A simulated program builds the two tables once and owns them; a standalone exe and a standalone AOT context adopt tables the emitter sealed at code-generation time and carries as constant data, so their startup builds and allocates nothing.
  • A distinct-name hash collision fails the seal like a mangled-hash collision already did; a never-sealed or failed object answers every probe with a miss.
  • The exe adopts its tables before any registration call, and its globals now carry name, hash, offset and shared flag; the standalone constructor verifies every function index and every global offset against its emitted table.
  • Sixteen rtti_core builtins (name_lookup_*) let the emitters build and read a sealed table; nano compiles the same source and answers from the same tables.

Observable behavior.

  • findFunction(name) cost grows with N -> one probe, independent of N.
  • fnByMangledName and globalOffsetByMangledName (every global-by-hash access in AOT, JIT and interpreter) 5.8 ns -> 2.2 ns on 8k keys, in an eighth of the memory.
  • findFunction on an overloaded name returns an arbitrary overload -> the lowest-index one; findFunctions returns hash-map order -> index order.
  • findVariable from a standalone exe crashed on a null name -> answers; nano's isUnique on a miss reported true -> false, as the full runtime does.
  • A compile with two different names on one 64-bit hash succeeds -> fails as an internal compiler error naming both.

Where to look. include/daScript/simulate/name_lookup.h and src/simulate/name_lookup.cpp are the object; the emitters are daslib/aot_standalone.das (writeLookupTables) and modules/dasLLVM/daslib/llvm_exe.das (emit_exe_lookups); the seal-time offset recomputation in writeVariableLookup mirrors InitGlobalVariable and is the one place two rules must agree.

Validation, claims, ledger

Validation

  • Big-labelled tests, which CI does not run: bin/test_standalone_ctx and bin/test_nano_ctx both exit 0 (macOS arm64, Release).
  • dasLLVM module-owned suite: bin/daslang dastest/dastest.das -jit -- --timing-outliers 10 --test modules/dasLLVM/tests 86/86 on macOS arm64; modules/dasLLVM/tests/llvm_exe_name_lookup.das passes interpreted and as an exe.
  • The hot-path check for fnByMangledName / globalOffsetByMangledName: a scratch benchmark of the probe against das_hash_map on the same 8k keys, clang -O2, Apple M-series, best of five: 2.2 ns against 5.8 ns by hash, 25 ns against 41 ns by name.
  • External sweep for the removed members: every GitHub hit is a vendored daScript copy; no daspkg-index package names them.
  • The codex round ran over the arc at its tip, read every changed file, and returned no findings.
  • The full preflight ran once after the fix batch; its JIT lane reported two load flakes (a split-link cache drop in tests/assert_once/test_assert_over_temporary.das, a process-kill timeout in tests/fio/test_process.das) that pass alone, and its AOT lane needed the rtti_name_lookup_* declarations in aot_builtin_rtti.h, validated with preflight --only tests-aot (full AOT build and sweep, pass).
  • The dasVulkan shared module was rebuilt from modules/dasVulkan/build first: the rebase pulled a vulkan_boost.das that calls a binding the older module lacked, which red-lit lint and the compile sweep on every Vulkan root.
  • The duplicate-function report did not run: its exporter refuses the tree on the must-fail fixture tests-cpp/small/test_compilation_callback_fail.das, unrelated to this change.
  • The comment harvest and style-hygiene rows were skipped by standing instruction.

Claims - stated, not tested

  • The Windows arm of llvm_exe_name_lookup.das (the set "PATH=..." child command) copies llvm_exe_native_paths.das verbatim and was not run on Windows; the nightly module-suite lane runs it.
  • The word layout the exe carries is little-endian; the static_asserts pin it on every target the runtime archive compiles for, and a wasm32 cross-target dump was read by hand, but no cross-compiled exe was executed.
  • nano's size table in nano/README.md was not re-measured: name_lookup.cpp adds the build half nano never runs, and the cortex-m4 toolchain is a nightly linux cell.
  • A negative control for the key scramble in the perfect hash: the 10k-key doctest fails without it, observed once during development, not re-run as a control.

Not done

  • tabAdLookup stays a das_hash_map; nothing hot reads it.
  • The seal builds on every Program::simulate, const-folding simulates included; sampled at under 0.1% of a real compile.
  • jit_get_global_mnh is declared memory(none), which lets LLVM reorder it freely; adopting first removes the window this change would have widened, the attribute itself predates the change.
  • Dragon proposals on pre-existing checklist rules (moving the five test rules of modules/dasLLVM/REVIEW.md into modules/dasLLVM/tests/REVIEW.md, splitting two of its rules, the debug_info pin rule) are outside this arc.

Copilot AI lite review requested due to automatic review settings September 8, 2026 17:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Context reporting in src/runtime/context.cpp can null-dereference functionLookup/variableLookup on never-simulated contexts.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR replaces linear-by-name and hash-map-based mangled-name lookups in Context with sealed perfect-hash NameLookup tables, enabling O(1) function/global resolution by name and by mangled hash, and letting standalone -exe and AOT standalone contexts adopt emitter-sealed constant lookup tables (avoiding startup table construction).

Changes:

  • Introduces das::NameLookup (sealed perfect-hash tables for mangled-name hashes and plain names) and wires it into Context for function/global lookups.
  • Updates emitters (LLVM -exe and AOT standalone) to build/seal/adopt lookup tables at code-generation time and updates runtime JIT registration APIs accordingly.
  • Adds C++/das tests plus RTTI builtins and documentation for building/serializing NameLookup tables.
File summaries
File Description
tests-cpp/small/test_name_lookup.das Adds a small script fixture used by C++ lookup tests.
tests-cpp/small/test_name_lookup.cpp Adds doctest coverage for NameLookup sealing/adoption and Context by-name/by-hash behavior.
tests-cpp/big/standalone_ctx/test_standalone_ctx.cpp Extends standalone-ctx integration test to validate new lookup behavior.
tests-cpp/big/standalone_ctx/standalone_init_fixture.das Adds shared global + exported helper to exercise shared-global lookup/offset behavior.
tests-cpp/big/nano_ctx/test_nano_ctx.cpp Extends nano context integration test to validate new by-name and by-hash lookups.
src/simulate/standalone_ctx_utils.cpp Removes legacy registration into removed tabMnLookup.
src/simulate/name_lookup.cpp Implements NameLookup building, sealing, and adoption logic.
src/runtime/context.cpp Switches Context to use functionLookup/variableLookup for by-name lookups and reporting.
src/builtin/module_jit.cpp Updates standalone-exe JIT context registration APIs and adds adoption entrypoint for sealed tables.
src/builtin/module_builtin_rtti.cpp Exposes NameLookup builder/inspection functions to emitters via rtti_core.
src/ast/ast_simulate.cpp Builds/seals Context function/global NameLookup tables during simulation (replacing old maps).
nano/src/nano_context.cpp Updates nano’s Context by-name variable/function lookup implementations.
nano/REVIEW.md Updates nano review checklist wording/scope related to context member changes and testing expectations.
nano/include/daScript/simulate/simulate.h Updates nano Context API fields and by-hash lookup implementation to use NameLookup.
nano/CMakeLists.txt Adds name_lookup.cpp to nano’s reused runtime sources list.
nano/ARCHITECTURE.md Updates nano architecture description to reflect the new sealed lookup mechanism and reused source count.
modules/dasLLVM/tests/llvm_exe_name_lookup.das Adds module-owned test that validates by-name lookups work both interpreted and in -exe artifacts.
modules/dasLLVM/tests/_name_lookup_root.das Adds the script fixture used by the LLVM exe name-lookup test.
modules/dasLLVM/REVIEW.md Clarifies wording of test discipline rules for spawned child processes and cache placement.
modules/dasLLVM/daslib/llvm_jit.das Updates comment to match new standalone-exe global registration behavior.
modules/dasLLVM/daslib/llvm_jit_run.das Bumps LLVM_JIT_CODEGEN_VERSION and emitter hash due to ABI/behavior changes.
modules/dasLLVM/daslib/llvm_exe.das Emits sealed lookup tables as constant data and adopts them early; updates global registration signature.
include/daScript/simulate/simulate.h Replaces tabMnLookup/tabGMnLookup with functionLookup/variableLookup and updates by-hash lookups.
include/daScript/simulate/REVIEW.md Updates checklist criteria to cover broader ABI/public member changes and perf-validation wording.
include/daScript/simulate/name_lookup.h Adds the public NameLookup API and layout pins for emitter serialization.
include/daScript/simulate/ARCHITECTURE.md Documents the new lookup design, ownership modes, and performance characteristics.
include/daScript/simulate/aot_builtin_rtti.h Declares AOT-visible RTTI entry points for NameLookup builder/inspection functions.
doc/source/stdlib/handmade/function-rtti-name_lookup_seal-0xe859d3033156b01.rst Documents name_lookup_seal.
doc/source/stdlib/handmade/function-rtti-name_lookup_name_slots-0x843def8a75a15.rst Documents name_lookup_name_slots.
doc/source/stdlib/handmade/function-rtti-name_lookup_name_head-0x1db1092788d8cc73.rst Documents name_lookup_name_head.
doc/source/stdlib/handmade/function-rtti-name_lookup_name_hash-0xaaa1df5fdee04233.rst Documents name_lookup_name_hash.
doc/source/stdlib/handmade/function-rtti-name_lookup_name_disp-0xaa40dba56bcd5663.rst Documents name_lookup_name_disp.
doc/source/stdlib/handmade/function-rtti-name_lookup_name_buckets-0xc771560f6c131fb.rst Documents name_lookup_name_buckets.
doc/source/stdlib/handmade/function-rtti-name_lookup_mnh_slots-0xacc87eebdfa17415.rst Documents name_lookup_mnh_slots.
doc/source/stdlib/handmade/function-rtti-name_lookup_mnh_disp-0x5f1d16a55ceea87c.rst Documents name_lookup_mnh_disp.
doc/source/stdlib/handmade/function-rtti-name_lookup_mnh_buckets-0x623f630fac05a289.rst Documents name_lookup_mnh_buckets.
doc/source/stdlib/handmade/function-rtti-name_lookup_insert-0x1df6f40370eafa48.rst Documents name_lookup_insert.
doc/source/stdlib/handmade/function-rtti-name_lookup_entry_value-0x8770c19fdc477823.rst Documents name_lookup_entry_value.
doc/source/stdlib/handmade/function-rtti-name_lookup_entry_next-0xde41b4c5e481bd98.rst Documents name_lookup_entry_next.
doc/source/stdlib/handmade/function-rtti-name_lookup_entry_mnh-0x2dbf39242b1b1cfd.rst Documents name_lookup_entry_mnh.
doc/source/stdlib/handmade/function-rtti-name_lookup_entry_index-0xa8e92693addbad91.rst Documents name_lookup_entry_index.
doc/source/stdlib/handmade/function-rtti-name_lookup_destroy-0x3fdb742796d6d4b.rst Documents name_lookup_destroy.
doc/source/stdlib/handmade/function-rtti-name_lookup_create-0xdc119a2c4b28198f.rst Documents name_lookup_create.
doc/source/stdlib/handmade/function-rtti-name_lookup_count-0x9780e5f677f52ae5.rst Documents name_lookup_count.
doc/source/stdlib/handmade/annotation-rtti-NameLookup.rst Documents the NameLookup handled type annotation.
doc/reflections/das2rst.das Adds grouping for name_lookup_* functions in generated RTTI docs.
daslib/aot_standalone.das Emits sealed lookup tables into generated standalone context C++ and adopts/verifies them at startup.
CMakeLists.txt Adds name_lookup sources/headers to build+install lists.
Review details
  • Files reviewed: 48/48 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/runtime/context.cpp Outdated
Copilot AI review requested due to automatic review settings September 8, 2026 18:00
@borisbat
borisbat force-pushed the bbatkin/context-name-lookup branch from d105a83 to c1ba353 Compare September 8, 2026 18:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It changes core runtime lookup behavior and public ABI across runtime/JIT/nano/emitter paths, so it warrants final human review despite strong test coverage.

Review details
  • Files reviewed: 48/48 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

…ugh one sealed perfect-hash object each (functionLookup / variableLookup replace tabMnLookup / tabGMnLookup): findFunction, findFunctions, findFunction(name, isUnique) and findVariable were a strcmp walk over the whole table - an editor calling invoke_in_context by name per scene node on a program with thousands of functions spent 96% of its frame there - and are now one probe, one 64-bit compare and a same-name chain in function-index order, with no strcmp at all since hash equality is name equality everywhere else in the runtime; a distinct-name hash collision fails the seal the way a mangled-hash collision already does, and a failed seal or a never-sealed object answers every probe with a miss. fnByMangledName and globalOffsetByMangledName - the latter on every global-by-hash access in all three tiers - read one entry (2.2 ns against das_hash_map's 5.8 on 8k keys, an eighth of the memory). A simulated program builds the tables (buildMNLookup / buildGMNLookup insert then seal once, the blob is owned and freed with the object); a standalone exe and a standalone AOT context adopt them: the emitter seals the same object at code-generation time through the rtti_core name_lookup_* builtins and writes the arrays into the artifact as constant data in the word layout name_lookup.h pins, so the generated constructor builds and owns nothing - llvm_exe.das emits private constant globals and adopts them right after the context is created, before any registration call (jit_register_standalone_variable now carries index, name and the shared flag; codegen version 0x77, emitter pin re-hashed for a comment), aot_standalone.das emits static const aggregates and verifies every function's index and every global's runtime offset against the emitted tables. FillFunction no longer touches the table and the relocation walk is gone since entries hold indices. A standalone exe's globals now have names, which findVariable used to strcmp as null; the old buildGMNLookup collision path indexed globalVariables by byte offset. nano shares name_lookup.cpp unmodified (the SDK install ships it) and its findFunction / findVariable use the same tables. The new builtins are documented; the hot-path, nano and dasLLVM checklists carry the rulings the review round reached. Tests: tests-cpp/small/test_name_lookup (a 10k-key suffix-only adversarial table, an adopted copy answering like its builder and owning nothing, a name that dies right after its insert, a fresh object and a failed seal both missing, a Context's overloads, uniqueness, misses, a fork sharing the tables, a context that never simulated), modules/dasLLVM/tests/llvm_exe_name_lookup (interpreted and as an exe), cases in the standalone and nano big tests including a shared global.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@borisbat
borisbat force-pushed the bbatkin/context-name-lookup branch from c1ba353 to 2e93819 Compare September 8, 2026 18:20
Copilot AI review requested due to automatic review settings September 8, 2026 18:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It introduces broad, ABI-breaking changes across runtime lookup paths and standalone/exe emitters, which warrants final human verification despite strong new test coverage.

Review details
  • Files reviewed: 48/48 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@borisbat
borisbat merged commit 927db66 into master Sep 8, 2026
50 of 52 checks passed
@borisbat
borisbat deleted the bbatkin/context-name-lookup branch September 8, 2026 20:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants