[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

ISR Function prologues on AVR with Binutils and LD

From: Georg Johann Lay
Subject: ISR Function prologues on AVR with Binutils and LD
Date: Thu, 3 Nov 2022 12:29:42 +0100
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Thunderbird/102.2.2


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;



As it stands, the code generated by gcc, I assume, looks something like:

        gccisr 0

        add rX ...

        gccisr 1
        gccisr 2

Which would compile into something like: (in its "fuller form")

        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

        __gcc_isr 1
.L__stack_usage = 0 + __gcc_isr.n_pushed
        lds r24,a
        subi r24,lo8(-(1))
        sts a,r24
        __gcc_isr 2
        __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:

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:

        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

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

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

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.

        <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...


Gcc attributes that influence function prologue and epilogue are:

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


reply via email to

[Prev in Thread] Current Thread [Next in Thread]