package types import "../target" import "core:fmt" import "core:mem" // Type is a compact ID. Builtin scalar types occupy the stable low range; // recursive and nominal types are interned in Store starting at DYNAMIC_START. Type :: distinct u32 INVALID :: Type(0) VOID :: Type(1) INT :: Type(2) I8 :: Type(3) I16 :: Type(4) I32 :: Type(5) I64 :: Type(6) U8 :: Type(7) U16 :: Type(8) U32 :: Type(9) U64 :: Type(10) ISIZE :: Type(11) USIZE :: Type(12) F32 :: Type(13) F64 :: Type(14) C_CHAR :: Type(15) C_SCHAR :: Type(16) C_UCHAR :: Type(17) C_SHORT :: Type(18) C_USHORT :: Type(19) C_INT :: Type(20) C_UINT :: Type(21) C_LONG :: Type(22) C_ULONG :: Type(23) C_LONGLONG :: Type(24) C_ULONGLONG :: Type(25) C_FLOAT :: Type(26) C_DOUBLE :: Type(27) C_LONGDOUBLE :: Type(28) DYNAMIC_START :: Type(64) Numeric_Category :: enum u8 { None, Signed_Integer, Unsigned_Integer, Float, } Kind :: enum u8 { Invalid, Void, Int_Constraint, Scalar, Array, Pointer, Slice, Optional, Named, Struct, } Node :: struct { kind: Kind, child: Type, count: u64, sentinel: u64, field_start: u32, field_count: u32, pkg: u32, name: u32, qualifier: u32, file: u32, mutable: bool, many: bool, has_sentinel: bool, inferred_count: bool, c_layout: bool, opaque: bool, declared: bool, } Field :: struct { name: u32, type: Type, } Store :: struct { nodes: [dynamic]Node, fields: [dynamic]Field, selected: target.Target, allocator: mem.Allocator, } init_store :: proc(allocator := context.allocator) -> Store { store: Store store.nodes.allocator = allocator store.fields.allocator = allocator store.selected = target.DEFAULT store.allocator = allocator return store } destroy_store :: proc(store: ^Store) { delete(store.nodes) delete(store.fields) } clone_store :: proc(source: ^Store, allocator := context.allocator) -> Store { store := init_store(allocator) append(&store.nodes, ..source.nodes[:]) append(&store.fields, ..source.fields[:]) store.selected = source.selected return store } intern :: proc(store: ^Store, candidate: Node) -> Type { if candidate.kind != .Struct && candidate.kind != .Named { for existing, index in store.nodes { if existing == candidate { return DYNAMIC_START+Type(index) } } } id := DYNAMIC_START+Type(len(store.nodes)) append(&store.nodes, candidate) return id } named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff) -> Type { normalized_file := file if qualifier != 0 else u32(0) for existing, index in store.nodes { if (existing.kind == .Named || existing.kind == .Struct) && existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier && existing.file == normalized_file { return DYNAMIC_START+Type(index) } } return intern(store, Node{kind=.Named, pkg=pkg, name=name, qualifier=qualifier, file=normalized_file}) } find_named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0) -> Type { for existing, index in store.nodes { if (existing.kind == .Named || existing.kind == .Struct) && existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier { return DYNAMIC_START+Type(index) } } return INVALID } define_struct :: proc(store: ^Store, id: Type, fields: []Field, c_layout, opaque: bool) -> bool { existing, ok := node(store, id) if !ok || (existing.kind != .Named && existing.kind != .Struct) || existing.declared { return false } index := int(id-DYNAMIC_START) store.nodes[index].kind = .Struct store.nodes[index].c_layout = c_layout store.nodes[index].opaque = opaque store.nodes[index].declared = true store.nodes[index].field_start = u32(len(store.fields)) store.nodes[index].field_count = u32(len(fields)) append(&store.fields, ..fields) return true } fields_for :: proc(store: ^Store, value: Type) -> []Field { item, ok := node(store, value) if !ok || item.kind != .Struct { return nil } start := int(item.field_start) end := start+int(item.field_count) if start < 0 || end > len(store.fields) { return nil } return store.fields[start:end] } kind :: proc(value: Type, store: ^Store = nil) -> Kind { switch value { case INVALID: return .Invalid case VOID: return .Void case INT: return .Int_Constraint } if value >= I8 && value <= C_LONGDOUBLE { return .Scalar } if store != nil && value >= DYNAMIC_START { index := int(value-DYNAMIC_START) if index >= 0 && index < len(store.nodes) { return store.nodes[index].kind } } return .Invalid } node :: proc(store: ^Store, value: Type) -> (Node, bool) { if store == nil || value < DYNAMIC_START { return {}, false } index := int(value-DYNAMIC_START) if index < 0 || index >= len(store.nodes) { return {}, false } return store.nodes[index], true } is_valid :: proc(value: Type) -> bool { return value != INVALID } is_void :: proc(value: Type) -> bool { return value == VOID } is_constraint :: proc(value: Type) -> bool { return value == INT } is_c :: proc(value: Type) -> bool { return value >= C_CHAR && value <= C_LONGDOUBLE } as_c_primitive :: proc(value: Type) -> (target.C_Primitive, bool) { switch value { case C_CHAR: return .Char, true case C_SCHAR: return .Schar, true case C_UCHAR: return .Uchar, true case C_SHORT: return .Short, true case C_USHORT: return .Ushort, true case C_INT: return .Int, true case C_UINT: return .Uint, true case C_LONG: return .Long, true case C_ULONG: return .Ulong, true case C_LONGLONG: return .Longlong, true case C_ULONGLONG: return .Ulonglong, true case C_FLOAT: return .Float, true case C_DOUBLE: return .Double, true case C_LONGDOUBLE: return .Longdouble, true } return {}, false } category :: proc(value: Type, selected := target.DEFAULT) -> Numeric_Category { switch value { case I8, I16, I32, I64, ISIZE: return .Signed_Integer case U8, U16, U32, U64, USIZE: return .Unsigned_Integer case F32, F64: return .Float case: primitive, ok := as_c_primitive(value) if !ok { return .None } switch target.c_primitive_layout(selected, primitive).kind { case .Signed_Integer: return .Signed_Integer case .Unsigned_Integer: return .Unsigned_Integer case .Float: return .Float } } return .None } bits :: proc(value: Type, selected := target.DEFAULT) -> int { switch value { case I8, U8: return 8 case I16, U16: return 16 case I32, U32, F32: return 32 case I64, U64, F64: return 64 case ISIZE, USIZE: return target.pointer_bits(selected) case: primitive, ok := as_c_primitive(value) return target.c_primitive_layout(selected, primitive).bits if ok else 0 } } alignment :: proc(value: Type, selected := target.DEFAULT) -> int { if primitive, ok := as_c_primitive(value); ok { return target.c_primitive_layout(selected, primitive).alignment } width := bits(value, selected)/8 return min(max(width, 1), 8) } representation :: proc(value: Type, selected := target.DEFAULT) -> Type { if value == ISIZE { return I64 } if value == USIZE { return U64 } primitive, ok := as_c_primitive(value) if !ok { return value } layout := target.c_primitive_layout(selected, primitive) if layout.kind == .Float { return F32 if layout.bits == 32 else F64 } if layout.kind == .Signed_Integer { switch layout.bits { case 8: return I8 case 16: return I16 case 32: return I32 case: return I64 } } switch layout.bits { case 8: return U8 case 16: return U16 case 32: return U32 case: return U64 } } is_concrete_scalar :: proc(value: Type) -> bool { return kind(value) == .Scalar } is_concrete :: proc(value: Type, store: ^Store = nil) -> bool { value_kind := kind(value, store) if value_kind == .Scalar || value_kind == .Array || value_kind == .Pointer || value_kind == .Slice || value_kind == .Optional { return true } if value_kind == .Struct { item, ok := node(store, value) return ok && item.declared } return false } is_pointer :: proc(value: Type, store: ^Store) -> bool { return kind(value, store) == .Pointer } is_array :: proc(value: Type, store: ^Store) -> bool { return kind(value, store) == .Array } is_slice :: proc(value: Type, store: ^Store) -> bool { return kind(value, store) == .Slice } is_optional :: proc(value: Type, store: ^Store) -> bool { return kind(value, store) == .Optional } is_struct :: proc(value: Type, store: ^Store) -> bool { return kind(value, store) == .Struct } is_optional_pointer :: proc(value: Type, store: ^Store) -> bool { item, ok := node(store, value) return ok && item.kind == .Optional && is_pointer(item.child, store) } is_runtime_value :: proc(value: Type, store: ^Store) -> bool { value_kind := kind(value, store) if value_kind == .Scalar || value_kind == .Pointer { return true } if value_kind == .Slice || value_kind == .Array || value_kind == .Optional { return !contains_c_struct_by_value(value, store) } if value_kind == .Struct { item, ok := node(store, value) return ok && item.declared && !item.opaque && !contains_c_struct_by_value(value, store) } return false } contains_c_struct_by_value :: proc(value: Type, store: ^Store, depth := 0) -> bool { if depth > 256 { return true } item, ok := node(store, value) if !ok { return false } if item.kind == .Pointer { return false } if item.kind == .Struct { if item.c_layout { return true } for field in fields_for(store, value) { if contains_c_struct_by_value(field.type, store, depth+1) { return true } } return false } if item.kind == .Array || item.kind == .Slice || item.kind == .Optional { return contains_c_struct_by_value(item.child, store, depth+1) } return false } is_c_signature_type :: proc(value: Type, store: ^Store, allow_void := false) -> bool { if allow_void && is_void(value) { return true } return is_concrete_scalar(value) || is_pointer(value, store) || is_optional_pointer(value, store) } child_type :: proc(value: Type, store: ^Store) -> Type { item, ok := node(store, value) return item.child if ok else INVALID } logical_count :: proc(value: Type, store: ^Store) -> u64 { item, ok := node(store, value) return item.count if ok else 0 } physical_count :: proc(value: Type, store: ^Store) -> u64 { item, ok := node(store, value) if !ok { return 0 } return item.count + (u64(1) if item.has_sentinel else u64(0)) } is_mutable :: proc(value: Type, store: ^Store) -> bool { item, ok := node(store, value) return ok && item.mutable } is_many_pointer :: proc(value: Type, store: ^Store) -> bool { item, ok := node(store, value) return ok && item.kind == .Pointer && item.many } is_c_struct :: proc(value: Type, store: ^Store) -> bool { item, ok := node(store, value) return ok && item.kind == .Struct && item.c_layout } pointer :: proc(store: ^Store, child: Type, mutable, many: bool) -> Type { return intern(store, Node{kind=.Pointer, child=child, mutable=mutable, many=many}) } slice :: proc(store: ^Store, child: Type, mutable: bool, has_sentinel := false, sentinel: u64 = 0) -> Type { return intern(store, Node{ kind=.Slice, child=child, mutable=mutable, has_sentinel=has_sentinel, sentinel=sentinel, }) } array :: proc( store: ^Store, child: Type, count: u64, mutable: bool, has_sentinel := false, sentinel: u64 = 0, ) -> Type { return intern(store, Node{ kind=.Array, child=child, count=count, mutable=mutable, has_sentinel=has_sentinel, sentinel=sentinel, }) } optional :: proc(store: ^Store, child: Type) -> Type { return intern(store, Node{kind=.Optional, child=child}) } with_array_count :: proc(store: ^Store, value: Type, count: u64) -> Type { item, ok := node(store, value) if !ok || item.kind != .Array { return value } item.count = count item.inferred_count = false return intern(store, item) } can_weaken_pointer :: proc(from, to: Type, store: ^Store) -> bool { from_node, from_ok := node(store, from) to_node, to_ok := node(store, to) return from_ok && to_ok && from_node.kind == .Pointer && to_node.kind == .Pointer && from_node.child == to_node.child && from_node.many == to_node.many && from_node.mutable && !to_node.mutable } is_opaque_struct :: proc(value: Type, store: ^Store) -> bool { item, ok := node(store, value) return ok && item.kind == .Struct && item.opaque } size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 { #partial switch kind(value, store) { case .Scalar: return u64(bits(value, selected)/8) case .Pointer: return u64(target.pointer_bits(selected)/8) case .Slice: return u64(target.pointer_bits(selected)/8*2) case .Array: item, _ := node(store, value) return physical_count(value, store)*size(item.child, store, selected) case .Optional: item, _ := node(store, value) if is_pointer(item.child, store) { return u64(target.pointer_bits(selected)/8) } child_size := size(item.child, store, selected) child_align := u64(alignment_of(item.child, store, selected)) return ((child_size+1+child_align-1)/child_align)*child_align case .Struct: offset: u64 max_align: u64 = 1 for field in fields_for(store, value) { field_align := u64(alignment_of(field.type, store, selected)) offset = (offset+field_align-1)/field_align*field_align offset += size(field.type, store, selected) max_align = max(max_align, field_align) } return (offset+max_align-1)/max_align*max_align case: return 0 } } alignment_of :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> int { #partial switch kind(value, store) { case .Scalar: return alignment(value, selected) case .Pointer, .Slice: return target.pointer_bits(selected)/8 case .Array, .Optional: return alignment_of(child_type(value, store), store, selected) case .Struct: result := 1 for field in fields_for(store, value) { result = max(result, alignment_of(field.type, store, selected)) } return result case: return 1 } } is_concrete_integer :: proc(value: Type) -> bool { category := category(value) return kind(value) == .Scalar && (category == .Signed_Integer || category == .Unsigned_Integer) } is_float :: proc(value: Type, selected := target.DEFAULT) -> bool { return kind(value) == .Scalar && category(value, selected) == .Float } is_signed :: proc(value: Type, selected := target.DEFAULT) -> bool { return kind(value) == .Scalar && category(value, selected) == .Signed_Integer } is_unsigned :: proc(value: Type, selected := target.DEFAULT) -> bool { return kind(value) == .Scalar && category(value, selected) == .Unsigned_Integer } equal :: proc(a, b: Type) -> bool { return a == b } same_numeric_family :: proc(a, b: Type) -> bool { if category(a) != category(b) { return false } // C primitives are intentionally distinct semantic types. Exact-width // Brolang scalars may widen only to other Brolang scalars. return !is_c(a) && !is_c(b) } can_widen :: proc(from, to: Type) -> bool { if equal(from, to) { return true } return is_concrete_scalar(from) && is_concrete_scalar(to) && same_numeric_family(from, to) && bits(from) < bits(to) } widest :: proc(a, b: Type) -> Type { if equal(a, b) && is_concrete_scalar(a) { return a } if !is_concrete_scalar(a) || !is_concrete_scalar(b) || !same_numeric_family(a, b) { return INVALID } if bits(a) >= bits(b) { return a } return b } smallest_signed_for_literal :: proc(value: i64) -> Type { if value >= -128 && value <= 127 { return I8 } if value >= -32768 && value <= 32767 { return I16 } if value >= -2147483648 && value <= 2147483647 { return I32 } return I64 } name :: proc(value: Type) -> string { switch value { case INVALID: return "" case VOID: return "void" case INT: return "int" case I8: return "i8" case I16: return "i16" case I32: return "i32" case I64: return "i64" case U8: return "u8" case U16: return "u16" case U32: return "u32" case U64: return "u64" case ISIZE: return "isize" case USIZE: return "usize" case F32: return "f32" case F64: return "f64" case C_CHAR: return "c_char" case C_SCHAR: return "c_schar" case C_UCHAR: return "c_uchar" case C_SHORT: return "c_short" case C_USHORT: return "c_ushort" case C_INT: return "c_int" case C_UINT: return "c_uint" case C_LONG: return "c_long" case C_ULONG: return "c_ulong" case C_LONGLONG: return "c_longlong" case C_ULONGLONG: return "c_ulonglong" case C_FLOAT: return "c_float" case C_DOUBLE: return "c_double" case C_LONGDOUBLE: return "c_longdouble" case: return fmt.tprintf("", value) } }