BASIC assembler help
Steve Fryatt (216) 2105 posts |
Now that the cogs have turned a bit and I’ve spotted my earlier mistake about absolute addressing, the way that you’re handling LDRs and STRs still seems very odd. You’ve got all of your storage locations inside your assembled code, which is unusual. A module would allocate its workspace on the fly, then use offsets into that memory – which explains the indexing through the workspace pointer in R12. You’re not doing that, although whether you should is a separate question. However, you do seem to be trying to mash the two approaches together, which explains why you’re having so many problems with EQUDs and labels and offsets and stuff. Since your code opens with
then any of your code (that falls within +/- 4K of this location) can access it just fine with
or whatever. The assembler will just turn this into
for you behind the scenes, so it’s as relocatable as anything that you do with R12. As such, there’s no need for you to separately define
or pass the address of the code in through R0 for storage in R12 and use as a base for data access. The assembler will do a much neater job, whatever the values of
and leave it to worry about the numbers for you. This is also true of the vector routines and other callbacks: you don’t need to set or read the R12 value, because the assembler still knows how to reference the variable locations to the LDRs and STRs through PC. ARM assembler has always been relocatable. If your code is less than 4K in size, or 8K if you stick the data block in the middle and not at the head, your code always knows the exact relationship between the instructions and will contain that baked in to a PC-relative LDR or STR.
Druck’s advice is very wise. This code has all the hallmarks of a module, and should be a module. The reason for using the R12 stuff is that in a module, the data isn’t stored in locations assembled into the code with EQUD and the like. That’s reserved for static constants which never change. The data goes into a workspace, which is allocated at run-time from a separate chunk of RMA. This is what I was trying to get towards above with the First, the storage offset values for LDR and STR are set up once, not in the assembler, but outside – often using a workspace allocation function. For example
The definition of
The storage is set up in the module’s init routine. On all module entry points, R12 points to the “module workspace word”, which is a single word in memory that’s initially set to zero by the OS. One word isn’t much, but the convention is that you allocate the actual workspace using OS_Module 6 and then store its address in the workspace word. After that, the workspace word is largely useless, so again convention is that R12 is immediately updated to point to the actual workspace.
Freeing the workspace is the last thing that we do in finalisation.
And that’s it. Whenever you enter a routine through one of the module entry points, you’ll have R12 pointing to the module workspace word. That’s pointing to the pointer to your workspace, so you get to the actual workspace pointer into R12 by
as above, and then you can do things like
with impunity. In callback routines, where you’ve supplied a value for R12, this will usually be the direct pointer to your workspace, as you’ll have done the Again, this is all nice and simple to understand. The reason things got complicated up-thread is because you’re trying to do some mashup between the two standard ways of working. This seems like a really bad idea. If you’re assembling the variables into the codespace, such as
then use absolute addressing like
If, on the other hand, the values are in workspace allocated on the fly, such as in a module, then index through R12. Don’t mix the two. If your code gets bigger than the +/- 4K limit for absolute addressing, use the workspace approach that supports up to +/- 4K of workspace, not distance distance between the LDR and the value that it’s loading. That said, there’s a reason why we use higher level languages these days. |
Rick Murray (539) 13840 posts |
I concur. This should be written from the ground up as a module that can be installed and de-installed as the user desires. You have Steve’s explanation above.
So very much this. If you have the DDE, then it’s not hard to write a module in C. The only reason I didn’t write my module in C in the beginning was two reasons: But, yes, high level languages are good because the compiler worries about the specifics of how the processor works, leaving you to worry about calling your vector releases in the right order. ;-) |
Martin Avison (27) 1494 posts |
Like Steve, my cogs have turned a little. Following on from him saying…
the following code snippet illustrates why writing to BASIC memory can so easily be very dangerous: which gives:
The first Memory Dump shows the whole BASIC variable space. The start of the list of variables starting in “m” is at &85B4, and points to &9028 which contains &9030 (=> next in the list), the rest of the variable name “m1%”, and &9030 (=> data containing 1111). The second in the list at &9030 contains &0 (end of list), “m2%”, and a pointer to the 2222 data. Line 7 writes a 4-byte string to mm1%, which sounds reasonable as 4 bytes were allocated. With just 2 variables used, there are 4 addresses within the variable memory, imagine how many there are with hundreds of variables. If ANY of these are corrupted, trouble will approach. In my experience, weird happenings in a BASIC program are usually caused by the program corrupting memory. Anything which writes directly to memory is suspect – including indirection operators !?$, and assembling code. The tricky bit is finding where … which is why I added list validation to Reporter. |
Rick Murray (539) 13840 posts |
Ah, but the majority of code you’ll see (<cough> including some of mine </cough>) writes the numbers as if counting from 1, not zero. ;-)
The gotcha here is not that you’re overwriting the memory by accident so much as BASIC always terminates strings with no way to disable this behaviour. You can in PRINT by using ‘;’ at the end, but in the above example, it’s always terminated. Something to watch out for in data blocks that expect to be null terminated, if you set the variable as |
Martin Avison (27) 1494 posts |
@Rick: Agreed – it was just a minimal program to illustrate that consecutive DIMs do not allocate consecutive memory blocks – there are many pitfalls which may be hidden. |
Steve Drain (222) 1620 posts |
Corrected that for you. ;-) |
Alan Adams (2486) 1149 posts |
Thanks guys. Lots of good stuff to think about there. It is probaby worth explaining why I made some of the decisions I did, even if those turn out not to be good reasons. 1 It’s in BASIC assembler because I don’t have the DDE and objasm, and it won’t work in BASIC alone. 2 The original design flashes an icon on the iconbar. That’s done in the BASIC program, triggered by the pollword. That bit has been working for quite a while. So it is designed as an application, with an assistant in assembler. 3 The code is in RMA because it needs to run under interrupts, and can’t do that in application space which gets paged out. Is there somewhere else i should put it? 4 The reason I use BASIC variables to point into the data space is so that the BASIC program can access what the assembler has done. That’s proved useful in debugging the assembler. 5 The reason for the fiddling about with &10000 was to try and put the code on an easily readable boundary, making checking address offsets easier when reading the listing. The original version assembled directly into an RMA block, and this “simplification” seems to have produced some of the problems. That’s easily fixed. 6 The reason for not making it a module is that I don’t feel comfortable with that, particularly as I can’t see how to debug one. I also don’t know how much Wimp stuff can be done within a module. It feels to me, possible erroneously, that Wimp stuff is for applications. The original design concept changed once I realised that the flasher stopped working during a lot of things that I wanted an indication of, such as large *copy operations. That lead to two additional features being planned – one to use the pointer colours, and the other on the ARMX6 to add hardware to flash the existing unconnected front-panel LED. Both of these need to be done in a callback routine it turns out, and it was adding that, that started to show up some of these problems. So it’s evolved away from a fairly simple design, as these things tend to do. I’m going to have to think about some redesigning, although I’m not yet convinced that it will become a module. By using Basic$Crunch to hide some of the issues I’ve got code that runs, doesn’t crash the machine, and does change some of the pointer colours, although not correctly. So it’s close, and I don’t want to go too far backwards in order to go forwards. A start will be to ensure I assemble into a single DIMmed area with no embedded variables – I had forgotten about that one. Separating out the data space will need a bit more work. As it’s a nice day, I think I’m going out on the bike first to clear my head a bit. |
David J. Ruck (33) 1635 posts |
3 the RMA is the correct place, but it should be wrapped as module, for the advantages given above |
Alan Adams (2486) 1149 posts |
For some reason I couldn’t get my head around the idea of a Wimp_Poll in a module. Debugging at the moment relies on the BASIC part of the system – the assembler stores a couple of values in some spare locations, whose address is known to BASIC. The BASIC part uses these to do things like *reportmem using the addresses it’s just found. That’s one reason for having BASIC variables containing offsets into the code block, or more accurately into the data that’s currently part of the code block. Every time a significant event occurs the pollword is changed. Part of the BASIC that responds to that dumps some data out using the mechanism above. It means I see a sequence of things happening as the lights go off and on. The bit with the iconbar lights does work. One reason for fiddling around with the address of the block was for using *memoryI with a base address I could remember, and easily add offsets to. It’s just I got the creation of it wrong. My thought at the moment is to finish debugging the callback code this way, as it’s nearly there. Then think about changing the memory layout, and then see if I can understand how a module works. Steve’s version should help me with that bit. The good thing for this fun(?) activity, is that I discovered that an event I had coming up, that would have put some pressure on my time, has been cancelled for the usual reasons. |
Rick Murray (539) 13840 posts |
Indeed. The danger of assumptions. :-)
Thanks. I should have said at least 5 bytes. Didn’t want to confuse the matter with word alignment.
Fair enough. 3 The code is in RMA because it needs to run under interrupts, So, should be a module. ;-) If you want to keep your BASIC front-end, then simply get your shiny new module to set a word somewhere to 0 (no LEDs), and then 1-3 depending on which bits you want to mean “read” or “write”. Then, provide a SWI that returns the address of this word. Your front-end can read this address, and then pass it to Wimp_Poll(Idle) as a PollWord. When the Wimp notices this word is non-zero, you’ll get polled with event PollWord_NonZero. It provides the value of the word for you, so you can act upon it directly.
Carefully. ;-) It’s not RISC OS’ strong suit, especially when you’re in a privileged mode and any little whoopsie with stacking/unstacking will likely stiff the machine.
There are four ways of accessing code in modules. Finally, there is the Run entry. This is akin to the BBC MOS idea of “entering the module as a language”. You are entered in User mode. You do not return until you’re done. While originally intended to allow you to write full screen applications as a module (as was actually recommended in the Arthur 1.2 PRM!), there’s no reason why you cannot call Wimp_Initialise and start yourself up as a task. Pretty much the only difference between that and a normal task is that you won’t have any slot that gets paged in and out (except under C, but that’s an implementation issue) as your workspace will be in the RMA (except C…). If you open up Task Manager and look at the list of current tasks, below that it’ll say “Module tasks” and these are ones that are built into modules. Like “Free” and “Filer”.
Do you not use FilerAction to do it multitasking?
Because by now you needed to save state somewhere between the event happening and the callback dealing with it.
I’ve been out in the garden. Started light, rotovating ground again for my new potato patch. Then took a breather with planting some flowers. And finally sated my inner moppet by levelling the ground where the pine tree used to be (it fell in a storm about a decade ago). This involved a shovel and a pickaxe. Which was… actually quite gratifying. I’ve just eaten a pack of long-life croissants which were about as awful as you can imagine them to be, but I’m hungry (haven’t eaten yet today, was… busy…) and wanted to sit out in the sun (as I’m doing to write this) and not faff around in the kitchen. I’ll go in soon, it’s starting to get chilly. |
Alan Adams (2486) 1149 posts |
Thanks Rick, helpful as ever.
I do, but some things on my various computers don’t. For example my big BASIC system backs up its database by using *copy to save the files to a different computer. It does them one per wimp_poll so the server can still respond to its other clients, but it would still be nice to see the activity flagged.
Which brings me to the current debugging challenge. When an event needs processing an identical flag value is stored in two places. One is the pollword, and the BASIC part acts on this correctly. The other is used by the callback, and for some reason seems to have a different value by the time the callback runs. It’s not as simple as the sequencing of these I don’t think, because it’s not a lag of one event between them either way. The pollword typically goes 0, 2, 0 for a read, while the callback seems to see 0, 3, 3. Added to this I don’t think it’s changing more than one of the three colours associated with the pointer, or it’s changing them to the wrong values. I suspect I can improve that code using LDM/STM operations, a bit like the Z80’s LDIR instruction. It’s essentially copying 12 bytes from one memory area to another.
until I got on the bike, when the clouds came over and the air started to get very damp – not quite Scotch mist, but getting there. |
Steve Pampling (1551) 8170 posts |
and tenderising for the hands, unless you already have a degree of callousing. |
Martin Avison (27) 1494 posts |
Debugging asembler, even if in a module, is perfectly possible with Reporter. You should see the vast amount of debugging info I can get when debugging Reporter! (I do use two Reporters – one to debug and one to report). Otrher methods are available. And as others have said, a module can certainly be be a Wimp task – indeed, currently I have more module tasks running than application ones! |
Alan Adams (2486) 1149 posts |
I left that post unchallenged until today. However yesterday I had the same thing again. Within the assembler section I had a function reference. That function referenced another function, which assembled code inline (i.e. the code was not set up as a subroutine, but more like a macro). The code in that second function was causing a crash. I added a ; before the FNcrashmachine reference. The next time the code was assembled, the machine crashed. I then deleted the FNcrashmachine function reference. The assembled code didn’t crash. On previous occasions I’ve tried using REM as well with the same result. I think Steve’s put his finger on the reason – you run with Basic$Crunch set, and I do not. That will remove the commented code before the assembler sees it. |
Steve Pampling (1551) 8170 posts |
Curious. It’s more efficient to run with it set and costs nothing at all in the readability of the original code. There was some debate a while back with the view that the system default should be to set it. |
Alan Adams (2486) 1149 posts |
I’ve been back to Steve Fryatt’s detailed explanation several times. This time round with a really strong cup of coffee.
I had to spend about ten minutes re-reading this bit before I twigged. I think this and the following section is saying that R12 in a module entry is the address of the address of the workspace, not the plain address of the workspace. It’s this sort of thing that’s made me shy of getting into module code. What made me realise was when he said the address of the workspace should be stored there, and I wondered why it was being stored at address zero. |
Martin Avison (27) 1494 posts |
Yes and no … R12 is the address of a Private Word for your module. If you only want to use one word for ‘data’ then that is it. Normally though you need to hold more data, so memory is obtained elsewhere, and you use your Private Word to hold that address (which is the case you gave). |
Phil Pemberton (7989) 71 posts |
Apologies for resurrecting an old thread — but I hit a snag with the code in Steve’s post at https://www.riscosopen.org/forum/forums/11/topics/16298?page=3#posts-119636
There’s an error here, if you allocate 4 bytes then 64, the second allocation will get address &FFFFFFFC, when it should get address 4. The correct code is:
But thanks for the idea, Steve! It’s tidied up my BASIC Assembler code a fair bit. |