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
+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,