backend: quoted expressions in the native back end (WEB F4) #106
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Expression.valueandExpression.capturesare planned natives.Source
Acceptance criteria
Dependencies
None known.
Notes
Adds or changes a native, syntax or a name the compiler looks up: land it in two commits (teach both forms, refresh the seed, then migrate), as
CLAUDE.md"Seed and breaking changes" says.Moved from the planning lists of the docs; the design record stays the specification.
[loop:torbscript] intake: in scope (author orion, label loop), source WEB.md F4, not a duplicate. Adds natives, so it needs the two-commit rule (part 1, seed publish, part 2). No dependencies; ready buffer full: #4, #163, #164, #223 are ready. next: waiting (refine queued, ready buffer full) until 2026-10-10T04:00:00Z
[loop:torbscript] claim refine by session:loop-torbscript-20261026 until 2026-10-26T08:00:00Z
[loop:torbscript] refine: the issue text is stale; only
Expression.captures()and the typed-binding proof are open, next: readyVerified on main (
c72cabec)Expression.value()is done: both back ends build anExpression<Value>as a record (tree, source, location,lazyvalue cell),compiler/src/ir/lower/quoted.trb,value()is an intrinsic inir/lower/call.trb(no C native), held bytests/conformance/quotation-values.trb. WEB.md problem 5 is marked closed for that part; only theplannedRuntimeOf("Expression.value"/"Expression.captures", ...)rows incompiler/src/backend/c/natives.trbstill read "planned".captures()(quoted.trbheader: "is not built";docs/language/functions/quoted-expressions.mdbanner). The checker already requires every capture to beEncode(semantics/checker/quote.trb,encodedCaptures), so each capture can become anEncodedValuethroughEncodedValue.of(std/encoding/src/values.trb).std/webdoes not exist yet, so "typed form bindings" is proven with a user-definedfn input(bind: Expression<String>)in a conformance test.decided by the loop:
captures()stays an intrinsic (likevalue()), not a new C native: noruntime/change, no new driver flag, no syntax,std/expressiondeclarations unchanged. The two-commit rule therefore does not apply (single commitfeat(backend): build Expression.captures natively). If the worker finds it must change astd/declaration the compiler's own build reads, it stops and comments instead of going on.assertkeeps its own special lowering (ir/lower/quote.trb) and is not touched. The captures list is a secondlazycell next tovalue, so a program that never callscaptures()pays no run time; the extraEncodeinstances per captured type are measured in forge CI (binary size of the compiler/test targets) and accepted if the growth is small.Acceptance criteria (replace the issue's)
expression.captures()answersList<EncodedValue>inCaptured.indexorder, natively and in the VM (torb run,torb test), for scalars,String, a record and a list capture; a quotation without captures answers[].fn input(bind: Expression<String>)called asinput bind: form.emailbuilds natively and answersnameOf(bind),bind.value()andbind.captures()(the typed form binding of WEB.md 3.5).captures()is lazy: not evaluated unless asked; capture values are the ones at the quotation site, read once.plannedRuntimeOf("Expression.value"/"Expression.captures")rows gone fromnatives.trb(value is an intrinsic); docs banner and WEB.md problem 5 / F4 updated,skills/regenerated.Steps (one PR, each step one worker session)
compiler/src/ir/lower/quoted.trb(+ir/instantiate.trblayout ofExpression,treeBuilderOffield count): add the hidden fifth field, alazycell ofList<EncodedValue>built fromquotation.capturesvia a monomorphisedEncodedValue.of<T>per capture type (seelowerLazyArgument,listOfValues); fix thefields.length() != 4check.compiler/src/ir/lower/call.trb:owner == "Expression" && name == "captures"next tovalue, forcing the new cell (lowerQuotedValuepattern); drop the two planned rows inbackend/c/natives.trb; check the VM path through the IR (no VM change expected).tests/conformance/quotation-captures.trb(+.expected,.exit,.allocations), a typed-binding case from criterion 2, extendcompiler/tests/quotations.test.trbonly if lowering text is asserted; updatedocs/language/functions/quoted-expressions.md(remove the captures part of the banner, keep the other unsupported nodes listed: map literal, range, implicit-selffield, non-constant top-levelconst),docs/standard-library/expression.md, WEB.md problem 5 and F4 row, ENCODING.md slice 5 row; regenerateskills/.Out of scope (no issue yet, just in time): the four unsupported node kinds above.
Gates run in forge CI only; locally
torb format --checkon touched files and one conformance test.[loop:torbscript] claim implement by session:loop-torbscript-20261026 until 2026-10-26T08:00:00Z
[loop:torbscript] implement: checkpoint, design explored, no code written yet (context guard), no PR yet, next: implement (re-dispatch; the claim stays live)
Findings for the next worker (all on main
c72cabec,compiler/src/ir/):ir/instantiate.trb~l.431 appends the hiddenvaluecell field toExpression<Value>; add a second fieldcaptureswithcellType program, checker, instantiation, <List<EncodedValue> TypeId>, <its IrType>. Then fix thefields.length() != 4checks (lower/quoted.trbtreeBuilderOf,lowerQuotedValue) to 5, andConstructfields inlowerQuotation(add the second cell).lower/closure.trblowerLazyArgumenttakes an ASTExpression(ClosureSource.Thunk), so a captures thunk needs a thirdClosureSourcecase (e.g.Captures(quotation)) handled inlowerClosureBody(l.1276),loweredClosureBody(taskValuematch, l.~1157) and the parameter-binding match (l.~1350). SplitlowerLazyArgumentso the cell building (cellTypeFor, emptyOptionSlot, Construct) is shared; callclosureValueOfwithat= the quotation span (plans come fromlowering.quotedCaptures.get(at), already set inlowerQuotation, same order asCaptured.index).bindCaptures, for eachquotation.capturesbinding read its slot (lowering.slots.get(declared), orbindingReferenceOffor boxed) and emit a call toEncodedValue.of<T>:lookupStaticMember(checker, encodedValueType, "of")->.Method(_, symbol);mapping = memberMappingOf(...)(ir/witness.trb) plusmapping.set(signature.generics.last(), captureType)forValue;instanceFor lowering, symbol, mapping(ir/instances.trb);emitCallwith operands [value, bool false] (the defaultprivateFieldsmust be passed explicitly;StaticContents.Literal(LiteralValue.Boolean(false))viaconstantSlot). Pattern to copy:emitWithCapacityinlower/collection.trbl.285. Collect the results withlistOfValues(lowering, builder, slots, encodedValueTypeId, listIrType, at)and return it.EncodedValueTypeId viapreludeType(lowering, "EncodedValue", [])(report unsupported if absent).captures()call:lower/call.trb, next to theExpression.valueintrinsic; addlowerQuotedCapturesinquoted.trbmirroringlowerQuotedValue(field 4 instead of 3,lowerForcedRead).Expression.value/Expression.capturesrows inbackend/c/natives.trb; teststests/conformance/quotation-captures.trb(+ .expected/.exit/.allocations) and theinput bind: form.emailcase; docs banner,docs/standard-library/expression.md, WEB.md problem 5/F4, ENCODING.md slice 5, regenerateskills/. Single commitfeat(backend): build Expression.captures natively. Gates in forge CI only.Worktree removed (nothing was written); recreate with
ws wt new torbscript/language 106 expression-captures --branch loop/106-expression-captures.[loop:torbscript] implement: re-dispatched from checkpoint #6732 (continuation 2 of 3, light bootstrap to stay under the context guard), claim stays live, next: implement (worker running)
[loop:torbscript] implement: checkpoint 2, branch
loop/106-expression-capturespushed (WIP commit53ba5a4d, amend it, keep one commit), no PR yet, next: implement (continuation 3 of 3; the claim stays live)Done (in
53ba5a4d)compiler/src/ir/instantiate.trb:buildNominalLayoutappends a 5th fieldcaptures(cell ofList<EncodedValue>) aftervalue; newpublic fn capturesTypeOf(var checker: Checker): TypeId?=TypeForm.Traits([nominal(List, [nominal(EncodedValue, [])])])(the same interned TypeIdstd/expressiondeclares as the result ofcaptures()). Not compiled yet: checkchecker.types.intern/checker.wellKnown.symbolNamedare callable from there (symbolNamedisfnon WellKnown l.137).Left, exact recipe (all verified against main, no more exploring needed)
ir/lower/closure.trb:type ClosureSource(l.261) addcase Captures(quotation: Quotation, at: Span)(+use Quotation from "../../semantics/checker/context"). Matches:loweredClosureBodytaskValue (l.~1157).Thunk(_) | .Captures(_, _) => None;bindClosureParameters(l.~1350) same,return;lowerClosureBody(l.1276).Captures(quotation, at) =>callcapturesListOf(lowering, builder, quotation, result, at)(new, in quoted.trb), return unless supported,finishBlock Terminator.Return(borrowed(slot)). SplitlowerLazyArgument(l.307) into a privatelazyCellOf(lowering, builder, source: ClosureSource, item: TypeId, at: Span): Slot?(all of its current body,value.span->at) and addpublic fn lowerCapturesCell(lowering, builder, quotation, item, at) = lazyCellOf(..., ClosureSource.Captures(quotation, at), ...).capturePlansOfalready finds the plans throughlowering.quotedCaptures.get(at)(set inlowerQuotation, not removed).ir/lower/quoted.trb: newpublic fn capturesListOf(var lowering, var builder, quotation: Quotation, annotation: IrTypeId, at: Span): Slot?:encoded = preludeTypeOrReport(lowering, "EncodedValue", at)?;lookupStaticMember(lowering.checker, encoded, "of")(semantics/checker/member) ->.Method(_, symbol);parameter = lowering.checker.signature(signatureOf(lowering.checker, symbol)).generics.last();privateFields = constantSlot(lowering, builder, None, StaticContents.Literal(LiteralValue.Boolean(false)), booleanTypeOf(lowering)); percapture in quotation.captures:declared = lowering.checker.binding(capture.binding).at; slot =lowering.slots.get(declared), elsebindingReferenceOf(lowering, builder, declared)(closure.trb l.249) +Readinto a temporary;mapping = memberMappingOf(lowering, symbol, encoded, encoded)(../witness),mapping.set parameter, substituted(lowering, capture.type);function = instanceFor(lowering, symbol, mapping)?(../instances);emitCall lowering, builder, None, function, [slot, privateFields], irTypeOfClosed(lowering, encoded), at(./call, l.1060). End:listOfValues lowering, builder, values, encoded, annotation, at.In
lowerQuotationafter the value cell:item = capturesTypeOf(lowering.checker)(report unsupported if None),lowerCapturesCell lowering, builder, quotation, item, at, addborrowed(capturesCell)as 5th Construct field.treeBuilderOf(l.142) andlowerQuotedValue(l.803):!= 4->!= 5(+ message "... value and captures"). AddlowerQuotedCaptures=lowerQuotedValuewith field 4 (factor both into oneforcedFieldOf(..., index, what)). Update the module doc comment (l.1-17:captures()is now built, second lazy cell).ir/lower/call.trbl.373: next tovalue,if owner == "Expression" && name == "captures" && operands.length() == 1 { return lowerQuotedCaptures ... }(+ import l.57).value/capturesarenative fninstd/expression, andcompiler/tests/natives-audit.test.trbrequires a manifest entry per std native. Replace the twoplannedRuntimeOf(...)rows inbackend/c/natives.trbl.1988-1989 withloweredOf("Expression.value")/loweredOf("Expression.captures")(target.Lowered, likespawn); indocs/design/NATIVES.mdl.850-851 set TargetLowered(verdictkeep, groupcompilerunchanged, so the Counts section stays). Alsodocs/BACKEND.mdl.3562 says they are.Planned("5.11"): fix.tests/conformance/quotation-captures.trb(+.expected/.exit/.allocations, copy the shape ofquotation-values.*),fn input(bind: Expression<String>)case;docs/language/functions/quoted-expressions.mdbanner,docs/standard-library/expression.md, WEB.md problem 5/F4, ENCODING.md l.937 slice 5 row; regenerateskills/(a nightly torb exists at/tmp/torb110/x/torb-nightly-20261025-linux-x64/bin/torb:torb docs skill docs skills,torb format --check <files>).feat(backend): build Expression.captures natively, force-push the branch, open the PR (Closes #106), one blocking CI wait.Worktree:
/home/node/workspaces/orion/code/wt/torbscript-language-106-expression-captures(kept). Worker usage: ~90k prompt tokens (context guard at 80k).[loop:torbscript] implement: Expression.captures built natively (hidden 5th
capturesfield and lazy cell,capturesListOf, intrinsic in call.trb,loweredOfmanifest rows, NATIVES/BACKEND/ENCODING/WEB/quoted-expressions docs, skills regenerated, conformancequotation-captures), PR #495 (single commit661fb742, Argus review requested), next: waiting (forge CI run 1826 pending after 10 min)Remainder, to be fixed in the same commit once CI reports
ir/lower/quoted.trb/closure.trb(the nightly torb could not parse them, sotorb format --checkis unchecked for both), fix on the same branch.tests/conformance/quotation-captures.allocationsis missing (CI prints the count;unbudgetedonly with a reason) andquotation-values.allocations(302) will rise by the cells of its quotations: set both from the CI counts, with a commit message line saying why.quotation-captures.expectedwas written by hand: the order100, 18forbase + minimum + baseand"Grace"for aStringcapture are assumptions; correct them from the CI diff.Worktree
code/wt/torbscript-language-106-expression-capturesis pushed and kept for the fix round.[loop:torbscript] claim fix by session:loop-torbscript-20261026 until 2026-10-26T08:00:00Z (PR 495 bootstrap red)
[loop:torbscript] fix: the bootstrap compile errors are fixed in the single commit (now
bbeba43b, force-pushed):closure.trbcalled the enum caseClosureSource.Thunk valuewithout parentheses,quoted.trbhadmatch capturesTypeOf lowering.checker {(needs parentheses) and usedtypeas a binding name (keyword, nowcapturedType); the nightly torb parses both files andtorb format --checkis clean;check-commits.shclean; Argus review re-requested. next: waiting (forge CI run 1841 still queued after 11 min, no job started)Remainder, to be fixed in the same commit once CI reports
ir/lower/quoted.trb/closure.trb/instantiate.trb(never compiled before; the nightly torb is too old to check them): fix on the same branch.tests/conformance/quotation-captures.allocationsis still missing (CI prints the count;unbudgetedonly with a reason) andquotation-values.allocations(302) will rise: set both from the CI counts, with a commit message line saying why.quotation-captures.expectedwas written by hand (order100, 18forbase + minimum + base,"Grace"for theStringcapture): correct it from the CI diff.Worktree
code/wt/torbscript-language-106-expression-capturesis removed; recreate it fromloop/106-expression-captures.[loop:torbscript] claim fix by session:loop-torbscript-20261026-0724 until 2026-10-26T13:16:00Z (PR 495: not mergeable after main moved to
4f8e15cf, rebase and fix CI findings)[loop:torbscript] fix (checkpoint): rebased the single commit onto main
4f8e15cf(one conflict incompiler/src/ir/lower/call.trb, resolved by keeping both theExpression.capturescase and main'snativeEntryOfcall), new head3a0c2f56,tools/check-commits.shclean, force-pushed toloop/106-expression-captures. Forge CI of the new head:bootstrap (linux-x64)is green (the earlier compile errors are gone);tier Bis red on three gates, all from this change: (1)tests/conformance/quotation-captures.trb:15does not compile,"{nameOf bind} = {bind.value()}"reportsCannot infer Value of nameOfplusExpected } after the expression(command-call inside an interpolation; write{nameOf(bind)}and check whethernameOfneeds a type argument), failing in the native build and in both VM lanes; (2)tests/conformance/quotation-values.trballocates 330 blocks against its budget of 302 (+28), so either the captures cell is built eagerly where it should be lazy, or the budget needssh tools/conformance.sh --update-budgets --filter quotation-valuesafter checking the cause.gates,tier Aand the other jobs were still pending. Argus review not re-requested yet (CI red). Queue wait was about 55 min before the first job started. Token usage of this worker: about 100k. next: fix round continues in worktreetorbscript-language-106(waiting for a continuation worker)Checkpoint (Orion loop): head
e3248733on loop/106-expression-captures, PR 495. Fixed the two tier B failures: (1) quotation-captures.trb now writes{nameOf(bind)}(a command call is not valid inside an interpolation); (2) quotation-values allocated 330 vs 302 because every quotation got a memo cell (+ an environment when it captures) forcaptures()whether asked or not.captures()is now a plain thunk field with no cell: a capture-free quotation makes no block for it, a capturing one only its environment. Budget untouched; if the count still differs, CI names it and I adjust. CI is queued on the shared runners (gates/pdisc pending), tier A/B/bootstrap not started yet. Next: wait for full green, then re-request Argus.[loop:torbscript] release fix: the fix worker pushed
e3248733(quotation-captures interpolation fix, captures() thunk without a memo cell) and ended while forge CI of that head was still queued; remainder per the checkpoint above (adjust quotation-values.allocations if tier B reports a count above 302, then re-request Argus). next: waiting (CI on PR 495 heade3248733)