diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 9e3e701270..74db39f4fc 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -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()) } diff --git a/vlib/v/gen/c/infix.v b/vlib/v/gen/c/infix.v index c28e711576..22509fd0e1 100644 --- a/vlib/v/gen/c/infix.v +++ b/vlib/v/gen/c/infix.v @@ -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(')') } diff --git a/vlib/v/gen/c/testdata/div_by_zero_is_zero.c.must_have b/vlib/v/gen/c/testdata/div_by_zero_is_zero.c.must_have new file mode 100644 index 0000000000..c5ecb2fa19 --- /dev/null +++ b/vlib/v/gen/c/testdata/div_by_zero_is_zero.c.must_have @@ -0,0 +1,2 @@ +int c = (int)(VSAFE_DIV_int(a , z)); +int d = (int)(VSAFE_MOD_int(a , z)); diff --git a/vlib/v/gen/c/testdata/div_by_zero_is_zero.out b/vlib/v/gen/c/testdata/div_by_zero_is_zero.out new file mode 100644 index 0000000000..c338e4cdec --- /dev/null +++ b/vlib/v/gen/c/testdata/div_by_zero_is_zero.out @@ -0,0 +1,4 @@ +a == 42 +z == 0 +c == a / z == 0 +d == a % z == 42 diff --git a/vlib/v/gen/c/testdata/div_by_zero_is_zero.vv b/vlib/v/gen/c/testdata/div_by_zero_is_zero.vv new file mode 100644 index 0000000000..d1f280b7e8 --- /dev/null +++ b/vlib/v/gen/c/testdata/div_by_zero_is_zero.vv @@ -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}') diff --git a/vlib/v/help/build/build-c.txt b/vlib/v/help/build/build-c.txt index 87c1395206..d14b1114c9 100644 --- a/vlib/v/help/build/build-c.txt +++ b/vlib/v/help/build/build-c.txt @@ -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). diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index 76be470fd5..3244cadfde 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -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 }