Subroutines in MIPS and other beginner silliness
I'm using Project Euler to learn MIPS, specifically using Problem 6 to learn how to use a subroutine. Unfortunately, I am doing something very wrong because my answer that I'm getting is far too large in magnitude. Is my problem here with how I am using the subroutine, or is it something else entirely?
## p6.asm
##
## Andrew Levenson, 2010
## Project Euler, Problem 6
##
## Calculate the difference
## between the sum of the squares
## and the square of the sum
## of all natural numbers n
## such that n < 1000
.text
.globl main
main:
init:
## Registers
ori $t0, $0, 0x0 # $t0 will be used for scratch
ori $t1, $0, 0x0 # $t1 is the loop counter and n
ori $t2, $0, 0x0 # $t2 will be the sum of the squares
ori $t3, $0, 0x0 # $t3 will be the square of the sum
ori $t4, $0, 0x3E8 # $t4 = 1000, the limit
loop:
## Main loop
addiu $t1, $t1, 0x1 # Increment n
sltu $t0, $t1, $t4 # Is n less than 1000?
beq $t0, $0, diff # if $t0 == 0 then jump to diff
sll $0, $0, $0 # no op
addu $t3, $t3, $t1 # sum = sum + n
move $a0, $t1 # put n in $a0
jal square # jump to square and save position to $ra
sll $0, $0, $0 # no op
addu $t2, $t2, $a0 # The sum of the squares =
# sum + n **2
j loop # jump to loop
sll $0, $0, $0 # no op
square:
## Subroutine that squares a given number
mul $a0, $a0, $a0 # Argument = Argument ** 2
jr $ra # jump to $ra
# $ra = instruction where jal was called
开发者_如何学Go sll $0, $0, $0 # no op
diff:
## Finds the difference of $t2 and $t3
## But first, we have to square $t3
move $a0, $t3 # puts $t3 in $a0
jal square # jump to square and save position to $ra
sll $0, $0, $0 # no op
move $t3, $a0 # $t3 = $a0 = $t3 ** 2
subu $t0, $t2, $t3 # $t0 = $t2 - $t3
print:
li $v0, 0x1 # system call #1 - print int
move $a0, $t0
syscall # execute
li $v0, 0xA # system call #10 - exit
syscall
## End of Program
I think the main problem is that question 6 asks you to work with the numbers from 1 to 100, whereas your code works with 1 to 999!
The subroutine call looks OK. Some suggestions:
1) You have a strange mixture of basic ops vs. pseudo-ops. The ori
instructions at the top can be written using li
(just as the constants for the syscalls at the bottom are). Alternatively, if you deliberately want to use basic ops as a learning exercise, note that move $a0, $t1
can be written as addu $a0, $t1, $0
(or or $a0, $1, $0
also works).
2) The conditional branch with sltu
and beq
can be written as bge $t1, $t4, diff
. This is a pseudo-op which expands to sltu
and beq
, but is interesting because it automatically uses $1
as a temporary register - by convention this register is reserved for use as the "assembler temporary" and named $at
.
3) sll $0, $0, $0
is really a sllv
instruction, as the shift amount there is a register. The canonical MIPS no-op is sll $0, $0, 0
which assembles to an opcode of 0x00000000
. Better still, just write nop
.
4) Where explicit branch delay slots are being used (note that some assemblers will reorder instructions automatically to fill them - e.g. gas
does this unless you tell it not to with .set reorder
), it's helpful to clearly mark them in some way so that they stand out. A useful convention is to give them an extra bit of indentation:
move $a0, $t1 # put n in $a0
jal square # jump to square and save position to $ra
nop # no-op
(A nop
tends to stand out anyway, but if you start trying to fill them with useful instructions, you will quickly go mad if you don't mark them in some way.)
精彩评论