feat: add integer widths and checked numeric conversions
This commit is contained in:
@@ -95,13 +95,13 @@ indices require `usize`; integer literals and flexible inferred values receive t
|
||||
|
||||
`while` repeats a block or single statement while its `bool` condition holds, re-evaluating the condition on every iteration. guards follow the same braced/braceless convention as `if`. loop bodies introduce child scopes; their locals, including locals in nested conditional arms, begin uninitialized on each scope entry.
|
||||
|
||||
`for 0..10 |i| { ... }` iterates an ascending, upper-exclusive integer range; `..=` includes the upper bound. a capture is required: `|i|` binds an immutable value with the bounds' integer family (`int` or `usize`) in the body scope, while `|_|` discards it. bounds resolve in the enclosing scope and are evaluated left-to-right once, so changing a bound variable in the body does not change the range. reversed ranges are empty; equal bounds produce zero iterations with `..` and one with `..=`. inclusive ranges terminate safely at the maximum integer.
|
||||
`for 0..10 |i| { ... }` iterates an ascending, upper-exclusive integer range; `..=` includes the upper bound. a capture is required: `|i|` binds an immutable value with the bounds' exact integer type in the body scope, while `|_|` discards it. bounds resolve in the enclosing scope and are evaluated left-to-right once, so changing a bound variable in the body does not change the range. reversed ranges are empty; equal bounds produce zero iterations with `..` and one with `..=`. inclusive ranges terminate safely at the maximum of each integer type.
|
||||
|
||||
range bounds that are compound or unary expressions require their own parentheses, such as `(-2)..(limit + 1)`. a braceless body also requires parentheses around the entire iterable: `for (0..10) |i| total = total + i`. current execution supports direct integer ranges only; first-class range values, collection iteration, pointer captures and index captures remain unimplemented. [programs/range_loop.hon](programs/range_loop.hon) skips `5` while summing `0..10`; `./build/honey run programs/range_loop.hon` prints `40`.
|
||||
|
||||
`break` exits the nearest enclosing `for` or `while`; `continue` restarts a `while` condition or advances a `for` range through its guarded update. both are operand-free statements permitted within loop bodies and nested conditional arms; labels and values are unsupported. controls outside loops produce keyword-spanned diagnostics, including in untaken or unreachable code, and trap only if reached. code after a transfer is still resolved and type-checked but does not execute on that path. body locals are reset on re-entry after `continue`; loop transfers do not guarantee a function return. both controls lower to existing IR jumps, without new interpreter instructions or loop frames. [programs/loop_control.hon](programs/loop_control.hon) skips an iteration and exits early; `./build/honey run programs/loop_control.hon` prints `12`.
|
||||
|
||||
name resolution (`@source/sema`) runs after parsing. it predeclares package names and each block's direct locals, allowing forward references and mutually recursive functions. parameters and top-level function-body locals share a scope; each conditional arm and loop body introduces a child scope whose names do not escape it. signatures resolve names in the enclosing package scope. shadowing is forbidden, including reuse of builtin type names (`int`, `float`, `i64`, `f64`, `usize`, `bool`, `string`, `void`); sibling scopes may independently reuse names.
|
||||
name resolution (`@source/sema`) runs after parsing. it predeclares package names and each block's direct locals, allowing forward references and mutually recursive functions. parameters and top-level function-body locals share a scope; each conditional arm and loop body introduces a child scope whose names do not escape it. signatures resolve names in the enclosing package scope. shadowing is forbidden, including reuse of builtin type names; sibling scopes may independently reuse names.
|
||||
|
||||
the driver uses `@source/compilation` to own loaded source buffers, copied paths, one AST arena, the package graph and a shared identifier pool. `add_source` transfers its `ArrayList(u8)` allocation only on success, resetting the caller's list; infrastructure failure leaves the bytes caller-owned and rolls back the source registration, partial AST and source diagnostics. interned names may remain in the shared pool. `deinit` releases all retained frontend storage. metadata borrows are invalidated by registration growth, but source/package/node IDs remain stable within their compilation.
|
||||
|
||||
@@ -119,10 +119,12 @@ function fallthrough obligations are separate from body types. `check.fallthroug
|
||||
|
||||
the checker keeps union-find parent links and group sizes in a compact array separate from per-node inference metadata. root walks and path compression access only that link storage; both temporary arrays are released before `check.check` returns.
|
||||
|
||||
numeric typing follows contextual rules within Honey's current `int` and `float` families. integer literals can adopt a supported numeric family only when their value is exactly representable; float literals remain float and opt into ordinary IEEE rounding. numeric demands propagate through arithmetic and inferred bindings before undemanded integers default to `int`. conflicting demands are errors; fixed `int`/`i64`, `usize`, and `float`/`f64` values do not implicitly convert between families. `+`, `-`, `*` and unary `-` require compatible numeric operands; `/` requires and supplies float context. `3 / 2`, `3.0 / 2`, and `ratio float :: 3 / 2` are valid. `value f64 :: 9007199254740993` is diagnosed, including when float context arrives through aliases, division, or array elements; `9007199254740993.0` explicitly permits rounding. exactly representable integers beyond the signed/unsigned integer ranges remain valid in float context. explicit numeric casts are not yet implemented.
|
||||
numeric types include `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`, `isize`, `usize` and `f64`. on the supported 64-bit target, `isize` and `usize` are signed and unsigned 64-bit types. current `int`/`i64`, `uint`/`u64` and `float`/`f64` annotations share their runtime types; other concrete types remain distinct even when their widths coincide. integer literals adopt numeric context through arithmetic, inferred bindings, arrays and calls before undemanded integers default to `int`. fixed values never convert implicitly between types. integer arithmetic checks the selected type's bounds; unsigned negation is invalid. `/` requires and supplies float context. implicit integer-to-float literal context requires exact representation: `value f64 :: 9007199254740993` is diagnosed, while `9007199254740993.0` permits IEEE rounding.
|
||||
|
||||
the conversion policy is value-preserving by default: any future implicit widening must preserve every value of the source type, not merely the value observed at one use. narrowing, potentially inexact integer-to-float conversion, float-to-integer conversion, and signedness changes that cannot preserve the full source range require an explicit operation. integer arithmetic remains checked rather than wrapping; floating arithmetic retains IEEE rounding.
|
||||
|
||||
explicit numeric conversion uses `T(value)`, such as `u8(index)`, `usize(byte)` or `f64(count)`, with exactly one numeric argument. integer conversions trap with `integer arithmetic overflow` if the value is outside the destination range; float-to-integer conversion truncates toward zero, then checks bounds, rejecting NaN and infinity. integer-to-float conversion permits rounding, including `f64(9007199254740993)`. operands retain their independent types rather than being narrowed by the result context; a positive constant integer conversion operand above `i64`'s maximum defaults to `u64`. boolean conversions, arbitrary-width integers, `f32`, C scalar types and implicit typed-value widening are not implemented. native storage uses LLVM `i8`/`i16`/`i32`/`i64`, including array elements and function parameters/results.
|
||||
|
||||
`@std/strconv.parse_float(allocator, text)` parses a complete borrowed `[]u8` decimal into `f64`. it accepts an optional sign, decimal point, and exponent; whitespace, embedded NUL bytes, trailing input, hexadecimal, infinities, and NaNs return `invalid_number`. finite subnormals and signed zero are preserved; overflow and nonzero values rounded to zero return `out_of_range`. allocation failures propagate as `out_of_memory`, and temporary storage is always released. the Darwin backend uses libc's C-locale conversion without changing the process locale; rounding follows the caller's floating-point rounding mode. numeric lowering uses this interface, keeping exact integer-to-float validation compiler-specific.
|
||||
|
||||
type errors carry source spans and diagnostic identities; invalid operands propagate their existing identity without duplicate errors, while independent expressions, arguments and nested bodies are still checked. inference cycles without a type anchor are diagnosed. function signatures are checked independently of their bodies. bare `return` and same-line `return expression` are supported inside function bodies and must match the declared result type. nonvoid return-path checking is structural: every path must return or terminate in a diagnostic trap, and both conditional successors are considered without constant folding. a missing `else` or a loop permits fallthrough, even for a literal `true` condition; a later return can close that path. statements after a return and untaken branches or loops are still type-checked but are not executed.
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
## current boundary
|
||||
|
||||
directory packages support file-local import namespaces and postfix declaration
|
||||
selection through the interpreter and native backend. the richer types/control
|
||||
flow used by the compiler remain unsupported. the driver retains frontend storage
|
||||
selection through the interpreter and native backend. concrete 8/16/32/64-bit
|
||||
integers and checked numeric conversions work through both execution paths;
|
||||
records and the richer types/control flow used by the compiler remain unsupported.
|
||||
the driver retains frontend storage
|
||||
and source provenance through `@source/compilation`, with compilation-wide AST node identities.
|
||||
file commands retain single-file behavior; directory commands compile the
|
||||
package's production files and load its dependencies in separate scopes.
|
||||
@@ -32,6 +34,8 @@ general record fields, imported types, test discovery or FFI.
|
||||
## subsequent dependency gates
|
||||
|
||||
- [ ] concrete data models: required integer widths/conversions, records/tuples and construction, fields, enums, distinct/alias types, unions and matching. examples: `source/lexer/token.hon`, `source/sema/resolve.hon`, `std/mem/mem.hon`.
|
||||
- [x] native integer widths (`i8`–`i64`, `u8`–`u64`, `isize`/`usize`) and checked explicit numeric conversions, including float truncation/range checks and integer-to-float rounding. preserve exact types through arrays, calls and range captures; retain strict implicit conversion rules.
|
||||
- [ ] records/tuples and construction, fields, enums, distinct/alias types, unions and matching.
|
||||
- [ ] memory representation: source pointers/references, mutable and sentinel slices, optionals, layout/cast builtins and allocator-required libc calls. internal IR array addresses are not source pointers. examples: `std/mem/mem.hon`, `std/arraylist/arraylist.hon`.
|
||||
- [ ] fallible results and cleanup: error-set composition, `try`/`catch`, `defer`/`errdefer`; infrastructure errors remain distinct from diagnostic traps. examples: `source/main.hon:41`, `source/sema/check/check.hon:117`.
|
||||
- [ ] generic specialization and compile-time execution/reflection sufficient for existing `ArrayList`, hash maps, `enummap`, `meta` and `io.print`; include type/value parameters and compile-time generated storage.
|
||||
|
||||
@@ -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)})
|
||||
|
||||
+108
-21
@@ -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
|
||||
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
|
||||
|
||||
+117
-3
@@ -230,6 +230,120 @@ CASES = {
|
||||
),
|
||||
}
|
||||
|
||||
# Exercise each representation through storage, calls, ranges and checked updates.
|
||||
for integer_type, bits, signed in (
|
||||
("i8", 8, True), ("i16", 16, True), ("i32", 32, True),
|
||||
("i64", 64, True), ("isize", 64, True),
|
||||
("u8", 8, False), ("u16", 16, False), ("u32", 32, False),
|
||||
("u64", 64, False), ("usize", 64, False),
|
||||
):
|
||||
minimum = -(1 << (bits - 1)) if signed else 0
|
||||
maximum = (1 << (bits - int(signed))) - 1
|
||||
CASES[f"{integer_type}_storage"] = (
|
||||
f"""
|
||||
seed [2]mut {integer_type} :: [{minimum}, {maximum}]
|
||||
identity proc(a [2]mut {integer_type}) [2]mut {integer_type} {{ return a }}
|
||||
main proc() [2]mut {integer_type} {{
|
||||
a := identity(seed)
|
||||
b := a
|
||||
a[0] = 1
|
||||
return b
|
||||
}}
|
||||
""", f"[{minimum}, {maximum}]\n".encode(), None,
|
||||
)
|
||||
CASES[f"{integer_type}_inclusive_max"] = (
|
||||
f"""
|
||||
main proc() int {{
|
||||
start {integer_type} :: {maximum - 1}
|
||||
end {integer_type} :: {maximum}
|
||||
count := 0
|
||||
for start..=end |i| {{
|
||||
if (i < start) return -1
|
||||
count = count + 1
|
||||
continue
|
||||
}}
|
||||
return count
|
||||
}}
|
||||
""", b"2\n", None,
|
||||
)
|
||||
for operation, left, right in (
|
||||
("+", maximum, 1), ("-", minimum, 1), ("*", maximum, 2),
|
||||
):
|
||||
CASES[f"{integer_type}_overflow_{operation}"] = (
|
||||
f"main proc() {integer_type} {{ a {integer_type} :: {left}\nreturn a {operation} {right} }}",
|
||||
b"", b"integer arithmetic overflow",
|
||||
)
|
||||
CASES[f"{integer_type}_literal_range"] = (
|
||||
f"main proc() {integer_type} {{ return {maximum + 1} }}",
|
||||
b"", b"numeric literal is outside",
|
||||
)
|
||||
if signed:
|
||||
CASES[f"{integer_type}_negate_min"] = (
|
||||
f"main proc() {integer_type} {{ a {integer_type} :: {minimum}\nreturn -a }}",
|
||||
b"", b"integer arithmetic overflow",
|
||||
)
|
||||
|
||||
CASES.update({
|
||||
"integer_casts": (
|
||||
"""
|
||||
identity proc(n u8) u8 { return n }
|
||||
main proc() [4]i64 {
|
||||
large u64 :: 18446744073709551615
|
||||
small := identity(u8(i16(255)))
|
||||
if (large != u64(18446744073709551615)) return [0, 0, 0, 0]
|
||||
return [i64(small), i64(i8(-128)), i64(u32(4294967295)), i64(u8(255))]
|
||||
}
|
||||
""",
|
||||
b"[255, -128, 4294967295, 255]\n", None,
|
||||
),
|
||||
"cast_unsigned_max": ("main proc() u64 { return u64(18446744073709551615) }", b"18446744073709551615\n", None),
|
||||
"cast_float_rounding": ("main proc() f64 { return f64(9007199254740993) - 9007199254740992.0 }", b"0\n", None),
|
||||
"cast_fractional": ("main proc() [4]i64 { return [i64(i8(-128.9)), i64(u8(-0.9)), i64(u8(255.9)), i64(i8(127.9))] }", b"[-128, 0, 255, 127]\n", None),
|
||||
"cast_float_u64_max_below": ("main proc() u64 { return u64(18446744073709549568.0) }", b"18446744073709549568\n", None),
|
||||
"cast_float_i64_min": ("main proc() i64 { return i64(-9223372036854775808.0) }", b"-9223372036854775808\n", None),
|
||||
"cast_unused_trap": ("bad proc() u8 { return u8(256) }\nmain proc() int { return 42 }", b"42\n", None),
|
||||
"cast_short_circuit": ("main proc() bool { return true or u8(256) == 0 }", b"true\n", None),
|
||||
"cast_bool_invalid": ("main proc() u8 { return u8(true) }", b"", b""),
|
||||
"cast_zero_arguments": ("main proc() u8 { return u8() }", b"", b"argument count"),
|
||||
"cast_extra_arguments": ("main proc() u8 { return u8(1, 2) }", b"", b"argument count"),
|
||||
"cast_type_as_value": ("main proc() int { u8\nreturn 42 }", b"", b"type name cannot"),
|
||||
"integer_no_implicit_narrowing": ("main proc() u8 { a u16 :: 1\nreturn a }", b"", b"return does not match"),
|
||||
"integer_no_implicit_signedness": ("main proc() i8 { a u8 :: 1\nreturn a }", b"", b"return does not match"),
|
||||
"integer_no_implicit_widening": ("main proc() u16 { a u8 :: 1\nreturn a }", b"", b"return does not match"),
|
||||
"integer_assignment_cast": (
|
||||
"main proc() u8 { a u8 := 1\nb u16 :: 254\na = u8(b + 1)\nreturn a }",
|
||||
b"255\n", None,
|
||||
),
|
||||
"integer_function_value": (
|
||||
"identity proc(n i16) i16 { return n }\nmain proc() i16 { f :: identity\nreturn f(i16(-32768)) }",
|
||||
b"-32768\n", None,
|
||||
),
|
||||
"integer_typed_float_cast": (
|
||||
"main proc() f64 { a u64 :: 18446744073709551615\nreturn f64(a) - 18446744073709551616.0 }",
|
||||
b"0\n", None,
|
||||
),
|
||||
"integer_bad_index": ("main proc() i64 { a :: [42]\ni u8 :: 0\nreturn a[i] }", b"", b"array index must have type usize"),
|
||||
"integer_cast_index": ("main proc() i64 { a :: [42]\ni u8 :: 0\nreturn a[usize(i)] }", b"42\n", None),
|
||||
"integer_cast_unknown": ("main proc() u8 { return u8(missing) }", b"", b"unknown name"),
|
||||
"integer_cast_bad_unused": ("bad proc() u8 { return u8(true) }\nmain proc() int { return 42 }", b"42\n", b""),
|
||||
})
|
||||
for name, expression in {
|
||||
"narrow": "u8(256)",
|
||||
"signed_negative": "u8(-1)",
|
||||
"signed_high": "i8(128)",
|
||||
"unsigned_to_signed": "i64(u64(18446744073709551615))",
|
||||
"float_high": "u8(256.0)",
|
||||
"float_low": "i8(-129.0)",
|
||||
"float_unsigned_negative": "u8(-1.0)",
|
||||
"float_signed_limit": "i64(9223372036854775808.0)",
|
||||
"float_unsigned_limit": "u64(18446744073709551616.0)",
|
||||
"nan": "i32(0.0 / 0.0)",
|
||||
"infinity": "u32(1.0 / 0.0)",
|
||||
}.items():
|
||||
CASES[f"cast_trap_{name}"] = (
|
||||
f"main proc() i64 {{ return i64({expression}) }}", b"", b"integer arithmetic overflow",
|
||||
)
|
||||
|
||||
|
||||
def run(*args, env=None):
|
||||
return subprocess.run(args, cwd=ROOT, capture_output=True, timeout=30, env=env)
|
||||
@@ -245,9 +359,9 @@ def check(source, executable, expected=None):
|
||||
if expected is not None:
|
||||
stdout, error = expected
|
||||
assert interpreted.stdout == stdout, (source.name, outcome(interpreted), stdout)
|
||||
assert interpreted.returncode == (1 if error else 0), outcome(interpreted)
|
||||
if error:
|
||||
assert error in interpreted.stderr, outcome(interpreted)
|
||||
assert interpreted.returncode == (1 if error is not None else 0), outcome(interpreted)
|
||||
if error is not None:
|
||||
assert interpreted.stderr and error in interpreted.stderr, outcome(interpreted)
|
||||
for optimization in ("-O0", "-O2"):
|
||||
built = run(str(HONEY), "build", str(source), "-o", str(executable), optimization)
|
||||
assert built.returncode in (0, 1), (source.name, optimization, outcome(built))
|
||||
|
||||
Reference in New Issue
Block a user