[WHLSL] Implement atomic operations and barriers
authormmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 22 Sep 2018 23:22:55 +0000 (23:22 +0000)
committermmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 22 Sep 2018 23:22:55 +0000 (23:22 +0000)
https://bugs.webkit.org/show_bug.cgi?id=189025

Reviewed by Dean Jackson.

In the interpreter, atomic operations don't need to be atomic.

* WebGPUShadingLanguageRI/Intrinsics.js:
(Intrinsics.):
* WebGPUShadingLanguageRI/StandardLibrary.js:
(let.standardLibrary):
* WebGPUShadingLanguageRI/Test.js:
(tests.atomics):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@236387 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Tools/ChangeLog
Tools/WebGPUShadingLanguageRI/Intrinsics.js
Tools/WebGPUShadingLanguageRI/StandardLibrary.js
Tools/WebGPUShadingLanguageRI/Test.js

index 1b3f8e8..4f1fab2 100644 (file)
@@ -1,3 +1,19 @@
+2018-09-22  Myles C. Maxfield  <mmaxfield@apple.com>
+
+        [WHLSL] Implement atomic operations and barriers
+        https://bugs.webkit.org/show_bug.cgi?id=189025
+
+        Reviewed by Dean Jackson.
+
+        In the interpreter, atomic operations don't need to be atomic.
+
+        * WebGPUShadingLanguageRI/Intrinsics.js:
+        (Intrinsics.):
+        * WebGPUShadingLanguageRI/StandardLibrary.js:
+        (let.standardLibrary):
+        * WebGPUShadingLanguageRI/Test.js:
+        (tests.atomics):
+
 2018-09-22  Thibault Saunier  <tsaunier@igalia.com>
 
         [WPE] Be very permissive in the MiniBrowser.
