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) BOOL :: Type(29) FLOAT :: Type(30) RANGE :: Type(31) DYNAMIC_START :: Type(64) Numeric_Category :: enum u8 { None, Signed_Integer, Unsigned_Integer, Float, } Kind :: enum u8 { Invalid, Void, Int_Constraint, Float_Constraint, Range_Constraint, Scalar, Array, Pointer, Slice, Range, Optional, Function, Named, Alias, Distinct, Enum, Struct, Union, } Node :: struct { kind: Kind, child: Type, count: u64, sentinel: u64, explicit_size: u64, field_start: u32, field_count: u32, explicit_alignment: u32, pkg: u32, name: u32, qualifier: u32, file: u32, mutable: bool, many: bool, has_sentinel: bool, inferred_count: bool, c_abi: bool, variadic: bool, c_layout: bool, opaque: bool, declared: bool, explicit_backing: bool, } Field :: struct { name: u32, type: Type, offset: u64, } Enum_Member :: struct { name: u32, value: i128, } Store :: struct { nodes: [dynamic]Node, fields: [dynamic]Field, enum_members: [dynamic]Enum_Member, selected: target.Target, allocator: mem.Allocator, } init_store :: proc(allocator := context.allocator) -> Store { store: Store store.nodes.allocator = allocator store.fields.allocator = allocator store.enum_members.allocator = allocator store.selected = target.DEFAULT store.allocator = allocator return store } destroy_store :: proc(store: ^Store) { delete(store.nodes) delete(store.fields) delete(store.enum_members) } clone_store :: proc(source: ^Store, allocator := context.allocator) -> Store { store := init_store(allocator) append(&store.nodes, ..source.nodes[:]) append(&store.fields, ..source.fields[:]) append(&store.enum_members, ..source.enum_members[:]) store.selected = source.selected return store } intern :: proc(store: ^Store, candidate: Node) -> Type { if candidate.kind != .Struct && candidate.kind != .Union && candidate.kind != .Named && candidate.kind != .Distinct && candidate.kind != .Enum { 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 == .Alias || existing.kind == .Distinct || existing.kind == .Enum || existing.kind == .Struct || existing.kind == .Union) && 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 == .Alias || existing.kind == .Distinct || existing.kind == .Enum || existing.kind == .Struct || existing.kind == .Union) && existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier { return DYNAMIC_START+Type(index) } } return INVALID } define_alias :: proc(store: ^Store, id, child: Type) -> bool { existing, ok := node(store, id) if !ok || existing.kind != .Named || existing.declared { return false } index := int(id-DYNAMIC_START) store.nodes[index].kind = .Alias store.nodes[index].child = child store.nodes[index].declared = true return true } define_distinct :: proc(store: ^Store, id, child: Type) -> bool { existing, ok := node(store, id) if !ok || existing.kind != .Named || existing.declared { return false } index := int(id-DYNAMIC_START) store.nodes[index].kind = .Distinct store.nodes[index].child = child store.nodes[index].declared = true return true } define_enum :: proc(store: ^Store, id, backing: Type, members: []Enum_Member, explicit_backing: bool) -> bool { existing, ok := node(store, id) if !ok || existing.kind != .Named || existing.declared { return false } index := int(id-DYNAMIC_START) store.nodes[index].kind = .Enum store.nodes[index].child = backing store.nodes[index].field_start = u32(len(store.enum_members)) store.nodes[index].field_count = u32(len(members)) store.nodes[index].explicit_backing = explicit_backing store.nodes[index].declared = true append(&store.enum_members, ..members) return true } define_record :: proc( store: ^Store, id: Type, fields: []Field, c_layout, opaque: bool, is_union := false, explicit_size: u64 = 0, explicit_alignment: u32 = 0, tag: Type = INVALID, ) -> bool { existing, ok := node(store, id) if !ok || (existing.kind != .Named && existing.kind != .Struct && existing.kind != .Union) || (existing.declared && !existing.opaque) { return false } index := int(id-DYNAMIC_START) store.nodes[index].kind = .Union if is_union else .Struct store.nodes[index].c_layout = c_layout store.nodes[index].opaque = opaque store.nodes[index].declared = true store.nodes[index].explicit_size = explicit_size store.nodes[index].explicit_alignment = explicit_alignment // A tagged union stashes its discriminant enum in `child` (untagged unions and // structs leave it INVALID); the per-variant tag value is derived from the enum // member whose name matches the variant, so no extra storage is needed. store.nodes[index].child = tag store.nodes[index].field_start = u32(len(store.fields)) store.nodes[index].field_count = u32(len(fields)) append(&store.fields, ..fields) return true } // enum_anonymous interns an unnamed enum (used as the synthesized discriminant of a // `union(enum)` tagged union). Members carry the variant names with dense 0-based // values, so the same name→member→value lookup used for `union(Enum)` resolves tags. enum_anonymous :: proc(store: ^Store, members: []Enum_Member, backing: Type) -> Type { start := u32(len(store.enum_members)) append(&store.enum_members, ..members) return intern(store, Node{ kind=.Enum, child=backing, field_start=start, field_count=u32(len(members)), declared=true, }) } define_struct :: proc(store: ^Store, id: Type, fields: []Field, c_layout, opaque: bool) -> bool { return define_record(store, id, fields, c_layout, opaque) } fields_for :: proc(store: ^Store, value: Type) -> []Field { item, ok := node(store, value) if !ok || (item.kind != .Struct && item.kind != .Union) { 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] } params_for :: proc(store: ^Store, value: Type) -> []Field { item, ok := node(store, value) if !ok || item.kind != .Function { 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] } enum_members_for :: proc(store: ^Store, value: Type) -> []Enum_Member { item, ok := node(store, value) if !ok || item.kind != .Enum { return nil } start := int(item.field_start) end := start+int(item.field_count) if start < 0 || end > len(store.enum_members) { return nil } return store.enum_members[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 case FLOAT: return .Float_Constraint case RANGE: return .Range_Constraint case BOOL: return .Scalar } 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_bool :: proc(value: Type) -> bool { return value == BOOL } is_constraint :: proc(value: Type) -> bool { return value == INT || value == FLOAT || value == RANGE } // constraint_target reports the concrete type a constraint binding (local, or a // param/result monomorphized per call site) takes for an inferred value, or // INVALID if the value's family is incompatible. FLOAT accepts integers by // defaulting them to f64: a constant integer becomes a float literal in // build_constant_expr, while a runtime integer then fails the cross-family f64 // coercion in coerce_expr (the intended mismatch error). RANGE accepts any range // type, keeping its inferred element type. constraint_target :: proc(constraint, inferred: Type, store: ^Store = nil) -> Type { switch constraint { case INT: return inferred if is_concrete_integer(inferred) else INVALID case FLOAT: if is_float(inferred) { return inferred } return F64 if is_concrete_integer(inferred) else INVALID case RANGE: return inferred if is_range(inferred, store) else INVALID } return INVALID } // constraint_accepts reports strict family membership, used when widening a // constraint binding across assignments (no integer-to-float defaulting here). constraint_accepts :: proc(constraint, concrete: Type, store: ^Store = nil) -> bool { switch constraint { case INT: return is_concrete_integer(concrete) case FLOAT: return is_float(concrete) case RANGE: return is_range(concrete, store) } return false } 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 BOOL: return 1 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 == .Range || value_kind == .Optional || value_kind == .Enum { return true } if value_kind == .Struct || value_kind == .Union { 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_range :: proc(value: Type, store: ^Store) -> bool { return kind(value, store) == .Range } is_optional :: proc(value: Type, store: ^Store) -> bool { return kind(value, store) == .Optional } is_function :: proc(value: Type, store: ^Store) -> bool { return kind(value, store) == .Function } is_struct :: proc(value: Type, store: ^Store) -> bool { return kind(value, store) == .Struct } is_record :: proc(value: Type, store: ^Store) -> bool { value_kind := kind(value, store) return value_kind == .Struct || value_kind == .Union } is_union :: proc(value: Type, store: ^Store) -> bool { return kind(value, store) == .Union } // A tagged union is a `.Union` whose `child` is a valid enum (the discriminant). is_tagged_union :: proc(value: Type, store: ^Store) -> bool { item, ok := node(store, value) return ok && item.kind == .Union && is_enum(item.child, store) } union_tag_enum :: proc(value: Type, store: ^Store) -> Type { if !is_tagged_union(value, store) { return INVALID } item, _ := node(store, value) return item.child } // union_payload_offset is the byte offset of a tagged union's payload carrier (after // the discriminant), shared by `size` and the LLVM emitter so construction, field // access, and layout agree. Zero for untagged unions. union_payload_offset :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 { if !is_tagged_union(value, store) { return 0 } item, _ := node(store, value) tag_size := size(item.child, store, selected) payload_align: u64 = 1 for field in fields_for(store, value) { payload_align = max(payload_align, u64(alignment_of(field.type, store, selected))) } return (tag_size+payload_align-1)/payload_align*payload_align } is_distinct :: proc(value: Type, store: ^Store) -> bool { return kind(value, store) == .Distinct } is_enum :: proc(value: Type, store: ^Store) -> bool { return kind(value, store) == .Enum } resolve_alias :: proc(value: Type, store: ^Store, depth := 0) -> Type { if depth > 64 { return INVALID } item, ok := node(store, value) if !ok || item.kind != .Alias { return value } return resolve_alias(item.child, store, depth+1) } is_c_record_field_type :: proc(value: Type, store: ^Store, depth := 0) -> bool { if depth > 256 { return false } resolved := resolve_alias(value, store) if contains_distinct(resolved, store) { return false } if is_concrete_scalar(resolved) || is_pointer(resolved, store) || is_optional_pointer(resolved, store) { return true } item, ok := node(store, resolved) if !ok { return false } if item.kind == .Enum { return item.explicit_backing && is_concrete_integer(item.child) } if item.kind == .Array { return item.count > 0 && !item.has_sentinel && !item.inferred_count && is_c_record_field_type(item.child, store, depth+1) } if item.kind != .Struct && item.kind != .Union { return false } if !item.c_layout || !item.declared || item.opaque || item.field_count == 0 { return false } for field in fields_for(store, resolved) { if !is_c_record_field_type(field.type, store, depth+1) { return false } } return true } 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, depth := 0) -> bool { if depth > 256 { return false } value_kind := kind(value, store) if value_kind == .Scalar || value_kind == .Pointer { return true } if value_kind == .Slice || value_kind == .Array || value_kind == .Range || value_kind == .Optional { return !contains_c_struct_by_value(value, store) } if value_kind == .Struct || value_kind == .Union { item, ok := node(store, value) return ok && item.declared && !item.opaque && (!item.c_layout || item.field_count > 0) } if value_kind == .Distinct || value_kind == .Enum { item, ok := node(store, value) return ok && item.declared && is_runtime_value(item.child, store, depth+1) } return false } can_construct_distinct :: proc(from, to: Type, store: ^Store) -> bool { item, ok := node(store, to) return ok && item.kind == .Distinct && item.declared && equal(from, item.child) } runtime_representation :: proc(value: Type, store: ^Store, depth := 0) -> Type { if depth > 256 { return INVALID } item, ok := node(store, value) if !ok || (item.kind != .Distinct && item.kind != .Enum) { return value } return runtime_representation(item.child, store, depth+1) } 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.opaque || (item.c_layout && item.field_count == 0) { 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 == .Union { return item.opaque || (item.c_layout && item.field_count == 0) } if item.kind == .Array || item.kind == .Slice || item.kind == .Range || 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 } if contains_distinct(value, store) { return false } if item, ok := node(store, value); ok && item.kind == .Enum { return item.explicit_backing && is_concrete_integer(item.child) } return is_concrete_scalar(value) || is_pointer(value, store) || is_optional_pointer(value, store) || (is_c_struct(value, store) && is_runtime_value(value, store)) } // contains_distinct reports whether a `distinct` type is reachable from `value` // (by value, behind a pointer, through fields/params/children). A pointer to a // distinct type still counts — distinct types do not cross the C ABI even behind // indirection. The `seen` set makes the graph walk terminate on self-referential // records (e.g. `?*mut Node` inside `Node`), which previously recursed until the // depth cap and wrongly reported `true`. contains_distinct :: proc(value: Type, store: ^Store) -> bool { seen: [dynamic]Type defer delete(seen) return contains_distinct_seen(value, store, &seen) } contains_distinct_seen :: proc(value: Type, store: ^Store, seen: ^[dynamic]Type) -> bool { for visited in seen { if visited == value { return false } } item, ok := node(store, value) if !ok { return false } if item.kind == .Distinct { return true } append(seen, value) if item.kind == .Struct || item.kind == .Union { for field in fields_for(store, value) { if contains_distinct_seen(field.type, store, seen) { return true } } } if item.kind == .Function { for param in params_for(store, value) { if contains_distinct_seen(param.type, store, seen) { return true } } } return is_valid(item.child) && contains_distinct_seen(item.child, store, seen) } is_c_integer_promotion_candidate :: proc(value: Type) -> bool { return value >= C_CHAR && value <= C_USHORT } c_vararg_promotion :: proc(value: Type, selected := target.DEFAULT, store: ^Store = nil) -> Type { if store != nil { if item, ok := node(store, value); ok && item.kind == .Enum { return c_vararg_promotion(item.child, selected, store) } } if !is_concrete_scalar(value) { return value } if is_float(value, selected) && bits(value, selected) < bits(C_DOUBLE, selected) { return C_DOUBLE } if is_concrete_integer(value) { value_bits := bits(value, selected) int_bits := bits(C_INT, selected) if value_bits < int_bits { return C_INT } if is_c_integer_promotion_candidate(value) && value_bits == int_bits { return C_INT if is_signed(value, selected) else C_UINT } } return value } is_c_vararg_type :: proc(value: Type, store: ^Store) -> bool { if item, ok := node(store, value); ok && item.kind == .Enum { return item.explicit_backing && is_concrete_integer(item.child) } 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 } array_pointer :: proc(value: Type, store: ^Store) -> (pointer_item, array_item: Node, ok: bool) { pointer_ok: bool pointer_item, pointer_ok = node(store, value) if !pointer_ok || pointer_item.kind != .Pointer || pointer_item.many { return {}, {}, false } array_ok: bool array_item, array_ok = node(store, pointer_item.child) if !array_ok || array_item.kind != .Array { return {}, {}, false } return pointer_item, array_item, true } container :: proc(value: Type, store: ^Store) -> (Node, bool) { item, ok := node(store, value) if !ok { return {}, false } if item.kind == .Array || item.kind == .Slice || (item.kind == .Pointer && item.many) { return item, true } pointer_node, array_node, array_ok := array_pointer(value, store) if !array_ok { return {}, false } array_node.mutable = pointer_node.mutable && array_node.mutable return array_node, true } function_pointer :: proc(value: Type, store: ^Store) -> (pointer_item, function_item: Node, function_type: Type, ok: bool) { pointer_node, pointer_ok := node(store, value) if !pointer_ok || pointer_node.kind != .Pointer { return {}, {}, INVALID, false } function_node, function_ok := node(store, pointer_node.child) if !function_ok || function_node.kind != .Function { return {}, {}, INVALID, false } return pointer_node, function_node, pointer_node.child, true } is_c_struct :: proc(value: Type, store: ^Store) -> bool { item, ok := node(store, value) return ok && (item.kind == .Struct || item.kind == .Union) && item.c_layout } pointer :: proc( store: ^Store, child: Type, mutable, many: bool, has_sentinel := false, sentinel: u64 = 0, ) -> Type { return intern(store, Node{ kind=.Pointer, child=child, mutable=mutable, many=many, has_sentinel=has_sentinel, sentinel=sentinel, }) } 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, }) } range :: proc(store: ^Store, child: Type) -> Type { return intern(store, Node{kind=.Range, child=child}) } 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}) } function_params_equal :: proc(store: ^Store, item: Node, params: []Type) -> bool { if item.field_count != u32(len(params)) { return false } start := int(item.field_start) end := start+int(item.field_count) if start < 0 || end > len(store.fields) { return false } for param, index in params { if store.fields[start+index].type != param { return false } } return true } function :: proc(store: ^Store, params: []Type, result: Type, c_abi, variadic: bool) -> Type { for existing, index in store.nodes { if existing.kind == .Function && existing.child == result && existing.c_abi == c_abi && existing.variadic == variadic && function_params_equal(store, existing, params) { return DYNAMIC_START+Type(index) } } start := len(store.fields) for param in params { append(&store.fields, Field{type=param}) } return intern(store, Node{ kind=.Function, child=result, field_start=u32(start), field_count=u32(len(params)), c_abi=c_abi, variadic=variadic, }) } 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) if !from_ok || !to_ok || from_node.kind != .Pointer || to_node.kind != .Pointer || from_node.many != to_node.many || (to_node.mutable && !from_node.mutable) { return false } if to_node.has_sentinel && (!from_node.has_sentinel || from_node.sentinel != to_node.sentinel) { return false } same_child := from_node.child == to_node.child c_string := from_node.many && from_node.child == U8 && to_node.child == C_CHAR && from_node.has_sentinel && from_node.sentinel == 0 && !to_node.mutable return same_child || c_string } can_weaken_slice :: 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 == .Slice && to_node.kind == .Slice && from_node.child == to_node.child && (!to_node.mutable || from_node.mutable) && (!to_node.has_sentinel || (from_node.has_sentinel && from_node.sentinel == to_node.sentinel)) } can_decay_array_pointer :: proc(from, to: Type, store: ^Store) -> bool { from_pointer, array, from_ok := array_pointer(from, store) to_node, to_ok := node(store, to) if !from_ok || !to_ok { return false } mutable := from_pointer.mutable && array.mutable if to_node.mutable && !mutable { return false } if to_node.has_sentinel && (!array.has_sentinel || array.sentinel != to_node.sentinel) { return false } if to_node.kind == .Slice { return array.child == to_node.child } if to_node.kind != .Pointer || !to_node.many { return false } same_child := array.child == to_node.child c_string := array.child == U8 && to_node.child == C_CHAR && array.has_sentinel && array.sentinel == 0 && !to_node.mutable return same_child || c_string } is_opaque_struct :: proc(value: Type, store: ^Store) -> bool { item, ok := node(store, value) return ok && (item.kind == .Struct || item.kind == .Union) && 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 .Range: child_size := size(child_type(value, store), store, selected) child_align := u64(alignment_of(child_type(value, store), store, selected)) raw_size := child_size*2+1 return (raw_size+child_align-1)/child_align*child_align 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 .Function: return 0 case .Distinct, .Enum: return size(child_type(value, store), store, selected) case .Struct: item, _ := node(store, value) if item.explicit_size > 0 { return item.explicit_size } 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 .Union: item, _ := node(store, value) if item.explicit_size > 0 { return item.explicit_size } carrier_size: u64 max_align: u64 = 1 for field in fields_for(store, value) { carrier_size = max(carrier_size, size(field.type, store, selected)) max_align = max(max_align, u64(alignment_of(field.type, store, selected))) } if is_enum(item.child, store) { payload_offset := union_payload_offset(value, store, selected) total_align := max(max_align, u64(alignment_of(item.child, store, selected))) return (payload_offset+carrier_size+total_align-1)/total_align*total_align } return (carrier_size+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, .Range, .Optional: return alignment_of(child_type(value, store), store, selected) case .Function: return 1 case .Distinct, .Enum: return alignment_of(child_type(value, store), store, selected) case .Struct: item, _ := node(store, value) if item.explicit_alignment > 0 { return int(item.explicit_alignment) } result := 1 for field in fields_for(store, value) { result = max(result, alignment_of(field.type, store, selected)) } return result case .Union: item, _ := node(store, value) if item.explicit_alignment > 0 { return int(item.explicit_alignment) } result := 1 for field in fields_for(store, value) { result = max(result, alignment_of(field.type, store, selected)) } if is_enum(item.child, store) { result = max(result, alignment_of(item.child, 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) } // can_coerce_c_integer reports whether `from` may implicitly convert to `to` // under C's integer conversion rules. Brolang keeps its own exact-width scalars // strict (`u32 -> i32` is rejected), but C interop types deliberately follow C: // virtually every C library relies on it — e.g. an unsigned-backed enum constant // (`c_uint`) passed to an `int` (`c_int`) parameter — so disallowing it would // make C interop cumbersome. Scope: widening (sext/zext) and same-width // signedness changes (no-op reinterpret); narrowing is intentionally excluded so // lossy conversions stay an error, matching brolang's trap-on-narrow philosophy. can_coerce_c_integer :: proc(from, to: Type) -> bool { return from != to && is_c(from) && is_c(to) && is_concrete_integer(from) && is_concrete_integer(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 BOOL: return "bool" case INT: return "int" case FLOAT: return "float" case RANGE: return "range" 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) } }