mirror of
https://github.com/ldc-developers/ldc.git
synced 2025-04-28 22:21:31 +03:00

* WIP: Objective-C support * Further work on implementation * ObjC dynamic cast * Add swift stub class attribute * Classes, protocols and ivars * Fix compilation issues * Fix objc ir codegen * Add objc linker option * Add swift stub classref get ir gen * Minor cleanup * Fix objc link flag being added on non-darwin platforms * Refactor objc gen * remove use of std::nullopt * Emit protocol tables * Remove unused variable * Formatting * Fix build in release mode. Thanks for nothing, c++. * Fix consistency * Fix dynamic casts * Fix tocall parentfd ref and arm msgsend call * Make instance variables work * Implicitly add isa pointer to objc classes. * Fix protocol referencing & allow pragma mangle * Fix protocol linkage * Fix direct call support * always generate var type for methods * Fix test 16096a * Fix extern ivar symbol gen, retain method decls * Remove arm32 and x86 support * Check method and ivar info before pushing to member list * Make ObjcMethod info untyped. * Make ivar and method gen more robust * Generate optional protocol symbols * Use bitcasting instead of creating multiple type defs * Fix invalid protocol list struct gen * More codegen robustness * emit protocol table as const * Make protocol table anon struct * Fix callable type, generate protocol_list_t properly. * Cast vthis to argtype * Handle protorefs and classrefs properly * seperate label ref and deref * Fix method lookup * Enable objective-c tests * Enable objc_call_static test * Scan both classes and protocols for method ref * Enable objective-c tests on arm as well. * supress objc linker warning in tests * Fix class and protocol gen structure * Fix objc_protocol_sections test * ObjcMethod only get callee for functions with bodies * Fix protocol class method gen * Make ObjcMethod anon again * Fix missing emit calls * Fix classref gen * Implement some of the requested changes * Enable compilable tests * Fix property selector gen, ugly hack for final funcs. * Fix segfault in referencing fd->type * Refactor implementation * Fix null references in class and method lookup * include unordered_map * Get functionality on-par with prev impl. * Fix super context calls * Move -L-w flag to d_do_test and use IN_LLVM in objc.d/h * add LDC version tag to -L-w flag * Update CHANGELOG.md
1079 lines
36 KiB
C++
1079 lines
36 KiB
C++
//===-- tocall.cpp --------------------------------------------------------===//
|
||
//
|
||
// LDC – the LLVM D compiler
|
||
//
|
||
// This file is distributed under the BSD-style LDC license. See the LICENSE
|
||
// file for details.
|
||
//
|
||
//===----------------------------------------------------------------------===//
|
||
|
||
#include "dmd/compiler.h"
|
||
#include "dmd/declaration.h"
|
||
#include "dmd/errors.h"
|
||
#include "dmd/expression.h"
|
||
#include "dmd/id.h"
|
||
#include "dmd/mtype.h"
|
||
#include "dmd/target.h"
|
||
#include "dmd/template.h"
|
||
#include "gen/abi/abi.h"
|
||
#include "gen/arrays.h"
|
||
#include "gen/classes.h"
|
||
#include "gen/dvalue.h"
|
||
#include "gen/funcgenstate.h"
|
||
#include "gen/functions.h"
|
||
#include "gen/irstate.h"
|
||
#include "gen/llvm.h"
|
||
#include "gen/llvmhelpers.h"
|
||
#include "gen/logger.h"
|
||
#include "gen/nested.h"
|
||
#include "gen/mangling.h"
|
||
#include "gen/pragma.h"
|
||
#include "gen/tollvm.h"
|
||
#include "gen/runtime.h"
|
||
#include "ir/irfunction.h"
|
||
#include "ir/irtype.h"
|
||
#include "llvm/IR/LLVMContext.h"
|
||
#include <llvm/IR/DerivedTypes.h>
|
||
|
||
using namespace dmd;
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
IrFuncTy &DtoIrTypeFunction(DValue *fnval) {
|
||
if (DFuncValue *dfnval = fnval->isFunc()) {
|
||
if (dfnval->func) {
|
||
return getIrFunc(dfnval->func)->irFty;
|
||
}
|
||
}
|
||
|
||
return getIrType(fnval->type->toBasetype(), true)->getIrFuncTy();
|
||
}
|
||
|
||
TypeFunction *DtoTypeFunction(DValue *fnval) {
|
||
Type *type = fnval->type->toBasetype();
|
||
if (type->ty == TY::Tfunction) {
|
||
return static_cast<TypeFunction *>(type);
|
||
}
|
||
if (type->ty == TY::Tdelegate) {
|
||
// FIXME: There is really no reason why the function type should be
|
||
// unmerged at this stage, but the frontend still seems to produce such
|
||
// cases; for example for the uint(uint) next type of the return type of
|
||
// (&zero)(), leading to a crash in DtoCallFunction:
|
||
// ---
|
||
// void test8198() {
|
||
// uint delegate(uint) zero() { return null; }
|
||
// auto a = (&zero)()(0);
|
||
// }
|
||
// ---
|
||
// Calling merge() here works around the symptoms, but does not fix the
|
||
// root cause.
|
||
|
||
Type *next = merge(type->nextOf());
|
||
assert(next->ty == TY::Tfunction);
|
||
return static_cast<TypeFunction *>(next);
|
||
}
|
||
|
||
llvm_unreachable("Cannot get TypeFunction* from non lazy/function/delegate");
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
static void addExplicitArguments(std::vector<LLValue *> &args, AttrSet &attrs,
|
||
IrFuncTy &irFty, LLFunctionType *calleeType,
|
||
Expressions &argexps,
|
||
Parameters *formalParams) {
|
||
// Number of arguments added to the LLVM type that are implicit on the
|
||
// frontend side of things (this, context pointers, etc.)
|
||
const size_t implicitLLArgCount = args.size();
|
||
|
||
// Number of formal arguments in the LLVM type (i.e. excluding varargs).
|
||
const size_t formalLLArgCount = irFty.args.size();
|
||
|
||
// Number of formal arguments in the D call expression (excluding varargs).
|
||
const size_t formalDArgCount = Parameter::dim(formalParams);
|
||
|
||
// The number of explicit arguments in the D call expression (including
|
||
// varargs), not all of which necessarily generate a LLVM argument.
|
||
const size_t explicitDArgCount = argexps.size();
|
||
|
||
// construct and initialize an IrFuncTyArg object for each vararg
|
||
std::vector<IrFuncTyArg *> optionalIrArgs;
|
||
for (size_t i = formalDArgCount; i < explicitDArgCount; i++) {
|
||
Type *argType = argexps[i]->type;
|
||
bool passByVal = gABI->passByVal(irFty.type, argType);
|
||
|
||
llvm::AttrBuilder initialAttrs(getGlobalContext());
|
||
if (passByVal) {
|
||
initialAttrs.addByValAttr(DtoType(argType));
|
||
if (auto alignment = DtoAlignment(argType))
|
||
initialAttrs.addAlignmentAttr(alignment);
|
||
} else {
|
||
DtoAddExtendAttr(argType, initialAttrs);
|
||
}
|
||
|
||
optionalIrArgs.push_back(new IrFuncTyArg(argType, passByVal, std::move(initialAttrs)));
|
||
optionalIrArgs.back()->parametersIdx = i;
|
||
}
|
||
|
||
// let the ABI rewrite the IrFuncTyArg objects
|
||
gABI->rewriteVarargs(irFty, optionalIrArgs);
|
||
|
||
const size_t explicitLLArgCount = formalLLArgCount + optionalIrArgs.size();
|
||
args.resize(implicitLLArgCount + explicitLLArgCount,
|
||
static_cast<llvm::Value *>(nullptr));
|
||
|
||
size_t dArgIndex = 0;
|
||
for (size_t i = 0; i < explicitLLArgCount; ++i, ++dArgIndex) {
|
||
const bool isVararg = (i >= formalLLArgCount);
|
||
IrFuncTyArg *irArg = nullptr;
|
||
if (isVararg) {
|
||
irArg = optionalIrArgs[i - formalLLArgCount];
|
||
} else {
|
||
irArg = irFty.args[i];
|
||
}
|
||
|
||
// Make sure to evaluate argument expressions for which there's no LL
|
||
// parameter (e.g., empty structs for some ABIs).
|
||
if (irArg->parametersIdx < formalDArgCount) {
|
||
for (; dArgIndex < irArg->parametersIdx; ++dArgIndex) {
|
||
toElem(argexps[dArgIndex]);
|
||
}
|
||
}
|
||
|
||
Expression *const argexp = argexps[dArgIndex];
|
||
Parameter *const formalParam =
|
||
isVararg ? nullptr : Parameter::getNth(formalParams, dArgIndex);
|
||
|
||
// evaluate argument expression
|
||
DValue *const dval = DtoArgument(formalParam, argexp);
|
||
|
||
// load from lvalue/let TargetABI rewrite it/...
|
||
bool isLValueExp = argexp->isLvalue();
|
||
// regard temporaries as rvalues here
|
||
if (isLValueExp) {
|
||
auto ae = argexp;
|
||
if (auto ce = ae->isCommaExp())
|
||
ae = ce->getTail();
|
||
if (auto ve = ae->isVarExp()) {
|
||
if (auto vd = ve->var->isVarDeclaration()) {
|
||
if (vd->storage_class & STCtemp)
|
||
isLValueExp = false;
|
||
}
|
||
}
|
||
}
|
||
llvm::Value *llVal = irFty.putArg(*irArg, dval, isLValueExp,
|
||
dArgIndex == explicitDArgCount - 1);
|
||
|
||
const size_t llArgIdx = implicitLLArgCount + i;
|
||
llvm::Type *const paramType =
|
||
(isVararg ? nullptr : calleeType->getParamType(llArgIdx));
|
||
|
||
// Hack around LDC assuming structs and static arrays are in memory:
|
||
// If the function wants a struct, and the argument value is a
|
||
// pointer to a struct, load from it before passing it in.
|
||
if (isaPointer(llVal) && DtoIsInMemoryOnly(argexp->type) &&
|
||
((!isVararg && !isaPointer(paramType)) ||
|
||
(isVararg && !irArg->byref && !irArg->isByVal()))) {
|
||
Logger::println("Loading struct type for function argument");
|
||
llVal = DtoLoad(DtoType(dval->type), llVal);
|
||
}
|
||
|
||
// parameter type mismatch, this is hard to get rid of
|
||
if (!isVararg && llVal->getType() != paramType) {
|
||
IF_LOG {
|
||
Logger::cout() << "arg: " << *llVal << '\n';
|
||
Logger::cout() << "expects: " << *paramType << '\n';
|
||
}
|
||
if (isaStruct(llVal)) {
|
||
llVal = DtoSlicePaint(llVal, paramType);
|
||
} else {
|
||
llVal = DtoBitCast(llVal, paramType);
|
||
}
|
||
}
|
||
|
||
args[llArgIdx] = llVal;
|
||
attrs.addToParam(llArgIdx, irArg->attrs);
|
||
|
||
if (isVararg) {
|
||
delete irArg;
|
||
}
|
||
}
|
||
|
||
for (; dArgIndex < explicitDArgCount; ++dArgIndex) {
|
||
toElem(argexps[dArgIndex]);
|
||
}
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
static LLValue *getTypeinfoArrayArgumentForDVarArg(Expressions *argexps,
|
||
int begin) {
|
||
IF_LOG Logger::println("doing d-style variadic arguments");
|
||
LOG_SCOPE
|
||
|
||
// number of non variadic args
|
||
IF_LOG Logger::println("num non vararg params = %d", begin);
|
||
|
||
const size_t numArgExps = argexps ? argexps->size() : 0;
|
||
const size_t numVariadicArgs = numArgExps - begin;
|
||
|
||
// build type info array
|
||
LLType *typeinfotype = DtoType(getTypeInfoType());
|
||
LLArrayType *typeinfoarraytype =
|
||
LLArrayType::get(typeinfotype, numVariadicArgs);
|
||
|
||
auto typeinfomem = new llvm::GlobalVariable(
|
||
gIR->module, typeinfoarraytype, true, llvm::GlobalValue::InternalLinkage,
|
||
nullptr, "._arguments.storage");
|
||
IF_LOG Logger::cout() << "_arguments storage: " << *typeinfomem << '\n';
|
||
|
||
std::vector<LLConstant *> vtypeinfos;
|
||
vtypeinfos.reserve(numVariadicArgs);
|
||
for (size_t i = begin; i < numArgExps; i++) {
|
||
Expression *argExp = (*argexps)[i];
|
||
vtypeinfos.push_back(DtoTypeInfoOf(argExp->loc, argExp->type));
|
||
}
|
||
|
||
// apply initializer
|
||
LLConstant *tiinits = LLConstantArray::get(typeinfoarraytype, vtypeinfos);
|
||
typeinfomem->setInitializer(tiinits);
|
||
|
||
return DtoConstSlice(DtoConstSize_t(numVariadicArgs), typeinfomem);
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
static LLType *getAtomicType(LLType *type) {
|
||
switch (const size_t N = getTypeBitSize(type)) {
|
||
case 8:
|
||
case 16:
|
||
case 32:
|
||
case 64:
|
||
case 128:
|
||
return llvm::IntegerType::get(gIR->context(), static_cast<unsigned>(N));
|
||
default:
|
||
return nullptr;
|
||
}
|
||
}
|
||
|
||
bool DtoLowerMagicIntrinsic(IRState *p, FuncDeclaration *fndecl, CallExp *e,
|
||
DValue *&result) {
|
||
// va_start instruction
|
||
if (fndecl->llvmInternal == LLVMva_start) {
|
||
if (e->arguments->length < 1 || e->arguments->length > 2) {
|
||
error(e->loc, "`va_start` instruction expects 1 (or 2) arguments");
|
||
fatal();
|
||
}
|
||
DLValue *ap = toElem((*e->arguments)[0])->isLVal(); // va_list
|
||
assert(ap);
|
||
// variadic extern(D) function with implicit _argptr?
|
||
if (LLValue *argptrMem = p->func()->_argptr) {
|
||
// then va_copy the _argptr
|
||
DLValue argptr(ap->type, argptrMem);
|
||
gABI->vaCopy(ap, &argptr);
|
||
} else {
|
||
LLValue *llAp = gABI->prepareVaStart(ap);
|
||
p->ir->CreateCall(GET_INTRINSIC_DECL(vastart, llAp->getType()), llAp, "");
|
||
}
|
||
result = nullptr;
|
||
return true;
|
||
}
|
||
|
||
// va_copy instruction
|
||
if (fndecl->llvmInternal == LLVMva_copy) {
|
||
if (e->arguments->length != 2) {
|
||
error(e->loc, "`va_copy` instruction expects 2 arguments");
|
||
fatal();
|
||
}
|
||
DLValue *dest = toElem((*e->arguments)[0])->isLVal(); // va_list
|
||
assert(dest);
|
||
DValue *src = toElem((*e->arguments)[1]); // va_list
|
||
gABI->vaCopy(dest, src);
|
||
result = nullptr;
|
||
return true;
|
||
}
|
||
|
||
// va_arg instruction
|
||
if (fndecl->llvmInternal == LLVMva_arg) {
|
||
if (e->arguments->length != 1) {
|
||
error(e->loc, "`va_arg` instruction expects 1 argument");
|
||
fatal();
|
||
}
|
||
if (DtoIsInMemoryOnly(e->type)) {
|
||
error(e->loc,
|
||
"`va_arg` instruction does not support structs and static arrays");
|
||
fatal();
|
||
}
|
||
DLValue *ap = toElem((*e->arguments)[0])->isLVal(); // va_list
|
||
assert(ap);
|
||
LLValue *llAp = gABI->prepareVaArg(ap);
|
||
LLType *llType = DtoType(e->type);
|
||
result = new DImValue(e->type, p->ir->CreateVAArg(llAp, llType));
|
||
return true;
|
||
}
|
||
|
||
// va_end instruction
|
||
if (fndecl->llvmInternal == LLVMva_end) {
|
||
if (e->arguments->length != 1) {
|
||
error(e->loc, "`va_end` instruction expects 1 argument");
|
||
fatal();
|
||
}
|
||
DLValue *ap = toElem((*e->arguments)[0])->isLVal(); // va_list
|
||
assert(ap);
|
||
LLValue *llAp = gABI->prepareVaArg(ap);
|
||
p->ir->CreateCall(GET_INTRINSIC_DECL(vaend, llAp->getType()), llAp);
|
||
result = nullptr;
|
||
return true;
|
||
}
|
||
|
||
// C alloca
|
||
if (fndecl->llvmInternal == LLVMalloca) {
|
||
if (e->arguments->length != 1) {
|
||
error(e->loc, "`alloca` expects 1 argument");
|
||
fatal();
|
||
}
|
||
Expression *exp = (*e->arguments)[0];
|
||
DValue *expv = toElem(exp);
|
||
if (expv->type->toBasetype()->ty != TY::Tint32) {
|
||
expv = DtoCast(e->loc, expv, Type::tint32);
|
||
}
|
||
result = new DImValue(e->type,
|
||
p->ir->CreateAlloca(LLType::getInt8Ty(p->context()),
|
||
DtoRVal(expv), ".alloca"));
|
||
return true;
|
||
}
|
||
|
||
// fence instruction
|
||
if (fndecl->llvmInternal == LLVMfence) {
|
||
if (e->arguments->length < 1 || e->arguments->length > 2) {
|
||
error(e->loc, "`fence` instruction expects 1 (or 2) arguments");
|
||
fatal();
|
||
}
|
||
auto atomicOrdering =
|
||
static_cast<llvm::AtomicOrdering>((*e->arguments)[0]->toInteger());
|
||
llvm::SyncScope::ID scope = llvm::SyncScope::System;
|
||
if (e->arguments->length == 2) {
|
||
scope = static_cast<llvm::SyncScope::ID>((*e->arguments)[1]->toInteger());
|
||
}
|
||
// orderings below acquire are invalid; like clang, don't emit any
|
||
// instruction in those cases
|
||
if (static_cast<int>(atomicOrdering) >
|
||
static_cast<int>(llvm::AtomicOrdering::Monotonic)) {
|
||
p->ir->CreateFence(atomicOrdering, scope);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// atomic store instruction
|
||
if (fndecl->llvmInternal == LLVMatomic_store) {
|
||
if (e->arguments->length != 3) {
|
||
error(e->loc, "atomic store instruction expects 3 arguments");
|
||
fatal();
|
||
}
|
||
Expression *exp1 = (*e->arguments)[0];
|
||
Expression *exp2 = (*e->arguments)[1];
|
||
int atomicOrdering = (*e->arguments)[2]->toInteger();
|
||
|
||
DValue *dval = toElem(exp1);
|
||
LLValue *ptr = DtoRVal(exp2);
|
||
LLType *pointeeType = DtoType(exp2->type->isTypePointer()->nextOf());
|
||
|
||
LLValue *val = nullptr;
|
||
if (pointeeType->isIntegerTy()) {
|
||
val = DtoRVal(dval);
|
||
} else if (auto atype = getAtomicType(pointeeType)) {
|
||
auto lval = makeLValue(exp1->loc, dval);
|
||
val = DtoLoad(atype, lval);
|
||
} else {
|
||
error(e->loc,
|
||
"atomic store only supports types of size 1/2/4/8/16 bytes, not `%s`",
|
||
exp1->type->toChars());
|
||
fatal();
|
||
}
|
||
|
||
llvm::StoreInst *ret = p->ir->CreateStore(val, ptr);
|
||
ret->setAtomic(llvm::AtomicOrdering(atomicOrdering));
|
||
if (auto alignment = getTypeAllocSize(val->getType())) {
|
||
ret->setAlignment(llvm::Align(alignment));
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// atomic load instruction
|
||
if (fndecl->llvmInternal == LLVMatomic_load) {
|
||
if (e->arguments->length != 2) {
|
||
error(e->loc, "atomic load instruction expects 2 arguments");
|
||
fatal();
|
||
}
|
||
|
||
Expression *exp = (*e->arguments)[0];
|
||
int atomicOrdering = (*e->arguments)[1]->toInteger();
|
||
|
||
LLValue *ptr = DtoRVal(exp);
|
||
LLType *pointeeType = DtoType(e->type);
|
||
LLType *loadedType = pointeeType;
|
||
Type *retType = exp->type->nextOf();
|
||
|
||
if (!pointeeType->isIntegerTy()) {
|
||
if (auto atype = getAtomicType(pointeeType)) {
|
||
loadedType = atype;
|
||
} else {
|
||
error(e->loc,
|
||
"atomic load only supports types of size 1/2/4/8/16 bytes, "
|
||
"not `%s`",
|
||
retType->toChars());
|
||
fatal();
|
||
}
|
||
}
|
||
|
||
llvm::LoadInst *load = p->ir->CreateLoad(loadedType, ptr);
|
||
if (auto alignment = getTypeAllocSize(loadedType)) {
|
||
load->setAlignment(llvm::Align(alignment));
|
||
}
|
||
load->setAtomic(llvm::AtomicOrdering(atomicOrdering));
|
||
llvm::Value *val = load;
|
||
if (loadedType != pointeeType) {
|
||
val = DtoAllocaDump(val, retType);
|
||
result = new DLValue(retType, val);
|
||
} else {
|
||
result = new DImValue(retType, val);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// cmpxchg instruction
|
||
if (fndecl->llvmInternal == LLVMatomic_cmp_xchg) {
|
||
if (e->arguments->length != 6) {
|
||
error(e->loc, "`cmpxchg` instruction expects 6 arguments");
|
||
fatal();
|
||
}
|
||
if (e->type->ty != TY::Tstruct) {
|
||
error(e->loc, "`cmpxchg` instruction returns a struct");
|
||
fatal();
|
||
}
|
||
Expression *exp1 = (*e->arguments)[0];
|
||
Expression *exp2 = (*e->arguments)[1];
|
||
Expression *exp3 = (*e->arguments)[2];
|
||
const auto successOrdering =
|
||
llvm::AtomicOrdering((*e->arguments)[3]->toInteger());
|
||
const auto failureOrdering =
|
||
llvm::AtomicOrdering((*e->arguments)[4]->toInteger());
|
||
const bool isWeak = (*e->arguments)[5]->toInteger() != 0;
|
||
|
||
LLValue *ptr = DtoRVal(exp1);
|
||
LLType *pointeeType = DtoType(exp1->type->isTypePointer()->nextOf());
|
||
DValue *dcmp = toElem(exp2);
|
||
DValue *dval = toElem(exp3);
|
||
|
||
LLValue *cmp = nullptr;
|
||
LLValue *val = nullptr;
|
||
if (pointeeType->isIntegerTy()) {
|
||
cmp = DtoRVal(dcmp);
|
||
val = DtoRVal(dval);
|
||
} else if (auto atype = getAtomicType(pointeeType)) {
|
||
auto cmpLVal = makeLValue(exp2->loc, dcmp);
|
||
cmp = DtoLoad(atype, cmpLVal);
|
||
auto lval = makeLValue(exp3->loc, dval);
|
||
val = DtoLoad(atype, lval);
|
||
} else {
|
||
error(e->loc,
|
||
"`cmpxchg` only supports types of size 1/2/4/8/16 bytes, not `%s`",
|
||
exp2->type->toChars());
|
||
fatal();
|
||
}
|
||
|
||
auto ret =
|
||
p->ir->CreateAtomicCmpXchg(ptr, cmp, val,
|
||
llvm::MaybeAlign(), // default alignment
|
||
successOrdering, failureOrdering);
|
||
ret->setWeak(isWeak);
|
||
|
||
// we return a struct; allocate on stack and store to both fields manually
|
||
// (avoiding DtoAllocaDump() due to bad optimized codegen, most likely
|
||
// because of i1)
|
||
auto mem = DtoAlloca(e->type);
|
||
llvm::Type* memty = DtoType(e->type);
|
||
DtoStore(p->ir->CreateExtractValue(ret, 0), DtoGEP(memty, mem, 0u, 0));
|
||
DtoStoreZextI8(p->ir->CreateExtractValue(ret, 1), DtoGEP(memty, mem, 0, 1));
|
||
|
||
result = new DLValue(e->type, mem);
|
||
return true;
|
||
}
|
||
|
||
// atomicrmw instruction
|
||
if (fndecl->llvmInternal == LLVMatomic_rmw) {
|
||
if (e->arguments->length != 3) {
|
||
error(e->loc, "`atomicrmw` instruction expects 3 arguments");
|
||
fatal();
|
||
}
|
||
|
||
TemplateInstance *ti = fndecl->parent->isTemplateInstance();
|
||
assert(ti);
|
||
const char *opString = ti->tempdecl->isTemplateDeclaration()->intrinsicName;
|
||
assert(opString);
|
||
|
||
static const char *ops[] = {"xchg", "add", "sub", "and", "nand", "or",
|
||
"xor", "max", "min", "umax", "umin", nullptr};
|
||
|
||
int op = 0;
|
||
for (;; ++op) {
|
||
if (ops[op] == nullptr) {
|
||
error(e->loc, "unknown `atomicrmw` operation `%s`", opString);
|
||
fatal();
|
||
}
|
||
if (strcmp(opString, ops[op]) == 0) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
Expression *exp1 = (*e->arguments)[0];
|
||
Expression *exp2 = (*e->arguments)[1];
|
||
int atomicOrdering = (*e->arguments)[2]->toInteger();
|
||
LLValue *ptr = DtoRVal(exp1);
|
||
LLValue *val = DtoRVal(exp2);
|
||
LLValue *ret =
|
||
p->ir->CreateAtomicRMW(llvm::AtomicRMWInst::BinOp(op), ptr, val,
|
||
llvm::MaybeAlign(), // default alignment
|
||
llvm::AtomicOrdering(atomicOrdering));
|
||
result = new DImValue(exp2->type, ret);
|
||
return true;
|
||
}
|
||
|
||
// bitop
|
||
if (fndecl->llvmInternal == LLVMbitop_bt ||
|
||
fndecl->llvmInternal == LLVMbitop_btr ||
|
||
fndecl->llvmInternal == LLVMbitop_btc ||
|
||
fndecl->llvmInternal == LLVMbitop_bts) {
|
||
if (e->arguments->length != 2) {
|
||
error(e->loc, "bitop intrinsic expects 2 arguments");
|
||
fatal();
|
||
}
|
||
|
||
Expression *exp1 = (*e->arguments)[0];
|
||
Expression *exp2 = (*e->arguments)[1];
|
||
LLValue *ptr = DtoRVal(exp1);
|
||
LLValue *bitnum = DtoRVal(exp2);
|
||
|
||
unsigned bitmask = DtoSize_t()->getBitWidth() - 1;
|
||
assert(bitmask == 31 || bitmask == 63);
|
||
// auto q = cast(size_t*)ptr + (bitnum >> (64bit ? 6 : 5));
|
||
LLValue *q =
|
||
DtoGEP1(DtoSize_t(), ptr,
|
||
p->ir->CreateLShr(bitnum, bitmask == 63 ? 6 : 5), "bitop.q");
|
||
|
||
// auto mask = 1 << (bitnum & bitmask);
|
||
LLValue *mask =
|
||
p->ir->CreateAnd(bitnum, DtoConstSize_t(bitmask), "bitop.tmp");
|
||
mask = p->ir->CreateShl(DtoConstSize_t(1), mask, "bitop.mask");
|
||
|
||
// auto result = (*q & mask) ? -1 : 0;
|
||
LLValue *val =
|
||
p->ir->CreateZExt(DtoLoad(DtoSize_t(), q, "bitop.tmp"), DtoSize_t(), "bitop.val");
|
||
LLValue *ret = p->ir->CreateAnd(val, mask, "bitop.tmp");
|
||
ret = p->ir->CreateICmpNE(ret, DtoConstSize_t(0), "bitop.tmp");
|
||
ret = p->ir->CreateSelect(ret, DtoConstInt(-1), DtoConstInt(0),
|
||
"bitop.result");
|
||
|
||
if (fndecl->llvmInternal != LLVMbitop_bt) {
|
||
llvm::Instruction::BinaryOps op;
|
||
if (fndecl->llvmInternal == LLVMbitop_btc) {
|
||
// *q ^= mask;
|
||
op = llvm::Instruction::Xor;
|
||
} else if (fndecl->llvmInternal == LLVMbitop_btr) {
|
||
// *q &= ~mask;
|
||
mask = p->ir->CreateNot(mask);
|
||
op = llvm::Instruction::And;
|
||
} else if (fndecl->llvmInternal == LLVMbitop_bts) {
|
||
// *q |= mask;
|
||
op = llvm::Instruction::Or;
|
||
} else {
|
||
llvm_unreachable("Unrecognized bitop intrinsic.");
|
||
}
|
||
|
||
LLValue *newVal = p->ir->CreateBinOp(op, val, mask, "bitop.new_val");
|
||
newVal = p->ir->CreateTrunc(newVal, DtoSize_t(), "bitop.tmp");
|
||
DtoStore(newVal, q);
|
||
}
|
||
|
||
result = new DImValue(e->type, ret);
|
||
return true;
|
||
}
|
||
|
||
if (fndecl->llvmInternal == LLVMbitop_vld) {
|
||
if (e->arguments->length != 1) {
|
||
error(e->loc, "`bitop.vld` intrinsic expects 1 argument");
|
||
fatal();
|
||
}
|
||
// TODO: Check types
|
||
|
||
Expression *exp1 = (*e->arguments)[0];
|
||
LLValue *ptr = DtoRVal(exp1);
|
||
result = new DImValue(e->type, DtoVolatileLoad(DtoType(e->type), ptr));
|
||
return true;
|
||
}
|
||
|
||
if (fndecl->llvmInternal == LLVMbitop_vst) {
|
||
if (e->arguments->length != 2) {
|
||
error(e->loc, "`bitop.vst` intrinsic expects 2 arguments");
|
||
fatal();
|
||
}
|
||
// TODO: Check types
|
||
|
||
Expression *exp1 = (*e->arguments)[0];
|
||
Expression *exp2 = (*e->arguments)[1];
|
||
LLValue *ptr = DtoRVal(exp1);
|
||
LLValue *val = DtoRVal(exp2);
|
||
DtoVolatileStore(val, ptr);
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
class ImplicitArgumentsBuilder {
|
||
public:
|
||
ImplicitArgumentsBuilder(std::vector<LLValue *> &args, AttrSet &attrs,
|
||
const Loc &loc, DValue *fnval,
|
||
LLFunctionType *llCalleeType, Expressions *argexps,
|
||
Type *resulttype, LLValue *sretPointer)
|
||
: args(args), attrs(attrs), loc(loc), fnval(fnval), argexps(argexps),
|
||
resulttype(resulttype), sretPointer(sretPointer),
|
||
// computed:
|
||
isDelegateCall(fnval->type->toBasetype()->ty == TY::Tdelegate),
|
||
dfnval(fnval->isFunc()), irFty(DtoIrTypeFunction(fnval)),
|
||
tf(DtoTypeFunction(fnval)),
|
||
llArgTypesBegin(llCalleeType->param_begin()) {}
|
||
|
||
void addImplicitArgs(bool directcall) {
|
||
if (gABI->passThisBeforeSret(tf)) {
|
||
addContext(directcall);
|
||
addSret();
|
||
} else {
|
||
addSret();
|
||
addContext(directcall);
|
||
}
|
||
|
||
addArguments();
|
||
}
|
||
|
||
bool hasContext = false; // set after addImplicitArgs invocation
|
||
|
||
private:
|
||
// passed:
|
||
std::vector<LLValue *> &args;
|
||
AttrSet &attrs;
|
||
const Loc &loc;
|
||
DValue *const fnval;
|
||
Expressions *const argexps;
|
||
Type *const resulttype;
|
||
LLValue *const sretPointer;
|
||
|
||
// computed:
|
||
const bool isDelegateCall;
|
||
DFuncValue *const dfnval;
|
||
IrFuncTy &irFty;
|
||
TypeFunction *const tf;
|
||
LLFunctionType::param_iterator llArgTypesBegin;
|
||
|
||
// Adds an optional sret pointer argument.
|
||
void addSret() {
|
||
if (!irFty.arg_sret) {
|
||
return;
|
||
}
|
||
|
||
size_t index = args.size();
|
||
LLValue *pointer = sretPointer;
|
||
if (!pointer) {
|
||
pointer = DtoRawAlloca(DtoType(tf->nextOf()),
|
||
DtoAlignment(resulttype), ".sret_tmp");
|
||
}
|
||
|
||
args.push_back(pointer);
|
||
attrs.addToParam(index, irFty.arg_sret->attrs);
|
||
|
||
// verify that sret and/or inreg attributes are set
|
||
const auto &sretAttrs = irFty.arg_sret->attrs;
|
||
(void)sretAttrs;
|
||
assert((sretAttrs.contains(LLAttribute::StructRet) ||
|
||
sretAttrs.contains(LLAttribute::InReg)) &&
|
||
"Sret arg not sret or inreg?");
|
||
}
|
||
|
||
// Adds an optional context/this pointer argument and sets hasContext.
|
||
void addContext(bool directcall) {
|
||
const bool thiscall = irFty.arg_this;
|
||
const bool nestedcall = irFty.arg_nest;
|
||
const bool objccall = irFty.type->linkage == LINK::objc;
|
||
|
||
hasContext = thiscall || nestedcall || isDelegateCall || objccall;
|
||
if (!hasContext)
|
||
return;
|
||
|
||
size_t index = args.size();
|
||
auto argtype = *(llArgTypesBegin + index);
|
||
|
||
if (dfnval && (dfnval->func->ident == Id::ensure ||
|
||
dfnval->func->ident == Id::require)) {
|
||
// can be the this "context" argument for a contract invocation
|
||
// (pass a pointer to the aggregate `this` pointer, which can naturally be
|
||
// used as the contract's parent context in case the contract features
|
||
// nested functions capturing `this` from the contract's parent)
|
||
LLValue *thisptrLval = gIR->func()->thisArg;
|
||
if (auto parentfd = dfnval->func->parent->isFuncDeclaration()) {
|
||
if (auto iface = parentfd->parent->isInterfaceDeclaration()) {
|
||
// an interface contract expects the interface pointer, not the
|
||
// class pointer
|
||
Type *thistype = gIR->func()->decl->vthis->type;
|
||
if (thistype != iface->type) {
|
||
DImValue *dthis = new DImValue(thistype, DtoLoad(DtoType(thistype),thisptrLval));
|
||
thisptrLval = DtoAllocaDump(DtoCastClass(loc, dthis, iface->type));
|
||
}
|
||
}
|
||
}
|
||
args.push_back(thisptrLval);
|
||
} else if (thiscall && dfnval && dfnval->vthis) {
|
||
|
||
if (objccall && directcall) {
|
||
|
||
// ... or a Objective-c direct call argument
|
||
if (auto func = dfnval->func->isFuncDeclaration()) {
|
||
if (auto klass = func->isThis()->isClassDeclaration()) {
|
||
|
||
// Create obj_super struct with (this, <class ref>)
|
||
auto obj_super = DtoAggrPair(
|
||
DtoBitCast(dfnval->vthis, argtype),
|
||
gIR->objc.deref(klass, getOpaquePtrType()),
|
||
"super"
|
||
);
|
||
|
||
|
||
// Allocate and store obj_super struct into a new variable.
|
||
auto clsaddr = DtoRawAlloca(obj_super->getType(), 16, "super");
|
||
DtoStore(obj_super, clsaddr);
|
||
|
||
args.push_back(clsaddr);
|
||
}
|
||
}
|
||
} else {
|
||
|
||
// ... or a normal 'this' argument
|
||
args.push_back(DtoBitCast(dfnval->vthis, argtype));
|
||
}
|
||
} else if (isDelegateCall) {
|
||
// ... or a delegate context arg
|
||
LLValue *ctxarg;
|
||
if (fnval->isLVal()) {
|
||
ctxarg = DtoLoad(getOpaquePtrType(),
|
||
DtoGEP(DtoType(fnval->type), DtoLVal(fnval), 0u, 0),
|
||
".ptr");
|
||
} else {
|
||
ctxarg = gIR->ir->CreateExtractValue(DtoRVal(fnval), 0, ".ptr");
|
||
}
|
||
args.push_back(ctxarg);
|
||
} else if (nestedcall) {
|
||
// ... or a nested function context arg
|
||
if (dfnval) {
|
||
LLValue *contextptr = DtoNestedContext(loc, dfnval->func);
|
||
args.push_back(contextptr);
|
||
} else {
|
||
args.push_back(llvm::UndefValue::get(getOpaquePtrType()));
|
||
}
|
||
} else {
|
||
error(loc, "Context argument required but none given");
|
||
fatal();
|
||
}
|
||
|
||
// add attributes
|
||
if (irFty.arg_this) {
|
||
attrs.addToParam(index, irFty.arg_this->attrs);
|
||
} else if (irFty.arg_nest) {
|
||
attrs.addToParam(index, irFty.arg_nest->attrs);
|
||
}
|
||
|
||
if (irFty.arg_objcSelector) {
|
||
assert(dfnval);
|
||
|
||
auto methodptr = gIR->objc.getMethod(dfnval->func)->selector;
|
||
args.push_back(DtoLoad(methodptr->getType(), methodptr));
|
||
}
|
||
}
|
||
|
||
// D vararg functions need a "TypeInfo[] _arguments" argument.
|
||
void addArguments() {
|
||
if (!irFty.arg_arguments) {
|
||
return;
|
||
}
|
||
|
||
int numFormalParams = tf->parameterList.length();
|
||
LLValue *argumentsArg =
|
||
getTypeinfoArrayArgumentForDVarArg(argexps, numFormalParams);
|
||
|
||
args.push_back(argumentsArg);
|
||
attrs.addToParam(args.size() - 1, irFty.arg_arguments->attrs);
|
||
}
|
||
};
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
static LLValue *DtoCallableValue(DValue *fn) {
|
||
Type *type = fn->type->toBasetype();
|
||
if (type->ty == TY::Tfunction) {
|
||
return DtoRVal(fn);
|
||
}
|
||
if (type->ty == TY::Tdelegate) {
|
||
if (fn->isLVal()) {
|
||
LLValue *dg = DtoLVal(fn);
|
||
llvm::StructType *st = isaStruct(DtoType(fn->type));
|
||
LLValue *funcptr = DtoGEP(st, dg, 0, 1);
|
||
return DtoLoad(st->getElementType(1), funcptr, ".funcptr");
|
||
}
|
||
LLValue *dg = DtoRVal(fn);
|
||
assert(isaStruct(dg));
|
||
return gIR->ir->CreateExtractValue(dg, 1, ".funcptr");
|
||
}
|
||
|
||
llvm_unreachable("Not a callable type.");
|
||
}
|
||
|
||
// FIXME: this function is a mess !
|
||
DValue *DtoCallFunction(const Loc &loc, Type *resulttype, DValue *fnval,
|
||
Expressions *arguments, LLValue *sretPointer, bool directcall) {
|
||
IF_LOG Logger::println("DtoCallFunction()");
|
||
LOG_SCOPE
|
||
|
||
// make sure the D callee type has been processed
|
||
DtoType(fnval->type);
|
||
|
||
// get func value if any
|
||
DFuncValue *const dfnval = fnval->isFunc();
|
||
|
||
// get function type info
|
||
IrFuncTy &irFty = DtoIrTypeFunction(fnval);
|
||
TypeFunction *const tf = DtoTypeFunction(fnval);
|
||
Type *const returntype = tf->next;
|
||
const TY returnTy = returntype->toBasetype()->ty;
|
||
|
||
if (resulttype == nullptr) {
|
||
resulttype = returntype;
|
||
}
|
||
|
||
// get callee llvm value
|
||
LLValue *callable = DtoCallableValue(fnval);
|
||
LLFunctionType *callableTy = irFty.funcType;
|
||
if (dfnval && dfnval->func->isCsymbol()) {
|
||
// See note in DtoDeclareFunction about K&R foward declared (void) functions
|
||
// later defined as (...) functions. We want to use the non-variadic one.
|
||
if (irFty.funcType->getNumParams() == 0 && irFty.funcType->isVarArg())
|
||
callableTy = gIR->module.getFunction(getIRMangledName(dfnval->func, LINK::c))
|
||
->getFunctionType();
|
||
}
|
||
|
||
// IF_LOG Logger::cout() << "callable: " << *callable << '\n';
|
||
|
||
// parameter attributes
|
||
AttrSet attrs;
|
||
|
||
// return attrs
|
||
attrs.addToReturn(irFty.ret->attrs);
|
||
|
||
std::vector<LLValue *> args;
|
||
args.reserve(irFty.args.size());
|
||
|
||
// handle implicit arguments (sret, context/this, _arguments)
|
||
ImplicitArgumentsBuilder iab(args, attrs, loc, fnval, callableTy, arguments,
|
||
resulttype, sretPointer);
|
||
iab.addImplicitArgs(directcall);
|
||
|
||
// handle explicit arguments
|
||
|
||
Logger::println("doing normal arguments");
|
||
IF_LOG {
|
||
Logger::println("Arguments so far: (%d)", static_cast<int>(args.size()));
|
||
Logger::indent();
|
||
for (auto &arg : args) {
|
||
Logger::cout() << *arg << '\n';
|
||
}
|
||
Logger::undent();
|
||
Logger::cout() << "Function type: " << tf->toChars() << '\n';
|
||
// Logger::cout() << "LLVM functype: " << *callable->getType() << '\n';
|
||
}
|
||
|
||
if (arguments) {
|
||
addExplicitArguments(args, attrs, irFty, callableTy, *arguments,
|
||
tf->parameterList.parameters);
|
||
}
|
||
|
||
if (irFty.arg_objcSelector) {
|
||
|
||
// Use runtime msgSend function bitcasted as original call
|
||
const char *msgSend = gABI->objcMsgSendFunc(resulttype, irFty, directcall);
|
||
auto t = callable->getType();
|
||
callable = getRuntimeFunction(loc, gIR->module, msgSend);
|
||
callable = DtoBitCast(callable, t);
|
||
}
|
||
|
||
// call the function
|
||
llvm::CallBase *call = gIR->funcGen().callOrInvoke(callable, callableTy, args,
|
||
"", tf->isnothrow());
|
||
|
||
// PGO: Insert instrumentation or attach profile metadata at indirect call
|
||
// sites.
|
||
if (!call->getCalledFunction()) {
|
||
auto &PGO = gIR->funcGen().pgo;
|
||
PGO.emitIndirectCallPGO(call, callable);
|
||
}
|
||
|
||
// get return value
|
||
const int sretArgIndex =
|
||
(irFty.arg_sret && irFty.arg_this && gABI->passThisBeforeSret(tf) ? 1
|
||
: 0);
|
||
LLValue *retllval = irFty.arg_sret ? args[sretArgIndex]
|
||
: static_cast<llvm::Instruction *>(call);
|
||
bool retValIsLVal =
|
||
(tf->isref() && returnTy != TY::Tvoid) || (irFty.arg_sret != nullptr);
|
||
|
||
if (!retValIsLVal) {
|
||
// let the ABI transform the return value back
|
||
if (DtoIsInMemoryOnly(returntype)) {
|
||
retllval = irFty.getRetLVal(returntype, retllval);
|
||
retValIsLVal = true;
|
||
} else {
|
||
retllval = irFty.getRetRVal(returntype, retllval);
|
||
}
|
||
}
|
||
|
||
// repaint the type if necessary
|
||
Type *rbase = stripModifiers(resulttype->toBasetype(), true);
|
||
Type *nextbase = stripModifiers(returntype->toBasetype(), true);
|
||
if (!rbase->equals(nextbase)) {
|
||
IF_LOG Logger::println("repainting return value from '%s' to '%s'",
|
||
returntype->toChars(), rbase->toChars());
|
||
switch (rbase->ty) {
|
||
case TY::Tarray:
|
||
if (tf->isref()) {
|
||
retllval = DtoBitCast(retllval, DtoType(pointerTo(rbase)));
|
||
} else {
|
||
retllval = DtoSlicePaint(retllval, DtoType(rbase));
|
||
}
|
||
break;
|
||
|
||
case TY::Tsarray:
|
||
if (nextbase->ty == TY::Tvector && !tf->isref()) {
|
||
if (!retValIsLVal) {
|
||
// static arrays need to be dumped to memory; use vector alignment
|
||
retllval =
|
||
DtoAllocaDump(retllval, DtoType(rbase), DtoAlignment(nextbase),
|
||
".vector_to_sarray_tmp");
|
||
retValIsLVal = true;
|
||
}
|
||
break;
|
||
}
|
||
goto unknownMismatch;
|
||
|
||
case TY::Tclass:
|
||
case TY::Taarray:
|
||
case TY::Tpointer:
|
||
if (tf->isref()) {
|
||
retllval = DtoBitCast(retllval, DtoType(pointerTo(rbase)));
|
||
} else {
|
||
retllval = DtoBitCast(retllval, DtoType(rbase));
|
||
}
|
||
break;
|
||
|
||
case TY::Tstruct:
|
||
if (nextbase->ty == TY::Taarray && !tf->isref()) {
|
||
// In the D2 frontend, the associative array type and its
|
||
// object.AssociativeArray representation are used
|
||
// interchangably in some places. However, AAs are returned
|
||
// by value and not in an sret argument, so if the struct
|
||
// type will be used, give the return value storage here
|
||
// so that we get the right amount of indirections.
|
||
LLValue *val =
|
||
DtoInsertValue(llvm::UndefValue::get(DtoType(rbase)), retllval, 0);
|
||
retllval = DtoAllocaDump(val, rbase, ".aalvaluetmp");
|
||
retValIsLVal = true;
|
||
break;
|
||
}
|
||
goto unknownMismatch;
|
||
|
||
default:
|
||
unknownMismatch:
|
||
// Unfortunately, DMD has quirks resp. bugs with regard to name
|
||
// mangling: For voldemort-type functions which return a nested
|
||
// struct, the mangled name of the return type changes during
|
||
// semantic analysis.
|
||
//
|
||
// (When the function deco is first computed as part of
|
||
// determining the return type deco, its return type part is
|
||
// left off to avoid cycles. If mangle/toDecoBuffer is then
|
||
// called again for the type, it will pick up the previous
|
||
// result and return the full deco string for the nested struct
|
||
// type, consisting of both the full mangled function name, and
|
||
// the struct identifier.)
|
||
//
|
||
// Thus, the type merging in stripModifiers does not work
|
||
// reliably, and the equality check above can fail even if the
|
||
// types only differ in a qualifier.
|
||
//
|
||
// Because a proper fix for this in the frontend is hard, we
|
||
// just carry on and hope that the frontend didn't mess up,
|
||
// i.e. that the LLVM types really match up.
|
||
//
|
||
// An example situation where this case occurs is:
|
||
// ---
|
||
// auto iota() {
|
||
// static struct Result {
|
||
// this(int) {}
|
||
// inout(Result) test() inout { return cast(inout)Result(0); }
|
||
// }
|
||
// return Result.init;
|
||
// }
|
||
// void main() { auto r = iota(); }
|
||
// ---
|
||
Logger::println("Unknown return mismatch type, ignoring.");
|
||
break;
|
||
}
|
||
IF_LOG Logger::cout() << "final return value: " << *retllval << '\n';
|
||
}
|
||
|
||
// set calling convention and parameter attributes
|
||
LLAttributeList &attrlist = attrs;
|
||
if (auto cf = call->getCalledFunction()) {
|
||
call->setCallingConv(cf->getCallingConv());
|
||
if (cf->isIntrinsic()) { // override intrinsic attrs
|
||
attrlist =
|
||
llvm::Intrinsic::getAttributes(gIR->context(), cf->getIntrinsicID());
|
||
}
|
||
} else if (dfnval) {
|
||
call->setCallingConv(getCallingConvention(dfnval->func));
|
||
} else {
|
||
call->setCallingConv(gABI->callingConv(tf, iab.hasContext));
|
||
}
|
||
// merge in function attributes set in callOrInvoke
|
||
auto attrbuildattribs = call->getAttributes().getFnAttrs();
|
||
attrlist = attrlist.addFnAttributes(
|
||
gIR->context(), llvm::AttrBuilder(gIR->context(), attrbuildattribs));
|
||
call->setAttributes(attrlist);
|
||
|
||
// Special case for struct constructor calls: For temporaries, using the
|
||
// this pointer value returned from the constructor instead of the alloca
|
||
// passed as a parameter (which has the same value anyway) might lead to
|
||
// instruction dominance issues because of the way it interacts with the
|
||
// cleanups (see struct ctor hack in ToElemVisitor::visit(CallExp *)).
|
||
if (dfnval && dfnval->func && dfnval->func->isCtorDeclaration() &&
|
||
dfnval->func->isMember2()->isStructDeclaration()) {
|
||
return new DLValue(resulttype, dfnval->vthis);
|
||
}
|
||
|
||
if (retValIsLVal) {
|
||
return new DLValue(resulttype, retllval);
|
||
}
|
||
|
||
if (rbase->ty == TY::Tarray) {
|
||
return new DSliceValue(resulttype, retllval);
|
||
}
|
||
|
||
return new DImValue(resulttype, retllval);
|
||
}
|