I made an assembler for the "hack" assembly language, here's an example of what it looks like:
@16
M=1
@17
M=0
@16
D=M
@0
D=D-M
@18
D;JGT
@16
D=M
@17
M=D+M
@16
M=M+1
@4
0;JMP
@17
D=M
Currently it only works with predefined symbols, I want to know what to improve before I continue.
So how it works is, is it reads the file line by line and first identifies if its a C or A instruction (there is also an L instruction which i haven't yet implemented). If its a C instruction, it gets the dest, comp and jump fields and converts them to the correct binary code.
If its an A instruction it gets the symbol of the instruction and converts it to the correct binary code.
For now its just prints the binary code, it doesn't yet write to a file
I'm pretty embarrassed about the way I get the correct opcode for the instruction because it's basically just a bunch of if
statements. So please tell me how to improve.
x86 NASM btw
section .bss
buffer resb 320
line resb 10
instructionT resb 1
symbol resb 10
comp resb 6
jump resb 6
dest resb 6
op resb 64
fileptr resb 64
totalBytes resb 4
bytenum resb 4
SYSREAD equ 0
SYSWRITE equ 1
SYSOPEN equ 2
A_INSTRUCTION equ 0
C_INSTRUCTION equ 1
L_INSTRUCTION equ 2
true equ 1
false equ 0
section .data
filename db "file.asm", 0
opcode db "0000000000000000"
lastround db false
newline db 10
section .text
global _start
_start:
openfile filename
push rax ; file descriptor
call readfile
main:
call getline
push line
call instructionType
cmp byte [instructionT], C_INSTRUCTION
jne a_instruction
call getDetails ; get comp, dest and jump fields
mov edi, opcode
mov al, "1" ; first three bytes are 111
mov ecx, 3
repe stosb
inc edi
call compop ; opcode for comp field
push op
push 6
call fill
cmp byte [dest], 0 ; destination field is optional
jz nodest
call destop
push op
push 3
call fill
jmp jumpfield
nodest:
add edi, 3 ; fill adds 3 to edi so do it here if fill isnt called
jumpfield:
cmp byte [jump], 0 ; jump field is optional
jz cleanup
call jumpop
push op
push 3
call fill
cleanup:
push comp ; clears comp, dest and jump field
push 0
push 18 ; clear 18 bytes which is comp, dest and jump
call clear
jmp iteration
a_instruction:
call getSymbol ; for example @150 symbol = "150"
stringToNumber symbol
call binaryString
push symbol ; clear symbol for next iteration
push 0
push 10
call clear
iteration:
print opcode, 16
print newline, 1
push opcode ; clear opcode for next iteration
push "0"
push 16
call clear
cmp byte [lastround], true ; true if end of file
jne main
theEnd:
exit
binaryString: ; convert A instruction to binary version
mov eax, edi
mov ebx, 2
mov ecx, 15
mov edi, opcode + 15
binary:
cdq
div ebx
add dl, 48
mov byte [edi], dl
dec edi
loop binary
ret
instructionType:
mov edi, line
.whitespace:
cmp byte [edi], 0
jnz .out
inc edi
jmp .whitespace
.out:
cmp byte [edi], "@" ; @xxx = A_INSTRUCTION
jne .around1
mov byte [instructionT], A_INSTRUCTION
jmp .end
.around1:
cmp byte [edi], "(" ; (label) = L_INSTRUCTION
jne .around2
mov byte [instructionT], L_INSTRUCTION
jmp .end
.around2:
mov byte [instructionT], C_INSTRUCTION ; default = C_INSTRUCTION
.end:
ret
getDetails: ; get dest, comp and jump for C instruction
mov edi, line
inc edi ; get first byte
mov esi, edi
xor eax, eax
hasDest: ; if strings contains "=" it has the destination field
; ; example: A=M+1, destination = A
mov ecx, 10
mov al, "="
repne scasb
cmp byte [edi], 0 ; if no destination skip to the next loop
jz getComp
mov edi, dest
getDest:
movsb
cmp byte [esi], "="
jne getDest
inc esi
getComp:
mov edi, comp
.loop:
movsb
cmp byte [esi], ";"
je getJump
cmp byte [esi], 0
jnz .loop
jmp out
getJump:
inc esi
mov edi, jump
.loop:
movsb
cmp byte [esi], 0
jnz .loop
out:
ret
getSymbol:
mov esi, line
add esi, 2 ; skip @ or (
mov edi, symbol
.loop:
cmp byte [esi], ")"
je .end
cmp byte [esi], 0
je .end
cmp byte [esi], 10
je .end
movsb
jmp .loop
.end:
ret
getline:
mov ebx, [totalBytes] ; needed for checking end of file
mov esi, [fileptr]
push line
push 0
push 10
call clear ; clears the previous line
sub edi, 9 ; get back to starting index
whitespace:
cmp byte [esi], 10
je remove
jmp move
remove:
inc esi
inc byte [bytenum]
jmp whitespace
move:
inc byte [bytenum]
movsb
cmp [bytenum], ebx ; if (current byte == totalbytes) set lastround to true
jnge .around
mov byte [lastround], true
jmp .out
.around:
cmp byte [esi], 10
jne move
.out:
mov [fileptr], esi
ret
readfile: ; reads 30 bytes at a time until end of file
mov byte [totalBytes], 0
mov ebx, buffer
mov eax, SYSREAD
mov edi, [rsp + 8] ; file descriptor
mov esi, ebx ; buffer address
mov edx, 30
syscall
mov qword [fileptr], buffer
cmp eax, 0
jz .out
.readmore:
add ebx, eax ; move buffer address the amount of bytes that were read
add [totalBytes], eax
mov eax, SYSREAD
mov edi, [rsp + 8]
mov esi, ebx
mov edx, 30
syscall
cmp eax, 0 ; read until end of file
jnz .readmore
ret
.out:
fill: ; fills the opcode string (edi has the opcode address)
mov esi, [rsp + 16]
mov ecx, [rsp + 8]
repe movsb
ret 16
clear:
mov edi, [rsp + 24] ; address to be cleared
mov al, [rsp + 16]
mov ecx, [rsp + 8]
repe stosb
ret 24
here at the macros:
%macro print 2
push rdi
push rax
push rdi
push rdx
push rsi
push rcx
mov esi, %1
mov eax, SYSWRITE
mov edi, 1
mov edx, %2
syscall
pop rcx
pop rsi
pop rdx
pop rdi
pop rax
pop rdi
%endmacro
%macro stringToNumber 1
push rbp
push rcx
push rsi
push rax
mov rdi, 0 ; number stored here
mov ebp, %1
mov ecx, 0
cmp byte [ebp], "-"
jne %%loop
inc ecx
%%loop:
xor esi, esi
mov sil, byte [ebp + ecx]
sub sil, 48
cmp esi, 9 ; if this is greater than 9 the string has ended
jg %%exit
mov rax, 10
mul rdi ; multiply by 10
add rsi, rax
mov rdi, rsi
inc ecx
jmp %%loop
%%exit:
cmp byte [ebp], "-"
jne %%around
neg rdi
%%around:
pop rax
pop rsi
pop rcx
pop rbp
%endmacro
%macro openfile 1
mov eax, SYSOPEN
mov rdi, %1
mov esi, 2
mov edx, 0777
syscall
%endmacro
%macro getStringLength 1
mov edx, %1
xor ecx, ecx ;counter
%%loop:
mov bl, [edx + ecx]
inc ecx
cmp bl, 0
je %%exit
cmp bl, 10
je %%exit
jmp %%loop
%%exit:
sub ecx, 1
%endmacro
And here is the code for getting the correct opcode for the C instruction, it is over 300 lines :(
section .data
null db "null", 0
M db "M", 0
D db "D", 0
DM db "DM", 0
A db "A", 0
AM DB "AM", 0
AD db "AD", 0
ADM db "ADM", 0
MD db "MD", 0
jJGT db "JGT", 0
jJGE db "JGE", 0
jJEQ db "JEQ", 0
jJLT db "JLT", 0
jJNE db "JNE", 0
jJLE db "JLE", 0
jJMP db "JMP", 0
zero db "0", 0
one db "1", 0
negone db "-1", 0
notD db "!D", 0
notA db "!A", 0
notM db "!M", 0
minusD db "-D", 0
minusA db "-A", 0
minusM db "-M", 0
dplus1 db "D+1", 0
aplus1 db "A+1", 0
mplus1 db "M+1", 0
dminus1 db "D-1", 0
aminus1 db "A-1", 0
mminus1 db "M-1", 0
dplusa db "D+A", 0
dplusm db "D+M", 0
dminusa db "D-A", 0
dminusm db "D-M", 0
aminusd db "A-D", 0
mminusd db "M-D", 0
danda db "D&A", 0
dandm db "D&M", 0
dora db "D|A", 0
dorm db "D|M", 0
section .text
%macro strcmp 2
push rdi
mov edi, %1
getStringLength edi
mov esi, %2
repe cmpsb
pop rdi
%endmacro
; OPCODE FOR COMP
compop:
strcmp comp, zero
jnz .n1
mov rdx, "101010"
mov byte [opcode + 3], "0"
mov [op], rdx
jmp finishcomp
.n1:
strcmp comp, one
jnz .n2
mov rdx, "111111"
mov [op], rdx
mov byte [opcode + 3], "0"
jmp finishcomp
.n2:
strcmp comp, negone
jnz .n3
mov rdx, "111010"
mov [op], rdx
mov byte [opcode + 3], "0"
jmp finishcomp
.n3:
strcmp comp, D
jnz .n4
mov rdx, "001100"
mov [op], rdx
mov byte [opcode + 3], "0"
jmp finishcomp
.n4:
strcmp comp, A
jnz .n5
mov rdx, "110000"
mov [op], rdx
mov byte [opcode + 3], "0"
jmp finishcomp
.n5:
strcmp comp, M
jnz .n6
mov rdx, "110000"
mov [op], rdx
mov byte [opcode + 3], "1"
jmp finishcomp
.n6:
strcmp comp, notD
jnz .n7
mov rdx, "001101"
mov [op], rdx
mov byte [opcode + 3], "0"
jmp finishcomp
.n7:
strcmp comp, notA
jnz .n8
mov rdx, "110001"
mov [op], rdx
mov byte [opcode + 3], "0"
jmp finishcomp
.n8:
strcmp comp, notM
jnz .n9
mov rdx, "110001"
mov [op], rdx
mov byte [opcode + 3], "1"
jmp finishcomp
.n9:
strcmp comp, minusD
jnz .n10
mov rdx, "001111"
mov [op], rdx
mov byte [opcode + 3], "0"
jmp finishcomp
.n10:
strcmp comp, minusA
jnz .n11
mov rdx, "110011"
mov [op], rdx
mov byte [opcode + 3], "0"
jmp finishcomp
.n11:
strcmp comp, minusM
jnz .n12
mov rdx, "110011"
mov [op], rdx
mov byte [opcode + 3], "1"
jmp finishcomp
.n12:
strcmp comp, dplus1
jnz .n13
mov rdx, "011111"
mov [op], rdx
mov byte [opcode + 3], "0"
jmp finishcomp
.n13:
strcmp comp, aplus1
jnz .n14
mov rdx, "110111"
mov [op], rdx
mov byte [opcode + 3], "0"
jmp finishcomp
.n14:
strcmp comp, mplus1
jnz .n15
mov rdx, "110111"
mov [op], rdx
mov byte [opcode + 3], "1"
jmp finishcomp
.n15:
strcmp comp, dminus1
jnz .n16
mov rdx, "001110"
mov [op], rdx
mov byte [opcode + 3], "0"
jmp finishcomp
.n16:
strcmp comp, aminus1
jnz .n17
mov rdx, "110010"
mov [op], rdx
mov byte [opcode + 3], "0"
jmp finishcomp
.n17:
strcmp comp, mminus1
jnz .n18
mov rdx, "110010"
mov [op], rdx
mov byte [opcode + 3], "1"
jmp finishcomp
.n18:
strcmp comp, dplusa
jnz .n19
mov rdx, "000010"
mov [op], rdx
mov byte [opcode + 3], "0"
jmp finishcomp
.n19:
strcmp comp, dplusm
jnz .n20
mov rdx, "000010"
mov [op], rdx
mov byte [opcode + 3], "1"
jmp finishcomp
.n20:
strcmp comp, dminusa
jnz .n21
mov rdx, "010011"
mov [op], rdx
mov byte [opcode + 3], "0"
jmp finishcomp
.n21:
strcmp comp, dminusm
jnz .n22
mov rdx, "010011"
mov [op], rdx
mov byte [opcode + 3], "1"
jmp finishcomp
.n22:
strcmp comp, aminusd
jnz .n23
mov rdx, "000111"
mov [op], rdx
mov byte [opcode + 3], "0"
jmp finishcomp
.n23:
strcmp comp, mminusd
jnz .n24
mov rdx, "000111"
mov [op], rdx
mov byte [opcode + 3], "1"
jmp finishcomp
.n24:
strcmp comp, danda
jnz .n25
mov rdx, "000000"
mov [op], rdx
mov byte [opcode + 3], "0"
jmp finishcomp
.n25:
strcmp comp, dandm
jnz .n26
mov rdx, "000000"
mov [op], rdx
mov byte [opcode + 3], "1"
jmp finishcomp
.n26:
strcmp comp, dora
jnz .n27
mov rdx, "010101"
mov [op], rdx
mov byte [opcode + 3], "0"
jmp finishcomp
.n27:
strcmp comp, dorm
jnz finishcomp
mov rdx, "010101"
mov [op], rdx
mov byte [opcode + 3], "1"
finishcomp:
ret
; OPCODE FOR DESTINATION
destop:
strcmp dest, null
jnz n1
mov dword [op], "000"
jmp finish
n1:
strcmp dest, D
jnz n2
mov dword [op], "010"
jmp finish
n2:
strcmp dest, M
jnz n3
mov dword [op], "001"
jmp finish
n3:
strcmp dest, A
jnz n4
mov dword [op], "100"
jmp finish
n4:
strcmp dest, DM
jnz n5
mov dword [op], "011"
jmp finish
n5:
strcmp dest, AM
jnz n6
mov dword [op], "101"
jmp finish
n6:
strcmp dest, AD
jnz n7
mov dword [op], "110"
jmp finish
n7:
strcmp dest, ADM
jnz n8
mov dword [op], "111"
n8:
strcmp dest, MD
jnz finish
mov dword [op], "011"
finish:
ret
; OPCODE FOR JUMP FIELD
jumpop:
mov byte [jump + 3], 0
strcmp jump, jJGE
jnz .n1
mov dword [op], "011"
jmp endjump
.n1:
strcmp jump, null
jnz .n2
mov dword [op], "000"
jmp endjump
.n2:
strcmp jump, jJGT
jnz .n3
mov dword [op], "001"
jmp endjump
.n3:
strcmp jump, jJLT
jnz .n4
mov dword [op], "100"
jmp endjump
.n4:
strcmp jump, jJNE
jnz .n5
mov dword [op], "101"
jmp endjump
.n5:
strcmp jump, jJLE
jnz .n6
mov dword [op], "110"
jmp endjump
.n6:
strcmp jump, jJMP
jnz endjump
mov dword [op], "111"
endjump:
ret```