Skip to content

Commit

Permalink
Assigning T.init should only create a temporary when needed.
Browse files Browse the repository at this point in the history
  • Loading branch information
JohanEngelen committed Dec 1, 2018
1 parent 9dbd4da commit 8ff084a
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 4 deletions.
28 changes: 24 additions & 4 deletions gen/toir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -507,10 +507,30 @@ class ToElemVisitor : public Visitor {
}
};

// try to construct the lhs in-place
if (lhs->isLVal() && e->op == TOKconstruct &&
toInPlaceConstruction(lhs->isLVal(), e->e2)) {
return;
// Try to construct the lhs in-place.
if (lhs->isLVal() && (e->op == TOKconstruct)) {
Logger::println("attempting in-place construction");
if (toInPlaceConstruction(lhs->isLVal(), e->e2))
return;
}

// Try to assign to the lhs in-place.
// This extra complication at -O0 is to prevent excessive stack space usage
// when assigning to large structs.
// Note: If the assignment is non-trivial, a CallExp to opAssign is
// generated by the frontend instead of this AssignExp. The in-place
// construction is not valid if the rhs is not a literal (consider for
// example `a = foo(a)`), but also not if the rhs contains non-constant
// elements (consider for example `a = [0, a[0], 2]` or `a = [0, i, 2]`
// where `i` is a ref variable aliasing with a).
// Be conservative with this optimization for now: only do the optimization
// for struct `.init` assignment.
if (lhs->isLVal() && (e->op == TOKassign) &&
((e->e2->op == TOKstructliteral) &&
static_cast<StructLiteralExp *>(e->e2)->useStaticInit)) {
Logger::println("attempting in-place assignment");
if (toInPlaceConstruction(lhs->isLVal(), e->e2))
return;
}

DValue *r = toElem(e->e2);
Expand Down
115 changes: 115 additions & 0 deletions tests/codegen/assign_struct_init_without_stack.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Tests that even in non-optimized builds, member = member.init immediately does a memcpy,
// instead of going through a temporary stack allocated variable.
// Exception: when member has an opAssign with auto ref parameter a local temporary
// has to be passed because `.init` is an rvalue,

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

void opaque();
FloatStruct opaque(FloatStruct);

struct FloatStruct {
float[10] data;
}

FloatStruct globalStruct;

// CHECK-LABEL: define{{.*}} @{{.*}}_D32assign_struct_init_without_stack3fooFZv
void foo() {
globalStruct = FloatStruct.init;
// There should be only one memcpy.
// CHECK: call void @llvm.memcpy
// CHECK-NEXT: ret void
}

// CHECK-LABEL: define{{.*}} @{{.*}}_D32assign_struct_init_without_stack3hhhFZv
void hhh() {
globalStruct = FloatStruct([1, 0, 0, 0, 0, 0, 0, 0, 0, 42]);
// Future work: test optimized codegen (at -O0)
}

// CHECK-LABEL: define{{.*}} @{{.*}}_D32assign_struct_init_without_stack3gggFZv
void ggg() {
globalStruct = opaque(globalStruct);
// There should be one memcpy from a temporary (sret return).
// CHECK: alloca %assign_struct_init_without_stack.FloatStruct
// CHECK: call
// CHECK: call void @llvm.memcpy
// CHECK-NEXT: ret void
}

// CHECK-LABEL: define{{.*}} @{{.*}}_D32assign_struct_init_without_stack5arrayFZv
void array() {
int[5] arr = [0,1,2,3,4];
// Future work: test optimized codegen (at -O0)
}

// CHECK-LABEL: define{{.*}} @{{.*}}_D32assign_struct_init_without_stack6array2FKG3iZv
void array2(ref int[3] a) {
a = [4, a[0], 6];
// There should be a temporary!
// CHECK: alloca [3 x i32]
// CHECK: call void @llvm.memcpy
// CHECK-NEXT: ret void
}

struct OpAssignStruct {
float[10] data;
ubyte a;

ref OpAssignStruct opAssign(R)(auto ref R rhs) {
return this;
}
}
OpAssignStruct globalOpAssignStruct;
OpAssignStruct globalOpAssignStruct2;

// CHECK-LABEL: define{{.*}} @{{.*}}_D32assign_struct_init_without_stack16tupleassignByValFZv
void tupleassignByVal()
{
globalOpAssignStruct = OpAssignStruct.init;
// There should be one memcpy to a temporary.
// CHECK: alloca %assign_struct_init_without_stack.OpAssignStruct
// CHECK: call void @llvm.memcpy
// CHECK-NOT: memcpy
// CHECK: call{{.*}} %assign_struct_init_without_stack.OpAssignStruct* @{{.*}}_D32assign_struct_init_without_stack14OpAssignStruct__T8opAssignTSQCmQBhZQsMFNaNbNcNiNjNfQyZQBb
// CHECK-NEXT: ret void
}

// CHECK-LABEL: define{{.*}} @{{.*}}_D32assign_struct_init_without_stack16tupleassignByRefFZv
void tupleassignByRef()
{
globalOpAssignStruct = globalOpAssignStruct2;
// There should not be a memcpy.
// CHECK-NOT: memcpy
// CHECK: call{{.*}} %assign_struct_init_without_stack.OpAssignStruct* @{{.*}}_D32assign_struct_init_without_stack14OpAssignStruct__T8opAssignTSQCmQBhZQsMFNaNbNcNiNjNfKQzZQBc
// CHECK-NEXT: ret void
}

struct DtorStruct {
float[10] data;
~this() { opaque(); }
}
struct CtorStruct {
float[10] data;
this(int i) { opaque(); }
}
DtorStruct dtorStruct;
CtorStruct ctorStruct;

// CHECK-LABEL: define{{.*}} @{{.*}}_D32assign_struct_init_without_stack4ctorFZv
void ctor() {
ctorStruct = ctorStruct.init;
// There is no dtor, so can be optimized to only a memcpy.
// CHECK-NEXT: call void @llvm.memcpy
// CHECK-NEXT: ret void
}
// CHECK-LABEL: define{{.*}} @{{.*}}_D32assign_struct_init_without_stack4dtorFZv
void dtor() {
dtorStruct = dtorStruct.init;
// There should be a temporary and a call to opAssign
// CHECK: alloca %assign_struct_init_without_stack.DtorStruct
// CHECK: call void @llvm.memcpy{{.*}}_D32assign_struct_init_without_stack10DtorStruct6__initZ
// CHECK-NEXT: call {{.*}}_D32assign_struct_init_without_stack10DtorStruct8opAssignMFNcNjSQCkQBfZQi
// CHECK-NEXT: ret void
}

0 comments on commit 8ff084a

Please sign in to comment.