Skip to content

Commit 16b9bd1

Browse files
committed
Fix Arm calling convention by storing arguments to stack
In the previous implementation, shecc lacked the consideration of calling convention, so the function arguments were loaded into registers directly when encountering a function call, regardless of the target architecture. For the Arm architecture, if the number of arguments is greater than 4, the additional arguments were still loaded into registers instead of being passed to stack, causing dynamically linked programs to execute incorrectly. To ensure that dynamically linked programs execute correctly, these changes introduce the following strategy: - Each function allocates an additional 20 bytes of space using stack. - 16 bytes are used for the extra arguments. - 4 bytes are used to save and restore the global pointer stored in register r12 (ip). - Because the external call may change register r12, the compiled program could lose the address of the global stack. Therefore, 4 bytes are allocated to preserve its value. - If any internal function calls an external function with more than 4 arguments, the extra arguments are stored directly on stack. - If the callee is an internal function, all arguments are still loaded into registers.
1 parent 8912dc9 commit 16b9bd1

File tree

2 files changed

+54
-2
lines changed

2 files changed

+54
-2
lines changed

src/globals.c

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -948,7 +948,31 @@ func_t *add_func(char *func_name, bool synthesize)
948948
hashmap_put(FUNC_MAP, func_name, func);
949949
/* Use interned string for function name */
950950
strcpy(func->return_def.var_name, intern_string(func_name));
951-
func->stack_size = 4;
951+
/* Prepare space for function arguments and global pointer.
952+
*
953+
* For Arm architecture, the first four arguments are passed to r0 ~ r3,
954+
* and any additional arguments are passed to the stack.
955+
*
956+
* +-------------+ <-- sp + 20
957+
* | (val of ip) |
958+
* +-------------+ <-- sp + 16
959+
* | arg 8 |
960+
* +-------------+ <-- sp + 12
961+
* | arg 7 |
962+
* +-------------+ <-- sp + 8
963+
* | arg 6 |
964+
* +-------------+ <-- sp + 4
965+
* | arg 5 |
966+
* +-------------+ <-- sp
967+
*
968+
* However, register r12 (ip) holds the global pointer that points to
969+
* global stack, but this register may be changed after an external call.
970+
*
971+
* Therefore, the current strategy is preparing additional space for ip,
972+
* saves its original value to the stack before an external call, and
973+
* restores it from the stack after the external function returns.
974+
* */
975+
func->stack_size = 20;
952976

953977
if (synthesize)
954978
return func;

src/reg-alloc.c

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -770,12 +770,40 @@ void reg_alloc(void)
770770
if (!callee_func->num_params)
771771
spill_alive(bb, insn);
772772

773-
if (dynlink)
773+
if (dynlink) {
774774
callee_func->is_used = true;
775+
if (!callee_func->bbs) {
776+
int ofs = 0;
777+
if (args > 4) {
778+
/* Store args to stack for Arm output */
779+
for (int i = 4; i < args; i++) {
780+
ir = bb_add_ph2_ir(bb, OP_store);
781+
ir->src0 = i;
782+
ir->src1 = ofs;
783+
ofs += 4;
784+
}
785+
}
786+
/* FIXME:
787+
* use a better way to preserve global pointer */
788+
ir = bb_add_ph2_ir(bb, OP_store);
789+
ir->src0 = 12;
790+
ir->src1 = 16;
791+
}
792+
}
775793

776794
ir = bb_add_ph2_ir(bb, OP_call);
777795
strcpy(ir->func_name, insn->str);
778796

797+
if (dynlink) {
798+
if (!callee_func->bbs) {
799+
/* FIXME:
800+
* use a better way to restore global pointer */
801+
ir = bb_add_ph2_ir(bb, OP_load);
802+
ir->dest = 12;
803+
ir->src0 = 16;
804+
}
805+
}
806+
779807
is_pushing_args = false;
780808
args = 0;
781809

0 commit comments

Comments
 (0)