Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 19 additions & 11 deletions compiler/rustc_middle/src/ty/typetree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
; 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}"

; 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]:Integer}"

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}"
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#![crate_type = "lib"]
#![feature(autodiff)]

use std::autodiff::autodiff_reverse;

// 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;

#[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;
}

#[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 {

@ZuseZ4 ZuseZ4 Aug 11, 2026

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.

@wsmoses what typetree do you expect to be generated here, especially for larger N, of say 2048? Currently we only generate it for offset 0 of data and for scale.
Generating 2048 offsets seems extremely wasteful and -1 would be wrong, in the general case were scale and data have different base types (e.g. scale were int).
Afaik Enzyme doesn't directly let frontend generate typetree ranges?

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

same place I'm stuck tbh. dense offsets for N=2048 feel pretty wasteful, and -1 is wrong once scale isn't float. afaik Enzyme still doesn't give frontends a clean range form, so I'm not trying to "solve" that in this PR.

imo we keep the normalize fix + the i32 scale regression here, and wait for @wsmoses on what rustc should emit for big inline arrays. ltm what you'd want that follow-up to look like.

pub data: [f32; N],
pub scale: i32,
}

#[no_mangle]
#[inline(never)]
pub unsafe fn copy_inline_array(a: &InlineArray, b: &mut InlineArray) {
*b = *a;
}
Loading