Generella kommentarer till maskinkod

Här är ett antal punkter att tänka på när du skriver din assembelerkod. Punkterna sammanställdes ursprungligen av Gustaf Naeser (gaffe), har sedan uppdaterats av Tobias Amnell, och sedan av gaffe igen och igen och igen.

Det här dokumentet är under ständig utveckling och bearbetas varje gång ny input gör det nödvändigt.

Tydliga etiketter och namn

När konstanter används måste man tala om vad de representerar, t.ex att 48 används för att konvertera från ascii till decimaltal.

Ge etiketter (labels) vettiga och talande namn, annars blir de efter ett tag helt obegripliga (exit1: är oftast en dålig etikett när man hoppar ut ur något. Det förstår man kanske bättre när man blir tvungen att leta reda på vilken exit i ordningen man just skrev: -Var det exit394 eller exit349 jag skulle hoppa till?)

Detta gäller alla etiketter, även strängar och andra namngivna minnesareor. Om man kallar sina strängar string0, string1 och så vidare kanske man blir tvungen att sitta och leta efter den sträng man vill ha. Om man istället givit dem mer talande namn, greeting_message_str och report_io_failure_str, kan man undvika det.

Håll koll på minnet

Allokera alltid plats till din data. Behövs 100 bytes så se till att deklarera dem (annars kan det hända som fallet nedan).

Se alltid till att det inte går att överskrida de minnesbegränsningar programmet har. Allokeras 100 bytes till en inläsningbuffert så måste man se till att man aldrig får in fler än 100 bytes. Skulle man få in mer i den bufferten kommer ju annat att skrivas över. Ett typiskt sätt man kan upptäcka detta på är att programmet inte funkar andra körningen (dataarean har skrivit över programmets början/slut/whatever).

Allokera inte mer minne än vad som behövs! Minne kostar och ofta försöker man använda så lite minne som möjligt för att kunna spara in på hur mycket man behöver köpa till sin maskin. Kom ihåg, det är oftast fler än ett program som kör och om alla skulle ta en Meg för att vara på den säkra sidan...

Välj rätt instruktioner

add $t1, $zero, $zero heter li $t1, 0 och inget annat. Använd pseudoinstruktioner, det ger tydligare kod.

add $t1, $zero, $t2 heter move $t1, $t2 och inget annat. (se ovan)

Om man använder aritmetiska operationer när man inte behöver tar koden längre tid att exekvera. (Ok, nu är det så att kompilatorn kanske gör den här substitutionen själv, men vårt mål är att skriva läslig, och inte oförstålig, kod.)

Oftast heter det inte add $t1, $t1, -1 utan sub $t1, $t1, 1. Det finns ju som vanligt undantag. Personligen tycker jag ofta att det kan vara trevligt att använda negativa tal när man återställer stacken. Då ser man ju klart och tydligt att man har 12 och -12 bytes.

Läs igenom instruktionsuppsättningen, den brukar innehålla det man vill ha (eller något i närheten). Om man till exempel vill använda två additioner för att göra en multiplikation med fyra måste man motivera varför man gör det: går det snabbare? är det vackrare? har man redan fyllt någon pipe och vet att det blir bättre så? (Att läsa igenom instruktionsuppsättning / manualer brukar ha andra positiva följder, man förstår ibland bättre hur man bör konstruera sin kod för att snyggt lösa problem. Och det gäller inte bara maskinkod, utan allt: C, Ada, Perl, awk, lp, glassmaskinen, osv.)

Kommentera, förklara och var tydlig

Kommentering av maskinkod sker på flera nivåer. Den första är det man skriver i början av sitt program: Det här programmet ska...

Nedan följer ett förslag på ett filhuvud:

#################################################################
# Datorarkitektur, en programmeringsuppgift
#
# Beskrivning
#   Detta program hanterar avbrottsstyrd I/O på MIPS-processor
#  ...
#
# Författare: Anna Andersson, Bertil Bengtsson
#
# Revisioner
#   Ver 0.0 -- 1999-10-29 Utvecklingsversion
#   Ver 0.1 -- 2000-01-01 Färdig version
# 
# Källkod
#   ~namn/kurs/uppgift/filnamn.ext
#
# Startinstruktion:
#   Beskrivning av hur programmet startas!
#################################################################

Annat man kan ha med i beskrivningen är t.ex. begränsningar, förväntad input, var programmet returnerar osv.

Sedan kommenterar man de olika funktionella komponenterna (rutiner, subrutiner och alla större kodblock eller sammanhängande delar) så att en läsare förstår vad som ska hända i stort. Ett exempel på subrutinhuvud ges nedan:

#################################################################
# init_io
#  Initialiserar I/O-systemet
#
# Rutinen tar inga argument, och måste anropas innan någon annan 
# rutin anropas i systemet
#################################################################

Vad man vill veta om subrutiner är till exempel vad de gör (och hur), vad / hur de tar inparametrar, hur de gör med resultatet och om de har sidoeffekter. (Sidoeffekter är allt de ändrar på som till exempel stacken, minne eller register.)

Nu ska man egentligen kunna förstå vad programmet gör, men för att göra det ännu mer lättförståligt kommenterar man alla rader och talar om varför saker händer. Varför? Inte hur? Nej, hur står ju redan i koden. Man kan lugnt förutsätta att läsaren kan det programmeringsspråk som används. Det kan vara smart att ge pseudokod för att introducera variabler som man sedan kan använda i sin kod och då kan man ju som en kommentar säga

  # x represents the number of elements in the buffer.
  li $t1, 0                    #$t1 holds x and is initially 0 (the buffer is empty)
  ...
  add $t1, $t1, 1              #the number of elts in the buffer is increased (x++)

i stället för att skriva

  li $t1, 0                    #$t1 = 0
  ...
  add $t1, $t1, 1              #t1 = t1 + 1

och så skulle man naturligtvis skrivit pseudokod överst som beskrev den algoritm man använder... men det lämnas som en övning till den intresserade läsaren!

Gör det i rätt ordning

Lägg koden så att du aldrig hoppar till nästa rad(!) eller hoppar när du inte behöver!
  ...

  jal integration_over_xy      #do the integration
  j cleanup_and_exit           #goto the cleanup and exit

  #here comes a subroutine which...

integration_over_xy:
  mv ...
  ...
  jr $ra   #return

cleanup_and_exit:
  mv ...

Här kunde man ju bara lagt koden för cleanup_and_exit där man gör hoppet, man kommer ju aldrig att fortsätta ur integreringen till slutkoden.