mipsbubble-sortmips32

No output from bubble sort Algorithm implemented in MIPS (for strings)


No string output in MIPS assembly code. I tried looking this up for sometime now and I can't seem to find a proper answer. The code below implements bubble sort on a string, it only works on lowercase letters as it compares ascii code numbers.(sorry if I am not using proper terminology)

.data
InputPrompt: .asciiz "Please input a string(no more than 255 Characters: "
Buffer: .space 256


.text
li $v0, 4
la $a0, InputPrompt
syscall

li $v0, 8
la $a0, Buffer
li $a1, 255
syscall #taking input

jal BubbleSort

move $a0, $v0
li $v0, 4
syscall

li $v0, 10
syscall

BubbleSort:
    addi $sp, $sp, -32
    sw $ra, 0($sp)
    sw $t0, 4($sp) #temporary storage for the string
    sw $t1, 8($sp) #Used to go back to beginning of string
    sw $t2, 12($sp) #register to keep track of iterator
    sw $t3, 16($sp) #flag to check if a switch happened or not
    sw $t4, 20($sp) #for str[i] < str[i+1] condition
    sw $t5, 24($sp) #current byte
    sw $t6, 28($sp) #next byte
    
    la $t0, ($a0) #loading string
    li $t3, 0
    OutterLoop: #while(!sorted)
        move $t2, $zero #reset iterator
        beq $t3, 1, Done # (!sorted) condition
        li $t3, 1
        li $t1, 0
        InnerLoop:
            lb $t5, 0($t0) #load current byte
            lb $t6, 1($t0) #load next byte
            beqz $t6, EndString #'\0' has value of 0
            
            slt $t4, $t5, $t6 #str[i] < str[i+1]
            beq $t4, 1, ELSE
                
                sb $t6, 0($t0)
                sb $t5, 1($t0)
                li $t3, 0
                
            ELSE:
            addi $t0, $t0, 1
            addi $t1, $t1, 1
        j InnerLoop
        EndString:
        sub $t0, $t0, $t1 #go back to beginning of string
    j OutterLoop
    Done:
    
    move $v0, $t0 #return string
    
    lw $ra, 0($sp)
    lw $t0, 4($sp)
    lw $t2, 8($sp)
    lw $t3, 12($sp)
    lw $t4, 16($sp)
    lw $t5, 20($sp)
    lw $t6, 24($sp)
    addi $sp, $sp, 32
jr $ra
    

The comments above pretty much explain what each part does sufficiently.

I tried looking up through online resources on what the problem might be, but didn't get to any proper answers. Example input: hello Expected output: ehllo Output from code: (nothing)


Solution

  • The exit condition for the outer loop is that you have not swapped anything, therefore it is considered sorted.  To put it another way, the continuation condition for the outer loop is that something has been swapped, therefore it is considered non-sorted.

    However, you "swap" not just when less than, but less that or equal, and hello has two l's.  Thus, the outer loop never stops, when the input string has two letters the same.  Try helo for input and it will work.

    This swap on equal is unnecessary but harmless — until the string is fully sorted, when it becomes the only swap and thus prevents the loop from terminating — classic infinite loop.

    You should be able to debug this yourself.  Use single step and observe the outer loop and inner loop swapping continues ad nauseam even after the string is fully sorted.


    Let's look at your swap condition test:

            slt $t4, $t5, $t6 #str[i] < str[i+1]
            beq $t4, 1, ELSE
    

    That's doing $t5 < $t6, and then testing for the true condition for skipping the then-part.  So, the then-part execution condition is the opposite: $t5 >= $t6, which leads to swapping when 'l' == 'l' in 'hello'`.  In essence, you're doing:

    if ( p[0] >= p[1] ) {
        // swap when earlier letter is > following letter, but also when equal
        $t3 = 0;
    }
    

    where you really want:

    if ( p[0] > p[1] ) {
        // swap only when really necessary
        $t3 = 0;
    }
    

    To solve the problem, you need to test for $t5 <= $t6 (as the if-then condition for skipping the then-part) instead, such that the condition to execute the then-part is now $t5 > $t6.

    You can use sle instead of slt.

    Though FYI, this is a pseudo instruction that expands to 3 real machine code instructions, since the MIPS hardware doesn't actually have this operation, only the assembler knows this one.

    So, what can be done if we don't want that help from the assembler?

    We can swap the operands of slt, as in $t6 < $t5, and then also swap the exit test to branch to the ELSE if this condition doesn't hold — this would appear to be double negation but it isn't exactly: taken together it moves the equality test to the other condition (the other branch of the if-then), making it $t6 >= $t5 aka $t5 <= $t6 instead of just $t5 < $t6.

    That means skipping ahead to the ELSE part when ! (p[1] < p[0]), which is the same as p[1] >= p[0]:

    slt $t4, $t6, $t5   # str[i+1] < str[i]
    beq $t4, $0, ELSE   # goto ELSE when ! (str[i+1] < str[i])
                        # aka goto ELSE when str[i+1] >= str[i]
                        # aka goto ELSE when str[i] <= str[i+1]
    

    which is equivalent to:

    if ( p[1] >= p[0] ) goto ELSE; // aka if ( p[0] <= p[1] ) goto ELSE;
    // swap only when really necessary
    $t3 = 0;
    ELSE:
    

    which is equivalent to:

    if ( p[1] < p[0] ) { // and also same as: if ( p[0] > p[1] ) { 
        // swap only when necessary
        $t3 = 0;
    }
    

    This is super confusing stuff, easy to make a mistake or typo.  There are two different though related concepts here, one is negation, and the other is operand swapping.

    We combine both negation and operand swapping as here to use the slt instruction that MIPS hardware has.


    Suggestions and other notes: