Skip to content
Open
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
1 change: 1 addition & 0 deletions src/defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
23 changes: 19 additions & 4 deletions src/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

How about adding some comments?

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;
Expand Down Expand Up @@ -3455,6 +3460,16 @@ bool read_body_assignment(char *token,
if (lvalue.is_func) {
rs2 = opstack_pop();
rs1 = opstack_pop();

if (!lvalue.is_reference) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ditto.

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();
Expand Down
167 changes: 167 additions & 0 deletions src/reg-alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 &&
Comment thread
ChAoSUnItY marked this conversation as resolved.
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;
Expand Down Expand Up @@ -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);
Expand Down
88 changes: 88 additions & 0 deletions tests/driver.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Comment thread
ChAoSUnItY marked this conversation as resolved.
}
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 {
Expand Down
Loading