From 48546d0f4500ffc53939904404a7cafbf73cb032 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sun, 18 Jul 2021 19:41:39 +0300 Subject: [PATCH] all: do compile time const evaluation for `const x = "abc" + "xyz"` and `const x = 16 * 1024 + 5` (fix const prealloc_block_size) --- vlib/builtin/prealloc.c.v | 3 +- vlib/v/ast/ast.v | 15 ++- vlib/v/ast/comptime_const_values.v | 138 +++++++++++++++++++++++++++ vlib/v/ast/str.v | 48 +++++++++- vlib/v/checker/checker.v | 60 +++--------- vlib/v/checker/comptime_const_eval.v | 94 ++++++++++++++++++ vlib/v/gen/c/cgen.v | 63 ++++++++---- vlib/v/gen/c/cheaders.v | 12 ++- 8 files changed, 356 insertions(+), 77 deletions(-) create mode 100644 vlib/v/ast/comptime_const_values.v create mode 100644 vlib/v/checker/comptime_const_eval.v diff --git a/vlib/builtin/prealloc.c.v b/vlib/builtin/prealloc.c.v index b0feba6b34..0ef66fc650 100644 --- a/vlib/builtin/prealloc.c.v +++ b/vlib/builtin/prealloc.c.v @@ -13,8 +13,7 @@ module builtin // NB: `-prealloc` is NOT safe to be used for multithreaded programs! // size of the preallocated chunk -// TODO: see why comptime calculation of integer expressions fails -const prealloc_block_size = 16777216 +const prealloc_block_size = 16 * 1024 * 1024 __global g_memory_block &VMemoryBlock [heap] diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 115658c9a6..7692a4057d 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -213,6 +213,9 @@ pub: pub mut: typ Type // the type of the const field, it can be any type in V comments []Comment // comments before current const field + // the comptime_expr_value field is filled by the checker, when it has enough + // info to evaluate the constant at compile time + comptime_expr_value ComptTimeConstValue = empty_comptime_const_expr() } // const declaration @@ -1098,16 +1101,18 @@ pub: // .in_prexpr is also needed because of that, because the checker needs to // show warnings about the deprecated C->V conversions `string(x)` and // `string(x,y)`, while skipping the real pointer casts like `&string(x)`. +// 2021/07/17: TODO: since 6edfb2c, the above is fixed at the parser level, +// we need to remove the hacks/special cases in vfmt and the checker too. pub struct CastExpr { pub: arg Expr // `n` in `string(buf, n)` pub mut: - typ Type // `string` TODO rename to `type_to_cast_to` + typ Type // `string` + expr Expr // `buf` in `string(buf, n)` and `&Type(buf)` + typname string // `&Type` in `&Type(buf)` + expr_type Type // `byteptr`, the type of the `buf` expression + has_arg bool // true for `string(buf, n)`, false for `&Type(buf)` pos token.Position - expr Expr // `buf` in `string(buf, n)` - typname string // TypeSymbol.name - expr_type Type // `byteptr` - has_arg bool } pub struct AsmStmt { diff --git a/vlib/v/ast/comptime_const_values.v b/vlib/v/ast/comptime_const_values.v new file mode 100644 index 0000000000..e11571fe8b --- /dev/null +++ b/vlib/v/ast/comptime_const_values.v @@ -0,0 +1,138 @@ +module ast + +pub type ComptTimeConstValue = EmptyExpr | byte | f64 | i64 | rune | string + +pub fn empty_comptime_const_expr() ComptTimeConstValue { + return EmptyExpr{} +} + +pub fn (val ComptTimeConstValue) i64() ?i64 { + match val { + i64, byte { + return i64(val) + } + f64 { + if -9223372036854775808.0 <= val && val <= 9223372036854775807.0 { + return i64(val) + } + return none + } + string { + return val.i64() + } + rune { + return int(val) + } + EmptyExpr { + return none + } + } + return none +} + +pub fn (val ComptTimeConstValue) int() ?int { + match val { + f64 { + if -2147483648.0 <= val && val <= 2147483647.0 { + return int(val) + } + return none + } + i64 { + if -2147483648 <= val && val <= 2147483647 { + return int(val) + } + return none + } + byte { + return int(val) + } + string { + return val.int() + } + rune { + return none + } + EmptyExpr { + return none + } + } + return none +} + +pub fn (val ComptTimeConstValue) string() ?string { + match val { + i64, f64, byte { + return val.str() + } + string { + return val + } + rune { + return val.str() + } + EmptyExpr { + return none + } + } + return none +} + +pub fn (val ComptTimeConstValue) f64() ?f64 { + match val { + f64 { + return val + } + i64, byte { + return f64(val) + } + string { + return val.f64() + } + rune { + return none + } + EmptyExpr { + return none + } + } + return none +} + +pub fn (val ComptTimeConstValue) byte() ?byte { + match val { + byte { + return val + } + f64 { + if 0 <= val && val <= 255 { + return byte(val) + } + return none + } + i64 { + if 0 <= val && val <= 255 { + return byte(val) + } + return none + } + string { + x := val.int() + if 0 <= x && x <= 255 { + return byte(x) + } + return none + } + rune { + x := u32(val) + if 0 <= x && x <= 255 { + return byte(x) + } + return none + } + EmptyExpr { + return none + } + } + return none +} diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index 2af805430b..7d54455b8a 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -358,6 +358,9 @@ pub fn (x Expr) str() string { } return s } + SelectExpr { + return 'ast.SelectExpr' + } SelectorExpr { return '${x.expr.str()}.$x.field_name' } @@ -410,7 +413,50 @@ pub fn (x Expr) str() string { None { return 'none' } - else {} + IsRefType { + return 'isreftype(' + if x.is_type { + global_table.type_to_str(x.typ) + } else { + x.expr.str() + } + ')' + } + IfGuardExpr { + return x.var_name + ' := ' + x.expr.str() + } + StructInit { + sname := global_table.get_type_symbol(x.typ).name + return '$sname{....}' + } + ArrayDecompose { + return 'ast.ArrayDecompose' + } + Assoc { + return 'ast.Assoc' + } + ChanInit { + return 'ast.ChanInit' + } + ComptimeCall { + return 'ast.ComptimeCall' + } + EmptyExpr { + return 'ast.EmptyExpr' + } + LockExpr { + return 'ast.LockExpr' + } + MatchExpr { + return 'ast.MatchExpr' + } + NodeError { + return 'ast.NodeError' + } + OrExpr { + return 'ast.OrExpr' + } + SqlExpr { + return 'ast.SqlExpr' + } } return '[unhandled expr type $x.type_name()]' } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 213451f79f..6a8c54a889 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -3608,10 +3608,13 @@ pub fn (mut c Checker) const_decl(mut node ast.ConstDecl) { } mut needs_order := false mut done_fields := []int{} - for i, field in node.fields { + for i, mut field in node.fields { c.const_decl = field.name c.const_deps << field.name typ := c.check_expr_opt_call(field.expr, c.expr(field.expr)) + if ct_value := eval_comptime_const_expr(field.expr, 0) { + field.comptime_expr_value = ct_value + } node.fields[i].typ = c.table.mktyp(typ) for cd in c.const_deps { for j, f in node.fields { @@ -4345,7 +4348,7 @@ pub fn (mut c Checker) array_init(mut array_init ast.ArrayInit) ast.Type { } else if array_init.is_fixed && array_init.exprs.len == 1 && array_init.elem_type != ast.void_type { // [50]byte - mut fixed_size := 0 + mut fixed_size := i64(0) init_expr := array_init.exprs[0] c.expr(init_expr) match init_expr { @@ -4354,16 +4357,18 @@ pub fn (mut c Checker) array_init(mut array_init ast.ArrayInit) ast.Type { } ast.Ident { if init_expr.obj is ast.ConstField { - if cint := eval_int_expr(init_expr.obj.expr, 0) { - fixed_size = cint + if comptime_value := eval_comptime_const_expr(init_expr.obj.expr, + 0) + { + fixed_size = comptime_value.i64() or { fixed_size } } } else { c.error('non-constant array bound `$init_expr.name`', init_expr.pos) } } ast.InfixExpr { - if cint := eval_int_expr(init_expr, 0) { - fixed_size = cint + if comptime_value := eval_comptime_const_expr(init_expr, 0) { + fixed_size = comptime_value.i64() or { fixed_size } } } else { @@ -4373,7 +4378,7 @@ pub fn (mut c Checker) array_init(mut array_init ast.ArrayInit) ast.Type { if fixed_size <= 0 { c.error('fixed size cannot be zero or negative', init_expr.position()) } - idx := c.table.find_or_register_array_fixed(array_init.elem_type, fixed_size, + idx := c.table.find_or_register_array_fixed(array_init.elem_type, int(fixed_size), init_expr) if array_init.elem_type.has_flag(.generic) { array_init.typ = ast.new_type(idx).set_flag(.generic) @@ -4387,47 +4392,6 @@ pub fn (mut c Checker) array_init(mut array_init ast.ArrayInit) ast.Type { return array_init.typ } -fn eval_int_expr(expr ast.Expr, nlevel int) ?int { - if nlevel > 100 { - // protect against a too deep comptime eval recursion: - return none - } - match expr { - ast.IntegerLiteral { - return expr.val.int() - } - ast.InfixExpr { - left := eval_int_expr(expr.left, nlevel + 1) ? - right := eval_int_expr(expr.right, nlevel + 1) ? - match expr.op { - .plus { return left + right } - .minus { return left - right } - .mul { return left * right } - .div { return left / right } - .mod { return left % right } - .xor { return left ^ right } - .pipe { return left | right } - .amp { return left & right } - .left_shift { return left << right } - .right_shift { return left >> right } - else { return none } - } - } - ast.Ident { - if expr.obj is ast.ConstField { - // an int constant? - cint := eval_int_expr(expr.obj.expr, nlevel + 1) ? - return cint - } - } - else { - // dump(expr) - return none - } - } - return none -} - [inline] fn (mut c Checker) check_loop_label(label string, pos token.Position) { if label.len == 0 { diff --git a/vlib/v/checker/comptime_const_eval.v b/vlib/v/checker/comptime_const_eval.v new file mode 100644 index 0000000000..d514e08a97 --- /dev/null +++ b/vlib/v/checker/comptime_const_eval.v @@ -0,0 +1,94 @@ +module checker + +import v.ast + +fn eval_comptime_const_expr(expr ast.Expr, nlevel int) ?ast.ComptTimeConstValue { + if nlevel > 100 { + // protect against a too deep comptime eval recursion + return none + } + x := expr + match expr { + ast.IntegerLiteral { + return expr.val.i64() + } + ast.StringLiteral { + return expr.val + } + ast.CastExpr { + cast_expr_value := eval_comptime_const_expr(expr.expr, nlevel + 1) or { return none } + if expr.typ == ast.byte_type { + return cast_expr_value.byte() or { return none } + } + if expr.typ == ast.int_type { + match cast_expr_value { + byte { + eprintln('>>>>>>> byte cast_expr_value: $cast_expr_value | x: $x') + return i64(cast_expr_value) + } + i64 { + eprintln('>>>>>>> i64 cast_expr_value: $cast_expr_value | x: $x') + if int_min <= cast_expr_value && cast_expr_value <= int_max { + return i64(cast_expr_value) + } + } + else {} + } + return none + } + } + ast.InfixExpr { + left := eval_comptime_const_expr(expr.left, nlevel + 1) ? + right := eval_comptime_const_expr(expr.right, nlevel + 1) ? + if left is string && right is string { + match expr.op { + .plus { + return left + right + } + else { + return none + } + } + } else if left is i64 && right is i64 { + match expr.op { + .plus { return left + right } + .minus { return left - right } + .mul { return left * right } + .div { return left / right } + .mod { return left % right } + .xor { return left ^ right } + .pipe { return left | right } + .amp { return left & right } + .left_shift { return left << right } + .right_shift { return left >> right } + else { return none } + } + } else if left is byte && right is byte { + match expr.op { + .plus { return left + right } + .minus { return left - right } + .mul { return left * right } + .div { return left / right } + .mod { return left % right } + .xor { return left ^ right } + .pipe { return left | right } + .amp { return left & right } + .left_shift { return left << right } + .right_shift { return left >> right } + else { return none } + } + } + } + ast.Ident { + if expr.obj is ast.ConstField { + // an existing constant? + return eval_comptime_const_expr(expr.obj.expr, nlevel + 1) + } + } + else { + // eprintln('>>> nlevel: $nlevel | another $expr.type_name() | $expr ') + return none + } + } + return none +} diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 6d6208198e..b7280eb044 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -4846,24 +4846,7 @@ fn (mut g Gen) const_decl(node ast.ConstDecl) { continue } } - name := c_name(field.name) - /* - if field.typ == ast.byte_type { - g.const_decl_simple_define(name, val) - return - } - */ - /* - if ast.is_number(field.typ) { - g.const_decl_simple_define(name, val) - } else if field.typ == ast.string_type { - g.definitions.writeln('string _const_$name; // a string literal, inited later') - if g.pref.build_mode != .build_module { - g.stringliterals.writeln('\t_const_$name = $val;') - } - } else { - */ field_expr := field.expr match field.expr { ast.ArrayInit { @@ -4895,6 +4878,11 @@ fn (mut g Gen) const_decl(node ast.ConstDecl) { } } else { + if ct_value := comptime_expr_value(field) { + if g.const_decl_precomputed(field.mod, name, ct_value, field.typ) { + continue + } + } if is_simple_define_const(field) { // "Simple" expressions are not going to need multiple statements, // only the ones which are inited later, so it's safe to use expr_string @@ -4907,6 +4895,15 @@ fn (mut g Gen) const_decl(node ast.ConstDecl) { } } +fn comptime_expr_value(obj ast.ScopeObject) ?ast.ComptTimeConstValue { + if obj is ast.ConstField { + if obj.comptime_expr_value !is ast.EmptyExpr { + return obj.comptime_expr_value + } + } + return none +} + fn is_simple_define_const(obj ast.ScopeObject) bool { if obj is ast.ConstField { return match obj.expr { @@ -4917,6 +4914,38 @@ fn is_simple_define_const(obj ast.ScopeObject) bool { return false } +fn (mut g Gen) const_decl_precomputed(mod string, name string, ct_value ast.ComptTimeConstValue, typ ast.Type) bool { + mut styp := g.typ(typ) + cname := '_const_$name' + // eprintln('>> cname: $cname | styp: $styp | $ct_value.type_name() | $ct_value') + match ct_value { + byte { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + rune { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + i64 { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + f64 { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + string { + escaped_val := util.smart_quote(ct_value, false) + g.const_decl_write_precomputed(styp, cname, '_SLIT("$escaped_val")') + } + ast.EmptyExpr { + return false + } + } + return true +} + +fn (mut g Gen) const_decl_write_precomputed(styp string, cname string, ct_value string) { + g.definitions.writeln('$styp $cname = $ct_value; // precomputed') +} + fn (mut g Gen) const_decl_simple_define(name string, val string) { // Simple expressions should use a #define // so that we don't pollute the binary with unnecessary global vars diff --git a/vlib/v/gen/c/cheaders.v b/vlib/v/gen/c/cheaders.v index 5a3d71bfdd..8b3e228d75 100644 --- a/vlib/v/gen/c/cheaders.v +++ b/vlib/v/gen/c/cheaders.v @@ -179,7 +179,7 @@ const c_common_macros = ' # define VNORETURN _Noreturn # elif defined(__GNUC__) && __GNUC__ >= 2 # define VNORETURN __attribute__((noreturn)) - # endif + # endif #ifndef VNORETURN #define VNORETURN #endif @@ -191,7 +191,7 @@ const c_common_macros = ' #if (V_GCC_VERSION >= 40500L) #define VUNREACHABLE() do { __builtin_unreachable(); } while (0) #endif - #endif + #endif #if defined(__clang__) && defined(__has_builtin) #if __has_builtin(__builtin_unreachable) #define VUNREACHABLE() do { __builtin_unreachable(); } while (0) @@ -199,7 +199,7 @@ const c_common_macros = ' #endif #ifndef VUNREACHABLE #define VUNREACHABLE() do { } while (0) - #endif + #endif #endif //likely and unlikely macros @@ -232,13 +232,17 @@ static inline bool _us64_lt(uint64_t a, int64_t b) { return a < INT64_MAX && (in const c_helper_macros = '//============================== HELPER C MACROS =============================*/ // _SLIT0 is used as NULL string for literal arguments // `"" s` is used to enforce a string literal argument -#define _SLIT0 (string){.len=0} +#define _SLIT0 (string){.str=(byteptr)(""), .len=0, .is_lit=1} #define _SLIT(s) ((string){.str=(byteptr)("" s), .len=(sizeof(s)-1), .is_lit=1}) +#define _SLEN(s, n) ((string){.str=(byteptr)("" s), .len=n, .is_lit=1}) + // take the address of an rvalue #define ADDR(type, expr) (&((type[]){expr}[0])) + // copy something to the heap #define HEAP(type, expr) ((type*)memdup((void*)&((type[]){expr}[0]), sizeof(type))) #define HEAP_noscan(type, expr) ((type*)memdup_noscan((void*)&((type[]){expr}[0]), sizeof(type))) + #define _PUSH_MANY(arr, val, tmp, tmp_typ) {tmp_typ tmp = (val); array_push_many(arr, tmp.data, tmp.len);} #define _PUSH_MANY_noscan(arr, val, tmp, tmp_typ) {tmp_typ tmp = (val); array_push_many_noscan(arr, tmp.data, tmp.len);} '