Initial commit

This commit is contained in:
Niles Rogoff 2017-05-15 16:27:14 -04:00
commit 89c8c1f5b7
No known key found for this signature in database
GPG Key ID: B78B908F23430F80
3 changed files with 258 additions and 0 deletions

93
emulator.py Normal file
View File

@ -0,0 +1,93 @@
#!/usr/bin/env python3
asm = open("instructions", "r").read().split("\n")
asm = [[int(part, 16) if part[:2] == "0x" else part for part in instruction.split()] for instruction in asm if len(instruction) > 0 and instruction[0] != "#"]
rs = dict([[item, 0] for item in ["ax", "bx", "cx", "dx", "ip", "sp", "sb", "fl"]])
ram = [0 for i in range(1024)]
def debug():
# prints the registers and the first 10 values in ram
print(rs, end=" ");
print(str(ram[:10])[:-1] + ", ...]")
def handle_three(i):
instruction = i[0]
first = i[1]
second = i[2]
if instruction == "add": rs[second] += rs[first]
if instruction == "addi": rs[second] += first
if instruction == "sub": rs[second] -= rs[first]
if instruction == "subi": rs[second] -= first
if instruction == "mul": rs[second] *= rs[first]
if instruction == "muli": rs[second] *= first
if instruction == "div":
rs[second] /= rs[first]
rs[second] = int(rs[second])
if instruction == "divi":
rs[second] /= first
rs[second] = int(rs[second])
if instruction == "xor": rs[second] ^= rs[first]
if instruction == "xori": rs[second] ^= first
if instruction == "and": rs[second] &= rs[first]
if instruction == "andi": rs[second] &= first
if instruction == "mov": rs[second] = rs[first]
if instruction == "movi": rs[second] = first
if instruction == "test":
if rs[second] == rs[first]: rs["fl"] = 0b001
elif rs[second] < rs[first]: rs["fl"] = 0b010
else: rs["fl"] = 0b100
if instruction == "testi":
if rs[second] == first: rs["fl"] = 0b001
elif rs[second] < first: rs["fl"] = 0b010
else: rs["fl"] = 0b100
if instruction == "lea": rs[second] = ram[rs[first]]
if instruction == "leai": rs[second] = ram[first]
if instruction == "sea": ram[rs[first]] = rs[second]
if instruction == "seai": ram[first] = rs[second]
def handle_two(i):
instruction = i[0]
arg = i[1]
if instruction == "call":
handle_two(["pushi", rs["ip"]])
instruction = "jmp"
if instruction == "je":
if rs["fl"] & 1 > 0: instruction = "jmp"
if instruction == "jne":
if rs["fl"] & 1 == 0: instruction = "jmp"
if instruction == "jl":
if rs["fl"] & 0b100 > 0: instruction = "jmp"
if instruction == "jle":
if rs["fl"] & 0b101 > 0: instruction = "jmp"
if instruction == "jg":
if rs["fl"] & 0b010 > 0: instruction = "jmp"
if instruction == "jge":
if rs["fl"] & 0b011 > 0: instruction = "jmp"
if instruction == "jmp":
rs["ip"] = asm.index([arg + ":"]) - 1
if instruction == "push":
ram[rs["sp"]] = rs[arg]
rs["sp"] += 1
if instruction == "pushi":
ram[rs["sp"]] = arg
rs["sp"] += 1
if instruction == "pop":
rs["sp"] -= 1
rs[arg] = ram[rs["sp"]]
while True:
if rs["ip"] >= len(asm):
print("Out of instructions")
break;
instruction = asm[rs["ip"]]
if len(instruction) == 3: handle_three(instruction)
if len(instruction) == 2: handle_two(instruction)
if len(instruction) == 1:
instruction = instruction[0]
if instruction == "debug":
debug()
if instruction == "abort":
print("Aborted at " + str(rs["ip"]))
break
if instruction == "ret":
rs["sp"] -= 1
rs["ip"] = ram[rs["sp"]]
rs["ip"] += 1
debug()

142
instructions Normal file
View File

