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