Skip to content

feat(compiler): implement explicit PV11 compiler target (ADR-031) - #93

Merged
satran004 merged 15 commits into
mainfrom
integration/adr-031-compiler-target
Aug 30, 2026
Merged

feat(compiler): implement explicit PV11 compiler target (ADR-031)#93
satran004 merged 15 commits into
mainfrom
integration/adr-031-compiler-target

Conversation

@satran004

Copy link
Copy Markdown
Member

Summary

Implements ADR-031 and closes ADR-029 Phase 4 for the deliberately narrow V3/PV11 compiler profile.

  • introduces an immutable compiler target for Plutus V3 / PV11 / UPLC 1.1.0 and rejects every unknown or future profile without fallback
  • snapshots and propagates the resolved target through compiler discovery, PIR lowering, UPLC generation, optimization, and final program construction
  • uses the VM protocol feature registry as the canonical capability source, with lowering metadata and source-located diagnostics
  • validates the final optimized UPLC program so optimizers and direct emission paths cannot bypass target legality
  • exposes exact target selection and provenance through CompileResult, CLI, Gradle, annotation processing, JRL, MCP, and testkit evaluation
  • documents the process for adding later protocol versions as new pinned profiles; there is no LATEST alias or implicit compatibility assumption

Compatibility and correctness

  • existing compile entry points retain the exact V3/PV11 default
  • default and explicitly selected PV11 compilation are byte-identical and hash-stable in regression tests
  • existing CompileResult construction remains source-compatible through compatibility constructors
  • UPLC encoding and artifact schemas are unchanged; provenance is carried in APIs and tooling output
  • compile-result-aware evaluation uses the Java VM target-aware SPI; Scalus remains available for its compatibility paths and fails closed for uncertified explicit-target evaluation
  • V1/V2 generation, PV10 lowerings, future protocol profiles, and Phase 5 cost-directed optimization remain out of scope

Milestones

  1. target values, policy, exact registry, and unsupported-target diagnostics
  2. immutable compilation context, stage propagation, result provenance, and byte-stability tests
  3. canonical capabilities, lowering enforcement, optimizer reporting, and final UPLC validation
  4. CLI/build/AP/JRL/MCP/testkit integration and documentation
  5. repository-wide regression hardening for target-aware VM provider wiring

Each implementation milestone was developed and tested on a separate branch, reviewed, and merged into this integration branch.

Verification

  • focused compiler registry, propagation, lowering, final-validator, optimizer, diagnostics, and byte-stability tests
  • CLI and MCP compile/evaluate tests
  • Gradle plugin TestKit and annotation-processor tests
  • JRL, testkit, examples, stdlib, and Cardano client integration suites
  • ./gradlew build --no-daemon --console=plain — BUILD SUCCESSFUL (213 tasks)
  • final diff whitespace check and audit for raw protocol-version threshold checks

Closes #76
Related to #65

@satran004 satran004 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Review — ADR-031 explicit PV11 compiler target

Verdict: approve with notes — ready to merge. No blockers. The implementation matches ADR-031 D1–D10, the fail-closed contract holds through every emission route I could find, and the PR's central promise (no byte changes) was verified independently rather than taken from the description.

What was verified

ADR conformance (D1–D10)

  • D1/D3/D4: CompilerTarget is the full language/protocol/UPLC triple; CompilerTargetRegistry.SUPPORTED is an exact Set.of(PLUTUS_V3_PV11); profile-ID lookup is case-sensitive; no LATEST. PV10 / V1 / V2 / UPLC 1.0.0 / PV12 all → JULC0031, never fallback (CompilerTargetRegistryTest).
  • D2: julc-compiler now api project(':julc-vm') and reuses ProtocolFeatureRegistry; no parallel builtin table in the compiler.
  • D5: CompilationContext is resolved once in beginCompilation() before classpath library scanning and reaches LibraryCompiler, LibraryMethodRegistry, PirGenerator, UplcGenerator, UplcOptimizer, program construction and CompileResult. compilationSnapshotsOptionsAndResolvesTargetExactlyOnce proves mid-compile mutation of CompilerOptions cannot leak in.
  • D6: LAST_RELEASED_PV11_BUILTIN_TAG is gone; generateBuiltin asks featureProfile().isBuiltinAvailable(fun). Grep for raw >= 11 / PlutusTarget.CURRENT in compiler passes is clean (two leftovers noted in F4, both envelope-type-only).
  • D7: all BUILTIN_{1,2,3}_ARG table entries, insertCoin, JulcArray.fromList and the three Array type-methods declare LoweringRequirements; LoweringRequirementsTest pins all 15 batch-6 routes.
  • D8: UplcTargetValidator runs after the optimizer on every entry path, including compilePirToProgram and source-map builds; the optimizer reports applied pass names for JULC0035. foldBinaryOp refuses to fold an unavailable builtin (optimizer containment).
  • D9/D10: CompileResult gains target with explicit 6/5/3/2-arg compatibility constructors (binary-compatible; no record-deconstruction consumers in repo). Catalog entries JULC0031–0036 added in both DiagnosticCodes and diagnostics.json.

