all: more improvements for global variables (#10986)

pull/10990/head
Uwe Krüger 2021-07-29 09:57:31 +02:00 committed by GitHub
parent 7547882c11
commit 08aa6c08f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 168 additions and 13 deletions

View File

@ -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:

View File

@ -546,6 +546,7 @@ pub mut:
pub struct GlobalDecl {
pub:
mod string
pos token.Position
is_block bool // __global() block
pub mut:

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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
}