Many of you (understandably) applied a sort of nesting indentation to your assembly code something like this:
for_row: mov r9, 0
next_row:
COMPARISON
jge end_for_row
for_col: mov r10, 0
next_col:
COMPARISON
jge end_for_col
LOGIC
jmp for_col
end_for_col
jmp next_row
end_for_row
Whilst this is perfectly valid, generally this form of indentation is frowned upon.
In general I find the best format is to have labels aligned to the left and have code on separate lines, indented 4-8 spaces (be consistent!).
This makes it much quicker to scan and visually traverse the code.
Compare the readability of the above to:
for_row:
mov r9, 0
next_row:
COMPARISON
jge end_for_row
for_col:
mov r10, 0
next_col:
COMPARISON
jge end_for_col
LOGIC
jmp for_col
end_for_col:
jmp next_row
end_for_row:
Much easier to find the control labels right?
Rest assured EVERYONE does this. I've attached my personal matrix print code from first year in this gist so you can laugh at my code style. Note the handy diagram of the stack though. I love them as I have the conceptual memory of a goldfish sometimes.
Also - Camelcase in assembly? -10,000 marks…
When choosing labels, there are no scopes like you would have seen in haskell and java. As such, you need to pick unique names.
A few of you opted to suffix your labels:
; My First Function
for_row1:
CODE
next_row1:
CODE
for_col1:
CODE
next_col1:
CODE
end_for_col1:
CODE
end_for_row1:
; My Other Function
for_row_other:
CODE
next_row_other:
CODE
for_col_other:
CODE
next_col_other:
CODE
end_for_col_other:
CODE
end_for_row_other:
While this is perfectly fine, it's not the most readable format. Imagine scanning the lines of code, reading Left->right. It can also be quite hard to spot which jumps go to which labels if there are many which start with the same name.
Personally, I find prefixing is the best way to manage this. For example:
; Multiply Matrix
M_for_row:
CODE
M_next_row:
CODE
M_for_col:
CODE
M_next_col:
CODE
M_end_for_col:
CODE
M_end_for_row:
; Print Matrix
P_for_row:
CODE
P_next_row:
CODE
P_for_col:
CODE
P_next_col:
CODE
P_end_for_col:
CODE
P_end_for_row:
In a piece of assembly code, comments can rapidly make code unreadable. On the flipside, if used well, they can be fantastic for documenting what your code does.
For example:
P_for_col: ;start loop
mov rdi, 0
P_next_col:
;exits the for loop
cmp rdi, [rbx+8]
jge P_end_col
call output_tab
;get element
mov rax, rsi
imul rax, [rbx+8]
add rax, rdi
mov rax, [rbx+8*rax+16]
; push ready for output
push rax
call output_int
add rsp, 8
;increment column
inc rdi
jmp P_next_col
P_end_col:
Isn't partcularly clear or useful. Let's neaten it up.
P_for_col:
mov rdi, 0 ; col = 0
P_next_col: ; start for loop
cmp rdi, [rbx+8] ; exit for loop if col >= this.COLS
jge P_end_col
call output_tab
; element_addr = (this + 16) + 8 * (row * this.COLS + col)
mov rax, rsi ; rax = row
imul rax, [rbx+8] ; = row * this.COLS
add rax, rdi ; = row * this.COLS + col
mov rax, [rbx+8*rax+16] ; = this.elem[row, col]
push rax ; push this.elem[row, col]
call output_int ; output this.elem[row, col]
add rsp, 8 ; pop parameter
inc rdi ; col ++
jmp P_next_col
P_end_col:
call output_newline
Boom.
Notice several things:
- Comments are all aligned
- Comments explain what each step does (this much detail not always needed!)
- Additional comments to explain info like state pre-operation
- 3 and 4 letter operator params are aligned. A small change, but it makes a difference I think
- Newlines are injected between logical blocks of code
- Nice indentation makes control labels easy to spot
DO NOT STORE THINGS IN THIS
rax
is volatile which means that for calculations it's fine, but you shouldn't expect anything you put in there to be safe.
This also means that storing it isn't strictly necessary. A few of you added it to your register saving with push rax
and later pop rax
but you could have left these out.