Files
agila_open_tool/tools/v/vlib/v3/ssa/ssa.v
T

809 lines
18 KiB
V

module ssa
import v3.token
pub type ValueID = int
pub type TypeID = int
pub type BlockID = int
pub enum OpCode {
// Terminators
ret
br
jmp
switch_ // Multi-way branch: switch_ %val, default_block, [case_val, block]...
unreachable
// Binary (integer)
add
sub
mul
sdiv
srem
udiv
urem
// Binary (float)
fadd
fsub
fmul
fdiv
frem
// Bitwise
shl
ashr
lshr
and_
or_
xor
// Memory
alloca
load
store
get_element_ptr
heap_alloc // Heap allocate memory for a type (malloc+zero): returns ptr
fence // Memory ordering barrier
cmpxchg // Atomic compare-exchange
atomicrmw // Atomic read-modify-write
// Comparisons
lt
gt
le
ge
ult
ugt
ule
uge
eq
ne
// Other
call
call_indirect // Indirect call through function pointer
call_sret // Call with struct return (x8 indirect return on ARM64)
neg
trunc
sext
zext
fptoui
fptosi
uitofp
sitofp
bitcast
phi
select
assign // Copy op used during phi elimination
inline_string_init // Build a string struct by value: (string){str, len}
// Concurrency
go_call // Launch goroutine: go_call fn_ref, args...
spawn_call // Launch OS thread: spawn_call fn_ref, args...
// Aggregate (struct/tuple) operations
extractvalue
insertvalue
struct_init
}
pub enum AtomicOrdering {
not_atomic
unordered
monotonic
acquire
release
acq_rel
seq_cst
}
pub enum InlineHint {
none_ // No hint, let optimizer decide
always // Always inline (e.g. V's [inline] attribute)
never // Never inline (e.g. V's [noinline] attribute)
hint // Suggest inlining (optimizer may ignore)
}
pub enum TypeKind {
void_t
int_t
float_t
ptr_t
array_t
struct_t
func_t
label_t
metadata_t
}
pub struct Type {
pub:
kind TypeKind
width int
is_unsigned bool
elem_type TypeID // For Ptr, Array
len int // For Array
fields []TypeID
field_names []string
params []TypeID
ret_type TypeID
is_c_struct bool // True for C interop structs (raw field names, typedef to C struct)
is_union bool // True for union types (all fields overlap at offset 0)
}
pub struct TypeStore {
pub mut:
types []Type
cache map[string]TypeID
}
const recursive_type_slot_size = 256
pub fn TypeStore.new() TypeStore {
mut ts := TypeStore{
cache: map[string]TypeID{}
}
ts.types << Type{
kind: .void_t
}
return ts
}
pub fn (mut ts TypeStore) get_int(width int) TypeID {
key := 'i${width}'
if id := ts.cache[key] {
if id > 0 {
return id
}
}
id := ts.register(Type{ kind: .int_t, width: width })
ts.cache[key] = id
return id
}
pub fn (mut ts TypeStore) get_uint(width int) TypeID {
key := 'u${width}'
if id := ts.cache[key] {
if id > 0 {
return id
}
}
id := ts.register(Type{ kind: .int_t, width: width, is_unsigned: true })
ts.cache[key] = id
return id
}
pub fn (mut ts TypeStore) get_float(width int) TypeID {
key := 'f${width}'
if id := ts.cache[key] {
if id > 0 {
return id
}
}
id := ts.register(Type{ kind: .float_t, width: width })
ts.cache[key] = id
return id
}
pub fn (mut ts TypeStore) get_ptr(elem TypeID) TypeID {
key := 'p${elem}'
if id := ts.cache[key] {
if id > 0 {
return id
}
}
id := ts.register(Type{ kind: .ptr_t, elem_type: elem })
ts.cache[key] = id
return id
}
// get_array returns the cached fixed-array type for (elem, length).
pub fn (mut ts TypeStore) get_array(elem TypeID, length int) TypeID {
key := 'a${elem}_${length}'
if id := ts.cache[key] {
if id > 0 {
return id
}
}
id := ts.register(Type{ kind: .array_t, elem_type: elem, len: length })
ts.cache[key] = id
return id
}
// get_tuple returns a cached anonymous struct type holding the given element types.
pub fn (mut ts TypeStore) get_tuple(elem_types []TypeID) TypeID {
mut key := 'tuple'
for t in elem_types {
key += '_${t}'
}
if id := ts.cache[key] {
if id > 0 {
return id
}
}
id := ts.register(Type{
kind: .struct_t
fields: elem_types
})
ts.cache[key] = id
return id
}
pub fn (mut ts TypeStore) register(t Type) TypeID {
id := TypeID(ts.types.len)
ts.types << t
return id
}
pub enum ValueKind {
unknown
constant
argument
global
instruction
basic_block
string_literal // V string struct literal (by value)
c_string_literal // C string literal (raw char pointer)
func_ref
}
pub struct Value {
pub mut:
id ValueID
kind ValueKind
typ TypeID
name string
index int
uses []ValueID
}
// ConstantData carries the typed payload of a constant value. It mirrors v2's
// representation so backends can recover the original int/float/string value
// instead of re-parsing Value.name.
pub struct ConstantData {
pub:
int_val i64
float_val f64
str_val string
}
pub struct Instruction {
pub mut:
op OpCode
operands []ValueID
block BlockID
typ TypeID
pos token.Pos
atomic_ord AtomicOrdering
inline InlineHint // Inline hint for call instructions
}
pub struct BasicBlock {
pub mut:
id BlockID
val_id ValueID // SSA value representing the block (0 in v3's raw-block-id model)
name string
parent int
instrs []ValueID
preds []BlockID
succs []BlockID
// Dominators
idom BlockID
dom_tree []BlockID
}
pub enum CallConv {
c_decl
fast_call
wasm_std
}
pub enum Linkage {
external
private
internal
}
pub struct Function {
pub mut:
id int
name string
typ TypeID
blocks []BlockID
params []ValueID
is_c_extern bool // C-language extern function (no V body)
is_prototype bool // Registered declaration/signature whose body is not materialized yet
linkage Linkage
call_conv CallConv
}
pub struct GlobalVar {
pub mut:
name string
typ TypeID
linkage Linkage
alignment int
is_constant bool
initial_value i64 // For constants/enums, the initial integer value
initial_data []u8 // For constant arrays: serialized element data
}
pub struct TargetData {
pub:
ptr_size int = 8
endian_little bool = true
}
@[heap]
pub struct Module {
pub mut:
name string
target TargetData
type_store TypeStore
values []Value
instrs []Instruction
blocks []BasicBlock
funcs []Function
globals []GlobalVar
// C struct names: TypeID -> C struct name (e.g. for `struct stat`). Used by
// codegen to emit `typedef struct <name> ...;` and preserve the C layout.
c_struct_names map[int]string
// C structs marked @[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() &Module {
mut m := &Module{
type_store: TypeStore.new()
c_struct_names: map[int]string{}
c_typedef_structs: map[int]bool{}
const_cache: map[string]ValueID{}
}
m.values << Value{
kind: .unknown
id: 0
}
return m
}
pub fn (mut m Module) add_value(kind ValueKind, typ TypeID, name string, index int) ValueID {
id := ValueID(m.values.len)
m.values << Value{
id: id
kind: kind
typ: typ
name: name
index: index
}
return id
}
pub fn (mut m Module) add_instr(op OpCode, block BlockID, typ TypeID, operands []ValueID) ValueID {
instr_idx := m.instrs.len
m.instrs << Instruction{
op: op
block: block
typ: typ
operands: operands
}
val_id := m.add_value(.instruction, typ, '', instr_idx)
mut blk := m.blocks[block]
blk.instrs << val_id
m.blocks[block] = blk
for op_id in m.instrs[instr_idx].value_operands() {
if op_id > 0 && op_id < m.values.len && val_id !in m.values[op_id].uses {
mut op_val := m.values[op_id]
op_val.uses << val_id
m.values[op_id] = op_val
}
}
return val_id
}
// add_instr_front creates an instruction and prepends it to the block (used for
// phi insertion by mem2reg, which requires phis at the top of a block).
pub fn (mut m Module) add_instr_front(op OpCode, block BlockID, typ TypeID, operands []ValueID) ValueID {
instr_idx := m.instrs.len
m.instrs << Instruction{
op: op
block: block
typ: typ
operands: operands
}
val_id := m.add_value(.instruction, typ, '', instr_idx)
mut blk := m.blocks[block]
blk.instrs.prepend(val_id)
m.blocks[block] = blk
for op_id in m.instrs[instr_idx].value_operands() {
if op_id > 0 && op_id < m.values.len && val_id !in m.values[op_id].uses {
mut op_val := m.values[op_id]
op_val.uses << val_id
m.values[op_id] = op_val
}
}
return val_id
}
// append_phi_operands appends a (val, block_id) pair to a phi instruction.
pub fn (mut m Module) append_phi_operands(instr_idx int, val ValueID, block_id BlockID) {
mut instr := m.instrs[instr_idx]
instr.operands << val
instr.operands << block_id
m.instrs[instr_idx] = instr
if val > 0 && val < m.values.len {
// keep use lists consistent: the phi value uses `val`
}
}
pub fn (mut m Module) add_block(func_id int, name string) BlockID {
id := BlockID(m.blocks.len)
unique := '${name}_${id}'
m.blocks << BasicBlock{
id: id
name: unique
parent: func_id
}
mut f := m.funcs[func_id]
f.blocks << id
f.is_prototype = false
m.funcs[func_id] = f
return id
}
pub fn (mut m Module) new_function(name string, ret TypeID) int {
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
}
// --- Safe mutation helpers (avoid chained struct-array mutations) ---
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) block_add_succ(from BlockID, to BlockID) {
mut blk := m.blocks[from]
blk.succs << to
m.blocks[from] = blk
}
pub fn (mut m Module) block_add_pred(to BlockID, from BlockID) {
mut blk := m.blocks[to]
blk.preds << from
m.blocks[to] = blk
}
pub fn (mut m Module) add_global(name string, typ TypeID) ValueID {
id := m.globals.len
m.globals << GlobalVar{
name: name
typ: typ
linkage: .private
}
ptr_typ := m.type_store.get_ptr(typ)
return m.add_value(.global, ptr_typ, name, id)
}
// add_global_with_data registers a private global initialized from raw bytes
// (used for const arrays serialized to element data).
pub fn (mut m Module) add_global_with_data(name string, elem_type TypeID, is_const bool, data []u8) ValueID {
id := m.globals.len
m.globals << GlobalVar{
name: name
typ: elem_type
linkage: .private
is_constant: is_const
initial_data: data
}
ptr_typ := m.type_store.get_ptr(elem_type)
return m.add_value(.global, ptr_typ, name, id)
}
// add_external_global registers (or reuses) a global defined outside this module
// (e.g. C runtime globals such as __stdoutp). Returns the global pointer value.
pub fn (mut m Module) add_external_global(name string, typ TypeID) ValueID {
for v in m.values {
if v.kind == .global && v.name == name {
return v.id
}
}
id := m.globals.len
m.globals << GlobalVar{
name: name
typ: typ
linkage: .external
}
ptr_typ := m.type_store.get_ptr(typ)
return m.add_value(.global, ptr_typ, name, id)
}
pub fn (mut m Module) get_or_add_const(typ TypeID, name string) ValueID {
key := '${typ}:${name}'
if existing := m.const_cache[key] {
if existing > 0 {
return existing
}
}
id := m.add_value(.constant, typ, name, 0)
m.const_cache[key] = id
return id
}
// get_block_from_val converts a basic-block value operand to its block index.
pub fn (m &Module) get_block_from_val(val_id int) int {
return m.values[val_id].index
}
// type_size returns the byte size for an SSA type on the current target.
pub fn (m &Module) type_size(typ_id TypeID) int {
mut visiting := []bool{len: m.type_store.types.len}
mut cache := []int{len: m.type_store.types.len}
return m.type_size_inner(typ_id, 0, mut visiting, mut cache)
}
fn (m &Module) type_size_inner(typ_id TypeID, depth int, mut visiting []bool, mut cache []int) int {
if typ_id <= 0 || typ_id >= m.type_store.types.len {
return 0
}
if depth > 32 {
return recursive_type_slot_size
}
if cache[typ_id] > 0 {
return cache[typ_id]
}
typ := m.type_store.types[typ_id]
if typ.width > 0 {
size := (typ.width + 7) / 8
cache[typ_id] = size
return size
}
if typ.kind == .array_t {
if visiting[typ_id] {
return recursive_type_slot_size
}
visiting[typ_id] = true
elem := m.type_size_inner(typ.elem_type, depth + 1, mut visiting, mut cache)
visiting[typ_id] = false
mut total := elem * typ.len
if total <= 0 {
total = 0
}
cache[typ_id] = total
return total
}
if typ.elem_type > 0 && typ.fields.len == 0 {
cache[typ_id] = 8
return 8
}
if typ.fields.len == 0 {
if typ.params.len > 0 || typ.ret_type > 0 {
cache[typ_id] = 8
return 8
}
return 0
}
if typ.fields.len > 256 {
cache[typ_id] = 8
return 8
}
if visiting[typ_id] {
return recursive_type_slot_size
}
visiting[typ_id] = true
mut total := 0
if typ.is_union {
// Unions: all fields overlap at offset 0; size is the largest field,
// rounded up to the largest field alignment.
mut max_size := 0
mut max_align := 1
for i in 0 .. typ.fields.len {
field_typ := typ.fields[i]
s := m.type_size_inner(field_typ, depth + 1, mut visiting, mut cache)
if s > max_size {
max_size = s
}
a := m.type_align_for_layout(field_typ)
if a > max_align {
max_align = a
}
}
total = if max_align > 1 && max_size % max_align != 0 {
(max_size + max_align - 1) & ~(max_align - 1)
} else {
max_size
}
} else {
mut offset := 0
mut max_align := 1
for i in 0 .. typ.fields.len {
field_typ := typ.fields[i]
align := m.type_align_for_layout(field_typ)
if align > max_align {
max_align = align
}
if align > 1 && offset % align != 0 {
offset = (offset + align - 1) & ~(align - 1)
}
offset += m.type_size_inner(field_typ, depth + 1, mut visiting, mut cache)
}
total = if max_align > 1 && offset % max_align != 0 {
(offset + max_align - 1) & ~(max_align - 1)
} else {
offset
}
}
visiting[typ_id] = false
if total <= 0 {
total = 8
}
cache[typ_id] = total
return total
}
// type_align returns the ABI alignment for an SSA type on the current target.
pub fn (m &Module) type_align(typ_id TypeID) int {
return m.type_align_for_layout(typ_id)
}
fn (m &Module) type_align_for_layout(typ_id TypeID) int {
return m.type_align_for_layout_inner(typ_id, 0)
}
fn (m &Module) type_align_for_layout_inner(typ_id TypeID, depth int) int {
if typ_id <= 0 || typ_id >= m.type_store.types.len {
return 1
}
if depth > 16 {
return 8
}
typ := m.type_store.types[typ_id]
if typ.width > 0 {
size := (typ.width + 7) / 8
if size >= 8 {
return 8
}
if size >= 4 {
return 4
}
return 1
}
if typ.kind == .array_t {
return m.type_align_for_layout_inner(typ.elem_type, depth + 1)
}
if typ.elem_type > 0 && typ.fields.len == 0 {
return 8
}
if typ.fields.len > 0 {
return 8
}
if typ.params.len > 0 || typ.ret_type > 0 {
return 8
}
return 1
}
pub fn (mut m Module) replace_uses(old_id ValueID, new_id ValueID) {
if old_id <= 0 || old_id >= m.values.len {
return
}
for user_id in m.values[old_id].uses {
if user_id <= 0 || user_id >= m.values.len {
continue
}
val := m.values[user_id]
if val.kind != .instruction {
continue
}
mut instr := m.instrs[val.index]
for i in 0 .. instr.operands.len {
if instr.operands[i] == old_id {
instr.operands[i] = new_id
}
}
m.instrs[val.index] = instr
}
}
pub fn (i &Instruction) value_operands() []ValueID {
if i.op == .br {
if i.operands.len > 0 {
mut r := []ValueID{}
r << i.operands[0]
return r
}
return []ValueID{}
}
if i.op == .jmp {
return []ValueID{}
}
if i.op == .switch_ {
// switch_ %val, default_block, [case_val, block]...
// Only %val and the case values are SSA values; blocks are raw block ids.
mut r := []ValueID{}
if i.operands.len > 0 {
r << i.operands[0]
}
for oi := 2; oi < i.operands.len; oi += 2 {
r << i.operands[oi]
}
return r
}
if i.op == .phi {
mut r := []ValueID{}
for oi := 0; oi < i.operands.len; oi += 2 {
r << i.operands[oi]
}
return r
}
return i.operands
}
// struct_field_offset returns the byte offset of a field in a struct type.
pub fn (m &Module) struct_field_offset(typ_id TypeID, field_idx int) int {
if typ_id <= 0 || typ_id >= m.type_store.types.len {
return 0
}
typ := m.type_store.types[typ_id]
if typ.kind != .struct_t {
return 0
}
if typ.is_union {
return 0
}
mut visiting := []bool{len: m.type_store.types.len}
mut cache := []int{len: m.type_store.types.len}
visiting[typ_id] = true
mut offset := 0
for i in 0 .. field_idx {
if i >= typ.fields.len {
break
}
align := m.type_align_for_layout(typ.fields[i])
if align > 1 && offset % align != 0 {
offset = (offset + align - 1) & ~(align - 1)
}
offset += m.type_size_inner(typ.fields[i], 1, mut visiting, mut cache)
}
if field_idx < typ.fields.len {
align := m.type_align_for_layout(typ.fields[field_idx])
if align > 1 && offset % align != 0 {
offset = (offset + align - 1) & ~(align - 1)
}
}
return offset
}
// struct_field_size returns the byte size of a field in a struct type.
pub fn (m &Module) struct_field_size(typ_id TypeID, field_idx int) int {
if typ_id <= 0 || typ_id >= m.type_store.types.len {
return 0
}
typ := m.type_store.types[typ_id]
if typ.kind != .struct_t || field_idx < 0 || field_idx >= typ.fields.len {
return 0
}
mut visiting := []bool{len: m.type_store.types.len}
mut cache := []int{len: m.type_store.types.len}
visiting[typ_id] = true
return m.type_size_inner(typ.fields[field_idx], 1, mut visiting, mut cache)
}