50%

Codegen-Backend Trait and Dispatch

Goal

Fully flesh out the ori_codegen::CodegenBackend compile/finalize contract, make the CodegenBackendChoice dispatch backend-neutral at its own signature, and route every codegen-entry ori_llvm:: call site through it — collapsing the forked AOT/JIT emission projection and the codegen-entry #[cfg(feature = "llvm")] coupling down to one dispatch module.

Scope note on the #[cfg(feature = "llvm")] clause: 101 occurrences exist across oric/src; 86 sit outside this section’s in-scope six files, and the in-scope leader (11) is backend.rs, the dispatch module itself. This section collapses the codegen-entry subset only; the platform-toolchain occurrences stay out.

Context — verified terrain at head b1001be55

The terrain is a T, not a flat file list. The vertical is compile_common.rs -> backend.rs -> run_codegen_pipeline -> emit_realized_program. The crossbar is the AOT/JIT emission fork, which meets nowhere.

Dispatch surface (the central deliverable)

oric/src/commands/backend.rs (90 lines) is in this section’s touched set. It holds LlvmBackend:34, impl CodegenBackend for LlvmBackend:42, CodegenBackendChoice:76 (single variant Llvm), and CodegenBackendChoice::compile:83, declared -> Result<LlvmCodegenOutput<'ctx>, BackendError>. That concrete return type — not the trait’s method count — is what makes a second backend variant unrepresentable: the trait is already generic over an associated Artifact (ori_codegen/src/lib.rs:94), while the closed enum that dispatches it names one backend’s output type.

Contract crate

ori_codegen/src/lib.rs (98 lines, single file): BackendError:36, CodegenInput:62 (10 fields: pool, type_result, canon, source_path, module_name, symbol_prefix, target_triple, narrowing_policy, imported_type_metadata, imported_collection_surfaces), CodegenBackend:92. Its :9-21 doc-comment gates widening on types that do not yet exist.

Emission axis (the confirmed duplication)

SideSymbolCoordinateSpan
AOTemit_realized_programcodegen_pipeline/pipeline.rs:29848
AOTemit_all_functionscodegen_pipeline/pipeline.rs:34758
JITOwnedLLVMEvaluator::compile_all_functionsori_llvm/src/evaluator/compile.rs:148124

Named-but-thin migration targets

FileLinesContent
codegen_pipeline/mod.rs14module decls + 2 pub use — no dispatch coupling
codegen_pipeline/finalize.rs61one fn finalize_module
codegen_pipeline/pc2_hooks.rs51one fn run_pc2_hook_aot, diagnostic-only
codegen_pipeline/imported_mono.rs387build_imported_mono_functions:74 + helpers
compile_common.rs283builds CodegenInput + CodegenBackendChoice::Llvm at :143/:155, :259/:271
codegen_pipeline/pipeline.rs404the orchestration center

No file in this section’s touched set exceeds the 500-line cap. The largest is pipeline.rs at 404.

Corrections to this section’s prior baseline

