Skip to content

feature: asin/acos/atan inverse trig builtins#313

Merged
danieljohnmorris merged 5 commits into
mainfrom
feature/inverse-trig
May 16, 2026
Merged

feature: asin/acos/atan inverse trig builtins#313
danieljohnmorris merged 5 commits into
mainfrom
feature/inverse-trig

Conversation

@danieljohnmorris
Copy link
Copy Markdown
Collaborator

Summary

Geospatial personas (gis-analyst rerun4) keep hand-rolling a 6-term Taylor series for asin because the language ships sin/cos/tan/atan2 but no inverse trig. That's a token tax on every haversine and bearing computation, and a silent precision footgun. Rust's f64 already has asin, acos, atan natively, so the fix is plumbing across the three engines.

Manifesto framing: builtins are dedicated opcodes, so adding them removes Taylor expansions from agent output forever and saves tokens every time someone reaches for great-circle distance.

Repro

Before:

-- hand-rolled asin (the persona's actual code)
asin x:n>n;t1=x;x3=*x *x x;t2=/x3 6;x5=*x3 *x x;t3=*/x5 40 3;...

After:

hav lat1:n lon1:n lat2:n lon2:n>n;
  p1=*lat1 0.017453292519943295;
  ...
  s=sqrt a;
  as=asin s;
  *6371 *2 as

hav 51.5074 -0.1278 48.8566 2.3522 returns 343.55606034104164 on all engines.

What's in the diff

  • add asin/acos/atan inverse trig builtins (tree interpreter): registers the variants and wires the tree interpreter.
  • vm: add OP_ASIN/OP_ACOS/OP_ATAN and jit_* helper externs: new opcodes 171/172/173 + JIT helper functions following the existing NaN-on-non-number pattern.
  • cranelift: wire asin/acos/atan helpers in JIT and AOT codegen: declares the FuncIds, registers the symbols, extends the unary-math arm in both JIT and AOT paths.
  • test: cross-engine regression + haversine example for inverse trig: 16 cross-engine assertions in tests/regression_inverse_trig.rs plus examples/inverse-trig-haversine.ilo which tests/examples_engines.rs runs end-to-end.
  • docs: document asin/acos/atan in spec, ai.txt and SKILL.md: keeps the four canonical docs in sync.

Test plan

  • cargo test --release --features cranelift --test regression_inverse_trig — 16 passed
  • cargo test --release --features cranelift --test examples_engines — haversine example passes on tree + vm
  • cargo test --release --features cranelift full suite green locally (the only failures were libilo.a lookups that resolve when the static lib is in target/release/; CI builds at that path)
  • cargo fmt --check
  • cargo clippy --release --features cranelift --all-targets -- -D warnings

Follow-ups

None blocking. Worth considering whether to also expose pi as a constant so *180 /x pi reads cleanly without the magic 0.0174... literal, but that's a separate ask.

Register Asin/Acos/Atan in the Builtin enum, name+tag tables, and the
verifier's signature table. Wire the tree interpreter's unary-math arm
to dispatch to Rust's f64::asin/acos/atan. atan2 already lived as a
2-arg builtin; this completes the inverse-trig set so geospatial code
doesn't need a hand-rolled 6-term Taylor series.
New opcodes 171/172/173 routed through the existing unary-math
dispatch arm. The compiler emits them from the asin/acos/atan
builtins, mirroring sin/cos/tan. Cranelift JIT helpers (jit_asin,
jit_acos, jit_atan) follow the same NaN-on-non-number pattern as
jit_sin/jit_cos.
Declare the three new helper FuncIds and symbol registrations in both
the in-process JIT and the AOT module. Extend the unary-math arm so
OP_ASIN/OP_ACOS/OP_ATAN call into helpers.asin/acos/atan, and add the
opcodes to the num-result tracker so downstream f64-typed reads pick
up the result without a redundant bitcast.
Sixteen cross-engine assertions exercise asin/acos/atan on identity
points (0, 1, +/-0.5, sqrt(3)), the three sin/cos/tan round-trips, and
asin's NaN-on-out-of-domain behaviour. The haversine example computes
London to Paris in one line of ilo using real asin, no Taylor stub,
and runs through tests/examples_engines.rs across every engine.
Add the three inverse trig builtins to the Math table in SPEC.md, the
compact builtin block in ai.txt, and the transcendental list plus
builtin grid in skills/ilo/SKILL.md. Wording calls out radian ranges
and the NaN-out-of-domain behaviour for asin and acos so agents don't
need to retry on the first try.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 16, 2026

Codecov Report

❌ Patch coverage is 88.52459% with 7 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/vm/compile_cranelift.rs 50.00% 4 Missing ⚠️
src/vm/mod.rs 88.46% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

@danieljohnmorris danieljohnmorris merged commit 916c8c3 into main May 16, 2026
4 of 5 checks passed
@danieljohnmorris danieljohnmorris deleted the feature/inverse-trig branch May 16, 2026 17:27
danieljohnmorris added a commit that referenced this pull request May 16, 2026
#313 (inverse-trig: OP_ASIN=171, OP_ACOS=172, OP_ATAN=173) and #314
(flat-cross-engine: OP_FLAT=171) both landed without rebasing one
onto the other, so the merged main has two opcodes sharing value 171.
Clippy's exhaustive-pattern checks catch it as "unreachable pattern"
because the inverse-trig match arm matches all relevant values before
OP_FLAT's arm gets a chance.

Renumbering OP_FLAT to 174 (the next free slot after OP_ATAN=173)
restores a unique mapping. No semantic change beyond the byte value,
and bytecode is in-process so there's no compatibility concern.
danieljohnmorris added a commit that referenced this pull request May 16, 2026
twelve fixes since 0.11.3, surfaced by rerun4 personas plus standing asks: srt-Cranelift TLS desync (#306), CLI auto-run restoration (#307), OP_LISTAPPEND O(n^2) JIT memory regression (#308), precedence-pair hint false-positive on parens (#309), prefix ?? accepts call expression (#310), += pure-shape docs (#311), bare-mutation silent no-op verifier warning ILO-T033 (#312), asin/acos/atan inverse trig builtins (#313), flat cross-engine (#314), cond{~v} discard hint multi-stmt false-positive (#315), rsrt fn xs key-fn overloads (#316), xs.(expr) paren-after-dot diagnostic hint (#317).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant