Skip to content

Add rules for BigFloat integer constructors - #3461

Open
kshyatt wants to merge 2 commits into
mainfrom
ksh/bigfloat-int-constructors
Open

kshyatt wants to merge 2 commits into
mainfrom
ksh/bigfloat-int-constructors

Conversation

@kshyatt

@kshyatt kshyatt commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

BigFloat(x::Clong) and BigFloat(x::Culong) lower to mpfr_set_si / mpfr_set_ui, for which there is no derivative, so any active use of an integer-constructed BigFloat failed with EnzymeNoDerivativeError.

The argument is an integer, so the result never carries a derivative. This cannot be expressed as inactive, though: the result is a fresh mutable BigFloat that subsequent MPFR calls may write into, so it still needs a shadow to exist -- the same reason the existing no-argument BigFloat() rule allocates one. The shadow is built through the same constructor with a zero value, so it lands at the requested precision as a properly initialised MPFR value rather than raw zeroed memory.

Having a rule here also keeps Enzyme from compiling the body of these constructors at all, which matters beyond the missing derivative: eps(::Type{BigFloat}) is nextfloat(BigFloat(1)) - BigFloat(1), and compiling that construction inside an Enzyme-generated function miscompiles it into a BigFloat with an invalid d::Memory, segfaulting on first use. This repro segfaults before the change and passes after:

f(out) = (out[1] = eps(BigFloat) > 0 ? 1.0 : 0.0; nothing)
Enzyme.autodiff(Forward, Const(f), Const(zeros(3)))

Developed with 🤖

@github-actions

github-actions Bot commented Aug 15, 2026 •

Copy link
Copy Markdown
Contributor

Your PR requires formatting changes to meet the project's style guidelines.
Please consider running Runic (git runic main) to apply these changes.

Click here to view the suggested changes.
diff --git a/src/internal_rules/bigfloat.jl b/src/internal_rules/bigfloat.jl
index 1c2c93e0..c8454596 100644
--- a/src/internal_rules/bigfloat.jl
+++ b/src/internal_rules/bigfloat.jl
@@ -71,12 +71,12 @@ end
 # for which there is no derivative, so any active use of an integer-constructed
 # BigFloat otherwise fails with `EnzymeNoDerivativeError`.
 function EnzymeRules.forward(
-    config::EnzymeRules.FwdConfig,
-    Ty::Const{Type{BigFloat}},
-    RT::Type{<:Union{DuplicatedNoNeed,Duplicated,BatchDuplicated,BatchDuplicatedNoNeed}},
-    x::Const{Ti},
-    rs::Const{Base.MPFR.MPFRRoundingMode}...;
-    kwargs...,
+        config::EnzymeRules.FwdConfig,
+        Ty::Const{Type{BigFloat}},
+        RT::Type{<:Union{DuplicatedNoNeed, Duplicated, BatchDuplicated, BatchDuplicatedNoNeed}},
+        x::Const{Ti},
+        rs::Const{Base.MPFR.MPFRRoundingMode}...;
+        kwargs...,
     ) where {Ti <: Union{Clong, Culong}}
     rvals = map(r -> r.val, rs)
     if EnzymeRules.needs_primal(config) && EnzymeRules.needs_shadow(config)
