Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Program refuses to compile
#16
A very long (and hard :oops: ) msg.
I will answer it in chunks
Darkstar Wrote:To me this is bare minimum. Note I did not go into asking for support for TYPEs, a standard fare with QBASIC; or UNIONS, or OOP or a switch from 40bit floats to 32bit floats and that should lessen ZX ROM dependency making the compiler even more retargetable to other machines than it is right now. Nor am I asking for that all commands to use the heap like both of the SAVE and LOAD commands as that lessens even more the dependency of platform specific configurations and not to mention it makes the compiler more consistent. And I did not ask for more input/output choices like the ability to deal with 128K disks and RAM disks or even the disk systems that are available for the 48K machine. Put all of that together then you have a real usable compiler, well, maybe apart from OOP and UNIONS, that reaches beyond the bare minimum. After that UNIONS and OOP and 128K memory management and the support for the PLAY command can be considered to make an outstanding compiler.

These for points summarize the problems to achieve what you are saying:
  • * Putting OOP is not only complex, but also add some serious overhead (memory, speed), for an 8 bit compiler. In fact What you are asking for is C. Yes, you want C with BASIC keyboards. BASIC did never had OOP (it was a BASIC language for learner), and all that come later (mainly by Microsoft) are a derivation to modern languages. Some people ask for pointers, tamplates, and all the stuff C++ has. ¿Seriously? Do this make ZX BASIC a "better" compiler or language? Many learners are already having troubles using current features (e.g. undeclared variables, nexted scopes allowed in the last version and the like). And add such complex things just because they're cool or because they're using in 1 or 2 proyects is not worth the hassle. This is what I discussed in the FreeBasic forum.

    IMHO its better to have powerful data structures (list) in a friendly high level manner (no pointers). Many modern languages don't use pointers, why should ZX BASIC do so? BASIC was *never* intended for that (yes, POKE was removed from modern BASIC dialects, like VBASIC and QBASIC).

    * Supporting memory banks internally makes the compiler very platform dependable, and definitely much more complex. This has already been discussed, and the simplest approach is to mimic LOAD! and the similar commands, with external functions (.bas files with inline asm). Trying to mimic a "linear memory model" with the ZX Spectrum RAM scheme is, IMHO, a waste of time. Not only is complex, but also makes the compiler infinitely more hard. Each block code must be "measured" (in size) and rearranged (e.g. backpatching during linking phase which is currently done by the assembler itself).

    * For the FP format, 32 bits is not "faster". In fact, the 32 bits FP standard is temporarily converted to 40 bits in a format very similar to the one used by the FP ROM. FP ROM was used both for memory saving and compatibility purposed. I've been reading (and looking for) 32 bits FP routines for Z80 and haven't found anything of interest (any help will be welcome in this area).
Reply
#17
Darkstar Wrote:Still more research:

The example above compiles fine and it even compiles fine with a = peek(b) bxor 255.

The example below does not compile under -O3.

dim a as ubyte
dim bPtr as uinteger

for bPtr = 32768 to 32768 + 25
a = peek(bPtr) bxor 255
poke bPtr, a
next bPtr

If you change bxor to 25 or any other number but 255 and 255 does translate to cpl in assembly then it does compile.
Now I do not have to share the source.
It is up to you now Boriel.

Can you please check the version 1.4.0-1877 and check if this is fixed, please (it should).
Reply
#18
boriel Wrote:A very long (and hard :oops: ) msg.
I will answer it in chunks
Darkstar Wrote:To me this is bare minimum. Note I did not go into asking for support for TYPEs, a standard fare with QBASIC; or UNIONS, or OOP or a switch from 40bit floats to 32bit floats and that should lessen ZX ROM dependency making the compiler even more retargetable to other machines than it is right now. Nor am I asking for that all commands to use the heap like both of the SAVE and LOAD commands as that lessens even more the dependency of platform specific configurations and not to mention it makes the compiler more consistent. And I did not ask for more input/output choices like the ability to deal with 128K disks and RAM disks or even the disk systems that are available for the 48K machine. Put all of that together then you have a real usable compiler, well, maybe apart from OOP and UNIONS, that reaches beyond the bare minimum. After that UNIONS and OOP and 128K memory management and the support for the PLAY command can be considered to make an outstanding compiler.

