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