Skip to content

Commit e252e58

Browse files
committed
add test, cleanup
1 parent d86805c commit e252e58

8 files changed

Lines changed: 166 additions & 91 deletions

File tree

compiler/rustc_codegen_llvm/src/intrinsic.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ use crate::errors::{
4343
AutoDiffWithoutEnable, AutoDiffWithoutLto, IntrinsicSignatureMismatch, IntrinsicWrongArch,
4444
OffloadWithoutEnable, OffloadWithoutFatLTO, UnknownIntrinsic,
4545
};
46-
use crate::intrinsic::ty::typetree::{fnc_typetrees, typetree_from_ty};
46+
use crate::intrinsic::ty::typetree::fnc_typetrees;
4747
use crate::llvm::{self, Type, Value};
4848
use crate::type_of::LayoutLlvmExt;
4949
use crate::va_arg::emit_va_arg;
@@ -1933,10 +1933,8 @@ fn get_args_from_tuple<'ll, 'tcx>(
19331933
let llvm_ty = field.layout.llvm_type(bx.cx);
19341934
let pair_val = bx.load(llvm_ty, field.val.llval, field.val.align);
19351935

1936-
let extract_ty = field.layout.ty;
1937-
let tt = typetree_from_ty(bx.tcx(), extract_ty);
1938-
result.push(bx.extract_value(pair_val, 0, Some(tt.clone())));
1939-
result.push(bx.extract_value(pair_val, 1, Some(tt)));
1936+
result.push(bx.extract_value(pair_val, 0, None));
1937+
result.push(bx.extract_value(pair_val, 1, None));
19401938
tuple_index += 1;
19411939
}
19421940
PassMode::Indirect { .. } => {

compiler/rustc_codegen_llvm/src/llvm/enzyme_ffi.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ unsafe extern "C" {
6666
NameLen: libc::size_t,
6767
) -> Option<&Value>;
6868

69-
pub(crate) fn LLVMRustIsIntrinsicCall(V: &Value) -> bool;
69+
pub(crate) safe fn LLVMRustIsIntrinsicCall(V: &Value) -> bool;
7070
pub(crate) safe fn LLVMRustSupportsEnzymeMD(V: &Value) -> bool;
7171
pub(crate) fn LLVMRustSetEnzymeTypeMD(
7272
v: &Value,

compiler/rustc_codegen_llvm/src/typetree.rs

Lines changed: 68 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ fn process_typetree_recursive(
7070
extra_ints
7171
}
7272

73+
enum TTLocation {
74+
Definition,
75+
Callsite,
76+
Intrinsic,
77+
}
78+
7379
#[cfg_attr(not(feature = "llvm_enzyme"), allow(unused))]
7480
pub(crate) fn add_tt<'tcx, 'll>(
7581
llmod: &'ll llvm::Module,
@@ -104,11 +110,19 @@ pub(crate) fn add_tt<'tcx, 'll>(
104110
let attr_name = "enzyme_type";
105111
let c_attr_name = CString::new(attr_name).unwrap();
106112

113+
let tt_location: TTLocation = if llvm::LLVMRustIsIntrinsicCall(fn_def) {
114+
TTLocation::Callsite
115+
} else if llvm::LLVMRustSupportsEnzymeMD(fn_def) {
116+
TTLocation::Intrinsic
117+
} else {
118+
TTLocation::Definition
119+
};
120+
107121
let mut offset = 0;
108122
for (i, input) in inputs.iter().enumerate() {
109123
let (enzyme_tt, extra_ints) = to_enzyme_typetree(&input, llvm_data_layout, llcx);
110124

111-
// This scope is just a visual reminder that we *must* drop the enzyme_wrapper before
125+
// This scope is a simple solution since we *must* drop the enzyme_wrapper before
112126
// we drop any typetrees (mainly enzyme_tt and extra_ints). Drop calls can not accept
113127
// arguments like an enzyme_wrapper, so the typetree drop impl has to call get_instance
114128
// on the static enzyme instance, which is behind a Mutex. Therefore we'd deadlock if we
@@ -118,74 +132,59 @@ pub(crate) fn add_tt<'tcx, 'll>(
118132
let c_str = enzyme_wrapper.tree_to_cstr(enzyme_tt.inner);
119133

120134
let attr = llvm::CreateAttrStringValueFromCStr(llcx, &c_attr_name, &c_str);
121-
dbg!(&fn_def);
122-
if llvm::LLVMRustSupportsEnzymeMD(fn_def) {
123-
dbg!("extractvalue md");
124-
let md = enzyme_wrapper.tree_to_md(enzyme_tt.inner, llcx);
125-
unsafe {
126-
llvm::LLVMRustSetEnzymeTypeMD(fn_def, md.unwrap());
135+
let arg_pos = llvm::AttributePlace::Argument(i as u32 + offset);
136+
match tt_location {
137+
TTLocation::Definition => {
138+
attributes::apply_to_llfn(fn_def, arg_pos, &[attr]);
139+
}
140+
TTLocation::Callsite => {
141+
attributes::apply_to_callsite(fn_def, arg_pos, &[attr]);
142+
}
143+
TTLocation::Intrinsic => {
144+
let md = enzyme_wrapper.tree_to_md(enzyme_tt.inner, llcx);
145+
unsafe {
146+
llvm::LLVMRustSetEnzymeTypeMD(fn_def, md.unwrap());
147+
}
127148
}
128-
} else if unsafe { llvm::LLVMRustIsIntrinsicCall(fn_def) } {
129-
dbg!("callsite");
130-
attributes::apply_to_callsite(
131-
fn_def,
132-
llvm::AttributePlace::Argument(i as u32 + offset),
133-
&[attr],
134-
);
135-
} else {
136-
dbg!("llfn");
137-
attributes::apply_to_llfn(
138-
fn_def,
139-
llvm::AttributePlace::Argument(i as u32 + offset),
140-
&[attr],
141-
);
142149
}
143150
enzyme_wrapper.tree_to_string_free(c_str.as_ptr());
144151
for v in &extra_ints {
145152
offset += 1;
146153
let c_str = enzyme_wrapper.tree_to_cstr(v.inner);
147154
let int_attr = llvm::CreateAttrStringValueFromCStr(llcx, &c_attr_name, &c_str);
148-
dbg!(&fn_def);
149-
if llvm::LLVMRustSupportsEnzymeMD(fn_def) {
150-
dbg!("extractvalue input(?)");
151-
let md = enzyme_wrapper.tree_to_md(enzyme_tt.inner, llcx);
152-
unsafe {
153-
llvm::LLVMRustSetEnzymeTypeMD(fn_def, md.unwrap());
155+
let arg_pos = llvm::AttributePlace::Argument(i as u32 + offset);
156+
match tt_location {
157+
TTLocation::Intrinsic => {
158+
let md = enzyme_wrapper.tree_to_md(enzyme_tt.inner, llcx);
159+
unsafe {
160+
llvm::LLVMRustSetEnzymeTypeMD(fn_def, md.unwrap());
161+
}
162+
}
163+
TTLocation::Definition => {
164+
attributes::apply_to_llfn(fn_def, arg_pos, &[int_attr]);
165+
}
166+
TTLocation::Callsite => {
167+
attributes::apply_to_callsite(fn_def, arg_pos, &[int_attr]);
154168
}
155-
} else if unsafe { llvm::LLVMRustIsIntrinsicCall(fn_def) } {
156-
dbg!("callsite input");
157-
attributes::apply_to_callsite(
158-
fn_def,
159-
llvm::AttributePlace::Argument(i as u32 + offset),
160-
&[int_attr],
161-
);
162-
} else {
163-
dbg!("llfn");
164-
attributes::apply_to_llfn(
165-
fn_def,
166-
llvm::AttributePlace::Argument(i as u32 + offset),
167-
&[int_attr],
168-
);
169169
}
170170
enzyme_wrapper.tree_to_string_free(c_str.as_ptr());
171171
}
172172
}
173173
}
174-
// We will only fail this if Rust types got lowered to LLVM in a way that we didn't predict.
175-
// Error, so we can learn from our mistakes.
176-
//if unsafe { !llvm::LLVMRustIsIntrinsicCall(fn_def) } {
177-
// dbg!("checking parameter count");
178-
// let expected = offset as usize + inputs.len();
179-
// let actual = llvm::count_params(fn_def) as usize;
180-
// if expected != actual {
181-
// tcx.dcx().warn(format!(
182-
// "autodiff type-tree failure. We expected {expected} LLVM argument(s), \
183-
// but the generated LLVM function has {actual} parameter(s)"
184-
// ));
185-
// }
186-
//}
187-
188-
let (enzyme_tt, extra_ints) = to_enzyme_typetree(&ret_tt, llvm_data_layout, llcx);
174+
// We will fail here if Rust types got lowered to LLVM in a way that we didn't predict.
175+
// We Error, so we can learn from our mistakes.
176+
if matches!(tt_location, TTLocation::Definition) {
177+
let expected = offset as usize + inputs.len();
178+
let actual = llvm::count_params(fn_def) as usize;
179+
if expected != actual {
180+
tcx.dcx().warn(format!(
181+
"autodiff type-tree failure. We expected {expected} LLVM argument(s), \
182+
but the generated LLVM function has {actual} parameter(s)"
183+
));
184+
}
185+
}
186+
187+
let (enzyme_tt, _extra_ints) = to_enzyme_typetree(&ret_tt, llvm_data_layout, llcx);
189188
if ret_tt != RustTypeTree::new() {
190189
let enzyme_wrapper = EnzymeWrapper::get_instance();
191190
//if !extra_ints.is_empty() {
@@ -195,21 +194,20 @@ pub(crate) fn add_tt<'tcx, 'll>(
195194

196195
let ret_attr = llvm::CreateAttrStringValueFromCStr(llcx, &c_attr_name, &c_str);
197196

198-
if llvm::LLVMRustSupportsEnzymeMD(fn_def) {
199-
dbg!("extractvalue md");
200-
let md = enzyme_wrapper.tree_to_md(enzyme_tt.inner, llcx);
201-
unsafe {
202-
llvm::LLVMRustSetEnzymeTypeMD(fn_def, md.unwrap());
197+
let arg_pos = llvm::AttributePlace::ReturnValue;
198+
match tt_location {
199+
TTLocation::Definition => {
200+
attributes::apply_to_llfn(fn_def, arg_pos, &[ret_attr]);
201+
}
202+
TTLocation::Callsite => {
203+
attributes::apply_to_callsite(fn_def, arg_pos, &[ret_attr]);
204+
}
205+
TTLocation::Intrinsic => {
206+
let md = enzyme_wrapper.tree_to_md(enzyme_tt.inner, llcx);
207+
unsafe {
208+
llvm::LLVMRustSetEnzymeTypeMD(fn_def, md.unwrap());
209+
}
203210
}
204-
} else if unsafe { llvm::LLVMRustIsIntrinsicCall(fn_def) } {
205-
dbg!("intrinsiccall");
206-
attributes::apply_to_callsite(
207-
fn_def,
208-
llvm::AttributePlace::ReturnValue,
209-
&[ret_attr],
210-
);
211-
} else {
212-
attributes::apply_to_llfn(fn_def, llvm::AttributePlace::ReturnValue, &[ret_attr]);
213211
}
214212
enzyme_wrapper.tree_to_string_free(c_str.as_ptr());
215213
}

compiler/rustc_codegen_ssa/src/mir/operand.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@ use crate::common::IntPredicate;
2222
use crate::traits::*;
2323

2424

25-
use rustc_ast::expand::typetree::{TypeTree, FncTree};
25+
use rustc_ast::expand::typetree::TypeTree;
2626
use rustc_middle::ty::typetree::typetree_from_ty;
27-
//use rustc_middle::ty::typetree_from_ty;
2827
use crate::TyCtxt;
2928
use rustc_span::sym;
3029

compiler/rustc_codegen_ssa/src/traits/builder.rs

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::assert_matches;
22
use std::ops::Deref;
33

44
use rustc_abi::{Align, Scalar, Size, WrappingRange};
5-
use rustc_ast::expand::typetree::{TypeTree, FncTree};
5+
use rustc_ast::expand::typetree::{FncTree, TypeTree};
66
use rustc_hir::attrs::AttributeKind;
77
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
88
use rustc_middle::mir;
@@ -468,7 +468,6 @@ pub trait BuilderMethods<'a, 'tcx>:
468468
src_align: Align,
469469
size: Self::Value,
470470
flags: MemFlags,
471-
//tt: Option<FncTree>,
472471
);
473472
fn memset(
474473
&mut self,
@@ -477,7 +476,6 @@ pub trait BuilderMethods<'a, 'tcx>:
477476
size: Self::Value,
478477
align: Align,
479478
flags: MemFlags,
480-
//tt: Option<FncTree>,
481479
);
482480

483481
// Produce a value from calling the `vscale` intrinsic (containing the `vscale` multiplier that
@@ -524,16 +522,7 @@ pub trait BuilderMethods<'a, 'tcx>:
524522
let tt = typetree_from_ty(self.tcx(), layout.ty);
525523
// We seem to pass all values to memcpy with one more indirection.
526524
let tt = tt.add_indirection();
527-
dbg!(&tt);
528-
use rustc_middle::ty::print::with_no_trimmed_paths;
529-
530-
with_no_trimmed_paths!({
531-
eprintln!("memcpy ty = {:?}", layout.ty);
532-
});
533-
let fnc_tree = FncTree {
534-
args: vec![tt.clone(), tt],
535-
ret: TypeTree::new(),
536-
};
525+
let fnc_tree = FncTree { args: vec![tt.clone(), tt], ret: TypeTree::new() };
537526
let bytes = self.const_usize(layout.size.bytes());
538527
let bytes = if layout.peel_transparent_wrappers(self).ty.is_scalable_vector() {
539528
let vscale = self.vscale(self.type_i64());
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//@ needs-enzyme
2+
//@ ignore-cross-compile
3+
4+
use run_make_support::{llvm_filecheck, rfs, rustc};
5+
6+
fn main() {
7+
rustc().input("window.rs").arg("-Zautodiff=Enable").arg("-Clto=fat").emit("llvm-ir").run();
8+
//rustc().input("window.rs").arg("-Zautodiff=Enable,NoTT").arg("-Clto=fat").run_fail().assert_stderr_contains("Enzyme: Cannot deduce type of copy");
9+
//rustc().input("window.rs").arg("-Zautodiff=Enable,NoTT").arg("-Clto=fat").arg("-O").run();
10+
11+
llvm_filecheck().patterns("window.check").stdin_buf(rfs::read("window.ll")).run();
12+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
; Check that array TypeTree metadata is correctly generated for the iter/window case
2+
; We check all three relevant cases. The metadata generated for extractvalue,
3+
; the metadata for the memcpy call,
4+
; and the metadata for the function definition.
5+
; At the time of writing, at least the first two where necessary to make compilation succeed in debug mode.
6+
7+
CHECK-LABEL: ; <core::slice::iter::Windows<f64> as core::iter::traits::iterator::Iterator>::fold
8+
CHECK: extractvalue { ptr, i64 } %[[IN:[0-9]+]], 0, !enzyme_type ![[DATA_TT:[0-9]+]]
9+
CHECK: extractvalue { ptr, i64 } %[[IN]], 1, !enzyme_type ![[LEN_TT:[0-9]+]]
10+
11+
CHECK-LABEL: ; window::main
12+
CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 "enzyme_type"="{[0]:Pointer, [0,-1]:Float@double}" %vjp, ptr align 8 "enzyme_type"="{[0]:Pointer, [0,-1]:Float@double}" {{.*}}, i64 24, i1 false)
13+
14+
15+
CHECK: define {{.*}}void @f(ptr align 8 "enzyme_type"="{[-1]:Pointer, [-1,-1]:Float@double}" %x, ptr align 8 "enzyme_type"="{[-1]:Pointer, [-1,-1]:Float@double}" %0, ptr writeonly align 8 captures(none) "enzyme_type"="{[-1]:Pointer, [-1,-1]:Float@double}" %y)
16+
17+
18+
CHECK-DAG: ![[DATA_TT]] = !{!"Unknown", i32 -1, ![[PTR_TT:[0-9]+]]}
19+
CHECK-DAG: ![[PTR_TT]] = !{!"Pointer", i32 -1, ![[FLOAT_TT:[0-9]+]]}
20+
CHECK-DAG: ![[FLOAT_TT]] = !{!"Float@double"}
21+
22+
CHECK-DAG: ![[LEN_TT]] = !{!"Unknown", i32 0, ![[INT_TT:[0-9]+]], i32 1, ![[INT_TT]], i32 2, ![[INT_TT]], i32 3, ![[INT_TT]], i32 4, ![[INT_TT]], i32 5, ![[INT_TT]], i32 6, ![[INT_TT]], i32 7, ![[INT_TT]]}
23+
CHECK-DAG: ![[INT_TT]] = !{!"Integer"}
24+
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#![feature(autodiff)]
2+
3+
use std::autodiff::autodiff_reverse;
4+
5+
// This tests verifies that Enzyme can differentiate the iterator and window version of the for
6+
// loops given below. Iterators (especially the windows use here) cause a lot of extra abstractions
7+
// and indirections. Without extra typetree hints, Enzyme failed to differentiate them in debug
8+
// mode.
9+
10+
//@revisions: tt no_tt
11+
//@[tt] compile-flags: -Z autodiff=Enable
12+
//@[no_tt] compile-flags: -Z autodiff=Enable,NoTT
13+
//@[no_tt] build-fail
14+
15+
#[unsafe(no_mangle)]
16+
#[inline(never)]
17+
#[autodiff_reverse(f_rev, 2, Duplicated, Const, Duplicated)]
18+
fn f(x: &[f64; 3], args: &[f64; 3], y: &mut [f64; 2]) {
19+
y[0] = x.iter().map(|i| args[0] * i.powi(2)).sum();
20+
y[1] = x
21+
.windows(2)
22+
.map(|w| (args[1] - w[0]).powi(2) + args[2] * (w[1] - w[0].powi(2)).powi(2))
23+
.sum();
24+
// The iterators above are equivalent to the two following for loops.
25+
// for i in 0..3 {
26+
// y[0] += args[0] * x[i].powi(2);
27+
// }
28+
// for i in 0..2 {
29+
// y[1] += (args[1] - x[i]).powi(2) + args[2] * (x[i + 1] - x[i].powi(2)).powi(2);
30+
// }
31+
}
32+
33+
// Not generally recommended, but since we rewrite llvm-ir, it should be good enough.
34+
fn assert_abs_diff_eq<const N: usize>(x: &[f64; N], y: &[f64; N]) {
35+
for i in 0..N {
36+
assert_eq!(x[i], y[i]);
37+
}
38+
}
39+
40+
fn main() {
41+
let x = [3.0, 5.0, 7.0];
42+
let args = [2.0, 1.0, 100.0];
43+
44+
let mut vjp = ([0.0; 3], [0.0; 3]);
45+
let mut y = [0.0; 2];
46+
let mut dy = ([1.0, 0.0], [0.0, 1.0]);
47+
48+
f_rev(
49+
&x, &mut vjp.0, &mut vjp.1, &args, &mut y, &mut dy.0, &mut dy.1,
50+
);
51+
52+
assert_abs_diff_eq::<2>(&y, &[166.0, 34020.0]);
53+
assert_abs_diff_eq::<3>(&vjp.0, &[12.0, 20.0, 28.0]);
54+
assert_abs_diff_eq::<3>(&vjp.1, &[4804.0, 35208.0, -3600.0]);
55+
}

0 commit comments

Comments
 (0)