// 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 ssa import v2.types pub struct TargetData { pub: ptr_size int endian_little bool } @[heap] pub struct Module { pub mut: name string target TargetData type_store TypeStore // For parallel SSA workers: shared reference to main module's type_store. // When non-nil, type operations use this instead of the local type_store. shared_type_store &TypeStore = unsafe { nil } // Type checker environment (optional, for backends that need rich type info) env &types.Environment = unsafe { nil } // Arenas values []Value instrs []Instruction blocks []BasicBlock funcs []Function globals []GlobalVar // C struct names: TypeID -> C struct name (e.g., 114 -> "stat" for struct stat) // Used by the C gen to emit `typedef struct Struct_N;` instead of // generating a custom struct definition that would have the wrong memory layout. c_struct_names map[int]string // C structs marked with @[typedef] – already a C typedef, not a struct tag. c_typedef_structs map[int]bool // Constant cache: (type, name) -> ValueID for deduplication const_cache map[string]ValueID } pub fn Module.new(name string) &Module { mut m := &Module{ name: name type_store: TypeStore.new() } // Pre-allocate arenas to avoid repeated reallocation during SSA building. // A typical hello.v compilation needs ~158K values, ~134K instrs, ~10K blocks, ~1.8K funcs. // Pre-allocating avoids ARM64 backend issues with array growth reallocation. m.values = []Value{cap: 262144} m.instrs = []Instruction{cap: 262144} m.blocks = []BasicBlock{cap: 16384} m.funcs = []Function{cap: 4096} m.globals = []GlobalVar{cap: 2048} // Reserve ID 0 to represent "null" or "invalid", avoiding collisions // with map lookups returning 0. m.values << Value{ kind: .unknown id: 0 } return m } // release_outer_arenas_after_mir_lower releases SSA arena buffers that MIR has // copied by value. Nested slices (value uses, instruction operands, block edges, // function params/blocks) are intentionally not freed because MIR shallow-shares // them after lowering. pub fn (mut m Module) release_outer_arenas_after_mir_lower() { unsafe { m.values.free() m.instrs.free() m.blocks.free() m.funcs.free() } m.values = []Value{} m.instrs = []Instruction{} m.blocks = []BasicBlock{} m.funcs = []Function{} } pub fn (mut m Module) new_function(name string, ret TypeID, params []TypeID) int { // Check if function already exists (avoid duplicates from multiple files) for i, f in m.funcs { if f.name == name { return i } } id := m.funcs.len m.funcs << Function{ id: id name: name typ: ret } return id } pub fn (mut m Module) add_block(func_id int, name string) BlockID { id := m.blocks.len // FIX: Sanitize block names for C labels (replace . with _) safe_name := name.replace('.', '_') unique_name := '${safe_name}_${id}' // Store 'id' (index in blocks arena) in the Value val_id := m.add_value_node(.basic_block, 0, unique_name, id) m.blocks << BasicBlock{ id: id val_id: val_id name: unique_name parent: func_id } // Avoid m.funcs[func_id].blocks << id -- chained broken in ARM64 self-hosted mut f := m.funcs[func_id] f.blocks << id f.is_prototype = false m.funcs[func_id] = f return id } // Updated to accept 'index' pub fn (mut m Module) add_value_node(kind ValueKind, typ TypeID, name string, index int) ValueID { id := m.values.len m.values << Value{ id: id kind: kind typ: typ name: name index: index } return id } // --- Helpers to avoid chained struct-array mutations (broken in ARM64 self-hosted) --- pub fn (mut m Module) func_add_param(func_id int, param_val ValueID) { mut f := m.funcs[func_id] f.params << param_val m.funcs[func_id] = f } pub fn (mut m Module) func_set_c_extern(func_id int, val bool) { mut f := m.funcs[func_id] f.is_c_extern = val m.funcs[func_id] = f } pub fn (mut m Module) func_set_prototype(func_id int, val bool) { mut f := m.funcs[func_id] f.is_prototype = val m.funcs[func_id] = f } pub fn (mut m Module) func_clear_blocks(func_id int) { mut f := m.funcs[func_id] f.blocks.clear() m.funcs[func_id] = f } pub fn (mut m Module) func_clear_params(func_id int) { mut f := m.funcs[func_id] f.params.clear() m.funcs[func_id] = f } pub fn (mut m Module) func_set_params(func_id int, params []ValueID) { mut f := m.funcs[func_id] f.params = params m.funcs[func_id] = f } pub fn (mut m Module) block_add_succ(from int, to int) { mut blk := m.blocks[from] blk.succs << to m.blocks[from] = blk } pub fn (mut m Module) block_add_pred(to int, from int) { mut blk := m.blocks[to] blk.preds << from m.blocks[to] = blk } pub fn (mut m Module) nop_instr(val_idx int) { mut instr := m.instrs[val_idx] instr.op = .bitcast instr.operands = [] m.instrs[val_idx] = instr } // Get or create a constant value, reusing existing ones when possible. // This maintains SSA's immutability principle by avoiding duplicate constants. pub fn (mut m Module) get_or_add_const(typ TypeID, name string) ValueID { key := '${typ}:${name}' if existing := map_get_value_id(m.const_cache, key) { return existing } id := m.add_value_node(.constant, typ, name, 0) m.const_cache[key] = id return id } pub fn (m Module) get_block_from_val(val_id int) int { return m.values[val_id].index } pub fn (mut m Module) add_instr(op OpCode, block BlockID, typ TypeID, operands []ValueID) ValueID { // 1. Save Instruction Index instr_idx := m.instrs.len instr := Instruction{ op: op block: block typ: typ operands: operands } m.instrs << instr // 2. Pass instr_idx to Value val_id := m.add_value_node(.instruction, typ, '', instr_idx) // 3. Link Block — read whole struct, modify, write back (chained broken in ARM64) mut blk := m.blocks[block] blk.instrs << val_id m.blocks[block] = blk // 4. Update Def-Use — same pattern for op_id in operands { if op_id < m.values.len { mut v := m.values[op_id] v.uses << val_id m.values[op_id] = v } } return val_id } pub fn (mut m Module) add_global(name string, typ TypeID, is_const bool) int { return m.add_global_with_value(name, typ, is_const, 0) } pub fn (mut m Module) add_global_with_value(name string, typ TypeID, is_const bool, initial_value i64) int { id := m.globals.len g := GlobalVar{ name: name typ: typ linkage: .private // Local global, not external is_constant: is_const initial_value: initial_value } m.globals << g // FIX: The Value representing a global is a POINTER to the data ptr_typ := m.type_store.get_ptr(typ) return m.add_value_node(.global, ptr_typ, name, id) } pub fn (mut m Module) add_global_with_data(name string, elem_type TypeID, is_const bool, data []u8) int { id := m.globals.len g := GlobalVar{ name: name typ: elem_type linkage: .private is_constant: is_const initial_data: data } m.globals << g // The Value is a POINTER to element data (for direct indexing) ptr_typ := m.type_store.get_ptr(elem_type) return m.add_value_node(.global, ptr_typ, name, id) } // add_external_global adds an external global variable (defined outside this module) // Returns the ValueID for the global pointer pub fn (mut m Module) add_external_global(name string, typ TypeID) ValueID { // Check if this external global already exists for v in m.values { if v.kind == .global && v.name == name { return v.id } } // Create a new external global id := m.globals.len g := GlobalVar{ name: name typ: typ linkage: .external } m.globals << g // The Value representing a global is a POINTER to the data ptr_typ := m.type_store.get_ptr(typ) return m.add_value_node(.global, ptr_typ, name, id) } pub fn (mut m Module) add_instr_front(op OpCode, block BlockID, typ TypeID, operands []ValueID) ValueID { instr_idx := m.instrs.len instr := Instruction{ op: op block: block typ: typ operands: operands } m.instrs << instr val_id := m.add_value_node(.instruction, typ, '', instr_idx) // Prepend to block instructions — read whole struct, modify, write back (chained broken in ARM64) mut blk := m.blocks[block] blk.instrs.prepend(val_id) m.blocks[block] = blk // Update Def-Use — same pattern for op_id in operands { if op_id < m.values.len { mut v := m.values[op_id] v.uses << val_id m.values[op_id] = v } } return val_id } // append_phi_operands appends a (val, block_val) pair to a phi instruction's operands. // Append (val, block_val) pair to phi instruction operands. // Avoid m.instrs[idx].operands << x — chained append broken in ARM64 self-hosted. pub fn (mut m Module) append_phi_operands(instr_idx int, val int, block_val int) { // Read whole struct, modify, write back (chained broken in ARM64) mut instr := m.instrs[instr_idx] instr.operands << val instr.operands << block_val m.instrs[instr_idx] = instr } pub fn (mut m Module) replace_uses(old_val int, new_val int) { // Copy uses, because we modify instr operands which might change things uses := m.values[old_val].uses.clone() for use_id in uses { use_val := m.values[use_id] if use_val.kind == .instruction { // Read whole instr, modify operands, write back (chained broken in ARM64) mut instr := m.instrs[use_val.index] mut replaced := false for i in 0 .. instr.operands.len { if instr.operands[i] == old_val { instr.operands[i] = new_val replaced = true } } if replaced { m.instrs[use_val.index] = instr } // Only add to uses list once per user, even if used multiple times if replaced && use_id !in m.values[new_val].uses { mut nv := m.values[new_val] nv.uses << use_id m.values[new_val] = nv } } } mut ov := m.values[old_val] ov.uses = [] m.values[old_val] = ov } fn dfs(mut m Module, blk int, mut visited map[int]bool, mut rpo []int) { visited[blk] = true for s in m.blocks[blk].succs { if !visited[s] { dfs(mut m, s, mut visited, mut rpo) } } rpo << blk } fn (mut m Module) get_rpo(func Function) []int { mut visited := map[int]bool{} mut rpo := []int{} dfs(mut m, func.blocks[0], mut visited, mut rpo) // rpo.reverse_inplace() rpo = rpo.reverse() return rpo } // new_worker_module creates a lightweight Module for parallel SSA building. // Each worker gets its own copy of type_store (COW from main). // Has its own values/instrs/blocks arenas starting from empty. pub fn (mut m Module) new_worker_module() &Module { // Explicitly clone funcs and globals to avoid COW races between threads. mut wf := []Function{cap: m.funcs.len} for f in m.funcs { wf << f } mut wg := []GlobalVar{cap: m.globals.len} for g in m.globals { wg << g } // Deep-clone TypeStore to avoid COW races on types[] and cache map. mut wts := TypeStore{} wts.types = []Type{cap: m.type_store.types.len} for t in m.type_store.types { wts.types << t } wts.cache = m.type_store.cache.clone() mut w := &Module{ name: m.name shared_type_store: unsafe { nil } type_store: wts env: m.env funcs: wf globals: wg } // Seed worker with main module's values so that ValueIDs from Phases 1-3 // (constants, globals, func_refs, params) are valid in the worker. w.values = []Value{cap: m.values.len + 32768} for v in m.values { w.values << v } // Also seed instrs and blocks (typically empty after Phases 1-3, but just in case). w.instrs = []Instruction{cap: m.instrs.len + 32768} for instr in m.instrs { w.instrs << instr } w.blocks = []BasicBlock{cap: m.blocks.len + 2048} for blk in m.blocks { w.blocks << blk } return w } // FuncSSAData holds the SSA data produced by a worker for a single function. pub struct FuncSSAData { pub: func_idx int // Index into main module's funcs[] blocks []BlockID // Worker-local block IDs params []ValueID // Worker-local param ValueIDs } // merge_worker_module merges a worker's SSA arenas into the main module. // Workers are seeded with the main module's values/instrs/blocks from Phases 1-3. // seed_values/seed_instrs/seed_blocks/seed_funcs are the lengths of the seed data. // Only data beyond the seed is new and needs to be merged with ID remapping. // func_data contains (func_idx, blocks, params) for updating main funcs[]. pub fn (mut m Module) merge_worker_module(w &Module, func_data []FuncSSAData, seed_values int, seed_instrs int, seed_blocks int, seed_types int, seed_funcs int) { // Build type remapping: worker type IDs >= seed_types may differ from main. // For each new worker type, find or create equivalent in main. mut type_remap := []TypeID{len: w.type_store.types.len, init: 0} // Seed types map to themselves (identity) for ti := 0; ti < seed_types && ti < type_remap.len; ti++ { type_remap[ti] = ti } // Map new worker types to main types for ti := seed_types; ti < w.type_store.types.len; ti++ { wt := w.type_store.types[ti] // Generate cache key matching TypeStore conventions mut cache_key := '' match wt.kind { .int_t { cache_key = if wt.is_unsigned { 'u${wt.width}' } else { 'i${wt.width}' } } .float_t { cache_key = 'f${wt.width}' } .ptr_t { // Remap the elem_type first remapped_elem := if int(wt.elem_type) < type_remap.len { type_remap[int(wt.elem_type)] } else { wt.elem_type } cache_key = 'p${remapped_elem}' } .array_t { remapped_elem := if int(wt.elem_type) < type_remap.len { type_remap[int(wt.elem_type)] } else { wt.elem_type } cache_key = 'a${remapped_elem}_${wt.len}' } else {} } // Try cache lookup in main if cache_key.len > 0 { if existing := map_get_type_id(m.type_store.cache, cache_key) { type_remap[ti] = existing continue } } // Register new type in main with remapped field types remapped_ret := if int(wt.ret_type) < type_remap.len && int(wt.ret_type) >= seed_types { type_remap[int(wt.ret_type)] } else { wt.ret_type } remapped_elem2 := if int(wt.elem_type) < type_remap.len && int(wt.elem_type) >= seed_types { type_remap[int(wt.elem_type)] } else { wt.elem_type } mut new_type := Type{ kind: wt.kind width: wt.width len: wt.len is_c_struct: wt.is_c_struct is_union: wt.is_union is_unsigned: wt.is_unsigned ret_type: remapped_ret elem_type: remapped_elem2 } if wt.fields.len > 0 { mut new_fields := []TypeID{cap: wt.fields.len} for f in wt.fields { new_fields << if int(f) < type_remap.len && int(f) >= seed_types { type_remap[int(f)] } else { f } } new_type = Type{ ...new_type fields: new_fields field_names: wt.field_names } } if wt.params.len > 0 { mut new_params := []TypeID{cap: wt.params.len} for p in wt.params { new_params << if int(p) < type_remap.len && int(p) >= seed_types { type_remap[int(p)] } else { p } } new_type = Type{ ...new_type params: new_params } } new_id := m.type_store.register(new_type) if cache_key.len > 0 { m.type_store.cache[cache_key] = new_id } type_remap[ti] = new_id } // Remapping offsets: worker IDs in [seed..seed+N) → main IDs in [main.len..main.len+N). // For seed IDs (< seed_values), no remapping needed — they're the same in both. value_off := m.values.len - seed_values instr_off := m.instrs.len - seed_instrs block_off := m.blocks.len - seed_blocks // Append worker values beyond seed for wi := seed_values; wi < w.values.len; wi++ { wv := w.values[wi] mut new_index := wv.index if wv.kind == .instruction { new_index += instr_off } else if wv.kind == .basic_block { new_index += block_off } // Remap uses — only IDs >= seed need remapping mut new_uses := []ValueID{cap: wv.uses.len} for u in wv.uses { new_uses << if u >= seed_values { u + value_off } else { u } } // Remap type new_typ := if int(wv.typ) >= seed_types && int(wv.typ) < type_remap.len { type_remap[int(wv.typ)] } else { wv.typ } m.values << Value{ id: wi + value_off kind: wv.kind typ: new_typ name: wv.name index: new_index uses: new_uses } } // Append worker instructions beyond seed for ii := seed_instrs; ii < w.instrs.len; ii++ { instr := w.instrs[ii] mut new_ops := []ValueID{cap: instr.operands.len} for op in instr.operands { new_ops << if op >= seed_values { op + value_off } else { op } } // Remap type new_typ := if int(instr.typ) >= seed_types && int(instr.typ) < type_remap.len { type_remap[int(instr.typ)] } else { instr.typ } m.instrs << Instruction{ op: instr.op block: if instr.block >= seed_blocks { instr.block + block_off } else { instr.block } typ: new_typ operands: new_ops pos: instr.pos atomic_ord: instr.atomic_ord inline: instr.inline } } // Append worker blocks beyond seed for bi := seed_blocks; bi < w.blocks.len; bi++ { blk := w.blocks[bi] mut new_instrs := []ValueID{cap: blk.instrs.len} for vi in blk.instrs { new_instrs << if vi >= seed_values { vi + value_off } else { vi } } mut new_preds := []BlockID{cap: blk.preds.len} for p in blk.preds { new_preds << if p >= seed_blocks { p + block_off } else { p } } mut new_succs := []BlockID{cap: blk.succs.len} for s in blk.succs { new_succs << if s >= seed_blocks { s + block_off } else { s } } m.blocks << BasicBlock{ id: blk.id + block_off val_id: if blk.val_id >= seed_values { blk.val_id + value_off } else { blk.val_id } name: blk.name parent: blk.parent instrs: new_instrs preds: new_preds succs: new_succs } } // Update main module's funcs[] with blocks and params from worker for fd in func_data { if fd.func_idx >= m.funcs.len { continue } mut f := m.funcs[fd.func_idx] // Skip if function was already built by a previous worker's merge. // Without this guard, duplicate blocks from multiple workers get appended, // leading to mismatched parameter/value IDs and crashes. if f.blocks.len > 0 { continue } for blk_id in fd.blocks { f.blocks << if blk_id >= seed_blocks { blk_id + block_off } else { blk_id } } mut new_params := []ValueID{cap: fd.params.len} for p in fd.params { new_params << if p >= seed_values { p + value_off } else { p } } f.params = new_params m.funcs[fd.func_idx] = f } // Also add any new functions created by workers (stubs like wyhash, array_eq). for fi := seed_funcs; fi < w.funcs.len; fi++ { wfunc := w.funcs[fi] mut already_added := false for existing in m.funcs { if existing.name == wfunc.name { already_added = true break } } if already_added { continue } mut new_blocks := []BlockID{cap: wfunc.blocks.len} for blk_id in wfunc.blocks { new_blocks << if blk_id >= seed_blocks { blk_id + block_off } else { blk_id } } mut new_params := []ValueID{cap: wfunc.params.len} for p in wfunc.params { new_params << if p >= seed_values { p + value_off } else { p } } // Remap function return type new_typ := if int(wfunc.typ) >= seed_types && int(wfunc.typ) < type_remap.len { type_remap[int(wfunc.typ)] } else { wfunc.typ } m.funcs << Function{ id: m.funcs.len name: wfunc.name typ: new_typ blocks: new_blocks params: new_params } } }