From e4a0228a8030f1d8d8793464be9ea7f8ae889941 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 8 Aug 2025 10:00:11 +0100 Subject: Define the behaviour of integer division by zero --- src/check_expr.cpp | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) (limited to 'src/check_expr.cpp') diff --git a/src/check_expr.cpp b/src/check_expr.cpp index faa338f36..1b62de410 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -129,6 +129,8 @@ gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type gb_internal bool is_exact_value_zero(ExactValue const &v); +gb_internal IntegerDivisionByZeroKind check_for_integer_division_by_zero(Ast *node); + enum LoadDirectiveResult { LoadDirective_Success = 0, LoadDirective_Error = 1, @@ -4308,7 +4310,25 @@ gb_internal void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Typ } if (fail) { - error(y->expr, "Division by zero not allowed"); + if (is_type_integer(x->type) || (x->mode == Addressing_Constant && x->value.kind == ExactValue_Integer)) { + if (check_for_integer_division_by_zero(node) == IntegerDivisionByZero_Zero) { + // Okay + break; + } + } + + switch (op.kind) { + case Token_Mod: + case Token_ModMod: + case Token_ModEq: + case Token_ModModEq: + error(y->expr, "Division by zero through '%.*s' not allowed", LIT(token_strings[op.kind])); + break; + case Token_Quo: + case Token_QuoEq: + error(y->expr, "Division by zero not allowed"); + break; + } x->mode = Addressing_Invalid; return; } @@ -4348,7 +4368,30 @@ gb_internal void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Typ } } - x->value = exact_binary_operator_value(op.kind, a, b); + match_exact_values(&a, &b); + + + if (check_for_integer_division_by_zero(node) == IntegerDivisionByZero_Zero && + b.kind == ExactValue_Integer && big_int_is_zero(&b.value_integer) && + (op.kind == Token_QuoEq || op.kind == Token_Mod || op.kind == Token_ModMod)) { + if (op.kind == Token_QuoEq) { + // x/0 == 0 + x->value = b; + } else { + // x%0 == x + /* + NOTE(bill): @integer division by zero rules + + truncated: r = a - b*trunc(a/b) + floored: r = a - b*floor(a/b) + + IFF a/0 == 0, then (a%0 == a) or (a%%0 == a) + */ + x->value = a; + } + } else { + x->value = exact_binary_operator_value(op.kind, a, b); + } if (is_type_typed(x->type)) { if (node != nullptr) { @@ -9595,6 +9638,11 @@ gb_internal bool check_for_dynamic_literals(CheckerContext *c, Ast *node, AstCom return cl->elems.count > 0; } +gb_internal IntegerDivisionByZeroKind check_for_integer_division_by_zero(Ast *node) { + // TODO(bill): per file `#+feature` flags + return build_context.integer_division_by_zero_behaviour; +} + gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) { ExprKind kind = Expr_Expr; ast_node(cl, CompoundLit, node); -- cgit v1.2.3 From 991883d0e1376c1e5675e88c26fee89362adc7b1 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 8 Aug 2025 10:24:44 +0100 Subject: Add `#+feature integer-division-by-zero:` --- src/build_settings.cpp | 12 ++++++++++++ src/check_expr.cpp | 15 +++++++++++---- src/llvm_backend_expr.cpp | 30 ++++++++++++++++++++++++------ src/parser.cpp | 42 ++++++++++++++++++++++++++++++------------ 4 files changed, 77 insertions(+), 22 deletions(-) (limited to 'src/check_expr.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index c2a56d1bb..e21e7da12 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -352,12 +352,24 @@ u64 get_vet_flag_from_name(String const &name) { enum OptInFeatureFlags : u64 { OptInFeatureFlag_NONE = 0, OptInFeatureFlag_DynamicLiterals = 1u<<0, + + OptInFeatureFlag_IntegerDivisionByZero_Trap = 1u<<1, + OptInFeatureFlag_IntegerDivisionByZero_Zero = 1u<<2, + + OptInFeatureFlag_IntegerDivisionByZero_ALL = OptInFeatureFlag_IntegerDivisionByZero_Trap|OptInFeatureFlag_IntegerDivisionByZero_Zero, + }; u64 get_feature_flag_from_name(String const &name) { if (name == "dynamic-literals") { return OptInFeatureFlag_DynamicLiterals; } + if (name == "integer-division-by-zero:trap") { + return OptInFeatureFlag_IntegerDivisionByZero_Trap; + } + if (name == "integer-division-by-zero:zero") { + return OptInFeatureFlag_IntegerDivisionByZero_Zero; + } return OptInFeatureFlag_NONE; } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 1b62de410..b68fe0ed0 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -129,7 +129,7 @@ gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type gb_internal bool is_exact_value_zero(ExactValue const &v); -gb_internal IntegerDivisionByZeroKind check_for_integer_division_by_zero(Ast *node); +gb_internal IntegerDivisionByZeroKind check_for_integer_division_by_zero(CheckerContext *c, Ast *node); enum LoadDirectiveResult { LoadDirective_Success = 0, @@ -4311,7 +4311,7 @@ gb_internal void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Typ if (fail) { if (is_type_integer(x->type) || (x->mode == Addressing_Constant && x->value.kind == ExactValue_Integer)) { - if (check_for_integer_division_by_zero(node) == IntegerDivisionByZero_Zero) { + if (check_for_integer_division_by_zero(c, node) == IntegerDivisionByZero_Zero) { // Okay break; } @@ -4371,7 +4371,7 @@ gb_internal void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Typ match_exact_values(&a, &b); - if (check_for_integer_division_by_zero(node) == IntegerDivisionByZero_Zero && + if (check_for_integer_division_by_zero(c, node) == IntegerDivisionByZero_Zero && b.kind == ExactValue_Integer && big_int_is_zero(&b.value_integer) && (op.kind == Token_QuoEq || op.kind == Token_Mod || op.kind == Token_ModMod)) { if (op.kind == Token_QuoEq) { @@ -9638,8 +9638,15 @@ gb_internal bool check_for_dynamic_literals(CheckerContext *c, Ast *node, AstCom return cl->elems.count > 0; } -gb_internal IntegerDivisionByZeroKind check_for_integer_division_by_zero(Ast *node) { +gb_internal IntegerDivisionByZeroKind check_for_integer_division_by_zero(CheckerContext *c, Ast *node) { // TODO(bill): per file `#+feature` flags + u64 flags = check_feature_flags(c, node); + if ((flags & OptInFeatureFlag_IntegerDivisionByZero_Trap) != 0) { + return IntegerDivisionByZero_Trap; + } + if ((flags & OptInFeatureFlag_IntegerDivisionByZero_Zero) != 0) { + return IntegerDivisionByZero_Zero; + } return build_context.integer_division_by_zero_behaviour; } diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 6652c51d3..b20aef742 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -283,8 +283,26 @@ gb_internal lbValue lb_emit_unary_arith(lbProcedure *p, TokenKind op, lbValue x, return res; } -gb_internal IntegerDivisionByZeroKind lb_check_for_integer_division_by_zero(lbProcedure *p) { - // TODO(bill): per file `#+feature` flags +gb_internal IntegerDivisionByZeroKind lb_check_for_integer_division_by_zero_behaviour(lbProcedure *p) { + AstFile *file = nullptr; + + if (p->body && p->body->file()) { + file = p->body->file(); + } else if (p->type_expr && p->type_expr->file()) { + file = p->type_expr->file(); + } else if (p->entity && p->entity->file) { + file = p->entity->file; + } + + if (file != nullptr && file->feature_flags_set) { + u64 flags = file->feature_flags; + if (flags & OptInFeatureFlag_IntegerDivisionByZero_Trap) { + return IntegerDivisionByZero_Trap; + } + if (flags & OptInFeatureFlag_IntegerDivisionByZero_Zero) { + return IntegerDivisionByZero_Zero; + } + } return build_context.integer_division_by_zero_behaviour; } @@ -1143,7 +1161,7 @@ gb_internal LLVMValueRef lb_integer_division(lbProcedure *p, LLVMValueRef lhs, L incoming_values[1] = zero; - switch (lb_check_for_integer_division_by_zero(p)) { + switch (lb_check_for_integer_division_by_zero_behaviour(p)) { case IntegerDivisionByZero_Trap: lb_call_intrinsic(p, "llvm.trap", nullptr, 0, nullptr, 0); LLVMBuildUnreachable(p->builder); @@ -1158,7 +1176,7 @@ gb_internal LLVMValueRef lb_integer_division(lbProcedure *p, LLVMValueRef lhs, L LLVMValueRef res = incoming_values[0]; - switch (lb_check_for_integer_division_by_zero(p)) { + switch (lb_check_for_integer_division_by_zero_behaviour(p)) { case IntegerDivisionByZero_Trap: res = incoming_values[0]; break; @@ -1226,7 +1244,7 @@ gb_internal LLVMValueRef lb_integer_modulo(lbProcedure *p, LLVMValueRef lhs, LLV */ incoming_values[1] = lhs; - switch (lb_check_for_integer_division_by_zero(p)) { + switch (lb_check_for_integer_division_by_zero_behaviour(p)) { case IntegerDivisionByZero_Trap: lb_call_intrinsic(p, "llvm.trap", nullptr, 0, nullptr, 0); LLVMBuildUnreachable(p->builder); @@ -1241,7 +1259,7 @@ gb_internal LLVMValueRef lb_integer_modulo(lbProcedure *p, LLVMValueRef lhs, LLV LLVMValueRef res = incoming_values[0]; - switch (lb_check_for_integer_division_by_zero(p)) { + switch (lb_check_for_integer_division_by_zero_behaviour(p)) { case IntegerDivisionByZero_Trap: res = incoming_values[0]; break; diff --git a/src/parser.cpp b/src/parser.cpp index 1ccc3feaa..58d7acfa5 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6289,7 +6289,7 @@ gb_internal bool parse_build_tag(Token token_for_pos, String s) { return any_correct; } -gb_internal String vet_tag_get_token(String s, String *out) { +gb_internal String vet_tag_get_token(String s, String *out, bool allow_colon) { s = string_trim_whitespace(s); isize n = 0; while (n < s.len) { @@ -6297,7 +6297,7 @@ gb_internal String vet_tag_get_token(String s, String *out) { isize width = utf8_decode(&s[n], s.len-n, &rune); if (n == 0 && rune == '!') { - } else if (!rune_is_letter(rune) && !rune_is_digit(rune) && rune != '-') { + } else if (!rune_is_letter(rune) && !rune_is_digit(rune) && rune != '-' && !(allow_colon && rune == ':')) { isize k = gb_max(gb_max(n, width), 1); *out = substring(s, k, s.len); return substring(s, 0, k); @@ -6323,7 +6323,7 @@ gb_internal u64 parse_vet_tag(Token token_for_pos, String s) { u64 vet_not_flags = 0; while (s.len > 0) { - String p = string_trim_whitespace(vet_tag_get_token(s, &s)); + String p = string_trim_whitespace(vet_tag_get_token(s, &s, /*allow_colon*/false)); if (p.len == 0) { break; } @@ -6391,7 +6391,7 @@ gb_internal u64 parse_feature_tag(Token token_for_pos, String s) { u64 feature_not_flags = 0; while (s.len > 0) { - String p = string_trim_whitespace(vet_tag_get_token(s, &s)); + String p = string_trim_whitespace(vet_tag_get_token(s, &s, /*allow_colon*/true)); if (p.len == 0) { break; } @@ -6413,26 +6413,44 @@ gb_internal u64 parse_feature_tag(Token token_for_pos, String s) { } else { feature_flags |= flag; } + if (is_notted) { + switch (flag) { + case OptInFeatureFlag_IntegerDivisionByZero_Trap: + case OptInFeatureFlag_IntegerDivisionByZero_Zero: + syntax_error(token_for_pos, "Feature flag does not support notting with '!' - '%.*s'", LIT(p)); + break; + } + } } else { ERROR_BLOCK(); syntax_error(token_for_pos, "Invalid feature flag name: %.*s", LIT(p)); error_line("\tExpected one of the following\n"); error_line("\tdynamic-literals\n"); + error_line("\tinteger-division-by-zero:trap\n"); + error_line("\tinteger-division-by-zero:zero\n"); return OptInFeatureFlag_NONE; } } + u64 res = OptInFeatureFlag_NONE; + if (feature_flags == 0 && feature_not_flags == 0) { - return OptInFeatureFlag_NONE; - } - if (feature_flags == 0 && feature_not_flags != 0) { - return OptInFeatureFlag_NONE &~ feature_not_flags; + res = OptInFeatureFlag_NONE; + } else if (feature_flags == 0 && feature_not_flags != 0) { + res = OptInFeatureFlag_NONE &~ feature_not_flags; + } else if (feature_flags != 0 && feature_not_flags == 0) { + res = feature_flags; + } else { + GB_ASSERT(feature_flags != 0 && feature_not_flags != 0); + res = feature_flags &~ feature_not_flags; } - if (feature_flags != 0 && feature_not_flags == 0) { - return feature_flags; + + if ((res & OptInFeatureFlag_IntegerDivisionByZero_ALL) == + OptInFeatureFlag_IntegerDivisionByZero_ALL) { + syntax_error(token_for_pos, "Only one integer-division-by-zero feature flag can be enabled"); } - GB_ASSERT(feature_flags != 0 && feature_not_flags != 0); - return feature_flags &~ feature_not_flags; + + return res; } gb_internal String dir_from_path(String path) { -- cgit v1.2.3 From ee01643229cc015c35b3b1c237caa66a056bb9be Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 8 Aug 2025 10:41:05 +0100 Subject: Add `-integer-division-by-zero:self` --- src/build_settings.cpp | 7 ++++++- src/check_expr.cpp | 28 ++++++++++++++++++++++------ src/llvm_backend_expr.cpp | 17 +++++++++++++---- src/main.cpp | 9 ++++++--- src/parser.cpp | 1 + 5 files changed, 48 insertions(+), 14 deletions(-) (limited to 'src/check_expr.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index e21e7da12..a6dce5233 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -355,8 +355,9 @@ enum OptInFeatureFlags : u64 { OptInFeatureFlag_IntegerDivisionByZero_Trap = 1u<<1, OptInFeatureFlag_IntegerDivisionByZero_Zero = 1u<<2, + OptInFeatureFlag_IntegerDivisionByZero_Self = 1u<<3, - OptInFeatureFlag_IntegerDivisionByZero_ALL = OptInFeatureFlag_IntegerDivisionByZero_Trap|OptInFeatureFlag_IntegerDivisionByZero_Zero, + OptInFeatureFlag_IntegerDivisionByZero_ALL = OptInFeatureFlag_IntegerDivisionByZero_Trap|OptInFeatureFlag_IntegerDivisionByZero_Zero|OptInFeatureFlag_IntegerDivisionByZero_Self, }; @@ -370,6 +371,9 @@ u64 get_feature_flag_from_name(String const &name) { if (name == "integer-division-by-zero:zero") { return OptInFeatureFlag_IntegerDivisionByZero_Zero; } + if (name == "integer-division-by-zero:self") { + return OptInFeatureFlag_IntegerDivisionByZero_Self; + } return OptInFeatureFlag_NONE; } @@ -419,6 +423,7 @@ String linker_choices[Linker_COUNT] = { enum IntegerDivisionByZeroKind : u8 { IntegerDivisionByZero_Trap, IntegerDivisionByZero_Zero, + IntegerDivisionByZero_Self, }; // This stores the information for the specify architecture of this build diff --git a/src/check_expr.cpp b/src/check_expr.cpp index b68fe0ed0..782080c93 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -4311,7 +4311,7 @@ gb_internal void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Typ if (fail) { if (is_type_integer(x->type) || (x->mode == Addressing_Constant && x->value.kind == ExactValue_Integer)) { - if (check_for_integer_division_by_zero(c, node) == IntegerDivisionByZero_Zero) { + if (check_for_integer_division_by_zero(c, node) != IntegerDivisionByZero_Trap) { // Okay break; } @@ -4371,14 +4371,19 @@ gb_internal void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Typ match_exact_values(&a, &b); - if (check_for_integer_division_by_zero(c, node) == IntegerDivisionByZero_Zero && + IntegerDivisionByZeroKind zero_behaviour = check_for_integer_division_by_zero(c, node); + if (zero_behaviour != IntegerDivisionByZero_Trap && b.kind == ExactValue_Integer && big_int_is_zero(&b.value_integer) && (op.kind == Token_QuoEq || op.kind == Token_Mod || op.kind == Token_ModMod)) { if (op.kind == Token_QuoEq) { - // x/0 == 0 - x->value = b; + if (zero_behaviour == IntegerDivisionByZero_Zero) { + // x/0 == 0 + x->value = b; + } else { + // x/0 == x + x->value = a; + } } else { - // x%0 == x /* NOTE(bill): @integer division by zero rules @@ -4386,8 +4391,16 @@ gb_internal void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Typ floored: r = a - b*floor(a/b) IFF a/0 == 0, then (a%0 == a) or (a%%0 == a) + IFF a/0 == a, then (a%0 == 0) or (a%%0 == 0) */ - x->value = a; + + if (zero_behaviour == IntegerDivisionByZero_Zero) { + // x%0 == x + x->value = a; + } else { + // x%0 == 0 + x->value = b; + } } } else { x->value = exact_binary_operator_value(op.kind, a, b); @@ -9647,6 +9660,9 @@ gb_internal IntegerDivisionByZeroKind check_for_integer_division_by_zero(Checker if ((flags & OptInFeatureFlag_IntegerDivisionByZero_Zero) != 0) { return IntegerDivisionByZero_Zero; } + if ((flags & OptInFeatureFlag_IntegerDivisionByZero_Self) != 0) { + return IntegerDivisionByZero_Self; + } return build_context.integer_division_by_zero_behaviour; } diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index b20aef742..b44d2215e 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -302,6 +302,9 @@ gb_internal IntegerDivisionByZeroKind lb_check_for_integer_division_by_zero_beha if (flags & OptInFeatureFlag_IntegerDivisionByZero_Zero) { return IntegerDivisionByZero_Zero; } + if (flags & OptInFeatureFlag_IntegerDivisionByZero_Self) { + return IntegerDivisionByZero_Self; + } } return build_context.integer_division_by_zero_behaviour; } @@ -1159,7 +1162,6 @@ gb_internal LLVMValueRef lb_integer_division(lbProcedure *p, LLVMValueRef lhs, L lb_start_block(p, edge_case_block); - incoming_values[1] = zero; switch (lb_check_for_integer_division_by_zero_behaviour(p)) { case IntegerDivisionByZero_Trap: @@ -1167,7 +1169,10 @@ gb_internal LLVMValueRef lb_integer_division(lbProcedure *p, LLVMValueRef lhs, L LLVMBuildUnreachable(p->builder); break; case IntegerDivisionByZero_Zero: - // Already fine + incoming_values[1] = zero; + break; + case IntegerDivisionByZero_Self: + incoming_values[1] = lhs; break; } @@ -1178,6 +1183,7 @@ gb_internal LLVMValueRef lb_integer_division(lbProcedure *p, LLVMValueRef lhs, L switch (lb_check_for_integer_division_by_zero_behaviour(p)) { case IntegerDivisionByZero_Trap: + case IntegerDivisionByZero_Self: res = incoming_values[0]; break; case IntegerDivisionByZero_Zero: @@ -1242,7 +1248,6 @@ gb_internal LLVMValueRef lb_integer_modulo(lbProcedure *p, LLVMValueRef lhs, LLV IFF a/0 == 0, then (a%0 == a) or (a%%0 == a) */ - incoming_values[1] = lhs; switch (lb_check_for_integer_division_by_zero_behaviour(p)) { case IntegerDivisionByZero_Trap: @@ -1250,7 +1255,10 @@ gb_internal LLVMValueRef lb_integer_modulo(lbProcedure *p, LLVMValueRef lhs, LLV LLVMBuildUnreachable(p->builder); break; case IntegerDivisionByZero_Zero: - // Already fine + incoming_values[1] = lhs; + break; + case IntegerDivisionByZero_Self: + incoming_values[1] = zero; break; } @@ -1261,6 +1269,7 @@ gb_internal LLVMValueRef lb_integer_modulo(lbProcedure *p, LLVMValueRef lhs, LLV switch (lb_check_for_integer_division_by_zero_behaviour(p)) { case IntegerDivisionByZero_Trap: + case IntegerDivisionByZero_Self: res = incoming_values[0]; break; case IntegerDivisionByZero_Zero: diff --git a/src/main.cpp b/src/main.cpp index 0bfab0344..2f0b1c19b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1550,8 +1550,10 @@ gb_internal bool parse_build_flags(Array args) { build_context.integer_division_by_zero_behaviour = IntegerDivisionByZero_Trap; } else if (str_eq_ignore_case(value.value_string, "zero")) { build_context.integer_division_by_zero_behaviour = IntegerDivisionByZero_Zero; - } else { - gb_printf_err("-integer-division-by-zero options are 'trap' and 'zero'.\n"); + } else if (str_eq_ignore_case(value.value_string, "self")) { + build_context.integer_division_by_zero_behaviour = IntegerDivisionByZero_Self; + }else { + gb_printf_err("-integer-division-by-zero options are 'trap', 'zero', and 'self'.\n"); bad_flags = true; } break; @@ -2585,7 +2587,8 @@ gb_internal int print_show_help(String const arg0, String command, String option print_usage_line(2, "Specifies the default behaviour for integer division by zero."); print_usage_line(2, "Available Options:"); print_usage_line(3, "-integer-division-by-zero:trap Trap on division/modulo/remainder by zero"); - print_usage_line(3, "-integer-division-by-zero:zero x/0 == 0 and x%%0 == x and x%%%%0 == 0"); + print_usage_line(3, "-integer-division-by-zero:zero x/0 == 0 and x%%0 == x and x%%%%0 == x"); + print_usage_line(3, "-integer-division-by-zero:self x/0 == x and x%%0 == 0 and x%%%%0 == 0"); } } diff --git a/src/parser.cpp b/src/parser.cpp index 58d7acfa5..c63ffe747 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6428,6 +6428,7 @@ gb_internal u64 parse_feature_tag(Token token_for_pos, String s) { error_line("\tdynamic-literals\n"); error_line("\tinteger-division-by-zero:trap\n"); error_line("\tinteger-division-by-zero:zero\n"); + error_line("\tinteger-division-by-zero:self\n"); return OptInFeatureFlag_NONE; } } -- cgit v1.2.3