commit 89c8c1f5b7a98ccb234c4e4f05ca949b6e9694ce Author: Niles Rogoff Date: Mon May 15 16:27:14 2017 -0400 Initial commit diff --git a/emulator.py b/emulator.py new file mode 100644 index 0000000..5a69d6f --- /dev/null +++ b/emulator.py @@ -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() diff --git a/instructions b/instructions new file mode 100644 index 0000000..4e88928 --- /dev/null +++ b/instructions @@ -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 diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..15060e9 --- /dev/null +++ b/readme.md @@ -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