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

Avoid allocating array literals on initialization #493

Merged
merged 6 commits into from
Oct 9, 2013
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
103 changes: 103 additions & 0 deletions gen/arrays.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "aggregate.h"
#include "declaration.h"
#include "dsymbol.h"
#include "expression.h"
#include "init.h"
#include "module.h"
#include "mtype.h"
Expand Down Expand Up @@ -387,6 +388,108 @@ LLConstant* DtoConstArrayInitializer(ArrayInitializer* arrinit)
return DtoConstSlice(DtoConstSize_t(arrlen), gep, arrty);
}

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

bool isConstLiteral(ArrayLiteralExp* ale)
{
for (size_t i = 0; i < ale->elements->dim; ++i)
{
// We have to check specifically for '1', as SymOffExp is classified as
// '2' and the address of a local variable is not an LLVM constant.
if ((*ale->elements)[i]->isConst() != 1)
return false;
}
return true;
}

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

llvm::Constant* arrayLiteralToConst(IRState* p, ArrayLiteralExp* ale)
{
assert(isConstLiteral(ale) && "Array literal cannot be represented as a constant.");

// Build the initializer. We have to take care as due to unions in the
// element types (with different fields being initialized), we can end up
// with different types for the initializer values. In this case, we
// generate a packed struct constant instead of an array constant.
LLType *elementType = NULL;
bool differentTypes = false;

std::vector<LLConstant*> vals;
vals.reserve(ale->elements->dim);
for (unsigned i = 0; i < ale->elements->dim; ++i)
{
llvm::Constant *val = (*ale->elements)[i]->toConstElem(p);
if (!elementType)
elementType = val->getType();
else
differentTypes |= (elementType != val->getType());
vals.push_back(val);
}

if (differentTypes)
return llvm::ConstantStruct::getAnon(vals, true);

if (!elementType)
{
assert(ale->elements->dim == 0);
elementType = i1ToI8(voidToI8(DtoType(ale->type->toBasetype()->nextOf())));
return llvm::ConstantArray::get(LLArrayType::get(elementType, 0), vals);
}

llvm::ArrayType *t = llvm::ArrayType::get(elementType, ale->elements->dim);
return llvm::ConstantArray::get(t, vals);
}

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

void initializeArrayLiteral(IRState* p, ArrayLiteralExp* ale, LLValue* dstMem)
{
size_t elemCount = ale->elements->dim;

// Don't try to write nothing to a zero-element array, we might represent it
// as a null pointer.
if (elemCount == 0) return;

if (isConstLiteral(ale))
{
llvm::Constant* constarr = arrayLiteralToConst(p, ale);

// Emit a global for longer arrays, as an inline constant is always
// lowered to a series of movs or similar at the asm level. The
// optimizer can still decide to promote the memcpy intrinsic, so
// the cutoff merely affects compilation speed.
if (elemCount <= 4)
{
DtoStore(constarr, DtoBitCast(dstMem, getPtrToType(constarr->getType())));
}
else
{
llvm::GlobalVariable* gvar = new llvm::GlobalVariable(
*gIR->module,
constarr->getType(),
true,
LLGlobalValue::InternalLinkage,
constarr,
".arrayliteral"
);
DtoMemCpy(dstMem, gvar, DtoConstSize_t(getTypePaddedSize(constarr->getType())));
}
}
else
{
// Store the elements one by one.
for (size_t i = 0; i < elemCount; ++i)
{
DValue* e = (*ale->elements)[i]->toElem(p);

LLValue* elemAddr = DtoGEPi(dstMem, 0, i, "tmp", p->scopebb());
DVarValue* vv = new DVarValue(e->type, elemAddr);
DtoAssign(ale->loc, vv, e);
}
}
}

