Add core math builtins: pow, sqrt, log, exp, sin, cos#162
Merged
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
The doc cited hand-rolled Taylor `expx` "quietly returns wildly wrong values for x > 10⁴" - the only remaining silent-miscompile class in the feedback log. Every numerics-heavy program was forced to either roll its own with silent precision loss or skip the analysis. For an AI-targeted language that lists "data work" as a core use case, this was a real reliability hole. Adding six core transcendentals backed by std::f64, available across tree-walker, VM, and Cranelift JIT: - pow x y (2-arg) - sqrt, log (natural log), exp, sin, cos (1-arg, radians) Skipped tan/log10/log2/atan2 to keep the initial surface minimal - easy to add later if agents ask for them. Each builtin gets its own opcode (OP_POW/OP_SQRT/OP_LOG/OP_EXP/ OP_SIN/OP_COS at 97-102) with a jit_* extern "C" helper, matching the precedent set by abs/flr/cel/mod/min/max. No generic dispatch - every existing math builtin has a dedicated opcode and uniform fast-path treatment in the numeric classifier passes. Domain-invalid inputs (sqrt -1, log 0) return NaN silently as std::f64 does, matching the no-Result-wrapping convention of the existing math builtins. For non-numeric inputs (which the verifier should prevent but defensively handled), all three bytecode paths now uniformly return NaN: the JIT helpers (was TAG_NIL), VM dispatch (was unchecked transmute), and tree-walker (still raises RuntimeError - the type-checked tree path is self-consistent and the verifier guarantees it won't fire in well-formed programs). Cross-engine consistency restored.
12 cross-engine regression tests covering each of the six new builtins (pow, sqrt, log, exp, sin, cos) across tree, vm, and cranelift, plus two unit tests pinning the cross-engine NaN contract for non-numeric input (jit_math_helpers_return_nan_on_ non_number, vm_math_ops_return_nan_on_non_number). Numeric tolerance for the transcendentals uses |result - expected| < 1e-10, loose enough for f64 imprecision but tight enough to catch real bugs. The sin_large_argument test exercises sin 100000 which the prior hand-rolled Taylor produced wildly wrong values for; the new std::f64 path is accurate across the full float range. examples/math.ilo demonstrates the new builtins via a Euclidean `dist` helper, a compound-interest one-liner via pow, and a log/ exp round-trip. Each with `-- run:` / `-- out:` directives so tests/examples_engines.rs exercises them across every engine.
8caad93 to
cf5c723
Compare
check_infix_on_call and check_single_atom_after_op both wrote to fixed-name temp files. Under cargo llvm-cov's parallel test runner, two threads writing different content to the same path raced; whichever finished writing last "won" but the other test's ilo process might have already opened the file with mid-write content, producing ILO-R002 undefined function failures intermittently. Same fix shape as PR #163's run_file: AtomicU64 counter per function, appended to filename along with pid for uniqueness. Relaxed ordering matches the eval_inline.rs precedent (the counter only needs atomic increment; no cross-variable synchronization required).
PR #162's codecov/patch flagged the new math-builtin lines as under-covered at the patch level: the cross-engine integration tests do hit every path but codecov's patch heuristic doesn't weight spawned-binary tests the same as in-process unit tests. Added 12 in-process unit tests: - 6 VM-dispatch happy paths (vm_op_pow_happy etc.) via a small run_unary_math_op helper that crafts a tiny bytecode chunk - 6 jit_* helper happy paths exercising the extern "C" surface directly (cranelift-gated) Expanded the existing vm_math_ops_return_nan_on_non_number to cover OP_LOG, OP_EXP, OP_SIN, OP_COS too (previously only SQRT and POW), so the NaN-fallback contract is now pinned uniformly for all six new opcodes. Local lib-test count: 2738 → 2750. Coverage on src/vm/mod.rs: 87.22% → 87.68%. The integration tests already exercise the Cranelift translator branches; adding unit tests for those would require scaffolding that's not justified now.
PR #162's codecov/patch still failing after the first round of unit tests. Identified 38 uncovered new lines split across three buckets: - AOT Cranelift translator arms (`vm/compile_cranelift.rs`) which --run-cranelift doesn't exercise because it uses the JIT, not AOT - Interpreter runtime-type-error arms for the unary math builtins (the verifier catches these statically, but runtime checks remained untested) - Defensive `panic!` fallbacks inside test code (unreachable by design) Added 12 targeted tests: - 6 codegen tests in src/vm/compile_cranelift.rs (one per new opcode) that build a chunk and run it through the AOT pipeline to confirm object emission - 6 interpreter error-path tests in src/interpreter/mod.rs covering the non-number type-error arms for sqrt/log/exp/sin/cos/pow Coverage deltas: - interpreter/mod.rs: 96.50% → 96.76% - vm/compile_cranelift.rs: 88.08% → 88.81% - vm/mod.rs: 87.70% → 87.83% The remaining uncovered new lines are 5 `panic!` arms inside test functions whose inputs guarantee a non-panic return - covering them would require making the tests fail. If codecov still flags them after this push, the appropriate fix is a codecov-ignore on test code rather than adding misleading tests.
danieljohnmorris
added a commit
that referenced
this pull request
May 11, 2026
Follow-up to PR #162 math builtins. Adds the four remaining transcendentals every analysis program needs: phase angles atan2, decibels log10, bit-depth log2, oscillations tan. atan2 takes y first per C/Python convention. Opcodes 105-108.
danieljohnmorris
added a commit
that referenced
this pull request
May 11, 2026
Follow-up to PR #162 math builtins. Adds the four remaining transcendentals every analysis program needs: phase angles atan2, decibels log10, bit-depth log2, oscillations tan. atan2 takes y first per C/Python convention. Opcodes 105-108.
danieljohnmorris
added a commit
that referenced
this pull request
May 12, 2026
Follow-up to PR #162 math builtins. Adds the four remaining transcendentals every analysis program needs: phase angles atan2, decibels log10, bit-depth log2, oscillations tan. atan2 takes y first per C/Python convention. Opcodes 105-108.
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 the six core transcendentals as builtins backed by Rust's
std::f64. Resolves the only remaining silent-miscompile entry in the assessment doc.The doc cited hand-rolled Taylor
expx"quietly returns wildly wrong values for x > 10⁴" — every numerics-heavy program was forced to either roll its own with silent precision loss or skip the analysis. For an AI-targeted language that lists data work as a core use case, that was a real reliability hole — exactly the failure mode the manifesto exists to prevent.What's in the diff
builtins: add core transcendentals (pow, sqrt, log, exp, sin, cos)— six new builtins available across tree-walker, VM, and Cranelift JIT. Each gets its own opcode (OP_POW/OP_SQRT/OP_LOG/OP_EXP/OP_SIN/OP_COSat 97-102) with ajit_*extern "C" helper, matching the precedent set byabs/flr/cel/mod/min/max. Domain-invalid inputs (sqrt -1,log 0) return NaN silently asstd::f64does — same no-Result-wrapping convention as the existing math builtins.Cross-engine NaN consistency: for non-numeric inputs (which the verifier should prevent but defensively handled), all three bytecode paths uniformly return NaN. Caught in review — the JIT helpers previously returned
TAG_NIL, the VM transmuted unchecked bits, only the tree-walker behaved correctly. All three now agree.tests + example: pin math builtin behaviour across engines— 12 cross-engine regression tests + 2 unit tests pinning the cross-engine NaN contract. Numeric tolerance< 1e-10.sin_large_argumentexercisessin 100000which the hand-rolled Taylor used to fail catastrophically on.examples/math.ilodemonstrates a Euclideandisthelper, compound-interest viapow, andlog/expround-trip with-- run:/-- out:directives.Test plan
cargo test --release --features cranelift— full suite green, +14 tests (12 regression + 2 NaN-consistency unit)cargo fmt --all -- --checkcleancargo clippy --all-targets --features cranelift -- -D warningscleanpow 2 10 → 1024,sqrt 2 → 1.4142...,sin 0 → 0,cos 0 → 1across all three engineslog exp 5 → 5round-trippow 4 0.5 → 2(fractional exponent works)Opcode allocation
Claims opcodes 97-102. The parallel
fix/list-indexingbranch (PR #161) renumbered itsOP_ATto 103 to avoid collision; both PRs can merge in either order.Skipped on first cut
tan,log10,log2,atan2— kept the initial surface minimal. Easy follow-up if agents need them.