From ee67ebb017835007f8060c0e37ec9891907cdf7f Mon Sep 17 00:00:00 2001
From: Jakub Doka <jakub.doka2@gmail.com>
Date: Sun, 29 Dec 2024 13:30:29 +0100
Subject: [PATCH] adding cranelift backend

also splitting hbc binary into a separate crate to avoid dependency
cycle, the backand is thus far capable of compiling the simplest program
that returns an custom status code

Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
---
 .gitignore                      |   3 +
 Cargo.lock                      | 231 +++++++++++++-
 Cargo.toml                      |   2 +
 c/Cargo.toml                    |  10 +
 {lang => c}/src/main.rs         |  20 +-
 cranelift-backend/Cargo.toml    |  12 +
 cranelift-backend/src/lib.rs    | 534 ++++++++++++++++++++++++++++++++
 cranelift-backend/src/x86_64.rs | 299 ++++++++++++++++++
 depell/wasm-hbc/src/lib.rs      |   2 +-
 lang/Cargo.toml                 |   4 -
 lang/src/backend/hbvm.rs        |  19 +-
 lang/src/fs.rs                  |  23 +-
 lang/src/lib.rs                 |  40 ++-
 lang/src/parser.rs              |   5 +-
 lang/src/son.rs                 |  33 +-
 lang/src/ty.rs                  |   8 +-
 lang/src/utils.rs               |   4 +
 smh.hb                          |   3 +
 18 files changed, 1196 insertions(+), 56 deletions(-)
 create mode 100644 c/Cargo.toml
 rename {lang => c}/src/main.rs (61%)
 create mode 100644 cranelift-backend/Cargo.toml
 create mode 100644 cranelift-backend/src/lib.rs
 create mode 100644 cranelift-backend/src/x86_64.rs

diff --git a/.gitignore b/.gitignore
index 84b44e1a..b5f8365d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,9 @@
 /target
 rustc-ice-*
 
+a.out
+out.o
+
 # sqlite
 db.sqlite
 db.sqlite-journal
diff --git a/Cargo.lock b/Cargo.lock
index 29754c39..5855d91f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -39,10 +39,22 @@ dependencies = [
 ]
 
 [[package]]
-name = "anyhow"
-version = "1.0.89"
+name = "allocator-api2"
+version = "0.2.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
+name = "anyhow"
+version = "1.0.95"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
+
+[[package]]
+name = "arbitrary"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
 
 [[package]]
 name = "arc-swap"
@@ -229,7 +241,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "regex",
- "rustc-hash",
+ "rustc-hash 1.1.0",
  "shlex",
  "syn",
  "which",
@@ -259,6 +271,15 @@ dependencies = [
  "generic-array",
 ]
 
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+dependencies = [
+ "allocator-api2",
+]
+
 [[package]]
 name = "bytes"
 version = "1.8.0"