//////////////////////////////////////////////////////////////////////////////////////////
static LLValue* get_slice_ptr(DSliceValue* e, LLValue*& sz)
{
Expand Down
13 changes: 13 additions & 0 deletions gen/arrays.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
#include "gen/llvm.h"

struct ArrayInitializer;
struct ArrayLiteralExp;
class DSliceValue;
class DValue;
struct Expression;
struct IRState;
struct Loc;
struct Type;

Expand All @@ -32,6 +34,17 @@ LLType* DtoConstArrayInitializerType(ArrayInitializer* arrinit);
LLConstant* DtoConstArrayInitializer(ArrayInitializer* si);
LLConstant* DtoConstSlice(LLConstant* dim, LLConstant* ptr, Type *type = 0);

/// Returns whether the array literal can be evaluated to a (LLVM) constant.
bool isConstLiteral(ArrayLiteralExp* ale);

/// Returns the constant for the given array literal expression.
llvm::Constant* arrayLiteralToConst(IRState* p, ArrayLiteralExp* ale);

/// Initializes a chunk of memory with the contents of an array literal.
///
/// dstMem is expected to be a pointer to the array allocation.
void initializeArrayLiteral(IRState* p, ArrayLiteralExp* ale, LLValue* dstMem);

void DtoArrayCopySlices(DSliceValue* dst, DSliceValue* src);
void DtoArrayCopyToSlice(DSliceValue* dst, DValue* src);

Expand Down
171 changes: 46 additions & 125 deletions gen/toir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ DValue* AssignExp::toElem(IRState* p)
{
Logger::println("performing ref variable initialization");
// Note that the variable value is accessed directly (instead
// of via getLValue(), which would perform a load from the
// of via getLVal(), which would perform a load from the
// uninitialized location), and that rhs is stored as an l-value!
DVarValue* lhs = e1->toElem(p)->isVar();
assert(lhs);
Expand All @@ -508,6 +508,26 @@ DValue* AssignExp::toElem(IRState* p)
}
}

if (e1->op == TOKslice)
{
// Check if this is an initialization of a static array with an array
// literal that the frontend has foolishly rewritten into an
// assignment of a dynamic array literal to a slice.
Logger::println("performing static array literal assignment");
SliceExp * const se = static_cast<SliceExp *>(e1);
Type * const t2 = e2->type->toBasetype();
Type * const ta = se->e1->type->toBasetype();

if (se->lwr == NULL && ta->ty == Tsarray &&
e2->op == TOKarrayliteral &&
t2->nextOf()->mutableOf()->implicitConvTo(ta->nextOf()))
{
ArrayLiteralExp * const ale = static_cast<ArrayLiteralExp *>(e2);
initializeArrayLiteral(p, ale, se->e1->toElem(p)->getLVal());
return e1->toElem(p);
}
}

DValue* l = e1->toElem(p);
DValue* r = e2->toElem(p);

Expand Down Expand Up @@ -2850,9 +2870,9 @@ DValue* ArrayLiteralExp::toElem(IRState* p)
Type* elemType = arrayType->nextOf()->toBasetype();

// is dynamic ?
bool dyn = (arrayType->ty == Tarray);
bool const dyn = (arrayType->ty == Tarray);
// length
size_t len = elements->dim;
size_t const len = elements->dim;

// llvm target type
LLType* llType = DtoType(arrayType);
Expand All @@ -2872,95 +2892,34 @@ DValue* ArrayLiteralExp::toElem(IRState* p)
return new DSliceValue(type, DtoConstSize_t(0), getNullPtr(getPtrToType(llElemType)));
}

// dst pointer
LLValue* dstMem;
DSliceValue* dynSlice = NULL;
if(dyn)
{
dynSlice = DtoNewDynArray(loc, arrayType, new DConstValue(Type::tsize_t, DtoConstSize_t(len)), false);
dstMem = dynSlice->ptr;
}
else
dstMem = DtoRawAlloca(llStoType, 0, "arrayliteral");

// Check for const'ness
bool isAllConst = true;
for (size_t i = 0; i < len && isAllConst; ++i)
if (dyn)
{
Expression *expr = static_cast<Expression *>(elements->data[i]);
isAllConst = expr->isConst() == 1;
}

