v4 / vlib / v3 / ssa / ssa.v
852 lines · 795 sloc · 19.79 KB · 6c4d26f1f98c5464b012a375667ab345e462f7e0
Raw
1module ssa
2
3import v3.token
4
5// ValueID aliases value id values used by ssa.
6pub type ValueID = int
7
8// TypeID aliases type id values used by ssa.
9pub type TypeID = int
10
11// BlockID aliases block id values used by ssa.
12pub type BlockID = int
13
14// OpCode lists op code values used by ssa.
15pub enum OpCode {
16 // Terminators
17 ret
18 br
19 jmp
20 switch_ // Multi-way branch: switch_ %val, default_block, [case_val, block]...
21 unreachable
22 // Binary (integer)
23 add
24 sub
25 mul
26 sdiv
27 srem
28 udiv
29 urem
30 // Binary (float)
31 fadd
32 fsub
33 fmul
34 fdiv
35 frem
36 // Bitwise
37 shl
38 ashr
39 lshr
40 and_
41 or_
42 xor
43 // Memory
44 alloca
45 load
46 store
47 get_element_ptr
48 heap_alloc // Heap allocate memory for a type (malloc+zero): returns ptr
49 fence // Memory ordering barrier
50 cmpxchg // Atomic compare-exchange
51 atomicrmw // Atomic read-modify-write
52 // Comparisons
53 lt
54 gt
55 le
56 ge
57 ult
58 ugt
59 ule
60 uge
61 eq
62 ne
63 // Other
64 call
65 call_indirect // Indirect call through function pointer
66 call_sret // Call with struct return (x8 indirect return on ARM64)
67 neg
68 trunc
69 sext
70 zext
71 fptoui
72 fptosi
73 uitofp
74 sitofp
75 bitcast
76 phi
77 select
78 assign // Copy op used during phi elimination
79 inline_string_init // Build a string struct by value: (string){str, len}
80 // Concurrency
81 go_call // Launch goroutine: go_call fn_ref, args...
82 spawn_call // Launch OS thread: spawn_call fn_ref, args...
83 // Aggregate (struct/tuple) operations
84 extractvalue
85 insertvalue
86 struct_init
87}
88
89// AtomicOrdering lists atomic ordering values used by ssa.
90pub enum AtomicOrdering {
91 not_atomic
92 unordered
93 monotonic
94 acquire
95 release
96 acq_rel
97 seq_cst
98}
99
100// InlineHint lists inline hint values used by ssa.
101pub enum InlineHint {
102 none_ // No hint, let optimizer decide
103 always // Always inline (e.g. V's [inline] attribute)
104 never // Never inline (e.g. V's [noinline] attribute)
105 hint // Suggest inlining (optimizer may ignore)
106}
107
108// TypeKind lists type kind values used by ssa.
109pub enum TypeKind {
110 void_t
111 int_t
112 float_t
113 ptr_t
114 array_t
115 struct_t
116 func_t
117 label_t
118 metadata_t
119}
120
121// Type represents type data used by ssa.
122pub struct Type {
123pub:
124 kind TypeKind
125 width int
126 is_unsigned bool
127 elem_type TypeID // For Ptr, Array
128 len int // For Array
129 fields []TypeID
130 field_names []string
131 params []TypeID
132 ret_type TypeID
133 is_c_struct bool // True for C interop structs (raw field names, typedef to C struct)
134 is_union bool // True for union types (all fields overlap at offset 0)
135}
136
137// TypeStore represents type store data used by ssa.
138pub struct TypeStore {
139pub mut:
140 types []Type
141 cache map[string]TypeID
142}
143
144const recursive_type_slot_size = 256
145
146// new creates a TypeStore value for ssa.
147pub fn TypeStore.new() TypeStore {
148 mut ts := TypeStore{
149 cache: map[string]TypeID{}
150 }
151 ts.types << Type{
152 kind: .void_t
153 }
154 return ts
155}
156
157// get_int returns get int data for TypeStore.
158pub fn (mut ts TypeStore) get_int(width int) TypeID {
159 key := 'i${width}'
160 if id := ts.cache[key] {
161 if id > 0 {
162 return id
163 }
164 }
165 id := ts.register(Type{ kind: .int_t, width: width })
166 ts.cache[key] = id
167 return id
168}
169
170// get_uint returns get uint data for TypeStore.
171pub fn (mut ts TypeStore) get_uint(width int) TypeID {
172 key := 'u${width}'
173 if id := ts.cache[key] {
174 if id > 0 {
175 return id
176 }
177 }
178 id := ts.register(Type{ kind: .int_t, width: width, is_unsigned: true })
179 ts.cache[key] = id
180 return id
181}
182
183// get_float returns get float data for TypeStore.
184pub fn (mut ts TypeStore) get_float(width int) TypeID {
185 key := 'f${width}'
186 if id := ts.cache[key] {
187 if id > 0 {
188 return id
189 }
190 }
191 id := ts.register(Type{ kind: .float_t, width: width })
192 ts.cache[key] = id
193 return id
194}
195
196// get_ptr returns get ptr data for TypeStore.
197pub fn (mut ts TypeStore) get_ptr(elem TypeID) TypeID {
198 key := 'p${elem}'
199 if id := ts.cache[key] {
200 if id > 0 {
201 return id
202 }
203 }
204 id := ts.register(Type{ kind: .ptr_t, elem_type: elem })
205 ts.cache[key] = id
206 return id
207}
208
209// get_array returns the cached fixed-array type for (elem, length).
210pub fn (mut ts TypeStore) get_array(elem TypeID, length int) TypeID {
211 key := 'a${elem}_${length}'
212 if id := ts.cache[key] {
213 if id > 0 {
214 return id
215 }
216 }
217 id := ts.register(Type{ kind: .array_t, elem_type: elem, len: length })
218 ts.cache[key] = id
219 return id
220}
221
222// get_tuple returns a cached anonymous struct type holding the given element types.
223pub fn (mut ts TypeStore) get_tuple(elem_types []TypeID) TypeID {
224 mut key := 'tuple'
225 for t in elem_types {
226 key += '_${t}'
227 }
228 if id := ts.cache[key] {
229 if id > 0 {
230 return id
231 }
232 }
233 id := ts.register(Type{
234 kind: .struct_t
235 fields: elem_types
236 })
237 ts.cache[key] = id
238 return id
239}
240
241// register supports register handling for TypeStore.
242pub fn (mut ts TypeStore) register(t Type) TypeID {
243 id := TypeID(ts.types.len)
244 ts.types << t
245 return id
246}
247
248// ValueKind lists value kind values used by ssa.
249pub enum ValueKind {
250 unknown
251 constant
252 argument
253 global
254 instruction
255 basic_block
256 string_literal // V string struct literal (by value)
257 c_string_literal // C string literal (raw char pointer)
258 func_ref
259}
260
261// Value represents value data used by ssa.
262pub struct Value {
263pub mut:
264 id ValueID
265 kind ValueKind
266 typ TypeID
267 name string
268 index int
269 uses []ValueID
270}
271
272// ConstantData carries the typed payload of a constant value. It mirrors v2's
273// representation so backends can recover the original int/float/string value
274// instead of re-parsing Value.name.
275pub struct ConstantData {
276pub:
277 int_val i64
278 float_val f64
279 str_val string
280}
281
282// Instruction represents instruction data used by ssa.
283pub struct Instruction {
284pub mut:
285 op OpCode
286 operands []ValueID
287 block BlockID
288 typ TypeID
289 pos token.Pos
290 atomic_ord AtomicOrdering
291 inline InlineHint // Inline hint for call instructions
292}
293
294// BasicBlock represents basic block data used by ssa.
295pub struct BasicBlock {
296pub mut:
297 id BlockID
298 val_id ValueID // SSA value representing the block (0 in v3's raw-block-id model)
299 name string
300 parent int
301 instrs []ValueID
302 preds []BlockID
303 succs []BlockID
304 // Dominators
305 idom BlockID
306 dom_tree []BlockID
307}
308
309// CallConv lists call conv values used by ssa.
310pub enum CallConv {
311 c_decl
312 fast_call
313 wasm_std
314}
315
316// Linkage lists linkage values used by ssa.
317pub enum Linkage {
318 external
319 private
320 internal
321}
322
323// Function represents function data used by ssa.
324pub struct Function {
325pub mut:
326 id int
327 name string
328 typ TypeID
329 blocks []BlockID
330 params []ValueID
331 is_c_extern bool // C-language extern function (no V body)
332 is_prototype bool // Registered declaration/signature whose body is not materialized yet
333 linkage Linkage
334 call_conv CallConv
335}
336
337// GlobalVar represents global var data used by ssa.
338pub struct GlobalVar {
339pub mut:
340 name string
341 typ TypeID
342 linkage Linkage
343 alignment int
344 is_constant bool
345 initial_value i64 // For constants/enums, the initial integer value
346 initial_data []u8 // For constant arrays: serialized element data
347}
348
349// TargetData represents target data data used by ssa.
350pub struct TargetData {
351pub:
352 ptr_size int = 8
353 endian_little bool = true
354}
355
356// Module represents module data used by ssa.
357@[heap]
358pub struct Module {
359pub mut:
360 name string
361 target TargetData
362 type_store TypeStore
363 values []Value
364 instrs []Instruction
365 blocks []BasicBlock
366 funcs []Function
367 globals []GlobalVar
368 // C struct names: TypeID -> C struct name (e.g. for `struct stat`). Used by
369 // codegen to emit `typedef struct <name> ...;` and preserve the C layout.
370 c_struct_names map[int]string
371 // C structs marked @[typedef] — already a C typedef, not a struct tag.
372 c_typedef_structs map[int]bool
373 // Constant cache: "type:name" -> ValueID for deduplication.
374 const_cache map[string]ValueID
375}
376
377// new creates a Module value for ssa.
378pub fn Module.new() &Module {
379 mut m := &Module{
380 type_store: TypeStore.new()
381 c_struct_names: map[int]string{}
382 c_typedef_structs: map[int]bool{}
383 const_cache: map[string]ValueID{}
384 }
385 m.values << Value{
386 kind: .unknown
387 id: 0
388 }
389 return m
390}
391
392// add_value updates add value state for Module.
393pub fn (mut m Module) add_value(kind ValueKind, typ TypeID, name string, index int) ValueID {
394 id := ValueID(m.values.len)
395 m.values << Value{
396 id: id
397 kind: kind
398 typ: typ
399 name: name
400 index: index
401 }
402 return id
403}
404
405// add_instr updates add instr state for Module.
406pub fn (mut m Module) add_instr(op OpCode, block BlockID, typ TypeID, operands []ValueID) ValueID {
407 instr_idx := m.instrs.len
408 m.instrs << Instruction{
409 op: op
410 block: block
411 typ: typ
412 operands: operands
413 }
414 val_id := m.add_value(.instruction, typ, '', instr_idx)
415 mut blk := m.blocks[block]
416 blk.instrs << val_id
417 m.blocks[block] = blk
418 for op_id in m.instrs[instr_idx].value_operands() {
419 if op_id > 0 && op_id < m.values.len && val_id !in m.values[op_id].uses {
420 mut op_val := m.values[op_id]
421 op_val.uses << val_id
422 m.values[op_id] = op_val
423 }
424 }
425 return val_id
426}
427
428// add_instr_front creates an instruction and prepends it to the block (used for
429// phi insertion by mem2reg, which requires phis at the top of a block).
430pub fn (mut m Module) add_instr_front(op OpCode, block BlockID, typ TypeID, operands []ValueID) ValueID {
431 instr_idx := m.instrs.len
432 m.instrs << Instruction{
433 op: op
434 block: block
435 typ: typ
436 operands: operands
437 }
438 val_id := m.add_value(.instruction, typ, '', instr_idx)
439 mut blk := m.blocks[block]
440 blk.instrs.prepend(val_id)
441 m.blocks[block] = blk
442 for op_id in m.instrs[instr_idx].value_operands() {
443 if op_id > 0 && op_id < m.values.len && val_id !in m.values[op_id].uses {
444 mut op_val := m.values[op_id]
445 op_val.uses << val_id
446 m.values[op_id] = op_val
447 }
448 }
449 return val_id
450}
451
452// append_phi_operands appends a (val, block_id) pair to a phi instruction.
453pub fn (mut m Module) append_phi_operands(instr_idx int, val ValueID, block_id BlockID) {
454 mut instr := m.instrs[instr_idx]
455 instr.operands << val
456 instr.operands << block_id
457 m.instrs[instr_idx] = instr
458 if val > 0 && val < m.values.len {
459 // keep use lists consistent: the phi value uses `val`
460 }
461}
462
463// add_block updates add block state for Module.
464pub fn (mut m Module) add_block(func_id int, name string) BlockID {
465 id := BlockID(m.blocks.len)
466 unique := '${name}_${id}'
467 m.blocks << BasicBlock{
468 id: id
469 name: unique
470 parent: func_id
471 }
472 mut f := m.funcs[func_id]
473 f.blocks << id
474 f.is_prototype = false
475 m.funcs[func_id] = f
476 return id
477}
478
479// new_function supports new function handling for Module.
480pub fn (mut m Module) new_function(name string, ret TypeID) int {
481 for i, f in m.funcs {
482 if f.name == name {
483 return i
484 }
485 }
486 id := m.funcs.len
487 m.funcs << Function{
488 id: id
489 name: name
490 typ: ret
491 }
492 return id
493}
494
495// --- Safe mutation helpers (avoid chained struct-array mutations) ---
496
497// func_add_param supports func add param handling for Module.
498pub fn (mut m Module) func_add_param(func_id int, param_val ValueID) {
499 mut f := m.funcs[func_id]
500 f.params << param_val
501 m.funcs[func_id] = f
502}
503
504// func_set_c_extern supports func set c extern handling for Module.
505pub fn (mut m Module) func_set_c_extern(func_id int, val bool) {
506 mut f := m.funcs[func_id]
507 f.is_c_extern = val
508 m.funcs[func_id] = f
509}
510
511// func_set_prototype supports func set prototype handling for Module.
512pub fn (mut m Module) func_set_prototype(func_id int, val bool) {
513 mut f := m.funcs[func_id]
514 f.is_prototype = val
515 m.funcs[func_id] = f
516}
517
518// block_add_succ supports block add succ handling for Module.
519pub fn (mut m Module) block_add_succ(from BlockID, to BlockID) {
520 mut blk := m.blocks[from]
521 blk.succs << to
522 m.blocks[from] = blk
523}
524
525// block_add_pred supports block add pred handling for Module.
526pub fn (mut m Module) block_add_pred(to BlockID, from BlockID) {
527 mut blk := m.blocks[to]
528 blk.preds << from
529 m.blocks[to] = blk
530}
531
532// add_global updates add global state for Module.
533pub fn (mut m Module) add_global(name string, typ TypeID) ValueID {
534 id := m.globals.len
535 m.globals << GlobalVar{
536 name: name
537 typ: typ
538 linkage: .private
539 }
540 ptr_typ := m.type_store.get_ptr(typ)
541 return m.add_value(.global, ptr_typ, name, id)
542}
543
544// add_global_with_data registers a private global initialized from raw bytes
545// (used for const arrays serialized to element data).
546pub fn (mut m Module) add_global_with_data(name string, elem_type TypeID, is_const bool, data []u8) ValueID {
547 id := m.globals.len
548 m.globals << GlobalVar{
549 name: name
550 typ: elem_type
551 linkage: .private
552 is_constant: is_const
553 initial_data: data
554 }
555 ptr_typ := m.type_store.get_ptr(elem_type)
556 return m.add_value(.global, ptr_typ, name, id)
557}
558
559// add_external_global registers (or reuses) a global defined outside this module
560// (e.g. C runtime globals such as __stdoutp). Returns the global pointer value.
561pub fn (mut m Module) add_external_global(name string, typ TypeID) ValueID {
562 for v in m.values {
563 if v.kind == .global && v.name == name {
564 return v.id
565 }
566 }
567 id := m.globals.len
568 m.globals << GlobalVar{
569 name: name
570 typ: typ
571 linkage: .external
572 }
573 ptr_typ := m.type_store.get_ptr(typ)
574 return m.add_value(.global, ptr_typ, name, id)
575}
576
577// get_or_add_const returns get or add const data for Module.
578pub fn (mut m Module) get_or_add_const(typ TypeID, name string) ValueID {
579 key := '${typ}:${name}'
580 if existing := m.const_cache[key] {
581 if existing > 0 {
582 return existing
583 }
584 }
585 id := m.add_value(.constant, typ, name, 0)
586 m.const_cache[key] = id
587 return id
588}
589
590// get_block_from_val converts a basic-block value operand to its block index.
591pub fn (m &Module) get_block_from_val(val_id int) int {
592 return m.values[val_id].index
593}
594
595// type_size returns the byte size for an SSA type on the current target.
596pub fn (m &Module) type_size(typ_id TypeID) int {
597 mut visiting := []bool{len: m.type_store.types.len}
598 mut cache := []int{len: m.type_store.types.len}
599 return m.type_size_inner(typ_id, 0, mut visiting, mut cache)
600}
601
602// type_size_inner returns type size inner data for Module.
603fn (m &Module) type_size_inner(typ_id TypeID, depth int, mut visiting []bool, mut cache []int) int {
604 if typ_id <= 0 || typ_id >= m.type_store.types.len {
605 return 0
606 }
607 if depth > 32 {
608 return recursive_type_slot_size
609 }
610 if cache[typ_id] > 0 {
611 return cache[typ_id]
612 }
613 typ := m.type_store.types[typ_id]
614 if typ.width > 0 {
615 size := (typ.width + 7) / 8
616 cache[typ_id] = size
617 return size
618 }
619 if typ.kind == .array_t {
620 if visiting[typ_id] {
621 return recursive_type_slot_size
622 }
623 visiting[typ_id] = true
624 elem := m.type_size_inner(typ.elem_type, depth + 1, mut visiting, mut cache)
625 visiting[typ_id] = false
626 mut total := elem * typ.len
627 if total <= 0 {
628 total = 0
629 }
630 cache[typ_id] = total
631 return total
632 }
633 if typ.elem_type > 0 && typ.fields.len == 0 {
634 cache[typ_id] = 8
635 return 8
636 }
637 if typ.fields.len == 0 {
638 if typ.params.len > 0 || typ.ret_type > 0 {
639 cache[typ_id] = 8
640 return 8
641 }
642 return 0
643 }
644 if typ.fields.len > 256 {
645 cache[typ_id] = 8
646 return 8
647 }
648 if visiting[typ_id] {
649 return recursive_type_slot_size
650 }
651 visiting[typ_id] = true
652 mut total := 0
653 if typ.is_union {
654 // Unions: all fields overlap at offset 0; size is the largest field,
655 // rounded up to the largest field alignment.
656 mut max_size := 0
657 mut max_align := 1
658 for i in 0 .. typ.fields.len {
659 field_typ := typ.fields[i]
660 s := m.type_size_inner(field_typ, depth + 1, mut visiting, mut cache)
661 if s > max_size {
662 max_size = s
663 }
664 a := m.type_align_for_layout(field_typ)
665 if a > max_align {
666 max_align = a
667 }
668 }
669 total = if max_align > 1 && max_size % max_align != 0 {
670 (max_size + max_align - 1) & ~(max_align - 1)
671 } else {
672 max_size
673 }
674 } else {
675 mut offset := 0
676 mut max_align := 1
677 for i in 0 .. typ.fields.len {
678 field_typ := typ.fields[i]
679 align := m.type_align_for_layout(field_typ)
680 if align > max_align {
681 max_align = align
682 }
683 if align > 1 && offset % align != 0 {
684 offset = (offset + align - 1) & ~(align - 1)
685 }
686 offset += m.type_size_inner(field_typ, depth + 1, mut visiting, mut cache)
687 }
688 total = if max_align > 1 && offset % max_align != 0 {
689 (offset + max_align - 1) & ~(max_align - 1)
690 } else {
691 offset
692 }
693 }
694 visiting[typ_id] = false
695 if total <= 0 {
696 total = 8
697 }
698 cache[typ_id] = total
699 return total
700}
701
702// type_align returns the ABI alignment for an SSA type on the current target.
703pub fn (m &Module) type_align(typ_id TypeID) int {
704 return m.type_align_for_layout(typ_id)
705}
706
707// type_align_for_layout returns type align for layout data for Module.
708fn (m &Module) type_align_for_layout(typ_id TypeID) int {
709 return m.type_align_for_layout_inner(typ_id, 0)
710}
711
712// type_align_for_layout_inner returns type align for layout inner data for Module.
713fn (m &Module) type_align_for_layout_inner(typ_id TypeID, depth int) int {
714 if typ_id <= 0 || typ_id >= m.type_store.types.len {
715 return 1
716 }
717 if depth > 16 {
718 return 8
719 }
720 typ := m.type_store.types[typ_id]
721 if typ.width > 0 {
722 size := (typ.width + 7) / 8
723 if size >= 8 {
724 return 8
725 }
726 if size >= 4 {
727 return 4
728 }
729 return 1
730 }
731 if typ.kind == .array_t {
732 return m.type_align_for_layout_inner(typ.elem_type, depth + 1)
733 }
734 if typ.elem_type > 0 && typ.fields.len == 0 {
735 return 8
736 }
737 if typ.fields.len > 0 {
738 return 8
739 }
740 if typ.params.len > 0 || typ.ret_type > 0 {
741 return 8
742 }
743 return 1
744}
745
746// replace_uses supports replace uses handling for Module.
747pub fn (mut m Module) replace_uses(old_id ValueID, new_id ValueID) {
748 if old_id <= 0 || old_id >= m.values.len {
749 return
750 }
751 for user_id in m.values[old_id].uses {
752 if user_id <= 0 || user_id >= m.values.len {
753 continue
754 }
755 val := m.values[user_id]
756 if val.kind != .instruction {
757 continue
758 }
759 mut instr := m.instrs[val.index]
760 for i in 0 .. instr.operands.len {
761 if instr.operands[i] == old_id {
762 instr.operands[i] = new_id
763 }
764 }
765 m.instrs[val.index] = instr
766 }
767}
768
769// value_operands returns value operands data for Instruction.
770pub fn (i &Instruction) value_operands() []ValueID {
771 if i.op == .br {
772 if i.operands.len > 0 {
773 mut r := []ValueID{}
774 r << i.operands[0]
775 return r
776 }
777 return []ValueID{}
778 }
779 if i.op == .jmp {
780 return []ValueID{}
781 }
782 if i.op == .switch_ {
783 // switch_ %val, default_block, [case_val, block]...
784 // Only %val and the case values are SSA values; blocks are raw block ids.
785 mut r := []ValueID{}
786 if i.operands.len > 0 {
787 r << i.operands[0]
788 }
789 for oi := 2; oi < i.operands.len; oi += 2 {
790 r << i.operands[oi]
791 }
792 return r
793 }
794 if i.op == .phi {
795 mut r := []ValueID{}
796 for oi := 0; oi < i.operands.len; oi += 2 {
797 r << i.operands[oi]
798 }
799 return r
800 }
801 return i.operands
802}
803
804// struct_field_offset returns the byte offset of a field in a struct type.
805pub fn (m &Module) struct_field_offset(typ_id TypeID, field_idx int) int {
806 if typ_id <= 0 || typ_id >= m.type_store.types.len {
807 return 0
808 }
809 typ := m.type_store.types[typ_id]
810 if typ.kind != .struct_t {
811 return 0
812 }
813 if typ.is_union {
814 return 0
815 }
816 mut visiting := []bool{len: m.type_store.types.len}
817 mut cache := []int{len: m.type_store.types.len}
818 visiting[typ_id] = true
819 mut offset := 0
820 for i in 0 .. field_idx {
821 if i >= typ.fields.len {
822 break
823 }
824 align := m.type_align_for_layout(typ.fields[i])
825 if align > 1 && offset % align != 0 {
826 offset = (offset + align - 1) & ~(align - 1)
827 }
828 offset += m.type_size_inner(typ.fields[i], 1, mut visiting, mut cache)
829 }
830 if field_idx < typ.fields.len {
831 align := m.type_align_for_layout(typ.fields[field_idx])
832 if align > 1 && offset % align != 0 {
833 offset = (offset + align - 1) & ~(align - 1)
834 }
835 }
836 return offset
837}
838
839// struct_field_size returns the byte size of a field in a struct type.
840pub fn (m &Module) struct_field_size(typ_id TypeID, field_idx int) int {
841 if typ_id <= 0 || typ_id >= m.type_store.types.len {
842 return 0
843 }
844 typ := m.type_store.types[typ_id]
845 if typ.kind != .struct_t || field_idx < 0 || field_idx >= typ.fields.len {
846 return 0
847 }
848 mut visiting := []bool{len: m.type_store.types.len}
849 mut cache := []int{len: m.type_store.types.len}
850 visiting[typ_id] = true
851 return m.type_size_inner(typ.fields[field_idx], 1, mut visiting, mut cache)
852}
853