ISR Function prologues on AVR with Binutils and LD

Georg Johann Lay <[email protected]> Thu, 3 Nov 2022 12:29:42 +0100
Newsgroups gmane.comp.hardware.avr.gcc
Message-ID <[email protected]>
> Hi,
> 
> I've dug about in the sourcecode of gcc and gas, in order to understand
> how it works.
> 
> in particular, in gas
> 
> avr_patch_gccisr_frag (fragS *fr, int reg)
> 
> There is behaviour, specific to AVR that allows binutils to analyse the
> compiler output for interrupt routines, and causes it to generate
> special prologue/epilogues that preserve the CPU state.
> 
> My problem is that these appear to have been designed with a particular
> approach/model for interrupt handling in mind, and are proving to be
> frustrating now that I wish to write my own kernel for AVR.

Hi, the rationale behind this is to optimize ISR prologues and epilogues 
as of https://gcc.gnu.org/PR20296.

Because it's not feasible in gcc alone, gas is analyzing most of the 
instructions and is emitting appropriate prologues and epilogues.

> In particular, I want to call schedule() following an interrupt handler
> (and prior to returning to a thread) that causes schedule() to be
> required, which would in turn require saving the entire current thread
> state, but if I don't call it, then I want to avoid pushing a bunch of
> un-necessary regs.
> 
> For example
> 
> void __attribute__((signal)) __vector_20(void)
> {
>         char a;
> 
>         a++
> 
>         return;
> }
> 
> As it stands, the code generated by gcc, I assume, looks something like:
> 
> 
> .foo
>         gccisr 0
> 
>         add rX ...
> 
>         gccisr 1
>         gccisr 2
> 
> 
> Which would compile into something like: (in its "fuller form")
> 
> .foo
>         push    r1
>         push    r0
>         in      r0, 0x3f        ; 63
>         push    r0
>         eor     r1, r1
>         push rX
> 
>         add rx ...
> 
>         pop rX
>         pop r1
>         pop r0

Assuming "a" lives in static storage (so it won't be optimized away), 
gcc would generate

__vector_20:
	__gcc_isr 1
.L__stack_usage = 0 + __gcc_isr.n_pushed
	lds r24,a
	subi r24,lo8(-(1))
	sts a,r24
	__gcc_isr 2
	reti
	__gcc_isr 0,r24

Gas would scan this and recognize that SREG is touched, and emit:

00000000 <__vector_20>:
    0:	8f 93       	push	r24
    2:	8f b7       	in	r24, 0x3f	; SREG
    4:	8f 93       	push	r24
    6:	80 91 60 00 	lds	r24, 0x0060	; 0x800060 <a>
    a:	8f 5f       	subi	r24, 0xFF	; 255
    c:	80 93 60 00 	sts	0x0060, r24	; 0x800060 <a>
   10:	8f 91       	pop	r24
   12:	8f bf       	out	0x3f, r24	; SREG
   14:	8f 91       	pop	r24
   16:	18 95       	reti

The documentation for the pseudo-instruction __gcc_isr is here:
https://sourceware.org/binutils/docs/as/AVR-Pseudo-Instructions.html

> It seems that it even checks called functions, so its ideal for writing
> interrupt handlers.

It checks *whether* the ISR is calling functions (be they tail-calls, 
ordinary calls, transparent), and if so, the compiler won't emit 
__gcc_isr at all but resort to the "old" prologue; here asm generated by 
gcc:

__vector_20:
	push r1
	push r0
	in r0,__SREG__
	push r0
	clr __zero_reg__
	push r24
/* prologue: Signal */
.L__stack_usage = 4
	...
/* epilogue start */
	pop r24
	pop r0
	out __SREG__,r0
	pop r0
	pop r1
	reti

so is saves, restores, inits zero_reg (R1) even though that's not 
needed, and tmp_reg (R0) is not clobbered, either.

> However, I want to use a separate stack for IRQs, and this prevents me
> doing so, AFAICT.

If __gcc_isr is what's getting in your way, then you can switch back to 
old ISR prologues by attribute "no_gccisr" for individual ISRs.  If you 
want to disable __gcc_isr for the whole compilation unit, use option 
-mno-gas-isr-prologues.

> I could have my interrupt call an assembler stub beforehand to switch
> SP, but to do so, I must save a couple of registers to the stack. I
> don't want to save them *again* in the prologue, as its wasteful and on
> a hot path.
> 
> I could write code to switch stack within foo(), but this won't work,
> since the gccisr prologue will come before it, which is not right either.
> 
> I can see no other gcc / binutils option that would allow me to achieve
> this trick - I had hoped that I could use some sort of function
> prologue, but I can't see how, and it would suffer the same problems as
> above.

Sounds like what you want is attribute "naked" for the ISR, so that gcc 
won't generate prologues or epilogues at all.  According to the GCC 
documentation, only inline asm is safe in naked functions; but what you 
are going to do (manipulating stack) cannot be done from C/C++, anyways.

> I can see that I can trivially modify binutils so that it *NEVER* emits
> the code to save SREG, only the registers to push. This gets me half-way
> there.
> 
> What I need then, is a way to prepend my ISR "prologue" to foo()
> 
> In a simplistic way, I could call an assembler shim, eg.
> 
> .call_foo
>         <my ISR prologue to save SREG>
>         <switch stack>
>         rjmp foo()
> 
> which would clearly work, but the rjmp could be avoided if I can place

It' not clear what you are trying to do.  If, for example, an ISR should 
handle the scheduling, then you would write it in plain asm: Restore 
stack and manipulate return address so that a final RETI would jump to 
the point of execution of the respective task.

> the code of .call_foo immediately prior to the code of foo(). I assume
> the linker can be instructed to do this, but I'm at a bit of a loss as
> to how right now.
> 
> It seems like being able to disable the "extra prologue" and only have
> gas fix up the stack, would be a useful option.

Gas does not have the necessary information.  One could assume that each 
mentioned register would require PUSH / POP, but that's basically what 
already happens, albeit the code is emit by gcc.

> Documentation is sparse in this area, and if gcc has a more generic way
> of doing this or if I'm barking up the wrong tree, please do mention...
> 
> -Ian

Gcc attributes that influence function prologue and epilogue are:

signal, interrupt, naked, no_gccisr, OS_task, OS_main.

Johann