feat: add integer widths and checked numeric conversions

This commit is contained in:
2026-10-25 22:19:14 +01:00
parent 0385e46295
commit 3e93c6f000
12 changed files with 500 additions and 66 deletions
+60 -7
View File
@@ -308,15 +308,20 @@ run proc(
continue
}
}
.convert: {
operand :: frames.items[frame_index].values[usize(instruction.left)]
if (convert(operand, instruction.type)) |converted| value = converted
else return try runtime_trap(diagnostic_store, .integer_overflow, instruction.span)
}
.negate: {
operand :: frames.items[frame_index].values[usize(instruction.left)]
if instruction.type == .float {
value = ir.Value{ type = .float, data = ir.ValueData{ real = -operand.data.real } }
} else {
if operand.data.integer == minval!(i64) {
if operand.data.integer == ir.integer_min(instruction.type) {
return try runtime_trap(diagnostic_store, .integer_overflow, instruction.span)
}
value = ir.Value{ type = .int, data = ir.ValueData{ integer = -operand.data.integer } }
value = ir.Value{ type = instruction.type, data = ir.ValueData{ integer = -operand.data.integer } }
}
}
.logical_not: {
@@ -336,17 +341,21 @@ run proc(
else: unreachable
}
value = ir.Value{ type = .float, data = ir.ValueData{ real = result } }
} else if instruction.type == .usize {
} else if ir.unsigned_integer(instruction.type) {
result :: checked_unsigned(instruction.op, left.data.unsigned, right.data.unsigned)
if result |unsigned| {
value = ir.Value{ type = .usize, data = ir.ValueData{ unsigned = unsigned } }
if (unsigned > ir.integer_max(instruction.type)) return try runtime_trap(diagnostic_store, .integer_overflow, instruction.span)
value = ir.Value{ type = instruction.type, data = ir.ValueData{ unsigned = unsigned } }
} else {
return try runtime_trap(diagnostic_store, .integer_overflow, instruction.span)
}
} else {
result :: checked_integer(instruction.op, left.data.integer, right.data.integer)
if result |result_integer| {
value = ir.Value{ type = .int, data = ir.ValueData{ integer = result_integer } }
if (result_integer < ir.integer_min(instruction.type) or result_integer > i64(ir.integer_max(instruction.type))) {
return try runtime_trap(diagnostic_store, .integer_overflow, instruction.span)
}
value = ir.Value{ type = instruction.type, data = ir.ValueData{ integer = result_integer } }
} else {
return try runtime_trap(diagnostic_store, .integer_overflow, instruction.span)
}
@@ -357,7 +366,7 @@ run proc(
right :: frames.items[frame_index].values[usize(instruction.right)]
result := false
match left.type {
.int: result = match instruction.op {
.int, .isize, .i8, .i16, .i32: result = match instruction.op {
.equal: left.data.integer == right.data.integer
.not_equal: left.data.integer != right.data.integer
.less: left.data.integer < right.data.integer
@@ -366,7 +375,7 @@ run proc(
.greater_equal: left.data.integer >= right.data.integer
else: unreachable
}
.usize: result = match instruction.op {
.usize, .u8, .u16, .u32, .u64: result = match instruction.op {
.equal: left.data.unsigned == right.data.unsigned
.not_equal: left.data.unsigned != right.data.unsigned
.less: left.data.unsigned < right.data.unsigned
@@ -533,3 +542,47 @@ checked_unsigned proc(op ir.Op, left u64, right u64) ?u64 {
else: unreachable
}
}
@hide:file
convert proc(source ir.Value, target ir.Type) ?ir.Value {
result := ir.Value{ type = target }
if (target == .float) {
if (source.type == .float) result.data = source.data
else if (ir.unsigned_integer(source.type)) result.data = ir.ValueData{ real = f64(source.data.unsigned) }
else result.data = ir.ValueData{ real = f64(source.data.integer) }
return result
}
if (source.type == .float) {
real :: source.data.real
# Guard the conversion itself before testing narrower bounds; NaN fails
# ordered comparisons, and the upper 64-bit bound is exclusive.
if (ir.unsigned_integer(target)) {
if (!(real > -1.0 and real < 18446744073709551616.0)) return null
integer :: u64(real)
if (integer > ir.integer_max(target)) return null
result.data = ir.ValueData{ unsigned = integer }
} else {
if (!(real >= -9223372036854775808.0 and real < 9223372036854775808.0)) return null
integer :: i64(real)
if (integer < ir.integer_min(target) or integer > i64(ir.integer_max(target))) return null
result.data = ir.ValueData{ integer = integer }
}
return result
}
if (ir.unsigned_integer(source.type)) {
integer :: source.data.unsigned
if (integer > ir.integer_max(target)) return null
if (ir.unsigned_integer(target)) result.data = ir.ValueData{ unsigned = integer }
else result.data = ir.ValueData{ integer = i64(integer) }
} else {
integer :: source.data.integer
if (ir.unsigned_integer(target)) {
if (integer < 0 or u64(integer) > ir.integer_max(target)) return null
result.data = ir.ValueData{ unsigned = u64(integer) }
} else {
if (integer < ir.integer_min(target) or integer > i64(ir.integer_max(target))) return null
result.data = ir.ValueData{ integer = integer }
}
}
return result
}