mailto: blog -at- heyrick -dot- eu

Navi: Previous entry Display calendar Next entry
Switch to desktop version

FYI! Last read at 18:39 on 2024/11/21.

Samhain (Halloween)

It's that time of the year. And the final Sunday in October is also the final day of October, so we all get to experience yet another failure of the EU to effect real change to the lives of ordinary people by doing away with the mostly-despised messing around with the clocks twice a year.

I woke at got up at half seven to feed the cats, looked out the window and was "is it a full moon or something?". But no, it's just everything is now an hour later. So I'll probably be going to work again as the sun rises rather than in darkness, but on the other hand I'll be coming home as the sun goes down. A little bit of time to sit in the evening sun? Nope, probably too low and too chilly now, by the time I get home.

As I was feeding the cats, the morning was arriving to a heavy sky.

Not a good weather sky
Not a good weather sky.
It then rained biblically for the next four or five hours. Which is when I wrote most of this document.

For today's lunch, I decided on doing something a little special, since it's an important day. I started with a Greene's Cheesecake. This was easy to make, mix melted butter and biscuit crumb for the base, and then mix milk and powder for the topping. Assemble, stick in the fridge.

Making the cheesecake
Making the cheesecake.

While that was in the fridge setting, or whatever it does, I peeled and chipified some potatoes, and put them in the oven along with a Just Chicken Fray Bentos pie.

Chips and pie in the oven
Chips and pie in the oven.

While they were cooking, I carved the pumpkin. A generic smilie face this time. For me, Samhain is a happy festival. All ghosts welcome, ring the bell and I'll put the kettle on...

A carved pumpkin
A carved pumpkin.

I was wearing the T-shirt that I made on Wednesday. Thankfully pumpkins are pretty easy to draw, so I did this one in about fifteen minutes in Draw. It took almost as long to print it using photo settings, but since I subscribe to HP's Instant Ink I don't need to worry about ink consumption.

A T-shirt I made myself featuring a carved pumpkin with a mask
A T-shirt I made myself featuring a carved pumpkin with a mask.
I wore this to work on Thursday and Friday. Those who noticed thought it was funny. ☺

Lunch. Niiiiice.

Lunch
Lunch.
You can also see some rubbish (football?) on BBC One HD on the left. I realigned the dish because it's Doctor Who tonight, assuming I remember when it is actually on. No biggie if I don't.
On the right, I'm writing this in Zap.
You can also see the SNES style controller, used for testing/playing Mamie (see below), plus the satellite receiver's control gizmo. And some packs of tea biscuits because...tea.
Oh, and that flat ribbon on the right. That's where the other HDMI cable went. Right in bloody front of me. 🤦 🤦 🤦

I also put up some appropriate decorations.

Decorations
Decorations.
I didn't get to dress up as a witch, all that stuff is aimed at the other gender. I guess male witches just aren't a thing. I guess I could put on some old dirty clothes, hold my arms out, and stumble around croaking breeexiiiit! or something?

Tonight? Already lined up. Doctor Who if I remember. Then Motherland: Fort Salem on Prime video. A few episodes. Then, depending on how I feel, maybe an episode or two of Inside Job on Netflix.

And no pumpkin pie, soup, or ice cream. I don't actually like the taste of pumpkin. I just like putting candles in them. And, from time to time, blowing them up. ☺

 

I wrote a game!

I started writing a game during my summer holiday, and the version I had towards the end of it was "okay if a bit basic". My faithful beta testers (David, Vince, and Tony) provided numerous suggestions and my summer holiday project got a lot longer, bigger, and most importantly better.

Screenshot, scaled
Screenshot, scaled down from HD

It is a platform game. Part homage to the sorts of games I grew up with, and part wanting to create my own. You play the part of Lucy, a teenage girl who unwisely volunteered herself to explore Mamie Fletcher's House, a rumoured haunted house.
Armed with only a camera, and the local librarian's suggestion that taking photos of ghosts can get rid of them, you venture in. Because, come on, there's no such thing as a ghost. Right? Right?