index 68b0dd5..b5b2b6e 100644 (file)
@@ -230,12 +230,16 @@ class Intrinsics {
              "native typedef atomic_int",
              type => {
                  this.atomic_int = type;
+                 type.size = 1;
+                 type.populateDefaultValue = (buffer, offset) => buffer.set(offset, 0);
              });
 
         this._map.set(
              "native typedef atomic_uint",
              type => {
                  this.atomic_uint = type;
+                 type.size = 1;
+                 type.populateDefaultValue = (buffer, offset) => buffer.set(offset, 0);
              });
 
         for (let vectorType of VectorElementTypes) {
@@ -853,6 +857,248 @@ class Intrinsics {
         for (let setter of BuiltinMatrixSetter.functions())
             this._map.set(setter.toString(), func => setter.instantiateImplementation(func));
 
+        for (let addressSpace of ["thread", "threadgroup", "device"]) {
+            this._map.set(
+                `native void InterlockedAdd(atomic_uint* ${addressSpace},uint,uint* thread)`,
+                func => {
+                    func.implementation = function([atomic, value, originalValue]) {
+                        if (!atomic.loadValue())
+                            throw new WTrapError("[Atomics]", "Null atomic pointer");
+                        let a = atomic.loadValue().loadValue();
+                        let b = value.loadValue();
+                        let result = castToUint(a + b);
+                        if (originalValue.loadValue())
+                            originalValue.loadValue().copyFrom(EPtr.box(a), 1);
+                        atomic.loadValue().copyFrom(EPtr.box(result), 1);
+                    }
+                });
+
+            this._map.set(
+                `native void InterlockedAdd(atomic_int* ${addressSpace},int,int* thread)`,
+                func => {
+                    func.implementation = function([atomic, value, originalValue]) {
+                        if (!atomic.loadValue())
+                            throw new WTrapError("[Atomics]", "Null atomic pointer");
+                        let a = atomic.loadValue().loadValue();
+                        let b = value.loadValue();
+                        let result = castToInt(a + b);
+                        if (originalValue.loadValue())
+                            originalValue.loadValue().copyFrom(EPtr.box(a), 1);
+                        atomic.loadValue().copyFrom(EPtr.box(result), 1);
+                    }
+                });
+
+            this._map.set(
+                `native void InterlockedAnd(atomic_uint* ${addressSpace},uint,uint* thread)`,
+                func => {
+                    func.implementation = function([atomic, value, originalValue]) {
+                        if (!atomic.loadValue())
+                            throw new WTrapError("[Atomics]", "Null atomic pointer");
+                        let a = atomic.loadValue().loadValue();
+                        let b = value.loadValue();
+                        let result = castToUint(a & b);
+                        if (originalValue.loadValue())
+                            originalValue.loadValue().copyFrom(EPtr.box(a), 1);
+                        atomic.loadValue().copyFrom(EPtr.box(result), 1);
+                    }
+                });
+
+            this._map.set(
+                `native void InterlockedAnd(atomic_int* ${addressSpace},int,int* thread)`,
+                func => {
+                    func.implementation = function([atomic, value, originalValue]) {
+                        if (!atomic.loadValue())
+                            throw new WTrapError("[Atomics]", "Null atomic pointer");
+                        let a = atomic.loadValue().loadValue();
+                        let b = value.loadValue();
+                        let result = castToInt(a & b);
+                        if (originalValue.loadValue())
+                            originalValue.loadValue().copyFrom(EPtr.box(a), 1);
+                        atomic.loadValue().copyFrom(EPtr.box(result), 1);
+                    }
+                });
+
+            this._map.set(
+                `native void InterlockedExchange(atomic_uint* ${addressSpace},uint,uint* thread)`,
+                func => {
+                    func.implementation = function([atomic, value, originalValue]) {
+                        if (!atomic.loadValue())
+                            throw new WTrapError("[Atomics]", "Null atomic pointer");
+                        let a = atomic.loadValue().loadValue();
+                        let b = value.loadValue();
+                        if (originalValue.loadValue())
+                            originalValue.loadValue().copyFrom(EPtr.box(a), 1);
+                        atomic.loadValue().copyFrom(EPtr.box(b), 1);
+                    }
+                });
+
+            this._map.set(
+                `native void InterlockedExchange(atomic_int* ${addressSpace},int,int* thread)`,
+                func => {
+                    func.implementation = function([atomic, value, originalValue]) {
+                        if (!atomic.loadValue())
+                            throw new WTrapError("[Atomics]", "Null atomic pointer");
+                        let a = atomic.loadValue().loadValue();
+                        let b = value.loadValue();
+                        if (originalValue.loadValue())
+                            originalValue.loadValue().copyFrom(EPtr.box(a), 1);
+                        atomic.loadValue().copyFrom(EPtr.box(b), 1);
+                    }
+                });
+
+            this._map.set(
+                `native void InterlockedMax(atomic_uint* ${addressSpace},uint,uint* thread)`,
+                func => {
+                    func.implementation = function([atomic, value, originalValue]) {
+                        if (!atomic.loadValue())
+                            throw new WTrapError("[Atomics]", "Null atomic pointer");
+                        let a = atomic.loadValue().loadValue();
+                        let b = value.loadValue();
+                        let result = castToUint(a > b ? a : b);
+                        if (originalValue.loadValue())
+                            originalValue.loadValue().copyFrom(EPtr.box(a), 1);
+                        atomic.loadValue().copyFrom(EPtr.box(result), 1);
+                    }
+                });
+
+            this._map.set(
+                `native void InterlockedMax(atomic_int* ${addressSpace},int,int* thread)`,
+                func => {
+                    func.implementation = function([atomic, value, originalValue]) {
+                        if (!atomic.loadValue())
+                            throw new WTrapError("[Atomics]", "Null atomic pointer");
+                        let a = atomic.loadValue().loadValue();
+                        let b = value.loadValue();
+                        let result = castToInt(a > b ? a : b);
+                        if (originalValue.loadValue())
+                            originalValue.loadValue().copyFrom(EPtr.box(a), 1);
+                        atomic.loadValue().copyFrom(EPtr.box(result), 1);
+                    }
+                });
+
+            this._map.set(
+                `native void InterlockedMin(atomic_uint* ${addressSpace},uint,uint* thread)`,
+                func => {
+                    func.implementation = function([atomic, value, originalValue]) {
+                        if (!atomic.loadValue())
+                            throw new WTrapError("[Atomics]", "Null atomic pointer");
+                        let a = atomic.loadValue().loadValue();
+                        let b = value.loadValue();
+                        let result = castToUint(a < b ? a : b);
+                        if (originalValue.loadValue())
+                            originalValue.loadValue().copyFrom(EPtr.box(a), 1);
+                        atomic.loadValue().copyFrom(EPtr.box(result), 1);
+                    }
+                });
+
+            this._map.set(
+                `native void InterlockedMin(atomic_int* ${addressSpace},int,int* thread)`,
+                func => {
+                    func.implementation = function([atomic, value, originalValue]) {
+                        if (!atomic.loadValue())
+                            throw new WTrapError("[Atomics]", "Null atomic pointer");
+                        let a = atomic.loadValue().loadValue();
+                        let b = value.loadValue();
+                        let result = castToInt(a < b ? a : b);
+                        if (originalValue.loadValue())
+                            originalValue.loadValue().copyFrom(EPtr.box(a), 1);
+                        atomic.loadValue().copyFrom(EPtr.box(result), 1);
+                    }
+                });
+
+            this._map.set(
+                `native void InterlockedOr(atomic_uint* ${addressSpace},uint,uint* thread)`,
+                func => {
+                    func.implementation = function([atomic, value, originalValue]) {
+                        if (!atomic.loadValue())
+                            throw new WTrapError("[Atomics]", "Null atomic pointer");
+                        let a = atomic.loadValue().loadValue();
+                        let b = value.loadValue();
+                        let result = castToUint(a | b);
+                        if (originalValue.loadValue())
+                            originalValue.loadValue().copyFrom(EPtr.box(a), 1);
+                        atomic.loadValue().copyFrom(EPtr.box(result), 1);
+                    }
+                });
+
+            this._map.set(
+                `native void InterlockedOr(atomic_int* ${addressSpace},int,int* thread)`,
+                func => {
+                    func.implementation = function([atomic, value, originalValue]) {
+                        if (!atomic.loadValue())
+                            throw new WTrapError("[Atomics]", "Null atomic pointer");
+                        let a = atomic.loadValue().loadValue();
+                        let b = value.loadValue();
+                        let result = castToInt(a | b);
+                        if (originalValue.loadValue())
+                            originalValue.loadValue().copyFrom(EPtr.box(a), 1);
+                        atomic.loadValue().copyFrom(EPtr.box(result), 1);
+                    }
+                });
+
+            this._map.set(
+                `native void InterlockedXor(atomic_uint* ${addressSpace},uint,uint* thread)`,
+                func => {
+                    func.implementation = function([atomic, value, originalValue]) {
+                        if (!atomic.loadValue())
+                            throw new WTrapError("[Atomics]", "Null atomic pointer");
+                        let a = atomic.loadValue().loadValue();
+                        let b = value.loadValue();
+                        let result = castToUint(a ^ b);
+                        if (originalValue.loadValue())
+                            originalValue.loadValue().copyFrom(EPtr.box(a), 1);
+                        atomic.loadValue().copyFrom(EPtr.box(result), 1);
+                    }
+                });
+
+            this._map.set(
+                `native void InterlockedXor(atomic_int* ${addressSpace},int,int* thread)`,
+                func => {
+                    func.implementation = function([atomic, value, originalValue]) {
+                        if (!atomic.loadValue())
+                            throw new WTrapError("[Atomics]", "Null atomic pointer");
+                        let a = atomic.loadValue().loadValue();
+                        let b = value.loadValue();
+                        let result = castToInt(a ^ b);
+                        if (originalValue.loadValue())
+                            originalValue.loadValue().copyFrom(EPtr.box(a), 1);
+                        atomic.loadValue().copyFrom(EPtr.box(result), 1);
+                    }
+                });
+
+            this._map.set(
+                `native void InterlockedCompareExchange(atomic_uint* ${addressSpace},uint,uint,uint* thread)`,
+                func => {
+                    func.implementation = function([atomic, compareValue, value, originalValue]) {
+                        if (!atomic.loadValue())
+                            throw new WTrapError("[Atomics]", "Null atomic pointer");
+                        let a = atomic.loadValue().loadValue();
+                        let b = compareValue.loadValue();
+                        let c = value.loadValue();
+                        if (a == b)
+                            atomic.loadValue().copyFrom(EPtr.box(c), 1);
+                        if (originalValue.loadValue())
+                            originalValue.loadValue().copyFrom(EPtr.box(a), 1);
+                    }
+                });
+
+            this._map.set(
+                `native void InterlockedCompareExchange(atomic_int* ${addressSpace},int,int,int* thread)`,
+                func => {
+                    func.implementation = function([atomic, compareValue, value, originalValue]) {
+                        if (!atomic.loadValue())
+                            throw new WTrapError("[Atomics]", "Null atomic pointer");
+                        let a = atomic.loadValue().loadValue();
+                        let b = compareValue.loadValue();
+                        let c = value.loadValue();
+                        if (a == b)
+                            atomic.loadValue().copyFrom(EPtr.box(c), 1);
+                        if (originalValue.loadValue())
+                            originalValue.loadValue().copyFrom(EPtr.box(a), 1);
+                    }
+                });
+        }
+
         function checkUndefined(origin, explanation, value)
         {
             if (value == undefined)
index cf3093b..ad5a588 100644 (file)
@@ -1888,15 +1888,19 @@ let standardLibrary = (function() {
             }
         }
         print();
-        /*
+
         for (let type of [`uint`, `int`]) {
             for (let functionName of [`Add`, `And`, `Exchange`, `Max`, `Min`, `Or`, `Xor`]) {
                 print(`native void Interlocked${functionName}(thread atomic_${type}*, ${type}, thread ${type}*);`);
+                print(`native void Interlocked${functionName}(threadgroup atomic_${type}*, ${type}, thread ${type}*);`);
+                print(`native void Interlocked${functionName}(device atomic_${type}*, ${type}, thread ${type}*);`);
             }
             print(`native void InterlockedCompareExchange(thread atomic_${type}*, ${type}, ${type}, thread ${type}*);`);
+            print(`native void InterlockedCompareExchange(threadgroup atomic_${type}*, ${type}, ${type}, thread ${type}*);`);
+            print(`native void InterlockedCompareExchange(device atomic_${type}*, ${type}, ${type}, thread ${type}*);`);
         }
         print();
-        */
+
         for (let type of [`uchar`, `ushort`, `uint`, `char`, `short`, `int`, `half`, `float`]) {
             for (let length of [``, `2`, `3`, `4`]) {
                 print(`native ${type}${length} Sample(Texture1D<${type}${length}>, sampler, float location);`);
index bf9f55e..f2527e8 100644 (file)
@@ -5803,6 +5803,708 @@ tests.casts = function()
     checkInt(program, callFunction(program, "baz", [makeInt(program, 6)]), 14);
 }
 
+tests.atomics = function()
+{
+    let program = doPrep(`
+        test int foo(int z) {
+            atomic_int x;
+            int result;
+            InterlockedAdd(&x, z, &result);
+            return result;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 6)]), 0);
+    program = doPrep(`
+        test int foo(int z) {
+            atomic_int x;
+            int result;
+            InterlockedAdd(&x, z, &result);
+            InterlockedAdd(&x, z, &result);
+            return result;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 6)]), 6);
+    program = doPrep(`
+        test int foo(int z) {
+            atomic_int x;
+            int result;
+            InterlockedAdd(&x, z, &result);
+            return int(x);
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 6)]), 6);
+    program = doPrep(`
+        test int foo(int z) {
+            atomic_int x;
+            int result;
+            InterlockedAdd(&x, z, &result);
+            InterlockedAdd(&x, z, &result);
+            return int(x);
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 6)]), 12);
+    program = doPrep(`
+        test uint foo(uint z) {
+            atomic_uint x;
+            uint result;
+            InterlockedAdd(&x, z, &result);
+            return result;
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 6)]), 0);
+    program = doPrep(`
+        test uint foo(uint z) {
+            atomic_uint x;
+            uint result;
+            InterlockedAdd(&x, z, &result);
+            InterlockedAdd(&x, z, &result);
+            return result;
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 6)]), 6);
+    program = doPrep(`
+        test uint foo(uint z) {
+            atomic_uint x;
+            uint result;
+            InterlockedAdd(&x, z, &result);
+            return uint(x);
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 6)]), 6);
+    program = doPrep(`
+        test uint foo(uint z) {
+            atomic_uint x;
+            uint result;
+            InterlockedAdd(&x, z, &result);
+            InterlockedAdd(&x, z, &result);
+            return uint(x);
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 6)]), 12);
+    program = doPrep(`
+        test uint foo(uint x, uint y) {
+            atomic_uint z;
+            uint result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedAnd(&z, y, &result);
+            return uint(z);
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 5)]), 1);
+    program = doPrep(`
+        test uint foo(uint x, uint y) {
+            atomic_uint z;
+            uint result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedAnd(&z, y, &result);
+            return result;
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 5)]), 3);
+    program = doPrep(`
+        test int foo(int x, int y) {
+            atomic_int z;
+            int result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedAnd(&z, y, &result);
+            return int(z);
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 5)]), 1);
+    program = doPrep(`
+        test int foo(int x, int y) {
+            atomic_int z;
+            int result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedAnd(&z, y, &result);
+            return result;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 5)]), 3);
+    program = doPrep(`
+        test uint foo(uint x, uint y) {
+            atomic_uint z;
+            uint result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedExchange(&z, y, &result);
+            return uint(z);
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 5)]), 5);
+    program = doPrep(`
+        test uint foo(uint x, uint y) {
+            atomic_uint z;
+            uint result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedExchange(&z, y, &result);
+            return result;
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 5)]), 3);
+    program = doPrep(`
+        test int foo(int x, int y) {
+            atomic_int z;
+            int result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedExchange(&z, y, &result);
+            return int(z);
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 5)]), 5);
+    program = doPrep(`
+        test int foo(int x, int y) {
+            atomic_int z;
+            int result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedExchange(&z, y, &result);
+            return result;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 5)]), 3);
+    program = doPrep(`
+        test uint foo(uint x, uint y) {
+            atomic_uint z;
+            uint result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedMax(&z, y, &result);
+            return uint(z);
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 5)]), 5);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 5), makeUint(program, 3)]), 5);
+    program = doPrep(`
+        test uint foo(uint x, uint y) {
+            atomic_uint z;
+            uint result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedMax(&z, y, &result);
+            return result;
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 5)]), 3);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 5), makeUint(program, 3)]), 5);
+    program = doPrep(`
+        test int foo(int x, int y) {
+            atomic_int z;
+            int result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedMax(&z, y, &result);
+            return int(z);
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 5)]), 5);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 5), makeInt(program, 3)]), 5);
+    program = doPrep(`
+        test int foo(int x, int y) {
+            atomic_int z;
+            int result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedMax(&z, y, &result);
+            return result;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 5)]), 3);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 5), makeInt(program, 3)]), 5);
+    program = doPrep(`
+        test uint foo(uint x, uint y) {
+            atomic_uint z;
+            uint result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedMin(&z, y, &result);
+            return uint(z);
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 5)]), 3);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 5), makeUint(program, 3)]), 3);
+    program = doPrep(`
+        test uint foo(uint x, uint y) {
+            atomic_uint z;
+            uint result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedMin(&z, y, &result);
+            return result;
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 5)]), 3);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 5), makeUint(program, 3)]), 5);
+    program = doPrep(`
+        test int foo(int x, int y) {
+            atomic_int z;
+            int result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedMin(&z, y, &result);
+            return int(z);
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 5)]), 3);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 5), makeInt(program, 3)]), 3);
+    program = doPrep(`
+        test int foo(int x, int y) {
+            atomic_int z;
+            int result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedMin(&z, y, &result);
+            return result;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 5)]), 3);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 5), makeInt(program, 3)]), 5);
+    program = doPrep(`
+        test uint foo(uint x, uint y) {
+            atomic_uint z;
+            uint result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedOr(&z, y, &result);
+            return uint(z);
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 5)]), 7);
+    program = doPrep(`
+        test uint foo(uint x, uint y) {
+            atomic_uint z;
+            uint result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedOr(&z, y, &result);
+            return result;
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 5)]), 3);
+    program = doPrep(`
+        test int foo(int x, int y) {
+            atomic_int z;
+            int result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedOr(&z, y, &result);
+            return int(z);
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 5)]), 7);
+    program = doPrep(`
+        test int foo(int x, int y) {
+            atomic_int z;
+            int result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedOr(&z, y, &result);
+            return result;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 5)]), 3);
+    program = doPrep(`
+        test uint foo(uint x, uint y) {
+            atomic_uint z;
+            uint result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedXor(&z, y, &result);
+            return uint(z);
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 5)]), 6);
+    program = doPrep(`
+        test uint foo(uint x, uint y) {
+            atomic_uint z;
+            uint result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedXor(&z, y, &result);
+            return result;
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 5)]), 3);
+    program = doPrep(`
+        test int foo(int x, int y) {
+            atomic_int z;
+            int result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedXor(&z, y, &result);
+            return int(z);
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 5)]), 6);
+    program = doPrep(`
+        test int foo(int x, int y) {
+            atomic_int z;
+            int result;
+            InterlockedAdd(&z, x, &result);
+            InterlockedXor(&z, y, &result);
+            return result;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 5)]), 3);
+    program = doPrep(`
+        test uint foo(uint x, uint y, uint z) {
+            atomic_uint w;
+            uint result;
+            InterlockedAdd(&w, x, &result);
+            InterlockedCompareExchange(&w, y, z, &result);
+            return uint(w);
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 3), makeUint(program, 5)]), 5);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 4), makeUint(program, 5)]), 3);
+    program = doPrep(`
+        test uint foo(uint x, uint y, uint z) {
+            atomic_uint w;
+            uint result;
+            InterlockedAdd(&w, x, &result);
+            InterlockedCompareExchange(&w, y, z, &result);
+            return result;
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 3), makeUint(program, 5)]), 3);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 4), makeUint(program, 5)]), 3);
+    program = doPrep(`
+        test int foo(int x, int y, int z) {
+            atomic_int w;
+            int result;
+            InterlockedAdd(&w, x, &result);
+            InterlockedCompareExchange(&w, y, z, &result);
+            return int(w);
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 3), makeInt(program, 5)]), 5);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 4), makeInt(program, 5)]), 3);
+    program = doPrep(`
+        test int foo(int x, int y, int z) {
+            atomic_int w;
+            int result;
+            InterlockedAdd(&w, x, &result);
+            InterlockedCompareExchange(&w, y, z, &result);
+            return result;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 3), makeInt(program, 5)]), 3);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 4), makeInt(program, 5)]), 3);
+}
+
+tests.atomicsNull = function()
+{
+    let program = doPrep(`
+        test int foo(int z) {
+            atomic_int x;
+            InterlockedAdd(&x, z, null);
+            return int(x);
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 6)]), 6);
+    program = doPrep(`
+        test int foo(int z) {
+            atomic_int x;
+            InterlockedAdd(&x, z, null);
+            InterlockedAdd(&x, z, null);
+            return int(x);
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 6)]), 12);
+    program = doPrep(`
+        test uint foo(uint z) {
+            atomic_uint x;
+            InterlockedAdd(&x, z, null);
+            return uint(x);
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 6)]), 6);
+    program = doPrep(`
+        test uint foo(uint z) {
+            atomic_uint x;
+            InterlockedAdd(&x, z, null);
+            InterlockedAdd(&x, z, null);
+            return uint(x);
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 6)]), 12);
+    program = doPrep(`
+        test uint foo(uint x, uint y) {
+            atomic_uint z;
+            InterlockedAdd(&z, x, null);
+            InterlockedAnd(&z, y, null);
+            return uint(z);
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 5)]), 1);
+    program = doPrep(`
+        test int foo(int x, int y) {
+            atomic_int z;
+            InterlockedAdd(&z, x, null);
+            InterlockedAnd(&z, y, null);
+            return int(z);
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 5)]), 1);
+    program = doPrep(`
+        test uint foo(uint x, uint y) {
+            atomic_uint z;
+            InterlockedAdd(&z, x, null);
+            InterlockedExchange(&z, y, null);
+            return uint(z);
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 5)]), 5);
+    program = doPrep(`
+        test int foo(int x, int y) {
+            atomic_int z;
+            InterlockedAdd(&z, x, null);
+            InterlockedExchange(&z, y, null);
+            return int(z);
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 5)]), 5);
+    program = doPrep(`
+        test uint foo(uint x, uint y) {
+            atomic_uint z;
+            InterlockedAdd(&z, x, null);
+            InterlockedMax(&z, y, null);
+            return uint(z);
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 5)]), 5);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 5), makeUint(program, 3)]), 5);
+    program = doPrep(`
+        test int foo(int x, int y) {
+            atomic_int z;
+            InterlockedAdd(&z, x, null);
+            InterlockedMax(&z, y, null);
+            return int(z);
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 5)]), 5);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 5), makeInt(program, 3)]), 5);
+    program = doPrep(`
+        test uint foo(uint x, uint y) {
+            atomic_uint z;
+            InterlockedAdd(&z, x, null);
+            InterlockedMin(&z, y, null);
+            return uint(z);
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 5)]), 3);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 5), makeUint(program, 3)]), 3);
+    program = doPrep(`
+        test int foo(int x, int y) {
+            atomic_int z;
+            InterlockedAdd(&z, x, null);
+            InterlockedMin(&z, y, null);
+            return int(z);
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 5)]), 3);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 5), makeInt(program, 3)]), 3);
+    program = doPrep(`
+        test uint foo(uint x, uint y) {
+            atomic_uint z;
+            InterlockedAdd(&z, x, null);
+            InterlockedOr(&z, y, null);
+            return uint(z);
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 5)]), 7);
+    program = doPrep(`
+        test int foo(int x, int y) {
+            atomic_int z;
+            InterlockedAdd(&z, x, null);
+            InterlockedOr(&z, y, null);
+            return int(z);
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 5)]), 7);
+    program = doPrep(`
+        test uint foo(uint x, uint y) {
+            atomic_uint z;
+            InterlockedAdd(&z, x, null);
+            InterlockedXor(&z, y, null);
+            return uint(z);
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 5)]), 6);
+    program = doPrep(`
+        test int foo(int x, int y) {
+            atomic_int z;
+            InterlockedAdd(&z, x, null);
+            InterlockedXor(&z, y, null);
+            return int(z);
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 5)]), 6);
+    program = doPrep(`
+        test uint foo(uint x, uint y, uint z) {
+            atomic_uint w;
+            InterlockedAdd(&w, x, null);
+            InterlockedCompareExchange(&w, y, z, null);
+            return uint(w);
+        }
+    `);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 3), makeUint(program, 5)]), 5);
+    checkUint(program, callFunction(program, "foo", [makeUint(program, 3), makeUint(program, 4), makeUint(program, 5)]), 3);
+    program = doPrep(`
+        test int foo(int x, int y, int z) {
+            atomic_int w;
+            InterlockedAdd(&w, x, null);
+            InterlockedCompareExchange(&w, y, z, null);
+            return int(w);
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 3), makeInt(program, 5)]), 5);
+    checkInt(program, callFunction(program, "foo", [makeInt(program, 3), makeInt(program, 4), makeInt(program, 5)]), 3);
+
+    program = doPrep(`
+        test void foo() {
+            thread atomic_int* x = null;
+            InterlockedAdd(x, 1, null);
+        }
+    `);
+    checkFail(
+        () => callFunction(program, "foo", []),
+        (e) => e instanceof WTrapError);
+
+    program = doPrep(`
+        test void foo() {
+            thread atomic_uint* x = null;
+            InterlockedAdd(x, 1, null);
+        }
+    `);
+    checkFail(
+        () => callFunction(program, "foo", []),
+        (e) => e instanceof WTrapError);
+
+    program = doPrep(`
+        test void foo() {
+            thread atomic_int* x = null;
+            InterlockedAnd(x, 1, null);
+        }
+    `);
+    checkFail(
+        () => callFunction(program, "foo", []),
+        (e) => e instanceof WTrapError);
+
+    program = doPrep(`
+        test void foo() {
+            thread atomic_uint* x = null;
+            InterlockedAnd(x, 1, null);
+        }
+    `);
+    checkFail(
+        () => callFunction(program, "foo", []),
+        (e) => e instanceof WTrapError);
+
+    program = doPrep(`
+        test void foo() {
+            thread atomic_int* x = null;
+            InterlockedExchange(x, 1, null);
+        }
+    `);
+    checkFail(
+        () => callFunction(program, "foo", []),
+        (e) => e instanceof WTrapError);
+
+    program = doPrep(`
+        test void foo() {
+            thread atomic_uint* x = null;
+            InterlockedExchange(x, 1, null);
+        }
+    `);
+    checkFail(
+        () => callFunction(program, "foo", []),
+        (e) => e instanceof WTrapError);
+
+    program = doPrep(`
+        test void foo() {
+            thread atomic_int* x = null;
+            InterlockedMax(x, 1, null);
+        }
+    `);
+    checkFail(
+        () => callFunction(program, "foo", []),
+        (e) => e instanceof WTrapError);
+
+    program = doPrep(`
+        test void foo() {
+            thread atomic_uint* x = null;
+            InterlockedMax(x, 1, null);
+        }
+    `);
+    checkFail(
+        () => callFunction(program, "foo", []),
+        (e) => e instanceof WTrapError);
+
+    program = doPrep(`
+        test void foo() {
+            thread atomic_int* x = null;
+            InterlockedMin(x, 1, null);
+        }
+    `);
+    checkFail(
+        () => callFunction(program, "foo", []),
+        (e) => e instanceof WTrapError);
+
+    program = doPrep(`
+        test void foo() {
+            thread atomic_uint* x = null;
+            InterlockedMin(x, 1, null);
+        }
+    `);
+    checkFail(
+        () => callFunction(program, "foo", []),
+        (e) => e instanceof WTrapError);
+
+    program = doPrep(`
+        test void foo() {
+            thread atomic_int* x = null;
+            InterlockedOr(x, 1, null);
+        }
+    `);
+    checkFail(
+        () => callFunction(program, "foo", []),
+        (e) => e instanceof WTrapError);
+
+    program = doPrep(`
+        test void foo() {
+            thread atomic_uint* x = null;
+            InterlockedOr(x, 1, null);
+        }
+    `);
+    checkFail(
+        () => callFunction(program, "foo", []),
+        (e) => e instanceof WTrapError);
+
+    program = doPrep(`
+        test void foo() {
+            thread atomic_int* x = null;
+            InterlockedXor(x, 1, null);
+        }
+    `);
+    checkFail(
+        () => callFunction(program, "foo", []),
+        (e) => e instanceof WTrapError);
+
+    program = doPrep(`
+        test void foo() {
+            thread atomic_uint* x = null;
+            InterlockedXor(x, 1, null);
+        }
+    `);
+    checkFail(
+        () => callFunction(program, "foo", []),
+        (e) => e instanceof WTrapError);
+
+    program = doPrep(`
+        test void foo() {
+            thread atomic_int* x = null;
+            InterlockedCompareExchange(x, 1, 2, null);
+        }
+    `);
+    checkFail(
+        () => callFunction(program, "foo", []),
+        (e) => e instanceof WTrapError);
+
+    program = doPrep(`
+        test void foo() {
+            thread atomic_uint* x = null;
+            InterlockedCompareExchange(x, 1, 2, null);
+        }
+    `);
+    checkFail(
+        () => callFunction(program, "foo", []),
+        (e) => e instanceof WTrapError);
+}
+
 tests.pointerToMember = function()
 {
     checkFail(