@@ -340,6 +361,143 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "cranelift-backend"
+version = "0.1.0"
+dependencies = [
+ "cranelift-codegen",
+ "cranelift-frontend",
+ "cranelift-module",
+ "cranelift-object",
+ "hblang",
+ "target-lexicon",
+]
+
+[[package]]
+name = "cranelift-bforest"
+version = "0.115.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac89549be94911dd0e839b4a7db99e9ed29c17517e1c026f61066884c168aa3c"
+dependencies = [
+ "cranelift-entity",
+]
+
+[[package]]
+name = "cranelift-bitset"
+version = "0.115.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9bd49369f76c77e34e641af85d0956869237832c118964d08bf5f51f210875a"
+
+[[package]]
+name = "cranelift-codegen"
+version = "0.115.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd96ce9cf8efebd7f5ab8ced5a0ce44250280bbae9f593d74a6d7effc3582a35"
+dependencies = [
+ "bumpalo",
+ "cranelift-bforest",
+ "cranelift-bitset",
+ "cranelift-codegen-meta",
+ "cranelift-codegen-shared",
+ "cranelift-control",
+ "cranelift-entity",
+ "cranelift-isle",
+ "gimli 0.31.1",
+ "hashbrown 0.14.5",
+ "log",
+ "regalloc2",
+ "rustc-hash 2.1.0",
+ "serde",
+ "smallvec",
+ "target-lexicon",
+]
+
+[[package]]
+name = "cranelift-codegen-meta"
+version = "0.115.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a68e358827afe4bfb6239fcbf6fbd5ac56206ece8a99c8f5f9bbd518773281a"
+dependencies = [
+ "cranelift-codegen-shared",
+]
+
+[[package]]
+name = "cranelift-codegen-shared"
+version = "0.115.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e184c9767afbe73d50c55ec29abcf4c32f9baf0d9d22b86d58c4d55e06dee181"
+
+[[package]]
+name = "cranelift-control"
+version = "0.115.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cc7664f2a66f053e33f149e952bb5971d138e3af637f5097727ed6dc0ed95dd"
+dependencies = [
+ "arbitrary",
+]
+
+[[package]]
+name = "cranelift-entity"
+version = "0.115.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "118597e3a9cf86c3556fa579a7a23b955fa18231651a52a77a2475d305a9cf84"
+dependencies = [
+ "cranelift-bitset",
+]
+
+[[package]]
+name = "cranelift-frontend"
+version = "0.115.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7638ea1efb069a0aa18d8ee67401b6b0d19f6bfe5de5e9ede348bfc80bb0d8c7"
+dependencies = [
+ "cranelift-codegen",
+ "log",
+ "smallvec",
+ "target-lexicon",
+]
+
+[[package]]
+name = "cranelift-isle"
+version = "0.115.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15c53e1152a0b01c4ed2b1e0535602b8e86458777dd9d18b28732b16325c7dc0"
+
+[[package]]
+name = "cranelift-module"
+version = "0.115.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11841b3f54ac480db1e8e8d5678ba901a13b387012d315e3f8fba3e7b7a80447"
+dependencies = [
+ "anyhow",
+ "cranelift-codegen",
+ "cranelift-control",
+]
+
+[[package]]
+name = "cranelift-object"
+version = "0.115.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e235ddfd19f100855ad03358c7ae0a13070c38a000701054cab46458cca6e81"
+dependencies = [
+ "anyhow",
+ "cranelift-codegen",
+ "cranelift-control",
+ "cranelift-module",
+ "log",
+ "object",
+ "target-lexicon",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
+dependencies = [
+ "cfg-if",
+]
+
 [[package]]
 name = "crypto-common"
 version = "0.1.6"
@@ -441,6 +599,12 @@ version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
 
+[[package]]
+name = "foldhash"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
+
 [[package]]
 name = "form_urlencoded"
 version = "1.2.1"
@@ -532,6 +696,11 @@ name = "gimli"
 version = "0.31.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
+dependencies = [
+ "fallible-iterator 0.3.0",
+ "indexmap 2.6.0",
+ "stable_deref_trait",
+]
 
 [[package]]
 name = "glob"
@@ -579,6 +748,9 @@ name = "hashbrown"
 version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
+dependencies = [
+ "foldhash",
+]
 
 [[package]]
 name = "hashlink"
@@ -593,6 +765,16 @@ dependencies = [
 name = "hbbytecode"
 version = "0.1.0"
 
+[[package]]
+name = "hbc"
+version = "0.1.0"
+dependencies = [
+ "cranelift-backend",
+ "hblang",
+ "log",
+ "target-lexicon",
+]
+
 [[package]]
 name = "hblang"
 version = "0.1.0"
@@ -935,6 +1117,9 @@ version = "0.36.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
 dependencies = [
+ "crc32fast",
+ "hashbrown 0.15.0",
+ "indexmap 2.6.0",
  "memchr",
 ]
 
@@ -1048,6 +1233,20 @@ dependencies = [
  "getrandom",
 ]
 
+[[package]]
+name = "regalloc2"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "145c1c267e14f20fb0f88aa76a1c5ffec42d592c1d28b3cd9148ae35916158d3"
+dependencies = [
+ "allocator-api2",
+ "bumpalo",
+ "hashbrown 0.15.0",
+ "log",
+ "rustc-hash 2.1.0",
+ "smallvec",
+]
+
 [[package]]
 name = "regex"
 version = "1.11.1"
@@ -1118,6 +1317,12 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
 
+[[package]]
+name = "rustc-hash"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
+
 [[package]]
 name = "rustix"
 version = "0.38.37"
@@ -1192,18 +1397,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
 
 [[package]]
 name = "serde"
-version = "1.0.210"
+version = "1.0.217"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
+checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.210"
+version = "1.0.217"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
+checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1295,9 +1500,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
 
 [[package]]
 name = "syn"
-version = "2.0.79"
+version = "2.0.87"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
+checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1316,6 +1521,12 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
 
+[[package]]
+name = "target-lexicon"
+version = "0.12.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
+
 [[package]]
 name = "time"
 version = "0.3.36"
diff --git a/Cargo.toml b/Cargo.toml
index 48734ae9..09e09d33 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,6 +12,8 @@ members = [
 	"depell/wasm-fmt",
 	"depell/wasm-hbc",
 	"depell/wasm-rt",
+	"cranelift-backend",
+	"c",
 ]
 
 [workspace.dependencies]
diff --git a/c/Cargo.toml b/c/Cargo.toml
new file mode 100644
index 00000000..5fdf0121
--- /dev/null
+++ b/c/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "hbc"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+cranelift-backend = { version = "0.1.0", path = "../cranelift-backend" }
+hblang = { workspace = true, features = ["std"] }
+log = "0.4.22"
+target-lexicon = { version = "0.12", features = ["std"] }
diff --git a/lang/src/main.rs b/c/src/main.rs
similarity index 61%
rename from lang/src/main.rs
rename to c/src/main.rs
index dfc9103a..17ffc387 100644
--- a/lang/src/main.rs
+++ b/c/src/main.rs
@@ -1,4 +1,5 @@
-#[cfg(feature = "std")]
+use std::io;
+
 fn main() {
     use std::io::Write;
 
@@ -6,7 +7,22 @@ fn main() {
         let args = std::env::args().collect::<Vec<_>>();
         let args = args.iter().map(String::as_str).collect::<Vec<_>>();
         let resolvers = &[("ableos", hblang::ABLEOS_PATH_RESOLVER)];
-        let opts = hblang::Options::from_args(&args, out, resolvers)?;
+
+        let mut native = None;
+        let backend = match args.iter().position(|&v| v == "--target").map(|i| args[i + 1]) {
+            Some(hblang::backend::hbvm::TARGET_TRIPLE) => None,
+            Some(target) => Some(
+                native.insert(
+                    cranelift_backend::Backend::new(target.parse().map_err(io::Error::other)?)
+                        .map_err(io::Error::other)?,
+                ) as &mut dyn hblang::backend::Backend,
+            ),
+            None => Some(native.insert(
+                cranelift_backend::Backend::new(target_lexicon::HOST).map_err(io::Error::other)?,
+            ) as &mut dyn hblang::backend::Backend),
+        };
+
+        let opts = hblang::Options::from_args(&args, out, resolvers, backend)?;
         let file = args.iter().filter(|a| !a.starts_with('-')).nth(1).copied().unwrap_or("main.hb");
 
         hblang::run_compiler(file, opts, out, warnings)
diff --git a/cranelift-backend/Cargo.toml b/cranelift-backend/Cargo.toml
new file mode 100644
index 00000000..660df2d9
--- /dev/null
+++ b/cranelift-backend/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "cranelift-backend"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+cranelift-codegen = "0.115.0"
+cranelift-frontend = "0.115.0"
+cranelift-module = "0.115.0"
+cranelift-object = "0.115.0"
+hblang.workspace = true
+target-lexicon = "0.12"
diff --git a/cranelift-backend/src/lib.rs b/cranelift-backend/src/lib.rs
new file mode 100644
index 00000000..6d98f5bf
--- /dev/null
+++ b/cranelift-backend/src/lib.rs
@@ -0,0 +1,534 @@
+#![feature(if_let_guard)]
+#![feature(slice_take)]
+use {
+    cranelift_codegen::{
+        CodegenError, Final, FinalizedMachReloc, MachBufferFinalized,
+        ir::{InstBuilder, UserExternalName},
+        isa::LookupError,
+        settings::Configurable,
+    },
+    cranelift_frontend::FunctionBuilder,
+    cranelift_module::{Module, ModuleError},
+    hblang::{
+        nodes::Kind,
+        utils::{Ent, EntVec},
+    },
+    std::{fmt::Display, ops::Range},
+};
+
+mod x86_64;
+
+pub struct Backend {
+    ctx: cranelift_codegen::Context,
+    dt_ctx: cranelift_module::DataDescription,
+    fb_ctx: cranelift_frontend::FunctionBuilderContext,
+    module: Option<cranelift_object::ObjectModule>,
+    ctrl_plane: cranelift_codegen::control::ControlPlane,
+    funcs: Functions,
+    globals: EntVec<hblang::ty::Global, Global>,
+    asm: Assembler,
+}
+
+impl Backend {
+    pub fn new(triple: target_lexicon::Triple) -> Result<Self, BackendCreationError> {
+        Ok(Self {
+            ctx: cranelift_codegen::Context::new(),
+            dt_ctx: cranelift_module::DataDescription::new(),
+            fb_ctx: cranelift_frontend::FunctionBuilderContext::default(),
+            ctrl_plane: cranelift_codegen::control::ControlPlane::default(),
+            module: cranelift_object::ObjectModule::new(cranelift_object::ObjectBuilder::new(
+                cranelift_codegen::isa::lookup(triple)?.finish(
+                    cranelift_codegen::settings::Flags::new({
+                        let mut bl = cranelift_codegen::settings::builder();
+                        bl.set("enable_verifier", "true").unwrap();
+                        bl
+                    }),
+                )?,
+                "main",
+                cranelift_module::default_libcall_names(),
+            )?)
+            .into(),
+            funcs: Default::default(),
+            globals: Default::default(),
+            asm: Default::default(),
+        })
+    }
+}
+
+impl hblang::backend::Backend for Backend {
+    fn assemble_reachable(
+        &mut self,
+        from: hblang::ty::Func,
+        types: &hblang::ty::Types,
+        files: &hblang::utils::EntSlice<hblang::ty::Module, hblang::parser::Ast>,
+        to: &mut Vec<u8>,
+    ) -> hblang::backend::AssemblySpec {
+        debug_assert!(self.asm.frontier.is_empty());
+        debug_assert!(self.asm.funcs.is_empty());
+        debug_assert!(self.asm.globals.is_empty());
+
+        let mut module = self.module.take().expect("backend can assemble only once");
+
+        fn clif_name_to_ty(name: UserExternalName) -> hblang::ty::Id {
+            match name.namespace {
+                0 => hblang::ty::Kind::Func(hblang::ty::Func::new(name.index as _)),
+                1 => hblang::ty::Kind::Global(hblang::ty::Global::new(name.index as _)),
+                _ => unreachable!(),
+            }
+            .compress()
+        }
+
+        self.globals.shadow(types.ins.globals.len());
+
+        self.asm.frontier.push(from.into());
+        while let Some(itm) = self.asm.frontier.pop() {
+            match itm.expand() {
+                hblang::ty::Kind::Func(func) => {
+                    let fuc = &mut self.funcs.headers[func];
+                    self.asm.funcs.push(func);
+                    self.asm.frontier.extend(
+                        fuc.external_names.clone().map(|r| {
+                            clif_name_to_ty(self.funcs.external_names[r as usize].clone())
+                        }),
+                    );
+                    self.asm.name.clear();
+                    if func == from {
+                        self.asm.name.push_str("main");
+                    } else {
+                        let file = &files[types.ins.funcs[func].file];
+                        self.asm.name.push_str(&file.path);
+                        self.asm.name.push('.');
+                        self.asm.name.push_str(file.ident_str(types.ins.funcs[func].name));
+                    }
+                    let linkage = if func == from {
+                        cranelift_module::Linkage::Export
+                    } else {
+                        cranelift_module::Linkage::Local
+                    };
+                    build_signature(
+                        module.isa().default_call_conv(),
+                        types.ins.funcs[func].sig,
+                        types,
+                        &mut self.ctx.func.signature,
+                        &mut vec![],
+                    );
+                    fuc.module_id = Some(
+                        module
+                            .declare_function(&self.asm.name, linkage, &self.ctx.func.signature)
+                            .unwrap(),
+                    );
+                }
+                hblang::ty::Kind::Global(glob) => {
+                    self.asm.globals.push(glob);
+                    self.asm.name.clear();
+                    let file = &files[types.ins.globals[glob].file];
+                    self.asm.name.push_str(&file.path);
+                    self.asm.name.push('.');
+                    self.asm.name.push_str(file.ident_str(types.ins.globals[glob].name));
+                    self.globals[glob].module_id = Some(
+                        module
+                            .declare_data(
+                                &self.asm.name,
+                                cranelift_module::Linkage::Local,
+                                true,
+                                false,
+                            )
+                            .unwrap(),
+                    );
+                }
+                _ => unreachable!(),
+            }
+        }
+
+        for &func in &self.asm.funcs {
+            let fuc = &self.funcs.headers[func];
+            debug_assert!(!fuc.code.is_empty());
+            let names = &mut self.funcs.external_names
+                [fuc.external_names.start as usize..fuc.external_names.end as usize];
+            names.iter_mut().for_each(|nm| {
+                nm.index = fuc.module_id.unwrap().as_u32();
+                self.ctx.func.params.ensure_user_func_name(nm.clone());
+            });
+            module
+                .define_function_bytes(
+                    fuc.module_id.unwrap(),
+                    &self.ctx.func,
+                    fuc.alignment as _,
+                    &self.funcs.code[fuc.code.start as usize..fuc.code.end as usize],
+                    &self.funcs.relocs[fuc.relocs.start as usize..fuc.relocs.end as usize],
+                )
+                .unwrap();
+        }
+
+        for global in self.asm.globals.drain(..) {
+            let glob = &self.globals[global];
+            self.dt_ctx.clear();
+            self.dt_ctx.define(types.ins.globals[global].data.clone().into());
+            module.define_data(glob.module_id.unwrap(), &self.dt_ctx).unwrap();
+        }
+
+        module.finish().object.write_stream(to).unwrap();
+
+        hblang::backend::AssemblySpec { code_length: 0, data_length: 0, entry: 0 }
+    }
+
+    fn disasm<'a>(
+        &'a self,
+        _sluce: &[u8],
+        _eca_handler: &mut dyn FnMut(&mut &[u8]),
+        _types: &'a hblang::ty::Types,
+        _files: &'a hblang::utils::EntSlice<hblang::ty::Module, hblang::parser::Ast>,
+        _output: &mut String,
+    ) -> Result<(), std::boxed::Box<dyn core::error::Error + Send + Sync + 'a>> {
+        unimplemented!()
+    }
+
+    fn emit_body(
+        &mut self,
+        id: hblang::ty::Func,
+        nodes: &hblang::nodes::Nodes,
+        tys: &hblang::ty::Types,
+        files: &hblang::utils::EntSlice<hblang::ty::Module, hblang::parser::Ast>,
+    ) {
+        self.ctx.clear();
+
+        let mut lens = vec![];
+        let stack_ret = build_signature(
+            self.module.as_ref().unwrap().isa().default_call_conv(),
+            tys.ins.funcs[id].sig,
+            tys,
+            &mut self.ctx.func.signature,
+            &mut lens,
+        );
+
+        FuncBuilder {
+            bl: FunctionBuilder::new(&mut self.ctx.func, &mut self.fb_ctx),
+            nodes,
+            tys,
+            files,
+            values: &mut vec![None; nodes.len()],
+        }
+        .build(tys.ins.funcs[id].sig, &lens, stack_ret);
+
+        self.ctx.compile(self.module.as_ref().unwrap().isa(), &mut self.ctrl_plane).unwrap();
+        let code = self.ctx.compiled_code().unwrap();
+        self.funcs.push(id, &self.ctx.func, &code.buffer);
+    }
+}
+
+fn build_signature(
+    call_conv: cranelift_codegen::isa::CallConv,
+    sig: hblang::ty::Sig,
+    types: &hblang::ty::Types,
+    signature: &mut cranelift_codegen::ir::Signature,
+    arg_lens: &mut Vec<usize>,
+) -> bool {
+    signature.clear(call_conv);
+    match call_conv {
+        cranelift_codegen::isa::CallConv::SystemV => {
+            x86_64::build_systemv_signature(sig, types, signature, arg_lens)
+        }
+        _ => todo!(),
+    }
+}
+
+struct FuncBuilder<'a, 'b> {
+    bl: cranelift_frontend::FunctionBuilder<'b>,
+    nodes: &'a hblang::nodes::Nodes,
+    tys: &'a hblang::ty::Types,
+    #[expect(unused)]
+    files: &'a hblang::utils::EntSlice<hblang::ty::Module, hblang::parser::Ast>,
+    values: &'b mut [Option<Result<cranelift_codegen::ir::Value, cranelift_codegen::ir::Block>>],
+}
+
+impl FuncBuilder<'_, '_> {
+    pub fn build(mut self, sig: hblang::ty::Sig, arg_lens: &[usize], stack_ret: bool) {
+        let entry = self.bl.create_block();
+        self.bl.append_block_params_for_function_params(entry);
+        self.bl.switch_to_block(entry);
+        let mut arg_vals = self.bl.block_params(entry);
+
+        if stack_ret {
+            let ret_ptr = *arg_vals.take_first().unwrap();
+            self.values[hblang::nodes::MEM as usize] = Some(Ok(ret_ptr));
+        }
+
+        let Self { nodes, tys, .. } = self;
+
+        let mut parama_len = arg_lens.iter();
+        let mut typs = sig.args.args();
+        let mut args = nodes[hblang::nodes::VOID].outputs[hblang::nodes::ARG_START..].iter();
+        while let Some(aty) = typs.next(tys) {
+            let hblang::ty::Arg::Value(ty) = aty else { continue };
+            let loc = arg_vals.take(..*parama_len.next().unwrap()).unwrap();
+            let &arg = args.next().unwrap();
+            if ty.is_aggregate(tys) {
+                todo!()
+            } else {
+                debug_assert_eq!(loc.len(), 0);
+                self.values[arg as usize] = Some(Ok(loc[0]));
+            }
+        }
+
+        self.values[hblang::nodes::ENTRY as usize] = Some(Err(entry));
+
+        self.emit_node(hblang::nodes::VOID, hblang::nodes::VOID);
+
+        self.bl.finalize();
+    }
+
+    fn value_of(&self, nid: hblang::nodes::Nid) -> cranelift_codegen::ir::Value {
+        self.values[nid as usize].unwrap().unwrap()
+    }
+
+    fn block_of(&self, nid: hblang::nodes::Nid) -> cranelift_codegen::ir::Block {
+        self.values[nid as usize].unwrap().unwrap_err()
+    }
+
+    fn close_block(&mut self, nid: hblang::nodes::Nid) {
+        if matches!(self.nodes[nid].kind, Kind::Loop | Kind::Region) {
+            return;
+        }
+        self.bl.seal_block(self.block_of(nid));
+    }
+
+    fn emit_node(&mut self, nid: hblang::nodes::Nid, block: hblang::nodes::Nid) {
+        use hblang::nodes::*;
+
+        let mut args = vec![];
+        if matches!(self.nodes[nid].kind, Kind::Region | Kind::Loop) {
+            let side = 1 + self.values[nid as usize].is_some() as usize;
+            for &o in self.nodes[nid].outputs.iter() {
+                if self.nodes[o].is_data_phi() {
+                    args.push(self.value_of(self.nodes[0].inputs[side]));
+                }
+            }
+            match (self.nodes[nid].kind, self.values[nid as usize]) {
+                (Kind::Loop, Some(blck)) => {
+                    self.bl.ins().jump(blck.unwrap_err(), &args);
+                    self.bl.seal_block(blck.unwrap_err());
+                    return;
+                }
+                (Kind::Region, None) => {
+                    let next = self.bl.create_block();
+                    for &o in self.nodes[nid].outputs.iter() {
+                        if self.nodes[o].is_data_phi() {
+                            self.values[o as usize] = Some(Ok(self.bl.append_block_param(
+                                next,
+                                ty_to_clif_ty(self.nodes[o].ty, self.tys),
+                            )));
+                        }
+                    }
+                    self.bl.ins().jump(next, &args);
+                    self.bl.seal_block(next);
+                    self.values[nid as usize] = Some(Err(next));
+                    return;
+                }
+                _ => {}
+            }
+        }
+
+        let node = &self.nodes[nid];
+        self.values[nid as usize] = Some(match node.kind {
+            Kind::Start => {
+                debug_assert_eq!(self.nodes[node.outputs[0]].kind, Kind::Entry);
+                self.emit_node(node.outputs[0], block);
+                return;
+            }
+            Kind::If => {
+                let &[_, cnd] = node.inputs.as_slice() else { unreachable!() };
+                let &[then, else_] = node.outputs.as_slice() else { unreachable!() };
+
+                let then_bl = self.bl.create_block();
+                let else_bl = self.bl.create_block();
+                let c = self.value_of(cnd);
+                self.bl.ins().brif(c, then_bl, &[], else_bl, &[]);
+                self.values[then as usize] = Some(Err(then_bl));
+                self.values[else_ as usize] = Some(Err(else_bl));
+
+                self.close_block(block);
+                self.bl.switch_to_block(then_bl);
+                self.emit_node(then, then);
+                self.bl.switch_to_block(else_bl);
+                self.emit_node(else_, else_);
+                Err(self.block_of(block))
+            }
+            Kind::Region | Kind::Loop => {
+                if node.kind == Kind::Loop {
+                    let next = self.bl.create_block();
+                    for &o in self.nodes[nid].outputs.iter() {
+                        if self.nodes[o].is_data_phi() {
+                            self.values[o as usize] = Some(Ok(self.bl.append_block_param(
+                                next,
+                                ty_to_clif_ty(self.nodes[o].ty, self.tys),
+                            )));
+                        }
+                    }
+                    self.values[nid as usize] = Some(Err(next));
+                }
+                self.bl.ins().jump(self.values[nid as usize].unwrap().unwrap_err(), &args);
+                self.close_block(block);
+                self.bl.switch_to_block(self.values[nid as usize].unwrap().unwrap_err());
+                for &o in node.outputs.iter().rev() {
+                    self.emit_node(o, nid);
+                }
+                Err(self.block_of(block))
+            }
+            Kind::Return { .. } | Kind::Die => {
+                let ret = self.value_of(node.inputs[1]);
+                self.bl.ins().return_(&[ret]);
+                self.close_block(block);
+                self.emit_node(node.outputs[0], block);
+                Err(self.block_of(block))
+            }
+            Kind::Entry => {
+                for &o in node.outputs.iter().rev() {
+                    self.emit_node(o, nid);
+                }
+                return;
+            }
+            Kind::Then | Kind::Else => {
+                for &o in node.outputs.iter().rev() {
+                    self.emit_node(o, block);
+                }
+                Err(self.block_of(block))
+            }
+            Kind::Call { func: _, unreachable, .. } => {
+                if unreachable {
+                    todo!()
+                } else {
+                    todo!();
+                    //for &o in node.outputs.iter().rev() {
+                    //    if self.nodes[o].inputs[0] == nid
+                    //        || (matches!(self.nodes[o].kind, Kind::Loop | Kind::Region)
+                    //            && self.nodes[o].inputs[1] == nid)
+                    //    {
+                    //        self.emit_node(o, block);
+                    //    }
+                    //}
+                }
+            }
+            Kind::CInt { value } if self.nodes[nid].ty.is_integer() => Ok(self.bl.ins().iconst(
+                cranelift_codegen::ir::Type::int(self.tys.size_of(self.nodes[nid].ty) as u16 * 8)
+                    .unwrap(),
+                value,
+            )),
+            Kind::CInt { value } => Ok(match self.tys.size_of(self.nodes[nid].ty) {
+                4 => self.bl.ins().f32const(f64::from_bits(value as _) as f32),
+                8 => self.bl.ins().f64const(f64::from_bits(value as _)),
+                _ => unimplemented!(),
+            }),
+            Kind::BinOp { .. }
+            | Kind::UnOp { .. }
+            | Kind::Global { .. }
+            | Kind::Load { .. }
+            | Kind::Stre
+            | Kind::RetVal
+            | Kind::Stck => todo!(),
+            Kind::End | Kind::Phi | Kind::Arg | Kind::Mem | Kind::Loops | Kind::Join => return,
+            Kind::Assert { .. } => unreachable!(),
+        });
+    }
+}
+
+fn ty_to_clif_ty(ty: hblang::ty::Id, tys: &hblang::ty::Types) -> cranelift_codegen::ir::Type {
+    if ty.is_integer() {
+        cranelift_codegen::ir::Type::int(tys.size_of(ty) as u16 * 8).unwrap()
+    } else {
+        unimplemented!()
+    }
+}
+
+#[derive(Default)]
+struct Global {
+    module_id: Option<cranelift_module::DataId>,
+}
+
+#[derive(Default)]
+struct FuncHeaders {
+    module_id: Option<cranelift_module::FuncId>,
+    alignment: u32,
+    code: Range<u32>,
+    relocs: Range<u32>,
+    external_names: Range<u32>,
+}
+
+#[derive(Default)]
+struct Functions {
+    headers: EntVec<hblang::ty::Func, FuncHeaders>,
+    code: Vec<u8>,
+    relocs: Vec<FinalizedMachReloc>,
+    external_names: Vec<UserExternalName>,
+}
+
+impl Functions {
+    fn push(
+        &mut self,
+        id: hblang::ty::Func,
+        func: &cranelift_codegen::ir::Function,
+        code: &MachBufferFinalized<Final>,
+    ) {
+        self.headers.shadow(id.index() + 1);
+        self.headers[id] = FuncHeaders {
+            module_id: None,
+            alignment: code.alignment,
+            code: self.code.len() as u32..self.code.len() as u32 + code.data().len() as u32,
+            relocs: self.relocs.len() as u32..self.relocs.len() as u32 + code.relocs().len() as u32,
+            external_names: self.external_names.len() as u32
+                ..self.external_names.len() as u32 + func.params.user_named_funcs().len() as u32,
+        };
+        self.code.extend(code.data());
+        self.relocs.extend(code.relocs().iter().cloned());
+        self.external_names.extend(func.params.user_named_funcs().values().cloned());
+    }
+}
+
+#[derive(Default)]
+struct Assembler {
+    name: String,
+    frontier: Vec<hblang::ty::Id>,
+    globals: Vec<hblang::ty::Global>,
+    funcs: Vec<hblang::ty::Func>,
+}
+
+#[derive(Debug)]
+pub enum BackendCreationError {
+    UnsupportedTriplet(LookupError),
+    InvalidFlags(CodegenError),
+    UnsupportedModuleConfig(ModuleError),
+}
+
+impl Display for BackendCreationError {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        match self {
+            BackendCreationError::UnsupportedTriplet(err) => {
+                write!(f, "Unsupported triplet: {}", err)
+            }
+            BackendCreationError::InvalidFlags(err) => {
+                write!(f, "Invalid flags: {}", err)
+            }
+            BackendCreationError::UnsupportedModuleConfig(err) => {
+                write!(f, "Unsupported module configuration: {}", err)
+            }
+        }
+    }
+}
+impl core::error::Error for BackendCreationError {}
+
+impl From<LookupError> for BackendCreationError {
+    fn from(value: LookupError) -> Self {
+        Self::UnsupportedTriplet(value)
+    }
+}
+
+impl From<CodegenError> for BackendCreationError {
+    fn from(value: CodegenError) -> Self {
+        Self::InvalidFlags(value)
+    }
+}
+
+impl From<ModuleError> for BackendCreationError {
+    fn from(value: ModuleError) -> Self {
+        Self::UnsupportedModuleConfig(value)
+    }
+}
diff --git a/cranelift-backend/src/x86_64.rs b/cranelift-backend/src/x86_64.rs
new file mode 100644
index 00000000..851d9fba
--- /dev/null
+++ b/cranelift-backend/src/x86_64.rs
@@ -0,0 +1,299 @@
+// The classification code for the x86_64 ABI is taken from the clay language
+// https://github.com/jckarter/clay/blob/db0bd2702ab0b6e48965cd85f8859bbd5f60e48e/compiler/externals.cpp
+
+pub fn build_systemv_signature(
+    sig: hblang::ty::Sig,
+    types: &hblang::ty::Types,
+    signature: &mut cranelift_codegen::ir::Signature,
+    arg_lens: &mut Vec<usize>,
+) -> bool {
+    let mut alloca = Alloca::new();
+
+    alloca.next(false, sig.ret, types, &mut signature.returns);
+    let stack_ret = signature.params.len() == 1
+        && signature.params[0].purpose == cranelift_codegen::ir::ArgumentPurpose::StructReturn;
+
+    if stack_ret {
+        signature.params.append(&mut signature.returns);
+    }
+
+    let mut args = sig.args.args();
+    while let Some(arg) = args.next_value(types) {
+        let prev = signature.params.len();
+        alloca.next(true, arg, types, &mut signature.params);
+        arg_lens.push(signature.params.len() - prev);
+    }
+
+    stack_ret
+}
+
+/// Classification of "eightbyte" components.
+// N.B., the order of the variants is from general to specific,
+// such that `unify(a, b)` is the "smaller" of `a` and `b`.
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
+enum Class {
+    Int,
+    Sse,
+    SseUp,
+}
+
+#[derive(Clone, Copy, Debug)]
+struct Memory;
+
+// Currently supported vector size (AVX-512).
+const LARGEST_VECTOR_SIZE: usize = 512;
+const MAX_EIGHTBYTES: usize = LARGEST_VECTOR_SIZE / 64;
+
+fn classify_arg(
+    cx: &hblang::ty::Types,
+    arg: hblang::ty::Id,
+) -> Result<[Option<Class>; MAX_EIGHTBYTES], Memory> {
+    fn classify(
+        cx: &hblang::ty::Types,
+        layout: hblang::ty::Id,
+        cls: &mut [Option<Class>],
+        off: hblang::ty::Offset,
+    ) -> Result<(), Memory> {
+        let size = cx.size_of(layout);
+        if off & (cx.align_of(layout) - 1) != 0 {
+            if size != 0 {
+                return Err(Memory);
+            }
+            return Ok(());
+        }
+
+        let mut c = match layout.expand() {
+            _ if size == 0 => return Ok(()),
+            _ if layout.is_integer() || layout.is_pointer() => Class::Int,
+            _ if layout.is_float() => Class::Sse,
+
+            hblang::ty::Kind::Struct(s) => {
+                for (f, foff) in hblang::ty::OffsetIter::new(s, cx).into_iter(cx) {
+                    classify(cx, f.ty, cls, off + foff)?;
+                }
+                return Ok(());
+            }
+            hblang::ty::Kind::Tuple(tuple) => {
+                for (&ty, foff) in hblang::ty::OffsetIter::new(tuple, cx).into_iter(cx) {
+                    classify(cx, ty, cls, off + foff)?;
+                }
+                return Ok(());
+            }
+            hblang::ty::Kind::Enum(_) => Class::Int,
+            hblang::ty::Kind::Union(union) => {
+                for f in cx.union_fields(union) {
+                    classify(cx, f.ty, cls, off)?;
+                }
+                return Ok(());
+            }
+            hblang::ty::Kind::Slice(slice) if let Some(len) = cx.ins.slices[slice].len() => {
+                for i in 0..len as u32 {
+                    classify(
+                        cx,
+                        cx.ins.slices[slice].elem,
+                        cls,
+                        off + i * cx.size_of(cx.ins.slices[slice].elem),
+                    )?;
+                }
+                return Ok(());
+            }
+            hblang::ty::Kind::Slice(_) => {
+                classify(cx, hblang::ty::Id::UINT, cls, off)?;
+                classify(cx, hblang::ty::Id::UINT, cls, off + 8)?;
+                return Ok(());
+            }
+            hblang::ty::Kind::Opt(opt) => {
+                let base = cx.ins.opts[opt].base;
+                if cx.nieche_of(base).is_some() {
+                    classify(cx, base, cls, off)?;
+                } else {
+                    classify(cx, hblang::ty::Id::BOOL, cls, off)?;
+                    classify(cx, base, cls, off + cx.align_of(base))?;
+                }
+                return Ok(());
+            }
+
+            _ => unimplemented!(),
+        };
+
+        // Fill in `cls` for scalars (Int/Sse) and vectors (Sse).
+        let first = (off / 8) as usize;
+        let last = ((off + size - 1) / 8) as usize;
+        for cls in &mut cls[first..=last] {
+            *cls = Some(cls.map_or(c, |old| old.min(c)));
+
+            // Everything after the first Sse "eightbyte"
+            // component is the upper half of a register.
+            if c == Class::Sse {
+                c = Class::SseUp;
+            }
+        }
+
+        Ok(())
+    }
+
+    let size = cx.size_of(arg);
+    let n = ((size + 7) / 8) as usize;
+    if n > MAX_EIGHTBYTES {
+        return Err(Memory);
+    }
+
+    let mut cls = [None; MAX_EIGHTBYTES];
+    classify(cx, arg, &mut cls, 0)?;
+    if n > 2 {
+        if cls[0] != Some(Class::Sse) {
+            return Err(Memory);
+        }
+        if cls[1..n].iter().any(|&c| c != Some(Class::SseUp)) {
+            return Err(Memory);
+        }
+    } else {
+        let mut i = 0;
+        while i < n {
+            if cls[i] == Some(Class::SseUp) {
+                cls[i] = Some(Class::Sse);
+            } else if cls[i] == Some(Class::Sse) {
+                i += 1;
+                while i != n && cls[i] == Some(Class::SseUp) {
+                    i += 1;
+                }
+            } else {
+                i += 1;
+            }
+        }
+    }
+
+    Ok(cls)
+}
+
+fn reg_component(
+    cls: &[Option<Class>],
+    i: &mut usize,
+    size: hblang::ty::Size,
+) -> Option<cranelift_codegen::ir::Type> {
+    if *i >= cls.len() {
+        return None;
+    }
+
+    match cls[*i] {
+        None => None,
+        Some(Class::Int) => {
+            *i += 1;
+            Some(if size < 8 {
+                cranelift_codegen::ir::Type::int(size as u16 * 8).unwrap()
+            } else {
+                cranelift_codegen::ir::types::I64
+            })
+        }
+        Some(Class::Sse) => {
+            let vec_len =
+                1 + cls[*i + 1..].iter().take_while(|&&c| c == Some(Class::SseUp)).count();
+            *i += vec_len;
+            Some(if vec_len == 1 {
+                match size {
+                    4 => cranelift_codegen::ir::types::F32,
+                    _ => cranelift_codegen::ir::types::F64,
+                }
+            } else {
+                cranelift_codegen::ir::types::I64.by(vec_len as _).unwrap()
+            })
+        }
+        Some(c) => unreachable!("reg_component: unhandled class {:?}", c),
+    }
+}
+
+fn cast_target(
+    cls: &[Option<Class>],
+    size: hblang::ty::Size,
+    dest: &mut Vec<cranelift_codegen::ir::AbiParam>,
+) {
+    let mut i = 0;
+    let lo = reg_component(cls, &mut i, size).unwrap();
+    let offset = 8 * (i as u32);
+    dest.push(cranelift_codegen::ir::AbiParam::new(lo));
+    if size > offset {
+        if let Some(hi) = reg_component(cls, &mut i, size - offset) {
+            dest.push(cranelift_codegen::ir::AbiParam::new(hi));
+        }
+    }
+    assert_eq!(reg_component(cls, &mut i, 0), None);
+}
+
+const MAX_INT_REGS: usize = 6; // RDI, RSI, RDX, RCX, R8, R9
+const MAX_SSE_REGS: usize = 8; // XMM0-7
+
+pub struct Alloca {
+    int_regs: usize,
+    sse_regs: usize,
+}
+
+impl Alloca {
+    pub fn new() -> Self {
+        Self { int_regs: MAX_INT_REGS, sse_regs: MAX_SSE_REGS }
+    }
+
+    pub fn next(
+        &mut self,
+        is_arg: bool,
+        arg: hblang::ty::Id,
+        cx: &hblang::ty::Types,
+        dest: &mut Vec<cranelift_codegen::ir::AbiParam>,
+    ) {
+        let mut cls_or_mem = classify_arg(cx, arg);
+
+        if is_arg {
+            if let Ok(cls) = cls_or_mem {
+                let mut needed_int = 0;
+                let mut needed_sse = 0;
+                for c in cls {
+                    match c {
+                        Some(Class::Int) => needed_int += 1,
+                        Some(Class::Sse) => needed_sse += 1,
+                        _ => {}
+                    }
+                }
+                match (self.int_regs.checked_sub(needed_int), self.sse_regs.checked_sub(needed_sse))
+                {
+                    (Some(left_int), Some(left_sse)) => {
+                        self.int_regs = left_int;
+                        self.sse_regs = left_sse;
+                    }
+                    _ => {
+                        // Not enough registers for this argument, so it will be
+                        // passed on the stack, but we only mark aggregates
+                        // explicitly as indirect `byval` arguments, as LLVM will
+                        // automatically put immediates on the stack itself.
+                        if arg.is_aggregate(cx) {
+                            cls_or_mem = Err(Memory);
+                        }
+                    }
+                }
+            }
+        }
+
+        match cls_or_mem {
+            Err(Memory) => {
+                if is_arg {
+                    dest.push(cranelift_codegen::ir::AbiParam::new(
+                        cranelift_codegen::ir::types::I64,
+                    ));
+                } else {
+                    dest.push(cranelift_codegen::ir::AbiParam::special(
+                        cranelift_codegen::ir::types::I64,
+                        cranelift_codegen::ir::ArgumentPurpose::StructReturn,
+                    ));
+                }
+            }
+            Ok(ref cls) => {
+                // split into sized chunks passed individually
+                if arg.is_aggregate(cx) {
+                    cast_target(cls, cx.size_of(arg), dest);
+                } else {
+                    dest.push(cranelift_codegen::ir::AbiParam::new(
+                        reg_component(cls, &mut 0, cx.size_of(arg)).unwrap(),
+                    ));
+                }
+            }
+        }
+    }
+}
diff --git a/depell/wasm-hbc/src/lib.rs b/depell/wasm-hbc/src/lib.rs
index c34f2adc..1c7316b6 100644
--- a/depell/wasm-hbc/src/lib.rs
+++ b/depell/wasm-hbc/src/lib.rs
@@ -9,7 +9,7 @@ use {
         backend::hbvm::HbvmBackend,
         son::{Codegen, CodegenCtx},
         ty::Module,
-        Ent,
+        utils::Ent,
     },
 };
 