if (isAllConst)
{
// allocate room for initializers
std::vector<LLConstant *> initvals(len, NULL);

// true if array elements differ in type
bool mismatch = false;

// store elements
for (size_t i = 0; i < len; ++i)
if (arrayType->isImmutable() && isConstLiteral(this))
{
Expression *expr = static_cast<Expression *>(elements->data[i]);
llvm::Constant *c = expr->toConstElem(gIR);
if (llElemType != c->getType())
mismatch = true;
initvals[i] = c;
}
LLConstant *constarr;
if (mismatch)
constarr = LLConstantStruct::getAnon(gIR->context(), initvals); // FIXME should this pack?;
else
constarr = LLConstantArray::get(LLArrayType::get(llElemType, len), initvals);

#if LDC_LLVM_VER == 301
// Simply storing the constant array triggers a problem in LLVM 3.1.
// With -O3 the statement
// void[0] sa0 = (void[0]).init;
// is compiled to
// tail call void @llvm.trap()
// which is not what we want!
// Therefore a global variable is always used.
LLGlobalVariable *gvar = new LLGlobalVariable(*gIR->module, constarr->getType(), true, LLGlobalValue::InternalLinkage, constarr, ".constarrayliteral_init");
DtoMemCpy(dstMem, gvar, DtoConstSize_t(getTypePaddedSize(constarr->getType())));
#else
// If the type pointed to by dstMem is different from the array type
// then we must assign the value to a global variable.
if (constarr->getType() != dstMem->getType()->getPointerElementType())
{
LLGlobalVariable *gvar = new LLGlobalVariable(*gIR->module, constarr->getType(), true, LLGlobalValue::InternalLinkage, constarr, ".constarrayliteral_init");
DtoMemCpy(dstMem, gvar, DtoConstSize_t(getTypePaddedSize(constarr->getType())));
}
else
DtoStore(constarr, dstMem);
#endif
llvm::Constant* init = arrayLiteralToConst(p, this);
llvm::GlobalVariable* global = new llvm::GlobalVariable(
*gIR->module,
init->getType(),
true,
llvm::GlobalValue::InternalLinkage,
init,
".immutablearray"
);
return new DSliceValue(arrayType, DtoConstSize_t(elements->dim),
DtoBitCast(global, getPtrToType(llElemType)));
}

DSliceValue* dynSlice = DtoNewDynArray(loc, arrayType,
new DConstValue(Type::tsize_t, DtoConstSize_t(len)), false);
initializeArrayLiteral(p, this, DtoBitCast(dynSlice->ptr, getPtrToType(llStoType)));
return dynSlice;
}
else
{
// store elements
for (size_t i = 0; i < len; ++i)
{
Expression *expr = static_cast<Expression *>(elements->data[i]);
LLValue *elemAddr;
if(dyn)
elemAddr = DtoGEPi1(dstMem, i, "tmp", p->scopebb());
else
elemAddr = DtoGEPi(dstMem, 0, i, "tmp", p->scopebb());

// emulate assignment
DVarValue *vv = new DVarValue(expr->type, elemAddr);
DValue *e = expr->toElem(p);
DtoAssign(loc, vv, e);
}
llvm::Value* storage = DtoRawAlloca(llStoType, 0, "arrayliteral");
initializeArrayLiteral(p, this, storage);
return new DImValue(type, storage);
}

// return storage directly ?
if (!dyn)
return new DImValue(type, dstMem);

// return slice
return dynSlice;
}

//////////////////////////////////////////////////////////////////////////////////////////
Expand All @@ -2980,45 +2939,7 @@ LLConstant* ArrayLiteralExp::toConstElem(IRState* p)
// dynamic arrays can occur here as well ...
bool dyn = (bt->ty != Tsarray);

// Build the initializer. We have to take care as due to unions in the
// element types (with different fields being initialized), we can end up
// with different types for the initializer values. In this case, we
// generate a packed struct constant instead of an array constant.
LLType *elementType = NULL;
bool differentTypes = false;

std::vector<LLConstant*> vals;
vals.reserve(elements->dim);
for (unsigned i = 0; i < elements->dim; ++i)
{
LLConstant *val = (*elements)[i]->toConstElem(p);
if (!elementType)
elementType = val->getType();
else
differentTypes |= (elementType != val->getType());
vals.push_back(val);
}

LLConstant *initval;
if (differentTypes)
{
std::vector<llvm::Type*> types;
types.reserve(elements->dim);
for (unsigned i = 0; i < elements->dim; ++i)
types.push_back(vals[i]->getType());
LLStructType *t = llvm::StructType::get(gIR->context(), types, true);
initval = LLConstantStruct::get(t, vals);
}
else if (!elementType)
{
assert(elements->dim == 0);
initval = LLConstantArray::get(arrtype, vals);
}
else
{
LLArrayType *t = LLArrayType::get(elementType, elements->dim);
initval = LLConstantArray::get(t, vals);
}
llvm::Constant* initval = arrayLiteralToConst(p, this);

// if static array, we're done
if (!dyn)
Expand Down