These for points summarize the problems to achieve what you are saying:
  • * Putting OOP is not only complex, but also add some serious overhead (memory, speed), for an 8 bit compiler. In fact What you are asking for is C. Yes, you want C with BASIC keyboards. BASIC did never had OOP (it was a BASIC language for learner), and all that come later (mainly by Microsoft) are a derivation to modern languages. Some people ask for pointers, tamplates, and all the stuff C++ has. ¿Seriously? Do this make ZX BASIC a "better" compiler or language? Many learners are already having troubles using current features (e.g. undeclared variables, nexted scopes allowed in the last version and the like). And add such complex things just because they're cool or because they're using in 1 or 2 proyects is not worth the hassle. This is what I discussed in the FreeBasic forum.

    IMHO its better to have powerful data structures (list) in a friendly high level manner (no pointers). Many modern languages don't use pointers, why should ZX BASIC do so? BASIC was *never* intended for that (yes, POKE was removed from modern BASIC dialects, like VBASIC and QBASIC).

    * Supporting memory banks internally makes the compiler very platform dependable, and definitely much more complex. This has already been discussed, and the simplest approach is to mimic LOAD! and the similar commands, with external functions (.bas files with inline asm). Trying to mimic a "linear memory model" with the ZX Spectrum RAM scheme is, IMHO, a waste of time. Not only is complex, but also makes the compiler infinitely more hard. Each block code must be "measured" (in size) and rearranged (e.g. backpatching during linking phase which is currently done by the assembler itself).

    * For the FP format, 32 bits is not "faster". In fact, the 32 bits FP standard is temporarily converted to 40 bits in a format very similar to the one used by the FP ROM. FP ROM was used both for memory saving and compatibility purposed. I've been reading (and looking for) 32 bits FP routines for Z80 and haven't found anything of interest (any help will be welcome in this area).

Funny, I was trying to use the compiler without all the pointers and pokes and ran into nothing but walls and bugs and POKE was never removed from QBASIC or Visual BASIC 1.0 for DOS but it was removed I think for the Windows versions. I can very well live without OOP like I said, to be considered. No I am not asking for C in BASIC
but the opposite. For instance, I hate the C-style loops. But OOP can be useful if the compiler handles arrays like objects internally, one ERASE command and the OVERALL array descriptor is gone. Then I can use: SAVE "Contents"DATA MyArray() and the DIM for it is Dim MyArray(0 to 7,0 to 31) as STRING and lets say that the array is 7K long, then the program saves that seven K with the overall array container. Then the container/descriptor is at the base for the array pointers and then comes the array if uninitialized with the length of each element as one preceded of course with the two byte len descriptor for each element. If I assign a two byte string to a element then there is shifting or relocation. The idea behind REDIM is that one of the routines in it should have a array compactor/reallocator. Not exactly OOP
but something aking to it, to treat each array as an independent block transparent to the programmer like it was in QBASIC. Like I said, I could live without OOP and I do not really care for it in an 8bit compiler or for a minimalistic computer like the ZX or the old 8 bit machines. TYPEs are a different matter and then it helps to treat them as blocks as well and the min length of one for an uninitialized element could be ten or user specified. They had the number ten in the old days if I remember correctly or maybe that was the number of array elements. It could not even be one in the two byte len descriptor itself but instead just a reserved memory zone of ten bytes for each element of stated zero in length. I was working with fixed length array elements each five bytes or letters in size then it is better to have the reserved len user
specified. To load the array back then the DIM must match. This is what I am talking about, a friendly high level manner.

Memory banks could be handled like plug in with extensions for each machine, carefully designed so the overall structure of the *concept* of memory banks fits all machines with the same overall extensions for every machine BUT the implementations of those extensions is different for each machine. The ZX 128K is not the only machine with extra memory banks. So it is a matter of a good plug in model to encapsulate the idea of banks with a good model of fixed extensions to it to actually implement it on each machine. The backpatching will still be done by the compiler, no chance there. But if you are dealing with files compiled with the ROM switch then of course the backpatching addresses into ROM will change so a good dynamic list for the compiler is important. People then can write a part of their own plugins for different machines making your job easier.

