Jumping into ARM assembly


Ah, that's worth knowing, thanks.


Other than that, I'll just note that almost all of the instructions of the form 'mov rx, ry' can be eliminated, except for 'mov r8, r5' where you preserve the state of r5.  Also, if you split the dual uses of r10 as a multiplier into one that contains the reciprocal, and one that contains #10, you can move the setting of those outside of the loop.
 
Also, I note in your newline macro, you declare the length of a newline character is 2 bytes; that's not actually the case on linux and the Pandora - it's a single 0x0a.  I guess it works though because you've defined the contents of the newline address as a zero terminated string, so you've actually stored 0x0a and 0x00 there.  The zero byte isn't considered a printable character, so I guess it just silently ignores it.


Your comment to your syswrite macro is also not quite correct; you're not printing out the contents of r1, you're printing out what r1 points at.  But, eh, it's only a comment.


I've not got round to looking at the logic of the code yet, but initial impressions are favourable, and if you reckon it does what it's meant to then that's promising.

dmarschal mentioned that "\n" is coded as one "0xA" character in the binary file.


I don't get how to use this value in the code.


I'll correct the comment, thanks to "point" it out ;^) .
 
Last edited by a moderator:
Other than that, I'll just note that almost all of the instructions of the form 'mov rx, ry' can be eliminated, except for 'mov r8, r5' where you preserve the state of r5.  Also, if you split the dual uses of r10 as a multiplier into one that contains the reciprocal, and one that contains #10, you can move the setting of those outside of the loop.



About using more registers, can it have drawbacks ?


Does pushing more registers doing macro or function call has a weight ?
 
About using more registers, can it have drawbacks ?


Does pushing more registers doing macro or function call has a weight ?



ldm/stm with more registers are slower.


In general, having good practice with conserving registers can make a big difference in how much you need to access memory in critical loops and how well you can schedule the code. Which can impact performance a lot, especially on a processor like Cortex-A8.
 
I'm not quite sure what you mean there, Exo.  Do you mean it's always worth having free registers so you can fill them with stuff rather than having to spit it out into temporary memory?  In this case, since it's not in a routine that needs to store and load its used registers, and since he doesn't need to store any extra values, preloading the multiplication constants seems like a no-brainer to me.

dmarschal mentioned that "\n" is coded as one "0xA" character in the binary file.


I don't get how to use this value in the code.



Just say the length of newline is 1 byte, rather than 2 as you currently do.  That should still work, then you can try replacing the .asciz directive with a .ascii one, which will avoid writing out the ending 0x00.  You don't need to engineer an 0x0A value - '\n' already evaluates to that.


I don't think that'll cause any problems with alignment, as it appears that the .word directive aligns automatically, but I'd test it out at every stage in case I've misinterpreted what the assembler is doing there.
 
I mean it's good to be good at conserving registers (as I phrased it, "having good practice") for times when increased register pressure can make a performance difference. Not that there's a benefit to using less registers in this case, although I don't know what a good Cortex-A8 optimized version of this code would look like. Not that it makes any practical sense to try to optimize something like this that spends most of its time in that syscall.
 
Just say the length of newline is 1 byte, rather than 2 as you currently do.  That should still work, then you can try replacing the .asciz directive with a .ascii one, which will avoid writing out the ending 0x00.  You don't need to engineer an 0x0A value - '\n' already evaluates to that.


I don't think that'll cause any problems with alignment, as it appears that the .word directive aligns automatically, but I'd test it out at every stage in case I've misinterpreted what the assembler is doing there.



Working. Corrected above.
 
Unless your compiler implements function wrappers, assembler doesn't really have a concept of functions.  All you can do is branch to a label (BL - branch with link), and hope that at some point later that jumped to code will jump back to the address stored in the link register.  On top of that there are various headers that different ABIs require, and common code either side of the jump in and out to store registers to the stack, but those are incidental to the core concept.


Macros on the other hand are more like inline functions.  They're unrolled unto your code where used, so there's no jump back to LR on the end of them - they just finish and roll onto the next line in your code.  They may still have stack handling code on the front and back if they use a lot of registers and don't want the user to need to worry about keeping them free.


All of the above is just my understanding though.  I don't really understand ABIs, and the last assembler I used didn't have macros.
 
Last edited by a moderator:
I've been told that things like recursion can't be done with a macro as it produces infinite length code.


I suppose macros can execute functions ?


What's this .global thing when including functions ?
 
I've been told that things like recursion can't be done with a macro as it produces infinite length code.


I suppose macros can execute functions ?


What's this .global thing when including functions ?

Macro are pre-processor things. They generate code (they can generate function), but you cannot use macro to handle real time thing. If you create a macro to print a character, always the same caracter will be printed (at the same code place). A function is executed, so the same function will print any character (I hope my explanation make sense).
 
Doesn't macros can be passed argument at ?

Yes, but they are interpreted durring compilation. You cannot pas the "key you will press" in the macro for example.


Macro are things made to avoid typing things (if I simplify things). Again, they are pre-processed and the argument you pass are "constant", not real time variable (like the actual value of the key you pressed), but can be a variable reference (or register) that will contain the value of the keypressed.
 
I'm not sure I get the difference between a macro and a function...

As mentioned, macros generate code inline. That means that if a macro is used 10 times, then the code inside the macro will appear in the generated binary 10 times as well. And if that macro uses a second macro 3 times then that second macro is generated 30 times. It quickly adds up. This has the side-effect of making the program binary bigger and use more of the CPUs cache to store the extra instructions (possible performance penalty).

Functions are just a branch. No matter how many times you "call" a function, it's code will get generated only once (and branch instructions in multiple places jump into that code). So the program binary is smaller and the instructions of the functions only need to be stored once in CPU cache.

Not sure, but I think that functions might be a bit friendlier with the debugger. Since the code of a function appears only once in the code, the debugger can show when the execution is inside a function. Don't know if it can do the same if a macro is used instead.
 
Last edited by a moderator:
I strongly doubt a debugger can show you what macro you're in; they're generated inline, not with any branch instruction or any headers, so it appears as if you'd just repeated yourself in the code.


That's why you can't have a recursive macro.  Because it's expanded at assemble time, the assembler must expand both sides of any condition.  Since you must have a condition that decides when you've descended enough into the recursion, the assembler must both convert the side that stops recursion, and the side that carries on - at all recursion depths, and thus for ever.
 
I often see (e.g. from dmarschal's tetet code):


tblrot_eq: @ 45 deg
mov r9,r5
1:
bl far_du_const
add r6,r7
sub r8,r7
add r11,r10
subs r5,#0x10000
add r1,#1600
bpl 1b

mov r4,#0x58000
2:
bl far_dv_const
add r8,r7
sub r6,r7
add r4,r12
subs r9,#0x10000
add r1,#1600
bpl 2b
pop {r0-r12,pc}



What are 1: and 2: because I see other in the same code so it can't be the same label ?


Or is it like a private thing ?
 
I'm not sure what that 1b and 2b notation means, but I think for the purposes of understanding this, you can ignore the 'b' - these are conditional branches to labels '1:' and '2:', based on whether the SUBS command resulted in a positive value.  I guess they can be reused since whenever they're encountered again, they're reset - so anything after a defintion of the label takes that address.


You'll note also that pop command at the end you've included.  That's probably part of the function handling code - the other part is probably a 'push {r0-r12,lr}' which, combined with a label would indicate the start of a function. The 'bl' calls there are also function calls, so you can see both how functions are written and how they are used.
 
Back
Top