Skip to content

Commit

Permalink
Atomic operations. (#10610)
Browse files Browse the repository at this point in the history
* Atomic operations.

* Fix hashlink's atomic operations.

* Add a target.atomics define.

* Prevent complaint about "no assertions".

* Fix doc gen.

* Try to fix things...

* Rework api a bit.

* Add threaded atomic test, fix C#, fix a method signature.

* Make AtomicInt work on cppia, add JS implementation.

* Remove atomics on C#.

* Fix doc gen, again.

* `do ... while` loop for Java.
Add some `inline`, and remove some unneeded `extern`.

* Fix a potential race condition in the Java impl.

* Fix NativeStackTrace.callStack for hl 1.12+.

* Use cpp.AtomicInt instead of Int.

* Update AtomicInt.hx

* [hashlink] use library functions instead opcodes

* Fix typo and add warning note to AtomicObject.

* [hl] Fix AtomicObject, and bump hl-ver to make tests run.

* Add AtomicBool

* Inline AtomicBool

* Add C# atomics back.

* Fix doc gen for atomic bool.

* Fix typo.

* Add AtomicBool unit test and fix typo.

* Fix hxcpp.

* Remove leftover debug printf.

* Try to fix the C# CI failure.
  • Loading branch information
Apprentice-Alchemist authored Nov 24, 2022
1 parent 7980171 commit 7c7f284
Show file tree
Hide file tree
Showing 27 changed files with 812 additions and 10 deletions.
3 changes: 3 additions & 0 deletions extra/ImportAll.hx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ class ImportAll {
case "haxe.remoting.SyncSocketConnection": if( !(Context.defined("neko") || Context.defined("php") || Context.defined("cpp")) ) continue;
case "neko.vm.Ui" | "sys.db.Sqlite" | "sys.db.Mysql" if ( Context.defined("interp") ): continue;
case "sys.db.Sqlite" | "sys.db.Mysql" | "cs.db.AdoNet" if ( Context.defined("cs") ): continue;
case "haxe.atomic.AtomicBool" if(!Context.defined("target.atomics")): continue;
case "haxe.atomic.AtomicInt" if(!Context.defined("target.atomics")): continue;
case "haxe.atomic.AtomicObject" if(!Context.defined("target.atomics") || Context.defined("js") || Context.defined("cpp")): continue;
}
Context.getModule(cl);
} else if( sys.FileSystem.isDirectory(p + "/" + file) )
Expand Down
21 changes: 16 additions & 5 deletions src/context/common.ml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ type platform_config = {
pf_exceptions : exceptions_config;
(** the scoping of local variables *)
pf_scoping : var_scoping_config;
(** target supports atomic operations via haxe.Atomic **)
pf_supports_atomics : bool;
}

class compiler_callbacks = object(self)
Expand Down Expand Up @@ -539,7 +541,8 @@ let default_config =
pf_scoping = {
vs_scope = BlockScope;
vs_flags = [];
}
};
pf_supports_atomics = false;
}

let get_config com =
Expand Down Expand Up @@ -569,7 +572,8 @@ let get_config com =
vs_flags =
(if defined Define.JsUnflatten then ReserveAllTopLevelSymbols else ReserveAllTypesFlat)
:: if es6 then [NoShadowing; SwitchCasesNoBlocks;] else [VarHoisting; NoCatchVarShadowing];
}
};
pf_supports_atomics = true;
}
| Lua ->
{
Expand Down Expand Up @@ -652,7 +656,8 @@ let get_config com =
pf_scoping = { default_config.pf_scoping with
vs_flags = [NoShadowing];
vs_scope = FunctionScope;
}
};
pf_supports_atomics = true;
}
| Cs ->
{
Expand Down Expand Up @@ -680,6 +685,7 @@ let get_config com =
vs_scope = FunctionScope;
vs_flags = [NoShadowing]
};
pf_supports_atomics = true;
}
| Java ->
{
Expand Down Expand Up @@ -709,7 +715,8 @@ let get_config com =
{
vs_scope = FunctionScope;
vs_flags = [NoShadowing; ReserveAllTopLevelSymbols; ReserveNames(["_"])];
}
};
pf_supports_atomics = true;
}
| Python ->
{
Expand Down Expand Up @@ -740,6 +747,7 @@ let get_config com =
pf_capture_policy = CPWrapRef;
pf_pad_nulls = true;
pf_supports_threads = true;
pf_supports_atomics = true;
}
| Eval ->
{
Expand Down Expand Up @@ -937,7 +945,10 @@ let init_platform com pf =
raw_define com "target.unicode";
end;
raw_define_value com.defines "target.name" name;
raw_define com name
raw_define com name;
if com.config.pf_supports_atomics then begin
raw_define com "target.atomics"
end

let set_platform com pf file =
if com.platform <> Cross then failwith "Multiple targets";
Expand Down
1 change: 0 additions & 1 deletion src/generators/hlcode.ml
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,6 @@ let ostr fstr o =
| ORefData (r,d) -> Printf.sprintf "refdata %d, %d" r d
| ORefOffset (r,r2,off) -> Printf.sprintf "refoffset %d, %d, %d" r r2 off
| ONop s -> if s = "" then "nop" else "nop " ^ s

let fundecl_name f = if snd f.fpath = "" then "fun$" ^ (string_of_int f.findex) else (fst f.fpath) ^ "." ^ (snd f.fpath)

let dump pr code =
Expand Down
2 changes: 1 addition & 1 deletion src/generators/hlinterp.ml
Original file line number Diff line number Diff line change
Expand Up @@ -2546,7 +2546,7 @@ let check code macros =
reg r (rtype r2);
reg off HI32;
| ONop _ ->
()
();
) f.code
(* TODO : check that all path correctly initialize NULL values and reach a return *)
in
Expand Down
46 changes: 46 additions & 0 deletions std/cpp/_std/haxe/atomic/AtomicInt.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package haxe.atomic;