I hope that you find some 32bit routines for the Z80 to make it at least a user definable option if to go 32bit or 40bit for the ZX but if it is already converted to 40bit from 32bit then you can't be that far from a full fledged 32bit FP format plus what I have seen in the asm files for the compiler.

But OOP is out and that is good, after Microsoft's small basic and Scratch for Linux then I would let the kids use ZX BASIC to get a real feel for BASIC coding and compiler usage and a low level feel, an ASM feel even on how computers really work. Something that is entirely missing in today's world. Or beginners in computer learning.

Hey, maybe even disk routines can be written using some sort of a plug in structure and all of those routines use the heap.

An idea at least.
Reply
#19
boriel Wrote:
Darkstar Wrote:Still more research:

The example above compiles fine and it even compiles fine with a = peek(b) bxor 255.

The example below does not compile under -O3.

dim a as ubyte
dim bPtr as uinteger

for bPtr = 32768 to 32768 + 25
a = peek(bPtr) bxor 255
poke bPtr, a
next bPtr

If you change bxor to 25 or any other number but 255 and 255 does translate to cpl in assembly then it does compile.
Now I do not have to share the source.
It is up to you now Boriel.

Can you please check the version 1.4.0-1877 and check if this is fixed, please (it should).

It works fine now thank you very much.

This line sadly still bombs on me:

const BasePtr as uinteger = 32768
save "Contents"code BasePtr, 25
Reply
#20
I wrote:
"To load the array back then the DIM must match."

Of course, after the bytes header load then there must be a check to see if there is enough space on the heap for it after the computer has compacted it after finding a LOAD"Contents"DATA MyArray command. After the load then it must check if the data matches the overall descriptor set up by DIM in basic by the loading program. Then it must connect the pointers.
Reply
#21
I wrote:
"one ERASE command and the OVERALL array descriptor is gone. Then I can use: SAVE "Contents"DATA MyArray() and the DIM for it is Dim MyArray(0 to 7,0 to 31) as STRING and lets say that the array is 7K long, then the program saves that seven K with the overall array container. Then the container/descriptor is at the base for the array pointers and then comes the array"

Or to make matters simple then just the pure contents are saved, no overall descriptor and no pointers. Just the size of each element in two bytes and the elements themselves. Then upon loading there will be no duplicates than use up memory and then the duplicates have to be eliminated and everything recompacted again. Lot of extra steps and routines than can be done away with.

If the fixed reserved len is ten for each array element then upon saving the computer should compact it so that a three byte element takes five bytes, the len of the element (two bytes) plus the data itself. Upon loading the process should be reversed and that should be taken into account when calculating if there is enough space upon the heap to actually receive the array. To do that then a two byte descriptor should be enough that holds the reserved len and it of course should be checked after loading provided if there is enough space on the heap to load the actual byte file, tape or disk. There should also be another two bytes for the size of the largest element as the heap routines have to have that information to determine the workspace for itself. Other than those four bytes, just the pure element data.

The container/descriptor is at the base for the array pointers and then comes the contents of the array and the data is loaded right after the array pointers.

Then to check the DIM match it checks if there an equal number of pointers set up by the DIM in the loading program and the elements loaded in. To do that it has to count the number of elements loaded in by reading the len of the first two bytes and then jump that len plus two bytes to the next element and add one to a number of elements counter. Then it is a huge help to have the array totally compacted prior to saving it and it saves space.

The next step is connecting the contents of the array to the pointer structure after the array has been expanded according to the reserved length, or to plug it in. To make matters even simpler then the four bytes previously mentioned should be saved at the end of the array contents as it makes clean up unnecessary and the shifting down of the array by four bytes.

To plug in an array as a data stream or stream programming of encapsulating contents in a standalone block.

I wrote:
"Of course, after the bytes header load then there must be a check to see if there is enough space on the heap for it after the computer has compacted it after finding a LOAD"Contents"DATA MyArray command. After the load then it must check if the data matches the overall descriptor set up by DIM in basic by the loading program. Then it must connect the pointers."

