Home / Pragma / uplc
Mar 09, 11-12 AM (0)
Mar 10, 12-1 AM (0)
Mar 10, 1-2 AM (0)
Mar 10, 2-3 AM (0)
Mar 10, 3-4 AM (0)
Mar 10, 4-5 AM (0)
Mar 10, 5-6 AM (0)
Mar 10, 6-7 AM (0)
Mar 10, 7-8 AM (0)
Mar 10, 8-9 AM (0)
Mar 10, 9-10 AM (0)
Mar 10, 10-11 AM (0)
Mar 10, 11-12 PM (0)
Mar 10, 12-1 PM (0)
Mar 10, 1-2 PM (0)
Mar 10, 2-3 PM (0)
Mar 10, 3-4 PM (0)
Mar 10, 4-5 PM (0)
Mar 10, 5-6 PM (0)
Mar 10, 6-7 PM (0)
Mar 10, 7-8 PM (0)
Mar 10, 8-9 PM (0)
Mar 10, 9-10 PM (0)
Mar 10, 10-11 PM (0)
Mar 10, 11-12 AM (0)
Mar 11, 12-1 AM (0)
Mar 11, 1-2 AM (0)
Mar 11, 2-3 AM (0)
Mar 11, 3-4 AM (0)
Mar 11, 4-5 AM (0)
Mar 11, 5-6 AM (0)
Mar 11, 6-7 AM (0)
Mar 11, 7-8 AM (0)
Mar 11, 8-9 AM (0)
Mar 11, 9-10 AM (0)
Mar 11, 10-11 AM (0)
Mar 11, 11-12 PM (0)
Mar 11, 12-1 PM (0)
Mar 11, 1-2 PM (0)
Mar 11, 2-3 PM (0)
Mar 11, 3-4 PM (0)
Mar 11, 4-5 PM (0)
Mar 11, 5-6 PM (0)
Mar 11, 6-7 PM (0)
Mar 11, 7-8 PM (2)
Mar 11, 8-9 PM (0)
Mar 11, 9-10 PM (0)
Mar 11, 10-11 PM (0)
Mar 11, 11-12 AM (0)
Mar 12, 12-1 AM (0)
Mar 12, 1-2 AM (0)
Mar 12, 2-3 AM (0)
Mar 12, 3-4 AM (0)
Mar 12, 4-5 AM (0)
Mar 12, 5-6 AM (0)
Mar 12, 6-7 AM (0)
Mar 12, 7-8 AM (0)
Mar 12, 8-9 AM (0)
Mar 12, 9-10 AM (0)
Mar 12, 10-11 AM (0)
Mar 12, 11-12 PM (0)
Mar 12, 12-1 PM (0)
Mar 12, 1-2 PM (0)
Mar 12, 2-3 PM (0)
Mar 12, 3-4 PM (0)
Mar 12, 4-5 PM (0)
Mar 12, 5-6 PM (0)
Mar 12, 6-7 PM (0)
Mar 12, 7-8 PM (0)
Mar 12, 8-9 PM (0)
Mar 12, 9-10 PM (0)
Mar 12, 10-11 PM (0)
Mar 12, 11-12 AM (0)
Mar 13, 12-1 AM (0)
Mar 13, 1-2 AM (0)
Mar 13, 2-3 AM (0)
Mar 13, 3-4 AM (0)
Mar 13, 4-5 AM (0)
Mar 13, 5-6 AM (0)
Mar 13, 6-7 AM (0)
Mar 13, 7-8 AM (0)
Mar 13, 8-9 AM (0)
Mar 13, 9-10 AM (0)
Mar 13, 10-11 AM (0)
Mar 13, 11-12 PM (0)
Mar 13, 12-1 PM (0)
Mar 13, 1-2 PM (0)
Mar 13, 2-3 PM (0)
Mar 13, 3-4 PM (0)
Mar 13, 4-5 PM (0)
Mar 13, 5-6 PM (0)
Mar 13, 6-7 PM (0)
Mar 13, 7-8 PM (0)
Mar 13, 8-9 PM (0)
Mar 13, 9-10 PM (0)
Mar 13, 10-11 PM (0)
Mar 13, 11-12 AM (0)
Mar 14, 12-1 AM (0)
Mar 14, 1-2 AM (0)
Mar 14, 2-3 AM (0)
Mar 14, 3-4 AM (0)
Mar 14, 4-5 AM (0)
Mar 14, 5-6 AM (0)
Mar 14, 6-7 AM (0)
Mar 14, 7-8 AM (0)
Mar 14, 8-9 AM (0)
Mar 14, 9-10 AM (0)
Mar 14, 10-11 AM (0)
Mar 14, 11-12 PM (0)
Mar 14, 12-1 PM (0)
Mar 14, 1-2 PM (0)
Mar 14, 2-3 PM (0)
Mar 14, 3-4 PM (0)
Mar 14, 4-5 PM (0)
Mar 14, 5-6 PM (0)
Mar 14, 6-7 PM (0)
Mar 14, 7-8 PM (0)
Mar 14, 8-9 PM (0)
Mar 14, 9-10 PM (0)
Mar 14, 10-11 PM (0)
Mar 14, 11-12 AM (30)
Mar 15, 12-1 AM (0)
Mar 15, 1-2 AM (0)
Mar 15, 2-3 AM (0)
Mar 15, 3-4 AM (0)
Mar 15, 4-5 AM (0)
Mar 15, 5-6 AM (0)
Mar 15, 6-7 AM (0)
Mar 15, 7-8 AM (0)
Mar 15, 8-9 AM (0)
Mar 15, 9-10 AM (0)
Mar 15, 10-11 AM (0)
Mar 15, 11-12 PM (0)
Mar 15, 12-1 PM (0)
Mar 15, 1-2 PM (0)
Mar 15, 2-3 PM (0)
Mar 15, 3-4 PM (0)
Mar 15, 4-5 PM (0)
Mar 15, 5-6 PM (0)
Mar 15, 6-7 PM (0)
Mar 15, 7-8 PM (6)
Mar 15, 8-9 PM (0)
Mar 15, 9-10 PM (0)
Mar 15, 10-11 PM (0)
Mar 15, 11-12 AM (0)
Mar 16, 12-1 AM (0)
Mar 16, 1-2 AM (0)
Mar 16, 2-3 AM (0)
Mar 16, 3-4 AM (0)
Mar 16, 4-5 AM (0)
Mar 16, 5-6 AM (0)
Mar 16, 6-7 AM (0)
Mar 16, 7-8 AM (0)
Mar 16, 8-9 AM (0)
Mar 16, 9-10 AM (0)
Mar 16, 10-11 AM (0)
Mar 16, 11-12 PM (0)
Mar 16, 12-1 PM (0)
Mar 16, 1-2 PM (0)
Mar 16, 2-3 PM (0)
Mar 16, 3-4 PM (0)
Mar 16, 4-5 PM (0)
Mar 16, 5-6 PM (0)
Mar 16, 6-7 PM (0)
Mar 16, 7-8 PM (0)
Mar 16, 8-9 PM (0)
Mar 16, 9-10 PM (0)
Mar 16, 10-11 PM (0)
Mar 16, 11-12 AM (0)
38 commits this week Mar 10, 2026 - Mar 17, 2026
perf: step countdown + inner return drain loop + boxed CasesFrame
Three optimizations to the VM dispatch loop:

