Prechádzať zdrojové kódy

Atomic operations. (#10610)

* 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.
Zeta 2 rokov pred
rodič
commit
7c7f284a8d

+ 3 - 0
extra/ImportAll.hx

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

+ 16 - 5
src/context/common.ml

@@ -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)
@@ -539,7 +541,8 @@ let default_config =
 		pf_scoping = {
 			vs_scope = BlockScope;
 			vs_flags = [];
-		}
+		};
+		pf_supports_atomics = false;
 	}
 
 let get_config com =
@@ -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 ->
 		{
@@ -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 ->
 		{
@@ -680,6 +685,7 @@ let get_config com =
 				vs_scope = FunctionScope;
 				vs_flags = [NoShadowing]
 			};
+			pf_supports_atomics = true;
 		}
 	| Java ->
 		{
@@ -709,7 +715,8 @@ let get_config com =
 					{
 						vs_scope = FunctionScope;
 						vs_flags = [NoShadowing; ReserveAllTopLevelSymbols; ReserveNames(["_"])];
-					}
+					};
+			pf_supports_atomics = true;
 		}
 	| Python ->
 		{
@@ -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 ->
 		{
@@ -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";

+ 0 - 1
src/generators/hlcode.ml

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

+ 1 - 1
src/generators/hlinterp.ml

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

+ 46 - 0
std/cpp/_std/haxe/atomic/AtomicInt.hx

@@ -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 - 0
std/cpp/cppia/HostClasses.hx

@@ -150,6 +150,7 @@ class HostClasses {
 		"List",
 		"Map",
 		"String",
+		"haxe.atomic.AtomicInt"
 	];
 
 	static function parseClassInfo(externs:Map<String, Bool>, filename:String) {

+ 61 - 0
std/cs/_std/haxe/atomic/AtomicInt.hx

@@ -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 - 0
std/cs/_std/haxe/atomic/AtomicObject.hx

@@ -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 - 0
std/haxe/atomic/AtomicBool.hx

@@ -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 - 0
std/haxe/atomic/AtomicInt.hx

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

+ 44 - 0
std/haxe/atomic/AtomicObject.hx

@@ -0,0 +1,44 @@
+package haxe.atomic;
+
+#if !(target.atomics || core_api)
+#error "This target does not support atomic operations."
+#end
+
+#if (js || hxcpp)
+#error "JavaScript and Hxcpp do not support AtomicObject"
+#end
+
+/**
+	Atomic object. Use with care, this does not magically make it thread-safe to mutate objects.
+	Not supported on JavaScript or C++.
+**/
+@:coreType
+abstract AtomicObject<T:{}> {
+	public function new(value:T):Void;
+
+	/**
+		Atomically compares the value of `a` with `expected` and replaces `a` with `replacement` if they are equal..
+		Returns the original value of `a`.
+
+		Note that comparison is done by reference, and not by value.
+		While this is expected for most objects, this might give unexpected behaviour for strings.
+	**/
+	public function compareExchange(expected:T, replacement:T):T;
+
+	/**
+		Atomically exchanges `a` with `value`.
+		Returns the original value of `a`.
+	**/
+	public function exchange(value:T):T;
+
+	/**
+		Atomically fetches the value of `a`.
+	**/
+	public function load():T;
+
+	/**
+		Atomically stores `value` into `a`.
+		Returns the value that has been stored.
+	**/
+	public function store(value:T):T;
+}

+ 19 - 0
std/hl/Atomics.hx

@@ -0,0 +1,19 @@
+package hl;
+
+@:hlNative("std", "atomic_")
+extern class Atomics {
+	static function add32(r:hl.Ref<Int>, a:Int):Int;
+	static function sub32(r:hl.Ref<Int>, a:Int):Int;
+	static function and32(r:hl.Ref<Int>, a:Int):Int;
+	static function or32(r:hl.Ref<Int>, a:Int):Int;
+	static function xor32(r:hl.Ref<Int>, a:Int):Int;
+	static function compareExchange32(r:hl.Ref<Int>, a:Int, b:Int):Int;
+	static function exchange32(r:hl.Ref<Int>, val:Int):Int;
+	static function load32(r:hl.Ref<Int>):Int;
+	static function store32(r:hl.Ref<Int>, val:Int):Int;
+
+	static function compareExchangePtr(r:hl.Ref<Dynamic>, a:Dynamic, b:Dynamic):Dynamic;
+	static function exchangePtr(r:hl.Ref<Dynamic>, val:Dynamic):Dynamic;
+	static function loadPtr(r:hl.Ref<Dynamic>):Dynamic;
+	static function storePtr(r:hl.Ref<Dynamic>, val:Dynamic):Dynamic;
+}

+ 1 - 1
std/hl/_std/haxe/NativeStackTrace.hx

@@ -29,7 +29,7 @@ class NativeStackTrace {
 		var count = callStackRaw(null);
 		var arr = new NativeArray(count);
 		callStackRaw(arr);
-		return arr;
+		return arr.sub(1, arr.length - 1);
 	}
 
 	@:hlNative("std", "exception_stack_raw")

+ 49 - 0
std/hl/_std/haxe/atomic/AtomicInt.hx

@@ -0,0 +1,49 @@
+package haxe.atomic;
+
+#if (hl_ver < version("1.13.0") && !doc_gen)
+#error "Atomic operations require HL 1.13+"
+#end
+import hl.Atomics;
+
+abstract AtomicInt(hl.NativeArray<Int>) {
+	public inline function new(value:Int):Void {
+		this = new hl.NativeArray(1);
+		this[0] = value;
+	}
+
+	public inline function add(b:Int):Int {
+		return Atomics.add32(this.getRef(), b);
+	}
+
+	public inline function sub(b:Int):Int {
+		return Atomics.sub32(this.getRef(), b);
+	}
+
+	public inline function and(b:Int):Int {
+		return Atomics.and32(this.getRef(), b);
+	}
+
+	public inline function or(b:Int):Int {
+		return Atomics.or32(this.getRef(), b);
+	}
+
+	public inline function xor(b:Int):Int {
+		return Atomics.xor32(this.getRef(), b);
+	}
+
+	public inline function compareExchange(expected:Int, replacement:Int):Int {
+		return Atomics.compareExchange32(this.getRef(), expected, replacement);
+	}
+
+	public inline function exchange(value:Int):Int {
+		return Atomics.exchange32(this.getRef(), value);
+	}
+
+	public inline function load():Int {
+		return Atomics.load32(this.getRef());
+	}
+
+	public inline function store(value:Int):Int {
+		return Atomics.store32(this.getRef(), value);
+	}
+}

+ 31 - 0
std/hl/_std/haxe/atomic/AtomicObject.hx

@@ -0,0 +1,31 @@
+package haxe.atomic;
+
+#if (hl_ver < version("1.13.0") && !doc_gen)
+#error "Atomic operations require HL 1.13+"
+#end
+import hl.Atomics;
+
+// use hl.NativeArray<Dynamic> instead of hl.NativeArray<T>
+// so that the compiler doesn't get confused and emit hl.Ref.make(this.getRef())
+abstract AtomicObject<T:{}>(hl.NativeArray<Dynamic>) {
+	public inline function new(value:T):Void {
+		this = new hl.NativeArray(1);
+		this[0] = value;
+	}
+
+	public inline function compareExchange(expected:T, replacement:T):T {
+		return Atomics.compareExchangePtr(this.getRef(), expected, replacement);
+	}
+
+	public inline function exchange(value:T):T {
+		return Atomics.exchangePtr(this.getRef(), value);
+	}
+
+	public inline function load():T {
+		return Atomics.loadPtr(this.getRef());
+	}
+
+	public inline function store(value:T):T {
+		return Atomics.storePtr(this.getRef(), value);
+	}
+}

+ 34 - 0
std/java/_std/haxe/atomic/AtomicBool.hx

@@ -0,0 +1,34 @@
+package haxe.atomic;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+abstract AtomicBool(AtomicBoolean) {
+	public inline function new(value:Bool) {
+		this = new AtomicBoolean(value);
+	}
+
+	public inline function compareExchange(expected:Bool, replacement:Bool):Bool {
+		// Java's compareAndSet returns a boolean, so do a CAS loop to be able to return the original value without a potential race condition
+
+		var original;
+		var real_replacement;
+		do {
+			original = this.get();
+			real_replacement = original == expected ? replacement : original;
+		} while (!this.compareAndSet(original, real_replacement));
+		return original;
+	}
+
+	public inline function exchange(value:Bool):Bool {
+		return this.getAndSet(value);
+	}
+
+	public inline function load():Bool {
+		return this.get();
+	}
+
+	public inline function store(value:Bool):Bool {
+		this.set(value);
+		return value;
+	}
+}

+ 64 - 0
std/java/_std/haxe/atomic/AtomicInt.hx

@@ -0,0 +1,64 @@
+package haxe.atomic;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+abstract AtomicInt(AtomicInteger) {
+	public inline function new(value:Int) {
+		this = new AtomicInteger(value);
+	}
+
+	private inline function cas_loop(value:Int, op:(a:Int, b:Int) -> Int):Int {
+		var val;
+
+		do {
+			val = this.get();
+		} while (!this.compareAndSet(val, op(val, value)));
+
+		return val;
+	}
+
+	public inline function add(b:Int):Int {
+		return this.getAndAdd(b);
+	}
+
+	public inline function sub(b:Int):Int {
+		return this.getAndAdd(-b);
+	}
+
+	public inline function and(b:Int):Int {
+		return cas_loop(b, (a:Int, b:Int) -> a & b);
+	}
+
+	public inline function or(b:Int):Int {
+		return cas_loop(b, (a:Int, b:Int) -> a | b);
+	}
+
+	public inline function xor(b:Int):Int {
+		return cas_loop(b, (a:Int, b:Int) -> a ^ b);
+	}
+
+	public inline function compareExchange(expected:Int, replacement:Int):Int {
+		// Java's compareAndSet returns a boolean, so do a CAS loop to be able to return the original value without a potential race condition
+
+		var original;
+		var real_replacement;
+		do {
+			original = this.get();
+			real_replacement = original == expected ? replacement : original;
+		} while (!this.compareAndSet(original, real_replacement));
+		return original;
+	}
+
+	public inline function exchange(value:Int):Int {
+		return this.getAndSet(value);
+	}
+
+	public inline function load():Int {
+		return this.get();
+	}
+
+	public inline function store(value:Int):Int {
+		this.set(value);
+		return value;
+	}
+}

+ 34 - 0
std/java/_std/haxe/atomic/AtomicObject.hx

@@ -0,0 +1,34 @@
+package haxe.atomic;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+abstract AtomicObject<T:{}>(AtomicReference<T>) {
+	public inline function new(value:T) {
+		this = new AtomicReference(value);
+	}
+
+	public inline function compareExchange(expected:T, replacement:T):T {
+		// Java's compareAndSet returns a boolean, so do a CAS loop to be able to return the original value without a potential race condition
+
+		var original;
+		var real_replacement;
+		do {
+			original = this.get();
+			real_replacement = original == expected ? replacement : original;
+		} while (!this.compareAndSet(original, real_replacement));
+		return original;
+	}
+
+	public inline function exchange(value:T):T {
+		return this.getAndSet(value);
+	}
+
+	public inline function load():T {
+		return this.get();
+	}
+
+	public inline function store(value:T):T {
+		this.set(value);
+		return value;
+	}
+}

+ 46 - 0
std/js/_std/haxe/atomic/AtomicInt.hx

@@ -0,0 +1,46 @@
+package haxe.atomic;
+
+import js.lib.Atomics;
+
+abstract AtomicInt(js.lib.Int32Array) to js.lib.Int32Array {
+	public inline function new(value:Int) {
+		this = new js.lib.Int32Array(new js.lib.SharedArrayBuffer(js.lib.Int32Array.BYTES_PER_ELEMENT));
+		this[0] = value;
+	}
+
+	public inline function add(b:Int):Int {
+		return Atomics.add(this, 0, b);
+	}
+
+	public inline function sub(b:Int):Int {
+		return Atomics.sub(this, 0, b);
+	}
+
+	public inline function and(b:Int):Int {
+		return Atomics.and(this, 0, b);
+	}
+
+	public inline function or(b:Int):Int {
+		return Atomics.or(this, 0, b);
+	}
+
+	public inline function xor(b:Int):Int {
+		return Atomics.xor(this, 0, b);
+	}
+
+	public inline function compareExchange(expected:Int, replacement:Int):Int {
+		return Atomics.compareExchange(this, 0, expected, replacement);
+	}
+
+	public inline function exchange(value:Int):Int {
+		return Atomics.exchange(this, 0, value);
+	}
+
+	public inline function load():Int {
+		return Atomics.load(this, 0);
+	}
+
+	public inline function store(value:Int):Int {
+		return Atomics.store(this, 0, value);
+	}
+}

+ 99 - 0
std/js/lib/Atomics.hx

@@ -0,0 +1,99 @@
+package js.lib;
+
+private typedef E<A, B> = haxe.extern.EitherType<A, B>;
+private typedef IntTypedArray = E<Int8Array, E<Uint8Array, E<Int16Array, E<Uint16Array, E<Int32Array, Uint32Array>>>>>;
+
+/**
+	The Atomics object provides atomic operations as static methods. They are used with SharedArrayBuffer and ArrayBuffer objects.
+
+	Documentation [Atomics](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics) by [Mozilla Contributors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/contributors.txt), licensed under [CC-BY-SA 2.5](https://creativecommons.org/licenses/by-sa/2.5/).
+**/
+@:native("Atomics")
+extern class Atomics {
+	/**
+		Adds the provided value to the existing value at the specified index of the array.
+		Returns the old value at that index.
+		This atomic operation guarantees that no other write happens until the modified value is written back.
+	**/
+	static function add(typedArray:IntTypedArray, index:Int, value:Int):Int;
+
+	/**
+		Computes a bitwise AND on the value at the specified index of the array with the provided value.
+		Returns the old value at that index.
+		This atomic operation guarantees that no other write happens until the modified value is written back.
+	**/
+	static function and(typedArray:IntTypedArray, index:Int, value:Int):Int;
+
+	/**
+		Stores a value at the specified index of the array, if it equals a value.
+		Returns the old value.
+		This atomic operation guarantees that no other write happens until the modified value is written back.
+	**/
+	static function compareExchange(typedArray:IntTypedArray, index:Int, expectedValue:Int, replacementValue:Int):Int;
+
+	/**
+		Stores a value at the specified index of the array.
+		Returns the old value.
+		This atomic operation guarantees that no other write happens until the modified value is written back.
+	**/
+	static function exchange(typedArray:IntTypedArray, index:Int, value:Int):Int;
+
+	/**
+		An optimization primitive that can be used to determine whether to use locks or atomic operations.
+		Returns `true` if an atomic operation on arrays of the given element size will be implemented using a hardware atomic operation (as opposed to a lock). Experts only.
+	**/
+	static function isLockFree(size:Int):Bool;
+
+	/**
+		Returns the value at the specified index of the array.
+		This atomic operation guarantees that no other write happens until the modified value is written back.
+	**/
+	static function load(typedArray:IntTypedArray, index:Int):Int;
+
+	/**
+		Notifies agents that are waiting on the specified index of the array.
+		Returns the number of agents that were notified.
+	**/
+	static function notify(typedArray:IntTypedArray, index:Int, ?count:Int):Int;
+
+	/**
+		Computes a bitwise OR on the value at the specified index of the array with the provided value.
+		Returns the old value at that index.
+		This atomic operation guarantees that no other write happens until the modified value is written back.
+	**/
+	static function or(typedArray:IntTypedArray, index:Int, value:Int):Int;
+
+	/**
+		Stores a value at the specified index of the array.
+		Returns the value.
+		This atomic operation guarantees that no other write happens until the modified value is written back.
+	**/
+	static function store(typedArray:IntTypedArray, index:Int, value:Int):Int;
+
+	/**
+		Subtracts a value at the specified index of the array.
+		Returns the old value at that index.
+		This atomic operation guarantees that no other write happens until the modified value is written back.
+	**/
+	static function sub(typedArray:IntTypedArray, index:Int, value:Int):Int;
+
+	/**
+		Verifies that the specified index of the array still contains a value and sleeps awaiting or times out.
+		Returns either "ok", "not-equal", or "timed-out". If waiting is not allowed in the calling agent then it throws an Error exception.
+		Most browsers will not allow wait() on the browser's main thread.)
+	**/
+	static function wait(typedArray:Int32Array, index:Int, value:Int, ?timeout:Int):WaitValue;
+
+	/**
+		Computes a bitwise XOR on the value at the specified index of the array with the provided value.
+		Returns the old value at that index.
+		This atomic operation guarantees that no other write happens until the modified value is written back.
+	**/
+	static function xor(typedArray:IntTypedArray, index:Int, value:Int):Int;
+}
+
+enum abstract WaitValue(String) {
+	var OK = "ok";
+	var NotEqual = "not-equal";
+	var TimedOut = "timed-out";
+}

+ 16 - 0
std/js/lib/SharedArrayBuffer.hx

@@ -0,0 +1,16 @@
+package js.lib;
+
+/**
+	The SharedArrayBuffer object is used to represent a generic, fixed-length raw binary data buffer, similar to the ArrayBuffer object, but in a way that they can be used to create views on shared memory.
+	A SharedArrayBuffer is not a Transferable Object, unlike an ArrayBuffer which is transferable.
+
+	Documentation [SharedArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer) by [Mozilla Contributors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer/contributors.txt), licensed under [CC-BY-SA 2.5](https://creativecommons.org/licenses/by-sa/2.5/).
+**/
+@:native("SharedArrayBuffer")
+extern class SharedArrayBuffer {
+	final byteLength:Int;
+
+	function new(?length:Int):Void;
+
+	function slice(?begin:Int, ?end:Int):ArrayBuffer;
+}

+ 2 - 1
tests/threads/build.hxml

@@ -3,4 +3,5 @@
 --library utest
 --dce full
 -D analyzer-optimize
--D UTEST_PRINT_TESTS
+-D UTEST_PRINT_TESTS
+-D hl-ver=1.13.0

+ 23 - 0
tests/threads/src/cases/TestAtomics.hx

@@ -0,0 +1,23 @@
+package cases;
+
+
+#if target.atomics
+import haxe.atomic.AtomicInt;
+#end
+
+@:timeout(2000)
+class TestAtomics extends utest.Test {
+	#if target.atomics
+	function test(async:Async) {
+		var a = new AtomicInt(5);
+		final thread = Thread.create(() -> {
+			while(a.compareExchange(0, 2) != 0) {}
+		});
+		isTrue(a.compareExchange(5, 0) == 5);
+
+		while (a.compareExchange(2, 2) != 2) {}
+
+		async.done();
+	}
+	#end
+}

+ 1 - 1
tests/unit/compile-hl.hxml

@@ -3,4 +3,4 @@ compile-each.hxml
 -hl bin/unit.hl
 #-D interp
 -D hl-check
--D hl-ver=1.11.0
+-D hl-ver=1.13.0

+ 18 - 0
tests/unit/src/unitstd/haxe/atomic/AtomicBool.unit.hx

@@ -0,0 +1,18 @@
+#if (target.atomics)
+var a = new haxe.atomic.AtomicBool(true);
+
+a.load() == true;
+a.store(false) == false;
+a.load() == false;
+
+a.compareExchange(false, true) == false;
+a.load() == true;
+
+a.compareExchange(false, false) == true;
+a.load() == true;
+
+a.exchange(true) == true;
+a.load() == true;
+#else
+0 == 0; // prevent "no assertions" warning
+#end

+ 33 - 0
tests/unit/src/unitstd/haxe/atomic/AtomicInt.unit.hx

@@ -0,0 +1,33 @@
+#if target.atomics
+var a = new haxe.atomic.AtomicInt(0);
+
+a.load() == 0;
+
+a.store(5) == 5;
+a.load() == 5;
+
+a.add(5) == 5;
+a.load() == 10;
+
+a.sub(5) == 10;
+a.load() == 5;
+
+a.and(20) == 5;
+a.load() == 4;
+
+a.or(3) == 4;
+a.load() == 7;
+
+a.xor(2) == 7;
+a.load() == 5;
+
+a.compareExchange(0, 0) == 5;
+a.load() == 5;
+a.compareExchange(5, 0) == 5;
+a.load() == 0;
+
+a.exchange(10) == 0;
+a.load() == 10;
+#else
+0 == 0; // prevent "no assertions" warning
+#end

+ 15 - 0
tests/unit/src/unitstd/haxe/atomic/AtomicObject.unit.hx

@@ -0,0 +1,15 @@
+#if (target.atomics && !(js || cpp))
+var a = new haxe.atomic.AtomicObject("Hey World!");
+
+a.load() == "Hey World!";
+a.store("Hello World!") == "Hello World!";
+a.load() == "Hello World!";
+
+a.compareExchange("Hello World!", "Goodbye World!") == "Hello World!";
+a.load() == "Goodbye World!";
+
+a.exchange("Hello World!") == "Goodbye World!";
+a.load() == "Hello World!";
+#else
+0 == 0; // prevent "no assertions" warning
+#end