Optimal non-builtin valueOf in plutus-ledger-api Data.Value#7797
Merged
Conversation
ae21ea2 to
0ac70fa
Compare
zliu41
reviewed
May 28, 2026
zliu41
left a comment
Member
There was a problem hiding this comment.
Looks good, just one comment on the test.
0ac70fa to
cd7c252
Compare
8271c9e to
613670c
Compare
zliu41
reviewed
May 29, 2026
Rewrites `PlutusLedgerApi.V1.Data.Value.valueOf` so the non-builtin lookup path walks the underlying `BuiltinList` directly via `unsafeDataAsMap` / `unsafeDataAsB` / `unsafeDataAsI`, compares keys with `equalsByteString`, and short-circuits on the first match. No `Maybe` is materialised: the "absent" answer is `0`, returned in-place by the `nilCase` of each traversal. Avoids `withCurrencySymbol`'s continuation + `Map.lookup`'s `Maybe`-wrapping, and bypasses the `ToData k`/`UnsafeFromData a` dictionary work that `AssocMap.lookup` does per element. Semantics preserved. Adds `Spec.Data.Value.test_valueOf`: a QuickCheck property that compiles `valueOf` via TH, evaluates it on the CEK machine, and compares the result against the host-Haskell `valueOf` for the same inputs. Differential test against the Plinth compiler — any divergence is a compilation bug, not a semantics bug. Budget evidence (lookup matrix, `unsafeDataAsValue` baseline) lives on the companion experimental branch `yura/issue-2242-valueof-evidence`, kept out of this PR to avoid carrying ~96 golden files that would only ever regenerate on upstream plugin/cost-model changes. For IntersectMBO/plutus-private#2242.
613670c to
b4d1df4
Compare
zliu41
approved these changes
Jun 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PlutusLedgerApi.V1.Data.Value.valueOfnow walks the outer and innerBuiltinLists directly:unsafeDataAsMapfor the maps,unsafeDataAsBfor the keys,unsafeDataAsIfor the result,equalsByteStringto compare. The loop returns0from the nil-case, so there is noMaybeconstructed and immediately scrutinised. Same semantics, no public API change.Closes plutus-private#2242.
Evidence
The lookup matrix and the
unsafeDataAsValuebaseline are on a separate, never-merge PR (#7798) so the 96 golden files do not sit in this PR's CI surface. Those goldens regenerate on any upstream plugin or cost-model shift, which makes them noise for a regression gate.Old vs new
valueOf, CPU in thousands, GHC 9.6, plutus-ledger-api 1.65.0.0. Old =master(Map.lookup+withCurrencySymbolcontinuation). New = this PR.Shape legend: S1 = ada only (1 token); S3 = ada plus 2 single-token policies (3 tokens); S8 = ada plus 7 single-token policies (8 tokens); S100 = ada plus 10 policies of 10 tokens each (101 tokens). Lookup position is the row index of the matching
(currency, token)inside the outer/inner maps: ada = first, middle = around the centre, last = final entry, miss = absent key (full scan).First-position lookups (
ada) are constant in both versions because both short-circuit on the first key match; the new path is 1.44× faster on the constant cost. Linear scans (middle,last,miss) get a 1.6–1.7× speedup that holds across shapes, because the per-step cost dropped fromMap.lookup-with-dictionary-conversion to a singleequalsByteStringplus three rawunsafeDataAs*builtins. Memory dropped roughly in proportion.For comparison against the builtin path (
unsafeDataAsValue+lookupCoin), see #7798; the headline is that the newvalueOfbeats the builtin path on first-position lookups for large values (S100 ada: 3.77 M vs 22.11 M, ~5.9× cheaper).