@@ -109,12 +109,12 @@ function EnzymeRules.forward(
 end
 
 function EnzymeRules.augmented_primal(
-    config::EnzymeRules.RevConfig,
-    Ty::Const{Type{BigFloat}},
-    RT::Type{<:Union{DuplicatedNoNeed,Duplicated,BatchDuplicated,BatchDuplicatedNoNeed}},
-    x::Const{Ti},
-    rs::Const{Base.MPFR.MPFRRoundingMode}...;
-    kwargs...,
+        config::EnzymeRules.RevConfig,
+        Ty::Const{Type{BigFloat}},
+        RT::Type{<:Union{DuplicatedNoNeed, Duplicated, BatchDuplicated, BatchDuplicatedNoNeed}},
+        x::Const{Ti},
+        rs::Const{Base.MPFR.MPFRRoundingMode}...;
+        kwargs...,
     ) where {Ti <: Union{Clong, Culong}}
     rvals = map(r -> r.val, rs)
     primal = if EnzymeRules.needs_primal(config)
@@ -136,14 +136,14 @@ function EnzymeRules.augmented_primal(
 end
 
 function EnzymeRules.reverse(
-    config::EnzymeRules.RevConfig,
-    Ty::Const{Type{BigFloat}},
-    RT::Type{<:Union{DuplicatedNoNeed,Duplicated,BatchDuplicated,BatchDuplicatedNoNeed}},
-    tape,
-    x::Const{<:Union{Culong, Clong}},
-    rs::Const{Base.MPFR.MPFRRoundingMode}...;
-    kwargs...,
-)
+        config::EnzymeRules.RevConfig,
+        Ty::Const{Type{BigFloat}},
+        RT::Type{<:Union{DuplicatedNoNeed, Duplicated, BatchDuplicated, BatchDuplicatedNoNeed}},
+        tape,
+        x::Const{<:Union{Culong, Clong}},
+        rs::Const{Base.MPFR.MPFRRoundingMode}...;
+        kwargs...,
+    )
     # the integer argument carries no derivative
     return ntuple(Returns(nothing), Val(1 + length(rs)))
 end

@kshyatt
kshyatt force-pushed the ksh/bigfloat-int-constructors branch from 02d21ea to 921c454 Compare August 15, 2026 09:03
@github-actions

github-actions Bot commented Aug 15, 2026 •

Copy link
Copy Markdown
Contributor

Benchmark Results

main 2223e29... main / 2223e29...
basics/make_zero/namedtuple 0.0529 ± 0.0024 μs 0.0546 ± 0.0021 μs 0.969 ± 0.058
basics/make_zero/struct 0.259 ± 0.0067 μs 0.268 ± 0.0069 μs 0.963 ± 0.035
basics/overhead 5.26 ± 0.01 ns 4.33 ± 0.01 ns 1.21 ± 0.0036
basics/remake_zero!/namedtuple 0.224 ± 0.0052 μs 0.225 ± 0.0085 μs 0.992 ± 0.044
basics/remake_zero!/struct 0.225 ± 0.0062 μs 0.225 ± 0.0067 μs 1 ± 0.041
fold_broadcast/multidim_sum_bcast/1D 10.3 ± 1.8 μs 10.3 ± 0.46 μs 0.994 ± 0.18
fold_broadcast/multidim_sum_bcast/2D 10.2 ± 0.16 μs 10.2 ± 0.2 μs 1 ± 0.025
time_to_load 1.52 ± 0.015 s 1.52 ± 0.018 s 1 ± 0.015

Benchmark Plots

A plot of the benchmark results has been uploaded as an artifact at https://github.com/EnzymeAD/Enzyme.jl/actions/runs/36103894614/artifacts/10850443528.

@codecov

codecov Bot commented Aug 15, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 24.24242% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.44%. Comparing base (481fb6f) to head (c2ed260).

Files with missing lines Patch % Lines
src/internal_rules/bigfloat.jl 24.24% 25 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3461      +/-   ##
==========================================
+ Coverage   77.43%   78.44%   +1.01%     
==========================================
  Files          68       68              
  Lines       23278    22966     -312     
==========================================
- Hits        18025    18016       -9     
+ Misses       5253     4950     -303     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@kshyatt
kshyatt force-pushed the ksh/bigfloat-int-constructors branch from 921c454 to c2ed260 Compare August 16, 2026 18:31
Comment thread src/internal_rules/bigfloat.jl Outdated
rs::Const{Base.MPFR.MPFRRoundingMode}...;
kwargs...,
) where {Ti <: Union{Clong, Culong}}
rvals = map(r -> r.val, rs)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

closures are bad

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

closures are bad

Citation needed?

kshyatt and others added 2 commits September 25, 2026 08:40
`BigFloat(x::Clong)` and `BigFloat(x::Culong)` lower to `mpfr_set_si` /
`mpfr_set_ui`, for which there is no derivative, so any active use of an
integer-constructed BigFloat failed with `EnzymeNoDerivativeError`.

The argument is an integer, so the result never carries a derivative.
This cannot be expressed as `inactive`, though: the result is a fresh
mutable BigFloat that subsequent MPFR calls may write into, so it still
needs a shadow to exist -- the same reason the existing no-argument
`BigFloat()` rule allocates one. The shadow is built through the same
constructor with a zero value, so it lands at the requested precision as
a properly initialised MPFR value rather than raw zeroed memory.

Having a rule here also keeps Enzyme from compiling the body of these
constructors at all, which matters beyond the missing derivative:
`eps(::Type{BigFloat})` is `nextfloat(BigFloat(1)) - BigFloat(1)`, and
compiling that construction inside an Enzyme-generated function
miscompiles it into a BigFloat with an invalid `d::Memory`, segfaulting
on first use. This repro segfaults before the change and passes after:

    f(out) = (out[1] = eps(BigFloat) > 0 ? 1.0 : 0.0; nothing)
    Enzyme.autodiff(Forward, Const(f), Const(zeros(3)))

Note the reverse rules are untested: reverse-mode BigFloat scalar
arithmetic currently returns zero rather than the correct derivative,
independently of these constructors, so there is nothing meaningful to
assert yet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kshyatt
kshyatt force-pushed the ksh/bigfloat-int-constructors branch from b894ba5 to 2223e29 Compare September 25, 2026 06:40

This branch has not been deployed

No deployments
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.

3 participants