From #373.
Add mechanism to yaz180 to inline asm instructions without affecting optimizer.
I can see this becoming a thing where each target may get additional post-processing steps. We probably should have a better place to store the various peephole rules for copt, sccz80 and sdcc.
For __BREAK, I've changed the yaz180.h proto header to this:
// Halt the YAZ180 with single step hardware.
#define __BREAK __BREAK_HELPER()
__OPROTO(`a,d,e,h,l,iy,iyh',`a,d,e,h,l,iyl,iyh',void,,__BREAK_HELPER,void)
The define to __BREAK_HELPER() allows __BREAK; to be used as is inside c code.
__BREAK_HELPER is being defined as a function that preserves all registers except bc. This way inlining the asm won't affect any registers zsdcc is using.
In a new file yaz180_rule.1, a copt rule is added to replace the function call the compilers are generating with inlined asm:
call ___BREAK_HELPER
=
IFNDEF __IO_BREAK
EXTERN __IO_BREAK
ENDIF
ld bc,__IO_BREAK
out (c),c
call __BREAK_HELPER
=
IFNDEF __IO_BREAK
EXTERN __IO_BREAK
ENDIF
ld bc,__IO_BREAK
out (c),c
zsdcc adds another leading underscore to the name, sccz80 does not. I'm checking if __IO_BREAK is defined in case someone does something bizarre like include "config_private.inc" somehow as that will then include a file defining that label; I think an EXTERN will cause an error out of z80asm in that case. The EXTERN will work because the crt exports those labels as PUBLIC.
I can see this becoming a thing where each target may get additional post-processing steps. We probably should have a better place to store the various peephole rules for copt, sccz80 and sdcc.
Yes, that was my thinking behind: https://github.com/z88dk/z88dk/tree/feature/more_build_changes/lib/arch/z88 Something didn't feel quite right about what I was doing though so I halted work on it.
I'm afraid that I've got a case where this doesn't quite work.
EDIT:
Just thinking that although these are simple examples below, the real use case will be optimised to be broken too. If the ___BREAK; function is called just before a return; then sdcc will optimise this into a jp ___BREAK_HELPER which will be missed by the copt solution. Putting ___BREAK; just before a return; is something that is likely to happen often, to check what is actually left on the stack by seeing where the ret takes us.
Is there not some way to instruct sdcc to NEVER ever ever, optimise a function?
END EDIT
Where there is a simple assignment of function, the ___BREAK_HELPER doesn't work.
#include <stdlib.h>
#include <arch/yaz180.h>
void main(void)
{
__BREAK;
return;
}
The list looks like this
346 0000 SECTION code_compiler
347 0000 ;./atest.c:4: void main(void)
348 0000 ; ---------------------------------
349 0000 ; Function main
350 0000 ; ---------------------------------
351 0000 ;./atest.c:6: __BREAK;
352 0000 ;./atest.c:8: return;
353 0000 defc _main = ___BREAK_HELPER
354 0000 SECTION IGNORE
z80asm --cpu=z180 -b -d -o"break_app" -m -s -L. -L"/home/phillip/Z80/z88dk/lib/config/../..//libsrc/_DEVELOPMENT/lib/sdcc_iy" -D__SDCC -D__SDCC_IY -I"/home/phillip/Z80/z88dk/lib/config/../..//libsrc/_DEVELOPMENT/target/yaz180" --list -im -iyaz180 "@/tmp/tmpXXFAOtpi.lst"
Error at file '/tmp/tmpXX8g7pVZ.asm' line 353: symbol '___BREAK_HELPER' not defined
1 errors occurred during assembly
Errors in source file /home/phillip/Z80/z88dk/lib/config/../..//libsrc/_DEVELOPMENT/target/yaz180/yaz180_crt.asm:
Error at file '/tmp/tmpXX8g7pVZ.asm' line 353: symbol '___BREAK_HELPER' not defined
^ ---- defc _main = ___BREAK_HELPER
md5-07b3a4db8f54df7eb5cda9366a9b5cf8
```347 0000 SECTION code_compiler
348 0000 ;./atest.c:4: void anotherfunction(void)
349 0000 ; ---------------------------------
350 0000 ; Function anotherfunction
351 0000 ; ---------------------------------
352 0000 ;./atest.c:6: __BREAK;
353 0000 defc _anotherfunction = ___BREAK_HELPER
354 0000 SECTION code_compiler
355 0000 ;./atest.c:9: void main(void)
356 0000 ; ---------------------------------
357 0000 ; Function main
358 0000 ; ---------------------------------
359 0000 ;./atest.c:11: anotherfunction();
360 0000 ;./atest.c:12: return;
361 0000 defc _main = _anotherfunction
362 0000 SECTION IGNORE
Otherwise, it works as desired.
Thanks :+1:
Could something like this work?
#define __BREAK \
_Pragma("save") /* save the current optimization settings */ \
_Pragma("no-peep-return") /* do not let the peep hole optimizer do optimizations */ \
__BREAK_HELPER() \
_Pragma("restore") /* turn the optimizations back on */
At least to remove the ret optimisation issue?
Null function optimisation is a moot point, really.
Answer: doesn't work...
sdcc doesn't accept #pragma within a function.
I'll take a look at your solution tomorrow (it's getting too late here!). I'm a little nervous about depending on whitespace to sort out problems but I think it may be possible to make exceptions for certain names.
Yes, white space makes me nervous too (not a python fan). Hence I mentioned it.
The yaz180_rules.1 copt seems to need to match exactly with the output the compiler and initial copt produces.
The call ___BREAK_HELPER comes straight from the compiler with a tab character.
The jp ___BREAK_HELPER comes from copt with two spaces.
So yaz180_rules.1 has to match these inputs exactly, or it breaks.
I tested it.
Seems fragile to me.
I've fixed the space brittleness issue. In reality, sdcc's peephole optmizer is very fragile, depending on exact formatting of instructions and spacing for it to work.
It has made you a s**t load of work, just to insert two lines of asm.
Sounds like technical overhang to me. :sweat_smile:
Anyway, that solution looks solid.
The defc case is a moot one anyway.
(Unrelated, but worth celebrating... just fixed a longstanding PLD issue with my implementation of a soft flash programmer, so the last outstanding yaz180 hardware concern is a I2C proof of life. :hand: )