MLIR 24.0.0git
ABIRewriteContext.h
Go to the documentation of this file.
1//===- ABIRewriteContext.h - Dialect-specific ABI rewriting -----*- 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// This file defines ABIRewriteContext, the abstract interface for dialect-
10// specific ABI lowering rewrites. Each MLIR dialect that wants ABI lowering
11// (CIR, FIR, etc.) provides a concrete subclass.
12//
13// ABIRewriteContext consumes ABI classification results and drives the
14// creation of lowered function signatures, argument coercions, and call
15// site rewrites using dialect-specific operations.
16//
17//===----------------------------------------------------------------------===//
18
19#ifndef MLIR_ABI_ABIREWRITECONTEXT_H
20#define MLIR_ABI_ABIREWRITECONTEXT_H
21
22#include "mlir/IR/Builders.h"
23#include "mlir/IR/Operation.h"
24#include "mlir/IR/Types.h"
25#include "mlir/IR/Value.h"
27#include "llvm/ADT/STLExtras.h"
28#include "llvm/Support/Alignment.h"
29
30#include <cassert>
31
32namespace mlir {
33namespace abi {
34
35/// Classification of how a single argument or return value should be
36/// passed at the ABI level.
37///
38/// This is a dialect-agnostic representation. It mirrors the kinds
39/// found in the LLVM ABI library and in CIR's ABIArgInfo, but does
40/// not depend on either.
41enum class ArgKind : uint8_t {
42 /// Pass directly in registers, possibly coerced to a different type.
44
45 /// Like Direct, but with a sign/zero extension attribute.
47
48 /// Pass indirectly via a pointer (sret for returns, byval for args).
50
51 /// Pass a pointer to the caller's object with no copy implied (byref). The
52 /// callee must not write through it. Arguments only.
54
55 /// Ignore (void return, empty struct).
57
58 /// Expand an aggregate into its constituent scalar fields.
60};
61
62/// Describes how a single argument or return value is passed after ABI
63/// lowering.
66
67 /// The ABI-coerced type, if different from the original. Null means
68 /// use the original type.
69 Type coercedType = nullptr;
70
71 /// For Indirect and IndirectAliased: alignment of the pointed-to object.
72 llvm::Align indirectAlign = llvm::Align(1);
73
74 /// For Extend: whether to sign-extend (true) or zero-extend (false).
75 bool signExtend = false;
76
77 /// For Direct: whether a struct coercion can be flattened into
78 /// individual register-width arguments.
79 bool canFlatten = true;
80
81 /// For Indirect: whether the callee gets ownership (byval).
82 bool byVal = false;
83
84 /// For Indirect and IndirectAliased: target address space of the pointer
85 /// passed in place of the value. Zero is the default address space.
86 unsigned indirectAddrSpace = 0;
87
88 /// For Direct with coercion: the byte offset within the original aggregate
89 /// at which the coerced value lives. Non-zero when the low eightbyte is
90 /// NO_CLASS and the value is carried in a later eightbyte (x86-64 SysV).
91 unsigned directOffset = 0;
92
93 /// For Direct: the alignment of the argument's stack slot when the value
94 /// is passed in memory, if the ABI sets one apart from the type's.
95 llvm::MaybeAlign directAlign;
96
97 /// How many integer and vector argument registers the value occupies. Both
98 /// zero means it travels in memory, which is also what a target whose
99 /// classifier does not record the demand reports.
100 unsigned neededIntRegs = 0;
101 unsigned neededSseRegs = 0;
102
103 /// Whether the value is passed as-is, so a rewriter can leave it alone.
104 /// Only an uncoerced Direct with no stack-slot alignment qualifies.
105 /// Extend counts as needing a rewrite even though it only adds an
106 /// attribute, because the attribute changes observable behavior, and a
107 /// Direct with a stack-slot alignment does too because it needs
108 /// llvm.alignstack.
109 bool isPassThrough() const {
111 }
112
113 /// Whether two classifications describe the same wire format. Every field
114 /// participates, so a field added above must be added here as well.
115 bool operator==(const ArgClassification &other) const {
116 return kind == other.kind && coercedType == other.coercedType &&
117 indirectAlign == other.indirectAlign &&
118 signExtend == other.signExtend && canFlatten == other.canFlatten &&
119 byVal == other.byVal &&
121 directOffset == other.directOffset &&
122 directAlign == other.directAlign &&
123 neededIntRegs == other.neededIntRegs &&
125 }
126
128 return getDirect(/*coerced=*/nullptr, /*offset=*/0);
129 }
130
131 static ArgClassification getDirect(Type coerced, unsigned offset,
132 llvm::MaybeAlign align = std::nullopt) {
133 // isPassThrough ignores directOffset, so an offset with no coerced
134 // type to read at it would be silently ignored.
135 assert((!offset || coerced) &&
136 "a direct offset needs a coerced type to read at it");
139 c.coercedType = coerced;
140 c.directOffset = offset;
141 c.directAlign = align;
142 return c;
143 }
144
148 return c;
149 }
150
151 static ArgClassification getIndirect(llvm::Align align, bool byVal,
152 unsigned addrSpace) {
155 c.indirectAlign = align;
156 c.byVal = byVal;
157 c.indirectAddrSpace = addrSpace;
158 return c;
159 }
160
161 static ArgClassification getIndirectAliased(llvm::Align align,
162 unsigned addrSpace) {
165 c.indirectAlign = align;
166 c.indirectAddrSpace = addrSpace;
167 return c;
168 }
169
170 static ArgClassification getExtend(Type coerced, bool signExt) {
173 c.coercedType = coerced;
174 c.signExtend = signExt;
175 return c;
176 }
177};
178
179/// Holds the full ABI classification for a function: return type and
180/// all arguments.
184
185 /// Whether the classified return type was the source language's void.
186 ///
187 /// A void return classifies as Ignore, and so does a return the ABI drops,
188 /// such as an empty record. The two need opposite treatment: void is
189 /// already its own wire form, while a dropped record return has to be
190 /// rewritten to one. returnInfo alone cannot tell them apart, so whoever
191 /// produces the classification records it here, next to the return type it
192 /// came from. A consumer that re-derived it from something else could pair
193 /// a classification with the wrong answer, and reading a dropped return as
194 /// void means silently skipping the rewrite it needs.
195 ///
196 /// Left false when unknown, which costs a needless rewrite rather than a
197 /// skipped one.
198 bool returnsVoid = false;
199
200 /// Whether any value in the signature is passed differently from how it is
201 /// written, so a rewriter has work to do.
202 bool needsRewrite() const {
203 if (!returnsVoid && !returnInfo.isPassThrough())
204 return true;
205 return !llvm::all_of(argInfos, [](const ArgClassification &ac) {
206 return ac.isPassThrough();
207 });
208 }
209};
210
211/// ABIRewriteContext is the abstract interface that each dialect
212/// implements to perform ABI-specific rewrites on its operations.
213///
214/// The pass orchestrator calls these methods after ABI classification
215/// to rewrite function definitions and call sites.
217public:
218 virtual ~ABIRewriteContext() = default;
219
220 /// Rewrite a function definition to use ABI-lowered types.
221 ///
222 /// This creates a new function with the lowered signature, rewrites
223 /// the function body to adapt between the ABI types and the
224 /// original high-level types, and replaces the original function.
225 ///
226 /// \param funcOp The function to rewrite (via FunctionOpInterface).
227 /// \param fc The ABI classification for this function.
228 /// \param builder The OpBuilder to use for modifications.
229 /// \returns success() if the function was rewritten.
230 virtual LogicalResult
231 rewriteFunctionDefinition(FunctionOpInterface funcOp,
232 const FunctionClassification &fc,
233 OpBuilder &builder) = 0;
234
235 /// Rewrite a call operation to match the callee's ABI-lowered
236 /// signature.
237 ///
238 /// This coerces arguments, handles indirect returns (sret), and
239 /// adapts the call result back to the original high-level type.
240 ///
241 /// \param callOp The call operation to rewrite.
242 /// \param fc The ABI classification for the callee.
243 /// \param builder The OpBuilder to use for modifications.
244 /// \returns success() if the call was rewritten.
245 virtual LogicalResult rewriteCallSite(Operation *callOp,
246 const FunctionClassification &fc,
247 OpBuilder &builder) = 0;
248
249 /// Rewrite a single "fetch the next vararg" operation (e.g. C `va_arg`) to
250 /// match how \p ac says the fetched type is passed at the ABI level.
251 ///
252 /// \p ac classifies only the one type being fetched, in isolation, with the
253 /// whole register budget available. A vararg fetch advances a runtime
254 /// cursor (the platform va_list) through registers and then memory, so
255 /// whether a given fetch lands in a register depends on how much of the
256 /// budget earlier variadic arguments already consumed at run time, not on
257 /// the fetch's static position.
258 ///
259 /// An implementation may erase \p vaArgOp and replace its result, so a
260 /// caller walking the IR must collect the fetches before rewriting any of
261 /// them.
262 ///
263 /// The default implementation reports failure, so a dialect that has not
264 /// implemented vararg fetches does not need to override this. An overrider
265 /// that fails is responsible for emitting its own diagnostic.
266 ///
267 /// \param vaArgOp The fetch to rewrite. May be erased.
268 /// \param ac The ABI classification of the fetched type.
269 /// \param builder The OpBuilder to use for modifications.
270 /// \returns success() if the operation was rewritten.
271 virtual LogicalResult rewriteVAArg(Operation *vaArgOp,
272 const ArgClassification &ac,
273 OpBuilder &builder) {
274 return failure();
275 }
276
277 /// Return the dialect namespace this context handles (e.g. "cir").
278 virtual StringRef getDialectNamespace() const = 0;
279};
280
281} // namespace abi
282} // namespace mlir
283
284#endif // MLIR_ABI_ABIREWRITECONTEXT_H
This class helps build Operations.
Definition Builders.h:210
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
ABIRewriteContext is the abstract interface that each dialect implements to perform ABI-specific rewr...
virtual StringRef getDialectNamespace() const =0
Return the dialect namespace this context handles (e.g. "cir").
virtual LogicalResult rewriteVAArg(Operation *vaArgOp, const ArgClassification &ac, OpBuilder &builder)
Rewrite a single "fetch the next vararg" operation (e.g.
virtual ~ABIRewriteContext()=default
virtual LogicalResult rewriteFunctionDefinition(FunctionOpInterface funcOp, const FunctionClassification &fc, OpBuilder &builder)=0
Rewrite a function definition to use ABI-lowered types.
virtual LogicalResult rewriteCallSite(Operation *callOp, const FunctionClassification &fc, OpBuilder &builder)=0
Rewrite a call operation to match the callee's ABI-lowered signature.
ArgKind
Classification of how a single argument or return value should be passed at the ABI level.
@ Indirect
Pass indirectly via a pointer (sret for returns, byval for args).
@ IndirectAliased
Pass a pointer to the caller's object with no copy implied (byref).
@ Extend
Like Direct, but with a sign/zero extension attribute.
@ Expand
Expand an aggregate into its constituent scalar fields.
@ Ignore
Ignore (void return, empty struct).
@ Direct
Pass directly in registers, possibly coerced to a different type.
Include the generated interface declarations.
Describes how a single argument or return value is passed after ABI lowering.
static ArgClassification getIgnore()
bool canFlatten
For Direct: whether a struct coercion can be flattened into individual register-width arguments.
bool signExtend
For Extend: whether to sign-extend (true) or zero-extend (false).
bool isPassThrough() const
Whether the value is passed as-is, so a rewriter can leave it alone.
unsigned indirectAddrSpace
For Indirect and IndirectAliased: target address space of the pointer passed in place of the value.
llvm::MaybeAlign directAlign
For Direct: the alignment of the argument's stack slot when the value is passed in memory,...
static ArgClassification getDirect()
bool operator==(const ArgClassification &other) const
Whether two classifications describe the same wire format.
static ArgClassification getIndirect(llvm::Align align, bool byVal, unsigned addrSpace)
Type coercedType
The ABI-coerced type, if different from the original.
unsigned directOffset
For Direct with coercion: the byte offset within the original aggregate at which the coerced value li...
bool byVal
For Indirect: whether the callee gets ownership (byval).
static ArgClassification getIndirectAliased(llvm::Align align, unsigned addrSpace)
unsigned neededIntRegs
How many integer and vector argument registers the value occupies.
llvm::Align indirectAlign
For Indirect and IndirectAliased: alignment of the pointed-to object.
static ArgClassification getDirect(Type coerced, unsigned offset, llvm::MaybeAlign align=std::nullopt)
static ArgClassification getExtend(Type coerced, bool signExt)
Holds the full ABI classification for a function: return type and all arguments.
bool returnsVoid
Whether the classified return type was the source language's void.
bool needsRewrite() const
Whether any value in the signature is passed differently from how it is written, so a rewriter has wo...
SmallVector< ArgClassification > argInfos