From 47115d91e3abc102eda6694cff01667efc9e642c Mon Sep 17 00:00:00 2001 From: Sai Date: Tue, 28 Jul 2026 23:39:52 -0700 Subject: [PATCH] fix: repair ceil, hypot and $clog2 codegen 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 --- .../MATH_INTRINSICS/math_intrinsics.va | 46 +++++++++++++++++++ openvaf/hir_ty/src/builtin.rs | 4 +- openvaf/mir_llvm/src/builder.rs | 3 +- openvaf/mir_llvm/src/intrinsics.rs | 5 +- openvaf/test_data/osdi/math_intrinsics.snap | 14 ++++++ 5 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 integration_tests/MATH_INTRINSICS/math_intrinsics.va create mode 100644 openvaf/test_data/osdi/math_intrinsics.snap diff --git a/integration_tests/MATH_INTRINSICS/math_intrinsics.va b/integration_tests/MATH_INTRINSICS/math_intrinsics.va new file mode 100644 index 00000000..a95b7771 --- /dev/null +++ b/integration_tests/MATH_INTRINSICS/math_intrinsics.va @@ -0,0 +1,46 @@ +`include "constants.vams" +`include "disciplines.vams" + +// Exercises every math builtin that `Builder::intrinsic` routes to an LLVM +// intrinsic or a libm symbol. Most of them were called by no other model in +// the suite, so a missing or mis-declared entry in `CodegenCx::intrinsic` +// stayed invisible until a user hit it: `ceil` aborted codegen outright and +// `hypot` emitted a module that failed the LLVM verifier. +// +// Every argument is derived from a branch voltage, otherwise constant folding +// would evaluate the call before it ever reaches codegen and the coverage +// would be lost. +module math_intrinsics(A, C); + inout A, C; + electrical A, C; + + branch (A, C) br; + + (*desc = "Output scale", units = "A/V"*) parameter real scale = 1.0 from (0:inf); + + real x, unit, upper, acc; + integer ix; + + analog begin + x = V(br); + + // |unit| < 1 keeps asin/acos/atanh inside their domain + unit = x / sqrt(1.0 + x * x); + // upper >= 1 keeps ln/log/acosh inside theirs + upper = 1.0 + x * x; + + acc = sqrt(upper) + exp(-upper) + ln(upper) + log(upper); + acc = acc + floor(x) + ceil(x); + acc = acc + sin(x) + cos(x) + tan(unit); + acc = acc + asin(unit) + acos(unit) + atan(x); + acc = acc + sinh(unit) + cosh(unit) + tanh(x); + acc = acc + asinh(x) + acosh(upper) + atanh(unit); + acc = acc + hypot(x, 1.0) + atan2(x, 1.0) + pow(upper, unit); + + // real -> integer cast and $clog2 also resolve through the intrinsic path + ix = upper * 8.0; + acc = acc + $clog2(ix); + + I(br) <+ scale * acc; + end +endmodule diff --git a/openvaf/hir_ty/src/builtin.rs b/openvaf/hir_ty/src/builtin.rs index 51f5abef..8eaa57e1 100644 --- a/openvaf/hir_ty/src/builtin.rs +++ b/openvaf/hir_ty/src/builtin.rs @@ -187,7 +187,7 @@ bultins! { const fn REAL_INFO() -> Real; const fn REAL_MATH_1(Val(Real)) -> Real; const fn REAL_MATH_2(Val(Real),Val(Real)) -> Real; - const fn INT_MATH_2(Val(Integer),Val(Integer)) -> Integer; + const fn INT_MATH_1(Val(Integer)) -> Integer; VT = const { @@ -431,7 +431,7 @@ copied_builtins! { FLOOR = REAL_MATH_1 LN = REAL_MATH_1 LOG = REAL_MATH_1 - CLOG2 = INT_MATH_2 + CLOG2 = INT_MATH_1 LOG10 = REAL_MATH_1 CEIL = REAL_MATH_1 LIMEXP = REAL_MATH_1 diff --git a/openvaf/mir_llvm/src/builder.rs b/openvaf/mir_llvm/src/builder.rs index 6d8d3de2..582bb13a 100644 --- a/openvaf/mir_llvm/src/builder.rs +++ b/openvaf/mir_llvm/src/builder.rs @@ -908,7 +908,8 @@ impl<'ll> Builder<'_, '_, 'll> { Opcode::Log => NonNull::from(self.intrinsic(args, "llvm.log10.f64")).as_ptr(), Opcode::Clog2 => { let leading_zeros = - NonNull::from(self.intrinsic(&[args[0], true.into()], "llvm.ctlz")).as_ptr(); + NonNull::from(self.intrinsic(&[args[0], true.into()], "llvm.ctlz.i32")) + .as_ptr(); let total_bits = NonNull::from(self.cx.const_int(32)).as_ptr(); llvm_sys::core::LLVMBuildSub(self.llbuilder, total_bits, leading_zeros, UNNAMED) } diff --git a/openvaf/mir_llvm/src/intrinsics.rs b/openvaf/mir_llvm/src/intrinsics.rs index f04fd44a..2da1cbb0 100644 --- a/openvaf/mir_llvm/src/intrinsics.rs +++ b/openvaf/mir_llvm/src/intrinsics.rs @@ -37,7 +37,8 @@ impl<'a, 'll> CodegenCx<'a, 'll> { ifn!("llvm.log10.f64", fn(t_f64) -> t_f64); ifn!("llvm.log2.f64", fn(t_f64) -> t_f64); ifn!("llvm.floor.f64", fn(t_f64) -> t_f64); - ifn!("llvm.ctlz", fn(t_i32, t_bool) -> t_i32); + ifn!("llvm.ceil.f64", fn(t_f64) -> t_f64); + ifn!("llvm.ctlz.i32", fn(t_i32, t_bool) -> t_i32); // not technically intrinsics but part of the C standard library // TODO link custom mathematical functions @@ -56,7 +57,7 @@ impl<'a, 'll> CodegenCx<'a, 'll> { if name == "hypot" { let name = if self.target.options.is_like_windows { "_hypot" } else { "hypot" }; - return Some(self.insert_intrinsic(name, &[t_f64], t_f64, false)); + return Some(self.insert_intrinsic(name, &[t_f64, t_f64], t_f64, false)); } ifn!("strcmp", fn(t_str, t_str) -> t_i32); diff --git a/openvaf/test_data/osdi/math_intrinsics.snap b/openvaf/test_data/osdi/math_intrinsics.snap new file mode 100644 index 00000000..671c538a --- /dev/null +++ b/openvaf/test_data/osdi/math_intrinsics.snap @@ -0,0 +1,14 @@ +param "$mfactor" +units = "", desc = "Multiplier (Verilog-A $mfactor)", flags = ParameterFlags(PARA_KIND_INST) +param "scale" +units = "A/V", desc = "Output scale", flags = ParameterFlags(0x0) + +2 terminals +node "A" units = "V", runits = "A" +node "C" units = "V", runits = "A" +jacobian (A, A) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (A, C) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (C, A) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (C, C) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT_CONST) +0 states +has bound_step false