#if cppia
extern
#end
abstract AtomicInt(cpp.Pointer<Int>) {
public #if !(scriptable || cppia) inline #end function new(value:Int) {
this = cpp.Pointer.ofArray([value]);
}

public #if !(scriptable || cppia) inline #end function add(b:Int):Int {
return untyped __cpp__("_hx_atomic_add({0}, {1})", this, b);
}

public #if !(scriptable || cppia) inline #end function sub(b:Int):Int {
return untyped __cpp__("_hx_atomic_sub({0}, {1})", this, b);
}

public #if !(scriptable || cppia) inline #end function and(b:Int):Int {
return untyped __cpp__("_hx_atomic_and({0}, {1})", this, b);
}

public #if !(scriptable || cppia) inline #end function or(b:Int):Int {
return untyped __cpp__("_hx_atomic_or({0}, {1})", this, b);
}

public #if !(scriptable || cppia) inline #end function xor(b:Int):Int {
return untyped __cpp__("_hx_atomic_xor({0}, {1})", this, b);
}

public #if !(scriptable || cppia) inline #end function compareExchange(expected:Int, replacement:Int):Int {
return untyped __cpp__("_hx_atomic_compare_exchange({0}, {1}, {2})", this, expected, replacement);
}

public #if !(scriptable || cppia) inline #end function exchange(value:Int):Int {
return untyped __cpp__("_hx_atomic_exchange({0}, {1})", this, value);
}

public #if !(scriptable || cppia) inline #end function load():Int {
return untyped __cpp__("_hx_atomic_load({0})", this);
}

public #if !(scriptable || cppia) inline #end function store(value:Int):Int {
return untyped __cpp__("_hx_atomic_store({0}, {1})", this, value);
}
}
1 change: 1 addition & 0 deletions std/cpp/cppia/HostClasses.hx
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ class HostClasses {
"List",
"Map",
"String",
"haxe.atomic.AtomicInt"
];

