unary minus

This commit is contained in:
2026-06-12 17:55:07 +02:00
parent 66e41e1d9a
commit 94e56f6888
14 changed files with 378 additions and 18 deletions
+2 -1
View File
@@ -74,13 +74,14 @@ Expr_Kind :: enum u8 {
Invalid,
Integer,
Name,
Negate,
Add,
Call,
}
Expr :: struct {
span: source.Span,
integer: i64,
integer: u64,
args: []Expr_Id,
qualifier: symbol.Id,
name: symbol.Id,
+54 -2
View File
@@ -125,7 +125,7 @@ eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant {
continue
}
expr := checker.ast_module.exprs[frame.expr]
if expr.kind != .Add {
if expr.kind != .Add && expr.kind != .Negate {
result := Constant{kind = .Not_Constant}
if expr.kind == .Integer {
result = Constant{kind = .Value, value = i128(expr.integer)}
@@ -142,6 +142,22 @@ eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant {
}
continue
}
if frame.stage == 1 && expr.kind == .Negate {
operand := Constant{kind = .Not_Constant}
if expr.left != ast.INVALID_EXPR && int(expr.left) < len(checker.constants) {
operand = checker.constants[expr.left]
}
result := Constant{kind = .Not_Constant}
if operand.kind == .Overflow {
result = Constant{kind = .Overflow}
} else if operand.kind == .Value {
value, overflow := intrinsics.overflow_sub(i128(0), operand.value)
result = Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value}
}
checker.constants[frame.expr] = result
_ = pop(&stack)
continue
}
if frame.stage == 1 {
stack[frame_index].stage = 2
if expr.right != ast.INVALID_EXPR && int(expr.right) < len(checker.ast_module.exprs) &&
@@ -407,6 +423,8 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
switch expr.kind {
case .Call:
append(&stack, ..expr.args)
case .Negate:
append(&stack, expr.left)
case .Add:
append(&stack, expr.left, expr.right)
case .Invalid, .Integer, .Name:
@@ -644,7 +662,10 @@ infer_expr :: proc(
last = types.INVALID
_ = pop(&stack)
case .Integer:
last = types.smallest_signed_for_literal(expr.integer)
last = types.I64
if expr.integer <= 0x7fff_ffff_ffff_ffff {
last = types.smallest_signed_for_literal(i64(expr.integer))
}
_ = pop(&stack)
case .Name:
last = types.INVALID
@@ -661,6 +682,9 @@ infer_expr :: proc(
}
}
_ = pop(&stack)
case .Negate:
stack[frame_index].stage = 5
append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION})
case .Add:
stack[frame_index].stage = 1
append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION})
@@ -690,6 +714,13 @@ infer_expr :: proc(
}
continue
}
if frame.stage == 5 {
if !types.is_signed(last) {
last = types.INVALID
}
_ = pop(&stack)
continue
}
if frame.stage == 1 {
stack[frame_index].left = last
stack[frame_index].stage = 2
@@ -1097,6 +1128,9 @@ build_expr :: proc(
}
}
_ = pop(&stack)
case .Negate:
stack[frame_index].stage = 5
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION})
case .Add:
stack[frame_index].stage = 1
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION})
@@ -1147,6 +1181,24 @@ build_expr :: proc(
}
continue
}
if frame.stage == 5 {
operand := last
operand_type := checker.module.exprs[operand].type
if !types.is_signed(operand_type) {
id := source.add(checker.diagnostics, expr.span, "negation requires a signed integer")
last = invalid_hir_expr(checker, expr.span, id)
} else {
last = add_hir_expr(checker, hir.Expr{
kind=.Negate, span=expr.span, type=operand_type, left=operand,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
if types.is_signed(frame.expected) {
last = coerce_expr(checker, last, frame.expected, expr.span)
}
}
_ = pop(&stack)
continue
}
if frame.stage == 1 {
stack[frame_index].left = last
stack[frame_index].stage = 2
+1
View File
@@ -77,6 +77,7 @@ Expr_Kind :: enum u8 {
Local,
Global,
Widen,
Negate,
Add,
Call,
}
+1
View File
@@ -72,6 +72,7 @@ Opcode :: enum u8 {
Load,
Store,
Widen,
Neg_Checked,
Add_Checked,
Call,
Trap,
+3
View File
@@ -83,6 +83,9 @@ lex :: proc(
case '+':
append_token(&stream, source_file, .Plus, cursor, cursor+1)
cursor += 1
case '-':
append_token(&stream, source_file, .Minus, cursor, cursor+1)
cursor += 1
case '.':
append_token(&stream, source_file, .Dot, cursor, cursor+1)
cursor += 1
+33 -1
View File
@@ -60,7 +60,7 @@ valid_value :: proc(instructions: []ir.Instruction, value_id: ir.Instruction_Id,
return false
}
switch instructions[value_id].op {
case .Param, .Const, .Load_Global, .Load, .Widen, .Add_Checked, .Call:
case .Param, .Const, .Load_Global, .Load, .Widen, .Neg_Checked, .Add_Checked, .Call:
return true
case .Alloca, .Store, .Trap, .Return, .Return_Void:
return false
@@ -234,6 +234,34 @@ emit_instruction_stream :: proc(
fmt.sbprintf(&emitter.builder, " %%v%d = sext %s ", instruction_index, llvm_type(from_type))
write_operand(&emitter.builder, instructions, instruction.a, from_type)
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type))
case .Neg_Checked:
if !valid_value(instructions, instruction.a, instruction.type) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid negation operand")
continue
}
type_name := llvm_type(instruction.type)
fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_index)
strings.write_string(&emitter.builder, "{ ")
fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.ssub.with.overflow.%s(%s 0, %s ", type_name, type_name, type_name, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type)
fmt.sbprintf(&emitter.builder, ")\n")
fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue ", instruction_index)
strings.write_string(&emitter.builder, "{ ")
fmt.sbprintf(&emitter.builder, "%s, i1 } %%pair%d, 0\n", type_name, instruction_index)
fmt.sbprintf(&emitter.builder, " %%overflow%d = extractvalue ", instruction_index)
strings.write_string(&emitter.builder, "{ ")
fmt.sbprintf(&emitter.builder, "%s, i1 } %%pair%d, 1\n", type_name, instruction_index)
fmt.sbprintf(
&emitter.builder,
" br i1 %%overflow%d, label %%overflow_trap%d, label %%overflow_continue%d\n",
instruction_index,
instruction_index,
instruction_index,
)
fmt.sbprintf(&emitter.builder, "overflow_trap%d:\n", instruction_index)
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "signed integer negation overflow")
emit_trap_call(emitter, message)
fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_index)
case .Add_Checked:
if !valid_value(instructions, instruction.a, instruction.type) ||
!valid_value(instructions, instruction.b, instruction.type) {
@@ -473,6 +501,10 @@ emit_declarations :: proc(emitter: ^Emitter) {
fmt.sbprintf(&emitter.builder, "%d", bits)
strings.write_string(&emitter.builder, ", i1 } @llvm.sadd.with.overflow.i")
fmt.sbprintf(&emitter.builder, "%d(i%d, i%d)\n", bits, bits, bits)
strings.write_string(&emitter.builder, "declare { i")
fmt.sbprintf(&emitter.builder, "%d", bits)
strings.write_string(&emitter.builder, ", i1 } @llvm.ssub.with.overflow.i")
fmt.sbprintf(&emitter.builder, "%d(i%d, i%d)\n", bits, bits, bits)
}
strings.write_string(
&emitter.builder,
+11
View File
@@ -138,6 +138,9 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
case .Widen:
stack[frame_index].stage = 1
append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Negate:
stack[frame_index].stage = 5
append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Add:
stack[frame_index].stage = 2
append(&stack, Lower_Expr_Frame{expr=expr.left})
@@ -156,6 +159,14 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
}
continue
}
if frame.stage == 5 {
last = append_instruction(state, ir.Instruction{
op=.Neg_Checked, span=expr.span, type=expr.type, target=ir.INVALID_REF,
a=last, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
})
_ = pop(&stack)
continue
}
if frame.stage == 1 {
last = append_instruction(state, ir.Instruction{
op=.Widen, span=expr.span, type=expr.type, target=ir.INVALID_REF,
+47 -4
View File
@@ -4,8 +4,8 @@ import "../ast"
import "../source"
import "../symbol"
import "../token"
import "base:intrinsics"
import "core:fmt"
import "core:strconv"
import "core:strings"
Parser :: struct {
@@ -168,14 +168,32 @@ parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Tok
})
}
parse_integer_magnitude :: proc(text: string) -> (u64, bool) {
value: u64
for byte in transmute([]byte)text {
if byte < '0' || byte > '9' {
return 0, false
}
next, overflow := intrinsics.overflow_mul(value, u64(10))
if overflow {
return 0, false
}
value, overflow = intrinsics.overflow_add(next, u64(byte-'0'))
if overflow {
return 0, false
}
}
return value, len(text) > 0
}
parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
tok := current(parser)
#partial switch tok.kind {
case .Integer:
advance(parser)
value, ok := strconv.parse_i64(token_text(parser, tok))
value, ok := parse_integer_magnitude(token_text(parser, tok))
if !ok {
return invalid_expr(parser, tok.span, "integer literal does not fit in i64")
return invalid_expr(parser, tok.span, "integer literal magnitude does not fit in u64")
}
return add_expr(parser, ast.Expr{
kind=.Integer,
@@ -250,6 +268,14 @@ infix_binding_power :: proc(kind: token.Kind) -> (left, right: int, ok: bool) {
return 0, 0, false
}
prefix_binding_power :: proc(kind: token.Kind) -> (right: int, ok: bool) {
#partial switch kind {
case .Minus:
return 20, true
}
return 0, false
}
parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int) -> ast.Expr_Id {
if nesting > MAX_EXPRESSION_NESTING {
tok := current(parser)
@@ -258,7 +284,24 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
}
return invalid_expr(parser, tok.span, "expression nesting exceeds 256 levels")
}
left := parse_primary(parser, nesting)
left := ast.INVALID_EXPR
if right_power, ok := prefix_binding_power(current(parser).kind); ok {
operator := advance(parser)
if parser.delimiter_depth > 0 {
skip_newlines(parser)
}
operand := parse_expression_bp(parser, right_power, nesting+1)
operand_expr := parser.module.exprs[operand]
left = add_expr(parser, ast.Expr{
kind=.Negate,
span=span_from(operator.span, operand_expr.span),
left=operand,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
} else {
left = parse_primary(parser, nesting)
}
if parser.delimiter_depth > 0 {
skip_newlines(parser)
}
+1
View File
@@ -14,6 +14,7 @@ Kind :: enum u8 {
Colon_Colon,
Equal,
Plus,
Minus,
Dot,
Left_Paren,
Right_Paren,