Skip to content
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

Emit lambdas as linkonce_odr, including contained globals #3650

Merged
merged 3 commits into from
Feb 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions gen/declarations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -269,13 +269,7 @@ class CodegenVisitor : public Visitor {
"manifest constant being codegen'd!");
assert(!irs->dcomputetarget);

// Check if we are defining or just declaring the global in this module.
// If we reach here during codegen of an available_externally function,
// new variable declarations should stay external and therefore must not
// have an initializer.
bool define = !(decl->storage_class & STCextern) && !decl->inNonRoot();

getIrGlobal(decl)->getValue(define);
getIrGlobal(decl)->getValue(/*define=*/true);
}
}

Expand Down
5 changes: 5 additions & 0 deletions gen/function-inlining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ bool defineAsExternallyAvailable(FuncDeclaration &fdecl) {
return false;
}

if (fdecl.isFuncLiteralDeclaration()) {
// defined as discardable linkonce_odr in each referencing CU
IF_LOG Logger::println("isFuncLiteralDeclaration() == true");
return false;
}
if (fdecl.isUnitTestDeclaration()) {
IF_LOG Logger::println("isUnitTestDeclaration() == true");
return false;
Expand Down
22 changes: 15 additions & 7 deletions gen/tollvm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,16 +226,24 @@ LLValue *DtoDelegateEquals(TOK op, LLValue *lhs, LLValue *rhs) {
////////////////////////////////////////////////////////////////////////////////

LinkageWithCOMDAT DtoLinkage(Dsymbol *sym) {
LLGlobalValue::LinkageTypes linkage;
LLGlobalValue::LinkageTypes linkage = LLGlobalValue::ExternalLinkage;
if (hasWeakUDA(sym)) {
linkage = LLGlobalValue::WeakAnyLinkage;
}
// Function (incl. delegate) literals are emitted into each referencing
// compilation unit; use template linkage to prevent conflicts.
else if (sym->isFuncLiteralDeclaration() || sym->isInstantiated()) {
linkage = templateLinkage;
} else {
linkage = LLGlobalValue::ExternalLinkage;
// Function (incl. delegate) literals are emitted into each referencing
// compilation unit, so use linkonce_odr for all lambdas and all global
// variables they define.
auto potentialLambda = sym;
if (auto vd = sym->isVarDeclaration()) {
if (vd->isDataseg())
potentialLambda = vd->toParent2();
}

if (potentialLambda->isFuncLiteralDeclaration()) {
linkage = LLGlobalValue::LinkOnceODRLinkage;
} else if (sym->isInstantiated()) {
linkage = templateLinkage;
}
}

return {linkage, needsCOMDAT()};
Expand Down
20 changes: 14 additions & 6 deletions ir/irvar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,20 @@ LLValue *IrGlobal::getValue(bool define) {
define = defineOnDeclare(V);
}

if (define && !(V->storage_class & STCextern)) {
auto gvar = llvm::dyn_cast<LLGlobalVariable>(value);
const bool isDefined = !gvar // bitcast pointer to a helper global
|| gvar->hasInitializer();
if (!isDefined)
this->define();
if (define) {
if (V->storage_class & STCextern) {
// external
} else if (!gIR->funcGenStates.empty() &&
gIR->topfunc()->getLinkage() ==
LLGlobalValue::AvailableExternallyLinkage) {
// don't define globals while codegen'ing available_externally functions
} else {
auto gvar = llvm::dyn_cast<LLGlobalVariable>(value);
const bool isDefined = !gvar // bitcast pointer to a helper global
|| gvar->hasInitializer();
if (!isDefined)
this->define();
}
}

return value;
Expand Down
46 changes: 46 additions & 0 deletions tests/codegen/lambdas_gh3648.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Tests that lambdas and contained globals are emitted as linkonce_odr.

// RUN: %ldc -output-ll -of=%t.ll %s && FileCheck %s < %t.ll

enum bar = ()
{
static int global_bar;
};

enum bar_inlined = ()
{
pragma(inline, true);
shared static int global_bar_inlined;
};

void foo()
{
bar();
bar_inlined();

(x) // template
{
__gshared int lambda_templ;
return x + lambda_templ;
}(123);
}

// the global variables should be defined as linkonce_odr:
// CHECK: _D14lambdas_gh36489__lambda5FZ10global_bari{{.*}} = linkonce_odr thread_local global
// CHECK: _D14lambdas_gh36489__lambda6FZ18global_bar_inlinedOi{{.*}} = linkonce_odr global
// CHECK: _D14lambdas_gh36483fooFZ__T9__lambda1TiZQnFiZ12lambda_templi{{.*}} = linkonce_odr global

// foo() should only call two lambdas:
// CHECK: define {{.*}}_D14lambdas_gh36483fooFZv
// CHECK-NEXT: call {{.*}}__lambda5
// CHECK-NEXT: call {{.*}}__T9__lambda1
// CHECK-NEXT: ret void

// bar() should be defined as linkonce_odr:
// CHECK: define linkonce_odr {{.*}}__lambda5

// bar_inlined() should NOT have made it to the .ll:
// CHECK-NOT: define {{.*}}__lambda6

// the template lambda instance should be defined as linkonce_odr:
// CHECK: define linkonce_odr {{.*}}__T9__lambda1
26 changes: 26 additions & 0 deletions tests/codegen/lambdas_gh3648b.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Tests that *imported* lambdas and contained globals are emitted as linkonce_odr.

// RUN: %ldc -output-ll -of=%t.ll %s -I%S && FileCheck %s < %t.ll

import lambdas_gh3648;

void foo()
{
bar();
bar_inlined();
}

// the global variables should be defined as linkonce_odr:
// CHECK: _D14lambdas_gh36489__lambda5FZ10global_bari{{.*}} = linkonce_odr thread_local global
// CHECK: _D14lambdas_gh36489__lambda6FZ18global_bar_inlinedOi{{.*}} = linkonce_odr global

// foo() should only call one lambda:
// CHECK: define {{.*}}_D15lambdas_gh3648b3fooFZv
// CHECK-NEXT: call {{.*}}__lambda5
// CHECK-NEXT: ret void

// bar() should be defined as linkonce_odr:
// CHECK: define linkonce_odr {{.*}}__lambda5

// bar_inlined() should NOT have made it to the .ll:
// CHECK-NOT: define {{.*}}__lambda6