From 08aa6c08f6105ff66a4b2c120e3ba4d53b31ce78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kr=C3=BCger?= <45282134+UweKrueger@users.noreply.github.com> Date: Thu, 29 Jul 2021 09:57:31 +0200 Subject: [PATCH] all: more improvements for global variables (#10986) --- doc/docs.md | 46 ++++++++++++++ vlib/v/ast/ast.v | 1 + vlib/v/checker/checker.v | 5 +- vlib/v/fmt/fmt.v | 1 + vlib/v/gen/c/cgen.v | 24 ++++---- vlib/v/parser/parser.v | 1 + vlib/v/tests/init_global_test.v | 103 ++++++++++++++++++++++++++++++++ 7 files changed, 168 insertions(+), 13 deletions(-) diff --git a/doc/docs.md b/doc/docs.md index ab209929fc..b72f02334c 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -132,6 +132,7 @@ For more details and troubleshooting, please visit the [vab GitHub repository](h * [sizeof and __offsetof](#sizeof-and-__offsetof) * [Calling C from V](#calling-c-from-v) * [Atomics](#atomics) + * [Global Variables](#global-variables) * [Debugging](#debugging) * [Conditional compilation](#conditional-compilation) * [Compile time pseudo variables](#compile-time-pseudo-variables) @@ -4297,6 +4298,7 @@ fn C.atomic_compare_exchange_strong_u32(&u32, &u32, u32) bool const num_iterations = 10000000 +// see section "Global Variables" below __global ( atom u32 // ordinary variable but used as atomic ) @@ -4354,6 +4356,50 @@ It is not predictable how many replacements occur in which thread, but the sum w be 10000000. (With the non-atomic commands from the comments the value will be higher or the program will hang – dependent on the compiler optimization used.) +## Global Variables + +By default V does not allow global variables. However, in low level applications they have their +place so their usage can be enabled with the compiler flag `-enable-globals`. +Declarations of global variables must be surrounded with a `__global ( ... )` +specification – as in the example [above](#atomics). + +An initializer for global variables must be explicitly converted to the +desired target type. If no initializer is given a default initialization is done. +Some objects like semaphores and mutexes require an explicit initialization *in place*, i.e. +not with a value returned from a function call but with a method call by reference. +A separate `init()` function can be used for this purpose – it will be called before `main()`: + +```v globals +import sync + +__global ( + sem sync.Semaphore // needs initialization in `init()` + mtx sync.RwMutex // needs initialization in `init()` + f1 = f64(34.0625) // explicily initialized + shmap shared map[string]f64 // initialized as empty `shared` map + f2 f64 // initialized to `0.0` +) + +fn init() { + sem.init(0) + mtx.init() +} +``` +Be aware that in multi threaded applications the access to global variables is subject +to race conditions. There are several approaches to deal with these: + +- use `shared` types for the variable declarations and use `lock` blocks for access. + This is most appropriate for larger objects like structs, arrays or maps. +- handle primitive data types as "atomics" using special C-functions (see [above](#atomics)). +- use explicit synchronization primitives like mutexes to control access. The compiler + cannot really help in this case, so you have to know what you are doing. +- don't care – this approach is possible but makes only sense if the exact values + of global variables do not really matter. An example can be found in the `rand` module + where global variables are used to generate (non cryptographic) pseudo random numbers. + In this case data races lead to random numbers in different threads becoming somewhat + correlated, which is acceptable considering the performance penalty that using + synchonization primitives would represent. + ### Passing C compilation flags Add `#flag` directives to the top of your V files to provide C compilation flags like: diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 678a12d75a..1dd1883ad6 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -546,6 +546,7 @@ pub mut: pub struct GlobalDecl { pub: + mod string pos token.Position is_block bool // __global() block pub mut: diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 3d953db15a..f7a2a2e1d5 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -7197,13 +7197,14 @@ pub fn (mut c Checker) index_expr(mut node ast.IndexExpr) ast.Type { '(note, that variables may be mutable but string values are always immutable, like in Go and Java)', node.pos) } - if !c.inside_unsafe && ((typ.is_ptr() && !node.left.is_auto_deref_var()) || typ.is_pointer()) { + if !c.inside_unsafe && ((typ.is_ptr() && !typ.has_flag(.shared_f) + && !node.left.is_auto_deref_var()) || typ.is_pointer()) { mut is_ok := false if mut node.left is ast.Ident { if node.left.obj is ast.Var { v := node.left.obj as ast.Var // `mut param []T` function parameter - is_ok = ((v.is_mut && v.is_arg) || v.share == .shared_t) && !typ.deref().is_ptr() + is_ok = ((v.is_mut && v.is_arg)) && !typ.deref().is_ptr() } } if !is_ok && !c.pref.translated { diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index dd77b82d6d..8f23095ad1 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -982,6 +982,7 @@ pub fn (mut f Fmt) global_decl(node ast.GlobalDecl) { if node.is_block { f.writeln('') } + f.mark_types_import_as_used(field.typ) } f.comments_after_last_field(node.end_comments) if node.is_block { diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 4a9a9a6e6b..8a8131eaa4 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -49,7 +49,7 @@ mut: typedefs2 strings.Builder type_definitions strings.Builder // typedefs, defines etc (everything that goes to the top of the file) definitions strings.Builder // typedefs, defines etc (everything that goes to the top of the file) - global_initializations strings.Builder // default initializers for globals (goes in _vinit()) + global_inits map[string]strings.Builder // default initializers for globals (goes in _vinit()) inits map[string]strings.Builder // contents of `void _vinit/2{}` cleanups map[string]strings.Builder // contents of `void _vcleanup(){}` gowrappers strings.Builder // all go callsite wrappers @@ -206,7 +206,6 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { typedefs2: strings.new_builder(100) type_definitions: strings.new_builder(100) definitions: strings.new_builder(100) - global_initializations: strings.new_builder(100) gowrappers: strings.new_builder(100) stringliterals: strings.new_builder(100) auto_str_funcs: strings.new_builder(100) @@ -235,6 +234,7 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { g.timers.start('cgen init') for mod in g.table.modules { g.inits[mod] = strings.new_builder(100) + g.global_inits[mod] = strings.new_builder(100) g.cleanups[mod] = strings.new_builder(100) } g.init() @@ -2518,12 +2518,15 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { styp := g.typ(left.typ) g.write('$styp _var_$left.pos.pos = ') g.expr(left.expr) + mut sel := '.' if left.expr_type.is_ptr() { - g.write('/* left.expr_type */') - g.writeln('->$left.field_name;') - } else { - g.writeln('.$left.field_name;') + if left.expr_type.has_flag(.shared_f) { + sel = '->val.' + } else { + sel = '->' + } } + g.writeln('$sel$left.field_name;') } else {} } @@ -5116,6 +5119,7 @@ fn (mut g Gen) const_decl_init_later(mod string, name string, expr ast.Expr, typ fn (mut g Gen) global_decl(node ast.GlobalDecl) { mod := if g.pref.build_mode == .build_module && g.is_builtin_mod { 'static ' } else { '' } + key := node.mod // module name for field in node.fields { if g.pref.skip_unused { if field.name !in g.table.used_globals { @@ -5128,7 +5132,7 @@ fn (mut g Gen) global_decl(node ast.GlobalDecl) { styp := g.typ(field.typ) if field.has_expr { g.definitions.writeln('$mod$styp $field.name;') - g.global_initializations.writeln('\t$field.name = ${g.expr_string(field.expr)}; // global') + g.global_inits[key].writeln('\t$field.name = ${g.expr_string(field.expr)}; // global') } else { default_initializer := g.type_default(field.typ) if default_initializer == '{0}' { @@ -5136,7 +5140,7 @@ fn (mut g Gen) global_decl(node ast.GlobalDecl) { } else { g.definitions.writeln('$mod$styp $field.name; // global') if field.name !in ['as_cast_type_indexes', 'g_memory_block'] { - g.global_initializations.writeln('\t$field.name = *($styp*)&(($styp[]){${g.type_default(field.typ)}}[0]); // global') + g.global_inits[key].writeln('\t$field.name = *($styp*)&(($styp[]){${g.type_default(field.typ)}}[0]); // global') } } } @@ -5465,12 +5469,10 @@ fn (mut g Gen) write_init_function() { g.writeln('\tbuiltin_init();') g.writeln('\tvinit_string_literals();') // - g.writeln('\t// Initializations for global variables with default initializers') - g.write(g.global_initializations.str()) - // for mod_name in g.table.modules { g.writeln('\t// Initializations for module $mod_name :') g.write(g.inits[mod_name].str()) + g.write(g.global_inits[mod_name].str()) init_fn_name := '${mod_name}.init' if initfn := g.table.find_fn(init_fn_name) { if initfn.return_type == ast.void_type && initfn.params.len == 0 { diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 4978d4d16b..1234bce4fb 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -2977,6 +2977,7 @@ fn (mut p Parser) global_decl() ast.GlobalDecl { } return ast.GlobalDecl{ pos: start_pos.extend(p.prev_tok.position()) + mod: p.mod fields: fields end_comments: comments is_block: is_block diff --git a/vlib/v/tests/init_global_test.v b/vlib/v/tests/init_global_test.v index afa41395db..791b4a73fd 100644 --- a/vlib/v/tests/init_global_test.v +++ b/vlib/v/tests/init_global_test.v @@ -1,3 +1,5 @@ +import sync + fn one() int { return 1 } @@ -30,4 +32,105 @@ __global ( intmap map[string]int numberfns map[string]fn () int ch chan f64 + mys shared MyStruct + sem sync.Semaphore + shmap shared map[string]f64 + mtx sync.RwMutex + f1 = f64(34.0625) + f2 f64 ) + +fn init() { + // semaphores and mutexes must not be moved in memory, so for technical + // reasons they cannot have a "default constructor" at the moment and must + // be initialized "manually" + sem.init(0) + mtx.init() +} + +struct MyStruct { +mut: + x f64 + y f64 +} + +fn switch() { + for !sem.try_wait() { + lock mys { + if mys.x == 13.0 { + mys.x = 13.75 + } else if mys.y == 13.0 { + mys.y = 13.75 + } + } + lock mys { + mys.x, mys.y = mys.y, mys.x + } + } +} + +fn test_global_shared() { + lock mys { + mys.x = 13.0 + mys.y = -35.125 + } + t := go switch() + for _ in 0 .. 2500000 { + lock mys { + mys.x, mys.y = mys.y, mys.x + } + } + a, b := rlock mys { + mys.x, mys.y + } + sem.post() + t.wait() + assert (a == 13.75 && b == -35.125) || (a == -35.125 && b == 13.75) +} + +fn test_global_shared_map() { + lock shmap { + shmap['one'] = 1.25 + shmap['two'] = -0.75 + } + x, y := rlock shmap { + shmap['two'], shmap['one'] + } + assert x == -0.75 + assert y == 1.25 +} + +fn switch2() u64 { + mut cnt := u64(0) + for { + mtx.@lock() + f1, f2 = f2, f1 + if f1 == 17.0 || f2 == 17.0 { + mtx.unlock() + return cnt + } + mtx.unlock() + cnt++ + } + return 0 +} + +fn test_global_mutex() { + t := go switch2() + for _ in 0 .. 2500000 { + mtx.@lock() + f1, f2 = f2, f1 + mtx.unlock() + } + mtx.@lock() + if f1 == 0.0 { + f1 = 17.0 + } else { + f2 = 17.0 + } + mtx.@rlock() + assert (f1 == 17.0 && f2 == 34.0625) || (f1 == 34.0625 && f2 == 17.0) + mtx.runlock() + n := t.wait() + assert n > 0 +}