1. Replace unbudgeted_steps[9] total counter with a countdown that
   decrements to zero. Saves one array increment per step and turns
   the >= comparison into a free zero-check after decrement.

2. Restructure the run loop to drain return chains without going back
   through the outer compute loop. Eliminates Phase construction and
   matching on the hot Compute→Return transition.

3. Box the CasesFrame (rare, 0.3% of opcodes) to shrink Frame from
   32 to 16 bytes, halving memory bandwidth for every frame push/pop.
feat: bytecode VM support for new builtins, case-on-constants, and VarBig
- Expand builtin pre-wrap range from 0..92 to 0..101 for 9 new builtins
  (Value ops, MultiScalarMul)
- Add case_on_constant handler: Bool (with branch count validation),
  Unit, Integer, List (head/tail field pushing), Pair (fst/snd)
- Add VarBig opcode (0x16) with u32 index for De Bruijn indices > 255,
  fixing silent truncation that caused wrong variable lookups in large
  scripts (e.g. stablecoin contracts)
- Compiler emits Var (u8) for indices <= 255, VarBig (u32) otherwise;
  ApplyVar/ForceVar superinstructions restricted to u8 range
test: add upstreamable case-on-boolean conformance tests
5 new conformance tests in standard .uplc/.expected/.budget.expected
format, suitable for upstreaming to the official Plutus conformance
test suite:

