Skip to content

Add clang enzyme_notypeanalysis and enzyme_ta_norecur attributes - #3072

Open
markabate wants to merge 4 commits into
EnzymeAD:mainfrom
markabate:main
Open

Add clang enzyme_notypeanalysis and enzyme_ta_norecur attributes#3072
markabate wants to merge 4 commits into
EnzymeAD:mainfrom
markabate:main

Conversation

@markabate

Copy link
Copy Markdown

Adding clang attributes for the enzyme_notypeanalysis and enzyme_ta_norecur flags used by Enzyme.

enzyme_notypeanalysis:
https://github.com/search?q=repo%3AEnzymeAD%2FEnzyme+enzyme_notypeanalysis&type=code

enzyme_ta_norecur:
https://github.com/search?q=repo%3AEnzymeAD%2FEnzyme+enzyme_ta_norecur&type=code

Comment thread enzyme/Enzyme/TypeAnalysis/TypeAnalysis.cpp Outdated
Comment thread enzyme/Enzyme/Clang/EnzymeClang.cpp Outdated
@wsmoses

wsmoses commented Aug 5, 2026

Copy link
Copy Markdown
Member

@markabate can you rebase on main, I just landed an infra change [that among other things should fix the use of these within templates].

Also can we come up with a test, e.g. one where an internal function uses a union type or something

@ax3l

ax3l commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Codex:

Given Billy’s union/template suggestion and the MFIter construction site, I’d add two lit tests.

Background: The AMReX detail worth preserving is that MFIter constructors are declared in AMReX_MFIter.H but defined out of line in AMReX_MFIter.cpp. A test containing only locally defined functions would miss that important path.

1. Clang attribute lowering test

Add enzyme/test/Integration/ReverseMode/typeanalysis_attrs.cpp.

It should cover:

  • enzyme_notypeanalysis on a declaration-only constructor, modeling MFIter.
  • An internal templated function containing mixed union storage, exercising the newly landed template infrastructure.
  • enzyme_ta_norecur on a declaration-only function.
  • enzyme_ta_norecur on a global.
  • Both GNU and [[enzyme::...]] spellings.
  • Final IR attributes/metadata after PreserveNVVM.

Sketch:

// RUN: if [ %llvmver -ge 12 ]; then %clang++ -std=c++17 -O0 %s \
// RUN:   -S -emit-llvm -o - %newLoadClangEnzyme | FileCheck %s; fi

union IterState {
  double real;
  unsigned long long bits;
};

struct Iter {
  IterState state;

  // Declaration only, like amrex::MFIter.
  [[enzyme::notypeanalysis]]
  explicit Iter(int);
};

extern "C" void advance(IterState *)
    __attribute__((enzyme_ta_norecur));

extern IterState global_state
    __attribute__((enzyme_ta_norecur));

template <typename T>
[[enzyme::notypeanalysis]]
__attribute__((noinline))
static int opaque_union(T input) {
  union {
    T real;
    unsigned long long bits;
  } value;

  value.real = input;
  value.bits = 0;
  return static_cast<int>(value.bits);
}

double use(double x) {
  Iter iterator(1);
  advance(&global_state);
  return x + opaque_union(x);
}

// CHECK: @global_state = external global {{.*}}, !enzyme_ta_norecur
// CHECK: declare {{.*}}Iter{{.*}} #[[CTOR:[0-9]+]]
// CHECK: declare void @advance({{.*}}) #[[ADVANCE:[0-9]+]]
// CHECK: define internal {{.*}}opaque_union{{.*}} #[[TEMPLATE:[0-9]+]]
// CHECK-DAG: attributes #[[CTOR]] = { {{.*}}"enzyme_notypeanalysis"{{.*}} }
// CHECK-DAG: attributes #[[ADVANCE]] = { {{.*}}"enzyme_ta_norecur"{{.*}} }
// CHECK-DAG: attributes #[[TEMPLATE]] = { {{.*}}"enzyme_notypeanalysis"{{.*}} }

The exact mangled-constructor check should remain loose across LLVM versions.

This test also forces the rebased implementation to integrate the new attributes with handleEnzymeMarkerAttr, enzymeRegistrationKind, and the registration-global handling in PreserveNVVM. Otherwise the declaration-only constructor attribute will silently disappear—the precise failure relevant to AMReX.

2. Direct TypeAnalysis behavior test

Add enzyme/test/TypeAnalysis/skipattrs.ll.

Use two internal callees whose bodies access the same eight-byte storage as both double and i64:

  • One with "enzyme_notypeanalysis".
  • One with "enzyme_ta_norecur".

Have @caller invoke both, then run:

; RUN: %opt < %s %newLoadEnzyme -passes="print-type-analysis" \
; RUN:   -type-analysis-func=caller -S -o /dev/null | FileCheck %s

The important checks are:

; CHECK-LABEL: caller -
; CHECK-NOT: notype_union -
; CHECK-NOT: norecur_union -

That proves the attributes affect TypeAnalysis rather than merely appearing in emitted IR.

The same file should contain:

@ordinary = internal global i64 0
@ignored = internal global i64 0, !enzyme_ta_norecur !0

Propagate a floating-point type through @ordinary, access both globals, and assert that:

  • @ordinary still acquires Float@double.
  • @ignored does not.

The unannotated control global specifically catches the missing-braces regression from the review: if updateAnalysis returns for every global, the @ordinary check fails.

Optional diagnostic test

A small -Xclang -verify test should also include:

struct [[enzyme::ta_norecur]] Invalid {};
// expected-warning: attribute only applies to functions and globals

