diff --git a/Makefile b/Makefile
index 28630d7..623d5c8 100644
--- a/Makefile
+++ b/Makefile
@@ -10,8 +10,8 @@ WENDDIR = test-programs
 TESTS = $(shell find $(WENDDIR) -name '*.wend' -not -path '*/gfx/*')
 
 GFXDIR = $(WENDDIR)/gfx
+GFXLL  = $(patsubst $(GFXDIR)/%.wend, $(BUILDDIR)/%.ll,  $(wildcard $(GFXDIR)/*.wend))
 GFXASM = $(patsubst $(GFXDIR)/%.wend, $(BUILDDIR)/%.s,   $(wildcard $(GFXDIR)/*.wend))
-GFXOBJ = $(patsubst $(GFXDIR)/%.wend, $(BUILDDIR)/%.o,   $(wildcard $(GFXDIR)/*.wend))
 GFXEXE = $(patsubst $(GFXDIR)/%.wend, $(BUILDDIR)/%.exe, $(wildcard $(GFXDIR)/*.wend))
 
 .PHONY: all test clean
@@ -28,26 +28,26 @@ test: $(BUILDDIR)
 		mkdir -p $$DSTDIR ; \
 		echo -n Testing $$WEND... ;\
 		EXP=$$(echo $$WEND|sed s/\.wend/\.expected/) ; \
+		LL=$$DSTDIR/$$(basename $$WEND|sed s/\.wend/\.ll/) ; \
 		ASM=$$DSTDIR/$$(basename $$WEND|sed s/\.wend/\.s/) ; \
-		OBJ=$$DSTDIR/$$(basename $$WEND|sed s/\.wend/\.o/) ; \
 		ELF=$$DSTDIR/$$(basename $$WEND|sed s/\.wend//) ; \
-		python3 compiler.py $$WEND > $$ASM ; \
-		as --march=i386 --32 -gstabs -o $$OBJ $$ASM ; \
-		ld -m elf_i386 $$OBJ  -o $$ELF ; \
+		python3 compiler.py $$WEND > $$LL ; \
+		llc $$LL ; \
+		gcc $$ASM -o $$ELF ; \
 		$$ELF | diff $$EXP - ; \
 		echo ' ok' ; \
 	done
 
-$(BUILDDIR)/%.exe: $(BUILDDIR)/%.o
-	ld -m elf_i386 $< -o $@
+$(BUILDDIR)/%.exe: $(BUILDDIR)/%.s
+	gcc -o $@ $<
 
-$(BUILDDIR)/%.o: $(BUILDDIR)/%.s
-	as --march=i386 --32 -o $@ $<
+$(BUILDDIR)/%.s: $(BUILDDIR)/%.ll
+	llc $<
 
-$(BUILDDIR)/%.s: $(GFXDIR)/%.wend
+$(BUILDDIR)/%.ll: $(GFXDIR)/%.wend
 	python3 compiler.py $< > $@
 
-gfx: $(BUILDDIR) $(GFXASM) $(GFXOBJ) $(GFXEXE)
+gfx: $(BUILDDIR) $(GFXLL) $(GFXASM) $(GFXEXE)
 
 clean:
 	rm -rf $(BUILDDIR)
diff --git a/analyzer.py b/analyzer.py
index a0ac8c8..9c9d74d 100644
--- a/analyzer.py
+++ b/analyzer.py
@@ -1,96 +1,74 @@
 from syntree import *
 from symtable import *
 
-class LabelFactory: # this is a suffix to add to all function names
-    counter = 0     # in particular, it is useful for function overloading
-    @staticmethod   # it is also useful for different goto labels (loops, conditional statements etc) in assembly code
-    def new_label():
-        LabelFactory.counter += 1
-        return "uniqstr%d" % LabelFactory.counter
-
-def build_symtable(ast):
+def decorate(ast):
     if not isinstance(ast, Function) or ast.name != 'main' or ast.deco['type'] != Type.VOID or len(ast.args)>0:
         raise Exception('Cannot find a valid entry point')
     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
+    ast.deco['strings'] = set()  # collection of constant strings from the program
+    process_scope(ast, symtable) # N.B.: the decoration is made in two passes, necessary to propagate context to the function calls
+    process_scope(ast, symtable) # for the functions defined later on
 
 def process_scope(fun, symtable):
-    fun.deco['local'] = []       # set of local variable names: len*4 is the memory necessary on the stack, the names are here to be put in comments
     symtable.push_scope(fun.deco)
     for v in fun.args: # process function arguments
-        symtable.add_var(*v)
+        symtable.add_var(v.name, v.deco)
     for v in fun.var:  # process local variables
-        symtable.add_var(*v)
-        fun.deco['local'].append(v[0])
+        symtable.add_var(v.name, v.deco)
     for f in fun.fun:  # process nested functions: first add function symbols to the table
-        symtable.add_fun(f.name, [d['type'] for v,d in f.args], f.deco)
-        f.deco['label'] = f.name + '_' + LabelFactory.new_label() # still need unique labels
+        symtable.add_fun(f.name, [v.deco['type'] for v in f.args], f.deco)
     for f in fun.fun:  # then process nested function bodies
         process_scope(f, symtable)
     for s in fun.body: # process the list of statements
-        process_stat(s, symtable)
+        process_instruction(s, symtable)
     symtable.pop_scope()
 
-def process_stat(n, symtable): # process "statement" syntax tree nodes
+def process_instruction(n, symtable):
     match n:
-        case Print(): # no type checking is necessary
-            process_expr(n.expr, symtable)
+        case Print():                       # no type checking is necessary
+            process_instruction(n.expr, symtable)
         case Return():
-            if n.expr is None: return
-            process_expr(n.expr, symtable)
+            if n.expr is None: return # TODO semantic check for return; in non-void functions
+            process_instruction(n.expr, symtable)
             if symtable.ret_stack[-1]['type'] != n.expr.deco['type']:
                 raise Exception('Incompatible types in return statement, line %s', n.deco['lineno'])
         case Assign():
-            process_expr(n.expr, symtable)
-            deco = symtable.find_var(n.name)
-            n.deco |= { 'scope':deco['scope'], 'offset':deco['offset'], 'type':deco['type'] }
+            process_instruction(n.expr, symtable)
+            n.deco |= symtable.find_var(n.name)
             if n.deco['type'] != n.expr.deco['type']:
                 raise Exception('Incompatible types in assignment statement, line %s', n.deco['lineno'])
-        case FunCall(): # no type checking is necessary
-            process_expr(n, symtable)
         case While():
-            process_expr(n.expr, symtable)
+            process_instruction(n.expr, symtable)
             if n.expr.deco['type'] != Type.BOOL:
                 raise Exception('Non-boolean expression in while statement, line %s', n.deco['lineno'])
             for s in n.body:
-                process_stat(s, symtable)
+                process_instruction(s, symtable)
         case IfThenElse():
-            process_expr(n.expr, symtable)
+            process_instruction(n.expr, symtable)
             if n.expr.deco['type'] != Type.BOOL:
                 raise Exception('Non-boolean expression in if statement, line %s', n.deco['lineno'])
             for s in n.ibody + n.ebody:
-                process_stat(s, symtable)
-        case other: raise Exception('Unknown statement type')
-
-def process_expr(n, symtable): # process "expression" syntax tree nodes
-    match n:
+                process_instruction(s, symtable)
         case ArithOp():
-            process_expr(n.left,  symtable)
-            process_expr(n.right, symtable)
+            process_instruction(n.left,  symtable)
+            process_instruction(n.right, symtable)
             if n.left.deco['type'] != Type.INT or n.right.deco['type'] != Type.INT:
                 raise Exception('Arithmetic operation over non-integer type in line %s', n.deco['lineno'])
         case LogicOp():
-            process_expr(n.left,  symtable)
-            process_expr(n.right, symtable)
+            process_instruction(n.left,  symtable)
+            process_instruction(n.right, symtable)
             if (n.left.deco['type'] != n.right.deco['type']) or \
                (n.op in ['<=', '<', '>=', '>'] and n.left.deco['type'] != Type.INT) or \
                (n.op in ['&&', '||'] and n.left.deco['type'] != Type.BOOL):
                 raise Exception('Boolean operation over incompatible types in line %s', n.deco['lineno'])
-        case Var(): # no type checking is necessary
-            deco = symtable.find_var(n.name)
-            n.deco |= { 'scope':deco['scope'], 'offset':deco['offset'], 'type':deco['type'] }
+        case Var():                         # no type checking is necessary
+            n.deco |= symtable.find_var(n.name)
         case FunCall():
             for s in n.args:
-                process_expr(s, symtable)
-            deco = symtable.find_fun(n.name, [a.deco['type'] for a in n.args])
-            n.deco['fundeco'] = deco # save the function symbol, useful for overloading and for stack preparation
-            n.deco['type']    = deco['type']
-        case String(): # no type checking is necessary
-            n.deco['label'] = LabelFactory.new_label() # unique label for assembly code
-            symtable.ret_stack[1]['strings'].append((n.deco['label'], n.value))
-        case Integer() | Boolean(): pass # no type checking is necessary
-        case other: raise Exception('Unknown expression type', n)
+                process_instruction(s, symtable)
+            n.deco |= symtable.find_fun(n.name, [ a.deco['type'] for a in n.args ])
+        case String():                      # no type checking is necessary
+            symtable.ret_stack[1]['strings'].add((n.deco['label'], n.value))
+        case Integer() | Boolean(): pass    # no type checking is necessary
+        case other: raise Exception('Unknown instruction', n)
diff --git a/cfg.py b/cfg.py
new file mode 100644
index 0000000..5573858
--- /dev/null
+++ b/cfg.py
@@ -0,0 +1,42 @@
+class Instruction:
+    def __init__(self, string, t=None, a=None, b=None, c=None):
+        self.string, self.t, self.a, self.b, self.c = string, t, a, b, c # string, type and three parameters
+
+    def __repr__(self):
+        return self.string.format(a=self.a, b=self.b, c=self.c, t=self.t)
+
+class Phi:
+    def __init__(self, reg, choice=None):
+        self.reg, self.choice = reg, choice or {}
+
+    def __repr__(self):
+        choice = ', '.join([ f'[{value}, {block}]' for block, value in self.choice.items() ])
+        return f'{self.reg} = phi {choice}'
+
+class BasicBlock:
+    def __init__(self, label):
+        self.label = label
+        self.phi_functions, self.instructions  = [], []
+        self.successors, self.predecessors = set(), set()
+
+    def find_and_replace(self, find, replace):
+        for i in self.instructions:
+            i.a, i.b, i.c = ( replace if s==find else s for s in (i.a, i.b, i.c) )
+
+    def __repr__(self):
+        return f'{self.label}:\n' + \
+                ''.join( [ f'{phi}\n' for phi in self.phi_functions ] ) + \
+                ''.join( [ f'{i}\n'   for i   in self.instructions  ] )
+
+class ControlFlowGraph:
+    def __init__(self, header, footer):
+        self.header, self.footer, self.blocks, self.last = header, footer, {}, None
+
+    def add_block(self, label):
+        self.blocks[label] = BasicBlock(label)
+        self.last = self.blocks[label]
+
+    def __repr__(self):
+        return f'{self.header}\n' + \
+               ''.join( [ f'{block}' for block in self.blocks.values() ] ) + \
+               f'{self.footer}\n'
diff --git a/compiler.py b/compiler.py
index 2d380cf..9eae645 100644
--- a/compiler.py
+++ b/compiler.py
@@ -2,15 +2,18 @@
 from lexer import WendLexer
 from parser import WendParser
 from analyzer import *
-from transasm import *
+#from transasm import *
+#from transllvm import *
+from ir import ir
 
 if len(sys.argv)!=2:
     sys.exit('Usage: compiler.py path/source.wend')
-try:
+if True:
+#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)
+    decorate(ast)
+    print(ir(ast))
+#except Exception as e:
+#    print(e)
diff --git a/ir.py b/ir.py
new file mode 100644
index 0000000..75bf546
--- /dev/null
+++ b/ir.py
@@ -0,0 +1,117 @@
+from syntree import *
+from cfg import *
+
+class IR:
+    def __init__(self, prog):
+        self.prog, self.fun = prog, []
+
+    def __repr__(self):
+        return self.prog + ''.join([str(f) for f in self.fun])
+
+def ir(n):
+    data  = ''.join(['@{label} = constant [{size} x i8] c"{string}\\00"\n'.format(
+                    label  = label,
+                    size   = len(string)+1,                               # +1 for null termination
+                    string = ''.join(['\\%02x' % ord(x) for x in string]) # hex escape the string
+                ) for label,string in n.deco['strings']])
+    entry = n.deco['label']
+    prog  = f'''declare i32 @printf(i8*, ...)
+@newline = constant [ 2 x i8] c"\\0a\\00"
+@integer = constant [ 3 x i8] c"%d\\00"
+@string  = constant [ 3 x i8] c"%s\\00"
+@bool    = constant [11 x i8] c"false\\00true\\00"
+{data}
+define i32 @main() {{
+	call void @{entry}()
+	ret i32 0
+}}
+'''
+    ir = IR(prog)
+    fun(n, ir)
+    return ir
+
+def lltype(t):
+    return ['void', 'i32', 'i1'][t]
+
+def fun(n, ir):
+    label   = n.deco['label']
+    args    = ', '.join([ '{t}* %{n}'   .format(n = a[0], t = lltype(a[1])) for a in n.deco['nonlocal'] ] +
+                        [ '{t} %{n}.arg'.format(n = a.deco['fullname'], t = lltype(a.deco['type'])) for a in n.args ])
+    rettype = lltype(n.deco['type'])
+    retval  = '\tret void\n' if n.deco['type']==Type.VOID else '\tunreachable\n'
+    cfg = ControlFlowGraph(f'define {rettype} @{label}({args}) {{', f'{retval}}}')
+    cfg.add_block('0')
+    for v in n.args + n.var:
+        cfg.last.instructions.append( Instruction('\t%{a} = alloca {t}', lltype(v.deco['type']), v.deco['fullname']) )
+    for v in n.args:
+        cfg.last.instructions.append( Instruction('\tstore {t} %{a}.arg, {t}* %{a}', lltype(v.deco['type']), v.deco['fullname']) )
+    for f in n.fun: fun(f, ir)
+    for s in n.body: stat(n, cfg)
+    ir.fun.append(cfg)
+
+def stat(n, cfg):
+    pass
+
+'''
+def stat(n):
+    match n:
+        case Print():
+            match n.expr.deco['type']:
+                case Type.INT:
+                    asm = templates['print_int'].format(expr = stat(n.expr), label = LabelFactory.cur_label())
+                case Type.BOOL:
+                    asm = templates['print_bool'].format(expr = stat(n.expr), label = LabelFactory.cur_label())
+                case Type.STRING:
+                    asm = templates['print_string'].format(label = n.expr.deco['label'])
+                case other: raise Exception('Unknown expression type', n.expr)
+            return asm + (templates['print_newline'] if n.newline else '')
+        case Return():
+            return templates['return'].format(expr    = stat(n.expr) if n.expr else '',
+                                              rettype = lltype(n.expr.deco['type']) if n.expr else 'void',
+                                              label   = LabelFactory.cur_label() if n.expr else '')
+        case Assign():
+            return templates['assign'].format(expr    = stat(n.expr),
+                                              label   = LabelFactory.cur_label(),
+                                              vartype = lltype(n.deco['type']),
+                                              varname = n.deco['fullname'])
+        case While():
+            return templates['while'].format(expr   = stat(n.expr),
+                                             label1 = LabelFactory.cur_label(),
+                                             label2 = LabelFactory.new_label(),
+                                             body   = ''.join([stat(s) for s in n.body]))
+        case IfThenElse():
+            return templates['ifthenelse'].format(expr   = stat(n.expr),
+                                                  label1 = LabelFactory.cur_label(),
+                                                  label2 = LabelFactory.new_label(),
+                                                  ibody  = ''.join([stat(s) for s in n.ibody]),
+                                                  ebody  = ''.join([stat(s) for s in n.ebody]))
+        case ArithOp() | LogicOp():
+            e1, l1 = stat(n.left),  LabelFactory.cur_label()
+            e2, l2 = stat(n.right), LabelFactory.cur_label()
+            t = lltype(n.left.deco['type'])
+            op = {'+':'add', '-':'sub', '*':'mul', '/':'sdiv', '%':'srem','||':'or', '&&':'and','<=':'icmp sle', '<':'icmp slt', '>=':'icmp sge', '>':'icmp sgt', '==':'icmp eq', '!=':'icmp ne'}
+            l = LabelFactory.new_label()
+            return f'{e1}{e2}\t%{l} = {op[n.op]} {t} %{l1}, %{l2}\n';
+        case Integer():
+            label = LabelFactory.new_label()
+            return f'\t%{label} = add i32 0, {n.value}\n'
+        case Boolean():
+            label = LabelFactory.new_label()
+            return f'\t%{label} = or i1 0, {int(n.value)}\n'
+        case Var():
+            label = LabelFactory.new_label()
+            vartype = 'i1' if n.deco['type']==Type.BOOL else 'i32'
+            return f'\t%{label} = load {vartype}, {vartype}* %{n.deco["fullname"]}\n'
+        case FunCall():
+            arg_bodies = ''
+            args = []
+            for e in n.args:
+                arg_bodies += stat(e)
+                args.append(lltype(e.deco['type']) + ' %' + LabelFactory.cur_label())
+            args = ', '.join([ '{t}* %{n}'.format(n = a[0], t = lltype(a[1])) for a in n.deco['nonlocal'] ] + args)
+            rettype = lltype(n.deco['type'])
+            label1 = n.deco['label']
+            lvalue = '' if rettype=='void' else '%' + LabelFactory.new_label() + ' = '
+            return f'{arg_bodies}\t{lvalue}call {rettype} @{label1}({args})\n'
+        case other: raise Exception('Unknown instruction', n)
+'''
diff --git a/lexer.py b/lexer.py
index 7169385..dff8bb1 100644
--- a/lexer.py
+++ b/lexer.py
@@ -55,7 +55,7 @@ def tokenize(self, text):
                 if sym1 != '"' or accum and accum[-1]=='\\':        # if not quote mark (or if escaped quote mark),
                     accum += sym1                                   # continue the scan
                 else:
-                    yield Token('STRING', accum, lineno)            # otherwise emit the token
+                    yield Token('STRING', accum.encode().decode('unicode-escape'), lineno) # otherwise emit the token
                     state, accum = 0, '' # start new scan
             if sym1 == '\n':
                 lineno += 1
diff --git a/parser.py b/parser.py
index 0e72f55..f29804a 100644
--- a/parser.py
+++ b/parser.py
@@ -19,7 +19,7 @@ def __eq__(self, other):
 class WendParser: # the grammar is a list of triplets [nonterminal, production rule, AST node constructor]
     grammar = [['fun',            ['fun_type', 'ID', 'LPAREN', 'param_list', 'RPAREN', 'BEGIN', 'var_list', 'fun_list', 'statement_list', 'END'],
                                                                                                       lambda p: Function(p[1].value, p[3], p[6], p[7], p[8], {'type':p[0], 'lineno':p[1].lineno})],
-               ['var',            ['TYPE', 'ID'],                                                     lambda p: (p[1].value, {'type':Type.INT if p[0].value=='int' else Type.BOOL, 'lineno':p[0].lineno})],
+               ['var',            ['TYPE', 'ID'],                                                     lambda p: Var(p[1].value, {'type':Type.INT if p[0].value=='int' else Type.BOOL, 'lineno':p[0].lineno})],
                ['param_list',     ['var'],                                                            lambda p: p],
                ['param_list',     [],                                                                 lambda p: p],
                ['param_list',     ['param_list', 'COMMA', 'var'],                                     lambda p: p[0] + [ p[2] ]],
@@ -49,7 +49,7 @@ class WendParser: # the grammar is a list of triplets [nonterminal, production r
                ['conjunction',    ['literal'],                                                        lambda p: p[0]],
                ['conjunction',    ['conjunction', 'AND', 'literal'],                                  lambda p: LogicOp(p[1].value, p[0], p[2], {'lineno':p[1].lineno})],
                ['literal',        ['comparand'],                                                      lambda p: p[0]],
-               ['literal',        ['NOT', 'comparand'],                                               lambda p: LogicOp('==', Boolean(False), p[1], {'lineno':p[0].lineno})],
+               ['literal',        ['NOT', 'comparand'],                                               lambda p: LogicOp('==', Boolean(False, {}), p[1], {'lineno':p[0].lineno})],
                ['comparand',      ['addend'],                                                         lambda p: p[0]],
                ['comparand',      ['addend', 'COMP', 'addend'],                                       lambda p: LogicOp(p[1].value, p[0], p[2], {'lineno':p[1].lineno})],
                ['addend',         ['term'],                                                           lambda p: p[0]],
@@ -61,7 +61,7 @@ class WendParser: # the grammar is a list of triplets [nonterminal, production r
                ['term',           ['term', 'TIMES', 'factor'],                                        lambda p: ArithOp(p[1].value, p[0], p[2], {'lineno':p[1].lineno})],
                ['factor',         ['atom'],                                                           lambda p: p[0]],
                ['factor',         ['PLUS', 'atom'],                                                   lambda p: p[1]],
-               ['factor',         ['MINUS', 'atom'],                                                  lambda p: ArithOp('-', Integer(0), p[1], {'lineno':p[0].lineno})],
+               ['factor',         ['MINUS', 'atom'],                                                  lambda p: ArithOp('-', Integer(0, {}), p[1], {'lineno':p[0].lineno})],
                ['atom',           ['BOOLEAN'],                                                        lambda p: Boolean(p[0].value=='true', {'lineno':p[0].lineno})],
                ['atom',           ['INTEGER'],                                                        lambda p: Integer(int(p[0].value), {'lineno':p[0].lineno})],
                ['atom',           ['ID', 'LPAREN', 'arg_list', 'RPAREN'],                             lambda p: FunCall(p[0].value, p[2], {'lineno':p[0].lineno})],
diff --git a/ssa.py b/ssa.py
new file mode 100644
index 0000000..1c6fa8f
--- /dev/null
+++ b/ssa.py
@@ -0,0 +1,205 @@
+class Instruction:
+    def __init__(self, string, t=None, a=None, b=None, c=None):
+        self.string, self.t, self.a, self.b, self.c = string, t, a, b, c # string, type and three parameters
+
+    def __repr__(self):
+        return self.string.format(a=self.a, b=self.b, c=self.c, t=self.t)
+
+class Phi:
+    def __init__(self, reg, choice=None):
+        self.reg, self.choice = reg, choice or {}
+
+    def __repr__(self):
+        choice = ', '.join([ f'[{value}, {block}]' for block, value in self.choice.items() ])
+        return f'{self.reg} = phi {choice}'
+
+class BasicBlock:
+    def __init__(self, label):
+        self.label = label
+        self.phi_functions, self.instructions  = [], []
+        self.successors, self.predecessors = set(), set()
+
+    def find_and_replace(self, find, replace):
+        for i in self.instructions:
+            i.a, i.b, i.c = ( replace if s==find else s for s in (i.a, i.b, i.c) )
+
+    def __repr__(self):
+        return f'{self.label}:\n' + \
+                ''.join( [ f'{phi}\n' for phi in self.phi_functions ] ) + \
+                ''.join( [ f'{i}\n'   for i   in self.instructions  ] )
+
+class ControlFlowGraph:
+    def __init__(self):
+        self.blocks = {}
+        self.dominance_frontier = {}                                #  dominance_frontier[v] is a set of blocks, control flow join points (where two or more potential definitions can come together)
+
+    def add_block(self, label):
+        self.blocks[label] = BasicBlock(label)
+
+    def compute_adjacency(self):                                    # TODO do we need to reset successors and predecessors?
+        for b1 in self.blocks.values():
+        # TODO if we have ret inside the block, no need to create an outgoing edge
+            i = b1.instructions[-1]                                 # last instruction, most often a jump or branch
+            if 'br' not in i.string: continue                          # but it can also be a ret instruction or even unreachable
+            for succ in [i.a] if 'br label' in i.string else [i.b, i.c]:
+                b2 = self.blocks[succ]
+                b1.successors.add(b2)
+                b2.predecessors.add(b1)
+
+    def __repr__(self):
+        return ''.join( [ f'{block}' for block in self.blocks.values() ] )
+
+    def compute_dominance_frontier(self):
+        def dfs(block, visited, postorder):
+            visited.add(block)
+            for b in block.successors:
+                if b not in visited: dfs(b, visited, postorder)
+            postorder.append(block)
+        postorder = []                                              # for efficiency reasons, we visit blocks in a way that ensures
+        dfs(self.blocks['entry'], set(), postorder)                 # processing of each block after its predecessors have been processed
+
+        dom = { b : set(self.blocks.values()) for b in self.blocks.values() }
+        dom[ self.blocks['entry'] ] = { self.blocks['entry'] }      # dom[b] contains every block that dominates b
+        changed = True
+        while changed:                                              # the iterative dominator algorithm [Cooper, Harvey and Kennedy, 2006]
+            changed = False
+            for b in postorder[-2::-1]:                             # for all blocks (except the source) in reverse postorder
+                new_dom = set.intersection(*[ dom[p] for p in b.predecessors ]) | { b }
+                if dom[b] != new_dom:
+                    dom[b] = new_dom
+                    changed = True
+
+        idom = { self.blocks['entry'] : None }                      # idom[b] contains exactly one block, the immediate dominator of b
+        for b in postorder[-2::-1]:                                 # reverse or not we do not care here, but do not visit the source block
+            idom[b] = max(dom[b] - {b}, key=lambda x: len(dom[x]))  # immediate dominator is the one with the maximum number of dominators (except the block itself)
+
+        self.dominance_frontier = { b : set() for b in self.blocks.values() }
+        for b in self.blocks.values():                              # the dominance-frontier algorithm
+            if len(b.predecessors) < 2: continue
+            for p in b.predecessors:
+                runner = p
+                while runner != idom[b]:
+                    self.dominance_frontier[runner].add(b)
+                    runner = idom[runner]
+
+    def mem2reg(self):
+        self.compute_dominance_frontier()
+
+        variables = [ i.a for i in self.blocks['entry'].instructions if 'alloca' in i.string ]
+        phi = { v:set() for v in variables }                        # map (variable -> set of basic blocks)
+        for v in variables:
+            blocks_with_store = { b for b in self.blocks.values() for i in b.instructions if 'store' in i.string and i.b==v }
+            blocks_to_consider = blocks_with_store.copy()
+            while blocks_to_consider:
+                block = blocks_to_consider.pop()
+                for frontier in self.dominance_frontier[block]:
+                    phi[v].add(frontier)
+                    if frontier not in blocks_with_store:
+                        blocks_to_consider.add(frontier)
+
+        for v, bb in phi.items():                                   # insert phi nodes (for the moment without choices)
+            for b in bb:
+                b.phi_functions.append(Phi(v + '_' + b.label))
+
+        def store_load(block, visited, stack):
+            def find_variable(v, stack):                            # walk the stack back until
+                for frame in reversed(stack):                       # the current variable instance is found
+                    if v in frame: return frame[v]
+
+            for phi in block.phi_functions:                         # place phi node choice for the current path
+                v = phi.reg[:-len(block.label)-1]                   # phi saves the choice into a register named foo_bar, where foo is the name of the variable, and bar is the name of the basic block
+                b, val = find_variable(v, stack)
+                phi.choice[b] = val
+                stack[-1][v] = (block.label, phi.reg)
+            if block in visited: return                             # we need to revisit blocks with phi functions as many times as we have incoming edges,
+            visited.add(block)                                      # therefore the visited check is made after the choice placement
+
+            for i in block.instructions[:]:                         # iterate through a copy since we modify the list
+                match i.string:
+                    case _ if 'load' in i.string:
+                        _, val = find_variable(i.b, stack)
+                        block.instructions.remove(i)
+                        block.find_and_replace(i.a, val)
+                    case _ if 'store' in i.string:
+                        stack[-1][i.b] = (block.label, i.a)
+                        block.instructions.remove(i)
+                    case _ if 'br i1' in i.string:
+                        stack.append({})
+                        store_load(self.blocks[i.b], visited, stack)
+                        stack.pop()
+                        stack.append({})
+                        store_load(self.blocks[i.c], visited, stack)
+                        stack.pop()
+                    case _ if 'br label' in i.string:
+                        store_load(self.blocks[i.a],  visited, stack)
+        stack = [{}] # stack of maps (variable -> (block, value)); necessary for storing context while branching
+        store_load(self.blocks['entry'], set(), stack)
+
+cfg = ControlFlowGraph()
+cfg.add_block('entry')
+cfg.add_block('bb1')
+cfg.add_block('bb2')
+cfg.add_block('bb3')
+cfg.add_block('bb4')
+cfg.add_block('bb5')
+cfg.add_block('bb6')
+cfg.add_block('bb7')
+cfg.add_block('bb8')
+
+cfg.blocks['entry'].instructions = [
+    Instruction('\t{a} = alloca {t}', 'i32', '%a'),
+    Instruction('\tbr label %{a}', None, 'bb1'),
+]
+
+cfg.blocks['bb1'].instructions = [
+    Instruction('\tstore {t} {a}, {t}* {b}', 'i32', '1', '%a'),
+    Instruction('\tbr i1 %{a}, label %{b}, label %{c}', None, 'foo', 'bb2', 'bb4'),
+]
+
+cfg.blocks['bb2'].instructions = [
+    Instruction('\t{a} = load {t}, {t}* {b}', 'i32', '%a0', '%a'),
+    Instruction('\t{a} = add {t} {b}, {c}', 'i32', '%t0', '%a0', '1'),
+    Instruction('\tstore {t} {a}, {t}* {b}', 'i32', '%t0', '%a'),
+    Instruction('\tbr label %{a}', None, 'bb3'),
+]
+
+cfg.blocks['bb3'].instructions = [
+    Instruction('\t{a} = load {t}, {t}* {b}', 'i32', '%a1', '%a'),
+    Instruction('\t{a} = add {t} {b}, {c}', 'i32', '%t1', '%a1', '%a1'),
+    Instruction('\tstore {t} {a}, {t}* {b}', 'i32', '%t1', '%a'),
+    Instruction('\tbr i1 %{a}, label %{b}, label %{c}', None, 'foo', 'bb8', 'bb2'),
+]
+
+cfg.blocks['bb4'].instructions = [
+    Instruction('\tbr i1 %{a}, label %{b}, label %{c}', None, 'foo', 'bb5', 'bb6'),
+]
+
+cfg.blocks['bb5'].instructions = [
+    Instruction('\t{a} = load {t}, {t}* {b}', 'i32', '%a2', '%a'),
+    Instruction('\tstore {t} {a}, {t}* {b}', 'i32', '2', '%a'),
+    Instruction('\tbr label %{a}', None, 'bb7'),
+]
+
+cfg.blocks['bb6'].instructions = [
+    Instruction('\t{a} = load {t}, {t}* {b}', 'i32', '%a3', '%a'),
+    Instruction('\tstore {t} {a}, {t}* {b}', 'i32', '3', '%a'),
+    Instruction('\tbr label %{a}', None, 'bb7'),
+]
+
+cfg.blocks['bb7'].instructions = [
+    Instruction('\t{a} = load {t}, {t}* {b}', 'i32', '%a4', '%a'),
+    Instruction('\tbr label %{a}', None, 'bb8'),
+]
+
+cfg.blocks['bb8'].instructions = [
+    Instruction('\t{a} = load {t}, {t}* {b}', 'i32', '%a5', '%a'),
+    Instruction('\tret {a}', None, '%a5'),
+]
+
+print(cfg)
+cfg.compute_adjacency()
+
+cfg.mem2reg()
+
+print(cfg)
+
diff --git a/symtable.py b/symtable.py
index 166e59c..7472ee1 100644
--- a/symtable.py
+++ b/symtable.py
@@ -3,30 +3,24 @@ def __init__(self):
         self.variables = [{}]     # stack of variable symbol tables
         self.functions = [{}]     # stack of function symbol tables
         self.ret_stack = [ None ] # stack of enclosing function symbols, useful for return statements
-        self.scope_cnt = 0        # global scope counter for the display table allocation
-        self.var_cnt   = 0        # per scope variable counter, serves as an id in a stack frame
 
     def add_fun(self, name, argtypes, deco):  # a function can be identified by its name and a list of argument types, e.g.
         signature = (name, *argtypes)         # fun foo(x:bool, y:int) : int {...} has ('foo',Type.BOOL,Type.INT) signature
         if signature in self.functions[-1]:
             raise Exception('Double declaration of the function %s %s' % (signature[0], signature[1:]))
         self.functions[-1][signature] = deco
-        deco['scope'] = self.scope_cnt # id for the function block in the scope display table
-        self.scope_cnt += 1
 
     def add_var(self, name, deco):
         if name in self.variables[-1]:
             raise Exception('Double declaration of the variable %s' % name)
         self.variables[-1][name] = deco
-        deco['scope']  = self.ret_stack[-1]['scope'] # pointer to the display entry
-        deco['offset'] = self.var_cnt                # id of the variable in the corresponding stack frame
-        self.var_cnt += 1
+        deco['fullname'] = self.ret_stack[-1]['label'] + '.' + name
 
     def push_scope(self, deco):
+        deco['nonlocal'] = set() # set of nonlocal variable names in the function body
         self.variables.append({})
         self.functions.append({})
         self.ret_stack.append(deco)
-        self.var_cnt = 0 # reset the per scope variable counter
 
     def pop_scope(self):
         self.variables.pop()
@@ -34,14 +28,26 @@ def pop_scope(self):
         self.ret_stack.pop()
 
     def find_var(self, name):
-        for i in reversed(range(len(self.variables))):
-            if name in self.variables[i]:
-                return self.variables[i][name]
-        raise Exception('No declaration for the variable %s' % name)
+        try:
+            for i in reversed(range(len(self.variables))): # find the variable symbol
+                if name in self.variables[i]: break
+            deco = self.variables[i][name]
+            for i in range(i+1, len(self.variables)):      # propagate the pointer to the current context
+                self.ret_stack[i]['nonlocal'].add((deco['fullname'], deco['type']))
+            return deco
+        except KeyError: raise Exception('No declaration for the variable %s' % name)
 
     def find_fun(self, name, argtypes):
-        signature = (name, *argtypes)
-        for i in reversed(range(len(self.functions))):
-            if signature in self.functions[i]:
-                return self.functions[i][signature]
-        raise Exception('No declaration for the function %s' % signature[0], signature[1:])
+        try:
+            signature = (name, *argtypes)
+            for i in reversed(range(len(self.functions))): # find the function symbol
+                if signature in self.functions[i]: break
+            deco = self.functions[i][signature]
+            if 'nonlocal' in deco:                         # propagate context pointers to the function call
+                for fullname,t in deco['nonlocal']:
+                    label = fullname.partition('.')[0]
+                    for f in reversed(self.ret_stack):
+                        if f['label'] == label: break
+                        f['nonlocal'].add((fullname, t))
+            return deco
+        except KeyError: raise Exception('No declaration for the function %s' % signature[0], signature[1:])
diff --git a/syntree.py b/syntree.py
index 718b5aa..5b28975 100644
--- a/syntree.py
+++ b/syntree.py
@@ -1,3 +1,12 @@
+class LabelFactory: # this is a suffix to add to all function names
+    counter = 0     # in particular, it is useful for function overloading
+    @staticmethod   # it is also useful for different goto labels (loops, conditional statements etc) in assembly code
+    def cur_label():
+        return "uniqstr%d" % LabelFactory.counter
+    def new_label():
+        LabelFactory.counter += 1
+        return "uniqstr%d" % LabelFactory.counter
+
 class Type:
     VOID   = 0
     INT    = 1
@@ -5,60 +14,60 @@ class Type:
     STRING = 3
 
 class Function:
-    def __init__(self, name, args, var, fun, body, deco=None):
-        self.name = name       # function name, string
-        self.args = args       # function arguments, list of tuples (name, type)
-        self.var  = var        # local variables, list of tuples (name, type)
-        self.fun  = fun        # nested functions, list of Function nodes
-        self.body = body       # function body, list of statement nodes (Print/Return/Assign/While/IfThenElse/FunCall)
-        self.deco = deco or {} # decoration dictionary to be filled by the parser (line number) and by the semantic analyzer (return type, scope id etc)
+    def __init__(self, name, args, var, fun, body, deco):
+        self.name = name                                                 # function name, string
+        self.args = args                                                 # function arguments, list of tuples (name, type)
+        self.var  = var                                                  # local variables, list of tuples (name, type)
+        self.fun  = fun                                                  # nested functions, list of Function nodes
+        self.body = body                                                 # function body, list of statement nodes (Print/Return/Assign/While/IfThenElse/FunCall)
+        self.deco = deco | {'label' : name+'_'+LabelFactory.new_label()} # decoration dictionary to be filled by the parser (line number) and by the semantic analyzer (return type, scope id etc)
 
 # statements
 class Print:
-    def __init__(self, expr, newline, deco=None):
-        self.expr, self.newline, self.deco = expr, newline, deco or {}
+    def __init__(self, expr, newline, deco):
+        self.expr, self.newline, self.deco = expr, newline, deco
 
 class Return:
-    def __init__(self, expr, deco=None):
-        self.expr, self.deco = expr, deco or {}
+    def __init__(self, expr, deco):
+        self.expr, self.deco = expr, deco
 
 class Assign:
-    def __init__(self, name, expr, deco=None):
-        self.name, self.expr, self.deco = name, expr, deco or {}
+    def __init__(self, name, expr, deco):
+        self.name, self.expr, self.deco = name, expr, deco
 
 class While:
-    def __init__(self, expr, body, deco=None):
-        self.expr, self.body, self.deco = expr, body, deco or {}
+    def __init__(self, expr, body, deco):
+        self.expr, self.body, self.deco = expr, body, deco
 
 class IfThenElse:
-    def __init__(self, expr, ibody, ebody, deco=None):
-        self.expr, self.ibody, self.ebody, self.deco = expr, ibody, ebody, deco or {}
+    def __init__(self, expr, ibody, ebody, deco):
+        self.expr, self.ibody, self.ebody, self.deco = expr, ibody, ebody, deco
 
 # expressions
 class ArithOp:
-    def __init__(self, op, left, right, deco=None):
-        self.op, self.left, self.right, self.deco = op, left, right, (deco or {}) | {'type':Type.INT}
+    def __init__(self, op, left, right, deco):
+        self.op, self.left, self.right, self.deco = op, left, right, deco | {'type' : Type.INT}
 
 class LogicOp:
-    def __init__(self, op, left, right, deco=None):
-        self.op, self.left, self.right, self.deco = op, left, right, (deco or {}) | {'type':Type.BOOL}
+    def __init__(self, op, left, right, deco):
+        self.op, self.left, self.right, self.deco = op, left, right, deco | {'type' : Type.BOOL}
 
 class Integer:
-    def __init__(self, value, deco=None):
-        self.value, self.deco = value, (deco or {}) | {'type':Type.INT}
+    def __init__(self, value, deco):
+        self.value, self.deco = value, deco | {'type' : Type.INT}
 
 class Boolean:
-    def __init__(self, value, deco=None):
-        self.value, self.deco = value, (deco or {}) | {'type':Type.BOOL}
+    def __init__(self, value, deco):
+        self.value, self.deco = value, deco | {'type' : Type.BOOL}
 
 class String:
-    def __init__(self, value, deco=None):
-        self.value, self.deco = value, (deco or {}) | {'type':Type.STRING}
+    def __init__(self, value, deco):
+        self.value, self.deco = value, deco | {'type' : Type.STRING, 'label' : LabelFactory.new_label() }
 
 class Var:
-    def __init__(self, name, deco=None):
-        self.name, self.deco = name, deco or {}
+    def __init__(self, name, deco):
+        self.name, self.deco = name, deco
 
 class FunCall: # depending on the context, a function call can be a statement or an expression
-    def __init__(self, name, args, deco=None):
-        self.name, self.args, self.deco = name, args, deco or {}
+    def __init__(self, name, args, deco):
+        self.name, self.args, self.deco = name, args, deco
diff --git a/test-programs/elementary/scope.expected b/test-programs/elementary/scope.expected
index 00750ed..f1e5eee 100644
--- a/test-programs/elementary/scope.expected
+++ b/test-programs/elementary/scope.expected
@@ -1 +1,2 @@
 3
+2
diff --git a/test-programs/elementary/scope.wend b/test-programs/elementary/scope.wend
index e94eb1e..487ba20 100644
--- a/test-programs/elementary/scope.wend
+++ b/test-programs/elementary/scope.wend
@@ -1,6 +1,11 @@
 main() {
     int x;
 
+    h() {
+        int x;
+        f(0);
+    }
+
     f(int y) {
         g(int y) {
             x = y + 1;
@@ -13,5 +18,7 @@ main() {
     x = 0;
     f(x + 1);
     println x;
+    h();
+    println x;
 }
 
diff --git a/transasm.py b/transasm.py
deleted file mode 100644
index 0ec9fb4..0000000
--- a/transasm.py
+++ /dev/null
@@ -1,77 +0,0 @@
-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):
-    match n:
-        case Print():
-            match n.expr.deco['type']:
-                case Type.INT:
-                    asm = templates['print_int'].format(expr = expr(n.expr))
-                case Type.BOOL:
-                    asm = templates['print_bool'].format(expr = expr(n.expr))
-                case Type.STRING:
-                    asm = templates['print_string'].format(label = n.expr.deco['label'])
-                case other: raise Exception('Unknown expression type', n.expr)
-            return asm + (templates['print_linebreak'] if n.newline else '')
-        case Return():
-            return (expr(n.expr) if n.expr is not None and n.expr.deco['type'] != Type.VOID else '') + '\tret\n'
-        case Assign():
-            return templates['assign'].format(expression = expr(n.expr),
-                                                   scope = n.deco['scope']*4,
-                                                variable = n.deco['offset']*4)
-        case FunCall(): return expr(n)
-        case 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]))
-        case 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]))
-        case other: raise Exception('Unknown statement type', n)
-
-def expr(n): # convention: all expressions save their results to eax
-    match n:
-        case ArithOp() | 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')
-        case Integer() | Boolean():
-            return f'\tmovl ${int(n.value)}, %eax\n'
-        case Var():
-            return templates['var'].format(scope = n.deco['scope']*4, variable = n.deco['offset']*4)
-        case 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'])
-        case other: raise Exception('Unknown expression type', n)
diff --git a/transasm_recipe.py b/transasm_recipe.py
deleted file mode 100644
index cdad38d..0000000
--- a/transasm_recipe.py
+++ /dev/null
@@ -1,118 +0,0 @@
-templates = {
-'ascii' : '''{label}: .ascii "{string}"
-	{label}_len = . - {label}
-''',
-'var' : '''	movl display+{scope}, %eax
-	movl -{variable}(%eax), %eax
-''',
-'print_linebreak' : '''	pushl $10           # '\\n'
-	movl $4, %eax       # write system call
-	movl $1, %ebx       # stdout
-	leal 0(%esp), %ecx  # address of the character
-	movl $1, %edx       # one byte
-	int  $0x80          # make system call
-	addl $4, %esp
-''',
-'print_int' : '''{expr}
-	pushl %eax
-	call print_int32
-	addl $4, %esp
-''',
-'print_string' : '''	movl $4, %eax
-	movl $1, %ebx
-	movl ${label}, %ecx
-	movl ${label}_len, %edx
-	int  $0x80
-''',
-'print_bool' : '''{expr}
-	movl $truestr, %ecx
-	movl $truestr_len, %edx
-	test %eax, %eax
-	jnz 0f
-	movl $falsestr, %ecx
-	movl $falsestr_len, %edx
-0:	movl $4, %eax
-	movl $1, %ebx
-	int  $0x80
-''',
-'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:
-	movl 4(%esp), %eax  # the number to print
-	cdq
-	xorl %edx, %eax
-	subl %edx, %eax     # abs(%eax)
-	pushl $10           # base 10
-	movl %esp, %ecx     # buffer for the string to print
-	subl $16, %esp      # max 10 digits for a 32-bit number (keep %esp dword-aligned)
-0:	xorl %edx, %edx     #     %edx = 0
-	divl 16(%esp)       #     %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 0b              # until %eax==0         #                     <----- %edx = 6 ----->
-	cmp %eax, 24(%esp)  # if the number is negative
-	jge 0f
-	decl %ecx           # allocate one more character
-	movb $45, 0(%ecx)   # '-'
-0:	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 $20, %esp      # deallocate the buffer
-	ret
-'''}
diff --git a/transllvm.py b/transllvm.py
new file mode 100644
index 0000000..a25a62c
--- /dev/null
+++ b/transllvm.py
@@ -0,0 +1,88 @@
+from syntree import *
+from transllvm_recipe import templates
+
+def transllvm(n):
+    strings = ''.join([templates['ascii'].format( label  = label,
+                                                  size   = len(string)+1,                               # +1 for null termination
+                                                  string = ''.join(['\\%02x' % ord(x) for x in string]) # hex escape the string
+                                                 ) for label,string in n.deco['strings']])
+    main         = n.deco['label']
+    functions    = fun(n)
+    return templates['program'].format(**locals())
+
+def lltype(t):
+    return ['void', 'i32', 'i1'][t]
+
+def fun(n):
+    label   = n.deco['label']
+    args    = ', '.join([ '{t}* %{n}'   .format(n = a[0], t = lltype(a[1])) for a in n.deco['nonlocal'] ] +
+                        [ '{t} %{n}.arg'.format(n = a.deco['fullname'], t = lltype(a.deco['type'])) for a in n.args ])
+    alloca1 = ''.join([templates['alloca1'].format(name=v.deco['fullname'], vartype=lltype(v.deco['type'])) for v in n.args])
+    alloca2 = ''.join([templates['alloca2'].format(name=v.deco['fullname'], vartype=lltype(v.deco['type'])) for v in n.var])
+    nested  = ''.join([ fun(f) for f in n.fun ])
+    rettype = lltype(n.deco['type'])
+    retval  = '\tret void' if n.deco['type']==Type.VOID else '\tunreachable'
+    body    = ''.join([stat(s) for s in n.body])
+    return templates['function'].format(**locals())
+
+def stat(n):
+    match n:
+        case Print():
+            match n.expr.deco['type']:
+                case Type.INT:
+                    asm = templates['print_int'].format(expr = stat(n.expr), label = LabelFactory.cur_label())
+                case Type.BOOL:
+                    asm = templates['print_bool'].format(expr = stat(n.expr), label = LabelFactory.cur_label())
+                case Type.STRING:
+                    asm = templates['print_string'].format(label = n.expr.deco['label'])
+                case other: raise Exception('Unknown expression type', n.expr)
+            return asm + (templates['print_newline'] if n.newline else '')
+        case Return():
+            return templates['return'].format(expr    = stat(n.expr) if n.expr else '',
+                                              rettype = lltype(n.expr.deco['type']) if n.expr else 'void',
+                                              label   = LabelFactory.cur_label() if n.expr else '')
+        case Assign():
+            return templates['assign'].format(expr    = stat(n.expr),
+                                              label   = LabelFactory.cur_label(),
+                                              vartype = lltype(n.deco['type']),
+                                              varname = n.deco['fullname'])
+        case While():
+            return templates['while'].format(expr   = stat(n.expr),
+                                             label1 = LabelFactory.cur_label(),
+                                             label2 = LabelFactory.new_label(),
+                                             body   = ''.join([stat(s) for s in n.body]))
+        case IfThenElse():
+            return templates['ifthenelse'].format(expr   = stat(n.expr),
+                                                  label1 = LabelFactory.cur_label(),
+                                                  label2 = LabelFactory.new_label(),
+                                                  ibody  = ''.join([stat(s) for s in n.ibody]),
+                                                  ebody  = ''.join([stat(s) for s in n.ebody]))
+        case ArithOp() | LogicOp():
+            e1, l1 = stat(n.left),  LabelFactory.cur_label()
+            e2, l2 = stat(n.right), LabelFactory.cur_label()
+            t = lltype(n.left.deco['type'])
+            op = {'+':'add', '-':'sub', '*':'mul', '/':'sdiv', '%':'srem','||':'or', '&&':'and','<=':'icmp sle', '<':'icmp slt', '>=':'icmp sge', '>':'icmp sgt', '==':'icmp eq', '!=':'icmp ne'}
+            l = LabelFactory.new_label()
+            return f'{e1}{e2}\t%{l} = {op[n.op]} {t} %{l1}, %{l2}\n';
+        case Integer():
+            label = LabelFactory.new_label()
+            return f'\t%{label} = add i32 0, {n.value}\n'
+        case Boolean():
+            label = LabelFactory.new_label()
+            return f'\t%{label} = or i1 0, {int(n.value)}\n'
+        case Var():
+            label = LabelFactory.new_label()
+            vartype = 'i1' if n.deco['type']==Type.BOOL else 'i32'
+            return f'\t%{label} = load {vartype}, {vartype}* %{n.deco["fullname"]}\n'
+        case FunCall():
+            arg_bodies = ''
+            args = []
+            for e in n.args:
+                arg_bodies += stat(e)
+                args.append(lltype(e.deco['type']) + ' %' + LabelFactory.cur_label())
+            args = ', '.join([ '{t}* %{n}'.format(n = a[0], t = lltype(a[1])) for a in n.deco['nonlocal'] ] + args)
+            rettype = lltype(n.deco['type'])
+            label1 = n.deco['label']
+            lvalue = '' if rettype=='void' else '%' + LabelFactory.new_label() + ' = '
+            return f'{arg_bodies}\t{lvalue}call {rettype} @{label1}({args})\n'
+        case other: raise Exception('Unknown instruction', n)
diff --git a/transllvm_recipe.py b/transllvm_recipe.py
new file mode 100644
index 0000000..38e5706
--- /dev/null
+++ b/transllvm_recipe.py
@@ -0,0 +1,46 @@
+templates = {
+'program'       : '''declare i32 @printf(i8*, ...)
+@newline = constant [ 2 x i8] c"\\0a\\00"
+@integer = constant [ 3 x i8] c"%d\\00"
+@string  = constant [ 3 x i8] c"%s\\00"
+@bool    = constant [11 x i8] c"false\\00true\\00"
+{strings}
+define i32 @main() {{
+	call void @{main}()
+	ret i32 0
+}}
+{functions}''',
+'alloca1'       : '''	%{name} = alloca {vartype}
+	store {vartype} %{name}.arg, {vartype}* %{name}
+''',
+'alloca2'       : '''	%{name} = alloca {vartype}
+''',
+'function'      : '''define {rettype} @{label}({args}) {{
+{alloca1}{alloca2}{body}{retval}
+}}
+{nested}''',
+'ascii'         : '@{label} = constant [{size} x i8] c"{string}\\00"\n',
+'print_newline' : '	call i32 (i8*, ...) @printf(ptr @newline)\n',
+'print_string'  : '	call i32 (i8*, ...) @printf(ptr @string, ptr @{label})\n',
+'print_bool'    : '''{expr}	%{label}.offset = select i1 %{label}, i32 6, i32 0
+	%{label}.ptr = getelementptr [11 x i8], ptr @bool, i32 0, i32 %{label}.offset
+	call i32 (i8*, ...) @printf(ptr %{label}.ptr)
+''',
+'print_int'     : '{expr}	call i32 (i8*, ...)* @printf(ptr @integer, i32 %{label})\n',
+'return'        : '{expr}	ret {rettype} %{label}\n',
+'assign'        : '{expr}	store {vartype} %{label}, {vartype}* %{varname}\n',
+'while'         : '''	br label %{label2}.cond
+{label2}.cond:
+{expr}	br i1 %{label1}, label %{label2}.body, label %{label2}.end
+{label2}.body:
+{body}	br label %{label2}.cond
+{label2}.end:
+''',
+'ifthenelse'    : '''{expr}	br i1 %{label1}, label %{label2}.then, label %{label2}.else
+{label2}.then:
+{ibody}	br label %{label2}.end
+{label2}.else:
+{ebody}	br label %{label2}.end
+{label2}.end:
+'''
+}