With twenty two levels (and some devious challenges designed by Vince), different styles of level design (the one pictured is, obviously, brick), two types of ghost, tea to drink, keys to find, and spiders to avoid...

Mamie Fletcher's House is available for RISC OS using the Store app, at the reasonable price of £4.99.

You can see some more screenshots and infomation at https://heyrick.eu/mamie/, and there's a two level playable demo available from there or from Store.

 

On the technical side, the program is entirely original code and it is written in C. I'll write some articles about the game design in a few weeks (perhaps in December).
It is aimed at the Pi2 as a baseline (that is to say, ~900MHz ARMv7 processor). It runs a bit slow on the original Pi. I understand it will work under emulation, if the host machine is a fast one. At any rate, the demo is a slightly feature-reduced version of the real thing, so if the demo works well, the full version will do likewise.

 

Debugging a tiny Wimp program

As part of his tutorials, DavidS has released a Tiny Wimp program, available from https://asmfun.riscos.fr/down.html.

Annoyingly, and perhaps intentionally trying to be obtuse, the software is provided in Archive format (rather than Zip). This means that not only can it only be unpacked on RISC OS (and not looked at on a phone or PC), but you'll also get some gibberish file type handed to you (NetSurf defaults to XML) because Zip is a known standard, Arc is not.

Once it is open, you have !HelloWimp, which as promised is a 300 byte executable that loads and displays a template with a window that says "Hello Wimp!". There is also a licence file, and finally the important part, the source code. It is written in BASIC using the built-in assembler.

The first thing to note is that the !Run file sets up a default slot of 32K. One would say for this program, 4K would suffice, but asmusingly it crashes if we give it a slot of 4K even though it doesn't need anything like that much. We'll see why in just a moment.

Now let's turn our attention to the source code. If you wonder why I'm being really nit-picky here, this is being offered as a sort of tutorial on how to write a simply and small program in assembler. Therefore, it would help if it didn't do odd things and/or teach wrongness and bad habits.

 

DIM code% 1024