static function parseClassInfo(externs:Map<String, Bool>, filename:String) {
Expand Down
61 changes: 61 additions & 0 deletions std/cs/_std/haxe/atomic/AtomicInt.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package haxe.atomic;

private class IntWrapper {
public var value:Int;

public function new(value:Int) {
this.value = value;
}
}

abstract AtomicInt(IntWrapper) to IntWrapper {
public inline function new(value:Int) {
this = new IntWrapper(value);
}

private inline function cas_loop(value:Int, op:(a:Int, b:Int) -> Int):Int {
var oldValue;
var newValue;
do {
oldValue = load();
newValue = op(oldValue, value);
} while(compareExchange(oldValue, newValue) != oldValue);
return oldValue;
}

public inline function add(b:Int):Int {
return cas_loop(b, (a, b) -> a + b);
}

public inline function sub(b:Int):Int {
return cas_loop(b, (a, b) -> a - b);
}

public inline function and(b:Int):Int {
return cas_loop(b, (a, b) -> cast a & b);
}

public inline function or(b:Int):Int {
return cas_loop(b, (a, b) -> cast a | b);
}

public inline function xor(b:Int):Int {
return cas_loop(b, (a, b) -> cast a ^ b);
}

public inline function compareExchange(expected:Int, replacement:Int):Int {
return cs.Syntax.code("System.Threading.Interlocked.CompareExchange(ref ({0}).value, {1}, {2})", this, replacement, expected);
}

public inline function exchange(value:Int):Int {
return cs.Syntax.code("System.Threading.Interlocked.Exchange(ref ({0}).value, {1})", this, value);
}

public inline function load():Int {
return this.value; // according to the CLI spec reads and writes are atomic
}

public inline function store(value:Int):Int {
return this.value = value; // according to the CLI spec reads and writes are atomic
}
}
33 changes: 33 additions & 0 deletions std/cs/_std/haxe/atomic/AtomicObject.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package haxe.atomic;

import cs.system.threading.Interlocked.*;

private class ObjectWrapper<T:{}> {
public var value:T;

public function new(value:T) {
this.value = value;
}
}

extern abstract AtomicObject<T:{}>(ObjectWrapper<T>) {
public inline function new(value:T) {
this = new ObjectWrapper(value);
}

public inline function compareExchange(expected:T, replacement:T):T {
return cs.Syntax.code("System.Threading.Interlocked.CompareExchange(ref ({0}).value, {1}, {2})", this, replacement, expected);
}

public inline function exchange(value:T):T {
return cs.Syntax.code("System.Threading.Interlocked.Exchange(ref ({0}).value, {1})", this, value);
}

public inline function load():T {
return this.value; // according to the CLI spec reads and writes are atomic
}

public inline function store(value:T):T {
return this.value = value; // according to the CLI spec reads and writes are atomic
}
}
55 changes: 55 additions & 0 deletions std/haxe/atomic/AtomicBool.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package haxe.atomic;

#if !(target.atomics || core_api)
#error "Atomic operations are not supported on this target!"
#end

/**
Atomic boolean.
(js) The Atomics and SharedArrayBuffer objects need to be available. Errors will be thrown if this is not the case.
**/
@:coreApi
abstract AtomicBool(AtomicInt) {
private inline function toInt(v:Bool):Int {
return v ? 1 : 0;
}

private inline function toBool(v:Int):Bool {
return v == 1;
}

public inline function new(value:Bool):Void {
this = new AtomicInt(toInt(value));
}

/**
Atomically compares the value of `a` with `expected` and replaces `a` with `replacement` if they are equal..
Returns the original value of `a`.
**/
public inline function compareExchange(expected:Bool, replacement:Bool):Bool {
return toBool(this.compareExchange(toInt(expected), toInt(replacement)));
}

/**
Atomically exchanges `a` with `value`.
Returns the original value of `a`.
**/
public inline function exchange(value:Bool):Bool {
return toBool(this.exchange(toInt(value)));
}

/**
Atomically fetches the value of `a`.
**/
public inline function load():Bool {
return toBool(this.load());
}

/**
Atomically stores `value` into `a`.
Returns the value that has been stored.
**/
public inline function store(value:Bool):Bool {
return toBool(this.store(toInt(value)));
}
}
67 changes: 67 additions & 0 deletions std/haxe/atomic/AtomicInt.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package haxe.atomic;

#if !(target.atomics || core_api)
#error "This target does not support atomic operations."
#end

/**
Atomic integer.
(js) The Atomics and SharedArrayBuffer objects need to be available. Errors will be thrown if this is not the case.
**/
@:coreType
abstract AtomicInt {
public function new(value:Int):Void;

/**
Atomically adds `b` to `a`.
Returns the original value of `a`.
**/
public function add(b:Int):Int;

/**
Atomically substracts `b` from `a`.
Returns the original value of `a`.
**/
public function sub(b:Int):Int;

/**
Atomically computes the bitwise and of `a` and `b` and stores it in `a`.
Returns the original value of `a`.
**/
public function and(b:Int):Int;

/**
Atomically computes the bitwise or of `a` and `b` and stores it in `a`.
Returns the original value of `a`.
**/
public function or(b:Int):Int;

/**
Atomically computes the bitwise xor of `a` and `b` and stores it in `a`.
Returns the original value of `a`.
**/
public function xor(b:Int):Int;

/**
Atomically compares the value of `a` with `expected` and replaces `a` with `replacement` if they are equal..
Returns the original value of `a`.
**/
public function compareExchange(expected:Int, replacement:Int):Int;

/**
Atomically exchanges `a` with `value`.
Returns the original value of `a`.
**/
public function exchange(value:Int):Int;

/**
Atomically fetches the value of `a`.
**/
public function load():Int;

/**
Atomically stores `value` into `a`.
Returns the value that has been stored.
**/
public function store(value:Int):Int;
}
Loading

0 comments on commit 7c7f284

Please sign in to comment.