clang 24.0.0git
CIRABIRewriteContext.cpp
Go to the documentation of this file.
1//===- CIRABIRewriteContext.cpp - CIR ABI rewrite context ----------------===//
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
10#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
11#include "mlir/IR/Builders.h"
12#include "mlir/IR/Dominance.h"
19#include "llvm/ADT/STLExtras.h"
20#include "llvm/IR/Attributes.h"
21#include "llvm/Support/MathExtras.h"
22#include <algorithm>
23#include <array>
24#include <utility>
25
26using namespace cir;
27using namespace mlir;
28using namespace mlir::abi;
29
30// This rewrite context supports the Direct (with or without coercion),
31// Extend, Ignore, Indirect-return (sret), Indirect-argument (byval and
32// non-byval), and Expand (struct flattening) classifications.
33//
34// An Indirect argument is byval or not, following its classification's
35// byVal flag. byval is a by-value parameter the ABI passes in memory rather
36// than registers, usually for its size. Non-byval is a by-value parameter
37// whose type cannot be copied freely, because it has a non-trivial copy
38// constructor, move constructor, or destructor, so the callee works on the
39// caller's own object rather than a copy.
40//
41// At the call site both forward the caller's storage, since byval is copied
42// on the caller's side of the call. byval falls back to a slot of its own
43// when the operand's storage cannot be handed on. At the callee, byval fills
44// the CIRGen param slot with a copy of the incoming object, while non-byval
45// rewires that slot to the incoming pointer so the body mutates the caller's
46// storage in place.
47//
48// For Expand, the single struct argument is replaced by N scalar arguments
49// (one per field). At the callee, the N field block arguments are stored
50// directly into the parameter's own alloca (the CIRGen spill slot). At the
51// call site, the struct operand is decomposed into its fields by reading
52// each member from the source alloca (get_member + load) when the operand is
53// a load of an alloca, or via cir.extract_member otherwise.
54//
55// For Direct + canFlatten (where the coerced type is a multi-field struct), the
56// coerced struct is similarly passed as N separate arguments, one per field.
57// The callee stores the N scalar block args into one slot sized and aligned for
58// both the coerced struct and the original argument type, which the body reads.
59// The call site reads each field of the coerced struct out of memory holding
60// the operand as a separate call argument.
61
62namespace {
63
64/// Return the coerced RecordType for a Direct classification that should be
65/// flattened into individual scalar arguments, or a null type if the
66/// classification does not call for flattening.
67///
68/// Flattening applies when all four conditions hold:
69/// 1. The classification is Direct with a non-null coercedType.
70/// 2. canFlatten is set.
71/// 3. The coercedType is a struct (not a union).
72/// 4. The struct has more than one field (single-field structs are already
73/// scalar; flattening them produces no benefit and classic CodeGen skips
74/// them for the same reason).
75cir::RecordType getFlattenedCoercedType(const ArgClassification &ac) {
76 if (ac.kind != ArgKind::Direct || !ac.coercedType || !ac.canFlatten)
77 return {};
78 auto recTy = dyn_cast<cir::RecordType>(ac.coercedType);
79 if (!recTy || !recTy.isStruct() || recTy.getNumElements() <= 1)
80 return {};
81 return recTy;
82}
83
84/// Build the new argument-type list for a function whose ABI classification
85/// is \p fc. Handles Direct (with or without coercion), Extend, Ignore,
86/// Indirect (byval and non-byval), and Expand (struct flattening) arguments.
87/// The sret return pointer, when present, is prepended by
88/// rewriteFunctionDefinition rather than here.
89mlir::LogicalResult
90buildNewArgTypes(ArrayRef<mlir::Type> oldArgTypes,
91 const FunctionClassification &fc,
92 SmallVectorImpl<mlir::Type> &newArgTypes,
93 function_ref<mlir::InFlightDiagnostic()> emitError) {
94 assert(newArgTypes.empty() && "expected an empty output vector");
95 newArgTypes.reserve(oldArgTypes.size());
96 for (auto [idx, ac] : llvm::enumerate(fc.argInfos)) {
97 mlir::Type origTy = oldArgTypes[idx];
98 switch (ac.kind) {
99 case ArgKind::Direct:
100 // Direct with canFlatten and a struct coerced type: push one wire type
101 // per field of the coerced struct rather than the struct itself.
102 // Single-field coerced structs fall through to the non-flatten path —
103 // the struct is already scalar-sized and flattening adds no value.
104 if (cir::RecordType flatTy = getFlattenedCoercedType(ac)) {
105 llvm::append_range(newArgTypes, flatTy.getMembers());
106 } else {
107 // Direct with a coerced type: the lowered signature uses the coerced
108 // type; the body still expects origTy and insertArgCoercion recovers it
109 // from a coercion slot. Direct without coercion is a pass-through.
110 newArgTypes.push_back(ac.coercedType ? ac.coercedType : origTy);
111 }
112 break;
113 case ArgKind::Ignore:
114 break;
115 case ArgKind::Expand: {
116 // Flatten the struct into one wire argument per field. The
117 // reassembly in the callee body and the decomposition at the call
118 // site are handled by insertArgCoercion and rewriteCallSite.
119 auto recTy = cast<cir::RecordType>(origTy);
120 assert(recTy.isStruct() &&
121 "Expand classification requires a struct type, not a union");
122 assert(!recTy.getMembers().empty() &&
123 "Expand classification requires at least one struct field");
124 llvm::append_range(newArgTypes, recTy.getMembers());
125 break;
126 }
127 case ArgKind::Extend:
128 // Extend keeps the original (narrow) type in the signature; the
129 // sign/zero extension is communicated to LLVM via the llvm.signext /
130 // llvm.zeroext arg attribute, attached separately below. Any
131 // coercedType the classifier set on the Extend ArgClassification is
132 // informational (typically the register-width type the value gets
133 // extended to in registers) but does not change the CIR signature.
134 newArgTypes.push_back(origTy);
135 break;
136 case ArgKind::Indirect:
137 newArgTypes.push_back(cir::PointerType::get(origTy));
138 break;
139 }
140 }
141 return mlir::success();
142}
143
144/// Compute the new return type for a function whose return classification
145/// is \p retInfo. Direct returns keep (or coerce to) their type, Ignore and
146/// Indirect (sret) returns become void, Extend keeps its type; Expand emits
147/// an error.
148mlir::Type
149computeNewReturnType(mlir::Type origRetTy, const ArgClassification &retInfo,
150 mlir::MLIRContext *ctx,
151 function_ref<mlir::InFlightDiagnostic()> emitError) {
152 switch (retInfo.kind) {
153 case ArgKind::Direct:
154 // Direct return with a coerced type uses the coerced type on the wire;
155 // the rewriter inserts a coercion before each cir.return.
156 return retInfo.coercedType ? retInfo.coercedType : origRetTy;
157 case ArgKind::Ignore:
158 return cir::VoidType::get(ctx);
159 case ArgKind::Expand:
160 emitError() << "Expand return is not allowed (classic codegen rejects "
161 << "it in EmitFunctionEpilog)";
162 return nullptr;
163 case ArgKind::Extend:
164 // Same convention as Extend args: keep the original return type in the
165 // signature; the sign/zero extension is communicated via the
166 // llvm.signext / llvm.zeroext res attribute attached separately below.
167 return origRetTy;
168 case ArgKind::Indirect:
169 // sret: the value is returned through a pointer argument that the ABI
170 // synthesizes (rewriteFunctionDefinition prepends it to the argument
171 // list); it is not part of the source-level signature, so the wire
172 // return type becomes void.
173 return cir::VoidType::get(ctx);
174 }
175 llvm_unreachable("all ArgKind cases handled");
176}
177
178/// Create a typed poison constant to stand in for a value the body of a
179/// function (or the result of a call) still references but whose ABI
180/// classification is Ignore. Using poison is honest -- the value is
181/// genuinely unused at the ABI boundary -- and avoids a fake alloca+load
182/// pattern that would suggest we have a value when we don't.
183mlir::Value createIgnoredValue(mlir::OpBuilder &builder, mlir::Location loc,
184 mlir::Type ty) {
185 return cir::ConstantOp::create(builder, loc, ty, cir::PoisonAttr::get(ty));
186}
187
188/// Whether \p coercedTy is wider than the type a value of type \p origTy has
189/// in memory, so the coerced value passes bits the value does not define and
190/// cannot be noundef. This is the comparison classic CodeGen makes. A bool
191/// vector's memory type is its storage integer, one bit per element and at
192/// least a byte wide, not its power-of-two size.
193static bool isCoercionWiderThanMemory(mlir::Type origTy, mlir::Type coercedTy,
194 const mlir::DataLayout &dl) {
195 uint64_t memoryBits = dl.getTypeSizeInBits(origTy).getFixedValue();
196 if (auto vecTy = mlir::dyn_cast<cir::VectorType>(origTy);
197 vecTy && mlir::isa<cir::BoolType>(vecTy.getElementType()))
198 memoryBits = vecTy.getBoolStorageWidth();
199 return dl.getTypeSizeInBits(coercedTy).getFixedValue() > memoryBits;
200}
201
202/// Whether updateArgAttrs has anything to change: an arg that is Ignore (its
203/// slot is dropped), Extend (needs llvm.signext / llvm.zeroext), Indirect
204/// (needs the pointer attributes), Expand (changes the argument count), or a
205/// coerced Direct, which may be flattened or may lose llvm.noundef.
206static bool needsArgAttrUpdate(const FunctionClassification &fc) {
207 return llvm::any_of(fc.argInfos, [](const ArgClassification &ac) {
208 return ac.kind == ArgKind::Ignore || ac.kind == ArgKind::Extend ||
209 ac.kind == ArgKind::Indirect || ac.kind == ArgKind::Expand ||
210 (ac.kind == ArgKind::Direct && ac.coercedType);
211 });
212}
213
214/// Build an updated arg_attrs ArrayAttr that drops Ignore'd args, adds
215/// llvm.signext / llvm.zeroext on Extend args, adds the pointer attributes
216/// for Indirect args in place of llvm.nofpclass, drops llvm.noundef from a
217/// coerced Direct arg that passes undefined bits, gives each field of a
218/// Direct+canFlatten argument the argument's attributes, and gives each field
219/// of an Expand argument an empty dict. Direct and Extend slots keep their
220/// other existing attributes. \p origArgTypes provides the pre-rewrite type
221/// for each arg slot.
222mlir::ArrayAttr updateArgAttrs(mlir::MLIRContext *ctx, mlir::ModuleOp module,
223 ArrayRef<mlir::Type> origArgTypes,
224 mlir::ArrayAttr existingArgAttrs,
225 const FunctionClassification &fc,
226 const mlir::DataLayout &dl) {
227 mlir::Builder builder(ctx);
229 newArgAttrs.reserve(fc.argInfos.size());
230 for (auto [oldIdx, ac] : llvm::enumerate(fc.argInfos)) {
231 if (ac.kind == ArgKind::Ignore)
232 continue;
233 mlir::DictionaryAttr existing = builder.getDictionaryAttr({});
234 if (existingArgAttrs && oldIdx < existingArgAttrs.size())
235 existing = mlir::cast<mlir::DictionaryAttr>(existingArgAttrs[oldIdx]);
236 if (ac.kind == ArgKind::Direct && ac.coercedType &&
237 isCoercionWiderThanMemory(origArgTypes[oldIdx], ac.coercedType, dl)) {
238 mlir::NamedAttrList attrs(existing);
239 attrs.erase(mlir::LLVM::LLVMDialect::getNoUndefAttrName());
240 existing = attrs.getDictionary(ctx);
241 }
242 if (cir::RecordType flatTy = getFlattenedCoercedType(ac)) {
243 // Direct + canFlatten: as in classic CodeGen, each flattened field
244 // carries the attributes of the argument it came from.
245 newArgAttrs.append(flatTy.getNumElements(), existing);
246 } else if (ac.kind == ArgKind::Expand) {
247 // An expanded record has no attributes classic CodeGen would copy to its
248 // fields, so each field gets an empty dict.
249 auto recTy = cast<cir::RecordType>(origArgTypes[oldIdx]);
250 newArgAttrs.append(recTy.getNumElements(), builder.getDictionaryAttr({}));
251 } else if (ac.kind == ArgKind::Extend) {
252 StringRef attrName = ac.signExtend
253 ? mlir::LLVM::LLVMDialect::getSExtAttrName()
254 : mlir::LLVM::LLVMDialect::getZExtAttrName();
255 mlir::NamedAttrList attrs(existing);
256 attrs.set(attrName, builder.getUnitAttr());
257 newArgAttrs.push_back(attrs.getDictionary(ctx));
258 } else if (ac.kind == ArgKind::Indirect) {
259 // Indirect lowering hands the callee a pointer. llvm.align and
260 // llvm.noundef describe that pointer, not the bytes it points to, and
261 // llvm.nofpclass, which describes the value, does not apply to a
262 // pointer. Without byval the pointer points to the caller's own object.
263 // With byval the backend copies the pointee on the caller's side of the
264 // call, so the callee gets an object of its own even when the pointer
265 // points to the caller's own object.
266 mlir::Type pointeeTy = origArgTypes[oldIdx];
267 mlir::NamedAttrList attrs(existing);
268 attrs.erase(mlir::LLVM::LLVMDialect::getNoFPClassAttrName());
269 attrs.set(mlir::LLVM::LLVMDialect::getAlignAttrName(),
270 builder.getI64IntegerAttr(ac.indirectAlign.value()));
271 attrs.set(mlir::LLVM::LLVMDialect::getNoUndefAttrName(),
272 builder.getUnitAttr());
273 if (ac.byVal) {
274 // llvm.byval(T) records the pre-rewrite arg type because the opaque
275 // LLVM pointer cannot carry it. Classic adds llvm.noalias under
276 // -fpass-by-value-is-noalias, which CIR does not plumb through.
278 attrs.set(mlir::LLVM::LLVMDialect::getByValAttrName(),
279 mlir::TypeAttr::get(pointeeTy));
280 } else {
281 // llvm.nofreeobj says the object cannot be freed while the callee
282 // runs, which holds because the caller owns it across the call.
283 attrs.set(mlir::LLVM::LLVMDialect::getNoFreeObjAttrName(),
284 builder.getUnitAttr());
285 // llvm.dead_on_return says the object's lifetime ends in the callee,
286 // which holds unless the caller still has a destructor to run on it.
287 // A record with no layout entry has no destructor.
288 auto recTy = mlir::dyn_cast<cir::RecordType>(pointeeTy);
289 cir::RecordLayoutAttr layout =
290 recTy ? cir::tryGetRecordLayout(module, recTy.getName())
291 : cir::RecordLayoutAttr();
292 if (!layout || layout.getHasTrivialDtor())
293 attrs.set(mlir::LLVM::LLVMDialect::getDeadOnReturnAttrName(),
294 builder.getI64IntegerAttr(static_cast<int64_t>(
295 llvm::DeadOnReturnInfo().toIntValue())));
296 attrs.set(mlir::LLVM::LLVMDialect::getDereferenceableAttrName(),
297 builder.getI64IntegerAttr(
298 dl.getTypeSize(pointeeTy).getFixedValue()));
299 }
300 newArgAttrs.push_back(attrs.getDictionary(ctx));
301 } else {
302 newArgAttrs.push_back(existing);
303 }
304 }
305 return builder.getArrayAttr(newArgAttrs);
306}
307
308/// Build an updated res_attrs ArrayAttr (single entry, since CIR funcs have
309/// at most one result) that adds llvm.signext / llvm.zeroext on an Extend
310/// return and drops llvm.noundef from a coerced return that passes undefined
311/// bits. Preserves any other existing res attributes. Returns
312/// \p existingResAttrs unchanged, which may be null, when there is nothing to
313/// add or drop.
314mlir::ArrayAttr updateResAttrs(mlir::MLIRContext *ctx,
315 mlir::ArrayAttr existingResAttrs,
316 const ArgClassification &retInfo,
317 mlir::Type origRetTy,
318 const mlir::DataLayout &dl) {
319 bool needsNoUndefDrop =
320 retInfo.kind == ArgKind::Direct && retInfo.coercedType &&
321 isCoercionWiderThanMemory(origRetTy, retInfo.coercedType, dl);
322 if (retInfo.kind != ArgKind::Extend && !needsNoUndefDrop)
323 return existingResAttrs;
324
325 mlir::NamedAttrList attrs;
326 if (existingResAttrs && !existingResAttrs.empty())
327 attrs.append(
328 mlir::cast<mlir::DictionaryAttr>(existingResAttrs[0]).getValue());
329 if (retInfo.kind == ArgKind::Extend)
330 attrs.set(retInfo.signExtend ? "llvm.signext" : "llvm.zeroext",
331 mlir::UnitAttr::get(ctx));
332 if (needsNoUndefDrop)
333 attrs.erase(mlir::LLVM::LLVMDialect::getNoUndefAttrName());
334 return mlir::ArrayAttr::get(ctx, {attrs.getDictionary(ctx)});
335}
336
337/// The number of bytes a coercion memory slot needs to hold a value of type
338/// \p ty without truncating it. For most types this is the ordinary storage
339/// size. For a _BitInt it is deliberately the value's own literal byte
340/// footprint (ceil(width/8)) rather than the wider, ABI-alignment-padded
341/// footprint a _BitInt gets as a record member (see
342/// cir::IntType::getStorageTypeWidth): this coercion is about how many bytes
343/// the *value* needs to round-trip, not how a record would lay it out, and
344/// those are genuinely different questions for a _BitInt (e.g. _BitInt(33)
345/// only needs 5 bytes here, even though it occupies 8 padded bytes as a
346/// record member).
347static uint64_t coercionByteSize(mlir::Type ty, const mlir::DataLayout &dl) {
348 if (auto intTy = mlir::dyn_cast<cir::IntType>(ty))
349 return llvm::divideCeil(intTy.getWidth(), 8);
350 return dl.getTypeSize(ty);
351}
352
353/// A \p wantTy-typed view of the storage \p base points at. When \p wantTy is
354/// \p base's own pointee type, \p base is returned unchanged and \p offset must
355/// be 0. Any other type is reached by a bitcast, preceded by a byte stride
356/// of \p offset bytes when \p offset is nonzero. The view keeps \p base's
357/// address space. Any operations created are added to \p createdOps.
358static mlir::Value
359emitViewAtOffset(mlir::OpBuilder &builder, mlir::Location loc,
360 mlir::Type wantTy, mlir::Value base,
361 SmallPtrSetImpl<mlir::Operation *> &createdOps,
362 unsigned offset) {
363 auto basePtrTy = mlir::cast<cir::PointerType>(base.getType());
364 if (wantTy == basePtrTy.getPointee()) {
365 assert(offset == 0 && "a view of the storage's own type has no offset");
366 return base;
367 }
368 mlir::ptr::MemorySpaceAttrInterface addrSpace = basePtrTy.getAddrSpace();
369 mlir::Value view = base;
370 if (offset != 0) {
371 auto u8Ty = cir::IntType::get(builder.getContext(), 8, /*isSigned=*/false);
372 auto u8PtrTy = cir::PointerType::get(u8Ty, addrSpace);
373 auto u8Base = cir::CastOp::create(builder, loc, u8PtrTy,
374 cir::CastKind::bitcast, base);
375 createdOps.insert(u8Base);
376 auto strideTy =
377 cir::IntType::get(builder.getContext(), 64, /*isSigned=*/true);
378 auto strideVal = cir::ConstantOp::create(
379 builder, loc, cir::IntAttr::get(strideTy, offset));
380 createdOps.insert(strideVal);
381 view = cir::PtrStrideOp::create(builder, loc, u8PtrTy, u8Base, strideVal);
382 createdOps.insert(view.getDefiningOp());
383 }
384 auto typedView = cir::CastOp::create(builder, loc,
385 cir::PointerType::get(wantTy, addrSpace),
386 cir::CastKind::bitcast, view);
387 createdOps.insert(typedView);
388 return typedView;
389}
390
391/// A pointer to \p pointee in the target's alloca address space, which every
392/// temporary this pass allocates has to live in.
393static cir::PointerType getAllocaPtrTy(mlir::Type pointee,
394 const mlir::DataLayout &dl) {
395 mlir::ptr::MemorySpaceAttrInterface addrSpace;
396 if (auto asAttr = mlir::dyn_cast_if_present<mlir::IntegerAttr>(
397 dl.getAllocaMemorySpace()))
398 addrSpace = cir::TargetAddressSpaceAttr::get(pointee.getContext(),
399 asAttr.getUInt());
400 return cir::PointerType::get(pointee, addrSpace);
401}
402
403/// A coercion slot for \p srcTy and \p dstTy, which must differ. It is typed
404/// as \p srcTy unless coercionByteSize says \p dstTy is larger, aligned for
405/// both, and placed at the start of \p slotBlock. \p offset, where the
406/// coerced side sits within the slot, is only checked by assertions. The
407/// alloca is added to \p createdOps.
408static cir::AllocaOp createCoercionSlot(
409 mlir::OpBuilder &builder, mlir::Location loc, mlir::Type srcTy,
410 mlir::Type dstTy, mlir::Block *slotBlock, const mlir::DataLayout &dl,
411 SmallPtrSetImpl<mlir::Operation *> &createdOps, unsigned offset) {
412 assert(srcTy != dstTy && "callers must pre-check that the types differ");
413
414 uint64_t srcAlign = dl.getTypeABIAlignment(srcTy);
415 uint64_t dstAlign = dl.getTypeABIAlignment(dstTy);
416 uint64_t allocaAlign = std::max(srcAlign, dstAlign);
417 mlir::Type slotTy = coercionByteSize(srcTy, dl) >= coercionByteSize(dstTy, dl)
418 ? srcTy
419 : dstTy;
420
421 // Sizes are compared two ways on purpose: relative size in
422 // coercionByteSize terms, which is how slotTy was picked, and capacity in
423 // getTypeSize terms, which is what the alloca is given.
424 [[maybe_unused]] mlir::Type coercedTy = ((slotTy == srcTy) ? dstTy : srcTy);
425 assert((offset == 0 ||
426 coercionByteSize(coercedTy, dl) < coercionByteSize(slotTy, dl)) &&
427 "a direct offset must land on the coerced side, the smaller one");
428 assert((offset == 0 ||
429 offset + dl.getTypeSize(coercedTy) <= dl.getTypeSize(slotTy)) &&
430 "coerce slot too small for offset access");
431 assert((offset == 0 || offset % dl.getTypeABIAlignment(coercedTy) == 0) &&
432 "a direct offset must be aligned for the coerced access");
433
434 mlir::OpBuilder::InsertionGuard guard(builder);
435 builder.setInsertionPointToStart(slotBlock);
436 auto alloca = cir::AllocaOp::create(builder, loc, getAllocaPtrTy(slotTy, dl),
437 builder.getStringAttr("coerce"),
438 builder.getI64IntegerAttr(allocaAlign));
439 createdOps.insert(alloca);
440 return alloca;
441}
442
443/// Coerce \p src into a temporary memory slot typed for \p dstTy at the
444/// current builder insertion point, and return the destination-typed pointer
445/// to that slot without loading the value back out. This is the shared
446/// memory half of emitCoercion: callers that want the whole coerced value use
447/// emitCoercion (below); callers that want to read individual members of a
448/// coerced struct (the call-site struct flattening) take the returned pointer
449/// and emit their own cir.get_member + cir.load per field. Lowers uniformly
450/// for scalar, vector, and record types.
451///
452/// The slot is sized to the larger of the two types so that neither the store
453/// nor a later load ever runs past it: the coerced ABI type can be larger
454/// than the original (e.g. a 12-byte aggregate passed as `{i64, i64}`), so
455/// accessing the destination through a source-sized slot would over-read.
456/// Alignment is max(srcAlign, dstAlign) to satisfy both accesses. The slot
457/// is written through a source-typed view and returned as a destination-typed
458/// view.
459///
460/// The temporary alloca is placed at the start of \p slotBlock, which must
461/// dominate every use of the coerced value and must be a block that ends up
462/// inside the enclosing function's entry block after any later outlining.
463///
464/// \p offset is where the coerced value sits within the larger of the two
465/// types, non-zero when the ABI passes a value in a register read from
466/// partway into its storage. The coerced type must be the smaller of the
467/// two, so one that can exceed the value it coerces, such as a multi-field
468/// register tuple, must not be given an offset.
469///
470/// Any operations the helper creates are appended to \p createdOps so the
471/// caller can leave them out when it later rewires the source value's uses,
472/// which would otherwise clobber the store's value operand.
473mlir::Value emitCoercionToMemory(mlir::OpBuilder &builder, mlir::Location loc,
474 mlir::Type dstTy, mlir::Value src,
475 mlir::Block *slotBlock,
476 const mlir::DataLayout &dl,
477 SmallPtrSetImpl<mlir::Operation *> &createdOps,
478 unsigned offset) {
479 mlir::Type srcTy = src.getType();
480 cir::AllocaOp alloca = createCoercionSlot(builder, loc, srcTy, dstTy,
481 slotBlock, dl, createdOps, offset);
482 mlir::Type slotTy = alloca.getAllocaType();
483
484 // Store through a source-typed view of the slot.
485 mlir::Value srcSlot = emitViewAtOffset(
486 builder, loc, srcTy, alloca, createdOps, srcTy == slotTy ? 0 : offset);
487 auto store = cir::StoreOp::create(builder, loc, src, srcSlot);
488 createdOps.insert(store);
489
490 // Return a destination-typed view of the slot.
491 return emitViewAtOffset(builder, loc, dstTy, alloca, createdOps,
492 dstTy == slotTy ? 0 : offset);
493}
494
495/// Cast \p src to \p dstTy when both are pointers to the same pointee, so only
496/// the address space changes. Returns null when a real coercion is needed.
497static mlir::Value
498emitAddrSpaceCoercion(mlir::OpBuilder &builder, mlir::Location loc,
499 mlir::Type dstTy, mlir::Value src,
500 SmallPtrSetImpl<mlir::Operation *> &createdOps,
501 unsigned offset) {
502 auto srcPtrTy = dyn_cast<cir::PointerType>(src.getType());
503 auto dstPtrTy = dyn_cast<cir::PointerType>(dstTy);
504 if (offset || !srcPtrTy || !dstPtrTy ||
505 srcPtrTy.getPointee() != dstPtrTy.getPointee())
506 return {};
507 assert(srcPtrTy.getAddrSpace() != dstPtrTy.getAddrSpace() &&
508 "pointers to the same pointee must differ in address space");
509 auto cast = cir::CastOp::create(builder, loc, dstTy,
510 cir::CastKind::address_space, src);
511 createdOps.insert(cast);
512 return cast;
513}
514
515/// Coerce \p src to type \p dstTy by going through memory and load the whole
516/// coerced value back out. Builds on emitCoercionToMemory, adding the final
517/// load of the destination-typed view. A pointer that only changes address
518/// space is cast instead.
519mlir::Value emitCoercion(mlir::OpBuilder &builder, mlir::Location loc,
520 mlir::Type dstTy, mlir::Value src,
521 mlir::Block *slotBlock, const mlir::DataLayout &dl,
522 SmallPtrSetImpl<mlir::Operation *> &createdOps,
523 unsigned offset) {
524 if (mlir::Value cast =
525 emitAddrSpaceCoercion(builder, loc, dstTy, src, createdOps, offset))
526 return cast;
527
528 mlir::Value dstSlot = emitCoercionToMemory(builder, loc, dstTy, src,
529 slotBlock, dl, createdOps, offset);
530 auto load = cir::LoadOp::create(builder, loc, dstSlot);
531 createdOps.insert(load);
532 return load;
533}
534
535/// Convenience overload for callers that don't need the createdOps set
536/// (e.g. call-site coercion where we don't replaceAllUsesExcept).
537mlir::Value emitCoercion(mlir::OpBuilder &builder, mlir::Location loc,
538 mlir::Type dstTy, mlir::Value src,
539 mlir::Block *slotBlock, const mlir::DataLayout &dl,
540 unsigned offset) {
541 SmallPtrSet<mlir::Operation *, 4> ignored;
542 return emitCoercion(builder, loc, dstTy, src, slotBlock, dl, ignored, offset);
543}
544
545/// The block a coercion slot's alloca belongs at the start of.
546///
547/// Normally the enclosing function's entry block, where HoistAllocas expects
548/// allocas to be. A body carrying a call is not always inside a function
549/// when this pass runs, though, because LoweringPrepare runs after it: a
550/// namespace-scope `T g = makeT();` is still in its cir.global ctor region,
551/// and an OpenACC recipe's init and destroy bodies are in regions the module
552/// owns. Those take the outermost region below the module, which dominates
553/// the whole body and travels with it when the body is outlined.
554mlir::Block *coercionSlotBlock(mlir::Operation *op) {
555 if (auto funcOp = op->getParentOfType<mlir::FunctionOpInterface>())
556 return &funcOp->getRegion(0).front();
557 mlir::Region *region = op->getParentRegion();
558 while (mlir::Region *outer = region->getParentRegion()) {
559 if (mlir::isa<mlir::ModuleOp>(outer->getParentOp()))
560 break;
561 region = outer;
562 }
563 assert(!region->empty() && "coercion slot needs a block to hold the alloca");
564 return &region->front();
565}
566
567/// \p val's defining load, if it is simple, meaning neither volatile nor
568/// atomic. Null otherwise: a non-simple load's access has to survive as
569/// written, and a call result or any other first-class value has no defining
570/// load at all.
571static cir::LoadOp maybeGetSimpleLoad(mlir::Value val) {
572 cir::LoadOp load = val.getDefiningOp<cir::LoadOp>();
573 if (!load || load.getIsVolatile() || load.getMemOrder())
574 return {};
575 return load;
576}
577
578/// \p val's defining load, if it loads a record and is not atomic. Null
579/// otherwise. The load may be volatile, and then its bytes may only be read
580/// through a volatile copy, never in place.
581static cir::LoadOp maybeGetCopyableRecordLoad(mlir::Value val) {
582 cir::LoadOp load = val.getDefiningOp<cir::LoadOp>();
583 if (!load || load.getMemOrder() ||
584 !mlir::isa<cir::RecordType>(load.getType()))
585 return {};
586 return load;
587}
588
589/// Emits an error and returns failure if \p val, an operand of \p user, is an
590/// atomic load of a record. The record's bytes are read through a copy or in
591/// place, neither of which keeps the load atomic.
592static mlir::LogicalResult rejectAtomicRecordLoad(mlir::Operation *user,
593 mlir::Value val) {
594 auto load = val.getDefiningOp<cir::LoadOp>();
595 if (!load || !load.getMemOrder() ||
596 !mlir::isa<cir::RecordType>(load.getType()))
597 return mlir::success();
598 return user->emitOpError()
599 << "atomic load of a record operand not yet implemented in "
600 "CallConvLowering";
601}
602
603/// Whether the storage the record load \p load reads is an alloca that holds
604/// \p coercedTy at \p offset. With no offset, the coerced type's alloc size
605/// has to fit in the record's. At an offset, the coerced value has to end
606/// within the record's alloc size.
607static bool canReadInPlace(cir::LoadOp load, mlir::Type coercedTy,
608 const cir::CIRDataLayout &dl, unsigned offset) {
609 if (!cir::getUnderlyingAlloca(load.getAddr()))
610 return false;
611 uint64_t readEnd =
612 offset == 0 ? dl.getTypeAllocSize(coercedTy).getFixedValue()
613 : offset + dl.getTypeStoreSize(coercedTy).getFixedValue();
614 return readEnd <= dl.getTypeAllocSize(load.getType()).getFixedValue();
615}
616
617/// Read \p coercedTy at \p offset straight out of the storage the record load
618/// \p load reads, which must be an alloca. The read goes just before \p load,
619/// so it reads the bytes \p load reads, and takes the alloca's alignment at
620/// \p offset.
621static mlir::Value emitInPlaceCoercedLoad(mlir::OpBuilder &builder,
622 mlir::Location loc, cir::LoadOp load,
623 mlir::Type coercedTy,
624 unsigned offset) {
625 cir::AllocaOp src = cir::getUnderlyingAlloca(load.getAddr());
626 assert(src && "the record load must read an alloca");
627 mlir::OpBuilder::InsertionGuard guard(builder);
628 builder.setInsertionPoint(load);
629 SmallPtrSet<mlir::Operation *, 4> ignored;
630 mlir::Value view = emitViewAtOffset(builder, loc, coercedTy, load.getAddr(),
631 ignored, offset);
632 auto coerced = cir::LoadOp::create(builder, loc, view);
633 coerced.setAlignment(llvm::MinAlign(src.getAlignment(), offset));
634 return coerced;
635}
636
637/// Copy the bytes of the record \p load reads into a new coercion slot for
638/// \p coercedTy at \p offset, placed at the start of \p slotBlock, and return
639/// the slot. The slot is at least as aligned as the storage copied from. The
640/// copy goes just before \p load, so it reads the bytes \p load reads, and is
641/// volatile if \p load is.
642static cir::AllocaOp
643emitCopiedCoercionSlot(mlir::OpBuilder &builder, mlir::Location loc,
644 cir::LoadOp load, mlir::Type coercedTy,
645 mlir::Block *slotBlock, const mlir::DataLayout &dl,
646 unsigned offset) {
647 auto recTy = cast<cir::RecordType>(load.getType());
648 uint64_t srcAlign;
649 if (cir::AllocaOp src = cir::getUnderlyingAlloca(load.getAddr()))
650 srcAlign = src.getAlignment();
651 else
652 srcAlign = load.getAlignment().value_or(dl.getTypeABIAlignment(recTy));
653 SmallPtrSet<mlir::Operation *, 4> ignored;
654 cir::AllocaOp slot = createCoercionSlot(builder, loc, recTy, coercedTy,
655 slotBlock, dl, ignored, offset);
656 slot.setAlignment(std::max(slot.getAlignment(), srcAlign));
657 assert((offset == 0 || slot.getAllocaType() == recTy) &&
658 "a direct offset must leave the record as the slot's type");
659 mlir::OpBuilder::InsertionGuard guard(builder);
660 builder.setInsertionPoint(load);
661 mlir::Value recView =
662 emitViewAtOffset(builder, loc, recTy, slot, ignored, /*offset=*/0);
663 auto copy =
664 cir::CopyOp::create(builder, loc, recView, load.getAddr(),
665 builder.getI64IntegerAttr(slot.getAlignment()),
666 builder.getI64IntegerAttr(srcAlign));
667 copy.setIsVolatile(load.getIsVolatile());
668 return slot;
669}
670
671/// Copy the bytes of the record \p load reads into a coercion slot, as
672/// emitCopiedCoercionSlot does, then read \p coercedTy out of the slot at
673/// \p offset. The read takes the slot's alignment at \p offset.
674static mlir::Value emitCopiedCoercedLoad(mlir::OpBuilder &builder,
675 mlir::Location loc, cir::LoadOp load,
676 mlir::Type coercedTy,
677 mlir::Block *slotBlock,
678 const mlir::DataLayout &dl,
679 unsigned offset) {
680 cir::AllocaOp slot = emitCopiedCoercionSlot(builder, loc, load, coercedTy,
681 slotBlock, dl, offset);
682 mlir::OpBuilder::InsertionGuard guard(builder);
683 builder.setInsertionPoint(load);
684 SmallPtrSet<mlir::Operation *, 4> ignored;
685 mlir::Value coercedView =
686 emitViewAtOffset(builder, loc, coercedTy, slot, ignored, offset);
687 auto coerced = cir::LoadOp::create(builder, loc, coercedView);
688 coerced.setAlignment(llvm::MinAlign(slot.getAlignment(), offset));
689 return coerced;
690}
691
692/// Read \p coercedTy at \p offset out of the bytes the record load \p load
693/// reads: straight out of its storage if \p load is not volatile and
694/// canReadInPlace accepts it, and otherwise out of a coercion slot at the start
695/// of \p slotBlock that those bytes are copied into. emitInPlaceCoercedLoad
696/// does not mark its read volatile, so a volatile load is read through the
697/// volatile copy instead. Either way no new load of the record type is
698/// emitted.
699static mlir::Value emitCoercedRecordLoad(
700 mlir::OpBuilder &builder, mlir::Location loc, cir::LoadOp load,
701 mlir::Type coercedTy, mlir::Block *slotBlock, const mlir::DataLayout &dl,
702 const cir::CIRDataLayout &cirDataLayout, unsigned offset) {
703 if (!load.getIsVolatile() &&
704 canReadInPlace(load, coercedTy, cirDataLayout, offset))
705 return emitInPlaceCoercedLoad(builder, loc, load, coercedTy, offset);
706 return emitCopiedCoercedLoad(builder, loc, load, coercedTy, slotBlock, dl,
707 offset);
708}
709
710/// Whether a cir.copy of \p ty into \p dst that stands in for a store of it
711/// skips tail padding. Unless \p dst is an alloca, which holds a complete
712/// object, it can be a potentially-overlapping subobject, such as a
713/// [[no_unique_address]] member or a base, whose tail padding holds another
714/// object. Classic CodeGen writes only the data size into a destination it is
715/// handed (EmitCall) and into one that may overlap (EmitAggregateCopy).
716/// Copying the whole object into an alloca is equivalent, since nothing else
717/// lives in its tail padding. A struct has tail padding when its last members
718/// are pad members, which CIRGen emits for padding another object may reuse and
719/// for some other structs, such as an over-aligned one.
720static bool shouldSkipTailPadding(mlir::Value dst, mlir::Type ty) {
721 auto structTy = mlir::dyn_cast<cir::StructType>(ty);
722 if (!structTy || cir::getUnderlyingAlloca(dst))
723 return false;
724 llvm::ArrayRef<mlir::Type> members = structTy.getMembers();
725 llvm::ArrayRef<cir::RecordMemberKind> kinds = structTy.getMemberKinds();
726 for (auto [member, kind] : llvm::reverse(llvm::zip_equal(members, kinds)))
727 if (cir::memberOwnsBytes(member))
728 return kind == cir::RecordMemberKind::Pad;
729 return false;
730}
731
732/// Rewire the uses of the record value \p val, whose bytes sit at \p addr,
733/// which is aligned to \p addrAlign, so that each one finds the record in
734/// memory. \p val may already have been retyped to the type it is passed as, a
735/// coerced type or a pointer, but its uses still expect the record.
736///
737/// A store of \p val becomes a cir.copy from \p addr at the store, volatile if
738/// the store is and skipping tail padding as shouldSkipTailPadding says. A
739/// store of the record value writes only what its lowered type stores, so
740/// another union member's data past those bytes would be lost. A consumer may
741/// be rewritten before \p val's defining op, and has then already stored \p val
742/// into a slot of its own. That store becomes a copy here too.
743///
744/// Every other use is pointed at one cir.load of \p addr created at the
745/// builder's insertion point. Only a call argument, a return and a read of a
746/// member that is not itself a record may use it, since those either read
747/// \p addr when they are rewritten or read a field. Uses by an operation in
748/// \p exceptions are left alone. Fails, after reporting it, on an atomic store
749/// of \p val or a use by anything else, before anything is rewired.
750static mlir::LogicalResult
751rewireRecordUses(mlir::OpBuilder &builder, mlir::Location loc, mlir::Value val,
752 mlir::Value addr, uint64_t addrAlign,
753 const mlir::DataLayout &dl,
754 const SmallPtrSetImpl<mlir::Operation *> &exceptions) {
755 mlir::Type recTy = mlir::cast<cir::PointerType>(addr.getType()).getPointee();
756 assert(mlir::isa<cir::RecordType>(recTy) && "only a record is rewired");
759 for (mlir::OpOperand &use : val.getUses()) {
760 mlir::Operation *user = use.getOwner();
761 if (exceptions.contains(user))
762 continue;
763 if (auto store = mlir::dyn_cast<cir::StoreOp>(user)) {
764 if (store.getMemOrder())
765 return store.emitOpError()
766 << "atomic store of a record argument or result not yet "
767 "implemented in CallConvLowering";
768 stores.push_back(store);
769 continue;
770 }
771 auto member = mlir::dyn_cast<cir::ExtractMemberOp>(user);
772 bool isFieldRead = member && !mlir::isa<cir::RecordType>(member.getType());
773 if (!isFieldRead &&
774 !mlir::isa<cir::CIRCallOpInterface, cir::ReturnOp>(user))
775 return user->emitOpError()
776 << "use of a record argument or result other than by a store, "
777 "call, return or member read not yet implemented in "
778 "CallConvLowering";
779 otherUses.push_back(&use);
780 }
781
782 mlir::IntegerAttr srcAlign = builder.getI64IntegerAttr(addrAlign);
783 for (cir::StoreOp store : stores) {
784 mlir::OpBuilder::InsertionGuard guard(builder);
785 builder.setInsertionPoint(store);
786 uint64_t dstAlign =
787 store.getAlignment().value_or(dl.getTypeABIAlignment(recTy));
788 auto copy =
789 cir::CopyOp::create(builder, store.getLoc(), store.getAddr(), addr,
790 builder.getI64IntegerAttr(dstAlign), srcAlign);
791 copy.setIsVolatile(store.getIsVolatile());
792 copy.setSkipTailPadding(shouldSkipTailPadding(store.getAddr(), recTy));
793 store->erase();
794 }
795
796 if (otherUses.empty())
797 return mlir::success();
798 auto load = cir::LoadOp::create(builder, loc, addr);
799 load.setAlignment(addrAlign);
800 for (mlir::OpOperand *use : otherUses)
801 use->set(load);
802 return mlir::success();
803}
804
805/// Point the uses of \p val outside \p exceptions at the value of \p val's
806/// original type that \p view, a view of the start of a coercion slot, holds.
807/// For a record, rewireRecordUses points the uses at the slot. Any other type
808/// is loaded out of it at the builder's insertion point.
809static mlir::LogicalResult
810replaceUsesFromSlot(mlir::OpBuilder &builder, mlir::Location loc,
811 mlir::Value val, mlir::Value view,
812 const mlir::DataLayout &dl,
813 const SmallPtrSetImpl<mlir::Operation *> &exceptions) {
814 if (!mlir::isa<cir::RecordType>(
815 mlir::cast<cir::PointerType>(view.getType()).getPointee())) {
816 auto load = cir::LoadOp::create(builder, loc, view);
817 val.replaceAllUsesExcept(load, exceptions);
818 return mlir::success();
819 }
820 cir::AllocaOp slot = cir::getUnderlyingAlloca(view);
821 assert(slot && "the view must start at the coercion slot");
822 return rewireRecordUses(builder, loc, val, view, slot.getAlignment(), dl,
823 exceptions);
824}
825
826/// Coerce \p src, which is \p val retyped or the value replacing it, back to
827/// \p origTy through a coercion slot at the start of \p slotBlock, and point
828/// the uses of \p val outside \p createdOps at the result with
829/// replaceUsesFromSlot. A direct \p offset lands on the coerced side, so the
830/// view of \p origTy starts at the slot. A pointer that only changes address
831/// space is cast instead. The coercion's operations are added to
832/// \p createdOps.
833static mlir::LogicalResult
834emitCoercionAndReplaceUses(mlir::OpBuilder &builder, mlir::Location loc,
835 mlir::Type origTy, mlir::Value src, mlir::Value val,
836 mlir::Block *slotBlock, const mlir::DataLayout &dl,
837 SmallPtrSetImpl<mlir::Operation *> &createdOps,
838 unsigned offset) {
839 if (mlir::Value cast = emitAddrSpaceCoercion(builder, loc, origTy, src,
840 createdOps, offset)) {
841 val.replaceAllUsesExcept(cast, createdOps);
842 return mlir::success();
843 }
844 mlir::Value view = emitCoercionToMemory(builder, loc, origTy, src, slotBlock,
845 dl, createdOps, offset);
846 return replaceUsesFromSlot(builder, loc, val, view, dl, createdOps);
847}
848
849/// Coerce the value each cir.return returns to the new (coerced) return type.
850///
851/// When the returned value is a non-atomic load of a record, the coerced value
852/// is read by emitCoercedRecordLoad, and the record load is erased once nothing
853/// else uses it. Any other value is stored into a coercion slot and read back.
854/// A record with no storage behind it, such as a constant, defines no bytes
855/// beyond its fields, so the store writes every byte it defines. A call
856/// result's store is turned into a copy when its call is rewritten later.
857/// Fails, after reporting it, on an atomic load of a record.
858mlir::LogicalResult
859insertReturnCoercion(mlir::FunctionOpInterface funcOp, mlir::Type origRetTy,
860 mlir::Type coercedRetTy, mlir::OpBuilder &builder,
861 const mlir::DataLayout &dl,
862 const cir::CIRDataLayout &cirDataLayout, unsigned offset) {
864 funcOp.walk([&](cir::ReturnOp r) { returns.push_back(r); });
865 for (cir::ReturnOp r : returns) {
866 if (r.getInput().empty())
867 continue;
868 mlir::Value origVal = r.getInput()[0];
869 if (origVal.getType() == coercedRetTy)
870 continue;
871
872 if (failed(rejectAtomicRecordLoad(r, origVal)))
873 return mlir::failure();
874 if (cir::LoadOp recLoad = maybeGetCopyableRecordLoad(origVal)) {
875 mlir::Value coerced = emitCoercedRecordLoad(
876 builder, r.getLoc(), recLoad, coercedRetTy,
877 &funcOp->getRegion(0).front(), dl, cirDataLayout, offset);
878 r->setOperand(0, coerced);
879 if (recLoad->use_empty())
880 recLoad->erase();
881 continue;
882 }
883
884 builder.setInsertionPoint(r);
885 mlir::Value coerced =
886 emitCoercion(builder, r.getLoc(), coercedRetTy, origVal,
887 &funcOp->getRegion(0).front(), dl, offset);
888 r->setOperand(0, coerced);
889 }
890 return mlir::success();
891}
892
893/// \p recordVal's defining load, if it is simple and its address resolves to
894/// an alloca. Null otherwise.
895static cir::LoadOp getWholeRecordLoad(mlir::Value recordVal) {
896 cir::LoadOp load = maybeGetSimpleLoad(recordVal);
897 if (!load || !cir::getUnderlyingAlloca(load.getAddr()))
898 return {};
899 return load;
900}
901
902/// Returns \p load if the address it read from can be passed in place of the
903/// loaded value for an indirect argument that needs \p minAlign alignment,
904/// and null otherwise, including when \p load is null. A byval argument
905/// also needs storageUnwrittenBetween. On null the caller reports a
906/// non-byval argument or fills a byval slot of its own.
907///
908/// The address has to be a pointer to the loaded type in the default address
909/// space, since that is what the rewritten parameter is, and a cir.load pins
910/// the pointee type without pinning the address space. The storage it names
911/// has to be a slot allocated here, reached through storage-preserving casts,
912/// already stating at least \p minAlign. The enclosing function's own
913/// non-byval parameter qualifies as well, since its slot stands until
914/// finalizeParameterSlots and prepareNonByvalParameters has restated it with
915/// the alignment that parameter promises rather than the one CIRGen chose
916/// for a local copy. A byval parameter's slot keeps CIRGen's alignment, so
917/// it qualifies only where that already covers \p minAlign.
918///
919/// Raising a slot's alignment here would not hold for one that stands in
920/// for a parameter, since finalizeParameterSlots replaces it with the
921/// incoming pointer, which promises only what the caller gave it.
922static cir::LoadOp forwardableIndirectLoad(cir::LoadOp load,
923 uint64_t minAlign) {
924 if (!load || load.getAddr().getType() !=
925 cir::PointerType::get(load.getResult().getType()))
926 return {};
927 cir::AllocaOp slot = cir::getUnderlyingAlloca(load.getAddr());
928 return slot && slot.getAlignment() >= minAlign ? load : cir::LoadOp();
929}
930
931/// True when \p aliasAnalysis shows that nothing between \p load and \p call
932/// can write the storage \p load read, so a byval argument may name that
933/// storage rather than a copy of it. byval takes the callee's copy at the
934/// call, while the operand carries the value as of the load, so a write in
935/// between would reach the callee that the operand does not hold.
936/// prepareNonByvalParameters reads a parameter at its spill, which leaves the
937/// body's own stores in between.
938///
939/// A load in another block is not forwardable here, since the paths from it
940/// to the call are not walked.
941static bool storageUnwrittenBetween(cir::LoadOp load, mlir::Operation *call,
942 mlir::AliasAnalysis &aliasAnalysis) {
943 if (load->getBlock() != call->getBlock())
944 return false;
945 for (mlir::Operation *op = load->getNextNode(); op != call;
946 op = op->getNextNode()) {
947 assert(op && "the load must precede the call in the block they share");
948 if (op->getNumRegions() != 0 ||
949 aliasAnalysis.getModRef(op, load.getAddr()).isMod())
950 return false;
951 }
952 return true;
953}
954
955/// The alignment a copy may claim for the storage \p load read, which is the
956/// one \p load states, or the underlying slot's when it states none. A load
957/// with neither names storage this pass cannot reason about, so the copy is
958/// left to assume nothing.
959static mlir::IntegerAttr loadSourceAlignment(cir::LoadOp load,
960 mlir::OpBuilder &builder) {
961 if (mlir::IntegerAttr stated = load.getAlignmentAttr())
962 return stated;
963 if (cir::AllocaOp slot = cir::getUnderlyingAlloca(load.getAddr()))
964 return builder.getI64IntegerAttr(slot.getAlignment());
965 return builder.getI64IntegerAttr(1);
966}
967
968/// Decompose a struct value into one scalar call argument per field of \p
969/// recTy, appending the field values to \p newArgs. When \p structVal is a
970/// simple load from an alloca, read each field with cir.get_member +
971/// cir.load from the address the load used, emitted at the original load's
972/// position so they observe the same memory state, and record the now-dead
973/// whole-struct load in \p deadRecordLoads for later erasure. Otherwise (a
974/// call result, compound literal, or a volatile or atomic load) extract each
975/// field from the value with cir.extract_member. Loading the members from
976/// memory rather than extracting from a whole-struct value keeps the result in
977/// a form SROA can promote (it does not reason about extractvalue). Shared by
978/// the Expand and Direct+canFlatten argument paths.
979static void emitStructFieldArgs(mlir::OpBuilder &builder, mlir::Location loc,
980 mlir::Value structVal, cir::RecordType recTy,
981 SmallVectorImpl<mlir::Value> &newArgs,
982 SmallVectorImpl<cir::LoadOp> &deadRecordLoads) {
983 cir::LoadOp srcLoad = getWholeRecordLoad(structVal);
984
985 if (srcLoad) {
986 mlir::OpBuilder::InsertionGuard guard(builder);
987 builder.setInsertionPoint(srcLoad);
988 cir::PointerType baseTy = srcLoad.getAddr().getType();
989 for (auto [f, fieldTy] : llvm::enumerate(recTy.getMembers())) {
990 mlir::Type fieldPtrTy =
991 cir::PointerType::get(fieldTy, baseTy.getAddrSpace());
992 mlir::Value fieldPtr = cir::GetMemberOp::create(
993 builder, loc, fieldPtrTy, srcLoad.getAddr(), /*name=*/"",
994 /*index=*/f);
995 newArgs.push_back(cir::LoadOp::create(builder, loc, fieldPtr));
996 }
997 deadRecordLoads.push_back(srcLoad);
998 } else {
999 for (unsigned f = 0; f < recTy.getNumElements(); ++f)
1000 newArgs.push_back(
1001 cir::ExtractMemberOp::create(builder, loc, structVal, f));
1002 }
1003}
1004
1005/// Erase the loads that a rewritten call left unused. The old call must
1006/// already be erased, since until then it still counts as a user. One load can
1007/// feed two operands of the same call, as in f(s, s), so \p loads can hold the
1008/// same load twice. A load that another op still reads is left alone.
1009static void eraseDeadRecordLoads(ArrayRef<cir::LoadOp> loads) {
1010 llvm::SmallSetVector<mlir::Operation *, 4> uniqueLoads(llvm::from_range,
1011 loads);
1012 for (mlir::Operation *load : uniqueLoads)
1013 if (load->use_empty())
1014 load->erase();
1015}
1016
1017/// The store that spills indirect parameter \p blockArg, if the parameter's
1018/// only use is that store. Null otherwise.
1019static cir::StoreOp maybeFindParamSpill(mlir::BlockArgument blockArg) {
1020 if (!blockArg.hasOneUse())
1021 return {};
1022 auto store = dyn_cast<cir::StoreOp>(*blockArg.user_begin());
1023 if (!store || store.getValue() != blockArg)
1024 return {};
1025 return store;
1026}
1027
1028/// The store that spills non-byval indirect parameter \p blockArg, and the
1029/// slot it spills into. prepareNonByvalParameters has already established
1030/// that the spill is the block argument's only use and that it stores into
1031/// an alloca, which is what the assertions below rest on. Both results are
1032/// null when the parameter has no spill, which is so only when nothing uses
1033/// it at all.
1034static std::pair<cir::StoreOp, cir::AllocaOp>
1035findParamSpill(mlir::BlockArgument blockArg) {
1036 if (blockArg.use_empty())
1037 return {};
1038 cir::StoreOp store = maybeFindParamSpill(blockArg);
1039 assert(store && "non-byval arg's only use must be its CIRGen param spill");
1040 return {store, cast<cir::AllocaOp>(store.getAddr().getDefiningOp())};
1041}
1042
1043/// For each Direct arg with a coerced type, change the block argument's type to
1044/// the coerced type and insert a coercion at function entry that maps it back
1045/// to the original type for body uses, which find a record in the coercion slot
1046/// through rewireRecordUses. For each Indirect byval arg, change the block
1047/// argument's type to a pointer, then replace a record's single param spill
1048/// with a copy from that pointer. Without such a spill, a record's uses find
1049/// it at the pointer through rewireRecordUses, and a value of any other type is
1050/// loaded from the pointer at entry. For each Indirect non-byval arg, change
1051/// the block argument to a pointer and queue the param-slot alloca to be
1052/// replaced by it (no entry load or byte-copy) so the body operates on the
1053/// caller's storage in place. For each Expand arg, replace the single struct
1054/// block argument with N scalar block arguments (one per field) and store each
1055/// field directly into the parameter's own alloca (the CIRGen spill slot),
1056/// erasing the original whole-struct store.
1057///
1058/// \p hasSRetArg is true when the function has an sret return (a hidden return
1059/// pointer is prepended as block argument 0). Expand arguments expand the
1060/// block argument count, so a running index tracks the current block argument
1061/// position rather than computing the classification index + \p hasSRetArg
1062/// directly. Fails, after reporting it, when rewireRecordUses does.
1063mlir::LogicalResult
1064insertArgCoercion(mlir::FunctionOpInterface funcOp,
1065 const FunctionClassification &fc, mlir::OpBuilder &builder,
1066 const mlir::DataLayout &dl, bool hasSRetArg,
1067 SmallVectorImpl<std::pair<cir::AllocaOp, mlir::BlockArgument>>
1068 &pendingParamSlots) {
1069 mlir::Region &body = funcOp->getRegion(0);
1070 if (body.empty())
1071 return mlir::success();
1072 mlir::Block &entry = body.front();
1073
1074 // Running block argument index. Each non-Expand classification occupies
1075 // one block argument slot; each Expand classification occupies N slots
1076 // (one per struct field), so the running index must be incremented by N
1077 // rather than 1 after processing an Expand arg.
1078 unsigned blockArgIdx = hasSRetArg ? 1 : 0;
1079
1080 for (const ArgClassification &ac : fc.argInfos) {
1081 assert(blockArgIdx < entry.getNumArguments() &&
1082 "classification count must not exceed entry block arguments");
1083
1084 if (ac.kind == ArgKind::Expand) {
1085 // The block arg at blockArgIdx currently has the original struct type.
1086 // Replace it with N scalar args (one per field) and store each field
1087 // directly into the parameter's own alloca.
1088 mlir::BlockArgument origArg = entry.getArgument(blockArgIdx);
1089 auto recTy = cast<cir::RecordType>(origArg.getType());
1090 assert(recTy.isStruct() &&
1091 "Expand classification requires a struct type, not a union");
1092 unsigned numFields = recTy.getNumElements();
1093 assert(numFields > 0 &&
1094 "Expand classification requires at least one struct field");
1095 mlir::Location loc = funcOp.getLoc();
1096
1097 // CIRGen spills every by-value struct parameter into its local alloca
1098 // with a single store before any other use, so the struct block arg's
1099 // only use is that spill. Capture it and the destination alloca so the
1100 // expanded fields can be stored straight into that alloca, preserving
1101 // the alloca's variable name and `init` flag and avoiding a
1102 // reassemble-then-reload roundtrip. DCE may have run earlier and
1103 // removed the spill (leaving the block arg unused); tolerate that by
1104 // only flattening the signature and emitting no field stores.
1105 cir::StoreOp paramStore;
1106 cir::AllocaOp destAlloca;
1107 if (!origArg.use_empty()) {
1108 assert(origArg.hasOneUse() &&
1109 "Expand arg must have exactly one use (the CIRGen param spill)");
1110 paramStore = cast<cir::StoreOp>(*origArg.user_begin());
1111 assert(paramStore.getValue() == origArg &&
1112 "Expand arg's use must be the value operand of its store");
1113 destAlloca = cast<cir::AllocaOp>(paramStore.getAddr().getDefiningOp());
1114 }
1115
1116 // Erase the original whole-struct spill before retyping the block
1117 // argument, so the store is never left feeding a type-mismatched value.
1118 // The field stores take its place, just before the following operation
1119 // (the spill always precedes the entry block's terminator).
1120 mlir::Operation *fieldStoreInsertPt = nullptr;
1121 if (paramStore) {
1122 fieldStoreInsertPt = paramStore->getNextNode();
1123 assert(fieldStoreInsertPt &&
1124 "param spill must be followed by a block terminator");
1125 paramStore->erase();
1126 }
1127
1128 // Split the single struct block arg into N scalar field block args (slot
1129 // 0 reuses the original; slots 1..N-1 are inserted after it). The
1130 // reshape needs no insertion point. The field stores are gated on the
1131 // same destAlloca condition: when the spill survived we set the insert
1132 // point to its old slot (which sits after the CIRGen allocas) and store
1133 // each field there; when DCE removed the spill the parameter is dead, so
1134 // we only reshape the signature and emit no stores.
1135 if (destAlloca)
1136 builder.setInsertionPoint(fieldStoreInsertPt);
1137 for (auto [f, fieldTy] : llvm::enumerate(recTy.getMembers())) {
1138 if (f == 0)
1139 origArg.setType(fieldTy);
1140 else
1141 entry.insertArgument(blockArgIdx + f, fieldTy, loc);
1142 if (!destAlloca)
1143 continue;
1144 mlir::Type fieldPtrTy = cir::PointerType::get(
1145 fieldTy,
1146 mlir::cast<cir::PointerType>(destAlloca.getType()).getAddrSpace());
1147 auto fieldPtr = cir::GetMemberOp::create(builder, loc, fieldPtrTy,
1148 destAlloca, /*name=*/"",
1149 /*index=*/f);
1150 cir::StoreOp::create(builder, loc, entry.getArgument(blockArgIdx + f),
1151 fieldPtr);
1152 }
1153
1154 blockArgIdx += numFields;
1155 continue;
1156 }
1157
1158 mlir::BlockArgument blockArg = entry.getArgument(blockArgIdx);
1159
1160 if (cir::RecordType flatTy = getFlattenedCoercedType(ac)) {
1161 // Direct + canFlatten: the coerced type is a struct whose fields are
1162 // passed as separate arguments. As in the Expand path, the single block
1163 // arg becomes N scalar block args, and each is stored straight into a
1164 // slot sized and aligned for both the coerced struct and the original
1165 // argument type, which the body then reads.
1166 unsigned numFields = flatTy.getNumElements();
1167 assert(numFields >= 2 && "getFlattenedCoercedType guarantees >1 fields");
1168 Type origTy = blockArg.getType();
1169 Location loc = funcOp.getLoc();
1170
1171 // Change slot 0 to field 0's type; insert slots 1..N-1 after it.
1172 blockArg.setType(flatTy.getElementType(0));
1173 for (unsigned f = 1; f < numFields; ++f)
1174 entry.insertArgument(blockArgIdx + f, flatTy.getElementType(f), loc);
1175
1176 // setInsertionPointToStart: see comment in the Expand arm above.
1177 builder.setInsertionPointToStart(&entry);
1178 assert(!ac.directOffset &&
1179 "the coerced struct starts at slot offset 0 here, so a flattened "
1180 "coercion cannot honor a direct offset");
1181 SmallPtrSet<Operation *, 8> flattenOps;
1182 cir::AllocaOp slot;
1183 if (origTy == flatTy) {
1184 slot = cir::AllocaOp::create(
1185 builder, loc, getAllocaPtrTy(flatTy, dl),
1186 builder.getStringAttr("coerce"),
1187 builder.getI64IntegerAttr(dl.getTypeABIAlignment(flatTy)));
1188 flattenOps.insert(slot);
1189 } else {
1190 slot = createCoercionSlot(builder, loc, flatTy, origTy, &entry, dl,
1191 flattenOps, /*offset=*/0);
1192 }
1193 Value flatView = emitViewAtOffset(builder, loc, flatTy, slot, flattenOps,
1194 /*offset=*/0);
1195 auto flatPtrTy = cast<cir::PointerType>(flatView.getType());
1196 for (auto [f, fieldTy] : llvm::enumerate(flatTy.getMembers())) {
1197 Type fieldPtrTy =
1198 cir::PointerType::get(fieldTy, flatPtrTy.getAddrSpace());
1199 auto fieldPtr = cir::GetMemberOp::create(builder, loc, fieldPtrTy,
1200 flatView, /*name=*/"",
1201 /*index=*/f);
1202 flattenOps.insert(fieldPtr);
1203 auto storeOp = cir::StoreOp::create(
1204 builder, loc, entry.getArgument(blockArgIdx + f), fieldPtr);
1205 flattenOps.insert(storeOp);
1206 }
1207
1208 // Point the original body uses of the struct block arg, now field 0, at
1209 // the original-type view of the slot.
1210 Value origView = emitViewAtOffset(builder, loc, origTy, slot, flattenOps,
1211 /*offset=*/0);
1212 if (failed(replaceUsesFromSlot(builder, loc, blockArg, origView, dl,
1213 flattenOps)))
1214 return mlir::failure();
1215
1216 blockArgIdx += numFields;
1217 continue;
1218 }
1219
1220 if (ac.kind == ArgKind::Direct && ac.coercedType) {
1221 mlir::Type oldArgTy = blockArg.getType();
1222 mlir::Type newArgTy = ac.coercedType;
1223 if (oldArgTy == newArgTy) {
1224 ++blockArgIdx;
1225 continue;
1226 }
1227 blockArg.setType(newArgTy);
1228
1229 // The coercion's own operations are left out of the rewiring, since they
1230 // read blockArg as the coerced type.
1231 builder.setInsertionPointToStart(&entry);
1232 SmallPtrSet<mlir::Operation *, 4> coercionOps;
1233 if (failed(emitCoercionAndReplaceUses(builder, funcOp.getLoc(), oldArgTy,
1234 blockArg, blockArg, &entry, dl,
1235 coercionOps, ac.directOffset)))
1236 return mlir::failure();
1237 } else if (ac.kind == ArgKind::Indirect) {
1238 // byval and non-byval both lower to !cir.ptr<T>, and which it is shows
1239 // up only in the attrs updateArgAttrs applies. Body lowering differs:
1240 // the body addresses a byval parameter through the CIRGen slot it was
1241 // spilled into, so that slot has to be filled from the incoming
1242 // pointer, while a non-byval parameter's slot is replaced by the
1243 // pointer so the body works on the caller's storage in place.
1244 auto ptrTy = cir::PointerType::get(blockArg.getType());
1245
1246 if (!ac.byVal) {
1247 // Without byval, drop the spill store and let the slot's uses read the
1248 // incoming pointer, so the body operates on the caller's storage in
1249 // place. A byte-copy would be wrong for non-trivially-copyable
1250 // aggregates (e.g. libstdc++ SSO std::string, where it would leave
1251 // `_M_p` aliasing the source's `_M_local_buf`).
1252 auto [paramStore, destAlloca] = findParamSpill(blockArg);
1253
1254 if (paramStore)
1255 paramStore->erase();
1256
1257 // Update the block argument to point to its original type.
1258 blockArg.setType(ptrTy);
1259
1260 // Pointing the slot's uses at the incoming pointer waits until every
1261 // call site has been rewritten. A call that hands this parameter
1262 // straight on recognizes it by the slot its operand was loaded from,
1263 // and collapsing the slot here would leave that call reading a block
1264 // argument with no defining operation to inspect. A dead spill DCE
1265 // already removed leaves nothing to collapse.
1266 if (destAlloca)
1267 pendingParamSlots.emplace_back(destAlloca, blockArg);
1268 } else {
1269 // A record's spill slot is filled by a byte copy of the incoming
1270 // object rather than by a store of the loaded value: its padding
1271 // bytes can hold another union member's live data, which a value
1272 // store would leave unwritten. A _BitInt keeps its load and spill
1273 // store, which are its conversion between value and in-memory form.
1274 cir::StoreOp paramStore;
1275 if (mlir::isa<cir::RecordType>(blockArg.getType()))
1276 paramStore = maybeFindParamSpill(blockArg);
1277
1278 // Erasing the spill before the block argument is retyped keeps the
1279 // store from being left with a value operand of the wrong type.
1280 mlir::Value destAddr;
1281 mlir::IntegerAttr destAlign;
1282 mlir::Operation *spillPos = nullptr;
1283 bool spillIsVolatile = false;
1284 if (paramStore) {
1285 if (paramStore.getMemOrder())
1286 return paramStore.emitOpError()
1287 << "atomic store of a record argument or result not yet "
1288 "implemented in CallConvLowering";
1289 spillIsVolatile = paramStore.getIsVolatile();
1290 destAddr = paramStore.getAddr();
1291 cir::AllocaOp destAlloca = cir::getUnderlyingAlloca(destAddr);
1292 destAlign = paramStore.getAlignmentAttr();
1293 if (!destAlign && destAlloca)
1294 destAlign = destAlloca.getAlignmentAttr();
1295 spillPos = paramStore->getNextNode();
1296 assert(spillPos && "param spill must be followed by a block "
1297 "terminator");
1298 paramStore->erase();
1299 }
1300
1301 blockArg.setType(ptrTy);
1302
1303 if (destAddr) {
1304 mlir::OpBuilder::InsertionGuard guard(builder);
1305 builder.setInsertionPoint(spillPos);
1306 auto copy = cir::CopyOp::create(
1307 builder, funcOp.getLoc(), destAddr, blockArg, destAlign,
1308 builder.getI64IntegerAttr(ac.indirectAlign.value()));
1309 copy.setIsVolatile(spillIsVolatile);
1310 copy.setSkipTailPadding(
1311 shouldSkipTailPadding(destAddr, ptrTy.getPointee()));
1312 } else if (!blockArg.use_empty()) {
1313 builder.setInsertionPointToStart(&entry);
1314 if (mlir::isa<cir::RecordType>(ptrTy.getPointee())) {
1315 SmallPtrSet<mlir::Operation *, 1> noExceptions;
1316 if (failed(rewireRecordUses(builder, funcOp.getLoc(), blockArg,
1317 blockArg, ac.indirectAlign.value(), dl,
1318 noExceptions)))
1319 return mlir::failure();
1320 } else {
1321 auto loadOp =
1322 cir::LoadOp::create(builder, funcOp.getLoc(), blockArg);
1323 SmallPtrSet<mlir::Operation *, 1> loadOps = {loadOp};
1324 blockArg.replaceAllUsesExcept(loadOp.getResult(), loadOps);
1325 }
1326 }
1327 }
1328 }
1329 // Ignore, Extend, and Direct-without-coerce need no block-level changes.
1330
1331 ++blockArgIdx;
1332 }
1333 return mlir::success();
1334}
1335
1336/// Rewrite each cir.return so the return value flows through the sret
1337/// pointer (the prepended first block argument) and the function returns
1338/// void.
1339///
1340/// CIRGen emits a local `__retval` alloca and emits `cir.return %loaded`
1341/// where `%loaded = cir.load __retval`. The naive lowering -- store the
1342/// loaded SSA value through the sret pointer -- byte-copies the record,
1343/// which is wrong for non-trivially-copyable types: e.g. libstdc++'s SSO
1344/// `std::string` has a `_M_p` pointer that aliases the source's internal
1345/// `_M_local_buf`, so a byte-copy leaves the destination pointing at the
1346/// source's (now-dying) stack storage and the destination's destructor
1347/// later `free()`s a stack pointer.
1348///
1349/// Instead, route construction directly into the sret slot: find the
1350/// `__retval` alloca, replace its uses with the sret pointer, and drop the
1351/// trailing `cir.load __retval` so the rewritten return has no operand.
1352/// The CIRGen-emitted constructor / store-into-`__retval` then targets the
1353/// sret slot uniformly, matching classic CodeGen's "construct directly into
1354/// `%agg.result`" pattern.
1355///
1356/// CIRGen emits one `%v = cir.load %__retval` / `cir.return %v` pair per
1357/// return statement, and every such load reads the single `__retval`
1358/// alloca (CIR does not merge returns into a shared epilogue block). The
1359/// alloca is therefore rewired to the sret pointer once; each cir.return is
1360/// then collapsed to a bare return and its now-dead load erased. This
1361/// `cir.return (cir.load <alloca>)` shape is an invariant guaranteed by
1362/// CIRGen, so it is asserted via `cast<>` rather than guarded with a
1363/// fallback.
1364void insertSRetStores(mlir::FunctionOpInterface funcOp, mlir::Type origRetTy,
1365 mlir::OpBuilder &builder) {
1366 mlir::Value sretPtr = funcOp.getArguments()[0];
1367
1369 funcOp->walk([&](cir::ReturnOp retOp) { returnOps.push_back(retOp); });
1370
1371 cir::AllocaOp retAlloca = nullptr;
1372 for (cir::ReturnOp retOp : returnOps) {
1373 // Every cir.return in an sret function must carry the loaded return
1374 // value -- a bare return would mean the sret slot was never written.
1375 assert(!retOp.getInput().empty() &&
1376 "cir.return in sret function must have an operand");
1377
1378 cir::LoadOp retLoad =
1379 mlir::cast<cir::LoadOp>(retOp.getInput()[0].getDefiningOp());
1380
1381 // Rewire the shared `__retval` alloca to the sret pointer once.
1382 // replaceAllUsesWith updates every load of the alloca (including those
1383 // feeding the other cir.return ops) to read from sretPtr instead, so
1384 // all returns are covered by this single rewiring. Only then is the
1385 // now-unused alloca safe to erase.
1386 if (!retAlloca) {
1387 retAlloca = mlir::cast<cir::AllocaOp>(retLoad.getAddr().getDefiningOp());
1388 retAlloca.getResult().replaceAllUsesWith(sretPtr);
1389 retAlloca->erase();
1390 }
1391
1392 // The sret slot now holds the return value directly; replace the
1393 // value-carrying return with a void return (no operand).
1394 builder.setInsertionPoint(retOp);
1395 cir::ReturnOp::create(builder, retOp.getLoc());
1396 retOp->erase();
1397 if (retLoad.use_empty())
1398 retLoad->erase();
1399 }
1400}
1401
1402/// Build the attribute dictionary for the sret slot (slot 0 of an
1403/// sret-returning function or call). Matches classic CodeGen's
1404/// `sret(T) align A [noalias] writable dead_on_unwind`. noalias is only
1405/// valid on the callee's parameter, not at the call site, so it is gated by
1406/// \p withNoalias. Key order is irrelevant: DictionaryAttr sorts by name.
1407SmallVector<mlir::NamedAttribute> buildSretSlotAttrs(mlir::OpBuilder &builder,
1408 mlir::Type retTy,
1409 uint64_t align,
1410 bool withNoalias) {
1412 // The sret type must be carried explicitly: LLVM's sret attribute requires
1413 // it, and once the CIR `!cir.ptr<retTy>` lowers to an opaque LLVM `ptr` the
1414 // pointee type can no longer be recovered from the pointer.
1415 attrs.push_back(
1416 builder.getNamedAttr("llvm.sret", mlir::TypeAttr::get(retTy)));
1417 attrs.push_back(
1418 builder.getNamedAttr("llvm.align", builder.getI64IntegerAttr(align)));
1419 if (withNoalias)
1420 attrs.push_back(
1421 builder.getNamedAttr("llvm.noalias", builder.getUnitAttr()));
1422 attrs.push_back(builder.getNamedAttr("llvm.writable", builder.getUnitAttr()));
1423 attrs.push_back(
1424 builder.getNamedAttr("llvm.dead_on_unwind", builder.getUnitAttr()));
1425 return attrs;
1426}
1427
1428/// Prepend the sret slot's attrs at position 0 of newCall's arg_attrs.
1429/// Called after the call has been rewritten with the sret pointer at
1430/// operand 0, so the operand count now includes the sret slot. \p argAttrs
1431/// must already be shaped for the rewritten argument list (Extend slots
1432/// carry signext/zeroext, Ignore slots dropped); it is shifted to slots
1433/// 1..N behind the sret slot.
1434void applySretSlotAttrs(cir::CallOp newCall, mlir::ArrayAttr argAttrs,
1435 mlir::Type retTy, uint64_t align,
1436 mlir::OpBuilder &builder) {
1437 mlir::MLIRContext *ctx = newCall->getContext();
1439 buildSretSlotAttrs(builder, retTy, align, /*withNoalias=*/false);
1440
1441 SmallVector<mlir::Attribute> newArgAttrs;
1442 newArgAttrs.reserve(newCall.getArgOperands().size());
1443 newArgAttrs.push_back(mlir::DictionaryAttr::get(ctx, sretAttrs));
1444 if (argAttrs)
1445 llvm::append_range(newArgAttrs, argAttrs);
1446 assert(newArgAttrs.size() <= newCall.getArgOperands().size() &&
1447 "arg_attrs wider than the rewritten call's operand list");
1448 newArgAttrs.resize(newCall.getArgOperands().size(),
1449 mlir::DictionaryAttr::get(ctx));
1450 newCall->setAttr("arg_attrs", mlir::ArrayAttr::get(ctx, newArgAttrs));
1451}
1452
1453/// Copy the call attributes from \p source to the rebuilt \p target. The
1454/// callee is already established by CallOp::create and may have been rewritten,
1455/// so it is not copied. Existing discardable attributes on \p target take
1456/// precedence over attributes from \p source.
1457static void copyCallAttributes(cir::CallOp source, cir::CallOp target) {
1458 source->getName().walkInherentAttrs(source, [&](llvm::StringRef name,
1459 mlir::Attribute &attr) {
1460 if (name != cir::CIRDialect::getCalleeAttrName())
1461 target->setInherentAttr(mlir::StringAttr::get(source->getContext(), name),
1462 attr);
1463 });
1464 for (mlir::NamedAttribute attr : source->getDiscardableAttrs())
1465 if (!target->hasDiscardableAttr(attr.getName()))
1466 target->setDiscardableAttr(attr.getName(), attr.getValue());
1467}
1468
1469/// For an indirect call, prepend the callee function pointer as operand 0 so
1470/// CallOp::create rebuilds it as an indirect call, bitcasting it to a function
1471/// pointer that takes \p args, less the last \p numEllipsisArgs, and returns
1472/// \p retTy. Those last arguments were passed through the callee's ellipsis
1473/// and stay out of the function type, as in classic CodeGen's
1474/// GetFunctionType: LLVM treats operands past a call's function type as
1475/// variadic, and some backends pass variadic arguments differently. No-op
1476/// for direct calls.
1477static void prependIndirectCallee(cir::CallOp call,
1478 SmallVectorImpl<mlir::Value> &args,
1479 mlir::Type retTy, unsigned numEllipsisArgs,
1480 mlir::OpBuilder &builder) {
1481 if (!call.isIndirect())
1482 return;
1483 assert(numEllipsisArgs <= args.size() &&
1484 "more ellipsis arguments than arguments");
1485 mlir::Value calleePtr = call.getIndirectCall();
1486 SmallVector<mlir::Type> paramTypes;
1487 paramTypes.reserve(args.size() - numEllipsisArgs);
1488 llvm::transform(ArrayRef(args).drop_back(numEllipsisArgs),
1489 std::back_inserter(paramTypes),
1490 [](mlir::Value v) { return v.getType(); });
1491 // Lowering builds an indirect call's LLVM function type from the callee
1492 // pointer's pointee and takes the call's result from that type, so the
1493 // pointee's return type has to track the rewrite: an sret return would
1494 // leave a result the call no longer produces, and a coerced return one of
1495 // the wrong type. The ellipsis has to survive for the same reason: the
1496 // rebuilt pointee is what makes the lowered call variadic, and only a
1497 // variadic call gets the vector-register count that the x86_64 SysV ABI
1498 // passes in AL and that the callee's va_arg reads back.
1499 cir::FuncType calleeFnTy = getIndirectCalleeType(call);
1500 assert((calleeFnTy.isVarArg() || numEllipsisArgs == 0) &&
1501 "only a variadic callee takes arguments through an ellipsis");
1502 auto newPtrTy = cir::PointerType::get(
1503 cir::FuncType::get(paramTypes, retTy, calleeFnTy.isVarArg()));
1504 if (calleePtr.getType() != newPtrTy)
1505 calleePtr = cir::CastOp::create(builder, call.getLoc(), newPtrTy,
1506 cir::CastKind::bitcast, calleePtr);
1507 args.insert(args.begin(), calleePtr);
1508}
1509
1510/// Rewrite an indirect-return (sret) call site: prepend a return-slot pointer
1511/// as operand 0, make the call return void, and either reuse a dominating
1512/// single-use store destination as the slot (so construction flows directly
1513/// into it) or allocate a fresh slot and point the result's uses at it, a
1514/// record's through rewireRecordUses and any other type's through a load of the
1515/// slot after the call. \p newArgs is the already-shaped (Ignore-dropped,
1516/// coercion-applied) non-sret argument list, the last \p numEllipsisArgs of
1517/// which were passed through the callee's ellipsis. The caller guarantees the
1518/// call has a result and an indirect-return classification. Fails, after
1519/// reporting it, when rewireRecordUses does.
1520mlir::LogicalResult rewriteIndirectReturnCall(
1521 cir::CallOp call, const FunctionClassification &fc,
1522 ArrayRef<mlir::Value> newArgs, unsigned numEllipsisArgs,
1523 mlir::Type origRetTy, ArrayRef<mlir::Type> origCallArgTypes,
1524 mlir::OpBuilder &builder, const mlir::DataLayout &dl) {
1525 mlir::MLIRContext *ctx = call->getContext();
1526 auto ptrTy = cir::PointerType::get(origRetTy);
1527 builder.setInsertionPoint(call);
1528 uint64_t sretAlign = fc.returnInfo.indirectAlign.value();
1529
1530 // CIRGen emits `cir.store %callResult, %dest` when the call's result is
1531 // bound to a local (e.g. `T s = make();`). Allocating a fresh sret slot
1532 // and copying into %dest would byte-copy the record, which is wrong for
1533 // non-trivially-copyable types (the libstdc++ SSO `_M_p` pointer
1534 // survives a byte-copy but ends up pointing at the dying temp's local
1535 // buffer, so the destination's destructor later `free()`s a stack
1536 // pointer). When the result has a single store-into-%dest use, use
1537 // %dest as the sret slot directly so construction flows into it,
1538 // matching classic CodeGen's "pass %s as sret" pattern. %dest must
1539 // dominate the call so the rewritten call (which takes it as operand 0)
1540 // does not use a value before its definition.
1541 mlir::Value sretSlot = nullptr;
1542 cir::StoreOp reuseStore = nullptr;
1543 if (call.getResult().hasOneUse()) {
1544 mlir::Operation *user = *call.getResult().getUsers().begin();
1545 if (auto store = mlir::dyn_cast<cir::StoreOp>(user))
1546 if (store.getValue() == call.getResult() &&
1547 store.getAddr().getType() == ptrTy &&
1548 mlir::DominanceInfo().properlyDominates(store.getAddr(), call)) {
1549 sretSlot = store.getAddr();
1550 reuseStore = store;
1551 }
1552 }
1553 if (!sretSlot) {
1554 auto alloca = cir::AllocaOp::create(
1555 builder, call.getLoc(), ptrTy,
1556 /*name=*/builder.getStringAttr("sret"),
1557 /*alignment=*/builder.getI64IntegerAttr(sretAlign));
1558 sretSlot = alloca;
1559 }
1560
1561 SmallVector<mlir::Value> sretArgs;
1562 sretArgs.push_back(sretSlot);
1563 sretArgs.append(newArgs.begin(), newArgs.end());
1564
1565 // numEllipsisArgs counts from the end, so the sret slot prepended here stays
1566 // in the retyped signature, as it does in classic CodeGen.
1567 mlir::Type sretVoidTy = cir::VoidType::get(ctx);
1568 prependIndirectCallee(call, sretArgs, sretVoidTy, numEllipsisArgs, builder);
1569 auto newCall = cir::CallOp::create(
1570 builder, call.getLoc(), call.getCalleeAttr(), sretVoidTy, sretArgs);
1571 copyCallAttributes(call, newCall);
1572 newCall->removeAttr("res_attrs");
1573
1574 // Shape the per-argument attrs exactly as the non-sret path does, through
1575 // updateArgAttrs, before prepending the sret slot, so sret composes with
1576 // every argument classification.
1577 mlir::ArrayAttr argAttrs = call->getAttrOfType<mlir::ArrayAttr>("arg_attrs");
1578 if (needsArgAttrUpdate(fc))
1579 argAttrs = updateArgAttrs(ctx, call->getParentOfType<mlir::ModuleOp>(),
1580 origCallArgTypes, argAttrs, fc, dl);
1581 applySretSlotAttrs(newCall, argAttrs, origRetTy, sretAlign, builder);
1582
1583 if (reuseStore) {
1584 // The callee now constructs directly into the destination slot, so the
1585 // original store-from-result is redundant; dropping it avoids a
1586 // byte-copy of the record.
1587 reuseStore->erase();
1588 } else if (mlir::isa<cir::RecordType>(origRetTy)) {
1589 builder.setInsertionPointAfter(newCall);
1590 SmallPtrSet<mlir::Operation *, 1> noExceptions;
1591 if (failed(rewireRecordUses(builder, call.getLoc(), call.getResult(),
1592 sretSlot, sretAlign, dl, noExceptions)))
1593 return mlir::failure();
1594 } else {
1595 builder.setInsertionPointAfter(newCall);
1596 auto load = cir::LoadOp::create(builder, call.getLoc(), origRetTy, sretSlot,
1597 /*isDeref=*/mlir::UnitAttr(),
1598 /*isVolatile=*/mlir::UnitAttr(),
1599 /*is_nontemporal=*/mlir::UnitAttr(),
1600 /*alignment=*/mlir::IntegerAttr(),
1601 /*sync_scope=*/cir::SyncScopeKindAttr(),
1602 /*mem_order=*/cir::MemOrderAttr(),
1603 /*invariant=*/mlir::UnitAttr());
1604 call.getResult().replaceAllUsesWith(load);
1605 }
1606 call->erase();
1607 return mlir::success();
1608}
1609
1610/// Whether \p ty, a type the classifier named for one register of a
1611/// coercion, is carried in a vector register.
1612bool isSSERegisterClass(mlir::Type ty) {
1613 return mlir::isa<cir::VectorType, cir::FPTypeInterface>(ty);
1614}
1615
1616} // namespace
1617
1618cir::FuncType cir::getIndirectCalleeType(cir::CIRCallOpInterface call) {
1619 assert(call.isIndirect() && "expected an indirect call");
1620 return cast<cir::FuncType>(
1621 cast<cir::PointerType>(call.getIndirectCall().getType()).getPointee());
1622}
1623
1624/// Bring \p funcOp's non-byval indirect parameter \p argNo into the shape the
1625/// rest of the rewrite assumes. \p claimedSlots carries the slots \p funcOp's
1626/// earlier non-byval indirect parameters took. The shape itself, and the
1627/// cases reported rather than repaired, are documented on
1628/// CIRABIRewriteContext::prepareNonByvalParameters in the header.
1629static mlir::LogicalResult prepareOneNonByvalParameter(
1630 cir::FuncOp funcOp, unsigned argNo, const ArgClassification &ac,
1631 mlir::BlockArgument blockArg, mlir::DominanceInfo &dom,
1632 SmallPtrSetImpl<mlir::Operation *> &claimedSlots) {
1633 // The spill is the store that writes the parameter itself. Any other use
1634 // consumes the record value. At -O1 and above such a use comes from
1635 // cir-simplify: CIRGen marks a const-qualified parameter's slot const, so
1636 // the load of it folds to the stored parameter.
1637 cir::StoreOp spill;
1638 cir::StoreOp extraSpill;
1639 mlir::Operation *otherUse = nullptr;
1641 for (mlir::OpOperand &use : blockArg.getUses()) {
1642 auto store = dyn_cast<cir::StoreOp>(use.getOwner());
1643 if (store && store.getValue() == blockArg) {
1644 // Which of two stores is the spill decides which slot stands in for the
1645 // parameter, and the use list is in no particular order, so there is
1646 // nothing to prefer between them.
1647 if (spill)
1648 extraSpill = store;
1649 else
1650 spill = store;
1651 continue;
1652 }
1653 // Only an argument is served by a load of the slot. Any other consumer
1654 // belongs to a rewrite that reads the parameter its own way: a returned
1655 // record, for one, is rewritten through the sret slot, which assumes the
1656 // returned load names the return slot and not this one.
1657 auto call = dyn_cast<cir::CIRCallOpInterface>(use.getOwner());
1658 if (call && llvm::is_contained(call.getArgOperands(), blockArg))
1659 callArgs.push_back(&use);
1660 else if (!otherUse)
1661 otherUse = use.getOwner();
1662 }
1663
1664 if (extraSpill)
1665 return extraSpill->emitOpError()
1666 << "non-byval parameter " << argNo
1667 << " spilled more than once is not yet implemented in "
1668 "CallConvLowering";
1669
1670 if (otherUse)
1671 return otherUse->emitOpError()
1672 << "non-byval parameter " << argNo
1673 << " consumed other than as a call argument is not yet implemented "
1674 "in CallConvLowering";
1675
1676 if (!spill) {
1677 // Every other kind of use was reported above, so the parameter has no
1678 // uses at all and needs neither a slot nor a read.
1679 if (callArgs.empty())
1680 return mlir::success();
1681
1682 // Without a spill the parameter becomes the incoming pointer directly,
1683 // which is the storage an argument taken from it has to name. Giving it
1684 // the spill it lacks lets the checks and the read below apply unchanged,
1685 // and neither survives the pass: insertArgCoercion erases the store and
1686 // finalizeParameterSlots replaces the slot.
1687 mlir::OpBuilder builder(funcOp.getContext());
1688 builder.setInsertionPointToStart(blockArg.getOwner());
1689 auto synthesized = cir::AllocaOp::create(
1690 builder, funcOp.getLoc(), cir::PointerType::get(blockArg.getType()),
1691 builder.getStringAttr("nonbyval.param"),
1692 builder.getI64IntegerAttr(ac.indirectAlign.value()));
1693 spill =
1694 cir::StoreOp::create(builder, funcOp.getLoc(), blockArg, synthesized);
1695 }
1696
1697 // The incoming pointer replaces the slot itself, in the default address
1698 // space. Retargeting a cast of it would leave the allocation's other views
1699 // reading storage nothing writes. Storage in another address space, or
1700 // storage that is not a local alloca at all, is not something the incoming
1701 // pointer can be substituted for.
1702 cir::AllocaOp slot = spill.getAddr().getDefiningOp<cir::AllocaOp>();
1703 if (!slot ||
1704 spill.getAddr().getType() != cir::PointerType::get(blockArg.getType()))
1705 return spill->emitOpError()
1706 << "non-byval parameter " << argNo
1707 << " spilled to storage that cannot take the incoming pointer is "
1708 "not yet implemented in CallConvLowering";
1709
1710 // One slot stands in for one non-byval parameter, since the incoming
1711 // pointer replaces it. Two of them spilled to the same slot are each
1712 // spilled once, so the check above cannot see the collision and only
1713 // comparing the slots can.
1714 if (!claimedSlots.insert(slot).second)
1715 return spill->emitOpError()
1716 << "non-byval parameter " << argNo
1717 << " sharing its spill slot with another parameter is not yet "
1718 "implemented in CallConvLowering";
1719
1720 // CIRGen picked the slot's alignment for a local copy of the record, but the
1721 // slot is about to stand in for the parameter, and a call forwarding it may
1722 // only promise what the incoming pointer does.
1723 slot.setAlignment(ac.indirectAlign.value());
1724
1725 if (callArgs.empty())
1726 return mlir::success();
1727
1728 // A reader the spill does not dominate could read the slot before anything
1729 // wrote it, and a load placed at the spill would not dominate it either.
1730 for (mlir::OpOperand *callArg : callArgs)
1731 if (!dom.properlyDominates(spill, callArg->getOwner()))
1732 return callArg->getOwner()->emitOpError()
1733 << "non-byval parameter " << argNo
1734 << " read before its spill is not yet implemented in "
1735 "CallConvLowering";
1736
1737 // Read the record back out of the slot instead, so every reader sees the
1738 // shape CIRGen emits without cir-simplify: a load naming the storage the
1739 // argument would have to name anyway. Reading at the spill, not at the
1740 // reader, keeps the value the parameter's own whatever the body later
1741 // stores into the slot, and the load promises only the alignment the
1742 // classification gives the incoming pointer.
1743 //
1744 // A call that forwards the argument consumes the load once it recognizes
1745 // the slot. One that wants a copy keeps it, reading the incoming pointer
1746 // once finalizeParameterSlots replaces the slot.
1747 mlir::OpBuilder builder(spill);
1748 builder.setInsertionPointAfter(spill);
1749 auto reload = cir::LoadOp::create(builder, spill.getLoc(), slot);
1750 reload.setAlignment(ac.indirectAlign.value());
1751 for (mlir::OpOperand *callArg : callArgs)
1752 callArg->set(reload.getResult());
1753 return mlir::success();
1754}
1755
1757 cir::FuncOp funcOp, const FunctionClassification &fc) {
1758 if (!funcOp.isDefinition())
1759 return mlir::success();
1760 mlir::Region &body = funcOp->getRegion(0);
1761 if (body.empty())
1762 return mlir::success();
1763 mlir::Block &entry = body.front();
1764 mlir::DominanceInfo dom;
1765 SmallPtrSet<mlir::Operation *, 4> claimedSlots;
1766
1767 // No signature has been rewritten yet, so no sret pointer has been prepended
1768 // and no Expand argument has been split into its fields. Every
1769 // classification therefore still maps to the entry block argument at its own
1770 // index.
1771 for (auto [idx, ac] : llvm::enumerate(fc.argInfos)) {
1772 if (ac.kind != ArgKind::Indirect || ac.byVal)
1773 continue;
1774 assert(idx < entry.getNumArguments() &&
1775 "classification count must not exceed entry block arguments");
1776 if (failed(prepareOneNonByvalParameter(
1777 funcOp, idx, ac, entry.getArgument(idx), dom, claimedSlots)))
1778 return mlir::failure();
1779 }
1780 return mlir::success();
1781}
1782
1784 for (auto [slot, incoming] : pendingParamSlots) {
1785 slot.getResult().replaceAllUsesWith(incoming);
1786 slot->erase();
1787 }
1788 pendingParamSlots.clear();
1789}
1790
1792 mlir::FunctionOpInterface funcOpInterface, const FunctionClassification &fc,
1793 mlir::OpBuilder &builder) {
1794 // The pass driver (CallConvLoweringPass) only ever hands us cir.func ops.
1795 // Cast once at the top so the rest of the function reads in CIR's own
1796 // vocabulary, and so we can dispatch to the CIRGlobalValueInterface for
1797 // isDefinition() (FunctionOpInterface alone does not inherit from
1798 // CIRGlobalValueInterface).
1799 cir::FuncOp funcOp = mlir::cast<cir::FuncOp>(funcOpInterface);
1800
1801 if (!fc.needsRewrite())
1802 return mlir::success();
1803
1804 ArrayRef<mlir::Type> oldArgTypes = funcOp.getArgumentTypes();
1805 ArrayRef<mlir::Type> oldResultTypes = funcOp.getResultTypes();
1806 mlir::MLIRContext *ctx = funcOp->getContext();
1807
1808 // CIR follows LLVM IR's single-result rule: a function returns either
1809 // zero or one value. Document the invariant so a future multi-result
1810 // change forces us to revisit the return-handling below.
1811 assert(oldResultTypes.size() <= 1 &&
1812 "CIR functions return zero or one value");
1813
1814 SmallVector<mlir::Type> newArgTypes;
1815 if (mlir::failed(buildNewArgTypes(oldArgTypes, fc, newArgTypes,
1816 [&]() { return funcOp.emitOpError(); })))
1817 return mlir::failure();
1818
1819 mlir::Type voidTy = cir::VoidType::get(ctx);
1820 mlir::Type origRetTy = oldResultTypes.empty() ? voidTy : oldResultTypes[0];
1821 mlir::Type newRetTy = computeNewReturnType(
1822 origRetTy, fc.returnInfo, ctx, [&]() { return funcOp.emitOpError(); });
1823 if (!newRetTy)
1824 return mlir::failure();
1825 SmallVector<mlir::Type> newResultTypes = {newRetTy};
1826
1827 // sret return: the value is returned through a pointer the ABI inserts as
1828 // argument 0. This pointer is not part of the function's source-level
1829 // signature -- it is synthesized here -- and the wire return type was
1830 // already set to void by computeNewReturnType. Every classification index
1831 // therefore maps to a block argument shifted by one in the body handling
1832 // below.
1833 bool hasSRet =
1834 fc.returnInfo.kind == ArgKind::Indirect && !oldResultTypes.empty();
1835 if (hasSRet)
1836 newArgTypes.insert(newArgTypes.begin(), cir::PointerType::get(origRetTy));
1837
1838 if (funcOp.isDefinition()) {
1839 mlir::Region &body = funcOp->getRegion(0);
1840 if (!body.empty()) {
1841 // Prepend the sret pointer block argument and route every cir.return
1842 // through it before any index-based argument handling below (which
1843 // then accounts for the +1 offset).
1844 if (hasSRet) {
1845 body.front().insertArgument(0u, cir::PointerType::get(origRetTy),
1846 funcOp.getLoc());
1847 insertSRetStores(funcOp, origRetTy, builder);
1848 }
1849
1850 // Reshape the entry block for the classifications that change what a
1851 // parameter looks like to the body: a Direct arg with a coerced type
1852 // gets a memory roundtrip back to its original type, an Indirect arg
1853 // becomes a pointer, and an Expand arg becomes one block argument per
1854 // field. Done before the Ignore-drop below so the entry block
1855 // argument indices used here still refer to the original positions.
1856 if (failed(insertArgCoercion(funcOp, fc, builder, dl, hasSRet,
1857 pendingParamSlots)))
1858 return mlir::failure();
1859
1860 // Direct return with coerced type: insert a coercion at every
1861 // cir.return so the returned value matches the (coerced) return
1862 // type in the new function signature set below.
1863 if (fc.returnInfo.kind == ArgKind::Direct && fc.returnInfo.coercedType &&
1864 !oldResultTypes.empty() && fc.returnInfo.coercedType != origRetTy &&
1865 failed(insertReturnCoercion(
1866 funcOp, origRetTy, fc.returnInfo.coercedType, builder, dl,
1867 cirDataLayout, fc.returnInfo.directOffset)))
1868 return mlir::failure();
1869
1870 mlir::Block &entry = body.front();
1871
1872 // Drop each Ignored argument's block argument, replacing any remaining
1873 // body uses with a poison constant (an Ignore arg is not passed at the
1874 // ABI level, so any use is vacuous; poison says exactly that). Walk
1875 // forward with a running block-argument index that mirrors
1876 // insertArgCoercion: an Expand arg or a Direct+canFlatten arg occupies N
1877 // slots, every other kept kind one. On erase, do not advance the index
1878 // -- the next block argument shifts into the vacated slot.
1879 unsigned blockArgIdx = hasSRet ? 1 : 0;
1880 for (auto [i, ac] : llvm::enumerate(fc.argInfos)) {
1881 if (blockArgIdx >= entry.getNumArguments())
1882 break;
1883 if (ac.kind == ArgKind::Ignore) {
1884 mlir::BlockArgument arg = entry.getArgument(blockArgIdx);
1885 if (!arg.use_empty()) {
1886 builder.setInsertionPointToStart(&entry);
1887 mlir::Value poison =
1888 createIgnoredValue(builder, funcOp.getLoc(), arg.getType());
1889 arg.replaceAllUsesWith(poison);
1890 }
1891 entry.eraseArgument(blockArgIdx);
1892 continue;
1893 }
1894 if (cir::RecordType flatTy = getFlattenedCoercedType(ac))
1895 blockArgIdx += flatTy.getNumElements();
1896 else if (ac.kind == ArgKind::Expand)
1897 blockArgIdx += cast<cir::RecordType>(oldArgTypes[i]).getNumElements();
1898 else
1899 ++blockArgIdx;
1900 }
1901 }
1902
1903 // When the return is classified Ignore but the original function had
1904 // a non-void return type, every cir.return becomes a naked return.
1905 // This relies on the invariant that computeNewReturnType has set
1906 // newRetTy = void for Ignore above, and that the function type is
1907 // updated below to match. Asserting this keeps the dependency
1908 // explicit.
1909 if (fc.returnInfo.kind == ArgKind::Ignore && !oldResultTypes.empty()) {
1910 assert(mlir::isa<cir::VoidType>(newRetTy) &&
1911 "Ignore-return path requires the new return type to be void");
1913 funcOp.walk([&](cir::ReturnOp r) { returns.push_back(r); });
1914 for (cir::ReturnOp r : returns) {
1915 if (r.getNumOperands() == 0)
1916 continue;
1917 builder.setInsertionPoint(r);
1918 cir::ReturnOp::create(builder, r.getLoc());
1919 r.erase();
1920 }
1921 }
1922 }
1923
1924 mlir::Type newFnTy = funcOp.cloneTypeWith(newArgTypes, newResultTypes);
1925 funcOp.setFunctionTypeAttr(mlir::TypeAttr::get(newFnTy));
1926
1927 // Rebuild arg_attrs when the function has an sret slot (slot 0 needs the
1928 // sret attribute set) or any arg needs updateArgAttrs.
1929 if (hasSRet || needsArgAttrUpdate(fc)) {
1930 auto existing = funcOp->getAttrOfType<mlir::ArrayAttr>("arg_attrs");
1931 mlir::ArrayAttr updated =
1932 updateArgAttrs(ctx, module, oldArgTypes, existing, fc, dl);
1933 if (hasSRet) {
1934 // Prepend the sret slot's attribute dict (slot 0); the per-argument
1935 // dicts shift to slots 1..N. noalias is valid only on the callee's
1936 // parameter, so it is added only for definitions.
1937 SmallVector<mlir::NamedAttribute> sretAttrs = buildSretSlotAttrs(
1938 builder, origRetTy, fc.returnInfo.indirectAlign.value(),
1939 /*withNoalias=*/funcOp.isDefinition());
1941 withSret.push_back(mlir::DictionaryAttr::get(ctx, sretAttrs));
1942 llvm::append_range(withSret, updated);
1943 funcOp->setAttr("arg_attrs", mlir::ArrayAttr::get(ctx, withSret));
1944 } else {
1945 funcOp->setAttr("arg_attrs", updated);
1946 }
1947 }
1948
1949 if (mlir::isa<cir::VoidType>(newRetTy)) {
1950 funcOp->removeAttr("res_attrs");
1951 } else if (fc.returnInfo.kind == ArgKind::Extend ||
1952 fc.returnInfo.kind == ArgKind::Direct) {
1953 auto existing = funcOp->getAttrOfType<mlir::ArrayAttr>("res_attrs");
1954 if (mlir::ArrayAttr updated =
1955 updateResAttrs(ctx, existing, fc.returnInfo, origRetTy, dl))
1956 funcOp->setAttr("res_attrs", updated);
1957 }
1958
1959 return mlir::success();
1960}
1961
1962mlir::LogicalResult
1964 const FunctionClassification &fc,
1965 mlir::OpBuilder &builder) {
1966 // The rewrite below pairs the classification with the call's operands one
1967 // for one, so a call passing arguments through an ellipsis needs a
1968 // classification built from its own operands rather than from the callee's
1969 // declared parameters. Both directions of a mismatch have to be reported
1970 // before the pass-through early return, or a call whose declared parameters
1971 // happen to be pass-through is left as written with its surplus operands
1972 // never classified.
1973 //
1974 // A surplus operand means fc was built from the callee's declared
1975 // parameters alone. A shortfall means the callee was declared no_proto,
1976 // which turns off the verifier's argument-count check altogether.
1977 unsigned numOperands =
1978 mlir::cast<cir::CIRCallOpInterface>(callOp).getNumArgOperands();
1979 if (numOperands > fc.argInfos.size())
1980 return callOp->emitOpError()
1981 << "variadic arguments not yet implemented in CallConvLowering";
1982 if (numOperands < fc.argInfos.size())
1983 return callOp->emitOpError()
1984 << "call passes fewer arguments than the callee declares, which is "
1985 "not yet implemented in CallConvLowering";
1986
1987 if (!fc.needsRewrite())
1988 return mlir::success();
1989
1990 if (mlir::isa<cir::TryCallOp>(callOp))
1991 return callOp->emitOpError()
1992 << "TryCallOp not yet implemented in CallConvLowering";
1993
1994 auto call = mlir::cast<cir::CallOp>(callOp);
1995 mlir::MLIRContext *ctx = callOp->getContext();
1996 mlir::Block *slotBlock = coercionSlotBlock(call);
1997
1998 builder.setInsertionPoint(call);
1999
2001 mlir::ValueRange argOperands = call.getArgOperands();
2002 newArgs.reserve(argOperands.size());
2003
2004 // Loads that the new call leaves unused: a Direct (coerced or flattened) or
2005 // Expand record is read out of its load's storage or a copy of it, and an
2006 // Indirect argument passes or copies from the address the load read from.
2007 // The old call still uses them, so erase them only after it is gone.
2008 SmallVector<cir::LoadOp> deadRecordLoads;
2009
2010 // Capture original arg types before building newArgs (an Indirect argument
2011 // changes the wire argument from T to !cir.ptr<T>, so we save the
2012 // pre-rewrite types here for use in updateArgAttrs).
2013 SmallVector<mlir::Type> origCallArgTypes;
2014 llvm::append_range(origCallArgTypes, argOperands.getTypes());
2015
2016 // Operands past an indirect callee's declared parameters were passed
2017 // through its ellipsis. Note where they begin in newArgs, so
2018 // prependIndirectCallee can leave them out of the retyped callee type. A
2019 // direct call has no callee pointer to retype, so it records no boundary.
2020 unsigned numDeclared = fc.argInfos.size();
2021 if (call.isIndirect()) {
2022 numDeclared = getIndirectCalleeType(call).getNumInputs();
2023 assert(numDeclared <= fc.argInfos.size() &&
2024 "the classification covers at least the declared parameters");
2025 }
2026 // Recorded before the Ignore skip below, so an ignored first ellipsis
2027 // argument still marks where the ellipsis arguments begin in newArgs.
2028 std::optional<unsigned> ellipsisStart;
2029 for (auto [idx, ac] : llvm::enumerate(fc.argInfos)) {
2030 if (idx == numDeclared)
2031 ellipsisStart = newArgs.size();
2032 if (ac.kind == ArgKind::Ignore)
2033 continue;
2034 mlir::Value arg = argOperands[idx];
2035 if (cir::RecordType flatTy = getFlattenedCoercedType(ac)) {
2036 // Direct + canFlatten: pass one scalar call argument per field of the
2037 // ABI-coerced struct. A non-atomic record load is read in place or
2038 // through a copy of its storage, as emitCoercedRecordLoad decides, and
2039 // any other operand is stored into a coercion slot first. When the types
2040 // already match, the fields come from the struct value directly (reading
2041 // from its source alloca when possible).
2042 if (arg.getType() != flatTy) {
2043 SmallPtrSet<mlir::Operation *, 4> coercionOps;
2044 assert(!ac.directOffset &&
2045 "the coerced struct starts at slot offset 0 here, so a "
2046 "flattened coercion cannot honor a direct offset");
2047 if (failed(rejectAtomicRecordLoad(call, arg)))
2048 return mlir::failure();
2049 mlir::OpBuilder::InsertionGuard guard(builder);
2050 mlir::Value coercedPtr;
2051 uint64_t baseAlign = 0;
2052 if (cir::LoadOp recLoad = maybeGetCopyableRecordLoad(arg)) {
2053 mlir::Value base = recLoad.getAddr();
2054 if (!recLoad.getIsVolatile() &&
2055 canReadInPlace(recLoad, flatTy, cirDataLayout, /*offset=*/0)) {
2056 cir::AllocaOp baseAlloca = cir::getUnderlyingAlloca(base);
2057 assert(baseAlloca && "canReadInPlace requires an alloca");
2058 baseAlign = baseAlloca.getAlignment();
2059 } else {
2060 base = emitCopiedCoercionSlot(builder, call.getLoc(), recLoad,
2061 flatTy, slotBlock, dl, /*offset=*/0);
2062 }
2063 builder.setInsertionPoint(recLoad);
2064 coercedPtr = emitViewAtOffset(builder, call.getLoc(), flatTy, base,
2065 coercionOps, /*offset=*/0);
2066 deadRecordLoads.push_back(recLoad);
2067 } else {
2068 coercedPtr =
2069 emitCoercionToMemory(builder, call.getLoc(), flatTy, arg,
2070 slotBlock, dl, coercionOps, /*offset=*/0);
2071 }
2072 auto coercedPtrTy = mlir::cast<cir::PointerType>(coercedPtr.getType());
2073 for (auto [f, fieldTy] : llvm::enumerate(flatTy.getMembers())) {
2074 mlir::Type fieldPtrTy =
2075 cir::PointerType::get(fieldTy, coercedPtrTy.getAddrSpace());
2076 auto fieldPtr =
2077 cir::GetMemberOp::create(builder, call.getLoc(), fieldPtrTy,
2078 coercedPtr, /*name=*/"", /*index=*/f);
2079 auto field = cir::LoadOp::create(builder, call.getLoc(), fieldTy,
2080 fieldPtr.getResult());
2081 // Storage read in place may be less aligned than the coerced struct,
2082 // so each field claims only what its offset in it allows.
2083 if (baseAlign)
2084 field.setAlignment(
2085 llvm::MinAlign(baseAlign, flatTy.getElementOffset(dl, f)));
2086 newArgs.push_back(field);
2087 }
2088 } else {
2089 emitStructFieldArgs(builder, call.getLoc(), arg, flatTy, newArgs,
2090 deadRecordLoads);
2091 }
2092 } else if (ac.kind == ArgKind::Expand) {
2093 // Decompose the struct value into its constituent scalar fields and
2094 // pass each as a separate argument.
2095 auto recTy = cast<cir::RecordType>(arg.getType());
2096 assert(recTy.isStruct() &&
2097 "Expand classification requires a struct type, not a union");
2098 emitStructFieldArgs(builder, call.getLoc(), arg, recTy, newArgs,
2099 deadRecordLoads);
2100 } else if (ac.kind == ArgKind::Direct && ac.coercedType &&
2101 arg.getType() != ac.coercedType) {
2102 if (failed(rejectAtomicRecordLoad(call, arg)))
2103 return mlir::failure();
2104 if (cir::LoadOp recLoad = maybeGetCopyableRecordLoad(arg)) {
2105 newArgs.push_back(emitCoercedRecordLoad(
2106 builder, call.getLoc(), recLoad, ac.coercedType, slotBlock, dl,
2107 cirDataLayout, ac.directOffset));
2108 deadRecordLoads.push_back(recLoad);
2109 } else {
2110 newArgs.push_back(emitCoercion(builder, call.getLoc(), ac.coercedType,
2111 arg, slotBlock, dl, ac.directOffset));
2112 }
2113 } else if (ac.kind == ArgKind::Indirect) {
2114 uint64_t minAlign = ac.indirectAlign.value();
2115
2116 // A record operand that names storage is passed from that storage
2117 // rather than from the loaded value: a record value carries only the
2118 // fields of its type, so a store leaves the type's padding bytes
2119 // unwritten, and for a union those can be another member's live data.
2120 // A byval _BitInt is passed from its value, since the load and the
2121 // store below are its conversion between value and in-memory form.
2122 cir::LoadOp srcLoad;
2123 if (!ac.byVal || mlir::isa<cir::RecordType>(arg.getType()))
2124 srcLoad = maybeGetSimpleLoad(arg);
2125
2126 // A non-byval argument must name the caller's storage, so that the
2127 // object the callee operates on is the one the caller destroys. That
2128 // means passing the address the operand was loaded from rather than the
2129 // loaded value, so a store to that storage after the load is visible to
2130 // the callee. byval fills a slot below, this cannot.
2131 if (!ac.byVal) {
2132 cir::LoadOp fwdLoad = forwardableIndirectLoad(srcLoad, minAlign);
2133 if (!fwdLoad)
2134 return call->emitOpError()
2135 << "non-byval indirect argument that does not name the "
2136 "caller's storage is not yet implemented in "
2137 "CallConvLowering";
2138 newArgs.push_back(fwdLoad.getAddr());
2139 deadRecordLoads.push_back(fwdLoad);
2140 continue;
2141 }
2142
2143 // byval hands the callee a copy taken at the call, so naming the object's
2144 // own storage leaves it with the same copy and saves this one. That
2145 // holds only while nothing writes that storage between the load and the
2146 // call, since the operand carries the value as of the load. A volatile
2147 // load is not forwarded, since the copy byval takes at the call is not
2148 // volatile.
2149 if (cir::LoadOp fwdLoad = forwardableIndirectLoad(srcLoad, minAlign);
2150 fwdLoad && storageUnwrittenBetween(fwdLoad, call.getOperation(),
2151 aliasAnalysis)) {
2152 newArgs.push_back(fwdLoad.getAddr());
2153 deadRecordLoads.push_back(fwdLoad);
2154 continue;
2155 }
2156
2157 // Otherwise fill a slot, at the load's position when there is one so
2158 // the copy reads the memory state the load read.
2159 if (failed(rejectAtomicRecordLoad(call, arg)))
2160 return mlir::failure();
2161 if (!srcLoad)
2162 srcLoad = maybeGetCopyableRecordLoad(arg);
2163 auto ptrTy = cir::PointerType::get(arg.getType());
2164 mlir::IntegerAttr slotAlign = builder.getI64IntegerAttr(minAlign);
2165 mlir::OpBuilder::InsertionGuard guard(builder);
2166 if (srcLoad)
2167 builder.setInsertionPoint(srcLoad);
2168
2169 auto slot =
2170 cir::AllocaOp::create(builder, call.getLoc(), ptrTy,
2171 builder.getStringAttr("byval"), slotAlign);
2172 if (srcLoad) {
2173 auto copy = cir::CopyOp::create(builder, call.getLoc(), slot,
2174 srcLoad.getAddr(), slotAlign,
2175 loadSourceAlignment(srcLoad, builder));
2176 copy.setIsVolatile(srcLoad.getIsVolatile());
2177 deadRecordLoads.push_back(srcLoad);
2178 } else {
2179 // A record with no storage behind it, such as a constant, defines no
2180 // bytes beyond its fields. A producer rewritten later turns this store
2181 // into a copy through rewireRecordUses.
2182 cir::StoreOp::create(builder, call.getLoc(), arg, slot);
2183 }
2184 newArgs.push_back(slot);
2185 } else {
2186 newArgs.push_back(arg);
2187 }
2188 }
2189 unsigned numEllipsisArgs =
2190 ellipsisStart ? newArgs.size() - *ellipsisStart : 0;
2191
2192 bool hasResult = call.getNumResults() > 0;
2193 mlir::Type origRetTy =
2194 hasResult ? call.getResult().getType() : cir::VoidType::get(ctx);
2195
2196 // An indirect (sret) return has a different call shape than the coerce /
2197 // extend / ignore return handling further down (the value is returned
2198 // through a prepended pointer slot, not as a result), so dispatch to a
2199 // dedicated helper for it; everything below handles the by-value returns.
2200 if (fc.returnInfo.kind == ArgKind::Indirect && hasResult) {
2201 if (failed(rewriteIndirectReturnCall(call, fc, newArgs, numEllipsisArgs,
2202 origRetTy, origCallArgTypes, builder,
2203 dl)))
2204 return mlir::failure();
2205 eraseDeadRecordLoads(deadRecordLoads);
2206 return mlir::success();
2207 }
2208
2209 mlir::Type callRetTy = origRetTy;
2210 if (fc.returnInfo.kind == ArgKind::Ignore && hasResult)
2211 callRetTy = cir::VoidType::get(ctx);
2212 bool returnNeedsCoercion =
2213 hasResult && fc.returnInfo.kind == ArgKind::Direct &&
2214 fc.returnInfo.coercedType && fc.returnInfo.coercedType != origRetTy;
2215 if (returnNeedsCoercion)
2216 callRetTy = fc.returnInfo.coercedType;
2217
2218 builder.setInsertionPoint(call);
2219 prependIndirectCallee(call, newArgs, callRetTy, numEllipsisArgs, builder);
2220 auto newCall = cir::CallOp::create(builder, call.getLoc(),
2221 call.getCalleeAttr(), callRetTy, newArgs);
2222 copyCallAttributes(call, newCall);
2223
2224 // Direct return with coercion: the new call returns the coerced type, which
2225 // is stored into a coercion slot where the old call's uses find the original
2226 // type.
2227 if (returnNeedsCoercion) {
2228 builder.setInsertionPointAfter(newCall);
2229 SmallPtrSet<mlir::Operation *, 4> coercionOps;
2230 if (failed(emitCoercionAndReplaceUses(builder, call.getLoc(), origRetTy,
2231 newCall.getResult(), call.getResult(),
2232 slotBlock, dl, coercionOps,
2233 fc.returnInfo.directOffset)))
2234 return mlir::failure();
2235 }
2236
2237 if (needsArgAttrUpdate(fc)) {
2238 auto existing = call->getAttrOfType<mlir::ArrayAttr>("arg_attrs");
2239 newCall->setAttr("arg_attrs", updateArgAttrs(ctx, module, origCallArgTypes,
2240 existing, fc, dl));
2241 }
2242 // Add llvm.signext / llvm.zeroext to an Extend return, and drop llvm.noundef
2243 // from a coerced Direct return that passes undefined bits.
2244 if (fc.returnInfo.kind == ArgKind::Extend ||
2245 (hasResult && fc.returnInfo.kind == ArgKind::Direct)) {
2246 auto existing = call->getAttrOfType<mlir::ArrayAttr>("res_attrs");
2247 if (mlir::ArrayAttr updated =
2248 updateResAttrs(ctx, existing, fc.returnInfo, origRetTy, dl))
2249 newCall->setAttr("res_attrs", updated);
2250 } else if (hasResult && mlir::isa<cir::VoidType>(callRetTy)) {
2251 newCall->removeAttr("res_attrs");
2252 }
2253
2254 if (hasResult && fc.returnInfo.kind == ArgKind::Ignore) {
2255 // The new call returns void, but the original call's result may still
2256 // have uses. Substitute a poison constant of the original type so
2257 // those uses remain well-formed without pretending we have a real
2258 // value at the ABI boundary.
2259 if (!call.getResult().use_empty()) {
2260 builder.setInsertionPointAfter(newCall);
2261 mlir::Value poison =
2262 createIgnoredValue(builder, call.getLoc(), origRetTy);
2263 call.getResult().replaceAllUsesWith(poison);
2264 }
2265 } else if (hasResult && !returnNeedsCoercion) {
2266 // returnNeedsCoercion already wired up the coerced result above.
2267 call.getResult().replaceAllUsesWith(newCall.getResult());
2268 }
2269
2270 call->erase();
2271 eraseDeadRecordLoads(deadRecordLoads);
2272
2273 return mlir::success();
2274}
2275
2277 cir::FuncOp funcOp,
2278 mlir::OpBuilder &builder) {
2279 auto oldPtrTy = mlir::cast<cir::PointerType>(addrOp.getAddr().getType());
2280 cir::FuncType newFuncTy = funcOp.getFunctionType();
2281 // An extension rides on an argument attribute and leaves the signature
2282 // alone, so such a callee still matches the written type.
2283 if (newFuncTy == oldPtrTy.getPointee())
2284 return;
2285
2286 // The verifier requires the retype even when nothing reads the address.
2287 addrOp.getAddr().setType(cir::PointerType::get(newFuncTy));
2288 if (addrOp.getAddr().use_empty())
2289 return;
2290
2291 // A later indirect call through the written type is classified and coerced
2292 // on its own, so casting back leaves it to that rewrite.
2293 mlir::OpBuilder::InsertionGuard guard(builder);
2294 builder.setInsertionPointAfter(addrOp);
2295 auto bitcast = cir::CastOp::create(builder, addrOp.getLoc(), oldPtrTy,
2296 cir::CastKind::bitcast, addrOp.getAddr());
2297 addrOp.getAddr().replaceAllUsesExcept(bitcast.getResult(), bitcast);
2298}
2299
2300namespace {
2301
2302/// What one `va_arg` expansion needs from the op it rewrites. The x86-64
2303/// cursor fields are reached through `vaFields` by index: 0 gp_offset,
2304/// 1 fp_offset, 2 overflow_arg_area, 3 reg_save_area.
2305struct VAArgFetch {
2306 VAArgFetch(mlir::Location loc, mlir::Value valist,
2307 llvm::ArrayRef<mlir::Type> vaFields, mlir::Type resultTy,
2308 const ArgClassification &ac, const mlir::DataLayout &dl,
2309 mlir::ModuleOp module)
2310 : loc(loc), valist(valist), vaFields(vaFields), resultTy(resultTy),
2311 ac(ac), dl(dl), module(module) {
2312 assert(vaFields.size() == 4 &&
2313 "the x86-64 va_list is a four-field cursor, checked by the caller");
2314 }
2315
2316 mlir::Location loc;
2317 mlir::Value valist;
2319 mlir::Type resultTy;
2320 const ArgClassification &ac;
2321 const mlir::DataLayout &dl;
2322 mlir::ModuleOp module;
2323};
2324
2325/// The cursor fields a register fetch reads, and the predicate saying every
2326/// class it needs still has room. An offset is null when the fetch needs no
2327/// register of that class.
2328struct RegisterCursor {
2329 mlir::Value gpOffsetP;
2330 mlir::Value fpOffsetP;
2331 mlir::Value gpOffset;
2332 mlir::Value fpOffset;
2333 mlir::Value inRegs;
2334};
2335
2336mlir::LogicalResult reportVAArgNYI(cir::VAArgOp op, llvm::StringRef what) {
2337 op->emitOpError() << "va_arg of " << what
2338 << " not yet implemented in CallConvLowering";
2339 return mlir::failure();
2340}
2341
2342/// Sets \p isRegPair when a two-register argument is a coerced pair, one
2343/// register per member, rather than a single wide scalar, and records in
2344/// \p pairIsSse which element is SSE class. Fails on a coercion that is not
2345/// two eightbytes.
2346mlir::LogicalResult classifyRegisterPair(cir::VAArgOp op,
2347 const ArgClassification &ac,
2348 std::array<bool, 2> &pairIsSse,
2349 bool &isRegPair) {
2350 auto pairTy = mlir::dyn_cast<cir::RecordType>(ac.coercedType);
2351 if (!pairTy)
2352 return mlir::success();
2353
2354 if (pairTy.getNumElements() != 2)
2355 return reportVAArgNYI(op, "a register coercion that is not two eightbytes");
2356
2357 assert(!ac.directOffset &&
2358 "a pair already spans both eightbytes, so it cannot also start "
2359 "partway into the value");
2360 for (auto [i, memberTy] : llvm::enumerate(pairTy.getMembers()))
2361 pairIsSse[i] = isSSERegisterClass(memberTy);
2362 isRegPair = true;
2363 return mlir::success();
2364}
2365
2366/// The alignment an argument is placed at. A record can require more than
2367/// the types of its members imply, from an `aligned` attribute on the record
2368/// or on one of its fields, and neither raises the alignment of any type.
2369/// Only the record layout knows, so the member-derived value alone can be too
2370/// small.
2371uint64_t argumentAreaAlign(mlir::Type ty, mlir::ModuleOp modOp,
2372 const mlir::DataLayout &dl) {
2373 uint64_t align = dl.getTypeABIAlignment(ty);
2374 if (auto recTy = mlir::dyn_cast<cir::RecordType>(ty))
2375 if (auto layout = cir::tryGetRecordLayout(modOp, recTy.getName()))
2376 align = std::max<uint64_t>(align, layout.getRecordAlign());
2377 return align;
2378}
2379
2380mlir::Value roundPointerUpToAlignment(CIRBaseBuilderTy &b, mlir::Location loc,
2381 mlir::Value bytePtr, uint64_t align,
2382 const mlir::DataLayout &dl) {
2383 assert(mlir::cast<cir::PointerType>(bytePtr.getType()).getPointee() ==
2384 b.getUIntNTy(8) &&
2385 "the bump strides in bytes, so the pointee must be u8");
2386 assert(llvm::isPowerOf2_64(align) &&
2387 "mask rounding needs a power-of-two alignment");
2388 mlir::Value bumped =
2389 b.createPtrStride(loc, bytePtr, b.getSignedInt(loc, align - 1, 32));
2390 std::optional<uint64_t> indexWidth =
2391 dl.getTypeIndexBitwidth(bytePtr.getType());
2392 assert(indexWidth && "a pointer in the argument area has an index width");
2393 mlir::Value mask = b.getSignedInt(loc, -static_cast<int64_t>(align),
2394 static_cast<unsigned>(*indexWidth));
2395 return cir::PtrMaskOp::create(b, loc, bytePtr.getType(), bumped, mask);
2396}
2397
2398/// Reads the argument's address out of the overflow area, which also advances
2399/// the cursor past the argument.
2400mlir::Value buildOverflowAddrAndAdvance(CIRBaseBuilderTy &b,
2401 const VAArgFetch &f) {
2402 cir::IntType byteTy = b.getUIntNTy(8);
2403 mlir::Value overflowP = b.createGetMember(
2404 f.loc, b.getPointerTo(f.vaFields[2]), f.valist, "overflow_arg_area", 2);
2405 mlir::Value overflow = b.createLoad(f.loc, overflowP);
2406 mlir::Value bytePtr = b.createPtrBitcast(overflow, byteTy);
2407
2408 uint64_t tyAlign = argumentAreaAlign(f.resultTy, f.module, f.dl);
2409 if (tyAlign > 8)
2410 bytePtr = roundPointerUpToAlignment(b, f.loc, bytePtr, tyAlign, f.dl);
2411
2412 uint64_t stride =
2413 llvm::alignTo(f.dl.getTypeAllocSize(f.resultTy).getFixedValue(), 8);
2414 mlir::Value strideVal = b.getSignedInt(f.loc, stride, 32);
2415 mlir::Value next = b.createPtrStride(f.loc, bytePtr, strideVal);
2416 b.createStore(f.loc, next, overflowP);
2417 return bytePtr;
2418}
2419
2420/// Loads the cursor offsets and builds the predicate that sends the fetch to
2421/// the register-save area. Both offsets count from the start of that area, so
2422/// the integer limit is the six GP registers at 48 and the vector limit is
2423/// those plus the eight SSE registers at 176.
2424RegisterCursor buildRegisterGate(CIRBaseBuilderTy &b, const VAArgFetch &f,
2425 unsigned neededInt, unsigned neededSse) {
2426 RegisterCursor cursor;
2427 if (neededInt) {
2428 cursor.gpOffsetP = b.createGetMember(f.loc, b.getPointerTo(f.vaFields[0]),
2429 f.valist, "gp_offset", 0);
2430 cursor.gpOffset = b.createLoad(f.loc, cursor.gpOffsetP);
2431 mlir::Value limit =
2432 b.getConstantInt(f.loc, cursor.gpOffset.getType(), 48 - neededInt * 8);
2433 cursor.inRegs =
2434 b.createCompare(f.loc, cir::CmpOpKind::le, cursor.gpOffset, limit);
2435 }
2436 if (neededSse) {
2437 cursor.fpOffsetP = b.createGetMember(f.loc, b.getPointerTo(f.vaFields[1]),
2438 f.valist, "fp_offset", 1);
2439 cursor.fpOffset = b.createLoad(f.loc, cursor.fpOffsetP);
2440 mlir::Value limit = b.getConstantInt(f.loc, cursor.fpOffset.getType(),
2441 176 - neededSse * 16);
2442 mlir::Value fitsInFp =
2443 b.createCompare(f.loc, cir::CmpOpKind::le, cursor.fpOffset, limit);
2444 cursor.inRegs = cursor.inRegs
2445 ? b.createLogicalAnd(f.loc, cursor.inRegs, fitsInFp)
2446 : fitsInFp;
2447 }
2448 return cursor;
2449}
2450
2451/// Copies each half of a non-contiguous pair out of the register-save area
2452/// into \p regPairTemp, laid out as the coerced pair.
2453void reassembleRegisterPair(CIRBaseBuilderTy &b, mlir::Location loc,
2454 const ArgClassification &ac,
2455 const RegisterCursor &cursor,
2456 const std::array<bool, 2> &pairIsSse,
2457 mlir::Value regSaveArea, mlir::Value regPairTemp) {
2458 auto pairTy = mlir::cast<cir::RecordType>(ac.coercedType);
2459 // Both halves of an all-SSE pair sit in 16-byte slots, which classic
2460 // CodeGen tells the load about. It leaves a mixed pair to the element's
2461 // own alignment, so match that rather than claiming the slot there.
2462 bool bothSse = pairIsSse[0] && pairIsSse[1];
2463 // Track how many of each class came before, since a slot is reached from
2464 // its class's own cursor.
2465 unsigned seenOfClass[2] = {0, 0};
2466 for (unsigned i = 0; i < 2; ++i) {
2467 bool isSse = pairIsSse[i];
2468 mlir::Value base = isSse ? cursor.fpOffset : cursor.gpOffset;
2469 unsigned regSize = isSse ? 16 : 8;
2470 unsigned prior = seenOfClass[isSse];
2471 ++seenOfClass[isSse];
2472 mlir::Value off = base;
2473 if (prior) {
2474 off = b.createAdd(loc, base,
2475 b.getConstantInt(loc, base.getType(), prior * regSize));
2476 }
2477 mlir::Value src = b.createPtrStride(loc, regSaveArea, off);
2478 mlir::Type elemTy = pairTy.getElementType(i);
2479 mlir::Value elemPtr = b.createPtrBitcast(src, elemTy);
2480 mlir::Value val = bothSse ? b.createAlignedLoad(loc, elemPtr, 16)
2481 : b.createLoad(loc, elemPtr);
2482 b.createStore(
2483 loc, val,
2484 b.createGetMember(loc, b.getPointerTo(elemTy), regPairTemp, "", i));
2485 }
2486}
2487
2488/// The bytes carried by the registers of this fetch's one class.
2489uint64_t registerSlotSize(unsigned neededInt, unsigned neededSse) {
2490 assert(!(neededInt && neededSse) &&
2491 "a fetch needing both classes is a pair, reassembled elsewhere");
2492 return neededSse ? neededSse * 16 : neededInt * 8;
2493}
2494
2495/// Whether the register cannot be read in place, either because it carries
2496/// less than the whole result or because its slot is under-aligned for it.
2497bool needsTempCopy(const VAArgFetch &f, unsigned neededInt,
2498 unsigned neededSse) {
2499 uint64_t tySize = f.dl.getTypeSize(f.resultTy).getFixedValue();
2500 if (f.ac.coercedType &&
2501 (f.ac.directOffset || registerSlotSize(neededInt, neededSse) < tySize))
2502 return true;
2503 // A slot is only as aligned as its class, 8 for a GP register and 16 for an
2504 // SSE one, so a fetch the ABI places more strictly is copied through a temp.
2505 uint64_t slotAlign = neededSse ? 16 : 8;
2506 return argumentAreaAlign(f.resultTy, f.module, f.dl) > slotAlign;
2507}
2508
2509/// Copies what the registers carry into \p temp, which is the size of the
2510/// whole result, and returns the address to read the result from.
2511mlir::Value copyRegisterToTemp(CIRBaseBuilderTy &b, mlir::Location loc,
2512 const VAArgFetch &f, mlir::Value regAddr,
2513 mlir::Value temp, unsigned neededInt,
2514 unsigned neededSse) {
2515 cir::IntType byteTy = b.getUIntNTy(8);
2516 uint64_t tySize = f.dl.getTypeSize(f.resultTy).getFixedValue();
2517
2518 if (f.ac.coercedType &&
2519 (f.ac.directOffset || registerSlotSize(neededInt, neededSse) < tySize)) {
2520 // The registers carry less than the whole result, either because the
2521 // eightbytes below directOffset hold no field or because the result is
2522 // wider than the registers carrying it. Copy only what they carry, so
2523 // that reading the temp cannot run on into the neighboring slot.
2524 mlir::Value val = b.createAlignedLoad(
2525 loc, b.createPtrBitcast(regAddr, f.ac.coercedType), 8);
2526 mlir::Value dst = temp;
2527 if (f.ac.directOffset) {
2528 mlir::Value tempByte = b.createPtrBitcast(temp, byteTy);
2529 mlir::Value offset = b.getSignedInt(loc, f.ac.directOffset, 32);
2530 dst = b.createPtrStride(loc, tempByte, offset);
2531 }
2532 b.createStore(loc, val, b.createPtrBitcast(dst, f.ac.coercedType));
2533 return b.createPtrBitcast(temp, byteTy);
2534 }
2535
2536 mlir::Value regView = b.createPtrBitcast(regAddr, f.resultTy);
2537 if (mlir::isa<cir::RecordType>(f.resultTy))
2538 cir::CopyOp::create(
2539 b, loc, temp, regView,
2540 b.getI64IntegerAttr(argumentAreaAlign(f.resultTy, f.module, f.dl)),
2541 b.getI64IntegerAttr(8));
2542 else
2543 b.createStore(loc, b.createAlignedLoad(loc, regView, 8), temp);
2544 return b.createPtrBitcast(temp, byteTy);
2545}
2546
2547void advanceRegisterCursors(CIRBaseBuilderTy &b, mlir::Location loc,
2548 const RegisterCursor &cursor, unsigned neededInt,
2549 unsigned neededSse) {
2550 if (neededInt) {
2551 b.createStore(loc,
2552 b.createAdd(loc, cursor.gpOffset,
2553 b.getConstantInt(loc, cursor.gpOffset.getType(),
2554 neededInt * 8)),
2555 cursor.gpOffsetP);
2556 }
2557 if (neededSse) {
2558 b.createStore(loc,
2559 b.createAdd(loc, cursor.fpOffset,
2560 b.getConstantInt(loc, cursor.fpOffset.getType(),
2561 neededSse * 16)),
2562 cursor.fpOffsetP);
2563 }
2564}
2565
2566} // namespace
2567
2568mlir::LogicalResult
2569CIRABIRewriteContext::rewriteVAArg(mlir::Operation *vaArgOp,
2570 const ArgClassification &ac,
2571 mlir::OpBuilder &opBuilder) {
2572 auto op = mlir::cast<cir::VAArgOp>(vaArgOp);
2573 CIRBaseBuilderTy builder(opBuilder);
2574 mlir::Location loc = op.getLoc();
2575 mlir::Type resultTy = op.getType();
2576 mlir::Value valist = op.getArgList();
2577
2578 // An ignored type travels in no register and no stack slot, so the fetch
2579 // reads nothing and must leave the cursor unchanged. The value holds no
2580 // bytes, so poison stands in for it.
2581 if (ac.kind == ArgKind::Ignore) {
2582 builder.setInsertionPoint(op);
2583 op.getResult().replaceAllUsesWith(
2584 createIgnoredValue(builder, loc, resultTy));
2585 op->erase();
2586 return mlir::success();
2587 }
2588
2589 if (ac.kind == ArgKind::Indirect && !ac.byVal)
2590 return reportVAArgNYI(op, "a non-trivially-copyable type");
2591
2592 // neededInt counts 8-byte integer slots and neededSse counts 16-byte vector
2593 // slots. Zero of both means the type travels in memory and is read
2594 // straight from the overflow area.
2595 unsigned neededInt = ac.neededIntRegs;
2596 unsigned neededSse = ac.neededSseRegs;
2597
2598 // Which coerced-pair element (0 = low eightbyte, 1 = high) is SSE rather
2599 // than INTEGER class. Only meaningful when isRegPair is set.
2600 std::array<bool, 2> pairIsSse = {false, false};
2601 bool isRegPair = false;
2602 if (ac.kind == ArgKind::Direct && neededInt + neededSse == 2 &&
2603 ac.coercedType) {
2604 if (classifyRegisterPair(op, ac, pairIsSse, isRegPair).failed())
2605 return mlir::failure();
2606 }
2607
2608 auto vaListRecTy = mlir::dyn_cast<cir::RecordType>(
2609 mlir::cast<cir::PointerType>(valist.getType()).getPointee());
2610 if (!vaListRecTy || vaListRecTy.getNumElements() != 4) {
2611 return reportVAArgNYI(op,
2612 "a va_list that is not the four-field gp_offset / "
2613 "fp_offset / overflow_arg_area / reg_save_area "
2614 "cursor");
2615 }
2616
2617 const VAArgFetch fetch{loc, valist, vaListRecTy.getMembers(), resultTy, ac,
2618 dl, module};
2619 cir::IntType byteTy = builder.getUIntNTy(8);
2620
2621 builder.setInsertionPoint(op);
2622
2623 mlir::Value addr;
2624 if (neededInt == 0 && neededSse == 0) {
2625 addr = buildOverflowAddrAndAdvance(builder, fetch);
2626 } else {
2627 RegisterCursor cursor =
2628 buildRegisterGate(builder, fetch, neededInt, neededSse);
2629
2630 // A two-eightbyte pair that is purely INTEGER class is contiguous in the
2631 // register-save area, since GP slots are 8-byte packed, so the address of
2632 // its low eightbyte is already the address of the whole value. A pure
2633 // SSE pair or a mixed pair is not contiguous, since SSE slots are 16-byte
2634 // spaced and a mixed pair's halves live in disjoint areas, so each half is
2635 // copied into a temp laid out as the coerced pair.
2636 bool pairNeedsReassembly = isRegPair && neededSse != 0;
2637
2638 // Both temps are allocated here, since a ternary arm yields their address.
2639 mlir::Value regPairTemp;
2640 if (pairNeedsReassembly) {
2641 // The temp is written one member at a time through the coerced pair, so
2642 // it has to meet that type's alignment, and read back as the result, so
2643 // it has to meet the result's alignment too.
2644 regPairTemp = builder.createAlloca(
2645 loc, builder.getPointerTo(ac.coercedType), "vaarg.reg",
2647 std::max(dl.getTypeABIAlignment(ac.coercedType),
2648 argumentAreaAlign(resultTy, module, dl))));
2649 }
2650
2651 mlir::Value regTemp;
2652 bool copyThroughTemp =
2653 !pairNeedsReassembly && needsTempCopy(fetch, neededInt, neededSse);
2654 if (copyThroughTemp) {
2655 regTemp =
2656 builder.createAlloca(loc, builder.getPointerTo(resultTy), "vaarg.reg",
2658 argumentAreaAlign(resultTy, module, dl)));
2659 }
2660
2661 addr = cir::TernaryOp::create(
2662 builder, loc, cursor.inRegs,
2663 /*trueBuilder=*/
2664 [&](mlir::OpBuilder &ob, mlir::Location l) {
2665 CIRBaseBuilderTy b(ob);
2666 mlir::Value regSaveArea = b.createLoad(
2667 l, b.createGetMember(l, b.getPointerTo(fetch.vaFields[3]),
2668 valist, "reg_save_area", 3));
2669 regSaveArea = b.createPtrBitcast(regSaveArea, byteTy);
2670
2671 mlir::Value regAddr;
2672 if (pairNeedsReassembly) {
2673 reassembleRegisterPair(b, l, ac, cursor, pairIsSse,
2674 regSaveArea, regPairTemp);
2675 regAddr = b.createPtrBitcast(regPairTemp, byteTy);
2676 } else {
2677 mlir::Value off =
2678 neededSse ? cursor.fpOffset : cursor.gpOffset;
2679 regAddr = b.createPtrStride(l, regSaveArea, off);
2680 if (copyThroughTemp) {
2681 regAddr = copyRegisterToTemp(b, l, fetch, regAddr, regTemp,
2682 neededInt, neededSse);
2683 }
2684 }
2685
2686 advanceRegisterCursors(b, l, cursor, neededInt, neededSse);
2687 cir::YieldOp::create(b, l, regAddr);
2688 },
2689 /*falseBuilder=*/
2690 [&](mlir::OpBuilder &ob, mlir::Location l) {
2691 CIRBaseBuilderTy b(ob);
2692 mlir::Value memAddr = buildOverflowAddrAndAdvance(b, fetch);
2693 cir::YieldOp::create(b, l, memAddr);
2694 })
2695 .getResult();
2696 }
2697
2698 // Every path above places the result at least this well aligned.
2699 mlir::Value resultAddr = builder.createPtrBitcast(addr, resultTy);
2700 uint64_t resultAlign = argumentAreaAlign(resultTy, module, dl);
2701 if (mlir::isa<cir::RecordType>(resultTy)) {
2702 SmallPtrSet<mlir::Operation *, 1> noExceptions;
2703 if (failed(rewireRecordUses(builder, loc, op.getResult(), resultAddr,
2704 resultAlign, dl, noExceptions)))
2705 return mlir::failure();
2706 } else {
2707 op.getResult().replaceAllUsesWith(
2708 builder.createAlignedLoad(loc, resultAddr, resultAlign));
2709 }
2710 op->erase();
2711 return mlir::success();
2712}
static mlir::LogicalResult prepareOneNonByvalParameter(cir::FuncOp funcOp, unsigned argNo, const ArgClassification &ac, mlir::BlockArgument blockArg, mlir::DominanceInfo &dom, SmallPtrSetImpl< mlir::Operation * > &claimedSlots)
Bring funcOp's non-byval indirect parameter argNo into the shape the rest of the rewrite assumes.
*collection of selector each with an associated kind and an ordered *collection of selectors A selector has a kind
void rewriteFunctionAddress(cir::GetGlobalOp addrOp, cir::FuncOp funcOp, mlir::OpBuilder &builder)
Retype addrOp, which holds the address of funcOp, to the signature funcOp was rewritten to,...
mlir::LogicalResult rewriteFunctionDefinition(mlir::FunctionOpInterface funcOp, const mlir::abi::FunctionClassification &fc, mlir::OpBuilder &builder) override
void finalizeParameterSlots()
Replace each non-byval indirect parameter's spill slot with the incoming pointer, so the body operate...
mlir::LogicalResult prepareNonByvalParameters(cir::FuncOp funcOp, const mlir::abi::FunctionClassification &fc)
Bring each non-byval indirect parameter of funcOp into the shape the rest of the rewrite assumes: the...
mlir::LogicalResult rewriteVAArg(mlir::Operation *vaArgOp, const mlir::abi::ArgClassification &ac, mlir::OpBuilder &builder) override
Expand a cir.va_arg into the x86-64 SysV register-save-area / overflow-area sequence.
mlir::LogicalResult rewriteCallSite(mlir::Operation *callOp, const mlir::abi::FunctionClassification &fc, mlir::OpBuilder &builder) override
cir::PtrStrideOp createPtrStride(mlir::Location loc, mlir::Value base, mlir::Value stride)
mlir::Value createAdd(mlir::Location loc, mlir::Value lhs, mlir::Value rhs, OverflowBehavior ob=OverflowBehavior::None)
cir::GetMemberOp createGetMember(mlir::Location loc, mlir::Type resultTy, mlir::Value base, llvm::StringRef name, unsigned index)
cir::PointerType getPointerTo(mlir::Type ty)
cir::IntType getUIntNTy(int n)
mlir::Value createPtrBitcast(mlir::Value src, mlir::Type newPointeeTy)
mlir::Value createAlloca(mlir::Location loc, cir::PointerType addrType, llvm::StringRef name, mlir::IntegerAttr alignment, mlir::Value dynAllocSize)
cir::LoadOp createLoad(mlir::Location loc, mlir::Value ptr, bool isVolatile=false, uint64_t alignment=0, bool isNontemporal=false)
mlir::Value getSignedInt(mlir::Location loc, int64_t val, unsigned numBits)
cir::CmpOp createCompare(mlir::Location loc, cir::CmpOpKind kind, mlir::Value lhs, mlir::Value rhs)
cir::ConstantOp getConstantInt(mlir::Location loc, mlir::Type ty, int64_t value)
mlir::Value createAlignedLoad(mlir::Location loc, mlir::Value ptr, uint64_t alignment)
mlir::Value createLogicalAnd(mlir::Location loc, mlir::Value lhs, mlir::Value rhs)
cir::StoreOp createStore(mlir::Location loc, mlir::Value val, mlir::Value dst, bool isVolatile=false, bool isNontemporal=false, mlir::IntegerAttr align={}, cir::SyncScopeKindAttr scope={}, cir::MemOrderAttr order={})
llvm::TypeSize getTypeAllocSize(mlir::Type ty) const
Returns the offset in bytes between successive objects of the specified type, including alignment pad...
llvm::TypeSize getTypeStoreSize(mlir::Type ty) const
Returns the maximum number of bytes that may be overwritten by storing the specified type.
C++ view class that accepts both !cir.struct and !cir.union types.
Definition CIRTypes.h:150
llvm::ArrayRef< mlir::Type > getMembers() const
Definition CIRTypes.cpp:649
mlir::StringAttr getName() const
Definition CIRTypes.cpp:654
bool isStruct() const
Definition CIRTypes.cpp:684
size_t getNumElements() const
Definition CIRTypes.h:188
static CharUnits fromQuantity(QuantityType Quantity)
Construct a CharUnits quantity from a raw integer type.
Definition CharUnits.h:58
RecordLayoutAttr tryGetRecordLayout(mlir::ModuleOp mod, mlir::StringAttr name)
Same lookup as getRecordLayout, but returns a null attribute instead of asserting when the record has...
bool memberOwnsBytes(mlir::Type memberTy)
Whether a record member occupies bytes of its record.
Definition CIRTypes.h:126
cir::FuncType getIndirectCalleeType(cir::CIRCallOpInterface call)
The signature the indirect call call reaches its callee through.
AllocaOp getUnderlyingAlloca(mlir::Value addr)
The alloca that defines addr, looking through casts that preserve the underlying storage.
const internal::VariadicAllOfMatcher< Attr > attr
constexpr size_t align(size_t Size)
Aligns a size to the pointer alignment.
Definition PrimType.h:218
RangeSelector member(std::string ID)
Given a MemberExpr, selects the member token. ID is the node's binding in the match result.
U cast(CodeGen::Address addr)
Definition Address.h:327
unsigned long uint64_t
__builtin_bitreverse32 uint32_t __builtin_riscv_aaddu_u32 __builtin_riscv_asubu_u32 __builtin_elementwise_add_sat __builtin_elementwise_sub_sat int32_t uint32_t uint32_t __builtin_riscv_mhacc_i32 __builtin_riscv_mhaccu_u32 __builtin_riscv_mhaccsu_i32 __builtin_riscv_mulq_i32 int64_t
static bool noaliasOnByvalAttr()