Prior claimVerified state
codegen_pipeline/mod.rs is 573 lines, over cap, the orchestration center14 lines. The orchestration code is in pipeline.rs (404). The split already landed.
oric/tests/phases/codegen/*.rs is the verification corpus18 files, all platform-toolchain (linker_*, mangling, object_emit, targets, wasm, debug_*). Zero reference run_codegen_pipeline, emit_realized_program, CodegenBackendChoice, or compile_to_llvm. It is the wrong corpus.
CodegenBackendChoice contains LLVM and ori_backendSingle-variant Llvm. ori_backend is not a crate; plans/native-backend is 19/19 not-started.
CodegenInput carries ExecutableProgram + TargetSpec + CompiledLayoutPlanIt carries none of the three. TargetSpec and CompiledLayoutPlan have zero declarations in compiler/ (doc-comment mentions only).
owns_crates includes ori_targetNo such crate exists in compiler_repo/compiler/. That declaration is aspirational; do not author it here.

Out of scope (approved narrow-scope decision, unchanged)

The platform-toolchain subset — commands/build/*, commands/target*.rs, commands/demangle/*, llvm_dump/* (linking, mangling, target/sysroot resolution, executable packaging, process launch) — stays outside the backend trait. Reference-repo pain concentration corroborates this boundary: the highest-pain backend-abstraction issues in mature compilers (rust #107370, #109855, #113092, #113869) are all linker/SDK/LTO work-product concerns, none of which touch a trait seam.

Ownership and deferral boundary — what this section may author

Type / symbolDisposition here
EmittedArtifact, ArtifactRequest, finalize()Author here. Zero references anywhere in the tree; no SSOT to fork.
TargetSpec, CompiledLayoutPlan, VmLayoutPlan, ReprEvidenceDo not author here. Owned by plans/repr-opt §13 (verified not-started), already named as the deferral target by s-fdca8921. Declare a blocked-by on that section for the CodegenInput fields instead of inventing them.
ExecutableProgram, ExecutableProgramPartsDo not reshape. enum-layout-ssot#s-0f9158ab and repr-opt #11/#12 are in-progress over the same artifact.
ori_backend crateDoes not exist; plans/native-backend owns it and is entirely not-started. Design for a second variant, do not add one.
oric/src/commands/backend.rsNo plan named this file before now. This section owns it.

Constrained symbols — do not reshape

SymbolCoordinateConstraint
compile_module_with_testsori_llvm/src/evaluator/compile.rs:84Litigated dead end; its own #[expect(clippy::too_many_arguments)] at :80 records the refutation. Do not carrier-ize its arguments.
realize_closed_contractsoric/src/commands/emit_aims_state/mod.rs:223Deliberately realizes a degraded artifact for the read-only ori emit-aims-state diagnostic. Unifying it changes shipped output or production contracts.
validateori_repr/src/executable/program_freeze.rs:15Sole ExecutableProgram::validate edge (reached at oric/src/realization/program.rs:318); hotspot two orders of magnitude above this terrain. Change callers, never it.
freeze_executable_programori_repr/src/executable/program_freeze.rs:32Same surface, same rule.
realize_arc_programoric/src/realization/program.rs:270Frozen by s-30e3c9a6 as the canonical pre-codegen seam. This section owns the axis strictly below it.

prepare_analysis_products:154 and lower_arc_functions:257 outrank the emission functions on churn and betweenness and sit above this section’s boundary. Do not drag them in.

Emission-axis collapse — the section’s real deliverable

Source read of both sides in full at b1001be55 puts the shared skeleton at ~23 steps across the union: pool, repr_plan, ArcClassifier::new, TypeInfoStore::new_with_plan, TypeLayoutResolver::new, register_user_types, FunctionCompiler::new, bind_executable_program, declare_all, declare_mono_functions, declare_artifact_remainder, compile_impls_from_artifact, bind_executable_method_targets, bind_user_drop_targets, prepare_all_from_artifact, prepare_mono_from_artifact, prepare_artifact_remainder_from_artifact, compute_nounwind_set, emit_prepared_functions, apply_posthoc_nounwind, plus the error-count/description tail.

Detector-sizing rule: the parallel-drift sibling-divergence row for this pair scores jaccard 0.2727 with shared_count: 3, because emit_realized_program delegates the fan-out to emit_all_functions while the JIT side inlines it, so the callee-set comparison sees only the prologue. The detector understates this finding. Size the collapse from the source read, never from the row. The routing note’s “14 identical projection steps” figure is not derivable from any graph surface and is not used.

Parameterization surface — the genuine AOT/JIT deltas the shared projection must carry, not erase:

DeltaAOTJIT
Builder / EH modelIrBuilder::new_aot(scx, eh_model) from target_tripleIrBuilder::new_jit(scx_ref)
Annotated signaturesexports::artifact_annotated_signatures (with import_sigs)Self::collect_annotated_sigs(executable)
Import declarationdeclare_imports(import_sigs)declare_imported_functions
Imported bodiesnot prepared per-importper-import prepare_all_from_artifact
Test wrappersnonecompile_tests(tests), plus test names in the deferred-parent set
Exports / entryexports::project_callable_exports + generate_main_wrappernone

Sequencing: AOT-last. Shape the shared helper against the cold, isolated JIT side (compile_all_functions: hotspot 2.10, betweenness 0.0, span 124), then re-point the hot, high-betweenness AOT path (emit_realized_program: hotspot 95.66, betweenness 41.7).

Collapse completeness rule: covering only the 3-callee prologue leaves the LEAK:algorithmic-duplication open. Authoring a third emit_shared_program alongside the existing pair instead of parameterizing one is LEAK:reuse-bypass.

Collapse-target note: both halves already sit inside one graph community whose members are exactly the FunctionCompiler artifact-projection protocol they both drive. The shared helper’s module boundary exists; it does not need inventing.

Boundary disposition for ori_llvm:: importers

32 files under oric/src import ori_llvm:::

BucketCount
In-scope codegen-entry (backend.rs, codegen_pipeline/{exports,finalize,pc2_hooks,pipeline}.rs, compile_common.rs)6
Declared platform-toolchain, out of scope14
Undeclared — no owner in any section12

The undeclared 12: commands/run/mod.rs, db/mod.rs, debug_flags/{aims_ablation_phase5_core,aims_ablation_phase5_lineage,diagnostic_dumps}.rs, problem/codegen/{mod,tests}.rs, query/tests.rs, test/runner/{arc_lowering,llvm_backend}.rs, test/runner/llvm_backend/{compile,imports}.rs.

Each of the 12 gets an explicit disposition here — migrate through the dispatch, or a named exemption in the boundary set. s-522301d7 will assert that every ori_llvm:: import outside the designated dispatch module fires a violation; against today’s tree that is 26 files. Settling the exempt set here is what keeps that lint from landing red on work this section declared out of scope.

Hygiene constraints the design must satisfy

AxisBinding constraint
Backend-neutrality (PHASE-62)EmittedArtifact names no LLVM placement, header/counter shape, ABI, opcode, or helper identity. CodegenBackendChoice::compile -> LlvmCodegenOutput is today’s live instance of that violation; fixing it is a cure, not a new constraint.
Duplication (SSOT-18 / SSOT-36 / DESIGN-17)Collapse the whole ~23-step skeleton; parameterize one entry rather than adding a third.
Parameter hygiene (WASTE-03/04, DESIGN-19)CodegenPipelineInput already carries 15 fields, CodegenInput 10. Do not pay for finalize() with loose booleans/options, and do not thread a field through backend.rs that only pipeline.rs reads. LlvmBackend exists to hold the driver-side inputs CodegenInput cannot carry — respect that split.
Dispatch shape (COVER-28)Keep the closed enum exhaustively matched at backend.rs:86-88. Never a boolean/if over backend identity; never dyn CodegenBackend — the set is fixed.
Visibility (NAME-10)run_codegen_pipeline is pub(super); LlvmCodegenOutput is pub(in crate::commands). Do not widen either to pub merely to cross the crate edge.
Comment hygiene (COMMENT-45)ori_codegen/src/lib.rs:17 carries future-state framing (“not yet expressible”, “gated on those types landing”). When the gate resolves, rewrite the doc to state what the contract is.

Evidence and verification contract

Coverage baseline (source-confirmed, independent of the stale test-topology facet): emit_realized_program, emit_all_functions, compile_all_functions, run_codegen_pipeline, realize_codegen_program, compile_module_with_tests, collect_annotated_sigs, declare_imported_functions, finalize_jit, and all four ori_codegen public items have zero direct and zero covering tests. The one exception is BackendError::from, reached only from the mirror-enum test file. oric/tests/backend_stub_conformance.rs:47-62 declares its own StubBackendChoice and never constructs the real CodegenBackendChoice (INVERTED-TDD:seam-injected-only-coverage + LAYER_GAP:production-entry-point).

Ordering is binding: the production-path pin lands before any migration. A migration that silently changed declaration order, the nounwind batch, main-wrapper emission, or export projection ships green against today’s suite.

LayerProbe for this section
L12 production entry pointDrive the real oric::commands::backend::CodegenBackendChoice through ori build and ori test --backend=llvm; assert the real emitted artifact. Keep the NoOp/Refusing stub backends; delete the mirror selector so the real enum is constructible from a test.
L9 cross-executor paritycompiler_repo/diagnostics/dual-exec-verify.sh --test-only <scope> for the migrated path. Mandatory: this is a compiler-scope section and both halves of the axis are executor-facing.
L10 leak-freedomZero leaked owners/storage/handles on every affected executor. Mandatory alongside L9 per layer-coverage.md §3.1.
Refactor equivalenceORI_DUMP_AFTER_LLVM=1 baseline on one AOT program and one --backend=llvm test program before the collapse; ir-diff.sh after. Byte-identical IR is the strongest available evidence for a behavior-preserving collapse.
Emitted-shape auditcodegen-audit.sh / ORI_AUDIT_CODEGEN=1 (already consumed by finalize.rs:52 and finalize_jit) for RC/ABI shape drift.
Debug/release divergencedebug-release-compare.sh — the JIT half of the axis is the FastISel path.

Burden-sole caveat (.claude/rules/arc.md §STOP): this section changes emission orchestration, not ownership. Any RC delta observed is a regression signal, never an optimization lead, and any RC count quoted comes from the burden-sole stream (--emit-rc-remarks / rc-stats.sh --rc-remarks). A default-path RC count is false-green.

Negative pins (target/layout-mismatch, unsupported-artifact) are authored against the real enum. A negative pin written against a stub selector repeats the defect it exists to close.

Verification rule for graph claims: callers "run_codegen_pipeline" resolves 0 rows despite two confirmed call sites, history-coupling and CO_CHANGED_WITH return 0 rows for every emission-axis symbol, surprise-coupling returns 0 rows under a stale facet, and similar never bridges the AOT/JIT files. Blast radius for this section is source-derived only. Confirm every caller and coverage claim by source search before acting on it.

Prior-art decision rule

rustc_codegen_ssa is the right reference and is interrogated, not copied:

  • Adopt the invariant: one whole-crate driver generic over the backend trait (rustc_codegen_ssa::base::codegen_crate, base.rs:681) delegating to the concrete backend (rustc_codegen_llvm::codegen_crate, lib.rs:353). rustc has one such driver; Ori has two. That asymmetry is this section’s axis.
  • Adopt the DummyCodegenBackend shape (rustc_interface/src/util.rs:349) as the production-wired answer to the coverage gap: reachable from the real driver, not a test-local mirror.
  • Reject ExtraBackendMethods’ width (traits/backend.rs:132 — allocator shims, module submission, CGU naming). CodegenBackend plus finalize + ArtifactRequest is two methods and one associated type; that is the end state. Importing rustc’s width is DESIGN:speculative-generality.
  • Reject rustc’s partitioning / par_map / async module submission — no current requirement.
  • Refute the direct transfer: rustc has no JIT path and never faced an AOT/JIT emission fork. It is prior art for the invariant, not for the fix. The collapse is a parameterized shared projection, Ori-shaped.

Trait-completeness bar (corroborated by rust #123119 and zig #15899): every declared trait method is genuinely implemented and reached by the real dispatch. The typeck-into-codegen back-edge zig #15899 describes is verified gone in Ori — register_resolved_collection_burdens now sits at oric/src/realization/program.rs:243, above the codegen boundary, with zero call sites in oric/src/commands/ or ori_llvm/.

Cross-plan contract

ConsumerConstraint on this section’s frozen shape
s-79743ab2 (this plan, not-started)Freezes ExecutionBackend { Vm, Compiled(CodegenBackendChoice) } and pins native-backend’s driver integration to CodegenBackend. Consumes the artifact type frozen here.
s-522301d7 (this plan, not-started)Polices the ori_llvm:: import boundary this section defines.
bytecode-vm#s-429b48fe, #s-c22d62a9 (not-started)Require CodegenBackendChoice to stay compiled-codegen-only so ExecutionBackend can wrap it without the VM implementing CodegenBackend.
native-backend#s-3caa36e9, #s-dc7e3131 (not-started)The consumers that need a second enum variant.
repr-opt §13 (not-started)Owns TargetSpec / CompiledLayoutPlan; blocks the CodegenInput widening clause.

Read bytecode-vm#s-429b48fe and native-backend#s-3caa36e9 before freezing the compile/finalize signature — this section’s artifact type is their input.

Required reading before execution

  1. plans/backend-boundary/content/shared-pre-codegen-orchestration--s-30e3c9a6.md in full — boundary, exclusions, cross-plan contract, do-not-unify carve-outs.
  2. plans/backend-boundary/content/walking-skeleton--s-fdca8921.md, “Narrowed contract and deferral anchors” — the authority for narrowing the CodegenInput clause.
  3. compiler_repo/compiler/oric/src/commands/backend.rs, all 90 lines.
  4. compiler_repo/compiler/ori_codegen/src/lib.rs, all 98 lines.
  5. pipeline.rs:298-404 and compile.rs:148-271 side by side, both in full.
  6. compiler_repo/compiler/oric/tests/backend_stub_conformance.rs:47-62.

Spec references

.claude/rules/layer-coverage.md §1 (layer matrix) + §3.1 (mandatory L9 + L10 for compiler-scope sections); .claude/rules/finding-categories.md (LEAK:backend-ownership-calculus-fork, LEAK:algorithmic-duplication, LEAK:reuse-bypass, INVERTED-TDD:seam-injected-only-coverage, LAYER_GAP:production-entry-point, DESIGN:speculative-generality, GAP:conditional-fallthrough-not-exhaustive); .claude/rules/impl-hygiene.md WASTE-12 (500-line cap — no file in this touched set currently exceeds it); .claude/rules/arc.md §STOP (burden-sole RC measurement); .claude/rules/missions.md §ori_llvm (faithful emission; parallel emission paths fracture parity) and §Compiler (one validated executable artifact, consumed by every backend without re-derivation).

Work items

  • Add oric/src/commands/backend.rs (the CodegenBackendChoice dispatch module — absent from this section’s declared file set even though it owns the section’s central deliverable) to this section’s touched files. Fix CodegenBackendChoice::compile (backend.rs:83-89, today -> Result<LlvmCodegenOutput<'ctx>, BackendError>) to return a backend-neutral EmittedArtifact via the trait’s generic Artifact associated type — this hardcoded concrete return type is the actual blocker on a second backend variant, not the trait’s method count. Flesh out CodegenInput’s fingerprint validation, backend-private Module ownership, typed ArtifactRequest, backend-neutral EmittedArtifact, and BackendError; declare a blocked-by dependency on plans/repr-opt section 13 for the TargetSpec/CompiledLayoutPlan fields instead of inventing them here — that section is not-started and owns those types exclusively, already named as their deferral target by s-fdca8921. Collapse the full emission axis (codegen_pipeline/pipeline.rs:298 emit_realized_program / pipeline.rs:347 emit_all_functions vs ori_llvm/src/evaluator/compile.rs:148 compile_all_functions, the confirmed ~23-step shared skeleton, not only the 3-callee prologue the parallel-drift detector surfaces) into one shared parameterized projection, sequenced AOT-last: shape the helper against the cold, isolated JIT side first, then re-point the hot, high-betweenness AOT path. Migrate finalize.rs, imported_mono.rs, pc2_hooks.rs, and compile_common.rs through CodegenBackendChoice with target/layout-mismatch and unsupported-artifact negative pins driven against the real enum (built on the production-path pin work item). Declare a disposition — migrate or explicit exemption — for the 12 currently-undeclared ori_llvm::-importing files (under commands/run/, db/, debug_flags/, problem/codegen/, query/tests.rs, test/runner/) before s-522301d7’s boundary-hygiene lint lands and flags them as unowned violations.
  • Land a production-path pin BEFORE any migration: drive the real oric::commands::backend::CodegenBackendChoice end-to-end through ori build and ori test --backend=llvm, asserting the real emitted artifact. Every symbol on the emission axis (emit_realized_program, emit_all_functions, compile_all_functions) currently has zero direct and zero covering tests; the sole existing pin, oric/tests/backend_stub_conformance.rs:47-62, constructs a local StubBackendChoice enum documented verbatim as mirroring CodegenBackendChoice’s shape — it never constructs the real enum (TEST-14 / LAYER_GAP:production-entry-point). This item establishes real dispatch coverage first so the subsequent contract-fleshing, emission-axis collapse, and file migration cannot silently change declaration order, the nounwind batch, or export projection without a test noticing.

Intel dossier — pointer and tier disposition

Dossier: codegen-backend-trait-and-dispatch--s-90024726.intel.md (this section’s content/intel/ sidecar)

Read that dossier from line 1 through EOF before acting on this section. This block is a pointer and an audit record, never a substitute: not the BLUF, not a summary, not selected tiers.

Dossier tierTitleLinesDispositionWhere / why
00. Bottom line up front56integratedGoal; Context — verified terrain at head b1001be55 (incl. Corrections to this section’s prior baseline); Ownership and deferral boundary; Emission-axis collapse; Evidence and verification contract
11. The target — verbatim from the section body31integratedContext — verified terrain / Corrections to this section’s prior baseline (the 573-line, wrong-test-corpus, ori_backend-variant, CodegenInput-fields, and ori_target claims each replaced with the verified state); Out of scope
22. The terrain — symbols and files60integratedContext — verified terrain: Dispatch surface, Contract crate, Emission axis, Named-but-thin migration targets (T-shape terrain, backend.rs added, thin leaves marked as having no dispatch coupling)
33. Code-graph recon125integratedEmission-axis collapse (prologue-vs-fan-out undercount, collapse-target community, AOT-last sequencing by churn/betweenness); Boundary disposition for ori_llvm:: importers (32-file census, the undeclared 12, s-522301d7 collision); Evidence and verification contract (zero-coverage baseline, graph-claim verification rule); Constrained symbols (prepare_analysis_products / lower_arc_functions above the boundary)
3D3D. Diagnostic / observability surface28integratedEvidence and verification contract — probe table (ir-diff.sh refactor-equivalence baseline, codegen-audit.sh, dual-exec-verify.sh, debug-release-compare.sh) plus the burden-sole RC caveat
3H3H. Hygiene constraints the recommended design must satisfy29integratedHygiene constraints the design must satisfy (PHASE-62 backend-neutrality, SSOT-18/36, PARAM_SPRAWL, COVER-28 dispatch shape, NAME-10 visibility, COMMENT-45); Emission-axis collapse (completeness rule); Evidence and verification contract (L9/L10 mandatory, negative pins against the real enum)
44. Cluster / family — sibling sections and cross-plan dependents36integratedCross-plan contract (s-79743ab2, s-522301d7, bytecode-vm s-429b48fe/s-c22d62a9, native-backend s-3caa36e9/s-dc7e3131, repr-opt §13); Required reading before execution (s-30e3c9a6 and s-fdca8921 first)
55. Conformance audit — parallel-drift36integratedEmission-axis collapse — detector-sizing rule (jaccard 0.2727 / shared_count 3 understates; size from source, discard the unsourced 14-step figure); the two low-signal parallel-drift rows are excluded from the section’s scope by that same rule
66. Plan ownership — who owns this code now28integratedOwnership and deferral boundary (author EmittedArtifact/ArtifactRequest/finalize here; blocked-by repr-opt §13 for TargetSpec/CompiledLayoutPlan; do not reshape ExecutableProgram; backend.rs now owned); Context — corrections table (ori_target aspirational)
77. Prior art36integratedPrior-art decision rule (adopt the one-driver invariant and the DummyCodegenBackend production-wired shape; reject ExtraBackendMethods width and rustc partitioning; refute the direct transfer — the collapse is Ori-shaped)
88. Sentiment — community heat30integratedGoal / Out of scope (platform-toolchain exclusion corroborated by the linker/SDK/LTO pain cluster); Prior-art decision rule — trait-completeness bar (rust #123119, zig #15899 back-edge verified gone at realization/program.rs:243)
99. Declared coverage gaps19integratedEvidence and verification contract — verification rule for graph claims (callers resolves 0 rows, co-change dark, surprise-coupling stale-empty, similar never bridges AOT/JIT; blast radius source-derived only); Emission-axis collapse — the unsourced 14-step figure is explicitly not used
1010. Recommended recon entry points30integratedRequired reading before execution (the ordered read path); Evidence and verification contract (production-path pin lands before any migration; ORI_DUMP_AFTER_LLVM baseline captured before the collapse)