Add Spec.Data.Value.UnionBudget -- raw sorted-merge vs builtin matrix
Compares the on-chain builtin union path (unsafeDataAsValue + unionValue +
mkValue) against a hand-rolled sorted-merge over the raw BuiltinData
representation, so V3 guidance on union cost rests on concrete numbers
rather than estimates.
The new module Spec.Data.Value.UnionBudget adds 8 goldenBundle entries:
union_S1,3,8,100 in builtin and raw variants. Both bundle paths take two
BuiltinData-encoded Values, the same value on both sides, mirroring the
conservation-of-value pattern from production validators, produce the
merged BuiltinData, and report the CEK budget. The bundle does not chain
a lookupCoin or valueOf on the result so the measured cost is the union
itself, not a union-then-lookup composite.
The hand-rolled unionValuePositiveRaw is a Plinth translation of
Philip's pvalueUnionFast (Plutarch, slack thread 1776810760.659419).
Sorted-merge with a three-way branch on key comparison; assumes both
inputs sorted by lexicographic key and inner-pair values strictly
positive. Sorted-merge is O(L + R) per level; lookup-and-merge through
AssocMap.union would be O(L x R).
unionValuePositiveRaw is module-internal and not exported from
plutus-ledger-api. The positive-quantity precondition is documented at
the call site, not encoded in the type system. The unionValueNonZero
variant from the slack discussion is left for a follow-up commit; the
production case Philip cares about is tx outputs, which are strictly
positive.
Result matrix, raw vs builtin CPU ratio: S1 5.20x, S3 5.65x, S8 5.83x,
S100 4.47x. Byte-identical output between builtin and raw at every
shape; the raw column closes most of the gap that the typed unionWith
from plutus#7799 opens. Goldens regenerated under both GHC 9.6 and GHC
9.12 (per ghc-version-support cabal stanza). 24 goldens times 2 GHC
versions equals 48 files.
For IntersectMBO/plutus-private#2243.