public inbox for gcc@gcc.gnu.org
 help / color / mirror / Atom feed
* How to generate a call inst. sequence?
@ 2022-01-19  4:53 Andras Tantos
  2022-01-19 10:45 ` Richard Sandiford
  0 siblings, 1 reply; 3+ messages in thread
From: Andras Tantos @ 2022-01-19  4:53 UTC (permalink / raw)
  To: gcc

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

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: How to generate a call inst. sequence?
  2022-01-19  4:53 How to generate a call inst. sequence? Andras Tantos
@ 2022-01-19 10:45 ` Richard Sandiford
  2022-01-20 23:47   ` Andras Tantos
  0 siblings, 1 reply; 3+ messages in thread
From: Richard Sandiford @ 2022-01-19 10:45 UTC (permalink / raw)
  To: Andras Tantos; +Cc: gcc

Andras Tantos <andras@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?

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: How to generate a call inst. sequence?
  2022-01-19 10:45 ` Richard Sandiford
@ 2022-01-20 23:47   ` Andras Tantos
  0 siblings, 0 replies; 3+ messages in thread
From: Andras Tantos @ 2022-01-20 23:47 UTC (permalink / raw)
  To: Richard Sandiford; +Cc: gcc

On Wed, 2022-01-19 at 10:45 +0000, Richard Sandiford wrote:
> Andras Tantos <andras@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
> 

Richard,

Thanks for the reply. While what you're saying makes sense, it didn't
solve my problems. The symptoms changed, but didn't completely go away.
At the same time, I realized that - in this architecture - link-
register-style calls are more efficient anyway, so I've changed my call
implementation to that configuration. That got rid of the need for
sloving this particular problem.

So, just to document for people who might be looking at this thread in
the future: this wasn't the complete answer to my problem, but I took a
different route which removed the whole problem class.

Thanks again,
Andras




^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2022-01-20 23:48 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-01-19  4:53 How to generate a call inst. sequence? Andras Tantos
2022-01-19 10:45 ` Richard Sandiford
2022-01-20 23:47   ` Andras Tantos

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).