Native Plan Boundary — shared plan between direct and emitted native execution
This document names the current shared native plan objects for M6.25 and draws the boundary between IR, shared plan data, direct-runtime helper state, and emit-only state.
It complements ARCHITECTURE.md, ROADMAP.md, NATIVE_EXECUTION_BOUNDARY.md, and NATIVE_EXECUTION_CHECKLIST.md.
Why this doc exists
M6.25 is no longer at the stage where "native plan boundary" can stay as an implicit idea.
The current engine already has one concrete plan path that both native surfaces consume:
- direct native execution in
src/processor/XsltProcessor.ts - emitted native code in
src/xslt/codegen/emit.ts
This note exists to make that boundary explicit so later widening work does not smuggle emit-only or execution-only concerns across it by accident.
Current shared plan objects
The current shared native plan objects are:
NativeTransformPlaninsrc/xslt/codegen/emitInstructions.tsApplyTemplatesTemplatePlaninsrc/xslt/codegen/nativeApplyTemplates.ts
NativeTransformPlan is the top-level plan that both native consumers use.
It currently carries:
entryTemplate: the template that starts native executioncurrentNodeExpressionandcurrentNodeMayBeNull: how the native path establishes the current focusneedsCurrentNodeBinding: whether that focus needs a named local bindingsetupStatements: top-level setup such as lazy global-binding gettersoutputExpression: the final expression that produces serialized outputruntimeHelpers: the runtime helper names the plan depends on
ApplyTemplatesTemplatePlan is the recursive sub-plan for supported
xsl:apply-templates dispatch.
It currently carries:
- the matched template to render
- whether the match path is absolute
- the simple match path used for dispatch
- nested candidate plans for recursive native dispatch
What belongs where
IR contract
The IR contract remains StylesheetIR in src/xslt/compile/ir.ts.
That is the compiler-owned semantic contract shared across interpreter, analysis, direct native planning, and emitted native lowering.
The IR owns:
- templates
- global bindings
- source locations
- parsed XPath ASTs
- instruction structure
The IR does not own:
- helper function inventories
- native setup statements
- emitted module imports
- source-map wiring
- direct-runtime helper objects
Shared native plan
The shared native plan is the post-IR plan layer produced by
tryCreateNativeTransformPlan(...).
This is the contract shared by:
- direct native execution through
executeNativeTransformPlan(...) - emitted native module generation through
emitStylesheetModule(...)
Today, that plan is already somewhat lowered toward executable JavaScript and TypeScript. It is not a second semantic IR. It is the current shared native execution plan.
That means the plan may include:
- lowered expressions
- helper names
- setup statements for the supported slice
- recursive apply-templates dispatch plans
But it must still stay free of host-specific packaging concerns.
Direct-runtime helper state
Direct-runtime helper state lives outside the shared plan.
Today that means:
NATIVE_RUNTIME_HELPERSinsrc/processor/XsltProcessor.ts- actual helper implementations in
src/runtime/index.ts - the parsed source document and runtime
ctxvalues supplied at execution time
This state is execution-owned rather than compiler-owned.
Emit-only state
Emit-only state also lives outside the shared plan.
Today that means concerns such as:
- module import rendering
- runtime module specifiers
- export layout
- source-map comments
- file-path-specific emission wrapping
Those concerns are owned by src/xslt/codegen/emit.ts and neighboring
emission code, not by NativeTransformPlan itself.
What this means in practice
The current boundary is:
StylesheetIR: semantic compiler contractNativeTransformPlanplusApplyTemplatesTemplatePlan: shared native plan- runtime helper objects and documents: direct execution state
- imports, module wrappers, and source-map decoration: emit-only state
That is enough for M6.25 because both native consumers already share one plan source of truth, even though that plan is still a pragmatic lowered form rather than a future idealized declarative runtime plan.
What must not drift
Future widening work should preserve these rules:
- do not move runtime caches or live helper objects into
StylesheetIR - do not push module-import or source-map concerns into
NativeTransformPlan - do not let direct native and emitted native invent separate template-dispatch planning rules
- if a native widening needs a special-case path in only one consumer, fix the shared plan boundary instead of normalizing the divergence
Current limitation, stated plainly
The current shared native plan is explicit, but it is not yet the final shape Weaver would want long-term.
It is intentionally a transitional plan layer that is:
- shared across both native consumers
- explicit enough to reason about
- still close to executable output for the current slice
That is acceptable for M6.25. The important part for this increment is that the boundary is now named and reviewable instead of being implied by helper reuse.