all: more improvements for global variables (#10986)
parent
7547882c11
commit
08aa6c08f6
46
doc/docs.md
46
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)
|
* [sizeof and __offsetof](#sizeof-and-__offsetof)
|
||||||
* [Calling C from V](#calling-c-from-v)
|
* [Calling C from V](#calling-c-from-v)
|
||||||
* [Atomics](#atomics)
|
* [Atomics](#atomics)
|
||||||
|
* [Global Variables](#global-variables)
|
||||||
* [Debugging](#debugging)
|
* [Debugging](#debugging)
|
||||||
* [Conditional compilation](#conditional-compilation)
|
* [Conditional compilation](#conditional-compilation)
|
||||||
* [Compile time pseudo variables](#compile-time-pseudo-variables)
|
* [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
|
const num_iterations = 10000000
|
||||||
|
|
||||||
|
// see section "Global Variables" below
|
||||||
__global (
|
__global (
|
||||||
atom u32 // ordinary variable but used as atomic
|
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
|
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.)
|
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
|
### Passing C compilation flags
|
||||||
|
|
||||||
Add `#flag` directives to the top of your V files to provide C compilation flags like:
|
Add `#flag` directives to the top of your V files to provide C compilation flags like:
|
||||||
|
|
|
@ -546,6 +546,7 @@ pub mut:
|
||||||
|
|
||||||
pub struct GlobalDecl {
|
pub struct GlobalDecl {
|
||||||
pub:
|
pub:
|
||||||
|
mod string
|
||||||
pos token.Position
|
pos token.Position
|
||||||
is_block bool // __global() block
|
is_block bool // __global() block
|
||||||
pub mut:
|
pub mut:
|
||||||
|
|
|
@ -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)',
|
'(note, that variables may be mutable but string values are always immutable, like in Go and Java)',
|
||||||
node.pos)
|
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
|
mut is_ok := false
|
||||||
if mut node.left is ast.Ident {
|
if mut node.left is ast.Ident {
|
||||||
if node.left.obj is ast.Var {
|
if node.left.obj is ast.Var {
|
||||||
v := node.left.obj as ast.Var
|
v := node.left.obj as ast.Var
|
||||||
// `mut param []T` function parameter
|
// `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 {
|
if !is_ok && !c.pref.translated {
|
||||||
|
|
|
@ -982,6 +982,7 @@ pub fn (mut f Fmt) global_decl(node ast.GlobalDecl) {
|
||||||
if node.is_block {
|
if node.is_block {
|
||||||
f.writeln('')
|
f.writeln('')
|
||||||
}
|
}
|
||||||
|
f.mark_types_import_as_used(field.typ)
|
||||||
}
|
}
|
||||||
f.comments_after_last_field(node.end_comments)
|
f.comments_after_last_field(node.end_comments)
|
||||||
if node.is_block {
|
if node.is_block {
|
||||||
|
|
|
@ -49,7 +49,7 @@ mut:
|
||||||
typedefs2 strings.Builder
|
typedefs2 strings.Builder
|
||||||
type_definitions strings.Builder // typedefs, defines etc (everything that goes to the top of the file)
|
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)
|
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{}`
|
inits map[string]strings.Builder // contents of `void _vinit/2{}`
|
||||||
cleanups map[string]strings.Builder // contents of `void _vcleanup(){}`
|
cleanups map[string]strings.Builder // contents of `void _vcleanup(){}`
|
||||||
gowrappers strings.Builder // all go callsite wrappers
|
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)
|
typedefs2: strings.new_builder(100)
|
||||||
type_definitions: strings.new_builder(100)
|
type_definitions: strings.new_builder(100)
|
||||||
definitions: strings.new_builder(100)
|
definitions: strings.new_builder(100)
|
||||||
global_initializations: strings.new_builder(100)
|
|
||||||
gowrappers: strings.new_builder(100)
|
gowrappers: strings.new_builder(100)
|
||||||
stringliterals: strings.new_builder(100)
|
stringliterals: strings.new_builder(100)
|
||||||
auto_str_funcs: 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')
|
g.timers.start('cgen init')
|
||||||
for mod in g.table.modules {
|
for mod in g.table.modules {
|
||||||
g.inits[mod] = strings.new_builder(100)
|
g.inits[mod] = strings.new_builder(100)
|
||||||
|
g.global_inits[mod] = strings.new_builder(100)
|
||||||
g.cleanups[mod] = strings.new_builder(100)
|
g.cleanups[mod] = strings.new_builder(100)
|
||||||
}
|
}
|
||||||
g.init()
|
g.init()
|
||||||
|
@ -2518,12 +2518,15 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
|
||||||
styp := g.typ(left.typ)
|
styp := g.typ(left.typ)
|
||||||
g.write('$styp _var_$left.pos.pos = ')
|
g.write('$styp _var_$left.pos.pos = ')
|
||||||
g.expr(left.expr)
|
g.expr(left.expr)
|
||||||
|
mut sel := '.'
|
||||||
if left.expr_type.is_ptr() {
|
if left.expr_type.is_ptr() {
|
||||||
g.write('/* left.expr_type */')
|
if left.expr_type.has_flag(.shared_f) {
|
||||||
g.writeln('->$left.field_name;')
|
sel = '->val.'
|
||||||
} else {
|
} else {
|
||||||
g.writeln('.$left.field_name;')
|
sel = '->'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
g.writeln('$sel$left.field_name;')
|
||||||
}
|
}
|
||||||
else {}
|
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) {
|
fn (mut g Gen) global_decl(node ast.GlobalDecl) {
|
||||||
mod := if g.pref.build_mode == .build_module && g.is_builtin_mod { 'static ' } else { '' }
|
mod := if g.pref.build_mode == .build_module && g.is_builtin_mod { 'static ' } else { '' }
|
||||||
|
key := node.mod // module name
|
||||||
for field in node.fields {
|
for field in node.fields {
|
||||||
if g.pref.skip_unused {
|
if g.pref.skip_unused {
|
||||||
if field.name !in g.table.used_globals {
|
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)
|
styp := g.typ(field.typ)
|
||||||
if field.has_expr {
|
if field.has_expr {
|
||||||
g.definitions.writeln('$mod$styp $field.name;')
|
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 {
|
} else {
|
||||||
default_initializer := g.type_default(field.typ)
|
default_initializer := g.type_default(field.typ)
|
||||||
if default_initializer == '{0}' {
|
if default_initializer == '{0}' {
|
||||||
|
@ -5136,7 +5140,7 @@ fn (mut g Gen) global_decl(node ast.GlobalDecl) {
|
||||||
} else {
|
} else {
|
||||||
g.definitions.writeln('$mod$styp $field.name; // global')
|
g.definitions.writeln('$mod$styp $field.name; // global')
|
||||||
if field.name !in ['as_cast_type_indexes', 'g_memory_block'] {
|
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('\tbuiltin_init();')
|
||||||
g.writeln('\tvinit_string_literals();')
|
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 {
|
for mod_name in g.table.modules {
|
||||||
g.writeln('\t// Initializations for module $mod_name :')
|
g.writeln('\t// Initializations for module $mod_name :')
|
||||||
g.write(g.inits[mod_name].str())
|
g.write(g.inits[mod_name].str())
|
||||||
|
g.write(g.global_inits[mod_name].str())
|
||||||
init_fn_name := '${mod_name}.init'
|
init_fn_name := '${mod_name}.init'
|
||||||
if initfn := g.table.find_fn(init_fn_name) {
|
if initfn := g.table.find_fn(init_fn_name) {
|
||||||
if initfn.return_type == ast.void_type && initfn.params.len == 0 {
|
if initfn.return_type == ast.void_type && initfn.params.len == 0 {
|
||||||
|
|
|
@ -2977,6 +2977,7 @@ fn (mut p Parser) global_decl() ast.GlobalDecl {
|
||||||
}
|
}
|
||||||
return ast.GlobalDecl{
|
return ast.GlobalDecl{
|
||||||
pos: start_pos.extend(p.prev_tok.position())
|
pos: start_pos.extend(p.prev_tok.position())
|
||||||
|
mod: p.mod
|
||||||
fields: fields
|
fields: fields
|
||||||
end_comments: comments
|
end_comments: comments
|
||||||
is_block: is_block
|
is_block: is_block
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import sync
|
||||||
|
|
||||||
fn one() int {
|
fn one() int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
@ -30,4 +32,105 @@ __global (
|
||||||
intmap map[string]int
|
intmap map[string]int
|
||||||
numberfns map[string]fn () int
|
numberfns map[string]fn () int
|
||||||
ch chan f64
|
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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue