From e8e0d9fa42549167f6b1ea214b5728784bda3299 Mon Sep 17 00:00:00 2001 From: Maciej Obarski Date: Mon, 24 Aug 2020 09:04:50 +0200 Subject: [PATCH] all: [direct_array_access] tag (#6203) --- doc/docs.md | 8 +- vlib/builtin/array_test.v | 31 +++++++ vlib/v/ast/ast.v | 1 + vlib/v/gen/cgen.v | 167 +++++++++++++++++++++++--------------- vlib/v/parser/fn.v | 2 + 5 files changed, 144 insertions(+), 65 deletions(-) diff --git a/doc/docs.md b/doc/docs.md index aed0be67a1..ec0b73c8e3 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -2270,7 +2270,7 @@ eprintln('$vm.name $vm.version\n $vm.description') The generated C code is usually fast enough, when you compile your code with `-prod`. There are some situations though, where you may want to give -additional hints to the C compiler, so that it can further optimize some +additional hints to the compiler, so that it can further optimize some blocks of code. NB: These are *rarely* needed, and should not be used, unless you @@ -2282,6 +2282,12 @@ how their programs actually perform". try to inline them, which in some cases, may be beneficial for performance, but may impact the size of your executable. +`[direct_array_access]` - in functions tagged with `[direct_array_access]` +the compiler will translate array operations directly into C array operations - +omiting bounds checking. This may save a lot of time in a function that iterates +over an array but at the cost of making the function unsafe - unless +the boundries will be checked by the user. + `if _likely_(bool expression) {` this hints the C compiler, that the passed boolean expression is very likely to be true, so it can generate assembly code, with less chance of branch misprediction. In the JS backend, diff --git a/vlib/builtin/array_test.v b/vlib/builtin/array_test.v index a48cb08ede..dc2eb8e618 100644 --- a/vlib/builtin/array_test.v +++ b/vlib/builtin/array_test.v @@ -1001,3 +1001,34 @@ fn test_array_string_pop() { assert a.len == 0 assert a.cap == 3 } + + +[direct_array_access] +fn test_direct_array_access() { + mut a := [11,22,33,44] + assert a[0] == 11 + assert a[2] == 33 + x := a[0] + a[0] = 21 + a[1] += 2 + a[2] = x + 3 + a[3] -= a[1] + assert a == [21, 24, 14, 20] +} + +[direct_array_access] +fn test_direct_array_access_via_ptr() { + mut b := [11,22,33,44] + unsafe { + mut a := &b + assert a[0] == 11 + assert a[2] == 33 + x := a[0] + a[0] = 21 + a[1] += 2 + a[2] = x + 3 + a[3] -= a[1] + assert a == [21, 24, 14, 20] + } +} + diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 623b8056c4..d8243e79c4 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -258,6 +258,7 @@ pub: body_pos token.Position file string is_generic bool + is_direct_arr bool // direct array access attrs []table.Attr pub mut: stmts []Stmt diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 5a3d2286c4..7a5a46f76e 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -801,6 +801,7 @@ fn (mut g Gen) stmt(node ast.Stmt) { } } ast.FnDecl { + g.gen_attrs(node.attrs) // g.tmp_count = 0 TODO mut skip := false pos := g.out.buf.len @@ -2857,12 +2858,22 @@ fn (mut g Gen) index_expr(node ast.IndexExpr) { // `(*(Val*)array_get(vals, i)).field = x;` is_selector := node.left is ast.SelectorExpr if g.is_assign_lhs && !is_selector && node.is_setter { - g.is_array_set = true - g.write('array_set(') - if !left_is_ptr || node.left_type.has_flag(.shared_f) { - g.write('&') + is_direct_array_access := g.fn_decl != 0 && g.fn_decl.is_direct_arr + array_ptr_type_str := match elem_typ.kind { + .function { 'voidptr*' } + else { '$elem_type_str*' } + } + if is_direct_array_access { + g.write('(($array_ptr_type_str)') + } else { + g.is_array_set = true // special handling of assign_op and closing with '})' + g.write('array_set(') + if !left_is_ptr || node.left_type.has_flag(.shared_f) { + g.write('&') + } } g.expr(node.left) + // TODO: test direct_array_access when 'shared' is implemented if node.left_type.has_flag(.shared_f) { if left_is_ptr { g.write('->val') @@ -2870,72 +2881,89 @@ fn (mut g Gen) index_expr(node ast.IndexExpr) { g.write('.val') } } - g.write(', ') - g.expr(node.index) - mut need_wrapper := true - /* - match node.right { - ast.EnumVal, ast.Ident { - // `&x` is enough for variables and enums - // `&(Foo[]){ ... }` is only needed for function calls and literals - need_wrapper = false - } - else {} - } - */ - if need_wrapper { - if elem_typ.kind == .function { - g.write(', &(voidptr[]) { ') + if is_direct_array_access { + if left_is_ptr && !node.left_type.has_flag(.shared_f) { + g.write('->') } else { - g.write(', &($elem_type_str[]) { ') + g.write('.') } + g.write('data)[') + g.expr(node.index) + g.write(']') } else { - g.write(', &') + g.write(', ') + g.expr(node.index) + mut need_wrapper := true + /* + match node.right { + ast.EnumVal, ast.Ident { + // `&x` is enough for variables and enums + // `&(Foo[]){ ... }` is only needed for function calls and literals + need_wrapper = false + } + else {} + } + */ + if need_wrapper { + if elem_typ.kind == .function { + g.write(', &(voidptr[]) { ') + } else { + g.write(', &($elem_type_str[]) { ') + } + } else { + g.write(', &') + } + // `x[0] *= y` + if g.assign_op != .assign && + g.assign_op in token.assign_tokens && info.elem_type != table.string_type { + // TODO move this + g.write('*($elem_type_str*)array_get(') + if left_is_ptr && !node.left_type.has_flag(.shared_f) { + g.write('*') + } + g.expr(node.left) + if node.left_type.has_flag(.shared_f) { + if left_is_ptr { + g.write('->val') + } else { + g.write('.val') + } + } + g.write(', ') + g.expr(node.index) + g.write(') ') + op := match g.assign_op { + .mult_assign { '*' } + .plus_assign { '+' } + .minus_assign { '-' } + .div_assign { '/' } + .xor_assign { '^' } + .mod_assign { '%' } + .or_assign { '|' } + .and_assign { '&' } + .left_shift_assign { '<<' } + .right_shift_assign { '>>' } + else { '' } + } + g.write(op) + } } - // `x[0] *= y` - if g.assign_op != .assign && - g.assign_op in token.assign_tokens && info.elem_type != table.string_type { - // TODO move this - g.write('*($elem_type_str*)array_get(') + } else { + is_direct_array_access := g.fn_decl != 0 && g.fn_decl.is_direct_arr + array_ptr_type_str := match elem_typ.kind { + .function { 'voidptr*' } + else { '$elem_type_str*' } + } + if is_direct_array_access { + g.write('(($array_ptr_type_str)') + } else { + g.write('(*($array_ptr_type_str)array_get(') if left_is_ptr && !node.left_type.has_flag(.shared_f) { g.write('*') } - g.expr(node.left) - if node.left_type.has_flag(.shared_f) { - if left_is_ptr { - g.write('->val') - } else { - g.write('.val') - } - } - g.write(', ') - g.expr(node.index) - g.write(') ') - op := match g.assign_op { - .mult_assign { '*' } - .plus_assign { '+' } - .minus_assign { '-' } - .div_assign { '/' } - .xor_assign { '^' } - .mod_assign { '%' } - .or_assign { '|' } - .and_assign { '&' } - .left_shift_assign { '<<' } - .right_shift_assign { '>>' } - else { '' } - } - g.write(op) - } - } else { - if elem_typ.kind == .function { - g.write('(*(voidptr*)array_get(') - } else { - g.write('(*($elem_type_str*)array_get(') - } - if left_is_ptr && !node.left_type.has_flag(.shared_f) { - g.write('*') } g.expr(node.left) + // TODO: test direct_array_access when 'shared' is implemented if node.left_type.has_flag(.shared_f) { if left_is_ptr { g.write('->val') @@ -2943,9 +2971,20 @@ fn (mut g Gen) index_expr(node ast.IndexExpr) { g.write('.val') } } - g.write(', ') - g.expr(node.index) - g.write('))') + if is_direct_array_access { + if left_is_ptr && !node.left_type.has_flag(.shared_f) { + g.write('->') + } else { + g.write('.') + } + g.write('data)[') + g.expr(node.index) + g.write(']') + } else { + g.write(', ') + g.expr(node.index) + g.write('))') + } } } else if sym.kind == .map { info := sym.info as table.Map diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index dbe9baa3a2..005b0c824a 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -132,6 +132,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl { p.top_level_statement_start() start_pos := p.tok.position() is_deprecated := p.attrs.contains('deprecated') + is_direct_arr := p.attrs.contains('direct_array_access') mut is_unsafe := p.attrs.contains('unsafe') is_pub := p.tok.kind == .key_pub if is_pub { @@ -318,6 +319,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl { return_type: return_type args: args is_deprecated: is_deprecated + is_direct_arr: is_direct_arr is_pub: is_pub is_generic: is_generic is_variadic: is_variadic