Tests run fresh

  • ./gradlew :julc-vm:test :julc-compiler:test --tests '*CompilerTarget*' --tests '*UplcTargetValidator*' --tests '*LoweringRequirements*' --tests '*Pv11CompilerContract*' :julc-testkit:test88 + 24 + 190 tests, 0 failures.

Byte-stability vs main — verified directly. No existing test pins a real compiled-script hash, and CompilerTargetPropagationTest compares default-vs-explicit both on the branch. So origin/main was checked out in a worktree, the 10 real validators in julc-plugin-test/src/main/plutus/ + julc-playground/src/main/resources/examples/ were compiled on both trees with a throwaway probe, and FLAT length + sha256 diffed: 10/10 identical (MintingPolicy, SimpleSpending, VestingValidator, AuthorizedMintingPolicy, MultiSigValidator, TimelockValidator, TokenGateValidator, TypedMintingPolicy, TypedMultiSigValidator, TypedVestingValidator).

Findings

F1 (medium, compatibility) — testkit String-source entry points now hard-require a target-aware VM provider.
ValidatorTest.evaluate/assertValidates/assertRejects(String, …) (ValidatorTest.java:126), ContractTest.evaluate, JulcEval and MethodEvaluator.evaluateRaw now call vm.evaluate(program, target, …). Provider priority is Truffle 200 > Java 100 > Scalus 50, so mixed classpaths are fine — but a consumer with only julc-vm-scalus (the setup main's README recommended until this PR) gets a raw UnsupportedOperationException from the SPI default (JulcVmProvider.java:53), not a JULC* diagnostic with remediation. ADR-sanctioned direction, and the external julc-examples / julc-helloworld repos are already on vm-java, so nothing known breaks — but this needs (a) an actionable message ("add testRuntimeOnly julc-vm-java"), consistent with D10's own standard, and (b) an explicit release-note entry. The PR body's "fails closed for uncertified explicit-target evaluation" undersells it.

F2 (medium-low, observable semantics) — Those same paths previously used the language-only path, which on the Java VM defaults to PV10 (JulcVm.java:133-137 javadoc). They now evaluate under exact PV11 (semantics variant E, PV11 decode limits). An improvement, but a semantic change to existing test-suite behaviour; belongs in release notes.

F3 (low, docs/code mismatch)julc-examples/README.md:35-38 now prescribes compileWithDetails + evaluate(compiledResult, …), but no example was migrated (VestingValidatorTest.java:99-132, the README's "simplest starting point", still uses compile / evaluate(source, …)). Also, the only public way to get a plain CompileResult from ValidatorTest is the heavier PIR/UPLC-capturing compileWithDetails; the new compileResult(String, StdlibLookup) is private. Suggest making it public.

F4 (low, invariant hygiene)PlutusTarget.CURRENT survives in core as a parallel "current" alias feeding textEnvelopeType() (ScriptPurposeMetadata.java:13, julc-cli/.../ConvertCommand.java:42). Cannot drift with one target, but it is exactly the shape ADR-031 argues against; follow-up: derive envelope type from CompileResult.target().ledgerTarget().ledgerLanguage().

F5 (info)

  • compileWithSiblings with a parent-less path (JulcCompiler.java:714-720) no longer classpath-scans @OnchainLibrary sources (old code delegated to compile(String)). Edge case; arguably more consistent now.
  • TypeMethodRegistry.dispatch(context, …) accepts the context but handlers never receive it — plumbing without a consumer until a second target exists.
  • SubsetValidator is not context-aware (D5 lists it); nothing feature-bearing lives there today.
  • julc-annotation-processor/build.gradle:16 still has Scalus-only test runtime; works (its tests don't hit target-aware paths) but is inconsistent with the repo-wide move to vm-java.

Recommended before/at merge

  1. Decide whether F1 gets a catalog diagnostic in this PR or as an immediate follow-up.
  2. Release notes should state F1 + F2 and the CompileResult record-component change (ADR "Binary compatibility" section requires it be documented).
  3. Optional: UplcTargetValidatorTest.rejectsFutureBuiltinSurvivingAnOptimizerPass validates a hand-built program, not an actual optimizer-introduced term — fine today since no pass introduces builtins, but ADR-032 passes should add a real one.

Review performed with Claude Code (Fable 5).

@satran004
satran004 merged commit f639f83 into main Aug 30, 2026
2 checks passed
@satran004
satran004 deleted the integration/adr-031-compiler-target branch August 30, 2026 12:33
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.

ADR-029 Phase 4: Add an explicit V3/PV11 compiler target and diagnostics

1 participant