Skip to content

Fix ceil(), hypot() and $clog2() codegen - #22

Merged
Kreijstal merged 1 commit into
OpenVAF:mobfrom
sai-v-ch:fix-math-intrinsic-codegen
Jul 29, 2026
Merged

Fix ceil(), hypot() and $clog2() codegen#22
Kreijstal merged 1 commit into
OpenVAF:mobfrom
sai-v-ch:fix-math-intrinsic-codegen

Conversation

@sai-v-ch

Copy link
Copy Markdown
Collaborator

Three math builtins — ceil(), hypot() and $clog2() — cannot be used at all on mob. This is independent of the VAMS-2023 work in #19; I found it while checking that my own new builtins were declared correctly in CodegenCx::intrinsic, and it's the same root cause I flagged in #20, so I'd suggest taking this one first.

Each fails differently, but all three for one reason: no model in the suite calls them, so the code path is never executed in CI.

What happens today

Minimal model, ceil:

error: internal error: entered unreachable code: intrinsic llvm.ceil.f64 not found

Opcode::Ceil requests llvm.ceil.f64, but it was never declared alongside llvm.floor.f64, and Builder::intrinsic turns a missing declaration into unreachable!().

hypot:

Incorrect number of arguments passed to called function!
  %60 = call double @hypot(double %25, double %29)

It is declared double(double) while Opcode::Hypot passes two arguments, so the module fails the LLVM verifier.

$clog2 is broken twice over. The spec-conformant single-argument call is rejected up front:

error: invalid argument count: expected 2 arguments but found 1

because it is typed INT_MATH_2. Passing two arguments gets past the type checker and then silently discards the second, since hir_lower only reads args[0]. Fixing the signature then exposes the second problem:

Intrinsic name not mangled correctly for type arguments! Should be: llvm.ctlz.i32
ptr @llvm.ctlz

ctlz is overloaded, so it must carry the type suffix.

The fix

Four small changes: declare llvm.ceil.f64, give hypot its second parameter, retype $clog2 to one integer argument, and use the mangled llvm.ctlz.i32. INT_MATH_2 had no other user, so it becomes INT_MATH_1 rather than adding a second signature next to it.

Why a new integration model

I'd rather not fix these and leave the next one to be found by a user, so this adds MATH_INTRINSICS, which calls every builtin that Builder::intrinsic routes to an LLVM intrinsic or a libm symbol.

Two things it has to get right to be worth anything:

  • arguments derive from a branch voltage. With literal arguments, constant folding evaluates the call and codegen never sees it, so the model would pass whether or not the declarations are correct.
  • arguments stay in domainunit = x/sqrt(1+x²) for asin/acos/atanh, upper = 1+x² for ln/log/acosh — so the test fails on a real codegen problem rather than on a NaN.

Reverting any one of the four fixes makes it fail.

Testing

Against LLVM 18.1.8:

  • cargo test -p osdi — 27/27 (26 existing models plus the new one)
  • cargo test -p openvaf --test integration — 71/71, which compiles the new model to a shared library, dlopens it and processes its parameters
  • full workspace, RUN_SLOW_TESTS=1 RUN_DEV_TESTS=1 — green
  • cargo fmt --all -- --check — clean

Two pre-existing failures are unrelated and reproduce on a clean mob checkout: sourcegen osdi::gen_osdi_structs panics at sourcegen/src/osdi.rs:200, and the verilogae crate does not compile (CallBackKind::QueryPastState no longer exists). Neither is built by rust-ci.yml.

One note on scope: $clog2 now type-checks one argument, so any model that was passing two to work around the old signature would start erroring. That code was already getting its second argument ignored, so it cannot have been relied on for anything.

None of these three builtins could be used. Each failed differently and all
for the same underlying reason: no model in the test suite calls them, so the
LLVM path they take was never executed.

  ceil(x)     "intrinsic llvm.ceil.f64 not found" - the symbol is requested by
              Opcode::Ceil but was never declared in CodegenCx::intrinsic, and
              Builder::intrinsic turns a missing declaration into unreachable!()
  hypot(x,y)  declared as double(double) while Opcode::Hypot passes two
              arguments, so the module failed the LLVM verifier with
              "Incorrect number of arguments passed to called function"
  $clog2(x)   typed as INT_MATH_2, i.e. two integer arguments, so the spec
              conformant single argument call was rejected during type checking
              while a two argument call type checked and silently dropped the
              second (hir_lower only reads args[0]). Separately the overloaded
              ctlz intrinsic was declared unmangled as "llvm.ctlz", which the
              verifier rejects with "Intrinsic name not mangled correctly for
              type arguments! Should be: llvm.ctlz.i32"

INT_MATH_2 had no other user, so it becomes INT_MATH_1 rather than growing a
second signature.

Adds MATH_INTRINSICS, a model that calls every math builtin routed through
Builder::intrinsic. Arguments derive from a branch voltage so constant folding
cannot evaluate the calls away before they reach codegen, and are kept inside
each function's domain. Without the fixes above the model fails to compile;
with them the full osdi and openvaf integration suites pass.

Co-authored-by: Cursor <cursoragent@cursor.com>
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@Kreijstal
Kreijstal merged commit b4e317a into OpenVAF:mob Jul 29, 2026
@sai-v-ch
sai-v-ch deleted the fix-math-intrinsic-codegen branch August 1, 2026 03:47
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.

2 participants