bitwise operations
This commit is contained in:
@@ -372,7 +372,7 @@ block_reads_name :: proc(checker: ^Checker, statements: []ast.Stmt_Id, name: sym
|
||||
case .Call, .Array, .Struct_Literal, .Slice:
|
||||
append(&expr_stack, ..expr.args)
|
||||
append(&expr_stack, expr.left)
|
||||
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal, .Cast:
|
||||
case .Negate, .Not, .Bit_Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal, .Cast:
|
||||
append(&expr_stack, expr.left)
|
||||
case .Comptime:
|
||||
append(&expr_stack, expr.left)
|
||||
@@ -380,7 +380,8 @@ block_reads_name :: proc(checker: ^Checker, statements: []ast.Stmt_Id, name: sym
|
||||
case .Catch:
|
||||
append(&expr_stack, expr.left, expr.right)
|
||||
append(&statement_stack, ..expr.body)
|
||||
case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
||||
case .Add, .Sub, .Mul, .Div, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left, .Shift_Right,
|
||||
.Shift_Left_Saturating, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
||||
append(&expr_stack, expr.left, expr.right)
|
||||
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Inference_Hole,
|
||||
.Type, .Name, .Function_Literal, .Anonymous_Struct_Type:
|
||||
@@ -1243,6 +1244,23 @@ is_numeric_constant_expr :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> boo
|
||||
return eval_constant(checker, expr_id).kind == .Value || is_float_constant_expr(checker, expr_id)
|
||||
}
|
||||
|
||||
is_typed_integer_fold_candidate :: proc(checker: ^Checker, expr_id: ast.Expr_Id, depth := 0) -> bool {
|
||||
if depth > 64 || expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
||||
return false
|
||||
}
|
||||
expr := checker.ast_module.exprs[expr_id]
|
||||
#partial switch expr.kind {
|
||||
case .Integer:
|
||||
return true
|
||||
case .Negate, .Bit_Not, .Cast:
|
||||
return is_typed_integer_fold_candidate(checker, expr.left, depth+1)
|
||||
case .Add, .Sub, .Mul, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left, .Shift_Right, .Shift_Left_Saturating:
|
||||
return is_typed_integer_fold_candidate(checker, expr.left, depth+1) &&
|
||||
is_typed_integer_fold_candidate(checker, expr.right, depth+1)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
is_numeric_demand :: proc(value: types.Type, selected := target.DEFAULT) -> bool {
|
||||
return types.is_concrete_scalar(value) && !types.is_bool(value) ||
|
||||
types.is_float(value, selected)
|
||||
@@ -3249,7 +3267,7 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
|
||||
if expr.left != ast.INVALID_EXPR {
|
||||
append(&stack, expr.left)
|
||||
}
|
||||
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal, .Cast:
|
||||
case .Negate, .Not, .Bit_Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal, .Cast:
|
||||
append(&stack, expr.left)
|
||||
case .Comptime:
|
||||
if expr.left != ast.INVALID_EXPR {
|
||||
@@ -3268,7 +3286,8 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
|
||||
function := checker.ast_module.functions[function_id]
|
||||
mark_block_imports_used(checker, function.body, function.file)
|
||||
}
|
||||
case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
||||
case .Add, .Sub, .Mul, .Div, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left, .Shift_Right,
|
||||
.Shift_Left_Saturating, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
||||
append(&stack, expr.left, expr.right)
|
||||
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Inference_Hole, .Type, .Name, .Anonymous_Struct_Type:
|
||||
}
|
||||
@@ -4290,6 +4309,29 @@ infer_compound_expr :: proc(
|
||||
case .Not:
|
||||
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||
return types.BOOL
|
||||
case .Bit_Not:
|
||||
hint := expected if types.is_concrete_integer(expected) else types.INVALID
|
||||
operand := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, hint)
|
||||
return operand if types.is_concrete_integer(operand) else types.INVALID
|
||||
case .Bit_And, .Bit_Or, .Bit_Xor:
|
||||
hint := expected if types.is_concrete_integer(expected) else types.INVALID
|
||||
left_const := is_numeric_constant_expr(checker, expr.left)
|
||||
right_const := is_numeric_constant_expr(checker, expr.right)
|
||||
left, right := types.INVALID, types.INVALID
|
||||
if left_const && !right_const && !types.is_valid(hint) {
|
||||
right = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
|
||||
left = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, right)
|
||||
} else {
|
||||
left = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, hint)
|
||||
right = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types, hint if types.is_valid(hint) else left)
|
||||
}
|
||||
result := types.widest(left, right)
|
||||
return result if types.is_concrete_integer(result) else types.INVALID
|
||||
case .Shift_Left, .Shift_Right, .Shift_Left_Saturating:
|
||||
hint := expected if types.is_concrete_integer(expected) else types.INVALID
|
||||
left := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, hint)
|
||||
right := infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types, types.U64)
|
||||
return left if types.is_concrete_integer(left) && types.is_unsigned(right, checker.target) else types.INVALID
|
||||
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
|
||||
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
|
||||
@@ -4599,7 +4641,8 @@ infer_expr :: proc(
|
||||
_ = pop(&stack)
|
||||
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
|
||||
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed, .Enum_Literal, .Cast,
|
||||
.Comptime, .Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
||||
.Comptime, .Bool, .Not, .Bit_Not, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left,
|
||||
.Shift_Right, .Shift_Left_Saturating, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
||||
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded, local_types, frame.expected)
|
||||
_ = pop(&stack)
|
||||
case .Function_Literal:
|
||||
@@ -7758,6 +7801,88 @@ build_compound_expr :: proc(
|
||||
kind=.Not, span=expr.span, type=types.BOOL, left=operand,
|
||||
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Bit_Not:
|
||||
hint := expected if types.is_concrete_integer(expected) else types.INVALID
|
||||
operand := build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, pkg, file)
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, operand); propagated {
|
||||
return invalid
|
||||
}
|
||||
operand_type := checker.module.exprs[operand].type
|
||||
if !types.is_concrete_integer(operand_type) {
|
||||
id := source.add(checker.diagnostics, expr.span, "'~' requires a concrete integer operand")
|
||||
return invalid_hir_expr(checker, expr.span, id, operand_type)
|
||||
}
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=.Bit_Not, span=expr.span, type=operand_type, left=operand,
|
||||
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Bit_And, .Bit_Or, .Bit_Xor:
|
||||
hint := expected if types.is_concrete_integer(expected) else types.INVALID
|
||||
left_const := is_numeric_constant_expr(checker, expr.left)
|
||||
right_const := is_numeric_constant_expr(checker, expr.right)
|
||||
left, right: hir.Expr_Id
|
||||
if left_const && !right_const && !types.is_valid(hint) {
|
||||
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, checker.module.exprs[right].type, pkg, file)
|
||||
} else {
|
||||
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, pkg, file)
|
||||
right_hint := hint if types.is_valid(hint) else checker.module.exprs[left].type
|
||||
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, right_hint, pkg, file)
|
||||
}
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, left, right); propagated {
|
||||
return invalid
|
||||
}
|
||||
result_type := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
|
||||
if !types.is_concrete_integer(result_type) {
|
||||
id := source.add(checker.diagnostics, expr.span, "bitwise operation requires compatible concrete integer operands")
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
left = coerce_expr(checker, left, result_type, checker.module.exprs[left].span)
|
||||
right = coerce_expr(checker, right, result_type, checker.module.exprs[right].span)
|
||||
kind := hir.Expr_Kind.Bit_And
|
||||
#partial switch expr.kind {
|
||||
case .Bit_Or: kind = .Bit_Or
|
||||
case .Bit_Xor: kind = .Bit_Xor
|
||||
}
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=kind, span=expr.span, type=result_type, left=left, right=right,
|
||||
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Shift_Left, .Shift_Right, .Shift_Left_Saturating:
|
||||
hint := expected if types.is_concrete_integer(expected) else types.INVALID
|
||||
left := build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, pkg, file)
|
||||
right := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.U64, pkg, file)
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, left, right); propagated {
|
||||
return invalid
|
||||
}
|
||||
left_type := checker.module.exprs[left].type
|
||||
right_type := checker.module.exprs[right].type
|
||||
if !types.is_concrete_integer(left_type) {
|
||||
id := source.add(checker.diagnostics, checker.module.exprs[left].span, "shifted value must be a concrete integer")
|
||||
return invalid_hir_expr(checker, expr.span, id, left_type)
|
||||
}
|
||||
if !types.is_unsigned(right_type, checker.target) {
|
||||
id := source.add(checker.diagnostics, checker.module.exprs[right].span, "shift count must be an unsigned integer")
|
||||
return invalid_hir_expr(checker, expr.span, id, left_type)
|
||||
}
|
||||
if constant := eval_integer_constant_in_context(checker, expr.right, pkg, file);
|
||||
constant.kind == .Value && expr.kind != .Shift_Left_Saturating &&
|
||||
constant.value >= i128(types.bits(left_type, checker.target)) {
|
||||
id := source.addf(
|
||||
checker.diagnostics, checker.module.exprs[right].span,
|
||||
"shift count %d exceeds %s width", constant.value, types.name(left_type),
|
||||
)
|
||||
return invalid_hir_expr(checker, expr.span, id, left_type)
|
||||
}
|
||||
kind := hir.Expr_Kind.Shift_Left
|
||||
#partial switch expr.kind {
|
||||
case .Shift_Right: kind = .Shift_Right
|
||||
case .Shift_Left_Saturating: kind = .Shift_Left_Saturating
|
||||
}
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=kind, span=expr.span, type=left_type, left=left, right=right,
|
||||
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .And, .Or:
|
||||
left := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.BOOL, pkg, file)
|
||||
right := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.BOOL, pkg, file)
|
||||
@@ -8096,6 +8221,14 @@ build_expr :: proc(
|
||||
continue
|
||||
}
|
||||
}
|
||||
if expr.kind == .Bit_Not || expr.kind == .Bit_And || expr.kind == .Bit_Or || expr.kind == .Bit_Xor ||
|
||||
expr.kind == .Shift_Left || expr.kind == .Shift_Right || expr.kind == .Shift_Left_Saturating {
|
||||
if folded, ok := try_fold_typed_integer_expr(checker, frame.expr, frame.expected, pkg, file); ok {
|
||||
last = folded
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
}
|
||||
constant := Constant{}
|
||||
_, static_name := current_static_binding(checker, expr.name)
|
||||
if expr.kind != .Name || symbol.is_valid(expr.qualifier) || !static_name {
|
||||
@@ -8109,7 +8242,8 @@ build_expr :: proc(
|
||||
switch expr.kind {
|
||||
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
|
||||
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed,
|
||||
.Bool, .Cast, .Comptime, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range,
|
||||
.Bool, .Cast, .Comptime, .Not, .Bit_Not, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left,
|
||||
.Shift_Right, .Shift_Left_Saturating, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range,
|
||||
.Enum_Literal:
|
||||
last = build_compound_expr(
|
||||
checker, expr, locals, global_reads, calls, frame.expected, pkg, file,
|
||||
@@ -9792,6 +9926,10 @@ build_block :: proc(
|
||||
rhs_expected := target_type
|
||||
if types.is_many_pointer(target_type, &checker.module.types) {
|
||||
rhs_expected = types.USIZE if statement.assignment_op == .Add else types.INVALID
|
||||
} else if statement.assignment_op == .Shift_Left ||
|
||||
statement.assignment_op == .Shift_Right ||
|
||||
statement.assignment_op == .Shift_Left_Saturating {
|
||||
rhs_expected = types.U64
|
||||
}
|
||||
value = build_expr(
|
||||
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||
@@ -9824,10 +9962,42 @@ build_block :: proc(
|
||||
case .Sub: assignment_op = .Sub
|
||||
case .Mul: assignment_op = .Mul
|
||||
case .Div: assignment_op = .Div
|
||||
case .Bit_And: assignment_op = .Bit_And
|
||||
case .Bit_Or: assignment_op = .Bit_Or
|
||||
case .Bit_Xor: assignment_op = .Bit_Xor
|
||||
case .Shift_Left: assignment_op = .Shift_Left
|
||||
case .Shift_Right: assignment_op = .Shift_Right
|
||||
case .Shift_Left_Saturating: assignment_op = .Shift_Left_Saturating
|
||||
}
|
||||
rhs_type := checker.module.exprs[value].type
|
||||
is_shift := statement.assignment_op == .Shift_Left ||
|
||||
statement.assignment_op == .Shift_Right ||
|
||||
statement.assignment_op == .Shift_Left_Saturating
|
||||
is_bitwise := statement.assignment_op == .Bit_And ||
|
||||
statement.assignment_op == .Bit_Or ||
|
||||
statement.assignment_op == .Bit_Xor
|
||||
result_type := types.widest(target_type, rhs_type)
|
||||
if statement.assignment_op == .Div && types.is_concrete_integer(result_type) {
|
||||
if is_shift {
|
||||
if !types.is_concrete_integer(target_type) || !types.is_unsigned(rhs_type, checker.target) {
|
||||
id := source.add(checker.diagnostics, statement.span, "shift assignment requires an integer target and unsigned integer count")
|
||||
value = invalid_hir_expr(checker, statement.span, id, target_type)
|
||||
} else if constant := eval_integer_constant_in_context(checker, statement.expr, ctx.pkg, ctx.file);
|
||||
constant.kind == .Value && statement.assignment_op != .Shift_Left_Saturating &&
|
||||
constant.value >= i128(types.bits(target_type, checker.target)) {
|
||||
id := source.addf(
|
||||
checker.diagnostics, statement.span,
|
||||
"shift count %d exceeds %s width", constant.value, types.name(target_type),
|
||||
)
|
||||
value = invalid_hir_expr(checker, statement.span, id, target_type)
|
||||
}
|
||||
} else if is_bitwise {
|
||||
if !types.is_concrete_integer(result_type) {
|
||||
id := source.add(checker.diagnostics, statement.span, "bitwise assignment requires compatible concrete integer operands")
|
||||
value = invalid_hir_expr(checker, statement.span, id, target_type)
|
||||
} else {
|
||||
value = coerce_expr(checker, value, target_type, statement.span)
|
||||
}
|
||||
} else if statement.assignment_op == .Div && types.is_concrete_integer(result_type) {
|
||||
id := source.add(
|
||||
checker.diagnostics,
|
||||
statement.span,
|
||||
|
||||
@@ -1270,14 +1270,18 @@ ct_eval_expr :: proc(
|
||||
return ct_add_value(state, Ct_Value{
|
||||
kind=.Range, type=types.range(store, child_type), start=start, count=2, active=i64(expr.integer),
|
||||
}), ct_flow(.Normal), true
|
||||
case .Negate, .Not:
|
||||
case .Negate, .Not, .Bit_Not:
|
||||
value, flow, ok := ct_eval_expr(state, expr.left, expected, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
}
|
||||
return ct_eval_unary(state, expr.kind, value, expr.span)
|
||||
case .Add, .Sub, .Mul, .Div, .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
|
||||
case .Add, .Sub, .Mul, .Div, .Bit_And, .Bit_Or, .Bit_Xor, .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
|
||||
left_expected := expected if expr.kind == .Div && types.is_float(expected, checker.target) else types.INVALID
|
||||
if (expr.kind == .Bit_And || expr.kind == .Bit_Or || expr.kind == .Bit_Xor) &&
|
||||
types.is_concrete_integer(expected) {
|
||||
left_expected = expected
|
||||
}
|
||||
left, flow, ok := ct_eval_expr(state, expr.left, left_expected, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
@@ -1287,6 +1291,16 @@ ct_eval_expr :: proc(
|
||||
return INVALID_CT_VALUE, right_flow, right_ok
|
||||
}
|
||||
return ct_eval_binary(state, expr.kind, left, right, expr.span)
|
||||
case .Shift_Left, .Shift_Right, .Shift_Left_Saturating:
|
||||
left, flow, ok := ct_eval_expr(state, expr.left, expected, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
}
|
||||
right, right_flow, right_ok := ct_eval_expr(state, expr.right, types.U64, depth+1)
|
||||
if !right_ok || right_flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, right_flow, right_ok
|
||||
}
|
||||
return ct_eval_binary(state, expr.kind, left, right, expr.span)
|
||||
case .And, .Or:
|
||||
left, flow, ok := ct_eval_expr(state, expr.left, types.BOOL, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
@@ -2033,6 +2047,19 @@ ct_unwrap_optional :: proc(state: ^Ct_State, id: Ct_Value_Id, span: source.Span)
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "postfix '?' requires an optional")
|
||||
}
|
||||
|
||||
ct_normalize_integer :: proc(state: ^Ct_State, value: i128, type: types.Type) -> i128 {
|
||||
bits := types.bits(type, state.checker.target)
|
||||
mask := (i128(1) << u32(bits))-1
|
||||
raw := value & mask
|
||||
if types.is_signed(type, state.checker.target) {
|
||||
sign := i128(1) << u32(bits-1)
|
||||
if raw & sign != 0 {
|
||||
return raw-(i128(1) << u32(bits))
|
||||
}
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
ct_eval_unary :: proc(state: ^Ct_State, op: ast.Expr_Kind, id: Ct_Value_Id, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
|
||||
if id == INVALID_CT_VALUE || int(id) >= len(state.values) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||
@@ -2044,6 +2071,13 @@ ct_eval_unary :: proc(state: ^Ct_State, op: ast.Expr_Kind, id: Ct_Value_Id, span
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if value.integer == 0 else 0}), ct_flow(.Normal), true
|
||||
}
|
||||
if op == .Bit_Not {
|
||||
if value.kind != .Integer || !types.is_concrete_integer(value.type) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "'~' requires a concrete integer operand")
|
||||
}
|
||||
value.integer = ct_normalize_integer(state, ~value.integer, value.type)
|
||||
return ct_add_value(state, value), ct_flow(.Normal), true
|
||||
}
|
||||
if value.kind == .Integer {
|
||||
result, overflow := intrinsics.overflow_sub(i128(0), value.integer)
|
||||
if overflow {
|
||||
@@ -2125,6 +2159,62 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
|
||||
"integer '/' is not allowed; use divtrunc!, divfloor!, divexact!, or divceil!",
|
||||
)
|
||||
}
|
||||
if op == .Bit_And || op == .Bit_Or || op == .Bit_Xor {
|
||||
result_type := types.widest(left.type, right.type)
|
||||
if !types.is_concrete_integer(result_type) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "bitwise operation requires compatible concrete integer operands")
|
||||
}
|
||||
value := left.integer & right.integer
|
||||
#partial switch op {
|
||||
case .Bit_Or: value = left.integer | right.integer
|
||||
case .Bit_Xor: value = left.integer ~ right.integer
|
||||
}
|
||||
value = ct_normalize_integer(state, value, result_type)
|
||||
return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=value}), ct_flow(.Normal), true
|
||||
}
|
||||
if op == .Shift_Left || op == .Shift_Right || op == .Shift_Left_Saturating {
|
||||
if !types.is_concrete_integer(left.type) || !types.is_unsigned(right.type, state.checker.target) || right.integer < 0 {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "shift requires a concrete integer value and unsigned integer count")
|
||||
}
|
||||
bits := types.bits(left.type, state.checker.target)
|
||||
if right.integer >= i128(bits) {
|
||||
if op != .Shift_Left_Saturating {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "shift count exceeds integer width")
|
||||
}
|
||||
endpoint := i128(0)
|
||||
if left.integer != 0 {
|
||||
if types.is_signed(left.type, state.checker.target) {
|
||||
endpoint = -(i128(1) << u32(bits-1)) if left.integer < 0 else (i128(1) << u32(bits-1))-1
|
||||
} else {
|
||||
endpoint = (i128(1) << u32(bits))-1
|
||||
}
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Integer, type=left.type, integer=endpoint}), ct_flow(.Normal), true
|
||||
}
|
||||
count := u32(right.integer)
|
||||
if op == .Shift_Right {
|
||||
value := left.integer >> count
|
||||
if !types.is_signed(left.type, state.checker.target) {
|
||||
value = ct_normalize_integer(state, left.integer, left.type) >> count
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Integer, type=left.type, integer=value}), ct_flow(.Normal), true
|
||||
}
|
||||
if op == .Shift_Left_Saturating {
|
||||
factor := i128(1) << count
|
||||
value := left.integer*factor
|
||||
if types.is_signed(left.type, state.checker.target) {
|
||||
minimum := -(i128(1) << u32(bits-1))
|
||||
maximum := (i128(1) << u32(bits-1))-1
|
||||
value = max(minimum, min(maximum, value))
|
||||
} else {
|
||||
maximum := (i128(1) << u32(bits))-1
|
||||
value = min(maximum, ct_normalize_integer(state, left.integer, left.type)*factor)
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Integer, type=left.type, integer=value}), ct_flow(.Normal), true
|
||||
}
|
||||
value := ct_normalize_integer(state, ct_normalize_integer(state, left.integer, left.type) << count, left.type)
|
||||
return ct_add_value(state, Ct_Value{kind=.Integer, type=left.type, integer=value}), ct_flow(.Normal), true
|
||||
}
|
||||
value: i128
|
||||
overflow := false
|
||||
#partial switch op {
|
||||
@@ -3478,7 +3568,12 @@ ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) ->
|
||||
flow = ct_flow(.Normal)
|
||||
}
|
||||
} else {
|
||||
value, flow, ok = ct_eval_expr(state, statement.expr, expected, depth+1)
|
||||
value_expected := expected
|
||||
if statement.assignment_op == .Shift_Left || statement.assignment_op == .Shift_Right ||
|
||||
statement.assignment_op == .Shift_Left_Saturating {
|
||||
value_expected = types.U64
|
||||
}
|
||||
value, flow, ok = ct_eval_expr(state, statement.expr, value_expected, depth+1)
|
||||
}
|
||||
if !ok || flow.kind != .Normal {
|
||||
return flow, ok
|
||||
@@ -3493,6 +3588,12 @@ ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) ->
|
||||
case .Sub: op = .Sub
|
||||
case .Mul: op = .Mul
|
||||
case .Div: op = .Div
|
||||
case .Bit_And: op = .Bit_And
|
||||
case .Bit_Or: op = .Bit_Or
|
||||
case .Bit_Xor: op = .Bit_Xor
|
||||
case .Shift_Left: op = .Shift_Left
|
||||
case .Shift_Right: op = .Shift_Right
|
||||
case .Shift_Left_Saturating: op = .Shift_Left_Saturating
|
||||
case: op = .Add
|
||||
}
|
||||
bin_flow: Ct_Flow
|
||||
@@ -4046,6 +4147,26 @@ eval_integer_constant_in_context :: proc(
|
||||
return Constant{kind=.Value, value=integer}
|
||||
}
|
||||
|
||||
try_fold_typed_integer_expr :: proc(
|
||||
checker: ^Checker,
|
||||
expr_id: ast.Expr_Id,
|
||||
expected: types.Type,
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
) -> (hir.Expr_Id, bool) {
|
||||
if !is_typed_integer_fold_candidate(checker, expr_id) {
|
||||
return hir.INVALID_EXPR, false
|
||||
}
|
||||
state := ct_state_make(checker, pkg, file, diagnose=false)
|
||||
defer ct_state_destroy(&state)
|
||||
value, flow, ok := ct_eval_expr(&state, expr_id, expected)
|
||||
if !ok || flow.kind != .Normal || value == INVALID_CT_VALUE || int(value) >= len(state.values) ||
|
||||
state.values[value].kind != .Integer {
|
||||
return hir.INVALID_EXPR, false
|
||||
}
|
||||
return ct_materialize_value(&state, value, checker.ast_module.exprs[expr_id].span, expected), true
|
||||
}
|
||||
|
||||
eval_comptime_statements :: proc(
|
||||
checker: ^Checker,
|
||||
statements: []ast.Stmt_Id,
|
||||
|
||||
Reference in New Issue
Block a user