Skip to content

Commit a5f9899

Browse files
authored
v: support -div-by-zero-is-zero, which allows for x / 0 == 0 and x % 0 == x, avoiding division by zero traps/panics (#24981)
1 parent c1db600 commit a5f9899

File tree

7 files changed

+73
-5
lines changed

7 files changed

+73
-5
lines changed

vlib/v/gen/c/cgen.v

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ mut:
7979
json_forward_decls strings.Builder // json type forward decls
8080
sql_buf strings.Builder // for writing exprs to args via `sqlite3_bind_int()` etc
8181
global_const_defs map[string]GlobalConstDef
82+
vsafe_arithmetic_ops map[string]VSafeArithmeticOp // 'VSAFE_DIV_u8' -> {11, /}, 'VSAFE_MOD_u8' -> {11,%}, 'VSAFE_MOD_i64' -> the same but with 9
8283
sorted_global_const_names []string
8384
file &ast.File = unsafe { nil }
8485
table &ast.Table = unsafe { nil }
@@ -408,6 +409,9 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO
408409
global_g.force_main_console = global_g.force_main_console || g.force_main_console
409410

410411
// merge maps
412+
for k, v in g.vsafe_arithmetic_ops {
413+
global_g.vsafe_arithmetic_ops[k] = v
414+
}
411415
for k, v in g.global_const_defs {
412416
global_g.global_const_defs[k] = v
413417
}
@@ -661,6 +665,16 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO
661665
if g.channel_definitions.len > 0 {
662666
b.write_string2('\n// V channel code:\n', g.channel_definitions.str())
663667
}
668+
if g.vsafe_arithmetic_ops.len > 0 {
669+
for vsafe_fn_name, val in g.vsafe_arithmetic_ops {
670+
styp := g.styp(val.typ)
671+
if val.op == .div {
672+
b.writeln('static inline ${styp} ${vsafe_fn_name}(${styp} x, ${styp} y) { if (_unlikely_(0 == y)) { return 0; } else { return x / y; } }')
673+
} else {
674+
b.writeln('static inline ${styp} ${vsafe_fn_name}(${styp} x, ${styp} y) { if (_unlikely_(0 == y)) { return x; } else { return x % y; } }')
675+
}
676+
}
677+
}
664678
if g.pref.is_coverage {
665679
b.write_string2('\n// V coverage:\n', g.cov_declarations.str())
666680
}

vlib/v/gen/c/infix.v

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,6 +1190,11 @@ fn (mut g Gen) gen_is_none_check(node ast.InfixExpr) {
11901190
g.write(' ${node.op.str()} 2') // none state
11911191
}
11921192

1193+
struct VSafeArithmeticOp {
1194+
typ ast.Type
1195+
op token.Kind
1196+
}
1197+
11931198
// gen_plain_infix_expr generates basic code for infix expressions,
11941199
// without any overloading of any kind
11951200
// i.e. v`a + 1` => c`a + 1`
@@ -1199,14 +1204,19 @@ fn (mut g Gen) gen_plain_infix_expr(node ast.InfixExpr) {
11991204
needs_cast := node.left_type.is_number() && node.right_type.is_number()
12001205
&& node.op in [.plus, .minus, .mul, .div, .mod] && !(g.pref.translated
12011206
|| g.file.is_translated)
1207+
is_safe_div := node.op == .div && g.pref.div_by_zero_is_zero
1208+
is_safe_mod := node.op == .mod && g.pref.div_by_zero_is_zero
1209+
mut typ := node.promoted_type
1210+
mut typ_str := g.styp(typ)
12021211
if needs_cast {
1203-
typ_str := if node.left_ct_expr {
1204-
g.styp(g.type_resolver.get_type_or_default(node.left, node.left_type))
1212+
typ = if node.left_ct_expr {
1213+
g.type_resolver.get_type_or_default(node.left, node.left_type)
12051214
} else if node.left !in [ast.Ident, ast.CastExpr] && node.right_ct_expr {
1206-
g.styp(g.type_resolver.get_type_or_default(node.right, node.promoted_type))
1215+
g.type_resolver.get_type_or_default(node.right, node.promoted_type)
12071216
} else {
1208-
g.styp(node.promoted_type)
1217+
node.promoted_type
12091218
}
1219+
typ_str = g.styp(typ)
12101220
g.write('(${typ_str})(')
12111221
}
12121222
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) {
12271237
}
12281238
g.write('memcmp(')
12291239
}
1240+
mut opstr := node.op.str()
1241+
if is_safe_div || is_safe_mod {
1242+
vsafe_fn_name := if is_safe_div { 'VSAFE_DIV_${typ_str}' } else { 'VSAFE_MOD_${typ_str}' }
1243+
g.write(vsafe_fn_name)
1244+
g.write('(')
1245+
g.vsafe_arithmetic_ops[vsafe_fn_name] = VSafeArithmeticOp{
1246+
typ: typ
1247+
op: node.op
1248+
}
1249+
opstr = ','
1250+
}
12301251
g.expr(node.left)
12311252
if !is_ctemp_fixed_ret {
1232-
g.write(' ${node.op.str()} ')
1253+
g.write(' ')
1254+
g.write(opstr)
1255+
g.write(' ')
12331256
} else {
12341257
g.write(', ')
12351258
}
@@ -1246,6 +1269,9 @@ fn (mut g Gen) gen_plain_infix_expr(node ast.InfixExpr) {
12461269
if is_ctemp_fixed_ret {
12471270
g.write(', sizeof(${g.styp(node.right_type)}))')
12481271
}
1272+
if is_safe_div || is_safe_mod {
1273+
g.write(')')
1274+
}
12491275
if needs_cast {
12501276
g.write(')')
12511277
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
int c = (int)(VSAFE_DIV_int(a , z));
2+
int d = (int)(VSAFE_MOD_int(a , z));
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
a == 42
2+
z == 0
3+
c == a / z == 0
4+
d == a % z == 42
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// vtest vflags: -div-by-zero-is-zero
2+
a := 42
3+
z := 0
4+
c := a / z
5+
d := a % z
6+
println('a == ${a}')
7+
println('z == ${z}')
8+
println('c == a / z == ${c}')
9+
println('d == a % z == ${d}')

vlib/v/help/build/build-c.txt

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

vlib/v/pref/pref.v

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,8 @@ pub mut:
250250
wasm_stack_top int = 1024 + (16 * 1024) // stack size for webassembly backend
251251
wasm_validate bool // validate webassembly code, by calling `wasm-validate`
252252
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.
253+
// game prototyping flags:
254+
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
253255
// temp
254256
// use_64_int bool
255257
// forwards compatibility settings:
@@ -547,6 +549,9 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin
547549
'-warn-about-allocs' {
548550
res.warn_about_allocs = true
549551
}
552+
'-div-by-zero-is-zero' {
553+
res.div_by_zero_is_zero = true
554+
}
550555
'-sourcemap-src-included' {
551556
res.sourcemap_src_included = true
552557
}

0 commit comments

Comments
 (0)