v: support -div-by-zero-is-zero, which allows for x / 0 == 0 and x % 0 == x, avoiding division by zero traps/panics (#24981)

This commit is contained in:
Delyan Angelov 2025-07-28 10:41:18 +03:00 committed by GitHub
parent c1db6006bd
commit a5f9899ae3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 73 additions and 5 deletions

View File

@ -79,6 +79,7 @@ mut:
json_forward_decls strings.Builder // json type forward decls
sql_buf strings.Builder // for writing exprs to args via `sqlite3_bind_int()` etc
global_const_defs map[string]GlobalConstDef
vsafe_arithmetic_ops map[string]VSafeArithmeticOp // 'VSAFE_DIV_u8' -> {11, /}, 'VSAFE_MOD_u8' -> {11,%}, 'VSAFE_MOD_i64' -> the same but with 9
sorted_global_const_names []string
file &ast.File = unsafe { nil }
table &ast.Table = unsafe { nil }
@ -408,6 +409,9 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO
global_g.force_main_console = global_g.force_main_console || g.force_main_console
// merge maps
for k, v in g.vsafe_arithmetic_ops {
global_g.vsafe_arithmetic_ops[k] = v
}
for k, v in g.global_const_defs {
global_g.global_const_defs[k] = v
}
@ -661,6 +665,16 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO
if g.channel_definitions.len > 0 {
b.write_string2('\n// V channel code:\n', g.channel_definitions.str())
}
if g.vsafe_arithmetic_ops.len > 0 {
for vsafe_fn_name, val in g.vsafe_arithmetic_ops {
styp := g.styp(val.typ)
if val.op == .div {
b.writeln('static inline ${styp} ${vsafe_fn_name}(${styp} x, ${styp} y) { if (_unlikely_(0 == y)) { return 0; } else { return x / y; } }')
} else {
b.writeln('static inline ${styp} ${vsafe_fn_name}(${styp} x, ${styp} y) { if (_unlikely_(0 == y)) { return x; } else { return x % y; } }')
}
}
}
if g.pref.is_coverage {
b.write_string2('\n// V coverage:\n', g.cov_declarations.str())
}

View File

@ -1190,6 +1190,11 @@ fn (mut g Gen) gen_is_none_check(node ast.InfixExpr) {
g.write(' ${node.op.str()} 2') // none state
}
struct VSafeArithmeticOp {
typ ast.Type
op token.Kind
}
// gen_plain_infix_expr generates basic code for infix expressions,
// without any overloading of any kind
// i.e. v`a + 1` => c`a + 1`
@ -1199,14 +1204,19 @@ fn (mut g Gen) gen_plain_infix_expr(node ast.InfixExpr) {
needs_cast := node.left_type.is_number() && node.right_type.is_number()
&& node.op in [.plus, .minus, .mul, .div, .mod] && !(g.pref.translated
|| g.file.is_translated)
is_safe_div := node.op == .div && g.pref.div_by_zero_is_zero
is_safe_mod := node.op == .mod && g.pref.div_by_zero_is_zero
mut typ := node.promoted_type
mut typ_str := g.styp(typ)
if needs_cast {
typ_str := if node.left_ct_expr {
g.styp(g.type_resolver.get_type_or_default(node.left, node.left_type))
typ = if node.left_ct_expr {
g.type_resolver.get_type_or_default(node.left, node.left_type)
} else if node.left !in [ast.Ident, ast.CastExpr] && node.right_ct_expr {
g.styp(g.type_resolver.get_type_or_default(node.right, node.promoted_type))
g.type_resolver.get_type_or_default(node.right, node.promoted_type)
} else {
g.styp(node.promoted_type)
node.promoted_type
}
typ_str = g.styp(typ)
g.write('(${typ_str})(')
}
if node.left_type.is_ptr() && node.left.is_auto_deref_var() && !node.right_type.is_pointer() {
@ -1227,9 +1237,22 @@ fn (mut g Gen) gen_plain_infix_expr(node ast.InfixExpr) {
}
g.write('memcmp(')
}
mut opstr := node.op.str()
if is_safe_div || is_safe_mod {
vsafe_fn_name := if is_safe_div { 'VSAFE_DIV_${typ_str}' } else { 'VSAFE_MOD_${typ_str}' }
g.write(vsafe_fn_name)
g.write('(')
g.vsafe_arithmetic_ops[vsafe_fn_name] = VSafeArithmeticOp{
typ: typ
op: node.op
}
opstr = ','
}
g.expr(node.left)
if !is_ctemp_fixed_ret {
g.write(' ${node.op.str()} ')
g.write(' ')
g.write(opstr)
g.write(' ')
} else {
g.write(', ')
}
@ -1246,6 +1269,9 @@ fn (mut g Gen) gen_plain_infix_expr(node ast.InfixExpr) {
if is_ctemp_fixed_ret {
g.write(', sizeof(${g.styp(node.right_type)}))')
}
if is_safe_div || is_safe_mod {
g.write(')')
}
if needs_cast {
g.write(')')
}

View File

@ -0,0 +1,2 @@
int c = (int)(VSAFE_DIV_int(a , z));
int d = (int)(VSAFE_MOD_int(a , z));

View File

@ -0,0 +1,4 @@
a == 42
z == 0
c == a / z == 0
d == a % z == 42

View File

@ -0,0 +1,9 @@
// vtest vflags: -div-by-zero-is-zero
a := 42
z := 0
c := a / z
d := a % z
println('a == ${a}')
println('z == ${z}')
println('c == a / z == ${c}')
println('d == a % z == ${d}')

View File

@ -361,3 +361,11 @@ see also `v help build`.
https://learn.microsoft.com/en-us/cpp/build/reference/fp-specify-floating-point-behavior?view=msvc-170&redirectedfrom=MSDN
https://clang.llvm.org/docs/UsersManual.html#cmdoption-ffast-math
https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#index-ffast-math
-div-by-zero-is-zero
Avoids division by zero errors, by treating `x / 0` as being equal to `0`.
It also treats `x % 0` as being equal to `x`, because `x%y`==`x-y*(x/y)`,
which in turn eliminates another source of division by zero errors.
It is mainly useful when prototyping games, and other experimental code with
lots of arithmetic expressions, where you do not want to check for the zero divisor
all the time (which can break your flow).

View File

@ -250,6 +250,8 @@ pub mut:
wasm_stack_top int = 1024 + (16 * 1024) // stack size for webassembly backend
wasm_validate bool // validate webassembly code, by calling `wasm-validate`
warn_about_allocs bool // -warn-about-allocs warngs about every single allocation, e.g. 'hi $name'. Mostly for low level development where manual memory management is used.
// game prototyping flags:
div_by_zero_is_zero bool // -div-by-zero-is-zero makes so `x / 0 == 0`, i.e. eliminates the division by zero panics/segfaults
// temp
// use_64_int bool
// forwards compatibility settings:
@ -547,6 +549,9 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin
'-warn-about-allocs' {
res.warn_about_allocs = true
}
'-div-by-zero-is-zero' {
res.div_by_zero_is_zero = true
}
'-sourcemap-src-included' {
res.sourcemap_src_included = true
}