Skip to content

Commit 7d7740d

Browse files
JIT lowering for binary/ternary/array-input harmonic primitives
Extends the harmonic-intrinsic JIT (commit 621a80a) with three new arity classes and the hot-path measurement showing real-world impact: NEWLY JIT'D (8 primitives): Binary i64,i64 -> i64: gcd(a, b) — Euclidean GCD lcm(a, b) — least common multiple safe_mod(a, b) — substrate-folded modulus Ternary i64,i64,i64 -> i64: mod_pow(b, e, m) — fast modular exponentiation Array-input i64(ptr) -> i64 (uses L1.6 buffer layout): arr_sum_int(arr) — wrapping sum arr_product(arr) — wrapping product arr_min_int(arr) — min element arr_max_int(arr) — max element The array-input pattern is what completes the integration story. The intrinsic shim reads `*p` as length and walks slots 1..=len exactly like the L1.6 input bridge does — same layout, same extern fn signature. Lets a JIT'd fn build an array inline (or receive one as a parameter) and aggregate it without bouncing back to tree-walk for the reduction. ARCHITECTURAL NOTE — float-returning intrinsics deferred: harmony_value(n) and value_danger(n) return f64 (as i64 bit-pattern in the JIT). The cli-side dispatch closure currently wraps every i64 return as Value::HInt, which would corrupt the float interpretation when these are returned at the top level. The shims exist in lib.rs but are NOT in the dual_band intercept table. To enable, mirror the L1.6 returns_array_int plumbing for a returns_float flag (small additional work; deferred because nothing in current hot paths needs it). HOT-PATH IMPACT — NSL-KDD harmonic_anomaly fit: Tree-walk: 363 ms JIT (pre-L1.6, arrays in dispatch): 363 ms (no JIT actually used) JIT (post-L1.6 input bridge): 191 ms (1.9x) JIT (+ harmonic-primitive intrinsics): 107 ms (3.4x total) Same 15 of 53 user fns JIT (ha.score etc.); the additional speedup comes from their INTERNAL calls to attractor_distance / is_attractor / nth_fibonacci / etc. now being native instead of bouncing back to tree-walk for each invocation. TESTS: 24 in jit_harmonic_intrinsics.rs (was 16 + 8 new — gcd, lcm, safe_mod, mod_pow, arr_sum_int internal, arr_product internal, arr_min/max_int internal, plus a combined substrate workload that exercises NewArray + ArrayIndex + harmonic_unalign + ArrayLen without tree-walk fallback). All pass. 77 codegen tests total (was 69 + 8). 161 OMC tests still green. docs/jit_real_world.md updated with the 3.4x measurement. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 621a80a commit 7d7740d

4 files changed

Lines changed: 421 additions & 48 deletions

File tree

docs/jit_real_world.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,10 @@ Same workload (`examples/datascience/nsl_kdd_validation.omc`, 5000 rows):
113113
|---|--:|--:|
114114
| Tree-walk | 363 ms | n/a |
115115
| JIT (pre-L1.6) | 363 ms | 1 of 4 user fns |
116-
| **JIT (post-L1.6)** | **191 ms** | **15 of 53 user fns** (incl. `ha.score`) |
116+
| JIT (post-L1.6) | 191 ms | 15 of 53 user fns (incl. `ha.score`) |
117+
| **JIT (+ harmonic-primitive intrinsics)** | **107 ms** | 15 of 53 user fns (same fns, but inner harmonic calls now native) |
117118

118-
**1.9× wall-clock speedup on the real harmonic_anomaly workload.** The hot-loop fn `ha.score` now actually runs through the JIT instead of falling back to tree-walk.
119+
**3.4× wall-clock speedup on the real harmonic_anomaly workload.** The hot-loop fn `ha.score` now runs through the JIT, and its inner calls to `attractor_distance` / `is_attractor` / `nth_fibonacci` / etc. are also native code instead of bouncing back to the tree-walk builtin dispatch per call.
119120

120121
Synthetic microbench (sum over arr_range(0, 1000), 1000 iterations):
121122

omnimcode-codegen/src/dual_band.rs

