Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upstream riscv64 #25

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,28 @@ set(EXTCC "CC=${CMAKE_C_COMPILER}" "CXX=${CMAKE_CXX_COMPILER}")

set(gollvm_binroot "${CMAKE_CURRENT_BINARY_DIR}")

# Set MPN path according to the target processor
string(REGEX REPLACE "-" " " lht_components ${LLVM_DEFAULT_TARGET_TRIPLE})
separate_arguments(lht_components)
list(GET lht_components 0 llarch)

if( ${llarch} STREQUAL "x86_64" )
set(MPN_PATH "x86_64 generic")
elseif( ${llarch} STREQUAL "aarch64" )
set(MPN_PATH "arm64 generic")
elseif( ${llarch} STREQUAL "riscv64" )
set(MPN_PATH "riscv generic")
else()
message(SEND_ERROR "Arch ${llarch} not yet supported")
endif()

externalproject_add(libgmp
URL https://gmplib.org/download/gmp/gmp-6.2.0.tar.bz2 https://mirrors.kernel.org/gnu/gmp/gmp-6.2.0.tar.bz2
URL_MD5 c24161e0dd44cae78cd5f67193492a21
DOWNLOAD_DIR ${CMAKE_CURRENT_BINARY_DIR}/external-downloads
BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/external/gmp-build
SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/external/gmp
CONFIGURE_COMMAND <SOURCE_DIR>/configure --prefix=${EXTINSTALLDIR} ${EXTCPPFLAGS} ${EXTLDFLAGS} ${EXTCC}
CONFIGURE_COMMAND <SOURCE_DIR>/configure --prefix=${EXTINSTALLDIR} ${EXTCPPFLAGS} ${EXTLDFLAGS} ${EXTCC} --build=${LLVM_HOST_TRIPLE} --host=${LLVM_DEFAULT_TARGET_TRIPLE} MPN_PATH=${MPN_PATH}
BUILD_COMMAND make -j${PROCESSOR_COUNT} install
LOG_CONFIGURE 1
LOG_BUILD 1
Expand All @@ -59,7 +74,7 @@ externalproject_add(libmpfr
DOWNLOAD_DIR ${CMAKE_CURRENT_BINARY_DIR}/external-downloads
BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/external/mpfr
SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/external/mpfr
CONFIGURE_COMMAND <SOURCE_DIR>/configure --with-gmp=${CMAKE_CURRENT_BINARY_DIR}/external/gmp --prefix=${EXTINSTALLDIR} ${EXTCPPFLAGS} ${EXTLDFLAGS} ${EXTCC}
CONFIGURE_COMMAND <SOURCE_DIR>/configure --with-gmp=${CMAKE_CURRENT_BINARY_DIR}/external/gmp --prefix=${EXTINSTALLDIR} ${EXTCPPFLAGS} ${EXTLDFLAGS} ${EXTCC} -build=${LLVM_HOST_TRIPLE} --host=${LLVM_DEFAULT_TARGET_TRIPLE}
BUILD_COMMAND make -j${PROCESSOR_COUNT} install
LOG_CONFIGURE 1
LOG_BUILD 1
Expand All @@ -74,7 +89,7 @@ externalproject_add(libmpc
BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/external/mpc
SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/external/mpc
PREFIX ${EXTINSTALLDIR}
CONFIGURE_COMMAND <SOURCE_DIR>/configure --with-gmp=${CMAKE_CURRENT_BINARY_DIR}/external/gmp --with-mpfr=${CMAKE_CURRENT_BINARY_DIR}/external/mpfr --prefix=${EXTINSTALLDIR} ${EXTCPPFLAGS} ${EXTLDFLAGS} ${EXTCC}
CONFIGURE_COMMAND <SOURCE_DIR>/configure --with-gmp=${CMAKE_CURRENT_BINARY_DIR}/external/gmp --with-mpfr=${CMAKE_CURRENT_BINARY_DIR}/external/mpfr --prefix=${EXTINSTALLDIR} ${EXTCPPFLAGS} ${EXTLDFLAGS} ${EXTCC} -build=${LLVM_HOST_TRIPLE} --host=${LLVM_DEFAULT_TARGET_TRIPLE}
BUILD_COMMAND make -j${PROCESSOR_COUNT} install
LOG_CONFIGURE 1
LOG_BUILD 1
Expand Down
239 changes: 238 additions & 1 deletion bridge/go-llvm-cabi-oracle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ class EightByteInfo {
void incorporateScalar(Btype *bt);
void determineABITypesForARM_AAPCS();
void determineABITypesForX86_64_SysV();
void determineABITypesForRISC_V();
TypeManager *tm() const { return typeManager_; }
};

Expand All @@ -158,6 +159,9 @@ EightByteInfo::EightByteInfo(Btype *bt, TypeManager *tmgr)
determineABITypesForARM_AAPCS();
}
break;
case llvm::CallingConv::C:
determineABITypesForRISC_V();
break;
default:
llvm::errs() << "unsupported llvm::CallingConv::ID " << cconv << "\n";
break;
Expand Down Expand Up @@ -489,6 +493,80 @@ 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::determineABITypesForRISC_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 -> two float structs
if (ebr.types.size() == 2) {
assert(ebr.types[0] == tm()->llvmDoubleType() ||
ebr.types[0] == tm()->llvmFloatType() ||
ebr.types[1] == tm()->llvmDoubleType() ||
ebr.types[1] == tm()->llvmFloatType());
ebr.abiDirectType = tm()->makeLLVMTwoElementStructType(ebr.types[0], ebr.types[1]);
} 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
Expand Down Expand Up @@ -554,6 +632,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;
Expand All @@ -576,6 +658,11 @@ class ABIState {
availSIMDFPRegs_ = t;
argCount_ += 1;
}
void addDirectFloatArg() {
if (availFloatRegs_)
availFloatRegs_ -= 1;
argCount_ += 1;
}
void addIndirectArg() { argCount_ += 1; }
void addIndirectReturn() {
if (availIntRegs_)
Expand All @@ -589,13 +676,15 @@ 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; }

private:
unsigned availIntRegs_;
unsigned availSSERegs_;
unsigned availSIMDFPRegs_;
unsigned availFloatRegs_;
unsigned argCount_;
};

Expand Down Expand Up @@ -637,7 +726,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;
Expand All @@ -649,6 +739,9 @@ void CABIOracle::setCC()
case llvm::CallingConv::ARM_AAPCS:
cc_ = std::unique_ptr<CABIOracleArgumentAnalyzer>(new CABIOracleARM_AAPCS(typeManager_));
break;
case llvm::CallingConv::C:
cc_ = std::unique_ptr<CABIOracleArgumentAnalyzer>( new CABIOracleRISC_V(typeManager_));
break;
default:
llvm::errs() << "unsupported llvm::CallingConv::ID " << ccID_ << "\n";
break;
Expand Down Expand Up @@ -1155,3 +1248,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(&regsInt, &regsFloat);

// Make direct/indirect decision
CABIParamAttr attr = AttrNone;
if (canPassDirectly(regsInt, regsFloat, state)) {
std::vector<llvm::Type *> 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 &regions = 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);
}
14 changes: 14 additions & 0 deletions bridge/go-llvm-cabi-oracle.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 5 additions & 0 deletions bridge/go-llvm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Expand Down
2 changes: 1 addition & 1 deletion cmake/modules/AddGollvm.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ if(GOLLVM_USE_SPLIT_STACK)
set(C_SUPPORTS_SPLIT_STACK 1)
endif()
if(NOT C_SUPPORTS_SPLIT_STACK)
message(SEND_ERROR "C compiler does not support -fsplit-stack")
# message(SEND_ERROR "C compiler does not support -fsplit-stack")
else()
set(USING_SPLIT_STACK 1)
endif()
Expand Down
Loading