That's why C is usually preferred.
As I wrote earlier, I've been playing with m68k assembly lately. My first program looked somewhat like this:
.LFORMAT: .string "%c" .text .align 2 .LENDL: .string "\n" .text .align 2 .globl main .type main, @function main: link.w %a6,#-4 | allocate a stack frame moveq #65,#-4(%a6) | Move 65 (ASCII A) to an address on our | local address space .L0: move.l #-4(%a6),-(%sp) | push the address to the stack pea .LFORMAT | push the format to the stack jbsr printf | run printf add.l #1,#-4(%a6) | add one to the letter cmpi.l #91,#-4(%a6) | compare with 91 (ASCII Z + 1) jblt .L0 | if less, jump to label L0 pea .LENDL | push format to the stack jbsr printf | run printf unlk %a6 | clear stack frame clr.l %d0 | store 0 in %d0 (return value) rts | return .align 2 .size main, .-main
IOW, print out the alphabet. Nothing spectacular, and it worked. Then I decided to take it one step further, and rather than using an address in memory, I switched to using a register, which is supposed to be a tiny bit faster—even if that shouldn't be noticeable in this case:
main: link.w %a6,#-4 | allocate a stack frame moveq #65,%d0 | Move 65 (ASCII A) to %d0 .L0: move.l %d0,-(%sp) | push the address to the stack pea .LFORMAT | push the format to the stack jbsr printf | run printf add.l #1,%d0 | add one to the letter cmpi.l #91,%d0 | compare with 91 (ASCII Z + 1) jblt .L0 | if less, jump to label L0 pea .LENDL | push format to the stack jbsr printf | run printf unlk %a6 | clear stack frame clr.l %d0 | store 0 in %d0 (return value) rts | return .align 2 .size main, .-main
(that's only the main section, but please assume the rest is still there:-)
When I ran that, it bombed. It would print out the A, and then whole lines of nothingness. When I ran it inside gdb, it wouldn't even print out the A. It took me quite some time to figure out what went wrong.
main: link.w %a6,#-4 | allocate a stack frame moveq #65,%d7 | Move 65 (ASCII A) to %d7 .L0: extb.l %d7 move.l %d7,-(%sp) | push the address to the stack pea .LFORMAT | push the format to the stack jbsr printf | run printf add.l #1,%d7 | add one to the letter cmpi.l #91,%d7 | compare with 91 (ASCII Z + 1) jblt .L0 | if less, jump to label L0 pea .LENDL | push format to the stack jbsr printf | run printf unlk %a6 | clear stack frame clr.l %d0 | store 0 in %d0 (return value) rts | return .align 2 .size main, .-main
%d0 is used to store a function's return value. If I store my data in %d0, then the next time I call a function, that data is overwritten. Since I do call a function inside my loop, that function overwrites my data every time. Using a different register (say, %d7) fixes this.
Right. Apart from the fact that it's totally non-portable, assembly
language also has quite a few quirks that you don't learn about when
taught structured programming
.
It'd be nice if there were a document somewhere that would explain how C functions expect to be called. I found out most of the above through trial and error, and through compiling stuff with -save-temps and looking at the result, comparing it with the 68k Programmers' Reference Manual. That works, but it's not really efficient—and not totally error-proof, too.
Hi,
Wow it's been a few years, but I used to program my Amiga almost exclusively in 68k assembly language. Your blog post brought back a few dim & distant memories
Just to comment though:
1) What assembler do you use? The mnemonics for a few instructions are a little different to how they were in my day. In particular I never had to prefix registers with %. Also, branch instructions weren't prefixed with a j, eg; bsr, blt, etc.
2) moveq always operates on full long-words, so you don't need to extend d7 to a longword at the start of your loop .L0 (ext.l d7).
3) Also you can use addq.l #1,d7 to do a quick addition as well.
4) Finally, the fastest way to clear a register on a plain 68000 (faster than clr.l) was always moveq #0,dx.
Sorry, probably far more 68k comments than you ever wanted
Cheers, Rich.
gas, the GNU assembler. No need to get another one if I've got a few running Linux installations with that assembler already installed
jbsr isn't actually an instruction; it's a gas feature, which checks the operand, and uses the 'best' branch instruction available based on the target CPU and the displacement size (byte, word, ...). Very handy
Oh, it's still there? I thought I'd removed it. Whoops.
Gas does that by itself, too. It checks what operand you're using, and silently uses addq/addi/adda if that's more appropriate. I think it's possible to forcibly disable that, though I'm not sure. Not that it'd make sense to do that IMO, but, well.
Same is true for move/movei/moveq/..., BTW.
Oh, that's interesting. Thanks
Not at all. Keep them coming!
Thanks.