Which z88dk version is good?

If you like transforming your statements into code, this is the place for you

Moderator: Programming Moderators

Alcoholics Anonymous
Posts: 513
Joined: Mon May 29, 2017 7:00 pm

Re: Which z88dk version is good?

Post by Alcoholics Anonymous » Sun Jun 25, 2017 6:05 pm

Henk de Groot wrote:
Sun Jun 25, 2017 11:26 am
I know from experience in the past that a good C compiler produces very efficient assembly code, in a lot of cases (way) better than assembling by hand.
For the z80 we're not there yet but things aren't too bad either. Sometimes I look at the code generated and think "hey, that's pretty good", more often it's "alright, that's acceptable" (this is largely down to zsdcc using ix for a stack frame, something that would rarely be done in asm) and occasionally it's "wtf?. If you need to recover speed or code size, you change that C code or rewrite that part in asm. After some experience you get an idea of what will lead to better code, what will lead to bad code and what should be done in asm (hopefully we have a large part of the latter group covered by the assembly language libraries). Keep in mind only a fraction of the code in a project is in the time critical path so for most code it doesn't matter if you have optimal asm or not and that's where something like c can shine as it can save a great deal of development time and can promote innovation because it's easier to try things that would be difficult or time consuming to do in pure asm. The try in C and implement in asm when necessary is also a fruitful development path.

There are some things to keep in mind to help the code generators: use unsigned numbers whenever possible, use the smallest type possible (zsdcc especially does well with unsigned char), keep C functions uncomplicated when possible - if they get big split them, don't rely on the compilers to notice that intermediate values can be eliminated - do that explicitly instead, and try to reduce the number of parameters passed to a function. As always the best way to find out if the generated code is better one way or another is to look at the compiler output. I'd suggest writing C code as you normally would and then have a look at the generated code to see if something's gone awry if performance or code size seems poor. Most of the time you won't have to change anything.

However, even at 3.5MHz we do get pretty good results (there are 40-50 games out there using software sprites already) and at 28MHz with graphics hardware there is a lot of room to be imperfect. Even interpreted basic can do interesting things at that speed.
By the looks of is zsdcc does a very good job and is superior to the old compiler. Sccz80 will be phased out in the future?
No I don't think so. There are reasons to use sccz80 instead of zsdcc.

Anyone using zsdcc will soon discover that with optimization set high (-SO3 --max-allocs-per-node200000) the compiler can be very slow. I have programs that take 15-25 minutes to compile on my laptop. This is for the 48k spectrum. Now imagine writing C code for the Next and its megabyte of memory (assuming C code fills a lot of space rather than just data which contributes little time to compilation). In this circumstance sccz80 becomes a very good development compiler since it can do quick modify/build cycles while writing code and then zsdcc could be used for release builds or for testing. Doing this does mean you need to restrict yourself to a smaller subset of C that sccz80 supports (principally no multi-dimensional arrays and you may not want to do this). You can also try reducing optimization level on zsdcc to speed things up but that will result in bigger code which may not fit in the designated memory space and it still may be slow. Moving to a makefile to build a project would help to mitigate this.

In the immediate future we will be moving to allow sccz80- and zsdcc-generated object files to be mixed in the same compile so that either compiler can be used for a particular .c file. At the moment this isn't possible for the most part for technical reasons.
Yes, I was thinking about a very simple demo "Pong" program where the ball is one sprite (extremely simple as it is a black square) and the bats are 4 stacked sprites acting as one group (same black squares, so a lot easier than making a bad guy). This would be about the most basic example still showing the movement, bouncing and collision detection (with the walls, the bat groups and the border). With these sprites it should be easy.
Yes. You may have to synchronize with the raster before moving sprites to avoid gaps being display between them if only some positions are updated when the raster crosses the sprites. You don't even need to do it the "spectrum way" as you can either poll the current raster line or you can create a raster interrupt that will execute/flag after the last raster line is drawn at the bottom of the screen. Not having to wait for a vbi interrupt (raster line = 0) gives you even more time to finish updates.

