I've had a go at adding support for writing standalone language ROMs with PLASMA. This means that you can create an "application ROM" like Wordwise or ViewSheet using PLASMA, with the fact it is written in PLASMA instead of machine code being invisible to the user. (Not that it's anything to be ashamed of.
My point is that it "just works", like any other language ROM.)
The code to do this is a little bit hacky and experimental but I think it should be reasonably solid. You can find it on the
rom branch in my repository:
https://github.com/ZornsLemma/PLASMA/tree/rom I've also created a
stardot9 tag with the current works-for-me-at-least version of the code.
Dave has very kindly altered the PLASMA licence to MIT and that change has also been merged in. Thanks again Dave!
Given a compiled PLASMA module pnle.mo, you can turn it into a langage ROM pnle.rom by doing:
Code: Select all
python plasmatool/link.py -o rom-full.a samplesrc/acorn/pnle.mo
acme --setpc 0x8000 -o pnle.rom rom-full.a
The service code, PLASMA bytecode interpreter and the "must have" PLASMA library routines take about 3.25K of the 16K address space in the ROM. I suspect that PLASMA bytecode is a lot denser than 6502 machine code in general, so that should help to compensate for the space taken up by this support code.
As with the standard PLASMA VM, you can mix PLASMA code with hand-written assembler in your program. The standard "asm" functions work as usual, and there's also support for what I'm calling "raw" assembler, which is a bit more flexible; I will probably do a follow-up post at some point with more on how this works. So you can use whichever is best for any particular task within your program, and if you wanted to you could even start off with a prototype of your language ROM written in PLASMA and gradually convert it to pure machine code a function at a time.
There are three examples on the "rom" branch in my repo, which are automatically built by the makefile and put on the demo.ssd disc. I've attached a copy of that if anyone wants to play with it without building the code for themselves. They are:
- R.PNLE - a simple "no language environment" language, which sits in a loop accepting * commands from the keyboard and passing them to the OS. Start this with *PNLE. If you want to write your own language ROM, this is probably a good place to start - I have tried to comment the source code thoroughly, and in particular it shows how to install an error handler, which almost all language ROMs will need. (In fact, the bulk of the source code is comments, so please don't let the apparent verbosity put you off - there are only 33 lines of actual code in there.)
- R.TESTROM - a convoluted test/demonstration of some corner cases in the static linking process. Start this with *TESTROM. This might be useful as an example if you want to start taking advantage of some of the more advanced features of link.py. It shows the use of "raw" assembler, as opposed to PLASMA "asm" functions, for example.
- R.HANOI - a *hack* of the existing Towers of Hanoi sample code built as a language ROM. Start this with *HANOI. Unlike the previous two examples, this assumes it is running from sideways RAM and will not work if it's run from unwritable memory.
There are a few restrictions on PLASMA code being used in a language ROM, unfortunately.
Global variables are read-only
The main restriction on PLASMA code in a language ROM is that global variables are read-only. If your ROM will *only* be used from sideways RAM which follows the standard Acorn convention (so if it's paged in, it's implicitly writeable), you can ignore this.
link.py will give a warning for "simple" attempts to write to global variables, although this will not catch everything.
One workaround for the lack of writeable globals is to call a main() function immediately from the init code and put your would-be globals in there as local variables. You'll need to pass pointers to them to any functions that need to access them.
You can also use global pointers to point to memory allocated on the heap. For example, instead of:
Code: Select all
word myglobal // 16-bit word
...
myglobal = 42
...
if myglobal < 60
...
you can do:
Code: Select all
const myglobal_ptr = $0070 // PLASMA VM leaves $70-$8F inclusive free for user (as BASIC does)
...
*myglobal_ptr = xheapalloc(2) // do this in init or a "main()" function ASAP
...
*myglobal_ptr = 42
...
if *myglobal_ptr < 60
...
With either of these approaches, a useful technique is to define a structure which holds all the globals:
Code: Select all
struc t_globals
word global1
word global2
byte global3
...
end
...
// In "init" or early in main()
*myglobal_ptr = xheapalloc(t_globals)
...
myglobal_ptr=>global1 = 42
...
puts("global1=")
puti(myglobal_ptr=>global1)
as this means you don't have to worry about allocating them all individually (and, if you chose the option of passing pointers into functions which need the globals, avoids having to pass multiple pointers). (samplesrc/acorn/testrom3.pla has an example of this.)
Globals are not completely useless; you can use pre-initialised globals (particularly arrays) for read-only tables of data. In general you should probably use "const" instead of simple scalar read-only globals you don't plan to take the address of, as the generated code will be smaller with "const".
There aren't that many standard libraries for Acorn PLASMA at the moment anyway, but this restriction on globals is the main reason you need to be careful which libraries you include. All the standard features of "cmdsys", "cmdsysac" and "acornos" are available, and the 32-bit arithmetic "int32" module should work fine. If there's anything else you're interested in please ask and/or give it a try and let me know if it doesn't work.
This restriction is why I describe R.HANOI as a hack. I didn't rewrite the code to get rid of global variables as it's supposed to be an example of normal PLASMA code using a few Acorn features and I wanted to keep it simple. It could be tweaked to work from ROM if you really wanted to, of course.
Constant strings are read-only and live in the ROM address space
If your code uses a constant string (e.g. mystr = "foo"), that constant string will be located within the ROM and never be copied into RAM automatically. This means:
- You shouldn't try to pass it to an OS call which might involve paging in another ROM (e.g. writing it to a file using OSGBPB), as that ROM won't be able to see the string in your language ROM's address space.
- You shouldn't try to modify it, assuming you want your language ROM to still work when it's not in sideways RAM. This is probably bad practice anyway, but maybe if you know what you're doing it has its place when running on the standard RAM-based VM.
The standard "fileio" module won't work in ROM
There are various reasons for this, and it's not quite production-ready even when used with the normal PLASMA VM anyway. If anyone is particularly interested in this let me know and I'll see what I can do, or at least explain why it isn't practical.
Anyone writing a language ROM in PLASMA should probably just use the standard Acorn OS filing system calls for I/O, though; there is some support for this in the acornos module, and you can use the standard PLASMA call() function to call any OS routine.
The program should never terminate
In a normal PLASMA environment, when control falls off the end of the the top-level module, control returns to the PLASMA VM prompt. A language ROM takes over the machine and there is nothing to hand back control to, so if your program stops running the machine will just hang. If you want to allow a way out, you can provide a mechanism for the user to enter * commands, and that will allow them to transfer control to the language of their choice. Or of course they can press CTRL-BREAK to re-enter the default language.
I could have made the code do "*BASIC" if control falls off the end of the top-level module, but that would have wasted a little bit of space for something that isn't all that useful, and would also assume that BASIC is installed. You can always do an explicit:
at the end of your program if you really want to have this behaviour.
All that said, I don't think these restrictions are too onerous. If you try it please let me know how you get on; if you run into problems please post your source code here (e.g. as a zip file or a link to github) and I'll try to help. At the risk of stating the obvious, it's probably best to make small incremental changes to your code and test at each step, then if something breaks it will be a bit more obvious where the problem is (whether that problem is in your code or something in the PLASMA world itself, such as link.py being buggy).
I will probably make an even-more-rambling technical post at some point about how this works internally and some alternative approaches I could have taken (and still might take), etc, but I think this is probably long enough as it is already so I will stop here for now. Questions welcome, as always!