All,
I'm working on porting GCC to a processor architecture that doesn't have
a (HW) stack nor a call instruction. This means that for calls, I need
to generate the following instruction sequence:
// move stack-pointer:
$sp <- $sp-4
// load return address:
$r3 <- return_label
// store return address on stack:
mem[$sp] <- $r3
// jump to callee:
$pc <- <address_of_function>
return_label:
Now, I can do all of that as a multi-instruction string sequence in my
.md file (which is what I'm doing right now), but there are two problems
with that approach. First, it hard-codes the temp register ($r3 above)
and requires me to reserve it even though it could be used between calls
by the register allocator. Second this approach (I think at least)
prevents any passes from merging stack-frame preparation for the call
arguments, such as eliminating the stack-pointer update above.
I thought I could circumvent these problems by emitting a piece of RTL
in the 'call' pattern:
(define_expand "call"
[(call
(match_operand:QI 0 "memory_operand" "")
(match_operand 1 "" "")
)]
""
{
brew_expand_call(Pmode, operands);
})
where brew_expand_call is:
void brew_expand_call(machine_mode mode, rtx *operands)
{
gcc_assert (MEM_P(operands[0]));
rtx_code_label *label = gen_label_rtx();
rtx label_ref = gen_rtx_LABEL_REF(SImode, label);
rtx temp_reg = gen_reg_rtx(mode);
// $sp <- $sp - 4
emit_insn(gen_subsi3(
stack_pointer_rtx,
stack_pointer_rtx,
GEN_INT(4)
));
// $r3 <- <ret_label>
emit_insn(gen_move_insn(
temp_reg,
label_ref
));
// mem[$sp] <- $r3
emit_insn(gen_move_insn(
gen_rtx_MEM(Pmode, stack_pointer_rtx),
temp_reg
));
emit_jump_insn(gen_jump(operands[0]));
emit_label(label);
}
If I try to compile the following test:
void x(void)
{
}
int main(void)
{
x();
return 0;
}
I get an assert:
during RTL pass: expand
dump file: call.c.252r.expand
call.c: In function ‘main’:
call.c:9:1: internal compiler error: in as_a, at is-a.h:242
9 | }
| ^
0x6999b7 rtx_insn* as_a<rtx_insn*, rtx_def>(rtx_def*)
../../brew-gcc/gcc/is-a.h:242
0x6999b7 rtx_sequence::insn(int) const
../../brew-gcc/gcc/rtl.h:1439
0x6999b7 mark_jump_label_1
../../brew-gcc/gcc/jump.cc:1077
0xcfc31f mark_jump_label_1
../../brew-gcc/gcc/jump.cc:1171
0xcfc73d mark_all_labels
../../brew-gcc/gcc/jump.cc:332
0xcfc73d rebuild_jump_labels_1
../../brew-gcc/gcc/jump.cc:74
0x9e8e62 execute
../../brew-gcc/gcc/cfgexpand.cc:6845
The reference dump file:
;; Function x (x, funcdef_no=0, decl_uid=1383, cgraph_uid=1,
symbol_order=0)
;; Generating RTL for gimple basic block 2
try_optimize_cfg iteration 1
Merging block 3 into block 2...
Merged blocks 2 and 3.
Merged 2 and 3 without moving.
Merging block 4 into block 2...
Merged blocks 2 and 4.
Merged 2 and 4 without moving.
try_optimize_cfg iteration 2
;;
;; Full RTL generated for this function:
;;
(note 1 0 3 NOTE_INSN_DELETED)
(note 3 1 2 2 [bb 2] NOTE_INSN_BASIC_BLOCK)
(note 2 3 0 2 NOTE_INSN_FUNCTION_BEG)
;; Function main (main, funcdef_no=1, decl_uid=1386, cgraph_uid=2,
symbol_order=1)
;; Generating RTL for gimple basic block 2
;; Generating RTL for gimple basic block 3
EMERGENCY DUMP:
int main ()
{
(note 3 1 2 4 [bb 4] NOTE_INSN_BASIC_BLOCK)
(note 2 3 4 4 NOTE_INSN_FUNCTION_BEG)
(note 4 2 5 2 [bb 2] NOTE_INSN_BASIC_BLOCK)
(insn 5 4 6 2 (set (reg/f:SI 1 $sp)
(minus:SI (reg/f:SI 1 $sp)
(const_int 4 [0x4]))) "call.c":7:5 -1
(nil))
(insn 6 5 7 2 (set (reg:SI 25)
(label_ref:SI 9)) "call.c":7:5 -1
(insn_list:REG_LABEL_OPERAND 9 (nil)))
(insn 7 6 8 2 (set (mem:SI (reg/f:SI 1 $sp) [0 S4 A32])
(reg:SI 25)) "call.c":7:5 -1
(nil))
(jump_insn 8 7 9 2 (set (pc)
(label_ref (mem:QI (symbol_ref:SI ("x") [flags 0x3]
<function_decl 0x7f594efa9200 x>) [0 x S1 A8]))) "call.c":7:5 -1
(nil))
(code_label 9 8 10 2 3 (nil) [1 uses])
(call_insn 10 9 11 2 (call (mem:QI (symbol_ref:SI ("x") [flags 0x3]
<function_decl 0x7f594efa9200 x>) [0 x S1 A8])
(const_int 16 [0x10])) "call.c":7:5 -1
(nil)
(nil))
(insn 11 10 12 2 (set (reg:SI 23 [ _3 ])
(const_int 0 [0])) "call.c":8:12 -1
(nil))
(code_label 12 11 13 3 4 (nil) [0 uses])
(note 13 12 14 3 [bb 3] NOTE_INSN_BASIC_BLOCK)
(insn 14 13 15 3 (set (reg:SI 24 [ <retval> ])
(reg:SI 23 [ _3 ])) "call.c":9:1 -1
(nil))
(jump_insn 15 14 16 3 (set (pc)
(label_ref 17)) "call.c":9:1 -1
(nil))
(code_label 17 16 20 5 2 (nil) [0 uses])
(note 20 17 18 5 [bb 5] NOTE_INSN_BASIC_BLOCK)
(insn 18 20 19 5 (set (reg/i:SI 4 $r4)
(reg:SI 24 [ <retval> ])) "call.c":9:1 -1
(nil))
(insn 19 18 0 5 (use (reg/i:SI 4 $r4)) "call.c":9:1 -1
(nil))
}
As a test to narrow the problem down, I removed the 'emit_jump_insn'
call above. That generated an assembly (thus proving the theory that the
assert has something to do with that), but then the assembly doesn't
contain my label, only a reference to it; which of course later on would
result in a linker error.
So, what am I doing wrong and how can I achieve what I want?
Thank you all wise sages!
Andras