bitwise operations

This commit is contained in:
2026-07-19 00:36:31 +02:00
parent f9448363e4
commit c7e3162ecb
21 changed files with 219439 additions and 140077 deletions
+244
View File
@@ -177,6 +177,240 @@ lexer_preserves_newlines_and_skips_comments :: proc(t: ^testing.T) {
testing.expect_value(t, stream.items[1].kind, token.Kind.Identifier)
}
@(test)
lexer_recognizes_bitwise_operators_with_longest_match :: proc(t: ^testing.T) {
source_file := source.Source{path="test.bro", text="~ & &= | |= xor xor= << <<= >> >>= <<| <<|= ^"}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
expected := [?]token.Kind{
.Tilde, .Ampersand, .Ampersand_Equal, .Pipe, .Pipe_Equal, .Keyword_Xor, .Xor_Equal,
.Less_Less, .Less_Less_Equal, .Greater_Greater, .Greater_Greater_Equal,
.Less_Less_Pipe, .Less_Less_Pipe_Equal, .Caret, .Eof,
}
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, len(stream.items), len(expected))
for kind, index in expected {
testing.expect_value(t, stream.items[index].kind, kind)
}
}
@(test)
parser_applies_bitwise_precedence_and_preserves_capture_and_deref_pipes :: proc(t: ^testing.T) {
text := `main func() void {
_ = 1 + 2 << 1 & 7 xor 3 | 4 == 5 and true or false
value i32 = 1
pointer *i32 = &value
_ = pointer^ & 1
if (none | none) |captured| { _ = captured }
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&module)
root := module.exprs[module.statements[module.functions[0].body[0]].expr]
and_expr := module.exprs[root.left]
equality := module.exprs[and_expr.left]
bit_or := module.exprs[equality.left]
bit_xor := module.exprs[bit_or.left]
bit_and := module.exprs[bit_xor.left]
shift := module.exprs[bit_and.left]
addition := module.exprs[shift.left]
deref_and := module.exprs[module.statements[module.functions[0].body[3]].expr]
if_statement := module.statements[module.functions[0].body[4]]
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, root.kind, ast.Expr_Kind.Or)
testing.expect_value(t, and_expr.kind, ast.Expr_Kind.And)
testing.expect_value(t, equality.kind, ast.Expr_Kind.Eq)
testing.expect_value(t, bit_or.kind, ast.Expr_Kind.Bit_Or)
testing.expect_value(t, bit_xor.kind, ast.Expr_Kind.Bit_Xor)
testing.expect_value(t, bit_and.kind, ast.Expr_Kind.Bit_And)
testing.expect_value(t, shift.kind, ast.Expr_Kind.Shift_Left)
testing.expect_value(t, addition.kind, ast.Expr_Kind.Add)
testing.expect_value(t, deref_and.kind, ast.Expr_Kind.Bit_And)
testing.expect_value(t, module.exprs[deref_and.left].kind, ast.Expr_Kind.Deref)
testing.expect_value(t, module.exprs[if_statement.expr].kind, ast.Expr_Kind.Bit_Or)
testing.expect_value(t, len(if_statement.captures), 1)
}
@(test)
bitwise_operations_lower_to_guarded_llvm_integer_instructions :: proc(t: ^testing.T) {
text := `ops func(a i32, b i32, count u8) i32 {
_ = ~a
_ = a & b
_ = a | b
_ = a xor b
_ = a << count
_ = a >> count
return a <<| count
}
uops func(a u32, count u8) u32 {
_ = a >> count
return a <<| count
}
main func() i32 {
_ = uops(4, 1)
return ops(1, 2, 1)
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
ir_module := lower.lower(&hir_module)
defer ir.destroy_module(&ir_module)
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
defer delete(llvm_text)
found: [7]bool
for function in ir_module.functions {
for instruction in function.instructions {
#partial switch instruction.op {
case .Bit_Not: found[0] = true
case .Bit_And: found[1] = true
case .Bit_Or: found[2] = true
case .Bit_Xor: found[3] = true
case .Shift_Left: found[4] = true
case .Shift_Right: found[5] = true
case .Shift_Left_Saturating: found[6] = true
}
}
}
testing.expect_value(t, len(diagnostics.items), 0)
for present in found {
testing.expect(t, present)
}
testing.expect(t, strings.contains(llvm_text, " = and i32 "))
testing.expect(t, strings.contains(llvm_text, " = or i32 "))
testing.expect(t, strings.contains(llvm_text, " = xor i32 "))
testing.expect(t, strings.contains(llvm_text, " = shl i32 "))
testing.expect(t, strings.contains(llvm_text, " = ashr i32 "))
testing.expect(t, strings.contains(llvm_text, " = lshr i32 "))
testing.expect(t, strings.contains(llvm_text, "@llvm.sshl.sat.i32"))
testing.expect(t, strings.contains(llvm_text, "@llvm.ushl.sat.i32"))
testing.expect(t, strings.contains(llvm_text, "shift_in_range"))
testing.expect(t, strings.contains(llvm_text, "shift_trap"))
}
@(test)
typed_bitwise_constants_fold_in_runtime_expressions :: proc(t: ^testing.T) {
text := `main func() void {
_ = ~u8(0)
_ = (u8(240) & u8(204)) xor u8(15)
_ = u8(129) << 1
_ = i8(-4) >> 1
_ = u8(1) <<| 8
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
testing.expect_value(t, len(diagnostics.items), 0)
for statement_id in hir_module.functions[0].body {
statement := hir_module.statements[statement_id]
testing.expect(t, statement.expr != hir.INVALID_EXPR)
testing.expect_value(t, hir_module.exprs[statement.expr].kind, hir.Expr_Kind.Integer)
}
}
@(test)
bitwise_checker_rejects_invalid_operands_and_known_overshifts :: proc(t: ^testing.T) {
text := `D :: distinct u8
E :: enum { one }
main func() void {
p *u8 = none
d D = D(1)
e E = .one
signed_count i8 = 1
_ = true & false
_ = 1.0 | 2.0
_ = ~p
_ = ~d
_ = ~e
_ = u8(1) & i8(1)
_ = u8(1) << signed_count
_ = u8(1) << 8
_ = p >> u8(1)
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
found_bitwise := false
found_shift := false
found_overshift := false
for diagnostic in diagnostics.items {
found_bitwise = found_bitwise || strings.contains(diagnostic.message, "bitwise") || strings.contains(diagnostic.message, "'~'")
found_shift = found_shift || strings.contains(diagnostic.message, "shift count") || strings.contains(diagnostic.message, "shifted value")
found_overshift = found_overshift || strings.contains(diagnostic.message, "exceeds u8 width")
}
testing.expect(t, found_bitwise)
testing.expect(t, found_shift)
testing.expect(t, found_overshift)
}
@(test)
runtime_ordinary_overshift_traps :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-bitwise-overshift"
main_path := "/tmp/brolang-test-bitwise-overshift/main.bro"
output := "/tmp/brolang-test-bitwise-overshift-output"
text := `shift func(value u8, count u8) u8 { return value << count }
main func() i32 {
_ = shift(1, 8)
return 0
}
`
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
defer _ = os.remove(output)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
state, stdout, stderr, err := os2.process_exec(os2.Process_Desc{command=[]string{output}}, context.allocator)
defer delete(stdout)
defer delete(stderr)
testing.expect(t, err == nil)
testing.expect(t, state.exit_code != 0)
testing.expect(t, strings.contains(string(stderr), "shift count exceeds integer width"))
}
@(test)
parser_accepts_grouped_params_and_multiline_statements :: proc(t: ^testing.T) {
text := `sum func(a,
@@ -5236,6 +5470,16 @@ yield_compiles_and_runs :: proc(t: ^testing.T) {
testing.expect_value(t, state.exit_code, 42)
}
@(test)
bitwise_example_compiles_and_runs :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-bitwise"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/bitwise", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 42)
}
@(test)
value_loop_label_does_not_shadow_own_yield_target :: proc(t: ^testing.T) {
text := `main func() i32 {