v/vlib/v/transformer/index_state.v

124 lines
4.2 KiB
V

module transformer
struct KeyVal {
key string
value int
}
[if debug_bounds_checking ?]
fn debug_bounds_checking(str string) {
println(str)
}
// IndexState is used to track the index analysis performed when parsing the code
// `IndexExpr` nodes are annotated with `is_direct`, indicating that the array index can be safely directly accessed.
// The c_gen code check will handle this annotation and perform this direct memory access. The following cases are considered valid for this optimisation:
// 1. the array size is known and has a `len` larger than the index requested
// 2. the array was previously accessed with a higher value which would have reported the issue already
// 3. the array was created from a range expression a := range[10..13] and the offset'ed indexes are safe
// Current limitations:
// * 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
// * `enum` and `const` indexes are not optimised (they could probably be looked up)
// * for loops with multiple var in their init and/or inc are not analysed
// * mut array are not analysed as their size can be reduced, but self-assignment in a single line
pub struct IndexState {
mut:
// max_index has the biggest array index accessed for then named array
// so if a[2] was set or read, it will be 2
// A new array with no .len will recorded as -1 (accessing a[0] would be invalid)
// the value -2 is used to indicate that the array should not be analysed
// this is used for a mut array
max_index map[string]int
// We need to snapshot when entering `if` and `for` blocks and restore on exit
// as the statements may not be run. This is managed by indent() & unindent().
saved_disabled []bool
saved_key_vals [][]KeyVal
pub mut:
// on encountering goto/break/continue statements we stop any analysis
// for the current function (as the code is not linear anymore)
disabled bool
level int
}
// we are remembering the last array accessed and checking if the value is safe
// the node is updated with this information which can then be used by the code generators
fn (mut i IndexState) safe_access(key string, new int) bool {
$if no_bounds_checking {
return false
}
if i.disabled {
return false
}
old := i.max_index[key] or {
debug_bounds_checking('$i.level ${key}.len = $new')
i.max_index[key] = new
return false
}
if new > old {
if old < -1 {
debug_bounds_checking('$i.level $key[$new] unsafe (mut array)')
return false
}
debug_bounds_checking('$i.level $key[$new] unsafe (index was $old)')
i.max_index[key] = new
return false
}
debug_bounds_checking('$i.level $key[$new] safe (index is $old)')
return true
}
// safe_offset returns for a previvous array what was the highest
// offset we ever accessed for that identifier
fn (mut i IndexState) safe_offset(key string) int {
$if no_bounds_checking {
return -2
}
if i.disabled {
return -2
}
return i.max_index[key] or { -1 }
}
// indent is used for when encountering new code blocks (if, for and functions)
// The code analysis needs to take into consideration blocks of code which
// may not run at runtime (if/for) and therefore even if a new maximum for an
// index access is found on an if branch it can not be used within the parent
// code. The same is true with for blocks. indent() snapshot the current state,
// to allow restoration with unindent()
// Also within a function, analysis must be `disabled` when goto or break are
// encountered as the code flow is then not lineear, and only restart when a
// new function analysis is started.
[if !no_bounds_checking]
fn (mut i IndexState) indent(is_function bool) {
mut kvs := []KeyVal{cap: i.max_index.len}
for k, v in i.max_index {
kvs << KeyVal{k, v}
}
i.saved_disabled << i.disabled
i.saved_key_vals << kvs
if is_function {
i.disabled = false
}
i.level += 1
}
// restoring the data as it was before the if/for/unsafe block
[if !no_bounds_checking]
fn (mut i IndexState) unindent() {
i.level -= 1
mut keys := []string{cap: i.max_index.len}
for k, _ in i.max_index {
keys << k
}
for k in keys {
i.max_index.delete(k)
}
for saved in i.saved_key_vals.pop() {
i.max_index[saved.key] = saved.value
}
i.disabled = i.saved_disabled.pop()
}