Allow to call a fucntion returning a struct

This commit is contained in:
Rui Ueyama 2020-08-27 23:16:53 +09:00
parent d63b1f410a
commit c72df1c9be
5 changed files with 137 additions and 5 deletions

View File

@ -222,6 +222,7 @@ struct Node {
Type *func_ty;
Node *args;
bool pass_by_stack;
Obj *ret_buffer;
// Goto or labeled statement
char *label;

View File

@ -112,6 +112,12 @@ static void gen_addr(Node *node) {
gen_addr(node->lhs);
println(" add $%d, %%rax", node->member->offset);
return;
case ND_FUNCALL:
if (node->ret_buffer) {
gen_expr(node);
return;
}
break;
}
error_tok(node->tok, "not an lvalue");
@ -391,10 +397,16 @@ static void push_args2(Node *args, bool first_pass) {
//
// - If a function is variadic, set the number of floating-point type
// arguments to RAX.
static int push_args(Node *args) {
static int push_args(Node *node) {
int stack = 0, gp = 0, fp = 0;
for (Node *arg = args; arg; arg = arg->next) {
// If the return type is a large struct/union, the caller passes
// a pointer to a buffer as if it were the first argument.
if (node->ret_buffer && node->ty->size > 16)
gp++;
// Load as many arguments to the registers as possible.
for (Node *arg = node->args; arg; arg = arg->next) {
Type *ty = arg->ty;
switch (ty->kind) {
@ -437,11 +449,56 @@ static int push_args(Node *args) {
stack++;
}
push_args2(args, true);
push_args2(args, false);
push_args2(node->args, true);
push_args2(node->args, false);
// If the return type is a large struct/union, the caller passes
// a pointer to a buffer as if it were the first argument.
if (node->ret_buffer && node->ty->size > 16) {
println(" lea %d(%%rbp), %%rax", node->ret_buffer->offset);
push();
}
return stack;
}
static void copy_ret_buffer(Obj *var) {
Type *ty = var->ty;
int gp = 0, fp = 0;
if (has_flonum1(ty)) {
assert(ty->size == 4 || 8 <= ty->size);
if (ty->size == 4)
println(" movss %%xmm0, %d(%%rbp)", var->offset);
else
println(" movsd %%xmm0, %d(%%rbp)", var->offset);
fp++;
} else {
for (int i = 0; i < MIN(8, ty->size); i++) {
println(" mov %%al, %d(%%rbp)", var->offset + i);
println(" shr $8, %%rax");
}
gp++;
}
if (ty->size > 8) {
if (has_flonum2(ty)) {
assert(ty->size == 12 || ty->size == 16);
if (ty->size == 12)
println(" movss %%xmm%d, %d(%%rbp)", fp, var->offset + 8);
else
println(" movsd %%xmm%d, %d(%%rbp)", fp, var->offset + 8);
} else {
char *reg1 = (gp == 0) ? "%al" : "%dl";
char *reg2 = (gp == 0) ? "%rax" : "%rdx";
for (int i = 8; i < MIN(16, ty->size); i++) {
println(" mov %s, %d(%%rbp)", reg1, var->offset + i);
println(" shr $8, %s", reg2);
}
}
}
}
// Generate code for a given node.
static void gen_expr(Node *node) {
println(" .loc %d %d", node->tok->file->file_no, node->tok->line_no);
@ -578,10 +635,16 @@ static void gen_expr(Node *node) {
return;
}
case ND_FUNCALL: {
int stack_args = push_args(node->args);
int stack_args = push_args(node);
gen_expr(node->lhs);
int gp = 0, fp = 0;
// If the return type is a large struct/union, the caller passes
// a pointer to a buffer as if it were the first argument.
if (node->ret_buffer && node->ty->size > 16)
pop(argreg64[gp++]);
for (Node *arg = node->args; arg; arg = arg->next) {
Type *ty = arg->ty;
@ -646,6 +709,14 @@ static void gen_expr(Node *node) {
println(" movswl %%ax, %%eax");
return;
}
// If the return type is a small struct, a value is returned
// using up to two registers.
if (node->ret_buffer && node->ty->size <= 16) {
copy_ret_buffer(node->ret_buffer);
println(" lea %d(%%rbp), %%rax", node->ret_buffer->offset);
}
return;
}
}

View File

@ -2236,6 +2236,11 @@ static Node *funcall(Token **rest, Token *tok, Node *fn) {
node->func_ty = ty;
node->ty = ty->return_ty;
node->args = head.next;
// If a function returns a struct, it is caller's responsibility
// to allocate a space for the return value.
if (node->ty->kind == TY_STRUCT || node->ty->kind == TY_UNION)
node->ret_buffer = new_lvar("", node->ty);
return node;
}

View File

@ -92,3 +92,26 @@ int struct_test7(Ty7 x, int n) {
default: return x.c;
}
}
Ty4 struct_test24(void) {
return (Ty4){10, 20, 30, 40};
}
Ty5 struct_test25(void) {
return (Ty5){10, 20, 30};
}
Ty6 struct_test26(void) {
return (Ty6){10, 20, 30};
}
typedef struct { unsigned char a[10]; } Ty20;
typedef struct { unsigned char a[20]; } Ty21;
Ty20 struct_test27(void) {
return (Ty20){10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
}
Ty21 struct_test28(void) {
return (Ty21){1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
}

View File

@ -168,6 +168,15 @@ int struct_test15(Ty5 x, int n) {
}
}
typedef struct { unsigned char a[10]; } Ty20;
typedef struct { unsigned char a[20]; } Ty21;
Ty4 struct_test24(void);
Ty5 struct_test25(void);
Ty6 struct_test26(void);
Ty20 struct_test27(void);
Ty21 struct_test28(void);
int main() {
ASSERT(3, ret3());
ASSERT(8, add2(3, 5));
@ -288,6 +297,29 @@ int main() {
ASSERT(20, ({ Ty5 x={10,20,30}; struct_test15(x, 1); }));
ASSERT(30, ({ Ty5 x={10,20,30}; struct_test15(x, 2); }));
ASSERT(10, struct_test24().a);
ASSERT(20, struct_test24().b);
ASSERT(30, struct_test24().c);
ASSERT(40, struct_test24().d);
ASSERT(10, struct_test25().a);
ASSERT(20, struct_test25().b);
ASSERT(30, struct_test25().c);
ASSERT(10, struct_test26().a[0]);
ASSERT(20, struct_test26().a[1]);
ASSERT(30, struct_test26().a[2]);
ASSERT(10, struct_test27().a[0]);
ASSERT(60, struct_test27().a[5]);
ASSERT(100, struct_test27().a[9]);
ASSERT(1, struct_test28().a[0]);
ASSERT(5, struct_test28().a[4]);
ASSERT(10, struct_test28().a[9]);
ASSERT(15, struct_test28().a[14]);
ASSERT(20, struct_test28().a[19]);
printf("OK\n");
return 0;
}