The basic pattern here is based on a trinity: Execution (The load command), Order (it's format or the data keyword) and File as in to file something between places to be executed or to file or to fill in the pointer structure, then the plug in execution is complete.
Reply
#22
Darkstar Wrote:This line sadly still bombs on me:

const BasePtr as uinteger = 32768
save "Contents"code BasePtr, 25
Please try now, Version 1.4.0-s1880, and tell me if SAVE now works (silly bug: I just forgot that translation).
Reply
#23
boriel Wrote:
Darkstar Wrote:This line sadly still bombs on me:

const BasePtr as uinteger = 32768
save "Contents"code BasePtr, 25
Please try now, Version 1.4.0-s1880, and tell me if SAVE now works (silly bug: I just forgot that translation).

It works without a hitch now, thank you for fixing this as I am not totally blocked as I was before.
The nested scopes were a good addition me thinks.
I did write somthing else and I am including it below.

Code:
'dim MyArray(0 to 4) as string => {{"ABCDE","ABCDE","ABCDE","ABCDE","ABCDE"} ' each element is 5 letters or bytes in length

const BasePtr as uinteger= 32768 ' 32K
dim MyArray(0 to 4) as string
dim i as ubyte

dim Key as string


' init the array
for i = 0 to 4
    MyArray(i) = "ABCDE" ' 5 in length
next i

' I had to init the array here to make sure each element is five bytes in length and I could not do it through the dim line above
' so this is just another workaround. I had more options in the menu but I took them out but they allowed for meaningful content creation.
' The content was created by those options so I could get away in this instance with a dumb fill of the array but what if I want to fill the
' array with something meaningful right from the get go and perhaps with a variable length in the elements? No DATA lines and no DIM and even
' more workarounds to account for the variable length. Ugly ugly hacks. It could even FORCE me to go to the ASM level.

MenuEntryPoint1:
paper 7
flash 0
bright 0
over 0
inverse 0
ink 0
border 6
MenuEntryPoint2:
cls

print
print "1 Load file"
print "2 Save file"
print
print "3 Quit"

do
    Key = inkey
loop until Key = "1" or Key = "2" or Key = "3"

if Key = "3" then
    randomize usr 0
end if
cls

if Key = "1" then
    gosub LoadFile
end if

if Key = "2" then
    gosub SaveFile
end if
'goto MenuEntryPoint2
' This is how it should read or to go to Point2 instead of Point1 but because the SAVE command messees up the color
' then this workaround was the only option and it alters the program flow.
goto MenuEntryPoint1
' The save message itself or the ROM based message gets displayed in yellow paper and that I also consider a bug but I did not look into
' a yet another workaround for it.

' All of these workarounds sure mess up the code and that is why I did not look into it for I have had it with unfriendly workarounds and they just
' make things way more complex than they have to be.

' But the good news is that due to some recent fixes I can eliminate some of them but far from all of them and I can't eliminte the linier array workaround either
' as of now but that is one of the things I took out of this BAS file.

' I do not want to share ugly hacks.

LoadFile:
print "Loading file"
print
load "File"code BasePtr, 25

' Do lots of stuff that has been deleted from this BAS file

return

SaveFile:
print "Saving file"
print

' Do lots of stuff that has been deleted from this BAS file

save "File"code BasePtr, 25
return
Reply
#24
Another example of Excecution Order File.

The method to reset the machine now is randomize usr 0 or even print usr 0 but the first method pulls in the randomize library and the print method pulls in the 1K print routines and maybe I
have custom print routines written in asm. I could always do ASM JP 0 END ASM but that both forces me to do ASM level coding and the start address of zero is perhaps not the same for
every machine if it excludes the Z80 processor.

How do we fix this?

Simple, we just add the keyword RESET to the language.

But this is just execution, there is no order or file.
It appears to be so but you would be wrong for thinking that.

RESET, then it looks for the platform or the format or the ZX in this case and then it does file or to confirm the reset routine for that platform (JP 0) by looking it up in a list or a file and then to
connect it in the right place or to pull it inline.

This way, the platform dependent routines can be kept seperate from the concept of a reset.

This is another way to look at encapsulation.
Reply
#25
Darkstar Wrote:Another example of Excecution Order File.

The method to reset the machine now is randomize usr 0 or even print usr 0 but the first method pulls in the randomize library and the print method pulls in the 1K print routines and maybe I
have custom print routines written in asm. I could always do ASM JP 0 END ASM but that both forces me to do ASM level coding and the start address of zero is perhaps not the same for
every machine if it excludes the Z80 processor.

How do we fix this?

Simple, we just add the keyword RESET to the language.

But this is just execution, there is no order or file.
It appears to be so but you would be wrong for thinking that.

RESET, then it looks for the platform or the format or the ZX in this case and then it does file or to confirm the reset routine for that platform (JP 0) by looking it up in a list or a file and then to
connect it in the right place or to pull it inline.

This way, the platform dependent routines can be kept seperate from the concept of a reset.

This is another way to look at encapsulation.

you can contribute a macro RESET
Code:
#define RESET   ASM \
                RST 0 \
                END ASM

I think it's better to do this rather than expanding the language (cost more, compiler - arch dependant, etc).
This is what other languages (e.g. C) did and was a great success. I'm still in the way to change the compiler so people can define and expand the language themselves.
Reply
#26
boriel Wrote:* Supporting memory banks internally makes the compiler very platform dependable, and definitely much more complex. This has already been discussed, and the simplest approach is to mimic LOAD! and the similar commands, with external functions (.bas files with inline asm). Trying to mimic a "linear memory model" with the ZX Spectrum RAM scheme is, IMHO, a waste of time. Not only is complex, but also makes the compiler infinitely more hard. Each block code must be "measured" (in size) and rearranged (e.g. backpatching during linking phase which is currently done by the assembler itself).

It's not easy to invisibly support a linear address space. The machines very widely in how capable their banking mechanism is. The spectrum probably has the weakest banking scheme (+3 excluded) only allowing memory to be paged into the top 16k bank. The most flexible is probably the Enterprise (although it's not the only one with a similar scheme) which divides the space into four 16k pages each of which can have any of 256 physical memory pages paged in. But not all memory pages are equal with some being "contended" (shared with the display driver) and some might be partly allocated by the operating system.

