Some newbie questions

Other misc things
Post Reply
DarkSchneider
Member
Posts: 71
Joined: Sun Apr 01, 2018 4:02 pm

Some newbie questions

Post by DarkSchneider »

Hi,

Re-started with z88dk since a few time (well, 2 days only ;) ), looks great just like remember, and at this time I have some questions that maybe someone could know the answer:
  1. How can I get the end of program address? Currently I only achieved it doing something like this:
    Compile with -DAMALLOC, then:

    Code: Select all

    	static uint total, largest, memini;
    	static void *mem;
    	mallinfo(&total, &largest);
    	mem=malloc(largest);
    	memini=mem;
    	free(mem)
    
    Then memini has aprox the starting of the heap.
  2. Also, there is some way to specify to use all the heap area with a tolerance? (tolerance = room for the stack). Currently I do it by this way:
    Execution of previous to get memini, then:

    Code: Select all

    	mallinit();
    	sbrk(memini, (total / 3 * 4) - tolerance); // "/3*4" because -DAMALLOC uses 3/4 of heap
    	mallinfo(&total, &largest);
    
    OK maybe some bytes are wasted, but it is fine for me.
  3. Put data within code section, instead at the end. Not sure if there is some pragma or something. Now I achieve it doing:
    Create some code file something like:
    mem1.asm

    Code: Select all

    section code_compiler
    PUBLIC _data1
    _data1:
    REPT <size_required_in_bytes>
    	db 0
    ENDR
    Then, for use simply reference it, can be a direct byte array, or with a struct, i.e.:

    somefile.c

    Code: Select all

    typedef struct {
    	unsigned int id;
    	int x, y;
    } MyData;
    extern MyData *data1; // for array, for single object use without pointer
    
    my_function() {
       ...
       data1[2].id = 453;
       ...
    }
    And then just compile in the order you want it:

    Code: Select all

    zcc ... main.c part1.c mem1.asm part2.c ...
    You will have your data space located properly.
User avatar
dom
Well known member
Posts: 2076
Joined: Sun Jul 15, 2007 10:01 pm

Re: Some newbie questions

Post by dom »

DarkSchneider wrote: Wed Feb 15, 2023 10:48 amHi,

Re-started with z88dk since a few time (well, 2 days only ;) ), looks great just like remember, and at this time I have some questions that maybe someone could know the answer:
Welcome back!
  1. How can I get the end of program address? Currently I only achieved it doing something like this:
There's a symbol generated by the assembler called __tail which you can access this way:

Code: Select all

#include <stdio.h>

extern void *_tail;

int main() {
        printf("Boo tail is %x\n",&_tail);
}
Also, there is some way to specify to use all the heap area with a tolerance? (tolerance = room for the stack). Currently I do it by this way:
There's no "easy" way to do it - we tend to play safe since debugging stack issues is painful - I guess the best way is to not use AMALLOC and sbrk() between __tail and sp - [some number]
Put data within code section, instead at the end. Not sure if there is some pragma or something.
One of the following should do want you want (the first is for modifiable data, the second for variables marked as const).

Just out of interest, what's the reason for needing to do this?

Code: Select all

#pragma dataseg code_compiler
#pragma constseg code_compiler
DarkSchneider
Member
Posts: 71
Joined: Sun Apr 01, 2018 4:02 pm

Re: Some newbie questions

Post by DarkSchneider »

OK thanks now it is much clear.
dom wrote: Wed Feb 15, 2023 6:32 pm One of the following should do want you want (the first is for modifiable data, the second for variables marked as const).

Just out of interest, what's the reason for needing to do this?

Code: Select all

#pragma dataseg code_compiler
#pragma constseg code_compiler
Because the special nature slot-page-bank of MSX-DOS2 memory map. At his moment I think is more suitable to handle banking manually. I.e. you can have the special situation on your ISR (Interrupt Service Routine) that you to have this map:

Code: Select all

page 0 -> program (music_player within $0100-$3fff)
page 1 -> FMBIOS ROM (another sub-slot than RAM, $4000-$7fff)
page 2 -> maybe music data
page 3 -> cannot be modified on MSX-DOS2, so the default RAM slot-mapper-bank, maybe music data
So I have the need for self-contained code within 16KB blocks.

Note: for the music data to be on page 2-3, we could simply malloc it at first as I have noticed the malloc uses high-to-low order when allocating.

This takes me to the next question:

4. It is possible to have replicated the CRT on different places of code? If we get the previous example, the music_player would need them in its 16KB block, and the code at page 1 ($4000-$7fff) would need its own if the page 0 was switched, in the case the CRT is added just when used instead putting it all at the end (don't know how z88dk place it).

I suppose the answer is no, but asking don't harm :) so the way to have self-contained code would be with ASM when required, but having its data defined as mentioned.
DarkSchneider
Member
Posts: 71
Joined: Sun Apr 01, 2018 4:02 pm

Re: Some newbie questions

Post by DarkSchneider »

Just for clarify, when saying CRT, I mean the internal functions used by C, counterpart of the MSX-C CRUN, not the initialization code:
crun.rel contains routines to load and save 'automatic' variables. Also there are a number of 8 and 16 bits arithmetic functions, such as multiply, divide, shift and compare. These functions are supposed to be used internal only.
Not sure what lib does it in z88dk, as use the zcc frontent that makes all the work.
DarkSchneider
Member
Posts: 71
Joined: Sun Apr 01, 2018 4:02 pm

Re: Some newbie questions

Post by DarkSchneider »

Well now I have another question (sorry for the inconvenience), that is:

5. Passing parameters to ASM functions in registers. When I read: https://github.com/z88dk/z88dk/wiki/InlineAssembler
The preferred way to implement assembly subroutines called from C is that prototypes are supplied in a header file to tell the C compiler how to call the asm functions and then a separate asm file contains the asm implementation. This has the additional advantage that it's easy to produce assembler entry points with parameters passed in the most convenient registers rather that on the stack.
Not sure what it means, as I think we have only the __z88dk_fastcall that uses a single register.
Currently I have only achieved that with inline asm, by this way:
Let take an example with a function that takes 2 parameters in A and B and return result in A.

add.asm

Code: Select all

PUBLIC _add
_add:
	add b
	ret
I can only call it from C code using:

Code: Select all

typedef unsigned char byte;

extern byte add(byte a, byte b) __naked;

void my_func() {
	static byte a, b, c;
... some code ...
	#asm
		ld a, (_st_main_b)
		ld b, a
		ld a, (_st_main_a)
		call _add
		ld (_st_main_c), a
	#endasm
... some more code...
}
This is, using static (or global) variables as can use their symbols for reference. Then:
- Is this the correct way? And
- Is there a way to make a macro from it? With parameters and with and without result value.
User avatar
dom
Well known member
Posts: 2076
Joined: Sun Jul 15, 2007 10:01 pm

Re: Some newbie questions

Post by dom »

Because the special nature slot-page-bank of MSX-DOS2 memory map. At his moment I think is more suitable to handle banking manually. I.e. you can have the special situation on your ISR (Interrupt Service Routine) that you to have this map:
When I added banking support, for simplicity it was intended that the the home bank is always paged in. This combined with the compiler support routines being located towards the start of the binary means that multiple copies aren't needed.

For MSX-DOS2, the banks are in the 0x8000 range (matching the ROM banking model) which gives 32k for the base application. Using the #pragma bank NN directive automatically gets all variables into the right place.

However, it is possible to package everything up in a single bank with no crt0 - effectively creating a binary for a bank but to be safe you'll have to avoid using anything that shares with other banks or needs setup, for example stdio.h

Code: Select all

% zcc +msx -crt0 crt.asm   tail.c

tail.c:
extern void *_tail;

static int blah = 2;

int func() {
        return (int)&_tail * blah;
}

crt.asm:
        org 32768
When that's compiled you'll get a single binary with the crt0 code for 16x16 multiply included in it. There's no section ordering, so the sections get added as they are reached - you'll see that the static int blah is located at 0x8000.

Obviously you'll lose the benefit of intra-bank calls being resolved automatically so you'll have to track addresses (or do a jump table in crt0.asm) and create the trampoline yourself.

Passing parameters, I think that wiki snippet makes most sense when writing library routines. The "newlib" routines are written in two parts: A core z80 implementation that can be used from assembler (so has parameters in the most appropriate registers) and a glue routine that maps between the C calling convention and the assembler code.

__z88dk_fastcall uses up to dehl to pass parameters in registers so for a "C" solution that's all that can be passed.

For interaction with the bios or other code where performance isn't critical, you could use AsmCall in <arch/z80.h>
Timmy
Well known member
Posts: 392
Joined: Sat Mar 10, 2012 4:18 pm

Re: Some newbie questions

Post by Timmy »

Disclaimer: This is not the preferred way. Just what I do, because it's easier for my head.

This is an example I just made and is actually for the ROM mode, but it should explain a bit how inline assembly works.

If you declare a global variable "uchar glob" then you can access it inline using "ld a, _glob".

Fastcalls can only have one parameter as hl (i think there are more possible but i forgot how).

Callees can have multiple parameters but you have to pop them from the stack yourself.

Code: Select all

#include <msx/defs.h>
#include <sys/types.h>
#include <conio.h>

#define NAMTBL 0x1800

void __CALLEE__ vpoke1(int address, uchar value) __naked
{
	#asm
		pop ix
		pop bc	; c = value
		pop hl	; hl = vram address
		push ix
		
		ld	a,l
		di
		out	($99), a
		ld	a,h
		and	63
		or 	64
		ei
		out	($99), a
		
		ld a, c
		out	($98), a
		ret
	#endasm
}

uchar __FASTCALL__ vpeek1 (int address) __naked
{
	#asm
		; hl = address
		ld	a,l		
		di
		out	($99), a
		ld	a,h
		and	63
		ei
		out	($99), a
		
		in a, ($98)
		ld h, 0
		ld l, a
		ret
	#endasm
}

void main()
{
	uchar c;
	vpoke1(NAMTBL + 10,48);
	c = vpeek1(NAMTBL + 10);
	vpoke1(NAMTBL + 20,c);
	while (1)
	{}
}
DarkSchneider
Member
Posts: 71
Joined: Sun Apr 01, 2018 4:02 pm

Re: Some newbie questions

Post by DarkSchneider »

Very interesting.

Yes the idea is that of creating independent binaries to be loaded. Already have all the MSX-DOS2 mapper stuff working.

About function parameters, 2 interesting things:
- Be able to pass up to 4-byte with fastcall, simply reordering the data and the appropriate casting can be very useful.
- The process exposed by @Timmy of POP RET_address, POP parameters, then PUSH RET_address. It was rounded my mind for a while and if confirmed that works fine then nice, so it is very easy to accommodate the data in registers and quickly, as PUSH/POP in Z80 is really fast.

My major concern was to use my already coded ASM, and with those 2 options I think it can be with minimal code added at beginning of the function to accommodate data when required and that’s all, no need to touch the remaining part.
DarkSchneider
Member
Posts: 71
Joined: Sun Apr 01, 2018 4:02 pm

Re: Some newbie questions

Post by DarkSchneider »

Well most is working now. But:

6. How can compile an independent binary with OS functionality (full stdio)?
Let's say I have my crt0 like this, with its ORG and functions table:

Code: Select all

org 0x4000

extern _add, _sub, _load
dw _add, _sub, _load
Then when load makes use of file functions, I have this output:
compiled with:

Code: Select all

% zcc +msx -subtype=msxdos2 -crt0 crt2.asm dll2.c -o dll2.bin
output:

Code: Select all

close.asm:17: error: undefined symbol: MSXDOS
  ^---- MSXDOS
fopen.c::fopen::0::0:19: error: undefined symbol: __sgoioblk
  ^---- __sgoioblk
fopen.c::fopen::0::0:19: error: undefined symbol: __sgoioblk_end
  ^---- __sgoioblk_end
fopen.c::fopen::0::0:19: error: undefined symbol: __sgoioblk_end
  ^---- __sgoioblk_end
read.asm:22: error: undefined symbol: MSXDOS
  ^---- MSXDOS
fgetc.c::fgetc::0::0:28: error: undefined symbol: fputc_cons
  ^---- fputc_cons
readbyte.asm:20: error: undefined symbol: MSXDOS
  ^---- MSXDOS
target/msx/stdio/fgetc_cons.asm:50: error: undefined symbol: __CLIB_FIRMWARE_KEYBOARD_CLICK
  ^---- __CLIB_FIRMWARE_KEYBOARD_CLICK
open.asm:34: error: undefined symbol: MSXDOS
  ^---- MSXDOS
open.asm:43: error: undefined symbol: MSXDOS
  ^---- MSXDOS
open.asm:51: error: undefined symbol: MSXDOS
  ^---- MSXDOS
closeall.c::closeall::0::0:21: error: undefined symbol: __sgoioblk
  ^---- __sgoioblk
closeall.c::closeall::0::0:21: error: undefined symbol: __sgoioblk_end
  ^---- __sgoioblk_end
Not sure what I have to include or link so it can be placed at the ORG defined at custom crt0, and with the table at first (because that we have to prevent all the standard CRT to be inserted).
With all other functions not using OS (i.e. atoi and etc.) there is no problem at all.

One solution would be to create my own lib interacting with MSX-DOS2 ones, but if can use what is already made, better =D
User avatar
dom
Well known member
Posts: 2076
Joined: Sun Jul 15, 2007 10:01 pm

Re: Some newbie questions

Post by dom »

I've not tried it out, but looking at those errors I think the following should do it:

Code: Select all

crt0.asm:

    org 0x4000

    extern _add, _sub, _load
    dw _add, _sub, _load


    defc    crt0 = 1
    INCLUDE "zcc_opt.def"

    defc MSXDOS = 5
    PUBLIC MSXDOS
    
    EXTERN fputc_cons_native
    PUBLIC fputc_cons
    defc fputc_cons = fputc_cons_native

    ; Firmware click setting (MSX/SVI)
    IF !DEFINED_CLIB_FIRMWARE_KEYBOARD_CLICK
       defc CLIB_FIRMWARE_KEYBOARD_CLICK = -1
    ENDIF
    PUBLIC __CLIB_FIRMWARE_KEYBOARD_CLICK
    defc __CLIB_FIRMWARE_KEYBOARD_CLICK = CLIB_FIRMWARE_KEYBOARD_CLICK

    ; Define maximum number of open files 
    IF !DEFINED_CLIB_FOPEN_MAX
        DEFC    CLIB_FOPEN_MAX = 10
    ENDIF
    PUBLIC  __FOPEN_MAX
    defc    __FOPEN_MAX = CLIB_FOPEN_MAX
    
    ; Maximum number of fds available
    IF !DEFINED_CLIB_OPEN_MAX
        ; Map this old nofileio pragma into a modern form
        IF DEFINED_nofileio
            defc    CLIB_OPEN_MAX = 0
        ELSE
            defc    CLIB_OPEN_MAX = CLIB_FOPEN_MAX
        ENDIF
    ENDIF
    PUBLIC  __CLIB_OPEN_MAX
    defc    __CLIB_OPEN_MAX = CLIB_OPEN_MAX

    PUBLIC  __sgoioblk
    PUBLIC  __sgoioblk_end
__sgoioblk:                     ;stdio control block
    ; stdin
    defw  0
    defw 19
    defs  6
    ; stdout
    defw  0
    defw 21
    defs  6
    ; stderr
    defw  0
    defw 21
    defs  6
    defs (CLIB_FOPEN_MAX - 3) * 10
__sgoioblk_end:                 ;end of stdio control block
This is basically pulling bits from lib/crt/classic that you need - it also allows pragmas to override the number of files/keyboard click behaviour.
Last edited by dom on Fri Feb 17, 2023 12:35 pm, edited 1 time in total.
Reason: Some missing bits
User avatar
dom
Well known member
Posts: 2076
Joined: Sun Jul 15, 2007 10:01 pm

Re: Some newbie questions

Post by dom »

DarkSchneider wrote: Fri Feb 17, 2023 12:06 pmOne solution would be to create my own lib interacting with MSX-DOS2 ones, but if can use what is already made, better =D
You could use the fcntl (open, close, read, write etc) functions directly to get rid of the extra overhead of stdio.
DarkSchneider
Member
Posts: 71
Joined: Sun Apr 01, 2018 4:02 pm

Re: Some newbie questions

Post by DarkSchneider »

OK so basically if I am not mistaken is bring to our own CRT the required from their sources.
dom wrote: Fri Feb 17, 2023 12:42 pm
DarkSchneider wrote: Fri Feb 17, 2023 12:06 pmOne solution would be to create my own lib interacting with MSX-DOS2 ones, but if can use what is already made, better =D
You could use the fcntl (open, close, read, write etc) functions directly to get rid of the extra overhead of stdio.
Great! Didn't know much about that lib, it has great file access capabilities, including folders for DOS2 (using chdir, cannot open directly with a slash or backslash in file name), and even detects de end of file automatically if you send to read a greater value. And the binary size is very small.
Using that, also our CRT only required to define MSXDOS, so it is this small:

Code: Select all

org 0x4000

extern _add, _sub, _load...
dw _add, _sub, _load...

    defc MSXDOS = 5
    PUBLIC MSXDOS
Anyway have to try your previous answer for full stdio capabilities, looking for what could be missing and adding it.

There are so many libs, it would require a deep look indeed.
DarkSchneider
Member
Posts: 71
Joined: Sun Apr 01, 2018 4:02 pm

Re: Some newbie questions

Post by DarkSchneider »

A quick question about integer maths, what multiplication and division uses it by default, and if they can be changed.

I.e. if I multply 2 16-bit numbers, it is done in the classic way, or uses some fast methods like these?
https://www.cpcwiki.eu/index.php/Progr ... its_result
https://www.cpcwiki.eu/index.php/Progr ... its_result
User avatar
dom
Well known member
Posts: 2076
Joined: Sun Jul 15, 2007 10:01 pm

Re: Some newbie questions

Post by dom »

By default the "small" routines are used which will use the standard algorithms. You can add -lfastmath which will use optimised versions.

It's easy to override - just implement the appropriate routines in your application.
Timmy
Well known member
Posts: 392
Joined: Sat Mar 10, 2012 4:18 pm

Re: Some newbie questions

Post by Timmy »

I have a simple newbie question too:

What is a simple, quick, perhaps even inline, or #define, way to create a small and fast function for making an unsiged integer out of 2 unsigned chars?
For example, "x = 0; a = uinte( 128, x );" and a will be 32768 afterwards? I feel like there must be a fast way to pack (and unpack) these chars but I just don't see it. It feels like this must be something inside the compiler already.

Also is there something similar for making an unsigned long from 2 uints?

Thanks in advance.
User avatar
dom
Well known member
Posts: 2076
Joined: Sun Jul 15, 2007 10:01 pm

Re: Some newbie questions

Post by dom »

Some ideas to play around with...

Code: Select all

int func0(unsigned char x, unsigned char y)
{
        return ((int)x << 8) + y;
}

int func1(unsigned char x, unsigned char y)
{
        return (128 << 8) + y;
}


int func2(unsigned char x, unsigned char y) __z88dk_sdccdecl
{
   return *(int *)&x;
}

int func3(int x, int y) __z88dk_sdccdecl
{
   return *(long *)&x;
}


int func4(int x)
{
   unsigned char a,b;

   *(int *)&b = x;
}
Timmy
Well known member
Posts: 392
Joined: Sat Mar 10, 2012 4:18 pm

Re: Some newbie questions

Post by Timmy »

Wow, thanks! That __z88dk_sdccdecl is crazy efficient!

Because I usually have a problem with juggling uchar parameters in function calls, and in combination with inline assembly that means I usually do conversions like "(y<<8)+x" all the time just to fit them in a fastcall.

This doesn't mean I can use fastcalls when I have multiple small parameters, but this __z88dk_sdccdecl might make my code a lot more readable.

I can now, for example, write this: (not recommended)

Code: Select all

int __CALLEE__ quicksubt (uchar e, uchar d, uchar l, uchar h) __naked __z88dk_sdccdecl
// returns: hl - de
{
	#asm
	pop bc
	pop de
	pop hl
	push bc
	or a
	sbc hl, de
	ret
	#endasm
}
with very little register usage. This will save me a lot of juggling and popping 16 bits uchar parameters where one register is always zero!

Obviously I still need to pop things because this cannot be a fastcall, but I feel this will save me some headaches. Thanks!

(And my apologies if this usage is not really intended. :) )