@ -0,0 +1,142 @@
jmp main
# takes ax: address to store result in
# takes bx: first number to multiply
# takes cx: second number to multiply
# preserves only ax and bx
multiply:
movi 0x0 dx
multiply_loop:
testi 0x0 cx
je multiply_done
add bx dx
subi 0x1 cx
jmp multiply_loop
multiply_done:
sea ax dx
ret
# takes ax: address to store result in
# takes bx: number to calculate the factorial of
# preserves ax, dx (doesn't use it)
factorial:
push ax
movi 0x1 cx
factorial_loop:
testi 0x1 bx
je factorial_done
mov sp ax
addi 0x1 sp
call multiply
pop cx
subi 0x1 bx
jmp factorial_loop
factorial_done:
pop ax
sea ax cx
ret
# takes ax: address to store result in
# takes bx: number to calculate the factorial of
# preserves ax, dx (doesn't use it)
factorial_recursive:
push ax
movi 0x1 cx
testi 0x1 bx
je fac_rec_end
push bx
mov sp ax
addi 0x1 sp
subi 0x1 bx
call factorial_recursive
pop cx
pop bx
# bx = n
# cx = f(n-1)
mov sp ax
addi 0x1 sp
call multiply
pop cx
# cx = n*f(n-1)
fac_rec_end:
pop ax
sea ax cx
ret
# ax -> pointer to steps accumulator, expects that *ax is initialized to zero
# bx -> input number
hailstone:
testi 0x1 bx
jne hailstone_cont
ret
hailstone_cont:
mov bx cx
andi 0x1 cx
testi 0x0 cx
je hailstone_even
# was odd
muli 0x3 bx
addi 0x1 bx
jmp hailstone_handled
hailstone_even:
# was even
divi 0x2 bx
hailstone_handled:
# add 1 to the step counter
lea ax cx
addi 0x1 cx
sea ax cx
#call hailstone
#ret
# tail call recursion bitch
jmp hailstone
# ax -> pointer to where to store the result
# bx -> input number
# cx -> accumulator, expected to be set to 1 on the first call
factorial_tcr:
testi 0x1 bx
jne factorial_tcr_cont
sea ax cx
ret
factorial_tcr_cont:
mul bx cx
subi 0x1 bx
# tail call recursion
jmp factorial_tcr
main:
# make space on the stack
mov sp ax
addi 0x1 sp
# put 6 on bx and call factorial
movi 0x4 bx
call factorial
# make more space on the stack
mov sp ax
addi 0x1 sp
# put 6 on bx and call factorial_recursive
movi 0x5 bx
call factorial_recursive
# make more space on the stack
mov sp ax
addi 0x1 sp
# put 6 on bx and 1 on cx and call factorial_tcr
movi 0x6 bx
movi 0x1 cx
call factorial_tcr
# make even more space on the stack
mov sp ax
# the steps counter expects to be started at zero
pushi 0x0
# 73 decimal
movi 0x49 bx
call hailstone
# take the number of steps to hailstone 73 (should be 115) and puts it on ax
# takes 6! (calculated using tail call recursion) and puts it on bx (should be 720)
# takes 5! calculated using regular recursion and puts it on cx (should be 120)
# takes 4! calculated iteratively and puts it on dx (should be 24)
pop ax
pop bx
pop cx
pop dx

23
readme.md Normal file
View File

@ -0,0 +1,23 @@
## small assembly interpreter
Made for fun, put assembly code in the file called "instructions" and it will run it
The example "instructions" file I wrote contains three different ways of calculating the factorial of a number (iteratively, recursively and tail call recursively) and a tail call recursive hailstone implementation. It calculates the number of steps to hailstone 73 (0x49), 6!, 5! and 4! and puts them on ax, bx, cx and dx, then exits. The correct answers should be 115, 720, 120 and 24
The registers are ax, bx, cx and dx. The stack pointers are sp and sb (sb is unused), instruction pointer is ip and flags register is fl
The stack starts at zero and grows up, the maximum address in ram is 1023. sp points to the item after the top item on the stack, but if you just use push and pop you won't have to worry about it.
The lea instruction takes a register that holds an address and a register to put the contents of that address into, leai takes a literal address and loads it into the register specified by the second argument. sea and seai both take their arguments in that order as well
The call function is just the jmp instruction but first it pushes the instruction pointer to the stack. The ret instruction just pops an instruction pointer and jumps to it.
test and testi set three bits on the flags register, 001 if they are equal, 010 for greater than and 100 for less than. The conditional jumps include je, jne, jl, jg, jle and jge
At any point you can use the debug instruction to print out the contents of the registers and the first ten values in ram, or the abort instruction to debug then exit.
Registers are callee-preserved.
All integer literals are hexadecimal and start with 0x
Comments start with `#` but any unrecognized instruction is treated as a comment