Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+702
View File
@@ -0,0 +1,702 @@
// 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 <name> 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
}
}
}