pre-release of Megalinker: a linker to build of Megaroms for MSX using SDCC

By salutte

Expert (100)

salutte's picture

07-09-2020, 11:59

Hi all,

I've been dealing with making megaroms using sdcc for the best part of the last two years, and in the process I created a complex framework to build them. As part of such framework I created linkers that enabled the creation of megaroms using sdcc, but they were quite limited. The first version: k5linker had a lot of hardcoded constrains, the second version, modulelinker was mapper independent. But the current linker is a breakthrough w.r.t. to the previous ones, and I plan to release it independently from the framework.

This weekend I finished the support for external libraries (i.e. fusion-C), and I just got my first fusion-C hello world running.
With this I reached all the features that I was aiming for, and I think its mature enough for a pre-release:

https://github.com/MartinezTorres/megalinker

It has:

  • Each c file (or asm) is compiled in its own module.
  • Each module may contain as much as 8K of banked content. Each module can be mapped to only one of the A, B, C, or D pages.
  • Calls inside modules are for free, calls between modules must be dealt manually.
  • Support for any 8K mapper.
  • Support for non-const initialized variables and static variables.
  • A very much simplified interface to load and restore modules in each of the 4 pages.
  • Support for ___nonbanked sections, which are placed in RAM during boot, and thus available for ISR routines and trampolines.
  • Support for rel files and lib files. Non used rel files are automatically discarded.
  • Support to manually combine multiple modules in one segment (e.g., to use fusion-C code).

The underlying mechanism is pretty solid, but there is still a lot of testing and documentation work, but I am still open to changes on the API and to add features.
If you find this project interesting please check it!

Login or register to post comments

By Bengalack

Master (173)

Bengalack's picture

07-09-2020, 22:50

This sounds really awesome. I've been thinking that maybe my next project should be megarom, but don't know where to look really. Also, I use SDCC so this sounds just perfect (when the time comes - my project is not ready yet, but I'm getting my hopes up :-) )

By dom

Supporter (3)

dom's picture

10-09-2020, 00:20

You may want to take a look at the z88dk implementation - it uses a modification to sdcc to support adjusting the stack-offset for argument passing across banks. It's not an ideal solution, the alternative is probably to use the FUZIX modification and pad the stack for every function call which has downsides as well of course.

Demo project here: https://github.com/z88dk/z88dk/tree/master/examples/banked which runs on MSX, Gameboy, ZX Spectrum.

MSX docs here: https://github.com/z88dk/z88dk/wiki/Platform---MSX#megarom-mode

Obviously, I've removed the possibility of creating and using arbitrarily complex memory maps and "abused" the mappers to create a custom memory model that realises a non-pageable area of the memory space to hold library routines and common code. I chose to use ROM rather than RAM in your project to implement this common area: some of the targets supported by z88dk are RAM constrained to say the least. The reduction in flexibility is a bit of a trade off for ease of use/portability for programs written mostly in C.

Adopting this common model across targets should hopefully make developing bank auto-layout/packing a lot simpler and portable - from my personal perspective the last thing I want is to have all 100+ targets doing the same thing in their own esoteric way.

By salutte

Expert (100)

salutte's picture

14-09-2020, 10:32

Hi dom,

I've checked the z88dk solution, and it is neat! However, we aim for different goals. Of course, z88dk targets a common denominator between platforms, to minimize the support to the 100+ targets! However, I aimed to have a finer grained way to control the content of banks.

Also, one of my design goals is not to modify sdcc, because I do not want to maintain a patch for it. On this line of thought, geijoenr has worked on modifying sdcc to implement far pointer support for z80, thus enabling the use of banks using standard C syntax. However, as far as I know, there were some problems on this implementation. He commented his approach in:
https://www.msx.org/forum/msx-talk/development/making-real-2...

As I found that bank management errors while developing C megaroms were quite common (e.g., if you pass a pointer to data in one bank, and then you unload that bank). I ultimately decided to force the use of a manual loading and unloading of the banks. This enables top flexibility, but at the same time, the linker can check for inconsistencies and do some sort of error checking.

One of my typical use cases for megalinker is: load in A the code of the level I am on, in B the code of the enemy, in C the code of the library I need right now (graphical or sound), and in D the data (e.g., music, sprites, etc.). Then calls between pages are way cheap because everything is loaded at the same time. The common code should contain only trampolines(~25 bytes each) or very light weight functions, thus RAM usage is very limited. In fact, Anchors Aweigh works entirely without trampolines, thus uses no extra ram (it uses page A as a common block, as z88dk does).

Actually, the megalinker itself is reasonably platform independent, as the magic happens mostly on the CRT, and the macros. I have never worked with rom mappers for GB, but I bet that by changing the CRT, it should work with megalinker without changing the syntax.

z88dk inspired me to add support for 16KB segments to megalinker, and also to support different rom entry points (which now are hardcoded).

In any case, I find it nice that there are several options to deal with this topic :)

By geijoenr

Master (178)

geijoenr's picture

14-09-2020, 11:27

Quote:

geijoenr has worked on modifying sdcc to implement far pointer support for z80, thus enabling the use of banks using standard C syntax. However, as far as I know, there were some problems on this implementation.

Yep, I wasn't aware of the functionality in z88dk but what I have done looks similar, using pragmas to define the bank a module should be linked to and transparent banked/non_banked calls using attributes. The handling of parameters is automatic in my case, I don't understand why an attribute is needed but I will take a look at the implementation to compare.

Of course there are many problems with this! function pointers do not work at all and there are some quirks with the stack when mixing chained banked and non banked calls.
Also data access is totally manual, as the alternative would be using far pointers for everything, which seems to be a non-starter from the performance point of view. So even though the banked calls are automatic, bank switches for data access are still manual.

but overall it works well enough, L'Abaye des Morts uses this feature.

By geijoenr

Master (178)

geijoenr's picture

14-09-2020, 11:54

oh, no wonder I didn't know about it. The support to z88dk was added in August.

By dom

Supporter (3)

dom's picture

16-09-2020, 20:35

Quote:

Yep, I wasn't aware of the functionality in z88dk but what I have done looks similar, using pragmas to define the bank a module should be linked to and transparent banked/non_banked calls using attributes. The handling of parameters is automatic in my case, I don't understand why an attribute is needed but I will take a look at the implementation to compare.

I used the main stack to stuff the previous bank and the extra return address so the arguments are offset by 4 bytes, but I think your idea of using a secondary stack/buffer is quite neat - using the secondary stack for the stuffing will keep the stack aligned and code easier to write which means the attribute can be dropped at the cost of a little bit more RAM.

With sccz80 the const section changes with the #pragma which keeps static immutable data together with the code in the same bank. With sdcc you have to use the --constseg option to do the same thing

By salutte

Expert (100)

salutte's picture

17-09-2020, 09:00

In megalinker I prefer not to implement the automatic trampoline generation. I implemented it once using the dual stack approach, but then the cost of calling a function through a trampoline jumped to around 300 extra cycles. My main concern is that automatic trampoline generation hides this cost, and makes the developer less aware of it. the z88dk should be quite more light weight, but in any case, I prefer not to hide this cost. Also, it makes concepts such as function pointers more difficult.

Hence I prefer to work using the many-bank approach and manual management of banks, and still leave the option of using trampolines, but then the user must implement them explicitly.