Enzyme main
Loading...
Searching...
No Matches
EnzymeCallMarkers.h
Go to the documentation of this file.
1//===- EnzymeCallMarkers.h - The __enzyme_* call grammar --------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// The arguments of an `__enzyme_autodiff`/`__enzyme_fwddiff` call are a small
10// language: marker globals that either name the activity of the argument after
11// them or configure the call as a whole, the arguments they speak for, and the
12// shadows that go with those.
13//
14// More than one place has to read it -- the LLVM pass that lowers the call, and
15// the MLIR raising that turns it into an enzyme.autodiff/enzyme.fwddiff op --
16// and a marker read as an argument, or an argument read as a marker, is a
17// silently wrong derivative rather than an error. So the names, what each one
18// takes, and where the shadows sit are written here once, over no IR in
19// particular, and each reader drives its own walk with them.
20//
21//===----------------------------------------------------------------------===//
22
23#ifndef ENZYME_CALL_MARKERS_H
24#define ENZYME_CALL_MARKERS_H
25
26#include "llvm/ADT/StringRef.h"
27#include "llvm/ADT/StringSwitch.h"
28
29#include <optional>
30
31namespace enzyme_markers {
32
33/// What a marker does to the call it appears in.
34enum class MarkerRole {
35 /// Names the activity of the argument that follows it.
37 /// Says the shadows come after all of the primals rather than beside each
38 /// one, in the same order.
40 /// Configures the call rather than an argument: nothing follows it that is
41 /// an argument.
43 /// Changes how the argument that follows is read without naming an activity.
45};
46
47} // namespace enzyme_markers
48
49/// Potential differentiable argument classifications
50enum class DIFFE_TYPE {
51 OUT_DIFF = 0, // add differential to an output struct. Only for scalar values
52 // in ReverseMode variants.
53 DUP_ARG = 1, // duplicate the argument and store differential inside.
54 // For references, pointers, or integers in ReverseMode variants.
55 // For all types in ForwardMode variants.
56 CONSTANT = 2, // no differential. Usable everywhere.
57 DUP_NONEED = 3 // duplicate this argument and store differential inside, but
58 // don't need the forward. Same as DUP_ARG otherwise.
59};
60
61namespace enzyme_markers {
62
63struct MarkerInfo {
65 /// Set where the role is Activity, and for the few call flags that also fix
66 /// the activity of what they name.
67 std::optional<DIFFE_TYPE> activity;
68 /// Operands the marker itself takes, before whatever follows it.
69 unsigned extraOperands;
70};
71
72/// The one description of the `__enzyme_*` argument markers.
73///
74/// Returns nothing for a name this does not know -- callers report that as the
75/// error it is rather than carrying on, since guessing at an unknown marker is
76/// how an argument gets read as a marker.
77inline std::optional<MarkerInfo> lookupEnzymeMarker(llvm::StringRef name) {
78 auto activity = [](DIFFE_TYPE a, unsigned extra = 0) {
79 return MarkerInfo{MarkerRole::Activity, a, extra};
80 };
81 auto flag = [](unsigned extra = 0) {
82 return MarkerInfo{MarkerRole::CallFlag, std::nullopt, extra};
83 };
84 auto flagWithActivity = [](DIFFE_TYPE a, unsigned extra) {
85 return MarkerInfo{MarkerRole::CallFlag, a, extra};
86 };
87
88 return llvm::StringSwitch<std::optional<MarkerInfo>>(name)
89 // Activities.
90 .Case("enzyme_const", activity(DIFFE_TYPE::CONSTANT))
91 .Case("enzyme_dup", activity(DIFFE_TYPE::DUP_ARG))
92 .Case("enzyme_dupnoneed", activity(DIFFE_TYPE::DUP_NONEED))
93 .Case("enzyme_out", activity(DIFFE_TYPE::OUT_DIFF))
94 // Vector activities, each followed by the offset between lanes.
95 .Case("enzyme_dupv", activity(DIFFE_TYPE::DUP_ARG, 1))
96 .Case("enzyme_dupnoneedv", activity(DIFFE_TYPE::DUP_NONEED, 1))
97 // Where the shadows are.
98 .Case("enzyme_interleave",
99 MarkerInfo{MarkerRole::Interleave, std::nullopt, 0})
100 // How the argument after is read.
101 .Case("enzyme_not_overwritten",
102 MarkerInfo{MarkerRole::Modifier, std::nullopt, 0})
103 // Whole-call flags that take nothing.
104 .Case("enzyme_noret", flag())
105 .Case("enzyme_nofree", flag())
106 .Case("enzyme_runtime_activity", flag())
107 .Case("enzyme_strong_zero", flag())
108 .Case("enzyme_primal_return", flag())
109 .Case("enzyme_const_return", flag())
110 .Case("enzyme_active_return", flag())
111 .Case("enzyme_dup_return", flag())
112 // Whole-call flags that take a value.
113 .Case("enzyme_byref", flag(1))
114 .Case("enzyme_allocated", flag(1))
115 .Case("enzyme_tape", flag(1))
116 .Case("enzyme_width", flag(1))
117 .Case("enzyme_interface", flag(1))
118 .Case("enzyme_active_rand_var", flag(1))
119 .Case("enzyme_trace", flagWithActivity(DIFFE_TYPE::CONSTANT, 1))
120 .Case("enzyme_duptrace", flagWithActivity(DIFFE_TYPE::CONSTANT, 1))
121 .Case("enzyme_likelihood", flagWithActivity(DIFFE_TYPE::CONSTANT, 1))
122 .Case("enzyme_observations", flagWithActivity(DIFFE_TYPE::CONSTANT, 1))
123 .Case("enzyme_duplikelihood", flagWithActivity(DIFFE_TYPE::DUP_ARG, 2))
124 .Default(std::nullopt);
125}
126
127/// Whether an activity is one that a shadow goes with.
131
132/// Where the primals stop and the shadows start.
134 /// One past the last operand that can be a primal.
135 unsigned primalEnd;
136 /// The first shadow operand, meaningful only when `interleaved`.
137 unsigned shadowStart;
139};
140
141/// Find `enzyme_interleave` among the operands, which has to be done before
142/// walking them: it says where the shadows are, and the walk needs that from
143/// the first argument on.
144///
145/// `markerAt(i)` gives the marker name operand `i` reads, if it reads one.
146template <typename MarkerAtFn>
147InterleaveSplit findEnzymeInterleave(unsigned first, unsigned count,
148 MarkerAtFn markerAt) {
149 for (unsigned i = first; i < count; ++i) {
150 std::optional<llvm::StringRef> name = markerAt(i);
151 if (name && *name == "enzyme_interleave")
152 return {/*primalEnd=*/i, /*shadowStart=*/i + 1, /*interleaved=*/true};
153 }
154 return {/*primalEnd=*/count, /*shadowStart=*/0, /*interleaved=*/false};
155}
156
157} // namespace enzyme_markers
158
159#endif // ENZYME_CALL_MARKERS_H
DIFFE_TYPE
Potential differentiable argument classifications.
MarkerRole
What a marker does to the call it appears in.
@ CallFlag
Configures the call rather than an argument: nothing follows it that is an argument.
@ Modifier
Changes how the argument that follows is read without naming an activity.
@ Interleave
Says the shadows come after all of the primals rather than beside each one, in the same order.
@ Activity
Names the activity of the argument that follows it.
bool markerActivityTakesShadow(DIFFE_TYPE a)
Whether an activity is one that a shadow goes with.
InterleaveSplit findEnzymeInterleave(unsigned first, unsigned count, MarkerAtFn markerAt)
Find enzyme_interleave among the operands, which has to be done before walking them: it says where th...
std::optional< MarkerInfo > lookupEnzymeMarker(llvm::StringRef name)
The one description of the __enzyme_* argument markers.
Where the primals stop and the shadows start.
unsigned shadowStart
The first shadow operand, meaningful only when interleaved.
unsigned primalEnd
One past the last operand that can be a primal.
std::optional< DIFFE_TYPE > activity
Set where the role is Activity, and for the few call flags that also fix the activity of what they na...
unsigned extraOperands
Operands the marker itself takes, before whatever follows it.