assemblymotorola68000

68000 Assembly – String Equality Check & Concatenation via Stack


I was given the following assignment:

  1. Verify if two strings A and B are equal, character by character.
  2. Create a third string C that is the concatenation of A and B.
  3. All logic must live in one or more subroutines with parameters passed via the stack.

I realized that points (2) and (3) were topics I’d already covered in previous posts, but then I paused my 68000 assembly studies… and I’m back today! :-)

My current attempt is based on the great suggestions from Sep Roland and Erik Eidt in my earlier question 68000 Assembly – Passing Parameters via Stack for String Concatenation (Mar 4).

My implementation:

          ORG    $8000

;— Data section —
StringA   DC.B   'Hello',0       ; source A
StringB   DC.B   'World',0       ; source B
StringC   DS.B   256             ; destination buffer
MatchRes  DS.W   1               ; result: 0 = equal, 1 = not equal

;— Entry point —
START:
    ; Concatenate A + B → C
    PEA.L   StringC
    PEA.L   StringB
    PEA.L   StringA
    BSR.S   ConcatStrings
    ADDA.L  #12,A7

    ; Compare A vs. B → D0 (0=equal,1≠)
    PEA.L   StringB
    PEA.L   StringA
    BSR.S   CompareStrings
    ADDA.L  #8,A7
    MOVE.B  D0,MatchRes

    SIMHALT

;— ConcatStrings(&A,&B,&C) —
ConcatStrings:
    MOVEA.L 4(A7),A0        ; A0 = &StringA
    MOVEA.L 12(A7),A1       ; A1 = &StringC
CopyA:
    MOVE.B  (A0)+,(A1)+     ; copy byte from A to C
    BNE.S   CopyA
    SUBQ.L  #1,A1           ; back up over null
    MOVEA.L 8(A7),A0        ; A0 = &StringB
CopyB:
    MOVE.B  (A0)+,(A1)+     ; copy byte from B to C
    BNE.S   CopyB
    RTS

;— CompareStrings(&A,&B) → D0 (0=equal,1≠) —
CompareStrings:
    MOVEA.L 4(A7),A0        ; A0 = &StringA
    MOVEA.L 8(A7),A1        ; A1 = &StringB
CmpLp:
    MOVE.B  (A0)+,D0        ; next char A→D0
    MOVE.B  (A1)+,D1        ; next char B→D1
    CMP.B   D1,D0           ; compare
    BNE.S   NotEq
    TST.B   D0              ; null terminator?
    BNE.S   CmpLp
    CLR.L   D0              ; equal
    RTS
NotEq:
    MOVEQ   #1,D0           ; not equal
    RTS

          END     START

Questions

  1. Can this code be shortened or further optimized?
  2. Have I correctly interpreted the assignment requirements (i.e. is the logic for both compare and concatenate routines sound)?
  3. Are there any additional checks or features (edge-case handling, error flags, etc.) I could add to make this more robust?

Thanks in advance for any tips!

Edit

          ORG    $8000

; --- data --------------------------------------------------------
StringA   DC.B  'Hello',0          ; source string A
StringB   DC.B  'World',0          ; source string B
StringC   DS.B  256                ; destination buffer (caller must ensure size)
MatchRes  DS.W  1                  ; result: 0 = equal, 1 = not equal

; --- main --------------------------------------------------------
START:
    PEA.L  StringC                 ; push &C
    PEA.L  StringB                 ; push &B
    PEA.L  StringA                 ; push &A

    BSR.S  CompareStrings          ; compare A vs. B
    MOVE.W D0,MatchRes             ; store result (word)

    BSR.S  ConcatStrings           ; concatenate A + B → C
    ADDA.L #12,A7                  ; pop A, B, C

    SIMHALT                        ; halt simulator

; --- CompareStrings(&A,&B) → D0 (0=equal,1=not equal) -------------
; D0 = 1 by default (not equal), set to 0 only if all chars match
CompareStrings:
    MOVEQ   #1,D0                  ; default: not equal
    MOVEA.L 4(A7),A0               ; A0 = address of A
    MOVEA.L 8(A7),A1               ; A1 = address of B
CmpLoop:
    MOVE.B  (A0)+,D1               ; load next char from A
    CMP.B   (A1)+,D1               ; compare with next char from B
    BNE.S   EndCompare             ; mismatch → leave D0 = 1
    TST.B   D1                     ; test for null terminator
    BNE.S   CmpLoop                ; if not null, continue
    CLR.W   D0                     ; all matched → equal (D0 = 0)
EndCompare:
    RTS                            ; return

; --- ConcatStrings(&A,&B,&C) ------------------------------------
ConcatStrings:
    MOVEA.L 4(A7),A0               ; A0 = address of A
    MOVEA.L 12(A7),A1              ; A1 = address of C
CopyA:
    MOVE.B  (A0)+,(A1)+            ; copy byte from A to C
    BNE.S   CopyA                  ; repeat until null
    SUBQ.L  #1,A1                  ; back up over null terminator
    MOVEA.L 8(A7),A0               ; A0 = address of B
CopyB:
    MOVE.B  (A0)+,(A1)+            ; copy byte from B to C
    BNE.S   CopyB                  ; repeat until null
    RTS                            ; return

          END   START

