Skip to content

Commit 14b33ba

Browse files
authored
transformer: refactor + apply transform to (hopefully) all nodes (#13216)
1 parent d67be63 commit 14b33ba

File tree

8 files changed

+797
-659
lines changed

8 files changed

+797
-659
lines changed

vlib/v/ast/ast.v

Lines changed: 75 additions & 70 deletions
Large diffs are not rendered by default.

vlib/v/checker/checker.v

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1821,7 +1821,7 @@ fn (mut c Checker) stmt(node ast.Stmt) {
18211821
node.typ = c.expr(node.expr)
18221822
c.expected_type = ast.void_type
18231823
mut or_typ := ast.void_type
1824-
match node.expr {
1824+
match mut node.expr {
18251825
ast.IndexExpr {
18261826
if node.expr.or_expr.kind != .absent {
18271827
node.is_expr = true
@@ -1837,7 +1837,7 @@ fn (mut c Checker) stmt(node ast.Stmt) {
18371837
else {}
18381838
}
18391839
if !c.pref.is_repl && (c.stmt_level == 1 || (c.stmt_level > 1 && !c.is_last_stmt)) {
1840-
if node.expr is ast.InfixExpr {
1840+
if mut node.expr is ast.InfixExpr {
18411841
if node.expr.op == .left_shift {
18421842
left_sym := c.table.final_sym(node.expr.left_type)
18431843
if left_sym.kind != .array {
@@ -2422,7 +2422,7 @@ pub fn (mut c Checker) expr(node ast.Expr) ast.Type {
24222422
c.error('expected `string` instead of `$expr_sym.name` (e.g. `field.name`)',
24232423
node.field_expr.position())
24242424
}
2425-
if node.field_expr is ast.SelectorExpr {
2425+
if mut node.field_expr is ast.SelectorExpr {
24262426
left_pos := node.field_expr.expr.position()
24272427
if c.comptime_fields_type.len == 0 {
24282428
c.error('compile time field access can only be used when iterating over `T.fields`',
@@ -3025,7 +3025,7 @@ pub fn (mut c Checker) ident(mut node ast.Ident) ast.Type {
30253025
c.inside_const = false
30263026
c.mod = old_c_mod
30273027

3028-
if obj.expr is ast.CallExpr {
3028+
if mut obj.expr is ast.CallExpr {
30293029
if obj.expr.or_block.kind != .absent {
30303030
typ = typ.clear_flag(.optional)
30313031
}

vlib/v/checker/for.v

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,10 @@ fn (mut c Checker) for_stmt(mut node ast.ForStmt) {
147147
if !node.is_inf && typ.idx() != ast.bool_type_idx && !c.pref.translated {
148148
c.error('non-bool used as for condition', node.pos)
149149
}
150-
if node.cond is ast.InfixExpr {
150+
if mut node.cond is ast.InfixExpr {
151151
infix := node.cond
152152
if infix.op == .key_is {
153-
if infix.left in [ast.Ident, ast.SelectorExpr] && infix.right is ast.TypeNode {
153+
if infix.right is ast.TypeNode && infix.left in [ast.Ident, ast.SelectorExpr] {
154154
is_variable := if mut infix.left is ast.Ident {
155155
infix.left.kind == .variable
156156
} else {

vlib/v/checker/if.v

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
5050
if node.is_comptime { // Skip checking if needed
5151
// smartcast field type on comptime if
5252
mut comptime_field_name := ''
53-
if branch.cond is ast.InfixExpr {
53+
if mut branch.cond is ast.InfixExpr {
5454
if branch.cond.op == .key_is {
5555
if branch.cond.right !is ast.TypeNode {
5656
c.error('invalid `\$if` condition: expected a type', branch.cond.right.position())
@@ -106,7 +106,7 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
106106
}
107107
} else if c.pref.output_cross_c {
108108
mut is_freestanding_block := false
109-
if branch.cond is ast.Ident {
109+
if mut branch.cond is ast.Ident {
110110
if branch.cond.name == 'freestanding' {
111111
is_freestanding_block = true
112112
}

vlib/v/checker/match.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import strings
88
pub fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type {
99
node.is_expr = c.expected_type != ast.void_type
1010
node.expected_type = c.expected_type
11-
if node.cond is ast.ParExpr && !c.pref.translated {
11+
if mut node.cond is ast.ParExpr && !c.pref.translated {
1212
c.error('unnecessary `()` in `match` condition, use `match expr {` instead of `match (expr) {`.',
1313
node.cond.pos)
1414
}

vlib/v/parser/parse_type.v

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ pub fn (mut p Parser) parse_array_type(expecting token.Kind) ast.Type {
2727
}
2828
ast.Ident {
2929
mut show_non_const_error := false
30-
if const_field := p.table.global_scope.find_const('${p.mod}.$size_expr.name') {
31-
if const_field.expr is ast.IntegerLiteral {
30+
if mut const_field := p.table.global_scope.find_const('${p.mod}.$size_expr.name') {
31+
if mut const_field.expr is ast.IntegerLiteral {
3232
fixed_size = const_field.expr.val.int()
3333
} else {
34-
if const_field.expr is ast.InfixExpr {
34+
if mut const_field.expr is ast.InfixExpr {
3535
mut t := transformer.new_transformer(p.pref)
36-
folded_expr := t.infix_expr(const_field.expr)
36+
folded_expr := t.infix_expr(mut const_field.expr)
3737

3838
if folded_expr is ast.IntegerLiteral {
3939
fixed_size = folded_expr.val.int()

vlib/v/transformer/index_state.v

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
module transformer
2+
3+
struct KeyVal {
4+
key string
5+
value int
6+
}
7+
8+
[if debug_bounds_checking ?]
9+
fn debug_bounds_checking(str string) {
10+
println(str)
11+
}
12+
13+
// IndexState is used to track the index analysis performed when parsing the code
14+
// `IndexExpr` nodes are annotated with `is_direct`, indicating that the array index can be safely directly accessed.
15+
16+
// The c_gen code check will handle this annotation and perform this direct memory access. The following cases are considered valid for this optimisation:
17+
// 1. the array size is known and has a `len` larger than the index requested
18+
// 2. the array was previously accessed with a higher value which would have reported the issue already
19+
// 3. the array was created from a range expression a := range[10..13] and the offset'ed indexes are safe
20+
21+
// Current limitations:
22+
// * any function using break/continue or goto/label stopped from being optimised as soon as the relevant AST nodes are found as the code can not be ensured to be sequential
23+
// * `enum` and `const` indexes are not optimised (they could probably be looked up)
24+
// * for loops with multiple var in their init and/or inc are not analysed
25+
// * mut array are not analysed as their size can be reduced, but self-assignment in a single line
26+
27+
pub struct IndexState {
28+
mut:
29+
// max_index has the biggest array index accessed for then named array
30+
// so if a[2] was set or read, it will be 2
31+
// A new array with no .len will recorded as -1 (accessing a[0] would be invalid)
32+
// the value -2 is used to indicate that the array should not be analysed
33+
// this is used for a mut array
34+
max_index map[string]int
35+
// We need to snapshot when entering `if` and `for` blocks and restore on exit
36+
// as the statements may not be run. This is managed by indent() & unindent().
37+
saved_disabled []bool
38+
saved_key_vals [][]KeyVal
39+
pub mut:
40+
// on encountering goto/break/continue statements we stop any analysis
41+
// for the current function (as the code is not linear anymore)
42+
disabled bool
43+
level int
44+
}
45+
46+
// we are remembering the last array accessed and checking if the value is safe
47+
// the node is updated with this information which can then be used by the code generators
48+
fn (mut i IndexState) safe_access(key string, new int) bool {
49+
$if no_bounds_checking {
50+
return false
51+
}
52+
if i.disabled {
53+
return false
54+
}
55+
old := i.max_index[key] or {
56+
debug_bounds_checking('$i.level ${key}.len = $new')
57+
i.max_index[key] = new
58+
return false
59+
}
60+
if new > old {
61+
if old < -1 {
62+
debug_bounds_checking('$i.level $key[$new] unsafe (mut array)')
63+
return false
64+
}
65+
debug_bounds_checking('$i.level $key[$new] unsafe (index was $old)')
66+
i.max_index[key] = new
67+
return false
68+
}
69+
debug_bounds_checking('$i.level $key[$new] safe (index is $old)')
70+
return true
71+
}
72+
73+
// safe_offset returns for a previvous array what was the highest
74+
// offset we ever accessed for that identifier
75+
fn (mut i IndexState) safe_offset(key string) int {
76+
$if no_bounds_checking {
77+
return -2
78+
}
79+
if i.disabled {
80+
return -2
81+
}
82+
return i.max_index[key] or { -1 }
83+
}
84+
85+
// indent is used for when encountering new code blocks (if, for and functions)
86+
// The code analysis needs to take into consideration blocks of code which
87+
// may not run at runtime (if/for) and therefore even if a new maximum for an
88+
// index access is found on an if branch it can not be used within the parent
89+
// code. The same is true with for blocks. indent() snapshot the current state,
90+
// to allow restoration with unindent()
91+
// Also within a function, analysis must be `disabled` when goto or break are
92+
// encountered as the code flow is then not lineear, and only restart when a
93+
// new function analysis is started.
94+
[if !no_bounds_checking]
95+
fn (mut i IndexState) indent(is_function bool) {
96+
mut kvs := []KeyVal{cap: i.max_index.len}
97+
for k, v in i.max_index {
98+
kvs << KeyVal{k, v}
99+
}
100+
i.saved_disabled << i.disabled
101+
i.saved_key_vals << kvs
102+
if is_function {
103+
i.disabled = false
104+
}
105+
i.level += 1
106+
}
107+
108+
// restoring the data as it was before the if/for/unsafe block
109+
[if !no_bounds_checking]
110+
fn (mut i IndexState) unindent() {
111+
i.level -= 1
112+
mut keys := []string{cap: i.max_index.len}
113+
for k, _ in i.max_index {
114+
keys << k
115+
}
116+
for k in keys {
117+
i.max_index.delete(k)
118+
}
119+
for saved in i.saved_key_vals.pop() {
120+
i.max_index[saved.key] = saved.value
121+
}
122+
i.disabled = i.saved_disabled.pop()
123+
}

0 commit comments

Comments
 (0)