perf: typed MapKey enum removes routing-tsp str(j) tax on numeric maps#267
Merged
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Adds MapKey { Text(String), Int(i64) } and switches Value::Map from
Arc<HashMap<String, Value>> to Arc<HashMap<MapKey, Value>>. Numbers floor
to i64 at the builtin boundary (matching at xs i); NaN/Infinity are
rejected as runtime errors. Bool keys are stringified to Text — two-arm
bool maps are always shorter as a guard.
mget/mset/mhas/mdel/mkeys/mvals/grp/frq build typed keys through
MapKey::from_value at the boundary. JSON serialisation stringifies
numeric keys (JS/Python convention) — round-trip remains lossy since
JSON object keys are always strings.
Removes the routing-tsp tax: callers no longer need 'k=str j' before
every iteration when keying by a loop index.
HeapObj::Map switches to HashMap<MapKey, NanVal>. Adds nanval_to_map_key and map_key_to_nanval helpers to convert at the NanVal boundary, mirroring the tree-walker's MapKey::from_value. OP_MGET/MSET/MHAS/MDEL/MKEYS/MVALS all build a typed key from the NanVal register before dispatch. Wrong-type (non-text, non-number) and non-finite keys raise VmError::Type uniformly across the tree, VM, and Cranelift backends. RC=1 in-place mset fast path is preserved. JIT helpers (jit_mget, jit_mset, jit_mhas, jit_mdel) report runtime errors on bad key types instead of silently returning nil, matching tree/VM semantics.
Drops the hard-coded 'key must be t' constraint on mget/mset/mhas/mdel. is_valid_map_key_arg now accepts any scalar (Text or Number) and checks compatibility against the declared map key type. Maps declared as M n _ accept numeric keys without an ILO-T013 error. mkeys returns L<declared-key-type> instead of the old hard-coded L t. mvals returns L<declared-value-type>. frq xs:L a yields M a n (key type follows the list element type), with Bool flattened to Text since bools are stringified at the MapKey boundary.
regression_numeric_map_keys: 15 cross-engine tests covering mset/mget, mhas (present + missing), mdel, mkeys (sorted determinism), mvals, len, jdmp (numeric keys stringify in JSON), float-floor-to-int, and Int-vs-Text distinctness via frq. Each test runs tree, VM, and cranelift. examples/numeric-map-keys.ilo: demonstrates the typed-key surface end to end across all engines (hist, has-key, del-key, key-count, sorted-keys). regression_frq + examples/frq.ilo: updated to reflect typed keys. Numeric frq inputs produce M n n (lookup with the bare number), and Int/Text are now distinct keys with no collision on shared print form.
63999b3 to
29a5420
Compare
5 tasks
danieljohnmorris
added a commit
that referenced
this pull request
May 15, 2026
- slc / take / drop accept negative indices counting from end (bounds clamp), matching at xs i. Closes the quant-trader fencepost and the slc xs -np 1 np ergonomics gap (#266). - Map keys are typed: text or integer. mset m 7 v and mget m 7 work directly, no str conversion. Int(1) and Text("1") are distinct. Float keys floor to i64; jdmp stringifies numeric keys for JSON (#267). - Add map / flt / fld to the builtin reference. All HOFs (map, flt, fld, srt, grp, uniqby, partition, flatmap) now work cross-engine on tree, VM, Cranelift JIT, and AOT (#274 #277 #278 #279 #280 #283). - New Inline lambdas subsection: Phase 1 literals are cross-engine, Phase 2 closure capture is tree-only with automatic fallthrough surfacing ILO-R012 on VM and Cranelift (#265 #284). - AOT-compiled binaries from ilo compile now strip the top-level ~/^ wrapper byte-for-byte the same as in-process runners (#281).
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
Adds a typed
MapKey { Text(String), Int(i64) }to ilo's map representation. Before this change,Value::Mapwas keyed byString, so any program iterating a map by a numeric index had to callk=str jbefore everymget/mset. The routing-tsp benchmark (and any agent-written code that builds an index lookup table) paid that tax on every iteration.With typed keys:
mset m 7 vandmget m 7work directly. Floats floor to i64 at the builtin boundary (matchingat xs i); NaN/Infinity raise a runtime error. Text and Int are distinct keys:Int(1)andText("1")no longer collide. Bool keys are stringified to Text (a bool map is always shorter as a two-arm?, so the surface syntax isn't worth the lexer ambiguity).JSON serialisation stringifies numeric keys (JS/Python convention). The round-trip remains lossy, since JSON object keys are always strings, deserialising back to a Record with text fields.
Repro before/after
Before (routing-tsp pattern):
After: no stringification needed.
What's in the diff
interpreter: introduce typed MapKey enum for Value::Map(280e389):Value::MapbecomesArc<HashMap<MapKey, Value>>. AddsMapKey::from_valuefor the builtin boundary,Ordfor deterministicmkeys/mvalsorder, and stringifies numeric keys onto_json.vm: thread MapKey through bytecode and Cranelift JIT(1f4a547):HeapObj::Mapswitches toHashMap<MapKey, NanVal>.OP_MGET/MSET/MHAS/MDEL/MKEYS/MVALSbuild typed keys via newnanval_to_map_keyhelper. RC=1 in-placemsetfast path is preserved. JIT helpers now report runtime errors on bad key types (matching tree/VM).verify: infer map key type from declarations and builtin args(192390e): drops the hard-coded "key must be t" constraint;mkeysreturnsL<declared-key-type>;frq xs:L ayieldsM a n.test + example: cross-engine coverage for typed map keys(63999b3): 15 new cross-engine tests; newexamples/numeric-map-keys.ilo; updatedregression_frqandexamples/frq.ilofor the typed-key semantics.Test plan
cargo fmt --checkcleancargo build --release --features craneliftcleancargo test --release --features craneliftpasses (full suite green: ~2900 lib tests + every integration crate)cargo clippy --release --features cranelift --all-targets -- -D warningscleanexamples/numeric-map-keys.iloexercises all five entry points across tree, VM, and craneliftregression_numeric_map_keyscovers mset, mget, mhas, mdel, mkeys, mvals, len, jdmp, float-floor, Int-vs-Text distinctness across all three enginesFollow-ups
from_jsonpath still producesRecordfor generic objects rather thanValue::Map. That's untouched here, but if/when the JSON shape grows aM n _variant, we'd want to populateMapKey::Intfrom numeric-looking string keys.