mirror of
https://github.com/vlang/v.git
synced 2025-08-03 09:47:15 -04:00
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:
parent
c1db6006bd
commit
a5f9899ae3
@ -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())
|
||||
}
|
||||
|
@ -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(')')
|
||||
}
|
||||
|
2
vlib/v/gen/c/testdata/div_by_zero_is_zero.c.must_have
vendored
Normal file
2
vlib/v/gen/c/testdata/div_by_zero_is_zero.c.must_have
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
int c = (int)(VSAFE_DIV_int(a , z));
|
||||
int d = (int)(VSAFE_MOD_int(a , z));
|
4
vlib/v/gen/c/testdata/div_by_zero_is_zero.out
vendored
Normal file
4
vlib/v/gen/c/testdata/div_by_zero_is_zero.out
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
a == 42
|
||||
z == 0
|
||||
c == a / z == 0
|
||||
d == a % z == 42
|
9
vlib/v/gen/c/testdata/div_by_zero_is_zero.vv
vendored
Normal file
9
vlib/v/gen/c/testdata/div_by_zero_is_zero.vv
vendored
Normal 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}')
|
@ -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).
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user