refactor: precompute compiler enum mappings

This commit is contained in:
2026-10-30 10:10:43 +01:00
parent d7e637dc3b
commit 30a98db379
13 changed files with 452 additions and 279 deletions
+2
View File
@@ -243,6 +243,8 @@ AST nodes retain a compact 16-byte layout on the bootstrap target: a kind, three
identifier interning is compilation-owned: initialize a `strpool.StringPool`, pass it to `lexer.scan` and `sema.resolve` before the diagnostic store, and release it after lowering. scanning interns identifier spellings once per pool; parsing preserves their IDs in compact AST storage, exposed through `ast.name_id`, while retaining source spans for diagnostics. name resolution keys scopes by those IDs and interns builtin names into the same pool, so identifier hashing and equality do not scan spelling bytes. IDs are meaningful only within their originating pool; independent compilations may assign the same numeric ID to different names. interning failures propagate as infrastructure errors rather than producing an invalid identifier ID. IR retains no pool dependency. identifier interning is compilation-owned: initialize a `strpool.StringPool`, pass it to `lexer.scan` and `sema.resolve` before the diagnostic store, and release it after lowering. scanning interns identifier spellings once per pool; parsing preserves their IDs in compact AST storage, exposed through `ast.name_id`, while retaining source spans for diagnostics. name resolution keys scopes by those IDs and interns builtin names into the same pool, so identifier hashing and equality do not scan spelling bytes. IDs are meaningful only within their originating pool; independent compilations may assign the same numeric ID to different names. interning failures propagate as infrastructure errors rather than producing an invalid identifier ID. IR retains no pool dependency.
compiler token/operator mappings, scalar type metadata and backend spellings use compile-time `@std/enummap` tables; state-dependent layout and control-flow dispatch remain ordinary code. `enummap.init_filled(E, V, storage_default, fields)` initializes unused value slots so sparse maps can become owned compile-time constants without changing which keys are present. `enummap.get` still performs runtime linear lookup; static construction is not a constant-time lookup guarantee.
resolver scopes use `@std/hashmap.AutoHashMap(K, V)`, which shares `init`, `put`, `get` and `deinit` with custom `HashMap` and `StringHashMap`. automatic keys support integers, booleans, enums, distinct types, fixed arrays and records recursively; record padding is ignored. slices, floating-point values, pointers, optionals and unions require explicit hashing and equality through `HashMap` (`StringHashMap` supplies content-based byte-string operations). keys and values are stored by value; referenced resources remain caller-owned. the builtin declaration count is inferred, and builtin ID storage derives its fixed stack-array length from that declaration array rather than repeating a count or initializer. resolver scopes use `@std/hashmap.AutoHashMap(K, V)`, which shares `init`, `put`, `get` and `deinit` with custom `HashMap` and `StringHashMap`. automatic keys support integers, booleans, enums, distinct types, fixed arrays and records recursively; record padding is ignored. slices, floating-point values, pointers, optionals and unions require explicit hashing and equality through `HashMap` (`StringHashMap` supplies content-based byte-string operations). keys and values are stored by value; referenced resources remain caller-owned. the builtin declaration count is inferred, and builtin ID storage derives its fixed stack-array length from that declaration array rather than repeating a count or initializer.
`sema.resolve` returns an owned binding table indexed by AST `NodeId`, without modifying the AST. `sema.binding` identifies a declaration, package namespace, builtin type, or invalid binding carrying its diagnostic ID; nodes with no binding return `.none`. `sema.resolve_packages` additionally borrows package roots and import links, predeclares every package before resolving qualified uses, and keeps import aliases in their originating source file. selected declarations have the same binding identity as unqualified declarations. release the result with `sema.deinit`. unknown names and duplicate declarations add source-spanned diagnostics; invalid syntax and failed imports retain their existing diagnostic identity. same-scope duplicates retain the first declaration for recovery, while forbidden inner declarations invalidate that name's uses rather than silently binding them to an outer declaration. `sema.resolve` returns an owned binding table indexed by AST `NodeId`, without modifying the AST. `sema.binding` identifies a declaration, package namespace, builtin type, or invalid binding carrying its diagnostic ID; nodes with no binding return `.none`. `sema.resolve_packages` additionally borrows package roots and import links, predeclares every package before resolving qualified uses, and keeps import aliases in their originating source file. selected declarations have the same binding identity as unqualified declarations. release the result with `sema.deinit`. unknown names and duplicate declarations add source-spanned diagnostics; invalid syntax and failed imports retain their existing diagnostic identity. same-scope duplicates retain the first declaration for recovery, while forbidden inner declarations invalidate that name's uses rather than silently binding them to an outer declaration.
+18 -13
View File
@@ -1,5 +1,6 @@
import "@std/mem" import "@std/mem"
import "@std/arraylist" import "@std/arraylist"
import "@std/enummap"
import "@source/ast" import "@source/ast"
#! owns output bytes and retains its allocator; release with deinit. #! owns output bytes and retains its allocator; release with deinit.
@@ -161,19 +162,23 @@ append_text proc(renderer @mut Renderer, text []u8) void ! mem.AllocError {
for (text) |byte| try arraylist.append(&renderer.bytes, byte) for (text) |byte| try arraylist.append(&renderer.bytes, byte)
} }
@hide:file
assignment_operator_texts :: $enummap.init_filled(
ast.BinaryOperator, []u8, "", {
add = "+",
subtract = "-",
multiply = "*",
divide = "/",
bit_and = "&",
bit_or = "|",
bit_xor = "xor",
shift_left = "<<",
shift_right = ">>",
shift_left_saturating = "<<|",
},
)
@hide:file @hide:file
assignment_operator_text proc(operator ast.BinaryOperator) []u8 { assignment_operator_text proc(operator ast.BinaryOperator) []u8 {
return match operator { return enummap.get(&assignment_operator_texts, operator)?
.add: "+"
.subtract: "-"
.multiply: "*"
.divide: "/"
.bit_and: "&"
.bit_or: "|"
.bit_xor: "xor"
.shift_left: "<<"
.shift_right: ">>"
.shift_left_saturating: "<<|"
else: unreachable
}
} }
+50 -34
View File
@@ -1,6 +1,7 @@
import "@std/mem" import "@std/mem"
import "@std/arraylist" import "@std/arraylist"
import "@std/debug" import "@std/debug"
import "@std/enummap"
import "@source/diagnostics" import "@source/diagnostics"
FunctionId :: distinct u32 FunctionId :: distinct u32
@@ -33,44 +34,51 @@ aggregate proc(type Type) bool {
return type == .array or type == .record return type == .array or type == .record
} }
@hide:file
integer_widths :: $enummap.init_filled(Type, usize, usize(0), {
i8 = 8, u8 = 8,
i16 = 16, u16 = 16,
i32 = 32, u32 = 32,
int = 64, isize = 64, usize = 64, u64 = 64,
})
#! integer storage widths for the supported 64-bit target; zero for noninteger types. #! integer storage widths for the supported 64-bit target; zero for noninteger types.
integer_bits proc(type Type) usize { integer_bits proc(type Type) usize {
return match type { if (enummap.get(&integer_widths, type)) |width| return width
.i8, .u8: 8 return 0
.i16, .u16: 16
.i32, .u32: 32
.int, .isize, .usize, .u64: 64
else: 0
}
} }
unsigned_integer proc(type Type) bool { unsigned_integer proc(type Type) bool {
return type == .u8 or type == .u16 or type == .u32 or type == .u64 or type == .usize return type == .u8 or type == .u16 or type == .u32 or type == .u64 or type == .usize
} }
@hide:file
integer_maxima :: $enummap.init_filled(Type, u64, u64(0), {
i8 = 127,
i16 = 32767,
i32 = 2147483647,
int = u64(maxval!(i64)), isize = u64(maxval!(i64)),
u8 = 255,
u16 = 65535,
u32 = 4294967295,
u64 = maxval!(u64), usize = maxval!(u64),
})
integer_max proc(type Type) u64 { integer_max proc(type Type) u64 {
return match type { return enummap.get(&integer_maxima, type)?
.i8: 127
.i16: 32767
.i32: 2147483647
.int, .isize: u64(maxval!(i64))
.u8: 255
.u16: 65535
.u32: 4294967295
.u64, .usize: maxval!(u64)
else: unreachable
}
} }
@hide:file
integer_minima :: $enummap.init_filled(Type, i64, i64(0), {
i8 = i64(-128),
i16 = i64(-32768),
i32 = i64(-2147483648),
int = minval!(i64), isize = minval!(i64),
u8 = 0, u16 = 0, u32 = 0, u64 = 0, usize = 0,
})
integer_min proc(type Type) i64 { integer_min proc(type Type) i64 {
return match type { return enummap.get(&integer_minima, type)?
.i8: -128
.i16: -32768
.i32: -2147483648
.int, .isize: minval!(i64)
.u8, .u16, .u32, .u64, .usize: 0
else: unreachable
}
} }
ValueData :: union { ValueData :: union {
@@ -412,24 +420,32 @@ aligned_size proc(size, alignment usize) usize {
return divtrunc!(size + alignment - 1, alignment) * alignment return divtrunc!(size + alignment - 1, alignment) * alignment
} }
@hide:file
scalar_alignments :: $enummap.init_filled(Type, usize, usize(1), {
float = 8, string = 8, function = 8, pointer = 8,
})
@hide:file
scalar_sizes :: $enummap.init_filled(Type, usize, usize(0), {
float = 8, function = 8, pointer = 8,
string = 16,
bool = 1,
void = 0,
})
storage_alignment proc(module @Module, type Type, shape ArrayId) usize { storage_alignment proc(module @Module, type Type, shape ArrayId) usize {
if (aggregate(type)) return module.arrays.items[usize(shape)].alignment if (aggregate(type)) return module.arrays.items[usize(shape)].alignment
bits :: integer_bits(type) bits :: integer_bits(type)
if (bits != 0) return divtrunc!(bits, 8) if (bits != 0) return divtrunc!(bits, 8)
return if (type == .float or type == .string or type == .function or type == .pointer) 8 else 1 if (enummap.get(&scalar_alignments, type)) |alignment| return alignment
return 1
} }
storage_size proc(module @Module, type Type, shape ArrayId) usize { storage_size proc(module @Module, type Type, shape ArrayId) usize {
if (aggregate(type)) return module.arrays.items[usize(shape)].byte_size if (aggregate(type)) return module.arrays.items[usize(shape)].byte_size
bits :: integer_bits(type) bits :: integer_bits(type)
if (bits != 0) return divtrunc!(bits, 8) if (bits != 0) return divtrunc!(bits, 8)
return match type { return enummap.get(&scalar_sizes, type)?
.float, .function, .pointer: 8
.string: 16
.bool: 1
.void: 0
else: unreachable
}
} }
#! loads a scalar or borrows an aggregate from byte cells without allocating. #! loads a scalar or borrows an aggregate from byte cells without allocating.
+8 -4
View File
@@ -1,6 +1,7 @@
import "@std/mem" import "@std/mem"
import "@std/strmap" import "@std/strmap"
import "@std/arraylist" import "@std/arraylist"
import "@std/enummap"
import "@source/diagnostics" import "@source/diagnostics"
import "@source/strpool" import "@source/strpool"
@@ -474,12 +475,15 @@ is_digit proc(char u8) bool {
} }
} }
@hide:file
diagnostic_codes :: $enummap.init(ErrorCode, diagnostics.Code, {
float_must_end_with_digit = .float_must_end_with_digit,
unterminated_string = .unterminated_string,
})
@hide:file @hide:file
diagnostic_code proc(code ErrorCode) diagnostics.Code { diagnostic_code proc(code ErrorCode) diagnostics.Code {
return match code { return enummap.get(&diagnostic_codes, code)?
.float_must_end_with_digit: .float_must_end_with_digit
.unterminated_string: .unterminated_string
}
} }
@hide:file @hide:file
+63 -48
View File
@@ -1,7 +1,64 @@
import "@std/io" import "@std/io"
import "@std/enummap"
import "@source/ir" import "@source/ir"
import "@source/diagnostics" import "@source/diagnostics"
@hide:file
bitwise_opcodes :: $enummap.init_filled(ir.Op, []u8, "", {
bit_and = "and",
bit_or = "or",
bit_xor = "xor",
})
@hide:file
float_opcodes :: $enummap.init_filled(ir.Op, []u8, "", {
add = "fadd",
subtract = "fsub",
multiply = "fmul",
divide = "fdiv",
})
@hide:file
integer_opcodes :: $enummap.init_filled(ir.Op, []u8, "", {
add = "add",
subtract = "sub",
multiply = "mul",
})
@hide:file
float_predicates :: $enummap.init_filled(ir.Op, []u8, "", {
equal = "oeq",
not_equal = "une",
less = "olt",
less_equal = "ole",
greater = "ogt",
greater_equal = "oge",
})
@hide:file
integer_predicates :: $enummap.init_filled(ir.Op, []u8, "", {
equal = "eq",
not_equal = "ne",
less = "lt",
less_equal = "le",
greater = "gt",
greater_equal = "ge",
})
@hide:file
conversion_upper_bounds :: $enummap.init_filled(ir.Type, []u8, "", {
i8 = "1.280000e+02",
i16 = "3.276800e+04",
i32 = "2.147483648e+09",
int = "9.223372036854775808e+18",
isize = "9.223372036854775808e+18",
u8 = "2.560000e+02",
u16 = "6.553600e+04",
u32 = "4.294967296e+09",
u64 = "1.8446744073709551616e+19",
usize = "1.8446744073709551616e+19",
})
@hide @hide
emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruction) void ! io.WriteError { emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruction) void ! io.WriteError {
output :: state.output output :: state.output
@@ -311,12 +368,7 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
.bit_and, .bit_or, .bit_xor: { .bit_and, .bit_or, .bit_xor: {
left :: try load(state, instruction.left) left :: try load(state, instruction.left)
right :: try load(state, instruction.right) right :: try load(state, instruction.right)
opcode []u8 :: match instruction.op { opcode :: enummap.get(&bitwise_opcodes, instruction.op)?
.bit_and: "and"
.bit_or: "or"
.bit_xor: "xor"
else: unreachable
}
try io.print(" %r{} = {s} i{} %r{}, %r{}\n", output, {result, opcode, ir.integer_bits(instruction.type), left, right}) try io.print(" %r{} = {s} i{} %r{}, %r{}\n", output, {result, opcode, ir.integer_bits(instruction.type), left, right})
} }
.shift_left, .shift_right, .shift_left_saturating: result = try emit_shift(state, instruction) .shift_left, .shift_right, .shift_left_saturating: result = try emit_shift(state, instruction)
@@ -324,13 +376,7 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
left :: try load(state, instruction.left) left :: try load(state, instruction.left)
right :: try load(state, instruction.right) right :: try load(state, instruction.right)
if (instruction.type == .float) { if (instruction.type == .float) {
opcode []u8 :: match instruction.op { opcode :: enummap.get(&float_opcodes, instruction.op)?
.add: "fadd"
.subtract: "fsub"
.multiply: "fmul"
.divide: "fdiv"
else: unreachable
}
try io.print(" %r{} = {s} double %r{}, %r{}\n", output, {result, opcode, left, right}) try io.print(" %r{} = {s} double %r{}, %r{}\n", output, {result, opcode, left, right})
} else if (instruction.op == .divide) { } else if (instruction.op == .divide) {
bits :: ir.integer_bits(instruction.type) bits :: ir.integer_bits(instruction.type)
@@ -349,12 +395,7 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
opcode []u8 :: if (ir.unsigned_integer(instruction.type)) "udiv" else "sdiv" opcode []u8 :: if (ir.unsigned_integer(instruction.type)) "udiv" else "sdiv"
try io.print(" %r{} = {s} i{} %r{}, %r{}\n", output, {result, opcode, bits, left, right}) try io.print(" %r{} = {s} i{} %r{}, %r{}\n", output, {result, opcode, bits, left, right})
} else { } else {
opcode []u8 :: match instruction.op { opcode :: enummap.get(&integer_opcodes, instruction.op)?
.add: "add"
.subtract: "sub"
.multiply: "mul"
else: unreachable
}
bits :: ir.integer_bits(instruction.type) bits :: ir.integer_bits(instruction.type)
sign []u8 :: if (ir.unsigned_integer(instruction.type)) "u" else "s" sign []u8 :: if (ir.unsigned_integer(instruction.type)) "u" else "s"
pair :: fresh(state) pair :: fresh(state)
@@ -370,15 +411,7 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
right :: try load(state, instruction.right) right :: try load(state, instruction.right)
operand_type :: state.values[usize(instruction.left)] operand_type :: state.values[usize(instruction.left)]
if (operand_type.kind == .float) { if (operand_type.kind == .float) {
predicate []u8 :: match instruction.op { predicate :: enummap.get(&float_predicates, instruction.op)?
.equal: "oeq"
.not_equal: "une"
.less: "olt"
.less_equal: "ole"
.greater: "ogt"
.greater_equal: "oge"
else: unreachable
}
try io.print(" %r{} = fcmp {s} double %r{}, %r{}\n", output, {result, predicate, left, right}) try io.print(" %r{} = fcmp {s} double %r{}, %r{}\n", output, {result, predicate, left, right})
} else { } else {
prefix []u8 := "" prefix []u8 := ""
@@ -386,15 +419,7 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
if (ir.unsigned_integer(operand_type.kind)) prefix = "u" if (ir.unsigned_integer(operand_type.kind)) prefix = "u"
else prefix = "s" else prefix = "s"
} }
predicate []u8 :: match instruction.op { predicate :: enummap.get(&integer_predicates, instruction.op)?
.equal: "eq"
.not_equal: "ne"
.less: "lt"
.less_equal: "le"
.greater: "gt"
.greater_equal: "ge"
else: unreachable
}
try io.print(" %r{} = icmp {s}{s} ", output, {result, prefix, predicate}) try io.print(" %r{} = icmp {s}{s} ", output, {result, prefix, predicate})
try write_type(output, state.module, operand_type) try write_type(output, state.module, operand_type)
try io.print(" %r{}, %r{}\n", output, {left, right}) try io.print(" %r{}, %r{}\n", output, {left, right})
@@ -644,17 +669,7 @@ emit_conversion proc(state @mut State, instruction ir.Instruction) usize ! io.Wr
upper_valid :: fresh(state) upper_valid :: fresh(state)
valid :: fresh(state) valid :: fresh(state)
failed :: fresh(state) failed :: fresh(state)
upper []u8 :: match destination { upper :: enummap.get(&conversion_upper_bounds, destination)?
.i8: "1.280000e+02"
.i16: "3.276800e+04"
.i32: "2.147483648e+09"
.int, .isize: "9.223372036854775808e+18"
.u8: "2.560000e+02"
.u16: "6.553600e+04"
.u32: "4.294967296e+09"
.u64, .usize: "1.8446744073709551616e+19"
else: unreachable
}
try io.print(" %r{} = call double @llvm.trunc.f64(double %r{})\n", output, {truncated, operand}) try io.print(" %r{} = call double @llvm.trunc.f64(double %r{})\n", output, {truncated, operand})
if (destination_unsigned) try io.print(" %r{} = fcmp oge double %r{}, 0.0\n", output, {lower_valid, truncated}) if (destination_unsigned) try io.print(" %r{} = fcmp oge double %r{}, 0.0\n", output, {lower_valid, truncated})
else try io.print(" %r{} = fcmp oge double %r{}, -{s}\n", output, {lower_valid, truncated, upper}) else try io.print(" %r{} = fcmp oge double %r{}, -{s}\n", output, {lower_valid, truncated, upper})
+33 -23
View File
@@ -211,13 +211,19 @@ write_type proc(output io.Writer, module @ir.Module, type Type) void ! io.WriteE
} }
} }
@hide:file
abi_extensions :: $enummap.init_filled(ir.Type, []u8, "", {
i8 = "signext",
i16 = "signext",
u8 = "zeroext",
u16 = "zeroext",
bool = "zeroext",
})
@hide:file @hide:file
abi_extension proc(kind ir.Type) []u8 { abi_extension proc(kind ir.Type) []u8 {
return match kind { if (enummap.get(&abi_extensions, kind)) |extension| return extension
.i8, .i16: "signext" return ""
.u8, .u16, .bool: "zeroext"
else: ""
}
} }
@hide @hide
@@ -495,34 +501,38 @@ check_local proc(state @State, slot u32, read bool, span diagnostics.Span) void
try io.print(" call void @check_init(ptr %i{}, i1 {}, i64 {}, i64 {}, i64 {})\n", state.output, {slot, read, usize(span.source), position.line, position.column}) try io.print(" call void @check_init(ptr %i{}, i1 {}, i64 {}, i64 {}, i64 {})\n", state.output, {slot, read, usize(span.source), position.line, position.column})
} }
@hide:file
memory_symbols :: $enummap.init_filled(diagnostics.Code, []u8, "", {
null_unwrap = "null_unwrap",
invalid_slice_bounds = "invalid_slice_bounds",
sentinel_mismatch = "sentinel_mismatch",
overlapping_memory_copy = "overlapping_memory_copy",
unsupported_foreign_call = "unsupported_foreign_call",
invalid_pointer_access = "invalid_pointer_access",
division_by_zero = "division_by_zero",
invalid_shift_count = "invalid_shift_count",
})
@hide:file
trap_symbols :: $enummap.init_filled(diagnostics.Code, []u8, "", {
integer_overflow = "overflow",
index_out_of_bounds = "bounds",
inactive_union_field = "inactive_union",
})
@hide:file @hide:file
memory_symbol proc(code diagnostics.Code) []u8 { memory_symbol proc(code diagnostics.Code) []u8 {
return match code { return enummap.get(&memory_symbols, code)?
.null_unwrap: "null_unwrap"
.invalid_slice_bounds: "invalid_slice_bounds"
.sentinel_mismatch: "sentinel_mismatch"
.overlapping_memory_copy: "overlapping_memory_copy"
.unsupported_foreign_call: "unsupported_foreign_call"
.invalid_pointer_access: "invalid_pointer_access"
.division_by_zero: "division_by_zero"
.invalid_shift_count: "invalid_shift_count"
else: unreachable
}
} }
@hide:file @hide:file
trap proc(state @State, code diagnostics.Code, span diagnostics.Span) void ! io.WriteError { trap proc(state @State, code diagnostics.Code, span diagnostics.Span) void ! io.WriteError {
position :: location(state.line_starts, span) position :: location(state.line_starts, span)
symbol []u8 :: match code { symbol :: enummap.get(&trap_symbols, code)
.integer_overflow: "overflow"
.index_out_of_bounds: "bounds"
.inactive_union_field: "inactive_union"
else: ""
}
message :: enummap.get(diagnostics.Code, []u8, &diagnostics.messages, code)? message :: enummap.get(diagnostics.Code, []u8, &diagnostics.messages, code)?
length :: message.len length :: message.len
try io.print(" call void @trap(i64 -1, i64 {}, i64 {}, i64 {}, ptr @", state.output, {usize(span.source), position.line, position.column}) try io.print(" call void @trap(i64 -1, i64 {}, i64 {}, i64 {}, ptr @", state.output, {usize(span.source), position.line, position.column})
if (symbol.len != 0) try io.write_all(state.output, symbol) if (symbol) |name| try io.write_all(state.output, name)
else try io.print("memory_{s}", state.output, {memory_symbol(code)}) else try io.print("memory_{s}", state.output, {memory_symbol(code)})
try io.print(", i64 {})\n unreachable\n", state.output, {length}) try io.print(", i64 {})\n unreachable\n", state.output, {length})
} }
+65 -48
View File
@@ -1,6 +1,7 @@
import "@std/mem" import "@std/mem"
import "@std/arraylist" import "@std/arraylist"
import "@std/strconv" import "@std/strconv"
import "@std/enummap"
import "@ffi/c" import "@ffi/c"
import "@source/ast" import "@source/ast"
import "@source/diagnostics" import "@source/diagnostics"
@@ -181,35 +182,43 @@ lower_module proc(
return module return module
} }
@hide:file
base_types :: $enummap.init_filled(
typecheck.TypeKind, ir.Type, ir.Type.invalid, {
int = .int,
usize = .usize,
i8 = .i8,
i16 = .i16,
i32 = .i32,
isize = .isize,
u8 = .u8,
u16 = .u16,
u32 = .u32,
u64 = .u64,
array = .array,
record = .record,
pointer = .pointer,
float = .float,
bool = .bool,
string = .string,
void = .void,
function = .function,
none = .invalid,
invalid = .invalid,
range = .invalid,
type_value = .invalid,
null = .invalid,
anyopaque = .invalid,
},
)
@hide:file @hide:file
lower_type proc(state @State, type typecheck.Type) ir.Type { lower_type proc(state @State, type typecheck.Type) ir.Type {
if (type.kind == .enumeration) return lower_type(state, typecheck.Type{ kind = typecheck.enum_type(state.checked, type.enum_type).backing }) if (type.kind == .enumeration) return lower_type(state, typecheck.Type{ kind = typecheck.enum_type(state.checked, type.enum_type).backing })
if (type.kind == .slice) return .record if (type.kind == .slice) return .record
if (type.kind == .fallible) return .record if (type.kind == .fallible) return .record
if (type.kind == .optional) return lower_optional_type(state, typecheck.array_type(state.checked, type.array_type).element) if (type.kind == .optional) return lower_optional_type(state, typecheck.array_type(state.checked, type.array_type).element)
return match type.kind { return enummap.get(&base_types, type.kind)?
.int: .int
.usize: .usize
.i8: .i8
.i16: .i16
.i32: .i32
.isize: .isize
.u8: .u8
.u16: .u16
.u32: .u32
.u64: .u64
.array: .array
.record: .record
.pointer: .pointer
.float: .float
.bool: .bool
.string: .string
.void: .void
.function: .function
.enumeration: unreachable
.none, .invalid, .range, .type_value, .null, .anyopaque: .invalid
.slice, .optional, .fallible: unreachable
}
} }
@hide:file @hide:file
@@ -1046,27 +1055,40 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
} }
} }
@hide:file
binary_ops :: $enummap.init_filled(
ast.BinaryOperator, ir.Op, ir.Op.constant, {
add = .add,
subtract = .subtract,
multiply = .multiply,
divide = .divide,
bit_and = .bit_and,
bit_or = .bit_or,
bit_xor = .bit_xor,
shift_left = .shift_left,
shift_right = .shift_right,
shift_left_saturating = .shift_left_saturating,
equal = .equal,
not_equal = .not_equal,
less = .less,
less_equal = .less_equal,
greater = .greater,
greater_equal = .greater_equal,
},
)
@hide:file
unary_ops :: $enummap.init_filled(
ast.UnaryOperator, ir.Op, ir.Op.constant, {
negate = .negate,
logical_not = .logical_not,
bit_not = .bit_not,
},
)
@hide:file @hide:file
binary_op proc(operator ast.BinaryOperator) ir.Op { binary_op proc(operator ast.BinaryOperator) ir.Op {
return match operator { return enummap.get(&binary_ops, operator)?
.add: .add
.subtract: .subtract
.multiply: .multiply
.divide: .divide
.bit_and: .bit_and
.bit_or: .bit_or
.bit_xor: .bit_xor
.shift_left: .shift_left
.shift_right: .shift_right
.shift_left_saturating: .shift_left_saturating
.equal: .equal
.not_equal: .not_equal
.less: .less
.less_equal: .less_equal
.greater: .greater
.greater_equal: .greater_equal
.logical_and, .logical_or: unreachable
}
} }
@hide:file @hide:file
@@ -2249,12 +2271,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
} }
operand :: try lower_expression(state, expression.operand) operand :: try lower_expression(state, expression.operand)
if (state.terminated or trap_type(state, id)) return ir.no_value if (state.terminated or trap_type(state, id)) return ir.no_value
op ir.Op :: match expression.operator { op :: enummap.get(&unary_ops, expression.operator)?
.negate: .negate
.logical_not: .logical_not
.bit_not: .bit_not
.address_of, .dereference, .unwrap: unreachable
}
return try emit(state, ir.Instruction{ op = op, type = type, left = operand, span = span }) return try emit(state, ir.Instruction{ op = op, type = type, left = operand, span = span })
} }
.binary: { .binary: {
+97 -61
View File
@@ -1,5 +1,6 @@
import "@std/mem" import "@std/mem"
import "@std/arraylist" import "@std/arraylist"
import "@std/enummap"
import "@source/diagnostics" import "@source/diagnostics"
import "@source/lexer" import "@source/lexer"
@@ -937,24 +938,32 @@ parse_function_type proc(state @mut State, start ast.Span, invalid bool) ast.Nod
return id return id
} }
@hide:file
type_starters :: $enummap.init_filled(lexer.TokenKind, bool, false, {
ident = true, open_bracket = true, struct = true, c_struct = true, opaque = true,
enum = true, union = true, distinct = true, alias = true, match = true, if = true,
at = true, star = true, question = true, open_paren = true, proc = true, c_func = true,
})
@hide:file @hide:file
starts_type proc(kind lexer.TokenKind) bool { starts_type proc(kind lexer.TokenKind) bool {
return ( return enummap.get(&type_starters, kind) != null
kind == .ident or kind == .open_bracket or kind == .struct or kind == .c_struct or kind == .opaque
or kind == .enum or kind == .union or kind == .distinct or kind == .alias or kind == .match
or kind == .if
or kind == .at or kind == .star or kind == .question or kind == .open_paren or kind == .proc or kind == .c_func
)
} }
@hide:file
member_names :: $enummap.init_filled(lexer.TokenKind, bool, false, {
ident = true, if = true, else = true, for = true, inline = true, while = true,
proc = true, return = true, yield = true, break = true, continue = true,
true = true, false = true, and = true, or = true, xor = true, mut = true,
struct = true, c_struct = true, opaque = true, enum = true, union = true,
distinct = true, alias = true, match = true, import = true, test = true,
c_func = true, null = true, undefined = true, try = true, catch = true,
defer = true, errdefer = true,
})
@hide:file @hide:file
member_name proc(kind lexer.TokenKind) bool { member_name proc(kind lexer.TokenKind) bool {
return match kind { return enummap.get(&member_names, kind) != null
.ident, .if, .else, .for, .inline, .while, .proc, .return, .yield, .break, .continue,
.true, .false, .and, .or, .xor, .mut, .struct, .c_struct, .opaque, .enum, .union, .distinct,
.alias, .match, .import, .test, .c_func, .null, .undefined, .try, .catch, .defer, .errdefer: true
else: false
}
} }
@hide:file @hide:file
@@ -2112,52 +2121,77 @@ consume_either proc(state @mut State, kinds []lexer.TokenKind) lexer.Token ! Syn
return .unexpected_token return .unexpected_token
} }
@hide:file
prefix_binding_powers :: $enummap.init_filled(
lexer.TokenKind, BindingPowerScalar, BindingPowerScalar(0), {
minus = BindingPowerScalar(50), bang = BindingPowerScalar(50), tilde = BindingPowerScalar(50),
ampersand = BindingPowerScalar(50), try = BindingPowerScalar(50),
})
@hide:file @hide:file
prefix_binding_power proc(kind lexer.TokenKind) ?BindingPowerScalar { prefix_binding_power proc(kind lexer.TokenKind) ?BindingPowerScalar {
return match kind { return enummap.get(&prefix_binding_powers, kind)
.minus, .bang, .tilde, .ampersand, .try: BindingPowerScalar(50)
else: null
}
} }
@hide:file
infix_binding_powers :: $enummap.init_filled(lexer.TokenKind, BindingPower,
BindingPower{ left = BindingPowerScalar(0), right = BindingPowerScalar(0) },
{
or = BindingPower{ left = BindingPowerScalar(2), right = BindingPowerScalar(3) },
and = BindingPower{ left = BindingPowerScalar(4), right = BindingPowerScalar(5) },
equal_equal = BindingPower{ left = BindingPowerScalar(10), right = BindingPowerScalar(11) },
not_equal = BindingPower{ left = BindingPowerScalar(10), right = BindingPowerScalar(11) },
less = BindingPower{ left = BindingPowerScalar(20), right = BindingPowerScalar(21) },
less_equal = BindingPower{ left = BindingPowerScalar(20), right = BindingPowerScalar(21) },
greater = BindingPower{ left = BindingPowerScalar(20), right = BindingPowerScalar(21) },
greater_equal = BindingPower{ left = BindingPowerScalar(20), right = BindingPowerScalar(21) },
ampersand = BindingPower{ left = BindingPowerScalar(22), right = BindingPowerScalar(23) },
xor = BindingPower{ left = BindingPowerScalar(22), right = BindingPowerScalar(23) },
pipe = BindingPower{ left = BindingPowerScalar(22), right = BindingPowerScalar(23) },
shift_left = BindingPower{ left = BindingPowerScalar(25), right = BindingPowerScalar(26) },
shift_right = BindingPower{ left = BindingPowerScalar(25), right = BindingPowerScalar(26) },
shift_left_saturating = BindingPower{ left = BindingPowerScalar(25), right = BindingPowerScalar(26) },
plus = BindingPower{ left = BindingPowerScalar(30), right = BindingPowerScalar(31) },
minus = BindingPower{ left = BindingPowerScalar(30), right = BindingPowerScalar(31) },
star = BindingPower{ left = BindingPowerScalar(40), right = BindingPowerScalar(41) },
slash = BindingPower{ left = BindingPowerScalar(40), right = BindingPowerScalar(41) },
},
)
@hide:file @hide:file
infix_binding_power proc(kind lexer.TokenKind) ?BindingPower { infix_binding_power proc(kind lexer.TokenKind) ?BindingPower {
return match kind { return enummap.get(&infix_binding_powers, kind)
.or: BindingPower{ left = BindingPowerScalar(2), right = BindingPowerScalar(3) }
.and: BindingPower{ left = BindingPowerScalar(4), right = BindingPowerScalar(5) }
.equal_equal, .not_equal: BindingPower{ left = BindingPowerScalar(10), right = BindingPowerScalar(11) }
.less, .less_equal, .greater, .greater_equal: BindingPower{ left = BindingPowerScalar(20), right = BindingPowerScalar(21) }
.ampersand, .xor, .pipe: BindingPower{ left = BindingPowerScalar(22), right = BindingPowerScalar(23) }
.shift_left, .shift_right, .shift_left_saturating: BindingPower{ left = BindingPowerScalar(25), right = BindingPowerScalar(26) }
.plus, .minus: BindingPower{ left = BindingPowerScalar(30), right = BindingPowerScalar(31) }
.star, .slash: BindingPower{ left = BindingPowerScalar(40), right = BindingPowerScalar(41) }
else: null
}
} }
@hide:file
binary_operators :: $enummap.init_filled(
lexer.TokenKind,
ast.BinaryOperator,
ast.BinaryOperator.add, {
and = .logical_and,
or = .logical_or,
plus = .add,
minus = .subtract,
star = .multiply,
slash = .divide,
ampersand = .bit_and,
pipe = .bit_or,
xor = .bit_xor,
shift_left = .shift_left,
shift_right = .shift_right,
shift_left_saturating = .shift_left_saturating,
equal_equal = .equal,
not_equal = .not_equal,
less = .less,
less_equal = .less_equal,
greater = .greater,
greater_equal = .greater_equal,
},
)
@hide:file @hide:file
binary_operator proc(kind lexer.TokenKind) ast.BinaryOperator { binary_operator proc(kind lexer.TokenKind) ast.BinaryOperator {
return match kind { return enummap.get(&binary_operators, kind)?
.and: .logical_and
.or: .logical_or
.plus: .add
.minus: .subtract
.star: .multiply
.slash: .divide
.ampersand: .bit_and
.pipe: .bit_or
.xor: .bit_xor
.shift_left: .shift_left
.shift_right: .shift_right
.shift_left_saturating: .shift_left_saturating
.equal_equal: .equal
.not_equal: .not_equal
.less: .less
.less_equal: .less_equal
.greater: .greater
.greater_equal: .greater_equal
else: unreachable
}
} }
@hide:file @hide:file
@@ -2165,19 +2199,21 @@ is_assignment_operator proc(kind lexer.TokenKind) bool {
return kind == .equal or assignment_operator(kind) != null return kind == .equal or assignment_operator(kind) != null
} }
@hide:file
assignment_operators :: $enummap.init_filled(lexer.TokenKind, ast.BinaryOperator, ast.BinaryOperator.add, {
plus_equal = .add,
minus_equal = .subtract,
star_equal = .multiply,
slash_equal = .divide,
ampersand_equal = .bit_and,
pipe_equal = .bit_or,
xor_equal = .bit_xor,
shift_left_equal = .shift_left,
shift_right_equal = .shift_right,
shift_left_saturating_equal = .shift_left_saturating,
})
@hide:file @hide:file
assignment_operator proc(kind lexer.TokenKind) ?ast.BinaryOperator { assignment_operator proc(kind lexer.TokenKind) ?ast.BinaryOperator {
return match kind { return enummap.get(&assignment_operators, kind)
.plus_equal: .add
.minus_equal: .subtract
.star_equal: .multiply
.slash_equal: .divide
.ampersand_equal: .bit_and
.pipe_equal: .bit_or
.xor_equal: .bit_xor
.shift_left_equal: .shift_left
.shift_right_equal: .shift_right
.shift_left_saturating_equal: .shift_left_saturating
else: null
}
} }
+42 -34
View File
@@ -3,6 +3,7 @@ import "@std/mem"
import "@std/arraylist" import "@std/arraylist"
import "@std/hashmap" import "@std/hashmap"
import "@std/strconv" import "@std/strconv"
import "@std/enummap"
import "@source/ast" import "@source/ast"
import "@source/diagnostics" import "@source/diagnostics"
sema :: import "@source/sema" sema :: import "@source/sema"
@@ -1521,35 +1522,38 @@ numeric_kind proc(kind TypeKind) bool {
return integer_kind(kind) or kind == .float return integer_kind(kind) or kind == .float
} }
@hide:file
builtin_kinds :: $enummap.init(sema.BuiltinType, TypeKind, {
int = .int, i64 = .int,
float = .float, f64 = .float,
i8 = .i8,
i16 = .i16,
i32 = .i32,
isize = .isize,
u8 = .u8,
u16 = .u16,
u32 = .u32,
u64 = .u64, uint = .u64,
usize = .usize,
bool = .bool,
string = .string,
void = .void,
anyopaque = .anyopaque,
type = .type_value,
c_int = .i32,
c_uint = .u32,
c_long = .int, c_longlong = .int,
c_ulong = .u64, c_ulonglong = .u64,
c_char = .i8,
c_uchar = .u8,
c_short = .i16,
c_ushort = .u16,
unavailable = .none,
})
@hide:file @hide:file
builtin_kind proc(type sema.BuiltinType) TypeKind { builtin_kind proc(type sema.BuiltinType) TypeKind {
return match type { return enummap.get(&builtin_kinds, type)?
.int, .i64: .int
.float, .f64: .float
.i8: .i8
.i16: .i16
.i32: .i32
.isize: .isize
.u8: .u8
.u16: .u16
.u32: .u32
.u64, .uint: .u64
.usize: .usize
.bool: .bool
.string: .string
.void: .void
.anyopaque: .anyopaque
.type: .type_value
.c_int: .i32
.c_uint: .u32
.c_long, .c_longlong: .int
.c_ulong, .c_ulonglong: .u64
.c_char: .i8
.c_uchar: .u8
.c_short: .i16
.c_ushort: .u16
.unavailable: .none
}
} }
@hide:file @hide:file
@@ -1584,15 +1588,19 @@ bitwise proc(operator ast.BinaryOperator) bool {
return operator == .bit_and or operator == .bit_or or operator == .bit_xor or shift(operator) return operator == .bit_and or operator == .bit_or or operator == .bit_xor or shift(operator)
} }
@hide:file
integer_widths :: $enummap.init_filled(
TypeKind, usize, usize(0), {
i8 = 8, u8 = 8,
i16 = 16, u16 = 16,
i32 = 32, u32 = 32,
int = 64, isize = 64, u64 = 64, usize = 64,
})
@hide:file @hide:file
integer_width proc(kind TypeKind) usize { integer_width proc(kind TypeKind) usize {
return match kind { if (enummap.get(&integer_widths, kind)) |width| return width
.i8, .u8: 8 return 0
.i16, .u16: 16
.i32, .u32: 32
.int, .isize, .u64, .usize: 64
else: 0
}
} }
@hide:file @hide:file
+19 -12
View File
@@ -1,6 +1,7 @@
import "@std/mem" import "@std/mem"
import "@std/arraylist" import "@std/arraylist"
import "@std/hashmap" import "@std/hashmap"
import "@std/enummap"
import "@source/ast" import "@source/ast"
import "@source/diagnostics" import "@source/diagnostics"
import "@source/strpool" import "@source/strpool"
@@ -5295,20 +5296,26 @@ replace proc(state @mut State, target, source ast.NodeId) void ! mem.AllocError
ast.replace_node(state.builder, target, source) ast.replace_node(state.builder, target, source)
} }
@hide:file
builtin_identities :: $enummap.init_filled(
sema.BuiltinType,
sema.BuiltinType,
sema.BuiltinType.type, {
i64 = .int, c_long = .int, c_longlong = .int,
f64 = .float,
uint = .u64, c_ulong = .u64, c_ulonglong = .u64,
c_char = .i8,
c_uchar = .u8,
c_short = .i16,
c_ushort = .u16,
c_int = .i32,
c_uint = .u32,
})
@hide:file @hide:file
builtin_identity proc(kind sema.BuiltinType) sema.BuiltinType { builtin_identity proc(kind sema.BuiltinType) sema.BuiltinType {
return match kind { if (enummap.get(&builtin_identities, kind)) |identity| return identity
.i64, .c_long, .c_longlong: .int return kind
.f64: .float
.uint, .c_ulong, .c_ulonglong: .u64
.c_char: .i8
.c_uchar: .u8
.c_short: .i16
.c_ushort: .u16
.c_int: .i32
.c_uint: .u32
else: kind
}
} }
@hide:file @hide:file
+21 -2
View File
@@ -15,6 +15,25 @@ EnumMap proc($E, $V type) type {
init proc( init proc(
$E, $V type, $E, $V type,
values meta.EnumFieldStruct(E, ?V, some!(null)), values meta.EnumFieldStruct(E, ?V, some!(null)),
) EnumMap(E, V) {
return initialize(E, V, null, values)
}
#! initializes absent value slots without making their keys present.
#! use fully initialized storage when a sparse map must be materialized at compile time.
init_filled proc(
$E, $V type,
storage_default V,
values meta.EnumFieldStruct(E, ?V, some!(null)),
) EnumMap(E, V) {
return initialize(E, V, some!(storage_default), values)
}
@hide:file
initialize proc(
$E, $V type,
storage_default ?V,
values meta.EnumFieldStruct(E, ?V, some!(null)),
) EnumMap(E, V) { ) EnumMap(E, V) {
map EnumMap(E, V) := undefined map EnumMap(E, V) := undefined
@@ -25,7 +44,7 @@ init proc(
if field!(values, field.name) |value| { if field!(values, field.name) |value| {
map.present[i] = true map.present[i] = true
map.values[i] = value map.values[i] = value
} } else if (storage_default) |value| map.values[i] = value
} }
else: compile_error!("EnumMap key must be an enum") else: compile_error!("EnumMap key must be an enum")
} }
@@ -38,7 +57,7 @@ get proc($E, $V type, map @EnumMap(E, V), key E) ?V {
match typeinfo!(E) { match typeinfo!(E) {
.enum |info|: inline for info.fields |field, i| { .enum |info|: inline for info.fields |field, i| {
if key == field!(E, field.name) { if key == field!(E, field.name) {
if (map.present[i]) return map.values[i] if (map.present[i]) return some!(map.values[i])
return null return null
} }
} }
+16
View File
@@ -25,6 +25,22 @@ handles_sparse_enum_get test {
try testing.expect_equal(null, eof) try testing.expect_equal(null, eof)
} }
@hide:file
sparse_optional_values :: $init_filled(TestEnum, ?i16, null, {
ident = some!(null),
int = some!(some!(i16(42))),
})
filled_comptime_storage_preserves_absence_and_nested_optional_presence test {
absent :: get(&sparse_optional_values, TestEnum.eof)
present_null :: get(&sparse_optional_values, TestEnum.ident)
present_value :: get(&sparse_optional_values, TestEnum.int)
try testing.expect(absent == null)
try testing.expect(present_null != null)
try testing.expect(present_null? == null)
try testing.expect_equal(i16(42), present_value??)
}
@hide:file @hide:file
TestLeftError :: enum { TestLeftError :: enum {
shared shared
+18
View File
@@ -9039,6 +9039,24 @@ def check_typeinfo_packages(temporary, executable):
} }
""", b"42\n", None, """, b"42\n", None,
), ),
"real_enummap_filled_sparse_constants_preserve_optional_presence": (
"""
import "@std/meta"
import "@std/enummap"
Key :: enum(i16) { absent = -3, present_null = 8, present_value = 21 }
main proc() i16 {
values :: $(enummap.init_filled(Key, ?i16, null, {
present_null = some!(null),
present_value = some!(some!(i16(42))),
}))
empty :: $(enummap.init_filled(Key, i16, i16(99), {}))
if (enummap.get(&values, .absent) != null or enummap.get(&empty, .absent) != null) return -1
payload :: enummap.get(&values, .present_null)
if (payload == null or payload? != null) return -2
return enummap.get(&values, .present_value)??
}
""", b"42\n", None,
),
"metadata_slices_cross_producers_without_losing_type_identity": ( "metadata_slices_cross_producers_without_losing_type_identity": (
""" """
import "@std/meta" import "@std/meta"