From 340e538c77aeeefce5e732605f6366c89ebcdc68 Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Fri, 7 Aug 2026 09:47:39 -0300 Subject: [PATCH 1/3] Normalize autodiff typetree struct field types FieldDef::ty returns Unnormalized, so skipping normalization left anon-const array lengths unevaluated. That ICEd in struct_tail_for_codegen and emptied TypeTrees for plain [T; N] fields. Normalize with TypingEnv::fully_monomorphized before recursing. --- compiler/rustc_middle/src/ty/typetree.rs | 30 +++++++++++++++--------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_middle/src/ty/typetree.rs b/compiler/rustc_middle/src/ty/typetree.rs index 100c3170e12a9..290a0edf7e32a 100644 --- a/compiler/rustc_middle/src/ty/typetree.rs +++ b/compiler/rustc_middle/src/ty/typetree.rs @@ -122,7 +122,16 @@ fn typetree_from_ty_impl_inner<'tcx>( ty::Ref(..) | ty::RawPtr(..) => handle_indirection(ty, tcx, depth, visited), ty::Adt(def, _) if def.is_box() => handle_indirection(ty, tcx, depth, visited), ty::Array(element_ty, len_const) => { - let len = len_const.try_to_target_usize(tcx).unwrap_or(0); + // Lengths are normalized by callers (struct fields via + // `normalize_erasing_regions`); keep a graceful fallback rather + // than asserting when evaluation still fails. + let len = len_const.try_to_target_usize(tcx).unwrap_or_else(|| { + trace!( + "autodiff typetree: array length {len_const:?} not evaluable; \ + emitting empty TypeTree" + ); + 0 + }); if len == 0 { TypeTree::new() } else { @@ -169,20 +178,19 @@ fn typetree_from_ty_impl_inner<'tcx>( } } ty::Adt(adt_def, args) if adt_def.is_struct() => { - let struct_layout = - tcx.layout_of(ty::TypingEnv::fully_monomorphized().as_query_input(ty)); + let typing_env = ty::TypingEnv::fully_monomorphized(); + let struct_layout = tcx.layout_of(typing_env.as_query_input(ty)); if let Ok(layout) = struct_layout { let mut types = Vec::new(); for (field_idx, field_def) in adt_def.all_fields().enumerate() { - let field_ty = field_def.ty(tcx, args); - let field_tree = typetree_from_ty_impl_inner( - tcx, - field_ty.skip_norm_wip(), - depth + 1, - visited, - false, - ); + // `FieldDef::ty` returns `Unnormalized`; normalize before recursion so + // array lengths / projections are concrete for `struct_tail_for_codegen` + // and `try_to_target_usize` (see #160635). + let field_ty = + tcx.normalize_erasing_regions(typing_env, field_def.ty(tcx, args)); + let field_tree = + typetree_from_ty_impl_inner(tcx, field_ty, depth + 1, visited, false); let field_offset = layout.fields.offset(field_idx).bytes_usize(); From 915bdd088d2e766301c6eebbac48003bf555b684 Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Fri, 7 Aug 2026 09:47:43 -0300 Subject: [PATCH 2/3] Add autodiff typetree coverage for anon-const array lengths Cover both the ICE path through *mut [T; N] struct fields and the silent empty-TypeTree case for plain [T; N] fields. --- .../array-const-len.check | 16 ++++++ .../array-const-len-typetree/rmake.rs | 19 +++++++ .../array-const-len-typetree/test.rs | 51 +++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 tests/run-make/autodiff/type-trees/array-const-len-typetree/array-const-len.check create mode 100644 tests/run-make/autodiff/type-trees/array-const-len-typetree/rmake.rs create mode 100644 tests/run-make/autodiff/type-trees/array-const-len-typetree/test.rs diff --git a/tests/run-make/autodiff/type-trees/array-const-len-typetree/array-const-len.check b/tests/run-make/autodiff/type-trees/array-const-len-typetree/array-const-len.check new file mode 100644 index 0000000000000..6d10505d9ab30 --- /dev/null +++ b/tests/run-make/autodiff/type-trees/array-const-len-typetree/array-const-len.check @@ -0,0 +1,16 @@ +; ICE case (#160635): pointer-to-[T; N] field keeps float child metadata after normalizing N. +PTR-LABEL: define{{.*}}@copy_ptr_array( +PTR-NOT: define +PTR: call void @llvm.memcpy{{.*}}"enzyme_type"="{[0]:Pointer, [0,0]:Pointer, [0,0,-1]:Float@float, [0,8]:Float@float, [0,12]:Float@float}" + +; Silent case: plain [T; N] field produces float metadata (not an empty subtree). +; Known limitation: the struct arm collapses array element offset -1 to the field +; byte offset, so only byte 0 of `data: [f32; 8]` is classified as Float; bytes +; 4..32 stay unclassified (unlike pointer-to-array, which preserves [-1]). +INLINE-LABEL: define{{.*}}@copy_inline_array( +INLINE-NOT: define +INLINE: call void @llvm.memcpy{{.*}}"enzyme_type"="{[0]:Pointer, [0,0]:Float@float, [0,32]:Float@float}" + +; Enzyme accepts the ICE-shaped PtrArray TypeTree (not just rustc emission). +ADIFF-LABEL: define{{.*}}@ptr_array_sum( +ADIFF-SAME: ptr{{.*}}"enzyme_type"="{[-1]:Pointer, [-1,0]:Pointer, [-1,0,-1]:Float@float, [-1,8]:Float@float, [-1,12]:Float@float}" diff --git a/tests/run-make/autodiff/type-trees/array-const-len-typetree/rmake.rs b/tests/run-make/autodiff/type-trees/array-const-len-typetree/rmake.rs new file mode 100644 index 0000000000000..13450ed53bed7 --- /dev/null +++ b/tests/run-make/autodiff/type-trees/array-const-len-typetree/rmake.rs @@ -0,0 +1,19 @@ +//@ needs-enzyme +//@ ignore-cross-compile + +use run_make_support::{llvm_filecheck, rfs, rustc}; + +fn main() { + rustc() + .input("test.rs") + .arg("-Zautodiff=Enable,NoPostopt") + .opt_level("0") + .arg("-Clto=fat") + .emit("llvm-ir") + .run(); + + let ir = rfs::read("test.ll"); + llvm_filecheck().patterns("array-const-len.check").check_prefix("PTR").stdin_buf(&ir).run(); + llvm_filecheck().patterns("array-const-len.check").check_prefix("INLINE").stdin_buf(&ir).run(); + llvm_filecheck().patterns("array-const-len.check").check_prefix("ADIFF").stdin_buf(&ir).run(); +} diff --git a/tests/run-make/autodiff/type-trees/array-const-len-typetree/test.rs b/tests/run-make/autodiff/type-trees/array-const-len-typetree/test.rs new file mode 100644 index 0000000000000..c5af4c179bf7f --- /dev/null +++ b/tests/run-make/autodiff/type-trees/array-const-len-typetree/test.rs @@ -0,0 +1,51 @@ +#![crate_type = "lib"] +#![feature(autodiff)] + +use std::autodiff::autodiff_reverse; + +// Regression test for #160635: non-literal array lengths (anon consts) in struct +// fields must be normalized before typetree recursion. Without that, `*mut [f32; N]` +// ICEs in `struct_tail_for_codegen`, and a plain `[f32; N]` field silently yields +// an empty TypeTree. + +const N: usize = 8; + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct PtrArray { + pub p: *mut [f32; N], + pub q: f32, + pub r: f32, +} + +#[no_mangle] +#[inline(never)] +pub unsafe fn copy_ptr_array(a: &PtrArray, b: &mut PtrArray) { + *b = *a; +} + +// Run Enzyme over the ICE-shaped type so metadata is not only emitted but accepted. +#[autodiff_reverse(d_ptr_array_sum, Duplicated, Active)] +#[no_mangle] +#[inline(never)] +pub fn ptr_array_sum(s: &PtrArray) -> f32 { + s.q + s.r +} + +#[no_mangle] +pub fn exercise_ptr_array_sum(s: &PtrArray, ds: &mut PtrArray) -> f32 { + d_ptr_array_sum(s, ds, 1.0) +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct InlineArray { + pub data: [f32; N], + pub scale: f32, +} + +#[no_mangle] +#[inline(never)] +pub unsafe fn copy_inline_array(a: &InlineArray, b: &mut InlineArray) { + *b = *a; +} From 194b0db2cbd1b8389e7633453428d7de8422961e Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Tue, 11 Aug 2026 16:11:26 -0300 Subject: [PATCH 3/3] Tighten autodiff typetree array-const-len regression comments Drop the unhelpful acceptance wording, briefly explain the struct_tail_for_codegen ICE path, and make scale an i32 so the inline-array case cannot be hand-waved as floats everywhere. --- .../array-const-len-typetree/array-const-len.check | 12 +++++------- .../type-trees/array-const-len-typetree/test.rs | 14 ++++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/run-make/autodiff/type-trees/array-const-len-typetree/array-const-len.check b/tests/run-make/autodiff/type-trees/array-const-len-typetree/array-const-len.check index 6d10505d9ab30..0550adbe1dd37 100644 --- a/tests/run-make/autodiff/type-trees/array-const-len-typetree/array-const-len.check +++ b/tests/run-make/autodiff/type-trees/array-const-len-typetree/array-const-len.check @@ -1,16 +1,14 @@ -; ICE case (#160635): pointer-to-[T; N] field keeps float child metadata after normalizing N. +; Pointer-to-[T; N]: normalize N, keep a compact [-1] float child under the pointee. PTR-LABEL: define{{.*}}@copy_ptr_array( PTR-NOT: define PTR: call void @llvm.memcpy{{.*}}"enzyme_type"="{[0]:Pointer, [0,0]:Pointer, [0,0,-1]:Float@float, [0,8]:Float@float, [0,12]:Float@float}" -; Silent case: plain [T; N] field produces float metadata (not an empty subtree). -; Known limitation: the struct arm collapses array element offset -1 to the field -; byte offset, so only byte 0 of `data: [f32; 8]` is classified as Float; bytes -; 4..32 stay unclassified (unlike pointer-to-array, which preserves [-1]). +; Inline [T; N]: first float element plus the distinct `scale: i32` field. +; Struct flattening currently collapses array `-1` to the field base, so only +; byte 0 of `data` is classified until Enzyme gains a compact range encoding. INLINE-LABEL: define{{.*}}@copy_inline_array( INLINE-NOT: define -INLINE: call void @llvm.memcpy{{.*}}"enzyme_type"="{[0]:Pointer, [0,0]:Float@float, [0,32]:Float@float}" +INLINE: call void @llvm.memcpy{{.*}}"enzyme_type"="{[0]:Pointer, [0,0]:Float@float, [0,32]:Integer}" -; Enzyme accepts the ICE-shaped PtrArray TypeTree (not just rustc emission). ADIFF-LABEL: define{{.*}}@ptr_array_sum( ADIFF-SAME: ptr{{.*}}"enzyme_type"="{[-1]:Pointer, [-1,0]:Pointer, [-1,0,-1]:Float@float, [-1,8]:Float@float, [-1,12]:Float@float}" diff --git a/tests/run-make/autodiff/type-trees/array-const-len-typetree/test.rs b/tests/run-make/autodiff/type-trees/array-const-len-typetree/test.rs index c5af4c179bf7f..7633127a84db4 100644 --- a/tests/run-make/autodiff/type-trees/array-const-len-typetree/test.rs +++ b/tests/run-make/autodiff/type-trees/array-const-len-typetree/test.rs @@ -3,10 +3,13 @@ use std::autodiff::autodiff_reverse; -// Regression test for #160635: non-literal array lengths (anon consts) in struct -// fields must be normalized before typetree recursion. Without that, `*mut [f32; N]` -// ICEs in `struct_tail_for_codegen`, and a plain `[f32; N]` field silently yields -// an empty TypeTree. +// Regression for #160635: anon-const array lengths in struct fields need +// normalization before typetree walks. `*mut [f32; N]` used to ICE in +// `struct_tail_for_codegen` (deepest trailing field / unsizing tail), and a +// plain `[f32; N]` field used to emit an empty TypeTree. +// +// `scale` is `i32` (not `f32`) so a naive `[-1]:Float` over the whole struct +// would misclassify it. Array metadata has to stay bounded to `data`. const N: usize = 8; @@ -24,7 +27,6 @@ pub unsafe fn copy_ptr_array(a: &PtrArray, b: &mut PtrArray) { *b = *a; } -// Run Enzyme over the ICE-shaped type so metadata is not only emitted but accepted. #[autodiff_reverse(d_ptr_array_sum, Duplicated, Active)] #[no_mangle] #[inline(never)] @@ -41,7 +43,7 @@ pub fn exercise_ptr_array_sum(s: &PtrArray, ds: &mut PtrArray) -> f32 { #[repr(C)] pub struct InlineArray { pub data: [f32; N], - pub scale: f32, + pub scale: i32, } #[no_mangle]