From dc045806f98846f870c12babd6acee02aa9347a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kr=C3=BCger?= <45282134+UweKrueger@users.noreply.github.com> Date: Tue, 20 Jul 2021 07:31:20 +0200 Subject: [PATCH] docs: describe usage of atomic C functions (#10861) --- cmd/tools/vcheck-md.v | 17 +++++++++ doc/docs.md | 88 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/cmd/tools/vcheck-md.v b/cmd/tools/vcheck-md.v index 1176fc7a89..4b042e0108 100644 --- a/cmd/tools/vcheck-md.v +++ b/cmd/tools/vcheck-md.v @@ -444,6 +444,23 @@ fn (mut f MDFile) check_examples() CheckResult { } oks++ } + 'globals' { + res := cmdexecute('"$vexe" -w -Wfatal-errors -enable-globals -o x.c $vfile') + os.rm('x.c') or {} + if res != 0 || fmt_res != 0 { + if res != 0 { + eprintln(eline(f.path, e.sline, 0, '`example failed to compile with -enable-globals')) + } + if fmt_res != 0 { + eprintln(eline(f.path, e.sline, 0, '`example is not formatted')) + } + eprintln(vcontent) + should_cleanup_vfile = false + errors++ + continue + } + oks++ + } 'live' { res := cmdexecute('"$vexe" -w -Wfatal-errors -live -o x.c $vfile') if res != 0 || fmt_res != 0 { diff --git a/doc/docs.md b/doc/docs.md index ee2d46af0d..826a89287a 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -131,6 +131,7 @@ For more details and troubleshooting, please visit the [vab GitHub repository](h * [Structs with reference fields](#structs-with-reference-fields) * [sizeof and __offsetof](#sizeof-and-__offsetof) * [Calling C from V](#calling-c-from-v) + * [Atomics](#atomics) * [Debugging](#debugging) * [Conditional compilation](#conditional-compilation) * [Compile time pseudo variables](#compile-time-pseudo-variables) @@ -4251,6 +4252,93 @@ fn main() { } ``` +## Atomics + +V has no special support for atomics, yet, nevertheless it's possible to treat variables as atomics +by calling C functions from V. The standard C11 atomic functions like `atomic_store()` are usually +defined with the help of macros and C compiler magic to provide a kind of *overloaded C functions*. +Since V does not support overloading functions by intention there are wrapper functions defined in +C headers named `atomic.h` that are part of the V compiler infrastructure. + +There are dedicated wrappers for all unsigned integer types and for pointers. +(`byte` is not fully supported on Windows) – the function names include the type name +as suffix. e.g. `C.atomic_load_ptr()` or `C.atomic_fetch_add_u64()`. + +To use these functions the C header for the used OS has to be included and the functions +that are intended to be used have to be declared. Example: + +```v globals +$if windows { + #include "@VEXEROOT/thirdparty/stdatomic/win/atomic.h" +} $else { + #include "@VEXEROOT/thirdparty/stdatomic/nix/atomic.h" +} + +// declare functions we want to use - V does not parse the C header +fn C.atomic_store_u32(&u32, u32) +fn C.atomic_load_u32(&u32) u32 +fn C.atomic_compare_exchange_weak_u32(&u32, &u32, u32) bool +fn C.atomic_compare_exchange_strong_u32(&u32, &u32, u32) bool + +const num_iterations = 10000000 + +__global ( + atom u32 // ordinary variable but used as atomic +) + +fn change() int { + mut races_won_by_change := 0 + for { + mut cmp := u32(17) // addressable value to compare with and to store the found value + // atomic version of `if atom == 17 { atom = 23 races_won_by_change++ } else { cmp = atom }` + if C.atomic_compare_exchange_strong_u32(&atom, &cmp, 23) { + races_won_by_change++ + } else { + if cmp == 31 { + break + } + cmp = 17 // re-assign because overwritten with value of atom + } + } + return races_won_by_change +} + +fn main() { + C.atomic_store_u32(&atom, 17) + t := go change() + mut races_won_by_main := 0 + mut cmp17 := u32(17) + mut cmp23 := u32(23) + for i in 0 .. num_iterations { + // atomic version of `if atom == 17 { atom = 23 races_won_by_main++ }` + if C.atomic_compare_exchange_strong_u32(&atom, &cmp17, 23) { + races_won_by_main++ + } else { + cmp17 = 17 + } + desir := if i == num_iterations - 1 { u32(31) } else { u32(17) } + // atomic version of `for atom != 23 {} atom = desir` + for !C.atomic_compare_exchange_weak_u32(&atom, &cmp23, desir) { + cmp23 = 23 + } + } + races_won_by_change := t.wait() + atom_new := C.atomic_load_u32(&atom) + println('atom: $atom_new, #exchanges: ${races_won_by_main + races_won_by_change}') + // prints `atom: 31, #exchanges: 10000000`) + println('races won by\n- `main()`: $races_won_by_main\n- `change()`: $races_won_by_change') +} +``` + +In this example both `main()` and the spawned thread `change()` try to replace a value of `17` +in the global `atom` with a value of `23`. The replacement in the opposite direction is +done exactly 10000000 times. The last replacement will be with `31` which makes the spawned +thread finish. + +It is not predictable how many replacements occur in which thread, but the sum will always +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.) + ### Passing C compilation flags Add `#flag` directives to the top of your V files to provide C compilation flags like: