compound assignment
This commit is contained in:
+3
-3
@@ -15,8 +15,8 @@
|
|||||||
- exact-width `i8` through `i64`, `u8` through `u64`, `f32`, `f64`, `isize`, `usize`, `bool`, `void`, and inferred integer-constrained `int`
|
- exact-width `i8` through `i64`, `u8` through `u64`, `f32`, `f64`, `isize`, `usize`, `bool`, `void`, and inferred integer-constrained `int`
|
||||||
- target-dependent atomic C primitives from `c_char` through `c_longdouble`
|
- target-dependent atomic C primitives from `c_char` through `c_longdouble`
|
||||||
- C primitives remain semantically distinct from exact-width Brolang primitives until target lowering
|
- C primitives remain semantically distinct from exact-width Brolang primitives until target lowering
|
||||||
- contextual integer and character literals and constant folding of addition and negation trees
|
- contextual integer and character literals and constant folding of arithmetic and negation trees
|
||||||
- strict numeric conversions, checked integer addition, and unary negation
|
- strict numeric conversions, binary `+ - * /` with checked integer overflow and divide-by-zero traps (floats follow IEEE), and unary negation
|
||||||
- boolean literals, comparisons, unary `!`, and short-circuiting `and` / `or`
|
- boolean literals, comparisons, unary `!`, and short-circuiting `and` / `or`
|
||||||
- arrays `[N]T`, sentinel arrays `[N;S]T`, single-item pointers `@T`, many-item pointers `*T`, sentinel many-item pointers `[*;S]T`, pointer offsets, slices, and explicit slicing
|
- arrays `[N]T`, sentinel arrays `[N;S]T`, single-item pointers `@T`, many-item pointers `*T`, sentinel many-item pointers `[*;S]T`, pointer offsets, slices, and explicit slicing
|
||||||
- immutable UTF-8 string literals typed as pointers to static sentinel arrays: `@[N;0]u8`
|
- immutable UTF-8 string literals typed as pointers to static sentinel arrays: `@[N;0]u8`
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
- source-order native structs, defined or opaque `c_struct`, and keyed record literals
|
- source-order native structs, defined or opaque `c_struct`, and keyed record literals
|
||||||
- complete plain imported C structs and unions as runtime values; incomplete or unsupported-layout records remain pointer-only
|
- complete plain imported C structs and unions as runtime values; incomplete or unsupported-layout records remain pointer-only
|
||||||
- C function pointer types as pointer-sized runtime values, including manual `*c_func(...) T` spelling and nullable imported callback typedefs
|
- C function pointer types as pointer-sized runtime values, including manual `*c_func(...) T` spelling and nullable imported callback typedefs
|
||||||
- postfix pointer dereference, general writable locations, function calls, assignments, and returns
|
- postfix pointer dereference, general writable locations, function calls, assignments, compound assignment (`+= -= *= /=`) with single-evaluation lvalues, and returns
|
||||||
- first-class exclusive and inclusive integer ranges: `start..end` and `start..=end`
|
- first-class exclusive and inclusive integer ranges: `start..end` and `start..=end`
|
||||||
- boolean `if` statements and `while` loops with optional post-iteration assignment/expression clauses
|
- boolean `if` statements and `while` loops with optional post-iteration assignment/expression clauses
|
||||||
- `for` loops over ranges, arrays, slices, and pointers-to-arrays, with copy, pointer, and optional `usize` index captures
|
- `for` loops over ranges, arrays, slices, and pointers-to-arrays, with copy, pointer, and optional `usize` index captures
|
||||||
|
|||||||
@@ -100,7 +100,7 @@
|
|||||||
- `while condition : i = i + 1 { ... }` - execute the update after each completed iteration
|
- `while condition : i = i + 1 { ... }` - execute the update after each completed iteration
|
||||||
- the condition and update may be parenthesized independently for visual clarity
|
- the condition and update may be parenthesized independently for visual clarity
|
||||||
- update targets must already be declared and mutable; loops do not introduce implicit induction variables
|
- update targets must already be declared and mutable; loops do not introduce implicit induction variables
|
||||||
- compound assignment (`+=`) remains deferred
|
- update clauses support ordinary and compound assignment
|
||||||
- ranges (implemented; see section below)
|
- ranges (implemented; see section below)
|
||||||
- for loops (implemented; operates on ranges, arrays, slices, and pointers-to-arrays). examples:
|
- for loops (implemented; operates on ranges, arrays, slices, and pointers-to-arrays). examples:
|
||||||
- `for items |item| { ... }` - capture just the `item` value in the array/slice (uses copy semantics, i.e. gets a `T`)
|
- `for items |item| { ... }` - capture just the `item` value in the array/slice (uses copy semantics, i.e. gets a `T`)
|
||||||
@@ -112,7 +112,18 @@
|
|||||||
- `for 0..(len) |i| { ... }` or equivalently `for 0..=(len - 1) |i| { ... }` - calculating range bounds, expressions must be parenthesized
|
- `for 0..(len) |i| { ... }` or equivalently `for 0..=(len - 1) |i| { ... }` - calculating range bounds, expressions must be parenthesized
|
||||||
- for all conditionals/guards, parentheses are optional but allowed for visual clarity
|
- for all conditionals/guards, parentheses are optional but allowed for visual clarity
|
||||||
|
|
||||||
6. compound assignment: `+=`, `-=`, `*=`, `/=`
|
6. compound assignment: `+=`, `-=`, `*=`, `/=` (implemented)
|
||||||
|
- added the binary arithmetic operators `-`, `*`, `/` (previously only `+` existed); `*`/`/`
|
||||||
|
bind tighter than `+`/`-`, and prefix `-` (negation) is unchanged
|
||||||
|
- compound assignments preserve the target, operator, and right-hand side explicitly through
|
||||||
|
parsing and checking; lowering computes the target address once, then loads, applies the
|
||||||
|
operation, and stores through that address
|
||||||
|
- side-effecting index, field-base, and dereference expressions are evaluated once in
|
||||||
|
left-to-right order
|
||||||
|
- integer arithmetic traps on overflow (`Sub_Checked`/`Mul_Checked` via the LLVM
|
||||||
|
`.with.overflow` intrinsics) and integer `/` traps on divide-by-zero and `INT_MIN / -1`;
|
||||||
|
floats follow IEEE (`fadd`/`fsub`/`fmul`/`fdiv`, no trap)
|
||||||
|
- constant folding (global initializers) covers `-`, `*`, `/` alongside `+`
|
||||||
|
|
||||||
7. enums (native and c interop) (see below)
|
7. enums (native and c interop) (see below)
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,9 @@ Expr_Kind :: enum u8 {
|
|||||||
Negate,
|
Negate,
|
||||||
Not,
|
Not,
|
||||||
Add,
|
Add,
|
||||||
|
Sub,
|
||||||
|
Mul,
|
||||||
|
Div,
|
||||||
Eq,
|
Eq,
|
||||||
Ne,
|
Ne,
|
||||||
Lt,
|
Lt,
|
||||||
@@ -126,6 +129,14 @@ Stmt_Kind :: enum u8 {
|
|||||||
For,
|
For,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Assignment_Op :: enum u8 {
|
||||||
|
Set,
|
||||||
|
Add,
|
||||||
|
Sub,
|
||||||
|
Mul,
|
||||||
|
Div,
|
||||||
|
}
|
||||||
|
|
||||||
Stmt :: struct {
|
Stmt :: struct {
|
||||||
kind: Stmt_Kind,
|
kind: Stmt_Kind,
|
||||||
span: source.Span,
|
span: source.Span,
|
||||||
@@ -134,6 +145,10 @@ Stmt :: struct {
|
|||||||
type: Type_Syntax,
|
type: Type_Syntax,
|
||||||
immutable: bool,
|
immutable: bool,
|
||||||
pointer_capture: bool,
|
pointer_capture: bool,
|
||||||
|
// Assignments store the lvalue in `target`, the right-hand side in `expr`,
|
||||||
|
// and the source operator in `assignment_op`. `Set` is ordinary `=`;
|
||||||
|
// the arithmetic variants are `+=`, `-=`, `*=`, and `/=`.
|
||||||
|
assignment_op: Assignment_Op,
|
||||||
target: Expr_Id,
|
target: Expr_Id,
|
||||||
expr: Expr_Id,
|
expr: Expr_Id,
|
||||||
// `If` statements use `expr` as the condition, `captures` as optional
|
// `If` statements use `expr` as the condition, `captures` as optional
|
||||||
|
|||||||
+130
-35
@@ -65,6 +65,7 @@ Constant_Kind :: enum {
|
|||||||
Not_Constant,
|
Not_Constant,
|
||||||
Value,
|
Value,
|
||||||
Overflow,
|
Overflow,
|
||||||
|
Div_By_Zero,
|
||||||
}
|
}
|
||||||
|
|
||||||
Constant :: struct {
|
Constant :: struct {
|
||||||
@@ -145,7 +146,8 @@ eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
expr := checker.ast_module.exprs[frame.expr]
|
expr := checker.ast_module.exprs[frame.expr]
|
||||||
if expr.kind != .Add && expr.kind != .Negate {
|
if expr.kind != .Add && expr.kind != .Sub && expr.kind != .Mul &&
|
||||||
|
expr.kind != .Div && expr.kind != .Negate {
|
||||||
result := Constant{kind = .Not_Constant}
|
result := Constant{kind = .Not_Constant}
|
||||||
if expr.kind == .Integer {
|
if expr.kind == .Integer {
|
||||||
result = Constant{kind = .Value, value = i128(expr.integer)}
|
result = Constant{kind = .Value, value = i128(expr.integer)}
|
||||||
@@ -168,7 +170,9 @@ eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant {
|
|||||||
operand = checker.constants[expr.left]
|
operand = checker.constants[expr.left]
|
||||||
}
|
}
|
||||||
result := Constant{kind = .Not_Constant}
|
result := Constant{kind = .Not_Constant}
|
||||||
if operand.kind == .Overflow {
|
if operand.kind == .Div_By_Zero {
|
||||||
|
result = Constant{kind = .Div_By_Zero}
|
||||||
|
} else if operand.kind == .Overflow {
|
||||||
result = Constant{kind = .Overflow}
|
result = Constant{kind = .Overflow}
|
||||||
} else if operand.kind == .Value {
|
} else if operand.kind == .Value {
|
||||||
value, overflow := intrinsics.overflow_sub(i128(0), operand.value)
|
value, overflow := intrinsics.overflow_sub(i128(0), operand.value)
|
||||||
@@ -195,11 +199,30 @@ eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant {
|
|||||||
right = checker.constants[expr.right]
|
right = checker.constants[expr.right]
|
||||||
}
|
}
|
||||||
result := Constant{kind = .Not_Constant}
|
result := Constant{kind = .Not_Constant}
|
||||||
if left.kind == .Overflow || right.kind == .Overflow {
|
if left.kind == .Div_By_Zero || right.kind == .Div_By_Zero {
|
||||||
|
result = Constant{kind = .Div_By_Zero}
|
||||||
|
} else if left.kind == .Overflow || right.kind == .Overflow {
|
||||||
result = Constant{kind = .Overflow}
|
result = Constant{kind = .Overflow}
|
||||||
} else if left.kind == .Value && right.kind == .Value {
|
} else if left.kind == .Value && right.kind == .Value {
|
||||||
value, overflow := intrinsics.overflow_add(left.value, right.value)
|
value: i128
|
||||||
result = Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value}
|
overflow: bool
|
||||||
|
div_by_zero: bool
|
||||||
|
#partial switch expr.kind {
|
||||||
|
case .Sub: value, overflow = intrinsics.overflow_sub(left.value, right.value)
|
||||||
|
case .Mul: value, overflow = intrinsics.overflow_mul(left.value, right.value)
|
||||||
|
case .Div:
|
||||||
|
if right.value == 0 {
|
||||||
|
div_by_zero = true
|
||||||
|
} else {
|
||||||
|
value = left.value / right.value
|
||||||
|
}
|
||||||
|
case: value, overflow = intrinsics.overflow_add(left.value, right.value)
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case div_by_zero: result = Constant{kind = .Div_By_Zero}
|
||||||
|
case overflow: result = Constant{kind = .Overflow}
|
||||||
|
case: result = Constant{kind = .Value, value = value}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
checker.constants[frame.expr] = result
|
checker.constants[frame.expr] = result
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
@@ -611,7 +634,7 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
|
|||||||
}
|
}
|
||||||
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Keyed:
|
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Keyed:
|
||||||
append(&stack, expr.left)
|
append(&stack, expr.left)
|
||||||
case .Add, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
||||||
append(&stack, expr.left, expr.right)
|
append(&stack, expr.left, expr.right)
|
||||||
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Name:
|
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Name:
|
||||||
}
|
}
|
||||||
@@ -1163,7 +1186,8 @@ infer_expr :: proc(
|
|||||||
expr := checker.ast_module.exprs[frame.expr]
|
expr := checker.ast_module.exprs[frame.expr]
|
||||||
if frame.stage == 0 {
|
if frame.stage == 0 {
|
||||||
constant := eval_constant(checker, frame.expr)
|
constant := eval_constant(checker, frame.expr)
|
||||||
if constant.kind == .Overflow || (constant.kind == .Value && !fits_i64(constant.value)) {
|
if constant.kind == .Overflow || constant.kind == .Div_By_Zero ||
|
||||||
|
(constant.kind == .Value && !fits_i64(constant.value)) {
|
||||||
last = types.I64
|
last = types.I64
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
continue
|
continue
|
||||||
@@ -1244,7 +1268,7 @@ infer_expr :: proc(
|
|||||||
case .Negate:
|
case .Negate:
|
||||||
stack[frame_index].stage = 5
|
stack[frame_index].stage = 5
|
||||||
append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION})
|
append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION})
|
||||||
case .Add:
|
case .Add, .Sub, .Mul, .Div:
|
||||||
stack[frame_index].stage = 1
|
stack[frame_index].stage = 1
|
||||||
append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION})
|
append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION})
|
||||||
case .Call:
|
case .Call:
|
||||||
@@ -1338,7 +1362,7 @@ infer_expr :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if frame.stage == 2 {
|
if frame.stage == 2 {
|
||||||
if types.is_many_pointer(frame.left, &checker.module.types) && types.is_concrete_integer(last) {
|
if expr.kind == .Add && types.is_many_pointer(frame.left, &checker.module.types) && types.is_concrete_integer(last) {
|
||||||
last = frame.left
|
last = frame.left
|
||||||
} else {
|
} else {
|
||||||
last = types.widest(frame.left, last)
|
last = types.widest(frame.left, last)
|
||||||
@@ -1847,6 +1871,10 @@ build_constant_expr :: proc(
|
|||||||
if types.is_concrete_integer(expected) {
|
if types.is_concrete_integer(expected) {
|
||||||
recovery_type = expected
|
recovery_type = expected
|
||||||
}
|
}
|
||||||
|
if constant.kind == .Div_By_Zero {
|
||||||
|
id := source.add(checker.diagnostics, expr.span, "division by zero in constant expression")
|
||||||
|
return invalid_hir_expr(checker, expr.span, id, recovery_type)
|
||||||
|
}
|
||||||
if constant.kind == .Overflow ||
|
if constant.kind == .Overflow ||
|
||||||
(!types.is_concrete_integer(expected) && !fits_i64(constant.value)) {
|
(!types.is_concrete_integer(expected) && !fits_i64(constant.value)) {
|
||||||
id := source.add(
|
id := source.add(
|
||||||
@@ -2485,6 +2513,45 @@ build_compound_expr :: proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// build_binary_arith constructs the HIR node for `left op right`, where `op` is
|
||||||
|
// an arithmetic AST kind (`Add`/`Sub`/`Mul`/`Div`). It models many-pointer `+`
|
||||||
|
// as `Pointer_Add`, coerces both operands to their common type, and emits the
|
||||||
|
// "arithmetic requires compatible numeric operands" diagnostic when they have no
|
||||||
|
// shared numeric type.
|
||||||
|
build_binary_arith :: proc(
|
||||||
|
checker: ^Checker,
|
||||||
|
op: ast.Expr_Kind,
|
||||||
|
left, right: hir.Expr_Id,
|
||||||
|
span: source.Span,
|
||||||
|
) -> hir.Expr_Id {
|
||||||
|
// Pointer arithmetic is only defined for `+` (many-pointer + usize).
|
||||||
|
if op == .Add &&
|
||||||
|
types.is_many_pointer(checker.module.exprs[left].type, &checker.module.types) &&
|
||||||
|
types.equal(checker.module.exprs[right].type, types.USIZE) {
|
||||||
|
return add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Pointer_Add, span=span, type=checker.module.exprs[left].type,
|
||||||
|
left=left, right=right, target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
result := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
|
||||||
|
if !types.is_concrete_scalar(result) {
|
||||||
|
id := source.add(checker.diagnostics, span, "arithmetic requires compatible numeric operands")
|
||||||
|
return invalid_hir_expr(checker, span, id)
|
||||||
|
}
|
||||||
|
result_kind := hir.Expr_Kind.Add
|
||||||
|
#partial switch op {
|
||||||
|
case .Sub: result_kind = .Sub
|
||||||
|
case .Mul: result_kind = .Mul
|
||||||
|
case .Div: result_kind = .Div
|
||||||
|
}
|
||||||
|
coerced_left := coerce_expr(checker, left, result, checker.module.exprs[left].span)
|
||||||
|
coerced_right := coerce_expr(checker, right, result, checker.module.exprs[right].span)
|
||||||
|
return add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=result_kind, span=span, type=result, left=coerced_left, right=coerced_right,
|
||||||
|
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
build_expr :: proc(
|
build_expr :: proc(
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
expr_id: ast.Expr_Id,
|
expr_id: ast.Expr_Id,
|
||||||
@@ -2520,7 +2587,7 @@ build_expr :: proc(
|
|||||||
expr := checker.ast_module.exprs[frame.expr]
|
expr := checker.ast_module.exprs[frame.expr]
|
||||||
if frame.stage == 0 {
|
if frame.stage == 0 {
|
||||||
constant := eval_constant(checker, frame.expr)
|
constant := eval_constant(checker, frame.expr)
|
||||||
if constant.kind == .Value || constant.kind == .Overflow {
|
if constant.kind == .Value || constant.kind == .Overflow || constant.kind == .Div_By_Zero {
|
||||||
last = build_constant_expr(checker, expr, constant, frame.expected)
|
last = build_constant_expr(checker, expr, constant, frame.expected)
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
continue
|
continue
|
||||||
@@ -2609,7 +2676,7 @@ build_expr :: proc(
|
|||||||
case .Negate:
|
case .Negate:
|
||||||
stack[frame_index].stage = 5
|
stack[frame_index].stage = 5
|
||||||
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION})
|
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION})
|
||||||
case .Add:
|
case .Add, .Sub, .Mul, .Div:
|
||||||
stack[frame_index].stage = 1
|
stack[frame_index].stage = 1
|
||||||
// Preserve assignment/return context for literal operands, e.g.
|
// Preserve assignment/return context for literal operands, e.g.
|
||||||
// assigning `i + 1` back into a `u32` local.
|
// assigning `i + 1` back into a `u32` local.
|
||||||
@@ -2768,29 +2835,7 @@ build_expr :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if frame.stage == 2 {
|
if frame.stage == 2 {
|
||||||
left := frame.left
|
last = build_binary_arith(checker, expr.kind, frame.left, last, expr.span)
|
||||||
right := last
|
|
||||||
if types.is_many_pointer(checker.module.exprs[left].type, &checker.module.types) &&
|
|
||||||
types.equal(checker.module.exprs[right].type, types.USIZE) {
|
|
||||||
last = add_hir_expr(checker, hir.Expr{
|
|
||||||
kind=.Pointer_Add, span=expr.span, type=checker.module.exprs[left].type,
|
|
||||||
left=left, right=right, target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
||||||
})
|
|
||||||
_ = pop(&stack)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
result := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
|
|
||||||
if !types.is_concrete_scalar(result) {
|
|
||||||
id := source.add(checker.diagnostics, expr.span, "addition requires compatible numeric operands")
|
|
||||||
last = invalid_hir_expr(checker, expr.span, id)
|
|
||||||
} else {
|
|
||||||
left = coerce_expr(checker, left, result, checker.module.exprs[left].span)
|
|
||||||
right = coerce_expr(checker, right, result, checker.module.exprs[right].span)
|
|
||||||
last = add_hir_expr(checker, hir.Expr{
|
|
||||||
kind=.Add, span=expr.span, type=result, left=left, right=right,
|
|
||||||
target = hir.INVALID_REF, diagnostic = source.INVALID_DIAGNOSTIC,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -3061,14 +3106,64 @@ build_block :: proc(
|
|||||||
ctx.problematic^ = true
|
ctx.problematic^ = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
value := build_expr(
|
value: hir.Expr_Id
|
||||||
|
assignment_op := hir.Assignment_Op.Set
|
||||||
|
if statement.assignment_op != .Set {
|
||||||
|
rhs_expected := target_type
|
||||||
|
if types.is_many_pointer(target_type, &checker.module.types) {
|
||||||
|
rhs_expected = types.USIZE if statement.assignment_op == .Add else types.INVALID
|
||||||
|
}
|
||||||
|
value = build_expr(
|
||||||
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
|
rhs_expected, ctx.pkg, ctx.file,
|
||||||
|
)
|
||||||
|
if types.is_many_pointer(target_type, &checker.module.types) {
|
||||||
|
assignment_op = .Pointer_Add
|
||||||
|
if statement.assignment_op != .Add {
|
||||||
|
id := source.add(
|
||||||
|
checker.diagnostics,
|
||||||
|
statement.span,
|
||||||
|
"many-item pointers only support '+=' compound assignment",
|
||||||
|
)
|
||||||
|
value = invalid_hir_expr(checker, statement.span, id, types.USIZE)
|
||||||
|
} else {
|
||||||
|
value = coerce_expr(checker, value, types.USIZE, statement.span)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
#partial switch statement.assignment_op {
|
||||||
|
case .Add: assignment_op = .Add
|
||||||
|
case .Sub: assignment_op = .Sub
|
||||||
|
case .Mul: assignment_op = .Mul
|
||||||
|
case .Div: assignment_op = .Div
|
||||||
|
}
|
||||||
|
rhs_type := checker.module.exprs[value].type
|
||||||
|
result_type := types.widest(target_type, rhs_type)
|
||||||
|
if !types.is_concrete_scalar(result_type) ||
|
||||||
|
types.is_bool(result_type) {
|
||||||
|
id := source.add(
|
||||||
|
checker.diagnostics,
|
||||||
|
statement.span,
|
||||||
|
"arithmetic requires compatible numeric operands",
|
||||||
|
)
|
||||||
|
value = invalid_hir_expr(checker, statement.span, id, target_type)
|
||||||
|
} else {
|
||||||
|
// Compound assignment stores back into the original
|
||||||
|
// target type, so only an equal or widening RHS
|
||||||
|
// conversion is permitted.
|
||||||
|
value = coerce_expr(checker, value, target_type, statement.span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = build_expr(
|
||||||
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
target_type, ctx.pkg, ctx.file,
|
target_type, ctx.pkg, ctx.file,
|
||||||
)
|
)
|
||||||
value = coerce_expr(checker, value, target_type, statement.span)
|
value = coerce_expr(checker, value, target_type, statement.span)
|
||||||
|
}
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
append(&checker.module.statements, hir.Stmt{
|
append(&checker.module.statements, hir.Stmt{
|
||||||
kind=.Assignment, span=statement.span, local=hir.INVALID_LOCAL,
|
kind=.Assignment, span=statement.span, local=hir.INVALID_LOCAL,
|
||||||
|
assignment_op=assignment_op,
|
||||||
target=target_expr, expr=value, diagnostic=source.INVALID_DIAGNOSTIC,
|
target=target_expr, expr=value, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
||||||
|
|||||||
@@ -102,6 +102,9 @@ Expr_Kind :: enum u8 {
|
|||||||
Negate,
|
Negate,
|
||||||
Not,
|
Not,
|
||||||
Add,
|
Add,
|
||||||
|
Sub,
|
||||||
|
Mul,
|
||||||
|
Div,
|
||||||
Pointer_Add,
|
Pointer_Add,
|
||||||
Eq,
|
Eq,
|
||||||
Ne,
|
Ne,
|
||||||
@@ -151,6 +154,15 @@ Stmt_Kind :: enum u8 {
|
|||||||
For,
|
For,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Assignment_Op :: enum u8 {
|
||||||
|
Set,
|
||||||
|
Add,
|
||||||
|
Sub,
|
||||||
|
Mul,
|
||||||
|
Div,
|
||||||
|
Pointer_Add,
|
||||||
|
}
|
||||||
|
|
||||||
Stmt :: struct {
|
Stmt :: struct {
|
||||||
kind: Stmt_Kind,
|
kind: Stmt_Kind,
|
||||||
span: source.Span,
|
span: source.Span,
|
||||||
@@ -160,6 +172,10 @@ Stmt :: struct {
|
|||||||
expr: Expr_Id,
|
expr: Expr_Id,
|
||||||
iterator_type: types.Type,
|
iterator_type: types.Type,
|
||||||
pointer_capture: bool,
|
pointer_capture: bool,
|
||||||
|
// Assignments carry their operation explicitly. Arithmetic operations lower
|
||||||
|
// by computing the target address once, loading its current value, applying
|
||||||
|
// the operation to `expr`, and storing through the original address.
|
||||||
|
assignment_op: Assignment_Op,
|
||||||
// Boolean `If` statements use `expr` as the condition. Conditional unwraps
|
// Boolean `If` statements use `expr` as the condition. Conditional unwraps
|
||||||
// use `unwraps` for the ordered optional expressions and capture locals, and
|
// use `unwraps` for the ordered optional expressions and capture locals, and
|
||||||
// `guard` for the optional boolean checked after every unwrap succeeds.
|
// `guard` for the optional boolean checked after every unwrap succeeds.
|
||||||
|
|||||||
@@ -98,6 +98,9 @@ Opcode :: enum u8 {
|
|||||||
Decay_Array_Pointer,
|
Decay_Array_Pointer,
|
||||||
Neg_Checked,
|
Neg_Checked,
|
||||||
Add_Checked,
|
Add_Checked,
|
||||||
|
Sub_Checked,
|
||||||
|
Mul_Checked,
|
||||||
|
Div_Checked,
|
||||||
Pointer_Add,
|
Pointer_Add,
|
||||||
Not,
|
Not,
|
||||||
Compare,
|
Compare,
|
||||||
|
|||||||
@@ -150,11 +150,23 @@ lex :: proc(
|
|||||||
append_token(&stream, source_file, .Greater, start, cursor)
|
append_token(&stream, source_file, .Greater, start, cursor)
|
||||||
}
|
}
|
||||||
case '+':
|
case '+':
|
||||||
append_token(&stream, source_file, .Plus, cursor, cursor+1)
|
start := cursor
|
||||||
cursor += 1
|
cursor += 1
|
||||||
|
if cursor < len(bytes) && bytes[cursor] == '=' {
|
||||||
|
cursor += 1
|
||||||
|
append_token(&stream, source_file, .Plus_Equal, start, cursor)
|
||||||
|
} else {
|
||||||
|
append_token(&stream, source_file, .Plus, start, cursor)
|
||||||
|
}
|
||||||
case '-':
|
case '-':
|
||||||
append_token(&stream, source_file, .Minus, cursor, cursor+1)
|
start := cursor
|
||||||
cursor += 1
|
cursor += 1
|
||||||
|
if cursor < len(bytes) && bytes[cursor] == '=' {
|
||||||
|
cursor += 1
|
||||||
|
append_token(&stream, source_file, .Minus_Equal, start, cursor)
|
||||||
|
} else {
|
||||||
|
append_token(&stream, source_file, .Minus, start, cursor)
|
||||||
|
}
|
||||||
case '.':
|
case '.':
|
||||||
start := cursor
|
start := cursor
|
||||||
cursor += 1
|
cursor += 1
|
||||||
@@ -176,8 +188,23 @@ lex :: proc(
|
|||||||
append_token(&stream, source_file, .At, cursor, cursor+1)
|
append_token(&stream, source_file, .At, cursor, cursor+1)
|
||||||
cursor += 1
|
cursor += 1
|
||||||
case '*':
|
case '*':
|
||||||
append_token(&stream, source_file, .Star, cursor, cursor+1)
|
start := cursor
|
||||||
cursor += 1
|
cursor += 1
|
||||||
|
if cursor < len(bytes) && bytes[cursor] == '=' {
|
||||||
|
cursor += 1
|
||||||
|
append_token(&stream, source_file, .Star_Equal, start, cursor)
|
||||||
|
} else {
|
||||||
|
append_token(&stream, source_file, .Star, start, cursor)
|
||||||
|
}
|
||||||
|
case '/':
|
||||||
|
start := cursor
|
||||||
|
cursor += 1
|
||||||
|
if cursor < len(bytes) && bytes[cursor] == '=' {
|
||||||
|
cursor += 1
|
||||||
|
append_token(&stream, source_file, .Slash_Equal, start, cursor)
|
||||||
|
} else {
|
||||||
|
append_token(&stream, source_file, .Slash, start, cursor)
|
||||||
|
}
|
||||||
case '&':
|
case '&':
|
||||||
append_token(&stream, source_file, .Ampersand, cursor, cursor+1)
|
append_token(&stream, source_file, .Ampersand, cursor, cursor+1)
|
||||||
cursor += 1
|
cursor += 1
|
||||||
|
|||||||
+138
-42
@@ -240,7 +240,7 @@ valid_value :: proc(
|
|||||||
.Extract, .Select, .Unwrap,
|
.Extract, .Select, .Unwrap,
|
||||||
.Optional_Is_Some, .Optional_Value, .Orelse,
|
.Optional_Is_Some, .Optional_Value, .Orelse,
|
||||||
.Widen, .C_Vararg_Promote, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
|
.Widen, .C_Vararg_Promote, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
|
||||||
.Neg_Checked, .Add_Checked, .Pointer_Add, .Not, .Compare, .Call:
|
.Neg_Checked, .Add_Checked, .Sub_Checked, .Mul_Checked, .Div_Checked, .Pointer_Add, .Not, .Compare, .Call:
|
||||||
return true
|
return true
|
||||||
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
|
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
|
||||||
.Store, .Trap, .Label, .Br, .Cond_Br, .Return, .Return_Void:
|
.Store, .Trap, .Label, .Br, .Cond_Br, .Return, .Return_Void:
|
||||||
@@ -516,6 +516,117 @@ emit_entry_allocas :: proc(emitter: ^Emitter, instructions: []ir.Instruction) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// emit_checked_arithmetic emits a trapping integer add/sub/mul through the LLVM
|
||||||
|
// `.with.overflow` intrinsics, or a plain floating-point operation. `mnemonic`
|
||||||
|
// is the integer intrinsic stem ("add"/"sub"/"mul"); the signed/unsigned prefix
|
||||||
|
// is chosen from the operand type. `float_op` is the matching float instruction.
|
||||||
|
emit_checked_arithmetic :: proc(
|
||||||
|
emitter: ^Emitter,
|
||||||
|
instructions: []ir.Instruction,
|
||||||
|
instruction_index: int,
|
||||||
|
instruction: ir.Instruction,
|
||||||
|
mnemonic: string,
|
||||||
|
float_op: string,
|
||||||
|
overflow_message: string,
|
||||||
|
) {
|
||||||
|
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||||
|
if types.is_float(instruction.type, emitter.module.target) {
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, float_op, type_name)
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
|
||||||
|
strings.write_string(&emitter.builder, ", ")
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
|
||||||
|
strings.write_string(&emitter.builder, "\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
prefix := "u" if types.is_unsigned(instruction.type, emitter.module.target) else "s"
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_index)
|
||||||
|
strings.write_string(&emitter.builder, "{ ")
|
||||||
|
fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.%s%s.with.overflow.%s(%s ", type_name, prefix, mnemonic, type_name, type_name)
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
|
||||||
|
fmt.sbprintf(&emitter.builder, ", %s ", type_name)
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
|
||||||
|
fmt.sbprintf(&emitter.builder, ")\n")
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue ", instruction_index)
|
||||||
|
strings.write_string(&emitter.builder, "{ ")
|
||||||
|
fmt.sbprintf(&emitter.builder, "%s, i1 } %%pair%d, 0\n", type_name, instruction_index)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%overflow%d = extractvalue ", instruction_index)
|
||||||
|
strings.write_string(&emitter.builder, "{ ")
|
||||||
|
fmt.sbprintf(&emitter.builder, "%s, i1 } %%pair%d, 1\n", type_name, instruction_index)
|
||||||
|
fmt.sbprintf(
|
||||||
|
&emitter.builder,
|
||||||
|
" br i1 %%overflow%d, label %%overflow_trap%d, label %%overflow_continue%d\n",
|
||||||
|
instruction_index,
|
||||||
|
instruction_index,
|
||||||
|
instruction_index,
|
||||||
|
)
|
||||||
|
fmt.sbprintf(&emitter.builder, "overflow_trap%d:\n", instruction_index)
|
||||||
|
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, overflow_message)
|
||||||
|
emit_trap_call(emitter, message)
|
||||||
|
fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// emit_checked_division emits a trapping integer division guarding divide-by-zero
|
||||||
|
// and signed `INT_MIN / -1` overflow, or a plain floating-point division.
|
||||||
|
emit_checked_division :: proc(
|
||||||
|
emitter: ^Emitter,
|
||||||
|
instructions: []ir.Instruction,
|
||||||
|
instruction_index: int,
|
||||||
|
instruction: ir.Instruction,
|
||||||
|
) {
|
||||||
|
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||||
|
if types.is_float(instruction.type, emitter.module.target) {
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = fdiv %s ", instruction_index, type_name)
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
|
||||||
|
strings.write_string(&emitter.builder, ", ")
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
|
||||||
|
strings.write_string(&emitter.builder, "\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
signed := !types.is_unsigned(instruction.type, emitter.module.target)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%divzero%d = icmp eq %s ", instruction_index, type_name)
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
|
||||||
|
strings.write_string(&emitter.builder, ", 0\n")
|
||||||
|
fmt.sbprintf(
|
||||||
|
&emitter.builder,
|
||||||
|
" br i1 %%divzero%d, label %%divzero_trap%d, label %%divzero_ok%d\n",
|
||||||
|
instruction_index,
|
||||||
|
instruction_index,
|
||||||
|
instruction_index,
|
||||||
|
)
|
||||||
|
fmt.sbprintf(&emitter.builder, "divzero_trap%d:\n", instruction_index)
|
||||||
|
zero_message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "integer division by zero")
|
||||||
|
emit_trap_call(emitter, zero_message)
|
||||||
|
fmt.sbprintf(&emitter.builder, " unreachable\ndivzero_ok%d:\n", instruction_index)
|
||||||
|
if signed {
|
||||||
|
min_value := -(i128(1) << u32(types.bits(instruction.type, emitter.module.target) - 1))
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%divminlo%d = icmp eq %s ", instruction_index, type_name)
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
|
||||||
|
fmt.sbprintf(&emitter.builder, ", %d\n", min_value)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%divminhi%d = icmp eq %s ", instruction_index, type_name)
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
|
||||||
|
strings.write_string(&emitter.builder, ", -1\n")
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%divovf%d = and i1 %%divminlo%d, %%divminhi%d\n", instruction_index, instruction_index, instruction_index)
|
||||||
|
fmt.sbprintf(
|
||||||
|
&emitter.builder,
|
||||||
|
" br i1 %%divovf%d, label %%divovf_trap%d, label %%divovf_ok%d\n",
|
||||||
|
instruction_index,
|
||||||
|
instruction_index,
|
||||||
|
instruction_index,
|
||||||
|
)
|
||||||
|
fmt.sbprintf(&emitter.builder, "divovf_trap%d:\n", instruction_index)
|
||||||
|
ovf_message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "signed integer division overflow")
|
||||||
|
emit_trap_call(emitter, ovf_message)
|
||||||
|
fmt.sbprintf(&emitter.builder, " unreachable\ndivovf_ok%d:\n", instruction_index)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = sdiv %s ", instruction_index, type_name)
|
||||||
|
} else {
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = udiv %s ", instruction_index, type_name)
|
||||||
|
}
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
|
||||||
|
strings.write_string(&emitter.builder, ", ")
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
|
||||||
|
strings.write_string(&emitter.builder, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
emit_instruction_stream :: proc(
|
emit_instruction_stream :: proc(
|
||||||
emitter: ^Emitter,
|
emitter: ^Emitter,
|
||||||
instructions: []ir.Instruction,
|
instructions: []ir.Instruction,
|
||||||
@@ -1166,40 +1277,28 @@ emit_instruction_stream :: proc(
|
|||||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid addition operand")
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid addition operand")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
type_name := llvm_type(instruction.type, &emitter.module.types)
|
emit_checked_arithmetic(emitter, instructions, instruction_index, instruction, "add", "fadd", "integer addition overflow")
|
||||||
if types.is_float(instruction.type, emitter.module.target) {
|
case .Sub_Checked:
|
||||||
fmt.sbprintf(&emitter.builder, " %%v%d = fadd %s ", instruction_index, type_name)
|
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
|
||||||
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
|
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) {
|
||||||
strings.write_string(&emitter.builder, ", ")
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid subtraction operand")
|
||||||
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
|
|
||||||
strings.write_string(&emitter.builder, "\n")
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
intrinsic := "uadd" if types.is_unsigned(instruction.type, emitter.module.target) else "sadd"
|
emit_checked_arithmetic(emitter, instructions, instruction_index, instruction, "sub", "fsub", "integer subtraction overflow")
|
||||||
fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_index)
|
case .Mul_Checked:
|
||||||
strings.write_string(&emitter.builder, "{ ")
|
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
|
||||||
fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.%s.with.overflow.%s(%s ", type_name, intrinsic, type_name, type_name)
|
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) {
|
||||||
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid multiplication operand")
|
||||||
fmt.sbprintf(&emitter.builder, ", %s ", type_name)
|
continue
|
||||||
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
|
}
|
||||||
fmt.sbprintf(&emitter.builder, ")\n")
|
emit_checked_arithmetic(emitter, instructions, instruction_index, instruction, "mul", "fmul", "integer multiplication overflow")
|
||||||
fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue ", instruction_index)
|
case .Div_Checked:
|
||||||
strings.write_string(&emitter.builder, "{ ")
|
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
|
||||||
fmt.sbprintf(&emitter.builder, "%s, i1 } %%pair%d, 0\n", type_name, instruction_index)
|
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) {
|
||||||
fmt.sbprintf(&emitter.builder, " %%overflow%d = extractvalue ", instruction_index)
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid division operand")
|
||||||
strings.write_string(&emitter.builder, "{ ")
|
continue
|
||||||
fmt.sbprintf(&emitter.builder, "%s, i1 } %%pair%d, 1\n", type_name, instruction_index)
|
}
|
||||||
fmt.sbprintf(
|
emit_checked_division(emitter, instructions, instruction_index, instruction)
|
||||||
&emitter.builder,
|
|
||||||
" br i1 %%overflow%d, label %%overflow_trap%d, label %%overflow_continue%d\n",
|
|
||||||
instruction_index,
|
|
||||||
instruction_index,
|
|
||||||
instruction_index,
|
|
||||||
)
|
|
||||||
fmt.sbprintf(&emitter.builder, "overflow_trap%d:\n", instruction_index)
|
|
||||||
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "integer addition overflow")
|
|
||||||
emit_trap_call(emitter, message)
|
|
||||||
fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_index)
|
|
||||||
case .Pointer_Add:
|
case .Pointer_Add:
|
||||||
result_item, result_ok := types.node(&emitter.module.types, instruction.type)
|
result_item, result_ok := types.node(&emitter.module.types, instruction.type)
|
||||||
base_type := instructions[instruction.a].type if valid_instruction(instructions, instruction.a) else types.INVALID
|
base_type := instructions[instruction.a].type if valid_instruction(instructions, instruction.a) else types.INVALID
|
||||||
@@ -1835,20 +1934,17 @@ emit_messages :: proc(emitter: ^Emitter) {
|
|||||||
emit_declarations :: proc(emitter: ^Emitter) {
|
emit_declarations :: proc(emitter: ^Emitter) {
|
||||||
strings.write_string(&emitter.builder, "declare i64 @write(i32, ptr, i64)\ndeclare void @llvm.trap()\ndeclare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1 immarg)\n")
|
strings.write_string(&emitter.builder, "declare i64 @write(i32, ptr, i64)\ndeclare void @llvm.trap()\ndeclare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1 immarg)\n")
|
||||||
widths := [?]int{8, 16, 32, 64}
|
widths := [?]int{8, 16, 32, 64}
|
||||||
|
overflow_intrinsics := [?]string{"sadd", "uadd", "ssub", "usub", "smul", "umul"}
|
||||||
for bits in widths {
|
for bits in widths {
|
||||||
|
for name in overflow_intrinsics {
|
||||||
strings.write_string(&emitter.builder, "declare { i")
|
strings.write_string(&emitter.builder, "declare { i")
|
||||||
fmt.sbprintf(&emitter.builder, "%d", bits)
|
fmt.sbprintf(&emitter.builder, "%d", bits)
|
||||||
strings.write_string(&emitter.builder, ", i1 } @llvm.sadd.with.overflow.i")
|
strings.write_string(&emitter.builder, ", i1 } @llvm.")
|
||||||
fmt.sbprintf(&emitter.builder, "%d(i%d, i%d)\n", bits, bits, bits)
|
strings.write_string(&emitter.builder, name)
|
||||||
strings.write_string(&emitter.builder, "declare { i")
|
strings.write_string(&emitter.builder, ".with.overflow.i")
|
||||||
fmt.sbprintf(&emitter.builder, "%d", bits)
|
|
||||||
strings.write_string(&emitter.builder, ", i1 } @llvm.uadd.with.overflow.i")
|
|
||||||
fmt.sbprintf(&emitter.builder, "%d(i%d, i%d)\n", bits, bits, bits)
|
|
||||||
strings.write_string(&emitter.builder, "declare { i")
|
|
||||||
fmt.sbprintf(&emitter.builder, "%d", bits)
|
|
||||||
strings.write_string(&emitter.builder, ", i1 } @llvm.ssub.with.overflow.i")
|
|
||||||
fmt.sbprintf(&emitter.builder, "%d(i%d, i%d)\n", bits, bits, bits)
|
fmt.sbprintf(&emitter.builder, "%d(i%d, i%d)\n", bits, bits, bits)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
strings.write_string(
|
strings.write_string(
|
||||||
&emitter.builder,
|
&emitter.builder,
|
||||||
"\ndefine internal void @bro.trap(ptr %message, i64 %length) noreturn {\nentry:\n %written = call i64 @write(i32 2, ptr %message, i64 %length)\n call void @llvm.trap()\n unreachable\n}\n\n",
|
"\ndefine internal void @bro.trap(ptr %message, i64 %length) noreturn {\nentry:\n %written = call i64 @write(i32 2, ptr %message, i64 %length)\n call void @llvm.trap()\n unreachable\n}\n\n",
|
||||||
|
|||||||
@@ -124,9 +124,11 @@ lower_location :: proc(state: ^State, expr_id: hir.Expr_Id, for_write := false)
|
|||||||
return lower_nested_expr(state, expr.left)
|
return lower_nested_expr(state, expr.left)
|
||||||
case .Index:
|
case .Index:
|
||||||
container_type := state.hir_module.exprs[expr.left].type
|
container_type := state.hir_module.exprs[expr.left].type
|
||||||
container := lower_nested_expr(state, expr.left)
|
container := ir.INVALID_INSTRUCTION
|
||||||
if types.is_array(container_type, &state.hir_module.types) {
|
if types.is_array(container_type, &state.hir_module.types) {
|
||||||
container = lower_location(state, expr.left, for_write)
|
container = lower_location(state, expr.left, for_write)
|
||||||
|
} else {
|
||||||
|
container = lower_nested_expr(state, expr.left)
|
||||||
}
|
}
|
||||||
index := lower_nested_expr(state, expr.right)
|
index := lower_nested_expr(state, expr.right)
|
||||||
return append_instruction(state, ir.Instruction{
|
return append_instruction(state, ir.Instruction{
|
||||||
@@ -137,9 +139,11 @@ lower_location :: proc(state: ^State, expr_id: hir.Expr_Id, for_write := false)
|
|||||||
})
|
})
|
||||||
case .Field:
|
case .Field:
|
||||||
base_type := state.hir_module.exprs[expr.left].type
|
base_type := state.hir_module.exprs[expr.left].type
|
||||||
base := lower_nested_expr(state, expr.left)
|
base := ir.INVALID_INSTRUCTION
|
||||||
if !types.is_pointer(base_type, &state.hir_module.types) {
|
if !types.is_pointer(base_type, &state.hir_module.types) {
|
||||||
base = lower_location(state, expr.left, for_write)
|
base = lower_location(state, expr.left, for_write)
|
||||||
|
} else {
|
||||||
|
base = lower_nested_expr(state, expr.left)
|
||||||
}
|
}
|
||||||
return append_instruction(state, ir.Instruction{
|
return append_instruction(state, ir.Instruction{
|
||||||
op=.Field_Address, span=expr.span, type=expr.type, integer=expr.integer,
|
op=.Field_Address, span=expr.span, type=expr.type, integer=expr.integer,
|
||||||
@@ -468,7 +472,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
|||||||
case .Negate:
|
case .Negate:
|
||||||
stack[frame_index].stage = 5
|
stack[frame_index].stage = 5
|
||||||
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
||||||
case .Add, .Pointer_Add:
|
case .Add, .Sub, .Mul, .Div, .Pointer_Add:
|
||||||
stack[frame_index].stage = 2
|
stack[frame_index].stage = 2
|
||||||
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
||||||
case .Call:
|
case .Call:
|
||||||
@@ -537,8 +541,15 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if frame.stage == 3 {
|
if frame.stage == 3 {
|
||||||
|
op := ir.Opcode.Add_Checked
|
||||||
|
#partial switch expr.kind {
|
||||||
|
case .Sub: op = .Sub_Checked
|
||||||
|
case .Mul: op = .Mul_Checked
|
||||||
|
case .Div: op = .Div_Checked
|
||||||
|
case .Pointer_Add: op = .Pointer_Add
|
||||||
|
}
|
||||||
last = append_instruction(state, ir.Instruction{
|
last = append_instruction(state, ir.Instruction{
|
||||||
op=.Pointer_Add if expr.kind == .Pointer_Add else .Add_Checked,
|
op=op,
|
||||||
span=expr.span, type=expr.type, target=ir.INVALID_REF,
|
span=expr.span, type=expr.type, target=ir.INVALID_REF,
|
||||||
a=frame.left, b=last, diagnostic=source.INVALID_DIAGNOSTIC,
|
a=frame.left, b=last, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
@@ -605,6 +616,43 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
|||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
case .Assignment:
|
case .Assignment:
|
||||||
|
if statement.assignment_op != .Set && statement.target != hir.INVALID_EXPR {
|
||||||
|
address := lower_location(state, statement.target, true)
|
||||||
|
target_type := types.INVALID
|
||||||
|
if int(statement.target) < len(hir_module.exprs) {
|
||||||
|
target_type = hir_module.exprs[statement.target].type
|
||||||
|
}
|
||||||
|
if address == ir.INVALID_INSTRUCTION || !types.is_valid(target_type) {
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Trap, span=statement.span, type=types.VOID,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=statement.diagnostic,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
current := append_instruction(state, ir.Instruction{
|
||||||
|
op=.Load, span=statement.span, type=target_type,
|
||||||
|
target=ir.INVALID_REF, a=address, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
rhs := lower_expr(state, statement.expr)
|
||||||
|
op := ir.Opcode.Add_Checked
|
||||||
|
#partial switch statement.assignment_op {
|
||||||
|
case .Sub: op = .Sub_Checked
|
||||||
|
case .Mul: op = .Mul_Checked
|
||||||
|
case .Div: op = .Div_Checked
|
||||||
|
case .Pointer_Add: op = .Pointer_Add
|
||||||
|
}
|
||||||
|
value := append_instruction(state, ir.Instruction{
|
||||||
|
op=op, span=statement.span, type=target_type,
|
||||||
|
target=ir.INVALID_REF, a=current, b=rhs,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Store, span=statement.span, type=target_type,
|
||||||
|
target=ir.INVALID_REF, a=address, b=value, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
value := lower_expr(state, statement.expr)
|
value := lower_expr(state, statement.expr)
|
||||||
slot := ir.INVALID_INSTRUCTION
|
slot := ir.INVALID_INSTRUCTION
|
||||||
value_type := types.INVALID
|
value_type := types.INVALID
|
||||||
|
|||||||
@@ -663,8 +663,10 @@ infix_binding_power :: proc(kind: token.Kind) -> (left, right: int, ok: bool) {
|
|||||||
return 6, 7, true
|
return 6, 7, true
|
||||||
case .Equal_Equal, .Bang_Equal, .Less, .Less_Equal, .Greater, .Greater_Equal:
|
case .Equal_Equal, .Bang_Equal, .Less, .Less_Equal, .Greater, .Greater_Equal:
|
||||||
return 8, 9, true
|
return 8, 9, true
|
||||||
case .Plus:
|
case .Plus, .Minus:
|
||||||
return 10, 11, true
|
return 10, 11, true
|
||||||
|
case .Star, .Slash:
|
||||||
|
return 12, 13, true
|
||||||
}
|
}
|
||||||
return 0, 0, false
|
return 0, 0, false
|
||||||
}
|
}
|
||||||
@@ -681,10 +683,24 @@ infix_expr_kind :: proc(kind: token.Kind) -> ast.Expr_Kind {
|
|||||||
case .Less_Equal: return .Le
|
case .Less_Equal: return .Le
|
||||||
case .Greater: return .Gt
|
case .Greater: return .Gt
|
||||||
case .Greater_Equal: return .Ge
|
case .Greater_Equal: return .Ge
|
||||||
|
case .Plus: return .Add
|
||||||
|
case .Minus: return .Sub
|
||||||
|
case .Star: return .Mul
|
||||||
|
case .Slash: return .Div
|
||||||
case: return .Add
|
case: return .Add
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compound_assignment_op :: proc(kind: token.Kind) -> (ast.Assignment_Op, bool) {
|
||||||
|
#partial switch kind {
|
||||||
|
case .Plus_Equal: return .Add, true
|
||||||
|
case .Minus_Equal: return .Sub, true
|
||||||
|
case .Star_Equal: return .Mul, true
|
||||||
|
case .Slash_Equal: return .Div, true
|
||||||
|
}
|
||||||
|
return .Set, false
|
||||||
|
}
|
||||||
|
|
||||||
is_simple_range_bound :: proc(expr: ast.Expr) -> bool {
|
is_simple_range_bound :: proc(expr: ast.Expr) -> bool {
|
||||||
if expr.parenthesized {
|
if expr.parenthesized {
|
||||||
return true
|
return true
|
||||||
@@ -1029,6 +1045,22 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
})
|
})
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
if assignment_op, is_compound := compound_assignment_op(current(parser).kind); is_compound {
|
||||||
|
advance(parser)
|
||||||
|
skip_newlines(parser)
|
||||||
|
value := parse_expression(parser)
|
||||||
|
span := span_from(parser.module.exprs[expr].span, parser.module.exprs[value].span)
|
||||||
|
id := ast.stmt_id(len(parser.module.statements))
|
||||||
|
append(&parser.module.statements, ast.Stmt{
|
||||||
|
kind=.Assignment,
|
||||||
|
span=span,
|
||||||
|
assignment_op=assignment_op,
|
||||||
|
target=expr,
|
||||||
|
expr=value,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
return id
|
||||||
|
}
|
||||||
id := ast.stmt_id(len(parser.module.statements))
|
id := ast.stmt_id(len(parser.module.statements))
|
||||||
append(&parser.module.statements, ast.Stmt{
|
append(&parser.module.statements, ast.Stmt{
|
||||||
kind=.Expression,
|
kind=.Expression,
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ Kind :: enum u8 {
|
|||||||
Greater_Equal,
|
Greater_Equal,
|
||||||
Plus,
|
Plus,
|
||||||
Minus,
|
Minus,
|
||||||
|
Slash,
|
||||||
|
Plus_Equal,
|
||||||
|
Minus_Equal,
|
||||||
|
Star_Equal,
|
||||||
|
Slash_Equal,
|
||||||
Dot,
|
Dot,
|
||||||
Range,
|
Range,
|
||||||
Range_Inclusive,
|
Range_Inclusive,
|
||||||
|
|||||||
@@ -3047,6 +3047,35 @@ main :: func() void {}
|
|||||||
testing.expect_value(t, hir_module.globals[2].static_value, i64(-3))
|
testing.expect_value(t, hir_module.globals[2].static_value, i64(-3))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
constant_division_by_zero_has_a_precise_diagnostic :: proc(t: ^testing.T) {
|
||||||
|
text := `value :: 5 / 0
|
||||||
|
main :: func() void {}
|
||||||
|
`
|
||||||
|
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_division_by_zero := false
|
||||||
|
found_overflow := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found_division_by_zero = found_division_by_zero ||
|
||||||
|
strings.contains(diagnostic.message, "division by zero in constant expression")
|
||||||
|
found_overflow = found_overflow ||
|
||||||
|
strings.contains(diagnostic.message, "integer constant expression exceeds signed i64 range")
|
||||||
|
}
|
||||||
|
testing.expect(t, found_division_by_zero)
|
||||||
|
testing.expect(t, !found_overflow)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
out_of_range_negative_constants_are_diagnosed :: proc(t: ^testing.T) {
|
out_of_range_negative_constants_are_diagnosed :: proc(t: ^testing.T) {
|
||||||
text := `positive :: 9223372036854775808
|
text := `positive :: 9223372036854775808
|
||||||
@@ -4728,3 +4757,477 @@ main :: func() i32 {
|
|||||||
testing.expect_value(t, index_address_count, 0)
|
testing.expect_value(t, index_address_count, 0)
|
||||||
testing.expect(t, !strings.contains(llvm_text, "index_ok"))
|
testing.expect(t, !strings.contains(llvm_text, "index_ok"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
lexer_emits_compound_assignment_and_slash_tokens :: proc(t: ^testing.T) {
|
||||||
|
source_file := source.Source{path="test.bro", 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)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect_value(t, stream.items[0].kind, token.Kind.Plus_Equal)
|
||||||
|
testing.expect_value(t, stream.items[1].kind, token.Kind.Minus_Equal)
|
||||||
|
testing.expect_value(t, stream.items[2].kind, token.Kind.Star_Equal)
|
||||||
|
testing.expect_value(t, stream.items[3].kind, token.Kind.Slash_Equal)
|
||||||
|
testing.expect_value(t, stream.items[4].kind, token.Kind.Slash)
|
||||||
|
testing.expect_value(t, stream.items[5].kind, token.Kind.Star)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
binary_operators_respect_multiplicative_precedence :: proc(t: ^testing.T) {
|
||||||
|
source_file := source.Source{path="test.bro", text="value :: 1 + 2 * 3\nmain :: func() void {}\n"}
|
||||||
|
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.globals[0].expr]
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect_value(t, root.kind, ast.Expr_Kind.Add)
|
||||||
|
testing.expect_value(t, module.exprs[root.left].integer, u64(1))
|
||||||
|
testing.expect_value(t, module.exprs[root.right].kind, ast.Expr_Kind.Mul)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
division_parses_left_associatively :: proc(t: ^testing.T) {
|
||||||
|
source_file := source.Source{path="test.bro", text="value :: 8 / 4 / 2\nmain :: func() void {}\n"}
|
||||||
|
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.globals[0].expr]
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect_value(t, root.kind, ast.Expr_Kind.Div)
|
||||||
|
testing.expect_value(t, module.exprs[root.left].kind, ast.Expr_Kind.Div)
|
||||||
|
testing.expect_value(t, module.exprs[root.right].integer, u64(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
compound_assignment_preserves_operation_and_rhs :: proc(t: ^testing.T) {
|
||||||
|
text := `main :: func() void {
|
||||||
|
x i32 = 0
|
||||||
|
x += 5
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
body := module.functions[0].body
|
||||||
|
statement := module.statements[body[1]]
|
||||||
|
testing.expect_value(t, statement.kind, ast.Stmt_Kind.Assignment)
|
||||||
|
testing.expect_value(t, statement.assignment_op, ast.Assignment_Op.Add)
|
||||||
|
testing.expect(t, statement.target != ast.INVALID_EXPR)
|
||||||
|
testing.expect_value(t, module.exprs[statement.target].kind, ast.Expr_Kind.Name)
|
||||||
|
testing.expect_value(t, module.exprs[statement.expr].kind, ast.Expr_Kind.Integer)
|
||||||
|
testing.expect_value(t, module.exprs[statement.expr].integer, u64(5))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
compound_assignment_evaluates_lvalue_once :: proc(t: ^testing.T) {
|
||||||
|
// A compound assignment to an indexed lvalue must compute the element address
|
||||||
|
// once and reuse it for the load and the store, rather than re-lowering the
|
||||||
|
// lvalue (which would re-evaluate any side-effecting index subexpression).
|
||||||
|
text := `bump :: func() usize {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
main :: func() i32 {
|
||||||
|
values [3]mut i32 = [10, 20, 30]
|
||||||
|
values[bump()] += 5
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
call_count := 0
|
||||||
|
index_address_count := 0
|
||||||
|
for function in ir_module.functions {
|
||||||
|
if !function.is_main {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for instruction in function.instructions {
|
||||||
|
#partial switch instruction.op {
|
||||||
|
case .Call: call_count += 1
|
||||||
|
case .Index_Address: index_address_count += 1
|
||||||
|
case:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// `bump()` is the lvalue's index. The fix shares one address between the load
|
||||||
|
// and the store, so the side-effecting index runs exactly once and a single
|
||||||
|
// Index_Address is emitted; the buggy double-lowering produced two of each.
|
||||||
|
testing.expect_value(t, call_count, 1)
|
||||||
|
testing.expect_value(t, index_address_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
compound_assignment_evaluates_nested_locations_once :: proc(t: ^testing.T) {
|
||||||
|
text := `Box :: struct {
|
||||||
|
value i32
|
||||||
|
}
|
||||||
|
row :: func() usize {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
column :: func() usize {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
pointer_for :: func(value @mut i32) @mut i32 {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
main :: func() i32 {
|
||||||
|
matrix [2]mut [2]mut i32 = [[1, 2], [3, 4]]
|
||||||
|
(matrix[row()])[column()] += 1
|
||||||
|
boxes [2]mut Box = [Box { value = 5 }, Box { value = 6 }]
|
||||||
|
boxes[row()].value += 1
|
||||||
|
value i32 = 7
|
||||||
|
pointer_for(&value)^ += 1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
call_count := 0
|
||||||
|
call_names: [4]string
|
||||||
|
index_address_count := 0
|
||||||
|
field_address_count := 0
|
||||||
|
for function in ir_module.functions {
|
||||||
|
if !function.is_main {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for instruction in function.instructions {
|
||||||
|
#partial switch instruction.op {
|
||||||
|
case .Call:
|
||||||
|
function_id := ir.as_function(instruction.target)
|
||||||
|
if call_count < len(call_names) &&
|
||||||
|
function_id != ir.INVALID_FUNCTION &&
|
||||||
|
int(function_id) < len(hir_module.functions) {
|
||||||
|
call_names[call_count] = symbol.resolve(
|
||||||
|
&symbols,
|
||||||
|
hir_module.functions[function_id].name,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
call_count += 1
|
||||||
|
case .Index_Address: index_address_count += 1
|
||||||
|
case .Field_Address: field_address_count += 1
|
||||||
|
case:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// row(), column(), the second row(), and pointer_for() each run once. The
|
||||||
|
// nested matrix target needs two index addresses; the indexed field needs
|
||||||
|
// one index address and one field address.
|
||||||
|
testing.expect_value(t, call_count, 4)
|
||||||
|
testing.expect_value(t, call_names, [4]string{"row", "column", "row", "pointer_for"})
|
||||||
|
testing.expect_value(t, index_address_count, 3)
|
||||||
|
testing.expect_value(t, field_address_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
compound_assignment_supports_pointer_add_only :: proc(t: ^testing.T) {
|
||||||
|
valid_text := `main :: func() i32 {
|
||||||
|
values [3]mut i32 = [10, 20, 30]
|
||||||
|
pointer *mut i32 = (&values).ptr
|
||||||
|
pointer += 1
|
||||||
|
offset usize = 1
|
||||||
|
pointer += offset
|
||||||
|
return pointer^
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=valid_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)
|
||||||
|
|
||||||
|
pointer_add_count := 0
|
||||||
|
for function in ir_module.functions {
|
||||||
|
if !function.is_main {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for instruction in function.instructions {
|
||||||
|
pointer_add_count += 1 if instruction.op == .Pointer_Add else 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect_value(t, pointer_add_count, 2)
|
||||||
|
|
||||||
|
invalid_text := `main :: func() void {
|
||||||
|
values [1]mut i32 = [10]
|
||||||
|
pointer *mut i32 = (&values).ptr
|
||||||
|
pointer -= 1
|
||||||
|
}
|
||||||
|
`
|
||||||
|
invalid_source := source.Source{path="invalid.bro", text=invalid_text}
|
||||||
|
invalid_diagnostics := source.init_diagnostics(&invalid_source)
|
||||||
|
defer source.destroy_diagnostics(&invalid_diagnostics)
|
||||||
|
invalid_symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&invalid_symbols)
|
||||||
|
invalid_stream := lexer.lex(&invalid_source, &invalid_diagnostics, &invalid_symbols)
|
||||||
|
defer delete(invalid_stream.items)
|
||||||
|
invalid_ast := parser.parse(&invalid_stream, &invalid_source, &invalid_diagnostics)
|
||||||
|
defer ast.destroy_module(&invalid_ast)
|
||||||
|
invalid_hir := checker.check(&invalid_ast, &invalid_diagnostics, &invalid_symbols)
|
||||||
|
defer hir.destroy_module(&invalid_hir)
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for diagnostic in invalid_diagnostics.items {
|
||||||
|
found = found || strings.contains(
|
||||||
|
diagnostic.message,
|
||||||
|
"many-item pointers only support '+=' compound assignment",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
testing.expect(t, found)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
compound_assignment_preserves_checked_numeric_operations :: proc(t: ^testing.T) {
|
||||||
|
text := `main :: func() i32 {
|
||||||
|
signed i32 = 24
|
||||||
|
signed += 6
|
||||||
|
signed -= 2
|
||||||
|
signed *= 3
|
||||||
|
signed /= 4
|
||||||
|
unsigned u32 = 24
|
||||||
|
unsigned += 6
|
||||||
|
unsigned -= 2
|
||||||
|
unsigned *= 3
|
||||||
|
unsigned /= 4
|
||||||
|
float f64 = 24.0
|
||||||
|
float += 6.0
|
||||||
|
float -= 2.0
|
||||||
|
float *= 3.0
|
||||||
|
float /= 4.0
|
||||||
|
return signed
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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)
|
||||||
|
|
||||||
|
operation_counts: [hir.Assignment_Op]int
|
||||||
|
for statement_id in hir_module.functions[0].body {
|
||||||
|
statement := hir_module.statements[statement_id]
|
||||||
|
if statement.kind == .Assignment {
|
||||||
|
operation_counts[statement.assignment_op] += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect_value(t, operation_counts[.Add], 3)
|
||||||
|
testing.expect_value(t, operation_counts[.Sub], 3)
|
||||||
|
testing.expect_value(t, operation_counts[.Mul], 3)
|
||||||
|
testing.expect_value(t, operation_counts[.Div], 3)
|
||||||
|
|
||||||
|
add_count := 0
|
||||||
|
sub_count := 0
|
||||||
|
mul_count := 0
|
||||||
|
div_count := 0
|
||||||
|
for instruction in ir_module.functions[0].instructions {
|
||||||
|
#partial switch instruction.op {
|
||||||
|
case .Add_Checked: add_count += 1
|
||||||
|
case .Sub_Checked: sub_count += 1
|
||||||
|
case .Mul_Checked: mul_count += 1
|
||||||
|
case .Div_Checked: div_count += 1
|
||||||
|
case:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
testing.expect_value(t, add_count, 3)
|
||||||
|
testing.expect_value(t, sub_count, 3)
|
||||||
|
testing.expect_value(t, mul_count, 3)
|
||||||
|
testing.expect_value(t, div_count, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
compound_assignment_rejects_narrowing_and_mixed_numeric_families :: proc(t: ^testing.T) {
|
||||||
|
text := `main :: func() void {
|
||||||
|
narrow i8 = 1
|
||||||
|
wide i32 = 2
|
||||||
|
narrow += wide
|
||||||
|
signed i32 = 3
|
||||||
|
unsigned u32 = 4
|
||||||
|
signed += unsigned
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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_narrowing := false
|
||||||
|
found_mixed_family := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found_narrowing = found_narrowing ||
|
||||||
|
strings.contains(diagnostic.message, "cannot implicitly convert i32 to i8")
|
||||||
|
found_mixed_family = found_mixed_family ||
|
||||||
|
strings.contains(diagnostic.message, "arithmetic requires compatible numeric operands")
|
||||||
|
}
|
||||||
|
testing.expect(t, found_narrowing)
|
||||||
|
testing.expect(t, found_mixed_family)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
compound_assignment_compiles_and_runs :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-compound"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/programs/compound_assignment", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 23)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
binary_arithmetic_rejects_non_numeric_operands :: proc(t: ^testing.T) {
|
||||||
|
text := `main :: func() i32 {
|
||||||
|
a i32 = 1
|
||||||
|
b u32 = 2
|
||||||
|
_ = a / b
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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 := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found = found || strings.contains(diagnostic.message, "arithmetic requires compatible numeric operands")
|
||||||
|
}
|
||||||
|
testing.expect(t, found)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
compound_assignment_requires_writable_target :: proc(t: ^testing.T) {
|
||||||
|
text := `main :: func() i32 {
|
||||||
|
x :: 5
|
||||||
|
x += 1
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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 := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found = found || strings.contains(diagnostic.message, "assignment target is not writable")
|
||||||
|
}
|
||||||
|
testing.expect(t, found)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
checked_division_and_subtraction_emit_guarded_llvm :: proc(t: ^testing.T) {
|
||||||
|
text := `main :: func() i32 {
|
||||||
|
a i32 = 10
|
||||||
|
b i32 = 3
|
||||||
|
c i32 = a - b
|
||||||
|
return c / b
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect(t, strings.contains(llvm_text, "@llvm.ssub.with.overflow.i32"))
|
||||||
|
testing.expect(t, strings.contains(llvm_text, "sdiv i32"))
|
||||||
|
testing.expect(t, strings.contains(llvm_text, "divzero_trap"))
|
||||||
|
testing.expect(t, strings.contains(llvm_text, "divovf_trap"))
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ main :: func() void {
|
|||||||
_ = native.configured_value(9)
|
_ = native.configured_value(9)
|
||||||
_ = native.IMPORTED_SHADOW_OBJECT
|
_ = native.IMPORTED_SHADOW_OBJECT
|
||||||
_ = native.IMPORTED_SHADOW_FUNCTION
|
_ = native.IMPORTED_SHADOW_FUNCTION
|
||||||
native.imported_global = native.IMPORTED_MAGIC
|
native.imported_global += native.IMPORTED_MAGIC
|
||||||
native.imported_record_global.value = native.IMPORTED_MAGIC
|
native.imported_record_global.value += 2
|
||||||
color native.Imported_Color :: native.IMPORTED_COLOR
|
color native.Imported_Color :: native.IMPORTED_COLOR
|
||||||
_ = native.imported_check_state(
|
_ = native.imported_check_state(
|
||||||
color,
|
color,
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Milestone 6: compound assignment (`+=`, `-=`, `*=`, `/=`) and the binary
|
||||||
|
# arithmetic operators `-`, `*`, `/` with multiplicative precedence.
|
||||||
|
|
||||||
|
check_float :: func() i32 {
|
||||||
|
x f64 = 10.0
|
||||||
|
x /= 4.0 # 2.5
|
||||||
|
x *= 2.0 # 5.0
|
||||||
|
x -= 1.0 # 4.0
|
||||||
|
x += 0.5 # 4.5
|
||||||
|
if x > 4.0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
check_unsigned :: func() i32 {
|
||||||
|
n u32 = 100
|
||||||
|
n /= 7 # 14 (truncating integer division)
|
||||||
|
n -= 4 # 10
|
||||||
|
if n == 10 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: func() i32 {
|
||||||
|
total i32 = 0
|
||||||
|
total += 10 # 10
|
||||||
|
total -= 3 # 7
|
||||||
|
total *= 4 # 28
|
||||||
|
total /= 2 # 14
|
||||||
|
|
||||||
|
# binary operators honour precedence: 14 + (2 * 3) - 4 == 16
|
||||||
|
total = total + 2 * 3 - 4
|
||||||
|
|
||||||
|
# compound assignment as a while-loop update
|
||||||
|
i i32 = 0
|
||||||
|
while i < 5 : i += 1 {
|
||||||
|
total += 1 # +5 => 21
|
||||||
|
}
|
||||||
|
|
||||||
|
total += check_float() # +1 => 22
|
||||||
|
total += check_unsigned() # +1 => 23
|
||||||
|
return total
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user