feat: add integer widths and checked numeric conversions
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -24,6 +24,48 @@ Type :: enum {
|
||||
array,
|
||||
usize,
|
||||
address,
|
||||
i8, i16, i32, isize,
|
||||
u8, u16, u32, u64,
|
||||
}
|
||||
|
||||
#! integer storage widths for the supported 64-bit target; zero for noninteger types.
|
||||
integer_bits proc(type Type) usize {
|
||||
return match type {
|
||||
.i8, .u8: 8
|
||||
.i16, .u16: 16
|
||||
.i32, .u32: 32
|
||||
.int, .isize, .usize, .u64: 64
|
||||
else: 0
|
||||
}
|
||||
}
|
||||
|
||||
unsigned_integer proc(type Type) bool {
|
||||
return type == .u8 or type == .u16 or type == .u32 or type == .u64 or type == .usize
|
||||
}
|
||||
|
||||
integer_max proc(type Type) u64 {
|
||||
return match type {
|
||||
.i8: 127
|
||||
.i16: 32767
|
||||
.i32: 2147483647
|
||||
.int, .isize: u64(maxval!(i64))
|
||||
.u8: 255
|
||||
.u16: 65535
|
||||
.u32: 4294967295
|
||||
.u64, .usize: maxval!(u64)
|
||||
else: unreachable
|
||||
}
|
||||
}
|
||||
|
||||
integer_min proc(type Type) i64 {
|
||||
return match type {
|
||||
.i8: -128
|
||||
.i16: -32768
|
||||
.i32: -2147483648
|
||||
.int, .isize: minval!(i64)
|
||||
.u8, .u16, .u32, .u64, .usize: 0
|
||||
else: unreachable
|
||||
}
|
||||
}
|
||||
|
||||
ValueData :: union {
|
||||
@@ -74,6 +116,7 @@ Op :: enum {
|
||||
load_address
|
||||
store_address
|
||||
negate
|
||||
convert
|
||||
logical_not
|
||||
add
|
||||
subtract
|
||||
|
||||
@@ -136,6 +136,7 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
||||
}
|
||||
try io.write_all(output, ")\n")
|
||||
}
|
||||
.convert: result = try emit_conversion(state, instruction)
|
||||
.negate: {
|
||||
operand :: try load(state, instruction.left)
|
||||
if (instruction.type == .float) {
|
||||
@@ -143,10 +144,12 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
||||
} else {
|
||||
pair :: fresh(state)
|
||||
overflow :: fresh(state)
|
||||
try io.print(" %r{} = call {{ i64, i1 }} @llvm.ssub.with.overflow.i64(i64 0, i64 %r{})\n", output, {pair, operand})
|
||||
try io.print(" %r{} = extractvalue {{ i64, i1 }} %r{}, 1\n", output, {overflow, pair})
|
||||
bits :: ir.integer_bits(instruction.type)
|
||||
sign []u8 :: if (ir.unsigned_integer(instruction.type)) "u" else "s"
|
||||
try io.print(" %r{} = call {{ i{}, i1 }} @llvm.{s}sub.with.overflow.i{}(i{} 0, i{} %r{})\n", output, {pair, bits, sign, bits, bits, bits, operand})
|
||||
try io.print(" %r{} = extractvalue {{ i{}, i1 }} %r{}, 1\n", output, {overflow, bits, pair})
|
||||
try check_failure(state, overflow, .integer_overflow, instruction.span)
|
||||
try io.print(" %r{} = extractvalue {{ i64, i1 }} %r{}, 0\n", output, {result, pair})
|
||||
try io.print(" %r{} = extractvalue {{ i{}, i1 }} %r{}, 0\n", output, {result, bits, pair})
|
||||
}
|
||||
}
|
||||
.logical_not: {
|
||||
@@ -172,13 +175,14 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
||||
.multiply: "mul"
|
||||
else: unreachable
|
||||
}
|
||||
sign []u8 :: if (instruction.type == .usize) "u" else "s"
|
||||
bits :: ir.integer_bits(instruction.type)
|
||||
sign []u8 :: if (ir.unsigned_integer(instruction.type)) "u" else "s"
|
||||
pair :: fresh(state)
|
||||
overflow :: fresh(state)
|
||||
try io.print(" %r{} = call {{ i64, i1 }} @llvm.{s}{s}.with.overflow.i64(i64 %r{}, i64 %r{})\n", output, {pair, sign, opcode, left, right})
|
||||
try io.print(" %r{} = extractvalue {{ i64, i1 }} %r{}, 1\n", output, {overflow, pair})
|
||||
try io.print(" %r{} = call {{ i{}, i1 }} @llvm.{s}{s}.with.overflow.i{}(i{} %r{}, i{} %r{})\n", output, {pair, bits, sign, opcode, bits, bits, left, bits, right})
|
||||
try io.print(" %r{} = extractvalue {{ i{}, i1 }} %r{}, 1\n", output, {overflow, bits, pair})
|
||||
try check_failure(state, overflow, .integer_overflow, instruction.span)
|
||||
try io.print(" %r{} = extractvalue {{ i64, i1 }} %r{}, 0\n", output, {result, pair})
|
||||
try io.print(" %r{} = extractvalue {{ i{}, i1 }} %r{}, 0\n", output, {result, bits, pair})
|
||||
}
|
||||
}
|
||||
.equal, .not_equal, .less, .less_equal, .greater, .greater_equal: {
|
||||
@@ -199,7 +203,7 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
||||
} else {
|
||||
prefix []u8 := ""
|
||||
if (instruction.op != .equal and instruction.op != .not_equal) {
|
||||
if (operand_type.kind == .usize) prefix = "u"
|
||||
if (ir.unsigned_integer(operand_type.kind)) prefix = "u"
|
||||
else prefix = "s"
|
||||
}
|
||||
predicate []u8 :: match instruction.op {
|
||||
@@ -219,3 +223,69 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
||||
}
|
||||
try store(state, instruction.destination, result)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
emit_conversion proc(state @mut State, instruction ir.Instruction) usize ! io.WriteError {
|
||||
output :: state.output
|
||||
source :: state.values[usize(instruction.left)].kind
|
||||
destination :: instruction.type
|
||||
operand :: try load(state, instruction.left)
|
||||
if (source == .float and destination == .float) return operand
|
||||
source_bits :: ir.integer_bits(source)
|
||||
destination_bits :: ir.integer_bits(destination)
|
||||
source_unsigned :: ir.unsigned_integer(source)
|
||||
destination_unsigned :: ir.unsigned_integer(destination)
|
||||
if (destination == .float) {
|
||||
result :: fresh(state)
|
||||
opcode []u8 :: if (source_unsigned) "uitofp" else "sitofp"
|
||||
try io.print(" %r{} = {s} i{} %r{} to double\n", output, {result, opcode, source_bits, operand})
|
||||
return result
|
||||
}
|
||||
if (source == .float) {
|
||||
# compare the truncated value before fptosi/fptoui, whose out-of-range result is poison.
|
||||
truncated :: fresh(state)
|
||||
lower_valid :: fresh(state)
|
||||
upper_valid :: fresh(state)
|
||||
valid :: fresh(state)
|
||||
failed :: fresh(state)
|
||||
upper []u8 :: match destination {
|
||||
.i8: "1.280000e+02"
|
||||
.i16: "3.276800e+04"
|
||||
.i32: "2.147483648e+09"
|
||||
.int, .isize: "9.223372036854775808e+18"
|
||||
.u8: "2.560000e+02"
|
||||
.u16: "6.553600e+04"
|
||||
.u32: "4.294967296e+09"
|
||||
.u64, .usize: "1.8446744073709551616e+19"
|
||||
else: unreachable
|
||||
}
|
||||
try io.print(" %r{} = call double @llvm.trunc.f64(double %r{})\n", output, {truncated, operand})
|
||||
if (destination_unsigned) try io.print(" %r{} = fcmp oge double %r{}, 0.0\n", output, {lower_valid, truncated})
|
||||
else try io.print(" %r{} = fcmp oge double %r{}, -{s}\n", output, {lower_valid, truncated, upper})
|
||||
try io.print(" %r{} = fcmp olt double %r{}, {s}\n", output, {upper_valid, truncated, upper})
|
||||
try io.print(" %r{} = and i1 %r{}, %r{}\n", output, {valid, lower_valid, upper_valid})
|
||||
try io.print(" %r{} = xor i1 %r{}, true\n", output, {failed, valid})
|
||||
try check_failure(state, failed, .integer_overflow, instruction.span)
|
||||
result :: fresh(state)
|
||||
opcode []u8 :: if (destination_unsigned) "fptoui" else "fptosi"
|
||||
try io.print(" %r{} = {s} double %r{} to i{}\n", output, {result, opcode, truncated, destination_bits})
|
||||
return result
|
||||
}
|
||||
if (!source_unsigned and (destination_unsigned or destination_bits < source_bits)) {
|
||||
failed :: fresh(state)
|
||||
try io.print(" %r{} = icmp slt i{} %r{}, {}\n", output, {failed, source_bits, operand, ir.integer_min(destination)})
|
||||
try check_failure(state, failed, .integer_overflow, instruction.span)
|
||||
}
|
||||
if (destination_bits < source_bits or (source_unsigned and !destination_unsigned and destination_bits == source_bits)) {
|
||||
failed :: fresh(state)
|
||||
predicate []u8 :: if (source_unsigned) "ugt" else "sgt"
|
||||
try io.print(" %r{} = icmp {s} i{} %r{}, {}\n", output, {failed, predicate, source_bits, operand, ir.integer_max(destination)})
|
||||
try check_failure(state, failed, .integer_overflow, instruction.span)
|
||||
}
|
||||
if (source_bits == destination_bits) return operand
|
||||
result :: fresh(state)
|
||||
opcode []u8 := "trunc"
|
||||
if (source_bits < destination_bits) opcode = if (source_unsigned) "zext" else "sext"
|
||||
try io.print(" %r{} = {s} i{} %r{} to i{}\n", output, {result, opcode, source_bits, operand, destination_bits})
|
||||
return result
|
||||
}
|
||||
|
||||
+15
-4
@@ -123,7 +123,10 @@ bytes_constant proc(output io.Writer, bytes []u8) void ! io.WriteError {
|
||||
@hide
|
||||
write_type proc(output io.Writer, module @ir.Module, type Type) void ! io.WriteError {
|
||||
match type.kind {
|
||||
.int, .usize: try io.write_all(output, "i64")
|
||||
.i8, .u8: try io.write_all(output, "i8")
|
||||
.i16, .u16: try io.write_all(output, "i16")
|
||||
.i32, .u32: try io.write_all(output, "i32")
|
||||
.int, .isize, .u64, .usize: try io.write_all(output, "i64")
|
||||
.float: try io.write_all(output, "double")
|
||||
.bool: try io.write_all(output, "i1")
|
||||
.string: try io.write_all(output, "%string")
|
||||
@@ -243,7 +246,15 @@ emit_global proc(output io.Writer, module @ir.Module, global ir.Global, index us
|
||||
@hide:file
|
||||
print_call proc(output io.Writer, module @ir.Module, type Type, operand []u8) void ! io.WriteError {
|
||||
match type.kind {
|
||||
.int, .usize: try io.print(" call void @print_integer(i32 1, i64 {s}, i1 {})\n", output, {operand, type.kind == .usize})
|
||||
.i8, .i16, .i32, .int, .isize, .u8, .u16, .u32, .u64, .usize: {
|
||||
bits :: ir.integer_bits(type.kind)
|
||||
unsigned :: ir.unsigned_integer(type.kind)
|
||||
if (bits < 64) {
|
||||
extension []u8 :: if (unsigned) "zext" else "sext"
|
||||
try io.print(" %print_value = {s} i{} {s} to i64\n", output, {extension, bits, operand})
|
||||
try io.print(" call void @print_integer(i32 1, i64 %print_value, i1 {})\n", output, {unsigned})
|
||||
} else try io.print(" call void @print_integer(i32 1, i64 {s}, i1 {})\n", output, {operand, unsigned})
|
||||
}
|
||||
.float: try io.print(" call void @print_float(double {s})\n", output, {operand})
|
||||
.bool: try io.print(" call void @print_bool(i1 {s})\n", output, {operand})
|
||||
.string: try io.print(" call void @print_string(%string {s})\n", output, {operand})
|
||||
@@ -334,8 +345,8 @@ check_failure proc(state @mut State, condition usize, code diagnostics.Code, spa
|
||||
@hide
|
||||
write_constant proc(output io.Writer, module @ir.Module, value ir.Value) void ! io.WriteError {
|
||||
match value.type {
|
||||
.int: try io.print("{}", output, {value.data.integer})
|
||||
.usize: try io.print("{}", output, {value.data.unsigned})
|
||||
.i8, .i16, .i32, .int, .isize: try io.print("{}", output, {value.data.integer})
|
||||
.u8, .u16, .u32, .u64, .usize: try io.print("{}", output, {value.data.unsigned})
|
||||
.float: {
|
||||
bits :: ptrcast!(@u64, &value.data.real)^
|
||||
try io.print("bitcast (i64 {} to double)", output, {bits})
|
||||
|
||||
@@ -7,6 +7,25 @@ runtime ::
|
||||
`declare ptr @__error()
|
||||
`declare i32 @snprintf(ptr, i64, ptr, ...)
|
||||
`declare void @exit(i32) noreturn
|
||||
`declare double @llvm.trunc.f64(double)
|
||||
`declare { i8, i1 } @llvm.sadd.with.overflow.i8(i8, i8)
|
||||
`declare { i8, i1 } @llvm.ssub.with.overflow.i8(i8, i8)
|
||||
`declare { i8, i1 } @llvm.smul.with.overflow.i8(i8, i8)
|
||||
`declare { i8, i1 } @llvm.uadd.with.overflow.i8(i8, i8)
|
||||
`declare { i8, i1 } @llvm.usub.with.overflow.i8(i8, i8)
|
||||
`declare { i8, i1 } @llvm.umul.with.overflow.i8(i8, i8)
|
||||
`declare { i16, i1 } @llvm.sadd.with.overflow.i16(i16, i16)
|
||||
`declare { i16, i1 } @llvm.ssub.with.overflow.i16(i16, i16)
|
||||
`declare { i16, i1 } @llvm.smul.with.overflow.i16(i16, i16)
|
||||
`declare { i16, i1 } @llvm.uadd.with.overflow.i16(i16, i16)
|
||||
`declare { i16, i1 } @llvm.usub.with.overflow.i16(i16, i16)
|
||||
`declare { i16, i1 } @llvm.umul.with.overflow.i16(i16, i16)
|
||||
`declare { i32, i1 } @llvm.sadd.with.overflow.i32(i32, i32)
|
||||
`declare { i32, i1 } @llvm.ssub.with.overflow.i32(i32, i32)
|
||||
`declare { i32, i1 } @llvm.smul.with.overflow.i32(i32, i32)
|
||||
`declare { i32, i1 } @llvm.uadd.with.overflow.i32(i32, i32)
|
||||
`declare { i32, i1 } @llvm.usub.with.overflow.i32(i32, i32)
|
||||
`declare { i32, i1 } @llvm.umul.with.overflow.i32(i32, i32)
|
||||
`declare { i64, i1 } @llvm.sadd.with.overflow.i64(i64, i64)
|
||||
`declare { i64, i1 } @llvm.ssub.with.overflow.i64(i64, i64)
|
||||
`declare { i64, i1 } @llvm.smul.with.overflow.i64(i64, i64)
|
||||
|
||||
+34
-14
@@ -126,6 +126,14 @@ lower_type proc(type typecheck.Type) ir.Type {
|
||||
return match type.kind {
|
||||
.int: .int
|
||||
.usize: .usize
|
||||
.i8: .i8
|
||||
.i16: .i16
|
||||
.i32: .i32
|
||||
.isize: .isize
|
||||
.u8: .u8
|
||||
.u16: .u16
|
||||
.u32: .u32
|
||||
.u64: .u64
|
||||
.array: .array
|
||||
.float: .float
|
||||
.bool: .bool
|
||||
@@ -325,7 +333,7 @@ predeclare_locals proc(state @mut State, block ast.NodeId) void ! (mem.AllocErro
|
||||
.for_stmt: {
|
||||
loop :: ast.for_stmt(state.tree, statement)
|
||||
family :: typecheck.type_of(state.checked, loop.iterable).range_element
|
||||
type ir.Type :: if (family == .usize) .usize else .int
|
||||
type :: lower_type(typecheck.Type{ kind = family })
|
||||
slot :: try ir.add_local(state.module, ir.FunctionId(state.function), type, ir.no_array)
|
||||
state.storage[usize(statement)] = Storage{ kind = .local, index = u32(slot) }
|
||||
if (loop.capture) |capture| state.storage[usize(capture)] = state.storage[usize(statement)]
|
||||
@@ -498,8 +506,8 @@ lower_for proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnos
|
||||
kind = .branch, value = finished, target = exit, alternative = increment, span = interval.span,
|
||||
}
|
||||
state.block = increment
|
||||
unit := ir.Value{ type = .int, data = ir.ValueData{ integer = 1 } }
|
||||
if (family == .usize) unit = ir.Value{ type = .usize, data = ir.ValueData{ unsigned = 1 } }
|
||||
unit := ir.Value{ type = family, data = ir.ValueData{ integer = 1 } }
|
||||
if (ir.unsigned_integer(family)) unit.data = ir.ValueData{ unsigned = 1 }
|
||||
one :: try constant(state, unit, interval.span)
|
||||
next :: try emit(state, ir.Instruction{ op = .add, type = family, left = last, right = one, span = interval.span })
|
||||
_ = try emit(state, ir.Instruction{ op = .store_local, type = family, left = next, slot = slot, span = interval.span })
|
||||
@@ -776,11 +784,12 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
||||
}
|
||||
.unary: {
|
||||
expression :: ast.unary_expr(tree, id)
|
||||
if (expression.operator == .negate and type == .int and ast.expr_kind(tree, expression.operand) == .literal_int) {
|
||||
if (expression.operator == .negate and ir.integer_bits(type) != 0 and !ir.unsigned_integer(type) and ast.expr_kind(tree, expression.operand) == .literal_int) {
|
||||
text :: ast.source_text(tree, ast.node_span(tree, expression.operand))
|
||||
if (integer_magnitude(text, u64(maxval!(i64)) + 1)) |magnitude| {
|
||||
if (magnitude == u64(maxval!(i64)) + 1) {
|
||||
return try constant(state, ir.Value{ type = .int, data = ir.ValueData{ integer = minval!(i64) } }, span)
|
||||
magnitude_limit :: ir.integer_max(type) + 1
|
||||
if (integer_magnitude(text, magnitude_limit)) |magnitude| {
|
||||
if (magnitude == magnitude_limit) {
|
||||
return try constant(state, ir.Value{ type = type, data = ir.ValueData{ integer = ir.integer_min(type) } }, span)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -817,6 +826,17 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
||||
return try emit(state, ir.Instruction{ op = op, type = type, left = left, right = right, span = span })
|
||||
}
|
||||
.call: {
|
||||
target :: sema.binding(state.resolution, ast.call_expr(tree, id).callee)
|
||||
if (target.kind == .builtin) {
|
||||
operand := ir.no_value
|
||||
for (0..ast.call_arg_count(tree, id)) |i| {
|
||||
value :: try lower_expression(state, ast.call_arg(tree, id, i))
|
||||
if (state.terminated) return ir.no_value
|
||||
if (i == 0) operand = value
|
||||
}
|
||||
if (trap_type(state, id)) return ir.no_value
|
||||
return try emit(state, ir.Instruction{ op = .convert, type = type, left = operand, span = span })
|
||||
}
|
||||
callee :: try lower_expression(state, ast.call_expr(tree, id).callee)
|
||||
if (state.terminated) return ir.no_value
|
||||
# recursion borrows later scratch slots; no items pointer survives a child.
|
||||
@@ -856,7 +876,7 @@ integer_magnitude proc(text []u8, limit u64) ?u64 {
|
||||
for (text) |byte| {
|
||||
if (byte < '0' or byte > '9') return null
|
||||
digit :: u64(byte - '0')
|
||||
if (value > divtrunc!(limit - digit, u64(10))) return null
|
||||
if (digit > limit or value > divtrunc!(limit - digit, u64(10))) return null
|
||||
value = value * 10 + digit
|
||||
}
|
||||
return value
|
||||
@@ -878,13 +898,13 @@ lower_number proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError
|
||||
span :: ast.node_span(state.tree, id)
|
||||
text :: ast.source_text(state.tree, span)
|
||||
type :: lower_type(typecheck.type_of(state.checked, id))
|
||||
if (type == .int) {
|
||||
if (integer_magnitude(text, u64(maxval!(i64)))) |magnitude| {
|
||||
return try constant(state, ir.Value{ type = .int, data = ir.ValueData{ integer = i64(magnitude) } }, span)
|
||||
if (ir.integer_bits(type) != 0 and !ir.unsigned_integer(type)) {
|
||||
if (integer_magnitude(text, ir.integer_max(type))) |magnitude| {
|
||||
return try constant(state, ir.Value{ type = type, data = ir.ValueData{ integer = i64(magnitude) } }, span)
|
||||
}
|
||||
} else if (type == .usize) {
|
||||
if (integer_magnitude(text, maxval!(u64))) |magnitude| {
|
||||
return try constant(state, ir.Value{ type = .usize, data = ir.ValueData{ unsigned = magnitude } }, span)
|
||||
} else if (ir.unsigned_integer(type)) {
|
||||
if (integer_magnitude(text, ir.integer_max(type))) |magnitude| {
|
||||
return try constant(state, ir.Value{ type = type, data = ir.ValueData{ unsigned = magnitude } }, span)
|
||||
}
|
||||
} else {
|
||||
value :: strconv.parse_float(state.module.allocator, text) catch |err| {
|
||||
|
||||
+2
-2
@@ -203,8 +203,8 @@ print_result proc(output io.Writer, module @ir.Module, value ir.Value) void ! io
|
||||
@hide:file
|
||||
print_value proc(output io.Writer, module @ir.Module, value ir.Value) void ! io.WriteError {
|
||||
match value.type {
|
||||
.int: try io.print("{}", output, {value.data.integer})
|
||||
.usize: try io.print("{}", output, {value.data.unsigned})
|
||||
.int, .i8, .i16, .i32, .isize: try io.print("{}", output, {value.data.integer})
|
||||
.usize, .u8, .u16, .u32, .u64: try io.print("{}", output, {value.data.unsigned})
|
||||
.float: try io.print("{d}", output, {value.data.real})
|
||||
.bool: try io.print("{}", output, {value.data.boolean})
|
||||
.string: try io.print("{s}", output, {ir.string_bytes(module, value.data.string)})
|
||||
|
||||
+110
-23
@@ -18,6 +18,14 @@ TypeKind :: enum {
|
||||
range,
|
||||
array,
|
||||
usize,
|
||||
i8,
|
||||
i16,
|
||||
i32,
|
||||
isize,
|
||||
u8,
|
||||
u16,
|
||||
u32,
|
||||
u64,
|
||||
}
|
||||
|
||||
ArrayId :: distinct u32
|
||||
@@ -84,6 +92,7 @@ Cell :: struct {
|
||||
numeric bool = false
|
||||
anchored bool = false
|
||||
demand TypeKind = .none
|
||||
conversion_unsigned bool = false
|
||||
fixed bool = false
|
||||
local bool = false
|
||||
assignment_value bool = false
|
||||
@@ -224,6 +233,7 @@ check proc(
|
||||
for (0..count) |i| {
|
||||
id :: ast.NodeId(i)
|
||||
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .call) continue
|
||||
if (conversion_kind(&state, id) != .none) continue
|
||||
callee :: ast.call_expr(tree, id).callee
|
||||
signature :: try infer_shape(&state, callee, false)
|
||||
if (signature.function != ast.NodeId(maxval!(u32))) {
|
||||
@@ -236,6 +246,7 @@ check proc(
|
||||
for (0..count) |i| {
|
||||
id :: ast.NodeId(i)
|
||||
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .call) continue
|
||||
if (conversion_kind(&state, id) != .none) continue
|
||||
callee :: ast.call_expr(tree, id).callee
|
||||
signature :: group_type(&state, callee)
|
||||
if (signature.function != ast.NodeId(maxval!(u32))) continue
|
||||
@@ -314,7 +325,8 @@ check proc(
|
||||
if (root(&state, i) != i or cells[i].assignment_value != assignment_values) continue
|
||||
if (cells[i].type.kind == .invalid) continue
|
||||
if (cells[i].numeric and cells[i].anchored) {
|
||||
kind TypeKind :: if (cells[i].demand != .none) cells[i].demand else .int
|
||||
kind TypeKind :: if (cells[i].demand != .none) cells[i].demand
|
||||
else if (cells[i].conversion_unsigned) .u64 else .int
|
||||
cells[i].type = Type{ kind = kind }
|
||||
}
|
||||
}
|
||||
@@ -323,6 +335,7 @@ check proc(
|
||||
for (0..count) |i| {
|
||||
id :: ast.NodeId(i)
|
||||
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .call) continue
|
||||
if (conversion_kind(&state, id) != .none) continue
|
||||
callee :: ast.call_expr(tree, id).callee
|
||||
signature :: group_type(&state, callee)
|
||||
if (signature.function != ast.NodeId(maxval!(u32)) or signature.kind == .none or signature.kind == .invalid) continue
|
||||
@@ -505,6 +518,7 @@ join proc(state @mut State, left, right ast.NodeId) void {
|
||||
state.cells[a].numeric = state.cells[a].numeric or state.cells[b].numeric
|
||||
state.cells[a].anchored = state.cells[a].anchored or state.cells[b].anchored
|
||||
if (state.cells[a].demand == .none) state.cells[a].demand = state.cells[b].demand
|
||||
state.cells[a].conversion_unsigned = state.cells[a].conversion_unsigned or state.cells[b].conversion_unsigned
|
||||
if (state.cells[a].literal == ast.NodeId(maxval!(u32))) state.cells[a].literal = state.cells[b].literal
|
||||
if (state.cells[a].producer == ast.NodeId(maxval!(u32))) state.cells[a].producer = state.cells[b].producer
|
||||
}
|
||||
@@ -563,14 +577,7 @@ check_annotation proc(state @mut State, id ast.NodeId, allow_void bool) Type ! (
|
||||
match binding.kind {
|
||||
.invalid: type = invalid_type(binding.data.diagnostic)
|
||||
.builtin: {
|
||||
type.kind = match binding.data.builtin {
|
||||
.int, .i64: .int
|
||||
.float, .f64: .float
|
||||
.usize: .usize
|
||||
.bool: .bool
|
||||
.string: .string
|
||||
.void: .void
|
||||
}
|
||||
type.kind = builtin_kind(binding.data.builtin)
|
||||
if (type.kind == .void and !allow_void) {
|
||||
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_void_type, ast.node_span(state.tree, id)))
|
||||
}
|
||||
@@ -813,8 +820,16 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
||||
}
|
||||
.call: {
|
||||
callee :: ast.call_expr(tree, id).callee
|
||||
try collect_expression(state, callee)
|
||||
try depend(state, callee, id)
|
||||
destination :: conversion_kind(state, id)
|
||||
if (destination != .none) {
|
||||
state.cells[usize(callee)].role = .annotation
|
||||
state.cells[usize(callee)].type = Type{ kind = destination }
|
||||
state.types[usize(callee)] = Type{ kind = destination }
|
||||
state.cells[usize(id)].type = Type{ kind = destination }
|
||||
} else {
|
||||
try collect_expression(state, callee)
|
||||
try depend(state, callee, id)
|
||||
}
|
||||
for (0..ast.call_arg_count(tree, id)) |i| {
|
||||
argument :: ast.call_arg(tree, id, i)
|
||||
try collect_expression(state, argument)
|
||||
@@ -849,9 +864,51 @@ declaration_reference proc(tree @ast.Tree, id ast.NodeId) bool {
|
||||
return kind == .identifier or kind == .selection
|
||||
}
|
||||
|
||||
@hide:file
|
||||
integer_kind proc(kind TypeKind) bool {
|
||||
return (
|
||||
kind == .int or kind == .i8 or kind == .i16 or kind == .i32 or kind == .isize
|
||||
or unsigned_kind(kind)
|
||||
)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
unsigned_kind proc(kind TypeKind) bool {
|
||||
return kind == .u8 or kind == .u16 or kind == .u32 or kind == .u64 or kind == .usize
|
||||
}
|
||||
|
||||
@hide:file
|
||||
numeric_kind proc(kind TypeKind) bool {
|
||||
return kind == .int or kind == .float or kind == .usize
|
||||
return integer_kind(kind) or kind == .float
|
||||
}
|
||||
|
||||
@hide:file
|
||||
builtin_kind proc(type sema.BuiltinType) TypeKind {
|
||||
return match type {
|
||||
.int, .i64: .int
|
||||
.float, .f64: .float
|
||||
.i8: .i8
|
||||
.i16: .i16
|
||||
.i32: .i32
|
||||
.isize: .isize
|
||||
.u8: .u8
|
||||
.u16: .u16
|
||||
.u32: .u32
|
||||
.u64, .uint: .u64
|
||||
.usize: .usize
|
||||
.bool: .bool
|
||||
.string: .string
|
||||
.void: .void
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
conversion_kind proc(state @State, id ast.NodeId) TypeKind {
|
||||
callee :: ast.call_expr(state.tree, id).callee
|
||||
binding :: sema.binding(state.resolution, callee)
|
||||
if (binding.kind != .builtin) return .none
|
||||
kind :: builtin_kind(binding.data.builtin)
|
||||
return if (numeric_kind(kind)) kind else .none
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -1072,7 +1129,7 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
||||
return
|
||||
}
|
||||
operand :: state.types[usize(expression.operand)]
|
||||
if (operand.kind != .invalid and (!numeric_kind(operand.kind) or operand.kind == .usize)) {
|
||||
if (operand.kind != .invalid and (!numeric_kind(operand.kind) or unsigned_kind(operand.kind))) {
|
||||
try fail(state, id, .invalid_operand, ast.node_span(tree, id))
|
||||
}
|
||||
}
|
||||
@@ -1098,11 +1155,12 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
||||
expression :: ast.range_expr(tree, id)
|
||||
start_type :: state.types[usize(expression.start)]
|
||||
end_type :: state.types[usize(expression.end)]
|
||||
family TypeKind :: if (start_type.kind == .usize or end_type.kind == .usize) .usize else .int
|
||||
if (start_type.kind != .invalid and start_type.kind != family) {
|
||||
family TypeKind :: if (integer_kind(start_type.kind)) start_type.kind
|
||||
else if (integer_kind(end_type.kind)) end_type.kind else .int
|
||||
if (start_type.kind != .invalid and (!integer_kind(start_type.kind) or start_type.kind != family)) {
|
||||
try fail(state, expression.start, .invalid_range_bound, ast.node_span(tree, expression.start))
|
||||
}
|
||||
if (end_type.kind != .invalid and end_type.kind != family) {
|
||||
if (end_type.kind != .invalid and (!integer_kind(end_type.kind) or end_type.kind != family)) {
|
||||
try fail(state, expression.end, .invalid_range_bound, ast.node_span(tree, expression.end))
|
||||
}
|
||||
}
|
||||
@@ -1143,6 +1201,19 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
||||
validate_call proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
tree :: state.tree
|
||||
expression :: ast.call_expr(tree, id)
|
||||
if (conversion_kind(state, id) != .none) {
|
||||
if (ast.call_arg_count(tree, id) != 1) {
|
||||
try fail(state, id, .argument_count_mismatch, expression.span)
|
||||
}
|
||||
for 0..ast.call_arg_count(tree, id) |i| {
|
||||
argument :: ast.call_arg(tree, id, i)
|
||||
actual :: state.types[usize(argument)]
|
||||
if (actual.kind != .invalid and !numeric_kind(actual.kind)) {
|
||||
try fail(state, argument, .invalid_operand, ast.node_span(tree, argument))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
callee :: state.types[usize(expression.callee)]
|
||||
signature :: group_type(state, expression.callee)
|
||||
if (signature.function == ast.NodeId(maxval!(u32))) {
|
||||
@@ -1219,9 +1290,7 @@ constant_integer proc(state @mut State, id ast.NodeId) ?ConstantInteger {
|
||||
if (declaration.mutability != .constant) return null
|
||||
if (declaration.type) |annotation| {
|
||||
type_binding :: sema.binding(state.resolution, annotation)
|
||||
if (type_binding.kind != .builtin) return null
|
||||
kind :: type_binding.data.builtin
|
||||
if (kind != .int and kind != .i64 and kind != .usize) return null
|
||||
if (type_binding.kind != .builtin or !integer_kind(builtin_kind(type_binding.data.builtin))) return null
|
||||
}
|
||||
return constant_integer(state, declaration.initializer)
|
||||
}
|
||||
@@ -1338,6 +1407,13 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
|
||||
if (state.cells[index].type.kind == .invalid) return state.cells[index].type
|
||||
state.cells[index].visiting = true
|
||||
defer state.cells[index].visiting = false
|
||||
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .call) {
|
||||
destination :: conversion_kind(state, id)
|
||||
if (destination != .none) {
|
||||
seed(state, id, Type{ kind = destination })
|
||||
return group_type(state, id)
|
||||
}
|
||||
}
|
||||
if (state.cells[usize(id)].role == .expression and declaration_reference(state.tree, id)) {
|
||||
binding :: sema.binding(state.resolution, id)
|
||||
if (binding.kind == .declaration) {
|
||||
@@ -1435,6 +1511,17 @@ apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem
|
||||
if (mutable_target(state, view.target)) try context_demand(state, view.value, state.types[usize(view.target)], .type_mismatch, early)
|
||||
}
|
||||
if (state.cells[i].role != .expression or state.cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .call) continue
|
||||
if (conversion_kind(state, id) != .none) {
|
||||
for 0..ast.call_arg_count(tree, id) |j| {
|
||||
argument :: ast.call_arg(tree, id, j)
|
||||
if (constant_integer(state, argument)) |value| {
|
||||
if (!value.negative and value.magnitude > u64(maxval!(i64))) {
|
||||
state.cells[root(state, usize(argument))].conversion_unsigned = true
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
signature :: group_type(state, ast.call_expr(tree, id).callee)
|
||||
if (signature.function == ast.NodeId(maxval!(u32))) continue
|
||||
count :: ast.function_param_count(tree, signature.function)
|
||||
@@ -1448,7 +1535,7 @@ apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem
|
||||
|
||||
@hide:file
|
||||
context_demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code, early bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
if (early and expected.kind != .array and expected.kind != .usize) return
|
||||
if (early and expected.kind != .array and !integer_kind(expected.kind)) return
|
||||
try demand(state, id, expected, code)
|
||||
}
|
||||
|
||||
@@ -1466,8 +1553,8 @@ bound_family proc(state @mut State, id ast.NodeId) TypeKind {
|
||||
left :: bound_family(state, view.left)
|
||||
right :: bound_family(state, view.right)
|
||||
if (left == .float or right == .float) return .float
|
||||
if (left == .usize or right == .usize) return .usize
|
||||
if (left == .int or right == .int) return .int
|
||||
if (integer_kind(left)) return left
|
||||
if (integer_kind(right)) return right
|
||||
return .none
|
||||
}
|
||||
|
||||
@@ -1480,7 +1567,7 @@ prepare_ranges proc(state @mut State, assignment_values, constrain bool) void !
|
||||
view :: ast.range_expr(tree, id)
|
||||
left :: bound_family(state, view.start)
|
||||
right :: bound_family(state, view.end)
|
||||
family TypeKind :: if (left == .usize or right == .usize) .usize else .int
|
||||
family TypeKind :: if (integer_kind(left)) left else if (integer_kind(right)) right else .int
|
||||
if (constrain) {
|
||||
try range_bound_context(state, view.start, family)
|
||||
try range_bound_context(state, view.end, family)
|
||||
|
||||
@@ -11,6 +11,8 @@ BuiltinType :: enum {
|
||||
bool, string,
|
||||
void, i64,
|
||||
f64, usize,
|
||||
i8, i16, i32, isize,
|
||||
u8, u16, u32, u64, uint,
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -29,6 +31,15 @@ builtins [_]Builtin :: [
|
||||
{ name = "i64", type = .i64 },
|
||||
{ name = "f64", type = .f64 },
|
||||
{ name = "usize", type = .usize },
|
||||
{ name = "i8", type = .i8 },
|
||||
{ name = "i16", type = .i16 },
|
||||
{ name = "i32", type = .i32 },
|
||||
{ name = "isize", type = .isize },
|
||||
{ name = "u8", type = .u8 },
|
||||
{ name = "u16", type = .u16 },
|
||||
{ name = "u32", type = .u32 },
|
||||
{ name = "u64", type = .u64 },
|
||||
{ name = "uint", type = .uint },
|
||||
]
|
||||
|
||||
@hide:file
|
||||
|
||||
Reference in New Issue
Block a user