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