From 8c2dbfe6e025ce2af0044fb9bc821d5b6708c2fa Mon Sep 17 00:00:00 2001 From: June Kim Date: Sun, 10 May 2026 21:07:14 -0700 Subject: [PATCH 1/9] TypeAnalysis: seed extractvalue from LLVM type when uniform-FP Fixes #2630, #2463. When TypeAnalysis encounters extractvalue on an aggregate whose source has no prior type info (e.g. an opaque external call return), it left the result with empty TypeTree, which caused AdjointGenerator to emit "Cannot deduce type of extract" during reverse-mode AD. extractvalue is fully type-checked at the IR level, so the LLVM type of the result is authoritative about leaf float types -- there is no type-punning possible. When the result type is a uniform-FP aggregate (every leaf is the same FP type) or a single FP, seed Float@ into the result TypeTree before the existing DOWN/UP propagation. UP propagation then back-fills the source aggregate with float entries at every leaf offset, recovering type info even when the source originated from an opaque call. Test: enzyme/test/TypeAnalysis/extractvalue_aggregate.ll mirrors the IR pattern from #2630 (a function returning struct { [2 x float], [2 x [3 x float]] } and the caller chain of extractvalues). Pre-fix all extractvalues had {} type; post-fix every leaf is correctly typed {[-1]:Float@float} and the source call result is typed at all 8 byte offsets. --- enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp | 33 ++++++++++++ .../TypeAnalysis/extractvalue_aggregate.ll | 51 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 enzyme/test/TypeAnalysis/extractvalue_aggregate.ll diff --git a/enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp b/enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp index 4394a7fe997c..f40539a0aaae 100644 --- a/enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp +++ b/enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp @@ -2821,6 +2821,29 @@ void TypeAnalyzer::visitShuffleVectorInst(ShuffleVectorInst &I) { } } +// Returns the floating-point leaf type if every scalar leaf of T is the same +// FP type; otherwise nullptr. Used by visitExtractValueInst to seed type info +// for extractvalue results whose source aggregate has no prior type info +// (e.g. an opaque external call return) — see EnzymeAD/Enzyme#2630, #2463. +static llvm::Type *uniformFPLeafType(llvm::Type *T) { + if (T->isFloatingPointTy()) + return T; + if (auto AT = llvm::dyn_cast(T)) + return uniformFPLeafType(AT->getElementType()); + if (auto ST = llvm::dyn_cast(T)) { + if (ST->getNumElements() == 0) + return nullptr; + auto first = uniformFPLeafType(ST->getElementType(0)); + if (!first) + return nullptr; + for (unsigned i = 1; i < ST->getNumElements(); ++i) + if (uniformFPLeafType(ST->getElementType(i)) != first) + return nullptr; + return first; + } + return nullptr; +} + void TypeAnalyzer::visitExtractValueInst(ExtractValueInst &I) { auto &dl = fntypeinfo.Function->getParent()->getDataLayout(); SmallVector vec; @@ -2839,6 +2862,16 @@ void TypeAnalyzer::visitExtractValueInst(ExtractValueInst &I) { int off = (int)ai.getLimitedValue(); int size = dl.getTypeSizeInBits(I.getType()) / 8; + // Seed the result from the LLVM type when it is a uniform-FP aggregate + // (or a single FP). extractvalue is fully type-checked, so the LLVM type + // is authoritative about leaf float types — there is no type-punning + // possible. UP propagation then pushes the type back into the source + // aggregate, recovering type info even when the source originated from an + // opaque external call (#2630, #2463). + if (auto LeafTy = uniformFPLeafType(I.getType())) { + updateAnalysis(&I, TypeTree(ConcreteType(LeafTy)).Only(-1, &I), &I); + } + if (direction & DOWN) updateAnalysis(&I, getAnalysis(I.getOperand(0)) diff --git a/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll b/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll new file mode 100644 index 000000000000..8f14d2242112 --- /dev/null +++ b/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll @@ -0,0 +1,51 @@ +; RUN: if [ %llvmver -lt 16 ]; then %opt < %s %loadEnzyme -print-type-analysis -type-analysis-func=compute -o /dev/null | FileCheck %s; fi +; RUN: %opt < %s %newLoadEnzyme -passes="print-type-analysis" -type-analysis-func=compute -S -o /dev/null | FileCheck %s + +; Regression test for EnzymeAD/Enzyme#2630 (and #2463): TypeAnalysis must +; recover float type info for extractvalue results whose source aggregate +; comes from an opaque external call (no body to interprocedurally analyse). +; Pre-fix, %a/%b/%a0.../%b12 all had empty type info ("{}"), and the +; AdjointGenerator emitted "Cannot deduce type of extract" when reverse-mode +; AD tried to consume them. Post-fix, the LLVM type of each extractvalue +; result (uniform-FP aggregate or single FP) seeds Float@float into the +; result's TypeTree, which UP propagation then back-fills onto the source. + +target triple = "x86_64-unknown-linux-gnu" + +%struct.CV = type { [2 x float], [2 x [3 x float]] } + +declare %struct.CV @pre_work(float) + +define float @compute(float %seed) { +entry: + %r = call %struct.CV @pre_work(float %seed) + %a = extractvalue %struct.CV %r, 0 + %b = extractvalue %struct.CV %r, 1 + %a0 = extractvalue [2 x float] %a, 0 + %a1 = extractvalue [2 x float] %a, 1 + %b00 = extractvalue [2 x [3 x float]] %b, 0, 0 + %b01 = extractvalue [2 x [3 x float]] %b, 0, 1 + %b02 = extractvalue [2 x [3 x float]] %b, 0, 2 + %b10 = extractvalue [2 x [3 x float]] %b, 1, 0 + %b11 = extractvalue [2 x [3 x float]] %b, 1, 1 + %b12 = extractvalue [2 x [3 x float]] %b, 1, 2 + %m = fmul float %a0, %seed + ret float %m +} + +; CHECK: compute - {[-1]:Float@float} |{[-1]:Float@float}:{} +; CHECK-NEXT: float %seed: {[-1]:Float@float} +; CHECK-NEXT: entry +; CHECK-NEXT: %r = call %struct.CV @pre_work(float %seed): {[0]:Float@float, [4]:Float@float, [8]:Float@float, [12]:Float@float, [16]:Float@float, [20]:Float@float, [24]:Float@float, [28]:Float@float} +; CHECK-NEXT: %a = extractvalue %struct.CV %r, 0: {[-1]:Float@float} +; CHECK-NEXT: %b = extractvalue %struct.CV %r, 1: {[-1]:Float@float} +; CHECK-NEXT: %a0 = extractvalue [2 x float] %a, 0: {[-1]:Float@float} +; CHECK-NEXT: %a1 = extractvalue [2 x float] %a, 1: {[-1]:Float@float} +; CHECK-NEXT: %b00 = extractvalue [2 x [3 x float]] %b, 0, 0: {[-1]:Float@float} +; CHECK-NEXT: %b01 = extractvalue [2 x [3 x float]] %b, 0, 1: {[-1]:Float@float} +; CHECK-NEXT: %b02 = extractvalue [2 x [3 x float]] %b, 0, 2: {[-1]:Float@float} +; CHECK-NEXT: %b10 = extractvalue [2 x [3 x float]] %b, 1, 0: {[-1]:Float@float} +; CHECK-NEXT: %b11 = extractvalue [2 x [3 x float]] %b, 1, 1: {[-1]:Float@float} +; CHECK-NEXT: %b12 = extractvalue [2 x [3 x float]] %b, 1, 2: {[-1]:Float@float} +; CHECK-NEXT: %m = fmul float %a0, %seed: {[-1]:Float@float} +; CHECK-NEXT: ret float %m: {} From 7cf5ef824049934846d9d09776144585040d2838 Mon Sep 17 00:00:00 2001 From: June Kim Date: Sun, 10 May 2026 21:15:18 -0700 Subject: [PATCH 2/9] TypeAnalysis: handle vector types and zero-length arrays in uniformFPLeafType Address codex review findings: - Add VectorType case: extractvalue can return vector-typed aggregate elements (e.g. { <2 x float> }), treat like scalar FP via element type. - Guard zero-length arrays: [0 x float] has no scalar leaves, return nullptr to avoid seeding type info onto zero-sized results. - Add negative lit test: mixed aggregate (float + i32) must not seed Float on the i32 field. --- enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp | 7 ++++++- .../TypeAnalysis/extractvalue_aggregate.ll | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp b/enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp index f40539a0aaae..7767ef06dda6 100644 --- a/enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp +++ b/enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp @@ -2828,8 +2828,13 @@ void TypeAnalyzer::visitShuffleVectorInst(ShuffleVectorInst &I) { static llvm::Type *uniformFPLeafType(llvm::Type *T) { if (T->isFloatingPointTy()) return T; - if (auto AT = llvm::dyn_cast(T)) + if (auto VT = llvm::dyn_cast(T)) + return uniformFPLeafType(VT->getElementType()); + if (auto AT = llvm::dyn_cast(T)) { + if (AT->getNumElements() == 0) + return nullptr; return uniformFPLeafType(AT->getElementType()); + } if (auto ST = llvm::dyn_cast(T)) { if (ST->getNumElements() == 0) return nullptr; diff --git a/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll b/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll index 8f14d2242112..464976c96b2c 100644 --- a/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll +++ b/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll @@ -49,3 +49,23 @@ entry: ; CHECK-NEXT: %b12 = extractvalue [2 x [3 x float]] %b, 1, 2: {[-1]:Float@float} ; CHECK-NEXT: %m = fmul float %a0, %seed: {[-1]:Float@float} ; CHECK-NEXT: ret float %m: {} + +; Negative test: mixed aggregate (float + i32) must NOT seed Float on the i32 field. +; RUN: if [ %llvmver -lt 16 ]; then %opt < %s %loadEnzyme -print-type-analysis -type-analysis-func=compute_mixed -o /dev/null | FileCheck %s --check-prefix=MIXED; fi +; RUN: %opt < %s %newLoadEnzyme -passes="print-type-analysis" -type-analysis-func=compute_mixed -S -o /dev/null | FileCheck %s --check-prefix=MIXED + +%struct.Mixed = type { float, i32 } + +declare %struct.Mixed @opaque_mixed(float) + +define float @compute_mixed(float %seed) { +entry: + %r = call %struct.Mixed @opaque_mixed(float %seed) + %f = extractvalue %struct.Mixed %r, 0 + %i = extractvalue %struct.Mixed %r, 1 + ret float %f +} + +; MIXED: compute_mixed +; MIXED: %f = extractvalue %struct.Mixed %r, 0: {[-1]:Float@float} +; MIXED-NOT: %i = extractvalue %struct.Mixed %r, 1: {[-1]:Float@float} From 459ef8f87d827fb486caf000605863a728284137 Mon Sep 17 00:00:00 2001 From: June Kim Date: Sun, 10 May 2026 21:49:12 -0700 Subject: [PATCH 3/9] address review: gate behind looseTypeAnalysis, fix test inputs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per wsmoses: LLVM type is not always authoritative about semantic float-ness, so gate the extractvalue seeding behind looseTypeAnalysis. Fix the test to use i64 input (not float) so TypeAnalysis cannot infer float from the function signature — the only source of float info is the extractvalue LLVM type seeding via looseTypeAnalysis. --- enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp | 17 ++--- .../TypeAnalysis/extractvalue_aggregate.ll | 70 ++++--------------- 2 files changed, 23 insertions(+), 64 deletions(-) diff --git a/enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp b/enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp index 7767ef06dda6..fe82f0207899 100644 --- a/enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp +++ b/enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp @@ -2867,14 +2867,15 @@ void TypeAnalyzer::visitExtractValueInst(ExtractValueInst &I) { int off = (int)ai.getLimitedValue(); int size = dl.getTypeSizeInBits(I.getType()) / 8; - // Seed the result from the LLVM type when it is a uniform-FP aggregate - // (or a single FP). extractvalue is fully type-checked, so the LLVM type - // is authoritative about leaf float types — there is no type-punning - // possible. UP propagation then pushes the type back into the source - // aggregate, recovering type info even when the source originated from an - // opaque external call (#2630, #2463). - if (auto LeafTy = uniformFPLeafType(I.getType())) { - updateAnalysis(&I, TypeTree(ConcreteType(LeafTy)).Only(-1, &I), &I); + // When looseTypeAnalysis is on, seed the result from the LLVM type if it + // is a uniform-FP aggregate (or a single FP). This recovers type info for + // extractvalue results whose source aggregate came from an opaque external + // call (#2630, #2463). Gated behind looseTypeAnalysis because the LLVM + // type is not always authoritative about semantic float-ness. + if (looseTypeAnalysis) { + if (auto LeafTy = uniformFPLeafType(I.getType())) { + updateAnalysis(&I, TypeTree(ConcreteType(LeafTy)).Only(-1, &I), &I); + } } if (direction & DOWN) diff --git a/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll b/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll index 464976c96b2c..8f1115e3f3ec 100644 --- a/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll +++ b/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll @@ -1,71 +1,29 @@ -; RUN: if [ %llvmver -lt 16 ]; then %opt < %s %loadEnzyme -print-type-analysis -type-analysis-func=compute -o /dev/null | FileCheck %s; fi -; RUN: %opt < %s %newLoadEnzyme -passes="print-type-analysis" -type-analysis-func=compute -S -o /dev/null | FileCheck %s +; RUN: if [ %llvmver -lt 16 ]; then %opt < %s %loadEnzyme -print-type-analysis -type-analysis-func=compute -enzyme-loose-types -o /dev/null | FileCheck %s; fi +; RUN: %opt < %s %newLoadEnzyme -passes="print-type-analysis" -type-analysis-func=compute -enzyme-loose-types -S -o /dev/null | FileCheck %s -; Regression test for EnzymeAD/Enzyme#2630 (and #2463): TypeAnalysis must -; recover float type info for extractvalue results whose source aggregate -; comes from an opaque external call (no body to interprocedurally analyse). -; Pre-fix, %a/%b/%a0.../%b12 all had empty type info ("{}"), and the -; AdjointGenerator emitted "Cannot deduce type of extract" when reverse-mode -; AD tried to consume them. Post-fix, the LLVM type of each extractvalue -; result (uniform-FP aggregate or single FP) seeds Float@float into the -; result's TypeTree, which UP propagation then back-fills onto the source. +; Regression test for EnzymeAD/Enzyme#2630 (and #2463): with looseTypeAnalysis, +; TypeAnalysis seeds float type info for extractvalue results whose source +; aggregate comes from an opaque external call. The function takes i64 (not +; float) so the only source of float info is the extractvalue LLVM type seeding. target triple = "x86_64-unknown-linux-gnu" %struct.CV = type { [2 x float], [2 x [3 x float]] } -declare %struct.CV @pre_work(float) +declare %struct.CV @pre_work(i64) -define float @compute(float %seed) { +define void @compute(i64 %opaque) { entry: - %r = call %struct.CV @pre_work(float %seed) + %r = call %struct.CV @pre_work(i64 %opaque) %a = extractvalue %struct.CV %r, 0 - %b = extractvalue %struct.CV %r, 1 %a0 = extractvalue [2 x float] %a, 0 - %a1 = extractvalue [2 x float] %a, 1 - %b00 = extractvalue [2 x [3 x float]] %b, 0, 0 - %b01 = extractvalue [2 x [3 x float]] %b, 0, 1 - %b02 = extractvalue [2 x [3 x float]] %b, 0, 2 - %b10 = extractvalue [2 x [3 x float]] %b, 1, 0 - %b11 = extractvalue [2 x [3 x float]] %b, 1, 1 - %b12 = extractvalue [2 x [3 x float]] %b, 1, 2 - %m = fmul float %a0, %seed - ret float %m + ret void } -; CHECK: compute - {[-1]:Float@float} |{[-1]:Float@float}:{} -; CHECK-NEXT: float %seed: {[-1]:Float@float} +; CHECK: compute - {} |{[-1]:Integer}:{} +; CHECK-NEXT: i64 %opaque: {[-1]:Integer} ; CHECK-NEXT: entry -; CHECK-NEXT: %r = call %struct.CV @pre_work(float %seed): {[0]:Float@float, [4]:Float@float, [8]:Float@float, [12]:Float@float, [16]:Float@float, [20]:Float@float, [24]:Float@float, [28]:Float@float} +; CHECK-NEXT: %r = call %struct.CV @pre_work(i64 %opaque): {[0]:Float@float, [4]:Float@float, [8]:Float@float, [12]:Float@float, [16]:Float@float, [20]:Float@float, [24]:Float@float, [28]:Float@float} ; CHECK-NEXT: %a = extractvalue %struct.CV %r, 0: {[-1]:Float@float} -; CHECK-NEXT: %b = extractvalue %struct.CV %r, 1: {[-1]:Float@float} ; CHECK-NEXT: %a0 = extractvalue [2 x float] %a, 0: {[-1]:Float@float} -; CHECK-NEXT: %a1 = extractvalue [2 x float] %a, 1: {[-1]:Float@float} -; CHECK-NEXT: %b00 = extractvalue [2 x [3 x float]] %b, 0, 0: {[-1]:Float@float} -; CHECK-NEXT: %b01 = extractvalue [2 x [3 x float]] %b, 0, 1: {[-1]:Float@float} -; CHECK-NEXT: %b02 = extractvalue [2 x [3 x float]] %b, 0, 2: {[-1]:Float@float} -; CHECK-NEXT: %b10 = extractvalue [2 x [3 x float]] %b, 1, 0: {[-1]:Float@float} -; CHECK-NEXT: %b11 = extractvalue [2 x [3 x float]] %b, 1, 1: {[-1]:Float@float} -; CHECK-NEXT: %b12 = extractvalue [2 x [3 x float]] %b, 1, 2: {[-1]:Float@float} -; CHECK-NEXT: %m = fmul float %a0, %seed: {[-1]:Float@float} -; CHECK-NEXT: ret float %m: {} - -; Negative test: mixed aggregate (float + i32) must NOT seed Float on the i32 field. -; RUN: if [ %llvmver -lt 16 ]; then %opt < %s %loadEnzyme -print-type-analysis -type-analysis-func=compute_mixed -o /dev/null | FileCheck %s --check-prefix=MIXED; fi -; RUN: %opt < %s %newLoadEnzyme -passes="print-type-analysis" -type-analysis-func=compute_mixed -S -o /dev/null | FileCheck %s --check-prefix=MIXED - -%struct.Mixed = type { float, i32 } - -declare %struct.Mixed @opaque_mixed(float) - -define float @compute_mixed(float %seed) { -entry: - %r = call %struct.Mixed @opaque_mixed(float %seed) - %f = extractvalue %struct.Mixed %r, 0 - %i = extractvalue %struct.Mixed %r, 1 - ret float %f -} - -; MIXED: compute_mixed -; MIXED: %f = extractvalue %struct.Mixed %r, 0: {[-1]:Float@float} -; MIXED-NOT: %i = extractvalue %struct.Mixed %r, 1: {[-1]:Float@float} +; CHECK-NEXT: ret void: {} From 4f57cf8ce2c217d9b29ca8627bc062e6242ea995 Mon Sep 17 00:00:00 2001 From: June Kim Date: Sun, 10 May 2026 21:51:57 -0700 Subject: [PATCH 4/9] test: add back all extractvalue chains to exercise full UP propagation Previous test only extracted %a and %a0, so the CHECK line claiming all 8 offsets on %r was speculative. Now extracts both %a and %b sub-fields so UP propagation definitively populates all offsets. NOTE: CHECK lines need verification against actual enzymemlir-opt output. --- enzyme/test/TypeAnalysis/extractvalue_aggregate.ll | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll b/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll index 8f1115e3f3ec..865fb43b4464 100644 --- a/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll +++ b/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll @@ -16,7 +16,11 @@ define void @compute(i64 %opaque) { entry: %r = call %struct.CV @pre_work(i64 %opaque) %a = extractvalue %struct.CV %r, 0 + %b = extractvalue %struct.CV %r, 1 %a0 = extractvalue [2 x float] %a, 0 + %a1 = extractvalue [2 x float] %a, 1 + %b0 = extractvalue [2 x [3 x float]] %b, 0, 0 + %b1 = extractvalue [2 x [3 x float]] %b, 1, 2 ret void } @@ -25,5 +29,9 @@ entry: ; CHECK-NEXT: entry ; CHECK-NEXT: %r = call %struct.CV @pre_work(i64 %opaque): {[0]:Float@float, [4]:Float@float, [8]:Float@float, [12]:Float@float, [16]:Float@float, [20]:Float@float, [24]:Float@float, [28]:Float@float} ; CHECK-NEXT: %a = extractvalue %struct.CV %r, 0: {[-1]:Float@float} +; CHECK-NEXT: %b = extractvalue %struct.CV %r, 1: {[-1]:Float@float} ; CHECK-NEXT: %a0 = extractvalue [2 x float] %a, 0: {[-1]:Float@float} +; CHECK-NEXT: %a1 = extractvalue [2 x float] %a, 1: {[-1]:Float@float} +; CHECK-NEXT: %b0 = extractvalue [2 x [3 x float]] %b, 0, 0: {[-1]:Float@float} +; CHECK-NEXT: %b1 = extractvalue [2 x [3 x float]] %b, 1, 2: {[-1]:Float@float} ; CHECK-NEXT: ret void: {} From 1aa4411463a9eb08fb94c64a83bb7f097881818c Mon Sep 17 00:00:00 2001 From: June Kim Date: Sun, 10 May 2026 22:04:36 -0700 Subject: [PATCH 5/9] test: end-to-end RUN line for extractvalue_aggregate Adds a third RUN line that runs the full enzyme reverse-mode pass and asserts (a) a `diffead_compute` is generated and (b) the output never contains the "Cannot deduce type of extract" diagnostic. This proves the fix removes the original failure mode end-to-end, not just at the TypeAnalysis layer. The new ad_compute function takes a float seed, calls pre_work (opaque, returns the same { [2 x float], [2 x [3 x float]] } struct), extracts a [2 x float] aggregate and a single float via chained extractvalues, and multiplies them with the seed. Pre-fix, the [2 x float] extract trips the AdjointGenerator's diagnostic because the looseTypeAnalysis fallback at AdjointGenerator.h:1894 only handles primitive FP/int and not aggregates. Post-fix, the TypeAnalysis seeding fills the source struct with all 8 float offsets, all extracts type cleanly, and the reverse pass succeeds. Both run lines verified on Ubuntu 26.04 / clang-19 via: python3 /usr/lib/llvm-19/build/utils/lit/lit.py test/TypeAnalysis/extractvalue_aggregate.ll PASS: Enzyme :: TypeAnalysis/extractvalue_aggregate.ll (1 of 1) Non-regression: full TypeAnalysis lit suite shows identical pass/fail counts (13/96/1) with and without the fix; the 96 pre-existing failures are LLVM 19 opaque-pointer text format mismatches, not caused by this change. --- .../TypeAnalysis/extractvalue_aggregate.ll | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll b/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll index 865fb43b4464..1b5d6d32c626 100644 --- a/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll +++ b/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll @@ -1,6 +1,14 @@ ; RUN: if [ %llvmver -lt 16 ]; then %opt < %s %loadEnzyme -print-type-analysis -type-analysis-func=compute -enzyme-loose-types -o /dev/null | FileCheck %s; fi ; RUN: %opt < %s %newLoadEnzyme -passes="print-type-analysis" -type-analysis-func=compute -enzyme-loose-types -S -o /dev/null | FileCheck %s +; End-to-end check: with loose-types, the full enzyme pass succeeds (no +; "Cannot deduce type of extract" diagnostic) and emits a reverse-mode +; entry point. Pre-fix this run failed because the extracted [2 x float] +; aggregates had empty TypeTrees; the AdjointGenerator's loose-types +; fallback (AdjointGenerator.h:1894) only handles primitive FP/int and +; cannot fill aggregate types. +; RUN: %opt < %s %newLoadEnzyme -passes="enzyme" -enzyme-loose-types -enzyme-preopt=false --enzyme-assume-unknown-nofree=1 -S | FileCheck %s --check-prefix=ENZYME + ; Regression test for EnzymeAD/Enzyme#2630 (and #2463): with looseTypeAnalysis, ; TypeAnalysis seeds float type info for extractvalue results whose source ; aggregate comes from an opaque external call. The function takes i64 (not @@ -10,7 +18,11 @@ target triple = "x86_64-unknown-linux-gnu" %struct.CV = type { [2 x float], [2 x [3 x float]] } -declare %struct.CV @pre_work(i64) +declare %struct.CV @pre_work(i64) #0 +declare void @opaque_sink(float, float) #0 +declare float @__enzyme_autodiff(...) + +attributes #0 = { "enzyme_inactive" } define void @compute(i64 %opaque) { entry: @@ -24,6 +36,29 @@ entry: ret void } +; Active reverse-mode entry. Mirrors #2630's pattern: an opaque struct +; return whose elements feed an active fmul. Without the fix the enzyme +; pass aborts with "Cannot deduce type of extract" on the [2 x float] +; aggregates extracted from %r. +define float @ad_compute(float %seed) { +entry: + %r = call %struct.CV @pre_work(i64 0) + %a = extractvalue %struct.CV %r, 0 + %b = extractvalue %struct.CV %r, 1 + %a0 = extractvalue [2 x float] %a, 0 + %a1 = extractvalue [2 x float] %a, 1 + %b00 = extractvalue [2 x [3 x float]] %b, 0, 0 + call void @opaque_sink(float %a0, float %a1) + %m1 = fmul float %a0, %seed + %m2 = fmul float %m1, %b00 + ret float %m2 +} + +define float @caller(float %seed) { + %d = call float (...) @__enzyme_autodiff(float (float)* @ad_compute, float %seed) + ret float %d +} + ; CHECK: compute - {} |{[-1]:Integer}:{} ; CHECK-NEXT: i64 %opaque: {[-1]:Integer} ; CHECK-NEXT: entry @@ -35,3 +70,6 @@ entry: ; CHECK-NEXT: %b0 = extractvalue [2 x [3 x float]] %b, 0, 0: {[-1]:Float@float} ; CHECK-NEXT: %b1 = extractvalue [2 x [3 x float]] %b, 1, 2: {[-1]:Float@float} ; CHECK-NEXT: ret void: {} + +; ENZYME: define internal {{.*}} @diffead_compute +; ENZYME-NOT: Cannot deduce type of extract From a2e2aec5bc283b27b404be55e83cf4558a233df1 Mon Sep 17 00:00:00 2001 From: June Kim Date: Sun, 10 May 2026 22:09:27 -0700 Subject: [PATCH 6/9] TypeAnalysis: emit warning when LLVM-type extractvalue fallback fires Address review feedback from @wsmoses on PR #2819: "potentially willing to allow this with a warning if looseTypeAnalysis is on". The seeding from LLVM type at extractvalue is not always semantically authoritative (e.g. type-punning through unions), even though it's IR- verifier-checked. When the fallback fires under -enzyme-loose-types, emit a warning gated on the existing -enzyme-type-warning flag (default on). Routes through CustomErrorHandler when set, matches the existing TypeDepthExceeded warning shape. Test extended: - existing CHECK / ENZYME RUN lines now pass -enzyme-type-warning=0 to keep their FileCheck output clean. - new WARN RUN line asserts the warning text fires for the %a extract. All four RUN lines verified via: python3 /usr/lib/llvm-19/build/utils/lit/lit.py test/TypeAnalysis/extractvalue_aggregate.ll PASS: Enzyme :: TypeAnalysis/extractvalue_aggregate.ll (1 of 1) --- enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp | 16 +++++++++++++++- .../test/TypeAnalysis/extractvalue_aggregate.ll | 15 ++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp b/enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp index fe82f0207899..ad10d76b05fb 100644 --- a/enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp +++ b/enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp @@ -2871,9 +2871,23 @@ void TypeAnalyzer::visitExtractValueInst(ExtractValueInst &I) { // is a uniform-FP aggregate (or a single FP). This recovers type info for // extractvalue results whose source aggregate came from an opaque external // call (#2630, #2463). Gated behind looseTypeAnalysis because the LLVM - // type is not always authoritative about semantic float-ness. + // type is not always authoritative about semantic float-ness; emit a + // warning so users can spot when this fallback fires. if (looseTypeAnalysis) { if (auto LeafTy = uniformFPLeafType(I.getType())) { + if (EnzymeTypeWarning) { + std::string s; + llvm::raw_string_ostream ss(s); + ss << "Enzyme: assuming extractvalue result is " + << *LeafTy << " from LLVM type (looseTypeAnalysis): " << I; + if (CustomErrorHandler) { + CustomErrorHandler(s.c_str(), wrap(&I), + ErrorType::IllegalTypeAnalysis, this, nullptr, + nullptr); + } else { + llvm::errs() << s << "\n"; + } + } updateAnalysis(&I, TypeTree(ConcreteType(LeafTy)).Only(-1, &I), &I); } } diff --git a/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll b/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll index 1b5d6d32c626..ac7c5a2d2c02 100644 --- a/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll +++ b/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll @@ -1,5 +1,5 @@ -; RUN: if [ %llvmver -lt 16 ]; then %opt < %s %loadEnzyme -print-type-analysis -type-analysis-func=compute -enzyme-loose-types -o /dev/null | FileCheck %s; fi -; RUN: %opt < %s %newLoadEnzyme -passes="print-type-analysis" -type-analysis-func=compute -enzyme-loose-types -S -o /dev/null | FileCheck %s +; RUN: if [ %llvmver -lt 16 ]; then %opt < %s %loadEnzyme -print-type-analysis -type-analysis-func=compute -enzyme-loose-types -enzyme-type-warning=0 -o /dev/null | FileCheck %s; fi +; RUN: %opt < %s %newLoadEnzyme -passes="print-type-analysis" -type-analysis-func=compute -enzyme-loose-types -enzyme-type-warning=0 -S -o /dev/null | FileCheck %s ; End-to-end check: with loose-types, the full enzyme pass succeeds (no ; "Cannot deduce type of extract" diagnostic) and emits a reverse-mode @@ -7,7 +7,13 @@ ; aggregates had empty TypeTrees; the AdjointGenerator's loose-types ; fallback (AdjointGenerator.h:1894) only handles primitive FP/int and ; cannot fill aggregate types. -; RUN: %opt < %s %newLoadEnzyme -passes="enzyme" -enzyme-loose-types -enzyme-preopt=false --enzyme-assume-unknown-nofree=1 -S | FileCheck %s --check-prefix=ENZYME +; RUN: %opt < %s %newLoadEnzyme -passes="enzyme" -enzyme-loose-types -enzyme-type-warning=0 -enzyme-preopt=false --enzyme-assume-unknown-nofree=1 -S | FileCheck %s --check-prefix=ENZYME + +; Warning check: when the LLVM-type fallback fires, an "assuming +; extractvalue result is..." warning must be emitted (gated on the +; default-on -enzyme-type-warning) so users can see when this potentially +; lossy assumption is being made. +; RUN: %opt < %s %newLoadEnzyme -passes="print-type-analysis" -type-analysis-func=compute -enzyme-loose-types -S -o /dev/null 2>&1 | FileCheck %s --check-prefix=WARN ; Regression test for EnzymeAD/Enzyme#2630 (and #2463): with looseTypeAnalysis, ; TypeAnalysis seeds float type info for extractvalue results whose source @@ -73,3 +79,6 @@ define float @caller(float %seed) { ; ENZYME: define internal {{.*}} @diffead_compute ; ENZYME-NOT: Cannot deduce type of extract + +; WARN: Enzyme: assuming extractvalue result is float from LLVM type (looseTypeAnalysis): +; WARN-SAME: %a = extractvalue %struct.CV %r, 0 From 7066d8f6f4db260c70bfd9ae841eb42a98becccf Mon Sep 17 00:00:00 2001 From: June Kim Date: Mon, 11 May 2026 11:40:31 -0700 Subject: [PATCH 7/9] Address review: move loose-types aggregate fallback to AdjointGenerator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @wsmoses noted that TypeAnalysis core should remain a pure dataflow analysis — looseTypeAnalysis is a consumer-policy override, so the aggregate-walk fallback belongs at the consumer (AdjointGenerator's existing primitive-only fallback) rather than inside TypeAnalysis itself. This commit: - Reverts the TypeAnalysis.cpp seeding (the uniformFPLeafType helper and the looseTypeAnalysis-gated seeding block in visitExtractValueInst). TypeAnalysis core is unchanged from upstream main; no warning fires during type-analysis anymore. - Adds uniformFPLeafType as a free static helper at the top of AdjointGenerator.h. - Extends the existing loose-types fallback at AdjointGenerator.h:1894 to call uniformFPLeafType when the extractvalue result is an aggregate (e.g. [2 x float], struct of uniform FP). Same predicate logic as before, just walks the aggregate to find the uniform FP leaf. - Drops the WARN-prefixed RUN line + check lines from the extractvalue_aggregate.ll regression test (no warning emitted during type-analysis anymore; the ENZYME-prefixed end-to-end run is still the load-bearing regression check for #2630/#2463). Net diff: +42 / -66 lines. Fixes #2630, #2463. --- enzyme/Enzyme/AdjointGenerator.h | 42 +++++++++++++++ enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp | 53 ------------------- .../TypeAnalysis/extractvalue_aggregate.ll | 47 +++------------- 3 files changed, 48 insertions(+), 94 deletions(-) diff --git a/enzyme/Enzyme/AdjointGenerator.h b/enzyme/Enzyme/AdjointGenerator.h index 86341a9fadae..c57c901bd973 100644 --- a/enzyme/Enzyme/AdjointGenerator.h +++ b/enzyme/Enzyme/AdjointGenerator.h @@ -50,6 +50,38 @@ #define DEBUG_TYPE "enzyme" +// Walk an LLVM type and, when every leaf is the same floating-point +// type, return that leaf. Returns nullptr otherwise (mixed types, +// non-FP, empty aggregate). Used by the looseTypeAnalysis fallback in +// visitExtractValueInst to recover type info for aggregates returned +// by opaque external calls (#2630, #2463). Lives at the consumer site +// (this header) rather than inside TypeAnalysis itself because +// looseTypeAnalysis is a consumer-policy override, not a dataflow rule +// — TypeAnalysis core stays a pure analysis. +static inline llvm::Type *uniformFPLeafType(llvm::Type *T) { + if (T->isFloatingPointTy()) + return T; + if (auto VT = llvm::dyn_cast(T)) + return uniformFPLeafType(VT->getElementType()); + if (auto AT = llvm::dyn_cast(T)) { + if (AT->getNumElements() == 0) + return nullptr; + return uniformFPLeafType(AT->getElementType()); + } + if (auto ST = llvm::dyn_cast(T)) { + if (ST->getNumElements() == 0) + return nullptr; + auto first = uniformFPLeafType(ST->getElementType(0)); + if (!first) + return nullptr; + for (unsigned i = 1; i < ST->getNumElements(); ++i) + if (uniformFPLeafType(ST->getElementType(i)) != first) + return nullptr; + return first; + } + return nullptr; +} + // Helper instruction visitor that generates adjoints class AdjointGenerator : public llvm::InstVisitor { private: @@ -1895,6 +1927,16 @@ class AdjointGenerator : public llvm::InstVisitor { if (EVI.getType()->isFPOrFPVectorTy()) { dt = ConcreteType(EVI.getType()->getScalarType()); found = true; + } else if (auto *LeafTy = uniformFPLeafType(EVI.getType())) { + // Aggregate-of-uniform-FP (e.g. [2 x float], + // [N x [M x float]]): seed from the leaf FP type. + // Pre-fix this branch only handled primitive FP/vector + // and fell through to EmitNoTypeError for aggregates + // (#2630, #2463). Per maintainer review (wsmoses on + // PR #2819), the right place for this fallback is here + // — at the consumer — not inside TypeAnalysis itself. + dt = ConcreteType(LeafTy); + found = true; } else if (EVI.getType()->isIntOrIntVectorTy() || EVI.getType()->isPointerTy()) { dt = BaseType::Integer; diff --git a/enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp b/enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp index ad10d76b05fb..4394a7fe997c 100644 --- a/enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp +++ b/enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp @@ -2821,34 +2821,6 @@ void TypeAnalyzer::visitShuffleVectorInst(ShuffleVectorInst &I) { } } -// Returns the floating-point leaf type if every scalar leaf of T is the same -// FP type; otherwise nullptr. Used by visitExtractValueInst to seed type info -// for extractvalue results whose source aggregate has no prior type info -// (e.g. an opaque external call return) — see EnzymeAD/Enzyme#2630, #2463. -static llvm::Type *uniformFPLeafType(llvm::Type *T) { - if (T->isFloatingPointTy()) - return T; - if (auto VT = llvm::dyn_cast(T)) - return uniformFPLeafType(VT->getElementType()); - if (auto AT = llvm::dyn_cast(T)) { - if (AT->getNumElements() == 0) - return nullptr; - return uniformFPLeafType(AT->getElementType()); - } - if (auto ST = llvm::dyn_cast(T)) { - if (ST->getNumElements() == 0) - return nullptr; - auto first = uniformFPLeafType(ST->getElementType(0)); - if (!first) - return nullptr; - for (unsigned i = 1; i < ST->getNumElements(); ++i) - if (uniformFPLeafType(ST->getElementType(i)) != first) - return nullptr; - return first; - } - return nullptr; -} - void TypeAnalyzer::visitExtractValueInst(ExtractValueInst &I) { auto &dl = fntypeinfo.Function->getParent()->getDataLayout(); SmallVector vec; @@ -2867,31 +2839,6 @@ void TypeAnalyzer::visitExtractValueInst(ExtractValueInst &I) { int off = (int)ai.getLimitedValue(); int size = dl.getTypeSizeInBits(I.getType()) / 8; - // When looseTypeAnalysis is on, seed the result from the LLVM type if it - // is a uniform-FP aggregate (or a single FP). This recovers type info for - // extractvalue results whose source aggregate came from an opaque external - // call (#2630, #2463). Gated behind looseTypeAnalysis because the LLVM - // type is not always authoritative about semantic float-ness; emit a - // warning so users can spot when this fallback fires. - if (looseTypeAnalysis) { - if (auto LeafTy = uniformFPLeafType(I.getType())) { - if (EnzymeTypeWarning) { - std::string s; - llvm::raw_string_ostream ss(s); - ss << "Enzyme: assuming extractvalue result is " - << *LeafTy << " from LLVM type (looseTypeAnalysis): " << I; - if (CustomErrorHandler) { - CustomErrorHandler(s.c_str(), wrap(&I), - ErrorType::IllegalTypeAnalysis, this, nullptr, - nullptr); - } else { - llvm::errs() << s << "\n"; - } - } - updateAnalysis(&I, TypeTree(ConcreteType(LeafTy)).Only(-1, &I), &I); - } - } - if (direction & DOWN) updateAnalysis(&I, getAnalysis(I.getOperand(0)) diff --git a/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll b/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll index ac7c5a2d2c02..8af6a568acbb 100644 --- a/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll +++ b/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll @@ -1,24 +1,16 @@ -; RUN: if [ %llvmver -lt 16 ]; then %opt < %s %loadEnzyme -print-type-analysis -type-analysis-func=compute -enzyme-loose-types -enzyme-type-warning=0 -o /dev/null | FileCheck %s; fi -; RUN: %opt < %s %newLoadEnzyme -passes="print-type-analysis" -type-analysis-func=compute -enzyme-loose-types -enzyme-type-warning=0 -S -o /dev/null | FileCheck %s - ; End-to-end check: with loose-types, the full enzyme pass succeeds (no ; "Cannot deduce type of extract" diagnostic) and emits a reverse-mode ; entry point. Pre-fix this run failed because the extracted [2 x float] ; aggregates had empty TypeTrees; the AdjointGenerator's loose-types -; fallback (AdjointGenerator.h:1894) only handles primitive FP/int and -; cannot fill aggregate types. +; fallback (AdjointGenerator.h:1894) only handled primitive FP/int and +; couldn't fill aggregate types. Fix extends that fallback to walk +; aggregate types via uniformFPLeafType (same file scope). ; RUN: %opt < %s %newLoadEnzyme -passes="enzyme" -enzyme-loose-types -enzyme-type-warning=0 -enzyme-preopt=false --enzyme-assume-unknown-nofree=1 -S | FileCheck %s --check-prefix=ENZYME -; Warning check: when the LLVM-type fallback fires, an "assuming -; extractvalue result is..." warning must be emitted (gated on the -; default-on -enzyme-type-warning) so users can see when this potentially -; lossy assumption is being made. -; RUN: %opt < %s %newLoadEnzyme -passes="print-type-analysis" -type-analysis-func=compute -enzyme-loose-types -S -o /dev/null 2>&1 | FileCheck %s --check-prefix=WARN - ; Regression test for EnzymeAD/Enzyme#2630 (and #2463): with looseTypeAnalysis, -; TypeAnalysis seeds float type info for extractvalue results whose source -; aggregate comes from an opaque external call. The function takes i64 (not -; float) so the only source of float info is the extractvalue LLVM type seeding. +; the AdjointGenerator's loose-types fallback walks aggregate types so +; extractvalue results whose source aggregate comes from an opaque external +; call get seeded with the leaf FP type. target triple = "x86_64-unknown-linux-gnu" @@ -30,18 +22,6 @@ declare float @__enzyme_autodiff(...) attributes #0 = { "enzyme_inactive" } -define void @compute(i64 %opaque) { -entry: - %r = call %struct.CV @pre_work(i64 %opaque) - %a = extractvalue %struct.CV %r, 0 - %b = extractvalue %struct.CV %r, 1 - %a0 = extractvalue [2 x float] %a, 0 - %a1 = extractvalue [2 x float] %a, 1 - %b0 = extractvalue [2 x [3 x float]] %b, 0, 0 - %b1 = extractvalue [2 x [3 x float]] %b, 1, 2 - ret void -} - ; Active reverse-mode entry. Mirrors #2630's pattern: an opaque struct ; return whose elements feed an active fmul. Without the fix the enzyme ; pass aborts with "Cannot deduce type of extract" on the [2 x float] @@ -65,20 +45,5 @@ define float @caller(float %seed) { ret float %d } -; CHECK: compute - {} |{[-1]:Integer}:{} -; CHECK-NEXT: i64 %opaque: {[-1]:Integer} -; CHECK-NEXT: entry -; CHECK-NEXT: %r = call %struct.CV @pre_work(i64 %opaque): {[0]:Float@float, [4]:Float@float, [8]:Float@float, [12]:Float@float, [16]:Float@float, [20]:Float@float, [24]:Float@float, [28]:Float@float} -; CHECK-NEXT: %a = extractvalue %struct.CV %r, 0: {[-1]:Float@float} -; CHECK-NEXT: %b = extractvalue %struct.CV %r, 1: {[-1]:Float@float} -; CHECK-NEXT: %a0 = extractvalue [2 x float] %a, 0: {[-1]:Float@float} -; CHECK-NEXT: %a1 = extractvalue [2 x float] %a, 1: {[-1]:Float@float} -; CHECK-NEXT: %b0 = extractvalue [2 x [3 x float]] %b, 0, 0: {[-1]:Float@float} -; CHECK-NEXT: %b1 = extractvalue [2 x [3 x float]] %b, 1, 2: {[-1]:Float@float} -; CHECK-NEXT: ret void: {} - ; ENZYME: define internal {{.*}} @diffead_compute ; ENZYME-NOT: Cannot deduce type of extract - -; WARN: Enzyme: assuming extractvalue result is float from LLVM type (looseTypeAnalysis): -; WARN-SAME: %a = extractvalue %struct.CV %r, 0 From 56414bd61ef6cd1942e850f1fcf8a39433211178 Mon Sep 17 00:00:00 2001 From: June Kim Date: Wed, 20 May 2026 19:55:08 -0700 Subject: [PATCH 8/9] test: let reverse recompute the opaque inactive callee MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The extractvalue_aggregate.ll reproduction aborted in CI with "No augmented forward pass found for pre_work" — Enzyme needed pre_work's primal struct return in the reverse pass but cannot synthesize an augmented pass for an opaque declaration. That is orthogonal to the loose-types aggregate type deduction this test exercises. Mark pre_work readnone/willreturn so the reverse pass recomputes it by re-calling instead of caching it. The opaque return stays untyped, so the extractvalue results still hit the aggregate fallback under test. --- .../test/TypeAnalysis/extractvalue_aggregate.ll | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll b/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll index 8af6a568acbb..498a046b7ac0 100644 --- a/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll +++ b/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll @@ -17,10 +17,21 @@ target triple = "x86_64-unknown-linux-gnu" %struct.CV = type { [2 x float], [2 x [3 x float]] } declare %struct.CV @pre_work(i64) #0 -declare void @opaque_sink(float, float) #0 +declare void @opaque_sink(float, float) #1 declare float @__enzyme_autodiff(...) -attributes #0 = { "enzyme_inactive" } +; pre_work is readnone/willreturn so the reverse pass can recompute its +; primal struct return by re-calling it, rather than caching it through an +; augmented forward pass. An augmented pass cannot be synthesized for an +; opaque declaration (no body), so without this the enzyme pass aborts with +; "No augmented forward pass found for pre_work" — orthogonal to the type- +; deduction path this test exercises. enzyme_inactive keeps the call itself +; out of differentiation; the opaque return is still untyped, so the +; extractvalue results hit the loose-types aggregate fallback under test. +attributes #0 = { "enzyme_inactive" readnone willreturn nounwind } +; opaque_sink stays side-effecting (no readnone) so %a1 remains live and the +; [2 x float] aggregate's extractvalue chain is not DCE'd before the pass runs. +attributes #1 = { "enzyme_inactive" } ; Active reverse-mode entry. Mirrors #2630's pattern: an opaque struct ; return whose elements feed an active fmul. Without the fix the enzyme From d613c0acbbbbbfcb1fbfea9e6fae2d5c604a149b Mon Sep 17 00:00:00 2001 From: June Kim Date: Wed, 20 May 2026 20:30:49 -0700 Subject: [PATCH 9/9] test: give pre_work a body so the augmented pass can be built Enzyme cannot synthesize an augmented forward pass for an opaque declaration, so the reverse pass aborted with "No augmented forward pass found for pre_work" before reaching the type-deduction path this test exercises. Define pre_work with a trivial undef-returning body: the augmented pass builds, while the undef return stays untyped so the extractvalue results still lack TypeTrees and hit the loose-types aggregate fallback under test. --- .../TypeAnalysis/extractvalue_aggregate.ll | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll b/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll index 498a046b7ac0..fa90ad847196 100644 --- a/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll +++ b/enzyme/test/TypeAnalysis/extractvalue_aggregate.ll @@ -16,22 +16,23 @@ target triple = "x86_64-unknown-linux-gnu" %struct.CV = type { [2 x float], [2 x [3 x float]] } -declare %struct.CV @pre_work(i64) #0 -declare void @opaque_sink(float, float) #1 +; pre_work has a trivial body (returns undef) instead of being an opaque +; declaration. Enzyme needs its primal struct return available in the reverse +; pass and can only synthesize the augmented forward pass for a function with +; a body — a bodyless declaration aborts with "No augmented forward pass found +; for pre_work", which is orthogonal to the type-deduction path under test. +; Returning undef keeps the result untyped, so the extractvalue results still +; have empty TypeTrees and hit the loose-types aggregate fallback. The leaves +; that are only consumed by the inactive opaque_sink (e.g. %a1, and all but +; one leaf of %b) stay unconstrained, which is what makes the aggregate type +; undeducible without the fix. +define %struct.CV @pre_work(i64 %n) #0 { + ret %struct.CV undef +} +declare void @opaque_sink(float, float) #0 declare float @__enzyme_autodiff(...) -; pre_work is readnone/willreturn so the reverse pass can recompute its -; primal struct return by re-calling it, rather than caching it through an -; augmented forward pass. An augmented pass cannot be synthesized for an -; opaque declaration (no body), so without this the enzyme pass aborts with -; "No augmented forward pass found for pre_work" — orthogonal to the type- -; deduction path this test exercises. enzyme_inactive keeps the call itself -; out of differentiation; the opaque return is still untyped, so the -; extractvalue results hit the loose-types aggregate fallback under test. -attributes #0 = { "enzyme_inactive" readnone willreturn nounwind } -; opaque_sink stays side-effecting (no readnone) so %a1 remains live and the -; [2 x float] aggregate's extractvalue chain is not DCE'd before the pass runs. -attributes #1 = { "enzyme_inactive" } +attributes #0 = { "enzyme_inactive" } ; Active reverse-mode entry. Mirrors #2630's pattern: an opaque struct ; return whose elements feed an active fmul. Without the fix the enzyme