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