Lines changed: 137 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -681,59 +681,150 @@ impl<'ctx, 'a> DualBandLowerer<'ctx, 'a> {
681681
stack.push(self.splat(ret_iv, "log_ret_v")?);
682682
continue;
683683
}
684-
// Harmonic-primitive intrinsics: unary i64 -> i64
685-
// OMC builtins routed through extern Rust shims
686-
// declared in JitContext::new. Each entry maps
687-
// (OMC source name, argc, extern fn name).
684+
// Harmonic-primitive intrinsics: OMC builtins routed
685+
// through extern Rust shims declared in JitContext::new.
686+
// Three arity tables — unary, binary, ternary — share
687+
// the same pattern: pop args, extract α lane (i64),
688+
// call extern, splat result back as <2 x i64>.
688689
//
689-
// Adding a new entry here is enough to enable the
690-
// JIT for that builtin — the extern + global-mapping
691-
// side is already pluraled in lib.rs.
692-
const HARMONIC_INTRINSICS: &[(&str, usize, &str)] = &[
693-
("nth_fibonacci", 1, "omc_nth_fibonacci"),
694-
("is_attractor", 1, "omc_is_attractor"),
695-
("attractor_distance", 1, "omc_attractor_distance"),
696-
("hbit_tension", 1, "omc_attractor_distance"),
697-
("fibonacci_index", 1, "omc_fibonacci_index"),
698-
("attractor_bucket", 1, "omc_attractor_bucket"),
699-
("substrate_hash", 1, "omc_substrate_hash"),
700-
("zeckendorf_weight", 1, "omc_zeckendorf_weight"),
701-
("bit_count", 1, "omc_bit_count"),
702-
("bit_length", 1, "omc_bit_length"),
703-
("digit_sum", 1, "omc_digit_sum"),
704-
("digit_count", 1, "omc_digit_count"),
705-
("harmonic_unalign", 1, "omc_harmonic_unalign"),
706-
("harmonic_align", 1, "omc_fold"),
690+
// Unary table — including ARRAY-INPUT intrinsics where
691+
// the i64 arg is the L1.6 length-prefixed buffer pointer
692+
// (the JIT'd code already has that pointer on the stack
693+
// because arrays live as ptr-in-α from NewArray).
694+
const UNARY_INTRINSICS: &[(&str, &str)] = &[
695+
("nth_fibonacci", "omc_nth_fibonacci"),
696+
("is_attractor", "omc_is_attractor"),
697+
("attractor_distance", "omc_attractor_distance"),
698+
("hbit_tension", "omc_attractor_distance"),
699+
("fibonacci_index", "omc_fibonacci_index"),
700+
("attractor_bucket", "omc_attractor_bucket"),
701+
("substrate_hash", "omc_substrate_hash"),
702+
("zeckendorf_weight", "omc_zeckendorf_weight"),
703+
("bit_count", "omc_bit_count"),
704+
("bit_length", "omc_bit_length"),
705+
("digit_sum", "omc_digit_sum"),
706+
("digit_count", "omc_digit_count"),
707+
("harmonic_unalign", "omc_harmonic_unalign"),
708+
("harmonic_align", "omc_fold"),
709+
// harmony_value / value_danger return floats. They're
710+
// NOT in this table because the dispatch boundary
711+
// wraps every i64 return as Value::HInt, which
712+
// would corrupt the float's bit-pattern interpretation
713+
// at the top level. The shims still exist in lib.rs
714+
// for future use once a returns_float plumbing path
715+
// mirrors returns_array_int.
716+
// Array-input intrinsics (input is buffer pointer).
717+
("arr_sum_int", "omc_arr_sum_int"),
718+
("arr_product", "omc_arr_product"),
719+
("arr_min_int", "omc_arr_min_int"),
720+
("arr_max_int", "omc_arr_max_int"),
707721
];
708-
if let Some(&(_, _, extern_name)) = HARMONIC_INTRINSICS
722+
if let Some(&(_, extern_name)) = UNARY_INTRINSICS
709723
.iter()
710-
.find(|(n, ac, _)| n == name && *ac == *argc)
711-
{
712-
let v_v = self.pop(&mut stack, i, name)?;
713-
let alpha = self
714-
.builder
715-
.build_extract_element(v_v, i64_type.const_int(0, false), "intr_a")
716-
.map_err(|e| format!("intrinsic {} extract at op{}: {}", name, i, e))?;
717-
let alpha_iv = match alpha {
718-
BasicValueEnum::IntValue(iv) => iv,
719-
_ => return Err(format!("intrinsic {} arg not int at op{}", name, i)),
724+
.find(|(n, _)| n == name) {
725+
if *argc == 1 {
726+
let v_v = self.pop(&mut stack, i, name)?;
727+
let alpha = self
728+
.builder
729+
.build_extract_element(v_v, i64_type.const_int(0, false), "intr_a")
730+
.map_err(|e| format!("intrinsic {} extract at op{}: {}", name, i, e))?;
731+
let alpha_iv = match alpha {
732+
BasicValueEnum::IntValue(iv) => iv,
733+
_ => return Err(format!("intrinsic {} arg not int at op{}", name, i)),
734+
};
735+
let ext_fn = self.module
736+
.get_function(extern_name)
737+
.ok_or_else(|| format!("{} not declared at op{}", extern_name, i))?;
738+
let call = self
739+
.builder
740+
.build_call(ext_fn, &[alpha_iv.into()], "intr_call")
741+
.map_err(|e| format!("intrinsic {} call at op{}: {}", name, i, e))?;
742+
let ret = call
743+
.try_as_basic_value()
744+
.left()
745+
.ok_or_else(|| format!("intrinsic {} no value at op{}", name, i))?;
746+
let ret_iv = match ret {
747+
BasicValueEnum::IntValue(iv) => iv,
748+
_ => return Err(format!("intrinsic {} ret not int at op{}", name, i)),
749+
};
750+
stack.push(self.splat(ret_iv, "intr_ret_v")?);
751+
continue;
752+
}
753+
}
754+
// Binary i64,i64 -> i64 intrinsics.
755+
const BINARY_INTRINSICS: &[(&str, &str)] = &[
756+
("gcd", "omc_gcd"),
757+
("lcm", "omc_lcm"),
758+
("safe_mod", "omc_safe_mod"),
759+
];
760+
if let Some(&(_, extern_name)) = BINARY_INTRINSICS
761+
.iter()
762+
.find(|(n, _)| n == name) {
763+
if *argc == 2 {
764+
// Stack order: pushed left-to-right, so top = b, below = a.
765+
let b_v = self.pop(&mut stack, i, name)?;
766+
let a_v = self.pop(&mut stack, i, name)?;
767+
let zero = i64_type.const_int(0, false);
768+
let a_alpha = self.builder.build_extract_element(a_v, zero, "binintr_a")
769+
.map_err(|e| format!("binintr {} a extract at op{}: {}", name, i, e))?;
770+
let b_alpha = self.builder.build_extract_element(b_v, zero, "binintr_b")
771+
.map_err(|e| format!("binintr {} b extract at op{}: {}", name, i, e))?;
772+
let a_iv = match a_alpha {
773+
BasicValueEnum::IntValue(iv) => iv,
774+
_ => return Err(format!("binintr {} a not int at op{}", name, i)),
775+
};
776+
let b_iv = match b_alpha {
777+
BasicValueEnum::IntValue(iv) => iv,
778+
_ => return Err(format!("binintr {} b not int at op{}", name, i)),
779+
};
780+
let ext_fn = self.module
781+
.get_function(extern_name)
782+
.ok_or_else(|| format!("{} not declared at op{}", extern_name, i))?;
783+
let call = self.builder
784+
.build_call(ext_fn, &[a_iv.into(), b_iv.into()], "binintr_call")
785+
.map_err(|e| format!("binintr {} call at op{}: {}", name, i, e))?;
786+
let ret = call.try_as_basic_value().left()
787+
.ok_or_else(|| format!("binintr {} no value at op{}", name, i))?;
788+
let ret_iv = match ret {
789+
BasicValueEnum::IntValue(iv) => iv,
790+
_ => return Err(format!("binintr {} ret not int at op{}", name, i)),
791+
};
792+
stack.push(self.splat(ret_iv, "binintr_ret_v")?);
793+
continue;
794+
}
795+
}
796+
// Ternary i64,i64,i64 -> i64 intrinsics (currently mod_pow).
797+
if name == "mod_pow" && *argc == 3 {
798+
// Stack: top = c, mid = b, bottom = a.
799+
let c_v = self.pop(&mut stack, i, "mod_pow c")?;
800+
let b_v = self.pop(&mut stack, i, "mod_pow b")?;
801+
let a_v = self.pop(&mut stack, i, "mod_pow a")?;
802+
let zero = i64_type.const_int(0, false);
803+
let mut extract = |v, label: &str| -> Result<IntValue<'ctx>, CodegenError> {
804+
let e = self.builder.build_extract_element(v, zero, label)
805+
.map_err(|err| format!("mod_pow extract {} at op{}: {}", label, i, err))?;
806+
match e {
807+
BasicValueEnum::IntValue(iv) => Ok(iv),
808+
_ => Err(format!("mod_pow {} not int at op{}", label, i)),
809+
}
720810
};
721-
let ext_fn = self.module
722-
.get_function(extern_name)
723-
.ok_or_else(|| format!("{} not declared at op{}", extern_name, i))?;
724-
let call = self
725-
.builder
726-
.build_call(ext_fn, &[alpha_iv.into()], "intr_call")
727-
.map_err(|e| format!("intrinsic {} call at op{}: {}", name, i, e))?;
728-
let ret = call
729-
.try_as_basic_value()
730-
.left()
731-
.ok_or_else(|| format!("intrinsic {} no value at op{}", name, i))?;
811+
let a_iv = extract(a_v, "mp_a")?;
812+
let b_iv = extract(b_v, "mp_b")?;
813+
let c_iv = extract(c_v, "mp_c")?;
814+
let ext_fn = self.module.get_function("omc_mod_pow")
815+
.ok_or_else(|| format!("omc_mod_pow not declared at op{}", i))?;
816+
let call = self.builder.build_call(
817+
ext_fn,
818+
&[a_iv.into(), b_iv.into(), c_iv.into()],
819+
"mod_pow_call"
820+
).map_err(|e| format!("mod_pow call at op{}: {}", i, e))?;
821+
let ret = call.try_as_basic_value().left()
822+
.ok_or_else(|| format!("mod_pow no value at op{}", i))?;
732823
let ret_iv = match ret {
733824
BasicValueEnum::IntValue(iv) => iv,
734-
_ => return Err(format!("intrinsic {} ret not int at op{}", name, i)),
825+
_ => return Err(format!("mod_pow ret not int at op{}", i)),
735826
};
736-
stack.push(self.splat(ret_iv, "intr_ret_v")?);
827+
stack.push(self.splat(ret_iv, "mod_pow_ret_v")?);
737828
continue;
738829
}
739830
if name == "to_int" && *argc == 1 {

0 commit comments

Comments
 (0)