Andras Tantos <and...@tantosonline.com> writes: > 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>
Even though this is internally a jump, it still needs to be represented as a (call …) rtx in rtl, and emitted using emit_call_insn. In other words, the "call" expander must always emit a call_insn of some kind. (But it can emit other instructions too, such as the ones you describe above.) Richard > 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?