Skip to content

Commit

Permalink
assembly generation!
Browse files Browse the repository at this point in the history
  • Loading branch information
ssloy committed Jan 14, 2024
1 parent 0c3ba22 commit 88eb4b6
Show file tree
Hide file tree
Showing 6 changed files with 288 additions and 2 deletions.
21 changes: 20 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ PY3 := $(shell command -v python3 2 /dev/null)
PYTHON := $(VENV)/bin/python
INSTALL_STAMP := $(VENV)/.install.stamp

BUILDDIR=build
WENDDIR=test-data
TESTS = $(patsubst $(WENDDIR)/%.wend, $(BUILDDIR)/%.wend, $(wildcard $(WENDDIR)/*.wend))

.PHONY: test run clean

$(PYTHON):
Expand All @@ -25,9 +29,24 @@ test: $(INSTALL_STAMP)
$(PYTHON) -m pytest --cov . -v --cov-report term-missing ./tests/

run: $(INSTALL_STAMP)
@$(PYTHON) compiler.py
@mkdir -p $(BUILDDIR)
@cp -r $(WENDDIR)/* $(BUILDDIR)/
@for WEND in $(TESTS) ; do \
echo $$WEND ; \
EXP=$$(echo $$WEND|sed s/\.wend/\.expected/) ; \
ASM=$$(echo $$WEND|sed s/\.wend/\.s/) ; \
OBJ=$$(echo $$WEND|sed s/\.wend/\.o/) ; \
ELF=$$(echo $$WEND|sed s/\.wend//) ; \
$(PYTHON) compiler.py $$WEND > $$ASM ; \
as --march=i386 --32 -gstabs -o $$OBJ $$ASM ; \
ld -m elf_i386 $$OBJ -o $$ELF ; \
$$ELF | diff $$EXP - ; \
done



clean:
@rm -rf $(BUILDDIR)
rm -rf $(VENV) .pytest_cache .coverage
find . -type f -name *.pyc -delete
find . -type d -name __pycache__ -delete
Expand Down
3 changes: 3 additions & 0 deletions analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def build_symtable(ast):
symtable = SymbolTable()
symtable.add_fun(ast.name, [], ast.deco)
ast.deco['label'] = ast.name + '_' + LabelFactory.new_label() # unique label
ast.deco['strings'] = [] # collection of constant strings from the program
process_scope(ast, symtable)
ast.deco['scope_cnt'] = symtable.scope_cnt # total number of functions, necessary for the static scope display table allocation

Expand Down Expand Up @@ -102,6 +103,8 @@ def process_expr(n, symtable): # process "expression" syntax tree nodes
n.deco['type'] = deco['type']
elif isinstance(n, String): # no type checking is necessary
n.deco['type'] = Type.STRING
n.deco['label'] = LabelFactory.new_label() # unique label for assembly code
symtable.ret_stack[1]['strings'].append((n.deco['label'], n.value))
else:
raise Exception('Unknown expression type', n)

Expand Down
17 changes: 17 additions & 0 deletions compiler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import io, sys
from lexer import WendLexer
from parser import WendParser
from analyzer import *
from transasm import *

if len(sys.argv)!=2:
sys.exit('Usage: compiler.py path/source.wend')
try:
f = open(sys.argv[1], 'r')
tokens = WendLexer().tokenize(f.read())
ast = WendParser().parse(tokens)
build_symtable(ast)
print(transasm(ast))
except Exception as e:
print(e)

2 changes: 1 addition & 1 deletion test-data/mutual-recursion.expected
Original file line number Diff line number Diff line change
@@ -1 +1 @@
odd(-255) = True
odd(-255) = true
76 changes: 76 additions & 0 deletions transasm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from syntree import *
from analyzer import LabelFactory
from transasm_recipe import templates

def transasm(n):
strings = ''.join([templates['ascii'].format(**locals()) for label,string in n.deco['strings']])
display_size = n.deco['scope_cnt']*4
offset = n.deco['scope']*4
main = n.deco['label']
varsize = len(n.var)*4
functions = fun(n)
return templates['program'].format(**locals())

def fun(n):
label = n.deco['label']
nested = ''.join([ fun(f) for f in n.fun ])
body = ''.join([stat(s) for s in n.body])
return f'{label}:\n{body}\n\tret\n{nested}\n'

def stat(n):
if isinstance(n, Print):
newline = templates['print_linebreak'] if n.newline else ''
if n.expr.deco['type']==Type.INT:
return templates['print_int'].format(expr = expr(n.expr), newline = newline)
elif n.expr.deco['type']==Type.BOOL:
return templates['print_bool'].format(expr = expr(n.expr), newline = newline)
elif n.expr.deco['type']==Type.STRING:
return templates['print_string'].format(label = n.expr.deco['label'], newline = newline)
else:
raise Exception('Unknown expression type', n.expr)
elif isinstance(n, Return):
return (expr(n.expr) if n.expr is not None and n.expr.deco['type'] != Type.VOID else '') + '\tret\n'
elif isinstance(n, Assign):
return templates['assign'].format(expression = expr(n.expr),
scope = n.deco['scope']*4,
variable = n.deco['offset']*4)
elif isinstance(n, FunCall):
return expr(n)
elif isinstance(n, While):
return templates['while'].format(condition = expr(n.expr),
label1 = LabelFactory.new_label(),
label2 = LabelFactory.new_label(),
body = ''.join([stat(s) for s in n.body]))
elif isinstance(n, IfThenElse):
return templates['ifthenelse'].format(condition = expr(n.expr),
label1 = LabelFactory.new_label(),
label2 = LabelFactory.new_label(),
ibody = ''.join([stat(s) for s in n.ibody]),
ebody = ''.join([stat(s) for s in n.ebody]))
raise Exception('Unknown statement type', n)

def expr(n): # convention: all expressions save their results to eax
if isinstance(n, ArithOp) or isinstance(n, LogicOp):
args = expr(n.left) + '\tpushl %eax\n' + expr(n.right) + '\tmovl %eax, %ebx\n\tpopl %eax\n'
pyeq1 = {'+':'addl', '-':'subl', '*':'imull', '||':'orl', '&&':'andl'}
pyeq2 = {'<=':'jle', '<':'jl', '>=':'jge', '>':'jg', '==':'je', '!=':'jne'}
if n.op in pyeq1:
return args + f'\t{pyeq1[n.op]} %ebx, %eax\n'
elif n.op in pyeq2:
return args + f'\tcmp %ebx, %eax\n\tmovl $1, %eax\n\t{pyeq2[n.op]} 1f\n\txorl %eax, %eax\n1:\n'
elif n.op=='/':
return args + '\tcdq\n\tidivl %ebx, %eax\n'
elif n.op=='%':
return args + '\tcdq\n\tidivl %ebx, %eax\n\tmovl %edx, %eax\n'
raise Exception('Unknown binary operation')
elif isinstance(n, Integer) or isinstance(n, Boolean):
return f'\tmovl ${int(n.value)}, %eax\n'
elif isinstance(n, Var):
return templates['var'].format(scope = n.deco['scope']*4, variable = n.deco['offset']*4)
elif isinstance(n, FunCall):
return templates['funcall'].format(allocargs = ''.join(['%s\tpushl %%eax\n' % expr(a) for a in n.args]),
varsize = len(n.deco['fundeco']['local'])*4,
disphead = len(n.deco['fundeco']['local'])*4 + len(n.args)*4 - 4,
scope = n.deco['fundeco']['scope']*4,
funlabel = n.deco['fundeco']['label'])
raise Exception('Unknown expression type', n)
171 changes: 171 additions & 0 deletions transasm_recipe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
templates = {
'ascii' : '''
{label}:
.ascii "{string}"
{label}_len = . - {label}
''',

'var' : '''
movl display+{scope}, %eax
movl -{variable}(%eax), %eax
''',

'print_linebreak' : '''
pushl $10
call print_char
addl $4, %esp
''',

'print_int' : '''
{expr}
pushl %eax
call print_int32
addl $4, %esp
{newline}
''',

'print_string' : '''
movl $4, %eax
movl $1, %ebx
movl ${label}, %ecx
movl ${label}_len, %edx
int $0x80
{newline}
''',

'print_bool' : '''
{expr}
movl $truestr, %ecx
movl $truestr_len, %edx
test %eax, %eax
jnz 1f
movl $falsestr, %ecx
movl $falsestr_len, %edx
1:
movl $4, %eax
movl $1, %ebx
int $0x80
{newline}
''',

'assign' : '''
{expression}
pushl %eax
movl display+{scope}, %eax
popl %ebx
movl %ebx, -{variable}(%eax)
''',

'ifthenelse' : '''
{condition}
test %eax, %eax
jz {label1}
{ibody}
jmp {label2}
{label1}:
{ebody}
{label2}:
''',

'while' : '''
{label1}:
{condition}
test %eax, %eax
jz {label2}
{body}
jmp {label1}
{label2}:
''',

'funcall' : '''
pushl display+{scope}
{allocargs}
subl ${varsize}, %esp
leal {disphead}(%esp), %eax
movl %eax, display+{scope}
call {funlabel}
movl display+{scope}, %esp
addl $4, %esp
popl display+{scope}
''',

'program' : '''
.global _start
.data
{strings}
truestr:
.ascii "true"
truestr_len = . - truestr
falsestr:
.ascii "false"
falsestr_len = . - falsestr
.align 2
display:
.skip {display_size}
.text
_start:
leal -4(%esp), %eax
movl %eax, display+{offset}
subl ${varsize}, %esp # allocate locals
call {main}
addl ${varsize}, %esp # deallocate locals
_end: # do not care about clearing the stack
movl $1, %eax # _exit system call (check asm/unistd_32.h for the table)
movl $0, %ebx # error code 0
int $0x80 # make system call
{functions}
print_int32:
pushl %ebp # save the old base pointer value
movl %esp, %ebp # set the new base pointer value
movl 8(%ebp), %eax # the number to print
test %eax, %eax # if %eax>0 jump
jns abs_result
neg %eax # otherwise negate %eax
pushl $45
call print_char # print '-'
addl $4, %esp
abs_result:
movl %esp, %ecx # buffer for the string to print
subl $16, %esp # max 10 digits for a 32-bit number (keep %esp dword-aligned)
movl $10, %ebx # base 10
loop: # repeat
cdq # xorl %edx, %edx # %edx = 0
idivl %ebx # %eax = %edx:%eax/10 ; %edx = %edx:%eax % 10
decl %ecx # allocate one more digit
addb $48, %dl # %edx += '0' # 0,0,0,0,0,0,0,0,0,0,'1','2','3','4','5','6'
movb %dl, (%ecx) # store the digit # ^ ^ ^
test %eax, %eax # # %esp %ecx (after) %ecx (before)
jnz loop # until %eax==0 # <----- %edx = 6 ----->
movl $4, %eax # write system call
movl $1, %ebx # stdout
leal 16(%esp), %edx # the buffer to print
subl %ecx, %edx # number of digits
int $0x80 # make system call
addl $16, %esp # deallocate the buffer
movl %ebp, %esp # standard function footer
popl %ebp
ret
print_char:
pushl %ebp # save the old base pointer value
movl %esp, %ebp # set the new base pointer value
pushl %eax
pushl %ebx
pushl %ecx
pushl %edx
movl $4, %eax # write system call
movl $1, %ebx # stdout
leal 8(%ebp), %ecx # address of the character
movl $1, %edx # one byte
int $0x80 # make system call
popl %edx
popl %ecx
popl %ebx
popl %eax
movl %ebp, %esp
popl %ebp
ret
'''
}

0 comments on commit 88eb4b6

Please sign in to comment.