Skip to content

Fix LoongArch support #4652

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

Merged
merged 4 commits into from
May 23, 2024
Merged
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
132 changes: 124 additions & 8 deletions gen/abi/loongarch64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//===----------------------------------------------------------------------===//
//
// ABI spec:
// https://loongson.github.io/LoongArch-Documentation/LoongArch-ELF-ABI-EN.html
// https://github.com/loongson/la-abi-specs/blob/release/lapcs.adoc
//
//===----------------------------------------------------------------------===//

Expand All @@ -20,9 +20,101 @@
#include "gen/llvmhelpers.h"
#include "gen/tollvm.h"

using namespace dmd;

namespace {
struct Integer2Rewrite : BaseBitcastABIRewrite {
LLType *type(Type *t) override {
return LLStructType::get(gIR->context(),
{DtoType(Type::tint64), DtoType(Type::tint64)});
}
};

struct FlattenedFields {
Type *fields[2];
int length = 0; // use -1 to represent "no need to rewrite" condition
};

FlattenedFields visitStructFields(Type *ty, unsigned baseOffset) {
// recursively visit a POD struct to flatten it
FlattenedFields result;
if (auto ts = ty->isTypeStruct()) {
for (auto fi : ts->sym->fields) {
auto sub =
visitStructFields(fi->type->toBasetype(), baseOffset + fi->offset);
if (sub.length == -1 || result.length + sub.length > 2) {
result.length = -1;
return result;
}
for (unsigned i = 0; i < (unsigned)sub.length; ++i) {
result.fields[result.length++] = sub.fields[i];
}
}
return result;
}
switch (ty->ty) {
case TY::Tcomplex32: // treat it as {float32, float32}
result.fields[0] = pointerTo(Type::tfloat32);
result.fields[1] = pointerTo(Type::tfloat32);
result.length = 2;
break;
case TY::Tcomplex64: // treat it as {float64, float64}
result.fields[0] = pointerTo(Type::tfloat64);
result.fields[1] = pointerTo(Type::tfloat64);
result.length = 2;
break;
default:
if (ty->size() > 8) {
// field larger than GRLEN and FRLEN
result.length = -1;
break;
}
result.fields[0] = ty;
result.length = 1;
break;
}
return result;
}

struct HardfloatRewrite : BaseBitcastABIRewrite {
LLType *type(Type *ty, const FlattenedFields &flat) {
if (flat.length == 1) {
return LLStructType::get(gIR->context(), {DtoType(flat.fields[0])},
false);
}
assert(flat.length == 2);
LLType *t[2];
for (unsigned i = 0; i < 2; ++i) {
t[i] =
flat.fields[i]->isfloating()
? DtoType(flat.fields[i])
: LLIntegerType::get(gIR->context(), flat.fields[i]->size() * 8);
}
return LLStructType::get(gIR->context(), {t[0], t[1]}, false);
}
LLType *type(Type *ty) override {
return type(ty, visitStructFields(ty->toBasetype(), 0));
}
};
} // anonymous namespace

struct LoongArch64TargetABI : TargetABI {
private:
HardfloatRewrite hardfloatRewrite;
IndirectByvalRewrite indirectByvalRewrite{};
Integer2Rewrite integer2Rewrite;
IntegerRewrite integerRewrite;

bool requireHardfloatRewrite(Type *ty) {
if (!isPOD(ty) || !ty->toBasetype()->isTypeStruct())
return false;
auto result = visitStructFields(ty->toBasetype(), 0);
if (result.length <= 0)
return false;
if (result.length == 1)
return result.fields[0]->isfloating();
return result.fields[0]->isfloating() || result.fields[1]->isfloating();
}

public:
auto returnInArg(TypeFunction *tf, bool) -> bool override {
Expand All @@ -45,27 +137,51 @@ struct LoongArch64TargetABI : TargetABI {
}

void rewriteFunctionType(IrFuncTy &fty) override {
if (!fty.ret->byref) {
rewriteArgument(fty, *fty.ret);
if (!skipReturnValueRewrite(fty)) {
if (requireHardfloatRewrite(fty.ret->type)) {
// rewrite here because we should not apply this to variadic arguments
hardfloatRewrite.applyTo(*fty.ret);
} else {
rewriteArgument(fty, *fty.ret);
}
}

for (auto arg : fty.args) {
if (!arg->byref) {
rewriteArgument(fty, *arg);
if (requireHardfloatRewrite(arg->type)) {
// rewrite here because we should not apply this to variadic arguments
hardfloatRewrite.applyTo(*arg);
} else {
rewriteArgument(fty, *arg);
}
}
}
}

void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg) override {
if (arg.byref) {
return;
}

if (!isPOD(arg.type)) {
// non-PODs should be passed in memory
indirectByvalRewrite.applyTo(arg);
return;
}

Type *ty = arg.type->toBasetype();
if (ty->isintegral() && (ty->ty == TY::Tint32 || ty->ty == TY::Tuns32 ||
ty->ty == TY::Tdchar)) {
// In the LP64D ABI, both int32 and unsigned int32 are stored in
// general-purpose registers as proper sign extensions of their
// 32-bit values. So, the native ABI function's int32 arguments and
// return values should have the `signext` attribute.
// C example: https://godbolt.org/z/vcjErxj76
arg.attrs.addAttribute(LLAttribute::SExt);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refer: https://github.com/loongson/la-abi-specs/blob/release/lapcs.adoc

One exception to the above rule is that in the LP64D ABI, unsigned words, such as those representing unsigned int in C, are stored in general-purpose registers as proper sign extensions of their 32-bit values.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a source comment, I was pretty sure this has got to be wrong.

Copy link
Contributor Author

@heiher heiher May 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add a source comment and describe more here.

For example:

C

int32.c: (https://godbolt.org/z/vcjErxj76)

int check_int(int v)
{
	return v;
}

unsigned int check_uint(unsigned int v)
{
	return v;
}

llvm ir:

define dso_local noundef signext i32 @check_int(i32 noundef returned signext %0) local_unnamed_addr #0 !dbg !9 {
  ...
}

define dso_local noundef signext i32 @check_uint(i32 noundef returned signext %0) local_unnamed_addr #0 !dbg !18 {
  ...
}

D

int32.d:

extern(C) int check_int(int v)
{
	return v;
}

extern(C) uint check_uint(uint v)
{
	return v;
}

llvm ir without this patch:

define i32 @check_int(i32 returned %v_arg) local_unnamed_addr #0 {
  ...
}

define i32 @check_uint(i32 returned %v_arg) local_unnamed_addr #0 {
  ...
}

llvm ir with this patch:

define signext i32 @check_int(i32 returned signext %v_arg) local_unnamed_addr #0 {
  ...
}

define signext i32 @check_uint(i32 returned signext %v_arg) local_unnamed_addr #0 {
  ...
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that there's also TY::Tdchar, which is a 32-bit integer too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really. The corresponding type char32_t in C/C++ is also sign-extended. Thanks.

} else if (isAggregate(ty) && ty->size() && ty->size() <= 16) {
if (ty->size() > 8 && DtoAlignment(ty) < 16) {
// pass the aggregate as {int64, int64} to avoid wrong alignment
integer2Rewrite.applyToIfNotObsolete(arg);
} else {
integerRewrite.applyToIfNotObsolete(arg);
}
}
}
};

Expand Down
7 changes: 4 additions & 3 deletions runtime/druntime/src/core/thread/fiber.d
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ private
extern (C) void fiber_switchContext( void** oldp, void* newp ) nothrow @nogc;
version (AArch64)
extern (C) void fiber_trampoline() nothrow;
version (LoongArch64)
extern (C) void fiber_trampoline() nothrow;
}
else version (LDC_Windows)
{
Expand Down Expand Up @@ -1831,7 +1833,6 @@ private:
// Like others, FP registers and return address ($r1) are kept
// below the saved stack top (tstack) to hide from GC scanning.
// fiber_switchContext expects newp sp to look like this:
// 10: $r21 (reserved)
// 9: $r22 (frame pointer)
// 8: $r23
// ...
Expand All @@ -1847,8 +1848,8 @@ private:

// Only need to set return address ($r1). Everything else is fine
// zero initialized.
pstack -= size_t.sizeof * 11; // skip past space reserved for $r21-$r31
push (cast(size_t) &fiber_entryPoint);
pstack -= size_t.sizeof * 10; // skip past space reserved for $r22-$r31
push(cast(size_t) &fiber_trampoline); // see threadasm.S for docs
pstack += size_t.sizeof; // adjust sp (newp) above lr
}
else version (AsmAArch64_Posix)
Expand Down
25 changes: 24 additions & 1 deletion runtime/druntime/src/core/threadasm.S
Original file line number Diff line number Diff line change
Expand Up @@ -544,9 +544,10 @@ fiber_switchContext:
*/
.text
.globl fiber_switchContext
.type fiber_switchContext, %function
fiber_switchContext:
.cfi_startproc
.cfi_undefined $ra
.cfi_undefined 1
# reserve space on stack
addi.d $sp, $sp, -19 * 8

Expand Down Expand Up @@ -610,6 +611,28 @@ fiber_switchContext:

jr $ra
.cfi_endproc

/**
* When generating any kind of backtrace (gdb, exception handling) for
* a function called in a Fiber, we need to tell the unwinder to stop
* at our Fiber main entry point, i.e. we need to mark the bottom of
* the call stack. This can be done by clearing the return address register $ra
* prior to calling fiber_entryPoint (i.e. in fiber_switchContext) or
* using a .cfi_undefined directive for the return address register in the
* Fiber entry point. cfi_undefined seems to yield better results in gdb.
* Unfortunately we can't place it into fiber_entryPoint using inline
* asm, so we use this trampoline instead.
*/
.text
.global CSYM(fiber_trampoline)
.p2align 2
.type fiber_trampoline, %function
CSYM(fiber_trampoline):
.cfi_startproc
.cfi_undefined 1
// fiber_entryPoint never returns
bl CSYM(fiber_entryPoint)
.cfi_endproc
#elif defined(__arm__) && (defined(__ARM_EABI__) || defined(__APPLE__))
/************************************************************************************
* ARM ASM BITS
Expand Down
Loading