diff --git a/.gitignore b/.gitignore index 43c3309..7696260 100644 --- a/.gitignore +++ b/.gitignore @@ -2,12 +2,12 @@ # for the gofrontend deps (GMP, MPCR, etc) external/ -# These are separate git repos and are tracked independently -gofrontend/ -libgo/libbacktrace -libgo/libffi -libgo/libbacktrace/ -libgo/libffi/ +# # These are separate git repos and are tracked independently +# gofrontend/ +# libgo/libbacktrace +# libgo/libffi +# libgo/libbacktrace/ +# libgo/libffi/ # Compiled Object files *.slo diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..de0cb1f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "gofrontend"] + path = gofrontend + url = https://github.com/plctlab/gofrontend.git +[submodule "libgo/libffi"] + path = libgo/libffi + url = https://github.com/plctlab/libffi.git +[submodule "libgo/libbacktrace"] + path = libgo/libbacktrace + url = https://github.com/plctlab/libbacktrace.git diff --git a/bridge/go-llvm-cabi-oracle.cpp b/bridge/go-llvm-cabi-oracle.cpp index 45c2406..147648b 100644 --- a/bridge/go-llvm-cabi-oracle.cpp +++ b/bridge/go-llvm-cabi-oracle.cpp @@ -139,6 +139,7 @@ class EightByteInfo { void incorporateScalar(Btype *bt); void determineABITypesForARM_AAPCS(); void determineABITypesForX86_64_SysV(); + void determineAPITypesForRISC_V(); TypeManager *tm() const { return typeManager_; } }; @@ -158,6 +159,9 @@ EightByteInfo::EightByteInfo(Btype *bt, TypeManager *tmgr) determineABITypesForARM_AAPCS(); } break; + case llvm::CallingConv::C: + determineAPITypesForRISC_V(); + break; default: llvm::errs() << "unsupported llvm::CallingConv::ID " << cconv << "\n"; break; @@ -489,6 +493,76 @@ void EightByteInfo::determineABITypesForX86_64_SysV() ebrs_[0].abiDirectType = tm()->llvmDoubleType(); } +// Select the appropriate abi type for each eight-byte region within +// an EightByteInfo. Pure floating point types are mapped onto float, +// double, or <2 x float> (a vector type), integer types (or something +// that is a mix of integer and non-integer) are mapped onto the +// appropriately sized integer type. +// +// Problems arise in the code below when dealing with structures with +// constructs that inject additional padding. For example, consider +// the following struct passed by value: +// +// struct { +// f1 int8 +// f2 [0]uint64 +// f3 int8 +// } +// +// Without taking into account the over-alignment of field f3, we would +// wind up with two regions, each with type int8. This in itself is not so +// bad, but creating a struct from these two types (via ::computeABIStructType) +// would give us { int8, int8 }, in which the second field doesn't have +// the correct alignment. Work around this by checking for such situations +// and promoting the type of the first EBR to 64 bits. +// +void EightByteInfo::determineAPITypesForRISC_V() { + // In the direct case, ebrs_.size() cannot be greater than 2 because parameters + // larger than 16 bytes are passed indirectly. + assert(ebrs_.size() <= 2); + unsigned intRegions = 0; + unsigned floatRegions = 0; + for (auto &ebr : ebrs_) { + if (ebr.abiDirectType != nullptr) + continue; + TypDisp regionDisp = ebr.getRegionTypDisp(); + if (regionDisp == FlavSSE) { + // Case 1: two floats -> vector + if (ebr.types.size() == 2) + ebr.abiDirectType = tm()->llvmTwoFloatVecType(); + else if (ebr.types.size() == 1) { + assert(ebr.types[0] == tm()->llvmDoubleType() || + ebr.types[0] == tm()->llvmFloatType()); + ebr.abiDirectType = ebr.types[0]; + } else { + assert(false && "this should never happen"); + } + floatRegions += 1; + } else { + unsigned nel = ebr.offsets.size(); + unsigned bytes = ebr.offsets[nel-1] - ebr.offsets[0] + + tm()->llvmTypeSize(ebr.types[nel-1]); + assert(bytes && bytes <= 8); + // Preserve pointerness for the use of GC. + // TODO: this assumes pointer is 8 byte, so we never pack pointer + // and other stuff together. + if (ebr.types[0]->isPointerTy()) + ebr.abiDirectType = tm()->llvmPtrType(); + else + ebr.abiDirectType = tm()->llvmArbitraryIntegerType(bytes); + intRegions += 1; + } + } + + // See the example above for more on why this is needed. + if (intRegions == 2 && + ebrs_[0].abiDirectType->isIntegerTy()) + ebrs_[0].abiDirectType = tm()->llvmArbitraryIntegerType(8); + else if (floatRegions == 2 && + ebrs_[0].abiDirectType == tm()->llvmFloatType()) + ebrs_[0].abiDirectType = tm()->llvmDoubleType(); +} + //...................................................................... llvm::Type *CABIParamInfo::computeABIStructType(TypeManager *tm) const @@ -554,6 +628,10 @@ class ABIState { availIntRegs_ = 8; availSIMDFPRegs_ = 8; break; + case llvm::CallingConv::C: + availIntRegs_ = 8; + availFloatRegs_ = 8; + break; default: llvm::errs() << "unsupported llvm::CallingConv::ID " << cconv << "\n"; break; @@ -576,6 +654,11 @@ class ABIState { availSIMDFPRegs_ = t; argCount_ += 1; } + void addDirectFloatArg() { + if (availFloatRegs_) + availFloatRegs_ -= 1; + argCount_ += 1; + } void addIndirectArg() { argCount_ += 1; } void addIndirectReturn() { if (availIntRegs_) @@ -589,6 +672,7 @@ class ABIState { unsigned availIntRegs() const { return availIntRegs_; } unsigned availSSERegs() const { return availSSERegs_; } unsigned availSIMDFPRegs() const { return availSIMDFPRegs_; } + unsigned availFloatRegs() const { return availFloatRegs_; } void clearAvailIntRegs() { availIntRegs_ = 0; } void clearAvailSIMDFPRegs() { availSIMDFPRegs_ = 0; } @@ -596,6 +680,7 @@ class ABIState { unsigned availIntRegs_; unsigned availSSERegs_; unsigned availSIMDFPRegs_; + unsigned availFloatRegs_; unsigned argCount_; }; @@ -637,7 +722,8 @@ void CABIOracle::setCC() ccID_ = typeManager_->callingConv(); // Supported architectures at present. assert(ccID_ == llvm::CallingConv::X86_64_SysV || - ccID_ == llvm::CallingConv::ARM_AAPCS); + ccID_ == llvm::CallingConv::ARM_AAPCS || + ccID_ == llvm::CallingConv::C); if (cc_ != nullptr) { return; @@ -649,6 +735,9 @@ void CABIOracle::setCC() case llvm::CallingConv::ARM_AAPCS: cc_ = std::unique_ptr(new CABIOracleARM_AAPCS(typeManager_)); break; + case llvm::CallingConv::C: + cc_ = std::unique_ptr( new CABIOracleRISC_V(typeManager_)); + break; default: llvm::errs() << "unsupported llvm::CallingConv::ID " << ccID_ << "\n"; break; @@ -1155,3 +1244,147 @@ CABIParamInfo CABIOracleARM_AAPCS::analyzeABIReturn(Btype *resultType, } //...................................................................... + +CABIOracleRISC_V::CABIOracleRISC_V(TypeManager *typeManager) + : CABIOracleArgumentAnalyzer(typeManager) {} + +CABIParamDisp CABIOracleRISC_V::classifyArgType(Btype *btype) +{ + int64_t sz = tm_->typeSize(btype); + return (sz == 0 ? ParmIgnore : ((sz <= 16) ? ParmDirect : ParmIndirect)); +} + +// Given the number of registers that we think a param is going to consume, and +// a state object storing the registers used so far, canPassDirectly() makes a +// decision as to whether a given param can be passed directly in registers vs +// in memory. +// +// Note the first clause, "if (regsInt + regsSIMDFP == 1) return true". This may +// seem counter-intuitive (why no check against the state object?), but this way +// of doing things is the convention used by other front ends (e.g. clang). What +// is happening here is that for larger aggregate/array params (things that +// don't fit into a single register), we'll make the pass-through-memory +// semantics explicit in the function signature and generate the explict code to +// copy things into memory. For params that do fit into a single register, +// however, we just leave them all as by-value parameters and then assume that +// the back end will do the right thing (e.g. pass the first few in registers +// and then the remaining ones in memory). +// +// Doing things this way has performance advantages in that the middle-end +// (all of the machine-independent LLVM optimization passes) won't have +// to deal with the additional chunks of stack memory and code to copy +// things onto and off of the stack (not to mention the aliasing concerns +// when a local variable's address is taken and then passed in a function +// call). + +bool CABIOracleRISC_V::canPassDirectly(unsigned regsInt, + unsigned regsFloat, + ABIState &state) +{ + if (regsInt + regsFloat == 1) // see comment above + return true; + if (regsInt <= state.availIntRegs() && regsFloat <= state.availFloatRegs()) + return true; + return false; +} + +CABIParamInfo CABIOracleRISC_V::analyzeABIParam(Btype *paramType, ABIState &state) +{ + llvm::Type *ptyp = paramType->type(); + + // The only situations in which we should be seeing AuxT types here is + // in cases where we're analyzing the signatures of builtin functions, + // meaning that there should be no structures or arrays. + assert(paramType->flavor() != Btype::AuxT || ptyp->isVoidTy() || + !(ptyp->isStructTy() || ptyp->isArrayTy() || ptyp->isVectorTy() || + ptyp->isEmptyTy() || ptyp->isIntegerTy(8) || ptyp->isIntegerTy(16))); + + CABIParamDisp pdisp = classifyArgType(paramType); + + if (pdisp == ParmIgnore) { + // Empty struct or array + llvm::Type *voidType = tm_->llvmVoidType(); + return CABIParamInfo(voidType, ParmIgnore, AttrNone, -1); + } + + int sigOff = state.argCount(); + + if (pdisp == ParmIndirect) { + // Value will be passed in memory on stack. + // Stack is always in address space 0. + llvm::Type *ptrTyp = llvm::PointerType::get(ptyp, 0); + state.addIndirectArg(); + return CABIParamInfo(ptrTyp, ParmIndirect, AttrByVal, sigOff); + } + + // Figure out what to do in the direct case + assert(pdisp == ParmDirect); + EightByteInfo ebi(paramType, tm_); + + // Figure out how many registers it would take to pass this parm directly + unsigned regsInt = 0, regsFloat = 0; + ebi.getRegisterRequirements(®sInt, ®sFloat); + + // Make direct/indirect decision + CABIParamAttr attr = AttrNone; + if (canPassDirectly(regsInt, regsFloat, state)) { + std::vector abiTypes; + for (auto &ebr : ebi.regions()) { + abiTypes.push_back(ebr.abiDirectType); + if (ebr.attr != AttrNone) { + assert(attr == AttrNone || attr == ebr.attr); + attr = ebr.attr; + } + if (ebr.getRegionTypDisp() == FlavSSE) + state.addDirectFloatArg(); + else + state.addDirectIntArg(); + } + return CABIParamInfo(abiTypes, ParmDirect, attr, sigOff); + } else { + state.addIndirectArg(); + llvm::Type *ptrTyp = llvm::PointerType::get(ptyp, 0); + return CABIParamInfo(ptrTyp, ParmIndirect, AttrByVal, sigOff); + } +} + +CABIParamInfo CABIOracleRISC_V::analyzeABIReturn(Btype *resultType, + ABIState &state) { + llvm::Type *rtyp = resultType->type(); + CABIParamDisp rdisp = + (rtyp == tm_->llvmVoidType() ? ParmIgnore + : classifyArgType(resultType)); + + if (rdisp == ParmIgnore) { + // This corresponds to a function with no returns or + // returning an empty composite. + llvm::Type *voidType = tm_->llvmVoidType(); + return CABIParamInfo(voidType, ParmIgnore, AttrNone, -1); + } + + if (rdisp == ParmIndirect) { + // Return value will be passed in memory, via a hidden + // struct return param. + // It is on stack, therefore address space 0. + llvm::Type *ptrTyp = llvm::PointerType::get(rtyp, 0); + state.addIndirectReturn(); + return CABIParamInfo(ptrTyp, ParmIndirect, AttrStructReturn, 0); + } + + // Figure out what to do in the direct case + assert(rdisp == ParmDirect); + EightByteInfo ebi(resultType, tm_); + auto ®ions = ebi.regions(); + if (regions.size() == 1) { + // Single value + return CABIParamInfo(regions[0].abiDirectType, + ParmDirect, regions[0].attr, -1); + } + + // Two-element struct + assert(regions.size() == 2); + llvm::Type *abiTyp = + tm_->makeLLVMTwoElementStructType(regions[0].abiDirectType, + regions[1].abiDirectType); + return CABIParamInfo(abiTyp, ParmDirect, AttrNone, -1); +} diff --git a/bridge/go-llvm-cabi-oracle.h b/bridge/go-llvm-cabi-oracle.h index c186c38..6d39aba 100644 --- a/bridge/go-llvm-cabi-oracle.h +++ b/bridge/go-llvm-cabi-oracle.h @@ -248,4 +248,18 @@ class CABIOracleARM_AAPCS : public CABIOracleArgumentAnalyzer { bool canPassDirectly(unsigned regsInt, unsigned regsSSE, ABIState &state); }; +class CABIOracleRISC_V : public CABIOracleArgumentAnalyzer { +public: + // Given information on the param types and result type for a + // function, create an oracle object that can answer C ABI + // queries about the function. + CABIOracleRISC_V(TypeManager *typeManager); + CABIParamInfo analyzeABIParam(Btype *pType, ABIState &state); + CABIParamInfo analyzeABIReturn(Btype *resultType, ABIState &state); + +private: + CABIParamDisp classifyArgType(Btype *btype); + bool canPassDirectly(unsigned regsInt, unsigned regsSSE, ABIState &state); +}; + #endif // LLVMGOFRONTEND_GO_LLVM_CABI_ORACLE_H diff --git a/bridge/go-llvm.cpp b/bridge/go-llvm.cpp index 312b38b..a76cf32 100644 --- a/bridge/go-llvm.cpp +++ b/bridge/go-llvm.cpp @@ -93,6 +93,11 @@ Llvm_backend::Llvm_backend(llvm::LLVMContext &context, ownModule_->setDataLayout("e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128"); triple_ = llvm::Triple("aarch64-unknown-linux-gnu"); break; + case llvm::CallingConv::C: + ownModule_->setTargetTriple("riscv64-unknown-linux-gnu"); + ownModule_->setDataLayout("e-m:e-p:64:64-i64:64-i128:128-n64-S128"); + triple_ = llvm::Triple("riscv64-unknown-linux-gnu"); + break; default: std::cerr <<"Unsupported calling convention\n"; } diff --git a/driver/ArchCpusAttrs.h b/driver/ArchCpusAttrs.h index b083dda..33898bf 100644 --- a/driver/ArchCpusAttrs.h +++ b/driver/ArchCpusAttrs.h @@ -121,8 +121,16 @@ static const CpuAttrs attrs1[] = { { "", "" } // sentinel }; +// Triple: riscv64-unknown-linux-gnu +static const CpuAttrs attrs2[] = { + // first entry is default cpu march + {"sifive-u74", "+64bit,+f,+d,+m,+c,+a"}, + {"", ""} +}; + const TripleCpus triples[] = { { "x86_64-unknown-linux-gnu", &attrs0[0] }, { "aarch64-unknown-linux-gnu", &attrs1[0] }, + { "riscv64-unknown-linux-gnu", &attrs2[0] }, { "", nullptr } // sentinel }; diff --git a/driver/CompileGo.cpp b/driver/CompileGo.cpp index 6affe49..e0b9b25 100644 --- a/driver/CompileGo.cpp +++ b/driver/CompileGo.cpp @@ -411,6 +411,9 @@ bool CompileGoImpl::setup(const Action &jobAction) TargetOptions Options; + if (triple_.getArch() == llvm::Triple::riscv64) + Options.MCOptions.ABIName = "lp64d"; + auto jat = jobAction.type(); assert(jat == Action::A_CompileAndAssemble || jat == Action::A_Compile); @@ -767,6 +770,9 @@ void CompileGoImpl::setCConv() case Triple::aarch64: cconv_ = CallingConv::ARM_AAPCS; break; + case Triple::riscv64: + cconv_ = CallingConv::C; + break; default: errs() << "currently Gollvm is not supported on architecture " << triple_.getArchName().str()<< "\n"; diff --git a/driver/Driver.cpp b/driver/Driver.cpp index 8debbab..856ba15 100644 --- a/driver/Driver.cpp +++ b/driver/Driver.cpp @@ -417,10 +417,6 @@ ToolChain *Driver::setup() triple_ = defaultTargetTriple; if (const opt::Arg *arg = args_.getLastArg(gollvm::options::OPT_target_EQ)) triple_ = Triple(Triple::normalize(arg->getValue())); - if (triple_ != defaultTargetTriple) { - errs() << progname_ << ": error: gollvm doesn't support cross compiling yet\n"; - return nullptr; - } // Honor -dumpmachine if (args_.hasArg(gollvm::options::OPT_dumpmachine)) { diff --git a/driver/GccUtils.cpp b/driver/GccUtils.cpp index f8f8c8e..01fc06c 100644 --- a/driver/GccUtils.cpp +++ b/driver/GccUtils.cpp @@ -190,6 +190,16 @@ bool GCCInstallationDetector::selectLibDirs(state &s) s.triple.setTriple(triple_.str()); s.suffixes = {""}; break; + case llvm::Triple::riscv64: + s.tripleAliases = { + triple_.str(), + "riscv64-linux-gnu", "riscv64-unknown-linux-gnu" + }; + s.libdirs.push_back("/lib"); + s.libdirs.push_back("/lib64"); + s.triple.setTriple(triple_.str()); + s.suffixes = {""}; + break; default: llvm::errs() << "error: unsupported triple " << triple_.str() << " in " << __FUNCTION__ << "\n"; diff --git a/driver/GnuTools.cpp b/driver/GnuTools.cpp index ea148f5..ca80bef 100644 --- a/driver/GnuTools.cpp +++ b/driver/GnuTools.cpp @@ -218,6 +218,9 @@ void Linker::addLDM(llvm::opt::ArgStringList &cmdArgs) // Currently only support linux/arm64 cmdArgs.push_back("aarch64linux"); break; + case llvm::Triple::riscv64: + cmdArgs.push_back("elf64lriscv"); + break; default: // unhandled architecture cmdArgs.push_back("%unknown%"); diff --git a/driver/LinuxToolChain.cpp b/driver/LinuxToolChain.cpp index 73037f1..5e051c6 100644 --- a/driver/LinuxToolChain.cpp +++ b/driver/LinuxToolChain.cpp @@ -135,6 +135,10 @@ std::string Linux::getDynamicLinker(const llvm::opt::ArgList &args) Loader = "ld-linux-x86-64.so.2"; break; } + case llvm::Triple::riscv64: + LibDir = "lib"; + Loader = "ld-linux-riscv64-lp64d.so.1"; + break; } return "/" + LibDir + "/" + Loader; } diff --git a/gofrontend b/gofrontend new file mode 160000 index 0000000..6a33e7e --- /dev/null +++ b/gofrontend @@ -0,0 +1 @@ +Subproject commit 6a33e7e30c89edc12340dc470b44791bb1066feb diff --git a/libgo/libbacktrace b/libgo/libbacktrace new file mode 160000 index 0000000..4d2dd0b --- /dev/null +++ b/libgo/libbacktrace @@ -0,0 +1 @@ +Subproject commit 4d2dd0b172f2c9192f83ba93425f868f2a13c553 diff --git a/libgo/libffi b/libgo/libffi new file mode 160000 index 0000000..e67697c --- /dev/null +++ b/libgo/libffi @@ -0,0 +1 @@ +Subproject commit e67697c370e8875f28b2bf62fac25edad7558eca diff --git a/unittests/BackendCore/BackendCABIOracleTests.cpp b/unittests/BackendCore/BackendCABIOracleTests.cpp index c1535ba..4c80bb8 100644 --- a/unittests/BackendCore/BackendCABIOracleTests.cpp +++ b/unittests/BackendCore/BackendCABIOracleTests.cpp @@ -438,6 +438,190 @@ TEST(BackendCABIOracleTests, ExtendedArm64) { } } +TEST(BackendCABIOracleTests, ExtendedRV64) { + LLVMContext C; + std::unique_ptr bep( + new Llvm_backend(C, nullptr, nullptr, 0, llvm::Triple(), llvm::CallingConv::C)); + Llvm_backend *be = bep.get(); + + Btype *bi8t = be->integer_type(false, 8); + Btype *bu8t = be->integer_type(true, 8); + Btype *bu64t = be->integer_type(true, 64); + Btype *bu32t = be->integer_type(true, 32); + Btype *bi16t = be->integer_type(false, 16); + Btype *bf32t = be->float_type(32); + Btype *bf64t = be->float_type(64); + Btype *bpu64t = be->pointer_type(bu64t); + Btype *bpf64t = be->pointer_type(bf64t); + Btype *st0 = mkBackendStruct(be, nullptr); + Btype *st1 = mkBackendStruct(be, bi8t, "a", bu8t, "b", bf32t, "c", nullptr); + Btype *st2 = mkBackendStruct(be, bf64t, "f1", bf64t, "f2", nullptr); + Btype *st3 = mkBackendStruct(be, st2, "f1", bi8t, "f2", nullptr); + Btype *st4 = mkBackendStruct(be, bf32t, "f1", bf32t, "f2", nullptr); + Btype *st5 = mkBackendStruct(be, bf32t, "f1", nullptr); + Btype *st6 = mkBackendStruct(be, bf32t, "f1", bi8t, "a", bu8t, "b", + bu64t, "c", nullptr); + Btype *st7 = mkBackendStruct(be, bf32t, "f1", bu32t, "f2", nullptr); + Btype *st8 = mkBackendStruct(be, bi8t, "f1", bi16t, "f2", st7, "f3", nullptr); + Btype *stii = mkBackendStruct(be, bu64t, "a", bu64t, "b", nullptr); + Btype *stip = mkBackendStruct(be, bu64t, "a", bpu64t, "b", nullptr); + Btype *stpi = mkBackendStruct(be, bpu64t, "a", bu64t, "b", nullptr); + Btype *stpp = mkBackendStruct(be, bpu64t, "a", bpu64t, "b", nullptr); + Btype *at0 = be->array_type(bu32t, mkInt64Const(be, int64_t(0))); + Btype *at1 = be->array_type(bu32t, mkInt64Const(be, int64_t(1))); + Btype *at2 = be->array_type(bu32t, mkInt64Const(be, int64_t(3))); + Btype *at3 = be->array_type(bu8t, mkInt64Const(be, int64_t(16))); + + struct FcnItem { + FcnItem(const std::vector &r, + const std::vector &p, + const char *d, const char *t) + : results(r), parms(p), expDump(d), expTyp(t) { } + std::vector results; + std::vector parms; + const char *expDump; + const char *expTyp; + }; + + Btype *nt = nullptr; + std::vector items = { + + // 1 + FcnItem( { }, { }, + "Return: Ignore { void } sigOffset: -1 " + "Param 1: Direct AttrNest { i8* } sigOffset: 0", + "void (i8*)"), + + // 2 + FcnItem( { bi8t }, { }, + "Return: Direct AttrSext { i8 } sigOffset: -1 " + "Param 1: Direct AttrNest { i8* } sigOffset: 0", + "i8 (i8*)"), + + // 3 + FcnItem( { }, { bi8t }, + "Return: Ignore { void } sigOffset: -1 " + "Param 1: Direct AttrNest { i8* } sigOffset: 0 " + "Param 2: Direct AttrSext { i8 } sigOffset: 1", + "void (i8*, i8)"), + + // 4 + FcnItem( { }, { st5, bpf64t }, + "Return: Ignore { void } sigOffset: -1 " + "Param 1: Direct AttrNest { i8* } sigOffset: 0 " + "Param 2: Direct { float } sigOffset: 1 " + "Param 3: Direct { double* } sigOffset: 2", + "void (i8*, float, double*)"), + + // 5 + FcnItem({ bi8t, bf64t }, { bi8t, bu8t, st0 }, + "Return: Direct { { i8, double } } sigOffset: -1 " + "Param 1: Direct AttrNest { i8* } sigOffset: 0 " + "Param 2: Direct AttrSext { i8 } sigOffset: 1 " + "Param 3: Direct AttrZext { i8 } sigOffset: 2 " + "Param 4: Ignore { void } sigOffset: -1", + "{ i8, double } (i8*, i8, i8)"), + + // 6 + FcnItem({ st2 }, { st2, st0, st4, st1 }, + "Return: Direct { { double, double } } sigOffset: -1 " + "Param 1: Direct AttrNest { i8* } sigOffset: 0 " + "Param 2: Direct { double, double } sigOffset: 1 " + "Param 3: Ignore { void } sigOffset: -1 " + "Param 4: Direct { <2 x float> } sigOffset: 3 " + "Param 5: Direct { i64 } sigOffset: 4 ", + "{ double, double } (i8*, double, double, <2 x float>, i64)"), + + // 7 + FcnItem({ st3 }, { st3, st0, bu8t }, + "Return: Indirect AttrStructReturn { { { double, double }, i8 }* } sigOffset: 0 " + "Param 1: Direct AttrNest { i8* } sigOffset: 1 " + "Param 2: Indirect AttrByVal { { { double, double }, i8 }* } sigOffset: 2 " + "Param 3: Ignore { void } sigOffset: -1 " + "Param 4: Direct AttrZext { i8 } sigOffset: 3 ", + "void ({ { double, double }, i8 }*, i8*, " + "{ { double, double }, i8 }*, i8)"), + + // 8 + FcnItem( { st6 }, { st6, st6 }, + "Return: Direct { { i64, i64 } } sigOffset: -1 " + "Param 1: Direct AttrNest { i8* } sigOffset: 0 " + "Param 2: Direct { i64, i64 } sigOffset: 1 " + "Param 3: Direct { i64, i64 } sigOffset: 3", + "{ i64, i64 } (i8*, i64, i64, i64, i64)"), + + // 9 + FcnItem( { st8 }, { st8 }, + "Return: Direct { { i64, i32 } } sigOffset: -1 " + "Param 1: Direct AttrNest { i8* } sigOffset: 0 " + "Param 2: Direct { i64, i32 } sigOffset: 1", + "{ i64, i32 } (i8*, i64, i32)"), + + // 10 + FcnItem( { at0 }, { at1 }, + "Return: Ignore { void } sigOffset: -1 " + "Param 1: Direct AttrNest { i8* } sigOffset: 0 " + "Param 2: Direct { i32 } sigOffset: 1", + "void (i8*, i32)"), + + // 11 + FcnItem( { at2 }, { at3 }, + "Return: Direct { { i64, i32 } } sigOffset: -1 " + "Param 1: Direct AttrNest { i8* } sigOffset: 0 " + "Param 2: Direct { i64, i64 } sigOffset: 1", + "{ i64, i32 } (i8*, i64, i64)"), + + // 12 + // Make sure pointerness is preserved. + FcnItem( { stip }, { stii, stpp, stpi }, + "Return: Direct { { i64, i8* } } sigOffset: -1 " + "Param 1: Direct AttrNest { i8* } sigOffset: 0 " + "Param 2: Direct { i64, i64 } sigOffset: 1 " + "Param 3: Direct { i8*, i8* } sigOffset: 3 " + "Param 4: Direct { i8*, i64 } sigOffset: 5", + "{ i64, i8* } (i8*, i64, i64, i8*, i8*, i8*, i64)"), + }; + + unsigned count = 1; + for (auto &item : items) { + std::vector results; + std::vector params; + for (auto &r : item.results) + results.push_back(mkid(r)); + for (auto &p : item.parms) + params.push_back(mkid(p)); + Btype *rt = nullptr; + if (results.size() > 1) + rt = be->struct_type(results); + Btype *t = be->function_type(mkid(nt), params, results, rt, Location()); + BFunctionType *bft = t->castToBFunctionType(); + CABIOracle cab(bft, be->typeManager()); + + { + std::string reason; + bool equal = difftokens(item.expDump, cab.toString(), reason); + EXPECT_EQ("pass", equal ? "pass" : reason); + if (!equal) { + std::cerr << "count: " << count << "\n"; + std::cerr << "exp:\n" << item.expDump << "\n"; + std::cerr << "act:\n" << cab.toString() << "\n"; + } + } + { + std::string reason; + std::string result(repr(cab.getFunctionTypeForABI())); + bool equal = difftokens(item.expTyp, result, reason); + EXPECT_EQ("pass", equal ? "pass" : reason); + if (!equal) { + std::cerr << "count: " << count << "\n"; + std::cerr << "exp:\n" << item.expTyp << "\n"; + std::cerr << "act:\n" << result << "\n"; + } + } + count++; + } +} + TEST(BackendCABIOracleTests, RecursiveCall1Amd64) { FcnTestHarness h(llvm::CallingConv::X86_64_SysV); Llvm_backend *be = h.be(); @@ -685,6 +869,131 @@ TEST(BackendCABIOracleTests, RecursiveCall1Arm64) { EXPECT_FALSE(broken && "Module failed to verify."); } +TEST(BackendCABIOracleTests, RecursiveCall1RV64) { + FcnTestHarness h(llvm::CallingConv::C); + Llvm_backend *be = h.be(); + + // type s1 struct { + // f1, f2 float32 + // i1, i2, i3 int16 + // } + // type s2 struct { + // k float64 + // f1, f2 float32 + // } + // type s3 struct { + // f1, s1 + // f2, s2 + // } + // type s4 struct { + // } + // func foo(x s1, y s2, z s4, sm1 uint8, sm2 int8, w s3) s2 { + // if (sm1 == 0) { + // return y + // } + // return foo(x, y, z, sm1-1, sm2, w) + // } + // + + // Create struct types + Btype *bf32t = be->float_type(32); + Btype *bf64t = be->float_type(64); + Btype *bi16t = be->integer_type(false, 16); + Btype *bi8t = be->integer_type(false, 8); + Btype *bu8t = be->integer_type(true, 8); + Btype *s1 = mkBackendStruct(be, bf32t, "f1", bf32t, "f2", + bi16t, "i1", bi16t, "i2", bi16t, "i3", nullptr); + Btype *s2 = mkBackendStruct(be, bf64t, "k", bf32t, "f1", bf32t, "f2", + nullptr); + Btype *s3 = mkBackendStruct(be, s1, "f1", s2, "f2", nullptr); + Btype *s4 = mkBackendStruct(be, nullptr); + + // Create function type + BFunctionType *befty1 = mkFuncTyp(be, + L_PARM, s1, + L_PARM, s2, + L_PARM, s4, + L_PARM, bu8t, + L_PARM, bi8t, + L_PARM, s3, + L_RES, s2, + L_END); + Bfunction *func = h.mkFunction("foo", befty1); + + // sm1 == 0 + Bvariable *p3 = func->getNthParamVar(3); + Location loc; + Bexpression *vex = be->var_expression(p3, loc); + Bexpression *c0 = be->convert_expression(bu8t, mkInt32Const(be, 0), loc); + Bexpression *eq = be->binary_expression(OPERATOR_EQEQ, vex, c0, loc); + + // call + Bexpression *fn = be->function_code_expression(func, loc); + std::vector args; + Bvariable *p0 = func->getNthParamVar(0); + args.push_back(be->var_expression(p0, loc)); + + Bvariable *p1 = func->getNthParamVar(1); + args.push_back(be->var_expression(p1, loc)); + + Bvariable *p2 = func->getNthParamVar(2); + args.push_back(be->var_expression(p2, loc)); + + Bvariable *p3x = func->getNthParamVar(3); + Bexpression *vex3 = be->var_expression(p3x, loc); + Bexpression *c1 = be->convert_expression(bu8t, mkInt32Const(be, 1), loc); + Bexpression *minus = be->binary_expression(OPERATOR_MINUS, vex3, c1, loc); + args.push_back(minus); + + Bvariable *p4 = func->getNthParamVar(4); + args.push_back(be->var_expression(p4, loc)); + + Bvariable *p5 = func->getNthParamVar(5); + args.push_back(be->var_expression(p5, loc)); + Bexpression *call = be->call_expression(func, fn, args, nullptr, h.loc()); + + // return y + std::vector rvals1; + rvals1.push_back(be->var_expression(p1, loc)); + Bstatement *rst1 = h.mkReturn(rvals1, FcnTestHarness::NoAppend); + + // return call + std::vector rvals2; + rvals2.push_back(call); + Bstatement *rst2 = h.mkReturn(rvals2, FcnTestHarness::NoAppend); + + DECLARE_EXPECTED_OUTPUT(exp, R"RAW_RESULT( + %p3.ld.0 = load i8, i8* %p3.addr, align 1 + %sub.0 = sub i8 %p3.ld.0, 1 + %p4.ld.0 = load i8, i8* %p4.addr, align 1 + %cast.1 = bitcast { float, float, i16, i16, i16 }* %p0.addr to { <2 x float>, i48 }* + %field0.0 = getelementptr inbounds { <2 x float>, i48 }, { <2 x float>, i48 }* %cast.1, i32 0, i32 0 + %ld.1 = load <2 x float>, <2 x float>* %field0.0, align 8 + %field1.0 = getelementptr inbounds { <2 x float>, i48 }, { <2 x float>, i48 }* %cast.1, i32 0, i32 1 + %ld.2 = load i48, i48* %field1.0, align 8 + %cast.2 = bitcast { double, float, float }* %p1.addr to { double, <2 x float> }* + %field0.1 = getelementptr inbounds { double, <2 x float> }, { double, <2 x float> }* %cast.2, i32 0, i32 0 + %ld.3 = load double, double* %field0.1, align 8 + %field1.1 = getelementptr inbounds { double, <2 x float> }, { double, <2 x float> }* %cast.2, i32 0, i32 1 + %ld.4 = load <2 x float>, <2 x float>* %field1.1, align 8 + %call.0 = call addrspace(0) { double, <2 x float> } @foo(i8* nest undef, <2 x float> %ld.1, i48 %ld.2, double %ld.3, <2 x float> %ld.4, i8 zeroext %sub.0, i8 signext %p4.ld.0, { { float, float, i16, i16, i16 }, { double, float, float } }* byval({ { float, float, i16, i16, i16 }, { double, float, float } }) %p5) + %cast.3 = bitcast { double, float, float }* %sret.actual.0 to { double, <2 x float> }* + store { double, <2 x float> } %call.0, { double, <2 x float> }* %cast.3, align 8 + %cast.4 = bitcast { double, float, float }* %sret.actual.0 to { double, <2 x float> }* + %ld.5 = load { double, <2 x float> }, { double, <2 x float> }* %cast.4, align 8 + ret { double, <2 x float> } %ld.5 + )RAW_RESULT"); + + bool isOK = h.expectStmt(rst2, exp); + EXPECT_TRUE(isOK && "Statement does not have expected contents"); + + // if statement + h.mkIf(eq, rst1, rst2); + + bool broken = h.finish(PreserveDebugInfo); + EXPECT_FALSE(broken && "Module failed to verify."); +} + TEST(BackendCABIOracleTests, PassAndReturnArraysAmd64) { FcnTestHarness h(llvm::CallingConv::X86_64_SysV); Llvm_backend *be = h.be(); @@ -776,6 +1085,53 @@ TEST(BackendCABIOracleTests, PassAndReturnArraysArm64) { EXPECT_FALSE(broken && "Module failed to verify."); } +TEST(BackendCABIOracleTests, PassAndReturnArraysRV64) { + FcnTestHarness h(llvm::CallingConv::C); + Llvm_backend *be = h.be(); + + Btype *bf32t = be->float_type(32); + Btype *bf64t = be->float_type(64); + Btype *at2f = be->array_type(bf32t, mkInt64Const(be, int64_t(2))); + Btype *at3d = be->array_type(bf64t, mkInt64Const(be, int64_t(3))); + + // func foo(fp [2]float32) [3]float64 + BFunctionType *befty1 = mkFuncTyp(be, + L_PARM, at2f, + L_RES, at3d, + L_END); + Bfunction *func = h.mkFunction("foo", befty1); + + // foo(fp) + Location loc; + Bvariable *p0 = func->getNthParamVar(0); + Bexpression *vex = be->var_expression(p0, loc); + Bexpression *fn = be->function_code_expression(func, loc); + std::vector args; + args.push_back(vex); + Bexpression *call = be->call_expression(func, fn, args, nullptr, h.loc()); + + // return foo(fp) + std::vector rvals; + rvals.push_back(call); + h.mkReturn(rvals); + + DECLARE_EXPECTED_OUTPUT(exp, R"RAW_RESULT( + %cast.0 = bitcast [2 x float]* %p0.addr to <2 x float>* + %ld.0 = load <2 x float>, <2 x float>* %cast.0, align 8 + call addrspace(0) void @foo([3 x double]* sret([3 x double]) "go_sret" %sret.actual.0, i8* nest undef, <2 x float> %ld.0) + %cast.1 = bitcast [3 x double]* %sret.formal.0 to i8* + %cast.2 = bitcast [3 x double]* %sret.actual.0 to i8* + call addrspace(0) void @llvm.memcpy.p0i8.p0i8.i64(i8* align 8 %cast.1, i8* align 8 %cast.2, i64 24, i1 false) + ret void + )RAW_RESULT"); + + bool isOK = h.expectBlock(exp); + EXPECT_TRUE(isOK && "Block does not have expected contents"); + + bool broken = h.finish(PreserveDebugInfo); + EXPECT_FALSE(broken && "Module failed to verify."); +} + TEST_P(BackendCABIOracleTests, EmptyStructParamsAndReturns) { auto cc = GetParam(); FcnTestHarness h(cc); @@ -992,4 +1348,79 @@ TEST(BackendCABIOracleTests, PassAndReturnComplexArm64) { EXPECT_FALSE(broken && "Module failed to verify."); } +TEST(BackendCABIOracleTests, PassAndReturnComplexRV64) { + FcnTestHarness h(llvm::CallingConv::C); + Llvm_backend *be = h.be(); + + Btype *bc64t = be->complex_type(64); + Btype *bc128t = be->complex_type(128); + + // func foo(x complex64, y complex128) complex64 + BFunctionType *befty1 = mkFuncTyp(be, + L_PARM, bc64t, + L_PARM, bc128t, + L_RES, bc64t, + L_END); + Bfunction *func = h.mkFunction("foo", befty1); + + // z = foo(x, y) + Location loc; + Bvariable *x = func->getNthParamVar(0); + Bvariable *y = func->getNthParamVar(1); + Bexpression *xvex = be->var_expression(x, loc); + Bexpression *yvex = be->var_expression(y, loc); + Bexpression *fn1 = be->function_code_expression(func, loc); + std::vector args1 = {xvex, yvex}; + Bexpression *call1 = be->call_expression(func, fn1, args1, nullptr, h.loc()); + h.mkLocal("z", bc64t, call1); + + // Call with constant args + // foo(1+2i, 3+4i) + mpc_t mpc_val1, mpc_val2; + mpc_init2(mpc_val1, 256); + mpc_set_d_d(mpc_val1, 1.0, 2.0, GMP_RNDN); + mpc_init2(mpc_val2, 256); + mpc_set_d_d(mpc_val2, 3.0, 4.0, GMP_RNDN); + Bexpression *ccon1 = be->complex_constant_expression(bc64t, mpc_val1); + Bexpression *ccon2 = be->complex_constant_expression(bc128t, mpc_val2); + mpc_clear(mpc_val1); + mpc_clear(mpc_val2); + Bexpression *fn2 = be->function_code_expression(func, loc); + std::vector args2 = {ccon1, ccon2}; + Bexpression *call2 = be->call_expression(func, fn2, args2, nullptr, h.loc()); + + // return the call expr above + std::vector rvals = {call2}; + h.mkReturn(rvals); + + DECLARE_EXPECTED_OUTPUT(exp, R"RAW_RESULT( + %cast.0 = bitcast { float, float }* %p0.addr to <2 x float>* + %ld.0 = load <2 x float>, <2 x float>* %cast.0, align 8 + %field0.0 = getelementptr inbounds { double, double }, { double, double }* %p1.addr, i32 0, i32 0 + %ld.1 = load double, double* %field0.0, align 8 + %field1.0 = getelementptr inbounds { double, double }, { double, double }* %p1.addr, i32 0, i32 1 + %ld.2 = load double, double* %field1.0, align 8 + %call.0 = call addrspace(0) <2 x float> @foo(i8* nest undef, <2 x float> %ld.0, double %ld.1, double %ld.2) + %cast.2 = bitcast { float, float }* %sret.actual.0 to <2 x float>* + store <2 x float> %call.0, <2 x float>* %cast.2, align 8 + %cast.3 = bitcast { float, float }* %z to i8* + %cast.4 = bitcast { float, float }* %sret.actual.0 to i8* + call addrspace(0) void @llvm.memcpy.p0i8.p0i8.i64(i8* align 4 %cast.3, i8* align 4 %cast.4, i64 8, i1 false) + %ld.3 = load <2 x float>, <2 x float>* bitcast ({ float, float }* @const.0 to <2 x float>*), align 8 + %ld.4 = load double, double* getelementptr inbounds ({ double, double }, { double, double }* @const.1, i32 0, i32 0), align 8 + %ld.5 = load double, double* getelementptr inbounds ({ double, double }, { double, double }* @const.1, i32 0, i32 1), align 8 + %call.1 = call addrspace(0) <2 x float> @foo(i8* nest undef, <2 x float> %ld.3, double %ld.4, double %ld.5) + %cast.7 = bitcast { float, float }* %sret.actual.1 to <2 x float>* + store <2 x float> %call.1, <2 x float>* %cast.7, align 8 + %cast.8 = bitcast { float, float }* %sret.actual.1 to <2 x float>* + %ld.6 = load <2 x float>, <2 x float>* %cast.8, align 8 + ret <2 x float> %ld.6 + )RAW_RESULT"); + + bool isOK = h.expectBlock(exp); + EXPECT_TRUE(isOK && "Block does not have expected contents"); + + bool broken = h.finish(PreserveDebugInfo); + EXPECT_FALSE(broken && "Module failed to verify."); +} } // namespace