-
Notifications
You must be signed in to change notification settings - Fork 15.3k
[CIR] Implement global array dtor support #169070
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -76,6 +76,11 @@ struct LoweringPreparePass | |
| /// Build the function that initializes the specified global | ||
| cir::FuncOp buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op); | ||
|
|
||
| /// Handle the dtor region by registering destructor with __cxa_atexit | ||
| cir::FuncOp getOrCreateDtorFunc(CIRBaseBuilderTy &builder, cir::GlobalOp op, | ||
| mlir::Region &dtorRegion, | ||
| cir::CallOp &dtorCall); | ||
|
|
||
| /// Build a module init function that calls all the dynamic initializers. | ||
| void buildCXXGlobalInitFunc(); | ||
|
|
||
|
|
@@ -691,6 +696,101 @@ void LoweringPreparePass::lowerUnaryOp(cir::UnaryOp op) { | |
| op.erase(); | ||
| } | ||
|
|
||
| cir::FuncOp LoweringPreparePass::getOrCreateDtorFunc(CIRBaseBuilderTy &builder, | ||
| cir::GlobalOp op, | ||
| mlir::Region &dtorRegion, | ||
| cir::CallOp &dtorCall) { | ||
| assert(!cir::MissingFeatures::astVarDeclInterface()); | ||
| assert(!cir::MissingFeatures::opGlobalThreadLocal()); | ||
|
|
||
| cir::VoidType voidTy = builder.getVoidTy(); | ||
| auto voidPtrTy = cir::PointerType::get(voidTy); | ||
|
|
||
| // Look for operations in dtorBlock | ||
| mlir::Block &dtorBlock = dtorRegion.front(); | ||
|
|
||
| // The first operation should be a get_global to retrieve the address | ||
| // of the global variable we're destroying. | ||
| auto opIt = dtorBlock.getOperations().begin(); | ||
| cir::GetGlobalOp ggop = mlir::cast<cir::GetGlobalOp>(*opIt); | ||
|
|
||
| // The simple case is just a call to a destructor, like this: | ||
| // | ||
| // %0 = cir.get_global %globalS : !cir.ptr<!rec_S> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Its a little weird that this global is required... could we make a helper for 'createDestroy' (or whatever it is?) to take the 'location' of the get and synthesize it? It just seems odd we have a 'get_global' . Alternatively, can we assert that the global IS the current variable as a sanity assert?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that it's weird, but we need an which produces Obviously, I'd want to defer that to a future change.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh yeah, that seems sensible. Just seemed a little odd. I guess I was envisioning some sort of 'get-self' type thing... I guess I see what you mean, any call (in the non-array case) would need that. The way I could think of would be to have the block take an argument that has that value (like But I can see that being fine in a followup/with a FIXME put somewhere. |
||
| // cir.call %_ZN1SD1Ev(%0) : (!cir.ptr<!rec_S>) -> () | ||
| // (implicit cir.yield) | ||
| // | ||
| // That is, if the second operation is a call that takes the get_global result | ||
| // as its only operand, and the only other operation is a yield, then we can | ||
| // just return the called function. | ||
| if (dtorBlock.getOperations().size() == 3) { | ||
| auto callOp = mlir::dyn_cast<cir::CallOp>(&*(++opIt)); | ||
| auto yieldOp = mlir::dyn_cast<cir::YieldOp>(&*(++opIt)); | ||
| if (yieldOp && callOp && callOp.getNumOperands() == 1 && | ||
| callOp.getArgOperand(0) == ggop) { | ||
| dtorCall = callOp; | ||
| return getCalledFunction(callOp); | ||
| } | ||
| } | ||
|
|
||
| // Otherwise, we need to create a helper function to replace the dtor region. | ||
| // This name is kind of arbitrary, but it matches the name that classic | ||
| // codegen uses, based on the expected case that gets us here. | ||
| builder.setInsertionPointAfter(op); | ||
| SmallString<256> fnName("__cxx_global_array_dtor"); | ||
| uint32_t cnt = dynamicInitializerNames[fnName]++; | ||
| if (cnt) | ||
| fnName += "." + std::to_string(cnt); | ||
|
|
||
| // Create the helper function. | ||
| auto fnType = cir::FuncType::get({voidPtrTy}, voidTy); | ||
| cir::FuncOp dtorFunc = | ||
| buildRuntimeFunction(builder, fnName, op.getLoc(), fnType, | ||
| cir::GlobalLinkageKind::InternalLinkage); | ||
| mlir::Block *entryBB = dtorFunc.addEntryBlock(); | ||
|
|
||
| // Move everything from the dtor region into the helper function. | ||
| entryBB->getOperations().splice(entryBB->begin(), dtorBlock.getOperations(), | ||
| dtorBlock.begin(), dtorBlock.end()); | ||
|
|
||
| // Before erasing this, clone it back into the dtor region | ||
| cir::GetGlobalOp dtorGGop = | ||
| mlir::cast<cir::GetGlobalOp>(entryBB->getOperations().front()); | ||
| builder.setInsertionPointToStart(&dtorBlock); | ||
| builder.clone(*dtorGGop.getOperation()); | ||
erichkeane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Replace all uses of the help function's get_global with the function | ||
| // argument. | ||
| mlir::Value dtorArg = entryBB->getArgument(0); | ||
| dtorGGop.replaceAllUsesWith(dtorArg); | ||
| dtorGGop.erase(); | ||
|
|
||
| // Replace the yield in the final block with a return | ||
| mlir::Block &finalBlock = dtorFunc.getBody().back(); | ||
| auto yieldOp = cast<cir::YieldOp>(finalBlock.getTerminator()); | ||
| builder.setInsertionPoint(yieldOp); | ||
| cir::ReturnOp::create(builder, yieldOp->getLoc()); | ||
| yieldOp->erase(); | ||
|
|
||
| // Create a call to the helper function, passing the original get_global op | ||
| // as the argument. | ||
| cir::GetGlobalOp origGGop = | ||
| mlir::cast<cir::GetGlobalOp>(dtorBlock.getOperations().front()); | ||
| builder.setInsertionPointAfter(origGGop); | ||
| mlir::Value ggopResult = origGGop.getResult(); | ||
| dtorCall = builder.createCallOp(op.getLoc(), dtorFunc, ggopResult); | ||
|
|
||
| // Add a yield after the call. | ||
| auto finalYield = cir::YieldOp::create(builder, op.getLoc()); | ||
|
|
||
| // Erase everything after the yield. | ||
| dtorBlock.getOperations().erase(std::next(mlir::Block::iterator(finalYield)), | ||
| dtorBlock.end()); | ||
| dtorRegion.getBlocks().erase(std::next(dtorRegion.begin()), dtorRegion.end()); | ||
|
|
||
| return dtorFunc; | ||
| } | ||
|
|
||
| cir::FuncOp | ||
| LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) { | ||
| // TODO(cir): Store this in the GlobalOp. | ||
|
|
@@ -699,7 +799,7 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) { | |
| // Get a unique name | ||
| uint32_t cnt = dynamicInitializerNames[fnName]++; | ||
| if (cnt) | ||
| fnName += "." + llvm::Twine(cnt).str(); | ||
| fnName += "." + std::to_string(cnt); | ||
|
|
||
| // Create a variable initialization function. | ||
| CIRBaseBuilderTy builder(getContext()); | ||
|
|
@@ -722,22 +822,20 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) { | |
| if (!dtorRegion.empty()) { | ||
| assert(!cir::MissingFeatures::astVarDeclInterface()); | ||
| assert(!cir::MissingFeatures::opGlobalThreadLocal()); | ||
|
|
||
| // Create a variable that binds the atexit to this shared object. | ||
| builder.setInsertionPointToStart(&mlirModule.getBodyRegion().front()); | ||
| cir::GlobalOp handle = buildRuntimeVariable( | ||
| builder, "__dso_handle", op.getLoc(), builder.getI8Type(), | ||
| cir::GlobalLinkageKind::ExternalLinkage, cir::VisibilityKind::Hidden); | ||
|
|
||
| // Look for the destructor call in dtorBlock | ||
| mlir::Block &dtorBlock = dtorRegion.front(); | ||
| // If this is a simple call to a destructor, get the called function. | ||
| // Otherwise, create a helper function for the entire dtor region, | ||
| // replacing the current dtor region body with a call to the helper | ||
| // function. | ||
| cir::CallOp dtorCall; | ||
| for (auto op : reverse(dtorBlock.getOps<cir::CallOp>())) { | ||
| dtorCall = op; | ||
| break; | ||
| } | ||
| assert(dtorCall && "Expected a dtor call"); | ||
| cir::FuncOp dtorFunc = getCalledFunction(dtorCall); | ||
| assert(dtorFunc && "Expected a dtor call"); | ||
| cir::FuncOp dtorFunc = | ||
| getOrCreateDtorFunc(builder, op, dtorRegion, dtorCall); | ||
|
|
||
| // Create a runtime helper function: | ||
| // extern "C" int __cxa_atexit(void (*f)(void *), void *p, void *d); | ||
|
|
@@ -751,8 +849,8 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) { | |
| cir::FuncOp fnAtExit = | ||
| buildRuntimeFunction(builder, nameAtExit, op.getLoc(), fnAtExitType); | ||
|
|
||
| // Replace the dtor call with a call to __cxa_atexit(&dtor, &var, | ||
| // &__dso_handle) | ||
| // Replace the dtor (or helper) call with a call to | ||
| // __cxa_atexit(&dtor, &var, &__dso_handle) | ||
| builder.setInsertionPointAfter(dtorCall); | ||
| mlir::Value args[3]; | ||
| auto dtorPtrTy = cir::PointerType::get(dtorFunc.getFunctionType()); | ||
|
|
@@ -768,6 +866,7 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) { | |
| handle.getSymName()); | ||
| builder.createCallOp(dtorCall.getLoc(), fnAtExit, args); | ||
| dtorCall->erase(); | ||
| mlir::Block &dtorBlock = dtorRegion.front(); | ||
| entryBB->getOperations().splice(entryBB->end(), dtorBlock.getOperations(), | ||
| dtorBlock.begin(), | ||
| std::prev(dtorBlock.end())); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.