Solution

    1. Can this code be shortened or further optimized
    START:
        PEA.L   StringC
        PEA.L   StringB
        PEA.L   StringA
        BSR.S   CompareStrings    ; -> D0 (D1 A0 A1)
        MOVE.W  D0,MatchRes
        BSR.S   ConcatStrings     ; -> (A0 A1)
        ADDA.L  #12,A7
    
        SIMHALT
    
        ...
    
    CompareStrings:
        MOVEQ   #1,D0           ; Default to 'not equal'
        MOVEA.L 4(A7),A0        ; A0 = &StringA
        MOVEA.L 8(A7),A1        ; A1 = &StringB
    CmpLp:
        MOVE.B  (A0)+,D1        ; next char A→D1
        CMP.B   (A1)+,D1        ; compare to next char B
        BNE.S   NotEq
        TST.B   D1              ; null terminator?
        BNE.S   CmpLp
        CLR.W   D0              ; equal
    NotEq:
        RTS                     ; One RTS was shaved off!
    
    1. Have I correctly interpreted the assignment requirements

    If an assignment tells you to 1. Verify ... and 2. Create ..., then I would definitely do it in that order. You currently are doing it in the opposite order.

    1. Are there any additional checks or features

    Supplying a large-enough buffer for the concatenation result is by far the easiest way to avoid buffer overflow.
    And don't just test the program with 2 strings that have the same length. Try out all combinations of shorter, longer, same length, and empty (one or both). Then you'll know the program is correct.


    [EDIT]

    1. All logic must live in one or more subroutines with parameters passed via the stack.

    The assignment explicitly allows you to solve the task using a single subroutine, and Peter seems to advocate it. Therefore, below is my version of that approach albeit still character by character. This will be a bit faster for when the strings A and B are equal to each other or at least share some of the same characters from the start:

              ORG   $8000
    ; --- data --------------------------------------------------------
    StringA   DC.B  'I say Hello',0    ; source string A
    StringB   DC.B  'I say World',0    ; source string B
    StringC   DS.B  256                ; destination buffer (caller must ensure size)
    MatchRes  DS.W  1                  ; result: 0 = equal, 1 = not equal
    
    ; --- main --------------------------------------------------------
    START:
        PEA.L  StringC                 ; push &C
        PEA.L  StringB                 ; push &B
        PEA.L  StringA                 ; push &A
        BSR.S  CompareAndConcatStrings ; compare A vs. B, and also concatenate A + B → C
        MOVE.W D0, MatchRes            ; store result (word)
        ADDA.L #12, A7                 ; pop A, B, C
    
        SIMHALT                        ; halt simulator
    
    ; IN (stack) OUT (D0) MOD (D1,A0,A1,A2)
    ; D0 = 1 by default (not equal), set to 0 only if all chars match
    CompareAndConcatStrings:
        MOVEQ   #1, D0                 ; default: not equal
        MOVEA.L 4(A7), A0              ; A0 = address of A
        MOVEA.L 8(A7), A1              ; A1 = address of B
        MOVEA.L 12(A7), A2             ; A2 = address of C
    CmpLoop:
        MOVE.B  (A0)+, D1              ; load next char from A
        MOVE.B  D1, (A2)+              ; early start on the concatenation
        CMP.B   (A1)+, D1              ; compare with next char from B
        BNE.S   EndCompare             ; mismatch → leave D0 = 1
        TST.B   D1                     ; test for null terminator
        BNE.S   CmpLoop                ; if not null, continue
        CLR.W   D0                     ; all matched → equal (D0 = 0)
        ;;BRA.S   SkipNull    (omitted; I prefer 2 bytes less)
    EndCompare:
        TST.B   D1
        BEQ.S   SkipNull
    CopyRemainderA:
        MOVE.B  (A0)+, (A2)+           ; copy byte from A to C
        BNE.S   CopyRemainderA         ; repeat until null
    SkipNull:
        SUBQ.L  #1, A2                 ; back up over null terminator
        MOVEA.L 8(A7), A1              ; A0 = address of B
    CopyB:
        MOVE.B  (A1)+, (A2)+           ; copy byte from B to C
        BNE.S   CopyB                  ; repeat until null
        RTS                            ; return
    
              END   START
    

    [Edit 2]

    As suggested in a comment, we could use CMPM but it will not run faster and it will require some more bytes:

        ...
    
    CmpLoop:
        MOVE.B  (A0), (A2)+            ; early start on the concatenation
        CMPM.B  (A0)+, (A1)+           ; compare char from A with char from B
        BNE.S   EndCompare             ; mismatch → leave D0 = 1
        TST.B   -1(A0)                 ; test for null terminator
        BNE.S   CmpLoop                ; if not null, continue
        CLR.W   D0                     ; all matched → equal (D0 = 0)
        ;;BRA.S   SkipNull    (omitted; I prefer 2 bytes less)
    EndCompare:
        TST.B   -1(A0)
        BEQ.S   SkipNull
    CopyRemainderA:
        MOVE.B  (A0)+, (A2)+           ; copy byte from A to C
        BNE.S   CopyRemainderA         ; repeat until null
    SkipNull:
        SUBQ.L  #1, A2                 ; back up over null terminator
        MOVEA.L 8(A7), A1              ; A0 = address of B
    CopyB:
        MOVE.B  (A1)+, (A2)+           ; copy byte from B to C
        BNE.S   CopyB                  ; repeat until null
        RTS                            ; return
    
              END   START