- case_bool-1: case (con bool True) → selects branch 1
- case_bool-2: case (con bool False) → selects branch 0
- case_bool-3: case (equalsInteger 1 1) → True → branch 1
- case_bool-4: case (equalsInteger 1 2) → False → branch 0
- case_bool-5: case (lessThanInteger 1 2) → True → branch 1

These tests verify that Bool values (from builtins or constants)
can be used as scrutinees in case expressions, with False=tag 0
and True=tag 1. Both Scalus and uplc-turbo previously failed these.

Also retains the 10 Rust-level case_bool_conformance tests.
Total: 1723 tests passing.
fix: handle case expressions on boolean values in V3
In Plutus V3, Bool is matchable via case expressions: False maps to
tag 0, True maps to tag 1 (matching the Constr encoding). Builtins
like equalsInteger return Con(Boolean), but case expects Constr.

Added Bool-to-tag conversion in the FrameCases handler for both the
AST interpreter and bytecode VM. This fixes coop-1 through coop-7
benchmark scripts which were previously failing silently.

All 1713 tests pass including 10 new case-on-boolean tests.
perf: box ConstrFrame to shrink Frame enum size
The Constr variant contained 6 fields including a BumpVec, making the
Frame enum ~72 bytes. Boxing it puts only an 8-byte pointer in the
enum, reducing Frame to ~24 bytes. This halves the per-push/pop copy
cost for the common frame variants (AwaitArg, AwaitFunTerm, Force).

Small improvement on diverse workloads (escrow: 231→221µs, uniswap:
852→832µs). 818/818 conformance tests passing.
perf: jump table dispatch + AOT profiling binary
Replace guarded match patterns (op if op == Op::Xxx as u8 =>) with
direct numeric literals (0x01 =>) in the bytecode dispatch loop.
This allows the compiler to generate a jump table instead of chained
comparisons.

