diff --git a/src/defs.h b/src/defs.h index 761b70b7..0d6f3703 100644 --- a/src/defs.h +++ b/src/defs.h @@ -408,6 +408,7 @@ struct var { int loop_depth; /* Nesting depth if variable is in a loop */ int use_count; /* Number of times variable is used */ bool space_is_allocated; /* whether space is allocated for this variable */ + bool aggregate_storage_is_allocated; /* This flag is used to indicate to the compiler that the offset of * the variable is based on the top of the local stack. diff --git a/src/parser.c b/src/parser.c index 74d75a30..ad3ae6a0 100644 --- a/src/parser.c +++ b/src/parser.c @@ -3321,10 +3321,15 @@ bool read_body_assignment(char *token, } else if (lex_peek(T_open_bracket, NULL)) { /* dereference lvalue into function address */ rs1 = opstack_pop(); - vd = require_var(parent); - gen_name_to(vd->var_name); - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, rs1, NULL, PTR_SIZE, NULL); + + if (lvalue.is_reference) { + vd = require_var(parent); + gen_name_to(vd->var_name); + opstack_push(vd); + add_insn(parent, *bb, OP_read, vd, rs1, NULL, PTR_SIZE, NULL); + } else { + opstack_push(rs1); + } read_indirect_call(parent, bb); return true; @@ -3455,6 +3460,16 @@ bool read_body_assignment(char *token, if (lvalue.is_func) { rs2 = opstack_pop(); rs1 = opstack_pop(); + + if (!lvalue.is_reference) { + var_t *addr = + require_ref_var(parent, lvalue.type, lvalue.ptr_level); + gen_name_to(addr->var_name); + add_insn(parent, *bb, OP_address_of, addr, rs1, NULL, 0, + NULL); + rs1 = addr; + } + add_insn(parent, *bb, OP_write, NULL, rs1, rs2, PTR_SIZE, NULL); } else if (lvalue.is_reference) { rs2 = opstack_pop(); diff --git a/src/reg-alloc.c b/src/reg-alloc.c index e2d90d1c..b5e7aab5 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -44,6 +44,81 @@ int align_size(int i) return i <= 4 ? 4 : (i + 3) & ~3; } +bool aggregate_has_func_member_seen(type_t *type, type_t **seen, int seen_count) +{ + if (!type) + return false; + + for (int i = 0; i < seen_count; i++) + if (seen[i] == type) + return false; + + if (seen_count >= MAX_TYPES) + return false; + seen[seen_count++] = type; + + if (type->base_type == TYPE_typedef && type->base_struct) + type = type->base_struct; + + for (int i = 0; i < type->num_fields; i++) { + var_t *field = &type->fields[i]; + if (field->is_func) + return true; + + /* Pointer fields do not contain the pointed-to object. */ + if (!field->ptr_level && field->type && !field->type->ptr_level && + (field->type->base_type == TYPE_struct || + field->type->base_type == TYPE_union || + field->type->base_type == TYPE_typedef) && + aggregate_has_func_member_seen(field->type, seen, seen_count)) + return true; + } + + return false; +} + +bool aggregate_has_func_member(type_t *type) +{ + type_t *seen[MAX_TYPES]; + return aggregate_has_func_member_seen(type, seen, 0); +} + +bool var_is_func_param(func_t *func, var_t *var) +{ + if (!func || !var) + return false; + + for (int i = 0; i < func->num_params; i++) { + var_t *param = &func->param_defs[i]; + if (var == param || var->base == param || + (var->var_name[0] && !strcmp(var->var_name, param->var_name))) + return true; + for (int j = 0; j < param->subscripts_idx; j++) + if (var == param->subscripts[j]) + return true; + } + + return false; +} + +int func_param_index(func_t *func, var_t *var) +{ + if (!func || !var) + return -1; + + for (int i = 0; i < func->num_params; i++) { + var_t *param = &func->param_defs[i]; + if (var == param || var->base == param || + (var->var_name[0] && !strcmp(var->var_name, param->var_name))) + return i; + for (int j = 0; j < param->subscripts_idx; j++) + if (var == param->subscripts[j]) + return i; + } + + return -1; +} + bool check_live_out(basic_block_t *bb, var_t *var) { for (int i = 0; i < bb->live_out.size; i++) { @@ -709,6 +784,10 @@ void reg_alloc(void) else func->stack_size += align_size(sz); + if (!insn->rd->is_global && + aggregate_has_func_member(insn->rd->type)) + insn->rd->aggregate_storage_is_allocated = true; + dest = prepare_dest(bb, insn->rd, -1, -1); ir = bb_add_ph2_ir(bb, OP_address_of); ir->src0 = src0; @@ -747,6 +826,91 @@ void reg_alloc(void) insn->rs1->address_taken = true; insn->rs1->is_const = false; + /* OP_allocat reserves a word at offset for spilling the + * address of a local function-pointer aggregate, followed + * by its backing storage. Address-of must target that + * backing storage, never the spill slot: otherwise a + * later register spill overwrites a function member. + * + * Parameters do not have OP_allocat, so materialize their + * incoming value in the same layout before taking its + * address. + */ + if (!insn->rs1->is_global && + aggregate_has_func_member(insn->rs1->type)) { + if (!insn->rs1->aggregate_storage_is_allocated) { + int src = -1; + bool is_param = + var_is_func_param(func, insn->rs1) || + !insn->rs1->space_is_allocated; + + if (is_param) { + /* Load the incoming value while its original + * parameter offset is still available. */ + int param_idx = + func_param_index(func, insn->rs1); + if (param_idx >= 0 && + param_idx < MAX_ARGS_IN_REG) + src = param_idx; + else if (param_idx >= MAX_ARGS_IN_REG) + src = prepare_operand(bb, insn->rs1, -1); + else { + for (int i = 0; i < REG_CNT; i++) + if (REGS[i].var && + aggregate_has_func_member( + REGS[i].var->type)) { + src = i; + break; + } + if (src < 0) + src = 0; + } + /* Aggregate arguments are passed by address; + * copy src aggregate value, not that address. + */ + ir = bb_add_ph2_ir(bb, OP_read); + ir->src0 = src; + ir->src1 = insn->rs1->type->size; + ir->dest = src; + } + + insn->rs1->offset = func->stack_size; + insn->rs1->space_is_allocated = true; + insn->rs1->ofs_based_on_stack_top = false; + func->stack_size += PTR_SIZE; + if (insn->rs1->ptr_level) + sz = PTR_SIZE; + else + sz = insn->rs1->type->size; + if (insn->rs1->array_size) + func->stack_size += + align_size(insn->rs1->array_size * sz); + else + func->stack_size += align_size(sz); + insn->rs1->aggregate_storage_is_allocated = true; + + if (src >= 0) { + ir = bb_add_ph2_ir(bb, OP_store); + ir->src0 = src; + ir->src1 = insn->rs1->offset; + ir = bb_add_ph2_ir(bb, OP_store); + ir->src0 = src; + ir->src1 = insn->rs1->offset + PTR_SIZE; + vreg_clear_phys(insn->rs1); + REGS[src].var = NULL; + REGS[src].polluted = 0; + } + } + + dest = prepare_dest(bb, insn->rd, -1, -1); + ir = bb_add_ph2_ir(bb, OP_address_of); + ir->src0 = insn->rs1->offset + PTR_SIZE; + ir->dest = dest; + ir->ofs_based_on_stack_top = + insn->rs1->ofs_based_on_stack_top; + break; + } + /* make sure variable is on stack */ if (!insn->rs1->space_is_allocated) { insn->rs1->offset = bb->belong_to->stack_size; @@ -912,6 +1076,9 @@ void reg_alloc(void) is_pushing_args = false; args = 0; handle_abi = false; + + for (int i = 0; i < REG_CNT; i++) + REGS[i].var = NULL; break; case OP_func_ret: dest = prepare_dest(bb, insn->rd, -1, -1); diff --git a/tests/driver.sh b/tests/driver.sh index 7636cc6d..4d8f2f8a 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -1643,6 +1643,94 @@ int main() { } EOF +# Local function pointer, direct struct member, and pointer-to-struct member. +# The first path must use the pointer value directly; the latter two must load +# the pointer from the member slot. +try_ 6 << EOF +typedef struct { + int (*fn)(int); +} holder_t; + +int suc(int x) { return x + 1; } + +int main() { + int (*local)(int); + holder_t h; + holder_t *p = &h; + + local = suc; + h.fn = suc; + p->fn = suc; + + return local(1) + h.fn(1) + p->fn(1); +} +EOF + +# An indirect call with two arguments must not leave stale argument-register +# mappings visible to a later one-argument call. +try_ 155 << EOF +typedef struct { + int (*add)(int, int); +} pair_holder_t; + +int add(int a, int b) { return a + b; } +int one(int x) { return x + 100; } +int get_right() { return 20; } + +int main() { + pair_holder_t h; + int left = 10; + int right = get_right(); + h.add = add; + return h.add(left, right) + one(5) + right; +} +EOF + +# A function-pointer-containing aggregate passed in a register must be copied +# into local backing storage before taking its address. +try_ 5 << EOF +typedef struct { + int (*fn)(int); +} holder_t; + +int suc(int x) { return x + 1; } + +int call(holder_t h) { + holder_t *p = &h; + return p->fn(4); +} + +int main() { + holder_t h; + h.fn = suc; + return call(h); +} +EOF + +# Nested aggregates must also receive backing storage when addressed. +try_ 5 << EOF +typedef struct { + int (*fn)(int); +} inner_t; + +typedef struct { + inner_t inner; +} outer_t; + +int suc(int x) { return x + 1; } + +int call(outer_t o) { + outer_t *p = &o; + return p->inner.fn(4); +} + +int main() { + outer_t o; + o.inner.fn = suc; + return call(o); +} +EOF + # struct with multiple pointer declarations in same line try_ 42 << EOF typedef struct chunk {