In z88dk we're giving the programmer the ability and responsibility for placing stuff in memory and trusting that the programmer writes code that properly pages in stuff as needed. This is done with SECTIONs in the assembler/linker where the program can assign code and data to named sections (aka containers) which can have their own ORG address and the linker simply spits out one binary per section. So you could create one section for each of the spectrum's extra 16k banks and put stuff into it but the program is still responsible for making sure the correct page is active before accessing that data or code.

The C standard is starting to define what to do with banked memory in embedded systems in a technical report which is the step prior to being adopted as a standard. The scheme is similar :- the programmer defines what sections code and data is poured into by name. The implementation is supposed to create code that will automatically bank when needed and the mechanism is probably supplying one bank called 'common' that is always paged in and which contains the necessary banking code to perform necessary actions. But there are many issues to overcome particularly with performance.

Anyway it's not such a bad thing to leave the responsibility with the programmer.

Quote:* For the FP format, 32 bits is not "faster". In fact, the 32 bits FP standard is temporarily converted to 40 bits in a format very similar to the one used by the FP ROM. FP ROM was used both for memory saving and compatibility purposed. I've been reading (and looking for) 32 bits FP routines for Z80 and haven't found anything of interest (any help will be welcome in this area).

On the z80 the natural float size is 48 bits and you'll see many floating point implementations from the 70s and early 80s are 48 bit. At 48 bits the z80 is able to keep the float calculation in registers as much as any size smaller so those implementations naturally aimed for the largest precision that was still fast. One implementation called math48 was used in Turbo Pascal and it can hold two 48-bit floats at the same time - one in BCDEHL and the other in BCDEHL' (in the exx set) which suits an fp library perfectly as nearly all functions take either one or two parameters.

It's also a joy to program in assembly language which is something that always indicates the implementation is well suited to the processor. Have a look at this code implementing the atan2() function:

Code:
; double atan2(double y, double x)

SECTION code_fp_math48

PUBLIC am48_atan2

EXTERN am48_dcmpa, am48_ddiv, am48_atan, am48_dneg
EXTERN am48_dconst_pi, am48_dadd, am48_dpopret

am48_atan2:

   ; compute arctan y/x in the interval [-pi, +pi] radians
   ;
   ; enter : AC = double y
   ;         AC'= double x
   ;
   ; exit  : AC' = atan2(y,x)
   ;         carry reset
   ;
   ; uses  : af, af', bc', de', hl'

   push bc                     ; save AC
   push de
   push hl
  
   exx
   call am48_dcmpa             ; fabs(x) >= fabs(y) ? (eff |x| - |y|)
   exx
  
   jr nc, greater_equal

