diff --git a/Cargo.lock b/Cargo.lock
index 1d50ff1a..bf8427d5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -503,6 +503,7 @@ version = "0.1.0"
 dependencies = [
  "inkwell",
  "nac3core",
+ "parking_lot",
  "rustpython-parser",
 ]
 
diff --git a/nac3standalone/Cargo.toml b/nac3standalone/Cargo.toml
index 600ee138..5ca0a86c 100644
--- a/nac3standalone/Cargo.toml
+++ b/nac3standalone/Cargo.toml
@@ -5,6 +5,7 @@ authors = ["M-Labs"]
 edition = "2018"
 
 [dependencies]
-inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "master", features = ["llvm10-0"] }
+inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "master", features = ["llvm11-0"] }
 rustpython-parser = { git = "https://github.com/RustPython/RustPython", branch = "master" }
+parking_lot = "0.11.1"
 nac3core = { path = "../nac3core" }
diff --git a/nac3standalone/mandelbrot.py b/nac3standalone/mandelbrot.py
index 63fee33b..6c8de275 100644
--- a/nac3standalone/mandelbrot.py
+++ b/nac3standalone/mandelbrot.py
@@ -1,18 +1,18 @@
 def run() -> int32:
     minX = -2.0
     maxX = 1.0
-    width = 78
-    height = 36
+    width = 78.0
+    height = 36.0
     aspectRatio = 2.0
 
-    yScale = float64(maxX-minX)*(float64(height)/float64(width))*aspectRatio
+    yScale = (maxX-minX)*(height/width)*aspectRatio
 
-    y = 0
+    y = 0.0
     while y < height:
-        x = 0
+        x = 0.0
         while x < width:
-            c_r = minX+float64(x)*(maxX-minX)/float64(width)
-            c_i = float64(y)*yScale/float64(height)-yScale/2.0
+            c_r = minX+x*(maxX-minX)/width
+            c_i = y*yScale/height-yScale/2.0
             z_r = c_r
             z_i = c_i
             i = 0
@@ -24,7 +24,8 @@ def run() -> int32:
                 z_r = new_z_r
                 i = i + 1
             output(i)
-            x = x + 1
+            x = x + 1.0
         output(-1)
-        y = y + 1
+        y = y + 1.0
     return 0
+
diff --git a/nac3standalone/src/basic_symbol_resolver.rs b/nac3standalone/src/basic_symbol_resolver.rs
new file mode 100644
index 00000000..8e36afe7
--- /dev/null
+++ b/nac3standalone/src/basic_symbol_resolver.rs
@@ -0,0 +1,35 @@
+use nac3core::{
+    location::Location,
+    symbol_resolver::{SymbolResolver, SymbolValue},
+    top_level::DefinitionId,
+    typecheck::{
+        type_inferencer::PrimitiveStore,
+        typedef::{Type, Unifier},
+    },
+};
+use std::collections::HashMap;
+
+#[derive(Clone)]
+pub struct Resolver {
+    pub id_to_type: HashMap<String, Type>,
+    pub id_to_def: HashMap<String, DefinitionId>,
+    pub class_names: HashMap<String, Type>,
+}
+
+impl SymbolResolver for Resolver {
+    fn get_symbol_type(&self, _: &mut Unifier, _: &PrimitiveStore, str: &str) -> Option<Type> {
+        self.id_to_type.get(str).cloned()
+    }
+
+    fn get_symbol_value(&self, _: &str) -> Option<SymbolValue> {
+        unimplemented!()
+    }
+
+    fn get_symbol_location(&self, _: &str) -> Option<Location> {
+        unimplemented!()
+    }
+
+    fn get_identifier_def(&self, id: &str) -> Option<DefinitionId> {
+        self.id_to_def.get(id).cloned()
+    }
+}
diff --git a/nac3standalone/src/main.rs b/nac3standalone/src/main.rs
index f42a4f32..464d4cf1 100644
--- a/nac3standalone/src/main.rs
+++ b/nac3standalone/src/main.rs
@@ -1,30 +1,162 @@
 use std::fs;
 
-use inkwell::context::Context;
-use inkwell::targets::*;
-use rustpython_parser::parser;
+use inkwell::{targets::*, OptimizationLevel};
+use parking_lot::RwLock;
+use rustpython_parser::{
+    ast::{fold::Fold, StmtKind},
+    parser,
+};
+use std::{cell::RefCell, collections::HashMap, path::Path, sync::Arc};
 
-use nac3core::CodeGen;
+use nac3core::{
+    codegen::{CodeGenTask, WithCall, WorkerRegistry},
+    top_level::{DefinitionId, TopLevelComposer, TopLevelContext, TopLevelDef},
+    typecheck::{
+        type_inferencer::{FunctionData, Inferencer},
+        typedef::{FunSignature, FuncArg, TypeEnum},
+    },
+};
 
