From a8de618f1dcf4e169fa79ff2459fe9e9681b150e Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Thu, 11 Jun 2026 19:17:47 -0700 Subject: [PATCH 1/7] =?UTF-8?q?quote:=20revive=20aot=5Fmacros=20lowering?= =?UTF-8?q?=20=E2=80=94=20bitrot=20fixes,=20templates=5Fboost=20wiring,=20?= =?UTF-8?q?loud=20diagnostics=20(phases=200-1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revives daslib/quote.das (d38cd728f) so quote()/qmacro lower to plain AST-reconstruction code under aot_macros, making them AOT-able. - apply_to_vec: replace dead smart_ptr-era describe() cases with a generic tPointer -> vector branch (all AST node vectors are raw-pointer vectors post-gc_node); keep MakeStruct base-adjust for MakeFieldDecl? - quote.das convert_vec_expr: dst_tp != src_tp was a pointer compare (always true after clone_type), reinterpreting ExprAscend as ExprMakeStruct and stomping memory; now describe-compare + is-ExprMakeStruct guard - quote.das cvt_to_mks: move_new on a raw-pointer slot -> plain assignment - templates_boost: require daslib/quote public (QuotePass fires for every qmacro user; public so generated reconstruction calls resolve there) - gate: policies.aot_macros || per-module `options aot_macros` (same clause in the infer noAot-skip), enabling self-contained interpreted A/B tests - aot_cpp: NoAotMarker excludes functions with surviving quotes + LOG_ERROR (panic in make_visitor visitors is swallowed by runMacroFunction); CppAot writes #error into generated C++ as backstop - llvm_jit: preVisitExprQuote -> clean `unsupported` diagnostic Verified: lowered-vs-unlowered describe output identical across 6 shapes; -aot -aot-macros emits reconstruction C++; ast_match 380/380, template 10/10, linq 1971/1971 with the new wiring. Plan: QUOTE_LOWERING.md. Co-Authored-By: Claude Fable 5 --- QUOTE_LOWERING.md | 204 ++++++++++++++++++++++++++++ daslib/aot_cpp.das | 16 +++ daslib/quote.das | 14 +- daslib/templates_boost.das | 3 + modules/dasLLVM/daslib/llvm_jit.das | 5 + src/ast/ast_infer_type.cpp | 5 +- src/builtin/module_builtin_ast.cpp | 26 ++-- 7 files changed, 254 insertions(+), 19 deletions(-) create mode 100644 QUOTE_LOWERING.md diff --git a/QUOTE_LOWERING.md b/QUOTE_LOWERING.md new file mode 100644 index 000000000..110d2eaa7 --- /dev/null +++ b/QUOTE_LOWERING.md @@ -0,0 +1,204 @@ +# Quote lowering: AOT + JIT support for `quote` / `qmacro` + +**Goal:** `ExprQuote` (and therefore the whole qmacro family) compiles under AOT and JIT. +Mechanism: revive the existing infer-time lowering in `daslib/quote.das` that replaces +`quote(tree)` with generated AST-reconstruction code (`ExprMakeStruct`/`ExprAscend`/…), +which is plain das code and compiles in all three tiers. + +## Decisions (settled) + +- **Revive, don't rewrite.** `d38cd728f` ("enable ast quote in aot", Jul 2025) is the right + design: reflection-driven converter walking the quoted tree via `BasicStructureAnnotation` + field enumeration + `walk_and_convert` for leaf data. No per-node emitter to maintain. +- **Stays in `daslib/quote.das`.** +- **Wiring = option (a):** static `require daslib/quote` from `daslib/templates_boost.das`. + Activation stays cop-driven via the existing `policies.aot_macros` check + (quote.das:410) — the pass is inert when the policy is off; `module quote shared` makes + the inert require near-free. (Rejected: root-level `addExtraModule` injection — extras l + and on the program root only, but QuotePass must fire inside each macro module's own + compilation, where `[infer_macro]` visibility follows that module's require chain.) +- **Tests are mandatory before anything ships.** Basic suite first; lifting `no_aot`/jit + gates on existing tests is *extra* coverage afterwards, not a substitute. + +## Current-state map (verified 2026-06-11) + +- `ExprQuote` infers to `Expression?` handle; quoted subtree is never inferred + (`canVisitQuoteSubexpression` defaults false). Infer marks the enclosing function + `noAot` unless `policies.aot_macros` (src/ast/ast_infer_type.cpp:1440). +- Interpreter: `SimNode_AstGetExpression` holds a raw `Expression*` and returns + `expr->clone()` per evaluation (src/ast/ast_simulate.cpp:1280). +- qmacro expands to `apply_qmacro(quote(tree), rules-block)` (templates_boost.das:1179), + so lowering ExprQuote covers qmacro; the rules block is ordinary code. +- `daslib/quote.das`: `QuotePass` (`[infer_macro]` AstPassMacro) + `QuoteConverter` + replaces each ExprQuote via `convert_quote_to_expression`. Managed entities resolve + by name at runtime (`get_module` / `module_find_annotation` / `find_call_macro` / + `find_unique_function_ptr` / `module_find_structure` / `clone_file_info`). + **Orphaned:** nothing requires it (docs tooling only), zero tests. +- Lossiness blacklist (quote.das:151): `Function.annotations`, `EnumEntry.value/.at`, + `Enumeration.list`, `CaptureEntry.at`, `MakeFieldDecl.at`, `AnnotationArgument.at`; + `AnnotationArgumentInitData` carries only string/bool/int/float; FileInfo rebuilt by + name+tabSize (loses identity of `LineInfo.fileInfo`). +- AOT: `daslib/aot_cpp.das` has **no** ExprQuote handling — with `-aot-macros` set but + the pass not required, codegen emits garbage with no diagnostic. +- JIT: no `visitExprQuote` in `LlvmJitVisitor` → parent `getE` → `failed_E "unresolved + expression"` (llvm_jit.das:7017) → `g_errors` → `panic("Internal jit error…")` + (llvm_jit_run.das:295). Loud but misleading; all-or-nothing. +- JIT of macro contexts is separately disabled: `jit_llvm` simulate-macro skips when + `is_compiling_macros()` (llvm_macro.das:49). +- CLI: `daslang -aot … -aot-macros` sets `aot_macros` + `export_all` + 1MB stack + (utils/daScript/main.cpp:65). +- Policy-option promotion precedent: `policies.threadlock_context |= options.getBoolOption(…)` + (src/ast/ast_parse.cpp:1416). +- Blast radius: every in-tree quote user requires templates_boost (defer, decs_boost, + lpipe, static_let, typemacro_boost, interfaces, linq_fold_common, type_traits, …). + 41 test files use quote/qmacro (ast_match suite, linq_fold, 13× dasPEG, flatten/no_aot, + dasSQLITE/test_07_macros, fake_numeric, …), heavily overlapping the `no_aot`-gated set. + +## Phase 0 — empirical baseline (in progress) + +Worktree build (fresh master; both existing binaries are skewed vs today's +annotation-info merge), then: + +- [x] Interpreted probes: raw `quote`, `qmacro` incl. `$c` splice — green + (`SimNode_AstGetExpression` path intact). +- [x] Lowered probes (`options aot_macros`, interpreted): green after the bitrot fixes + below — consts, op2, calls, strings, `$c` splice, `qmacro_block`, `qmacro_type`, + make-struct, lambda. Probes in `probe_quote/` (become `tests/quote/` in Phase 3). +- [x] A/B: identical `describe_expression` output lowered vs unlowered (6 shapes). +- [x] `daslang -aot -aot-macros`: emits real reconstruction C++ + (`das_ascend_handle::make`, `clone_line_info`/`clone_file_info`), exit 0. +- [x] Reach probe: root-level `require daslib/quote` does NOT lower quotes inside a + required module (pass-macro visibility = the module's own require chain) — wiring + (b) rejected on evidence; with the templates_boost require the same module lowers. +- [x] Regression smoke (unlowered path + new require wiring): ast_match 380/380, + template 10/10, linq 1971/1971. + +### Bitrot fixed during Phase 0 + +1. **`apply_to_vec` gc_node spellings** (src/builtin/module_builtin_ast.cpp): dispatched + on smart_ptr-era `describe()` strings (`"ast::MakeStruct*"` — dead since the ast-module + removal). Fixed: one generic `tPointer` → `vector` branch (post-gc_node every + AST node vector is a plain raw-pointer vector); `"ast_core::MakeFieldDecl?"` keeps the + MakeStruct multiple-inheritance base-adjust special case. +2. **`convert_vec_expr` retarget stomp** (daslib/quote.das): `dst_tp != src_tp` was a + POINTER compare — always true after `clone_type` — so for pointer-element vectors the + code reinterpreted an `ExprAscend` as `ExprMakeStruct` and wrote `makeType` through the + wrong layout (memory stomp → gc_collect AV; survived 2025 only by layout luck). Fixed: + semantic compare (`describe(dst) != describe(src)`) + `arg_expr is ExprMakeStruct` guard. +3. **`cvt_to_mks` move_new** (daslib/quote.das): smart_ptr-era `move_new((*res)[i]) <| arg` + on what is now a raw-pointer slot. Fixed: plain assignment. + +## Phase 1 — plumbing + loud diagnostics (DONE 2026-06-11) + +- [x] `require daslib/quote public` in templates_boost (public: the generated + reconstruction calls — `cvt_to_mks`, `clone_line_info`, … — must resolve in the + qmacro-using module's scope). +- [x] `options aot_macros` per-module trigger: the option name is already valid + (CodeOfPolicies fields are auto-registered option names via + `getCodeOfPolicyOptions()`). QuotePass gate reads + `policies.aot_macros || prog options aot_macros`; the infer-time noAot-skip + (ast_infer_type.cpp) got the identical option clause — no parseDaScript + promotion needed. +- [x] QuotePass gate stays `aot_macros`-only (JIT extension deferred to Phase 5). +- [x] `aot_cpp.das` diagnostics — NOT a panic: a panic inside a make_visitor-driven + visitor is swallowed by `runMacroFunction`/`runWithCatch` (verified — error filed + on a program nobody reads post-compile, tool exits 0). Instead: `NoAotMarker` + gets `preVisitExprQuote` → marks the function `noAot` + `to_log(LOG_ERROR, …)` + naming file:line, cause, and fix (silent when the function is already noAot, i.e. + the normal no-aot_macros flow). Function falls back to interpreter; + `fail_on_lack_of_aot_export` escalates for exports. CppAot keeps a backstop + `visitExprQuote` that writes `#error` into the generated C++ (deterministic, + unlike panic). Verified on a negative probe: clean LOG_ERROR, broken emission gone. +- [x] `llvm_jit.das`: `preVisitExprQuote` → `unsupported(expr, "quote( ) is not jit-able + without aot_macros lowering (daslib/quote)")`, modeled on the try-catch case. + Lints clean; behavioral check needs an LLVM-enabled build (Phase 5). +- [x] Formatter verified + lint clean on all four changed das files (llvm_jit.das lint + needs `lib/LLVM.dll` present — dasbind resolves `getDasRoot()/lib/`). + +## Phase 2 — make the lowering correct + +Driven by Phase 0 findings + a blacklist audit. Known suspects, each needs a verdict +(legit-to-drop because infer rebuilds it, e.g. `ExprVar.pBlock` / `ExprReturn._block` +backlinks — vs lossy-and-matters): + +- [ ] `ExprBlock` annotations + `blockFlags`, `finally` lists. +- [ ] Capture lists (`CaptureEntryInitData` drops `at`). +- [ ] `Enumeration.list` ("too huge in TypeDecl") — does `qmacro(MyEnum.Value)` survive? +- [ ] `FileInfo`: replace per-call `clone_file_info` (fresh dummy per evaluation, + module_builtin_ast.cpp:1038) with a `resolve_file_info(name, tabSize)` builtin: + if compiling → look up `name` in the compiling program's `FileAccess` (which + caches FileInfo by name, so the dominant macro case returns the EXACT live + FileInfo — macro-module source is always parsed even under AOT; AOT only + carries the name string); else → per-name interned dummy (stable identity, + no source). Needs one small C++ binding; `Program.access` is not currently + exposed to das, and that's fine — resolve inside the builtin. +- [ ] `AnnotationArgumentInitData`: missing double / multi-value coverage? +- [ ] `Function.annotations` blacklist entry — reachable at all in quoted trees, or dead? +- [ ] String escapes / non-ASCII round-trip through `ExprConstString` reconstruction. + +Acceptance bar: lowered tree ≡ `expr->clone()` for every construct the basic suite +covers (modulo agreed-legit blacklist entries, each documented in quote.das). + +## Phase 3 — basic tests (`tests/quote/`) + +Self-contained via `options aot_macros` (Phase 1). For each category, A/B: expand the +same qmacro with lowering on vs off, compare `describe_expression` output, plus a +behavioral check that the spliced result still compiles/runs via `apply_template`: + +- consts (int/uint/float/double/string incl. escapes, enum refs, bitfields) +- op1/op2/op3, field chains, index, calls incl. named args + pipe +- blocks: statements, `finally`, block annotations, nested blocks +- make-struct / make-array / make-tuple / make-variant (move/clone semantics flags) +- lambdas + capture modes, generators, `qmacro_block` / `qmacro_expr` / + `qmacro_block_to_array` +- `qmacro_type` / `ExprTypeDecl`, fixed-array types (post-FA-rework shapes) +- tag splices: `$v $e $a $b $i $f $c $t` through the lowered path +- LineInfo: `force_at` behavior, error-reporting fidelity from a lowered macro +- negative: `-aot-macros` without lowering available → Phase 1 hard error fires; + jit + unlowered quote → clean `unsupported` message (not "Internal jit error") + +## Phase 4 — AOT lane + +- [ ] Wire `-aot-macros` into the tests/aot flow for `tests/quote/` (investigate how + per-directory dasAot invocation passes flags; register dir in tests/aot/CMakeLists). +- [ ] test_aot run green on tests/quote. +- [ ] Hash-mismatch fallback test: AOT objects built with `-aot-macros`, runtime without + (and vice versa) must fall back to interpreter cleanly, not link garbage. + +## Phase 5 — JIT lane (FOLLOW-UP: starts only after Phase 4 is green) + +Decision 2026-06-11: JIT is a follow-up effort, not part of the initial push. The only +JIT work in scope before then is the Phase 1 diagnostic (clean `unsupported` message +replacing the misleading "Internal jit error" panic). The QuotePass gate stays +`aot_macros`-only until this phase; extending it to `jit_enabled` lands here. + +- [ ] Extend QuotePass gate to `jit_enabled`. +- [ ] `tests/jit_tests/` coverage for quote under `options jit` (lowered path through + LlvmJitVisitor — make-struct of handled types, `to_array_move`, by-name lookups). +- [ ] Decide + implement JIT-of-macro-contexts: lift the `is_compiling_macros()` skip in + llvm_macro.das behind a policy. This is the compile-time payoff twin of AOT'd + macro modules. Separate PR; needs its own bake time. +- [ ] `LLVM_JIT_CODEGEN_VERSION` bump only if emitters change (diagnostic-only changes + don't). + +## Phase 6 — extra coverage + payoff measurement + +- [ ] Sweep: lift `options no_aot` from tests gated only because of quote/qmacro + (candidates from the 41-file list; case-by-case — some are no_aot for other + reasons). Same for jit exclusions (e.g. `failed_jit_abi`-style gates). +- [ ] daslib sweep: any module carrying `no_aot` purely for quote. +- [ ] Measure: imgui_demo compile profiling harness (D:\Work\daScript-profile playbook) + with AOT'd / jitted macro modules — this is the number that justifies the work. + +## Open questions + +1. ~~Gate predicate spelling~~ — RESOLVED: `aot_macros` only for the initial push; + `jit_enabled` extension lands in Phase 5. +2. ~~JIT-of-macro-contexts scope~~ — RESOLVED 2026-06-11: JIT is a follow-up phase, + after the AOT lane is green. +3. ~~FileInfo identity~~ — RESOLVED direction (2026-06-11): per-file interned dummy as + the floor (Boris), upgraded to the real live FileInfo when compiling — macro-module + source is parsed even under AOT, so the compiling program's FileAccess has the + actual FileInfo cached by name; AOT carries only the name string. See Phase 2. +4. `-aot-macros` default: once tests are green, does `daslang -aot` flip it on by + default (with `noAot` fallback removed), or stay opt-in for a release cycle? diff --git a/daslib/aot_cpp.das b/daslib/aot_cpp.das index eb50c3a91..7b7632843 100644 --- a/daslib/aot_cpp.das +++ b/daslib/aot_cpp.das @@ -511,6 +511,15 @@ class NoAotMarker : AstVisitor { } } } + // quote that was not lowered. without aot_macros infer already made the function noAot + // (then this stays silent); with aot_macros set it means daslib/quote lowering did not + // run — the module with the quote is missing daslib/templates_boost (or daslib/quote) + def override preVisitExprQuote(expr : ExprQuote?) { + if (func != null && !func.flags.noAot) { + func.flags.noAot = true + to_log(LOG_ERROR, "{describe(expr.at)}: quote( ) survived to AOT with aot_macros enabled — daslib/quote lowering did not run; require daslib/templates_boost (or daslib/quote) in the module containing the quote. Function '{func.name}' excluded from AOT.\n") + } + } }; class public PrologueMarker : AstVisitor { @@ -1814,6 +1823,13 @@ class public CppAot : AstVisitor { write(*ss, ")"); return that; } +// quote + def override visitExprQuote(var that : ExprQuote?) : ExpressionPtr { + // unreachable: NoAotMarker excludes functions with surviving quotes. a panic here + // would be swallowed by runMacroFunction, so write a hard #error into the C++ instead + write(*ss, "\n#error quote( ) was not lowered (daslib/quote) at {describe(that.at)}\n") + return that; + } // op1 def outPolicy(decl : TypeDeclPtr) { if (decl.baseType != Type.tHandle){ diff --git a/daslib/quote.das b/daslib/quote.das index c66555dd8..21fd3823c 100644 --- a/daslib/quote.das +++ b/daslib/quote.das @@ -130,9 +130,7 @@ def public cvt_to_mks(var args) : MakeStruct? { var res = new MakeStruct(uninitialized); *res |> resize(length(args)) for (arg, i in args, count()) { - { - move_new((*res)[i]) <| arg; - } + (*res)[i] = arg; } return res; } @@ -227,7 +225,10 @@ def private convert_vec_expr(mod : Module?; field_values : uint8 const?; xtype : for (idx in range(get_vector_length(field_values, src_tp))) { { var arg_expr = convert_quote_expr(mod, unsafe(reinterpret(get_vector_ptr_at_index(field_values, src_tp, idx))), src_tp, at); - if (dst_tp.baseType != Type.tString && dst_tp != src_tp) { + // retarget the element make-struct to the InitData wrapper type when get_dst_type + // substituted one; pointer compare (dst_tp != src_tp) is always true after clone_type, + // and writing makeType through a reinterpret of a non-ExprMakeStruct stomps the node + if (dst_tp.baseType != Type.tString && describe(dst_tp) != describe(src_tp) && arg_expr is ExprMakeStruct) { var make_str = unsafe(reinterpret(arg_expr)); make_str.makeType = clone_type(dst_tp); args |> emplace(make_str); @@ -407,7 +408,10 @@ class QuotePass : AstPassMacro { //! Pass macro that processes quoted AST expressions. def override apply(prog : ProgramPtr; mod : Module?) : bool { // Unwrapping ExprQuote is slow and inefficient, do it only if necessary. - if (!compiling_program().policies.aot_macros) return false // nothing to do + // Triggered by the aot_macros policy (`daslang -aot -aot-macros`) or per-module + // `options aot_macros` (self-contained tests, interpreted A/B). + let lower = compiling_program().policies.aot_macros || (prog._options |> find_arg("aot_macros") ?as tBool ?? false) + if (!lower) return false // nothing to do var astVisitor = new QuoteConverter() make_visitor(*astVisitor) $(astVisitorAdapter) { visit(prog, astVisitorAdapter) diff --git a/daslib/templates_boost.das b/daslib/templates_boost.das index 81aba7796..3150123f0 100644 --- a/daslib/templates_boost.das +++ b/daslib/templates_boost.das @@ -17,6 +17,9 @@ require daslib/ast require daslib/ast_boost require daslib/algorithm require daslib/strings_boost +// quote lowering (QuotePass + runtime reconstruction helpers); public so the +// reconstruction calls generated into qmacro-using modules resolve there +require daslib/quote public struct Template { //! This structure contains collection of substitution rules for a template. diff --git a/modules/dasLLVM/daslib/llvm_jit.das b/modules/dasLLVM/daslib/llvm_jit.das index 3a72ca4df..4b9d13254 100644 --- a/modules/dasLLVM/daslib/llvm_jit.das +++ b/modules/dasLLVM/daslib/llvm_jit.das @@ -3389,6 +3389,11 @@ class public LlvmJitVisitor : AstVisitor { unsupported(expr, "try-catch") } +// ExprQuote + def override preVisitExprQuote(expr : ExprQuote?) : void { + unsupported(expr, "quote( ) is not jit-able without aot_macros lowering (daslib/quote)") + } + // ExprIfThenElse /* if_cond_at: diff --git a/src/ast/ast_infer_type.cpp b/src/ast/ast_infer_type.cpp index 2d9c0166b..fa25ad6b1 100644 --- a/src/ast/ast_infer_type.cpp +++ b/src/ast/ast_infer_type.cpp @@ -1437,9 +1437,10 @@ namespace das { expr->type = new TypeDecl(Type::tPointer); expr->type->firstType = new TypeDecl(Type::tHandle); expr->type->firstType->annotation = (TypeAnnotation *)Module::require("ast_core")->findAnnotation("Expression"); - // mark quote as noAot + // mark quote as noAot, unless daslib/quote lowering will replace it + // (aot_macros policy or per-module `options aot_macros` — same gate as QuotePass) if (func) { - if (!program->policies.aot_macros) { + if (!program->policies.aot_macros && !program->options.getBoolOption("aot_macros", false)) { func->noAot = true; } } diff --git a/src/builtin/module_builtin_ast.cpp b/src/builtin/module_builtin_ast.cpp index 50a940996..3cff8baf3 100644 --- a/src/builtin/module_builtin_ast.cpp +++ b/src/builtin/module_builtin_ast.cpp @@ -801,10 +801,18 @@ namespace das { } template - static auto apply_to_vec(void* vec, string_view tstr, F apply) { - if (tstr == "string") { - return apply(static_cast*>(vec)); - } else if (tstr == "$::das_string") { + static auto apply_to_vec(void* vec, TypeDecl * type, F apply) { + auto tstr = type->describe(); + if (tstr == "ast_core::MakeFieldDecl?") { + // MakeStruct IS-A vector via multiple inheritance; the vector + // subobject is not at offset 0, the cast chain performs the base adjustment. + return apply(static_cast*>((MakeStruct*)vec)); + } + if (type->baseType == Type::tPointer || tstr == "string") { + // post-gc_node every AST node vector is a plain vector of raw pointers + return apply(static_cast*>(vec)); + } + if (tstr == "$::das_string") { return apply(static_cast*>(vec)); } else if (tstr == "ast_core::CaptureEntry") { return apply(static_cast*>(vec)); @@ -818,11 +826,6 @@ namespace das { return apply(static_cast*>(vec)); } else if (tstr == "rtti_core::LineInfo") { return apply(static_cast*>(vec)); - } else if (tstr == "ast::MakeStruct*") { - return apply(static_cast*>(vec)); - } else if (tstr == "ast::MakeFieldDecl*") { - auto vec2 = (MakeStruct*)(vec); // todo: hack, multiple inheritance breaks order in memory. - return apply(static_cast*>(vec2)); } else if (tstr == "ast_core::EnumEntry") { return apply(static_cast*>(vec)); } @@ -831,18 +834,17 @@ namespace das { } void* getVectorPtrAtIndex(void* vec, TypeDecl *type, int idx, Context * /*context*/, LineInfoArg * /*at*/) { - auto tstr = type->describe(); auto get_at = [idx](auto *vec) { return static_cast(&vec->at(idx)); }; - return apply_to_vec(vec, tstr, get_at); + return apply_to_vec(vec, type, get_at); } int32_t getVectorLength(void* vec, TypeDecl * type, Context * /*context*/, LineInfoArg * /*at*/) { auto get_size = [](auto *vec) { return vec->size(); }; - return (int) apply_to_vec(vec, type->describe(), get_size); + return (int) apply_to_vec(vec, type, get_size); } uint32_t getHandledTypeFieldOffset ( TypeAnnotationPtr annotation, char * name, Context * context, LineInfoArg * at ) { From 15e47b2574fe8f97f5f11a69b5d338440563cbe6 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Thu, 11 Jun 2026 19:58:54 -0700 Subject: [PATCH 2/7] quote: function-per-quote lowering, suite-wide audit flags, tests/quote (phases 2-3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - QuoteConverter wraps each lowered construction in a generated `quote`lowered`N private function and replaces the quote with a call. The inline form inflated every CALLER's stack frame by the sum of construction temporaries — fatal in recursive macros (flatten's lower_stmt overflowed any reasonable stack); now the big frame is a single non-recursive leaf - audit hammer: `daslang -aot-macros