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

Re: [MiNT] Missing clobbered register in gemlib



Helmut Karlowski wrote:
.globl _v_clswk
_v_clswk:
lea (-56,sp),sp
move.l d2,-(sp)		| d2 backuped
move.w 64(sp),d2		| d2  trashed
lea (26,sp),a0
move.l a0,6(sp)
clr.l 10(sp)
clr.l 14(sp)
move.l #_vdi_dummy,18(sp)
move.l #_vdi_dummy,22(sp)
lea (6,sp),a1
#APP				| begin inline assembly
movea.l a1,a0
move.l a0,d1
move.l (a0),a0
move.l #131072,(a0)+
eor.l d0,d0
move.l d0,(a0)+
move.l d0,(a0)+
move.w d2,(a0)		| d2 used
move.w #115,d0
trap #2
#NO_APP			| end  inline assembly
move.l (sp)+,d2		| d2 restored
lea (56,sp),sp
rts


Now I'm not very good at assembler, but I don't see where any registers
are saved or restored (except maybe d2.l)

You see the right things.

The only point you are missing is the function calling convention rule used by GCC. The calling convention is a kind of contract between the callers and callees. With GCC for MiNT, the rules for functions are:
- a function starts with a global label prepended with an underscore
- it ends with RTS
- it finds the arguments on the stack
- the function return value (if any) is put in d0.
- AND: changing the value of d0/d1/a0/a1 is allowed, but not the other registers (they can be used if backuped).

To ease reading the listing, GCC put the special comments #APP and #NO_APP to mark a block of inline assembly, manually written in the C source.

The registers d0/d1/a0/a1 are not backuped because it is legal to modify them. d2 is automatically backuped by GCC because it needs a free data register to do its work (this is unrelated to the trap). And a2 is not backuped because no one told GCC that it could be modified by the inline assembly block.

If a2 is actually modified by the VDI call, the contract of v_clswk() is not respected. The caller may have stored a variable in a2, it expects a2 to be left unmodified, but it is trashed, and a bug occurs.

Note that the GCC inline assembly is quite complicated. But the syntax of the clobber list is very simple. Any inline assembly block has to declare in the clobber list which registers it modifies. Then GCC adds automatically backup/restore code, when required.

Another case is the AES graf_handle() function. It is the one that caused the problem tracked down by Didier, Patrice and me, and was about the register a0. There is no doubt, it is proven that the VDI can modify a0. At the beginning of graf_handle(), GCC put important data into a0. Then it calls the trap #2. And after that, it uses a0 at the end of graf_handle() expecting the value has not changed. In this case, the bug occurs in graf_handle() itself, not in the caller. The solution was easy: add a0 to the clobber list, so GCC uses another register, and backups it if necessary.

There is no mystery in GCC behaviour.

The problems appears only at high levels of optimization, when the registers are heavily used to store intermediate data across function calls.

--
Vincent Rivière