Results (AOT, pre-compiled):
- auction_1-1: 120µs (matching AST interpreter's 118µs)
- auction_1-2: 369µs (vs AST's 325µs — 14% gap)

The bytecode VM shows dramatically better cache behavior (60% fewer
L1 misses) and branch prediction (33% fewer misses) compared to AST,
but executes ~19% more instructions due to opcode decoding overhead.
perf: slim LambdaBC/DelayBC values + zero-alloc Constr/Case frames
Two major optimizations to the bytecode VM:

1. LambdaBC/DelayBC values now store only (body_ip, id, env) instead of
   (body_ip, env, parameter, body). The AST refs for discharge are looked
   up from CompiledProgram tables only when needed (final output). This
   cuts the value size significantly, reducing arena allocation pressure.

2. Frame::Constr and Frame::Cases no longer heap-allocate Vec<u32> for
   field/branch offsets. Instead they store (offsets_start, count) and
   read offsets directly from the bytecode array on demand.

Bytecode VM now 25-44% faster than the AST interpreter:
  auction_1-1: 118µs → 82µs (1.44x)
  auction_1-2: 325µs → 259µs (1.25x)
  auction_1-3: 332µs → 257µs (1.29x)

All 1703 tests passing (818 bytecode conformance + 818 AST + 36 unit + 31 FLAT).
feat: working bytecode VM with compiler, dispatch loop, and 8 passing tests
Complete bytecode VM implementation:

Compiler (compiler.rs):
- Compiles Term AST to flat bytecode with backpatching
- Detects and emits superinstructions for common patterns
- Interns constants into a side-table pool
- 11 compiler unit tests

VM (vm.rs):
- Tight dispatch loop over u8 opcodes
- LambdaBC/DelayBC value variants for bytecode closures (body_ip + env)
- Delegates builtin execution to existing Machine::call
- Full CEK semantics: env, continuation stack, budget tracking

Passing integration tests:
- bc_eval_integer, bc_eval_unit, bc_eval_true
- bc_eval_identity (lambda + apply)
- bc_eval_force_delay
- bc_eval_add_integer (builtin)
- bc_eval_nested_apply (deep lambda nesting)
- bc_eval_if_then_else (polymorphic builtin with force/delay)
perf: pre-wrap constant pool Values at execution start
Instead of allocating a Value::Con wrapper in the arena for every
Const opcode execution, pre-wrap all constant pool entries into
Values once at the start of execute(). The Const opcode now does
a simple array index instead of an arena allocation.

~10% improvement on auction scripts (120→107µs on auction_1-1).
818/818 conformance tests passing.
perf: AOT bytecode benchmark + Vec-indexed lambda/delay discharge
- Replace HashMap lookups for lambda_info/delay_info with Vec indexing
  via compile-time u16 IDs embedded in the bytecode
- Benchmark pre-compiles bytecode once, only measures execution (AOT)
- ConstrBig opcode for large tags (u64), Constr stays u8 for common case
- 818/818 conformance tests still passing
feat: bytecode VM passes all 818 conformance tests
Key fixes:
- LambdaBC/DelayBC values now store original AST terms for discharge,
  enabling correct term reconstruction for output/error reporting
- Compiler records lambda_info/delay_info mappings from body_ip → AST
- Constr uses u8 tag (common case) with ConstrBig (u64) for large tags,
  keeping the hot path cache-friendly via separate opcodes
- ForceBuiltin/Force2Builtin superinstructions now only emitted when
  the builtin actually requires forcing (fixes argExpected test)

Test results: 1703 total passing
- 818 conformance tests via bytecode VM (NEW - all passing)
- 818 conformance tests via AST interpreter
- 36 unit tests (compiler + VM integration)
- 31 FLAT round-trip tests
test: add failing case-on-boolean conformance tests
7 failing tests that document a pre-existing upstream bug where case
expressions cannot match on boolean values. In Plutus V3, Bool should
be matchable via case (False=tag 0, True=tag 1), but uplc-turbo
returns NonConstrScrutinized because builtins return Con(Boolean)
instead of Constr(0/1).

This bug causes the coop-1 through coop-7 benchmark scripts to fail.
The benchmark framework was silently measuring failure-path speed.

3 passing tests confirm case-on-constr already works correctly.
perf: add ApplyVar superinstruction
When Apply's function is a Var (variable lookup), the function
evaluation is instant — just an array index into the environment.
The ApplyVar superinstruction skips FrameAwaitFunTerm entirely,
directly pushing FrameAwaitArg with the looked-up value.

Captures ~19% of Apply opcodes (444 out of 2344 in auction_1-2).
Saves 1 frame push + 1 frame pop + 1 Phase transition per occurrence.

818/818 conformance tests passing.
wip: bytecode compiler foundation
Add bytecode module with:
- Opcode definitions (10 core + 4 superinstructions + 4 specialized constants)
- Compiler that translates Term AST to flat bytecode with backpatching
- 11 compiler tests verifying correct opcode generation for all patterns

Superinstructions detected at compile time:
- ForceDelay: Force(Delay(body)) → single opcode, body inline
- ApplyLambda: Apply(Lambda(body), arg) → single opcode
- ForceBuiltin: Force(Builtin(f)) → single opcode
- Force2Builtin: Force(Force(Builtin(f))) → single opcode

Specialized constants for Unit, true, false, small integers avoid
constant pool lookup.

VM execution loop is stubbed — needs Value type extension for bytecode
closures (LambdaBC/DelayBC variants) before full implementation.
perf: enable LTO and replace Context linked list with Vec stack
Two changes:

1. Enable LTO (lto=true) and single codegen unit (codegen-units=1) for
   release and bench profiles. Gives the compiler full cross-crate
   visibility for inlining and dead code elimination. ~20-25% improvement.

2. Replace arena-allocated Context linked list with a pre-allocated
   Vec<Frame> stack. Eliminates per-frame arena allocation, improves
   cache locality (contiguous memory), and simplifies MachineState
   (no longer carries Context reference, reducing enum size).

Combined effect: uplc-turbo now ranks 3rd across all VM implementations,
ahead of Scalus CEK, Plutuz, and both Chrysalis variants.

Geo mean: 266µs → 201µs (24.6% faster on this benchmark run).