//===-- llvmhelpers.cpp ---------------------------------------------------===// // // LDC – the LLVM D compiler // // This file is distributed under the BSD-style LDC license. See the LICENSE // file for details. // //===----------------------------------------------------------------------===// #include "gen/llvmhelpers.h" #include "dmd/declaration.h" #include "dmd/errors.h" #include "dmd/expression.h" #include "dmd/id.h" #include "dmd/identifier.h" #include "dmd/init.h" #include "dmd/module.h" #include "dmd/template.h" #include "gen/abi/abi.h" #include "gen/arrays.h" #include "gen/classes.h" #include "gen/complex.h" #include "gen/dvalue.h" #include "gen/dynamiccompile.h" #include "gen/funcgenstate.h" #include "gen/functions.h" #include "gen/irstate.h" #include "gen/llvm.h" #include "gen/logger.h" #include "gen/nested.h" #include "gen/mangling.h" #include "gen/pragma.h" #include "gen/runtime.h" #include "gen/tollvm.h" #include "gen/typinf.h" #include "gen/uda.h" #include "ir/irdsymbol.h" #include "ir/irfunction.h" #include "ir/irmodule.h" #include "ir/irtypeaggr.h" #include "ir/irtypeclass.h" #include "llvm/MC/MCAsmInfo.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/ManagedStatic.h" #include "llvm/Target/TargetMachine.h" #include "llvm/Transforms/Utils/ModuleUtils.h" #include #include #include using namespace dmd; llvm::cl::opt clThreadModel( "fthread-model", llvm::cl::ZeroOrMore, llvm::cl::desc("Thread model"), llvm::cl::init(llvm::GlobalVariable::GeneralDynamicTLSModel), llvm::cl::values(clEnumValN(llvm::GlobalVariable::GeneralDynamicTLSModel, "global-dynamic", "Global dynamic TLS model (default)"), clEnumValN(llvm::GlobalVariable::LocalDynamicTLSModel, "local-dynamic", "Local dynamic TLS model"), clEnumValN(llvm::GlobalVariable::InitialExecTLSModel, "initial-exec", "Initial exec TLS model"), clEnumValN(llvm::GlobalVariable::LocalExecTLSModel, "local-exec", "Local exec TLS model"))); /****************************************************************************** * Simple Triple helpers for DFE * TODO: find better location for this ******************************************************************************/ bool isTargetWindowsMSVC() { return global.params.targetTriple->isWindowsMSVCEnvironment(); } /****************************************************************************** * Global context ******************************************************************************/ static llvm::ManagedStatic GlobalContext; llvm::LLVMContext &getGlobalContext() { return *GlobalContext; } /****************************************************************************** * DYNAMIC MEMORY HELPERS ******************************************************************************/ LLValue *DtoNew(const Loc &loc, Type *newtype) { // get runtime function llvm::Function *fn = getRuntimeFunction(loc, gIR->module, "_d_allocmemoryT"); // get type info LLConstant *ti = DtoTypeInfoOf(loc, newtype); assert(isaPointer(ti)); // call runtime allocator return gIR->CreateCallOrInvoke(fn, ti, ".gc_mem"); } void DtoDeleteMemory(const Loc &loc, DValue *ptr) { llvm::Function *fn = getRuntimeFunction(loc, gIR->module, "_d_delmemory"); LLValue *lval = (ptr->isLVal() ? DtoLVal(ptr) : makeLValue(loc, ptr)); gIR->CreateCallOrInvoke(fn, lval); } void DtoDeleteStruct(const Loc &loc, DValue *ptr) { llvm::Function *fn = getRuntimeFunction(loc, gIR->module, "_d_delstruct"); LLValue *lval = (ptr->isLVal() ? DtoLVal(ptr) : makeLValue(loc, ptr)); gIR->CreateCallOrInvoke(fn, lval, DtoTypeInfoOf(loc, ptr->type->nextOf())); } void DtoDeleteClass(const Loc &loc, DValue *inst) { llvm::Function *fn = getRuntimeFunction(loc, gIR->module, "_d_delclass"); LLValue *lval = (inst->isLVal() ? DtoLVal(inst) : makeLValue(loc, inst)); gIR->CreateCallOrInvoke(fn, lval); } void DtoDeleteInterface(const Loc &loc, DValue *inst) { llvm::Function *fn = getRuntimeFunction(loc, gIR->module, "_d_delinterface"); LLValue *lval = (inst->isLVal() ? DtoLVal(inst) : makeLValue(loc, inst)); gIR->CreateCallOrInvoke(fn, lval); } void DtoDeleteArray(const Loc &loc, DValue *arr) { llvm::Function *fn = getRuntimeFunction(loc, gIR->module, "_d_delarray_t"); // the TypeInfo argument must be null if the type has no dtor Type *elementType = arr->type->nextOf(); bool hasDtor = (elementType->toBasetype()->ty == TY::Tstruct && elementType->needsDestruction()); LLValue *typeInfo = !hasDtor ? getNullPtr() : DtoTypeInfoOf(loc, elementType); LLValue *lval = (arr->isLVal() ? DtoLVal(arr) : makeLValue(loc, arr)); gIR->CreateCallOrInvoke(fn, lval, typeInfo); } /****************************************************************************** * ALIGNMENT HELPERS ******************************************************************************/ unsigned DtoAlignment(Type *type) { const auto alignment = type->alignment(); if (!alignment.isDefault() && !alignment.isPack()) return alignment.get(); auto ts = type->toBasetype()->isTypeStruct(); return ts && !ts->sym->members ? 0 // opaque struct : type->alignsize(); } unsigned DtoAlignment(VarDeclaration *vd) { const unsigned typeAlignment = DtoAlignment(vd->type); if (vd->alignment.isDefault()) return typeAlignment; const unsigned explicitAlignValue = vd->alignment.get(); if (vd->alignment.isPack()) return std::min(typeAlignment, explicitAlignValue); return explicitAlignValue; } /****************************************************************************** * ALLOCA HELPERS ******************************************************************************/ llvm::AllocaInst *DtoAlloca(Type *type, const char *name) { return DtoRawAlloca(DtoMemType(type), DtoAlignment(type), name); } llvm::AllocaInst *DtoAlloca(VarDeclaration *vd, const char *name) { return DtoRawAlloca(DtoMemType(vd->type), DtoAlignment(vd), name); } llvm::AllocaInst *DtoArrayAlloca(Type *type, unsigned arraysize, const char *name) { LLType *lltype = DtoType(type); auto ai = new llvm::AllocaInst( lltype, gIR->module.getDataLayout().getAllocaAddrSpace(), DtoConstUint(arraysize), name, gIR->topallocapoint()); if (auto alignment = DtoAlignment(type)) { ai->setAlignment(llvm::Align(alignment)); } return ai; } llvm::AllocaInst *DtoRawAlloca(LLType *lltype, size_t alignment, const char *name) { auto ai = new llvm::AllocaInst( lltype, gIR->module.getDataLayout().getAllocaAddrSpace(), name, gIR->topallocapoint()); if (alignment) { ai->setAlignment(llvm::Align(alignment)); } return ai; } LLValue *DtoAllocaDump(DValue *val, const char *name) { return DtoAllocaDump(val, val->type, name); } LLValue *DtoAllocaDump(DValue *val, int alignment, const char *name) { return DtoAllocaDump(val, DtoType(val->type), alignment, name); } LLValue *DtoAllocaDump(DValue *val, Type *asType, const char *name) { return DtoAllocaDump(val, DtoType(asType), DtoAlignment(asType), name); } LLValue *DtoAllocaDump(DValue *val, LLType *asType, int alignment, const char *name) { if (val->isLVal()) { LLValue *lval = DtoLVal(val); LLType *asMemType = i1ToI8(voidToI8(asType)); LLValue *copy = DtoRawAlloca(asMemType, alignment, name); const auto minSize = std::min(getTypeAllocSize(DtoType(val->type)), getTypeAllocSize(asMemType)); const auto minAlignment = std::min(DtoAlignment(val->type), static_cast(alignment)); DtoMemCpy(copy, lval, DtoConstSize_t(minSize), minAlignment); // TODO: zero-out any remaining bytes? return copy; } return DtoAllocaDump(DtoRVal(val), asType, alignment, name); } LLValue *DtoAllocaDump(LLValue *val, int alignment, const char *name) { return DtoAllocaDump(val, val->getType(), alignment, name); } LLValue *DtoAllocaDump(LLValue *val, Type *asType, const char *name) { return DtoAllocaDump(val, DtoType(asType), DtoAlignment(asType), name); } LLValue *DtoAllocaDump(LLValue *val, LLType *asType, int alignment, const char *name) { LLType *memType = i1ToI8(voidToI8(val->getType())); LLType *asMemType = i1ToI8(voidToI8(asType)); LLType *allocaType = (getTypeStoreSize(memType) <= getTypeAllocSize(asMemType) ? asMemType : memType); LLValue *mem = DtoRawAlloca(allocaType, alignment, name); DtoStoreZextI8(val, mem); return mem; } /****************************************************************************** * ASSERT HELPERS ******************************************************************************/ void DtoAssert(Module *M, const Loc &loc, DValue *msg) { // func const char *fname = msg ? "_d_assert_msg" : "_d_assert"; llvm::Function *fn = getRuntimeFunction(loc, gIR->module, fname); // Arguments llvm::SmallVector args; // msg param if (msg) { args.push_back(DtoRVal(msg)); } // file param args.push_back(DtoModuleFileName(M, loc)); // line param args.push_back(DtoConstUint(loc.linnum())); // call gIR->CreateCallOrInvoke(fn, args); // after assert is always unreachable gIR->ir->CreateUnreachable(); } void DtoCAssert(Module *M, const Loc &loc, LLValue *msg) { const auto &triple = *global.params.targetTriple; const auto file = DtoConstCString(loc.filename() ? loc.filename() : M->srcfile.toChars()); const auto line = DtoConstUint(loc.linnum()); const auto fn = getCAssertFunction(loc, gIR->module); llvm::SmallVector args; if (triple.isOSDarwin()) { const auto irFunc = gIR->func(); const auto funcName = irFunc && irFunc->decl ? irFunc->decl->toPrettyChars() : ""; args.push_back(DtoConstCString(funcName)); args.push_back(file); args.push_back(line); args.push_back(msg); } else if (triple.isOSSolaris() || triple.isMusl() || global.params.isUClibcEnvironment || triple.isGNUEnvironment()) { const auto irFunc = gIR->func(); const auto funcName = (irFunc && irFunc->decl) ? irFunc->decl->toPrettyChars() : ""; args.push_back(msg); args.push_back(file); args.push_back(line); args.push_back(DtoConstCString(funcName)); } else if (triple.getEnvironment() == llvm::Triple::Android) { args.push_back(file); args.push_back(line); args.push_back(msg); } else if (global.params.isNewlibEnvironment) { const auto irFunc = gIR->func(); const auto funcName = irFunc && irFunc->decl ? irFunc->decl->toPrettyChars() : ""; args.push_back(file); args.push_back(line); args.push_back(DtoConstCString(funcName)); args.push_back(msg); } else { args.push_back(msg); args.push_back(file); args.push_back(line); } gIR->CreateCallOrInvoke(fn, args); gIR->ir->CreateUnreachable(); } /****************************************************************************** * THROW HELPER ******************************************************************************/ void DtoThrow(const Loc &loc, DValue *e) { LLFunction *fn = getRuntimeFunction(loc, gIR->module, "_d_throw_exception"); LLValue *arg = DtoRVal(e); gIR->CreateCallOrInvoke(fn, arg); gIR->ir->CreateUnreachable(); llvm::BasicBlock *bb = gIR->insertBB("afterthrow"); gIR->ir->SetInsertPoint(bb); } /****************************************************************************** * MODULE FILE NAME ******************************************************************************/ LLConstant *DtoModuleFileName(Module *M, const Loc &loc) { return DtoConstString(loc.filename() ? loc.filename() : M->srcfile.toChars()); } /****************************************************************************** * GOTO HELPER ******************************************************************************/ void DtoGoto(const Loc &loc, LabelDsymbol *target) { assert(!gIR->scopereturned()); LabelStatement *lblstmt = target->statement; if (!lblstmt) { error(loc, "the label `%s` does not exist", target->ident->toChars()); fatal(); } gIR->funcGen().jumpTargets.jumpToLabel(loc, target->ident); } /****************************************************************************** * ASSIGNMENT HELPER (store this in that) ******************************************************************************/ // is this a good approach at all ? void DtoAssign(const Loc &loc, DValue *lhs, DValue *rhs, EXP op, bool canSkipPostblit) { IF_LOG Logger::println("DtoAssign()"); LOG_SCOPE; Type *t = lhs->type->toBasetype(); assert(t->ty != TY::Tvoid && "Cannot assign values of type void."); if (t->ty == TY::Tnoreturn) { // nothing to assign return; } if (auto bfLVal = lhs->isBitFieldLVal()) { bfLVal->store(DtoRVal(rhs)); return; } if (t->ty == TY::Tbool) { DtoStoreZextI8(DtoRVal(rhs), DtoLVal(lhs)); } else if (t->ty == TY::Tstruct) { // don't copy anything to empty structs if (static_cast(t)->sym->fields.length > 0) { llvm::Value *src = DtoLVal(rhs); llvm::Value *dst = DtoLVal(lhs); // Check whether source and destination values are the same at compile // time as to not emit an invalid (overlapping) memcpy on trivial // struct self-assignments like 'A a; a = a;'. if (src != dst) DtoMemCpy(DtoType(lhs->type), dst, src); } } else if (t->ty == TY::Tarray || t->ty == TY::Tsarray) { DtoArrayAssign(loc, lhs, rhs, op, canSkipPostblit); } else if (t->ty == TY::Tdelegate) { LLValue *l = DtoLVal(lhs); LLValue *r = DtoRVal(rhs); IF_LOG { Logger::cout() << "lhs: " << *l << '\n'; Logger::cout() << "rhs: " << *r << '\n'; } DtoStore(r, l); } else if (t->ty == TY::Tclass) { assert(rhs->type->toBasetype()->ty == TY::Tclass); LLValue *l = DtoLVal(lhs); LLValue *r = DtoRVal(rhs); IF_LOG { Logger::cout() << "l : " << *l << '\n'; Logger::cout() << "r : " << *r << '\n'; } DtoStore(r, l); } else if (t->isComplex()) { LLValue *dst = DtoLVal(lhs); LLValue *src = DtoRVal(DtoCast(loc, rhs, lhs->type)); DtoStore(src, dst); } else { LLValue *l = DtoLVal(lhs); LLValue *r = DtoRVal(rhs); IF_LOG { Logger::cout() << "lhs: " << *l << '\n'; Logger::cout() << "rhs: " << *r << '\n'; } LLType *lit = DtoType(lhs->type); if (r->getType() != lit) { r = DtoRVal(DtoCast(loc, rhs, lhs->type)); IF_LOG { Logger::println("Type mismatch, really assigning:"); LOG_SCOPE Logger::cout() << "lhs: " << *l << '\n'; Logger::cout() << "rhs: " << *r << '\n'; } #if 1 if (r->getType() != lit) { // It's weird but it happens. TODO: try to remove this hack r = DtoBitCast(r, lit); } #else assert(r->getType() == lit); #endif } gIR->ir->CreateStore(r, l); } } /****************************************************************************** * NULL VALUE HELPER ******************************************************************************/ DValue *DtoNullValue(Type *type, Loc loc) { Type *basetype = type->toBasetype(); TY basety = basetype->ty; LLType *lltype = DtoType(basetype); // complex, needs to be first since complex are also floating if (basetype->isComplex()) { LLType *basefp = DtoComplexBaseType(basetype); LLValue *res = DtoAggrPair(DtoType(type), LLConstant::getNullValue(basefp), LLConstant::getNullValue(basefp)); return new DImValue(type, res); } // integer, floating, pointer, assoc array, delegate and class have no special // representation if (basetype->isIntegral() || basetype->isFloating() || basety == TY::Tpointer || basety == TY::Tnull || basety == TY::Tclass || basety == TY::Tdelegate || basety == TY::Taarray) { return new DNullValue(type, LLConstant::getNullValue(lltype)); } // dynamic array if (basety == TY::Tarray) { LLValue *len = DtoConstSize_t(0); LLValue *ptr = getNullPtr(); return new DSliceValue(type, len, ptr); } error(loc, "`null` not known for type `%s`", type->toChars()); fatal(); } /****************************************************************************** * CASTING HELPERS ******************************************************************************/ DValue *DtoCastInt(const Loc &loc, DValue *val, Type *_to) { LLType *tolltype = DtoType(_to); Type *to = _to->toBasetype(); Type *from = val->type->toBasetype(); assert(from->isIntegral()); LLValue *rval = DtoRVal(val); if (rval->getType() == tolltype) { return new DImValue(_to, rval); } size_t fromsz = size(from); size_t tosz = size(to); if (to->ty == TY::Tbool) { LLValue *zero = LLConstantInt::get(rval->getType(), 0, false); rval = gIR->ir->CreateICmpNE(rval, zero); } else if (to->isIntegral()) { if (fromsz < tosz || from->ty == TY::Tbool) { IF_LOG Logger::cout() << "cast to: " << *tolltype << '\n'; if (isLLVMUnsigned(from) || from->ty == TY::Tbool) { rval = new llvm::ZExtInst(rval, tolltype, "", gIR->scopebb()); } else { rval = new llvm::SExtInst(rval, tolltype, "", gIR->scopebb()); } } else if (fromsz > tosz) { rval = new llvm::TruncInst(rval, tolltype, "", gIR->scopebb()); } else { rval = DtoBitCast(rval, tolltype); } } else if (to->isComplex()) { return DtoComplex(loc, to, val); } else if (to->isFloating()) { if (from->isUnsigned()) { rval = new llvm::UIToFPInst(rval, tolltype, "", gIR->scopebb()); } else { rval = new llvm::SIToFPInst(rval, tolltype, "", gIR->scopebb()); } } else if (to->ty == TY::Tpointer) { IF_LOG Logger::cout() << "cast pointer: " << *tolltype << '\n'; rval = gIR->ir->CreateIntToPtr(rval, tolltype); } else { error(loc, "invalid cast from `%s` to `%s`", val->type->toChars(), _to->toChars()); fatal(); } return new DImValue(_to, rval); } DValue *DtoCastPtr(const Loc &loc, DValue *val, Type *to) { LLType *tolltype = DtoType(to); Type *totype = to->toBasetype(); Type *fromtype = val->type->toBasetype(); (void)fromtype; assert(fromtype->ty == TY::Tpointer || fromtype->ty == TY::Tfunction); LLValue *rval; if (totype->ty == TY::Tpointer || totype->ty == TY::Tclass || totype->ty == TY::Taarray) { LLValue *src = DtoRVal(val); IF_LOG { Logger::cout() << "src: " << *src << '\n'; Logger::cout() << "to type: " << *tolltype << '\n'; } rval = DtoBitCast(src, tolltype); } else if (totype->ty == TY::Tbool) { LLValue *src = DtoRVal(val); LLValue *zero = LLConstant::getNullValue(src->getType()); rval = gIR->ir->CreateICmpNE(src, zero); } else if (totype->isIntegral()) { rval = new llvm::PtrToIntInst(DtoRVal(val), tolltype, "", gIR->scopebb()); } else { error(loc, "invalid cast from `%s` to `%s`", val->type->toChars(), to->toChars()); fatal(); } return new DImValue(to, rval); } DValue *DtoCastFloat(const Loc &loc, DValue *val, Type *to) { if (val->type == to) { return val; } LLType *tolltype = DtoType(to); Type *totype = to->toBasetype(); Type *fromtype = val->type->toBasetype(); assert(fromtype->isFloating()); size_t fromsz = size(fromtype); size_t tosz = size(totype); LLValue *rval; if (totype->ty == TY::Tbool) { rval = DtoRVal(val); LLValue *zero = LLConstant::getNullValue(rval->getType()); rval = gIR->ir->CreateFCmpUNE(rval, zero); } else if (totype->isComplex()) { return DtoComplex(loc, to, val); } else if (totype->isFloating()) { if (fromsz == tosz) { rval = DtoRVal(val); assert(rval->getType() == tolltype); } else if (fromsz < tosz) { rval = new llvm::FPExtInst(DtoRVal(val), tolltype, "", gIR->scopebb()); } else if (fromsz > tosz) { rval = new llvm::FPTruncInst(DtoRVal(val), tolltype, "", gIR->scopebb()); } else { error(loc, "invalid cast from `%s` to `%s`", val->type->toChars(), to->toChars()); fatal(); } } else if (totype->isIntegral()) { if (totype->isUnsigned()) { rval = new llvm::FPToUIInst(DtoRVal(val), tolltype, "", gIR->scopebb()); } else { rval = new llvm::FPToSIInst(DtoRVal(val), tolltype, "", gIR->scopebb()); } } else { error(loc, "invalid cast from `%s` to `%s`", val->type->toChars(), to->toChars()); fatal(); } return new DImValue(to, rval); } DValue *DtoCastDelegate(const Loc &loc, DValue *val, Type *to) { if (to->toBasetype()->ty == TY::Tdelegate) { return DtoPaintType(loc, val, to); } if (to->toBasetype()->ty == TY::Tbool) { return new DImValue( to, DtoDelegateEquals(EXP::notEqual, DtoRVal(val), nullptr)); } error(loc, "invalid cast from `%s` to `%s`", val->type->toChars(), to->toChars()); fatal(); } DValue *DtoCastVector(const Loc &loc, DValue *val, Type *to) { assert(val->type->toBasetype()->ty == TY::Tvector); Type *totype = to->toBasetype(); LLType *tolltype = DtoType(to); if (totype->ty == TY::Tsarray) { // Reinterpret-cast without copy if the source vector is in memory. if (val->isLVal()) { LLValue *vector = DtoLVal(val); IF_LOG Logger::cout() << "src: " << *vector << " to type: " << *tolltype << " (casting address)\n"; return new DLValue(to, vector); } LLValue *vector = DtoRVal(val); IF_LOG Logger::cout() << "src: " << *vector << " to type: " << *tolltype << " (creating temporary)\n"; LLValue *array = DtoAllocaDump(vector, tolltype, DtoAlignment(val->type)); return new DLValue(to, array); } if (totype->ty == TY::Tvector && size(to) == size(val->type)) { return new DImValue(to, DtoBitCast(DtoRVal(val), tolltype)); } error(loc, "invalid cast from `%s` to `%s`", val->type->toChars(), to->toChars()); fatal(); } DValue *DtoCastStruct(const Loc &loc, DValue *val, Type *to) { Type *const totype = to->toBasetype(); if (totype->ty == TY::Tstruct) { // This a cast to repaint a struct to another type, which the language // allows for identical layouts (opCast() and so on have been lowered // earlier by the frontend). llvm::Value *lval = DtoLVal(val); return new DLValue(to, lval); } error(loc, "Internal Compiler Error: Invalid struct cast from `%s` to `%s`", val->type->toChars(), to->toChars()); fatal(); } DValue *DtoCast(const Loc &loc, DValue *val, Type *to) { Type *fromtype = val->type->toBasetype(); Type *totype = to->toBasetype(); if (fromtype->ty == TY::Taarray) { if (totype->ty == TY::Taarray) { // reinterpret-cast keeping lvalue-ness, IR types will match up if (val->isLVal()) return new DLValue(to, DtoLVal(val)); return new DImValue(to, DtoRVal(val)); } // DMD allows casting AAs to void*, even if they are internally // implemented as structs. if (totype->ty == TY::Tpointer) { IF_LOG Logger::println("Casting AA to pointer."); return new DImValue(to, DtoRVal(val)); } if (totype->ty == TY::Tbool) { IF_LOG Logger::println("Casting AA to bool."); LLValue *rval = DtoRVal(val); LLValue *zero = LLConstant::getNullValue(rval->getType()); return new DImValue(to, gIR->ir->CreateICmpNE(rval, zero)); } } if (fromtype->equals(totype)) { return val; } IF_LOG Logger::println("Casting from '%s' to '%s'", fromtype->toChars(), to->toChars()); LOG_SCOPE; if (fromtype->ty == TY::Tvector) { // First, handle vector types (which can also be isIntegral()). return DtoCastVector(loc, val, to); } if (fromtype->isIntegral()) { return DtoCastInt(loc, val, to); } if (fromtype->isComplex()) { return DtoCastComplex(loc, val, to); } if (fromtype->isFloating()) { return DtoCastFloat(loc, val, to); } switch (fromtype->ty) { case TY::Tclass: return DtoCastClass(loc, val, to); case TY::Tarray: case TY::Tsarray: return DtoCastArray(loc, val, to); case TY::Tpointer: case TY::Tfunction: return DtoCastPtr(loc, val, to); case TY::Tdelegate: return DtoCastDelegate(loc, val, to); case TY::Tstruct: return DtoCastStruct(loc, val, to); case TY::Tnull: case TY::Tnoreturn: return DtoNullValue(to, loc); default: error(loc, "invalid cast from `%s` to `%s`", val->type->toChars(), to->toChars()); fatal(); } } //////////////////////////////////////////////////////////////////////////////// DValue *DtoPaintType(const Loc &loc, DValue *val, Type *to) { Type *from = val->type->toBasetype(); IF_LOG Logger::println("repainting from '%s' to '%s'", from->toChars(), to->toChars()); Type *tb = to->toBasetype(); if (val->isLVal()) { return new DLValue(to, DtoLVal(val)); } if (auto slice = val->isSlice()) { if (tb->ty == TY::Tarray) { return new DSliceValue(to, slice->getLength(), slice->getPtr()); } } else if (auto func = val->isFunc()) { if (tb->ty == TY::Tdelegate) { return new DFuncValue(to, func->func, DtoRVal(func), func->vthis); } } else { // generic rvalue LLValue *rval = DtoRVal(val); LLType *tll = DtoType(tb); if (rval->getType() == tll) { return new DImValue(to, rval); } if (rval->getType()->isPointerTy() && tll->isPointerTy()) { return new DImValue(to, rval); } if (from->ty == TY::Tdelegate && tb->ty == TY::Tdelegate) { LLValue *context = gIR->ir->CreateExtractValue(rval, 0, ".context"); LLValue *funcptr = gIR->ir->CreateExtractValue(rval, 1, ".funcptr"); return new DImValue(to, DtoAggrPair(context, funcptr)); } } error(loc, "ICE: unexpected type repaint from `%s` to `%s`", from->toChars(), to->toChars()); fatal(); } /****************************************************************************** * PROCESSING QUEUE HELPERS ******************************************************************************/ void DtoResolveDsymbol(Dsymbol *dsym) { if (StructDeclaration *sd = dsym->isStructDeclaration()) { DtoResolveStruct(sd); } else if (ClassDeclaration *cd = dsym->isClassDeclaration()) { DtoResolveClass(cd); } else if (FuncDeclaration *fd = dsym->isFuncDeclaration()) { DtoResolveFunction(fd); } else if (TypeInfoDeclaration *tid = dsym->isTypeInfoDeclaration()) { DtoResolveTypeInfo(tid); } else if (VarDeclaration *vd = dsym->isVarDeclaration()) { DtoResolveVariable(vd); } } void DtoResolveVariable(VarDeclaration *vd) { if (auto tid = vd->isTypeInfoDeclaration()) { DtoResolveTypeInfo(tid); return; } IF_LOG Logger::println("DtoResolveVariable(%s)", vd->toPrettyChars()); LOG_SCOPE; // just forward aliases // TODO: Is this required here or is the check in VarDeclaration::codegen // sufficient? if (vd->aliasTuple) { Logger::println("aliasTuple"); DtoResolveDsymbol(vd->aliasTuple); return; } if (AggregateDeclaration *ad = vd->isMember()) { DtoResolveDsymbol(ad); } // global variable if (vd->isDataseg()) { Logger::println("data segment"); assert(!(vd->storage_class & STCmanifest) && "manifest constant being codegen'd!"); // don't duplicate work if (vd->ir->isResolved()) { return; } vd->ir->setDeclared(); auto irGlobal = getIrGlobal(vd, true); irGlobal->getValue(); } } /****************************************************************************** * DECLARATION EXP HELPER ******************************************************************************/ // TODO: Merge with DtoRawVarDeclaration! void DtoVarDeclaration(VarDeclaration *vd) { assert(!vd->isDataseg() && "Statics/globals are handled in DtoDeclarationExp."); assert(!vd->aliasTuple && "Aliases are handled in DtoDeclarationExp."); IF_LOG Logger::println("DtoVarDeclaration(vdtype = %s)", vd->type->toChars()); LOG_SCOPE if (vd->nestedrefs.length) { IF_LOG Logger::println( "has nestedref set (referenced by nested function/delegate)"); // A variable may not be really nested even if nextedrefs is not empty // in case it is referenced by a function inside __traits(compile) or // typeof. // assert(vd->ir->irLocal && "irLocal is expected to be already set by // DtoCreateNestedContext"); } if (isIrLocalCreated(vd)) { // Nothing to do if it has already been allocated. } else if (gIR->func()->sretArg && ((gIR->func()->decl->isNRVO() && gIR->func()->decl->nrvo_var == vd) || (vd->isResult() && !isSpecialRefVar(vd)))) { // Named Return Value Optimization (NRVO): // T f() { // T ret; // &ret == hidden pointer // ret = ... // return ret; // NRVO. // } assert(!isSpecialRefVar(vd) && "Can this happen?"); getIrLocal(vd, true)->value = gIR->func()->sretArg; gIR->DBuilder.EmitLocalVariable(gIR->func()->sretArg, vd); } else { // normal stack variable, allocate storage on the stack if it has not // already been done IrLocal *irLocal = getIrLocal(vd, true); Type *type = isSpecialRefVar(vd) ? pointerTo(vd->type) : vd->type; // We also allocate a variable for zero-sized variables, because they are technically not `null` when loaded. // The x86_64 ABI "loads" zero-sized function arguments, and without an allocation ASan will report an error (Github #4816). llvm::Value *allocainst; bool isRealAlloca = false; LLType *lltype = DtoType(type); // void for noreturn if (lltype->isVoidTy()) { allocainst = getNullPtr(); } else if (type != vd->type) { allocainst = DtoAlloca(type, vd->toChars()); isRealAlloca = true; } else { allocainst = DtoAlloca(vd, vd->toChars()); isRealAlloca = true; } irLocal->value = allocainst; if (!lltype->isVoidTy()) gIR->DBuilder.EmitLocalVariable(allocainst, vd); // Lifetime annotation is only valid on alloca. if (isRealAlloca) { // The lifetime of a stack variable starts from the point it is declared gIR->funcGen().localVariableLifetimeAnnotator.addLocalVariable( allocainst, DtoConstUlong(size(type))); } } IF_LOG Logger::cout() << "llvm value for decl: " << *getIrLocal(vd)->value << '\n'; if (vd->_init) { if (ExpInitializer *ex = vd->_init->isExpInitializer()) { // TODO: Refactor this so that it doesn't look like toElem has no effect. Logger::println("expression initializer"); toElem(ex->exp); } } } DValue *DtoDeclarationExp(Dsymbol *declaration) { IF_LOG Logger::print("DtoDeclarationExp: %s\n", declaration->toChars()); LOG_SCOPE; if (VarDeclaration *vd = declaration->isVarDeclaration()) { Logger::println("VarDeclaration"); // if aliasTuple is set, this VarDecl is redone as an alias to another symbol // this seems to be done to rewrite Tuple!(...) v; // as a TupleDecl that contains a bunch of individual VarDecls if (vd->aliasTuple) { return DtoDeclarationExp(vd->aliasTuple); } if (vd->storage_class & STCmanifest) { IF_LOG Logger::println("Manifest constant, nothing to do."); return nullptr; } // static if (vd->isDataseg()) { Declaration_codegen(vd); } else { DtoVarDeclaration(vd); } return makeVarDValue(vd->type, vd); } if (StructDeclaration *s = declaration->isStructDeclaration()) { Logger::println("StructDeclaration"); Declaration_codegen(s); } else if (FuncDeclaration *f = declaration->isFuncDeclaration()) { Logger::println("FuncDeclaration"); Declaration_codegen(f); } else if (ClassDeclaration *e = declaration->isClassDeclaration()) { Logger::println("ClassDeclaration"); Declaration_codegen(e); } else if (AttribDeclaration *a = declaration->isAttribDeclaration()) { Logger::println("AttribDeclaration"); // choose the right set in case this is a conditional declaration if (auto d = include(a, nullptr)) { for (unsigned i = 0; i < d->length; ++i) { DtoDeclarationExp((*d)[i]); } } } else if (TemplateMixin *m = declaration->isTemplateMixin()) { Logger::println("TemplateMixin"); for (Dsymbol *mdsym : *m->members) { DtoDeclarationExp(mdsym); } } else if (TupleDeclaration *tupled = declaration->isTupleDeclaration()) { Logger::println("TupleDeclaration"); assert(tupled->isexp && "Non-expression tuple decls not handled yet."); assert(tupled->objects); for (unsigned i = 0; i < tupled->objects->length; ++i) { auto exp = static_cast((*tupled->objects)[i]); DtoDeclarationExp(exp->s); } } else { // Do nothing for template/alias/enum declarations and static // assertions. We cannot detect StaticAssert without RTTI, so don't // even bother to check. IF_LOG Logger::println("Ignoring Symbol: %s", declaration->kind()); } return nullptr; } // does pretty much the same as DtoDeclarationExp, except it doesn't initialize, // and only handles var declarations LLValue *DtoRawVarDeclaration(VarDeclaration *var, LLValue *addr) { // we don't handle globals with this one assert(!var->isDataseg()); // we don't handle aliases either assert(!var->aliasTuple); IrLocal *irLocal = isIrLocalCreated(var) ? getIrLocal(var) : nullptr; // alloca if necessary if (!addr && (!irLocal || !irLocal->value)) { addr = DtoAlloca(var, var->toChars()); // add debug info if (!irLocal) { irLocal = getIrLocal(var, true); } gIR->DBuilder.EmitLocalVariable(addr, var); } // nested variable? // A variable may not be really nested even if nextedrefs is not empty // in case it is referenced by a function inside __traits(compile) or typeof. if (var->nestedrefs.length && isIrLocalCreated(var)) { if (!irLocal->value) { assert(addr); irLocal->value = addr; } else { assert(!addr || addr == irLocal->value); } } // normal local variable else { // if this already has storage, it must've been handled already if (irLocal->value) { if (addr && addr != irLocal->value) { // This can happen, for example, in scope(exit) blocks which // are translated to IR multiple times. // That *should* only happen after the first one is completely done // though, so just set the address. IF_LOG { Logger::println("Replacing LLVM address of %s", var->toChars()); LOG_SCOPE; Logger::cout() << "Old val: " << *irLocal->value << '\n'; Logger::cout() << "New val: " << *addr << '\n'; } irLocal->value = addr; } return addr; } assert(addr); irLocal->value = addr; } // return the alloca return irLocal->value; } /****************************************************************************** * INITIALIZER HELPERS ******************************************************************************/ LLConstant *DtoConstInitializer(const Loc &loc, Type *type, Initializer *init, const bool isCfile) { LLConstant *_init = nullptr; // may return zero if (!init) { if (type->toBasetype()->isTypeNoreturn()) { Logger::println("const noreturn initializer"); LLType *ty = DtoMemType(type); _init = LLConstant::getNullValue(ty); } else { IF_LOG Logger::println("const default initializer for %s", type->toChars()); Expression *initExp = defaultInit(type, loc, isCfile); _init = DtoConstExpInit(loc, type, initExp); } } else if (ExpInitializer *ex = init->isExpInitializer()) { Logger::println("const expression initializer"); _init = DtoConstExpInit(loc, type, ex->exp); } else if (ArrayInitializer *ai = init->isArrayInitializer()) { Logger::println("const array initializer"); _init = DtoConstArrayInitializer(ai, type, isCfile); } else if (init->isVoidInitializer()) { Logger::println("const void initializer"); LLType *ty = DtoMemType(type); _init = LLConstant::getNullValue(ty); } else if (init->isCInitializer()) { // TODO: ImportC error(loc, "LDC doesn't support C initializer lists yet"); fatal(); } else { // StructInitializer is no longer suposed to make it to the glue layer // in DMD 2.064. IF_LOG Logger::println("unsupported const initializer: %s", init->toChars()); } return _init; } //////////////////////////////////////////////////////////////////////////////// LLConstant *DtoConstExpInit(const Loc &loc, Type *targetType, Expression *exp) { IF_LOG Logger::println("DtoConstExpInit(targetType = %s, exp = %s)", targetType->toChars(), exp->toChars()); LOG_SCOPE LLConstant *val = toConstElem(exp, gIR); Type *baseValType = exp->type->toBasetype(); Type *baseTargetType = targetType->toBasetype(); // The situation here is a bit tricky: In an ideal world, we would always // have val->getType() == DtoType(targetType). But there are two reasons // why this is not true. One is that the LLVM type system cannot represent // all the C types, leading to differences in types being necessary e.g. for // union initializers. The second is that the frontend actually does not // explicitly lower things like initializing an array/vector with a scalar // constant, or since 2.061 sometimes does not get implicit conversions for // integers right. However, we cannot just rely on the actual Types being // equal if there are no rewrites to do because of – as usual – AST // inconsistency bugs. LLType *llType = val->getType(); LLType *targetLLType = DtoMemType(baseTargetType); // shortcut for zeros if (val->isNullValue()) return llvm::Constant::getNullValue(targetLLType); // extend i1 to i8 if (llType->isIntegerTy(1)) { llType = LLType::getInt8Ty(gIR->context()); #if LDC_LLVM_VER < 1800 val = llvm::ConstantExpr::getZExt(val, llType); #else val = llvm::ConstantFoldCastOperand(llvm::Instruction::ZExt, val, llType, *gDataLayout); #endif } if (llType == targetLLType) return val; if (baseTargetType->ty == TY::Tsarray) { Logger::println("Building constant array initializer from scalar."); assert(size(baseValType) > 0); const auto numTotalVals = size(baseTargetType) / size(baseValType); assert(size(baseTargetType) % size(baseValType) == 0); // may be a multi-dimensional array init, e.g., `char[2][3] x = 0xff` baseValType = stripModifiers(baseValType); LLSmallVector dims; // { 3, 2 } for (auto t = baseTargetType; t->ty == TY::Tsarray;) { dims.push_back(static_cast(t)->dim->toUInteger()); auto elementType = stripModifiers(t->nextOf()->toBasetype()); if (elementType->equals(baseValType)) break; t = elementType; } size_t product = 1; for (size_t i = dims.size(); i--;) { product *= dims[i]; auto at = llvm::ArrayType::get(val->getType(), dims[i]); LLSmallVector elements(dims[i], val); val = llvm::ConstantArray::get(at, elements); } (void)numTotalVals; (void) product; // Silence unused variable warning when assert is disabled. assert(product == numTotalVals); return val; } if (baseTargetType->ty == TY::Tvector) { Logger::println("Building constant vector initializer from scalar."); TypeVector *tv = static_cast(baseTargetType); assert(tv->basetype->ty == TY::Tsarray); dinteger_t elemCount = static_cast(tv->basetype)->dim->toInteger(); const auto elementCount = llvm::ElementCount::getFixed(elemCount); return llvm::ConstantVector::getSplat(elementCount, val); } if (llType->isIntegerTy() && targetLLType->isIntegerTy()) { // This should really be fixed in the frontend. Logger::println("Fixing up unresolved implicit integer conversion."); llvm::IntegerType *source = llvm::cast(llType); llvm::IntegerType *target = llvm::cast(targetLLType); (void)source; assert(target->getBitWidth() > source->getBitWidth() && "On initializer integer type mismatch, the target should be wider " "than the source."); #if LDC_LLVM_VER < 1800 return llvm::ConstantExpr::getZExtOrBitCast(val, target); #else return llvm::ConstantFoldCastOperand(llvm::Instruction::ZExt, val, target, *gDataLayout); #endif } Logger::println("Unhandled type mismatch, giving up."); return val; } //////////////////////////////////////////////////////////////////////////////// LLConstant *DtoTypeInfoOf(const Loc &loc, Type *type) { IF_LOG Logger::println("DtoTypeInfoOf(type = '%s')", type->toChars()); LOG_SCOPE auto tidecl = getOrCreateTypeInfoDeclaration(loc, type); auto tiglobal = DtoResolveTypeInfo(tidecl); return tiglobal; } //////////////////////////////////////////////////////////////////////////////// /// Allocates memory and passes on ownership. (never returns null) static char *DtoOverloadedIntrinsicName(TemplateInstance *ti, TemplateDeclaration *td) { IF_LOG Logger::println("DtoOverloadedIntrinsicName"); LOG_SCOPE; assert(td->intrinsicName); IF_LOG { Logger::println("template instance: %s", ti->toChars()); Logger::println("template declaration: %s", td->toChars()); Logger::println("intrinsic name: %s", td->intrinsicName); } // for now use the size in bits of the first template param in the instance assert(ti->tdtypes.length == 1); Type *T = static_cast(ti->tdtypes[0]); char prefix; if (T->isFloating() && !T->isComplex()) { prefix = 'f'; } else if (T->isIntegral()) { prefix = 'i'; } else { error(ti->loc, "%s `%s` has invalid template parameter for intrinsic: `%s`", ti->kind(), ti->toPrettyChars(), T->toChars()); fatal(); // or LLVM asserts } std::string name = td->intrinsicName; // replace `{f,i}#` by `{f,i}` (int: `i32`) or // `v{f,i}` (float4: `v4f32`) llvm::Type *dtype = DtoType(T); std::string replacement; if (dtype->isPPC_FP128Ty()) { // special case replacement = "ppcf128"; } else if (dtype->isVectorTy()) { auto vectorType = llvm::cast(dtype); llvm::raw_string_ostream stream(replacement); stream << 'v' << vectorType->getNumElements() << prefix << gDataLayout->getTypeSizeInBits(vectorType->getElementType()); stream.flush(); } else { replacement = prefix + std::to_string(gDataLayout->getTypeSizeInBits(dtype)); } size_t pos; while (std::string::npos != (pos = name.find('#'))) { if (pos > 0 && name[pos - 1] == prefix) { name.replace(pos - 1, 2, replacement); } else { if (pos && (name[pos - 1] == 'i' || name[pos - 1] == 'f')) { // Wrong type character. error(ti->loc, "%s `%s` has invalid parameter type for intrinsic `%s`: `%s` is " "not a%s type", ti->kind(), ti->toPrettyChars(), name.c_str(), T->toChars(), (name[pos - 1] == 'i' ? "n integral" : " floating-point")); } else { // Just plain wrong. (Error in declaration, not instantiation) error(td->loc, "%s `%s` has an invalid intrinsic name: `%s`", td->kind(), td->toPrettyChars(), name.c_str()); } fatal(); // or LLVM asserts } } IF_LOG Logger::println("final intrinsic name: %s", name.c_str()); return mem.xstrdup(name.c_str()); } /// For D frontend /// Fixup an overloaded intrinsic name string. void DtoSetFuncDeclIntrinsicName(TemplateInstance *ti, TemplateDeclaration *td, FuncDeclaration *fd) { if (fd->llvmInternal == LLVMintrinsic) { const auto cstr = DtoOverloadedIntrinsicName(ti, td); assert(cstr); fd->mangleOverride = {strlen(cstr), cstr}; } } //////////////////////////////////////////////////////////////////////////////// Type *stripModifiers(Type *type, bool transitive) { if (type->ty == TY::Tfunction) { return type; } if (transitive) { return unqualify(type, MODimmutable | MODconst | MODwild); } return castMod(type, 0); } //////////////////////////////////////////////////////////////////////////////// LLValue *makeLValue(const Loc &loc, DValue *value) { if (value->isLVal()) return DtoLVal(value); if (value->isIm() || value->isConst()) return DtoAllocaDump(value, ".makelvaluetmp"); LLValue *mem = DtoAlloca(value->type, ".makelvaluetmp"); DLValue var(value->type, mem); DtoAssign(loc, &var, value, EXP::blit); return mem; } //////////////////////////////////////////////////////////////////////////////// void callPostblit(const Loc &loc, Expression *exp, LLValue *val) { Type *tb = exp->type->toBasetype(); if ((exp->op == EXP::variable || exp->op == EXP::dotVariable || exp->op == EXP::star || exp->op == EXP::this_ || exp->op == EXP::index) && tb->ty == TY::Tstruct) { StructDeclaration *sd = static_cast(tb)->sym; if (sd->postblit) { FuncDeclaration *fd = sd->postblit; if (fd->storage_class & STCdisable) { error(loc, "%s `%s` is not copyable because it is annotated with `@disable`", sd->kind(), sd->toPrettyChars()); } Expressions args; DFuncValue dfn(fd, DtoCallee(fd), val); DtoCallFunction(loc, Type::tvoid, &dfn, &args); } } } //////////////////////////////////////////////////////////////////////////////// bool isSpecialRefVar(VarDeclaration *vd) { return (vd->storage_class & (STCref | STCparameter)) == STCref; } //////////////////////////////////////////////////////////////////////////////// bool isLLVMUnsigned(Type *t) { return t->isUnsigned() || t->ty == TY::Tpointer; } //////////////////////////////////////////////////////////////////////////////// void printLabelName(std::ostream &target, const char *func_mangle, const char *label_name) { target << gTargetMachine->getMCAsmInfo()->getPrivateGlobalPrefix().str() << func_mangle << "_" << label_name; } //////////////////////////////////////////////////////////////////////////////// void AppendFunctionToLLVMGlobalCtorsDtors(llvm::Function *func, const uint32_t priority, const bool isCtor) { if (isCtor) { llvm::appendToGlobalCtors(gIR->module, func, priority); } else { llvm::appendToGlobalDtors(gIR->module, func, priority); } } //////////////////////////////////////////////////////////////////////////////// void tokToICmpPred(EXP op, bool isUnsigned, llvm::ICmpInst::Predicate *outPred, llvm::Value **outConst) { switch (op) { case EXP::lessThan: *outPred = isUnsigned ? llvm::ICmpInst::ICMP_ULT : llvm::ICmpInst::ICMP_SLT; break; case EXP::lessOrEqual: *outPred = isUnsigned ? llvm::ICmpInst::ICMP_ULE : llvm::ICmpInst::ICMP_SLE; break; case EXP::greaterThan: *outPred = isUnsigned ? llvm::ICmpInst::ICMP_UGT : llvm::ICmpInst::ICMP_SGT; break; case EXP::greaterOrEqual: *outPred = isUnsigned ? llvm::ICmpInst::ICMP_UGE : llvm::ICmpInst::ICMP_SGE; break; default: llvm_unreachable("Invalid comparison operation"); } } //////////////////////////////////////////////////////////////////////////////// llvm::ICmpInst::Predicate eqTokToICmpPred(EXP op, bool invert) { assert(op == EXP::equal || op == EXP::notEqual || op == EXP::identity || op == EXP::notIdentity); bool isEquality = (op == EXP::equal || op == EXP::identity); if (invert) isEquality = !isEquality; return (isEquality ? llvm::ICmpInst::ICMP_EQ : llvm::ICmpInst::ICMP_NE); } //////////////////////////////////////////////////////////////////////////////// LLValue *createIPairCmp(EXP op, LLValue *lhs1, LLValue *lhs2, LLValue *rhs1, LLValue *rhs2) { const auto predicate = eqTokToICmpPred(op); LLValue *r1 = gIR->ir->CreateICmp(predicate, lhs1, rhs1); LLValue *r2 = gIR->ir->CreateICmp(predicate, lhs2, rhs2); LLValue *r = (predicate == llvm::ICmpInst::ICMP_EQ ? gIR->ir->CreateAnd(r1, r2) : gIR->ir->CreateOr(r1, r2)); return r; } /////////////////////////////////////////////////////////////////////////////// DValue *DtoSymbolAddress(const Loc &loc, Type *type, Declaration *decl) { IF_LOG Logger::println("DtoSymbolAddress ('%s' of type '%s')", decl->toChars(), decl->type->toChars()); LOG_SCOPE if (VarDeclaration *vd = decl->isVarDeclaration()) { // The magic variable __ctfe is always false at runtime if (vd->ident == Id::ctfe) { return new DConstValue(type, DtoConstBool(false)); } // this is an error! must be accessed with DotVarExp if (vd->needThis()) { error(loc, "need `this` to access member `%s`", vd->toChars()); fatal(); } // _arguments if (vd->ident == Id::_arguments && gIR->func()->_arguments) { Logger::println("Id::_arguments"); LLValue *v = gIR->func()->_arguments; assert(!isSpecialRefVar(vd) && "Code not expected to handle special ref " "vars, although it can easily be made " "to."); return new DLValue(type, v); } // _argptr if (vd->ident == Id::_argptr && gIR->func()->_argptr) { Logger::println("Id::_argptr"); LLValue *v = gIR->func()->_argptr; assert(!isSpecialRefVar(vd) && "Code not expected to handle special ref " "vars, although it can easily be made " "to."); return new DLValue(type, v); } // _dollar if (vd->ident == Id::dollar) { Logger::println("Id::dollar"); if (isIrVarCreated(vd)) { // This is the length of a range. return makeVarDValue(type, vd); } assert(!gIR->arrays.empty()); return new DImValue(type, DtoArrayLen(gIR->arrays.back())); } // typeinfo if (TypeInfoDeclaration *tid = vd->isTypeInfoDeclaration()) { Logger::println("TypeInfoDeclaration"); LLValue *m = DtoResolveTypeInfo(tid); return new DImValue(type, m); } // nested variable if (vd->nestedrefs.length) { Logger::println("nested variable"); return DtoNestedVariable(loc, type, vd); } // function parameter if (vd->isParameter()) { IF_LOG { Logger::println("function param"); Logger::println("type: %s", vd->type->toChars()); } FuncDeclaration *fd = vd->toParent2()->isFuncDeclaration(); if (fd && fd != gIR->func()->decl) { Logger::println("nested parameter"); return DtoNestedVariable(loc, type, vd); } if (vd->storage_class & STClazy) { Logger::println("lazy parameter"); assert(type->ty == TY::Tdelegate); } assert(!isSpecialRefVar(vd) && "Code not expected to handle special " "ref vars, although it can easily be " "made to."); return new DLValue(type, getIrValue(vd)); } Logger::println("a normal variable"); // take care of forward references of global variables if (vd->isDataseg() || (vd->storage_class & STCextern)) { DtoResolveVariable(vd); } return makeVarDValue(type, vd); } if (FuncLiteralDeclaration *flitdecl = decl->isFuncLiteralDeclaration()) { Logger::println("FuncLiteralDeclaration"); // We need to codegen the function here, because literals are not added // to the module member list. DtoDefineFunction(flitdecl); return new DFuncValue(flitdecl, DtoCallee(flitdecl, false)); } if (FuncDeclaration *fdecl = decl->isFuncDeclaration()) { Logger::println("FuncDeclaration"); fdecl = fdecl->toAliasFunc(); if (fdecl->llvmInternal == LLVMinline_asm) { // TODO: Is this needed? If so, what about other intrinsics? error(loc, "special ldc inline asm is not a normal function"); fatal(); } else if (fdecl->llvmInternal == LLVMinline_ir) { // TODO: Is this needed? If so, what about other intrinsics? error(loc, "special ldc inline ir is not a normal function"); fatal(); } DtoResolveFunction(fdecl); assert(!DtoIsMagicIntrinsic(fdecl)); return new DFuncValue(fdecl, DtoCallee(fdecl)); } if (SymbolDeclaration *sdecl = decl->isSymbolDeclaration()) { // this is the static initialiser (init symbol) for aggregates AggregateDeclaration *ad = sdecl->dsym; IF_LOG Logger::print("init symbol of %s\n", ad->toChars()); DtoResolveDsymbol(ad); auto sd = ad->isStructDeclaration(); // LDC extension: void[]-typed `__traits(initSymbol)`, for classes too auto tb = sdecl->type->toBasetype(); if (tb->ty != TY::Tstruct) { assert(tb->ty == TY::Tarray && tb->nextOf()->ty == TY::Tvoid); const auto size = DtoConstSize_t(ad->structsize); LLConstant *ptr = sd && sd->zeroInit() ? static_cast(getNullPtr()) : getIrAggr(ad)->getInitSymbol(); return new DSliceValue(type, size, ptr); } assert(sd); if (sd->zeroInit()) { error(loc, "no init symbol for zero-initialized struct"); fatal(); } LLValue *initsym = getIrAggr(sd)->getInitSymbol(); return new DLValue(type, initsym); } llvm_unreachable("Unimplemented VarExp type"); } llvm::Constant *DtoConstSymbolAddress(const Loc &loc, Declaration *decl) { // global variable if (VarDeclaration *vd = decl->isVarDeclaration()) { if (!vd->isDataseg()) { // Not sure if this can be triggered from user code, but it is // needed for the current hacky implementation of // AssocArrayLiteralExp::toElem, which requires on error // gagging to check for constantness of the initializer. error(loc, "cannot use address of non-global variable `%s` as constant " "initializer", vd->toChars()); if (!global.gag) { fatal(); } return nullptr; } DtoResolveVariable(vd); LLConstant *llc = llvm::dyn_cast(getIrValue(vd)); assert(llc); return llc; } // static function if (FuncDeclaration *fd = decl->isFuncDeclaration()) { return DtoCallee(fd); } llvm_unreachable("Taking constant address not implemented."); } llvm::Constant *buildStringLiteralConstant(StringExp *se, uint64_t bufferLength) { const auto stringLength = se->len; assert(bufferLength >= stringLength); if (se->sz == 1 && bufferLength <= stringLength + 1) { const DString data = se->peekString(); const bool nullTerminate = (bufferLength == stringLength + 1); return llvm::ConstantDataArray::getString( gIR->context(), {data.ptr, data.length}, nullTerminate); } Type *dtype = se->type->toBasetype(); Type *cty = dtype->nextOf()->toBasetype(); LLType *ct = DtoMemType(cty); LLArrayType *at = LLArrayType::get(ct, bufferLength); std::vector vals; vals.reserve(bufferLength); for (uint64_t i = 0; i < stringLength; ++i) { vals.push_back(LLConstantInt::get(ct, se->getIndex(i), false)); } const auto nullChar = LLConstantInt::get(ct, 0, false); for (uint64_t i = stringLength; i < bufferLength; ++i) { vals.push_back(nullChar); } return LLConstantArray::get(at, vals); } std::string llvmTypeToString(llvm::Type *type) { std::string result; llvm::raw_string_ostream stream(result); stream << *type; stream.flush(); return result; } // Is the specified symbol defined in the druntime/Phobos libs? // For instantiated symbols: is the template declared in druntime/Phobos? static bool isDefaultLibSymbol(Dsymbol *sym) { auto mod = sym->getModule(); if (!mod) return false; auto md = mod->md; if (!md) return false; if (md->packages.length == 0) return md->id == Id::object || md->id == Id::std; auto p = md->packages.ptr[0]; return p == Id::core || p == Id::etc || p == Id::ldc || (p == Id::std && // 3rd-party package: std.io (https://github.com/MartinNowak/io/) !((md->packages.length == 1 && md->id == Id::io) || (md->packages.length > 1 && md->packages.ptr[1] == Id::io))); } bool defineOnDeclare(Dsymbol *sym, bool isFunction) { // -linkonce-templates: all instantiated symbols if (global.params.linkonceTemplates != LinkonceTemplates::no) return sym->isInstantiated(); // -dllimport=defaultLibsOnly: all data symbols instantiated from // druntime/Phobos templates // see https://github.com/ldc-developers/ldc/issues/3931 return !isFunction && global.params.dllimport == DLLImport::defaultLibsOnly && sym->isInstantiated() && isDefaultLibSymbol(sym); } bool dllimportDataSymbol(Dsymbol *sym) { if (!global.params.targetTriple->isOSWindows()) return false; if (sym->isExport() || global.params.dllimport == DLLImport::all || (global.params.dllimport == DLLImport::defaultLibsOnly && isDefaultLibSymbol(sym))) { // Okay, this symbol is a candidate. Use dllimport unless we have a // guaranteed-codegen'd definition in a root module. if (auto mod = sym->isModule()) return !mod->isRoot(); // non-root ModuleInfo symbol if (sym->inNonRoot()) return true; // not instantiated, and defined in non-root if (sym->isInstantiated() && !defineOnDeclare(sym, false)) return true; // instantiated but potentially culled (needsCodegen()) if (auto vd = sym->isVarDeclaration()) if (vd->storage_class & STCextern) return true; // externally defined global variable } return false; } llvm::GlobalVariable *declareGlobal(const Loc &loc, llvm::Module &module, llvm::Type *type, llvm::StringRef mangledName, bool isConstant, bool isThreadLocal, bool useDLLImport) { // No TLS support for WebAssembly and AVR; spare users from having to add // __gshared everywhere. const auto arch = global.params.targetTriple->getArch(); if (arch == llvm::Triple::wasm32 || arch == llvm::Triple::wasm64 || arch == llvm::Triple::avr) isThreadLocal = false; llvm::GlobalVariable *existing = module.getGlobalVariable(mangledName, /*AllowInternal=*/true); if (existing) { const auto existingType = existing->getValueType(); if (existingType != type || existing->isConstant() != isConstant || existing->isThreadLocal() != isThreadLocal) { error(loc, "Global variable type does not match previous declaration with " "same mangled name: `%s`", mangledName.str().c_str()); const auto suppl = [&loc](const char *prefix, LLType *type, bool isConstant, bool isThreadLocal) { const auto typeName = llvmTypeToString(type); errorSupplemental(loc, "%s %s, %s, %s", prefix, typeName.c_str(), isConstant ? "const" : "mutable", isThreadLocal ? "thread-local" : "non-thread-local"); }; suppl("Previous IR type:", existingType, existing->isConstant(), existing->isThreadLocal()); suppl("New IR type: ", type, isConstant, isThreadLocal); fatal(); } return existing; } // Use a command line option for the thread model. // On PPC there is only local-exec available - in this case just ignore the // command line. const auto tlsModel = isThreadLocal ? (arch == llvm::Triple::ppc ? llvm::GlobalVariable::LocalExecTLSModel : clThreadModel.getValue()) : llvm::GlobalVariable::NotThreadLocal; auto gvar = new llvm::GlobalVariable(module, type, isConstant, llvm::GlobalValue::ExternalLinkage, nullptr, mangledName, nullptr, tlsModel); if (useDLLImport && global.params.targetTriple->isOSWindows()) gvar->setDLLStorageClass(LLGlobalValue::DLLImportStorageClass); return gvar; } void defineGlobal(llvm::GlobalVariable *global, llvm::Constant *init, Dsymbol *symbolForLinkageAndVisibility) { assert(global->isDeclaration() && "Global variable already defined"); assert(init); global->setInitializer(init); if (symbolForLinkageAndVisibility) setLinkageAndVisibility(symbolForLinkageAndVisibility, global); } llvm::GlobalVariable *defineGlobal(const Loc &loc, llvm::Module &module, llvm::StringRef mangledName, llvm::Constant *init, llvm::GlobalValue::LinkageTypes linkage, bool isConstant, bool isThreadLocal) { assert(init); auto global = declareGlobal(loc, module, init->getType(), mangledName, isConstant, isThreadLocal, /*useDLLImport*/ false); defineGlobal(global, init, nullptr); global->setLinkage(linkage); return global; } FuncDeclaration *getParentFunc(Dsymbol *sym) { if (!sym) { return nullptr; } // Static functions, non-extern(D) non-member functions and function (not // delegate) literals don't allow access to a parent context, even if they are // nested. if (FuncDeclaration *fd = sym->isFuncDeclaration()) { if (auto fld = fd->isFuncLiteralDeclaration()) { if (fld->tok == TOK::function_) return nullptr; } else if (fd->isStatic() || (!fd->isThis() && fd->_linkage() != LINK::d)) { return nullptr; } } // Fun fact: AggregateDeclarations are not Declarations. else if (AggregateDeclaration *ad = sym->isAggregateDeclaration()) { if (!ad->isNested()) { return nullptr; } } for (Dsymbol *parent = sym->parent; parent; parent = parent->parent) { if (FuncDeclaration *fd = parent->isFuncDeclaration()) { return fd; } if (AggregateDeclaration *ad = parent->isAggregateDeclaration()) { if (!ad->isNested()) { return nullptr; } } } return nullptr; } DLValue *DtoIndexAggregate(LLValue *src, AggregateDeclaration *ad, VarDeclaration *vd) { IF_LOG Logger::println("Indexing aggregate field %s:", vd->toPrettyChars()); LOG_SCOPE; // Make sure the aggregate is resolved, as subsequent code might expect // isIrVarCreated(vd). This is a bit of a hack, we don't actually need this // ourselves, DtoType below would be enough. DtoResolveDsymbol(ad); if (ad->classKind == ClassKind::objc) { auto tHandle = getI32Type(); auto tOffset = DtoLoad(tHandle, gIR->objc.getIvar(vd)->offset); // Offset is now stored in tOffset. LLValue *ptr = src; ptr = DtoBitCast(ptr, getOpaquePtrType()); ptr = DtoGEP1(getI8Type(), ptr, tOffset); return new DLValue(vd->type, ptr); } // Look up field to index or offset to apply. auto irTypeAggr = getIrType(ad->type)->isAggr(); assert(irTypeAggr); bool isFieldIdx; unsigned off = irTypeAggr->getMemberLocation(vd, isFieldIdx); LLValue *ptr = src; LLType * ty = nullptr; if (!isFieldIdx) { // apply byte-wise offset from object start ptr = DtoGEP1(getI8Type(), ptr, off); ty = DtoType(vd->type); } else { if (ad->structsize == 0) { // can happen for extern(C) structs assert(off == 0); } else { // Cast the pointer we got to the canonical struct type the indices are // based on. LLType *st = nullptr; if (auto irtc = irTypeAggr->isClass()) { st = irtc->getMemoryLLType(); } else { st = irTypeAggr->getLLType(); } ptr = DtoGEP(st, ptr, 0, off); ty = isaStruct(st)->getElementType(off); } } IF_LOG Logger::cout() << "Pointer: " << *ptr << '\n'; if (auto p = isaPointer(ty)) { if (p->getAddressSpace()) return new DDcomputeLValue(vd->type, p, ptr); } return new DLValue(vd->type, ptr); } unsigned getFieldGEPIndex(AggregateDeclaration *ad, VarDeclaration *vd) { auto irTypeAggr = getIrType(ad->type)->isAggr(); assert(irTypeAggr); bool isFieldIdx; unsigned off = irTypeAggr->getMemberLocation(vd, isFieldIdx); assert(isFieldIdx && "Cannot address field by a simple GEP."); return off; } DValue *makeVarDValue(Type *type, VarDeclaration *vd, llvm::Value *storage) { auto val = storage; if (!val) { assert(isIrVarCreated(vd) && "Variable not resolved."); val = getIrValue(vd); } assert(val->getType()->isPointerTy()); if (isSpecialRefVar(vd)) return new DSpecialRefValue(type, val); return new DLValue(type, val); }