+mod basic_symbol_resolver;
 
 fn main() {
     Target::initialize_all(&InitializationConfig::default());
 
     let program = match fs::read_to_string("mandelbrot.py") {
         Ok(program) => program,
-        Err(err) => { println!("Cannot open input file: {}", err); return; }
-    };
-    let ast = match parser::parse_program(&program) {
-        Ok(ast) => ast,
-        Err(err) => { println!("Parse error: {}", err); return; }
+        Err(err) => {
+            println!("Cannot open input file: {}", err);
+            return;
+        }
     };
 
-    let context = Context::create();
-    let mut codegen = CodeGen::new(&context);
-    match codegen.compile_toplevel(&ast.statements[0]) {
-        Ok(_) => (),
-        Err(err) => { println!("Compilation error: {}", err); return; }
-    }
-    codegen.print_ir();
-    codegen.output("mandelbrot.o");
+    let statements = match parser::parse_program(&program) {
+        Ok(mut ast) => {
+            let first = ast.remove(0);
+            if let StmtKind::FunctionDef { name, body, .. } = first.node {
+                if name != "run" {
+                    panic!("Parse error: expected function \"run\" but got {}", name);
+                }
+                body
+            } else {
+                panic!(
+                    "Parse error: expected function \"run\" but got {:?}",
+                    first.node
+                );
+            }
+        }
+        Err(err) => {
+            panic!("Parse error: {}", err);
+        }
+    };
+
+    let (_, composer) = TopLevelComposer::new();
+    let mut unifier = composer.unifier.clone();
+    let primitives = composer.primitives.clone();
+    let top_level = composer.to_top_level_context();
+    let fun = unifier.add_ty(TypeEnum::TFunc(RefCell::new(FunSignature {
+        args: vec![FuncArg {
+            name: "c".into(),
+            ty: primitives.int32,
+            default_value: None,
+        }],
+        ret: primitives.none,
+        vars: HashMap::new(),
+    })));
+    let def_id = top_level.definitions.read().len();
+    top_level
+        .definitions
+        .write()
+        .push(Arc::new(RwLock::new(TopLevelDef::Function {
+            name: "output".into(),
+            signature: fun,
+            instance_to_stmt: HashMap::new(),
+            instance_to_symbol: [("".to_string(), "output".to_string())]
+                .iter()
+                .cloned()
+                .collect(),
+            resolver: None,
+        })));
+    let resolver = Arc::new(basic_symbol_resolver::Resolver {
+        id_to_type: [("output".into(), fun)].iter().cloned().collect(),
+        id_to_def: [("output".into(), DefinitionId(def_id))]
+            .iter()
+            .cloned()
+            .collect(),
+        class_names: Default::default(),
+    });
+
+    let threads = ["test"];
+    let signature = FunSignature {
+        args: vec![],
+        ret: primitives.int32,
+        vars: HashMap::new(),
+    };
+
+    let mut function_data = FunctionData {
+        resolver: resolver.clone(),
+        bound_variables: Vec::new(),
+        return_type: Some(primitives.int32),
+    };
+    let mut virtual_checks = Vec::new();
+    let mut calls = HashMap::new();
+    let mut inferencer = Inferencer {
+        top_level: &top_level,
+        function_data: &mut function_data,
+        unifier: &mut unifier,
+        variable_mapping: Default::default(),
+        primitives: &primitives,
+        virtual_checks: &mut virtual_checks,
+        calls: &mut calls,
+    };
+
+    let statements = statements
+        .into_iter()
+        .map(|v| inferencer.fold_stmt(v))
+        .collect::<Result<Vec<_>, _>>()
+        .unwrap();
+    let mut identifiers = vec!["output".to_string()];
+    inferencer
+        .check_block(&statements, &mut identifiers)
+        .unwrap();
+    let top_level = Arc::new(TopLevelContext {
+        definitions: Arc::new(RwLock::new(std::mem::take(
+            &mut *top_level.definitions.write(),
+        ))),
+        unifiers: Arc::new(RwLock::new(vec![(
+            unifier.get_shared_unifier(),
+            primitives.clone(),
+        )])),
+    });
+    let task = CodeGenTask {
+        subst: Default::default(),
+        symbol_name: "run".to_string(),
+        body: statements,
+        unifier_index: 0,
+        resolver,
+        calls,
+        signature,
+    };
+    let f = Arc::new(WithCall::new(Box::new(|module| {
+        let triple = TargetMachine::get_default_triple();
+        let target =
+            Target::from_triple(&triple).expect("couldn't create target from target triple");
+        let target_machine = target
+            .create_target_machine(
+                &triple,
+                "",
+                "",
+                OptimizationLevel::Default,
+                RelocMode::Default,
+                CodeModel::Default,
+            )
+            .expect("couldn't create target machine");
+        target_machine
+            .write_to_file(module, FileType::Object, Path::new("mandelbrot.o"))
+            .expect("couldn't write module to file");
+    })));
+    let (registry, handles) = WorkerRegistry::create_workers(&threads, top_level, f);
+    registry.add_task(task);
+    registry.wait_tasks_complete(handles);
+    println!("object file is in mandelbrot.o")
 }