BASIC assembler and Basalt
Steve Drain (222) 1620 posts |
I have a problem to solve and would like to solicit suggestions. In another topic I have been asking about using SWIs in user mode to interact with BASIC. This arises from a very long-standing task on my to-do list – to modularise Basalt. Before doing that, I need a way to call the internal routines in the Basalt module from external programs. If this were done, there would be the immediate possibility of using them in BASIC assembler, which might have some benefits in making coding simpler and more flexible. 1 There is no problem in providing the address of a branch table to the routines, from which BASIC variables can be created for Using SWIs to call the routines works well, but incurs the inevitable overheads, which might be acceptable for the convenience gained. What I want, though, is a means to branch to the routines like the SWI dispatch, but without the overheads (and the moon on a stick). ;-) I can conceive a method that stores the table address in a register, that somehow gets updated when Basalt is (re)loaded, but when writing BASIC assembler and using the branches to its internal routines, dedicting a register for that is not at all convenient. So, is there some method that I cannot see that has solved similar problems in the past? 1 Here is something of what I mean. Notice that USR can take parameters and return float and string values, and CALL can take values as parameters as well as avoiding the awkward decoding of the variable list. You do not have to be using Basalt itself to call the SWIs.
|
Rick Murray (539) 13840 posts |
Hmmm, could you construct a jump table in a Dynamic Area? Then, when a client asks for the addresses for jumping into Basalt, Basalt actually returns the DA table. So then the client can call into Basalt with Another thing to put in mind. If you are going to allow hard linking (a la CLib), what will happen upon Just my ideas. Feel free to ignore… ;-) |
Steve Drain (222) 1620 posts |
Thanks, Rick. Your ideas have been stimulating. Basalt already uses a fixed block in RMA to vector the primary BASIC entry point through, which makes reloading the module possible. I had not thought of a dynamic area for this, but I do not think it is much different, because it will not have the same address every time the computer is run, in much the same way. I can see how this could be extended to a branch table, which would deal with the problem for in-line bits of BASIC assembler, but not for saved blocks of code. I could also see a way I could write modules to keep a branch table up to date using a service call, but that is not for the fainthearted. Now I think I can see a way to combine two branch tables to achieve my end, but it does mean that all pieces of assembled code will have to include one of them, which I did not want to do. Using SWIs is seductive because it is so straightforward and simple to code and use. ;-) |
Rick Murray (539) 13840 posts |
And? We don’t write code like that any more. Not since the BBC Micro. ;-)
? Why not provide a SWI to return the location of the branch table? If the SWI fails, Basalt isn’t loaded (or is an older version that doesn’t do the clever stuff). If it succeeds, then there’s your base address. I had originally written this: But thinking about it, that’s a pain in the ass for a programmer to remember. Much simpler, perhaps, to specify that a trashable register (can we nick R8?) is used as a function pointer, and then branch to the code address, like: MOV R8, #7 ; Basalt_SomethingFunc LDR PC, basalt_code and the jump table logic will exist there (akin to a standard SWI jump table). This minimises how much the application writer would need to do to get it working. Okay, none of this is as sexy as CLib’s embedded jump table, but the fact that you can’t softload a softloaded CLib is a pretty good demonstration of the failings of that method. If you look after the jump table (DA or RMA, doesn’t really matter which) then there should be no problem with transparently “upgrading” Basalt while such programs are running.
For the lulz, it might be an idea to set up a simulated jump table (get a pointer into your module, jump into it, then jump again) and a SWI. Both simply MOV PC,LR. So maybe the real life impact will be minor enough that you’ll just use SWIs (‘cos it’s loads simpler)? |
Steve Drain (222) 1620 posts |
Yes, I have put my table in the fixed RMA block and have a SWI to return the address. This works fine with runtime code, but not with saved code.
That works, and is one of the possible methods to call the BASIC branch routines. However, it requires the use of a register and those are [Edit out: not] generally allocated. It is also inelegant.
Same problem, I’m afraid. What I use for my calls is to assemble a block like this:
Then fill in the actual addresses of the routines in the words after the It is that sort of table I am now using, but to be useful for saved code the code itself must have a second table and start with a SWI call that fills in the address from the first table that are valid at that runtime. If your mind is boggling at that, so was mine up until today. ;-)
Your suggestion of finding out just how much overhead there is is a good one and I may yet do that. For now, I must tidy up what I already have. Thanks. |
Steve Drain (222) 1620 posts |
Just to round this off, for Rick’s interest if no-one else, I have done some checks. Using SWIs is about 2 to 3 times slower than using the indirection of branches, which is about 2 to 3 times slower than writing code without branching into the Basalt module. If the routine is a long one, these overheads represents a smaller proportion of the whole. Using SWIs requires no extra code in a routine. Using branches requires a small piece of initialisation code and a 512 byte table. Writing code without branching requires replication of the routines used, which can be simplified with a library. For my own purpose the branching is favourite, but if my intention to ease the route into writing code to interact with BASIC is followed, then SWIs are favourite. So, I will probably keep both. ;-) Will anyone be interested in using it? |
David Feugey (2125) 2709 posts |
Yes, definitively… |
Steve Drain (222) 1620 posts |
Writing that library, I have yet another question, probably for Jeffrey. Using the BASIC assembler I want a FN/macro that can preserve a variable number of registers. So I have, for example:
R% is the numerical value for the reglist, but if it is 0 – no registers need to be preserved – that results in a STMFD instruction with 0 in the register field. This seems to work, but what actually happens? It is probably not safe, but I am intrigued. ;-) |
Jeffrey Lee (213) 6048 posts |
ARM’s official stance is that LDM/STM with no registers in the list is unpredictable. So it’s probably best to avoid that situation (e.g. have FNpush/FNpop functions that select between LDM/STM, LDR/STR, and nothing depending on the number of registers – IIRC the rule is that on ARMv4+ LDR/STR is quicker than LDM/STM if there’s only one register to transfer) |
Steve Drain (222) 1620 posts |
That is encouraging, but in what way would you use it? Here is my take on interacting with BASIC from assembler; it is not about writing assembler to produce fast code to do complex tasks. Anyone with expertise and the manual can work out how to do it, although there is a handful of pitfalls, and the information is not in a convenient form. There have been very many such experts in the past, but there are probably only a few now. Here are some limitations that might put newcomers off:
For many years I have been writing code supported by routines that overcome these limitations, and recently I have been thinking about making these available, to encourage others so that they could extend BASIC by machine code as well as PROC/FN. Hence the questions in this topic. In essence, I use CALL or USR to interpret the program that follows the keyword, using the official BASIC interpreter routines, returning to the program at the appropriate point, and in the case of USR returning a value. This requires manipulation of values on the stack and knowledge of how USR returns values. The routines I have in mind here hide away the tricky bits and provide a programmer with a simpler interface, at the expense of some overheads, as I have mentioned. Because these routines already exist in Basalt my strategy is to make them available from there, rather than describe how to write them and include them in assembled code. Documenting the former might be relatively easy, but explaining the latter would not. ;-) A dilemma is how much overhead to accept in exchange for simplicity, but in the absence of arguments against, SWIs seem best suited. I will do this anyway, for the personal satisfaction, but I ask again, David, in what way would you use it? |
David Feugey (2125) 2709 posts |
To extend the Basic too. For example, I work now for a customer on a mini ERP system. Done under Windows with BBC Basic. Here, I use Forth for speed critical things, as compression routines. So basically, for DSLs. |
Steve Drain (222) 1620 posts |
I do not think ARM BBC BASIC is likely to be much use for a DSL as you descibe. Forth, maybe, and there is a RISC OS version, but that is not going to be portable, is it? I envision the use of CALL/USR as I describe as being an alternative to PROC/FN when a programmer wants a particular routine to be faster, but has been put off using assembler before. My guess is that most uses of CALL/USR only pass parameters in the resident integers. Just reminiscing, I first came across Forth in a version written in Spectrum BASIC! I did go on to experiment with White Lightning, so I understand the speed aspect, and parts of the Spectrum rom were written in Forth. |
Chris Evans (457) 1614 posts |
I’m intrigued. ERP stands for: |
Rick Murray (539) 13840 posts |
I know that as Effective Radiated Power. And the other as Digital Subscriber Line. Hmm. However, from experience, the French really like acronyms. ;-) It is probably Enterprise Resource Planning. What used to be stock control and human resources (namely making sure the right stuff and the right people were in the right places at the right time) now has a trendy middle-management friendly name… |
Steve Drain (222) 1620 posts |
And I took DSL to be domain specific language, which is why I doubted BASIC was a good starting point. ;-) |
David Feugey (2125) 2709 posts |
My idea is to make a DSL in ASM. But I need ASM code to be able to interact easily with BBC Basic code.
Yep
Yes and no. ASM is almost a DSL for BBC Basic. |
Steve Drain (222) 1620 posts |
Using BASIC, I think the equivalent to a DSL would be a task-specific BASIC library. From there you could speed up specific routines using assembler, called either within a PROC/FN, or replaced by my proposed syntax – see the example I posted earlier. Those ‘interact easily’, I think. Taken to extremes, you would want something like Basalt, but with task-specific keywords, which might be equivalent to library modules in Python. This is not inconceivable, but BASIC is not simply bent to such purposes.
Really? BASIC is a general purpose language (GPL) and assembler is the ultimate GPL. |
Rick Murray (539) 13840 posts |
Argh! GPL! Argh! :-P |
David Feugey (2125) 2709 posts |
I mean, it’s implemented like a DSL. Language in a language and different uses. |