Note: I might got the popping order wrong, I need to think about that later.
User avatar
dom
Well known member
Posts: 2076
Joined: Sun Jul 15, 2007 10:01 pm

Re: Some newbie questions

Post by dom »

Timmy wrote: Wed Feb 22, 2023 1:03 am Wow, thanks! That __z88dk_sdccdecl is crazy efficient!
The downside is that the calling side may not be wonderful - I've not looked at optimising it too much.
Because I usually have a problem with juggling uchar parameters in function calls, and in combination with inline assembly that means I usually do conversions like "(y<<8)+x" all the time just to fit them in a fastcall.
Cast y to an int as in my example and it'll generate better code for some reason.
Note: I might got the popping order wrong, I need to think about that later.
Yes, sdccdecl is pushed right to left
Timmy
Well known member
Posts: 392
Joined: Sat Mar 10, 2012 4:18 pm

Re: Some newbie questions

Post by Timmy »

dom wrote: Wed Feb 22, 2023 8:35 am The downside is that the calling side may not be wonderful - I've not looked at optimising it too much.
No worries. There will always be complexity somewhere. If it's not on the receiving side then it's on the calling side.

It still saves me a lot of extra bookkeeping of parameters, while you didn't had to do anything. That sounds like a win-win for now. :cool:

Thanks!
DarkSchneider
Member
Posts: 71
Joined: Sun Apr 01, 2018 4:02 pm

Re: Some newbie questions

Post by DarkSchneider »

Is there some aligned malloc in Classic library like in New? Currently doing it manually in some "weird" way, but if supported by libs, better.
Post Reply