all: do compile time const evaluation for `const x = "abc" + "xyz"` and `const x = 16 * 1024 + 5` (fix const prealloc_block_size)

pull/10858/head
Delyan Angelov 2021-07-18 19:41:39 +03:00
parent 3ccde5ce55
commit 48546d0f45
No known key found for this signature in database
GPG Key ID: 66886C0F12D595ED
8 changed files with 356 additions and 77 deletions

View File

@ -13,8 +13,7 @@ module builtin
// NB: `-prealloc` is NOT safe to be used for multithreaded programs! // NB: `-prealloc` is NOT safe to be used for multithreaded programs!
// size of the preallocated chunk // size of the preallocated chunk
// TODO: see why comptime calculation of integer expressions fails const prealloc_block_size = 16 * 1024 * 1024
const prealloc_block_size = 16777216
__global g_memory_block &VMemoryBlock __global g_memory_block &VMemoryBlock
[heap] [heap]

View File

@ -213,6 +213,9 @@ pub:
pub mut: pub mut:
typ Type // the type of the const field, it can be any type in V typ Type // the type of the const field, it can be any type in V
comments []Comment // comments before current const field 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 // const declaration
@ -1098,16 +1101,18 @@ pub:
// .in_prexpr is also needed because of that, because the checker needs to // .in_prexpr is also needed because of that, because the checker needs to
// show warnings about the deprecated C->V conversions `string(x)` and // show warnings about the deprecated C->V conversions `string(x)` and
// `string(x,y)`, while skipping the real pointer casts like `&string(x)`. // `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 struct CastExpr {
pub: pub:
arg Expr // `n` in `string(buf, n)` arg Expr // `n` in `string(buf, n)`
pub mut: 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 pos token.Position
expr Expr // `buf` in `string(buf, n)`
typname string // TypeSymbol.name
expr_type Type // `byteptr`
has_arg bool
} }
pub struct AsmStmt { pub struct AsmStmt {

View File

@ -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
}

View File

@ -358,6 +358,9 @@ pub fn (x Expr) str() string {
} }
return s return s
} }
SelectExpr {
return 'ast.SelectExpr'
}
SelectorExpr { SelectorExpr {
return '${x.expr.str()}.$x.field_name' return '${x.expr.str()}.$x.field_name'
} }
@ -410,7 +413,50 @@ pub fn (x Expr) str() string {
None { None {
return '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()]' return '[unhandled expr type $x.type_name()]'
} }

View File

@ -3608,10 +3608,13 @@ pub fn (mut c Checker) const_decl(mut node ast.ConstDecl) {
} }
mut needs_order := false mut needs_order := false
mut done_fields := []int{} mut done_fields := []int{}
for i, field in node.fields { for i, mut field in node.fields {
c.const_decl = field.name c.const_decl = field.name
c.const_deps << field.name c.const_deps << field.name
typ := c.check_expr_opt_call(field.expr, c.expr(field.expr)) 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) node.fields[i].typ = c.table.mktyp(typ)
for cd in c.const_deps { for cd in c.const_deps {
for j, f in node.fields { 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 } else if array_init.is_fixed && array_init.exprs.len == 1
&& array_init.elem_type != ast.void_type { && array_init.elem_type != ast.void_type {
// [50]byte // [50]byte
mut fixed_size := 0 mut fixed_size := i64(0)
init_expr := array_init.exprs[0] init_expr := array_init.exprs[0]
c.expr(init_expr) c.expr(init_expr)
match 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 { ast.Ident {
if init_expr.obj is ast.ConstField { if init_expr.obj is ast.ConstField {
if cint := eval_int_expr(init_expr.obj.expr, 0) { if comptime_value := eval_comptime_const_expr(init_expr.obj.expr,
fixed_size = cint 0)
{
fixed_size = comptime_value.i64() or { fixed_size }
} }
} else { } else {
c.error('non-constant array bound `$init_expr.name`', init_expr.pos) c.error('non-constant array bound `$init_expr.name`', init_expr.pos)
} }
} }
ast.InfixExpr { ast.InfixExpr {
if cint := eval_int_expr(init_expr, 0) { if comptime_value := eval_comptime_const_expr(init_expr, 0) {
fixed_size = cint fixed_size = comptime_value.i64() or { fixed_size }
} }
} }
else { else {
@ -4373,7 +4378,7 @@ pub fn (mut c Checker) array_init(mut array_init ast.ArrayInit) ast.Type {
if fixed_size <= 0 { if fixed_size <= 0 {
c.error('fixed size cannot be zero or negative', init_expr.position()) 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) init_expr)
if array_init.elem_type.has_flag(.generic) { if array_init.elem_type.has_flag(.generic) {
array_init.typ = ast.new_type(idx).set_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 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] [inline]
fn (mut c Checker) check_loop_label(label string, pos token.Position) { fn (mut c Checker) check_loop_label(label string, pos token.Position) {
if label.len == 0 { if label.len == 0 {

View File

@ -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
}

View File

@ -4846,24 +4846,7 @@ fn (mut g Gen) const_decl(node ast.ConstDecl) {
continue continue
} }
} }
name := c_name(field.name) 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 field_expr := field.expr
match field.expr { match field.expr {
ast.ArrayInit { ast.ArrayInit {
@ -4895,6 +4878,11 @@ fn (mut g Gen) const_decl(node ast.ConstDecl) {
} }
} }
else { 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) { if is_simple_define_const(field) {
// "Simple" expressions are not going to need multiple statements, // "Simple" expressions are not going to need multiple statements,
// only the ones which are inited later, so it's safe to use expr_string // 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 { fn is_simple_define_const(obj ast.ScopeObject) bool {
if obj is ast.ConstField { if obj is ast.ConstField {
return match obj.expr { return match obj.expr {
@ -4917,6 +4914,38 @@ fn is_simple_define_const(obj ast.ScopeObject) bool {
return false 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) { fn (mut g Gen) const_decl_simple_define(name string, val string) {
// Simple expressions should use a #define // Simple expressions should use a #define
// so that we don't pollute the binary with unnecessary global vars // so that we don't pollute the binary with unnecessary global vars

View File

@ -179,7 +179,7 @@ const c_common_macros = '
# define VNORETURN _Noreturn # define VNORETURN _Noreturn
# elif defined(__GNUC__) && __GNUC__ >= 2 # elif defined(__GNUC__) && __GNUC__ >= 2
# define VNORETURN __attribute__((noreturn)) # define VNORETURN __attribute__((noreturn))
# endif # endif
#ifndef VNORETURN #ifndef VNORETURN
#define VNORETURN #define VNORETURN
#endif #endif
@ -191,7 +191,7 @@ const c_common_macros = '
#if (V_GCC_VERSION >= 40500L) #if (V_GCC_VERSION >= 40500L)
#define VUNREACHABLE() do { __builtin_unreachable(); } while (0) #define VUNREACHABLE() do { __builtin_unreachable(); } while (0)
#endif #endif
#endif #endif
#if defined(__clang__) && defined(__has_builtin) #if defined(__clang__) && defined(__has_builtin)
#if __has_builtin(__builtin_unreachable) #if __has_builtin(__builtin_unreachable)
#define VUNREACHABLE() do { __builtin_unreachable(); } while (0) #define VUNREACHABLE() do { __builtin_unreachable(); } while (0)
@ -199,7 +199,7 @@ const c_common_macros = '
#endif #endif
#ifndef VUNREACHABLE #ifndef VUNREACHABLE
#define VUNREACHABLE() do { } while (0) #define VUNREACHABLE() do { } while (0)
#endif #endif
#endif #endif
//likely and unlikely macros //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 =============================*/ const c_helper_macros = '//============================== HELPER C MACROS =============================*/
// _SLIT0 is used as NULL string for literal arguments // _SLIT0 is used as NULL string for literal arguments
// `"" s` is used to enforce a string literal argument // `"" 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 _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 // take the address of an rvalue
#define ADDR(type, expr) (&((type[]){expr}[0])) #define ADDR(type, expr) (&((type[]){expr}[0]))
// copy something to the heap // copy something to the heap
#define HEAP(type, expr) ((type*)memdup((void*)&((type[]){expr}[0]), sizeof(type))) #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 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(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);} #define _PUSH_MANY_noscan(arr, val, tmp, tmp_typ) {tmp_typ tmp = (val); array_push_many_noscan(arr, tmp.data, tmp.len);}
' '