less:

   ; AC = y
   ; AC'= x
  
   call am48_ddiv
   call am48_atan
   call am48_dneg              ; AC'= -atan(x/y)
  
   ld a,b
   and $80                     ; a = sgn(y)
  
   call am48_dconst_pi
   dec l                       ; AC = pi/2

join:

   or b
   ld b,a                      ; AC = sgn(y)*pi/2
  
   call am48_dadd
   jp am48_dpopret

greater_equal:

   ; AC = y
   ; AC'= x

   ld a,b
   and $80
   push af                     ; save sgn(y)

   exx
  
   ; AC = x
   ; AC'= y
   ; stack = sgn(y)
  
   call am48_ddiv
   call am48_atan              ; AC'= atan(y/x)

   pop af                      ; a = sgn(y)
      
   bit 7,b
   jp z, am48_dpopret          ; if x >= 0 done

   call am48_dconst_pi         ; AC = pi
   jr join

;double atan2(double y, double x)
;{
;   double a;
;
;   if (fabs(x) >= fabs(y))
;   {
;      a = atan(y/x);
;      if (x < 0.0)
;      {
;         if (y >= 0.0)
;            a += _pi ;
;         else a -= _pi ;
;       }
;   }
;   else
;   {
;      a = -atan(x/y);
;      if (y < 0.0)
;         a -= _halfpi;
;      else
;         a += _halfpi;
;   }
;
;   return a;
;}

AC indicates the double in the primary register set and AC' the double in the exx set. AC stands for accumulator.


The 32-bit single precision float was an invention for 32-bit processors since that's the max bit width that can be handled on those processors in the quickest instructions. On the z80 the natural size is 48-bits as mentioned. A 16-bit float might be another option but that's too limited for a float type and is more suitable to a fixed point type.

With a 48-bit float the mantissa is 40 bits and with a 32-bit float the mantissa would be 24 bits. The only advantage the 32-bit float has in terms of execution speed is bit shifting only has to be done at most 24 times rather than 40. But on a z80 there is a sense that you're losing free precision bits when constraining to 32 bits.

Boriel you mentioned you found a 32-bit fp implementation for the z80. Do you have a link for it?
Reply
#27
Wow long time from this old thread.
Found this 24bit one, anyway (a week ago)
http://z80-heaven.wikidot.com/floating-point hope it helps!

Have you implemented something?
Reply
#28
boriel Wrote:Wow long time from this old thread.
Found this 24bit one, anyway (a week ago)
http://z80-heaven.wikidot.com/floating-point hope it helps!

Have you implemented something?

Yes it is quite an old thread. Thanks for the link.

No I have bot implemented anything because I have been busy with other things, like what life can throw at you.
It has not been easy these couple of years.

But who knows maybe in the future I might pick up the pace again, generally speaking as it relates to the ZX.
Reply
#29
boriel Wrote:Found this 24bit one, anyway (a week ago)
http://z80-heaven.wikidot.com/floating-point hope it helps!
Have you implemented something?

Unfortunately it barely scratches the surface - the ones we have usually number around 140 functions. It's hard to find real z80 floating point packages that can readily be used.

The floating point topic did come up again recently and there are a couple of links in there. I don't see it in that thread but digi also put a large assembly language library for the rabbit processors on github and that one also has a float package but obviously written for the rabbit which has extra instructions.

We did some benchmarking using Whetstone 1.2 to measure performance of some floating point packages with various z80 c compilers. The 48-bit one we are using came up number 3 in speed, not too far behind IAR which is a 32-bit float implementation. Hitech's 32-bit float implementation blows everyone away in terms of performance but is very unreliable, often leading to incorrect results. This was enough to convince me that we can do better than that at 32-bits using some fast 32-bit integer math. But it's more likely we'd do fixed point first because that would fit better with graphics. Floating point is not really that important in 8-bit computing anymore.

As always there are too many things to do so these things probably won't be attempted for some time, especially given how time consuming it is to implement floating point.
Reply


Forum Jump:


Users browsing this thread: 2 Guest(s)