Calling ASM, not working with interrupts enabled

ZX80, ZX 81, ZX Spectrum, TS2068 and other clones
Post Reply
derekfountain
Member
Posts: 121
Joined: Mon Mar 26, 2018 1:49 pm

Calling ASM, not working with interrupts enabled

Post by derekfountain »

I'm trying to get a single ASM function working. Its signature is:

Code: Select all

void rtunes_pixel( uint8_t, uint8_t, uint8_t ) __z88dk_callee;
I'm calling it lots, and it normally works fine, but just occasionally it crashes. :(

I have interrupts enabled, my interrupt routine is empty. It's compiling to:

Code: Select all

700   0000              _isr:
701   0000              	EXTERN asm_im2_push_registers
702   0000              	EXTERN asm_im2_pop_registers
703   0000  CD 00 00    	call asm_im2_push_registers
704   0003  CD 00 00    	call __im2_isr_isr
705   0006  CD 00 00    	call asm_im2_pop_registers
706   0009  FB          	ei
707   000A  ED 4D       	reti
708   000C              ;	---------------------------------
709   000C              ; Function _im2_isr_isr is a stub
710   000C              ; ---------------------------------
711   000C              EXTERN l_ret
712   000C              defc __im2_isr_isr = l_ret
So nothing going on there, but if I disable interrupts the crash goes away.

The problem seems to be in the way I'm trying to pick up the values passed by the C code. My function is simple and probably not relevant, but the top bit is:

Code: Select all

PUBLIC _rtunes_pixel
_rtunes_pixel:
;di
    pop af		; Callee convention, this is the return address
    pop de		; Args are passed as 2 unsigned chars, x,y. Y is in D, X is in E
    pop bc    ; C=1 for draw, 0 for clear, caller puts single byte on stack so B not used
    dec sp    ; adjust for single byte
    push af		; Push the return address back. Stack is now ready for the return.
;ei
...
That seems to collect the 3 values from the 'C' into D,E and C, and to restore the stack as required. As I say, the code does normally work. If I uncomment the DI/EI lines it always works, so something there is critical. If I make the final argument a uint16_t so the dec sp isn't needed then it also always works (without the DI/EI).

I'm new to this sort of thing, and I can't see what I'm doing wrong here. I can't see where in that pop/push sequence an interrupt might occur which would upset it. Wherever the interrupt comes in I'd expect the asm_im2_push_registers call to protect my sequence.

What am I doing wrong?
User avatar
dom
Well known member
Posts: 2076
Joined: Sun Jul 15, 2007 10:01 pm

Re: Calling ASM, not working with interrupts enabled

Post by dom »

If I read the code correctly you’re effectively trying to preserve the value of b on the stack despite having already popped it.

So if b was part of a return address to the caller of the caller or rtunes_pixel it would be corrupted by the interrupt.

If that theory is correct then moving the dec sp to before the pop bc (and using b as the pixel mode) should work.
derekfountain
Member
Posts: 121
Joined: Mon Mar 26, 2018 1:49 pm

Re: Calling ASM, not working with interrupts enabled

Post by derekfountain »

OK, I worked it out as I typed this. I'll post anyway for the record, and for my benefit when I inevitably make the same mistake again. :)

First, thanks for your help!

Second, you're right, moving the dec sp before the pop bc and using b as the pixel mode works. :)

Third, gaah, no, I'm still not seeing why!

I'm not trying to preserve anything, or do anything clever. I can't do the basics yet! I have a C routine which passes 3 8-bit values: x, y and mode. Popping X and Y in one operation into DE is fine, but then I need to do a "half pop" because the caller code (compiler generated) moved SP back up a byte. I understand that, and I understand why decrementing it one byte before popping BC works correctly, leaving the value I want in B.

What I'm not seeing is why dec-then-pop always works, but pop-then-dec normally works but just occasionally the interrupt comes in at, presumably, some precise point, and breaks it. But the registers are preserved before the interrupt routine, and restored afterwards, so how can a problem be possible?

...

It occurred to me as I typed that that yes, the registers are preserved, but the contents of the stack are not. The stack is used to stash the registers while the interrupt routine runs.

...

Ah, OK, it's clicked. If I do the "pop bc" before the "dec sp" then I get the 8-bit value I want in register C, and something I don't care about at this point in B, which is normally fine. But if the interrupt fires between those 2 instructions the interrupt routine will run with the stack pointer (momentarily) pointing at the 8-bit value which I don't care about at this point, which it then overwrites. When everything unwinds and I return back to the caller, the 8-bit value I didn't care about, and which I've allowed to get corrupted, suddenly looks rather important. And now wrong!
User avatar
dom
Well known member
Posts: 2076
Joined: Sun Jul 15, 2007 10:01 pm

Re: Calling ASM, not working with interrupts enabled

Post by dom »

Sorry, my explanation wasn't the clearest (note to self: don't write on a phone on in the middle of the night)

It's a subtle one but I'm glad you figured it out.
Post Reply