FOR pass%=4  TO 6 STEP 2
P% = &8000
O% = code%
[OPT pass%

Applications run at &8000, which is why he is using offset assembly here. That said, as long as references are kept relative (which is normal for branches and ADR both of which are PC-relative), it doesn't need to be built with offset assembly because it won't matter where it is actually run.

  ;***** BEGIN AIF HEADER ****************************************
  ;ANDEQ  R0,R0,R0      ;NOP, not compressed.
  ;ANDEQ  R0,R0,R0      ;Not self relocating.
  ;ANDEQ  R0,R0,R0      ;No zero init.
  ;BL     Start%        ;Our Entry point.
  ;SWI    "OS_Exit"     ;Safety exit in case of return.
  ;EQUD   RoSize%       ;Code section size (mult of 4)
  ;EQUD   0             ;No data outside of the code area.
  ;EQUD   0             ;No debug code.
  ;EQUD   0             ;No zero init.
  ;EQUD   0             ;No debuging.
  ;EQUD   &8000         ;Image base address.
  ;EQUD   0             ;No relocation workspace.
  ;EQUD   &20           ;We are 32-bit safe.
  ;EQUD   0             ;No seperate data section.
  ;EQUD   0             ;Reserved, should be 0.
  ;EQUD   0             ;Reserved, should be 0.
  ;ANDEQ  R0,R0,R0      ;No debug init.
  ;***** END AIF HEADER ******************************************

Ah, some cheating on the file size. A missing AIF header. This is not strictly necessary for RISC OS 5, however I understand RISC OS 4 may require it. Personally, I think it ought to be mandatory in order that non-32 bit software can be easily detected and rejected.

Note, also, the peculiar use of ANDEQ R0, R0, R0 for a zero word. It's either a zero or a branch, so EQUD 0 would have sufficed here. It is also a broken AIF header, as it is supposed to be 128 bytes long, although as is usual for RISC OS, this isn't enforced.
Notice also the confusion between "Code" and "Data" areas, which in the AIF spec are given as Read-Only and Read/Write. This would be important if RISC OS paid attention to this, because he is marking the program as Read-Only, with no Read/Write, and then dumping registers to a stack set up... in... uh... the 'nothing' area.

 

.Start%
    MOV     R0,R13                ;Setup stack.
    ADR     R13,codeEnd%
    ADD     R13,R13,#8192
    STMFD   R13!,{R0,R14}

    BL      init%                 ;Call init procedure.
    BL      pollLp%               ;Drop into programs Poll Loop.
    BL      closeOut%             ;Clean up, prepare to quit.

    LDMFD   R13!,{R0,R14}         ;Restore calling stack.
    MOV     R13,R0
    MOV     R0,#0                 ;No error.
    SWI     "OS_Exit"             ;Exit, quit out.

What a lot of twaddle, and quite broken.

It appears as if he is wanting to save and restore the registers around the program? I'm not exactly sure. Should that STMFD instruction say R0-R14 (R0 to R14 and all in between) instead of R0,R14 (only R0 and R14)?

It's pointless. One should make no assumptions about the status of any of the registers on entry to an Absolute (&FF8). For what it is worth, this is what RISC OS did for a tiny dump program I just threw together:

R0&00000001
R1&00008000
R2&00008000
R3&000000A0
R4&00008000
R5&3010509C
R6&00000000
R7&000000A0
R8&00000001
R9&30107DBC
R10&FA208000
R11&FA207FA0
R12&80000000
R13&80000000
R14&FC050C94

The state of most of the registers are what FileSwitch may have used in order to start up the application. The only registers that are specifically defined are R12 and R13 which are intentionally set to a dud value (R12 because that was the Arthur stack), and R14 which is a safety return address for programs that don't call OS_Exit like they should (it points to an OS_Exit instruction).

So no registers need preserved, and nothing should be assumed. Doing so is weird, and a waste of time. One can save 16 bytes by cutting out that rubbish.

And now we get to the truth of why 4K crashes the application. David works out the code end, and then adds 8K.
The fact that a 4K slot causes a crash is why you shouldn't ever do it like this. Instead, you should call OS_GetEnv, which will return the memory limit in R1. You can either set R13 to that (because it's a fully descending stack, so will work downwards from the top), or do some maths to ensure that you have enough space.
Never just blindly add random large amounts and hope it's good enough. Of course, if you use a real language, it will take care of the allocations for you.

 

;*********************************
;PROCEDURE = closeOut            *
;PARAMETERS = NONE               *
;PURPOSE                         *
;  Clean up  in preperation  for *
;  quitting.                     *
;*********************************
.closeOut%
    STMFD  R13!,{R0-R1,R14}

    LDR    R0,tsk%
    LDR    R1,tmag%
    SWI    "Wimp_CloseDown"

    LDMFD  R13!,{R0-R1,R14}

If you don't need registers, don't waste time preserving them. It is also recommended that you code with the expectation of APCS behaviour (that is to say, a function can freely use R0-R3 and may return with those registers different than they were on entry).
In this case, there's no need to preserve R0 and R1, and since we are running in user mode with no sub-functions, there's no point in stacking R14 either, a simple MOV PC, R14 at the end will return.

Additionally, the task handle and magic word are unnecessary. They are for closing a task that is not currently paged in (such as a module task). For a regular application, you only need call the SWI. It will be assumed that it's the current task that is closing.

Did you spot the bug, by the way? We're loading R14 off the stack into... R14. Which means that we'll pass through a BLMI to some random address and then pass right into the initialisation routine, before exiting through the OS_Exit call.
It is bollocks like this that justifies my belief that, in 2021, nobody should be attempting to write anything in assembler. We invented compilers so we no longer have to worry about what is placed on the stack where, and where it gets pulled into.
Any argument about "oh, but compilers generate less efficient code" basically evaporates when you understand how easy it is to make fundamental errors when writing in assembler. It is, by its nature, completely unforgiving. More than once in patching stuff to work on my Pi2, I have come across instructions like LDR R10, [R10]! (which means load R10 from the address pointed to by R10, and then write back the base address to R10 - effectively a bogus instruction). A simple typo, just like the R14 above, but one that can cause weird and unwanted behaviour. Possibly the only reason this was never noticed is because the side effects go by so quickly they just aren't seen.
This isn't to say you can't write buggy code in high level languages, you can, but there's a compiler to catch a lot of mistakes and take care of the low level grunt work. Here, with assembler, the grunt work is on you. Always. And with zero tolerance for error.

Seriously, do not write stuff in assembler these days, just don't.

 

;*********************************
;PROCEDURE = init%               *
;PARAMETERS = NONE               *
;PURPOSE                         *
;  Set up  program, in this case *
;  we load  our window  template *
;  create the window and open it.*
;*********************************
.tmag%
   EQUD     &4B534154             ;Magic number, in ASCII ="TASK".
.init%
    STMFD   R13!,{R0-R6,R14}

    MOV     R0,#200               ;
    LDR     R1,tmag%
    ADR     R2,TskName%
    SWI     "Wimp_Initialise"
    STR     R1,tsk%

    ADR     R1,template%          ;Get win def from template file.
    SWI     "Wimp_OpenTemplate"

    ADR     R1,wb%
    ADR     R2,ind%
    ADD     R3,R2,#&100
    MVN     R4,#0
    ADR     R5,tmp%
    MOV     R6,#0
    SWI     "Wimp_LoadTemplate"
    SWI     "Wimp_CreateWindow"   ;Register our window.

    STR     R0,winh%
    STR     R0,wb%


    SWI     "Wimp_CloseTemplate"

    ADR     R0,wb%
    SWI     "Wimp_GetWindowInfo"
    SWI     "Wimp_OpenWindow"     ;Open the window.

    LDMFD    R13!,{R0-R6,R15}

Initialising as a RISC OS 2.00 task (no message filtering). That's just lame.

Again, no registers are required to be preserved around the function, so APCS rules (save R4-R6), if anything, would do.
But the main problem that I can see here is the utter lack of error checking. Checking for errors isn't something that you retrofit at some nebulous "later", it has to be built into your mindset and come as a normal part of the process of writing software. I see all of these non-X form SWIs and no BVS and, frankly, this is just really bad code.
If you rename/remove the Templates file, you will indeed get a nice "File '<HelloWimp$Dir>.Template' not found" error, but it won't be your application doing that. Your application will be gone, control having been passed to the environment error handler.

Wimp_LoadTemplate does not return an error if the window was not found. It sets R6 to zero. What will give an error is the fall-through to Wimp_CreateWindow, which upon receiving bogus gibberish from the LoadTemplate SWI will more than likely report "There is not enough memory to create this window or menu".

Defensive coding, folks. Don't make assumptions.

 

;*********************************
;PROCEDRURE = pollLp%            *
;PARAMETERS = NONE               *
;PURPOSE                         *
;  Main loop of program, getting *
;  events  by calling  wimp poll.*
;  This loop does not end  until *
;  the events say  it is time to *
;  quit the program.             *
;*********************************
.pollLp%
    STMFD    R13!,{R0-R2,R14}     ;
.p0lp%
    ADR      R1,wb%
    MOV      R0,#&1800             ;Poll flags in R0.
    ORR      R0,R0,#&31
    SWI      "Wimp_Poll"          ;

    CMP      R0,#2                ;
    MOVEQ    R0,#0
    SWIEQ    "Wimp_OpenWindow"    ;
    BEQ      p0lp%

    CMP      R0,#3                ;
    MOVEQ    R0,#0
    BNE      p0c0
    SWI      "Wimp_CloseWindow"   ;
    B        p0dn%
