I'm writing a subroutine in Assembly for the Motorola 68000 that concatenates two strings. The subroutine receives two input strings, StringA
("Hello") and StringB
("World"), and stores the concatenated result in StringC
("HelloWorld").
The code compiles without errors and seems to work, but I am not sure if the output is correct or if the implementation is logically correct.
I wrote the following code:
ORG $8000
StringA DC.B 'Hello',0 ; First string with null terminator
StringB DC.B 'World',0 ; Second string with null terminator
StringC DS.B 20 ; Buffer for the concatenated string (large enough?)
START:
lea.l StringA,a0 ; a0 -> "Hello"
lea.l StringB,a1 ; a1 -> "World"
lea.l StringC,a2 ; a2 -> Buffer for concatenation
clr.b d0
jsr CopyA ; Call first subroutine
SIMHALT
CopyA:
move.b (a0)+,d0 ; Load character from StringA into d0
; Check if it is the null terminator
beq.s CopyB ; If yes, start copying StringB
move.b d0,(a2)+ ; Otherwise, copy character into StringC
bra CopyA
CopyB:
move.b (a1)+,d0 ; Load character from StringB into d0
move.b d0, (a2)+ ; Copy it into StringC
bne CopyB ; If the character is not null, continue copying
rts ; Return from subroutine
END START
Questions:
- Is my subroutine implementation correct? I used
jsr
andrts
, but I would like confirmation if this is the best approach in this context. - Can it be optimized in any way? For example, are there any redundant instructions I could remove?
I'm writing a subroutine in Assembly for the Motorola 68000 that concatenates two strings. The subroutine receives two input strings, StringA
("Hello") and StringB
("World"), and stores the concatenated result in StringC
("HelloWorld").
The code compiles without errors and seems to work, but I am not sure if the output is correct or if the implementation is logically correct.
I wrote the following code:
ORG $8000
StringA DC.B 'Hello',0 ; First string with null terminator
StringB DC.B 'World',0 ; Second string with null terminator
StringC DS.B 20 ; Buffer for the concatenated string (large enough?)
START:
lea.l StringA,a0 ; a0 -> "Hello"
lea.l StringB,a1 ; a1 -> "World"
lea.l StringC,a2 ; a2 -> Buffer for concatenation
clr.b d0
jsr CopyA ; Call first subroutine
SIMHALT
CopyA:
move.b (a0)+,d0 ; Load character from StringA into d0
; Check if it is the null terminator
beq.s CopyB ; If yes, start copying StringB
move.b d0,(a2)+ ; Otherwise, copy character into StringC
bra CopyA
CopyB:
move.b (a1)+,d0 ; Load character from StringB into d0
move.b d0, (a2)+ ; Copy it into StringC
bne CopyB ; If the character is not null, continue copying
rts ; Return from subroutine
END START
Questions:
- Is my subroutine implementation correct? I used
jsr
andrts
, but I would like confirmation if this is the best approach in this context. - Can it be optimized in any way? For example, are there any redundant instructions I could remove?
2 Answers
Reset to default 3The move
instruction on 68k is particularly powerful. They have given the move
instruction the fewest opcode bits, reserving many bits of the 16-bit instruction word so that it can supports two <ea>
operands. In 68k, an <ea>
operand (effective address) takes 6 bits, so having two of them means 12 bits, leaving only 4 bits left for other things. Given the size selection taking 2 bits (byte/word/long), there's a whopping 2 bits left for the move opcode itself.
Since the designers squeezed two <ea>
operands into this instruction, this means that move
can move memory to memory while also setting the condition codes!
Compare this to add
and most other 68k instruction: these only supports one <ea>
operand and a data (or address) register or an immediate as the other operand.
Thus, string copying should be done using an instruction like move (a0)+, (a2)+
¹.
Since this instruction sets the condition codes, you can follow it directly with a branch on condition (e.g. backwards on non-zero/non-null).
The algorithm will have to change just slightly (as illustrated in suggestions by @SepRoland), because using this move form, the null byte from the first string will be copied into the target buffer, so have to back up a2
by 1 byte before copying the 2nd string (but this time you do want the null copied to terminate string C so move (a1)+,(a2)+
is perfect and will not require any adjustment).
¹There is also the dbcc
instruction, that can be used to form a two instruction loop that moves memory limited by both count and also null terminator — these will run particularly fast on 68010 as it has a mode for dbcc
branching backwards by one instruction to run without instruction fetch. (Later 68ks have a more general instruction cache so can do larger loops all without instruction fetch.)
I am not sure if the output is correct.
What does the debugger's screen show you? If somewhere it has "HelloWorld" then that would seem it had worked, wouldn't it?
StringC DS.B 20 ; Buffer for the concatenated string (large enough?)
It a simple matter of adding both lengths and allowing an extra byte for the one terminating zero. So here 11 bytes would nail it. But let's not be stingy and allocate a decent size for a general-purpose buffer. And it is very good "assembly-style" if you use numbers that are a power of two.
Is my subroutine implementation correct? I used
jsr
andrts
, but I would like confirmation if this is the best approach in this context.
The best? Well no, because your subroutine is very near to the call site (distance from -128 to +127), you can reach it with less bytes using the bsr.s
instruction. It has the relative displacement built in the instruction word, unlike jsr
that has to add it after the instruction word. The rts
remains just like you wrote it. This is not faster, but it is required if one desires position independent code (with no absolute memory references).
Can it be optimized in any way? For example, are there any redundant instructions I could remove?
- The
clr.b d0
before calling the subroutine seems redundant. - The unconditional
bra CopyA
should certainly be avoided. I suggest you process the 1st string similarly to how you process the 2nd string, but that you subtract 1 from A2 in between both copy operations. That will remove the zero byte simply by having it overwritten with the first char from the second string (or zero if the second string is an empty one).
ORG $8000
StringA DC.B 'Hello',0 ; First string with null terminator
StringB DC.B 'World',0 ; Second string with null terminator
StringC DS.B 256 ; Buffer for the concatenated string
START:
lea.l StringA, a0 ; a0 -> "Hello"
lea.l StringB, a1 ; a1 -> "World"
lea.l StringC, a2 ; a2 -> Buffer for concatenation
bsr.s CopyA ; Call first subroutine
SIMHALT
CopyA:
move.b (a0)+, d0 ; Load character from StringA into d0
move.b d0, (a2)+ ; Copy it into StringC
bne.s CopyA ; If the character is not null, continue copying
subq.l #1, a2 ; Remove the null
CopyB:
move.b (a1)+, d0 ; Load character from StringB into d0
move.b d0, (a2)+ ; Copy it into StringC
bne.s CopyB ; If the character is not null, continue copying
rts ; Return from subroutine
END START
And this is an interesting variation:
ORG $8000
StringA DC.B 'Hello',0 ; First string with null terminator
StringB DC.B 'World',0 ; Second string with null terminator
StringC DS.B 256 ; Buffer for the concatenated string
START:
lea.l StringA, a0 ; a0 -> "Hello"
lea.l StringB, a1 ; a1 -> "World"
lea.l StringC, a2 ; a2 -> Buffer for concatenation
bsr.s CopyA ; Call first subroutine
SIMHALT
; IN (a0,a1,a2)
CopyA:
bsr.s Copy
subq.l #1, a2 ; Remove the null
movea.l a1, a0
Copy: move.b (a0)+, d0 ; Load character from StringB into d0
move.b d0, (a2)+ ; Copy it into StringC
bne.s Copy ; If the character is not null, continue copying
rts ; Return from subroutine
END START
For the 1st string, the local Copy subroutine gets called normally, and for the 2nd string the execution falls through into those Copy instructions. The first execution of rts
returns locally, but the second execution of rts
returns to the main program.
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745097710a4611081.html
cmp.b #0,d0
as themove.b
already sets the flags. – Jester Commented Mar 3 at 12:03