809 lines
18 KiB
V
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)
|
|
}
|