// Copyright (c) 2026 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module c import v2.ssa import strings pub struct Gen { mod &ssa.Module pub mut: link_builtin bool // When true, skip emitting runtime helpers (they come from builtin.o) mut: sb strings.Builder indent int current_fn_name string current_fn_idx int current_fn_ret ssa.TypeID } pub fn Gen.new(mod &ssa.Module) &Gen { return &Gen{ mod: mod sb: strings.new_builder(4096) } } // new_gen creates a C backend generator without relying on static method lookup. pub fn new_gen(mod &ssa.Module) &Gen { return &Gen{ mod: mod sb: strings.new_builder(4096) } } pub fn (mut g Gen) gen() string { g.sb.writeln('// Generated by V2 SSA Compiler') g.sb.writeln('#include ') g.sb.writeln('#include ') g.sb.writeln('#include ') g.sb.writeln('#include ') g.sb.writeln('#include ') g.sb.writeln('#include ') g.sb.writeln('#include ') g.sb.writeln('') // Undefine macOS macros that conflict with struct field names g.sb.writeln('#ifdef __APPLE__') g.sb.writeln('#undef sa_handler') g.sb.writeln('#undef sa_sigaction') g.sb.writeln('#endif') g.sb.writeln('') // Builtin type aliases g.sb.writeln('typedef int8_t i8;') g.sb.writeln('typedef int16_t i16;') g.sb.writeln('typedef int32_t i32;') g.sb.writeln('typedef int64_t i64;') g.sb.writeln('typedef uint8_t u8;') g.sb.writeln('typedef uint16_t u16;') g.sb.writeln('typedef uint32_t u32;') g.sb.writeln('typedef uint64_t u64;') g.sb.writeln('typedef float f32;') g.sb.writeln('typedef double f64;') g.sb.writeln('typedef int64_t isize;') g.sb.writeln('typedef uint64_t usize;') g.sb.writeln('typedef u8 rune;') g.sb.writeln('') // wyhash implementation (used by map) g.gen_wyhash() // Runtime helpers (only when not linking against builtin.o) if !g.link_builtin { g.gen_runtime_helpers() g.sb.writeln('') } // Struct declarations g.gen_struct_decls() // Global declarations g.gen_globals() // Function forward declarations for all functions g.gen_fn_forward_decls() // Spawn wrapper functions (pre-scanned from all spawn_call instructions) g.gen_spawn_wrappers() // Function bodies (skip stub functions - they'll come from builtin.o) for i, func in g.mod.funcs { if func.blocks.len == 0 { continue } if g.is_stub_function(func) { continue } g.current_fn_idx = i g.gen_function(func) } return g.sb.str() } // gen_spawn_wrappers pre-scans all functions for spawn_call instructions with // arguments and emits wrapper functions that unpack the args struct and call the // original function. These must be emitted before function bodies so that the // wrappers are visible at each pthread_create call site. fn (mut g Gen) gen_spawn_wrappers() { for func in g.mod.funcs { for blk_id in func.blocks { block := g.mod.blocks[blk_id] for instr_id in block.instrs { val := g.mod.values[instr_id] if val.kind != .instruction { continue } instr := g.mod.instrs[val.index] if instr.op !in [.spawn_call, .go_call] || instr.operands.len < 2 { continue } fn_ref := instr.operands[0] fn_val := g.mod.values[fn_ref] fn_name := sanitize_c_ident(fn_val.name) prefix := if instr.op == .spawn_call { '_spawn' } else { '_go' } wrapper_id := '${fn_name}_${val.name}' arg_count := instr.operands.len - 1 g.sb.write_string('struct ${prefix}_args_${wrapper_id} { ') for ai in 0 .. arg_count { arg_type := g.type_name(g.mod.values[instr.operands[ai + 1]].typ) g.sb.write_string('${arg_type} a${ai}; ') } g.sb.writeln('};') g.sb.writeln('static void* ${prefix}_wrapper_${wrapper_id}(void* _arg) {') g.sb.writeln('\tstruct ${prefix}_args_${wrapper_id}* _args = (struct ${prefix}_args_${wrapper_id}*)_arg;') g.sb.write_string('\t${fn_name}(') for ai in 0 .. arg_count { if ai > 0 { g.sb.write_string(', ') } g.sb.write_string('_args->a${ai}') } g.sb.writeln(');') g.sb.writeln('\tfree(_arg);') g.sb.writeln('\treturn NULL;') g.sb.writeln('}') g.sb.writeln('') } } } } fn (mut g Gen) gen_runtime_helpers() { // println g.sb.writeln('static void println(string s) {') g.sb.writeln('\tif (s.str && s.len > 0) {') g.sb.writeln('\t\tfwrite(s.str, 1, s.len, stdout);') g.sb.writeln('\t}') g.sb.writeln('\tputchar(10);') g.sb.writeln('}') g.sb.writeln('') // print g.sb.writeln('static void print(string s) {') g.sb.writeln('\tif (s.str && s.len > 0) {') g.sb.writeln('\t\tfwrite(s.str, 1, s.len, stdout);') g.sb.writeln('\t}') g.sb.writeln('}') g.sb.writeln('') // eprintln g.sb.writeln('static void eprintln(string s) {') g.sb.writeln('\tif (s.str && s.len > 0) {') g.sb.writeln('\t\tfwrite(s.str, 1, s.len, stderr);') g.sb.writeln('\t}') g.sb.writeln('\tfputc(10, stderr);') g.sb.writeln('}') g.sb.writeln('') // int_str helper (converts int to string) g.sb.writeln('static string int_str(i64 val) {') g.sb.writeln('\tstatic char buf[32];') g.sb.writeln('\tint len = snprintf(buf, sizeof(buf), "%lld", (long long)val);') g.sb.writeln('\treturn (string){.str = (i8*)buf, .len = len, .is_lit = 1};') g.sb.writeln('}') g.sb.writeln('') // string__plus helper g.sb.writeln('static string string__plus(string a, string b) {') g.sb.writeln('\ti64 new_len = a.len + b.len;') g.sb.writeln('\ti8* s = (i8*)malloc(new_len + 1);') g.sb.writeln('\tif (a.str && a.len > 0) memcpy(s, a.str, a.len);') g.sb.writeln('\tif (b.str && b.len > 0) memcpy(s + a.len, b.str, b.len);') g.sb.writeln('\ts[new_len] = 0;') g.sb.writeln('\treturn (string){.str = s, .len = new_len, .is_lit = 0};') g.sb.writeln('}') } fn (mut g Gen) gen_module_init_stubs() { // Generate empty stubs for module __v_init_consts functions // that the transformer injects into main() mut emitted := map[string]bool{} for func in g.mod.funcs { if func.blocks.len == 0 { continue } for block_id in func.blocks { block := g.mod.blocks[block_id] for instr_id in block.instrs { val := g.mod.values[instr_id] if val.kind != .instruction { continue } instr := g.mod.instrs[val.index] if instr.op == .call && instr.operands.len > 0 { fn_val := g.mod.values[instr.operands[0]] if fn_val.kind == .func_ref && fn_val.name.contains('__v_init_consts') { c_name := sanitize_c_ident(fn_val.name) if c_name !in emitted { g.sb.writeln('static void ${c_name}(void) {}') emitted[c_name] = true } } } } } } } fn (mut g Gen) gen_struct_decls() { // Forward declarations for all registered structs for _, name in g.mod.c_struct_names { g.sb.writeln('typedef struct ${name} ${name};') } g.sb.writeln('') // Full struct definitions for type_id, name in g.mod.c_struct_names { typ := g.mod.type_store.types[type_id] g.sb.writeln('struct ${name} {') for fi, field_type in typ.fields { field_name := if fi < typ.field_names.len { typ.field_names[fi] } else { 'field_${fi}' } g.sb.writeln('\t${g.type_name(field_type)} ${field_name};') } g.sb.writeln('};') g.sb.writeln('') } } fn (g &Gen) struct_name(type_id ssa.TypeID) string { // Check if there's a registered struct name in the module if name := g.mod.c_struct_names[type_id] { return name } return 'Struct_${type_id}' } fn (mut g Gen) gen_globals() { for glob in g.mod.globals { if glob.linkage == .external { g.sb.write_string('extern ') } g.sb.writeln('${g.type_name(glob.typ)} ${sanitize_c_ident(glob.name)};') } if g.mod.globals.len > 0 { g.sb.writeln('') } } fn (mut g Gen) gen_fn_forward_decls() { for func in g.mod.funcs { if func.name == 'main' { g.sb.writeln('int main(int argc, char** argv);') continue } // Skip C extern functions — they conflict with system header declarations. // Non-standard C functions (wyhash, etc.) are provided via inline headers. if func.is_c_extern { continue } c_name := g.fn_c_name(func.name) ret_type := g.type_name(func.typ) mut params := []string{} for i, pid in func.params { val := g.mod.values[pid] ptype := g.type_name(val.typ) if val.name.len == 0 { // C extern functions may have unnamed parameters params << '${ptype} _p${i}' } else { pname := sanitize_c_ident(val.name) params << '${ptype} ${pname}' } } param_str := if params.len > 0 { params.join(', ') } else { 'void' } g.sb.writeln('${ret_type} ${c_name}(${param_str});') } g.sb.writeln('') } // is_stub_function returns true for functions that only have a trivial // entry block with a return (empty stubs generated for non-main-module functions). // These functions will be provided by the linked builtin.o. fn (g &Gen) is_stub_function(func ssa.Function) bool { if func.blocks.len != 1 { return false } block := g.mod.blocks[func.blocks[0]] // A stub has 0-2 instructions (just a return, possibly with a zero constant) mut real_instrs := 0 for instr_id in block.instrs { val := g.mod.values[instr_id] if val.kind != .instruction { continue } instr := g.mod.instrs[val.index] if instr.op == .ret { continue } if instr.op in [.alloca, .heap_alloc, .load] { // alloca/heap_alloc+load for struct return stubs continue } real_instrs++ } return real_instrs == 0 } fn (g &Gen) fn_c_name(name string) string { c_name := sanitize_c_ident(name) // Check if this function name conflicts with a struct name for _, sname in g.mod.c_struct_names { if c_name == sname { return '${c_name}_fn' } } return c_name } fn (mut g Gen) gen_function(func ssa.Function) { g.current_fn_name = func.name g.current_fn_ret = func.typ if func.blocks.len == 0 { return } // Function signature if func.name == 'main' { g.sb.write_string('int main(int argc, char** argv)') } else { c_name := g.fn_c_name(func.name) ret_type := g.type_name(func.typ) mut params := []string{} for pid in func.params { val := g.mod.values[pid] ptype := g.type_name(val.typ) pname := sanitize_c_ident(val.name) params << '${ptype} ${pname}' } param_str := if params.len > 0 { params.join(', ') } else { 'void' } g.sb.write_string('${ret_type} ${c_name}(${param_str})') } g.sb.writeln(' {') g.indent = 1 // Collect all allocas and local variable declarations mut declared_vars := map[int]bool{} // Generate blocks for bi, block_id in func.blocks { block := g.mod.blocks[block_id] // Emit label for non-entry blocks if bi > 0 { g.sb.writeln('${block.name}:;') } for instr_id in block.instrs { val := g.mod.values[instr_id] if val.kind != .instruction { continue } instr := g.mod.instrs[val.index] match instr.op { .alloca { // Declare local variable elem_type := g.mod.type_store.types[instr.typ].elem_type if elem_type != 0 { g.write_indent() // Check if this is a multi-element alloca (fixed-size array on stack) mut count := 1 if instr.operands.len > 0 { count_val := g.mod.values[instr.operands[0]] count = count_val.name.int() if count < 1 { count = 1 } } if count > 1 { g.sb.writeln('${g.type_name(elem_type)} ${val.name}[${count}];') } else { g.sb.writeln('${g.type_name(elem_type)} ${val.name};') } declared_vars[val.id] = true } } .heap_alloc { // Heap-allocate: declare as pointer and call calloc elem_type := g.mod.type_store.types[instr.typ].elem_type if elem_type != 0 { g.write_indent() g.sb.writeln('${g.type_name(elem_type)}* ${val.name} = (${g.type_name(elem_type)}*)calloc(1, sizeof(${g.type_name(elem_type)}));') declared_vars[val.id] = true } } .store { if instr.operands.len >= 2 { src := instr.operands[0] dst := instr.operands[1] dst_val := g.mod.values[dst] src_val := g.mod.values[src] g.write_indent() if dst_val.kind == .global || (dst_val.kind == .instruction && g.mod.instrs[dst_val.index].op in [.alloca, .heap_alloc]) { if dst_val.kind == .instruction && g.mod.instrs[dst_val.index].op == .heap_alloc { g.sb.write_string('*${sanitize_c_ident(dst_val.name)} = ') } else { g.sb.write_string('${sanitize_c_ident(dst_val.name)} = ') } // If src is an alloca and dst's elem type is a pointer, // we need & (e.g., storing &Point{} alloca into a Point* variable) if src_val.kind == .instruction && g.mod.instrs[src_val.index].op == .alloca { mut dst_ptr_typ := ssa.TypeID(0) if dst_val.kind == .global { dst_ptr_typ = dst_val.typ } else { dst_ptr_typ = g.mod.instrs[dst_val.index].typ } if dst_ptr_typ < g.mod.type_store.types.len { dst_elem := g.mod.type_store.types[dst_ptr_typ].elem_type if dst_elem < g.mod.type_store.types.len && g.mod.type_store.types[dst_elem].kind == .ptr_t { g.sb.write_string('&') } } } g.gen_value(src) g.sb.writeln(';') } else { g.sb.write_string('*(') g.gen_value(dst) g.sb.write_string(') = ') g.gen_value(src) g.sb.writeln(';') } } } .load { if instr.operands.len >= 1 { src := instr.operands[0] src_val := g.mod.values[src] // Check if this load result is actually used if val.uses.len > 0 { g.write_indent() g.sb.write_string('${g.type_name(instr.typ)} ${val.name} = ') if src_val.kind == .global || (src_val.kind == .instruction && g.mod.instrs[src_val.index].op == .alloca) { g.gen_value(src) } else { g.sb.write_string('*(') g.gen_value(src) g.sb.write_string(')') } g.sb.writeln(';') } } } .ret { g.write_indent() if instr.operands.len > 0 { g.sb.write_string('return ') g.gen_value(instr.operands[0]) g.sb.writeln(';') } else { if g.current_fn_name == 'main' { g.sb.writeln('return 0;') } else { g.sb.writeln('return;') } } } .br { // Conditional branch: br cond, then_label, else_label if instr.operands.len >= 3 { cond := instr.operands[0] then_block := g.mod.get_block_from_val(instr.operands[1]) else_block := g.mod.get_block_from_val(instr.operands[2]) g.write_indent() g.sb.write_string('if (') g.gen_value(cond) g.sb.write_string(') goto ${g.mod.blocks[then_block].name}; ') g.sb.writeln('else goto ${g.mod.blocks[else_block].name};') } } .jmp { if instr.operands.len >= 1 { target_block := g.mod.get_block_from_val(instr.operands[0]) g.write_indent() g.sb.writeln('goto ${g.mod.blocks[target_block].name};') } } .call { if instr.operands.len >= 1 { fn_ref := instr.operands[0] fn_val := g.mod.values[fn_ref] fn_name := sanitize_c_ident(fn_val.name) // Look up target function to get parameter types mut target_fn_params := []ssa.ValueID{} for f in g.mod.funcs { if sanitize_c_ident(f.name) == fn_name { target_fn_params = f.params.clone() break } } g.write_indent() // Only declare result if used and non-void if val.uses.len > 0 && instr.typ != 0 { g.sb.write_string('${g.type_name(instr.typ)} ${val.name} = ') } g.sb.write_string('${fn_name}(') for ai := 1; ai < instr.operands.len; ai++ { if ai > 1 { g.sb.write_string(', ') } arg_val := g.mod.values[instr.operands[ai]] param_idx := ai - 1 // Check if arg is an alloca being passed to a pointer param if arg_val.kind == .instruction && g.mod.instrs[arg_val.index].op == .alloca && param_idx < target_fn_params.len { param_type := g.mod.values[target_fn_params[param_idx]].typ if param_type < g.mod.type_store.types.len && g.mod.type_store.types[param_type].kind == .ptr_t { // Parameter expects a pointer - emit &var g.sb.write_string('&') g.gen_value(instr.operands[ai]) continue } } // Check if arg is a pointer type but param expects non-pointer (auto-deref) if param_idx < target_fn_params.len { param_type := g.mod.values[target_fn_params[param_idx]].typ arg_type := arg_val.typ if arg_type < g.mod.type_store.types.len && g.mod.type_store.types[arg_type].kind == .ptr_t && param_type < g.mod.type_store.types.len && g.mod.type_store.types[param_type].kind != .ptr_t { // Arg is pointer but param expects value - emit *var g.sb.write_string('*(') g.gen_value(instr.operands[ai]) g.sb.write_string(')') continue } } g.gen_value(instr.operands[ai]) } g.sb.writeln(');') } } .add, .sub, .mul, .sdiv, .srem, .fadd, .fsub, .fmul, .fdiv { if instr.operands.len >= 2 { op_str := match instr.op { .add, .fadd { '+' } .sub, .fsub { '-' } .mul, .fmul { '*' } .sdiv, .fdiv { '/' } .srem { '%' } else { '+' } } g.write_indent() g.sb.write_string('${g.type_name(instr.typ)} ${val.name} = ') g.gen_value(instr.operands[0]) g.sb.write_string(' ${op_str} ') g.gen_value(instr.operands[1]) g.sb.writeln(';') } } .eq, .ne, .lt, .gt, .le, .ge { if instr.operands.len >= 2 { op_str := match instr.op { .eq { '==' } .ne { '!=' } .lt { '<' } .gt { '>' } .le { '<=' } .ge { '>=' } else { '==' } } g.write_indent() g.sb.write_string('bool ${val.name} = ') g.gen_value(instr.operands[0]) g.sb.write_string(' ${op_str} ') g.gen_value(instr.operands[1]) g.sb.writeln(';') } } .and_ { if instr.operands.len >= 2 { g.write_indent() g.sb.write_string('${g.type_name(instr.typ)} ${val.name} = ') g.gen_value(instr.operands[0]) // Use logical && for bool, bitwise & for integers is_bool := instr.typ < g.mod.type_store.types.len && g.mod.type_store.types[instr.typ].kind == .int_t && g.mod.type_store.types[instr.typ].width == 1 g.sb.write_string(if is_bool { ' && ' } else { ' & ' }) g.gen_value(instr.operands[1]) g.sb.writeln(';') } } .or_ { if instr.operands.len >= 2 { g.write_indent() g.sb.write_string('${g.type_name(instr.typ)} ${val.name} = ') g.gen_value(instr.operands[0]) // Use logical || for bool, bitwise | for integers is_bool := instr.typ < g.mod.type_store.types.len && g.mod.type_store.types[instr.typ].kind == .int_t && g.mod.type_store.types[instr.typ].width == 1 g.sb.write_string(if is_bool { ' || ' } else { ' | ' }) g.gen_value(instr.operands[1]) g.sb.writeln(';') } } .xor { if instr.operands.len >= 2 { g.write_indent() g.sb.write_string('${g.type_name(instr.typ)} ${val.name} = ') g.gen_value(instr.operands[0]) g.sb.write_string(' ^ ') g.gen_value(instr.operands[1]) g.sb.writeln(';') } } .shl { if instr.operands.len >= 2 { g.write_indent() g.sb.write_string('${g.type_name(instr.typ)} ${val.name} = ') g.gen_value(instr.operands[0]) g.sb.write_string(' << ') g.gen_value(instr.operands[1]) g.sb.writeln(';') } } .ashr, .lshr { if instr.operands.len >= 2 { g.write_indent() g.sb.write_string('${g.type_name(instr.typ)} ${val.name} = ') g.gen_value(instr.operands[0]) g.sb.write_string(' >> ') g.gen_value(instr.operands[1]) g.sb.writeln(';') } } .sext, .zext, .trunc, .bitcast { if instr.operands.len >= 1 { g.write_indent() target_tn := g.type_name(instr.typ) src_type := g.mod.values[instr.operands[0]].typ src_kind := if src_type < g.mod.type_store.types.len { g.mod.type_store.types[src_type].kind } else { ssa.TypeKind.void_t } dst_kind := if instr.typ < g.mod.type_store.types.len { g.mod.type_store.types[instr.typ].kind } else { ssa.TypeKind.void_t } // Pointer-to-struct cast: dereference through typed pointer if src_kind == .ptr_t && dst_kind == .struct_t { g.sb.write_string('${target_tn} ${val.name} = *(${target_tn}*)') } else { g.sb.write_string('${target_tn} ${val.name} = (${target_tn})') } g.gen_value(instr.operands[0]) g.sb.writeln(';') } } .sitofp, .uitofp { if instr.operands.len >= 1 { g.write_indent() g.sb.write_string('${g.type_name(instr.typ)} ${val.name} = (${g.type_name(instr.typ)})') g.gen_value(instr.operands[0]) g.sb.writeln(';') } } .fptosi, .fptoui { if instr.operands.len >= 1 { g.write_indent() g.sb.write_string('${g.type_name(instr.typ)} ${val.name} = (${g.type_name(instr.typ)})') g.gen_value(instr.operands[0]) g.sb.writeln(';') } } .get_element_ptr { // GEP -> struct field address: &base.field_name if instr.operands.len >= 2 { base_val := g.mod.values[instr.operands[0]] idx_val := g.mod.values[instr.operands[1]] field_idx := idx_val.name.int() // Check if base is a struct pointer (alloca produces ptr to struct) base_typ := g.mod.type_store.types[base_val.typ] mut emitted := false if base_typ.kind == .ptr_t { elem_typ := g.mod.type_store.types[base_typ.elem_type] if elem_typ.kind == .struct_t && field_idx < elem_typ.field_names.len { g.write_indent() g.sb.write_string('${g.type_name(instr.typ)} ${val.name} = &') g.gen_value(instr.operands[0]) // If base is an alloca, access as lvalue directly if base_val.kind == .instruction && g.mod.instrs[base_val.index].op == .alloca { g.sb.writeln('.${elem_typ.field_names[field_idx]};') } else { g.sb.writeln('->${elem_typ.field_names[field_idx]};') } emitted = true } } if !emitted { // Pointer arithmetic: base + index g.write_indent() g.sb.write_string('${g.type_name(instr.typ)} ${val.name} = ') g.gen_value(instr.operands[0]) g.sb.write_string(' + ') g.gen_value(instr.operands[1]) g.sb.writeln(';') } } } .extractvalue { if instr.operands.len >= 2 { g.write_indent() g.sb.write_string('${g.type_name(instr.typ)} ${val.name} = ') g.gen_value(instr.operands[0]) // Get field name from index constant idx_val := g.mod.values[instr.operands[1]] field_idx := idx_val.name.int() base_val := g.mod.values[instr.operands[0]] base_typ := g.mod.type_store.types[base_val.typ] if base_typ.kind == .struct_t && field_idx < base_typ.field_names.len { g.sb.writeln('.${base_typ.field_names[field_idx]};') } else { g.sb.writeln('; /* extractvalue idx=${field_idx} */') } } } .struct_init { g.write_indent() type_name := g.type_name(instr.typ) g.sb.write_string('${type_name} ${val.name} = (${type_name}){') typ := g.mod.type_store.types[instr.typ] for fi, op in instr.operands { if fi > 0 { g.sb.write_string(', ') } if fi < typ.field_names.len { g.sb.write_string('.${typ.field_names[fi]} = ') } g.gen_value(op) } g.sb.writeln('};') } .inline_string_init { if instr.operands.len >= 3 { g.write_indent() g.sb.write_string('string ${val.name} = (string){.str = (i8*)') g.gen_value(instr.operands[0]) g.sb.write_string(', .len = ') g.gen_value(instr.operands[1]) g.sb.write_string(', .is_lit = ') g.gen_value(instr.operands[2]) g.sb.writeln('};') } } .unreachable { g.write_indent() g.sb.writeln('__builtin_unreachable();') } .go_call { // go fn(args...) -> goroutines__goroutine_create(fn, args) if instr.operands.len >= 1 { fn_ref := instr.operands[0] fn_val := g.mod.values[fn_ref] fn_name := sanitize_c_ident(fn_val.name) arg_count := instr.operands.len - 1 if arg_count == 0 { // No arguments: pass function directly with NULL arg g.write_indent() g.sb.writeln('goroutines__goroutine_create((void*)(void(*)())${fn_name}, NULL, 0);') } else { wrapper_id := '${fn_name}_${val.name}' g.write_indent() g.sb.writeln('{') g.indent++ // Declare args struct on heap g.write_indent() g.sb.write_string('struct _go_args_${wrapper_id} { ') for ai in 0 .. arg_count { arg_type := g.type_name(g.mod.values[instr.operands[ai + 1]].typ) g.sb.write_string('${arg_type} a${ai}; ') } g.sb.writeln('};') g.write_indent() g.sb.writeln('struct _go_args_${wrapper_id}* _args = (struct _go_args_${wrapper_id}*)malloc(sizeof(struct _go_args_${wrapper_id}));') // Pack arguments for ai in 0 .. arg_count { g.write_indent() g.sb.write_string('_args->a${ai} = ') g.gen_value(instr.operands[ai + 1]) g.sb.writeln(';') } // Call goroutine_create with the unpacking wrapper g.write_indent() g.sb.writeln('goroutines__goroutine_create((void*)(void(*)())_go_wrapper_${wrapper_id}, _args, sizeof(struct _go_args_${wrapper_id}));') g.indent-- g.write_indent() g.sb.writeln('}') } } } .spawn_call { // spawn fn(args...) -> launch OS thread via pthread_create if instr.operands.len >= 1 { fn_ref := instr.operands[0] fn_val := g.mod.values[fn_ref] fn_name := sanitize_c_ident(fn_val.name) arg_count := instr.operands.len - 1 g.write_indent() g.sb.writeln('{') g.indent++ g.write_indent() g.sb.writeln('pthread_t _spawn_thread;') if arg_count == 0 { g.write_indent() g.sb.writeln('pthread_create(&_spawn_thread, NULL, (void*(*)(void*))${fn_name}, NULL);') } else { wrapper_id := '${fn_name}_${val.name}' g.write_indent() g.sb.write_string('struct _spawn_args_${wrapper_id} { ') for ai in 0 .. arg_count { arg_type := g.type_name(g.mod.values[instr.operands[ai + 1]].typ) g.sb.write_string('${arg_type} a${ai}; ') } g.sb.writeln('};') g.write_indent() g.sb.writeln('struct _spawn_args_${wrapper_id}* _args = (struct _spawn_args_${wrapper_id}*)malloc(sizeof(struct _spawn_args_${wrapper_id}));') for ai in 0 .. arg_count { g.write_indent() g.sb.write_string('_args->a${ai} = ') g.gen_value(instr.operands[ai + 1]) g.sb.writeln(';') } g.write_indent() g.sb.writeln('pthread_create(&_spawn_thread, NULL, _spawn_wrapper_${wrapper_id}, _args);') } g.write_indent() g.sb.writeln('pthread_detach(_spawn_thread);') g.indent-- g.write_indent() g.sb.writeln('}') } } else { // Other ops: emit as comment g.write_indent() g.sb.writeln('/* TODO: ${instr.op} */') } } } } g.sb.writeln('}') g.sb.writeln('') } fn (mut g Gen) gen_value(id ssa.ValueID) { if id == 0 { g.sb.write_string('0') return } val := g.mod.values[id] match val.kind { .constant { g.sb.write_string(val.name) } .argument { g.sb.write_string(sanitize_c_ident(val.name)) } .global { g.sb.write_string(sanitize_c_ident(val.name)) } .instruction { g.sb.write_string(val.name) } .string_literal { // Emit as V string struct literal escaped := escape_c_string(val.name) g.sb.write_string('(string){.str = (i8*)"${escaped}", .len = ${val.name.len}, .is_lit = 1}') } .c_string_literal { // Emit as raw C string (char*) — no re-escaping needed, // the value already contains C escape sequences from the source g.sb.write_string('"${val.name}"') } .func_ref { g.sb.write_string(g.fn_c_name(val.name)) } .basic_block { g.sb.write_string(val.name) } .unknown { g.sb.write_string('0') } } } fn (g &Gen) type_name(id ssa.TypeID) string { if id == 0 { return 'void' } if id >= g.mod.type_store.types.len { return 'void' } typ := g.mod.type_store.types[id] match typ.kind { .void_t { return 'void' } .int_t { return match typ.width { 1 { 'bool' } 8 { 'i8' } 16 { 'i16' } 32 { 'i32' } 64 { 'i64' } else { 'i64' } } } .float_t { return if typ.width == 32 { 'f32' } else { 'f64' } } .ptr_t { base := g.type_name(typ.elem_type) return '${base}*' } .struct_t { if typ.field_names.len > 0 { // Only use struct name if it was registered if _ := g.mod.c_struct_names[id] { return g.struct_name(id) } } return 'void*' } .array_t { return 'void*' } .func_t { return 'void*' } .label_t { return 'void*' } .metadata_t { return 'void*' } } } fn (mut g Gen) write_indent() { for _ in 0 .. g.indent { g.sb.write_string('\t') } } fn sanitize_c_ident(name string) string { if name.len == 0 { return '_empty' } // C interop: strip C__ prefix if name.starts_with('C__') { return name[3..] } mut s := name.replace('.', '__') s = s.replace('-', '_') // Replace operator symbols that can't appear in C identifiers s = s.replace('==', '_eq') s = s.replace('!=', '_ne') s = s.replace('<=', '_le') s = s.replace('>=', '_ge') s = s.replace('<<', '_shl') s = s.replace('>>', '_shr') s = s.replace('<', '_lt') s = s.replace('>', '_gt') s = s.replace('+', '_plus') s = s.replace('*', '_mul') s = s.replace('/', '_div') s = s.replace('%', '_mod') // Reserved C keywords if s in ['auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do', 'double', 'else', 'enum', 'extern', 'float', 'for', 'goto', 'if', 'inline', 'int', 'long', 'register', 'restrict', 'return', 'short', 'signed', 'sizeof', 'static', 'struct', 'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile', 'while', 'unix', 'linux', 'error'] { return 'v__${s}' } return s } fn escape_c_string(s string) string { mut sb := strings.new_builder(s.len + 8) for ch in s { match ch { `"` { sb.write_string('\\"') } `\\` { sb.write_string('\\\\') } `\n` { sb.write_string('\\n') } `\r` { sb.write_string('\\r') } `\t` { sb.write_string('\\t') } 0 { sb.write_string('\\0') } else { sb.write_u8(ch) } } } return sb.str() } fn (mut g Gen) gen_wyhash() { g.sb.writeln('#ifndef wyhash_final_version_4_2') g.sb.writeln('#define wyhash_final_version_4_2') g.sb.writeln('#define WYHASH_CONDOM 1') g.sb.writeln('#define WYHASH_32BIT_MUM 0') g.sb.writeln('#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__)') g.sb.writeln(' #define _likely_(x) __builtin_expect(x,1)') g.sb.writeln(' #define _unlikely_(x) __builtin_expect(x,0)') g.sb.writeln('#else') g.sb.writeln(' #define _likely_(x) (x)') g.sb.writeln(' #define _unlikely_(x) (x)') g.sb.writeln('#endif') g.sb.writeln('static inline uint64_t _wyrot(uint64_t x) { return (x>>32)|(x<<32); }') g.sb.writeln('static inline void _wymum(uint64_t *A, uint64_t *B){') g.sb.writeln('#if defined(__SIZEOF_INT128__)') g.sb.writeln(' __uint128_t r=*A; r*=*B; *A=(uint64_t)r; *B=(uint64_t)(r>>64);') g.sb.writeln('#elif defined(_MSC_VER) && defined(_M_X64)') g.sb.writeln(' *A=_umul128(*A,*B,B);') g.sb.writeln('#else') g.sb.writeln(' uint64_t ha=*A>>32, hb=*B>>32, la=(uint32_t)*A, lb=(uint32_t)*B, hi, lo;') g.sb.writeln(' uint64_t rh=ha*hb, rm0=ha*lb, rm1=hb*la, rl=la*lb, t=rl+(rm0<<32), c=t>32)+(rm1>>32)+c;') g.sb.writeln(' *A=lo; *B=hi;') g.sb.writeln('#endif') g.sb.writeln('}') g.sb.writeln('static inline uint64_t _wymix(uint64_t A, uint64_t B){ _wymum(&A,&B); return A^B; }') g.sb.writeln('#ifndef WYHASH_LITTLE_ENDIAN') g.sb.writeln(' #ifdef TARGET_ORDER_IS_LITTLE') g.sb.writeln(' #define WYHASH_LITTLE_ENDIAN 1') g.sb.writeln(' #else') g.sb.writeln(' #define WYHASH_LITTLE_ENDIAN 0') g.sb.writeln(' #endif') g.sb.writeln('#endif') g.sb.writeln('#if (WYHASH_LITTLE_ENDIAN)') g.sb.writeln(' static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return v;}') g.sb.writeln(' static inline uint64_t _wyr4(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return v;}') g.sb.writeln('#elif defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__)') g.sb.writeln(' static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return __builtin_bswap64(v);}') g.sb.writeln(' static inline uint64_t _wyr4(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return __builtin_bswap32(v);}') g.sb.writeln('#else') g.sb.writeln(' static inline uint64_t _wyr8(const uint8_t *p) {') g.sb.writeln(' uint64_t v; memcpy(&v, p, 8);') g.sb.writeln(' return (((v >> 56) & 0xff)| ((v >> 40) & 0xff00)| ((v >> 24) & 0xff0000)| ((v >> 8) & 0xff000000)| ((v << 8) & 0xff00000000)| ((v << 24) & 0xff0000000000)| ((v << 40) & 0xff000000000000)| ((v << 56) & 0xff00000000000000));') g.sb.writeln(' }') g.sb.writeln(' static inline uint64_t _wyr4(const uint8_t *p) {') g.sb.writeln(' uint32_t v; memcpy(&v, p, 4);') g.sb.writeln(' return (((v >> 24) & 0xff)| ((v >> 8) & 0xff00)| ((v << 8) & 0xff0000)| ((v << 24) & 0xff000000));') g.sb.writeln(' }') g.sb.writeln('#endif') g.sb.writeln('static inline uint64_t _wyr3(const uint8_t *p, size_t k) { return (((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1];}') g.sb.writeln('static inline uint64_t wyhash(const void *key, size_t len, uint64_t seed, const uint64_t *secret){') g.sb.writeln(' const uint8_t *p=(const uint8_t *)key; seed^=_wymix(seed^secret[0]^len,secret[1]); uint64_t a, b;') g.sb.writeln(' if(_likely_(len<=16)){') g.sb.writeln(' if(_likely_(len>=4)){ a=(_wyr4(p)<<32)|_wyr4(p+((len>>3)<<2)); b=(_wyr4(p+len-4)<<32)|_wyr4(p+len-4-((len>>3)<<2)); }') g.sb.writeln(' else if(_likely_(len>0)){ a=_wyr3(p,len); b=0; }') g.sb.writeln(' else a=b=0;') g.sb.writeln(' } else {') g.sb.writeln(' size_t i=len;') g.sb.writeln(' if(_unlikely_(i>=48)){') g.sb.writeln(' uint64_t see1=seed, see2=seed;') g.sb.writeln(' do{') g.sb.writeln(' seed=_wymix(_wyr8(p)^secret[1],_wyr8(p+8)^seed);') g.sb.writeln(' see1=_wymix(_wyr8(p+16)^secret[2],_wyr8(p+24)^see1);') g.sb.writeln(' see2=_wymix(_wyr8(p+32)^secret[3],_wyr8(p+40)^see2);') g.sb.writeln(' p+=48; i-=48;') g.sb.writeln(' }while(_likely_(i>=48));') g.sb.writeln(' seed^=see1^see2;') g.sb.writeln(' }') g.sb.writeln(' while(_unlikely_(i>16)){ seed=_wymix(_wyr8(p)^secret[1],_wyr8(p+8)^seed); i-=16; p+=16; }') g.sb.writeln(' a=_wyr8(p+i-16); b=_wyr8(p+i-8);') g.sb.writeln(' }') g.sb.writeln(' a^=secret[1]; b^=seed; _wymum(&a,&b);') g.sb.writeln(' return _wymix(a^secret[0]^len,b^secret[1]);') g.sb.writeln('}') g.sb.writeln('static const uint64_t _wyp[4] = {0x2d358dccaa6c78a5ull, 0x8bb84b93962eacc9ull, 0x4b33a62ed433d4a3ull, 0x4d5a2da51de1aa47ull};') g.sb.writeln('static inline uint64_t wyhash64(uint64_t A, uint64_t B){ A^=0x2d358dccaa6c78a5ull; B^=0x8bb84b93962eacc9ull; _wymum(&A,&B); return _wymix(A^0x2d358dccaa6c78a5ull,B^0x8bb84b93962eacc9ull);}') g.sb.writeln('#endif') g.sb.writeln('') }