Skip to content
Open
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
16 changes: 11 additions & 5 deletions gen/declarations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,15 @@ class CodegenVisitor : public Visitor {
}

// Define the __initZ symbol.
IrAggr *ir = getIrAggr(decl);
llvm::GlobalVariable *initZ = ir->getInitSymbol();
initZ->setInitializer(ir->getDefaultInit());
setLinkage(decl, initZ);
// If we reach here during codegen of an available_externally function,
// the struct initializer should stay external and therefore must not
// have an initializer.
if ((decl->getModule() == gIR->dmodule) || decl->isInstantiated()) {
Copy link
Member

@dnadlinger dnadlinger Sep 2, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This predicate looks like a good candidate for a helper function that takes a Dsymbol * or whatever – feeling compelled to copy-and-paste comments is probably a good indicator for that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely.

IrAggr *ir = getIrAggr(decl);
llvm::GlobalVariable *initZ = ir->getInitSymbol();
initZ->setInitializer(ir->getDefaultInit());
setLinkage(decl, initZ);
}

// emit typeinfo
DtoTypeInfoOf(decl->type);
Expand Down Expand Up @@ -341,7 +346,8 @@ class CodegenVisitor : public Visitor {
// If we reach here during codegen of an available_externally function,
// new variable declarations should stay external and therefore must not
// have an initializer.
if (!(decl->storage_class & STCextern) && !decl->inNonRoot()) {
if (!(decl->storage_class & STCextern) &&
((decl->getModule() == gIR->dmodule) || decl->isInstantiated())) {
// Build the initializer. Might use this->ir.irGlobal->value!
LLConstant *initVal =
DtoConstInitializer(decl->loc, decl->type, decl->_init);
Expand Down
10 changes: 7 additions & 3 deletions gen/function-inlining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,18 +143,21 @@ bool defineAsExternallyAvailable(FuncDeclaration &fdecl) {
return false;
}

if (fdecl.semanticRun >= PASSsemantic3) {
if ((fdecl.inlining != PINLINEalways) && fdecl.semanticRun >= PASSsemantic3) {
// If semantic analysis has come this far, the function will be defined
// elsewhere and should not get the available_externally attribute from
// here.
// here. However, pragma(inline, true) functions should remain
// defineAsExternallyAvailable candidates to fix GH #1712.
// TODO: This check prevents inlining of nested functions.
IF_LOG Logger::println("Semantic analysis already completed");
return false;
}

if (alreadyOrWillBeDefined(fdecl)) {
if ((fdecl.inlining != PINLINEalways) && alreadyOrWillBeDefined(fdecl)) {
// This check is needed because of ICEs happening because of unclear issues
// upon changing the codegen order without this check.
// pragma(inline, true) functions remain defineAsExternallyAvailable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what this is saying then is that alreadyOrWillBeDefined is buggy?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is non-singleobj compilation, because most (all?) instantiated functions are put into one module, which may be different from the callsite's module. So if alreadyOrWillBeDefined is true the function will be defined, yes, but for inlining it needs to be defined in every calling module.

// candidates to fix GH #1712.
IF_LOG Logger::println("Function will be defined later.");
return false;
}
Expand All @@ -170,6 +173,7 @@ bool defineAsExternallyAvailable(FuncDeclaration &fdecl) {

IF_LOG Logger::println("Potential inlining candidate");

if (fdecl.semanticRun < PASSsemantic3)
{
IF_LOG Logger::println("Do semantic analysis");
LOG_SCOPE
Expand Down
31 changes: 23 additions & 8 deletions gen/functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,9 @@ static llvm::Function *DtoDeclareVaFunction(FuncDeclaration *fdecl) {

////////////////////////////////////////////////////////////////////////////////

void DtoResolveFunction(FuncDeclaration *fdecl) {
/// When mayDefine is true, the call to this function may possibly define the
/// function too (e.g. for cross-module inlining).
void DtoResolveFunction(FuncDeclaration *fdecl, bool mayDefine) {
if ((!global.params.useUnitTests || !fdecl->type) &&
fdecl->isUnitTestDeclaration()) {
IF_LOG Logger::println("Ignoring unittest %s", fdecl->toPrettyChars());
Expand Down Expand Up @@ -404,7 +406,7 @@ void DtoResolveFunction(FuncDeclaration *fdecl) {

// queue declaration unless the function is abstract without body
if (!fdecl->isAbstract() || fdecl->fbody) {
DtoDeclareFunction(fdecl);
DtoDeclareFunction(fdecl, mayDefine);
}
}

Expand Down Expand Up @@ -434,8 +436,10 @@ void applyDefaultMathAttributes(IrFunction *irFunc) {

////////////////////////////////////////////////////////////////////////////////

void DtoDeclareFunction(FuncDeclaration *fdecl) {
DtoResolveFunction(fdecl);
/// When mayDefine is true, the call to this function may possibly define the
/// function too (e.g. for cross-module inlining).
void DtoDeclareFunction(FuncDeclaration *fdecl, bool mayDefine) {
DtoResolveFunction(fdecl, mayDefine);

if (fdecl->ir->isDeclared()) {
return;
Expand All @@ -462,7 +466,8 @@ void DtoDeclareFunction(FuncDeclaration *fdecl) {
// Check if fdecl should be defined too for cross-module inlining.
// If true, semantic is fully done for fdecl which is needed for some code
// below (e.g. code that uses fdecl->vthis).
const bool defineAtEnd = defineAsExternallyAvailable(*fdecl);
const bool defineAtEnd =
mayDefine && defineAsExternallyAvailable(*fdecl);
if (defineAtEnd) {
IF_LOG Logger::println(
"Function is an externally_available inline candidate.");
Expand Down Expand Up @@ -742,14 +747,24 @@ void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally) {
fd->loc.toChars());
LOG_SCOPE;
if (linkageAvailableExternally) {
IF_LOG Logger::println("linkageAvailableExternally = true");
IF_LOG Logger::println("linkageAvailableExternally == true");
}

if ((fd->getModule() != gIR->dmodule) && !fd->isInstantiated()) {
// The function is being emitted cross-module for inlining. We reach here
// for nested functions inside cross-module emitted functions. We have to
// explicitly set `linkageAvailableExternally` to true.
linkageAvailableExternally = true;
IF_LOG Logger::println("Cross-module emitted nested function: "
"linkageAvailableExternally = true");
}

if (fd->ir->isDefined()) {
if (!linkageAvailableExternally &&
(getIrFunc(fd)->func->getLinkage() ==
llvm::GlobalValue::AvailableExternallyLinkage)) {
// Fix linkage
IF_LOG Logger::println("Remove available_externally linkage.");
const auto lwc = lowerFuncLinkage(fd);
setLinkage(lwc, getIrFunc(fd)->func);
}
Expand Down Expand Up @@ -777,7 +792,7 @@ void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally) {
fatal();
}

DtoResolveFunction(fd);
DtoResolveFunction(fd, false);

if (fd->isUnitTestDeclaration() && !global.params.useUnitTests) {
IF_LOG Logger::println("No code generation for unit test declaration %s",
Expand All @@ -801,7 +816,7 @@ void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally) {
return;
}

DtoDeclareFunction(fd);
DtoDeclareFunction(fd, false);
assert(fd->ir->isDeclared());

// DtoResolveFunction might also set the defined flag for functions we
Expand Down
4 changes: 2 additions & 2 deletions gen/functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ llvm::FunctionType *DtoFunctionType(Type *t, IrFuncTy &irFty, Type *thistype,
bool hasSel = false);
llvm::FunctionType *DtoFunctionType(FuncDeclaration *fdecl);

void DtoResolveFunction(FuncDeclaration *fdecl);
void DtoDeclareFunction(FuncDeclaration *fdecl);
void DtoResolveFunction(FuncDeclaration *fdecl, bool mayDefine = true);
void DtoDeclareFunction(FuncDeclaration *fdecl, bool mayDefine = true);
void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally = false);

void DtoDefineNakedFunction(FuncDeclaration *fd);
Expand Down
46 changes: 46 additions & 0 deletions tests/codegen/inlining_gh1712.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Test inlining related to Github issue 1712

// REQUIRES: atleast_llvm307

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

module mod;

void test9785_2()
{
int j = 3;

void loop(scope const void function(int x) dg)
{
pragma(inline, true);
dg(++j);
}

static void func(int x)
{
pragma(inline, true);
assert(x == 4);
}

loop(&func);
}

void main()
{
test9785_2();
}

// There was a bug where pragma(inline, true) nested functions were incorrectly emitted with `available_externally` linkage.

// CHECK: define
// CHECK-NOT: available_externally
// CHECK-SAME: @{{.*}}D3mod10test9785_2FZv

// CHECK: define
// CHECK-NOT: available_externally
// CHECK-SAME: @{{.*}}D3mod10test9785_2FZ4loopMFMxPFiZvZv

// CHECK: define
// CHECK-NOT: available_externally
// CHECK-SAME: @{{.*}}D3mod10test9785_2FZ4funcFiZv
15 changes: 15 additions & 0 deletions tests/codegen/inlining_gh1712_originalbug.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// REQUIRES: atleast_llvm307

// RUN: %ldc -output-ll -od=%T -I%S -O0 -release -enable-cross-module-inlining %s %S/inputs/inlining_gh1712_string.d \
// RUN: && FileCheck %s < %T/inlining_gh1712_originalbug.ll

import inputs.inlinables;

void main()
{
call_template_foo(1);
}

// Make sure that the pragma(inline, true) function `call_template_foo` is defined, and not just declared.
// When it is correctly defined, optimization (available_externally+alwaysinline) even at -O0 means that it will dissappear from IR.
// CHECK-NOT: declare {{.*}} @call_template_foo
18 changes: 18 additions & 0 deletions tests/codegen/inlining_staticvar.d
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

// RUN: %ldc %s -I%S -c -output-ll -O3 -of=%t.O3.ll && FileCheck %s --check-prefix OPT3 < %t.O3.ll
// RUN: %ldc %s -I%S -c -output-ll -enable-inlining -O0 -of=%t.O0.ll && FileCheck %s --check-prefix OPT0 < %t.O0.ll

// Test linkage with separate compilation.
// RUN: %ldc -c %S/inputs/inlinables_staticvar.d -of=%t.import%obj \
// RUN: && %ldc -I%S -enable-inlining %t.import%obj -run %s

// Test linkage with non-singleobj build.
// RUN: %ldc -I%S -enable-inlining %S/inputs/inlinables_staticvar.d -run %s
// RUN: %ldc -I%S -O3 %S/inputs/inlinables_staticvar.d -run %s

Expand Down Expand Up @@ -55,6 +61,16 @@ void checkNestedStruct_2() @weak
assert(addAndCheckNestedStruct(7+101, 9));
}

void checkTemplatedNestedStruct_1() @weak
{
assert(addAndCheckTemplatedNestedStruct(0, 7));
}
void checkTemplatedNestedStruct_2() @weak
{
assert(addAndCheckTemplatedNestedStructIndirect(7, 101));
assert(addAndCheckTemplatedNestedStruct(7+101, 9));
}

// OPT0-LABEL: define{{.*}} @_Dmain(
// OPT3-LABEL: define{{.*}} @_Dmain(
extern(D)
Expand All @@ -68,4 +84,6 @@ void main()
checkInsideNestedFunc_2();
checkNestedStruct_1();
checkNestedStruct_2();
checkTemplatedNestedStruct_1();
checkTemplatedNestedStruct_2();
}
21 changes: 21 additions & 0 deletions tests/codegen/inputs/inlinables_staticvar.d
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,24 @@ pragma(inline, false) bool addAndCheckNestedStructIndirect(int checkbefore, int
}

/+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++/

pragma(inline, true) bool addAndCheckTemplatedNestedStruct(T)(T checkbefore, T increment)
{
struct NestedStruct(T)
{
static T structValue;
}

if (NestedStruct!T.structValue != checkbefore)
return false;

NestedStruct!T.structValue += increment;
return true;
}

pragma(inline, false) bool addAndCheckTemplatedNestedStructIndirect(T)(T checkbefore, T increment)
{
return addAndCheckTemplatedNestedStruct!T(checkbefore, increment);
}

/+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++/
6 changes: 6 additions & 0 deletions tests/codegen/inputs/inlining_gh1712_string.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import inputs.inlinables;

void foo()
{
call_template_foo(1);
}