Permalink
Browse files

Assigning T.init should only create a temporary when needed. (#2927)

  • Loading branch information...
JohanEngelen authored and kinke committed Dec 3, 2018
1 parent 7a2a598 commit 7966987178412c2541a98b4d57753d7d7799b1e7
Showing with 150 additions and 4 deletions.
  1. +24 −4 gen/toir.cpp
  2. +126 −0 tests/codegen/assign_struct_init_without_stack.d
@@ -502,10 +502,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);
@@ -0,0 +1,126 @@
// Tests minimal use of temporaries in non-optimized builds for struct and array assignment.
// Tests `.init` assignment in particular.
// RUN: %ldc -output-ll %s -of=%t.ll && FileCheck %s < %t.ll
void opaque();
FloatStruct opaque(FloatStruct);
FloatStruct opaqueret();
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 = opaqueret();
// opaque() could be accessing globalStruct, in-place assignment not possible.
// CHECK: sret_tmp = alloca %assign_struct_init_without_stack.FloatStruct
// CHECK: call {{.*}}opaqueret
// CHECK: call void @llvm.memcpy
// CHECK-NEXT: ret void
}
// CHECK-LABEL: define{{.*}} @{{.*}}_D32assign_struct_init_without_stack5localFZv
void local() {
FloatStruct local;
local = opaque(local);
// Not possible to do in-place assignment, so temporary must be created.
// CHECK: local = alloca %assign_struct_init_without_stack.FloatStruct
// CHECK: sret_tmp = alloca %assign_struct_init_without_stack.FloatStruct
// CHECK: call {{.*}}opaque
// 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 7966987

Please sign in to comment.