clang 24.0.0git
LoweringPrepare.cpp
Go to the documentation of this file.
1//===- LoweringPrepare.cpp - pareparation work for LLVM lowering ----------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "PassDetail.h"
10#include "mlir/IR/Attributes.h"
11#include "mlir/IR/BuiltinAttributeInterfaces.h"
12#include "mlir/IR/IRMapping.h"
13#include "mlir/IR/Location.h"
14#include "mlir/IR/Value.h"
16#include "clang/AST/Mangle.h"
17#include "clang/Basic/Cuda.h"
18#include "clang/Basic/Module.h"
32#include "llvm/ADT/StringRef.h"
33#include "llvm/ADT/TypeSwitch.h"
34#include "llvm/IR/Instructions.h"
35#include "llvm/Support/ErrorHandling.h"
36#include "llvm/Support/MemoryBuffer.h"
37#include "llvm/Support/Path.h"
38#include "llvm/Support/VirtualFileSystem.h"
39
40#include <memory>
41#include <optional>
42
43using namespace mlir;
44using namespace cir;
45
46namespace mlir {
47#define GEN_PASS_DEF_LOWERINGPREPARE
48#include "clang/CIR/Dialect/Passes.h.inc"
49} // namespace mlir
50
51static SmallString<128> getTransformedFileName(mlir::ModuleOp mlirModule) {
52 SmallString<128> fileName;
53
54 if (mlirModule.getSymName())
55 fileName = llvm::sys::path::filename(mlirModule.getSymName()->str());
56
57 if (fileName.empty())
58 fileName = "<null>";
59
60 for (size_t i = 0; i < fileName.size(); ++i) {
61 // Replace everything that's not [a-zA-Z0-9._] with a _. This set happens
62 // to be the set of C preprocessing numbers.
63 if (!clang::isPreprocessingNumberBody(fileName[i]))
64 fileName[i] = '_';
65 }
66
67 return fileName;
68}
69
70namespace {
71struct LoweringPreparePass
72 : public impl::LoweringPrepareBase<LoweringPreparePass> {
73 LoweringPreparePass() = default;
74
75 // `mlir::SymbolTableCollection` is move-only (it owns lazily-created
76 // `unique_ptr<SymbolTable>` entries), which makes the implicit copy
77 // constructor ill-formed. MLIR's `clonePass()` requires copy
78 // construction, so define one explicitly. Per-run state members
79 // (dynamic initializers, guard maps, symbol-table cache, etc.) all
80 // start fresh in the cloned pass, which matches MLIR convention for
81 // pass clones and is more correct than the previous default-generated
82 // behavior that silently copied them.
83 LoweringPreparePass(const LoweringPreparePass &other)
84 : impl::LoweringPrepareBase<LoweringPreparePass>(other) {}
85
86 void runOnOperation() override;
87
88 void runOnOp(mlir::Operation *op);
89 void lowerCastOp(cir::CastOp op);
90 void lowerComplexConjOp(cir::ComplexConjOp op);
91 void lowerComplexDivOp(cir::ComplexDivOp op);
92 void lowerComplexMulOp(cir::ComplexMulOp op);
93 void lowerGetGlobalOp(cir::GetGlobalOp op);
94 void lowerGlobalOp(cir::GlobalOp op);
95 void lowerThreeWayCmpOp(cir::CmpThreeWayOp op);
96 void lowerArrayDtor(cir::ArrayDtor op);
97 void lowerArrayCtor(cir::ArrayCtor op);
98 void lowerTrivialCopyCall(cir::CallOp op);
99 void lowerStoreOfConstAggregate(cir::StoreOp op);
100 void lowerLocalInitOp(cir::LocalInitOp op);
101 void lowerStdOp(cir::StdOpInterface op);
102
103 /// Return the FuncOp called by `callOp`. Uses the cached `symbolTables`
104 /// member to avoid the O(M) module-wide scan that the static
105 /// `mlir::SymbolTable::lookupNearestSymbolFrom` would do per call.
106 cir::FuncOp getCalledFunction(cir::CallOp callOp);
107
108 /// Return a private constant cir::GlobalOp with the given type and initial
109 /// value, suitable for backing a memcpy-initialized local aggregate.
110 ///
111 /// If a global with `baseName` (or one of its `.<n>` versioned siblings)
112 /// already has a matching type and initial value, that global is reused.
113 /// Otherwise a new global is created with the next available `.<n>` suffix
114 /// (matching CIRGenBuilder::createVersionedGlobal and OGCG behavior).
115 cir::GlobalOp getOrCreateConstAggregateGlobal(CIRBaseBuilderTy &builder,
116 mlir::Location loc,
117 llvm::StringRef baseName,
118 mlir::Type ty,
119 mlir::TypedAttr constant);
120
121 /// Build the function that initializes the specified global
122 cir::FuncOp buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op);
123
124 /// When looking at the 'global' op, create the wrapper function.
125 void defineGlobalThreadLocalWrapper(cir::GlobalOp op, cir::FuncOp initAlias,
126 bool isVarDefinition);
127 /// Create an initialization alias for a thread-local variable.
128 cir::FuncOp defineGlobalThreadLocalInitAlias(cir::GlobalOp op,
129 cir::FuncOp aliasee);
130 /// Get the declaration for the 'wrapper' function for a global-TLS variable.
131 cir::FuncOp getOrCreateThreadLocalWrapper(CIRBaseBuilderTy &builder,
132 cir::GlobalOp op);
133 // Function that generates the guard global variable, get-global, and 'if'
134 // condition for global TLS init function generation. This inserts an 'if'
135 // with the store at the beginning of the 'then' region, so inserts into the
136 // body should happen after that.
137 cir::IfOp buildGlobalTlsGuardCheck(CIRBaseBuilderTy &builder,
138 mlir::Location loc, cir::GlobalOp guard);
139 /// Handle the dtor region by registering destructor with __cxa_atexit
140 cir::FuncOp getOrCreateDtorFunc(CIRBaseBuilderTy &builder, cir::GlobalOp op,
141 mlir::Region &dtorRegion,
142 cir::CallOp &dtorCall);
143
144 /// Build a module init function that calls all the dynamic initializers.
145 void buildCXXGlobalInitFunc();
146 // Build an init function for all of the ordered global thread local storage
147 // variables.
148 void buildCXXGlobalTlsFunc();
149
150 /// Materialize global ctor/dtor list
151 void buildGlobalCtorDtorList();
152
153 cir::FuncOp buildRuntimeFunction(
154 mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc,
155 cir::FuncType type,
156 cir::GlobalLinkageKind linkage = cir::GlobalLinkageKind::ExternalLinkage);
157
158 cir::GlobalOp getOrCreateRuntimeVariable(
159 mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc,
160 mlir::Type type,
161 cir::GlobalLinkageKind linkage = cir::GlobalLinkageKind::ExternalLinkage,
162 cir::VisibilityKind visibility = cir::VisibilityKind::Default);
163
164 /// ------------
165 /// CUDA registration related
166 /// ------------
167
168 llvm::StringMap<FuncOp> cudaKernelMap;
169 llvm::SmallVector<std::pair<cir::GlobalOp, cir::CUDAVarRegistrationInfoAttr>>
170 cudaDeviceVars;
171
172 /// Build the CUDA module constructor that registers the fat binary
173 /// with the CUDA runtime.
174 void buildCUDAModuleCtor();
175 std::optional<FuncOp> buildCUDAModuleDtor();
176 std::optional<FuncOp> buildHIPModuleDtor();
177 std::optional<FuncOp> buildCUDARegisterGlobals();
178 void buildCUDARegisterVars(cir::CIRBaseBuilderTy &builder,
179 FuncOp regGlobalFunc);
180 void buildCUDARegisterGlobalFunctions(cir::CIRBaseBuilderTy &builder,
181 FuncOp regGlobalFunc);
182
183 /// Handle static local variable initialization with guard variables.
184 void handleStaticLocal(cir::GlobalOp globalOp, cir::LocalInitOp localInitOp);
185
186 /// Get or create __cxa_guard_acquire function.
187 cir::FuncOp getGuardAcquireFn(cir::PointerType guardPtrTy);
188
189 /// Get or create __cxa_guard_release function.
190 cir::FuncOp getGuardReleaseFn(cir::PointerType guardPtrTy);
191
192 /// Get or create the __init_tls function.
193 cir::FuncOp getTlsInitFn();
194
195 // Create the __tls_guard variable.
196 cir::GlobalOp createGlobalThreadLocalGuard(CIRBaseBuilderTy &builder,
197 mlir::Location loc);
198
199 /// Create a guard global variable for a static local.
200 cir::GlobalOp createGuardGlobalOp(CIRBaseBuilderTy &builder,
201 mlir::Location loc, llvm::StringRef name,
202 cir::IntType guardTy,
203 cir::GlobalLinkageKind linkage);
204
205 /// Get the guard variable for a static local declaration.
206 cir::GlobalOp getStaticLocalDeclGuardAddress(llvm::StringRef globalSymName) {
207 auto it = staticLocalDeclGuardMap.find(globalSymName);
208 if (it != staticLocalDeclGuardMap.end())
209 return it->second;
210 return nullptr;
211 }
212
213 /// Set the guard variable for a static local declaration.
214 void setStaticLocalDeclGuardAddress(llvm::StringRef globalSymName,
215 cir::GlobalOp guard) {
216 staticLocalDeclGuardMap[globalSymName] = guard;
217 }
218
219 /// Get or create the guard variable for a static local declaration.
220 cir::GlobalOp getOrCreateStaticLocalDeclGuardAddress(
221 CIRBaseBuilderTy &builder, cir::GlobalOp globalOp, StringRef guardName,
222 bool isLocalVarDecl, bool useInt8GuardVariable) {
223
224 cir::CIRDataLayout dataLayout(mlirModule);
225 cir::IntType guardTy;
226 clang::CharUnits guardAlignment;
227 // Guard variables are 64 bits in the generic ABI and size width on ARM
228 // (i.e. 32-bit on AArch32, 64-bit on AArch64).
229 if (useInt8GuardVariable) {
230 guardTy = cir::IntType::get(&getContext(), 8, /*isSigned=*/true);
231 guardAlignment = clang::CharUnits::One();
232 } else if (useARMGuardVarABI()) {
233 // Guard variables are size width on ARM (32-bit AArch32, 64-bit AArch64).
234 const unsigned sizeTypeSize =
235 astCtx->getTypeSize(astCtx->getSignedSizeType());
236 guardTy =
237 cir::IntType::get(&getContext(), sizeTypeSize, /*isSigned=*/true);
238 guardAlignment =
239 clang::CharUnits::fromQuantity(dataLayout.getABITypeAlign(guardTy));
240 } else {
241 guardTy = cir::IntType::get(&getContext(), 64, /*isSigned=*/true);
242 guardAlignment =
243 clang::CharUnits::fromQuantity(dataLayout.getABITypeAlign(guardTy));
244 }
245 assert(guardTy && guardAlignment.getQuantity() != 0);
246
247 llvm::StringRef globalSymName = globalOp.getSymName();
248 cir::GlobalOp guard = getStaticLocalDeclGuardAddress(globalSymName);
249 if (!guard) {
250 // Create the guard variable with a zero-initializer.
251 guard = createGuardGlobalOp(builder, globalOp->getLoc(), guardName,
252 guardTy, globalOp.getLinkage());
253 guard.setInitialValueAttr(cir::IntAttr::get(guardTy, 0));
254 guard.setDSOLocal(globalOp.getDsoLocal());
255 guard.setAlignment(guardAlignment.getAsAlign().value());
256 guard.setTlsModel(globalOp.getTlsModel());
257
258 // The ABI says: "It is suggested that it be emitted in the same COMDAT
259 // group as the associated data object." In practice, this doesn't work
260 // for non-ELF and non-Wasm object formats, so only do it for ELF and
261 // Wasm.
262 bool hasComdat = globalOp.getComdat();
263 const llvm::Triple &triple = astCtx->getTargetInfo().getTriple();
264 // TODO(cir): for now, we're just setting comdat to true, but it should
265 // contain a comdat reference name here instead.
266 if (!isLocalVarDecl && hasComdat &&
267 (triple.isOSBinFormatELF() || triple.isOSBinFormatWasm())) {
268 // This should be a comdat for the variable.
269 guard.setComdat(true);
270 } else if (hasComdat && globalOp.isWeakForLinker()) {
271 guard.setComdat(true);
272 }
273
274 setStaticLocalDeclGuardAddress(globalSymName, guard);
275 }
276 return guard;
277 }
278
279 ///
280 /// AST related
281 /// -----------
282
283 clang::ASTContext *astCtx;
284
285 /// Tracks current module.
286 mlir::ModuleOp mlirModule;
287
288 /// Cached symbol tables used to avoid repeated O(M) module-wide scans
289 /// during per-call/per-global symbol lookups. Lazily populated on first
290 /// use. Pass methods access this directly rather than threading it
291 /// through helper signatures (see PR feedback on #195919).
292 ///
293 /// Invariant: every site that mutates the module's symbol table either
294 /// (a) keeps `symbolTables` in sync via
295 /// `symbolTables.getSymbolTable(mlirModule).insert(...)` (as
296 /// `getOrCreateConstAggregateGlobal` does), or (b) creates a symbol
297 /// that is never resolved through the cache later. Today
298 /// `buildRuntimeFunction` and `getOrCreateRuntimeVariable` fall in the
299 /// (b) bucket: their callers either use a separate map
300 /// (`cudaKernelMap`, `staticLocalDeclGuardMap`, `dynamicInitializers`)
301 /// or the static `mlir::SymbolTable::lookupNearestSymbolFrom`, never
302 /// the cached path. If a future change adds a cached lookup of a
303 /// freshly created symbol, the corresponding create site MUST move
304 /// to bucket (a) (insert into the cache or call
305 /// `invalidateSymbolTable`).
306 mlir::SymbolTableCollection symbolTables;
307
308 /// Tracks existing dynamic initializers.
309 llvm::StringMap<uint32_t> dynamicInitializerNames;
310 llvm::SmallVector<cir::FuncOp> dynamicInitializers;
311 llvm::SmallVector<cir::FuncOp> globalThreadLocalInitializers;
312 llvm::StringMap<cir::FuncOp> threadLocalWrappers;
313 llvm::StringMap<cir::FuncOp> threadLocalInitAliases;
314
315 /// Tracks guard variables for static locals (keyed by global symbol name).
316 llvm::StringMap<cir::GlobalOp> staticLocalDeclGuardMap;
317
318 llvm::StringMap<llvm::SmallVector<cir::GlobalOp, 1>> constAggregateGlobals;
319
320 /// List of ctors and their priorities to be called before main()
321 llvm::SmallVector<std::pair<std::string, uint32_t>, 4> globalCtorList;
322 /// List of dtors and their priorities to be called when unloading module.
323 llvm::SmallVector<std::pair<std::string, uint32_t>, 4> globalDtorList;
324
325 /// Returns true if the target uses ARM-style guard variables for static
326 /// local initialization (32-bit guard, check bit 0 only).
327 bool useARMGuardVarABI() const {
328 switch (astCtx->getCXXABIKind()) {
329 case clang::TargetCXXABI::GenericARM:
330 case clang::TargetCXXABI::iOS:
331 case clang::TargetCXXABI::WatchOS:
332 case clang::TargetCXXABI::GenericAArch64:
333 case clang::TargetCXXABI::WebAssembly:
334 return true;
335 default:
336 return false;
337 }
338 }
339
340 void emitGlobalGuardedDtorRegion(CIRBaseBuilderTy &builder,
341 cir::GlobalOp global,
342 mlir::Region &dtorRegion, bool tls,
343 mlir::Block &entryBB) {
344 // Create a variable that binds the atexit to this shared object.
345 builder.setInsertionPointToStart(&mlirModule.getBodyRegion().front());
346 cir::GlobalOp handle = getOrCreateRuntimeVariable(
347 builder, "__dso_handle", global.getLoc(), builder.getI8Type(),
348 cir::GlobalLinkageKind::ExternalLinkage, cir::VisibilityKind::Hidden);
349
350 // If this is a simple call to a destructor, get the called function.
351 // Otherwise, create a helper function for the entire dtor region,
352 // replacing the current dtor region body with a call to the helper
353 // function.
354 cir::CallOp dtorCall;
355 cir::FuncOp dtorFunc =
356 getOrCreateDtorFunc(builder, global, dtorRegion, dtorCall);
357
358 // Create a runtime helper function:
359 // extern "C" int __cxa_atexit(void (*f)(void *), void *p, void *d);
360 cir::PointerType voidPtrTy = builder.getVoidPtrTy();
361 cir::PointerType voidFnPtrTy = builder.getVoidFnPtrTy({voidPtrTy});
362 cir::PointerType handlePtrTy = builder.getPointerTo(handle.getSymType());
363 IntType intTy = builder.getSIntNTy(32);
364 auto fnAtExitType =
365 cir::FuncType::get({voidFnPtrTy, voidPtrTy, handlePtrTy}, intTy);
366
367 llvm::StringLiteral nameAtExit = "__cxa_atexit";
368 if (tls)
369 nameAtExit = astCtx->getTargetInfo().getTriple().isOSDarwin()
370 ? llvm::StringLiteral("_tlv_atexit")
371 : llvm::StringLiteral("__cxa_thread_atexit");
372
373 cir::FuncOp fnAtExit = buildRuntimeFunction(builder, nameAtExit,
374 global.getLoc(), fnAtExitType);
375
376 // Replace the dtor (or helper) call with a call to
377 // __cxa_atexit(&dtor, &var, &__dso_handle)
378 builder.setInsertionPointAfter(dtorCall);
379 mlir::Value args[3];
380 auto dtorPtrTy = cir::PointerType::get(dtorFunc.getFunctionType());
381 args[0] = cir::GetGlobalOp::create(builder, dtorCall.getLoc(), dtorPtrTy,
382 dtorFunc.getSymName());
383 args[0] = cir::CastOp::create(builder, dtorCall.getLoc(), voidFnPtrTy,
384 cir::CastKind::bitcast, args[0]);
385 args[1] =
386 cir::CastOp::create(builder, dtorCall.getLoc(), voidPtrTy,
387 cir::CastKind::bitcast, dtorCall.getArgOperand(0));
388 args[2] = cir::GetGlobalOp::create(builder, handle.getLoc(), handlePtrTy,
389 handle.getSymName());
390 builder.createCallOp(dtorCall.getLoc(), fnAtExit, args);
391 dtorCall->erase();
392 mlir::Block &dtorBlock = dtorRegion.front();
393 entryBB.getOperations().splice(entryBB.end(), dtorBlock.getOperations(),
394 dtorBlock.begin(),
395 std::prev(dtorBlock.end()));
396 // make sure we leave the insert location after the operations we just
397 // inserted.
398 builder.setInsertionPointToEnd(&entryBB);
399 }
400
401 /// Emit the guarded initialization for a static local variable.
402 /// This handles the if/else structure after the guard byte check,
403 /// following OG's ItaniumCXXABI::EmitGuardedInit skeleton.
404 void emitCXXGuardedInitIf(CIRBaseBuilderTy &builder, cir::GlobalOp globalOp,
405 mlir::Region &ctorRegion, mlir::Region &dtorRegion,
406 cir::ASTVarDeclInterface varDecl,
407 mlir::Value guardPtr, cir::PointerType guardPtrTy,
408 bool threadsafe) {
409 auto loc = globalOp->getLoc();
410
411 // The semantics of dynamic initialization of variables with static or
412 // thread storage duration depends on whether they are declared at
413 // block-scope. The initialization of such variables at block-scope can be
414 // aborted with an exception and later retried (per C++20 [stmt.dcl]p4),
415 // and recursive entry to their initialization has undefined behavior (also
416 // per C++20 [stmt.dcl]p4). For such variables declared at non-block scope,
417 // exceptions lead to termination (per C++20 [except.terminate]p1), and
418 // recursive references to the variables are governed only by the lifetime
419 // rules (per C++20 [class.cdtor]p2), which means such references are
420 // perfectly fine as long as they avoid touching memory. As a result,
421 // block-scope variables must not be marked as initialized until after
422 // initialization completes (unless the mark is reverted following an
423 // exception), but non-block-scope variables must be marked prior to
424 // initialization so that recursive accesses during initialization do not
425 // restart initialization.
426
427 auto emitBody = [&]() {
428 // Emit the initializer and add a global destructor if appropriate.
429 mlir::Block *insertBlock = builder.getInsertionBlock();
430 if (!ctorRegion.empty()) {
431 assert(ctorRegion.hasOneBlock() && "Enforced by MaxSizedRegion<1>");
432
433 mlir::Block &block = ctorRegion.front();
434 insertBlock->getOperations().splice(
435 insertBlock->end(), block.getOperations(), block.begin(),
436 std::prev(block.end()));
437 }
438
439 if (!dtorRegion.empty()) {
440 assert(dtorRegion.hasOneBlock() && "Enforced by MaxSizedRegion<1>");
441
442 emitGlobalGuardedDtorRegion(builder, globalOp, dtorRegion, !threadsafe,
443 *insertBlock);
444 }
445 builder.setInsertionPointToEnd(insertBlock);
446 ctorRegion.getBlocks().clear();
447 };
448
449 // Variables used when coping with thread-safe statics and exceptions.
450 if (threadsafe) {
451 // Call __cxa_guard_acquire.
452 cir::CallOp acquireCall = builder.createCallOp(
453 loc, getGuardAcquireFn(guardPtrTy), mlir::ValueRange{guardPtr});
454 mlir::Value acquireResult = acquireCall.getResult();
455
456 auto acquireZero = builder.getConstantInt(
457 loc, mlir::cast<cir::IntType>(acquireResult.getType()), 0);
458 auto shouldInit = builder.createCompare(loc, cir::CmpOpKind::ne,
459 acquireResult, acquireZero);
460
461 // Create the IfOp for the shouldInit check.
462 // Pass an empty callback to avoid auto-creating a yield terminator.
463 auto ifOp =
464 cir::IfOp::create(builder, loc, shouldInit, /*withElseRegion=*/false,
465 [](mlir::OpBuilder &, mlir::Location) {});
466 mlir::OpBuilder::InsertionGuard insertGuard(builder);
467 builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
468
469 // Call __cxa_guard_abort along the exceptional edge.
470 // OG: CGF.EHStack.pushCleanup<CallGuardAbort>(EHCleanup, guard);
472
473 emitBody();
474
475 // Pop the guard-abort cleanup if we pushed one.
476 // OG: CGF.PopCleanupBlock();
478
479 // Call __cxa_guard_release. This cannot throw.
480 builder.createCallOp(loc, getGuardReleaseFn(guardPtrTy),
481 mlir::ValueRange{guardPtr});
482
483 builder.createYield(loc);
484 } else if (!varDecl.isLocalVarDecl()) {
485 // For non-local variables, store 1 into the first byte of the guard
486 // variable before the object initialization begins so that references
487 // to the variable during initialization don't restart initialization.
488 // OG: Builder.CreateStore(llvm::ConstantInt::get(CGM.Int8Ty, 1), ...);
489 // Then: CGF.EmitCXXGlobalVarDeclInit(D, var, shouldPerformInit);
490 globalOp->emitError("NYI: non-threadsafe init for non-local variables");
491 return;
492 } else {
493 emitBody();
494 // For local variables, store 1 into the first byte of the guard variable
495 // after the object initialization completes so that initialization is
496 // retried if initialization is interrupted by an exception.
497 builder.createStore(
498 loc, builder.getConstantInt(loc, guardPtrTy.getPointee(), 1),
499 guardPtr);
500 }
501
502 builder.createYield(loc); // Outermost IfOp
503 }
504
505 void setASTContext(clang::ASTContext *c) { astCtx = c; }
506};
507
508} // namespace
509
510cir::GlobalOp LoweringPreparePass::getOrCreateRuntimeVariable(
511 mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc,
512 mlir::Type type, cir::GlobalLinkageKind linkage,
513 cir::VisibilityKind visibility) {
514 cir::GlobalOp g = dyn_cast_or_null<cir::GlobalOp>(
515 mlir::SymbolTable::lookupNearestSymbolFrom(
516 mlirModule, mlir::StringAttr::get(mlirModule->getContext(), name)));
517 if (!g) {
518 g = cir::GlobalOp::create(builder, loc, name, type);
519 g.setLinkageAttr(
520 cir::GlobalLinkageKindAttr::get(builder.getContext(), linkage));
521 mlir::SymbolTable::setSymbolVisibility(
522 g, mlir::SymbolTable::Visibility::Private);
523 g.setGlobalVisibility(visibility);
524 }
525 return g;
526}
527
528cir::FuncOp LoweringPreparePass::buildRuntimeFunction(
529 mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc,
530 cir::FuncType type, cir::GlobalLinkageKind linkage) {
531 cir::FuncOp f = dyn_cast_or_null<FuncOp>(SymbolTable::lookupNearestSymbolFrom(
532 mlirModule, StringAttr::get(mlirModule->getContext(), name)));
533 if (!f) {
534 f = cir::FuncOp::create(builder, loc, name, type);
535 f.setLinkageAttr(
536 cir::GlobalLinkageKindAttr::get(builder.getContext(), linkage));
537 mlir::SymbolTable::setSymbolVisibility(
538 f, mlir::SymbolTable::Visibility::Private);
539
541 }
542 return f;
543}
544
545static mlir::Value lowerScalarToComplexCast(mlir::MLIRContext &ctx,
546 cir::CastOp op) {
547 cir::CIRBaseBuilderTy builder(ctx);
548 builder.setInsertionPoint(op);
549
550 mlir::Value src = op.getSrc();
551 mlir::Value imag = builder.getNullValue(src.getType(), op.getLoc());
552 return builder.createComplexCreate(op.getLoc(), src, imag);
553}
554
555static mlir::Value lowerComplexToScalarCast(mlir::MLIRContext &ctx,
556 cir::CastOp op,
557 cir::CastKind elemToBoolKind) {
558 cir::CIRBaseBuilderTy builder(ctx);
559 builder.setInsertionPoint(op);
560
561 mlir::Value src = op.getSrc();
562 if (!mlir::isa<cir::BoolType>(op.getType()))
563 return builder.createComplexReal(op.getLoc(), src);
564
565 // Complex cast to bool: (bool)(a+bi) => (bool)a || (bool)b
566 mlir::Value srcReal = builder.createComplexReal(op.getLoc(), src);
567 mlir::Value srcImag = builder.createComplexImag(op.getLoc(), src);
568
569 cir::BoolType boolTy = builder.getBoolTy();
570 mlir::Value srcRealToBool =
571 builder.createCast(op.getLoc(), elemToBoolKind, srcReal, boolTy);
572 mlir::Value srcImagToBool =
573 builder.createCast(op.getLoc(), elemToBoolKind, srcImag, boolTy);
574 return builder.createLogicalOr(op.getLoc(), srcRealToBool, srcImagToBool);
575}
576
577static mlir::Value lowerComplexToComplexCast(mlir::MLIRContext &ctx,
578 cir::CastOp op,
579 cir::CastKind scalarCastKind) {
580 CIRBaseBuilderTy builder(ctx);
581 builder.setInsertionPoint(op);
582
583 mlir::Value src = op.getSrc();
584 auto dstComplexElemTy =
585 mlir::cast<cir::ComplexType>(op.getType()).getElementType();
586
587 mlir::Value srcReal = builder.createComplexReal(op.getLoc(), src);
588 mlir::Value srcImag = builder.createComplexImag(op.getLoc(), src);
589
590 mlir::Value dstReal = builder.createCast(op.getLoc(), scalarCastKind, srcReal,
591 dstComplexElemTy);
592 mlir::Value dstImag = builder.createCast(op.getLoc(), scalarCastKind, srcImag,
593 dstComplexElemTy);
594 return builder.createComplexCreate(op.getLoc(), dstReal, dstImag);
595}
596
597void LoweringPreparePass::lowerCastOp(cir::CastOp op) {
598 mlir::MLIRContext &ctx = getContext();
599 mlir::Value loweredValue = [&]() -> mlir::Value {
600 switch (op.getKind()) {
601 case cir::CastKind::float_to_complex:
602 case cir::CastKind::int_to_complex:
603 return lowerScalarToComplexCast(ctx, op);
604 case cir::CastKind::float_complex_to_real:
605 case cir::CastKind::int_complex_to_real:
606 return lowerComplexToScalarCast(ctx, op, op.getKind());
607 case cir::CastKind::float_complex_to_bool:
608 return lowerComplexToScalarCast(ctx, op, cir::CastKind::float_to_bool);
609 case cir::CastKind::int_complex_to_bool:
610 return lowerComplexToScalarCast(ctx, op, cir::CastKind::int_to_bool);
611 case cir::CastKind::float_complex:
612 return lowerComplexToComplexCast(ctx, op, cir::CastKind::floating);
613 case cir::CastKind::float_complex_to_int_complex:
614 return lowerComplexToComplexCast(ctx, op, cir::CastKind::float_to_int);
615 case cir::CastKind::int_complex:
616 return lowerComplexToComplexCast(ctx, op, cir::CastKind::integral);
617 case cir::CastKind::int_complex_to_float_complex:
618 return lowerComplexToComplexCast(ctx, op, cir::CastKind::int_to_float);
619 default:
620 return nullptr;
621 }
622 }();
623
624 if (loweredValue) {
625 op.replaceAllUsesWith(loweredValue);
626 op.erase();
627 }
628}
629
630static mlir::Value buildComplexBinOpLibCall(
631 LoweringPreparePass &pass, CIRBaseBuilderTy &builder,
632 llvm::StringRef (*libFuncNameGetter)(llvm::APFloat::Semantics),
633 mlir::Location loc, cir::ComplexType ty, mlir::Value lhsReal,
634 mlir::Value lhsImag, mlir::Value rhsReal, mlir::Value rhsImag) {
635 cir::FPTypeInterface elementTy =
636 mlir::cast<cir::FPTypeInterface>(ty.getElementType());
637
638 llvm::StringRef libFuncName = libFuncNameGetter(
639 llvm::APFloat::SemanticsToEnum(elementTy.getFloatSemantics()));
640 llvm::SmallVector<mlir::Type, 4> libFuncInputTypes(4, elementTy);
641
642 cir::FuncType libFuncTy = cir::FuncType::get(libFuncInputTypes, ty);
643
644 // Insert a declaration for the runtime function to be used in Complex
645 // multiplication and division when needed
646 cir::FuncOp libFunc;
647 {
648 mlir::OpBuilder::InsertionGuard ipGuard{builder};
649 builder.setInsertionPointToStart(pass.mlirModule.getBody());
650 libFunc = pass.buildRuntimeFunction(builder, libFuncName, loc, libFuncTy);
651 }
652
653 cir::CallOp call =
654 builder.createCallOp(loc, libFunc, {lhsReal, lhsImag, rhsReal, rhsImag});
655 return call.getResult();
656}
657
658static llvm::StringRef
659getComplexDivLibCallName(llvm::APFloat::Semantics semantics) {
660 switch (semantics) {
661 case llvm::APFloat::S_IEEEhalf:
662 return "__divhc3";
663 case llvm::APFloat::S_IEEEsingle:
664 return "__divsc3";
665 case llvm::APFloat::S_IEEEdouble:
666 return "__divdc3";
667 case llvm::APFloat::S_PPCDoubleDouble:
668 return "__divtc3";
669 case llvm::APFloat::S_x87DoubleExtended:
670 return "__divxc3";
671 case llvm::APFloat::S_IEEEquad:
672 return "__divtc3";
673 default:
674 llvm_unreachable("unsupported floating point type");
675 }
676}
677
678static mlir::Value
679buildAlgebraicComplexDiv(CIRBaseBuilderTy &builder, mlir::Location loc,
680 mlir::Value lhsReal, mlir::Value lhsImag,
681 mlir::Value rhsReal, mlir::Value rhsImag) {
682 // (a+bi) / (c+di) = ((ac+bd)/(cc+dd)) + ((bc-ad)/(cc+dd))i
683 mlir::Value &a = lhsReal;
684 mlir::Value &b = lhsImag;
685 mlir::Value &c = rhsReal;
686 mlir::Value &d = rhsImag;
687
688 // The element type of the complex (lhs/rhs) determines whether floating
689 // point or integer ops are needed.
690 bool isFP = cir::isFPOrVectorOfFPType(a.getType());
691 auto mul = [&](mlir::Location l, mlir::Value x, mlir::Value y) {
692 return isFP ? builder.createFMul(l, x, y) : builder.createMul(l, x, y);
693 };
694 auto add = [&](mlir::Location l, mlir::Value x, mlir::Value y) {
695 return isFP ? builder.createFAdd(l, x, y) : builder.createAdd(l, x, y);
696 };
697 auto sub = [&](mlir::Location l, mlir::Value x, mlir::Value y) {
698 return isFP ? builder.createFSub(l, x, y) : builder.createSub(l, x, y);
699 };
700 auto div = [&](mlir::Location l, mlir::Value x, mlir::Value y) {
701 return isFP ? builder.createFDiv(l, x, y) : builder.createDiv(l, x, y);
702 };
703
704 mlir::Value ac = mul(loc, a, c); // a*c
705 mlir::Value bd = mul(loc, b, d); // b*d
706 mlir::Value cc = mul(loc, c, c); // c*c
707 mlir::Value dd = mul(loc, d, d); // d*d
708 mlir::Value acbd = add(loc, ac, bd); // ac+bd
709 mlir::Value ccdd = add(loc, cc, dd); // cc+dd
710 mlir::Value resultReal = div(loc, acbd, ccdd);
711
712 mlir::Value bc = mul(loc, b, c); // b*c
713 mlir::Value ad = mul(loc, a, d); // a*d
714 mlir::Value bcad = sub(loc, bc, ad); // bc-ad
715 mlir::Value resultImag = div(loc, bcad, ccdd);
716 return builder.createComplexCreate(loc, resultReal, resultImag);
717}
718
719static mlir::Value
721 mlir::Value lhsReal, mlir::Value lhsImag,
722 mlir::Value rhsReal, mlir::Value rhsImag) {
723 // Implements Smith's algorithm for complex division.
724 // SMITH, R. L. Algorithm 116: Complex division. Commun. ACM 5, 8 (1962).
725
726 // Let:
727 // - lhs := a+bi
728 // - rhs := c+di
729 // - result := lhs / rhs = e+fi
730 //
731 // The algorithm pseudocode looks like follows:
732 // if fabs(c) >= fabs(d):
733 // r := d / c
734 // tmp := c + r*d
735 // e = (a + b*r) / tmp
736 // f = (b - a*r) / tmp
737 // else:
738 // r := c / d
739 // tmp := d + r*c
740 // e = (a*r + b) / tmp
741 // f = (b*r - a) / tmp
742
743 mlir::Value &a = lhsReal;
744 mlir::Value &b = lhsImag;
745 mlir::Value &c = rhsReal;
746 mlir::Value &d = rhsImag;
747
748 // Smith's algorithm is only used for floating-point complex division.
749 assert(cir::isFPOrVectorOfFPType(a.getType()) &&
750 "range-reduction complex divide expects floating-point operands");
751
752 auto trueBranchBuilder = [&](mlir::OpBuilder &, mlir::Location) {
753 mlir::Value r = builder.createFDiv(loc, d, c); // r := d / c
754 mlir::Value rd = builder.createFMul(loc, r, d); // r*d
755 mlir::Value tmp = builder.createFAdd(loc, c, rd); // tmp := c + r*d
756
757 mlir::Value br = builder.createFMul(loc, b, r); // b*r
758 mlir::Value abr = builder.createFAdd(loc, a, br); // a + b*r
759 mlir::Value e = builder.createFDiv(loc, abr, tmp);
760
761 mlir::Value ar = builder.createFMul(loc, a, r); // a*r
762 mlir::Value bar = builder.createFSub(loc, b, ar); // b - a*r
763 mlir::Value f = builder.createFDiv(loc, bar, tmp);
764
765 mlir::Value result = builder.createComplexCreate(loc, e, f);
766 builder.createYield(loc, result);
767 };
768
769 auto falseBranchBuilder = [&](mlir::OpBuilder &, mlir::Location) {
770 mlir::Value r = builder.createFDiv(loc, c, d); // r := c / d
771 mlir::Value rc = builder.createFMul(loc, r, c); // r*c
772 mlir::Value tmp = builder.createFAdd(loc, d, rc); // tmp := d + r*c
773
774 mlir::Value ar = builder.createFMul(loc, a, r); // a*r
775 mlir::Value arb = builder.createFAdd(loc, ar, b); // a*r + b
776 mlir::Value e = builder.createFDiv(loc, arb, tmp);
777
778 mlir::Value br = builder.createFMul(loc, b, r); // b*r
779 mlir::Value bra = builder.createFSub(loc, br, a); // b*r - a
780 mlir::Value f = builder.createFDiv(loc, bra, tmp);
781
782 mlir::Value result = builder.createComplexCreate(loc, e, f);
783 builder.createYield(loc, result);
784 };
785
786 auto cFabs = cir::FAbsOp::create(builder, loc, c);
787 auto dFabs = cir::FAbsOp::create(builder, loc, d);
788 cir::CmpOp cmpResult =
789 builder.createCompare(loc, cir::CmpOpKind::ge, cFabs, dFabs);
790 auto ternary = cir::TernaryOp::create(builder, loc, cmpResult,
791 trueBranchBuilder, falseBranchBuilder);
792
793 return ternary.getResult();
794}
795
797 mlir::MLIRContext &context, clang::ASTContext &cc,
798 CIRBaseBuilderTy &builder, mlir::Type elementType) {
799
800 auto getHigherPrecisionFPType = [&context](mlir::Type type) -> mlir::Type {
801 if (mlir::isa<cir::FP16Type>(type))
802 return cir::SingleType::get(&context);
803
804 if (mlir::isa<cir::SingleType>(type) || mlir::isa<cir::BF16Type>(type))
805 return cir::DoubleType::get(&context);
806
807 if (mlir::isa<cir::DoubleType>(type))
808 return cir::LongDoubleType::get(&context, type);
809
810 return type;
811 };
812
813 auto getFloatTypeSemantics =
814 [&cc](mlir::Type type) -> const llvm::fltSemantics & {
815 const clang::TargetInfo &info = cc.getTargetInfo();
816 if (mlir::isa<cir::FP16Type>(type))
817 return info.getHalfFormat();
818
819 if (mlir::isa<cir::BF16Type>(type))
820 return info.getBFloat16Format();
821
822 if (mlir::isa<cir::SingleType>(type))
823 return info.getFloatFormat();
824
825 if (mlir::isa<cir::DoubleType>(type))
826 return info.getDoubleFormat();
827
828 if (mlir::isa<cir::LongDoubleType>(type)) {
829 if (cc.getLangOpts().OpenMP && cc.getLangOpts().OpenMPIsTargetDevice)
830 llvm_unreachable("NYI Float type semantics with OpenMP");
831 return info.getLongDoubleFormat();
832 }
833
834 if (mlir::isa<cir::FP128Type>(type)) {
835 if (cc.getLangOpts().OpenMP && cc.getLangOpts().OpenMPIsTargetDevice)
836 llvm_unreachable("NYI Float type semantics with OpenMP");
837 return info.getFloat128Format();
838 }
839
840 llvm_unreachable("Unsupported float type semantics");
841 };
842
843 const mlir::Type higherElementType = getHigherPrecisionFPType(elementType);
844 const llvm::fltSemantics &elementTypeSemantics =
845 getFloatTypeSemantics(elementType);
846 const llvm::fltSemantics &higherElementTypeSemantics =
847 getFloatTypeSemantics(higherElementType);
848
849 // Check that the promoted type can handle the intermediate values without
850 // overflowing. This can be interpreted as:
851 // (SmallerType.LargestFiniteVal * SmallerType.LargestFiniteVal) * 2 <=
852 // LargerType.LargestFiniteVal.
853 // In terms of exponent it gives this formula:
854 // (SmallerType.LargestFiniteVal * SmallerType.LargestFiniteVal
855 // doubles the exponent of SmallerType.LargestFiniteVal)
856 if (llvm::APFloat::semanticsMaxExponent(elementTypeSemantics) * 2 + 1 <=
857 llvm::APFloat::semanticsMaxExponent(higherElementTypeSemantics)) {
858 return higherElementType;
859 }
860
861 // The intermediate values can't be represented in the promoted type
862 // without overflowing.
863 return {};
864}
865
866static mlir::Value
867lowerComplexDiv(LoweringPreparePass &pass, CIRBaseBuilderTy &builder,
868 mlir::Location loc, cir::ComplexDivOp op, mlir::Value lhsReal,
869 mlir::Value lhsImag, mlir::Value rhsReal, mlir::Value rhsImag,
870 mlir::MLIRContext &mlirCx, clang::ASTContext &cc) {
871 cir::ComplexType complexTy = op.getType();
872 if (mlir::isa<cir::FPTypeInterface>(complexTy.getElementType())) {
873 cir::ComplexRangeKind range = op.getRange();
874 if (range == cir::ComplexRangeKind::Improved)
875 return buildRangeReductionComplexDiv(builder, loc, lhsReal, lhsImag,
876 rhsReal, rhsImag);
877
878 if (range == cir::ComplexRangeKind::Full)
880 loc, complexTy, lhsReal, lhsImag, rhsReal,
881 rhsImag);
882
883 if (range == cir::ComplexRangeKind::Promoted) {
884 mlir::Type originalElementType = complexTy.getElementType();
885 mlir::Type higherPrecisionElementType =
887 originalElementType);
888
889 if (!higherPrecisionElementType)
890 return buildRangeReductionComplexDiv(builder, loc, lhsReal, lhsImag,
891 rhsReal, rhsImag);
892
893 cir::CastKind floatingCastKind = cir::CastKind::floating;
894 lhsReal = builder.createCast(floatingCastKind, lhsReal,
895 higherPrecisionElementType);
896 lhsImag = builder.createCast(floatingCastKind, lhsImag,
897 higherPrecisionElementType);
898 rhsReal = builder.createCast(floatingCastKind, rhsReal,
899 higherPrecisionElementType);
900 rhsImag = builder.createCast(floatingCastKind, rhsImag,
901 higherPrecisionElementType);
902
903 mlir::Value algebraicResult = buildAlgebraicComplexDiv(
904 builder, loc, lhsReal, lhsImag, rhsReal, rhsImag);
905
906 mlir::Value resultReal = builder.createComplexReal(loc, algebraicResult);
907 mlir::Value resultImag = builder.createComplexImag(loc, algebraicResult);
908
909 mlir::Value finalReal =
910 builder.createCast(floatingCastKind, resultReal, originalElementType);
911 mlir::Value finalImag =
912 builder.createCast(floatingCastKind, resultImag, originalElementType);
913 return builder.createComplexCreate(loc, finalReal, finalImag);
914 }
915 }
916
917 return buildAlgebraicComplexDiv(builder, loc, lhsReal, lhsImag, rhsReal,
918 rhsImag);
919}
920
921void LoweringPreparePass::lowerComplexDivOp(cir::ComplexDivOp op) {
922 cir::CIRBaseBuilderTy builder(getContext());
923 builder.setInsertionPointAfter(op);
924 mlir::Location loc = op.getLoc();
925 mlir::TypedValue<cir::ComplexType> lhs = op.getLhs();
926 mlir::TypedValue<cir::ComplexType> rhs = op.getRhs();
927 mlir::Value lhsReal = builder.createComplexReal(loc, lhs);
928 mlir::Value lhsImag = builder.createComplexImag(loc, lhs);
929 mlir::Value rhsReal = builder.createComplexReal(loc, rhs);
930 mlir::Value rhsImag = builder.createComplexImag(loc, rhs);
931
932 mlir::Value loweredResult =
933 lowerComplexDiv(*this, builder, loc, op, lhsReal, lhsImag, rhsReal,
934 rhsImag, getContext(), *astCtx);
935 op.replaceAllUsesWith(loweredResult);
936 op.erase();
937}
938
939static llvm::StringRef
940getComplexMulLibCallName(llvm::APFloat::Semantics semantics) {
941 switch (semantics) {
942 case llvm::APFloat::S_IEEEhalf:
943 return "__mulhc3";
944 case llvm::APFloat::S_IEEEsingle:
945 return "__mulsc3";
946 case llvm::APFloat::S_IEEEdouble:
947 return "__muldc3";
948 case llvm::APFloat::S_PPCDoubleDouble:
949 return "__multc3";
950 case llvm::APFloat::S_x87DoubleExtended:
951 return "__mulxc3";
952 case llvm::APFloat::S_IEEEquad:
953 return "__multc3";
954 default:
955 llvm_unreachable("unsupported floating point type");
956 }
957}
958
959static mlir::Value lowerComplexMul(LoweringPreparePass &pass,
960 CIRBaseBuilderTy &builder,
961 mlir::Location loc, cir::ComplexMulOp op,
962 mlir::Value lhsReal, mlir::Value lhsImag,
963 mlir::Value rhsReal, mlir::Value rhsImag) {
964 // (a+bi) * (c+di) = (ac-bd) + (ad+bc)i
965 bool isFP = cir::isFPOrVectorOfFPType(lhsReal.getType());
966 auto mul = [&](mlir::Location l, mlir::Value x, mlir::Value y) {
967 return isFP ? builder.createFMul(l, x, y) : builder.createMul(l, x, y);
968 };
969 auto add = [&](mlir::Location l, mlir::Value x, mlir::Value y) {
970 return isFP ? builder.createFAdd(l, x, y) : builder.createAdd(l, x, y);
971 };
972 auto sub = [&](mlir::Location l, mlir::Value x, mlir::Value y) {
973 return isFP ? builder.createFSub(l, x, y) : builder.createSub(l, x, y);
974 };
975
976 mlir::Value resultRealLhs = mul(loc, lhsReal, rhsReal); // ac
977 mlir::Value resultRealRhs = mul(loc, lhsImag, rhsImag); // bd
978 mlir::Value resultImagLhs = mul(loc, lhsReal, rhsImag); // ad
979 mlir::Value resultImagRhs = mul(loc, lhsImag, rhsReal); // bc
980 mlir::Value resultReal = sub(loc, resultRealLhs, resultRealRhs);
981 mlir::Value resultImag = add(loc, resultImagLhs, resultImagRhs);
982 mlir::Value algebraicResult =
983 builder.createComplexCreate(loc, resultReal, resultImag);
984
985 cir::ComplexType complexTy = op.getType();
986 cir::ComplexRangeKind rangeKind = op.getRange();
987 if (mlir::isa<cir::IntType>(complexTy.getElementType()) ||
988 rangeKind == cir::ComplexRangeKind::Basic ||
989 rangeKind == cir::ComplexRangeKind::Improved ||
990 rangeKind == cir::ComplexRangeKind::Promoted)
991 return algebraicResult;
992
994
995 // Check whether the real part and the imaginary part of the result are both
996 // NaN. If so, emit a library call to compute the multiplication instead.
997 // We check a value against NaN by comparing the value against itself.
998 mlir::Value resultRealIsNaN = builder.createIsNaN(loc, resultReal);
999 mlir::Value resultImagIsNaN = builder.createIsNaN(loc, resultImag);
1000 mlir::Value resultRealAndImagAreNaN =
1001 builder.createLogicalAnd(loc, resultRealIsNaN, resultImagIsNaN);
1002
1003 return cir::TernaryOp::create(
1004 builder, loc, resultRealAndImagAreNaN,
1005 [&](mlir::OpBuilder &, mlir::Location) {
1006 mlir::Value libCallResult = buildComplexBinOpLibCall(
1007 pass, builder, &getComplexMulLibCallName, loc, complexTy,
1008 lhsReal, lhsImag, rhsReal, rhsImag);
1009 builder.createYield(loc, libCallResult);
1010 },
1011 [&](mlir::OpBuilder &, mlir::Location) {
1012 builder.createYield(loc, algebraicResult);
1013 })
1014 .getResult();
1015}
1016
1017void LoweringPreparePass::lowerComplexMulOp(cir::ComplexMulOp op) {
1018 cir::CIRBaseBuilderTy builder(getContext());
1019 builder.setInsertionPointAfter(op);
1020 mlir::Location loc = op.getLoc();
1021 mlir::TypedValue<cir::ComplexType> lhs = op.getLhs();
1022 mlir::TypedValue<cir::ComplexType> rhs = op.getRhs();
1023 mlir::Value lhsReal = builder.createComplexReal(loc, lhs);
1024 mlir::Value lhsImag = builder.createComplexImag(loc, lhs);
1025 mlir::Value rhsReal = builder.createComplexReal(loc, rhs);
1026 mlir::Value rhsImag = builder.createComplexImag(loc, rhs);
1027 mlir::Value loweredResult = lowerComplexMul(*this, builder, loc, op, lhsReal,
1028 lhsImag, rhsReal, rhsImag);
1029 op.replaceAllUsesWith(loweredResult);
1030 op.erase();
1031}
1032
1033void LoweringPreparePass::lowerComplexConjOp(cir::ComplexConjOp op) {
1034 mlir::Location loc = op.getLoc();
1035 CIRBaseBuilderTy builder(getContext());
1036 builder.setInsertionPointAfter(op);
1037
1038 mlir::Value operand = op.getOperand();
1039 mlir::Value operandReal = builder.createComplexReal(loc, operand);
1040 mlir::Value operandImag = builder.createComplexImag(loc, operand);
1041
1042 // The complex conjugate is formed by negating the imaginary component.
1043 const bool isFP = cir::isFPOrVectorOfFPType(operandReal.getType());
1044 mlir::Value resultImag = isFP ? builder.createFNeg(loc, operandImag)
1045 : builder.createMinus(loc, operandImag);
1046
1047 mlir::Value result =
1048 builder.createComplexCreate(loc, operandReal, resultImag);
1049 op->replaceAllUsesWith(mlir::ValueRange{result});
1050 op->erase();
1051}
1052
1053cir::FuncOp LoweringPreparePass::getOrCreateDtorFunc(CIRBaseBuilderTy &builder,
1054 cir::GlobalOp op,
1055 mlir::Region &dtorRegion,
1056 cir::CallOp &dtorCall) {
1057 mlir::OpBuilder::InsertionGuard guard(builder);
1059
1060 cir::VoidType voidTy = builder.getVoidTy();
1061 auto voidPtrTy = cir::PointerType::get(voidTy);
1062
1063 // Look for operations in dtorBlock
1064 mlir::Block &dtorBlock = dtorRegion.front();
1065
1066 // The first operation should be a get_global to retrieve the address
1067 // of the global variable we're destroying.
1068 auto opIt = dtorBlock.getOperations().begin();
1069 cir::GetGlobalOp ggop = mlir::cast<cir::GetGlobalOp>(*opIt);
1070
1071 // The simple case is just a call to a destructor, like this:
1072 //
1073 // %0 = cir.get_global %globalS : !cir.ptr<!rec_S>
1074 // cir.call %_ZN1SD1Ev(%0) : (!cir.ptr<!rec_S>) -> ()
1075 // (implicit cir.yield)
1076 //
1077 // That is, if the second operation is a call that takes the get_global result
1078 // as its only operand, and the only other operation is a yield, then we can
1079 // just return the called function.
1080 if (dtorBlock.getOperations().size() == 3) {
1081 auto callOp = mlir::dyn_cast<cir::CallOp>(&*(++opIt));
1082 auto yieldOp = mlir::dyn_cast<cir::YieldOp>(&*(++opIt));
1083 if (yieldOp && callOp && callOp.getNumOperands() == 1 &&
1084 callOp.getArgOperand(0) == ggop) {
1085 dtorCall = callOp;
1086 return getCalledFunction(callOp);
1087 }
1088 }
1089
1090 // Otherwise, we need to create a helper function to replace the dtor region.
1091 // This name is kind of arbitrary, but it matches the name that classic
1092 // codegen uses, based on the expected case that gets us here.
1093 builder.setInsertionPointAfter(op);
1094 SmallString<256> fnName("__cxx_global_array_dtor");
1095 uint32_t cnt = dynamicInitializerNames[fnName]++;
1096 if (cnt)
1097 fnName += "." + std::to_string(cnt);
1098
1099 // Create the helper function.
1100 auto fnType = cir::FuncType::get({voidPtrTy}, voidTy);
1101 cir::FuncOp dtorFunc =
1102 buildRuntimeFunction(builder, fnName, op.getLoc(), fnType,
1103 cir::GlobalLinkageKind::InternalLinkage);
1104
1105 SmallVector<mlir::NamedAttribute> paramAttrs;
1106 paramAttrs.push_back(
1107 builder.getNamedAttr("llvm.noundef", builder.getUnitAttr()));
1108 SmallVector<mlir::Attribute> argAttrDicts;
1109 argAttrDicts.push_back(
1110 mlir::DictionaryAttr::get(builder.getContext(), paramAttrs));
1111 dtorFunc.setArgAttrsAttr(
1112 mlir::ArrayAttr::get(builder.getContext(), argAttrDicts));
1113
1114 mlir::Block *entryBB = dtorFunc.addEntryBlock();
1115
1116 // Move everything from the dtor region into the helper function.
1117 entryBB->getOperations().splice(entryBB->begin(), dtorBlock.getOperations(),
1118 dtorBlock.begin(), dtorBlock.end());
1119
1120 // Before erasing this, clone it back into the dtor region
1121 cir::GetGlobalOp dtorGGop =
1122 mlir::cast<cir::GetGlobalOp>(entryBB->getOperations().front());
1123 builder.setInsertionPointToStart(&dtorBlock);
1124 builder.clone(*dtorGGop.getOperation());
1125
1126 // Replace all uses of the help function's get_global with the function
1127 // argument.
1128 mlir::Value dtorArg = entryBB->getArgument(0);
1129 dtorGGop.replaceAllUsesWith(dtorArg);
1130 dtorGGop.erase();
1131
1132 // Replace the yield in the final block with a return
1133 mlir::Block &finalBlock = dtorFunc.getBody().back();
1134 auto yieldOp = cast<cir::YieldOp>(finalBlock.getTerminator());
1135 builder.setInsertionPoint(yieldOp);
1136 cir::ReturnOp::create(builder, yieldOp->getLoc());
1137 yieldOp->erase();
1138
1139 // Create a call to the helper function, passing the original get_global op
1140 // as the argument.
1141 cir::GetGlobalOp origGGop =
1142 mlir::cast<cir::GetGlobalOp>(dtorBlock.getOperations().front());
1143 builder.setInsertionPointAfter(origGGop);
1144 mlir::Value ggopResult = origGGop.getResult();
1145 dtorCall = builder.createCallOp(op.getLoc(), dtorFunc, ggopResult);
1146
1147 // Add a yield after the call.
1148 auto finalYield = cir::YieldOp::create(builder, op.getLoc());
1149
1150 // Erase everything after the yield.
1151 dtorBlock.getOperations().erase(std::next(mlir::Block::iterator(finalYield)),
1152 dtorBlock.end());
1153 dtorRegion.getBlocks().erase(std::next(dtorRegion.begin()), dtorRegion.end());
1154
1155 return dtorFunc;
1156}
1157
1158cir::FuncOp
1159LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) {
1160 // TODO(cir): Store this in the GlobalOp.
1161 // This should come from the MangleContext, but for now I'm hardcoding it.
1162 SmallString<256> fnName("__cxx_global_var_init");
1163 // Get a unique name
1164 uint32_t cnt = dynamicInitializerNames[fnName]++;
1165 if (cnt)
1166 fnName += "." + std::to_string(cnt);
1167
1168 // Create a variable initialization function.
1169 CIRBaseBuilderTy builder(getContext());
1170 builder.setInsertionPointAfter(op);
1171 cir::VoidType voidTy = builder.getVoidTy();
1172 auto fnType = cir::FuncType::get({}, voidTy);
1173 FuncOp f = buildRuntimeFunction(builder, fnName, op.getLoc(), fnType,
1174 cir::GlobalLinkageKind::InternalLinkage);
1175
1176 // Forward the constrained floating-point marker recorded on the global by
1177 // CodeGen onto the generated initializer function. The marker on the global
1178 // is no longer meaningful once its regions have been moved out, so clear it.
1179 if (op.getStrictfp()) {
1180 f->setAttr(cir::CIRDialect::getStrictFPAttrName(),
1181 mlir::UnitAttr::get(&getContext()));
1182 op.setStrictfp(false);
1183 }
1184
1185 // Move over the initialization code of the ctor region.
1186 // The ctor region may have multiple blocks when exception handling
1187 // scaffolding creates extra blocks (e.g., unreachable/trap blocks).
1188 // We move all operations from the first block (minus the yield) into
1189 // the function entry, and discard extra blocks (which contain only
1190 // unreachable terminators from EH cleanup paths).
1191 mlir::Block *entryBB = f.addEntryBlock();
1192 builder.setInsertionPointToStart(entryBB);
1193
1194 // If this is a global TLS variable (that is, declared at namespace scope), we
1195 // have to emit the guard variable here.
1196 bool needsTlsGuard = op.getTlsRefs() && op.getTlsRefs()->getGuardName();
1197 cir::IfOp guardIf;
1198 if (needsTlsGuard) {
1199 guardIf = buildGlobalTlsGuardCheck(
1200 builder, op.getLoc(),
1201 getOrCreateStaticLocalDeclGuardAddress(
1202 builder, op, op.getTlsRefs()->getGuardName().getValue(),
1203 /*isLocalVarDecl=*/false,
1204 /*useInt8GuardVariable=*/op.hasInternalLinkage()));
1205 builder.setInsertionPointToEnd(&guardIf.getThenRegion().front());
1206 }
1207
1208 if (!op.getCtorRegion().empty()) {
1209 mlir::Block &block = op.getCtorRegion().front();
1210 mlir::Block *insertBlock = builder.getBlock();
1211 insertBlock->getOperations().splice(insertBlock->end(),
1212 block.getOperations(), block.begin(),
1213 std::prev(block.end()));
1214 }
1215
1216 // Register the destructor call with __cxa_atexit
1217 mlir::Region &dtorRegion = op.getDtorRegion();
1218 if (!dtorRegion.empty()) {
1220
1221 emitGlobalGuardedDtorRegion(builder, op, dtorRegion,
1222 op.getTlsModel().has_value(),
1223 *builder.getBlock());
1224 }
1225
1226 // If we're actually in the 'if' above, create a yield.
1227 if (needsTlsGuard) {
1228 builder.setInsertionPointToEnd(&guardIf.getThenRegion().back());
1229 cir::YieldOp::create(builder, op.getLoc());
1230 }
1231
1232 // Replace cir.yield with cir.return
1233 builder.setInsertionPointToEnd(entryBB);
1234 mlir::Operation *yieldOp = nullptr;
1235 if (!op.getCtorRegion().empty()) {
1236 mlir::Block &block = op.getCtorRegion().front();
1237 yieldOp = &block.getOperations().back();
1238 } else {
1239 assert(!dtorRegion.empty());
1240 mlir::Block &block = dtorRegion.front();
1241 yieldOp = &block.getOperations().back();
1242 }
1243
1244 assert(isa<cir::YieldOp>(*yieldOp));
1245 cir::ReturnOp::create(builder, yieldOp->getLoc());
1246 return f;
1247}
1248
1249cir::FuncOp
1250LoweringPreparePass::getGuardAcquireFn(cir::PointerType guardPtrTy) {
1251 // int __cxa_guard_acquire(__guard *guard_object);
1252 CIRBaseBuilderTy builder(getContext());
1253 mlir::OpBuilder::InsertionGuard ipGuard{builder};
1254 builder.setInsertionPointToStart(mlirModule.getBody());
1255 mlir::Location loc = mlirModule.getLoc();
1256 cir::IntType intTy = cir::IntType::get(&getContext(), 32, /*isSigned=*/true);
1257 auto fnType = cir::FuncType::get({guardPtrTy}, intTy);
1258 return buildRuntimeFunction(builder, "__cxa_guard_acquire", loc, fnType);
1259}
1260
1261cir::FuncOp
1262LoweringPreparePass::getGuardReleaseFn(cir::PointerType guardPtrTy) {
1263 // void __cxa_guard_release(__guard *guard_object);
1264 CIRBaseBuilderTy builder(getContext());
1265 mlir::OpBuilder::InsertionGuard ipGuard{builder};
1266 builder.setInsertionPointToStart(mlirModule.getBody());
1267 mlir::Location loc = mlirModule.getLoc();
1268 cir::VoidType voidTy = cir::VoidType::get(&getContext());
1269 auto fnType = cir::FuncType::get({guardPtrTy}, voidTy);
1270 return buildRuntimeFunction(builder, "__cxa_guard_release", loc, fnType);
1271}
1272
1273cir::FuncOp LoweringPreparePass::getTlsInitFn() {
1274 // void __tls_init(void);
1275 CIRBaseBuilderTy builder(getContext());
1276 mlir::OpBuilder::InsertionGuard _{builder};
1277 builder.setInsertionPointToStart(mlirModule.getBody());
1278 mlir::Location loc = mlirModule.getLoc();
1279 auto fnType = builder.getVoidFnTy();
1280 return buildRuntimeFunction(builder, "__tls_init", loc, fnType,
1281 cir::GlobalLinkageKind::InternalLinkage);
1282}
1283
1284cir::GlobalOp LoweringPreparePass::createGuardGlobalOp(
1285 CIRBaseBuilderTy &builder, mlir::Location loc, llvm::StringRef name,
1286 cir::IntType guardTy, cir::GlobalLinkageKind linkage) {
1287 mlir::OpBuilder::InsertionGuard guard(builder);
1288 builder.setInsertionPointToStart(mlirModule.getBody());
1289 cir::GlobalOp g = cir::GlobalOp::create(builder, loc, name, guardTy);
1290 g.setLinkageAttr(
1291 cir::GlobalLinkageKindAttr::get(builder.getContext(), linkage));
1292 mlir::SymbolTable::setSymbolVisibility(
1293 g, mlir::SymbolTable::Visibility::Private);
1294 return g;
1295}
1296
1297void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp,
1298 cir::LocalInitOp localInitOp) {
1299 CIRBaseBuilderTy builder(getContext());
1300
1301 std::optional<cir::ASTVarDeclInterface> astOption = globalOp.getAst();
1302 assert(astOption.has_value());
1303 cir::ASTVarDeclInterface varDecl = astOption.value();
1304
1305 builder.setInsertionPointAfter(localInitOp);
1306 mlir::Block *localInitBlock = builder.getInsertionBlock();
1307
1308 // Remove the terminator temporarily - we'll add it back at the end.
1309 mlir::Operation *ret = localInitBlock->getTerminator();
1310 ret->remove();
1311 // Note: These two insert-point-after sets are necessary, as the 'trailing'
1312 // operation has changed thanks to the terminator removal.
1313 builder.setInsertionPointAfter(localInitOp);
1314
1315 // Inline variables that weren't instantiated from variable templates have
1316 // partially-ordered initialization within their translation unit.
1317 bool nonTemplateInline =
1318 varDecl.isInline() &&
1319 !clang::isTemplateInstantiation(varDecl.getTemplateSpecializationKind());
1320
1321 // Inline namespace-scope variables require guarded initialization in a
1322 // __cxx_global_var_init function. This is not yet implemented.
1323 if (nonTemplateInline) {
1324 globalOp->emitError(
1325 "NYI: guarded initialization for inline namespace-scope variables");
1326 return;
1327 }
1328
1329 // We only need to use thread-safe statics for local non-TLS variables and
1330 // inline variables; other global initialization is always single-threaded
1331 // or (through lazy dynamic loading in multiple threads) unsequenced.
1332 bool threadsafe = astCtx->getLangOpts().ThreadsafeStatics &&
1333 (varDecl.isLocalVarDecl() || nonTemplateInline) &&
1334 !varDecl.getTLSKind();
1335
1336 // If we have a global variable with internal linkage and thread-safe statics
1337 // are disabled, we can just let the guard variable be of type i8.
1338 bool useInt8GuardVariable = !threadsafe && globalOp.hasInternalLinkage();
1339
1340 // Create the guard variable if we don't already have it.
1341 cir::GlobalOp guard = getOrCreateStaticLocalDeclGuardAddress(
1342 builder, globalOp, globalOp.getStaticLocalGuard()->getName().getValue(),
1343 varDecl.isLocalVarDecl(), useInt8GuardVariable);
1344 if (!guard) {
1345 // Error was already emitted, just restore the terminator and return.
1346 localInitBlock->push_back(ret);
1347 return;
1348 }
1349
1350 mlir::Value guardPtr = builder.createGetGlobal(guard, localInitOp.getTls());
1351
1352 // Test whether the variable has completed initialization.
1353 //
1354 // Itanium C++ ABI 3.3.2:
1355 // The following is pseudo-code showing how these functions can be used:
1356 // if (obj_guard.first_byte == 0) {
1357 // if ( __cxa_guard_acquire (&obj_guard) ) {
1358 // try {
1359 // ... initialize the object ...;
1360 // } catch (...) {
1361 // __cxa_guard_abort (&obj_guard);
1362 // throw;
1363 // }
1364 // ... queue object destructor with __cxa_atexit() ...;
1365 // __cxa_guard_release (&obj_guard);
1366 // }
1367 // }
1368 //
1369 // If threadsafe statics are enabled, but we don't have inline atomics, just
1370 // call __cxa_guard_acquire unconditionally. The "inline" check isn't
1371 // actually inline, and the user might not expect calls to __atomic libcalls.
1372 unsigned maxInlineWidthInBits =
1374
1375 if (!threadsafe || maxInlineWidthInBits) {
1376 // Load the first byte of the guard variable.
1377 auto bytePtrTy = cir::PointerType::get(builder.getSIntNTy(8));
1378 mlir::Value bytePtr = builder.createBitcast(guardPtr, bytePtrTy);
1379 mlir::Value guardLoad = builder.createAlignedLoad(
1380 localInitOp.getLoc(), bytePtr, *guard.getAlignment());
1381
1382 // Itanium ABI:
1383 // An implementation supporting thread-safety on multiprocessor
1384 // systems must also guarantee that references to the initialized
1385 // object do not occur before the load of the initialization flag.
1386 //
1387 // In LLVM, we do this by marking the load Acquire.
1388 if (threadsafe) {
1389 auto loadOp = mlir::cast<cir::LoadOp>(guardLoad.getDefiningOp());
1390 loadOp.setMemOrder(cir::MemOrder::Acquire);
1391 loadOp.setSyncScope(cir::SyncScopeKind::System);
1392 }
1393
1394 // For ARM, we should only check the first bit, rather than the entire byte:
1395 //
1396 // ARM C++ ABI 3.2.3.1:
1397 // To support the potential use of initialization guard variables
1398 // as semaphores that are the target of ARM SWP and LDREX/STREX
1399 // synchronizing instructions we define a static initialization
1400 // guard variable to be a 4-byte aligned, 4-byte word with the
1401 // following inline access protocol.
1402 // #define INITIALIZED 1
1403 // if ((obj_guard & INITIALIZED) != INITIALIZED) {
1404 // if (__cxa_guard_acquire(&obj_guard))
1405 // ...
1406 // }
1407 //
1408 // and similarly for ARM64:
1409 //
1410 // ARM64 C++ ABI 3.2.2:
1411 // This ABI instead only specifies the value bit 0 of the static guard
1412 // variable; all other bits are platform defined. Bit 0 shall be 0 when
1413 // the variable is not initialized and 1 when it is.
1414 if (useARMGuardVarABI() && !useInt8GuardVariable) {
1415 auto one = builder.getConstantInt(
1416 localInitOp.getLoc(), mlir::cast<cir::IntType>(guardLoad.getType()),
1417 1);
1418 guardLoad = builder.createAnd(localInitOp.getLoc(), guardLoad, one);
1419 }
1420
1421 // Check if the first byte of the guard variable is zero.
1422 auto zero = builder.getConstantInt(
1423 localInitOp.getLoc(), mlir::cast<cir::IntType>(guardLoad.getType()), 0);
1424 auto needsInit = builder.createCompare(localInitOp.getLoc(),
1425 cir::CmpOpKind::eq, guardLoad, zero);
1426
1427 // Build the guarded initialization inside an if block.
1428 cir::IfOp::create(
1429 builder, globalOp.getLoc(), needsInit,
1430 /*withElseRegion=*/false, [&](mlir::OpBuilder &, mlir::Location) {
1431 emitCXXGuardedInitIf(builder, globalOp, localInitOp.getCtorRegion(),
1432 localInitOp.getDtorRegion(), varDecl, guardPtr,
1433 builder.getPointerTo(guard.getSymType()),
1434 threadsafe);
1435 });
1436 } else {
1437 // Threadsafe statics without inline atomics - call __cxa_guard_acquire
1438 // unconditionally without the initial guard byte check.
1439 globalOp->emitError("NYI: guarded init without inline atomics support");
1440 return;
1441 }
1442
1443 // Insert the removed terminator back.
1444 builder.getInsertionBlock()->push_back(ret);
1445}
1446
1447void LoweringPreparePass::lowerLocalInitOp(cir::LocalInitOp initOp) {
1448
1449 // If we don't actually need to initialize anything anymore, we're done here.
1450 if (initOp.getCtorRegion().empty() && initOp.getDtorRegion().empty()) {
1451 initOp.erase();
1452 return;
1453 }
1454
1455 cir::GlobalOp globalOp = initOp.getReferencedGlobal(symbolTables);
1456 assert(globalOp && "No global-op found");
1457
1458 handleStaticLocal(globalOp, initOp);
1459
1460 // Remove the init local op, now that we've done everything we need with it.
1461 initOp.erase();
1462}
1464 // Note: Classic codegen needs to check that the VarDecl.getTLSKind() ==
1465 // TLS_Dynamic, but we don't attempt to emit the thread wrapper unless that is
1466 // already the case. So the only thing that matters here is whether it is
1467 // darwin.
1468 return astCtx.getTargetInfo().getTriple().isOSDarwin();
1469}
1470
1471static cir::GlobalLinkageKind
1473 if (isLocalLinkage(op.getLinkage()))
1474 return op.getLinkage();
1475
1476 if (isThreadWrapperReplaceable(astCtx))
1477 if (!isLinkOnceLinkage(op.getLinkage()) &&
1478 !isWeakODRLinkage(op.getLinkage()))
1479 return op.getLinkage();
1480
1481 // If this isn't a TU in which this variable is defined, the thread wrapper is
1482 // discardable.
1483 if (op.isDeclaration())
1484 return cir::GlobalLinkageKind::LinkOnceODRLinkage;
1485 return cir::GlobalLinkageKind::WeakODRLinkage;
1486}
1487
1488cir::FuncOp
1489LoweringPreparePass::getOrCreateThreadLocalWrapper(CIRBaseBuilderTy &builder,
1490 GlobalOp op) {
1491 mlir::OpBuilder::InsertionGuard insertGuard(builder);
1492 builder.setInsertionPointToStart(&mlirModule.getBodyRegion().front());
1493
1494 mlir::StringAttr wrapperName = op.getTlsRefs()->getWrapperName();
1495
1496 auto existingWrapperIter = threadLocalWrappers.find(wrapperName.getValue());
1497 if (existingWrapperIter != threadLocalWrappers.end())
1498 return existingWrapperIter->second;
1499
1500 // type is ptr-to-global-type(void);
1501 auto funcType = cir::FuncType::get({}, builder.getPointerTo(op.getSymType()));
1502 cir::FuncOp func =
1503 cir::FuncOp::create(builder, op.getLoc(), wrapperName, funcType);
1504
1505 cir::GlobalLinkageKind linkageKind =
1506 getThreadLocalWrapperLinkage(op, *astCtx);
1507 func.setLinkageAttr(
1508 cir::GlobalLinkageKindAttr::get(&getContext(), linkageKind));
1509
1510 // TODO(cir): This is supposed to refer to the comdat of the global symbol,
1511 // but that isn't in CIR yet.
1512 if (astCtx->getTargetInfo().getTriple().supportsCOMDAT() &&
1513 func.isWeakForLinker())
1514 func.setComdat(true);
1515
1516 mlir::SymbolTable::setSymbolVisibility(
1517 func, mlir::SymbolTable::Visibility::Private);
1518
1519 if (!isLocalLinkage(linkageKind)) {
1520 if (!isThreadWrapperReplaceable(*astCtx) ||
1521 isLinkOnceLinkage(linkageKind) || isWeakODRLinkage(linkageKind) ||
1522 op.getGlobalVisibility() == cir::VisibilityKind::Hidden)
1523 func.setGlobalVisibility(cir::VisibilityKind::Hidden);
1524 }
1525 if (isThreadWrapperReplaceable(*astCtx))
1526 op->emitError("Unhandled thread wrapper attributes for CC and Nounwind");
1527
1528 threadLocalWrappers.insert({wrapperName.getValue(), func});
1529 return func;
1530}
1531
1532void LoweringPreparePass::defineGlobalThreadLocalWrapper(cir::GlobalOp op,
1533 cir::FuncOp initAlias,
1534 bool isVarDefinition) {
1535 CIRBaseBuilderTy builder(getContext());
1536 cir::FuncOp wrapper = getOrCreateThreadLocalWrapper(builder, op);
1537 mlir::Block *entryBB = wrapper.addEntryBlock();
1538 builder.setInsertionPointToStart(entryBB);
1539 // If we are a situation where we have/need one, emit a call to the init
1540 // function.
1541 if (initAlias) {
1542 mlir::Location aliasLoc = initAlias.getLoc();
1543 if (!isVarDefinition) {
1544 // If this isn't a definition, we have to check that the alias exists.
1545 mlir::Value funcLoad = cir::GetGlobalOp::create(
1546 builder, aliasLoc, cir::PointerType::get(initAlias.getFunctionType()),
1547 initAlias.getSymName());
1548 mlir::Value nullCheck =
1549 builder.getNullValue(funcLoad.getType(), aliasLoc);
1550 mlir::Value cmp = cir::CmpOp::create(
1551 builder, aliasLoc, cir::CmpOpKind::ne, funcLoad, nullCheck);
1552 cir::IfOp::create(builder, aliasLoc, cmp, /*withElseRegion=*/false,
1553 [&](mlir::OpBuilder &, mlir::Location loc) {
1554 builder.createCallOp(aliasLoc, initAlias, {});
1555 cir::YieldOp::create(builder, aliasLoc);
1556 });
1557 } else {
1558 // If this IS a definition, we know the alias exists, so we can just emit
1559 // a call to it.
1560 builder.createCallOp(aliasLoc, initAlias, {});
1561 }
1562 }
1563 cir::GetGlobalOp get = builder.createGetGlobal(op, /*tls=*/true);
1564 cir::ReturnOp::create(builder, op.getLoc(), {get});
1565}
1566
1567cir::FuncOp
1568LoweringPreparePass::defineGlobalThreadLocalInitAlias(cir::GlobalOp op,
1569 cir::FuncOp aliasee) {
1570 CIRBaseBuilderTy builder(getContext());
1571 mlir::OpBuilder::InsertionGuard insertGuard(builder);
1572 builder.setInsertionPointToStart(&mlirModule.getBodyRegion().front());
1573 mlir::StringAttr aliasName = op.getTlsRefs()->getInitName();
1574 auto existingAliasIter = threadLocalInitAliases.find(aliasName.getValue());
1575
1576 if (existingAliasIter != threadLocalInitAliases.end())
1577 return existingAliasIter->second;
1578
1579 auto funcType = builder.getVoidFnTy();
1580 cir::FuncOp alias =
1581 cir::FuncOp::create(builder, op.getLoc(), aliasName, funcType);
1582 alias.setLinkage(op.getLinkage());
1583
1584 if (aliasee) {
1585 alias.setAliasee(aliasee.getSymName());
1586 } else {
1587 // If we don't have anything to alias (because this isn't a variable
1588 // definition!), we set this as just a function definition with no alias,
1589 // and extern-weak.
1590 alias.setLinkage(cir::GlobalLinkageKind::ExternalWeakLinkage);
1591 mlir::SymbolTable::setSymbolVisibility(
1592 alias, mlir::SymbolTable::Visibility::Private);
1593 }
1594
1595 threadLocalInitAliases.insert({aliasName.getValue(), alias});
1596 return alias;
1597}
1598
1599void LoweringPreparePass::lowerGlobalOp(GlobalOp op) {
1600 // Static locals are handled separately via guard variables.
1601 if (op.getStaticLocalGuard())
1602 return;
1603
1604 mlir::Region &ctorRegion = op.getCtorRegion();
1605 mlir::Region &dtorRegion = op.getDtorRegion();
1606 cir::FuncOp initAlias;
1607
1608 if (!ctorRegion.empty() || !dtorRegion.empty()) {
1609 // Build a variable initialization function and move the initialzation code
1610 // in the ctor region over.
1611 cir::FuncOp f = buildCXXGlobalVarDeclInitFunc(op);
1612
1613 // Clear the ctor and dtor region
1614 ctorRegion.getBlocks().clear();
1615 dtorRegion.getBlocks().clear();
1616
1618 if (op.getTlsModel() && !op.getStaticLocalGuard().has_value()) {
1619 // There are two types of global TLS variables: 'ordered' and 'unordered'.
1620 // 'ordered' are the common case. A call to any of them causes all of the
1621 // initializers for all other 'ordered' ones to be called, via a
1622 // `__tls_init` function. So the 'init alias' that gets called in the
1623 // wrapper for these goes directly to `__tls_init`.
1624
1625 // 'Unordered' values are the case for variable templates. In this case,
1626 // their init alias goes directly to their init function. The FE generates
1627 // a guard variable for them (since they cannot use the global guard), so
1628 // we differentiate them that way.
1629
1630 if (op.getTlsRefs()->getGuardName()) {
1631 // Unordered: the alias is the function we just generated.
1632 initAlias = defineGlobalThreadLocalInitAlias(op, f);
1633 } else {
1634 // Ordered: Get the __tls_init, and make the alias to that.
1635 initAlias = defineGlobalThreadLocalInitAlias(op, getTlsInitFn());
1636 // Ordered inits also need to get called from the __tls_init function,
1637 // so we add the init function to the list, so that we can add them to
1638 // it later.
1639 globalThreadLocalInitializers.push_back(f);
1640 }
1641 } else {
1642 dynamicInitializers.push_back(f);
1643 }
1644 } else if (op.getTlsModel() && op.getTlsRefs() && op.isDeclaration()) {
1645 // If this is a declaration and has no init function, we probably DO have to
1646 // create an alias that needs checking, so create it as extern-weak.
1647 initAlias = defineGlobalThreadLocalInitAlias(op, {});
1648 }
1649
1650 // We need a wrapper for TLS globals that MIGHT have a non-constant
1651 // initialization. The FE will have generated the TlsRefs for any with
1652 // known dynamic init, or unknown (extern) init.
1653 if (op.getTlsModel() && op.getTlsRefs())
1654 defineGlobalThreadLocalWrapper(op, initAlias, !op.isDeclaration());
1655
1657}
1658
1659void LoweringPreparePass::lowerGetGlobalOp(GetGlobalOp op) {
1660 if (!op.getTls())
1661 return;
1662 auto globalOp = mlir::cast<cir::GlobalOp>(
1663 symbolTables.lookupNearestSymbolFrom(op, op.getNameAttr()));
1664
1665 // Only global/namespace scope thread local variables need to have their
1666 // get-global operations rewritten to be calls to a wrapper function. If
1667 // we're not in a dynamic TLS (or one without the TLS markers), we can leave
1668 // this one as a get-global and return early.
1669 if (!globalOp.getTlsModel() || !globalOp.getTlsRefs())
1670 return;
1671
1672 // If this is a global TLS, we need to replace the call to 'get_global' with a
1673 // call to the wrapper function. Classic codegen figures out some cases where
1674 // we can omit this, but for now we're going to always put it in, as it is
1675 // effectively a no-op.
1676
1677 // The first 'GetGlobalOp' at the beginning of a ctor/dtor region on one of
1678 // these is for the purpose of creating/destroying. We want to skip replacing
1679 // THAT one, but leave all other get-global-ops in place, else
1680 // self-referential ops won't work right.
1681
1682 // Note that ctors/dtors are removed during this pass. We get away with these
1683 // checks because the only time that these situations can actually be true
1684 // (that is, the ctor/dtor region exist) is if we're in the process of
1685 // converting the ctor/dtor for this. If we're NOT doing that, the ctor/dtor
1686 // will have already disappeared.
1687 mlir::Operation *parentOp = op->getParentOp();
1688 if (parentOp == globalOp) {
1689 mlir::Region *ctorRegion = &globalOp.getCtorRegion();
1690 mlir::Region *dtorRegion = &globalOp.getDtorRegion();
1691
1692 if (!ctorRegion->empty() && &*ctorRegion->op_begin() == op.getOperation())
1693 return;
1694 if (!dtorRegion->empty() && &*dtorRegion->op_begin() == op.getOperation())
1695 return;
1696 }
1697
1698 CIRBaseBuilderTy builder(getContext());
1699 cir::FuncOp wrapperFunc = getOrCreateThreadLocalWrapper(builder, globalOp);
1700
1701 builder.setInsertionPoint(op);
1702 cir::CallOp call = builder.createCallOp(
1703 wrapperFunc.getLoc(),
1704 mlir::FlatSymbolRefAttr::get(wrapperFunc.getSymNameAttr()),
1705 wrapperFunc.getFunctionType().getReturnType(), {});
1706 op->replaceAllUsesWith(call);
1707 op.erase();
1708}
1709
1710void LoweringPreparePass::lowerThreeWayCmpOp(CmpThreeWayOp op) {
1711 CIRBaseBuilderTy builder(getContext());
1712 builder.setInsertionPointAfter(op);
1713
1714 mlir::Location loc = op->getLoc();
1715 cir::CmpThreeWayInfoAttr cmpInfo = op.getInfo();
1716
1717 mlir::Value ltRes =
1718 builder.getConstantInt(loc, op.getType(), cmpInfo.getLt());
1719 mlir::Value eqRes =
1720 builder.getConstantInt(loc, op.getType(), cmpInfo.getEq());
1721 mlir::Value gtRes =
1722 builder.getConstantInt(loc, op.getType(), cmpInfo.getGt());
1723
1724 mlir::Value transformedResult;
1725 if (cmpInfo.getOrdering() != CmpOrdering::Partial) {
1726 // Total ordering
1727 mlir::Value lt =
1728 builder.createCompare(loc, CmpOpKind::lt, op.getLhs(), op.getRhs());
1729 mlir::Value selectOnLt = builder.createSelect(loc, lt, ltRes, gtRes);
1730 mlir::Value eq =
1731 builder.createCompare(loc, CmpOpKind::eq, op.getLhs(), op.getRhs());
1732 transformedResult = builder.createSelect(loc, eq, eqRes, selectOnLt);
1733 } else {
1734 // Partial ordering
1735 cir::ConstantOp unorderedRes = builder.getConstantInt(
1736 loc, op.getType(), cmpInfo.getUnordered().value());
1737
1738 mlir::Value eq =
1739 builder.createCompare(loc, CmpOpKind::eq, op.getLhs(), op.getRhs());
1740 mlir::Value selectOnEq = builder.createSelect(loc, eq, eqRes, unorderedRes);
1741 mlir::Value gt =
1742 builder.createCompare(loc, CmpOpKind::gt, op.getLhs(), op.getRhs());
1743 mlir::Value selectOnGt = builder.createSelect(loc, gt, gtRes, selectOnEq);
1744 mlir::Value lt =
1745 builder.createCompare(loc, CmpOpKind::lt, op.getLhs(), op.getRhs());
1746 transformedResult = builder.createSelect(loc, lt, ltRes, selectOnGt);
1747 }
1748
1749 op.replaceAllUsesWith(transformedResult);
1750 op.erase();
1751}
1752
1753template <typename AttributeTy>
1754static llvm::SmallVector<mlir::Attribute>
1755prepareCtorDtorAttrList(mlir::MLIRContext *context,
1756 llvm::ArrayRef<std::pair<std::string, uint32_t>> list) {
1758 for (const auto &[name, priority] : list)
1759 attrs.push_back(AttributeTy::get(context, name, priority));
1760 return attrs;
1761}
1762
1763void LoweringPreparePass::buildGlobalCtorDtorList() {
1764 if (!globalCtorList.empty()) {
1765 llvm::SmallVector<mlir::Attribute> globalCtors =
1767 globalCtorList);
1768
1769 mlirModule->setAttr(cir::CIRDialect::getGlobalCtorsAttrName(),
1770 mlir::ArrayAttr::get(&getContext(), globalCtors));
1771 }
1772
1773 if (!globalDtorList.empty()) {
1774 llvm::SmallVector<mlir::Attribute> globalDtors =
1776 globalDtorList);
1777 mlirModule->setAttr(cir::CIRDialect::getGlobalDtorsAttrName(),
1778 mlir::ArrayAttr::get(&getContext(), globalDtors));
1779 }
1780}
1781
1782cir::GlobalOp
1783LoweringPreparePass::createGlobalThreadLocalGuard(CIRBaseBuilderTy &builder,
1784 mlir::Location loc) {
1785 mlir::OpBuilder::InsertionGuard guard(builder);
1786 builder.setInsertionPointToStart(mlirModule.getBody());
1787
1788 // The TLS Guard is always an Int8Ty.
1789 cir::IntType guardTy = builder.getSIntNTy(8);
1790 auto g = cir::GlobalOp::create(builder, loc, "__tls_guard", guardTy);
1791 g.setLinkageAttr(cir::GlobalLinkageKindAttr::get(
1792 builder.getContext(), cir::GlobalLinkageKind::InternalLinkage));
1793 g.setAlignment(clang::CharUnits::One().getAsAlign().value());
1794
1795 if (auto defTlsModel = mlirModule->getAttrOfType<TLSModelAttr>(
1796 cir::CIRDialect::getDefaultTlsModelAttrName())) {
1797 g.setTlsModel(defTlsModel.getValue());
1798 } else {
1799 // Default value, unless overridden in the IR/by the frontend.
1800 g.setTlsModel(TLSModel::GeneralDynamic);
1801 }
1802
1803 g.setInitialValueAttr(cir::IntAttr::get(guardTy, 0));
1804 return g;
1805}
1806
1807cir::IfOp LoweringPreparePass::buildGlobalTlsGuardCheck(
1808 CIRBaseBuilderTy &builder, mlir::Location loc, cir::GlobalOp guard) {
1809 cir::GetGlobalOp getGuard = builder.createGetGlobal(guard, /*tls=*/true);
1810 mlir::Value getGuardValue = getGuard;
1811
1812 // Classic codegen always just loads the first byte of the guard instead of
1813 // the whole thing. __tls_guard is already only 8 bits, but for the case of
1814 // unordered TLS, it gets created as 64 bits.
1815 if (guard.getSymType() != builder.getSIntNTy(8))
1816 getGuardValue = builder.createBitcast(
1817 getGuard, cir::PointerType::get(builder.getSIntNTy(8)));
1818
1819 mlir::Value guardLoad =
1820 builder.createAlignedLoad(loc, getGuardValue, *guard.getAlignment());
1821 auto zero = builder.getConstantInt(loc, builder.getSIntNTy(8), 0);
1822 cir::CmpOp compare =
1823 builder.createCompare(loc, cir::CmpOpKind::eq, guardLoad, zero);
1824 return cir::IfOp::create(
1825 builder, loc, compare,
1826 /*withElseRegion=*/false, [&](mlir::OpBuilder &, mlir::Location loc) {
1827 // Classic codegen still does this store as a i8, but it doesn't seem
1828 // reasonable to do an i8 store into a 64 bit value?
1829 builder.createStore(
1830 loc, builder.getConstantInt(loc, guard.getSymType(), 1), getGuard);
1831 });
1832}
1833
1834void LoweringPreparePass::buildCXXGlobalTlsFunc() {
1835 if (globalThreadLocalInitializers.empty())
1836 return;
1837
1838 // The global-ordered-init function for TLS variables just calls each of the
1839 // init-functions in order after doing a guard.
1840
1841 cir::FuncOp tlsInit = getTlsInitFn();
1842 mlir::Location loc = tlsInit.getLoc();
1843 CIRBaseBuilderTy builder(getContext());
1844 mlir::Block *entryBB = tlsInit.addEntryBlock();
1845 builder.setInsertionPointToStart(entryBB);
1846
1847 cir::IfOp ifOperation = buildGlobalTlsGuardCheck(
1848 builder, loc, createGlobalThreadLocalGuard(builder, loc));
1849
1850 // Emit the body of the guarded spot.
1851 builder.setInsertionPointToEnd(&ifOperation.getThenRegion().front());
1852 for (cir::FuncOp initFunc : globalThreadLocalInitializers)
1853 builder.createCallOp(loc, initFunc, {});
1854 cir::YieldOp::create(builder, loc);
1855
1856 builder.setInsertionPointAfter(ifOperation);
1857 cir::ReturnOp::create(builder, loc);
1858}
1859
1860void LoweringPreparePass::buildCXXGlobalInitFunc() {
1861 if (dynamicInitializers.empty())
1862 return;
1863
1864 // TODO: handle globals with a user-specified initialzation priority.
1865 // TODO: handle default priority more nicely.
1867
1868 SmallString<256> fnName;
1869 cir::GlobalLinkageKind linkage;
1870 // Include the filename in the symbol name. Including "sub_" matches gcc
1871 // and makes sure these symbols appear lexicographically behind the symbols
1872 // with priority (TBD). Module implementation units behave the same
1873 // way as a non-modular TU with imports.
1874 // TODO: check CXX20ModuleInits
1875 if (astCtx->getCurrentNamedModule() &&
1877 llvm::raw_svector_ostream out(fnName);
1878 std::unique_ptr<clang::MangleContext> mangleCtx(
1879 astCtx->createMangleContext());
1880 cast<clang::ItaniumMangleContext>(*mangleCtx)
1881 .mangleModuleInitializer(astCtx->getCurrentNamedModule(), out);
1882 linkage = cir::GlobalLinkageKind::ExternalLinkage;
1883 } else {
1884 fnName += "_GLOBAL__sub_I_";
1885 fnName += getTransformedFileName(mlirModule);
1886 linkage = cir::GlobalLinkageKind::InternalLinkage;
1887 }
1888
1889 CIRBaseBuilderTy builder(getContext());
1890 builder.setInsertionPointToEnd(&mlirModule.getBodyRegion().back());
1891 auto fnType = cir::FuncType::get({}, builder.getVoidTy());
1892 cir::FuncOp f = buildRuntimeFunction(builder, fnName, mlirModule.getLoc(),
1893 fnType, linkage);
1894 builder.setInsertionPointToStart(f.addEntryBlock());
1895 for (cir::FuncOp &f : dynamicInitializers)
1896 builder.createCallOp(f.getLoc(), f, {});
1897 // Add the global init function (not the individual ctor functions) to the
1898 // global ctor list.
1899 globalCtorList.emplace_back(fnName,
1900 cir::GlobalCtorAttr::getDefaultPriority());
1901
1902 cir::ReturnOp::create(builder, f.getLoc());
1903}
1904
1905/// Lower a cir.array.ctor or cir.array.dtor into a do-while loop that
1906/// iterates over every element. For cir.array.ctor ops whose partial_dtor
1907/// region is non-empty, the ctor loop is wrapped in a cir.cleanup.scope whose
1908/// EH cleanup performs a reverse destruction loop using the partial dtor body.
1910 clang::ASTContext *astCtx,
1911 mlir::Operation *op, mlir::Type eltTy,
1912 mlir::Value addr,
1913 mlir::Value numElements,
1914 uint64_t arrayLen, bool isCtor) {
1915 mlir::Location loc = op->getLoc();
1916 bool isDynamic = numElements != nullptr;
1917
1918 // TODO: instead of getting the size from the AST context, create alias for
1919 // PtrDiffTy and unify with CIRGen stuff.
1920 const unsigned sizeTypeSize =
1921 astCtx->getTypeSize(astCtx->getSignedSizeType());
1922
1923 // Both constructors and destructors use end = begin + numElements.
1924 // Constructors iterate forward [begin, end). Destructors iterate backward
1925 // from end, decrementing before calling the destructor on each element.
1926 mlir::Value begin, end;
1927 if (isDynamic) {
1928 begin = addr;
1929 end = cir::PtrStrideOp::create(builder, loc, eltTy, begin, numElements);
1930 } else {
1931 mlir::Value endOffsetVal =
1932 builder.getUnsignedInt(loc, arrayLen, sizeTypeSize);
1933 begin = cir::CastOp::create(builder, loc, eltTy,
1934 cir::CastKind::array_to_ptrdecay, addr);
1935 end = cir::PtrStrideOp::create(builder, loc, eltTy, begin, endOffsetVal);
1936 }
1937
1938 mlir::Value start = isCtor ? begin : end;
1939 mlir::Value stop = isCtor ? end : begin;
1940
1941 // For dynamic destructors, guard against zero elements.
1942 // This places the destructor loop emitted below inside the if block.
1943 cir::IfOp ifOp;
1944 if (isDynamic) {
1945 mlir::Value guardCond;
1946 if (isCtor) {
1947 mlir::Value zero = builder.getUnsignedInt(loc, 0, sizeTypeSize);
1948 guardCond = cir::CmpOp::create(builder, loc, cir::CmpOpKind::ne,
1949 numElements, zero);
1950 } else {
1951 // We could check for numElements != 0 in this case too, but this matches
1952 // what classic codegen does.
1953 guardCond =
1954 cir::CmpOp::create(builder, loc, cir::CmpOpKind::ne, start, stop);
1955 }
1956 ifOp = cir::IfOp::create(builder, loc, guardCond,
1957 /*withElseRegion=*/false,
1958 [&](mlir::OpBuilder &, mlir::Location) {});
1959 builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
1960 }
1961
1962 mlir::Value tmpAddr =
1963 builder.createAlloca(loc, /*addr type*/ builder.getPointerTo(eltTy),
1964 "__array_idx", builder.getAlignmentAttr(1));
1965 builder.createStore(loc, start, tmpAddr);
1966
1967 mlir::Block *bodyBlock = &op->getRegion(0).front();
1968
1969 // Clone the region body (ctor/dtor call and any setup ops like per-element
1970 // zero-init) into the loop, remapping the block argument to the current
1971 // element pointer.
1972 auto cloneRegionBodyInto = [&](mlir::Block *srcBlock,
1973 mlir::Value replacement) {
1974 mlir::IRMapping map;
1975 map.map(srcBlock->getArgument(0), replacement);
1976 for (mlir::Operation &regionOp : *srcBlock) {
1977 if (!mlir::isa<cir::YieldOp>(&regionOp))
1978 builder.clone(regionOp, map);
1979 }
1980 };
1981
1982 mlir::Block *partialDtorBlock = nullptr;
1983 if (auto arrayCtor = mlir::dyn_cast<cir::ArrayCtor>(op)) {
1984 mlir::Region &partialDtor = arrayCtor.getPartialDtor();
1985 if (!partialDtor.empty())
1986 partialDtorBlock = &partialDtor.front();
1987 } else if (auto arrayDtor = mlir::dyn_cast<cir::ArrayDtor>(op)) {
1988 // When the element destructor may throw, reuse the body block as the
1989 // partial-dtor block so that an exception thrown by an element's dtor
1990 // continues the reverse-destruction loop in the EH cleanup region. The
1991 // body block already stores the next element pointer to `tmpAddr`
1992 // before invoking the dtor, so when an exception unwinds from the
1993 // dtor call `tmpAddr` already points at the element that threw, and
1994 // the cleanup loop picks up from `tmpAddr - 1` and walks back to
1995 // `begin`.
1996 if (arrayDtor.getDtorMayThrow())
1997 partialDtorBlock = bodyBlock;
1998 }
1999
2000 auto emitCtorDtorLoop = [&]() {
2001 builder.createDoWhile(
2002 loc,
2003 /*condBuilder=*/
2004 [&](mlir::OpBuilder &b, mlir::Location loc) {
2005 auto currentElement = cir::LoadOp::create(b, loc, eltTy, tmpAddr);
2006 auto cmp = cir::CmpOp::create(builder, loc, cir::CmpOpKind::ne,
2007 currentElement, stop);
2008 builder.createCondition(cmp);
2009 },
2010 /*bodyBuilder=*/
2011 [&](mlir::OpBuilder &b, mlir::Location loc) {
2012 auto currentElement = cir::LoadOp::create(b, loc, eltTy, tmpAddr);
2013 if (isCtor) {
2014 cloneRegionBodyInto(bodyBlock, currentElement);
2015 mlir::Value stride = builder.getUnsignedInt(loc, 1, sizeTypeSize);
2016 auto nextElement = cir::PtrStrideOp::create(builder, loc, eltTy,
2017 currentElement, stride);
2018 builder.createStore(loc, nextElement, tmpAddr);
2019 } else {
2020 mlir::Value stride = builder.getSignedInt(loc, -1, sizeTypeSize);
2021 auto prevElement = cir::PtrStrideOp::create(builder, loc, eltTy,
2022 currentElement, stride);
2023 builder.createStore(loc, prevElement, tmpAddr);
2024 cloneRegionBodyInto(bodyBlock, prevElement);
2025 }
2026
2027 cir::YieldOp::create(b, loc);
2028 });
2029 };
2030
2031 if (partialDtorBlock) {
2032 cir::CleanupScopeOp::create(
2033 builder, loc, cir::CleanupKind::EH,
2034 /*bodyBuilder=*/
2035 [&](mlir::OpBuilder &b, mlir::Location loc) {
2036 emitCtorDtorLoop();
2037 cir::YieldOp::create(b, loc);
2038 },
2039 /*cleanupBuilder=*/
2040 [&](mlir::OpBuilder &b, mlir::Location loc) {
2041 auto cur = cir::LoadOp::create(b, loc, eltTy, tmpAddr);
2042 auto cmp =
2043 cir::CmpOp::create(builder, loc, cir::CmpOpKind::ne, cur, begin);
2044 cir::IfOp::create(
2045 builder, loc, cmp, /*withElseRegion=*/false,
2046 [&](mlir::OpBuilder &b, mlir::Location loc) {
2047 builder.createDoWhile(
2048 loc,
2049 /*condBuilder=*/
2050 [&](mlir::OpBuilder &b, mlir::Location loc) {
2051 auto el = cir::LoadOp::create(b, loc, eltTy, tmpAddr);
2052 auto neq = cir::CmpOp::create(
2053 builder, loc, cir::CmpOpKind::ne, el, begin);
2054 builder.createCondition(neq);
2055 },
2056 /*bodyBuilder=*/
2057 [&](mlir::OpBuilder &b, mlir::Location loc) {
2058 auto el = cir::LoadOp::create(b, loc, eltTy, tmpAddr);
2059 mlir::Value negOne =
2060 builder.getSignedInt(loc, -1, sizeTypeSize);
2061 auto prev = cir::PtrStrideOp::create(builder, loc, eltTy,
2062 el, negOne);
2063 builder.createStore(loc, prev, tmpAddr);
2064 cloneRegionBodyInto(partialDtorBlock, prev);
2065 builder.createYield(loc);
2066 });
2067 cir::YieldOp::create(builder, loc);
2068 });
2069 cir::YieldOp::create(b, loc);
2070 });
2071 } else {
2072 emitCtorDtorLoop();
2073 }
2074
2075 if (ifOp)
2076 cir::YieldOp::create(builder, loc);
2077
2078 op->erase();
2079}
2080
2081void LoweringPreparePass::lowerArrayDtor(cir::ArrayDtor op) {
2082 CIRBaseBuilderTy builder(getContext());
2083 builder.setInsertionPointAfter(op.getOperation());
2084
2085 mlir::Type eltTy = op->getRegion(0).getArgument(0).getType();
2086
2087 if (op.getNumElements()) {
2088 lowerArrayDtorCtorIntoLoop(builder, astCtx, op, eltTy, op.getAddr(),
2089 op.getNumElements(), /*arrayLen=*/0,
2090 /*isCtor=*/false);
2091 return;
2092 }
2093
2094 auto arrayLen =
2095 mlir::cast<cir::ArrayType>(op.getAddr().getType().getPointee()).getSize();
2096 lowerArrayDtorCtorIntoLoop(builder, astCtx, op, eltTy, op.getAddr(),
2097 /*numElements=*/nullptr, arrayLen,
2098 /*isCtor=*/false);
2099}
2100
2101void LoweringPreparePass::lowerArrayCtor(cir::ArrayCtor op) {
2102 cir::CIRBaseBuilderTy builder(getContext());
2103 builder.setInsertionPointAfter(op.getOperation());
2104
2105 mlir::Type eltTy = op->getRegion(0).getArgument(0).getType();
2106
2107 if (op.getNumElements()) {
2108 lowerArrayDtorCtorIntoLoop(builder, astCtx, op, eltTy, op.getAddr(),
2109 op.getNumElements(), /*arrayLen=*/0,
2110 /*isCtor=*/true);
2111 return;
2112 }
2113
2114 auto arrayLen =
2115 mlir::cast<cir::ArrayType>(op.getAddr().getType().getPointee()).getSize();
2116 lowerArrayDtorCtorIntoLoop(builder, astCtx, op, eltTy, op.getAddr(),
2117 /*numElements=*/nullptr, arrayLen,
2118 /*isCtor=*/true);
2119}
2120
2121cir::FuncOp LoweringPreparePass::getCalledFunction(cir::CallOp callOp) {
2122 mlir::SymbolRefAttr sym = llvm::dyn_cast_if_present<mlir::SymbolRefAttr>(
2123 callOp.getCallableForCallee());
2124 if (!sym)
2125 return nullptr;
2126 return symbolTables.lookupNearestSymbolFrom<cir::FuncOp>(callOp, sym);
2127}
2128
2129void LoweringPreparePass::lowerTrivialCopyCall(cir::CallOp op) {
2130 cir::FuncOp funcOp = getCalledFunction(op);
2131 if (!funcOp)
2132 return;
2133
2134 std::optional<cir::CtorKind> ctorKind = funcOp.getCxxConstructorKind();
2135 if (ctorKind && *ctorKind == cir::CtorKind::Copy &&
2136 funcOp.isCxxTrivialMemberFunction()) {
2137 // Replace the trivial copy constructor call with a `CopyOp`
2138 CIRBaseBuilderTy builder(getContext());
2139 mlir::ValueRange operands = op.getOperands();
2140 mlir::Value dest = operands[0];
2141 mlir::Value src = operands[1];
2142 builder.setInsertionPoint(op);
2143 builder.createCopy(dest, src);
2144 op.erase();
2145 }
2146}
2147
2148cir::GlobalOp LoweringPreparePass::getOrCreateConstAggregateGlobal(
2149 CIRBaseBuilderTy &builder, mlir::Location loc, llvm::StringRef baseName,
2150 mlir::Type ty, mlir::TypedAttr constant) {
2151 // Look up (and lazily populate) the per-base-name cache.
2152 llvm::SmallVector<cir::GlobalOp, 1> &versions =
2153 constAggregateGlobals[baseName];
2154
2155 // First, check globals we've already discovered for this base name.
2156 for (cir::GlobalOp gv : versions) {
2157 if (gv.getSymType() == ty && gv.getInitialValue() == constant)
2158 return gv;
2159 }
2160
2161 // No cached match. Scan the module's symbol table starting from the next
2162 // unscanned version. In practice this should usually exit on the first
2163 // iteration, but it's possible that some other pass or a previous
2164 // invocation of this pass created globals using this same logic.
2165 llvm::SmallString<128> name(baseName);
2166 size_t baseLen = name.size();
2167 unsigned version = versions.size();
2168 while (true) {
2169 name.resize(baseLen);
2170 if (version != 0) {
2171 name.push_back('.');
2172 llvm::Twine(version).toVector(name);
2173 }
2174 auto existingGv = symbolTables.lookupSymbolIn<cir::GlobalOp>(
2175 mlirModule, mlir::StringAttr::get(&getContext(), name));
2176 if (!existingGv)
2177 break;
2178 versions.push_back(existingGv);
2179 if (existingGv.getSymType() == ty &&
2180 existingGv.getInitialValue() == constant)
2181 return existingGv;
2182 ++version;
2183 }
2184
2185 // No match found, create a new global. The loop above found an unused name.
2186 mlir::OpBuilder::InsertionGuard guard(builder);
2187 builder.setInsertionPointToStart(mlirModule.getBody());
2188 auto gv =
2189 cir::GlobalOp::create(builder, loc, name, ty,
2190 /*isConstant=*/true,
2191 cir::LangAddressSpaceAttr::get(
2192 &getContext(), cir::LangAddressSpace::Default),
2193 cir::GlobalLinkageKind::PrivateLinkage);
2194 mlir::SymbolTable::setSymbolVisibility(
2195 gv, mlir::SymbolTable::Visibility::Private);
2196 gv.setInitialValueAttr(constant);
2197
2198 // Keep the cached symbol table in sync with the new global so subsequent
2199 // lookups for other base names find it.
2200 symbolTables.getSymbolTable(mlirModule).insert(gv);
2201
2202 versions.push_back(gv);
2203 return gv;
2204}
2205
2206void LoweringPreparePass::lowerStoreOfConstAggregate(cir::StoreOp op) {
2207 // Check if the value operand is a cir.const with aggregate type.
2208 auto constOp = op.getValue().getDefiningOp<cir::ConstantOp>();
2209 if (!constOp)
2210 return;
2211
2212 mlir::Type ty = constOp.getType();
2213 if (!mlir::isa<cir::ArrayType, cir::RecordType>(ty))
2214 return;
2215
2216 // Only transform stores to local variables (backed by cir.alloca).
2217 // Stores to other addresses (e.g. base_class_addr) should not be
2218 // transformed as they may be partial initializations.
2219 auto alloca = op.getAddr().getDefiningOp<cir::AllocaOp>();
2220 if (!alloca)
2221 return;
2222
2223 mlir::TypedAttr constant = constOp.getValue();
2224
2225 // OG implements several optimization tiers for constant aggregate
2226 // initialization. For now we always create a global constant + memcpy
2227 // (shouldCreateMemCpyFromGlobal). Future work can add the intermediate
2228 // tiers.
2232
2233 // Get function name from parent cir.func.
2234 auto func = op->getParentOfType<cir::FuncOp>();
2235 if (!func)
2236 return;
2237 llvm::StringRef funcName = func.getSymName();
2238
2239 // Get variable name from the alloca.
2240 llvm::StringRef varName = alloca.getName();
2241
2242 // Build base name: __const.<func>.<var>
2243 std::string baseName = ("__const." + funcName + "." + varName).str();
2244 CIRBaseBuilderTy builder(getContext());
2245
2246 // Check for existing globals and create a new global with a unique name
2247 // if no match is found.
2248 cir::GlobalOp gv = getOrCreateConstAggregateGlobal(builder, op.getLoc(),
2249 baseName, ty, constant);
2250
2251 // Now replace the store with get_global + copy.
2252 builder.setInsertionPoint(op);
2253
2254 auto ptrTy = cir::PointerType::get(ty);
2255 mlir::Value globalPtr =
2256 cir::GetGlobalOp::create(builder, op.getLoc(), ptrTy, gv.getSymName());
2257
2258 // Replace store with copy.
2259 builder.createCopy(op.getAddr(), globalPtr);
2260
2261 // Erase the original store.
2262 op.erase();
2263
2264 // Erase the cir.const if it has no remaining users.
2265 if (constOp.use_empty())
2266 constOp.erase();
2267}
2268
2269// Every raised operation carries the original callee, the operands, and the
2270// attributes of the call, so this one function lowers any of them back to an
2271// equivalent plain call.
2272void LoweringPreparePass::lowerStdOp(cir::StdOpInterface typedOp) {
2273 mlir::Operation *op = typedOp.getOperation();
2274 cir::CIRBaseBuilderTy builder(getContext());
2275 builder.setInsertionPointAfter(op);
2276 mlir::Type resultType;
2277 if (op->getNumResults())
2278 resultType = op->getResult(0).getType();
2279 cir::CallOp call = builder.createCallOp(
2280 op->getLoc(), typedOp.getOriginalFnAttr(), resultType, op->getOperands());
2281 for (mlir::NamedAttribute attr : op->getAttrs())
2282 if (attr.getName() != typedOp.getOriginalFnAttrName())
2283 call->setAttr(attr.getName(), attr.getValue());
2284
2285 op->replaceAllUsesWith(call);
2286 op->erase();
2287}
2288
2289void LoweringPreparePass::runOnOp(mlir::Operation *op) {
2290 if (auto arrayCtor = dyn_cast<cir::ArrayCtor>(op)) {
2291 lowerArrayCtor(arrayCtor);
2292 } else if (auto arrayDtor = dyn_cast<cir::ArrayDtor>(op)) {
2293 lowerArrayDtor(arrayDtor);
2294 } else if (auto stdOp = mlir::dyn_cast<cir::StdOpInterface>(op)) {
2295 lowerStdOp(stdOp);
2296 } else if (auto cast = mlir::dyn_cast<cir::CastOp>(op)) {
2297 lowerCastOp(cast);
2298 } else if (auto complexConj = mlir::dyn_cast<cir::ComplexConjOp>(op)) {
2299 lowerComplexConjOp(complexConj);
2300 } else if (auto complexDiv = mlir::dyn_cast<cir::ComplexDivOp>(op)) {
2301 lowerComplexDivOp(complexDiv);
2302 } else if (auto complexMul = mlir::dyn_cast<cir::ComplexMulOp>(op)) {
2303 lowerComplexMulOp(complexMul);
2304 } else if (auto glob = mlir::dyn_cast<cir::GlobalOp>(op)) {
2305 lowerGlobalOp(glob);
2306 if (auto regAttr = glob->getAttrOfType<CUDAVarRegistrationInfoAttr>(
2307 CUDAVarRegistrationInfoAttr::getMnemonic()))
2308 cudaDeviceVars.emplace_back(glob, regAttr);
2309 } else if (auto getGlob = mlir::dyn_cast<cir::GetGlobalOp>(op)) {
2310 lowerGetGlobalOp(getGlob);
2311 } else if (auto callOp = dyn_cast<cir::CallOp>(op)) {
2312 lowerTrivialCopyCall(callOp);
2313 } else if (auto storeOp = dyn_cast<cir::StoreOp>(op)) {
2314 lowerStoreOfConstAggregate(storeOp);
2315 } else if (auto fnOp = dyn_cast<cir::FuncOp>(op)) {
2316 if (auto globalCtor = fnOp.getGlobalCtorPriority())
2317 globalCtorList.emplace_back(fnOp.getName(), globalCtor.value());
2318 else if (auto globalDtor = fnOp.getGlobalDtorPriority())
2319 globalDtorList.emplace_back(fnOp.getName(), globalDtor.value());
2320
2321 if (mlir::Attribute attr =
2322 fnOp->getAttr(cir::CUDAKernelNameAttr::getMnemonic())) {
2323 auto kernelNameAttr = dyn_cast<CUDAKernelNameAttr>(attr);
2324 llvm::StringRef kernelName = kernelNameAttr.getKernelName();
2325 cudaKernelMap[kernelName] = fnOp;
2326 }
2327 } else if (auto threeWayCmp = dyn_cast<cir::CmpThreeWayOp>(op)) {
2328 lowerThreeWayCmpOp(threeWayCmp);
2329 } else if (auto initOp = dyn_cast<cir::LocalInitOp>(op)) {
2330 lowerLocalInitOp(initOp);
2331 }
2332}
2333
2334static llvm::StringRef getCUDAPrefix(clang::ASTContext *astCtx) {
2335 if (astCtx->getLangOpts().HIP)
2336 return "hip";
2337 return "cuda";
2338}
2339
2340static std::string addUnderscoredPrefix(llvm::StringRef prefix,
2341 llvm::StringRef name) {
2342 return ("__" + prefix + name).str();
2343}
2344
2345/// Creates a global constructor function for the module:
2346///
2347/// For CUDA:
2348/// \code
2349/// void __cuda_module_ctor() {
2350/// Handle = __cudaRegisterFatBinary(GpuBinaryBlob);
2351/// __cuda_register_globals(Handle);
2352/// }
2353/// \endcode
2354///
2355/// For HIP:
2356/// \code
2357/// void __hip_module_ctor() {
2358/// if (__hip_gpubin_handle == 0) {
2359/// __hip_gpubin_handle = __hipRegisterFatBinary(GpuBinaryBlob);
2360/// __hip_register_globals(__hip_gpubin_handle);
2361/// }
2362/// }
2363/// \endcode
2364void LoweringPreparePass::buildCUDAModuleCtor() {
2365 bool isHIP = astCtx->getLangOpts().HIP;
2366
2367 if (astCtx->getLangOpts().GPURelocatableDeviceCode)
2368 llvm_unreachable("GPU RDC NYI");
2369
2370 // For CUDA without -fgpu-rdc, it's safe to stop generating ctor
2371 // if there's nothing to register.
2372 if (cudaKernelMap.empty() && cudaDeviceVars.empty())
2373 return;
2374
2375 // There's no device-side binary, so no need to proceed for CUDA.
2376 // HIP has to create an external symbol in this case, which is NYI.
2377 mlir::Attribute cudaBinaryHandleAttr =
2378 mlirModule->getAttr(CIRDialect::getCUDABinaryHandleAttrName());
2379 if (!cudaBinaryHandleAttr) {
2380 if (isHIP)
2382 return;
2383 }
2384
2385 llvm::StringRef cudaGPUBinaryName =
2386 mlir::cast<CUDABinaryHandleAttr>(cudaBinaryHandleAttr)
2387 .getName()
2388 .getValue();
2389
2390 llvm::vfs::FileSystem &vfs =
2392 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> gpuBinaryOrErr =
2393 vfs.getBufferForFile(cudaGPUBinaryName);
2394 if (std::error_code ec = gpuBinaryOrErr.getError()) {
2395 mlirModule->emitError("cannot open GPU binary file: " + cudaGPUBinaryName +
2396 ": " + ec.message());
2397 return;
2398 }
2399 std::unique_ptr<llvm::MemoryBuffer> gpuBinary =
2400 std::move(gpuBinaryOrErr.get());
2401
2402 // Set up common types and builder.
2403 llvm::StringRef cudaPrefix = getCUDAPrefix(astCtx);
2404 mlir::Location loc = mlirModule->getLoc();
2405 CIRBaseBuilderTy builder(getContext());
2406 builder.setInsertionPointToStart(mlirModule.getBody());
2407
2408 Type voidTy = builder.getVoidTy();
2409 PointerType voidPtrTy = builder.getVoidPtrTy();
2410 PointerType voidPtrPtrTy = builder.getPointerTo(voidPtrTy);
2411 IntType intTy = builder.getSIntNTy(32);
2412 IntType charTy = cir::IntType::get(&getContext(), astCtx->getCharWidth(),
2413 /*isSigned=*/false);
2414
2415 // --- Create fatbin globals ---
2416
2417 // The section names are different for MAC OS X.
2418 llvm::StringRef fatbinConstName =
2419 astCtx->getLangOpts().HIP ? ".hip_fatbin" : ".nv_fatbin";
2420
2421 llvm::StringRef fatbinSectionName =
2422 astCtx->getLangOpts().HIP ? ".hipFatBinSegment" : ".nvFatBinSegment";
2423
2424 // Create the fatbin string constant with GPU binary contents.
2425 auto fatbinType =
2426 ArrayType::get(&getContext(), charTy, gpuBinary->getBuffer().size());
2427 std::string fatbinStrName = addUnderscoredPrefix(cudaPrefix, "_fatbin_str");
2428 GlobalOp fatbinStr = GlobalOp::create(builder, loc, fatbinStrName, fatbinType,
2429 /*isConstant=*/true, {},
2430 GlobalLinkageKind::PrivateLinkage);
2431 if (isHIP) {
2432 const unsigned HIPCodeObjectAlign = 4096;
2433 fatbinStr.setAlignment(HIPCodeObjectAlign);
2434 } else {
2435 fatbinStr.setAlignment(8);
2436 }
2437
2438 fatbinStr.setInitialValueAttr(cir::ConstArrayAttr::get(
2439 fatbinType, StringAttr::get(gpuBinary->getBuffer(), fatbinType)));
2440 fatbinStr.setSection(fatbinConstName);
2441 fatbinStr.setPrivate();
2442
2443 // Create the fatbin wrapper struct:
2444 // struct { int magic; int version; void *fatbin; void *unused; };
2445 mlir::Type fatbinWrapperMembers[] = {intTy, intTy, voidPtrTy, voidPtrTy};
2446 auto fatbinWrapperType = cir::StructType::get(
2447 &getContext(), fatbinWrapperMembers, /*packed=*/false, /*is_class=*/false,
2448 cir::RecordType::getAllDataKinds(fatbinWrapperMembers));
2449 std::string fatbinWrapperName =
2450 addUnderscoredPrefix(cudaPrefix, "_fatbin_wrapper");
2451 GlobalOp fatbinWrapper = GlobalOp::create(
2452 builder, loc, fatbinWrapperName, fatbinWrapperType,
2453 /*isConstant=*/true, {}, GlobalLinkageKind::PrivateLinkage);
2454 fatbinWrapper.setSection(fatbinSectionName);
2455
2456 constexpr unsigned cudaFatMagic = 0x466243b1;
2457 constexpr unsigned hipFatMagic = 0x48495046;
2458 unsigned fatMagic = isHIP ? hipFatMagic : cudaFatMagic;
2459
2460 auto magicInit = IntAttr::get(intTy, fatMagic);
2461 auto versionInit = IntAttr::get(intTy, 1);
2462 auto fatbinStrSymbol =
2463 mlir::FlatSymbolRefAttr::get(fatbinStr.getSymNameAttr());
2464 auto fatbinInit = GlobalViewAttr::get(voidPtrTy, fatbinStrSymbol);
2465 mlir::TypedAttr unusedInit = builder.getConstNullPtrAttr(voidPtrTy);
2466 fatbinWrapper.setInitialValueAttr(cir::ConstRecordAttr::get(
2467 fatbinWrapperType,
2468 mlir::ArrayAttr::get(&getContext(),
2469 {magicInit, versionInit, fatbinInit, unusedInit})));
2470
2471 // Create the GPU binary handle global variable.
2472 std::string gpubinHandleName =
2473 addUnderscoredPrefix(cudaPrefix, "_gpubin_handle");
2474
2475 GlobalOp gpuBinHandle = GlobalOp::create(
2476 builder, loc, gpubinHandleName, voidPtrPtrTy,
2477 /*isConstant=*/false, {}, cir::GlobalLinkageKind::InternalLinkage);
2478 gpuBinHandle.setInitialValueAttr(builder.getConstNullPtrAttr(voidPtrPtrTy));
2479 gpuBinHandle.setPrivate();
2480
2481 // Declare this function:
2482 // void **__{cuda|hip}RegisterFatBinary(void *);
2483
2484 std::string regFuncName =
2485 addUnderscoredPrefix(cudaPrefix, "RegisterFatBinary");
2486 FuncType regFuncType = FuncType::get({voidPtrTy}, voidPtrPtrTy);
2487 cir::FuncOp regFunc =
2488 buildRuntimeFunction(builder, regFuncName, loc, regFuncType);
2489
2490 std::string moduleCtorName = addUnderscoredPrefix(cudaPrefix, "_module_ctor");
2491 cir::FuncOp moduleCtor = buildRuntimeFunction(
2492 builder, moduleCtorName, loc, FuncType::get({}, voidTy),
2493 GlobalLinkageKind::InternalLinkage);
2494
2495 globalCtorList.emplace_back(moduleCtorName,
2496 cir::GlobalCtorAttr::getDefaultPriority());
2497 builder.setInsertionPointToStart(moduleCtor.addEntryBlock());
2499 if (isHIP) {
2500 // --- Create HIP CTOR ---
2501 // if (__hip_gpubin_handle == nullptr)
2502 // __hip_gpubin_handle = __hipRegisterFatBinary(&fatbinWrapper);
2503 // __hip_register_globals(__hip_gpubin_handle);
2504 // atexit(__hip_module_dtor);
2505 mlir::Block *entryBlock = builder.getInsertionBlock();
2506 mlir::Region *parent = entryBlock->getParent();
2507 mlir::Block *ifBlock = builder.createBlock(parent);
2508 mlir::Block *exitBlock = builder.createBlock(parent);
2509 {
2510 mlir::OpBuilder::InsertionGuard guard(builder);
2511 builder.setInsertionPointToEnd(entryBlock);
2512 mlir::Value handle =
2513 builder.createLoad(loc, builder.createGetGlobal(gpuBinHandle));
2514 auto handlePtrTy = mlir::cast<cir::PointerType>(handle.getType());
2515 mlir::Value nullPtr = builder.getNullPtr(handlePtrTy, loc);
2516 mlir::Value isNull =
2517 builder.createCompare(loc, cir::CmpOpKind::eq, handle, nullPtr);
2518 cir::BrCondOp::create(builder, loc, isNull, ifBlock, exitBlock);
2519 }
2520 {
2521 // Handle is null: load the fatbin and register it.
2522 mlir::OpBuilder::InsertionGuard guard(builder);
2523 builder.setInsertionPointToStart(ifBlock);
2524 mlir::Value wrapper = builder.createGetGlobal(fatbinWrapper);
2525 mlir::Value fatbinVoidPtr = builder.createBitcast(wrapper, voidPtrTy);
2526 cir::CallOp gpuBinaryHandleCall =
2527 builder.createCallOp(loc, regFunc, fatbinVoidPtr);
2528 mlir::Value gpuBinaryHandle = gpuBinaryHandleCall.getResult();
2529 // Store the value back to the global `__hip_gpubin_handle`.
2530 mlir::Value gpuBinaryHandleGlobal = builder.createGetGlobal(gpuBinHandle);
2531 builder.createStore(loc, gpuBinaryHandle, gpuBinaryHandleGlobal);
2532 cir::BrOp::create(builder, loc, exitBlock);
2533 }
2534 {
2535 // Exit block: load the (possibly newly-registered) handle, call
2536 // __hip_register_globals, and register the module dtor with atexit().
2537 mlir::OpBuilder::InsertionGuard guard(builder);
2538 builder.setInsertionPointToStart(exitBlock);
2539 mlir::Value gHandle =
2540 builder.createLoad(loc, builder.createGetGlobal(gpuBinHandle));
2541
2542 if (std::optional<FuncOp> regGlobal = buildCUDARegisterGlobals())
2543 builder.createCallOp(loc, *regGlobal, gHandle);
2544
2545 if (std::optional<FuncOp> dtor = buildHIPModuleDtor()) {
2546 cir::CIRBaseBuilderTy globalBuilder(getContext());
2547 globalBuilder.setInsertionPointToStart(mlirModule.getBody());
2548 FuncOp atexit = buildRuntimeFunction(
2549 globalBuilder, "atexit", loc,
2550 FuncType::get(PointerType::get(dtor->getFunctionType()), intTy));
2551 mlir::Value dtorFunc = GetGlobalOp::create(
2552 builder, loc, PointerType::get(dtor->getFunctionType()),
2553 mlir::FlatSymbolRefAttr::get(dtor->getSymNameAttr()));
2554 builder.createCallOp(loc, atexit, dtorFunc);
2555 }
2556 cir::ReturnOp::create(builder, loc);
2557 }
2558 return;
2559 }
2560 if (!astCtx->getLangOpts().GPURelocatableDeviceCode) {
2561
2562 // --- Create CUDA CTOR-DTOR ---
2563 // Register binary with CUDA runtime. This is substantially different in
2564 // default mode vs. separate compilation.
2565 // Corresponding code:
2566 // gpuBinaryHandle = __cudaRegisterFatBinary(&fatbinWrapper);
2567 mlir::Value wrapper = builder.createGetGlobal(fatbinWrapper);
2568 mlir::Value fatbinVoidPtr = builder.createBitcast(wrapper, voidPtrTy);
2569 cir::CallOp gpuBinaryHandleCall =
2570 builder.createCallOp(loc, regFunc, fatbinVoidPtr);
2571 mlir::Value gpuBinaryHandle = gpuBinaryHandleCall.getResult();
2572 // Store the value back to the global `__cuda_gpubin_handle`.
2573 mlir::Value gpuBinaryHandleGlobal = builder.createGetGlobal(gpuBinHandle);
2574 builder.createStore(loc, gpuBinaryHandle, gpuBinaryHandleGlobal);
2575
2576 // --- Generate __cuda_register_globals and call it ---
2577 if (std::optional<FuncOp> regGlobal = buildCUDARegisterGlobals()) {
2578 builder.createCallOp(loc, *regGlobal, gpuBinaryHandle);
2579 }
2580
2581 // From CUDA 10.1 onwards, we must call this function to end registration:
2582 // void __cudaRegisterFatBinaryEnd(void **fatbinHandle);
2583 // This is CUDA-specific, so no need to use `addUnderscoredPrefix`.
2585 astCtx->getTargetInfo().getSDKVersion(),
2587 cir::CIRBaseBuilderTy globalBuilder(getContext());
2588 globalBuilder.setInsertionPointToStart(mlirModule.getBody());
2589 FuncOp endFunc =
2590 buildRuntimeFunction(globalBuilder, "__cudaRegisterFatBinaryEnd", loc,
2591 FuncType::get({voidPtrPtrTy}, voidTy));
2592 builder.createCallOp(loc, endFunc, gpuBinaryHandle);
2593 }
2594 } else
2595 llvm_unreachable("GPU RDC NYI");
2596
2597 // Create destructor and register it with atexit() the way NVCC does it. Doing
2598 // it during regular destructor phase worked in CUDA before 9.2 but results in
2599 // double-free in 9.2.
2600 if (std::optional<FuncOp> dtor = buildCUDAModuleDtor()) {
2601
2602 // extern "C" int atexit(void (*f)(void));
2603 cir::CIRBaseBuilderTy globalBuilder(getContext());
2604 globalBuilder.setInsertionPointToStart(mlirModule.getBody());
2605 FuncOp atexit = buildRuntimeFunction(
2606 globalBuilder, "atexit", loc,
2607 FuncType::get(PointerType::get(dtor->getFunctionType()), intTy));
2608 mlir::Value dtorFunc = GetGlobalOp::create(
2609 builder, loc, PointerType::get(dtor->getFunctionType()),
2610 mlir::FlatSymbolRefAttr::get(dtor->getSymNameAttr()));
2611 builder.createCallOp(loc, atexit, dtorFunc);
2612 }
2613 cir::ReturnOp::create(builder, loc);
2614}
2615
2616std::optional<FuncOp> LoweringPreparePass::buildCUDAModuleDtor() {
2617 if (!mlirModule->getAttr(CIRDialect::getCUDABinaryHandleAttrName()))
2618 return {};
2619
2620 llvm::StringRef prefix = getCUDAPrefix(astCtx);
2621
2622 VoidType voidTy = VoidType::get(&getContext());
2623 PointerType voidPtrPtrTy = PointerType::get(PointerType::get(voidTy));
2624
2625 mlir::Location loc = mlirModule.getLoc();
2626
2627 cir::CIRBaseBuilderTy builder(getContext());
2628 builder.setInsertionPointToStart(mlirModule.getBody());
2629
2630 // define: void __cudaUnregisterFatBinary(void ** handle);
2631 std::string unregisterFuncName =
2632 addUnderscoredPrefix(prefix, "UnregisterFatBinary");
2633 FuncOp unregisterFunc = buildRuntimeFunction(
2634 builder, unregisterFuncName, loc, FuncType::get({voidPtrPtrTy}, voidTy));
2635
2636 // void __cuda_module_dtor();
2637 // Despite the name, OG doesn't treat it as a destructor, so it shouldn't be
2638 // put into globalDtorList. If it were a real dtor, then it would cause
2639 // double free above CUDA 9.2. The way to use it is to manually call
2640 // atexit() at end of module ctor.
2641 std::string dtorName = addUnderscoredPrefix(prefix, "_module_dtor");
2642 FuncOp dtor =
2643 buildRuntimeFunction(builder, dtorName, loc, FuncType::get({}, voidTy),
2644 GlobalLinkageKind::InternalLinkage);
2645
2646 builder.setInsertionPointToStart(dtor.addEntryBlock());
2647
2648 // For dtor, we only need to call:
2649 // __cudaUnregisterFatBinary(__cuda_gpubin_handle);
2650
2651 std::string gpubinName = addUnderscoredPrefix(prefix, "_gpubin_handle");
2652 GlobalOp gpubinGlobal = cast<GlobalOp>(mlirModule.lookupSymbol(gpubinName));
2653 mlir::Value gpubinAddress = builder.createGetGlobal(gpubinGlobal);
2654 mlir::Value gpubin = builder.createLoad(loc, gpubinAddress);
2655 builder.createCallOp(loc, unregisterFunc, gpubin);
2656 ReturnOp::create(builder, loc);
2657
2658 return dtor;
2659}
2660
2661/// Build the HIP module dtor:
2662///
2663/// void __hip_module_dtor() {
2664/// if (__hip_gpubin_handle != nullptr) {
2665/// __hipUnregisterFatBinary(__hip_gpubin_handle);
2666/// __hip_gpubin_handle = nullptr;
2667/// }
2668/// }
2669///
2670/// Despite the name, OG doesn't treat this as a real destructor: putting it on
2671/// the dtor list would cause a double-free. It is meant to be registered via
2672/// atexit() at the end of the module ctor.
2673std::optional<FuncOp> LoweringPreparePass::buildHIPModuleDtor() {
2674 if (!mlirModule->getAttr(CIRDialect::getCUDABinaryHandleAttrName()))
2675 return {};
2676
2677 llvm::StringRef prefix = getCUDAPrefix(astCtx);
2678
2679 VoidType voidTy = VoidType::get(&getContext());
2680 PointerType voidPtrPtrTy = PointerType::get(PointerType::get(voidTy));
2681
2682 mlir::Location loc = mlirModule.getLoc();
2683
2684 cir::CIRBaseBuilderTy builder(getContext());
2685 builder.setInsertionPointToStart(mlirModule.getBody());
2686
2687 // void __hipUnregisterFatBinary(void ** handle);
2688 std::string unregisterFuncName =
2689 addUnderscoredPrefix(prefix, "UnregisterFatBinary");
2690 FuncOp unregisterFunc = buildRuntimeFunction(
2691 builder, unregisterFuncName, loc, FuncType::get({voidPtrPtrTy}, voidTy));
2692
2693 std::string dtorName = addUnderscoredPrefix(prefix, "_module_dtor");
2694 FuncOp dtor =
2695 buildRuntimeFunction(builder, dtorName, loc, FuncType::get({}, voidTy),
2696 GlobalLinkageKind::InternalLinkage);
2697
2698 std::string gpubinName = addUnderscoredPrefix(prefix, "_gpubin_handle");
2699 GlobalOp gpuBinGlobal = cast<GlobalOp>(mlirModule.lookupSymbol(gpubinName));
2700
2701 mlir::Block *entryBlock = dtor.addEntryBlock();
2702 mlir::Block *ifBlock = builder.createBlock(&dtor.getBody());
2703 mlir::Block *exitBlock = builder.createBlock(&dtor.getBody());
2704
2705 mlir::OpBuilder::InsertionGuard guard(builder);
2706 builder.setInsertionPointToEnd(entryBlock);
2707 mlir::Value handle =
2708 builder.createLoad(loc, builder.createGetGlobal(gpuBinGlobal));
2709 auto handlePtrTy = mlir::cast<cir::PointerType>(handle.getType());
2710 mlir::Value nullPtr = builder.getNullPtr(handlePtrTy, loc);
2711 mlir::Value isNotNull =
2712 builder.createCompare(loc, cir::CmpOpKind::ne, handle, nullPtr);
2713 cir::BrCondOp::create(builder, loc, isNotNull, ifBlock, exitBlock);
2714
2715 {
2716 // Handle is non-null: unregister and clear it.
2717 mlir::OpBuilder::InsertionGuard ifGuard(builder);
2718 builder.setInsertionPointToStart(ifBlock);
2719 builder.createCallOp(loc, unregisterFunc, handle);
2720 builder.createStore(loc, nullPtr, builder.createGetGlobal(gpuBinGlobal));
2721 cir::BrOp::create(builder, loc, exitBlock);
2722 }
2723 {
2724 mlir::OpBuilder::InsertionGuard exitGuard(builder);
2725 builder.setInsertionPointToStart(exitBlock);
2726 cir::ReturnOp::create(builder, loc);
2727 }
2728
2729 return dtor;
2730}
2731
2732std::optional<FuncOp> LoweringPreparePass::buildCUDARegisterGlobals() {
2733 if (cudaKernelMap.empty() && cudaDeviceVars.empty())
2734 return {};
2735
2736 cir::CIRBaseBuilderTy builder(getContext());
2737 builder.setInsertionPointToStart(mlirModule.getBody());
2738
2739 mlir::Location loc = mlirModule.getLoc();
2740 llvm::StringRef cudaPrefix = getCUDAPrefix(astCtx);
2741
2742 auto voidTy = VoidType::get(&getContext());
2743 auto voidPtrTy = PointerType::get(voidTy);
2744 auto voidPtrPtrTy = PointerType::get(voidPtrTy);
2745
2746 // Create the function:
2747 // void __cuda_register_globals(void **fatbinHandle)
2748 std::string regGlobalFuncName =
2749 addUnderscoredPrefix(cudaPrefix, "_register_globals");
2750 auto regGlobalFuncTy = FuncType::get({voidPtrPtrTy}, voidTy);
2751 FuncOp regGlobalFunc =
2752 buildRuntimeFunction(builder, regGlobalFuncName, loc, regGlobalFuncTy,
2753 /*linkage=*/GlobalLinkageKind::InternalLinkage);
2754 builder.setInsertionPointToStart(regGlobalFunc.addEntryBlock());
2755
2756 buildCUDARegisterGlobalFunctions(builder, regGlobalFunc);
2757 buildCUDARegisterVars(builder, regGlobalFunc);
2758
2759 ReturnOp::create(builder, loc);
2760 return regGlobalFunc;
2761}
2762
2763void LoweringPreparePass::buildCUDARegisterGlobalFunctions(
2764 cir::CIRBaseBuilderTy &builder, FuncOp regGlobalFunc) {
2765 mlir::Location loc = mlirModule.getLoc();
2766 llvm::StringRef cudaPrefix = getCUDAPrefix(astCtx);
2767 cir::CIRDataLayout dataLayout(mlirModule);
2768
2769 auto voidTy = VoidType::get(&getContext());
2770 auto voidPtrTy = PointerType::get(voidTy);
2771 auto voidPtrPtrTy = PointerType::get(voidPtrTy);
2772 IntType intTy = builder.getSIntNTy(32);
2773 IntType charTy = cir::IntType::get(&getContext(), astCtx->getCharWidth(),
2774 /*isSigned=*/false);
2775
2776 // Extract the GPU binary handle argument.
2777 mlir::Value fatbinHandle = *regGlobalFunc.args_begin();
2778
2779 cir::CIRBaseBuilderTy globalBuilder(getContext());
2780 globalBuilder.setInsertionPointToStart(mlirModule.getBody());
2781
2782 // Declare CUDA internal functions:
2783 // int __cudaRegisterFunction(
2784 // void **fatbinHandle,
2785 // const char *hostFunc,
2786 // char *deviceFunc,
2787 // const char *deviceName,
2788 // int threadLimit,
2789 // uint3 *tid, uint3 *bid, dim3 *bDim, dim3 *gDim,
2790 // int *wsize
2791 // )
2792 // OG doesn't care about the types at all. They're treated as void*.
2793
2794 FuncOp cudaRegisterFunction = buildRuntimeFunction(
2795 globalBuilder, addUnderscoredPrefix(cudaPrefix, "RegisterFunction"), loc,
2796 FuncType::get({voidPtrPtrTy, voidPtrTy, voidPtrTy, voidPtrTy, intTy,
2797 voidPtrTy, voidPtrTy, voidPtrTy, voidPtrTy, voidPtrTy},
2798 intTy));
2799
2800 auto makeConstantString = [&](llvm::StringRef str) -> GlobalOp {
2801 auto strType = ArrayType::get(&getContext(), charTy, 1 + str.size());
2802 auto tmpString = cir::GlobalOp::create(
2803 globalBuilder, loc, (".str" + str).str(), strType,
2804 /*isConstant=*/true, {},
2805 /*linkage=*/cir::GlobalLinkageKind::PrivateLinkage);
2806
2807 // We must make the string zero-terminated.
2808 tmpString.setInitialValueAttr(
2809 ConstArrayAttr::get(strType, StringAttr::get(str + "\0", strType)));
2810 tmpString.setPrivate();
2811 return tmpString;
2812 };
2813
2814 cir::ConstantOp cirNullPtr = builder.getNullPtr(voidPtrTy, loc);
2815 bool isHIP = astCtx->getLangOpts().HIP;
2816 for (auto kernelName : cudaKernelMap.keys()) {
2817 FuncOp deviceStub = cudaKernelMap[kernelName];
2818 GlobalOp deviceFuncStr = makeConstantString(kernelName);
2819 mlir::Value deviceFunc = builder.createBitcast(
2820 builder.createGetGlobal(deviceFuncStr), voidPtrTy);
2821
2822 mlir::Value hostFunc;
2823 if (isHIP) {
2824 // Under HIP, the kernel-handle is a GlobalOp shadow created by CIR
2825 // codegen and named with the kernel-reference mangled name (e.g.
2826 // `@_Z2fnv` pointing at the device-stub function
2827 // `_Z17__device_stub__fnv`). The CUDAKernelNameAttr on the device-stub
2828 // uses the same name, so we can resolve the shadow by symbol lookup.
2829 auto funcHandle = cast<GlobalOp>(mlirModule.lookupSymbol(kernelName));
2830 hostFunc =
2831 builder.createBitcast(builder.createGetGlobal(funcHandle), voidPtrTy);
2832 } else {
2833 hostFunc = builder.createBitcast(
2834 GetGlobalOp::create(
2835 builder, loc, PointerType::get(deviceStub.getFunctionType()),
2836 mlir::FlatSymbolRefAttr::get(deviceStub.getSymNameAttr())),
2837 voidPtrTy);
2838 }
2839 builder.createCallOp(
2840 loc, cudaRegisterFunction,
2841 {fatbinHandle, hostFunc, deviceFunc, deviceFunc,
2842 ConstantOp::create(builder, loc, IntAttr::get(intTy, -1)), cirNullPtr,
2843 cirNullPtr, cirNullPtr, cirNullPtr, cirNullPtr});
2844 }
2845}
2846
2847// Emit `__{cuda|hip}RegisterVar` calls inside `__{cuda|hip}_register_globals`
2848// for every device-side shadow that carries a `cu.var_registration` attribute
2849// (attached by `CIRGenNVCUDARuntime::handleVarRegistration`).
2850void LoweringPreparePass::buildCUDARegisterVars(cir::CIRBaseBuilderTy &builder,
2851 FuncOp regGlobalFunc) {
2852 mlir::Location loc = mlirModule.getLoc();
2853 llvm::StringRef cudaPrefix = getCUDAPrefix(astCtx);
2854 cir::CIRDataLayout dataLayout(mlirModule);
2855
2856 PointerType voidPtrTy = builder.getVoidPtrTy();
2857 PointerType voidPtrPtrTy = builder.getPointerTo(voidPtrTy);
2858 IntType intTy = builder.getSIntNTy(32);
2859 IntType sizeTy =
2860 builder.getUIntNTy(astCtx->getTargetInfo().getMaxPointerWidth());
2861 IntType charTy = cir::IntType::get(&getContext(), astCtx->getCharWidth(),
2862 /*isSigned=*/false);
2863
2864 if (cudaDeviceVars.empty())
2865 return;
2866
2867 cir::CIRBaseBuilderTy globalBuilder(getContext());
2868 globalBuilder.setInsertionPointToStart(mlirModule.getBody());
2869
2870 // void __{cuda|hip}RegisterVar(void **fatbinHandle,
2871 // char *hostVar, char *deviceAddress,
2872 // const char *deviceName, int ext,
2873 // size_t size, int constant, int normalized);
2874 // OG ignores parameter types, treating pointers as void*.
2875 cir::VoidType voidTy = builder.getVoidTy();
2876 FuncOp cudaRegisterVar = buildRuntimeFunction(
2877 globalBuilder, addUnderscoredPrefix(cudaPrefix, "RegisterVar"), loc,
2878 FuncType::get({voidPtrPtrTy, voidPtrTy, voidPtrTy, voidPtrTy, intTy,
2879 sizeTy, intTy, intTy},
2880 voidTy));
2881
2882 auto makeConstantString = [&](llvm::StringRef str) -> GlobalOp {
2883 auto strType = ArrayType::get(&getContext(), charTy, 1 + str.size());
2884 auto tmpString = cir::GlobalOp::create(
2885 globalBuilder, loc, (".str" + str).str(), strType,
2886 /*isConstant=*/true, {},
2887 /*linkage=*/cir::GlobalLinkageKind::PrivateLinkage);
2888 tmpString.setInitialValueAttr(
2889 ConstArrayAttr::get(strType, StringAttr::get(str + "\0", strType)));
2890 tmpString.setPrivate();
2891 return tmpString;
2892 };
2893
2894 mlir::Value fatbinHandle = *regGlobalFunc.args_begin();
2895
2896 for (auto &[global, regAttr] : cudaDeviceVars) {
2897 switch (regAttr.getKind()) {
2898 case cir::CUDADeviceVarKind::Variable:
2899 break;
2900 case cir::CUDADeviceVarKind::Surface:
2901 llvm_unreachable("Surface registration NYI");
2902 case cir::CUDADeviceVarKind::Texture:
2903 llvm_unreachable("Texture registration NYI");
2904 }
2905
2906 if (regAttr.getIsManaged())
2907 llvm_unreachable("Managed variable registration NYI");
2908
2909 GlobalOp deviceNameStr = makeConstantString(regAttr.getDeviceSideName());
2910 mlir::Value deviceName = builder.createBitcast(
2911 builder.createGetGlobal(deviceNameStr), voidPtrTy);
2912 mlir::Value hostVar =
2913 builder.createBitcast(builder.createGetGlobal(global), voidPtrTy);
2914
2915 auto isExtern = ConstantOp::create(
2916 builder, loc, IntAttr::get(intTy, regAttr.getIsExtern() ? 1 : 0));
2917 llvm::TypeSize size = dataLayout.getTypeAllocSize(global.getSymType());
2918 auto varSize = ConstantOp::create(
2919 builder, loc, IntAttr::get(sizeTy, size.getFixedValue()));
2920 auto isConstant = ConstantOp::create(
2921 builder, loc, IntAttr::get(intTy, regAttr.getIsConstant() ? 1 : 0));
2922 auto normalized = ConstantOp::create(builder, loc, IntAttr::get(intTy, 0));
2923 builder.createCallOp(loc, cudaRegisterVar,
2924 {fatbinHandle, hostVar, deviceName, deviceName,
2925 isExtern, varSize, isConstant, normalized});
2926 }
2927}
2928
2929void LoweringPreparePass::runOnOperation() {
2930 mlir::Operation *op = getOperation();
2931 if (isa<::mlir::ModuleOp>(op))
2932 mlirModule = cast<::mlir::ModuleOp>(op);
2933
2934 llvm::SmallVector<mlir::Operation *> opsToTransform;
2935
2936 op->walk([&](mlir::Operation *op) {
2937 if (mlir::isa<cir::ArrayCtor, cir::ArrayDtor, cir::CastOp,
2938 cir::ComplexConjOp, cir::ComplexMulOp, cir::ComplexDivOp,
2939 cir::DynamicCastOp, cir::FuncOp, cir::CallOp,
2940 cir::GetGlobalOp, cir::GlobalOp, cir::StoreOp,
2941 cir::CmpThreeWayOp, cir::LocalInitOp, cir::StdOpInterface>(
2942 op))
2943 opsToTransform.push_back(op);
2944 });
2945
2946 for (mlir::Operation *o : opsToTransform)
2947 runOnOp(o);
2948
2949 buildCXXGlobalInitFunc();
2950 buildCXXGlobalTlsFunc();
2951 if (astCtx->getLangOpts().CUDA && !astCtx->getLangOpts().CUDAIsDevice)
2952 buildCUDAModuleCtor();
2953
2954 buildGlobalCtorDtorList();
2955}
2956
2957std::unique_ptr<Pass> mlir::createLoweringPreparePass() {
2958 return std::make_unique<LoweringPreparePass>();
2959}
2960
2961std::unique_ptr<Pass>
2963 auto pass = std::make_unique<LoweringPreparePass>();
2964 pass->setASTContext(astCtx);
2965 return std::move(pass);
2966}
Defines the clang::ASTContext interface.
static void emitBody(CodeGenFunction &CGF, const Stmt *S, const Stmt *NextLoop, int MaxLevel, int Level=0)
static llvm::FunctionCallee getGuardReleaseFn(CodeGenModule &CGM, llvm::PointerType *GuardPtrTy)
static llvm::FunctionCallee getGuardAcquireFn(CodeGenModule &CGM, llvm::PointerType *GuardPtrTy)
static mlir::Value buildRangeReductionComplexDiv(CIRBaseBuilderTy &builder, mlir::Location loc, mlir::Value lhsReal, mlir::Value lhsImag, mlir::Value rhsReal, mlir::Value rhsImag)
static llvm::StringRef getComplexDivLibCallName(llvm::APFloat::Semantics semantics)
static llvm::SmallVector< mlir::Attribute > prepareCtorDtorAttrList(mlir::MLIRContext *context, llvm::ArrayRef< std::pair< std::string, uint32_t > > list)
static llvm::StringRef getComplexMulLibCallName(llvm::APFloat::Semantics semantics)
static cir::GlobalLinkageKind getThreadLocalWrapperLinkage(GlobalOp op, clang::ASTContext &astCtx)
static mlir::Value buildComplexBinOpLibCall(LoweringPreparePass &pass, CIRBaseBuilderTy &builder, llvm::StringRef(*libFuncNameGetter)(llvm::APFloat::Semantics), mlir::Location loc, cir::ComplexType ty, mlir::Value lhsReal, mlir::Value lhsImag, mlir::Value rhsReal, mlir::Value rhsImag)
static mlir::Value lowerComplexMul(LoweringPreparePass &pass, CIRBaseBuilderTy &builder, mlir::Location loc, cir::ComplexMulOp op, mlir::Value lhsReal, mlir::Value lhsImag, mlir::Value rhsReal, mlir::Value rhsImag)
static std::string addUnderscoredPrefix(llvm::StringRef prefix, llvm::StringRef name)
static SmallString< 128 > getTransformedFileName(mlir::ModuleOp mlirModule)
static mlir::Value lowerComplexToComplexCast(mlir::MLIRContext &ctx, cir::CastOp op, cir::CastKind scalarCastKind)
static void lowerArrayDtorCtorIntoLoop(cir::CIRBaseBuilderTy &builder, clang::ASTContext *astCtx, mlir::Operation *op, mlir::Type eltTy, mlir::Value addr, mlir::Value numElements, uint64_t arrayLen, bool isCtor)
Lower a cir.array.ctor or cir.array.dtor into a do-while loop that iterates over every element.
static mlir::Value lowerComplexToScalarCast(mlir::MLIRContext &ctx, cir::CastOp op, cir::CastKind elemToBoolKind)
static mlir::Value buildAlgebraicComplexDiv(CIRBaseBuilderTy &builder, mlir::Location loc, mlir::Value lhsReal, mlir::Value lhsImag, mlir::Value rhsReal, mlir::Value rhsImag)
static llvm::StringRef getCUDAPrefix(clang::ASTContext *astCtx)
static bool isThreadWrapperReplaceable(clang::ASTContext &astCtx)
static mlir::Type higherPrecisionElementTypeForComplexArithmetic(mlir::MLIRContext &context, clang::ASTContext &cc, CIRBaseBuilderTy &builder, mlir::Type elementType)
static mlir::Value lowerScalarToComplexCast(mlir::MLIRContext &ctx, cir::CastOp op)
static mlir::Value lowerComplexDiv(LoweringPreparePass &pass, CIRBaseBuilderTy &builder, mlir::Location loc, cir::ComplexDivOp op, mlir::Value lhsReal, mlir::Value lhsImag, mlir::Value rhsReal, mlir::Value rhsImag, mlir::MLIRContext &mlirCx, clang::ASTContext &cc)
Defines the clang::Module class, which describes a module in the source code.
static bool compare(const PathDiagnostic &X, const PathDiagnostic &Y)
Defines the SourceManager interface.
Defines various enumerations that describe declaration and type specifiers.
Defines the TargetCXXABI class, which abstracts details of the C++ ABI that we're targeting.
mlir::Value createDiv(mlir::Location loc, mlir::Value lhs, mlir::Value rhs)
mlir::TypedAttr getConstNullPtrAttr(mlir::Type t)
mlir::Value createLogicalOr(mlir::Location loc, mlir::Value lhs, mlir::Value rhs)
mlir::Value createSub(mlir::Location loc, mlir::Value lhs, mlir::Value rhs, OverflowBehavior ob=OverflowBehavior::None)
cir::ConditionOp createCondition(mlir::Value condition)
Create a loop condition.
cir::CopyOp createCopy(mlir::Value dst, mlir::Value src, bool isVolatile=false, bool skipTailPadding=false)
Create a copy with inferred length.
cir::VoidType getVoidTy()
cir::ConstantOp getNullValue(mlir::Type ty, mlir::Location loc)
mlir::Value createCast(mlir::Location loc, cir::CastKind kind, mlir::Value src, mlir::Type newTy)
cir::PointerType getVoidFnPtrTy(mlir::TypeRange argTypes={})
Returns void (*)(T...) as a cir::PointerType.
mlir::Value createFDiv(mlir::Location loc, mlir::Value lhs, mlir::Value rhs)
mlir::Value createAdd(mlir::Location loc, mlir::Value lhs, mlir::Value rhs, OverflowBehavior ob=OverflowBehavior::None)
cir::PointerType getPointerTo(mlir::Type ty)
mlir::Value createFNeg(mlir::Location loc, mlir::Value operand)
mlir::Value createFAdd(mlir::Location loc, mlir::Value lhs, mlir::Value rhs)
mlir::Value createComplexImag(mlir::Location loc, mlir::Value operand)
cir::ConstantOp getNullPtr(mlir::Type ty, mlir::Location loc)
cir::IntType getUIntNTy(int n)
cir::DoWhileOp createDoWhile(mlir::Location loc, llvm::function_ref< void(mlir::OpBuilder &, mlir::Location)> condBuilder, llvm::function_ref< void(mlir::OpBuilder &, mlir::Location)> bodyBuilder)
Create a do-while operation.
cir::GetGlobalOp createGetGlobal(mlir::Location loc, cir::GlobalOp global, bool threadLocal=false)
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)
mlir::Value createAnd(mlir::Location loc, mlir::Value lhs, mlir::Value rhs)
mlir::Value createBitcast(mlir::Value src, mlir::Type newTy)
mlir::Value createFMul(mlir::Location loc, mlir::Value lhs, mlir::Value rhs)
cir::FuncType getVoidFnTy(mlir::TypeRange argTypes={})
Returns void (T...) as a cir::FuncType.
cir::CmpOp createCompare(mlir::Location loc, cir::CmpOpKind kind, mlir::Value lhs, mlir::Value rhs)
mlir::IntegerAttr getAlignmentAttr(clang::CharUnits alignment)
mlir::Value createSelect(mlir::Location loc, mlir::Value condition, mlir::Value trueValue, mlir::Value falseValue)
mlir::Value createMul(mlir::Location loc, mlir::Value lhs, mlir::Value rhs, OverflowBehavior ob=OverflowBehavior::None)
mlir::Value createMinus(mlir::Location loc, mlir::Value input, bool nsw=false)
cir::ConstantOp getConstantInt(mlir::Location loc, mlir::Type ty, int64_t value)
mlir::Value createComplexCreate(mlir::Location loc, mlir::Value real, mlir::Value imag)
cir::PointerType getVoidPtrTy(clang::LangAS langAS=clang::LangAS::Default)
mlir::Value createIsNaN(mlir::Location loc, mlir::Value operand)
cir::IntType getSIntNTy(int n)
mlir::Value createAlignedLoad(mlir::Location loc, mlir::Value ptr, uint64_t alignment)
cir::CallOp createCallOp(mlir::Location loc, mlir::SymbolRefAttr callee, mlir::Type returnType, mlir::ValueRange operands, llvm::ArrayRef< mlir::NamedAttribute > attrs={}, llvm::ArrayRef< mlir::NamedAttrList > argAttrs={}, llvm::ArrayRef< mlir::NamedAttribute > resAttrs={})
cir::YieldOp createYield(mlir::Location loc, mlir::ValueRange value={})
Create a yield operation.
mlir::Value createLogicalAnd(mlir::Location loc, mlir::Value lhs, mlir::Value rhs)
mlir::Value createFSub(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={})
cir::BoolType getBoolTy()
mlir::Value getUnsignedInt(mlir::Location loc, uint64_t val, unsigned numBits)
mlir::Value createComplexReal(mlir::Location loc, mlir::Value operand)
static llvm::SmallVector< RecordMemberKind > getAllDataKinds(llvm::ArrayRef< mlir::Type > members)
One Data kind per member.
Definition CIRTypes.cpp:156
Holds long-lived AST nodes (such as types and decls) that can be referred to throughout the semantic ...
Definition ASTContext.h:223
SourceManager & getSourceManager()
Definition ASTContext.h:884
MangleContext * createMangleContext(const TargetInfo *T=nullptr)
If T is null pointer, assume the target in ASTContext.
const LangOptions & getLangOpts() const
Definition ASTContext.h:980
uint64_t getTypeSize(QualType T) const
Return the size of the specified (complete) type T, in bits.
const TargetInfo & getTargetInfo() const
Definition ASTContext.h:942
QualType getSignedSizeType() const
Return the unique signed counterpart of the integer type corresponding to size_t.
Module * getCurrentNamedModule() const
Get module under construction, nullptr if this is not a C++20 module.
uint64_t getCharWidth() const
Return the size of the character type, in bits.
llvm::Align getAsAlign() const
getAsAlign - Returns Quantity as a valid llvm::Align, Beware llvm::Align assumes power of two 8-bit b...
Definition CharUnits.h:189
QuantityType getQuantity() const
getQuantity - Get the raw integer representation of this quantity.
Definition CharUnits.h:185
static CharUnits One()
One - Construct a CharUnits quantity of one.
Definition CharUnits.h:58
static CharUnits fromQuantity(QuantityType Quantity)
fromQuantity - Construct a CharUnits quantity from a raw integer type.
Definition CharUnits.h:63
llvm::vfs::FileSystem & getVirtualFileSystem() const
bool isModuleImplementation() const
Is this a module implementation.
Definition Module.h:882
FileManager & getFileManager() const
Exposes information about the current target.
Definition TargetInfo.h:227
const llvm::Triple & getTriple() const
Returns the target triple of the primary target.
unsigned getMaxAtomicInlineWidth() const
Return the maximum width lock-free atomic operation which can be inlined given the supported features...
Definition TargetInfo.h:865
virtual uint64_t getMaxPointerWidth() const
Return the maximum width of pointers on this target.
Definition TargetInfo.h:506
const llvm::VersionTuple & getSDKVersion() const
Defines the clang::TargetInfo interface.
static bool isLocalLinkage(GlobalLinkageKind linkage)
Definition CIROpsEnums.h:51
static bool isWeakODRLinkage(GlobalLinkageKind linkage)
Definition CIROpsEnums.h:39
static bool isLinkOnceLinkage(GlobalLinkageKind linkage)
Definition CIROpsEnums.h:33
const internal::VariadicDynCastAllOfMatcher< Decl, VarDecl > varDecl
Matches variable declarations.
const internal::VariadicAllOfMatcher< Attr > attr
bool isHIP(ID Id)
isHIP - Is this a HIP input.
Definition Types.cpp:314
RangeSelector name(std::string ID)
Given a node with a "name", (like NamedDecl, DeclRefExpr, CxxCtorInitializer, and TypeLoc) selects th...
bool isTemplateInstantiation(TemplateSpecializationKind Kind)
Determine whether this template specialization kind refers to an instantiation of an entity (as oppos...
Definition Specifiers.h:213
bool CudaFeatureEnabled(llvm::VersionTuple, CudaFeature)
Definition Cuda.cpp:119
LLVM_READONLY bool isPreprocessingNumberBody(unsigned char c)
Return true if this is the body character of a C preprocessing number, which is [a-zA-Z0-9_.
Definition CharInfo.h:168
@ CUDA_USES_FATBIN_REGISTER_END
Definition Cuda.h:84
std::unique_ptr< Pass > createLoweringPreparePass()
__packed_splat4 __packed_splat2 __packed_splat8 __packed_splat4 __packed_splat2 __packed_splat4 __packed_splat2 __packed_splat8 __packed_splat4 uint32_t
static bool hipModuleCtor()
static bool guardAbortOnException()
static bool opGlobalAnnotations()
static bool opGlobalCtorPriority()
static bool shouldSplitConstantStore()
static bool shouldUseMemSetToInitialize()
static bool opFuncExtraAttrs()
static bool shouldUseBZeroPlusStoresToInitialize()
static bool fastMathFlags()
static bool astVarDeclInterface()