Henk de Groot
Posts: 28
Joined: Tue May 30, 2017 8:23 pm

Re: Which z88dk version is good?

Post by Henk de Groot » Sat Jul 01, 2017 10:17 am

Okay, I just started experimenting with "pong". Just testing, first thing is to build up the playing area and figure out how to be as close as possible to the version in the Gimini AY-3-8500 chip (pong on a chip). The data sheet (http://www.pong-story.com/GIMINI1978.pdf) has a nice picture of the field geometry.

Now this is from an era where you did not have x by y pixels, but x axis is just timing and y are scan lines. To build is as realistically as possible I want to use the data in the data sheet to draw the field; this means I supply the function with timing values and scan lines and the functions has to work out X/Y on the spectrum screen.

The horizontal timing for the pong screen starts at 13.5 us and last pixel starts at 49.5 us, so screen width is 36.0 us. The smallest duration is 0.5 us so that is the basic pixel with. So the amount of "pixels" the pong game uses on the X plain is 36.0/0.5 = 72 pixels.

By wanting to use original timing I have to use float variables and float constants. I could multiply all values by 2 and use integers but for I just want to keep close and use the native values. However the old compiler does not like it:

Code: Select all

#include <stdio.h>

#define FLOATCONST1 36.0
#define FLOATCONST2 0.5

void main()
{
        int x;

        x = (int) (FLOATCONST1 / FLOATCONST2);
        printf("x = %d (expected: 72)\n", x);
}
The value of x will be calculated at compile time. When trying to compile:

zcc +zx -vn -lndos -lmath48 -create-app --list --c-code-in-asm what_float.c -o what_float.bin
Floating point exception
Makefile:33: recept voor doel 'what_float.tap' is mislukt
make: *** [what_float.tap] Fout 1

Compile with the new compiler works fine:

zcc +zx -vn -lndos -lmath48 -create-app -compiler=sdcc -SO3 --max-allocs-per-node200000 --reserve-regs-iy --list --c-code-in-asm what_float.c -o what_float2.bin
/home/pe1dnn/z88dk/lib/config/../..//include/spectrum.h:196: warning 93: type 'double' not supported assuming 'float'

So still issues with the old compiler. What works with the old compiler is this:

Code: Select all

#include <stdio.h>

#define FLOATCONST1 36.0
#define FLOATCONST2 0.5

void main()
{
        int x;
        float f1, f2;

        f1 = FLOATCONST1;
        f2 = FLOATCONST2;

        x = (int) (f1 / f2);
        printf("x = %d (expected: 72)\n", x);
}
I think the old compiler will now work this out at runtime, and that works just fine.

Alcoholics Anonymous
Posts: 513
Joined: Mon May 29, 2017 7:00 pm

Re: Which z88dk version is good?

Post by Alcoholics Anonymous » Sat Jul 01, 2017 2:57 pm

Henk de Groot wrote:
Sat Jul 01, 2017 10:17 am
zcc +zx -vn -lndos -lmath48 -create-app --list --c-code-in-asm what_float.c -o what_float.bin
Floating point exception
Makefile:33: recept voor doel 'what_float.tap' is mislukt
make: *** [what_float.tap] Fout 1
Thanks Henk, another bug report.
It looks like the compiler is not seeing the ".5" part of the "0.5" constant and is doing a division by zero.

There's a lot of interesting information in that datasheet you linked. A fixed point integer implementation would be faster as you say (especially if the fast integer math library is used where things like leading zero elimination and loop unrolling is done) but I think with this application, float is probably fast enough.

Henk de Groot
Posts: 28
Joined: Tue May 30, 2017 8:23 pm

Re: Which z88dk version is good?

Post by Henk de Groot » Sun Jul 02, 2017 9:05 pm

I continued with the zsdcc compiler but I keep having issues to get the first sprite on screen. I try to show sprite 0 and for now I only want it to show up so put fixed coordinates 100, 100. ZEsarUx keeps crashing on me. Any idea what I do wrong?
(rhetoric question as I answered myself below while working out this post. :D )

This is the stripped example of the C code to put the sprite on screen (even the stripped examples become quite large :( ):

what_out.c:

Code: Select all

#include <graphics.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct sprite_data_s {
	int	sprite_id;
	int	x;
	int	y;
	int	width;
	int	hight;
	int	x_dir;
	int	y_dir;
} sprite_data_t;

#define SPRITE_BALL	0

#define SPRITE_REG_SELECT    0x243b
#define SPRITE_REG_IO  	     0x253b
#define SPRITE_SPRITE_SELECT 0x303b

#define SPRITE_ONOFF_REG     0x15
#define SPRITE_PATTERN_REG   0x55
#define SPRITE_ATTRIBUTE_REG 0x57

#define SPRITE_DISABLE       0x00
#define SPRITE_ENABLE        0x01
#define SPRITE_BORDER_ENABLE 0x02

static void load_ball_sprite(sprite_data_t *);
static void put_ball_sprite(sprite_data_t *);
static void show_sprites(void);
static void hide_sprites(void);

int main(int argc, char *argv[])
{
	sprite_data_t ball;

	(void) argc;
	(void) argv;

	clg();
	zx_border(INK_BLACK);
	zx_colour(PAPER_BLACK|INK_WHITE);

	printf("Hit enter to configure the sprite location on screen");
	(void) getchar();

	ball.sprite_id = SPRITE_BALL;
	load_ball_sprite(&ball);

	ball.x = 100;
	ball.y = 100;
	put_ball_sprite(&ball);

	printf("Hit enter to make the sprite visible");
	(void) getchar();

	show_sprites();

	printf("Hit enter to make the sprite invisible");
	(void) getchar();

	hide_sprites();

	printf("Hit enter to exit");
	(void) getchar();

	return 0;
}

void load_ball_sprite(sprite_data_t *ball)
{
	int i, j, d;

	ball->width = 6;
	ball->hight = 5;

	outp(SPRITE_SPRITE_SELECT, ball->sprite_id);
    
	for(i = 0; i < 16; i++)
	 {
		d = 0xff;
		if(i >= ball->width)
		{
			d = 0xe3;
		}
		for(j = 0; j < 16; j++)
		{
			if(j >= ball->hight)
			{
				d = 0xe3;
			}
			outp(SPRITE_PATTERN_REG, d);
		}
	}
}

void put_ball_sprite(sprite_data_t *ball)
{
	int sprite_x, sprite_y;
	
	sprite_x = ball->x + 31; // sprite x value for given screen x coordinate
	sprite_y = ball->y + 31; // sprite x value for given screen x coordinate	

	outp(SPRITE_SPRITE_SELECT, ball->sprite_id);

	outp(SPRITE_ATTRIBUTE_REG, sprite_x % 0x00ff);
	outp(SPRITE_ATTRIBUTE_REG, sprite_y % 0x00ff);
	outp(SPRITE_ATTRIBUTE_REG, sprite_x > 255 ? 1 : 0);
	outp(SPRITE_ATTRIBUTE_REG, 128);
}

void show_sprites()
{
	outp(SPRITE_REG_SELECT, SPRITE_ONOFF_REG);
	outp(SPRITE_REG_IO, SPRITE_ENABLE | SPRITE_BORDER_ENABLE);
}

void hide_sprites()
{
	outp(SPRITE_REG_SELECT, SPRITE_ONOFF_REG);
	outp(SPRITE_REG_IO, SPRITE_DISABLE);
}
If this is compiled with:

zcc +zx -vn -lndos -lmath48 -create-app -compiler=sdcc -SO3 --max-allocs-per-node200000 --reserve-regs-iy --list --c-code-in-asm what_out.c -o what_out

(yes, I know, not all options are relevant for this stripped down example...)

It compiles fine. However when running "what_out.tap" in ZEsarUX gives a spectacular crash and I can't figure out why.

Now the funny thing. I saw the previous bug was solved for the old sccz80 compiler (damn those z88dk developers are fast! Awesome!). Anyway. Compiled the same code as:

zcc +zx -vn -lndos -lmath48 -create-app --list --c-code-in-asm what_out.c -o what_out

Guess what? It runs fine in ZEsarUX, exactly as expected. Now previously somebody posted a sprite example and used the new zsdcc compiler for it and it worked fine. The only difference I saw was the use of the new library and output function "z80_outp" instead of "outp". So somehow they are not quite compatible. The prototype for "outp" is located in stdlib.h which was also a surprise to me, it is a funny place as stdlib.h is a standard header file.

Now I looked at the assembly produced by zsdcc, snipit when writing 128 to port 0x0057:

Code: Select all

840   01BF              ;what_out.c:110: outp(SPRITE_ATTRIBUTE_REG, 128);
841   01BF  21 57 00            ld      hl,0x0057
842   01C2  E5                  push    hl
843   01C3  3E 80               ld      a,0x80
844   01C5  F5                  push    af
845   01C6  33                  inc     sp
846   01C7  CD 00 00            call    _outp_callee
So it builds a stackframe like this
<portnumber> (16 bit) (push hl)
<value> (8 bit) (pushes 16 bits (push af) but then increments sp (inc sp) to take one byte back)
<return address>

The return address is put there by the call to _outp_callee. _outp_callee is in libsrc/stdlib/outp_callee.asm. Here is what this assembly thinks is on the stack:

Code: Select all

._outp_callee
   pop af  -- This is the return address
   pop de  -- the value and half the port address (expected the value and unused byte)
   pop bc  -- This is the other half of the port address and one byte more from the stack than pushed (expected complete port nr)
   push af -- Put the return address back (trashes the stack of the caller by overwriting one byte)
So this explains it for me. The assembly library functions are apparently not compatible with zsdcc. So I guess I need the new libraries when I want to continue with zsdcc, but those are work in progress.

So what do to now. If using zsdcc implicitly means you need to use libraties compiled with zsdcc always then it should not even be an option. Or should zsdcc make the same stackframes as sccz80, in that case there is still some work to do.

I was staring at this problem all day, it looked like the program got out of control but i could not find the bug in my program. Ohh well, at least it is so much easier with a cross compiler, back in the day when developing on the spectrum itself those kind of crashes were very annoying as you had to load over and over again.

Alcoholics Anonymous
Posts: 513
Joined: Mon May 29, 2017 7:00 pm

Re: Which z88dk version is good?

Post by Alcoholics Anonymous » Sun Jul 02, 2017 11:28 pm

The classic library was written for sccz80 which expects parameters pushed on the stack in left to right order and chars pushed as two bytes.

sdcc's native push order is right to left - this is actually the right way to do things for C but sccz80 is saddled with a history where the wrong decision was made 40 years ago about push order. So sdcc was modified to accept a __smallc decoration to push things in left to right order. This is part of the solution to allow zsdcc to use the classic library. There are other caveats - sdcc cannot do left to right order for var arg functions. So there is a point of incompatibility for var arg functions which has been patched up in the classic library for things like printf and scanf.

However, what appears not yet to be patched up is that sdcc only pushes a single byte for char parameters. All classic library functions have to be declared as expecting int parameters instead of char to maintain compatibility with the asm implementation. This won't affect the code generated by sccz80 but it will change the "documented interface" for people who read the headers to find out what values are passed (they may be misled into thinking a parameter should accept 16-bit values when it really is 8-bit).

There is another issue in that the classic library is not currently taking advantage of zsdcc's ability to be informed of what registers are not affected by a library call since sccz80 does not have that feature. Work has to be done to add that information to all functions.

These problems come about due to pushing zsdcc to work with an existing library. Keep in mind this attempt to get zsdcc to work with the classic library is recent and these things are still being worked out. I will add a reminder about the single-byte push in issues. I believe this is the last significant problem with zsdcc / classic compatibility.

In the end, there will be a homogenization between classic and newlib and that's when the newlib way of doing things will likely be adopted in classic as it can simply borrow the same code base for common functions.
So I guess I need the new libraries when I want to continue with zsdcc, but those are work in progress.
The newlib is stable and should not be regarded as just a work in progress. There is still a major component missing (file io) that will alter the existing io model somewhat through expansion but otherwise it's substantially complete now. That's not to say there may not be bugs there but it's seen significant use already. And of course there are things in classic that are not in newlib and vice versa.

The difference with newlib is that it was written from scratch to work with any compiler so it's 100% compatible with zsdcc from the get-go.

Edit: BTW make sure you switch to using special function registers for IO instead of using the library function calls outp() - classic - or z80_outp() - newlib. This will allow inlined io instructions to be used so that the call/ret overhead is not there. It will also happen to clear up this point incompatibility you've found.

Henk de Groot
Posts: 28
Joined: Tue May 30, 2017 8:23 pm

Re: Which z88dk version is good?

Post by Henk de Groot » Mon Jul 03, 2017 7:25 pm

Thank you for your reply and taking your time to do so. I hope other readers will benefit from it too, for me that's the whole point of writing it down here. I'm still on a learning curve ramp with z88dk so not familiar with the current developments and the state of it. Good to know the new libraries are stable enough too.

Sccz80 still attracts me more than zsdcc, maybe because is it an integral part of z88dk and is there when compiling under Linux. For zsdcc you have to get it from a subversion source (can you believe there are still people struggling without GIT...) and then need to patch it up. Although I scripted it by now so it is simple, it is all in all not the same out of the box experience as sccz80. When I compiled the first "hello.c" program I didn't even know about sccz80 or zsdcc, all I see as end user is zcc. When, and if, I get pong finished (it is after all a hobby project, no idea where it will end, freedom is bliss) then I want others to be able to compile it too. So the more out of the box the better it will be repeatable.

As for the order on the stack, the sccz80 method looks weird to me. You would expect the first argument immediately above the return address. I wonder how printf works with a variable number of arguments, that must be a real pain. Maybe zsdcc should take the opportunity to break away and abandon it. It is of course a big step but sticking with this until eternity is no fun either.

I also am a big fan of very clean code and calling types for what they are. So changing a parameter from char to int doesn't sound like a good idea to me, it is hideous. The only thing that would be worse is also swapping high and low bytes. The byte order in push and pop saved us there so at least it is not another big/little-endian war.

Now for using special function registers. I read the other posts and the recommendations about types etc. I work a bit like a sculptor or like making in jigsaw puzzle. First make the outline that works and then refine. So on the first iteration I take shortcuts to get to fastest to something that works, and when re-factor bit by bit over and over until it is perfect (or at least that I'm pleased with it because as with all non trivial things compromises are inevitable). So special function registers were on the list. But now with this problem I might just as well do it now, so I started looking into that.

Another thing on the list are to ditch the floats although I kind of like using the real AY-3-8500 timing values. We'll see. Anyway, thanks for the reply Alvin.

Henk de Groot
Posts: 28
Joined: Tue May 30, 2017 8:23 pm

Re: Which z88dk version is good?

Post by Henk de Groot » Mon Jul 03, 2017 8:53 pm

Well, the special function registers work, however something strange is happening in sccz80:

what_sfr.c:

Code: Select all

#include <stdlib.h>

__sfr __banked __at 254 SINCLAIR_ZX_SPECTRUM_BORDER;

int main(int argc, char *argv[])
{
        (void) argc;
        (void) argv;

        SINCLAIR_ZX_SPECTRUM_BORDER = 2;

        return 0;
}
zcc +zx -vn -lndos -lmath48 -create-app --list --c-code-in-asm what_sfr.c -o what_sfr.bin
sccz80:"what_sfr.c" L:10 Error:#42:Unknown symbol: SINCLAIR_ZX_SPECTRUM_BORDER
Compilation aborted
Makefile:52: recept voor doel 'what_sfr.tap' is mislukt
make: *** [what_sfr.tap] Fout 1
pe1dnn@LinuxMint ~/spec/Own/zcc $

And compiling with zsdcc:

zcc +zx -vn -lndos -lmath48 -create-app -compiler=sdcc -SO3 --max-allocs-per-node200000 --reserve-regs-iy --list --c-code-in-asm what_sfr.c -o what_sfr.bin
pe1dnn@LinuxMint ~/spec/Own/zcc $

Now if I rename the variable to "IO_SINCLAIR_ZX_SPECTRUM_BORDER" or make it lowercase "sinclair_zx_spectrum_border" then it works. Oh, the variable name is quite long because at first I thought that might be the problem, but looks like it is not. Don't know if this applies to other variables as well, usually I only use uppercase for #define values. My variable were still capitals because it used to be #defines and I have not altered them yet..

Alcoholics Anonymous
Posts: 513
Joined: Mon May 29, 2017 7:00 pm

Re: Which z88dk version is good?

Post by Alcoholics Anonymous » Tue Jul 04, 2017 3:45 am

Code: Select all

__sfr __banked __at 254 SINCLAIR_ZX_SPECTRUM_BORDER;
Henk you are some kind of bug whisperer :D I'm glad you're finding these so they can be dealt with.

This one has been fixed tonight. SFRs could not begin with "S", "L" or "U" because those letters were being taken as qualifiers on the port number (254S would be a signed int, eg).
Sccz80 still attracts me more than zsdcc, maybe because is it an integral part of z88dk and is there when compiling under Linux. For zsdcc you have to get it from a subversion source (can you believe there are still people struggling without GIT...) and then need to patch it up.
The plan is to bring it in as a git submodule so that it is properly part of z88dk but that hasn't been done yet. Win32 and MacOSX users get everything in the nightly build package so they always have zsdcc available. Win32, being windows, does cover most users (I'm not sure what the exact number is but I think it's 80+%). I choose to develop on Windows for this reason, just to make sure things work properly.
As for the order on the stack, the sccz80 method looks weird to me. You would expect the first argument immediately above the return address. I wonder how printf works with a variable number of arguments, that must be a real pain. Maybe zsdcc should take the opportunity to break away and abandon it.
It is the wrong way to do things for C because of var args as you say. I don't know why it ever started like this; the only explanation is it kept the compile process simple in that arguments were processed in the order they were seen. sccz80 originally ran on z80 machines so maybe that was a consideration.

Anyway what sccz80 does for var arg functions is pass an additional parameter in the A register denoting how many 16-bit words have been pushed on the stack. Then the first thing var arg functions need to do is use that as an offset on the stack to find the first parameter and then walk the stack downward for additional parameters.

sccz80 has recently been modified to use right to left order with appropriate flag on the compile line so the future is normal parameter order for both compilers. With the classic library, that involves a lot of work because all the functions will have to be modified to collect parameters in the opposite order. newlib is already written for right to left order for zsdcc and the asm implementation is separate from the C interface so the main change will probably be to use the zsdcc C interface for sccz80 except for the char parameter cases.

Henk de Groot
Posts: 28
Joined: Tue May 30, 2017 8:23 pm

Re: Which z88dk version is good?

Post by Henk de Groot » Tue Jul 04, 2017 9:15 pm

Alcoholics Anonymous wrote:
Tue Jul 04, 2017 3:45 am
Henk you are some kind of bug whisperer :D I'm glad you're finding these so they can be dealt with.
Honestly, it is all by accident :lol: . But with the speed they are fixed I can't complain.
It is the wrong way to do things for C because of var args as you say.
I think not only C, but also Pascal or any other formal language, unless your CPU has a stack running up instead of down (maybe they exist?) but even then the push order would be the same. However storing a byte in a 16 bit space could have a good reason when addresses need to allign on word boundaries. I worked in the far past with Motorola 68040 processors and it liked longword boundaries so it could do burst reads (fetch 4 longwords in very fast succession). I think the MC68010 could not read from odd addresses and as I remember the MC68040 still had this restriction for instructions and on odd address data reads were expensive. This is from memory and it was a long time ago so I could be wrong.

Anyway pushing one byte for a char instead of two saves one byte on the stack but does waste time in the program having to execute more instructions (in this case an extra inc sp after push af). It's the same old story, saving space but adding complexity or saving complexity but adding space. If only Zilog implemented a "push a" you could have both, but then again most likely at the expense of processor complexity and speed.

I understand the way sccz80 solves the variable number of arguments. I looked at a normal call to a function but it does not put a value in register A for the number of arguments passed to a function. So the number of arguments passed is not a generic part of any function call but appears to be handled as a special case. So this implies that calling functions like printf without having a prototype would fail.

I was curious to test this hypothesis. So I just tried this:

Code: Select all

#include <stdio.h>

int main() {
    printf("%cHello, World!\n", (char) 12);
    return 0;
}
And the assembly has loading register A just before the function call:

Code: Select all

309   0004  21 0C 00            ld      hl,12   ;const
310   0007  E5                  push    hl
311   0008  3E 02               ld      a,2
312   000A  CD 00 00            call    printf
When I compile without including stdio.h it does indeed not know it has to load register A just before the function call:

Code: Select all

31    0004  21 0C 00            ld      hl,12   ;const
32    0007  E5                  push    hl
33    0008  CD 00 00            call    _printf
So that confirms it, for this compiler it is really important to have prototypes, not just to avoid warnings (the original K&R compiler did not even know about prototypes) but even to make it work. A "standard" compiler would work without the prototype as long as the types pushed to the stack match what the called function expects. But here it only works if register a happens to have value 2 by accident, for any other value it will print something strange as it can not even find the format string (and indeed running the .tap file in fuse displays junk).

Nice to read about the migration strategy to get from the old situation to the wanted behavior, it makes a lot of sense to me as outsider.

Any way something learned again. By the way, your fix works fine, thanks.

Alcoholics Anonymous
Posts: 513
Joined: Mon May 29, 2017 7:00 pm

Re: Which z88dk version is good?

Post by Alcoholics Anonymous » Thu Jul 06, 2017 4:09 am

Henk de Groot wrote:
Tue Jul 04, 2017 9:15 pm
I think not only C, but also Pascal or any other formal language, unless your CPU has a stack running up instead of down
I think Pascal convention is also left to right. It shouldn't really matter unless you allow var args.
Anyway pushing one byte for a char instead of two saves one byte on the stack but does waste time in the program having to execute more instructions (in this case an extra inc sp after push af). It's the same old story, saving space but adding complexity or saving complexity but adding space. If only Zilog implemented a "push a" you could have both, but then again most likely at the expense of processor complexity and speed.
It tends to be a win overall. In large programs, I've seen it save 300-400 bytes. This is not coming from single parameter functions (for the library these are all done by passing the param by register anyway) but from functions taking multiple char parameters. Two successive char parameters are packed into a single 16-bit word that is pushed.
So that confirms it, for this compiler it is really important to have prototypes, not just to avoid warnings (the original K&R compiler did not even know about prototypes) but even to make it work. A "standard" compiler would work without the prototype as long as the types pushed to the stack match what the called function expects. But here it only works if register a happens to have value 2 by accident, for any other value it will print something strange as it can not even find the format string (and indeed running the .tap file in fuse displays junk).
Yes sccz80 will require prototypes for var arg functions but it otherwise supports K&R.

zsdcc doesn't support K&R however and one reason is that it pushes chars as one byte. For var arg functions, it must push chars as two bytes (the standard requires promotion to int for var arg functions). So it too must have a prototype for var arg functions.

We should all avoid using K&R anyway :D

Post Reply