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"
18#include "llvm/ADT/STLExtras.h"
19#include "llvm/Support/MathExtras.h"
20#include <algorithm>
21#include <array>
22#include <utility>
23
24using namespace cir;
25using namespace mlir;
26using namespace mlir::abi;
27
28// This rewrite context supports the Direct (with or without coercion),
29// Extend, Ignore, Indirect-return (sret), Indirect-argument (byval and
30// non-byval), and Expand (struct flattening) classifications.
31//
32// An Indirect argument is byval or not, following its classification's
33// byVal flag. byval is a by-value parameter the ABI passes in memory rather
34// than registers, usually for its size. Non-byval is a by-value parameter
35// whose type cannot be copied freely, because it has a non-trivial copy
36// constructor, move constructor, or destructor, so the callee works on the
37// caller's own object rather than a copy.
38//
39// At the call site byval copies into a fresh alloca while a non-byval
40// argument forwards the caller's storage. At the callee, byval loads the
41// incoming pointer (a local copy), while non-byval rewires the CIRGen
42// param-slot alloca to the incoming pointer so the body mutates the caller's
43// storage in place.
44//
45// For Expand, the single struct argument is replaced by N scalar arguments
46// (one per field). At the callee, the N field block arguments are stored
47// directly into the parameter's own alloca (the CIRGen spill slot). At the
48// call site, the struct operand is decomposed into its fields by reading
49// each member from the source alloca (get_member + load) when the operand is
50// a load of an alloca, or via cir.extract_member otherwise.
51//
52// For Direct + canFlatten (where the coerced type is a multi-field struct),
53// the coerced struct is similarly flattened into N individual wire arguments.
54// The callee reassembles the N scalar block args into the coerced struct,
55// then coerces to the original argument type if the two types differ. The
56// call site coerces the original type to the coerced struct, then extracts
57// each field as a separate call argument.
58
59namespace {
60
61/// Return the coerced RecordType for a Direct classification that should be
62/// flattened into individual scalar arguments, or a null type if the
63/// classification does not call for flattening.
64///
65/// Flattening applies when all four conditions hold:
66/// 1. The classification is Direct with a non-null coercedType.
67/// 2. canFlatten is set.
68/// 3. The coercedType is a struct (not a union).
69/// 4. The struct has more than one field (single-field structs are already
70/// scalar; flattening them produces no benefit and classic CodeGen skips
71/// them for the same reason).
72cir::RecordType getFlattenedCoercedType(const ArgClassification &ac) {
73 if (ac.kind != ArgKind::Direct || !ac.coercedType || !ac.canFlatten)
74 return {};
75 auto recTy = dyn_cast<cir::RecordType>(ac.coercedType);
76 if (!recTy || !recTy.isStruct() || recTy.getNumElements() <= 1)
77 return {};
78 return recTy;
79}
80
81/// Build the new argument-type list for a function whose ABI classification
82/// is \p fc. Handles Direct (with or without coercion), Extend, Ignore,
83/// Indirect (byval and non-byval), and Expand (struct flattening) arguments.
84/// The sret return pointer, when present, is prepended by
85/// rewriteFunctionDefinition rather than here.
86mlir::LogicalResult
87buildNewArgTypes(ArrayRef<mlir::Type> oldArgTypes,
88 const FunctionClassification &fc,
89 SmallVectorImpl<mlir::Type> &newArgTypes,
90 function_ref<mlir::InFlightDiagnostic()> emitError) {
91 assert(newArgTypes.empty() && "expected an empty output vector");
92 newArgTypes.reserve(oldArgTypes.size());
93 for (auto [idx, ac] : llvm::enumerate(fc.argInfos)) {
94 mlir::Type origTy = oldArgTypes[idx];
95 switch (ac.kind) {
96 case ArgKind::Direct:
97 // Direct with canFlatten and a struct coerced type: push one wire type
98 // per field of the coerced struct rather than the struct itself.
99 // Single-field coerced structs fall through to the non-flatten path —
100 // the struct is already scalar-sized and flattening adds no value.
101 if (cir::RecordType flatTy = getFlattenedCoercedType(ac)) {
102 llvm::append_range(newArgTypes, flatTy.getMembers());
103 } else {
104 // Direct with a coerced type: the wire signature uses the coerced
105 // type; the body still expects origTy and insertArgCoercion recovers
106 // it via a memory round-trip. Direct without coercion is a
107 // pass-through.
108 newArgTypes.push_back(ac.coercedType ? ac.coercedType : origTy);
109 }
110 break;
111 case ArgKind::Ignore:
112 break;
113 case ArgKind::Expand: {
114 // Flatten the struct into one wire argument per field. The
115 // reassembly in the callee body and the decomposition at the call
116 // site are handled by insertArgCoercion and rewriteCallSite.
117 auto recTy = cast<cir::RecordType>(origTy);
118 assert(recTy.isStruct() &&
119 "Expand classification requires a struct type, not a union");
120 assert(!recTy.getMembers().empty() &&
121 "Expand classification requires at least one struct field");
122 llvm::append_range(newArgTypes, recTy.getMembers());
123 break;
124 }
125 case ArgKind::Extend:
126 // Extend keeps the original (narrow) type in the signature; the
127 // sign/zero extension is communicated to LLVM via the llvm.signext /
128 // llvm.zeroext arg attribute, attached separately below. Any
129 // coercedType the classifier set on the Extend ArgClassification is
130 // informational (typically the register-width type the value gets
131 // extended to in registers) but does not change the CIR signature.
132 newArgTypes.push_back(origTy);
133 break;
134 case ArgKind::Indirect:
135 newArgTypes.push_back(cir::PointerType::get(origTy));
136 break;
137 }
138 }
139 return mlir::success();
140}
141
142/// Compute the new return type for a function whose return classification
143/// is \p retInfo. Direct returns keep (or coerce to) their type, Ignore and
144/// Indirect (sret) returns become void, Extend keeps its type; Expand emits
145/// an error.
146mlir::Type
147computeNewReturnType(mlir::Type origRetTy, const ArgClassification &retInfo,
148 mlir::MLIRContext *ctx,
149 function_ref<mlir::InFlightDiagnostic()> emitError) {
150 switch (retInfo.kind) {
151 case ArgKind::Direct:
152 // Direct return with a coerced type uses the coerced type on the wire;
153 // the rewriter inserts a coercion before each cir.return.
154 return retInfo.coercedType ? retInfo.coercedType : origRetTy;
155 case ArgKind::Ignore:
156 return cir::VoidType::get(ctx);
157 case ArgKind::Expand:
158 emitError() << "Expand return is not allowed (classic codegen rejects "
159 << "it in EmitFunctionEpilog)";
160 return nullptr;
161 case ArgKind::Extend:
162 // Same convention as Extend args: keep the original return type in the
163 // signature; the sign/zero extension is communicated via the
164 // llvm.signext / llvm.zeroext res attribute attached separately below.
165 return origRetTy;
166 case ArgKind::Indirect:
167 // sret: the value is returned through a pointer argument that the ABI
168 // synthesizes (rewriteFunctionDefinition prepends it to the argument
169 // list); it is not part of the source-level signature, so the wire
170 // return type becomes void.
171 return cir::VoidType::get(ctx);
172 }
173 llvm_unreachable("all ArgKind cases handled");
174}
175
176/// Create a typed poison constant to stand in for a value the body of a
177/// function (or the result of a call) still references but whose ABI
178/// classification is Ignore. Using poison is honest -- the value is
179/// genuinely unused at the ABI boundary -- and avoids a fake alloca+load
180/// pattern that would suggest we have a value when we don't.
181mlir::Value createIgnoredValue(mlir::OpBuilder &builder, mlir::Location loc,
182 mlir::Type ty) {
183 return cir::ConstantOp::create(builder, loc, ty, cir::PoisonAttr::get(ty));
184}
185
186/// Build an updated arg_attrs ArrayAttr that drops Ignore'd args, adds
187/// llvm.signext / llvm.zeroext on Extend args, and adds the pointer
188/// attributes for Indirect args. Preserves any existing arg attributes on
189/// retained arg slots. \p origArgTypes provides the pre-rewrite type for
190/// each arg slot.
191mlir::ArrayAttr updateArgAttrs(mlir::MLIRContext *ctx,
192 ArrayRef<mlir::Type> origArgTypes,
193 mlir::ArrayAttr existingArgAttrs,
194 const FunctionClassification &fc,
195 const mlir::DataLayout &dl) {
196 mlir::Builder builder(ctx);
198 newArgAttrs.reserve(fc.argInfos.size());
199 for (auto [oldIdx, ac] : llvm::enumerate(fc.argInfos)) {
200 if (ac.kind == ArgKind::Ignore)
201 continue;
202 mlir::DictionaryAttr existing = builder.getDictionaryAttr({});
203 if (existingArgAttrs && oldIdx < existingArgAttrs.size())
204 existing = mlir::cast<mlir::DictionaryAttr>(existingArgAttrs[oldIdx]);
205 if (cir::RecordType flatTy = getFlattenedCoercedType(ac)) {
206 // Direct + canFlatten: one empty attribute dict per flattened field; the
207 // flattened scalar arguments carry no special ABI attributes.
208 newArgAttrs.append(flatTy.getNumElements(),
209 builder.getDictionaryAttr({}));
210 } else if (ac.kind == ArgKind::Expand) {
211 // Push one empty attribute dict per expanded field; the flattened
212 // scalar arguments carry no special ABI attributes.
213 auto recTy = cast<cir::RecordType>(origArgTypes[oldIdx]);
214 newArgAttrs.append(recTy.getNumElements(), builder.getDictionaryAttr({}));
215 } else if (ac.kind == ArgKind::Extend) {
216 StringRef attrName = ac.signExtend
217 ? mlir::LLVM::LLVMDialect::getSExtAttrName()
218 : mlir::LLVM::LLVMDialect::getZExtAttrName();
219 mlir::NamedAttrList attrs(existing);
220 attrs.set(attrName, builder.getUnitAttr());
221 newArgAttrs.push_back(attrs.getDictionary(ctx));
222 } else if (ac.kind == ArgKind::Indirect) {
223 // byval hands the callee its own copy. Without byval it gets a pointer
224 // to the caller's own object. Both state llvm.align and llvm.noundef,
225 // which constrains the pointer operand, not the pointee's contents.
226 //
227 // llvm.byval(T) records the pre-rewrite arg type because the opaque
228 // LLVM pointer cannot carry it. llvm.nofreeobj says the object cannot
229 // be freed while the callee runs, which holds because the caller owns it
230 // across the call.
231 mlir::Type pointeeTy = origArgTypes[oldIdx];
232 mlir::NamedAttrList attrs(existing);
233 attrs.set(mlir::LLVM::LLVMDialect::getAlignAttrName(),
234 builder.getI64IntegerAttr(ac.indirectAlign.value()));
235 attrs.set(mlir::LLVM::LLVMDialect::getNoUndefAttrName(),
236 builder.getUnitAttr());
237 if (ac.byVal) {
238 // Classic adds llvm.noalias under -fpass-by-value-is-noalias, which
239 // CIR does not plumb through.
241 attrs.set(mlir::LLVM::LLVMDialect::getByValAttrName(),
242 mlir::TypeAttr::get(pointeeTy));
243 } else {
244 // Classic adds llvm.dead_on_return when the object's lifetime ends in
245 // the callee, which needs the destructor's triviality from
246 // cir.record_layout's has_trivial_dtor.
248 attrs.set(mlir::LLVM::LLVMDialect::getNoFreeObjAttrName(),
249 builder.getUnitAttr());
250 attrs.set(mlir::LLVM::LLVMDialect::getDereferenceableAttrName(),
251 builder.getI64IntegerAttr(
252 dl.getTypeSize(pointeeTy).getFixedValue()));
253 }
254 newArgAttrs.push_back(attrs.getDictionary(ctx));
255 } else {
256 newArgAttrs.push_back(existing);
257 }
258 }
259 return builder.getArrayAttr(newArgAttrs);
260}
261
262/// Build an updated res_attrs ArrayAttr (single entry, since CIR funcs have
263/// at most one result) that adds llvm.signext / llvm.zeroext on an Extend
264/// return. Preserves any existing res attributes.
265mlir::ArrayAttr updateResAttrs(mlir::MLIRContext *ctx,
266 mlir::ArrayAttr existingResAttrs,
267 const ArgClassification &retInfo) {
268 if (retInfo.kind != ArgKind::Extend)
269 return existingResAttrs;
270
272 if (existingResAttrs && !existingResAttrs.empty())
273 for (mlir::NamedAttribute na :
274 mlir::cast<mlir::DictionaryAttr>(existingResAttrs[0]))
275 attrs.push_back(na);
276 StringRef attrName = retInfo.signExtend ? "llvm.signext" : "llvm.zeroext";
277 attrs.push_back(mlir::NamedAttribute(mlir::StringAttr::get(ctx, attrName),
278 mlir::UnitAttr::get(ctx)));
279 return mlir::ArrayAttr::get(ctx, {mlir::DictionaryAttr::get(ctx, attrs)});
280}
281
282/// The number of bytes a coercion memory slot needs to hold a value of type
283/// \p ty without truncating it. For most types this is the ordinary storage
284/// size. For a _BitInt it is deliberately the value's own literal byte
285/// footprint (ceil(width/8)) rather than the wider, ABI-alignment-padded
286/// footprint a _BitInt gets as a record member (see
287/// cir::IntType::getStorageTypeWidth): this coercion is about how many bytes
288/// the *value* needs to round-trip, not how a record would lay it out, and
289/// those are genuinely different questions for a _BitInt (e.g. _BitInt(33)
290/// only needs 5 bytes here, even though it occupies 8 padded bytes as a
291/// record member).
292static uint64_t coercionByteSize(mlir::Type ty, const mlir::DataLayout &dl) {
293 if (auto intTy = mlir::dyn_cast<cir::IntType>(ty))
294 return llvm::divideCeil(intTy.getWidth(), 8);
295 return dl.getTypeSize(ty);
296}
297
298/// Coerce \p src into a temporary memory slot typed for \p dstTy at the
299/// current builder insertion point, and return the destination-typed pointer
300/// to that slot without loading the value back out. This is the shared
301/// memory half of emitCoercion: callers that want the whole coerced value use
302/// emitCoercion (below); callers that want to read individual members of a
303/// coerced struct (the call-site struct flattening) take the returned pointer
304/// and emit their own cir.get_member + cir.load per field. Lowers uniformly
305/// for scalar, vector, and record types.
306///
307/// The slot is sized to the larger of the two types so that neither the store
308/// nor a later load ever runs past it: the coerced ABI type can be larger
309/// than the original (e.g. a 12-byte aggregate passed as `{i64, i64}`), so
310/// accessing the destination through a source-sized slot would over-read.
311/// Alignment is max(srcAlign, dstAlign) to satisfy both accesses. The slot
312/// is written through a source-typed view and returned as a destination-typed
313/// view.
314///
315/// The temporary alloca is placed at the start of \p slotBlock, which must
316/// dominate every use of the coerced value and must be a block that ends up
317/// inside the enclosing function's entry block after any later outlining.
318///
319/// \p offset is where the coerced value sits within the larger of the two
320/// types, non-zero when the ABI passes a value in a register read from
321/// partway into its storage. The coerced type must be the smaller of the
322/// two, so one that can exceed the value it coerces, such as a multi-field
323/// register tuple, must not be given an offset.
324///
325/// Any operations the helper creates are appended to \p createdOps so the
326/// caller can pass them to replaceAllUsesExcept and avoid clobbering the
327/// store's value operand when later rewiring the source value.
328mlir::Value emitCoercionToMemory(mlir::OpBuilder &builder, mlir::Location loc,
329 mlir::Type dstTy, mlir::Value src,
330 mlir::Block *slotBlock,
331 const mlir::DataLayout &dl,
332 SmallPtrSetImpl<mlir::Operation *> &createdOps,
333 unsigned offset) {
334 mlir::Type srcTy = src.getType();
335 assert(srcTy != dstTy &&
336 "emitCoercion callers must pre-check that the types differ");
337
338 uint64_t srcAlign = dl.getTypeABIAlignment(srcTy);
339 uint64_t dstAlign = dl.getTypeABIAlignment(dstTy);
340 uint64_t allocaAlign = std::max(srcAlign, dstAlign);
341 mlir::Type slotTy = coercionByteSize(srcTy, dl) >= coercionByteSize(dstTy, dl)
342 ? srcTy
343 : dstTy;
344
345 // Sizes are compared two ways on purpose: relative size in
346 // coercionByteSize terms, which is how slotTy was picked, and capacity in
347 // getTypeSize terms, which is what the alloca is given.
348 [[maybe_unused]] mlir::Type coercedTy = ((slotTy == srcTy) ? dstTy : srcTy);
349 assert((offset == 0 ||
350 coercionByteSize(coercedTy, dl) < coercionByteSize(slotTy, dl)) &&
351 "a direct offset must land on the coerced side, the smaller one");
352 assert((offset == 0 ||
353 offset + dl.getTypeSize(coercedTy) <= dl.getTypeSize(slotTy)) &&
354 "coerce slot too small for offset access");
355 assert((offset == 0 || offset % dl.getTypeABIAlignment(coercedTy) == 0) &&
356 "a direct offset must be aligned for the coerced access");
357
358 auto slotPtrTy = cir::PointerType::get(slotTy);
359 auto srcPtrTy = cir::PointerType::get(srcTy);
360 auto dstPtrTy = cir::PointerType::get(dstTy);
361
362 cir::AllocaOp alloca;
363 {
364 mlir::OpBuilder::InsertionGuard guard(builder);
365 builder.setInsertionPointToStart(slotBlock);
366 alloca = cir::AllocaOp::create(builder, loc, slotPtrTy,
367 builder.getStringAttr("coerce"),
368 builder.getI64IntegerAttr(allocaAlign));
369 }
370 createdOps.insert(alloca);
371
372 // The alloca already has slotTy, so asking for that type returns it
373 // unchanged. Any other type is reached by a bitcast, preceded by a byte
374 // stride when the coerced value lives at an offset.
375 auto slotView = [&](mlir::Type wantTy,
376 cir::PointerType wantPtrTy) -> mlir::Value {
377 if (wantTy == slotTy)
378 return alloca;
379 mlir::Value base = alloca;
380 if (offset != 0) {
381 auto u8Ty =
382 cir::IntType::get(builder.getContext(), 8, /*isSigned=*/false);
383 auto u8PtrTy = cir::PointerType::get(u8Ty);
384 auto u8Base = cir::CastOp::create(builder, loc, u8PtrTy,
385 cir::CastKind::bitcast, alloca);
386 createdOps.insert(u8Base);
387 auto strideTy =
388 cir::IntType::get(builder.getContext(), 64, /*isSigned=*/true);
389 auto strideVal = cir::ConstantOp::create(
390 builder, loc, cir::IntAttr::get(strideTy, offset));
391 createdOps.insert(strideVal);
392 base = cir::PtrStrideOp::create(builder, loc, u8PtrTy, u8Base, strideVal);
393 createdOps.insert(base.getDefiningOp());
394 }
395 auto cast = cir::CastOp::create(builder, loc, wantPtrTy,
396 cir::CastKind::bitcast, base);
397 createdOps.insert(cast);
398 return cast;
399 };
400
401 // Store through a source-typed view of the slot.
402 mlir::Value srcSlot = slotView(srcTy, srcPtrTy);
403 auto store = cir::StoreOp::create(builder, loc, src, srcSlot);
404 createdOps.insert(store);
405
406 // Return a destination-typed view of the slot.
407 return slotView(dstTy, dstPtrTy);
408}
409
410/// Coerce \p src to type \p dstTy by going through memory and load the whole
411/// coerced value back out. Builds on emitCoercionToMemory, adding the final
412/// load of the destination-typed view.
413mlir::Value emitCoercion(mlir::OpBuilder &builder, mlir::Location loc,
414 mlir::Type dstTy, mlir::Value src,
415 mlir::Block *slotBlock, const mlir::DataLayout &dl,
416 SmallPtrSetImpl<mlir::Operation *> &createdOps,
417 unsigned offset) {
418 mlir::Value dstSlot = emitCoercionToMemory(builder, loc, dstTy, src,
419 slotBlock, dl, createdOps, offset);
420 auto load = cir::LoadOp::create(builder, loc, dstSlot);
421 createdOps.insert(load);
422 return load;
423}
424
425/// Convenience overload for callers that don't need the createdOps set
426/// (e.g. call-site coercion where we don't replaceAllUsesExcept).
427mlir::Value emitCoercion(mlir::OpBuilder &builder, mlir::Location loc,
428 mlir::Type dstTy, mlir::Value src,
429 mlir::Block *slotBlock, const mlir::DataLayout &dl,
430 unsigned offset) {
431 SmallPtrSet<mlir::Operation *, 4> ignored;
432 return emitCoercion(builder, loc, dstTy, src, slotBlock, dl, ignored, offset);
433}
434
435/// The block a coercion slot's alloca belongs at the start of.
436///
437/// Normally the enclosing function's entry block, where HoistAllocas expects
438/// allocas to be. A body carrying a call is not always inside a function
439/// when this pass runs, though, because LoweringPrepare runs after it: a
440/// namespace-scope `T g = makeT();` is still in its cir.global ctor region,
441/// and an OpenACC recipe's init and destroy bodies are in regions the module
442/// owns. Those take the outermost region below the module, which dominates
443/// the whole body and travels with it when the body is outlined.
444mlir::Block *coercionSlotBlock(mlir::Operation *op) {
445 if (auto funcOp = op->getParentOfType<mlir::FunctionOpInterface>())
446 return &funcOp->getRegion(0).front();
447 mlir::Region *region = op->getParentRegion();
448 while (mlir::Region *outer = region->getParentRegion()) {
449 if (mlir::isa<mlir::ModuleOp>(outer->getParentOp()))
450 break;
451 region = outer;
452 }
453 assert(!region->empty() && "coercion slot needs a block to hold the alloca");
454 return &region->front();
455}
456
457/// Insert coercion before each cir.return so the returned value matches the
458/// new (coerced) return type.
459void insertReturnCoercion(mlir::FunctionOpInterface funcOp,
460 mlir::Type origRetTy, mlir::Type coercedRetTy,
461 mlir::OpBuilder &builder, const mlir::DataLayout &dl,
462 unsigned offset) {
464 funcOp.walk([&](cir::ReturnOp r) { returns.push_back(r); });
465 for (cir::ReturnOp r : returns) {
466 if (r.getInput().empty())
467 continue;
468 mlir::Value origVal = r.getInput()[0];
469 if (origVal.getType() == coercedRetTy)
470 continue;
471 builder.setInsertionPoint(r);
472 mlir::Value coerced =
473 emitCoercion(builder, r.getLoc(), coercedRetTy, origVal,
474 &funcOp->getRegion(0).front(), dl, offset);
475 r->setOperand(0, coerced);
476 }
477}
478
479/// \p val's defining load, if it is simple, meaning neither volatile nor
480/// atomic. Null otherwise: a non-simple load's access has to survive as
481/// written, and a call result or any other first-class value has no defining
482/// load at all.
483static cir::LoadOp maybeGetSimpleLoad(mlir::Value val) {
484 cir::LoadOp load = val.getDefiningOp<cir::LoadOp>();
485 if (!load || load.getIsVolatile() || load.getMemOrder())
486 return {};
487 return load;
488}
489
490/// \p recordVal's defining load, if it is simple and its address resolves to
491/// an alloca. Null otherwise.
492static cir::LoadOp getWholeRecordLoad(mlir::Value recordVal) {
493 cir::LoadOp load = maybeGetSimpleLoad(recordVal);
494 if (!load || !cir::getUnderlyingAlloca(load.getAddr()))
495 return {};
496 return load;
497}
498
499/// Whether a non-byval indirect argument may name \p addr, given the callee is
500/// told the argument is \p minAlign aligned. A slot allocated here qualifies,
501/// reached through storage-preserving casts, and so does the enclosing
502/// function's own non-byval parameter: its slot stands until
503/// finalizeParameterSlots, and states the alignment the parameter promises
504/// rather than the one CIRGen chose for a local copy.
505///
506/// The slot must already state that alignment. Raising it here would not
507/// survive one that stands in for a parameter, since finalizeParameterSlots
508/// replaces it with the incoming pointer, which would discard the raise and
509/// leave the callee over-promised.
510static bool forwardableNonByvalStorage(mlir::Value addr, uint64_t minAlign) {
511 cir::AllocaOp slot = cir::getUnderlyingAlloca(addr);
512 return slot && slot.getAlignment() >= minAlign;
513}
514
515/// Decompose a struct value into one scalar call argument per field of \p
516/// recTy, appending the field values to \p newArgs. When \p structVal is a
517/// simple load from an alloca, read each field with cir.get_member +
518/// cir.load from the address the load used, emitted at the original load's
519/// position so they observe the same memory state, and record the now-dead
520/// whole-struct load in \p deadRecordLoads for later erasure. Otherwise (a
521/// call result, compound literal, or a volatile or atomic load) extract each
522/// field from the value with cir.extract_member. Loading the members from
523/// memory rather than extracting from a whole-struct value keeps the result in
524/// a form SROA can promote (it does not reason about extractvalue). Shared by
525/// the Expand and Direct+canFlatten argument paths.
526static void emitStructFieldArgs(mlir::OpBuilder &builder, mlir::Location loc,
527 mlir::Value structVal, cir::RecordType recTy,
528 SmallVectorImpl<mlir::Value> &newArgs,
529 SmallVectorImpl<cir::LoadOp> &deadRecordLoads) {
530 cir::LoadOp srcLoad = getWholeRecordLoad(structVal);
531
532 if (srcLoad) {
533 mlir::OpBuilder::InsertionGuard guard(builder);
534 builder.setInsertionPoint(srcLoad);
535 cir::PointerType baseTy = srcLoad.getAddr().getType();
536 for (auto [f, fieldTy] : llvm::enumerate(recTy.getMembers())) {
537 mlir::Type fieldPtrTy =
538 cir::PointerType::get(fieldTy, baseTy.getAddrSpace());
539 mlir::Value fieldPtr = cir::GetMemberOp::create(
540 builder, loc, fieldPtrTy, srcLoad.getAddr(), /*name=*/"",
541 /*index=*/f);
542 newArgs.push_back(cir::LoadOp::create(builder, loc, fieldPtr));
543 }
544 deadRecordLoads.push_back(srcLoad);
545 } else {
546 for (unsigned f = 0; f < recTy.getNumElements(); ++f)
547 newArgs.push_back(
548 cir::ExtractMemberOp::create(builder, loc, structVal, f));
549 }
550}
551
552/// Erase the loads that a rewritten call left unused. The old call must
553/// already be erased, since until then it still counts as a user. One load can
554/// feed two operands of the same call, as in f(s, s), so \p loads can hold the
555/// same load twice. A load that another op still reads is left alone.
556static void eraseDeadRecordLoads(ArrayRef<cir::LoadOp> loads) {
557 llvm::SmallSetVector<mlir::Operation *, 4> uniqueLoads(llvm::from_range,
558 loads);
559 for (mlir::Operation *load : uniqueLoads)
560 if (load->use_empty())
561 load->erase();
562}
563
564/// The store that spills non-byval indirect parameter \p blockArg, and the
565/// slot it spills into. CIRGen spills every by-value parameter into a local
566/// alloca with a single store before any other use, and this pass runs on that
567/// CIRGen output before any alloca-promoting or splitting pass, so the block
568/// argument has exactly that one use. Both results are null when DCE already
569/// removed a dead spill.
570static std::pair<cir::StoreOp, cir::AllocaOp>
571findParamSpill(mlir::BlockArgument blockArg) {
572 if (blockArg.use_empty())
573 return {};
574 assert(blockArg.hasOneUse() &&
575 "non-byval arg must have exactly one use (the CIRGen param spill)");
576 auto store = cast<cir::StoreOp>(*blockArg.user_begin());
577 assert(store.getValue() == blockArg &&
578 "non-byval arg's use must be the value operand of its store");
579 return {store, cast<cir::AllocaOp>(store.getAddr().getDefiningOp())};
580}
581
582/// For each Direct arg with a coerced type, change the block argument's type
583/// to the coerced type and insert a coercion at function entry that maps it
584/// back to the original type for body uses. For each Indirect byval arg,
585/// change the block argument's type to a pointer and insert a load at entry
586/// so the body sees a local copy of the original value type. For each
587/// Indirect non-byval arg, change the block argument to a pointer and queue
588/// the CIRGen param-slot alloca to be replaced by it (no entry load /
589/// byte-copy) so the body operates on the caller's storage in place. For each
590/// Expand arg, replace the single struct block argument with N scalar block
591/// arguments (one per field) and store each field directly into the parameter's
592/// own alloca (the CIRGen spill slot), erasing the original whole-struct store.
593///
594/// \p hasSRetArg is true when the function has an sret return (a hidden return
595/// pointer is prepended as block argument 0). Expand arguments expand the
596/// block argument count, so a running index tracks the current block argument
597/// position rather than computing the classification index + \p hasSRetArg
598/// directly.
599void insertArgCoercion(
600 mlir::FunctionOpInterface funcOp, const FunctionClassification &fc,
601 mlir::OpBuilder &builder, const mlir::DataLayout &dl, bool hasSRetArg,
602 SmallVectorImpl<std::pair<cir::AllocaOp, mlir::BlockArgument>>
603 &pendingParamSlots) {
604 mlir::Region &body = funcOp->getRegion(0);
605 if (body.empty())
606 return;
607 mlir::Block &entry = body.front();
608
609 // Running block argument index. Each non-Expand classification occupies
610 // one block argument slot; each Expand classification occupies N slots
611 // (one per struct field), so the running index must be incremented by N
612 // rather than 1 after processing an Expand arg.
613 unsigned blockArgIdx = hasSRetArg ? 1 : 0;
614
615 for (const ArgClassification &ac : fc.argInfos) {
616 assert(blockArgIdx < entry.getNumArguments() &&
617 "classification count must not exceed entry block arguments");
618
619 if (ac.kind == ArgKind::Expand) {
620 // The block arg at blockArgIdx currently has the original struct type.
621 // Replace it with N scalar args (one per field) and store each field
622 // directly into the parameter's own alloca.
623 mlir::BlockArgument origArg = entry.getArgument(blockArgIdx);
624 auto recTy = cast<cir::RecordType>(origArg.getType());
625 assert(recTy.isStruct() &&
626 "Expand classification requires a struct type, not a union");
627 unsigned numFields = recTy.getNumElements();
628 assert(numFields > 0 &&
629 "Expand classification requires at least one struct field");
630 mlir::Location loc = funcOp.getLoc();
631
632 // CIRGen spills every by-value struct parameter into its local alloca
633 // with a single store before any other use, so the struct block arg's
634 // only use is that spill. Capture it and the destination alloca so the
635 // expanded fields can be stored straight into that alloca, preserving
636 // the alloca's variable name and `init` flag and avoiding a
637 // reassemble-then-reload roundtrip. DCE may have run earlier and
638 // removed the spill (leaving the block arg unused); tolerate that by
639 // only flattening the signature and emitting no field stores.
640 cir::StoreOp paramStore;
641 cir::AllocaOp destAlloca;
642 if (!origArg.use_empty()) {
643 assert(origArg.hasOneUse() &&
644 "Expand arg must have exactly one use (the CIRGen param spill)");
645 paramStore = cast<cir::StoreOp>(*origArg.user_begin());
646 assert(paramStore.getValue() == origArg &&
647 "Expand arg's use must be the value operand of its store");
648 destAlloca = cast<cir::AllocaOp>(paramStore.getAddr().getDefiningOp());
649 }
650
651 // Erase the original whole-struct spill before retyping the block
652 // argument, so the store is never left feeding a type-mismatched value.
653 // The field stores take its place, just before the following operation
654 // (the spill always precedes the entry block's terminator).
655 mlir::Operation *fieldStoreInsertPt = nullptr;
656 if (paramStore) {
657 fieldStoreInsertPt = paramStore->getNextNode();
658 assert(fieldStoreInsertPt &&
659 "param spill must be followed by a block terminator");
660 paramStore->erase();
661 }
662
663 // Split the single struct block arg into N scalar field block args (slot
664 // 0 reuses the original; slots 1..N-1 are inserted after it). The
665 // reshape needs no insertion point. The field stores are gated on the
666 // same destAlloca condition: when the spill survived we set the insert
667 // point to its old slot (which sits after the CIRGen allocas) and store
668 // each field there; when DCE removed the spill the parameter is dead, so
669 // we only reshape the signature and emit no stores.
670 if (destAlloca)
671 builder.setInsertionPoint(fieldStoreInsertPt);
672 for (auto [f, fieldTy] : llvm::enumerate(recTy.getMembers())) {
673 if (f == 0)
674 origArg.setType(fieldTy);
675 else
676 entry.insertArgument(blockArgIdx + f, fieldTy, loc);
677 if (!destAlloca)
678 continue;
679 mlir::Type fieldPtrTy = cir::PointerType::get(fieldTy);
680 auto fieldPtr = cir::GetMemberOp::create(builder, loc, fieldPtrTy,
681 destAlloca, /*name=*/"",
682 /*index=*/f);
683 cir::StoreOp::create(builder, loc, entry.getArgument(blockArgIdx + f),
684 fieldPtr);
685 }
686
687 blockArgIdx += numFields;
688 continue;
689 }
690
691 mlir::BlockArgument blockArg = entry.getArgument(blockArgIdx);
692
693 if (cir::RecordType flatTy = getFlattenedCoercedType(ac)) {
694 // Direct + canFlatten: the coerced type is a struct whose fields become
695 // individual wire arguments. The reconstruction mirrors the Expand path
696 // — replace the single block arg with N scalar block args, store them
697 // into an alloca of the coerced struct type, reload — but then applies
698 // an additional coercion from the coerced struct type to the original
699 // argument type if the two differ in layout.
700 unsigned numFields = flatTy.getNumElements();
701 assert(numFields >= 2 && "getFlattenedCoercedType guarantees >1 fields");
702 Type origTy = blockArg.getType();
703 Location loc = funcOp.getLoc();
704
705 // Change slot 0 to field 0's type; insert slots 1..N-1 after it.
706 blockArg.setType(flatTy.getElementType(0));
707 for (unsigned f = 1; f < numFields; ++f)
708 entry.insertArgument(blockArgIdx + f, flatTy.getElementType(f), loc);
709
710 // setInsertionPointToStart: see comment in the Expand arm above.
711 builder.setInsertionPointToStart(&entry);
712 auto flatPtrTy = cir::PointerType::get(flatTy);
713 uint64_t flatAlign = dl.getTypeABIAlignment(flatTy);
714 auto flatSlot = cir::AllocaOp::create(
715 builder, loc, flatPtrTy, builder.getStringAttr("coerce"),
716 builder.getI64IntegerAttr(flatAlign));
717 SmallPtrSet<Operation *, 8> flattenOps = {flatSlot};
718 for (auto [f, fieldTy] : llvm::enumerate(flatTy.getMembers())) {
719 Type fieldPtrTy = cir::PointerType::get(fieldTy);
720 auto fieldPtr = cir::GetMemberOp::create(builder, loc, fieldPtrTy,
721 flatSlot, /*name=*/"",
722 /*index=*/f);
723 flattenOps.insert(fieldPtr);
724 auto storeOp = cir::StoreOp::create(
725 builder, loc, entry.getArgument(blockArgIdx + f), fieldPtr);
726 flattenOps.insert(storeOp);
727 }
728 auto flatLoaded =
729 cir::LoadOp::create(builder, loc, flatTy, flatSlot.getResult());
730 flattenOps.insert(flatLoaded);
731
732 // If the coerced struct type differs from the original argument type,
733 // insert a memory round-trip to recover the original type for body uses.
734 Value finalVal = flatLoaded;
735 if (origTy != flatTy) {
736 SmallPtrSet<Operation *, 4> coercionOps;
737 assert(!ac.directOffset &&
738 "each field is read from slot offset 0 here, so a flattened "
739 "coercion cannot honor a direct offset");
740 finalVal = emitCoercion(builder, loc, origTy, flatLoaded, &entry, dl,
741 coercionOps, /*offset=*/0);
742 flattenOps.insert(coercionOps.begin(), coercionOps.end());
743 }
744
745 // Replace all original body uses of the struct block arg (now field 0)
746 // with the recovered original-type value.
747 blockArg.replaceAllUsesExcept(finalVal, flattenOps);
748
749 blockArgIdx += numFields;
750 continue;
751 }
752
753 if (ac.kind == ArgKind::Direct && ac.coercedType) {
754 mlir::Type oldArgTy = blockArg.getType();
755 mlir::Type newArgTy = ac.coercedType;
756 if (oldArgTy == newArgTy) {
757 ++blockArgIdx;
758 continue;
759 }
760 blockArg.setType(newArgTy);
761
762 builder.setInsertionPointToStart(&entry);
763 SmallPtrSet<mlir::Operation *, 4> coercionOps;
764 mlir::Value adapted =
765 emitCoercion(builder, funcOp.getLoc(), oldArgTy, blockArg, &entry, dl,
766 coercionOps, ac.directOffset);
767
768 // Replace blockArg uses with the adapted value, except inside the
769 // helper ops we just created. This is critical: the StoreOp's value
770 // operand is blockArg, and if we naively replaceAllUses it gets swapped
771 // to adapted (now of the original type != the alloca's pointee type).
772 blockArg.replaceAllUsesExcept(adapted, coercionOps);
773 } else if (ac.kind == ArgKind::Indirect) {
774 // byval and non-byval both lower to !cir.ptr<T>, and which it is shows
775 // up only in the attrs updateArgAttrs applies. Body lowering differs:
776 // byval copies into the callee (load at entry), while non-byval must
777 // operate on the caller's storage in place.
778 auto ptrTy = cir::PointerType::get(blockArg.getType());
779
780 if (!ac.byVal) {
781 // Without byval, drop the spill store and let the slot's uses read the
782 // incoming pointer, so the body operates on the caller's storage in
783 // place. A byte-copy would be wrong for non-trivially-copyable
784 // aggregates (e.g. libstdc++ SSO std::string, where it would leave
785 // `_M_p` aliasing the source's `_M_local_buf`).
786 auto [paramStore, destAlloca] = findParamSpill(blockArg);
787
788 if (paramStore)
789 paramStore->erase();
790
791 // Update the block argument to point to its original type.
792 blockArg.setType(ptrTy);
793
794 // Pointing the slot's uses at the incoming pointer waits until every
795 // call site has been rewritten. A call that hands this parameter
796 // straight on recognises it by the slot its operand was loaded from,
797 // and collapsing the slot here would leave that call reading a block
798 // argument with no defining operation to inspect. A dead spill DCE
799 // already removed leaves nothing to collapse.
800 if (destAlloca)
801 pendingParamSlots.emplace_back(destAlloca, blockArg);
802 } else {
803 // byval: load the incoming pointer so the body sees a T value (and
804 // any CIRGen param-slot store becomes a local copy of that value).
805 blockArg.setType(ptrTy);
806
807 builder.setInsertionPointToStart(&entry);
808 auto loadOp = cir::LoadOp::create(builder, funcOp.getLoc(), blockArg);
809 SmallPtrSet<mlir::Operation *, 1> loadOps = {loadOp};
810 blockArg.replaceAllUsesExcept(loadOp.getResult(), loadOps);
811 }
812 }
813 // Ignore, Extend, and Direct-without-coerce need no block-level changes.
814
815 ++blockArgIdx;
816 }
817}
818
819/// Rewrite each cir.return so the return value flows through the sret
820/// pointer (the prepended first block argument) and the function returns
821/// void.
822///
823/// CIRGen emits a local `__retval` alloca and emits `cir.return %loaded`
824/// where `%loaded = cir.load __retval`. The naive lowering -- store the
825/// loaded SSA value through the sret pointer -- byte-copies the record,
826/// which is wrong for non-trivially-copyable types: e.g. libstdc++'s SSO
827/// `std::string` has a `_M_p` pointer that aliases the source's internal
828/// `_M_local_buf`, so a byte-copy leaves the destination pointing at the
829/// source's (now-dying) stack storage and the destination's destructor
830/// later `free()`s a stack pointer.
831///
832/// Instead, route construction directly into the sret slot: find the
833/// `__retval` alloca, replace its uses with the sret pointer, and drop the
834/// trailing `cir.load __retval` so the rewritten return has no operand.
835/// The CIRGen-emitted constructor / store-into-`__retval` then targets the
836/// sret slot uniformly, matching classic CodeGen's "construct directly into
837/// `%agg.result`" pattern.
838///
839/// CIRGen emits one `%v = cir.load %__retval` / `cir.return %v` pair per
840/// return statement, and every such load reads the single `__retval`
841/// alloca (CIR does not merge returns into a shared epilogue block). The
842/// alloca is therefore rewired to the sret pointer once; each cir.return is
843/// then collapsed to a bare return and its now-dead load erased. This
844/// `cir.return (cir.load <alloca>)` shape is an invariant guaranteed by
845/// CIRGen, so it is asserted via `cast<>` rather than guarded with a
846/// fallback.
847void insertSRetStores(mlir::FunctionOpInterface funcOp, mlir::Type origRetTy,
848 mlir::OpBuilder &builder) {
849 mlir::Value sretPtr = funcOp.getArguments()[0];
850
852 funcOp->walk([&](cir::ReturnOp retOp) { returnOps.push_back(retOp); });
853
854 cir::AllocaOp retAlloca = nullptr;
855 for (cir::ReturnOp retOp : returnOps) {
856 // Every cir.return in an sret function must carry the loaded return
857 // value -- a bare return would mean the sret slot was never written.
858 assert(!retOp.getInput().empty() &&
859 "cir.return in sret function must have an operand");
860
861 cir::LoadOp retLoad =
862 mlir::cast<cir::LoadOp>(retOp.getInput()[0].getDefiningOp());
863
864 // Rewire the shared `__retval` alloca to the sret pointer once.
865 // replaceAllUsesWith updates every load of the alloca (including those
866 // feeding the other cir.return ops) to read from sretPtr instead, so
867 // all returns are covered by this single rewiring. Only then is the
868 // now-unused alloca safe to erase.
869 if (!retAlloca) {
870 retAlloca = mlir::cast<cir::AllocaOp>(retLoad.getAddr().getDefiningOp());
871 retAlloca.getResult().replaceAllUsesWith(sretPtr);
872 retAlloca->erase();
873 }
874
875 // The sret slot now holds the return value directly; replace the
876 // value-carrying return with a void return (no operand).
877 builder.setInsertionPoint(retOp);
878 cir::ReturnOp::create(builder, retOp.getLoc());
879 retOp->erase();
880 if (retLoad.use_empty())
881 retLoad->erase();
882 }
883}
884
885/// Build the attribute dictionary for the sret slot (slot 0 of an
886/// sret-returning function or call). Matches classic CodeGen's
887/// `sret(T) align A [noalias] writable dead_on_unwind`. noalias is only
888/// valid on the callee's parameter, not at the call site, so it is gated by
889/// \p withNoalias. Key order is irrelevant: DictionaryAttr sorts by name.
890SmallVector<mlir::NamedAttribute> buildSretSlotAttrs(mlir::OpBuilder &builder,
891 mlir::Type retTy,
892 uint64_t align,
893 bool withNoalias) {
895 // The sret type must be carried explicitly: LLVM's sret attribute requires
896 // it, and once the CIR `!cir.ptr<retTy>` lowers to an opaque LLVM `ptr` the
897 // pointee type can no longer be recovered from the pointer.
898 attrs.push_back(
899 builder.getNamedAttr("llvm.sret", mlir::TypeAttr::get(retTy)));
900 attrs.push_back(
901 builder.getNamedAttr("llvm.align", builder.getI64IntegerAttr(align)));
902 if (withNoalias)
903 attrs.push_back(
904 builder.getNamedAttr("llvm.noalias", builder.getUnitAttr()));
905 attrs.push_back(builder.getNamedAttr("llvm.writable", builder.getUnitAttr()));
906 attrs.push_back(
907 builder.getNamedAttr("llvm.dead_on_unwind", builder.getUnitAttr()));
908 return attrs;
909}
910
911/// Prepend the sret slot's attrs at position 0 of newCall's arg_attrs.
912/// Called after the call has been rewritten with the sret pointer at
913/// operand 0, so the operand count now includes the sret slot. \p argAttrs
914/// must already be shaped for the rewritten argument list (Extend slots
915/// carry signext/zeroext, Ignore slots dropped); it is shifted to slots
916/// 1..N behind the sret slot.
917void applySretSlotAttrs(cir::CallOp newCall, mlir::ArrayAttr argAttrs,
918 mlir::Type retTy, uint64_t align,
919 mlir::OpBuilder &builder) {
920 mlir::MLIRContext *ctx = newCall->getContext();
922 buildSretSlotAttrs(builder, retTy, align, /*withNoalias=*/false);
923
925 newArgAttrs.reserve(newCall.getArgOperands().size());
926 newArgAttrs.push_back(mlir::DictionaryAttr::get(ctx, sretAttrs));
927 if (argAttrs)
928 llvm::append_range(newArgAttrs, argAttrs);
929 assert(newArgAttrs.size() <= newCall.getArgOperands().size() &&
930 "arg_attrs wider than the rewritten call's operand list");
931 newArgAttrs.resize(newCall.getArgOperands().size(),
932 mlir::DictionaryAttr::get(ctx));
933 newCall->setAttr("arg_attrs", mlir::ArrayAttr::get(ctx, newArgAttrs));
934}
935
936/// For an indirect call, prepend the callee function pointer as operand 0 so
937/// CallOp::create rebuilds it as an indirect call, bitcasting it to a function
938/// pointer whose signature matches the rewritten operands and return type.
939/// No-op for direct calls.
940static void prependIndirectCallee(cir::CallOp call,
941 SmallVectorImpl<mlir::Value> &args,
942 mlir::Type retTy, mlir::OpBuilder &builder) {
943 if (!call.isIndirect())
944 return;
945 mlir::Value calleePtr = call.getIndirectCall();
946 SmallVector<mlir::Type> paramTypes;
947 paramTypes.reserve(args.size());
948 llvm::transform(args, std::back_inserter(paramTypes),
949 [](mlir::Value v) { return v.getType(); });
950 // Lowering builds an indirect call's LLVM function type from the callee
951 // pointer's pointee and takes the call's result from that type, so the
952 // pointee's return type has to track the rewrite: an sret return would
953 // leave a result the call no longer produces, and a coerced return one of
954 // the wrong type. The ellipsis has to survive for the same reason: the
955 // rebuilt pointee is what makes the lowered call variadic, and only a
956 // variadic call gets the vector-register count that the x86_64 SysV ABI
957 // passes in AL and that the callee's va_arg reads back.
958 auto calleeFnTy = cast<cir::FuncType>(
959 cast<cir::PointerType>(calleePtr.getType()).getPointee());
960 auto newPtrTy = cir::PointerType::get(
961 cir::FuncType::get(paramTypes, retTy, calleeFnTy.isVarArg()));
962 if (calleePtr.getType() != newPtrTy)
963 calleePtr = cir::CastOp::create(builder, call.getLoc(), newPtrTy,
964 cir::CastKind::bitcast, calleePtr);
965 args.insert(args.begin(), calleePtr);
966}
967
968/// Rewrite an indirect-return (sret) call site: prepend a return-slot
969/// pointer as operand 0, make the call return void, and either reuse a
970/// dominating single-use store destination as the slot (so construction
971/// flows directly into it) or allocate a fresh slot and load the result
972/// back out. \p newArgs is the already-shaped (Ignore-dropped,
973/// coercion-applied) non-sret argument list. The caller guarantees the
974/// call has a result and an indirect-return classification.
975void rewriteIndirectReturnCall(cir::CallOp call,
976 const FunctionClassification &fc,
977 ArrayRef<mlir::Value> newArgs,
978 mlir::Type origRetTy,
979 ArrayRef<mlir::Type> origCallArgTypes,
980 mlir::OpBuilder &builder,
981 const mlir::DataLayout &dl) {
982 mlir::MLIRContext *ctx = call->getContext();
983 auto ptrTy = cir::PointerType::get(origRetTy);
984 builder.setInsertionPoint(call);
985 uint64_t sretAlign = fc.returnInfo.indirectAlign.value();
986
987 // CIRGen emits `cir.store %callResult, %dest` when the call's result is
988 // bound to a local (e.g. `T s = make();`). Allocating a fresh sret slot
989 // and copying into %dest would byte-copy the record, which is wrong for
990 // non-trivially-copyable types (the libstdc++ SSO `_M_p` pointer
991 // survives a byte-copy but ends up pointing at the dying temp's local
992 // buffer, so the destination's destructor later `free()`s a stack
993 // pointer). When the result has a single store-into-%dest use, use
994 // %dest as the sret slot directly so construction flows into it,
995 // matching classic CodeGen's "pass %s as sret" pattern. %dest must
996 // dominate the call so the rewritten call (which takes it as operand 0)
997 // does not use a value before its definition.
998 mlir::Value sretSlot = nullptr;
999 cir::StoreOp reuseStore = nullptr;
1000 if (call.getResult().hasOneUse()) {
1001 mlir::Operation *user = *call.getResult().getUsers().begin();
1002 if (auto store = mlir::dyn_cast<cir::StoreOp>(user))
1003 if (store.getValue() == call.getResult() &&
1004 store.getAddr().getType() == ptrTy &&
1005 mlir::DominanceInfo().properlyDominates(store.getAddr(), call)) {
1006 sretSlot = store.getAddr();
1007 reuseStore = store;
1008 }
1009 }
1010 if (!sretSlot) {
1011 auto alloca = cir::AllocaOp::create(
1012 builder, call.getLoc(), ptrTy,
1013 /*name=*/builder.getStringAttr("sret"),
1014 /*alignment=*/builder.getI64IntegerAttr(sretAlign));
1015 sretSlot = alloca;
1016 }
1017
1018 SmallVector<mlir::Value> sretArgs;
1019 sretArgs.push_back(sretSlot);
1020 sretArgs.append(newArgs.begin(), newArgs.end());
1021
1022 mlir::Type sretVoidTy = cir::VoidType::get(ctx);
1023 prependIndirectCallee(call, sretArgs, sretVoidTy, builder);
1024 auto newCall = cir::CallOp::create(
1025 builder, call.getLoc(), call.getCalleeAttr(), sretVoidTy, sretArgs);
1026 for (mlir::NamedAttribute attr : call->getAttrs())
1027 if (!newCall->hasAttr(attr.getName()))
1028 newCall->setAttr(attr.getName(), attr.getValue());
1029 newCall->removeAttr("res_attrs");
1030
1031 // Shape the per-argument attrs exactly as the non-sret path does
1032 // (signext / zeroext for Extend, drop Ignore slots, byval / align for
1033 // Indirect, flatten for Expand and Direct+canFlatten) before prepending the
1034 // sret slot, so sret composes correctly with Extend / Ignore / Indirect /
1035 // Expand / Direct+canFlatten args.
1036 mlir::ArrayAttr argAttrs = call->getAttrOfType<mlir::ArrayAttr>("arg_attrs");
1037 bool needsArgAttrUpdate =
1038 llvm::any_of(fc.argInfos, [](const ArgClassification &ac) {
1039 return ac.kind == ArgKind::Ignore || ac.kind == ArgKind::Extend ||
1040 ac.kind == ArgKind::Indirect || ac.kind == ArgKind::Expand ||
1041 getFlattenedCoercedType(ac);
1042 });
1043 if (needsArgAttrUpdate)
1044 argAttrs = updateArgAttrs(ctx, origCallArgTypes, argAttrs, fc, dl);
1045 applySretSlotAttrs(newCall, argAttrs, origRetTy, sretAlign, builder);
1046
1047 if (reuseStore) {
1048 // The callee now constructs directly into the destination slot, so the
1049 // original store-from-result is redundant; dropping it avoids a
1050 // byte-copy of the record.
1051 reuseStore->erase();
1052 } else {
1053 builder.setInsertionPointAfter(newCall);
1054 auto load = cir::LoadOp::create(builder, call.getLoc(), origRetTy, sretSlot,
1055 /*isDeref=*/mlir::UnitAttr(),
1056 /*isVolatile=*/mlir::UnitAttr(),
1057 /*is_nontemporal=*/mlir::UnitAttr(),
1058 /*alignment=*/mlir::IntegerAttr(),
1059 /*sync_scope=*/cir::SyncScopeKindAttr(),
1060 /*mem_order=*/cir::MemOrderAttr(),
1061 /*invariant=*/mlir::UnitAttr());
1062 call.getResult().replaceAllUsesWith(load);
1063 }
1064 call->erase();
1065}
1066
1067/// Whether \p ty, a type the classifier named for one register of a
1068/// coercion, is carried in a vector register.
1069bool isSSERegisterClass(mlir::Type ty) {
1070 return mlir::isa<cir::VectorType, cir::FPTypeInterface>(ty);
1071}
1072
1073} // namespace
1074
1076 cir::FuncOp funcOp, const FunctionClassification &fc) {
1077 if (!funcOp.isDefinition())
1078 return;
1079 mlir::Region &body = funcOp->getRegion(0);
1080 if (body.empty())
1081 return;
1082 mlir::Block &entry = body.front();
1083
1084 // No signature has been rewritten yet, so no sret pointer has been prepended
1085 // and no Expand argument has been split into its fields. Every
1086 // classification therefore still maps to the entry block argument at its own
1087 // index.
1088 for (auto [idx, ac] : llvm::enumerate(fc.argInfos)) {
1089 if (ac.kind != ArgKind::Indirect || ac.byVal)
1090 continue;
1091 assert(idx < entry.getNumArguments() &&
1092 "classification count must not exceed entry block arguments");
1093 if (cir::AllocaOp slot = findParamSpill(entry.getArgument(idx)).second)
1094 slot.setAlignment(ac.indirectAlign.value());
1095 }
1096}
1097
1099 for (auto [slot, incoming] : pendingParamSlots) {
1100 slot.getResult().replaceAllUsesWith(incoming);
1101 slot->erase();
1102 }
1103 pendingParamSlots.clear();
1104}
1105
1107 mlir::FunctionOpInterface funcOpInterface, const FunctionClassification &fc,
1108 mlir::OpBuilder &builder) {
1109 // The pass driver (CallConvLoweringPass) only ever hands us cir.func ops.
1110 // Cast once at the top so the rest of the function reads in CIR's own
1111 // vocabulary, and so we can dispatch to the CIRGlobalValueInterface for
1112 // isDefinition() (FunctionOpInterface alone does not inherit from
1113 // CIRGlobalValueInterface).
1114 cir::FuncOp funcOp = mlir::cast<cir::FuncOp>(funcOpInterface);
1115
1116 if (!fc.needsRewrite())
1117 return mlir::success();
1118
1119 ArrayRef<mlir::Type> oldArgTypes = funcOp.getArgumentTypes();
1120 ArrayRef<mlir::Type> oldResultTypes = funcOp.getResultTypes();
1121 mlir::MLIRContext *ctx = funcOp->getContext();
1122
1123 // CIR follows LLVM IR's single-result rule: a function returns either
1124 // zero or one value. Document the invariant so a future multi-result
1125 // change forces us to revisit the return-handling below.
1126 assert(oldResultTypes.size() <= 1 &&
1127 "CIR functions return zero or one value");
1128
1129 SmallVector<mlir::Type> newArgTypes;
1130 if (mlir::failed(buildNewArgTypes(oldArgTypes, fc, newArgTypes,
1131 [&]() { return funcOp.emitOpError(); })))
1132 return mlir::failure();
1133
1134 mlir::Type voidTy = cir::VoidType::get(ctx);
1135 mlir::Type origRetTy = oldResultTypes.empty() ? voidTy : oldResultTypes[0];
1136 mlir::Type newRetTy = computeNewReturnType(
1137 origRetTy, fc.returnInfo, ctx, [&]() { return funcOp.emitOpError(); });
1138 if (!newRetTy)
1139 return mlir::failure();
1140 SmallVector<mlir::Type> newResultTypes = {newRetTy};
1141
1142 // sret return: the value is returned through a pointer the ABI inserts as
1143 // argument 0. This pointer is not part of the function's source-level
1144 // signature -- it is synthesized here -- and the wire return type was
1145 // already set to void by computeNewReturnType. Every classification index
1146 // therefore maps to a block argument shifted by one in the body handling
1147 // below.
1148 bool hasSRet =
1149 fc.returnInfo.kind == ArgKind::Indirect && !oldResultTypes.empty();
1150 if (hasSRet)
1151 newArgTypes.insert(newArgTypes.begin(), cir::PointerType::get(origRetTy));
1152
1153 if (funcOp.isDefinition()) {
1154 mlir::Region &body = funcOp->getRegion(0);
1155 if (!body.empty()) {
1156 // Prepend the sret pointer block argument and route every cir.return
1157 // through it before any index-based argument handling below (which
1158 // then accounts for the +1 offset).
1159 if (hasSRet) {
1160 body.front().insertArgument(0u, cir::PointerType::get(origRetTy),
1161 funcOp.getLoc());
1162 insertSRetStores(funcOp, origRetTy, builder);
1163 }
1164
1165 // In-body coercion for Direct-with-coerce / Extend args: change
1166 // block-arg types to the coerced types and insert a memory roundtrip
1167 // at the top of the entry block that converts each coerced value back
1168 // to its original type, then route existing body uses (including
1169 // in-body cir.call operands) through the recovered value. Done before
1170 // the Ignore-drop below so the entry block argument indices used here
1171 // still refer to the original positions.
1172 insertArgCoercion(funcOp, fc, builder, dl, hasSRet, pendingParamSlots);
1173
1174 // Direct return with coerced type: insert a coercion at every
1175 // cir.return so the returned value matches the (coerced) return
1176 // type in the new function signature set below.
1177 if (fc.returnInfo.kind == ArgKind::Direct && fc.returnInfo.coercedType &&
1178 !oldResultTypes.empty() && fc.returnInfo.coercedType != origRetTy)
1179 insertReturnCoercion(funcOp, origRetTy, fc.returnInfo.coercedType,
1180 builder, dl, fc.returnInfo.directOffset);
1181
1182 mlir::Block &entry = body.front();
1183
1184 // Drop each Ignored argument's block argument, replacing any remaining
1185 // body uses with a poison constant (an Ignore arg is not passed at the
1186 // ABI level, so any use is vacuous; poison says exactly that). Walk
1187 // forward with a running block-argument index that mirrors
1188 // insertArgCoercion: an Expand arg or a Direct+canFlatten arg occupies N
1189 // slots, every other kept kind one. On erase, do not advance the index
1190 // -- the next block argument shifts into the vacated slot.
1191 unsigned blockArgIdx = hasSRet ? 1 : 0;
1192 for (auto [i, ac] : llvm::enumerate(fc.argInfos)) {
1193 if (blockArgIdx >= entry.getNumArguments())
1194 break;
1195 if (ac.kind == ArgKind::Ignore) {
1196 mlir::BlockArgument arg = entry.getArgument(blockArgIdx);
1197 if (!arg.use_empty()) {
1198 builder.setInsertionPointToStart(&entry);
1199 mlir::Value poison =
1200 createIgnoredValue(builder, funcOp.getLoc(), arg.getType());
1201 arg.replaceAllUsesWith(poison);
1202 }
1203 entry.eraseArgument(blockArgIdx);
1204 continue;
1205 }
1206 if (cir::RecordType flatTy = getFlattenedCoercedType(ac))
1207 blockArgIdx += flatTy.getNumElements();
1208 else if (ac.kind == ArgKind::Expand)
1209 blockArgIdx += cast<cir::RecordType>(oldArgTypes[i]).getNumElements();
1210 else
1211 ++blockArgIdx;
1212 }
1213 }
1214
1215 // When the return is classified Ignore but the original function had
1216 // a non-void return type, every cir.return becomes a naked return.
1217 // This relies on the invariant that computeNewReturnType has set
1218 // newRetTy = void for Ignore above, and that the function type is
1219 // updated below to match. Asserting this keeps the dependency
1220 // explicit.
1221 if (fc.returnInfo.kind == ArgKind::Ignore && !oldResultTypes.empty()) {
1222 assert(mlir::isa<cir::VoidType>(newRetTy) &&
1223 "Ignore-return path requires the new return type to be void");
1225 funcOp.walk([&](cir::ReturnOp r) { returns.push_back(r); });
1226 for (cir::ReturnOp r : returns) {
1227 if (r.getNumOperands() == 0)
1228 continue;
1229 builder.setInsertionPoint(r);
1230 cir::ReturnOp::create(builder, r.getLoc());
1231 r.erase();
1232 }
1233 }
1234 }
1235
1236 mlir::Type newFnTy = funcOp.cloneTypeWith(newArgTypes, newResultTypes);
1237 funcOp.setFunctionTypeAttr(mlir::TypeAttr::get(newFnTy));
1238
1239 // Rebuild arg_attrs when the function has an sret slot (slot 0 needs the
1240 // sret attribute set) or any arg is Ignore (dropped from the output array),
1241 // Extend (needs llvm.signext / llvm.zeroext), Indirect (gains the pointer
1242 // attributes updateArgAttrs applies), Expand or Direct+canFlatten (both
1243 // change the argument count).
1244 bool needsArgAttrUpdate =
1245 hasSRet || llvm::any_of(fc.argInfos, [](const ArgClassification &ac) {
1246 return ac.kind == ArgKind::Ignore || ac.kind == ArgKind::Extend ||
1247 ac.kind == ArgKind::Indirect || ac.kind == ArgKind::Expand ||
1248 getFlattenedCoercedType(ac);
1249 });
1250 if (needsArgAttrUpdate) {
1251 auto existing = funcOp->getAttrOfType<mlir::ArrayAttr>("arg_attrs");
1252 mlir::ArrayAttr updated =
1253 updateArgAttrs(ctx, oldArgTypes, existing, fc, dl);
1254 if (hasSRet) {
1255 // Prepend the sret slot's attribute dict (slot 0); the per-argument
1256 // dicts shift to slots 1..N. noalias is valid only on the callee's
1257 // parameter, so it is added only for definitions.
1258 SmallVector<mlir::NamedAttribute> sretAttrs = buildSretSlotAttrs(
1259 builder, origRetTy, fc.returnInfo.indirectAlign.value(),
1260 /*withNoalias=*/funcOp.isDefinition());
1262 withSret.push_back(mlir::DictionaryAttr::get(ctx, sretAttrs));
1263 llvm::append_range(withSret, updated);
1264 funcOp->setAttr("arg_attrs", mlir::ArrayAttr::get(ctx, withSret));
1265 } else {
1266 funcOp->setAttr("arg_attrs", updated);
1267 }
1268 }
1269
1270 if (mlir::isa<cir::VoidType>(newRetTy)) {
1271 funcOp->removeAttr("res_attrs");
1272 } else if (fc.returnInfo.kind == ArgKind::Extend) {
1273 // Layer llvm.signext / llvm.zeroext onto an Extend return.
1274 auto existing = funcOp->getAttrOfType<mlir::ArrayAttr>("res_attrs");
1275 funcOp->setAttr("res_attrs", updateResAttrs(ctx, existing, fc.returnInfo));
1276 }
1277
1278 return mlir::success();
1279}
1280
1281mlir::LogicalResult
1283 const FunctionClassification &fc,
1284 mlir::OpBuilder &builder) {
1285 // The classification covers exactly the callee's declared parameters, and
1286 // the rewrite below pairs it with the call's operands one for one. Both
1287 // directions of a mismatch have to be reported before the pass-through early
1288 // return, or a call whose declared parameters happen to be pass-through is
1289 // left as written with its surplus operands never classified.
1290 //
1291 // A surplus operand went through an ellipsis. A shortfall means the callee
1292 // was declared no_proto, which turns off the verifier's argument-count check
1293 // altogether.
1294 unsigned numOperands =
1295 mlir::cast<cir::CIRCallOpInterface>(callOp).getNumArgOperands();
1296 if (numOperands > fc.argInfos.size())
1297 return callOp->emitOpError()
1298 << "variadic arguments not yet implemented in CallConvLowering";
1299 if (numOperands < fc.argInfos.size())
1300 return callOp->emitOpError()
1301 << "call passes fewer arguments than the callee declares, which is "
1302 "not yet implemented in CallConvLowering";
1303
1304 if (!fc.needsRewrite())
1305 return mlir::success();
1306
1307 if (mlir::isa<cir::TryCallOp>(callOp))
1308 return callOp->emitOpError()
1309 << "TryCallOp not yet implemented in CallConvLowering";
1310
1311 auto call = mlir::cast<cir::CallOp>(callOp);
1312 mlir::MLIRContext *ctx = callOp->getContext();
1313 mlir::Block *slotBlock = coercionSlotBlock(call);
1314
1315 builder.setInsertionPoint(call);
1316
1318 mlir::ValueRange argOperands = call.getArgOperands();
1319 newArgs.reserve(argOperands.size());
1320
1321 // Loads that the new call leaves unused: Expand and Direct+canFlatten read
1322 // the fields out of the source alloca, and a non-byval argument passes the
1323 // address the load read from. The old call still uses them, so erase them
1324 // only after it is gone.
1325 SmallVector<cir::LoadOp> deadRecordLoads;
1326
1327 // Capture original arg types before building newArgs (byval slots change
1328 // the wire argument from T to !cir.ptr<T>, so we save the pre-rewrite
1329 // types here for use in updateArgAttrs).
1330 SmallVector<mlir::Type> origCallArgTypes;
1331 llvm::append_range(origCallArgTypes, argOperands.getTypes());
1332 for (auto [idx, ac] : llvm::enumerate(fc.argInfos)) {
1333 if (ac.kind == ArgKind::Ignore)
1334 continue;
1335 mlir::Value arg = argOperands[idx];
1336 if (cir::RecordType flatTy = getFlattenedCoercedType(ac)) {
1337 // Direct + canFlatten: pass one scalar call argument per field of the
1338 // ABI-coerced struct. When the original and coerced types differ in
1339 // layout, coerce through a memory slot and read each field with
1340 // cir.get_member + cir.load from that slot. When the types already
1341 // match, decompose the struct value directly (reading from its source
1342 // alloca when possible).
1343 if (arg.getType() != flatTy) {
1344 SmallPtrSet<mlir::Operation *, 4> coercionOps;
1345 assert(!ac.directOffset &&
1346 "each field is read from slot offset 0 here, so a flattened "
1347 "coercion cannot honor a direct offset");
1348 mlir::Value coercedPtr =
1349 emitCoercionToMemory(builder, call.getLoc(), flatTy, arg, slotBlock,
1350 dl, coercionOps, /*offset=*/0);
1351 for (auto [f, fieldTy] : llvm::enumerate(flatTy.getMembers())) {
1352 mlir::Type fieldPtrTy = cir::PointerType::get(fieldTy);
1353 auto fieldPtr =
1354 cir::GetMemberOp::create(builder, call.getLoc(), fieldPtrTy,
1355 coercedPtr, /*name=*/"", /*index=*/f);
1356 newArgs.push_back(cir::LoadOp::create(builder, call.getLoc(), fieldTy,
1357 fieldPtr.getResult()));
1358 }
1359 } else {
1360 emitStructFieldArgs(builder, call.getLoc(), arg, flatTy, newArgs,
1361 deadRecordLoads);
1362 }
1363 } else if (ac.kind == ArgKind::Expand) {
1364 // Decompose the struct value into its constituent scalar fields and
1365 // pass each as a separate argument.
1366 auto recTy = cast<cir::RecordType>(arg.getType());
1367 assert(recTy.isStruct() &&
1368 "Expand classification requires a struct type, not a union");
1369 emitStructFieldArgs(builder, call.getLoc(), arg, recTy, newArgs,
1370 deadRecordLoads);
1371 } else if (ac.kind == ArgKind::Direct && ac.coercedType &&
1372 arg.getType() != ac.coercedType) {
1373 arg = emitCoercion(builder, call.getLoc(), ac.coercedType, arg, slotBlock,
1374 dl, ac.directOffset);
1375 newArgs.push_back(arg);
1376 } else if (ac.kind == ArgKind::Indirect) {
1377 // byval hands the callee its own copy. Without byval the argument must
1378 // name the caller's storage instead, so that the object the callee
1379 // operates on is the one the caller destroys. That means forwarding
1380 // the address the operand was loaded from rather than the loaded value,
1381 // so a store to that storage after the load is visible to the callee.
1382 if (!ac.byVal) {
1383 // The rewritten parameter is a pointer to the argument type in the
1384 // default address space, so an operand read through an address-space
1385 // cast cannot be handed on as it stands. cir.load already pins the
1386 // pointee type, so only the address space can differ.
1387 cir::LoadOp srcLoad = maybeGetSimpleLoad(arg);
1388 if (!srcLoad ||
1389 srcLoad.getAddr().getType() !=
1390 cir::PointerType::get(arg.getType()) ||
1391 !forwardableNonByvalStorage(srcLoad.getAddr(),
1392 ac.indirectAlign.value()))
1393 return call->emitOpError()
1394 << "non-byval indirect argument that does not name the "
1395 "caller's storage is not yet implemented in "
1396 "CallConvLowering";
1397 newArgs.push_back(srcLoad.getAddr());
1398 deadRecordLoads.push_back(srcLoad);
1399 continue;
1400 }
1401 auto ptrTy = cir::PointerType::get(arg.getType());
1402 auto slot = cir::AllocaOp::create(
1403 builder, call.getLoc(), ptrTy, builder.getStringAttr("byval"),
1404 builder.getI64IntegerAttr(ac.indirectAlign.value()));
1405 cir::StoreOp::create(builder, call.getLoc(), arg, slot);
1406 newArgs.push_back(slot);
1407 } else {
1408 newArgs.push_back(arg);
1409 }
1410 }
1411
1412 bool hasResult = call.getNumResults() > 0;
1413 mlir::Type origRetTy =
1414 hasResult ? call.getResult().getType() : cir::VoidType::get(ctx);
1415
1416 // An indirect (sret) return has a different call shape than the coerce /
1417 // extend / ignore return handling further down (the value is returned
1418 // through a prepended pointer slot, not as a result), so dispatch to a
1419 // dedicated helper for it; everything below handles the by-value returns.
1420 if (fc.returnInfo.kind == ArgKind::Indirect && hasResult) {
1421 rewriteIndirectReturnCall(call, fc, newArgs, origRetTy, origCallArgTypes,
1422 builder, dl);
1423 eraseDeadRecordLoads(deadRecordLoads);
1424 return mlir::success();
1425 }
1426
1427 mlir::Type callRetTy = origRetTy;
1428 if (fc.returnInfo.kind == ArgKind::Ignore && hasResult)
1429 callRetTy = cir::VoidType::get(ctx);
1430 bool returnNeedsCoercion =
1431 hasResult && fc.returnInfo.kind == ArgKind::Direct &&
1432 fc.returnInfo.coercedType && fc.returnInfo.coercedType != origRetTy;
1433 if (returnNeedsCoercion)
1434 callRetTy = fc.returnInfo.coercedType;
1435
1436 builder.setInsertionPoint(call);
1437 prependIndirectCallee(call, newArgs, callRetTy, builder);
1438 auto newCall = cir::CallOp::create(builder, call.getLoc(),
1439 call.getCalleeAttr(), callRetTy, newArgs);
1440 for (mlir::NamedAttribute attr : call->getAttrs())
1441 if (!newCall->hasAttr(attr.getName()))
1442 newCall->setAttr(attr.getName(), attr.getValue());
1443
1444 // Direct return with coercion: the new call returns the coerced type;
1445 // emit a coercion back to the original type for the call's existing uses.
1446 if (returnNeedsCoercion) {
1447 builder.setInsertionPointAfter(newCall);
1448 mlir::Value coercedBack =
1449 emitCoercion(builder, call.getLoc(), origRetTy, newCall.getResult(),
1450 slotBlock, dl, fc.returnInfo.directOffset);
1451 call.getResult().replaceAllUsesWith(coercedBack);
1452 }
1453
1454 // Layer llvm.signext / llvm.zeroext onto the new call's arg_attrs and
1455 // res_attrs for Extend args/return. Ignore args require a rebuild because
1456 // their slots are dropped; Indirect args need llvm.byval / llvm.align;
1457 // Expand and Direct+canFlatten args change the argument count.
1458 bool needsArgAttrUpdate =
1459 llvm::any_of(fc.argInfos, [](const ArgClassification &ac) {
1460 return ac.kind == ArgKind::Ignore || ac.kind == ArgKind::Extend ||
1461 ac.kind == ArgKind::Indirect || ac.kind == ArgKind::Expand ||
1462 getFlattenedCoercedType(ac);
1463 });
1464 if (needsArgAttrUpdate) {
1465 auto existing = call->getAttrOfType<mlir::ArrayAttr>("arg_attrs");
1466 newCall->setAttr("arg_attrs",
1467 updateArgAttrs(ctx, origCallArgTypes, existing, fc, dl));
1468 }
1469 if (fc.returnInfo.kind == ArgKind::Extend) {
1470 auto existing = call->getAttrOfType<mlir::ArrayAttr>("res_attrs");
1471 newCall->setAttr("res_attrs", updateResAttrs(ctx, existing, fc.returnInfo));
1472 } else if (hasResult && mlir::isa<cir::VoidType>(callRetTy)) {
1473 newCall->removeAttr("res_attrs");
1474 }
1475
1476 if (hasResult && fc.returnInfo.kind == ArgKind::Ignore) {
1477 // The new call returns void, but the original call's result may still
1478 // have uses. Substitute a poison constant of the original type so
1479 // those uses remain well-formed without pretending we have a real
1480 // value at the ABI boundary.
1481 if (!call.getResult().use_empty()) {
1482 builder.setInsertionPointAfter(newCall);
1483 mlir::Value poison =
1484 createIgnoredValue(builder, call.getLoc(), origRetTy);
1485 call.getResult().replaceAllUsesWith(poison);
1486 }
1487 } else if (hasResult && !returnNeedsCoercion) {
1488 // returnNeedsCoercion already wired up the coerced result above.
1489 call.getResult().replaceAllUsesWith(newCall.getResult());
1490 }
1491
1492 call->erase();
1493 eraseDeadRecordLoads(deadRecordLoads);
1494
1495 return mlir::success();
1496}
1497
1499 cir::FuncOp funcOp,
1500 mlir::OpBuilder &builder) {
1501 auto oldPtrTy = mlir::cast<cir::PointerType>(addrOp.getAddr().getType());
1502 cir::FuncType newFuncTy = funcOp.getFunctionType();
1503 // An extension rides on an argument attribute and leaves the signature
1504 // alone, so such a callee still matches the written type.
1505 if (newFuncTy == oldPtrTy.getPointee())
1506 return;
1507
1508 // The verifier requires the retype even when nothing reads the address.
1509 addrOp.getAddr().setType(cir::PointerType::get(newFuncTy));
1510 if (addrOp.getAddr().use_empty())
1511 return;
1512
1513 // A later indirect call through the written type stays correct, since it
1514 // reclassifies from that type and coerces to the signature funcOp was
1515 // rewritten to. Ellipsis arguments are the exception the indirect-call
1516 // path reports rather than lowers.
1517 mlir::OpBuilder::InsertionGuard guard(builder);
1518 builder.setInsertionPointAfter(addrOp);
1519 auto bitcast = cir::CastOp::create(builder, addrOp.getLoc(), oldPtrTy,
1520 cir::CastKind::bitcast, addrOp.getAddr());
1521 addrOp.getAddr().replaceAllUsesExcept(bitcast.getResult(), bitcast);
1522}
1523
1524namespace {
1525
1526/// What one `va_arg` expansion needs from the op it rewrites. The x86-64
1527/// cursor fields are reached through `vaFields` by index: 0 gp_offset,
1528/// 1 fp_offset, 2 overflow_arg_area, 3 reg_save_area.
1529struct VAArgFetch {
1530 VAArgFetch(mlir::Location loc, mlir::Value valist,
1531 llvm::ArrayRef<mlir::Type> vaFields, mlir::Type resultTy,
1532 const ArgClassification &ac, const mlir::DataLayout &dl,
1533 mlir::ModuleOp module)
1534 : loc(loc), valist(valist), vaFields(vaFields), resultTy(resultTy),
1535 ac(ac), dl(dl), module(module) {
1536 assert(vaFields.size() == 4 &&
1537 "the x86-64 va_list is a four-field cursor, checked by the caller");
1538 }
1539
1540 mlir::Location loc;
1541 mlir::Value valist;
1543 mlir::Type resultTy;
1544 const ArgClassification &ac;
1545 const mlir::DataLayout &dl;
1546 mlir::ModuleOp module;
1547};
1548
1549/// The cursor fields a register fetch reads, and the predicate saying every
1550/// class it needs still has room. An offset is null when the fetch needs no
1551/// register of that class.
1552struct RegisterCursor {
1553 mlir::Value gpOffsetP;
1554 mlir::Value fpOffsetP;
1555 mlir::Value gpOffset;
1556 mlir::Value fpOffset;
1557 mlir::Value inRegs;
1558};
1559
1560mlir::LogicalResult reportVAArgNYI(cir::VAArgOp op, llvm::StringRef what) {
1561 op->emitOpError() << "va_arg of " << what
1562 << " not yet implemented in CallConvLowering";
1563 return mlir::failure();
1564}
1565
1566/// Sets \p isRegPair when a two-register argument is a coerced pair, one
1567/// register per member, rather than a single wide scalar, and records in
1568/// \p pairIsSse which element is SSE class. Fails on a coercion that is not
1569/// two eightbytes.
1570mlir::LogicalResult classifyRegisterPair(cir::VAArgOp op,
1571 const ArgClassification &ac,
1572 std::array<bool, 2> &pairIsSse,
1573 bool &isRegPair) {
1574 auto pairTy = mlir::dyn_cast<cir::RecordType>(ac.coercedType);
1575 if (!pairTy)
1576 return mlir::success();
1577
1578 if (pairTy.getNumElements() != 2)
1579 return reportVAArgNYI(op, "a register coercion that is not two eightbytes");
1580
1581 assert(!ac.directOffset &&
1582 "a pair already spans both eightbytes, so it cannot also start "
1583 "partway into the value");
1584 for (auto [i, memberTy] : llvm::enumerate(pairTy.getMembers()))
1585 pairIsSse[i] = isSSERegisterClass(memberTy);
1586 isRegPair = true;
1587 return mlir::success();
1588}
1589
1590/// The alignment an argument is placed at. A record can require more than
1591/// the types of its members imply, from an `aligned` attribute on the record
1592/// or on one of its fields, and neither raises the alignment of any type.
1593/// Only the record layout knows, so the member-derived value alone can be too
1594/// small.
1595uint64_t argumentAreaAlign(mlir::Type ty, mlir::ModuleOp modOp,
1596 const mlir::DataLayout &dl) {
1597 uint64_t align = dl.getTypeABIAlignment(ty);
1598 if (auto recTy = mlir::dyn_cast<cir::RecordType>(ty))
1599 if (auto layout = cir::tryGetRecordLayout(modOp, recTy.getName()))
1600 align = std::max<uint64_t>(align, layout.getRecordAlign());
1601 return align;
1602}
1603
1604mlir::Value roundPointerUpToAlignment(CIRBaseBuilderTy &b, mlir::Location loc,
1605 mlir::Value bytePtr, uint64_t align,
1606 const mlir::DataLayout &dl) {
1607 assert(mlir::cast<cir::PointerType>(bytePtr.getType()).getPointee() ==
1608 b.getUIntNTy(8) &&
1609 "the bump strides in bytes, so the pointee must be u8");
1610 assert(llvm::isPowerOf2_64(align) &&
1611 "mask rounding needs a power-of-two alignment");
1612 mlir::Value bumped =
1613 b.createPtrStride(loc, bytePtr, b.getSignedInt(loc, align - 1, 32));
1614 std::optional<uint64_t> indexWidth =
1615 dl.getTypeIndexBitwidth(bytePtr.getType());
1616 assert(indexWidth && "a pointer in the argument area has an index width");
1617 mlir::Value mask = b.getSignedInt(loc, -static_cast<int64_t>(align),
1618 static_cast<unsigned>(*indexWidth));
1619 return cir::PtrMaskOp::create(b, loc, bytePtr.getType(), bumped, mask);
1620}
1621
1622/// Reads the argument's address out of the overflow area, which also advances
1623/// the cursor past the argument.
1624mlir::Value buildOverflowAddrAndAdvance(CIRBaseBuilderTy &b,
1625 const VAArgFetch &f) {
1626 cir::IntType byteTy = b.getUIntNTy(8);
1627 mlir::Value overflowP = b.createGetMember(
1628 f.loc, b.getPointerTo(f.vaFields[2]), f.valist, "overflow_arg_area", 2);
1629 mlir::Value overflow = b.createLoad(f.loc, overflowP);
1630 mlir::Value bytePtr = b.createPtrBitcast(overflow, byteTy);
1631
1632 uint64_t tyAlign = argumentAreaAlign(f.resultTy, f.module, f.dl);
1633 if (tyAlign > 8)
1634 bytePtr = roundPointerUpToAlignment(b, f.loc, bytePtr, tyAlign, f.dl);
1635
1636 uint64_t tySize = f.dl.getTypeSize(f.resultTy).getFixedValue();
1637 uint64_t stride = (tySize + 7) & ~UINT64_C(7);
1638 mlir::Value strideVal = b.getSignedInt(f.loc, stride, 32);
1639 mlir::Value next = b.createPtrStride(f.loc, bytePtr, strideVal);
1640 b.createStore(f.loc, next, overflowP);
1641 return bytePtr;
1642}
1643
1644/// Loads the cursor offsets and builds the predicate that sends the fetch to
1645/// the register-save area. Both offsets count from the start of that area, so
1646/// the integer limit is the six GP registers at 48 and the vector limit is
1647/// those plus the eight SSE registers at 176.
1648RegisterCursor buildRegisterGate(CIRBaseBuilderTy &b, const VAArgFetch &f,
1649 unsigned neededInt, unsigned neededSse) {
1650 RegisterCursor cursor;
1651 if (neededInt) {
1652 cursor.gpOffsetP = b.createGetMember(f.loc, b.getPointerTo(f.vaFields[0]),
1653 f.valist, "gp_offset", 0);
1654 cursor.gpOffset = b.createLoad(f.loc, cursor.gpOffsetP);
1655 mlir::Value limit =
1656 b.getConstantInt(f.loc, cursor.gpOffset.getType(), 48 - neededInt * 8);
1657 cursor.inRegs =
1658 b.createCompare(f.loc, cir::CmpOpKind::le, cursor.gpOffset, limit);
1659 }
1660 if (neededSse) {
1661 cursor.fpOffsetP = b.createGetMember(f.loc, b.getPointerTo(f.vaFields[1]),
1662 f.valist, "fp_offset", 1);
1663 cursor.fpOffset = b.createLoad(f.loc, cursor.fpOffsetP);
1664 mlir::Value limit = b.getConstantInt(f.loc, cursor.fpOffset.getType(),
1665 176 - neededSse * 16);
1666 mlir::Value fitsInFp =
1667 b.createCompare(f.loc, cir::CmpOpKind::le, cursor.fpOffset, limit);
1668 cursor.inRegs = cursor.inRegs
1669 ? b.createLogicalAnd(f.loc, cursor.inRegs, fitsInFp)
1670 : fitsInFp;
1671 }
1672 return cursor;
1673}
1674
1675/// Copies each half of a non-contiguous pair out of the register-save area
1676/// into \p regPairTemp, laid out as the coerced pair.
1677void reassembleRegisterPair(CIRBaseBuilderTy &b, mlir::Location loc,
1678 const ArgClassification &ac,
1679 const RegisterCursor &cursor,
1680 const std::array<bool, 2> &pairIsSse,
1681 mlir::Value regSaveArea, mlir::Value regPairTemp) {
1682 auto pairTy = mlir::cast<cir::RecordType>(ac.coercedType);
1683 // Both halves of an all-SSE pair sit in 16-byte slots, which classic
1684 // CodeGen tells the load about. It leaves a mixed pair to the element's
1685 // own alignment, so match that rather than claiming the slot there.
1686 bool bothSse = pairIsSse[0] && pairIsSse[1];
1687 // Track how many of each class came before, since a slot is reached from
1688 // its class's own cursor.
1689 unsigned seenOfClass[2] = {0, 0};
1690 for (unsigned i = 0; i < 2; ++i) {
1691 bool isSse = pairIsSse[i];
1692 mlir::Value base = isSse ? cursor.fpOffset : cursor.gpOffset;
1693 unsigned regSize = isSse ? 16 : 8;
1694 unsigned prior = seenOfClass[isSse];
1695 ++seenOfClass[isSse];
1696 mlir::Value off = base;
1697 if (prior) {
1698 off = b.createAdd(loc, base,
1699 b.getConstantInt(loc, base.getType(), prior * regSize));
1700 }
1701 mlir::Value src = b.createPtrStride(loc, regSaveArea, off);
1702 mlir::Type elemTy = pairTy.getElementType(i);
1703 mlir::Value elemPtr = b.createPtrBitcast(src, elemTy);
1704 mlir::Value val = bothSse ? b.createAlignedLoad(loc, elemPtr, 16)
1705 : b.createLoad(loc, elemPtr);
1706 b.createStore(
1707 loc, val,
1708 b.createGetMember(loc, b.getPointerTo(elemTy), regPairTemp, "", i));
1709 }
1710}
1711
1712/// The bytes carried by the registers of this fetch's one class.
1713uint64_t registerSlotSize(unsigned neededInt, unsigned neededSse) {
1714 assert(!(neededInt && neededSse) &&
1715 "a fetch needing both classes is a pair, reassembled elsewhere");
1716 return neededSse ? neededSse * 16 : neededInt * 8;
1717}
1718
1719/// Whether the register cannot be read in place, either because it carries
1720/// less than the whole result or because its slot is under-aligned for it.
1721bool needsTempCopy(const VAArgFetch &f, unsigned neededInt,
1722 unsigned neededSse) {
1723 uint64_t tySize = f.dl.getTypeSize(f.resultTy).getFixedValue();
1724 if (f.ac.coercedType &&
1725 (f.ac.directOffset || registerSlotSize(neededInt, neededSse) < tySize))
1726 return true;
1727 // A slot is only as aligned as its class, 8 for a GP register and 16 for an
1728 // SSE one, so a fetch the ABI places more strictly is copied through a temp.
1729 uint64_t slotAlign = neededSse ? 16 : 8;
1730 return argumentAreaAlign(f.resultTy, f.module, f.dl) > slotAlign;
1731}
1732
1733/// Copies what the registers carry into \p temp, which is the size of the
1734/// whole result, and returns the address to read the result from.
1735mlir::Value copyRegisterToTemp(CIRBaseBuilderTy &b, mlir::Location loc,
1736 const VAArgFetch &f, mlir::Value regAddr,
1737 mlir::Value temp, unsigned neededInt,
1738 unsigned neededSse) {
1739 cir::IntType byteTy = b.getUIntNTy(8);
1740 uint64_t tySize = f.dl.getTypeSize(f.resultTy).getFixedValue();
1741
1742 if (f.ac.coercedType &&
1743 (f.ac.directOffset || registerSlotSize(neededInt, neededSse) < tySize)) {
1744 // The registers carry less than the whole result, either because the
1745 // eightbytes below directOffset hold no field or because the result is
1746 // wider than the registers carrying it. Copy only what they carry, so
1747 // that reading the temp cannot run on into the neighboring slot.
1748 mlir::Value val = b.createAlignedLoad(
1749 loc, b.createPtrBitcast(regAddr, f.ac.coercedType), 8);
1750 mlir::Value dst = temp;
1751 if (f.ac.directOffset) {
1752 dst = b.createPtrStride(loc, b.createPtrBitcast(temp, byteTy),
1753 b.getSignedInt(loc, f.ac.directOffset, 32));
1754 }
1755 b.createStore(loc, val, b.createPtrBitcast(dst, f.ac.coercedType));
1756 return b.createPtrBitcast(temp, byteTy);
1757 }
1758
1759 mlir::Value val =
1760 b.createAlignedLoad(loc, b.createPtrBitcast(regAddr, f.resultTy), 8);
1761 b.createStore(loc, val, temp);
1762 return b.createPtrBitcast(temp, byteTy);
1763}
1764
1765void advanceRegisterCursors(CIRBaseBuilderTy &b, mlir::Location loc,
1766 const RegisterCursor &cursor, unsigned neededInt,
1767 unsigned neededSse) {
1768 if (neededInt) {
1769 b.createStore(loc,
1770 b.createAdd(loc, cursor.gpOffset,
1771 b.getConstantInt(loc, cursor.gpOffset.getType(),
1772 neededInt * 8)),
1773 cursor.gpOffsetP);
1774 }
1775 if (neededSse) {
1776 b.createStore(loc,
1777 b.createAdd(loc, cursor.fpOffset,
1778 b.getConstantInt(loc, cursor.fpOffset.getType(),
1779 neededSse * 16)),
1780 cursor.fpOffsetP);
1781 }
1782}
1783
1784} // namespace
1785
1786mlir::LogicalResult
1787CIRABIRewriteContext::rewriteVAArg(mlir::Operation *vaArgOp,
1788 const ArgClassification &ac,
1789 mlir::OpBuilder &opBuilder) {
1790 auto op = mlir::cast<cir::VAArgOp>(vaArgOp);
1791 CIRBaseBuilderTy builder(opBuilder);
1792 mlir::Location loc = op.getLoc();
1793 mlir::Type resultTy = op.getType();
1794 mlir::Value valist = op.getArgList();
1795
1796 // An ignored type travels in no register and no stack slot, so the fetch
1797 // reads nothing and must leave the cursor unchanged. The value holds no
1798 // bytes, so poison stands in for it.
1799 if (ac.kind == ArgKind::Ignore) {
1800 builder.setInsertionPoint(op);
1801 op.getResult().replaceAllUsesWith(
1802 createIgnoredValue(builder, loc, resultTy));
1803 op->erase();
1804 return mlir::success();
1805 }
1806
1807 if (ac.kind == ArgKind::Indirect && !ac.byVal)
1808 return reportVAArgNYI(op, "a non-trivially-copyable type");
1809
1810 // neededInt counts 8-byte integer slots and neededSse counts 16-byte vector
1811 // slots. Zero of both means the type travels in memory and is read
1812 // straight from the overflow area.
1813 unsigned neededInt = ac.neededIntRegs;
1814 unsigned neededSse = ac.neededSseRegs;
1815
1816 // Which coerced-pair element (0 = low eightbyte, 1 = high) is SSE rather
1817 // than INTEGER class. Only meaningful when isRegPair is set.
1818 std::array<bool, 2> pairIsSse = {false, false};
1819 bool isRegPair = false;
1820 if (ac.kind == ArgKind::Direct && neededInt + neededSse == 2 &&
1821 ac.coercedType) {
1822 if (classifyRegisterPair(op, ac, pairIsSse, isRegPair).failed())
1823 return mlir::failure();
1824 }
1825
1826 auto vaListRecTy = mlir::dyn_cast<cir::RecordType>(
1827 mlir::cast<cir::PointerType>(valist.getType()).getPointee());
1828 if (!vaListRecTy || vaListRecTy.getNumElements() != 4) {
1829 return reportVAArgNYI(op,
1830 "a va_list that is not the four-field gp_offset / "
1831 "fp_offset / overflow_arg_area / reg_save_area "
1832 "cursor");
1833 }
1834
1835 const VAArgFetch fetch{loc, valist, vaListRecTy.getMembers(), resultTy, ac,
1836 dl, module};
1837 cir::IntType byteTy = builder.getUIntNTy(8);
1838
1839 builder.setInsertionPoint(op);
1840
1841 mlir::Value addr;
1842 if (neededInt == 0 && neededSse == 0) {
1843 addr = buildOverflowAddrAndAdvance(builder, fetch);
1844 } else {
1845 RegisterCursor cursor =
1846 buildRegisterGate(builder, fetch, neededInt, neededSse);
1847
1848 // A two-eightbyte pair that is purely INTEGER class is contiguous in the
1849 // register-save area, since GP slots are 8-byte packed, so the address of
1850 // its low eightbyte is already the address of the whole value. A pure
1851 // SSE pair or a mixed pair is not contiguous, since SSE slots are 16-byte
1852 // spaced and a mixed pair's halves live in disjoint areas, so each half is
1853 // copied into a temp laid out as the coerced pair.
1854 bool pairNeedsReassembly = isRegPair && neededSse != 0;
1855
1856 // Both temps are allocated here, since a ternary arm yields their address.
1857 mlir::Value regPairTemp;
1858 if (pairNeedsReassembly) {
1859 // The temp is written one member at a time through the coerced pair, so
1860 // it has to meet that type's alignment, and read back as the result, so
1861 // it has to meet the result's alignment too.
1862 regPairTemp = builder.createAlloca(
1863 loc, builder.getPointerTo(ac.coercedType), "vaarg.reg",
1865 std::max(dl.getTypeABIAlignment(ac.coercedType),
1866 argumentAreaAlign(resultTy, module, dl))));
1867 }
1868
1869 mlir::Value regTemp;
1870 bool copyThroughTemp =
1871 !pairNeedsReassembly && needsTempCopy(fetch, neededInt, neededSse);
1872 if (copyThroughTemp) {
1873 regTemp =
1874 builder.createAlloca(loc, builder.getPointerTo(resultTy), "vaarg.reg",
1876 argumentAreaAlign(resultTy, module, dl)));
1877 }
1878
1879 addr = cir::TernaryOp::create(
1880 builder, loc, cursor.inRegs,
1881 /*trueBuilder=*/
1882 [&](mlir::OpBuilder &ob, mlir::Location l) {
1883 CIRBaseBuilderTy b(ob);
1884 mlir::Value regSaveArea = b.createLoad(
1885 l, b.createGetMember(l, b.getPointerTo(fetch.vaFields[3]),
1886 valist, "reg_save_area", 3));
1887 regSaveArea = b.createPtrBitcast(regSaveArea, byteTy);
1888
1889 mlir::Value regAddr;
1890 if (pairNeedsReassembly) {
1891 reassembleRegisterPair(b, l, ac, cursor, pairIsSse,
1892 regSaveArea, regPairTemp);
1893 regAddr = b.createPtrBitcast(regPairTemp, byteTy);
1894 } else {
1895 mlir::Value off =
1896 neededSse ? cursor.fpOffset : cursor.gpOffset;
1897 regAddr = b.createPtrStride(l, regSaveArea, off);
1898 if (copyThroughTemp) {
1899 regAddr = copyRegisterToTemp(b, l, fetch, regAddr, regTemp,
1900 neededInt, neededSse);
1901 }
1902 }
1903
1904 advanceRegisterCursors(b, l, cursor, neededInt, neededSse);
1905 cir::YieldOp::create(b, l, regAddr);
1906 },
1907 /*falseBuilder=*/
1908 [&](mlir::OpBuilder &ob, mlir::Location l) {
1909 CIRBaseBuilderTy b(ob);
1910 mlir::Value memAddr = buildOverflowAddrAndAdvance(b, fetch);
1911 cir::YieldOp::create(b, l, memAddr);
1912 })
1913 .getResult();
1914 }
1915
1916 // Every path above places the result at least this well aligned.
1917 mlir::Value result =
1918 builder.createAlignedLoad(loc, builder.createPtrBitcast(addr, resultTy),
1919 argumentAreaAlign(resultTy, module, dl));
1920 op.getResult().replaceAllUsesWith(result);
1921 op->erase();
1922 return mlir::success();
1923}
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 normalizeParameterSlotAlignments(cir::FuncOp funcOp, const mlir::abi::FunctionClassification &fc)
Restate each non-byval indirect parameter's CIRGen slot alignment as the alignment the ABI promises f...
void finalizeParameterSlots()
Replace each non-byval indirect parameter's CIRGen slot with the incoming pointer,...
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={})
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)
fromQuantity - Construct a CharUnits quantity from a raw integer type.
Definition CharUnits.h:63
RecordLayoutAttr tryGetRecordLayout(mlir::ModuleOp mod, mlir::StringAttr name)
Same lookup as getRecordLayout, but returns a null attribute instead of asserting when the record has...
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:213
U cast(CodeGen::Address addr)
Definition Address.h:327
unsigned long uint64_t
long int64_t
static bool deadOnReturnAttr()
static bool noaliasOnByvalAttr()