diff --git a/gen/abi/aarch64.cpp b/gen/abi/aarch64.cpp index 144312ff07..af93a8f432 100644 --- a/gen/abi/aarch64.cpp +++ b/gen/abi/aarch64.cpp @@ -53,10 +53,6 @@ struct AArch64TargetABI : TargetABI { AArch64TargetABI() {} bool returnInArg(TypeFunction *tf, bool) override { - if (tf->isref()) { - return false; - } - Type *rt = tf->next->toBasetype(); if (rt->ty == TY::Tstruct || rt->ty == TY::Tsarray) { auto argTypes = getArgTypes(rt); diff --git a/gen/abi/abi.cpp b/gen/abi/abi.cpp index 4050964041..d0bbf9bfb3 100644 --- a/gen/abi/abi.cpp +++ b/gen/abi/abi.cpp @@ -157,11 +157,23 @@ llvm::CallingConv::ID TargetABI::callingConv(FuncDeclaration *fdecl) { ////////////////////////////////////////////////////////////////////////////// +bool TargetABI::returnInArg(TypeFunction *tf, bool needsThis) { + // default: use sret for non-PODs or if a same-typed argument would be passed + // byval + Type *rt = tf->next->toBasetype(); + return !isPOD(rt) || passByVal(tf, rt); +} + bool TargetABI::preferPassByRef(Type *t) { // simple base heuristic: use a ref for all types > 2 machine words return size(t) > 2 * target.ptrsize; } +bool TargetABI::passByVal(TypeFunction *tf, Type *t) { + // default: all POD structs and static arrays + return DtoIsInMemoryOnly(t) && isPOD(t); +} + ////////////////////////////////////////////////////////////////////////////// void TargetABI::rewriteFunctionType(IrFuncTy &fty) { @@ -183,6 +195,15 @@ void TargetABI::rewriteVarargs(IrFuncTy &fty, } } +void TargetABI::rewriteArgument(IrFuncTy &fty, + IrFuncTyArg &arg) { + // default: pass non-PODs indirectly by-value + if (!isPOD(arg.type)) { + static IndirectByvalRewrite indirectByvalRewrite; + indirectByvalRewrite.applyTo(arg); + } +} + ////////////////////////////////////////////////////////////////////////////// LLValue *TargetABI::prepareVaStart(DLValue *ap) { @@ -223,28 +244,6 @@ const char *TargetABI::objcMsgSendFunc(Type *ret, IrFuncTy &fty, bool directcall ////////////////////////////////////////////////////////////////////////////// -// Some reasonable defaults for when we don't know what ABI to use. -struct UnknownTargetABI : TargetABI { - bool returnInArg(TypeFunction *tf, bool) override { - if (tf->isref()) { - return false; - } - - // Return structs and static arrays on the stack. The latter is needed - // because otherwise LLVM tries to actually return the array in a number - // of physical registers, which leads, depending on the target, to - // either horrendous codegen or backend crashes. - Type *rt = tf->next->toBasetype(); - return passByVal(tf, rt); - } - - bool passByVal(TypeFunction *, Type *t) override { - return DtoIsInMemoryOnly(t); - } -}; - -////////////////////////////////////////////////////////////////////////////// - TargetABI *TargetABI::getTarget() { switch (global.params.targetTriple->getArch()) { case llvm::Triple::x86: @@ -283,8 +282,10 @@ TargetABI *TargetABI::getTarget() { case llvm::Triple::wasm64: return getWasmTargetABI(); default: - Logger::cout() << "WARNING: Unknown ABI, guessing...\n"; - return new UnknownTargetABI; + warning(Loc(), + "unknown target ABI, falling back to generic implementation. C/C++ " + "interop will almost certainly NOT work."); + return new TargetABI; } } @@ -303,6 +304,7 @@ struct IntrinsicABI : TargetABI { if (ty->ty != TY::Tstruct) { return; } + assert(isPOD(arg.type)); // TODO: Check that no unions are passed in or returned. LLType *abiTy = DtoUnpaddedStructType(arg.type); diff --git a/gen/abi/abi.h b/gen/abi/abi.h index 48a42e51bb..84a33f1fa3 100644 --- a/gen/abi/abi.h +++ b/gen/abi/abi.h @@ -132,7 +132,7 @@ struct TargetABI { /// caller. /// The address is passed as additional function parameter using the StructRet /// attribute. - virtual bool returnInArg(TypeFunction *tf, bool needsThis) = 0; + virtual bool returnInArg(TypeFunction *tf, bool needsThis); /// Returns true if the specified parameter type (a POD) should be passed by /// ref for `in` params with -preview=in. @@ -147,7 +147,7 @@ struct TargetABI { /// parameter. /// The LL caller needs to pass a pointer to the original argument (the memcpy /// source). - virtual bool passByVal(TypeFunction *tf, Type *t) = 0; + virtual bool passByVal(TypeFunction *tf, Type *t); /// Returns true if the 'this' argument is to be passed before the 'sret' /// argument. @@ -156,7 +156,7 @@ struct TargetABI { /// Called to give ABI the chance to rewrite the types virtual void rewriteFunctionType(IrFuncTy &fty); virtual void rewriteVarargs(IrFuncTy &fty, std::vector &args); - virtual void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg) {} + virtual void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg); /// Prepares a va_start intrinsic call by transforming the D argument (of type /// va_list) to a low-level value (of type i8*) to be passed to LLVM's diff --git a/gen/abi/arm.cpp b/gen/abi/arm.cpp index 3b5ee7b58e..4f44ec9ee8 100644 --- a/gen/abi/arm.cpp +++ b/gen/abi/arm.cpp @@ -30,8 +30,6 @@ struct ArmTargetABI : TargetABI { // AAPCS 5.4 wants composites > 4-bytes returned by arg except for // Homogeneous Aggregates of up-to 4 float types (6.1.2.1) - an HFA. // TODO: see if Tsarray should be candidate for HFA. - if (tf->isref()) - return false; Type *rt = tf->next->toBasetype(); if (!isPOD(rt)) @@ -48,8 +46,7 @@ struct ArmTargetABI : TargetABI { // clang uses byval for types > 64-bytes, then llvm backend // converts back to non-byval. Without this special handling the // optimzer generates bad code (e.g. std.random unittest crash). - t = t->toBasetype(); - return ((t->ty == TY::Tsarray || t->ty == TY::Tstruct) && size(t) > 64); + return DtoIsInMemoryOnly(t) && isPOD(t) && size(t) > 64; // Note: byval can have a codegen problem with -O1 and higher. // What happens is that load instructions are being incorrectly @@ -70,6 +67,10 @@ struct ArmTargetABI : TargetABI { } void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg) override { + TargetABI::rewriteArgument(fty, arg); + if (arg.rewrite) + return; + // structs and arrays need rewrite as i32 arrays. This keeps data layout // unchanged when passed in registers r0-r3 and is necessary to match C ABI // for struct passing. Without out this rewrite, each field or array diff --git a/gen/abi/loongarch64.cpp b/gen/abi/loongarch64.cpp index ebf486e04c..2f64104118 100644 --- a/gen/abi/loongarch64.cpp +++ b/gen/abi/loongarch64.cpp @@ -101,7 +101,6 @@ struct HardfloatRewrite : BaseBitcastABIRewrite { struct LoongArch64TargetABI : TargetABI { private: HardfloatRewrite hardfloatRewrite; - IndirectByvalRewrite indirectByvalRewrite{}; Integer2Rewrite integer2Rewrite; IntegerRewrite integerRewrite; @@ -117,23 +116,8 @@ struct LoongArch64TargetABI : TargetABI { } public: - auto returnInArg(TypeFunction *tf, bool) -> bool override { - if (tf->isref()) { - return false; - } - Type *rt = tf->next->toBasetype(); - if (!isPOD(rt)) { - return true; - } - // pass by reference when > 2*GRLEN - return size(rt) > 16; - } - - auto passByVal(TypeFunction *, Type *t) -> bool override { - if (!isPOD(t)) { - return false; - } - return size(t) > 16; + bool passByVal(TypeFunction *, Type *t) override { + return isPOD(t) && size(t) > 16; } void rewriteVarargs(IrFuncTy &fty, @@ -149,11 +133,9 @@ struct LoongArch64TargetABI : TargetABI { } void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg, bool isVararg) { - if (!isPOD(arg.type)) { - // non-PODs should be passed in memory - indirectByvalRewrite.applyTo(arg); + TargetABI::rewriteArgument(fty, arg); + if (arg.rewrite) return; - } if (!isVararg && requireHardfloatRewrite(arg.type)) { hardfloatRewrite.applyTo(arg); diff --git a/gen/abi/mips64.cpp b/gen/abi/mips64.cpp index 84d42624d4..7414386012 100644 --- a/gen/abi/mips64.cpp +++ b/gen/abi/mips64.cpp @@ -24,29 +24,11 @@ struct MIPS64TargetABI : TargetABI { explicit MIPS64TargetABI(const bool Is64Bit) : Is64Bit(Is64Bit) {} - bool returnInArg(TypeFunction *tf, bool) override { - if (tf->isref()) { - return false; - } - - Type *rt = tf->next->toBasetype(); - - if (!isPOD(rt)) - return true; - - // Return structs and static arrays on the stack. The latter is needed - // because otherwise LLVM tries to actually return the array in a number - // of physical registers, which leads, depending on the target, to - // either horrendous codegen or backend crashes. - return (rt->ty == TY::Tstruct || rt->ty == TY::Tsarray); - } - - bool passByVal(TypeFunction *, Type *t) override { - TY ty = t->toBasetype()->ty; - return ty == TY::Tstruct || ty == TY::Tsarray; - } - void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg) override { + TargetABI::rewriteArgument(fty, arg); + if (arg.rewrite) + return; + // FIXME } }; diff --git a/gen/abi/nvptx.cpp b/gen/abi/nvptx.cpp index e85b86db1f..e1eab8cc90 100644 --- a/gen/abi/nvptx.cpp +++ b/gen/abi/nvptx.cpp @@ -27,13 +27,16 @@ struct NVPTXTargetABI : TargetABI { : llvm::CallingConv::PTX_Device; } bool passByVal(TypeFunction *, Type *t) override { - t = t->toBasetype(); - return ((t->ty == TY::Tsarray || t->ty == TY::Tstruct) && size(t) > 64); + return DtoIsInMemoryOnly(t) && isPOD(t) && size(t) > 64; } bool returnInArg(TypeFunction *tf, bool) override { - return !tf->isref() && DtoIsInMemoryOnly(tf->next); + return DtoIsInMemoryOnly(tf->next); } void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg) override { + TargetABI::rewriteArgument(fty, arg); + if (arg.rewrite) + return; + Type *ty = arg.type->toBasetype(); llvm::Optional ptr; if (ty->ty == TY::Tstruct && diff --git a/gen/abi/ppc.cpp b/gen/abi/ppc.cpp index d2401b4425..51a0ae4824 100644 --- a/gen/abi/ppc.cpp +++ b/gen/abi/ppc.cpp @@ -37,29 +37,27 @@ struct PPCTargetABI : TargetABI { explicit PPCTargetABI(const bool Is64Bit) : Is64Bit(Is64Bit) {} bool returnInArg(TypeFunction *tf, bool) override { - if (tf->isref()) { - return false; - } - Type *rt = tf->next->toBasetype(); // The ABI specifies that aggregates of size 8 bytes or less are // returned in r3/r4 (ppc) or in r3 (ppc64). Looking at the IR // generated by clang this seems not to be implemented. Regardless // of size, the aggregate is always returned as sret. - return rt->ty == TY::Tsarray || rt->ty == TY::Tstruct; + return DtoIsInMemoryOnly(rt); } bool passByVal(TypeFunction *, Type *t) override { // On ppc, aggregates are always passed as an indirect value. // On ppc64, they are always passed by value. However, clang // used byval for type > 64 bytes. - t = t->toBasetype(); - return (t->ty == TY::Tsarray || t->ty == TY::Tstruct) && - (!Is64Bit || size(t) > 64); + return DtoIsInMemoryOnly(t) && isPOD(t) && (!Is64Bit || size(t) > 64); } void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg) override { + TargetABI::rewriteArgument(fty, arg); + if (arg.rewrite) + return; + Type *ty = arg.type->toBasetype(); if (ty->ty == TY::Tstruct || ty->ty == TY::Tsarray) { diff --git a/gen/abi/ppc64le.cpp b/gen/abi/ppc64le.cpp index f540f727d5..6a6f792332 100644 --- a/gen/abi/ppc64le.cpp +++ b/gen/abi/ppc64le.cpp @@ -29,26 +29,18 @@ struct PPC64LETargetABI : TargetABI { explicit PPC64LETargetABI() : hfvaToArray(8) {} - bool returnInArg(TypeFunction *tf, bool) override { - if (tf->isref()) { - return false; - } - - Type *rt = tf->next->toBasetype(); - - if (!isPOD(rt)) - return true; - - return passByVal(tf, rt); - } - bool passByVal(TypeFunction *, Type *t) override { t = t->toBasetype(); - return t->ty == TY::Tsarray || (t->ty == TY::Tstruct && size(t) > 16 && - !isHFVA(t, hfvaToArray.maxElements)); + return isPOD(t) && + (t->ty == TY::Tsarray || (t->ty == TY::Tstruct && size(t) > 16 && + !isHFVA(t, hfvaToArray.maxElements))); } void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg) override { + TargetABI::rewriteArgument(fty, arg); + if (arg.rewrite) + return; + Type *ty = arg.type->toBasetype(); if (ty->ty == TY::Tstruct || ty->ty == TY::Tsarray) { if (ty->ty == TY::Tstruct && diff --git a/gen/abi/riscv64.cpp b/gen/abi/riscv64.cpp index cac1c2958a..674f24b34c 100644 --- a/gen/abi/riscv64.cpp +++ b/gen/abi/riscv64.cpp @@ -163,24 +163,16 @@ struct RISCV64TargetABI : TargetABI { return pointerTo(Type::tvoid); } bool returnInArg(TypeFunction *tf, bool) override { - if (tf->isref()) { - return false; - } Type *rt = tf->next->toBasetype(); - if (!size(rt)) - return false; - if (!isPOD(rt)) - return true; - return size(rt) > 16; + return !isPOD(rt) || size(rt) > 16; } bool passByVal(TypeFunction *, Type *t) override { - if (!size(t)) - return false; - if (t->toBasetype()->ty == TY::Tcomplex80) { + t = t->toBasetype(); + if (t->ty == TY::Tcomplex80) { // rewrite it later to bypass the RVal problem return false; } - return size(t) > 16; + return isPOD(t) && size(t) > 16; } void rewriteVarargs(IrFuncTy &fty, @@ -196,7 +188,11 @@ struct RISCV64TargetABI : TargetABI { } void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg, bool isVararg) { - if (!isVararg && isPOD(arg.type) && requireHardfloatRewrite(arg.type)) { + TargetABI::rewriteArgument(fty, arg); + if (arg.rewrite) + return; + + if (!isVararg && requireHardfloatRewrite(arg.type)) { hardfloatRewrite.applyTo(arg); return; } @@ -208,12 +204,6 @@ struct RISCV64TargetABI : TargetABI { return; } - if (!isPOD(arg.type)) { - // non-PODs should be passed in memory - indirectByvalRewrite.applyTo(arg); - return; - } - if (isAggregate(ty) && size(ty) && size(ty) <= 16) { if (size(ty) > 8 && DtoAlignment(ty) < 16) { // pass the aggregate as {int64, int64} to avoid wrong alignment diff --git a/gen/abi/spirv.cpp b/gen/abi/spirv.cpp index 22d5128a9b..8e15a3ce51 100644 --- a/gen/abi/spirv.cpp +++ b/gen/abi/spirv.cpp @@ -27,12 +27,9 @@ struct SPIRVTargetABI : TargetABI { : llvm::CallingConv::SPIR_FUNC; } bool passByVal(TypeFunction *, Type *t) override { - t = t->toBasetype(); - return ((t->ty == TY::Tsarray || t->ty == TY::Tstruct) && size(t) > 64); + return DtoIsInMemoryOnly(t) && isPOD(t) && size(t) > 64; } bool returnInArg(TypeFunction *tf, bool) override { - if (tf->isref()) - return false; Type *retty = tf->next->toBasetype(); if (retty->ty == TY::Tsarray) return true; @@ -42,6 +39,10 @@ struct SPIRVTargetABI : TargetABI { return false; } void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg) override { + TargetABI::rewriteArgument(fty, arg); + if (arg.rewrite) + return; + Type *ty = arg.type->toBasetype(); llvm::Optional ptr; if (ty->ty == TY::Tstruct && diff --git a/gen/abi/wasm.cpp b/gen/abi/wasm.cpp index 5d5c44daa8..f42056b13e 100644 --- a/gen/abi/wasm.cpp +++ b/gen/abi/wasm.cpp @@ -57,17 +57,8 @@ struct WasmTargetABI : TargetABI { DtoAlignment(t) <= DtoAlignment(singleWrappedScalarType); } - bool returnInArg(TypeFunction *tf, bool) override { - if (tf->isref()) { - return false; - } - - Type *rt = tf->next->toBasetype(); - return passByVal(tf, rt); - } - bool passByVal(TypeFunction *, Type *t) override { - return DtoIsInMemoryOnly(t) && !isDirectlyPassedAggregate(t); + return DtoIsInMemoryOnly(t) && isPOD(t) && !isDirectlyPassedAggregate(t); } }; diff --git a/gen/abi/win64.cpp b/gen/abi/win64.cpp index 3a1fdc8df2..b7a4a1bc71 100644 --- a/gen/abi/win64.cpp +++ b/gen/abi/win64.cpp @@ -117,9 +117,6 @@ struct Win64TargetABI : TargetABI { } bool returnInArg(TypeFunction *tf, bool needsThis) override { - if (tf->isref()) - return false; - Type *rt = tf->next->toBasetype(); // for non-static member functions, MSVC++ enforces sret for all structs diff --git a/gen/abi/x86-64.cpp b/gen/abi/x86-64.cpp index f73d81f043..67100b3b1b 100644 --- a/gen/abi/x86-64.cpp +++ b/gen/abi/x86-64.cpp @@ -197,10 +197,6 @@ struct X86_64TargetABI : TargetABI { }; bool X86_64TargetABI::returnInArg(TypeFunction *tf, bool) { - if (tf->isref()) { - return false; - } - Type *rt = tf->next->toBasetype(); // x87 creal is returned on the x87 stack diff --git a/gen/abi/x86.cpp b/gen/abi/x86.cpp index 5e8cd9eb94..a8912bfc62 100644 --- a/gen/abi/x86.cpp +++ b/gen/abi/x86.cpp @@ -101,9 +101,6 @@ struct X86TargetABI : TargetABI { } bool returnInArg(TypeFunction *tf, bool needsThis) override { - if (tf->isref()) - return false; - Type *rt = getExtraLoweredReturnType(tf); const bool externD = isExternD(tf); @@ -255,6 +252,10 @@ struct X86TargetABI : TargetABI { workaroundIssue1356(args); } + void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg) override { + // all handled in rewriteFunctionType() + } + // FIXME: LDC issue #1356 // MSVC targets don't support alignment attributes for LL byval args void workaroundIssue1356(std::vector &args) const { diff --git a/gen/functions.cpp b/gen/functions.cpp index d88dc91ab0..19cb709cef 100644 --- a/gen/functions.cpp +++ b/gen/functions.cpp @@ -101,7 +101,7 @@ llvm::FunctionType *DtoFunctionType(Type *type, IrFuncTy &irFty, Type *thistype, const bool byref = f->isref() && rt->toBasetype()->ty != TY::Tvoid; llvm::AttrBuilder attrs(getGlobalContext()); - if (abi->returnInArg(f, fd && fd->needThis())) { + if (!byref && abi->returnInArg(f, fd && fd->needThis())) { // sret return llvm::AttrBuilder sretAttrs(getGlobalContext()); sretAttrs.addStructRetAttr(DtoType(rt)); diff --git a/gen/target.cpp b/gen/target.cpp index 30f9e7c972..85bcf9f374 100644 --- a/gen/target.cpp +++ b/gen/target.cpp @@ -258,7 +258,7 @@ TypeTuple *Target::toArgTypes(Type *t) { } bool Target::isReturnOnStack(TypeFunction *tf, bool needsThis) { - return gABI->returnInArg(tf, needsThis); + return !tf->isref() && gABI->returnInArg(tf, needsThis); } bool Target::preferPassByRef(Type *t) { return gABI->preferPassByRef(t); } diff --git a/gen/toir.cpp b/gen/toir.cpp index 3ddee43be5..eea09ac405 100644 --- a/gen/toir.cpp +++ b/gen/toir.cpp @@ -2930,10 +2930,14 @@ bool toInPlaceConstruction(DLValue *lhs, Expression *rhs) { // E.g., `T v = foo();` if the callee `T foo()` uses sret. // In this case, pass `&v` as hidden sret argument, i.e., let `foo()` // construct the return value directly into the lhs lvalue. - if (DtoIsReturnInArg(ce)) { - Logger::println("success, in-place-constructing sret return value"); - ToElemVisitor::call(gIR, ce, DtoLVal(lhs)); - return true; + if (!ce->f || !DtoIsIntrinsic(ce->f)) { // intrinsics don't use sret + if (auto tf = ce->e1->type->toBasetype()->isTypeFunction()) { + if (target.isReturnOnStack(tf, ce->f && ce->f->needThis())) { + Logger::println("success, in-place-constructing sret return value"); + ToElemVisitor::call(gIR, ce, DtoLVal(lhs)); + return true; + } + } } // detect .ctor(args) diff --git a/gen/tollvm.cpp b/gen/tollvm.cpp index 0f0240b857..8b96fc6b1e 100644 --- a/gen/tollvm.cpp +++ b/gen/tollvm.cpp @@ -47,15 +47,6 @@ bool DtoIsInMemoryOnly(Type *type) { return (t == TY::Tstruct || t == TY::Tsarray); } -bool DtoIsReturnInArg(CallExp *ce) { - Type *t = ce->e1->type->toBasetype(); - if (t->ty == TY::Tfunction && (!ce->f || !DtoIsIntrinsic(ce->f))) { - return gABI->returnInArg(static_cast(t), - ce->f && ce->f->needThis()); - } - return false; -} - void DtoAddExtendAttr(Type *type, llvm::AttrBuilder &attrs) { type = type->toBasetype(); if (type->isintegral() && type->ty != TY::Tvector && size(type) <= 2) { diff --git a/gen/tollvm.h b/gen/tollvm.h index 5dd04e0599..14d22d38d4 100644 --- a/gen/tollvm.h +++ b/gen/tollvm.h @@ -47,11 +47,6 @@ LLType *stripAddrSpaces(LLType *v); // memory, referencing all values via LL pointers (structs and static arrays). bool DtoIsInMemoryOnly(Type *type); -// Returns true if the callee uses sret (struct return). -// In that case, the caller needs to allocate the return value and pass its -// address as additional parameter to the callee, which will set it up. -bool DtoIsReturnInArg(CallExp *ce); - // Adds an appropriate attribute if the type should be zero or sign extended. void DtoAddExtendAttr(Type *type, llvm::AttrBuilder &attrs);