From f67fdaa5f78efcad7efe0ca59069f57e957bf833 Mon Sep 17 00:00:00 2001
From: David Gonzalez Martin <davidgmbb@gmail.com>
Date: Tue, 25 Feb 2025 21:38:31 -0600
Subject: [PATCH] Extern functions and basic C ABI support

---
 src/LLVM.zig           |   2 +
 src/converter.zig      | 948 ++++++++++++++++++++++++++++++++++++-----
 src/converter_test.zig |   5 +
 src/llvm_api.zig       |   1 +
 src/main.zig           |   1 +
 tests/extern.bbb       |   5 +
 6 files changed, 846 insertions(+), 116 deletions(-)
 create mode 100644 tests/extern.bbb

diff --git a/src/LLVM.zig b/src/LLVM.zig
index 70ae75c..142d942 100644
--- a/src/LLVM.zig
+++ b/src/LLVM.zig
@@ -677,6 +677,8 @@ pub const Builder = opaque {
     pub fn create_sign_extend(builder: *Builder, value: *Value, destination_type: *Type) *Value {
         return api.LLVMBuildSExt(builder, value, destination_type, "");
     }
+
+    pub const create_unreachable = api.LLVMBuildUnreachable;
 };
 
 pub const GlobalValue = opaque {
diff --git a/src/converter.zig b/src/converter.zig
index 8816c7a..62079ff 100644
--- a/src/converter.zig
+++ b/src/converter.zig
@@ -100,6 +100,8 @@ const Module = struct {
     values: Value.Array = .{},
     current_function: ?*Variable = null,
     debug_tag: c_uint = 0,
+    void_type: *Type = undefined,
+    noreturn_type: *Type = undefined,
 
     const LLVM = struct {
         context: *llvm.Context,
@@ -135,13 +137,6 @@ const Module = struct {
         }
     }
 
-    pub fn void_type(module: *Module) *Type {
-        const index = 8;
-        const result = module.get_type(index);
-        assert(result.bb == .void);
-        return result;
-    }
-
     pub fn initialize(arena: *Arena, options: ConvertOptions) *Module {
         const context = llvm.Context.create();
         const handle = context.create_module(options.name);
@@ -185,7 +180,7 @@ const Module = struct {
 
         const llvm_i128 = context.get_integer_type(128).to_type();
 
-        _ = module.types.add(.{
+        module.void_type = module.types.add(.{
             .name = "void",
             .llvm = .{
                 .handle = context.get_void_type(),
@@ -252,6 +247,15 @@ const Module = struct {
             });
         }
 
+        module.noreturn_type = module.types.add(.{
+            .name = "noreturn",
+            .llvm = .{
+                .handle = context.get_void_type(),
+                .debug = if (maybe_di_builder) |di_builder| di_builder.create_basic_type("noreturn", 0, .void, .{ .no_return = true }) else undefined,
+            },
+            .bb = .noreturn,
+        });
+
         const infer_or_ignore_value = module.values.add();
         infer_or_ignore_value.* = .{
             .llvm = undefined,
@@ -268,7 +272,6 @@ pub const Function = struct {
     current_scope: *llvm.DI.Scope,
     locals: Variable.Array = .{},
     arguments: Variable.Array = .{},
-    calling_convention: CallingConvention,
 };
 
 pub const ConstantInteger = struct {
@@ -320,9 +323,26 @@ const Field = struct {
 };
 
 const FunctionType = struct {
-    semantic_argument_types: []const *Type,
+    return_type_abi: Abi.Information,
     semantic_return_type: *Type,
+    semantic_argument_types: [*]const *Type,
+    argument_type_abis: [*]const Abi.Information,
+    abi_argument_types: [*]const *Type,
+    semantic_argument_count: u32,
+    abi_argument_count: u32,
     calling_convention: CallingConvention,
+
+    fn get_semantic_argument_types(function_type: *const FunctionType) []const *Type {
+        return function_type.semantic_argument_types[0..function_type.semantic_argument_count];
+    }
+
+    fn get_argument_type_abis(function_type: *const FunctionType) []const Abi.Information {
+        return function_type.argument_type_abis[0..function_type.semantic_argument_count];
+    }
+
+    fn get_abi_argument_types(function_type: *const FunctionType) []const *Type {
+        return function_type.abi_argument_types[0..function_type.abi_argument_count];
+    }
 };
 
 const StructType = struct {
@@ -350,6 +370,7 @@ pub const Type = struct {
 
     pub const BB = union(enum) {
         void,
+        noreturn,
         forward_declaration,
         integer: struct {
             bit_count: u32,
@@ -361,12 +382,19 @@ pub const Type = struct {
         array: ArrayType,
     };
 
+    pub fn is_aggregate(ty: *const Type) bool {
+        return switch (ty.bb) {
+            .@"struct" => true,
+            else => false,
+        };
+    }
+
     pub fn get_bit_size(ty: *const Type) u64 {
         return switch (ty.bb) {
             .integer => |integer| integer.bit_count,
             .@"struct" => |struct_type| struct_type.bit_size,
             .bits => |bits| bits.backing_type.get_bit_size(),
-            .void, .forward_declaration, .function => unreachable,
+            .void, .forward_declaration, .function, .noreturn => unreachable,
             .array => |*array| array.element_type.get_bit_size() * array.element_count.?,
         };
     }
@@ -376,7 +404,7 @@ pub const Type = struct {
             .integer => |integer| @divExact(@max(8, lib.next_power_of_two(integer.bit_count)), 8),
             .@"struct" => |struct_type| struct_type.byte_size,
             .bits => |bits| bits.backing_type.get_byte_size(),
-            .void, .forward_declaration, .function => unreachable,
+            .void, .forward_declaration, .function, .noreturn => unreachable,
             .array => |*array| array.element_type.get_byte_size() * array.element_count.?,
         };
     }
@@ -386,7 +414,7 @@ pub const Type = struct {
             .integer => |integer| integer.bit_count,
             .@"struct" => |struct_type| struct_type.bit_alignment,
             .bits => |bits| bits.backing_type.get_bit_alignment(),
-            .void, .forward_declaration, .function => unreachable,
+            .void, .forward_declaration, .function, .noreturn => unreachable,
             .array => |*array| array.element_type.get_bit_alignment(),
         };
     }
@@ -396,7 +424,7 @@ pub const Type = struct {
             .integer => |integer| @divExact(@max(8, lib.next_power_of_two(integer.bit_count)), 8),
             .@"struct" => |struct_type| struct_type.byte_alignment,
             .bits => |bits| bits.backing_type.get_byte_alignment(),
-            .void, .forward_declaration, .function => unreachable,
+            .void, .forward_declaration, .function, .noreturn => unreachable,
             .array => |*array| array.element_type.get_byte_alignment(),
         };
     }
@@ -521,6 +549,8 @@ const Converter = struct {
                     const bit_count: u32 = @intCast(lib.parse.integer_decimal(identifier[1..]));
                     const ty = module.integer_type(bit_count, signedness);
                     return ty;
+                } else if (lib.string.equal(identifier, "noreturn")) {
+                    return module.noreturn_type;
                 } else {
                     const ty = module.types.find(identifier) orelse @trap();
                     return ty;
@@ -693,6 +723,65 @@ const Converter = struct {
         }
     }
 
+    fn parse_call(noalias converter: *Converter, noalias module: *Module, callable: *Value) *Value {
+        var llvm_abi_argument_value_buffer: [64]*llvm.Value = undefined;
+        const function_type = callable.type;
+        const calling_convention = function_type.bb.function.calling_convention;
+        _ = calling_convention;
+        const llvm_indirect_return_value: ?*Value = switch (function_type.bb.function.return_type_abi.kind) {
+            .indirect => @trap(),
+            else => null,
+        };
+
+        var semantic_argument_count: usize = 0;
+        var abi_argument_count: usize = 0;
+        const function_semantic_argument_count = function_type.bb.function.semantic_argument_count;
+
+        while (true) : (semantic_argument_count += 1) {
+            converter.skip_space();
+
+            if (converter.consume_character_if_match(right_parenthesis)) {
+                break;
+            }
+
+            const semantic_argument_index = semantic_argument_count;
+            if (semantic_argument_index >= function_semantic_argument_count) {
+                converter.report_error();
+            }
+
+            const semantic_argument_value = converter.parse_value(module, function_type.bb.function.semantic_argument_types[semantic_argument_index]);
+
+            const argument_abi = function_type.bb.function.argument_type_abis[semantic_argument_index];
+
+            switch (argument_abi.kind) {
+                .direct => {
+                    llvm_abi_argument_value_buffer[abi_argument_count] = semantic_argument_value.llvm;
+                    abi_argument_count += 1;
+                },
+                else => @trap(),
+            }
+        }
+
+        assert(abi_argument_count == function_type.bb.function.abi_argument_count);
+
+        const llvm_abi_argument_values = llvm_abi_argument_value_buffer[0..abi_argument_count];
+        const llvm_call = module.llvm.builder.create_call(callable.type.llvm.handle.to_function(), callable.llvm, llvm_abi_argument_values);
+        llvm_call.to_instruction().set_calling_convention(callable.llvm.get_calling_convention());
+
+        if (llvm_indirect_return_value) |indirect_value| {
+            _ = indirect_value;
+            @trap();
+        } else {
+            const call = module.values.add();
+            call.* = .{
+                .llvm = llvm_call,
+                .type = function_type.bb.function.semantic_return_type,
+                .bb = .instruction,
+            };
+            return call;
+        }
+    }
+
     fn parse_block(noalias converter: *Converter, noalias module: *Module) void {
         converter.skip_space();
 
@@ -862,9 +951,16 @@ const Converter = struct {
                         const variable = if (current_function.locals.find(statement_start_identifier)) |local| local else if (module.globals.find(statement_start_identifier)) |global| global else {
                             converter.report_error();
                         };
-                        const call = module.llvm.builder.create_call(variable.value.type.llvm.handle.to_function(), variable.value.llvm, &.{});
-                        _ = call;
-                        @trap();
+                        const return_value = converter.parse_call(module, variable.value);
+                        const is_noreturn = return_value.type.bb == .noreturn;
+                        const is_valid = return_value.type.bb == .void or is_noreturn;
+                        if (!is_valid) {
+                            converter.report_error();
+                        }
+
+                        if (is_noreturn) {
+                            _ = module.llvm.builder.create_unreachable();
+                        }
                     } else {
                         converter.report_error();
                     }
@@ -1347,35 +1443,7 @@ const Converter = struct {
                         converter.skip_space();
 
                         if (converter.consume_character_if_match(left_parenthesis)) {
-                            var llvm_arguments: [64]*llvm.Value = undefined;
-                            var argument_count: usize = 0;
-                            while (true) : (argument_count += 1) {
-                                converter.skip_space();
-
-                                if (converter.consume_character_if_match(right_parenthesis)) {
-                                    break;
-                                }
-
-                                switch (variable.value.type.bb.function.calling_convention) {
-                                    .c => {
-                                        @trap();
-                                    },
-                                    .unknown => {
-                                        const argument_value = converter.parse_value(module, variable.value.type.bb.function.semantic_argument_types[argument_count]);
-                                        llvm_arguments[argument_count] = argument_value.llvm;
-                                    },
-                                }
-                            }
-
-                            const llvm_argument_values = llvm_arguments[0..argument_count];
-                            const llvm_call = module.llvm.builder.create_call(variable.value.type.llvm.handle.to_function(), variable.value.llvm, llvm_argument_values);
-                            llvm_call.to_instruction().set_calling_convention(variable.value.llvm.get_calling_convention());
-                            const call = module.values.add();
-                            call.* = .{
-                                .llvm = llvm_call,
-                                .type = variable.value.type,
-                                .bb = .instruction,
-                            };
+                            const call = converter.parse_call(module, variable.value);
                             break :b call;
                         } else if (converter.consume_character_if_match('.')) {
                             converter.skip_space();
@@ -1512,6 +1580,397 @@ pub const BuildMode = enum {
     }
 };
 
+const CPUArchitecture = enum {
+    x86_64,
+};
+const OperatingSystem = enum {
+    linux,
+};
+pub const Target = struct {
+    cpu: CPUArchitecture,
+    os: OperatingSystem,
+
+    pub fn get_native() Target {
+        const builtin = @import("builtin");
+        return Target{
+            .cpu = switch (builtin.cpu.arch) {
+                .x86_64 => .x86_64,
+                else => @compileError("CPU not supported"),
+            },
+            .os = switch (builtin.os.tag) {
+                .linux => .linux,
+                else => @compileError("OS not supported"),
+            },
+        };
+    }
+};
+
+pub const Abi = struct {
+    const Kind = union(enum) {
+        ignore,
+        direct,
+        direct_pair: [2]*Type,
+        direct_coerce: *Type,
+        direct_coerce_int,
+        direct_split_struct_i32,
+        expand_coerce,
+        indirect: struct {
+            type: *Type,
+            alignment: u32,
+        },
+        expand,
+    };
+
+    const Attributes = struct {
+        by_reg: bool = false,
+        zero_extend: bool = false,
+        sign_extend: bool = false,
+        realign: bool = false,
+        by_value: bool = false,
+    };
+
+    const Information = struct {
+        kind: Kind,
+        indices: [2]u16 = .{ 0, 0 },
+        attributes: Abi.Attributes = .{},
+    };
+
+    pub const SystemV = struct {
+        pub const RegisterCount = struct {
+            gpr: u32,
+            sse: u32,
+        };
+        pub const Class = enum {
+            none,
+            memory,
+            integer,
+            sse,
+            sseup,
+
+            fn merge(accumulator: Class, field: Class) Class {
+                assert(accumulator != .memory);
+                if (accumulator == field) {
+                    return accumulator;
+                } else {
+                    var a = accumulator;
+                    var f = field;
+                    if (@intFromEnum(accumulator) > @intFromEnum(field)) {
+                        a = field;
+                        f = accumulator;
+                    }
+
+                    return switch (a) {
+                        .none => f,
+                        .memory => .memory,
+                        .integer => .integer,
+                        .sse, .sseup => .sse,
+                    };
+                }
+            }
+        };
+
+        fn classify(ty: *Type, base_offset: u64) [2]Class {
+            var result: [2]Class = undefined;
+            const is_memory = base_offset >= 8;
+            const current_index = @intFromBool(is_memory);
+            const not_current_index = @intFromBool(!is_memory);
+            assert(current_index != not_current_index);
+            result[current_index] = .memory;
+            result[not_current_index] = .none;
+
+            switch (ty.bb) {
+                .void, .noreturn => result[current_index] = .none,
+                .bits => result[current_index] = .integer,
+                .integer => result[current_index] = .integer, // TODO: weird cases
+                //     const integer_index = ty.get_integer_index();
+                //     switch (integer_index) {
+                //         8 - 1,
+                //         16 - 1,
+                //         32 - 1,
+                //         64 - 1,
+                //         64 + 8 - 1,
+                //         64 + 16 - 1,
+                //         64 + 32 - 1,
+                //         64 + 64 - 1,
+                //         => result[current_index] = .integer,
+                //         else => unreachable,
+                //     }
+                // },
+                // .typed_pointer => result[current_index] = .integer,
+                .@"struct" => |struct_type| {
+                    if (struct_type.byte_size <= 64) {
+                        const has_variable_array = false;
+                        if (!has_variable_array) {
+                            // const struct_type = ty.get_payload(.@"struct");
+                            result[current_index] = .none;
+                            const is_union = false;
+                            var member_offset: u32 = 0;
+                            for (struct_type.fields) |field| {
+                                const offset = base_offset + member_offset;
+                                const member_size = field.type.get_byte_size();
+                                const member_alignment = field.type.get_byte_alignment();
+                                member_offset = @intCast(lib.align_forward_u64(member_offset + member_size, ty.get_byte_alignment()));
+                                const native_vector_size = 16;
+                                if (ty.get_byte_size() > 16 and ((!is_union and ty.get_byte_size() != member_size) or ty.get_byte_size() > native_vector_size)) {
+                                    result[0] = .memory;
+                                    const r = classify_post_merge(ty.get_byte_size(), result);
+                                    return r;
+                                }
+
+                                if (offset % member_alignment != 0) {
+                                    result[0] = .memory;
+                                    const r = classify_post_merge(ty.get_byte_size(), result);
+                                    return r;
+                                }
+
+                                const member_classes = classify(field.type, offset);
+                                for (&result, member_classes) |*r, m| {
+                                    const merge_result = r.merge(m);
+                                    r.* = merge_result;
+                                }
+
+                                if (result[0] == .memory or result[1] == .memory) break;
+                            }
+
+                            const final = classify_post_merge(ty.get_byte_size(), result);
+                            result = final;
+                        }
+                    }
+                },
+                .array => |*array_type| {
+                    if (ty.get_byte_size() <= 64) {
+                        if (base_offset % ty.get_byte_alignment() == 0) {
+                            result[current_index] = .none;
+
+                            const vector_size = 16;
+                            if (ty.get_byte_size() > 16 and (ty.get_byte_size() != array_type.element_type.get_byte_size() or ty.get_byte_size() > vector_size)) {
+                                unreachable;
+                            } else {
+                                var offset = base_offset;
+
+                                for (0..array_type.element_count.?) |_| {
+                                    const element_classes = classify(array_type.element_type, offset);
+                                    offset += array_type.element_type.get_byte_size();
+                                    const merge_result = [2]Class{ result[0].merge(element_classes[0]), result[1].merge(element_classes[1]) };
+                                    result = merge_result;
+                                    if (result[0] == .memory or result[1] == .memory) {
+                                        break;
+                                    }
+                                }
+
+                                const final_result = classify_post_merge(ty.get_byte_size(), result);
+                                assert(final_result[1] != .sseup or final_result[0] != .sse);
+                                result = final_result;
+                            }
+                        }
+                    }
+                },
+                else => |t| @panic(@tagName(t)),
+            }
+
+            return result;
+        }
+
+        fn classify_post_merge(size: u64, classes: [2]Class) [2]Class {
+            if (classes[1] == .memory) {
+                return .{ .memory, .memory };
+            } else if (size > 16 and (classes[0] != .sse or classes[1] != .sseup)) {
+                return .{ .memory, classes[1] };
+            } else if (classes[1] == .sseup and classes[0] != .sse and classes[0] != .sseup) {
+                return .{ classes[0], .sse };
+            } else {
+                return classes;
+            }
+        }
+
+        fn get_int_type_at_offset(ty: *Type, offset: u32, source_type: *Type, source_offset: u32) *Type {
+            switch (ty.bb) {
+                .bits => {
+                    @trap();
+                    // const bitfield = ty.get_payload(.bitfield);
+                    // return get_int_type_at_offset(bitfield.backing_type, offset, if (source_type == ty) bitfield.backing_type else source_type, source_offset);
+                },
+                .integer => |integer_type| {
+                    switch (integer_type.bit_count) {
+                        64 => return ty,
+                        32, 16, 8 => {
+                            if (offset != 0) unreachable;
+                            const start = source_offset + ty.get_byte_size();
+                            const end = source_offset + 8;
+                            if (contains_no_user_data(source_type, start, end)) {
+                                return ty;
+                            }
+                        },
+                        else => unreachable,
+                    }
+                },
+                // .typed_pointer => return if (offset == 0) ty else unreachable,
+                .@"struct" => {
+                    if (get_member_at_offset(ty, offset)) |field| {
+                        return get_int_type_at_offset(field.type, @intCast(offset - field.byte_offset), source_type, source_offset);
+                    }
+                    unreachable;
+                },
+                .array => {
+                    @trap();
+                    // const array_type = ty.get_payload(.array);
+                    // const element_type = array_type.descriptor.element_type;
+                    // const element_size = element_type.size;
+                    // const element_offset = (offset / element_size) * element_size;
+                    // return get_int_type_at_offset(element_type, @intCast(offset - element_offset), source_type, source_offset);
+                },
+                else => |t| @panic(@tagName(t)),
+            }
+
+            if (source_type.get_byte_size() - source_offset > 8) {
+                @trap();
+                // return &instance.threads[ty.sema.thread].integers[63].type;
+            } else {
+                // const byte_count =  source_type.size - source_offset;
+                // const bit_count = byte_count * 8;
+                // return &instance.threads[ty.sema.thread].integers[bit_count - 1].type;
+                @trap();
+            }
+
+            unreachable;
+        }
+
+        fn get_member_at_offset(ty: *Type, offset: u32) ?*Field {
+            if (ty.get_byte_size() <= offset) {
+                return null;
+            }
+
+            var offset_it: u32 = 0;
+            var last_match: ?*Field = null;
+
+            _ = &offset_it;
+            _ = &last_match;
+
+            @trap();
+
+            // const struct_type = ty.get_payload(.@"struct");
+            // for (struct_type.fields) |field| {
+            //     if (offset_it > offset) {
+            //         break;
+            //     }
+            //
+            //     last_match = field;
+            //     offset_it = @intCast(lib.align_forward(offset_it + field.type.size, ty.alignment));
+            // }
+            //
+            // assert(last_match != null);
+            // return last_match;
+        }
+
+        fn contains_no_user_data(ty: *Type, start: u64, end: u64) bool {
+            if (ty.get_byte_size() <= start) {
+                return true;
+            }
+
+            switch (ty.bb) {
+                .@"struct" => |*struct_type| {
+                    var offset: u64 = 0;
+
+                    for (struct_type.fields) |field| {
+                        if (offset >= end) break;
+                        const field_start = if (offset < start) start - offset else 0;
+                        if (!contains_no_user_data(field.type, field_start, end - offset)) return false;
+                        offset += field.type.get_byte_size();
+                    }
+
+                    return true;
+                },
+                .array => |array_type| {
+                    for (0..array_type.element_count.?) |i| {
+                        const offset = i * array_type.element_type.get_byte_size();
+                        if (offset >= end) break;
+                        const element_start = if (offset < start) start - offset else 0;
+                        if (!contains_no_user_data(array_type.element_type, element_start, end - offset)) return false;
+                    }
+
+                    return true;
+                },
+                // .anonymous_struct => unreachable,
+                else => return false,
+            }
+        }
+
+        fn get_argument_pair(types: [2]*Type) Abi.Information {
+            const low_size = types[0].get_byte_size();
+            const high_alignment = types[1].get_byte_alignment();
+            const high_start = lib.align_forward_u64(low_size, high_alignment);
+            assert(high_start == 8);
+            return .{
+                .kind = .{
+                    .direct_pair = types,
+                },
+            };
+        }
+
+        fn indirect_argument(ty: *Type, free_integer_registers: u32) Abi.Information {
+            const is_illegal_vector = false;
+            if (!ty.is_aggregate() and !is_illegal_vector) {
+                if (ty.bb == .integer and ty.get_bit_size() < 32) {
+                    unreachable;
+                } else {
+                    return .{
+                        .kind = .direct,
+                    };
+                }
+            } else {
+                if (free_integer_registers == 0) {
+                    if (ty.get_byte_alignment() <= 8 and ty.get_byte_size() <= 8) {
+                        unreachable;
+                    }
+                }
+
+                if (ty.get_byte_alignment() < 8) {
+                    return .{
+                        .kind = .{
+                            .indirect = .{
+                                .type = ty,
+                                .alignment = 8,
+                            },
+                        },
+                        .attributes = .{
+                            .realign = true,
+                            .by_value = true,
+                        },
+                    };
+                } else {
+                    return .{
+                        .kind = .{
+                            .indirect = .{
+                                .type = ty,
+                                .alignment = @intCast(ty.get_byte_alignment()),
+                            },
+                        },
+                        .attributes = .{
+                            .by_value = true,
+                        },
+                    };
+                }
+            }
+            unreachable;
+        }
+
+        fn indirect_return(ty: *Type) Function.Abi.Information {
+            if (ty.is_aggregate()) {
+                return .{
+                    .kind = .{
+                        .indirect = .{
+                            .type = ty,
+                            .alignment = ty.alignment,
+                        },
+                    },
+                };
+            } else {
+                unreachable;
+            }
+        }
+    };
+};
+
 const ConvertOptions = struct {
     content: []const u8,
     path: [:0]const u8,
@@ -1520,6 +1979,7 @@ const ConvertOptions = struct {
     name: []const u8,
     has_debug_info: bool,
     objects: []const [:0]const u8,
+    target: Target,
 };
 
 pub noinline fn convert(options: ConvertOptions) void {
@@ -1647,9 +2107,9 @@ pub noinline fn convert(options: ConvertOptions) void {
                             column: u32,
                         };
                         var argument_buffer: [64]Argument = undefined;
-                        var argument_count: u32 = 0;
+                        var semantic_argument_count: u32 = 0;
 
-                        while (converter.offset < converter.content.len and converter.content[converter.offset] != right_parenthesis) : (argument_count += 1) {
+                        while (converter.offset < converter.content.len and converter.content[converter.offset] != right_parenthesis) : (semantic_argument_count += 1) {
                             converter.skip_space();
 
                             const argument_line = converter.get_line();
@@ -1669,7 +2129,7 @@ pub noinline fn convert(options: ConvertOptions) void {
 
                             _ = converter.consume_character_if_match(',');
 
-                            argument_buffer[argument_count] = .{
+                            argument_buffer[semantic_argument_count] = .{
                                 .name = argument_name,
                                 .type = argument_type,
                                 .line = argument_line,
@@ -1681,32 +2141,274 @@ pub noinline fn convert(options: ConvertOptions) void {
 
                         converter.skip_space();
 
-                        const return_type = converter.parse_type(module);
+                        const semantic_return_type = converter.parse_type(module);
                         const linkage_name = global_name;
 
-                        var argument_type_buffer: [argument_buffer.len]*llvm.Type = undefined;
                         var debug_argument_type_buffer: [argument_buffer.len + 1]*llvm.DI.Type = undefined;
 
-                        const arguments = argument_buffer[0..argument_count];
-                        const argument_types = argument_type_buffer[0..argument_count];
-                        const debug_argument_types = debug_argument_type_buffer[0 .. argument_count + 1];
+                        const semantic_arguments = argument_buffer[0..semantic_argument_count];
+                        const semantic_argument_types = lib.global.arena.allocate(*Type, semantic_argument_count);
+                        for (semantic_arguments, semantic_argument_types) |argument, *argument_type| {
+                            argument_type.* = argument.type;
+                        }
+                        const semantic_debug_argument_types = debug_argument_type_buffer[0 .. semantic_argument_count + 1];
 
-                        debug_argument_types[0] = return_type.llvm.debug;
+                        semantic_debug_argument_types[0] = semantic_return_type.llvm.debug;
 
-                        if (argument_count > 0) {
-                            switch (calling_convention) {
-                                .unknown => {
-                                    for (arguments, argument_types, debug_argument_types[1..]) |*argument, *argument_type, *debug_argument_type| {
-                                        argument_type.* = argument.type.llvm.handle;
-                                        debug_argument_type.* = argument.type.llvm.debug;
-                                    }
-                                },
-                                // TODO: C calling convention
-                                .c => @trap(),
-                            }
+                        var return_type_abi: Abi.Information = undefined;
+                        var argument_type_abi_buffer: [64]Abi.Information = undefined;
+
+                        switch (calling_convention) {
+                            .unknown => {
+                                return_type_abi = .{ .kind = .direct };
+
+                                for (0..semantic_argument_count) |i| {
+                                    argument_type_abi_buffer[i] = .{
+                                        .kind = .direct,
+                                        .indices = .{ @intCast(i), @intCast(i + 1) },
+                                    };
+                                }
+                            },
+                            // TODO: C calling convention
+                            .c => {
+                                // Return type abi
+                                switch (options.target.cpu) {
+                                    .x86_64 => switch (options.target.os) {
+                                        .linux => {
+                                            return_type_abi = ret_ty_abi: {
+                                                const type_classes = Abi.SystemV.classify(semantic_return_type, 0);
+                                                assert(type_classes[1] != .memory or type_classes[0] == .memory);
+                                                assert(type_classes[1] != .sseup or type_classes[0] == .sse);
+
+                                                const result_type = switch (type_classes[0]) {
+                                                    .none => switch (type_classes[1]) {
+                                                        .none => break :ret_ty_abi .{
+                                                            .kind = .ignore,
+                                                        },
+                                                        else => |t| @panic(@tagName(t)),
+                                                    },
+                                                    .integer => b: {
+                                                        const result_type = Abi.SystemV.get_int_type_at_offset(semantic_return_type, 0, semantic_return_type, 0);
+                                                        if (type_classes[1] == .none and semantic_return_type.get_bit_size() < 32) {
+                                                            // const signed = switch (semantic_return_type.sema.id) {
+                                                            //     .integer => @intFromEnum(semantic_return_type.get_payload(.integer).signedness) != 0,
+                                                            //     .bitfield => false,
+                                                            //     else => |t| @panic(@tagName(t)),
+                                                            // };
+                                                            // _ = signed;
+
+                                                            @trap();
+                                                            // break :rta .{
+                                                            //     .kind = .{
+                                                            //         .direct_coerce = semantic_return_type,
+                                                            //     },
+                                                            //     .attributes = .{
+                                                            //         .sign_extend = signed,
+                                                            //         .zero_extend = !signed,
+                                                            //     },
+                                                            // };
+                                                        }
+
+                                                        break :b result_type;
+                                                    },
+                                                    .memory => @trap(), // break :rta Abi.SystemV.indirect_return(semantic_return_type),
+                                                    else => |t| @panic(@tagName(t)),
+                                                };
+
+                                                const high_part: ?*Type = switch (type_classes[1]) {
+                                                    .none, .memory => null,
+                                                    .integer => b: {
+                                                        assert(type_classes[0] != .none);
+                                                        const high_part = Abi.SystemV.get_int_type_at_offset(semantic_return_type, 8, semantic_return_type, 8);
+                                                        break :b high_part;
+                                                    },
+                                                    else => |t| @panic(@tagName(t)),
+                                                };
+
+                                                if (high_part) |hp| {
+                                                    _ = hp;
+                                                    @trap();
+                                                    // break :rta Abi.SystemV.get_argument_pair(.{ result_type, hp });
+                                                } else {
+                                                    // TODO
+                                                    const is_type = true;
+                                                    if (is_type) {
+                                                        if (result_type == semantic_return_type) {
+                                                            break :ret_ty_abi Abi.Information{
+                                                                .kind = .direct,
+                                                            };
+                                                        } else {
+                                                            @trap();
+                                                            // break :rta Function.Abi.Information{
+                                                            //     .kind = .{
+                                                            //         .direct_coerce = result_type,
+                                                            //     },
+                                                            // };
+                                                        }
+                                                    } else {
+                                                        unreachable;
+                                                    }
+                                                }
+                                            };
+
+                                            var available_registers = Abi.SystemV.RegisterCount{
+                                                .gpr = 6,
+                                                .sse = 8,
+                                            };
+
+                                            if (return_type_abi.kind == .indirect) {
+                                                available_registers.gpr -= 1;
+                                            }
+
+                                            const return_by_reference = false;
+                                            if (return_by_reference) {
+                                                @trap();
+                                            }
+
+                                            for (semantic_arguments, argument_type_abi_buffer[0..semantic_arguments.len]) |semantic_argument, *argument_type_abi| {
+                                                const semantic_argument_type = semantic_argument.type;
+                                                var needed_registers = Abi.SystemV.RegisterCount{
+                                                    .gpr = 0,
+                                                    .sse = 0,
+                                                };
+                                                const argument_type_abi_classification: Abi.Information = ata: {
+                                                    const type_classes = Abi.SystemV.classify(semantic_argument_type, 0);
+                                                    assert(type_classes[1] != .memory or type_classes[0] == .memory);
+                                                    assert(type_classes[1] != .sseup or type_classes[0] == .sse);
+
+                                                    _ = &needed_registers; // autofix
+
+                                                    const result_type = switch (type_classes[0]) {
+                                                        .integer => b: {
+                                                            needed_registers.gpr += 1;
+                                                            const result_type = Abi.SystemV.get_int_type_at_offset(semantic_argument_type, 0, semantic_argument_type, 0);
+                                                            if (type_classes[1] == .none and semantic_argument_type.get_bit_size() < 32) {
+                                                                const signed = switch (semantic_argument_type.bb) {
+                                                                    .integer => |integer_type| integer_type.signed,
+                                                                    .bits => false, // TODO: signedness?
+                                                                    else => |t| @panic(@tagName(t)),
+                                                                };
+
+                                                                break :ata .{
+                                                                    .kind = .{
+                                                                        .direct_coerce = semantic_argument_type,
+                                                                    },
+                                                                    .attributes = .{
+                                                                        .sign_extend = signed,
+                                                                        .zero_extend = !signed,
+                                                                    },
+                                                                };
+                                                            }
+                                                            break :b result_type;
+                                                        },
+                                                        .memory => break :ata Abi.SystemV.indirect_argument(semantic_argument_type, available_registers.gpr),
+                                                        else => |t| @panic(@tagName(t)),
+                                                    };
+                                                    const high_part: ?*Type = switch (type_classes[1]) {
+                                                        .none, .memory => null,
+                                                        .integer => b: {
+                                                            assert(type_classes[0] != .none);
+                                                            needed_registers.gpr += 1;
+                                                            const high_part = Abi.SystemV.get_int_type_at_offset(semantic_argument_type, 8, semantic_argument_type, 8);
+                                                            break :b high_part;
+                                                        },
+                                                        else => |t| @panic(@tagName(t)),
+                                                    };
+
+                                                    if (high_part) |hp| {
+                                                        break :ata Abi.SystemV.get_argument_pair(.{ result_type, hp });
+                                                    } else {
+                                                        // TODO
+                                                        const is_type = true;
+                                                        if (is_type) {
+                                                            if (result_type == semantic_argument_type) {
+                                                                break :ata Abi.Information{
+                                                                    .kind = .direct,
+                                                                };
+                                                            } else if (result_type.bb == .integer and semantic_argument_type.bb == .integer and semantic_argument_type.get_byte_size() == result_type.get_byte_size()) {
+                                                                unreachable;
+                                                            } else {
+                                                                break :ata Abi.Information{
+                                                                    .kind = .{
+                                                                        .direct_coerce = result_type,
+                                                                    },
+                                                                };
+                                                            }
+                                                        }
+                                                        unreachable;
+                                                    }
+                                                };
+                                                argument_type_abi.* = if (available_registers.sse < needed_registers.sse or available_registers.gpr < needed_registers.gpr) b: {
+                                                    break :b Abi.SystemV.indirect_argument(semantic_argument_type, available_registers.gpr);
+                                                } else b: {
+                                                    available_registers.gpr -= needed_registers.gpr;
+                                                    available_registers.sse -= needed_registers.sse;
+                                                    break :b argument_type_abi_classification;
+                                                };
+                                            }
+                                        },
+                                    },
+                                }
+                            },
                         }
 
-                        const llvm_function_type = llvm.Type.Function.get(return_type.llvm.handle, argument_types, false);
+                        const argument_type_abis = lib.global.arena.allocate(Abi.Information, semantic_arguments.len);
+                        @memcpy(argument_type_abis, argument_type_abi_buffer[0..semantic_arguments.len]);
+
+                        var abi_argument_type_buffer: [64]*Type = undefined;
+                        var abi_argument_type_count: usize = 0;
+
+                        var llvm_abi_argument_type_buffer: [64]*llvm.Type = undefined;
+
+                        const abi_return_type = switch (return_type_abi.kind) {
+                            .ignore, .direct => semantic_return_type,
+                            .direct_coerce => |coerced_type| coerced_type,
+                            // .indirect => |indirect| b: {
+                            //     _ = abi_argument_types.append(get_typed_pointer(thread, .{
+                            //         .pointee = indirect.type,
+                            //     }));
+                            //     break :b &thread.void;
+                            // },
+                            // .direct_pair => |pair| get_anonymous_two_field_struct(thread, pair),
+                            else => |t| @panic(@tagName(t)),
+                        };
+
+                        for (argument_type_abis, semantic_argument_types) |*argument_abi, original_argument_type| {
+                            const start: u16 = @intCast(abi_argument_type_count);
+                            switch (argument_abi.kind) {
+                                .direct => {
+                                    abi_argument_type_buffer[abi_argument_type_count] = original_argument_type;
+                                    llvm_abi_argument_type_buffer[abi_argument_type_count] = original_argument_type.llvm.handle;
+                                    abi_argument_type_count += 1;
+                                },
+                                .direct_coerce => |coerced_type| {
+                                    abi_argument_type_buffer[abi_argument_type_count] = coerced_type;
+                                    llvm_abi_argument_type_buffer[abi_argument_type_count] = coerced_type.llvm.handle;
+                                    abi_argument_type_count += 1;
+                                },
+                                .direct_pair => |pair| {
+                                    abi_argument_type_buffer[abi_argument_type_count] = pair[0];
+                                    llvm_abi_argument_type_buffer[abi_argument_type_count] = pair[0].llvm.handle;
+                                    abi_argument_type_count += 1;
+                                    abi_argument_type_buffer[abi_argument_type_count] = pair[1];
+                                    llvm_abi_argument_type_buffer[abi_argument_type_count] = pair[1].llvm.handle;
+                                    abi_argument_type_count += 1;
+                                },
+                                // .indirect => |indirect| _ = abi_argument_types.append(get_typed_pointer(thread, .{
+                                //     .pointee = indirect.type,
+                                // })),
+                                else => |t| @panic(@tagName(t)),
+                            }
+
+                            const end: u16 = @intCast(abi_argument_type_count);
+                            argument_abi.indices = .{ start, end };
+                        }
+
+                        const abi_argument_types = lib.global.arena.allocate(*Type, abi_argument_type_count);
+                        @memcpy(abi_argument_types, abi_argument_type_buffer[0..abi_argument_type_count]);
+                        const llvm_abi_argument_types = llvm_abi_argument_type_buffer[0..abi_argument_type_count];
+
+                        const llvm_function_type = llvm.Type.Function.get(abi_return_type.llvm.handle, llvm_abi_argument_types, false);
                         const llvm_handle = module.llvm.handle.create_function(.{
                             .name = global_name,
                             .linkage = switch (is_export or is_extern) {
@@ -1721,7 +2423,7 @@ pub noinline fn convert(options: ConvertOptions) void {
                         var subroutine_type: *llvm.DI.Type.Subroutine = undefined;
                         const current_scope: *llvm.DI.Scope = if (module.llvm.di_builder) |di_builder| blk: {
                             const subroutine_type_flags = llvm.DI.Flags{};
-                            subroutine_type = di_builder.create_subroutine_type(module.llvm.file, debug_argument_types, subroutine_type_flags);
+                            subroutine_type = di_builder.create_subroutine_type(module.llvm.file, semantic_debug_argument_types, subroutine_type_flags);
                             const scope_line: u32 = @intCast(converter.line_offset + 1);
                             const local_to_unit = !is_export and !is_extern;
                             const flags = llvm.DI.Flags{};
@@ -1741,15 +2443,20 @@ pub noinline fn convert(options: ConvertOptions) void {
                             .bb = .{
                                 .function = .{
                                     .calling_convention = calling_convention,
-                                    .semantic_return_type = return_type,
+                                    .semantic_return_type = semantic_return_type,
                                     .semantic_argument_types = blk: {
-                                        const semantic_argument_types = lib.global.arena.allocate(*Type, argument_count);
-                                        for (arguments, semantic_argument_types) |argument, *argument_type| {
+                                        const sema_arg_types = lib.global.arena.allocate(*Type, semantic_argument_count);
+                                        for (semantic_arguments, sema_arg_types) |argument, *argument_type| {
                                             argument_type.* = argument.type;
                                         }
 
-                                        break :blk semantic_argument_types;
+                                        break :blk sema_arg_types.ptr;
                                     },
+                                    .semantic_argument_count = semantic_argument_count,
+                                    .abi_argument_count = @intCast(abi_argument_type_count),
+                                    .abi_argument_types = abi_argument_types.ptr,
+                                    .argument_type_abis = argument_type_abis.ptr,
+                                    .return_type_abi = return_type_abi,
                                 },
                             },
                         });
@@ -1770,6 +2477,10 @@ pub noinline fn convert(options: ConvertOptions) void {
                             const entry_block = module.llvm.context.create_basic_block("entry", llvm_handle);
                             module.llvm.builder.position_at_end(entry_block);
 
+                            var llvm_argument_buffer: [argument_buffer.len]*llvm.Argument = undefined;
+                            llvm_handle.get_arguments(&llvm_argument_buffer);
+                            const llvm_arguments = llvm_argument_buffer[0..semantic_argument_count];
+
                             const value = module.values.add();
                             value.* = .{
                                 .llvm = llvm_handle.to_value(),
@@ -1777,7 +2488,6 @@ pub noinline fn convert(options: ConvertOptions) void {
                                 .bb = .{
                                     .function = .{
                                         .current_basic_block = entry_block,
-                                        .calling_convention = calling_convention,
                                         .current_scope = current_scope,
                                     },
                                 },
@@ -1791,65 +2501,71 @@ pub noinline fn convert(options: ConvertOptions) void {
                             module.current_function = global;
                             defer module.current_function = null;
 
-                            const argument_variables = global.value.bb.function.arguments.add_many(argument_count);
-
-                            for (argument_variables, arguments) |*argument_variable, *argument| {
-                                const argument_alloca = module.llvm.builder.create_alloca(argument.type.llvm.handle, argument.name);
-                                const argument_value = module.values.add();
-                                argument_value.* = .{
-                                    .llvm = argument_alloca,
-                                    .type = argument.type,
-                                    .bb = .argument,
-                                };
-                                argument_variable.* = .{
-                                    .value = argument_value,
-                                    .name = argument.name,
-                                };
+                            switch (return_type_abi.kind) {
+                                .indirect => |indirect| {
+                                    _ = indirect;
+                                    @trap();
+                                },
+                                else => {},
                             }
 
-                            var llvm_argument_buffer: [argument_buffer.len]*llvm.Argument = undefined;
-                            llvm_handle.get_arguments(&llvm_argument_buffer);
-                            const llvm_arguments = llvm_argument_buffer[0..argument_count];
+                            if (semantic_arguments.len > 0) {
+                                const argument_variables = global.value.bb.function.arguments.add_many(semantic_argument_count);
+                                for (semantic_arguments, argument_type_abis, argument_variables, 0..) |semantic_argument, argument_abi, *argument_variable, argument_index| {
+                                    const argument_abi_count = argument_abi.indices[1] - argument_abi.indices[0];
+                                    const LowerKind = union(enum) {
+                                        direct,
+                                        direct_pair: [2]*Type,
+                                        direct_coerce: *Type,
+                                        indirect,
+                                    };
+                                    const lower_kind: LowerKind = switch (argument_abi.kind) {
+                                        .direct => .direct,
+                                        .direct_coerce => |coerced_type| if (semantic_argument.type == coerced_type) .direct else .{ .direct_coerce = coerced_type },
+                                        .direct_pair => |pair| .{ .direct_pair = pair },
+                                        .indirect => .indirect,
+                                        else => @trap(),
+                                    };
 
-                            if (argument_count > 0) {
-                                switch (calling_convention) {
-                                    .unknown => {
-                                        for (argument_variables, llvm_arguments) |*argument_variable, llvm_argument| {
-                                            _ = module.llvm.builder.create_store(llvm_argument.to_value(), argument_variable.value.llvm);
-                                        }
-                                    },
-                                    .c => @trap(),
-                                }
+                                    const llvm_storage = switch (lower_kind) {
+                                        .direct => d: {
+                                            assert(argument_abi_count == 1);
+                                            const argument_alloca = module.llvm.builder.create_alloca(semantic_argument.type.llvm.handle, semantic_argument.name);
+                                            const abi_argument_index = argument_abi.indices[0];
+                                            const llvm_argument = llvm_arguments[abi_argument_index];
+                                            _ = module.llvm.builder.create_store(llvm_argument.to_value(), argument_alloca);
+                                            break :d argument_alloca;
+                                        },
+                                        else => @trap(),
+                                    };
 
-                                if (module.llvm.di_builder) |di_builder| {
-                                    for (argument_variables, arguments, 0..) |argument_variable, argument, argument_number| {
+                                    const argument_value = module.values.add();
+                                    argument_value.* = .{
+                                        .llvm = llvm_storage,
+                                        .type = semantic_argument.type,
+                                        .bb = .argument,
+                                    };
+                                    argument_variable.* = .{
+                                        .value = argument_value,
+                                        .name = semantic_argument.name,
+                                    };
+
+                                    if (module.llvm.di_builder) |di_builder| {
                                         const always_preserve = true;
                                         const flags = llvm.DI.Flags{};
-                                        const parameter_variable = di_builder.create_parameter_variable(global.value.bb.function.current_scope, argument_variable.name, @intCast(argument_number + 1), module.llvm.file, argument.line, argument.type.llvm.debug, always_preserve, flags);
+                                        const parameter_variable = di_builder.create_parameter_variable(global.value.bb.function.current_scope, semantic_argument.name, @intCast(argument_index + 1), module.llvm.file, semantic_argument.line, semantic_argument.type.llvm.debug, always_preserve, flags);
                                         const inlined_at: ?*llvm.DI.Metadata = null; // TODO
-                                        const debug_location = llvm.DI.create_debug_location(module.llvm.context, argument.line, argument.column, global.value.bb.function.current_scope, inlined_at);
-                                        _ = di_builder.insert_declare_record_at_end(argument_variable.value.llvm, parameter_variable, di_builder.null_expression(), debug_location, global.value.bb.function.current_basic_block);
+                                        const debug_location = llvm.DI.create_debug_location(module.llvm.context, semantic_argument.line, semantic_argument.column, global.value.bb.function.current_scope, inlined_at);
+                                        _ = di_builder.insert_declare_record_at_end(llvm_storage, parameter_variable, di_builder.null_expression(), debug_location, global.value.bb.function.current_basic_block);
                                     }
                                 }
                             }
 
                             converter.parse_block(module);
-
-                            if (lib.optimization_mode == .Debug and module.llvm.di_builder == null) {
-                                const verify_result = llvm_handle.verify();
-                                if (!verify_result.success) {
-                                    os.abort();
-                                }
-                            }
                         }
 
                         if (module.llvm.di_builder) |di_builder| {
                             di_builder.finalize_subprogram(llvm_handle.get_subprogram());
-                        } else if (lib.optimization_mode == .Debug and global.value.bb == .function) {
-                            const verify_result = llvm_handle.verify();
-                            if (!verify_result.success) {
-                                os.abort();
-                            }
                         }
                     },
                     .@"struct" => {
diff --git a/src/converter_test.zig b/src/converter_test.zig
index d9bdc54..67dd7c7 100644
--- a/src/converter_test.zig
+++ b/src/converter_test.zig
@@ -58,6 +58,7 @@ fn unit_test(allocator: std.mem.Allocator, options: InvokeWrapper) !void {
         .build_mode = options.build_mode,
         .name = options.name,
         .has_debug_info = options.has_debug_info,
+        .target = converter.Target.get_native(),
     });
     const run_result = std.process.Child.run(.{
         .allocator = allocator,
@@ -172,3 +173,7 @@ test "bits" {
 test "basic_array" {
     try invsrc(@src());
 }
+
+test "extern" {
+    try invsrc(@src());
+}
diff --git a/src/llvm_api.zig b/src/llvm_api.zig
index 101dc8a..5fd533f 100644
--- a/src/llvm_api.zig
+++ b/src/llvm_api.zig
@@ -57,6 +57,7 @@ pub extern fn LLVMBuildInBoundsGEP2(builder: *llvm.Builder, ty: *llvm.Type, aggr
 pub extern fn LLVMBuildInsertValue(builder: *llvm.Builder, aggregate: *llvm.Value, element: *llvm.Value, index: c_uint, name: [*:0]const u8) *llvm.Value;
 pub extern fn LLVMBuildZExt(builder: *llvm.Builder, value: *llvm.Value, destination_type: *llvm.Type, name: [*:0]const u8) *llvm.Value;
 pub extern fn LLVMBuildSExt(builder: *llvm.Builder, value: *llvm.Value, destination_type: *llvm.Type, name: [*:0]const u8) *llvm.Value;
+pub extern fn LLVMBuildUnreachable(builder: *llvm.Builder) *llvm.Value;
 
 pub extern fn LLVMSetCurrentDebugLocation2(builder: *llvm.Builder, location: ?*llvm.DI.Location) void;
 
diff --git a/src/main.zig b/src/main.zig
index ba62540..1cf654b 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -182,6 +182,7 @@ pub fn main(argc: c_int, argv: [*:null]const ?[*:0]const u8) callconv(.C) c_int
         .content = file_content,
         .path = file_path,
         .has_debug_info = true,
+        .target = converter.Target.get_native(),
     });
     return 0;
 }
diff --git a/tests/extern.bbb b/tests/extern.bbb
new file mode 100644
index 0000000..7bcbe8a
--- /dev/null
+++ b/tests/extern.bbb
@@ -0,0 +1,5 @@
+[extern] exit = fn [cc(c)] (exit_code: s32) noreturn;
+[export] main = fn [cc(c)] () s32
+{
+    exit(0);
+}