v2 / vlib / v / transformer / index_state.v
123 lines · 113 sloc · 4.26 KB · 757929392e0e7a75fc1272116460981e589737d5
Raw
1module transformer
2
3struct KeyVal {
4 key string
5 value int
6}
7
8@[if debug_bounds_checking ?]
9fn 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
27pub struct IndexState {
28mut:
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
39pub 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
48fn (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 previous array what was the highest
74// offset we ever accessed for that identifier
75fn (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]
95fn (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]
110fn (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}
124