Skip to content

Instantly share code, notes, and snippets.

@icebreaker
Last active November 14, 2015 11:58
Show Gist options
  • Save icebreaker/3c260812b94ff434c303 to your computer and use it in GitHub Desktop.
Save icebreaker/3c260812b94ff434c303 to your computer and use it in GitHub Desktop.
A small "experimental" VM written in Ruby.
;
; Prints first N from the Fibonacci sequence.
;
read_n:
mov dx, 78
prc dx
mov dx, 58
prc dx
rdr dx
cmp dx, 0
je read_n
mov cx, 0
mov ax, 0
mov bx, 1
loop:
prr bx
push bx
add bx, ax
pop ax
add cx, 1
cmp cx, dx
jne loop
;
; Prints a NxM matrix of 'X's
;
read_x:
mov ax, 88
prc ax
mov ax, 58
prc ax
rdr ax
cmp ax, 0
je read_x
read_y:
mov bx, 89
prc bx
mov bx, 58
prc bx
rdr bx
cmp bx, 0
je read_y
mov cx, 0
mov dx, 0
draw_y:
push dx
draw_x:
mov dx, 88
prc dx
mov dx, 32
prc dx
add cx, 1
cmp cx, ax
jne draw_x
mov dx, 10
prc dx
xor cx, cx
pop dx
add dx, 1
cmp dx, bx
jne draw_y
push ax
mov ax, 10
prc ax
pop ax
#!/usr/bin/env ruby
class VM
REG_0 = 0x00
REG_1 = 0x01
REG_2 = 0x02
REG_3 = 0x03
REG_4 = 0x04
REG_N = 0x05
OP_NOP = 0x00
OP_PUSH = 0x01
OP_POP = 0x02
OP_LDR = 0x03
OP_MOV = 0x04
OP_ADD = 0x05
OP_SUB = 0x06
OP_CMP = 0x07
OP_JNE = 0x08
OP_JE = 0x09
OP_JMP = 0x10
OP_XOR = 0x11
OP_RDR = 0x12
OP_PRR = 0x13
OP_PRC = 0x14
OP_RDC = 0x15
attr_reader :registers
def initialize
@registers = [].fill(0x00, 0x00, REG_N)
@stack = []
end
def eval(bytecode)
i = 0
c = 0
l = bytecode.length - 3
while i <= l
case bytecode[i+0] # op
when OP_NOP # nop
# nop
when OP_PUSH # push r1
@stack.push(@registers[bytecode[i+1]])
when OP_POP # pop r1
@registers[bytecode[i+1]] = @stack.pop
when OP_LDR # ldr r1, 10
@registers[bytecode[i+1]] = bytecode[i+2]
when OP_MOV # mov r1, r2
@registers[bytecode[i+1]] = @registers[bytecode[i+2]]
when OP_ADD # add r1, r2
@registers[bytecode[i+1]] += @registers[bytecode[i+2]]
when OP_SUB # sub r1, r2
@registers[bytecode[i+1]] -= @registers[bytecode[i+2]]
when OP_CMP # cmp r1, r2
c = ((@registers[bytecode[i+1]] == @registers[bytecode[i+2]]) ? 1 : 0)
when OP_JNE # jne addr
i = bytecode[i+1] if c == 0
when OP_JE # je addr
i = bytecode[i+1] if c == 1
when OP_JMP # jmp addr
i = bytecode[i+1]
when OP_XOR # xor r1, r2
@registers[bytecode[i+1]] ^= @registers[bytecode[i+2]]
when OP_RDR # rdr r1
@registers[bytecode[i+1]] = STDIN.gets.chomp.to_i
when OP_PRR # prr r1
STDOUT.puts(@registers[bytecode[i+1]])
when OP_PRC # prc r1
STDOUT.putc(@registers[bytecode[i+1]])
when OP_RDC # rdc r1
@registers[bytecode[i+1]] = STDIN.getc.ord
else
raise ArgumentError, "Unsupported opcode: #{bytecode[i]}"
end
i += 3
end
end
end
class ASM
attr_reader :source, :bytecode
def initialize(source)
@source = source
@bytecode = []
compile!
end
private
REGS = {
'ax' => VM::REG_1,
'bx' => VM::REG_2,
'cx' => VM::REG_3,
'dx' => VM::REG_4
}
OPS = {
'nop' => VM::OP_NOP,
'push' => VM::OP_PUSH,
'pop' => VM::OP_POP,
'ldr' => VM::OP_LDR,
'mov' => VM::OP_MOV,
'add' => VM::OP_ADD,
'sub' => VM::OP_SUB,
'cmp' => VM::OP_CMP,
'jne' => VM::OP_JNE,
'je' => VM::OP_JE,
'jmp' => VM::OP_JMP,
'xor' => VM::OP_XOR,
'rdr' => VM::OP_RDR,
'prr' => VM::OP_PRR,
'prc' => VM::OP_PRC,
'rdc' => VM::OP_RDC
}
def compile!
labels = {}
@source.split("\n").each_with_index do |line, index|
line.strip!
next if line.length == 0 or line[0] == ';'
if line =~ /^(.*?)\s+(.*?)(\s*,\s*(.*?))?$/
op = OPS[$1.downcase]
raise ArgumentError, "Invalid operator: #{$1} on line #{index+1}" unless op
if $4
r1 = REGS[$2.downcase]
raise ArgumentError, "Invalid register: #{$2} on line #{index+1}" unless r1
if $4[1] == 'x'
r2 = REGS[$4.downcase]
raise ArgumentError, "Invalid register: #{$4} on line #{index+1}" unless r2
else
r2 = VM::REG_0
@bytecode += [VM::OP_LDR, r2, $4.to_i]
end
else
case op
when VM::OP_JNE, VM::OP_JE, VM::OP_JMP
r1 = labels[$2.downcase]
raise ArgumentError, "Invalid label: #{$2} on line #{index+1}" unless r1
else
r1 = REGS[$2.downcase]
raise ArgumentError, "Invalid register: #{$2} on line #{index+1}" unless r1
end
r2 = VM::OP_NOP
end
@bytecode += [op, r1, r2]
elsif line =~ /^(.*?):$/
labels[$1.strip] = @bytecode.length - 3
else
raise ArgumentError, "Invalid syntax: #{line} on line #{index + 1}"
end
end
end
end
if ARGV.size > 0
VM.new.eval(ASM.new(File.read(ARGV[0])).bytecode)
else
STDOUT.puts "usage: %s file.asm" % File.basename(__FILE__)
end
@texrg
Copy link

texrg commented Nov 14, 2015

This is interpreter.Is possible to create assembler?
convertassembler code to normal code for linux 32? http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html

creating working ELF binary from scratch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment