From e1c6240fd3e2217fe07faf67265c7a8e51aa0c0d Mon Sep 17 00:00:00 2001 From: Shao-Ce SUN Date: Sat, 7 May 2022 14:34:21 +0800 Subject: [PATCH 01/11] Create .gitmodules --- .gitmodules | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6c7e363 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,14 @@ +[submodule "gofrontend"] + path = gofrontend + url = https://github.com/plctlab/gofrontend.git + branch = master + +[submodule "libffi"] + path = libgo/libffi + url = https://github.com/plctlab/libffi.git + branch = master + +[submodule "libbacktrace"] + path = libgo/libbacktrace + url = https://github.com/plctlab/libbacktrace.git + branch = master From 000c33516cae3d24c1be9ddc5f9fb661b3d627a8 Mon Sep 17 00:00:00 2001 From: Shao-Ce SUN Date: Mon, 9 May 2022 18:44:03 +0800 Subject: [PATCH 02/11] add submodules --- .gitmodules | 9 ++------- gofrontend | 1 + libgo/libbacktrace | 1 + libgo/libffi | 1 + 4 files changed, 5 insertions(+), 7 deletions(-) create mode 160000 gofrontend create mode 160000 libgo/libbacktrace create mode 160000 libgo/libffi diff --git a/.gitmodules b/.gitmodules index 6c7e363..de0cb1f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,14 +1,9 @@ [submodule "gofrontend"] path = gofrontend url = https://github.com/plctlab/gofrontend.git - branch = master - -[submodule "libffi"] +[submodule "libgo/libffi"] path = libgo/libffi url = https://github.com/plctlab/libffi.git - branch = master - -[submodule "libbacktrace"] +[submodule "libgo/libbacktrace"] path = libgo/libbacktrace url = https://github.com/plctlab/libbacktrace.git - branch = master 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 From 22c8003835527e8c885fa7c30715231f541a819e Mon Sep 17 00:00:00 2001 From: Shao-Ce SUN Date: Mon, 9 May 2022 18:44:38 +0800 Subject: [PATCH 03/11] add modules --- .gitignore | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 From b11997a046f1c4da7d81b2c70c69e8ea4de7feff Mon Sep 17 00:00:00 2001 From: Qihan Cai Date: Tue, 17 May 2022 19:33:50 +1000 Subject: [PATCH 04/11] initial riscv cabi Change-Id: Id4bf54bb8da5cb3b98769809ad316f14101b0ff1 --- bridge/go-llvm-cabi-oracle.cpp | 359 ++++++++++++++++++++++++++++++++- bridge/go-llvm-cabi-oracle.h | 14 ++ 2 files changed, 372 insertions(+), 1 deletion(-) diff --git a/bridge/go-llvm-cabi-oracle.cpp b/bridge/go-llvm-cabi-oracle.cpp index 45c2406..8f3298f 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,55 @@ 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() +{ + assert(ebrs_.size() <= 2); + for (auto &ebr : ebrs_) { + if (ebr.abiDirectType != nullptr) + continue; + // 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(); + continue; + } + unsigned nel = ebr.offsets.size(); + unsigned bytes = ebr.offsets[nel - 1] - ebr.offsets[0] + + tm()->llvmTypeSize(ebr.types[nel - 1]); + assert(bytes && bytes <= 8); + ebr.abiDirectType = tm()->llvmArbitraryIntegerType(bytes); + } + + // See the example above for more on why this is needed. + if (ebrs_.size() == 2 && ebrs_[0].abiDirectType->isIntegerTy()) { + ebrs_[0].abiDirectType = tm()->llvmArbitraryIntegerType(8); + } +} + //...................................................................... llvm::Type *CABIParamInfo::computeABIStructType(TypeManager *tm) const @@ -554,6 +607,9 @@ class ABIState { availIntRegs_ = 8; availSIMDFPRegs_ = 8; break; + case llvm::CallingConv::C: + availIntRegs_ = 8; + availFloatRegs_ = 8; default: llvm::errs() << "unsupported llvm::CallingConv::ID " << cconv << "\n"; break; @@ -576,6 +632,11 @@ class ABIState { availSIMDFPRegs_ = t; argCount_ += 1; } + void addDirectFloatArg() { + if (availFloatRegs_) + availFloatRegs_ -= 1; + argCount_ += 1; + } void addIndirectArg() { argCount_ += 1; } void addIndirectReturn() { if (availIntRegs_) @@ -589,13 +650,16 @@ 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; } + void clearAvailFloatRegs() { availFloatRegs_ = 0; } private: unsigned availIntRegs_; unsigned availSSERegs_; unsigned availSIMDFPRegs_; + unsigned availFloatRegs_; unsigned argCount_; }; @@ -637,7 +701,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 +714,8 @@ 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_)); default: llvm::errs() << "unsupported llvm::CallingConv::ID " << ccID_ << "\n"; break; @@ -1155,3 +1222,293 @@ 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); +} + +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 From 71e6a0e4c31c1f90aa1169e52151196690803fcd Mon Sep 17 00:00:00 2001 From: Qihan Cai Date: Sun, 22 May 2022 18:57:37 +1000 Subject: [PATCH 05/11] luxvfan crosscompile Change-Id: Id5696e9d827464c0bb572b56c0d25ce8da5b65e7 --- driver/ArchCpusAttrs.h | 8 ++++++++ driver/CompileGo.cpp | 6 ++++++ driver/Driver.cpp | 4 ---- driver/GccUtils.cpp | 10 ++++++++++ driver/GnuTools.cpp | 3 +++ driver/LinuxToolChain.cpp | 4 ++++ 6 files changed, 31 insertions(+), 4 deletions(-) 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; } From e65f068523cd5db3bed17b99a4aec9ad38018c98 Mon Sep 17 00:00:00 2001 From: Qihan Cai Date: Sun, 22 May 2022 19:11:19 +1000 Subject: [PATCH 06/11] remove repeated definition of riscv cabi Change-Id: Ia2be82030b194d799eaf063ac925d37db36b1b43 --- bridge/go-llvm-cabi-oracle.cpp | 146 --------------------------------- 1 file changed, 146 deletions(-) diff --git a/bridge/go-llvm-cabi-oracle.cpp b/bridge/go-llvm-cabi-oracle.cpp index 8f3298f..ecb4e79 100644 --- a/bridge/go-llvm-cabi-oracle.cpp +++ b/bridge/go-llvm-cabi-oracle.cpp @@ -1226,152 +1226,6 @@ 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); -} - -CABIOracleRISC_V::CABIOracleRISC_V(TypeManager *typeManager) - : CABIOracleArgumentAnalyzer(typeManager) {} - - CABIParamDisp CABIOracleRISC_V::classifyArgType(Btype *btype) { int64_t sz = tm_->typeSize(btype); From 85780eab60e8ef65e61a69847084c4adf8f56a63 Mon Sep 17 00:00:00 2001 From: Qihan Cai Date: Tue, 24 May 2022 19:54:13 +1000 Subject: [PATCH 07/11] fix unsupported error message Change-Id: Ic9ada21c3689d2fc9e63d3c425b906f4cfd43f38 --- bridge/go-llvm-cabi-oracle.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bridge/go-llvm-cabi-oracle.cpp b/bridge/go-llvm-cabi-oracle.cpp index ecb4e79..c6b6c86 100644 --- a/bridge/go-llvm-cabi-oracle.cpp +++ b/bridge/go-llvm-cabi-oracle.cpp @@ -610,6 +610,7 @@ class ABIState { case llvm::CallingConv::C: availIntRegs_ = 8; availFloatRegs_ = 8; + break; default: llvm::errs() << "unsupported llvm::CallingConv::ID " << cconv << "\n"; break; @@ -716,6 +717,7 @@ void CABIOracle::setCC() break; case llvm::CallingConv::C: cc_ = std::unique_ptr( new CABIOracleRISC_V(typeManager_)); + break; default: llvm::errs() << "unsupported llvm::CallingConv::ID " << ccID_ << "\n"; break; From 091a1e2ad5a6494d66b4ef5c7ce0f3f7d9913d72 Mon Sep 17 00:00:00 2001 From: Qihan Cai Date: Tue, 24 May 2022 21:28:53 +1000 Subject: [PATCH 08/11] add assertion message Change-Id: I7a7a370ef8f065a90f98bf83044cdc3b8ca4ec4f --- bridge/go-llvm-cabi-oracle.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bridge/go-llvm-cabi-oracle.cpp b/bridge/go-llvm-cabi-oracle.cpp index c6b6c86..dc75e2d 100644 --- a/bridge/go-llvm-cabi-oracle.cpp +++ b/bridge/go-llvm-cabi-oracle.cpp @@ -518,6 +518,8 @@ void EightByteInfo::determineABITypesForX86_64_SysV() // 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); for (auto &ebr : ebrs_) { if (ebr.abiDirectType != nullptr) From 80f25434a469be45cc3538905b677b87645bb66f Mon Sep 17 00:00:00 2001 From: Qihan Cai Date: Tue, 24 May 2022 22:44:59 +1000 Subject: [PATCH 09/11] add a unit test (need to be tested on real machine), fix float issues Change-Id: Iba5040379c6233672e1670ebdaa7200e794c2db1 --- bridge/go-llvm-cabi-oracle.cpp | 50 +++-- bridge/go-llvm.cpp | 4 + .../BackendCore/BackendCABIOracleTests.cpp | 184 ++++++++++++++++++ 3 files changed, 222 insertions(+), 16 deletions(-) diff --git a/bridge/go-llvm-cabi-oracle.cpp b/bridge/go-llvm-cabi-oracle.cpp index dc75e2d..147648b 100644 --- a/bridge/go-llvm-cabi-oracle.cpp +++ b/bridge/go-llvm-cabi-oracle.cpp @@ -516,32 +516,51 @@ void EightByteInfo::determineABITypesForX86_64_SysV() // 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() -{ +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; - // 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(); - 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; } - unsigned nel = ebr.offsets.size(); - unsigned bytes = ebr.offsets[nel - 1] - ebr.offsets[0] + - tm()->llvmTypeSize(ebr.types[nel - 1]); - assert(bytes && bytes <= 8); - ebr.abiDirectType = tm()->llvmArbitraryIntegerType(bytes); } // See the example above for more on why this is needed. - if (ebrs_.size() == 2 && ebrs_[0].abiDirectType->isIntegerTy()) { + 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(); } //...................................................................... @@ -656,7 +675,6 @@ class ABIState { unsigned availFloatRegs() const { return availFloatRegs_; } void clearAvailIntRegs() { availIntRegs_ = 0; } void clearAvailSIMDFPRegs() { availSIMDFPRegs_ = 0; } - void clearAvailFloatRegs() { availFloatRegs_ = 0; } private: unsigned availIntRegs_; diff --git a/bridge/go-llvm.cpp b/bridge/go-llvm.cpp index 312b38b..2ed2ce6 100644 --- a/bridge/go-llvm.cpp +++ b/bridge/go-llvm.cpp @@ -93,6 +93,10 @@ 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"); default: std::cerr <<"Unsupported calling convention\n"; } diff --git a/unittests/BackendCore/BackendCABIOracleTests.cpp b/unittests/BackendCore/BackendCABIOracleTests.cpp index c1535ba..1b5bb6e 100644 --- a/unittests/BackendCore/BackendCABIOracleTests.cpp +++ b/unittests/BackendCore/BackendCABIOracleTests.cpp @@ -438,6 +438,190 @@ TEST(BackendCABIOracleTests, ExtendedArm64) { } } +TEST(BackendCABIOracleTests, ExtendedRISCV) { + 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(); From b19c7e15e891975d9559ef2b55cd7d134b0ba3a5 Mon Sep 17 00:00:00 2001 From: Qihan Cai Date: Tue, 24 May 2022 23:10:51 +1000 Subject: [PATCH 10/11] remove "unsupported calling convention" error Change-Id: Ided89d4e797d443242ec3ca227595f945bb73d28 --- bridge/go-llvm.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/bridge/go-llvm.cpp b/bridge/go-llvm.cpp index 2ed2ce6..a76cf32 100644 --- a/bridge/go-llvm.cpp +++ b/bridge/go-llvm.cpp @@ -97,6 +97,7 @@ Llvm_backend::Llvm_backend(llvm::LLVMContext &context, 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"; } From 8bdf08f653b1b4558370f2ba03d83fe2cbb3e13b Mon Sep 17 00:00:00 2001 From: Qihan Cai Date: Wed, 25 May 2022 12:44:30 +1000 Subject: [PATCH 11/11] add some more tests for cabioracle Change-Id: I3b3391046000b2b908c8dbd657f76e44054d0101 --- .../BackendCore/BackendCABIOracleTests.cpp | 249 +++++++++++++++++- 1 file changed, 248 insertions(+), 1 deletion(-) diff --git a/unittests/BackendCore/BackendCABIOracleTests.cpp b/unittests/BackendCore/BackendCABIOracleTests.cpp index 1b5bb6e..4c80bb8 100644 --- a/unittests/BackendCore/BackendCABIOracleTests.cpp +++ b/unittests/BackendCore/BackendCABIOracleTests.cpp @@ -438,7 +438,7 @@ TEST(BackendCABIOracleTests, ExtendedArm64) { } } -TEST(BackendCABIOracleTests, ExtendedRISCV) { +TEST(BackendCABIOracleTests, ExtendedRV64) { LLVMContext C; std::unique_ptr bep( new Llvm_backend(C, nullptr, nullptr, 0, llvm::Triple(), llvm::CallingConv::C)); @@ -869,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(); @@ -960,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); @@ -1176,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