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