.p0c0
    CMP      R0,#17               ;
    CMPNE    R0,#18
    LDREQ    R2,[R1,#16]
    BNE      p0lp%
    CMPEQ    R2,#0
    BEQ      p0dn%

    B        p0lp%                ;

.p0dn%
    LDMFD    R13!,{R0-R2,R15}     ;

A simple enough polling loop, that recycles the window block for the poll event data (this is okay, the Wimp copies the window data for its own use).
The mask flags are set up by loading &1800 (don't return Gain/Lose caret) and then merging in &31 (don't return pointer entering/leaving window, or null events).
I can't help but feel that there would be a lot more clarity by pointing to a word in memory and loading it. The word being a binary bitmask (like EQUD %1100000110001) so it is simpler to see what is and isn't being set. It would add one additional word to the code size.

Wimp_OpenWindow doesn't use R0 on entry, so why set it to zero? There's also the assumption here that flags will be preserved across a SWI call. This was the case in the 26 bit world, but it's not a valid assumption in the 32 bit realm. It would be safer to follow the first comparison (is it OpenWindow?) with a BNE to the next test.

You can see the "is it CloseWindow" block does this, if we again ignore the errant setting of R0 to zero.

The final bit looks for either sort of message (normal and recorded) for a message code of zero. That means quit the application. It works, but I'd have been inclined to put the BNE following the second comparison, and then have the load and final comparison be unconditional. There's something that irks me about having tests out of order and/or far too many chained conditionals. This... I can't quite put my finger on it, but it feels wrong.

 

ALIGN
.tmp%
    EQUS "hello"+STRING$(7,CHR$(0))
ALIGN
.template%
    EQUS "<HelloWimp$Dir>.Template"+CHR$(0)
ALIGN
.TskName%
    EQUS "Hello Wimp"+CHR$(0)
ALIGN
.codeEnd%

The embedded data. The first align is unnecessary, it's following code so it won't not be aligned.

 

]
varEnd%=codeEnd%+32
tsk%=codeEnd%
winh%=varEnd%-4
wb%=varEnd%: ind%=wb%+256:
RoSize%=O%-code%
NEXT

Now for some additional awfulness. The variables end is 32 bytes beyond the end of the code. The task handle follows the code. The window handle is four bytes back from the end of the variables. And the twenty four bytes in between? The window block follows the end of the vairables, and as it does double duty as the poll block, it is 256 bytes in size. The indirected data area follows that.
Finally, and with some measure of stupidity, the entire space from the start of the program and including the variables is sized and given as the size of the read only block. It's a good thing there's no AIF header, I guess.

It might have been better to follow .codeEnd with some EQUD statements for things like the task and window handles (only needs eight bytes, not thirty two!), and save to disc up until codeEnd, as the addresses need to be known but the actual space doesn't need to be saved and/or reloaded. It would certainly be clearer than that mess.

 

What I can say is "it appears to work", but it contains...

All in all, if I was teaching my daughter assembler (I wouldn't, there's no need these days) then I'd expect her to write code like this as a ten year old. I'd consider myself a failure of a father and a geek if she made it to be a teenager and wrote such rubbish.
And, certainly, this shouldn't be used as an example of anything other than why nobody should write code in assembler these days...

 

 

Your comments:

Gavin Wraith, 31st October 2021, 17:40
Adding my peppercorn: 'preperation' should be spelled 'preparation'.
Stuart Swales, 31st October 2021, 18:11
From current AIF document: NOP is encoded as MOV r0, r0. 
Stuart Swales, 31st October 2021, 18:19
MVN R4,#0 !!! 
 
FFS Just write MOV R4,#-1 and have done with it. No brownie points for being a smartarse.
Rick, 31st October 2021, 20:19
Sorry Stuart, 
 
Bad immediate constant at line XXX. 
 
😂
Rick, 31st October 2021, 20:21
Fixed the format fail, too. 
I wonder why it didn't show up in Netsurf? Hmm...
druck, 31st October 2021, 21:01
You can use integer variables for assembler labels (% postfix), most people don't bother, but it would be good to be consistent, pick one and stick to it.
Stuart Swales, 1st November 2021, 11:28
RE MVN/MOV Ah, that's probably BASIC being less helpful than AAsm/ObjAsm.
Rick, 1st November 2021, 13:56
druck, I would imagine the missing '%' after "p0c0" is another typo, as the rest appear to be marked as integers. Or maybe it was added later and he forgot that all the rest have a '%' suffix? 
But, yeah, consistency of urine... 

Add a comment (v0.11) [help?]
Your name:

 
Your email (optional):

 
Validation:
Please type 76093 backwards.

 
Your comment:

 

Navi: Previous entry Display calendar Next entry
Switch to desktop version

Search:

See the rest of HeyRick :-)