hth313 / Calypsi-tool-chains

Overview of the Calypsi tool chain and open source support packages
16 stars 0 forks source link

Medium Model for Code #32

Closed ProxyPlayerHD closed 4 months ago

ProxyPlayerHD commented 11 months ago

while creating the other issue i thought of another suggestion i had but didn't want to include in there because it didn't fit the theme. so i'm opening a new issue.

the idea being a Medium Code Model, similar to the Medium Data Model. ie code is limited to a single bank other than bank 0 and all program related pointers are 16-bits wide by default (so JSR/RTS instead of JSL/RTL).

this plus using a medium data model would allow you to create a minor version of position independent code. so you can place both the program and it's static data in any arbitrary bank and they would still function correctly without needing to be recompiled.

(though the guide says that the Medium Data Model uses 24-bit pointers by default, which seems confusing to me as i thought the point of the medium model was specifically to stay within the bounds of a bank to then make use of the more efficent 16-bit pointers and addresses. so wouldn't it make more sense to default to 16-bit pointers unless specified? or what am i missing here?)

hth313 commented 11 months ago

I suppose that I can add a small code model. There are some complications with that interrupts need to point to a function in bank zero and that they make call to other functions which will be in another bank. I will need to look into it a bit to see if that causes issues.

The medium data model uses 24 bit pointers because of default pointers in C and the stack. Taking the address of stack allocated object is perfectly fine in C and a default pointer shall be able to point to it. On the 65816 the stack is in bank zero which forces static objects to be in the same bank if it is to use 16 bit pointers. This is the Small data model. In the Medium data model a single bank (which is allowed to be non-zero) is be used. A 16 bit pointer cannot point to both areas, so a 24 bit pointer is used instead.

ProxyPlayerHD commented 11 months ago

i've never really looked into how the compiler handles interrupt functions in general. with my config, code is not allowed to be placed in bank 0, would that mean that even with the Large Code Model i wouldn't be able to create an interrupt function? even if it can be placed in bank 0 that wouldn't matter if the vectors are unwritable (ie they're in ROM), so how does an interrupt function make itself call-able?

or does it work like with the main function and you have to handle calling it through startup.s? in which case doesn't that already (sort of) solve the issue? so if startup.s' code is located in Bank 0 (maybe put into ROM) then you could use JSL to call the interrupt function located in another bank, and manually assign it the __far attribute so it returns with RTL. once back in startup.s it then uses RTI to return execution to where it was before. and if startup.s' code is located outside of bank 0 (ie within the bank where the rest of the code is located) then you can simply use JSR/RTS like for the main function. this of course relies on external code in bank 0 to work but i don't think that should be a issue as it can't always be assumed the compiler's output is the only piece of code present on a system.

Taking the address of stack allocated object is perfectly fine in C and a default pointer shall be able to point to it. On the 65816 the stack is in bank zero which forces static objects to be in the same bank if it is to use 16 bit pointers. This is the Small data model.

ok i had to re-read that a few times to get what you mean. addressable objects can be located in either static data or the stack, so both being in seperate banks makes it impossible to refer to both using a 16-bit pointer.

it would be possible to keep the position independence of the static data by having static objects default to a 16-bit pointer, and stack objects to a 24-bit pointer though that requires knowing if an object is static at compile/linking time, plus it means that pointers have 2 different sizes which likely makes thing a lot more complicated. (or maybe treat both as 16-bit pointers and only 0-extend the stack one when it's actually used it to access data though that requires the compiler to keep track of which 16-bit address refers to bank n and which to bank 0)

another option would be to always 16-bit pointers but load the data bank with 0 everytime a stack object is being accessed (or more specifically it would save the current data bank, then load it with 0, and afterwards restore it. so you can still set it to whatever you want in startup.s). obviously this is a lot slower, but maybe it's easier to implement?

honestly i'm not 100% sure how to solve this. this is mostly just me throwing ideas around, so i'm sorry if they aren't as feasible as i imagine them to be.

hth313 commented 11 months ago

The compiler handles the placement of interrupt functions and use a specific section name for them so that they are placed in bank 0. This is already done. The C startup is also in bank 0. They have to be as the vectors are 16 bits and can only point to bank 0.

Interrupt function may have a vector address, or it may be omitted in which case you need to provide some mechanism to install their vector that works with you hardware/OS.

I plan to solve the call issue between interrupts in a medium code model by having a compiler generated trampoline that is placed in the 64K code bank and called with jsl from the interrupt. The trampoline will jsr the function in the same bank (which assumes being called from within its bank) and they return back to the interrupt function in bank 0 using jsl. This should be completely transparent handled by the compiler.

hth313 commented 11 months ago

When it comes to data the problem is that C normally works with untyped pointers and such pointers can point to any data, be it a stack variable or a static variable. Such pointer can be passed to a library function and it cannot know if the data pointer points to the stack or static data. In fact, in one call it can be to the stack and in another it can be some static data and the third time some memory obtained using malloc().

You can assign memory qualifiers to pointers, so you can have a __near pointer which is 16 bits and pass it around, but the type associated with the data pointer needs to carried around with the memory attribute at compile time. This allows the compiler to generate appropriate code to take advantage of knowing what kind of pointer it is. However, such pointers cannot be given to a library call or any function expecting a pointer without such attribute as the code needed to access it will differ. In such cases the compiler enforces a conversion (it may force you to insert an explicit cast) from a specific memory pointer to a generic one (without attributes). On the cc65816 it means that it will convert it from a 16 bit pointer to a 24 bit pointer, essentially passing the bank with it at runtime.

As there is no provided explicit memory attribute for bank 0, a pointer to a stack variable is immediately converted to a 24 bit pointer. This is done by design to keep the number of memory attributes down, there are already enough complexity in this.

hth313 commented 4 months ago

Release 5.3 adds the Compact code model.

ProxyPlayerHD commented 4 months ago

thank you, very nice! i'm going to have to check that out.

one step closer to generating programs usable in a native 65816 OS! only thing missing is relocatable code/data, which could be done entirely with the linker by adding support for the o65 file format (which could be used for all 3 target CPUs). but i'll likely open a seperate issue for that