This ensures invalid placement produces a diagnostic rather than dereferencing a null dyn_cast<VarDecl>.

@markabate

Copy link
Copy Markdown
Author

@wsmoses Do you have more information on the difference between enzyme_notyeanalysis and enzyme_ta_norecur, to help with the unit tests?

My understanding is enzyme_notypeanalysis is only applied to functions and exits from the visitCallBase function here. But visitCallBase only seems to be used with Julia types here.

enzyme_ta_norecur seems to exit you from the updateAnalysis loop.

@wsmoses

wsmoses commented Aug 5, 2026

Copy link
Copy Markdown
Member

visitCallBase is called by call call instructions [that's just an explicit dispatch].

@wsmoses

wsmoses commented Aug 5, 2026

Copy link
Copy Markdown
Member

and honestly I forgot enzyme_notypeanalysis existed and it should be replaced with the standard enzyme_ta_norecur in all cases

@wsmoses

wsmoses commented Aug 5, 2026

Copy link
Copy Markdown
Member

note we can call the c++ attribute enzyme::notypeanalysis or something still

@markabate

markabate commented Aug 6, 2026

Copy link
Copy Markdown
Author

@wsmoses @ax3l I added a unit test and removed the clang attribute I was using to set the enzyme_notypeanalysis internal function attribute - so now there's just one clang attribute called enzyme_notypeanalysis, which sets enzyme_ta_norecur.

If you remove __attribute__((enzyme_notypeanalysis)) from the unit test, you get the updateAnalysis error we've been seeing:

global/u2/m/mabate/src/Enzyme/enzyme/test/Integration/ReverseMode/notypeanalysis.c:16:8: error: Enzyme: <analysis> %3 = alloca double, align 8: {[-1]:Pointer, [-1,-1]:Float@double}, intvals: {} %8 = fdiv double %6, %7: {[-1]:Float@double}, intvals: {} %14 = fadd double %11, %13: {[-1]:Float@double}, intvals: {} %5 = alloca double, align 8: {[-1]:Pointer, [-1,-1]:Float@double}, intvals: {} double %0: {[-1]:Float@double}, intvals: {} double %1: {[-1]:Float@double}, intvals: {} %4 = alloca double, align 8: {[-1]:Pointer, [-1,-1]:Float@double}, intvals: {} %6 = load double, ptr %3, align 8: {[-1]:Float@double}, intvals: {} %7 = load double, ptr %4, align 8: {[-1]:Float@double}, intvals: {} %9 = load double, ptr %3, align 8: {[-1]:Float@double}, intvals: {} @dPtr = internal global %union.DoublePtr zeroinitializer, align 8: {[-1]:Pointer, [-1,-1]:Pointer, [-1,-1,0]:Float@double}, intvals: {} %10 = load ptr, ptr @dPtr, align 8: {[-1]:Pointer, [-1,0]:Float@double}, intvals: {} %11 = load double, ptr %5, align 8: {[-1]:Float@double}, intvals: {} %12 = load i64, ptr @dPtr, align 8: {[-1]:Integer}, intvals: {} %13 = sitofp i64 %12 to double: {[-1]:Float@double}, intvals: {} </analysis> Illegal updateAnalysis prev:{[-1]:Pointer, [-1,-1]:Pointer, [-1,-1,0]:Float@double} new: {[-1,-1]:Integer} val: @dPtr = internal global %union.DoublePtr zeroinitializer, align 8 origin= %12 = load i64, ptr @dPtr, align 8 16 | double divide(double x, double y) {

I'm still wondering if there's a way Enzyme could gracefully handle situations like this. Could Enzyme leave some types undecided until the activity analysis happens? We can also discuss more at the next design meeting.

@wsmoses

wsmoses commented Aug 8, 2026

Copy link
Copy Markdown
Member

yeah there are ways we can try to improve support even without this -- but let's get in one step at a time

@wsmoses
wsmoses enabled auto-merge August 8, 2026 19:13
@wsmoses

wsmoses commented Aug 8, 2026

Copy link
Copy Markdown
Member

also @markabate can you fix the format and remove the old enzyme_notypeanalysis references?

@markabate

Copy link
Copy Markdown
Author

@wsmoses It looks like the enzyme_notypeanalysis annotation was only used in TraceInterface and TraceGenerator:

newTraceFunction->addFnAttr("enzyme_notypeanalysis");
freeTraceFunction->addFnAttr("enzyme_notypeanalysis");
getTraceFunction->addFnAttr("enzyme_notypeanalysis");
getChoiceFunction->addFnAttr("enzyme_notypeanalysis");
insertCallFunction->addFnAttr("enzyme_notypeanalysis");
insertChoiceFunction->addFnAttr("enzyme_notypeanalysis");
insertArgumentFunction->addFnAttr("enzyme_notypeanalysis");
insertReturnFunction->addFnAttr("enzyme_notypeanalysis");
insertFunctionFunction->addFnAttr("enzyme_notypeanalysis");
insertChoiceGradientFunction->addFnAttr("enzyme_notypeanalysis");
insertArgumentGradientFunction->addFnAttr("enzyme_notypeanalysis");
hasCallFunction->addFnAttr("enzyme_notypeanalysis");
hasChoiceFunction->addFnAttr("enzyme_notypeanalysis");

Attribute::get(call.getContext(), "enzyme_notypeanalysis"));

if (ci->hasFnAttribute("enzyme_notypeanalysis")) {

Do you think this is safe to remove?

auto-merge was automatically disabled August 13, 2026 20:51

Head branch was pushed to by a user without write access

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