diff --git a/lang/Cargo.toml b/lang/Cargo.toml
index effdee22..8ce808f1 100644
--- a/lang/Cargo.toml
+++ b/lang/Cargo.toml
@@ -3,10 +3,6 @@ name = "hblang"
 version = "0.1.0"
 edition = "2021"
 
-[[bin]]
-name = "hbc"
-path = "src/main.rs"
-
 [[bin]]
 name = "fuzz"
 path = "src/fuzz_main.rs"
diff --git a/lang/src/backend/hbvm.rs b/lang/src/backend/hbvm.rs
index af18293a..f3a63803 100644
--- a/lang/src/backend/hbvm.rs
+++ b/lang/src/backend/hbvm.rs
@@ -8,7 +8,7 @@ use {
         utils::{EntSlice, EntVec},
     },
     alloc::{boxed::Box, collections::BTreeMap, string::String, vec::Vec},
-    core::{assert_matches::debug_assert_matches, mem, ops::Range},
+    core::{assert_matches::debug_assert_matches, error, mem, ops::Range},
     hbbytecode::{self as instrs, *},
     reg::Reg,
 };
@@ -106,6 +106,8 @@ pub struct HbvmBackend {
     offsets: Vec<Offset>,
 }
 
+pub const TARGET_TRIPLE: &str = "hbvm-virt-ableos";
+
 impl HbvmBackend {
     fn emit(&mut self, instr: (usize, [u8; instrs::MAX_SIZE])) {
         emit(&mut self.code, instr);
@@ -113,12 +115,18 @@ impl HbvmBackend {
 }
 
 impl Backend for HbvmBackend {
-    fn assemble_bin(&mut self, entry: ty::Func, types: &Types, to: &mut Vec<u8>) {
+    fn assemble_bin(
+        &mut self,
+        entry: ty::Func,
+        types: &Types,
+        files: &EntSlice<Module, parser::Ast>,
+        to: &mut Vec<u8>,
+    ) {
         to.extend([0u8; HEADER_SIZE]);
 
         binary_prelude(to);
         let AssemblySpec { code_length, data_length, entry } =
-            self.assemble_reachable(entry, types, to);
+            self.assemble_reachable(entry, types, files, to);
 
         let exe = AbleOsExecutableHeader {
             magic_number: [0x15, 0x91, 0xD2],
@@ -138,6 +146,7 @@ impl Backend for HbvmBackend {
         &mut self,
         from: ty::Func,
         types: &Types,
+        _files: &EntSlice<Module, parser::Ast>,
         to: &mut Vec<u8>,
     ) -> AssemblySpec {
         debug_assert!(self.asm.frontier.is_empty());
@@ -215,7 +224,7 @@ impl Backend for HbvmBackend {
         types: &'a Types,
         files: &'a EntSlice<Module, parser::Ast>,
         output: &mut String,
-    ) -> Result<(), hbbytecode::DisasmError<'a>> {
+    ) -> Result<(), alloc::boxed::Box<dyn error::Error + Send + Sync + 'a>> {
         use hbbytecode::DisasmItem;
         let functions = types
             .ins
@@ -250,7 +259,7 @@ impl Backend for HbvmBackend {
                     }),
             )
             .collect::<BTreeMap<_, _>>();
-        hbbytecode::disasm(&mut sluce, &functions, output, eca_handler)
+        hbbytecode::disasm(&mut sluce, &functions, output, eca_handler).map_err(Into::into)
     }
 
     fn emit_ct_body(
diff --git a/lang/src/fs.rs b/lang/src/fs.rs
index 61a8f46b..81348603 100644
--- a/lang/src/fs.rs
+++ b/lang/src/fs.rs
@@ -1,6 +1,6 @@
 use {
     crate::{
-        backend::hbvm::HbvmBackend,
+        backend::{hbvm::HbvmBackend, Backend},
         parser::{Ast, Ctx, FileKind},
         son::{self},
         ty, FnvBuildHasher,
@@ -12,7 +12,6 @@ use {
         borrow::ToOwned,
         collections::VecDeque,
         eprintln,
-        ffi::OsStr,
         io::{self, Write as _},
         path::{Path, PathBuf},
         string::ToString,
@@ -72,6 +71,7 @@ pub struct Options<'a> {
     pub dump_asm: bool,
     pub extra_threads: usize,
     pub resolver: Option<PathResolver<'a>>,
+    pub backend: Option<&'a mut dyn Backend>,
 }
 
 impl<'a> Options<'a> {
@@ -79,6 +79,7 @@ impl<'a> Options<'a> {
         args: &[&str],
         out: &mut Vec<u8>,
         resolvers: &'a [(&str, PathResolver)],
+        backend: Option<&'a mut dyn Backend>,
     ) -> std::io::Result<Self> {
         if args.contains(&"--help") || args.contains(&"-h") {
             writeln!(out, "Usage: hbc [OPTIONS...] <FILE>")?;
@@ -124,6 +125,7 @@ impl<'a> Options<'a> {
                     )
                 })
                 .transpose()?,
+            backend,
         })
     }
 }
@@ -158,10 +160,11 @@ pub fn run_compiler(
         write!(out, "{}", &parsed.ast[0])?;
     } else {
         let mut backend = HbvmBackend::default();
+        let backend = options.backend.unwrap_or(&mut backend);
 
         let mut ctx = crate::son::CodegenCtx::default();
         *ctx.parser.errors.get_mut() = parsed.errors;
-        let mut codegen = son::Codegen::new(&mut backend, &parsed.ast, &mut ctx);
+        let mut codegen = son::Codegen::new(backend, &parsed.ast, &mut ctx);
         codegen.push_embeds(parsed.embeds);
         codegen.generate(ty::Module::MAIN);
 
@@ -299,7 +302,7 @@ pub struct CantLoadFile {
 
 impl core::fmt::Display for CantLoadFile {
     fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
-        write!(f, "can't load file: {}", display_rel_path(&self.path),)
+        write!(f, "can't load file: {}", crate::display_rel_path(&self.path),)
     }
 }
 
@@ -350,7 +353,7 @@ pub fn parse_from_fs(
                 if !physiscal_path.exists() {
                     return Err(io::Error::new(
                         io::ErrorKind::NotFound,
-                        format!("can't find file: {}", display_rel_path(&physiscal_path)),
+                        format!("can't find file: {}", crate::display_rel_path(&physiscal_path)),
                     ));
                 }
 
@@ -377,7 +380,7 @@ pub fn parse_from_fs(
                         e.kind(),
                         format!(
                             "can't load embed file: {}: {e}",
-                            display_rel_path(&physiscal_path)
+                            crate::display_rel_path(&physiscal_path)
                         ),
                     )
                 })?;
@@ -395,7 +398,7 @@ pub fn parse_from_fs(
         let path = path.to_str().ok_or_else(|| {
             io::Error::new(
                 io::ErrorKind::InvalidData,
-                format!("path contains invalid characters: {}", display_rel_path(&path)),
+                format!("path contains invalid characters: {}", crate::display_rel_path(&path)),
             )
         })?;
         Ok(Ast::new(path, std::fs::read_to_string(path)?, ctx, &mut |path, from, kind| {
@@ -441,9 +444,3 @@ pub fn parse_from_fs(
         errors,
     })
 }
-
-pub fn display_rel_path(path: &(impl AsRef<OsStr> + ?Sized)) -> std::path::Display {
-    static CWD: std::sync::LazyLock<PathBuf> =
-        std::sync::LazyLock::new(|| std::env::current_dir().unwrap_or_default());
-    std::path::Path::new(path).strip_prefix(&*CWD).unwrap_or(std::path::Path::new(path)).display()
-}
diff --git a/lang/src/lib.rs b/lang/src/lib.rs
index e93bde2a..3e8d3289 100644
--- a/lang/src/lib.rs
+++ b/lang/src/lib.rs
@@ -33,8 +33,10 @@
 
 #[cfg(feature = "std")]
 pub use fs::*;
-pub use utils::Ent;
-use {self::ty::Builtin, alloc::vec::Vec};
+use {
+    self::{ty::Builtin, utils::Ent},
+    alloc::vec::Vec,
+};
 
 #[macro_use]
 extern crate alloc;
@@ -72,6 +74,7 @@ pub mod backend {
             utils::EntSlice,
         },
         alloc::{string::String, vec::Vec},
+        core::error,
     };
 
     pub mod hbvm;
@@ -87,6 +90,7 @@ pub mod backend {
             &mut self,
             from: ty::Func,
             types: &Types,
+            files: &EntSlice<Module, parser::Ast>,
             to: &mut Vec<u8>,
         ) -> AssemblySpec;
         fn disasm<'a>(
@@ -96,11 +100,11 @@ pub mod backend {
             types: &'a Types,
             files: &'a EntSlice<Module, parser::Ast>,
             output: &mut String,
-        ) -> Result<(), hbbytecode::DisasmError<'a>>;
+        ) -> Result<(), alloc::boxed::Box<dyn error::Error + Send + Sync + 'a>>;
         fn emit_body(
             &mut self,
             id: ty::Func,
-            ci: &Nodes,
+            nodes: &Nodes,
             tys: &Types,
             files: &EntSlice<Module, parser::Ast>,
         );
@@ -108,20 +112,26 @@ pub mod backend {
         fn emit_ct_body(
             &mut self,
             id: ty::Func,
-            ci: &Nodes,
+            nodes: &Nodes,
             tys: &Types,
             files: &EntSlice<Module, parser::Ast>,
         ) {
-            self.emit_body(id, ci, tys, files);
+            self.emit_body(id, nodes, tys, files);
         }
 
-        fn assemble_bin(&mut self, from: ty::Func, types: &Types, to: &mut Vec<u8>) {
-            self.assemble_reachable(from, types, to);
+        fn assemble_bin(
+            &mut self,
+            from: ty::Func,
+            types: &Types,
+            files: &EntSlice<Module, parser::Ast>,
+            to: &mut Vec<u8>,
+        ) {
+            self.assemble_reachable(from, types, files, to);
         }
     }
 }
 
-mod utils;
+pub mod utils;
 
 mod debug {
     use core::fmt::Debug;
@@ -521,3 +531,15 @@ fn test_parse_files(
         embed_map.iter().map(|&(_, content)| content.to_owned().into_bytes()).collect(),
     )
 }
+
+#[cfg(feature = "std")]
+pub fn display_rel_path(path: &(impl AsRef<std::ffi::OsStr> + ?Sized)) -> std::path::Display {
+    static CWD: std::sync::LazyLock<std::path::PathBuf> =
+        std::sync::LazyLock::new(|| std::env::current_dir().unwrap_or_default());
+    std::path::Path::new(path).strip_prefix(&*CWD).unwrap_or(std::path::Path::new(path)).display()
+}
+
+#[cfg(not(feature = "std"))]
+pub fn display_rel_path(path: &str) -> &str {
+    path
+}
diff --git a/lang/src/parser.rs b/lang/src/parser.rs
index 50359391..a105c6af 100644
--- a/lang/src/parser.rs
+++ b/lang/src/parser.rs
@@ -1461,10 +1461,7 @@ impl<D: core::fmt::Display> core::fmt::Display for Report<'_, D> {
 
 fn report_to(file: &str, path: &str, pos: Pos, msg: &dyn fmt::Display, out: &mut impl fmt::Write) {
     let (line, mut col) = lexer::line_col(file.as_bytes(), pos);
-    #[cfg(feature = "std")]
-    let disp = crate::fs::display_rel_path(path);
-    #[cfg(not(feature = "std"))]
-    let disp = path;
+    let disp = crate::display_rel_path(path);
     _ = writeln!(out, "{}:{}:{}: {}", disp, line, col, msg);
 
     let line = &file[file[..pos as usize].rfind('\n').map_or(0, |i| i + 1)
diff --git a/lang/src/son.rs b/lang/src/son.rs
index b6e5bf3a..e5e9563f 100644
--- a/lang/src/son.rs
+++ b/lang/src/son.rs
@@ -27,10 +27,10 @@ use {
     core::{
         assert_matches::debug_assert_matches,
         cell::RefCell,
+        error,
         fmt::{self, Debug, Display, Write},
         format_args as fa, mem,
     },
-    hbbytecode::DisasmError,
 };
 
 const DEFAULT_ACLASS: usize = 0;
@@ -616,16 +616,20 @@ impl<'a> Codegen<'a> {
 
     pub fn assemble_comptime(&mut self) -> Comptime {
         self.ct.code.clear();
-        self.backend.assemble_bin(ty::Func::MAIN, self.tys, &mut self.ct.code);
+        self.backend.assemble_bin(ty::Func::MAIN, self.tys, self.files, &mut self.ct.code);
         self.ct.reset();
         core::mem::take(self.ct)
     }
 
     pub fn assemble(&mut self, buf: &mut Vec<u8>) {
-        self.backend.assemble_bin(ty::Func::MAIN, self.tys, buf);
+        self.backend.assemble_bin(ty::Func::MAIN, self.tys, self.files, buf);
     }
 
-    pub fn disasm(&mut self, output: &mut String, bin: &[u8]) -> Result<(), DisasmError> {
+    pub fn disasm(
+        &mut self,
+        output: &mut String,
+        bin: &[u8],
+    ) -> Result<(), alloc::boxed::Box<dyn error::Error + Send + Sync + '_>> {
         self.backend.disasm(bin, &mut |_| {}, self.tys, self.files, output)
     }
 
@@ -669,7 +673,8 @@ impl<'a> Codegen<'a> {
 
         // TODO: return them back
 
-        let entry = self.ct_backend.assemble_reachable(fuc, self.tys, &mut self.ct.code).entry;
+        let entry =
+            self.ct_backend.assemble_reachable(fuc, self.tys, self.files, &mut self.ct.code).entry;
 
         #[cfg(debug_assertions)]
         {
@@ -4403,8 +4408,24 @@ mod tests {
         core::fmt::Write,
     };
 
+    pub struct Logger;
+
+    impl log::Log for Logger {
+        fn enabled(&self, _: &log::Metadata) -> bool {
+            true
+        }
+
+        fn log(&self, record: &log::Record) {
+            if self.enabled(record.metadata()) {
+                std::eprintln!("{}", record.args())
+            }
+        }
+
+        fn flush(&self) {}
+    }
+
     fn generate(ident: &str, input: &str, output: &mut String) {
-        _ = log::set_logger(&crate::fs::Logger);
+        _ = log::set_logger(&Logger);
         log::set_max_level(log::LevelFilter::Info);
         //log::set_max_level(log::LevelFilter::Trace);
 
diff --git a/lang/src/ty.rs b/lang/src/ty.rs
index d919d42a..d8b621ba 100644
--- a/lang/src/ty.rs
+++ b/lang/src/ty.rs
@@ -83,7 +83,7 @@ pub enum Arg {
 }
 
 impl ArgIter {
-    pub(crate) fn next(&mut self, tys: &Types) -> Option<Arg> {
+    pub fn next(&mut self, tys: &Types) -> Option<Arg> {
         let ty = tys.ins.args[self.0.next()?];
         if ty == Id::TYPE {
             return Some(Arg::Type(tys.ins.args[self.0.next().unwrap()]));
@@ -91,7 +91,7 @@ impl ArgIter {
         Some(Arg::Value(ty))
     }
 
-    pub(crate) fn next_value(&mut self, tys: &Types) -> Option<Id> {
+    pub fn next_value(&mut self, tys: &Types) -> Option<Id> {
         loop {
             match self.next(tys)? {
                 Arg::Type(_) => continue,
@@ -299,6 +299,10 @@ impl Id {
             _ => false,
         }
     }
+
+    pub fn is_aggregate(&self, tys: &Types) -> bool {
+        self.loc(tys) == Loc::Stack
+    }
 }
 
 #[derive(PartialEq, Eq, Clone, Copy)]
diff --git a/lang/src/utils.rs b/lang/src/utils.rs
index 2fce52a3..0a9bdb40 100644
--- a/lang/src/utils.rs
+++ b/lang/src/utils.rs
@@ -363,6 +363,10 @@ impl Vc {
         }
     }
 
+    pub fn is_empty(&self) -> bool {
+        self.len() == 0
+    }
+
     fn len_mut(&mut self) -> &mut Nid {
         unsafe {
             if self.is_inline() {
diff --git a/smh.hb b/smh.hb
index e69de29b..935e4eb4 100644
--- a/smh.hb
+++ b/smh.hb
@@ -0,0 +1,3 @@
+main := fn(): uint {
+	return 69
+}