all: do compile time const evaluation for `const x = "abc" + "xyz"` and `const x = 16 * 1024 + 5` (fix const prealloc_block_size)
parent
3ccde5ce55
commit
48546d0f45
|
@ -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]
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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()]'
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);}
|
||||
'
|
||||
|
|
Loading…
Reference in New Issue