compound assignment

This commit is contained in:
2026-06-22 21:20:15 +02:00
parent 663f4dc658
commit 6512ccd543
14 changed files with 995 additions and 99 deletions
+15
View File
@@ -84,6 +84,9 @@ Expr_Kind :: enum u8 {
Negate,
Not,
Add,
Sub,
Mul,
Div,
Eq,
Ne,
Lt,
@@ -126,6 +129,14 @@ Stmt_Kind :: enum u8 {
For,
}
Assignment_Op :: enum u8 {
Set,
Add,
Sub,
Mul,
Div,
}
Stmt :: struct {
kind: Stmt_Kind,
span: source.Span,
@@ -134,6 +145,10 @@ Stmt :: struct {
type: Type_Syntax,
immutable: 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,
expr: Expr_Id,
// `If` statements use `expr` as the condition, `captures` as optional
+134 -39
View File
@@ -65,6 +65,7 @@ Constant_Kind :: enum {
Not_Constant,
Value,
Overflow,
Div_By_Zero,
}
Constant :: struct {
@@ -145,7 +146,8 @@ eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant {
continue
}
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}
if expr.kind == .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]
}
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}
} else if operand.kind == .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]
}
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}
} else if left.kind == .Value && right.kind == .Value {
value, overflow := intrinsics.overflow_add(left.value, right.value)
result = Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value}
value: i128
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
_ = 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:
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)
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Name:
}
@@ -1163,7 +1186,8 @@ infer_expr :: proc(
expr := checker.ast_module.exprs[frame.expr]
if frame.stage == 0 {
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
_ = pop(&stack)
continue
@@ -1244,7 +1268,7 @@ infer_expr :: proc(
case .Negate:
stack[frame_index].stage = 5
append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION})
case .Add:
case .Add, .Sub, .Mul, .Div:
stack[frame_index].stage = 1
append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION})
case .Call:
@@ -1338,7 +1362,7 @@ infer_expr :: proc(
continue
}
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
} else {
last = types.widest(frame.left, last)
@@ -1847,6 +1871,10 @@ build_constant_expr :: proc(
if types.is_concrete_integer(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 ||
(!types.is_concrete_integer(expected) && !fits_i64(constant.value)) {
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(
checker: ^Checker,
expr_id: ast.Expr_Id,
@@ -2520,7 +2587,7 @@ build_expr :: proc(
expr := checker.ast_module.exprs[frame.expr]
if frame.stage == 0 {
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)
_ = pop(&stack)
continue
@@ -2609,7 +2676,7 @@ build_expr :: proc(
case .Negate:
stack[frame_index].stage = 5
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
// Preserve assignment/return context for literal operands, e.g.
// assigning `i + 1` back into a `u32` local.
@@ -2768,29 +2835,7 @@ build_expr :: proc(
continue
}
if frame.stage == 2 {
left := frame.left
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,
})
}
last = build_binary_arith(checker, expr.kind, frame.left, last, expr.span)
_ = pop(&stack)
continue
}
@@ -3061,14 +3106,64 @@ build_block :: proc(
ctx.problematic^ = true
continue
}
value := build_expr(
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
target_type, ctx.pkg, ctx.file,
)
value = coerce_expr(checker, value, target_type, statement.span)
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,
target_type, ctx.pkg, ctx.file,
)
value = coerce_expr(checker, value, target_type, statement.span)
}
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind=.Assignment, span=statement.span, local=hir.INVALID_LOCAL,
assignment_op=assignment_op,
target=target_expr, expr=value, diagnostic=source.INVALID_DIAGNOSTIC,
})
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
+16
View File
@@ -102,6 +102,9 @@ Expr_Kind :: enum u8 {
Negate,
Not,
Add,
Sub,
Mul,
Div,
Pointer_Add,
Eq,
Ne,
@@ -151,6 +154,15 @@ Stmt_Kind :: enum u8 {
For,
}
Assignment_Op :: enum u8 {
Set,
Add,
Sub,
Mul,
Div,
Pointer_Add,
}
Stmt :: struct {
kind: Stmt_Kind,
span: source.Span,
@@ -160,6 +172,10 @@ Stmt :: struct {
expr: Expr_Id,
iterator_type: types.Type,
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
// use `unwraps` for the ordered optional expressions and capture locals, and
// `guard` for the optional boolean checked after every unwrap succeeds.
+3
View File
@@ -98,6 +98,9 @@ Opcode :: enum u8 {
Decay_Array_Pointer,
Neg_Checked,
Add_Checked,
Sub_Checked,
Mul_Checked,
Div_Checked,
Pointer_Add,
Not,
Compare,
+30 -3
View File
@@ -150,11 +150,23 @@ lex :: proc(
append_token(&stream, source_file, .Greater, start, cursor)
}
case '+':
append_token(&stream, source_file, .Plus, cursor, cursor+1)
start := cursor
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 '-':
append_token(&stream, source_file, .Minus, cursor, cursor+1)
start := cursor
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 '.':
start := cursor
cursor += 1
@@ -176,8 +188,23 @@ lex :: proc(
append_token(&stream, source_file, .At, cursor, cursor+1)
cursor += 1
case '*':
append_token(&stream, source_file, .Star, cursor, cursor+1)
start := cursor
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 '&':
append_token(&stream, source_file, .Ampersand, cursor, cursor+1)
cursor += 1
+141 -45
View File
@@ -240,7 +240,7 @@ valid_value :: proc(
.Extract, .Select, .Unwrap,
.Optional_Is_Some, .Optional_Value, .Orelse,
.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
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
.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(
emitter: ^Emitter,
instructions: []ir.Instruction,
@@ -1166,40 +1277,28 @@ emit_instruction_stream :: proc(
emit_recovery_value(emitter, instruction_index, instruction, "invalid addition operand")
continue
}
type_name := llvm_type(instruction.type, &emitter.module.types)
if types.is_float(instruction.type, emitter.module.target) {
fmt.sbprintf(&emitter.builder, " %%v%d = fadd %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_checked_arithmetic(emitter, instructions, instruction_index, instruction, "add", "fadd", "integer addition overflow")
case .Sub_Checked:
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid subtraction operand")
continue
}
intrinsic := "uadd" if types.is_unsigned(instruction.type, emitter.module.target) else "sadd"
fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_index)
strings.write_string(&emitter.builder, "{ ")
fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.%s.with.overflow.%s(%s ", type_name, intrinsic, 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, "integer addition overflow")
emit_trap_call(emitter, message)
fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_index)
emit_checked_arithmetic(emitter, instructions, instruction_index, instruction, "sub", "fsub", "integer subtraction overflow")
case .Mul_Checked:
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid multiplication operand")
continue
}
emit_checked_arithmetic(emitter, instructions, instruction_index, instruction, "mul", "fmul", "integer multiplication overflow")
case .Div_Checked:
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid division operand")
continue
}
emit_checked_division(emitter, instructions, instruction_index, instruction)
case .Pointer_Add:
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
@@ -1835,19 +1934,16 @@ emit_messages :: 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")
widths := [?]int{8, 16, 32, 64}
overflow_intrinsics := [?]string{"sadd", "uadd", "ssub", "usub", "smul", "umul"}
for bits in widths {
strings.write_string(&emitter.builder, "declare { i")
fmt.sbprintf(&emitter.builder, "%d", bits)
strings.write_string(&emitter.builder, ", i1 } @llvm.sadd.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.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)
for name in overflow_intrinsics {
strings.write_string(&emitter.builder, "declare { i")
fmt.sbprintf(&emitter.builder, "%d", bits)
strings.write_string(&emitter.builder, ", i1 } @llvm.")
strings.write_string(&emitter.builder, name)
strings.write_string(&emitter.builder, ".with.overflow.i")
fmt.sbprintf(&emitter.builder, "%d(i%d, i%d)\n", bits, bits, bits)
}
}
strings.write_string(
&emitter.builder,
+52 -4
View File
@@ -124,9 +124,11 @@ lower_location :: proc(state: ^State, expr_id: hir.Expr_Id, for_write := false)
return lower_nested_expr(state, expr.left)
case .Index:
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) {
container = lower_location(state, expr.left, for_write)
} else {
container = lower_nested_expr(state, expr.left)
}
index := lower_nested_expr(state, expr.right)
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:
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) {
base = lower_location(state, expr.left, for_write)
} else {
base = lower_nested_expr(state, expr.left)
}
return append_instruction(state, ir.Instruction{
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:
stack[frame_index].stage = 5
append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Add, .Pointer_Add:
case .Add, .Sub, .Mul, .Div, .Pointer_Add:
stack[frame_index].stage = 2
append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Call:
@@ -537,8 +541,15 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
continue
}
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{
op=.Pointer_Add if expr.kind == .Pointer_Add else .Add_Checked,
op=op,
span=expr.span, type=expr.type, target=ir.INVALID_REF,
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,
})
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)
slot := ir.INVALID_INSTRUCTION
value_type := types.INVALID
+33 -1
View File
@@ -663,8 +663,10 @@ infix_binding_power :: proc(kind: token.Kind) -> (left, right: int, ok: bool) {
return 6, 7, true
case .Equal_Equal, .Bang_Equal, .Less, .Less_Equal, .Greater, .Greater_Equal:
return 8, 9, true
case .Plus:
case .Plus, .Minus:
return 10, 11, true
case .Star, .Slash:
return 12, 13, true
}
return 0, 0, false
}
@@ -681,10 +683,24 @@ infix_expr_kind :: proc(kind: token.Kind) -> ast.Expr_Kind {
case .Less_Equal: return .Le
case .Greater: return .Gt
case .Greater_Equal: return .Ge
case .Plus: return .Add
case .Minus: return .Sub
case .Star: return .Mul
case .Slash: return .Div
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 {
if expr.parenthesized {
return true
@@ -1029,6 +1045,22 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_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))
append(&parser.module.statements, ast.Stmt{
kind=.Expression,
+5
View File
@@ -25,6 +25,11 @@ Kind :: enum u8 {
Greater_Equal,
Plus,
Minus,
Slash,
Plus_Equal,
Minus_Equal,
Star_Equal,
Slash_Equal,
Dot,
Range,
Range_Inclusive,