Speed up Heap::isMarkedConcurrently
authorjfbastien@apple.com <jfbastien@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 27 Sep 2016 23:41:34 +0000 (23:41 +0000)
committerjfbastien@apple.com <jfbastien@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 27 Sep 2016 23:41:34 +0000 (23:41 +0000)
https://bugs.webkit.org/show_bug.cgi?id=162095

Reviewed by Filip Pizlo.

Source/JavaScriptCore:

Speed up isMarkedConcurrently by using WTF::consumeLoad.

* heap/MarkedBlock.h:
(JSC::MarkedBlock::areMarksStale):
(JSC::MarkedBlock::areMarksStaleWithDependency):
(JSC::MarkedBlock::isMarkedConcurrently): do away with the load-load fence

Source/WTF:

Heap::isMarkedConcurrently had a load-load fence which is expensive on weak memory ISAs such as ARM.

This patch is fairly perf-neutral overall, but the GC's instrumentation reports:
  GC:Eden is 93% average runtime after change
  GC:Full is 76% average runtime after change

The fence was there because:
 1. If the read of m_markingVersion in MarkedBlock::areMarksStale isn't what's expected then;
 2. The read of m_marks in MarkedBlock::isMarked needs to observe the value that was stored *before* m_markingVersion was stored.

This ordering isn't guaranteed on ARM, which has a weak memory model.

There are 3 ways to guarantee this ordering:
 A. Use a barrier instruction.
 B. Use a load-acquire (new in ARMv8).
 C. use ARM's address dependency rule, which C++ calls memory_order_consume.

In general:
 A. is slow but orders all of memory in an intuitive manner.
 B. is faster-ish and has the same property-ish.
 C. should be faster still, but *only orders dependent loads*. This last part is critical! Consume isn't an all-out replacement for acquire (acquire is rather a superset of consume).

ARM explains the address dependency rule in their document "barrier litmus tests and cookbook":

> *Resolving by the use of barriers and address dependency*
>
> There is a rule within the ARM architecture that:
> Where the value returned by a read is used to compute the virtual address of a subsequent read or write (this is known as an address dependency), then these two memory accesses will be observed in program order. An address dependency exists even if the value read by the first read has no effect in changing the virtual address (as might be the case if the value returned is masked off before it is used, or if it had no effect on changing a predicted address value).
> This restriction applies only when the data value returned from one read is used as a data value to calculate the address of a subsequent read or write. This does not apply if the data value returned from one read is used to determine the condition code flags, and the values of the flags are used for condition code evaluation to determine the address of a subsequent reads, either through conditional execution or the evaluation of a branch. This is known as a control dependency.
> Where both a control and address dependency exist, the ordering behaviour is consistent with the address dependency.

C++'s memory_order_consume is unfortunately unimplemented by C++ compilers, and maybe unimplementable as spec'd. I'm working with interested folks in the committee to fix this situation: http://wg21.link/p0190r2

* wtf/Atomics.h:
(WTF::zeroWithConsumeDependency): a 0 which carries a dependency
(WTF::consumeLoad): pixie magic

Tools:

* TestWebKitAPI/CMakeLists.txt:
* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WTF/Consume.cpp: Added.
(testConsume):
(TestWebKitAPI::TEST):

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

Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/heap/MarkedBlock.h
Source/WTF/ChangeLog
Source/WTF/wtf/Atomics.h
Tools/ChangeLog
Tools/TestWebKitAPI/CMakeLists.txt
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WTF/Consume.cpp [new file with mode: 0644]

index dccf0c6..d0dcf05 100644 (file)
@@ -1,3 +1,17 @@
+2016-09-27  JF Bastien  <jfbastien@apple.com>
+
+        Speed up Heap::isMarkedConcurrently
+        https://bugs.webkit.org/show_bug.cgi?id=162095
+
+        Reviewed by Filip Pizlo.
+
+        Speed up isMarkedConcurrently by using WTF::consumeLoad.
+
+        * heap/MarkedBlock.h:
+        (JSC::MarkedBlock::areMarksStale):
+        (JSC::MarkedBlock::areMarksStaleWithDependency):
+        (JSC::MarkedBlock::isMarkedConcurrently): do away with the load-load fence
+
 2016-09-27  Mark Lam  <mark.lam@apple.com>
 
         Add some needed CatchScopes in code that should not throw.
index dd9e844..a6469f1 100644 (file)
@@ -269,9 +269,14 @@ public:
     void noteMarked();
         
     WeakSet& weakSet();
-    
-    bool areMarksStale(HeapVersion markingVersion);
+
     bool areMarksStale();
+    bool areMarksStale(HeapVersion markingVersion);
+    struct MarksWithDependency {
+        bool areStale;
+        ConsumeDependency dependency;
+    };
+    MarksWithDependency areMarksStaleWithDependency(HeapVersion markingVersion);
     
     void aboutToMark(HeapVersion markingVersion);
         
@@ -474,6 +479,15 @@ inline bool MarkedBlock::areMarksStale(HeapVersion markingVersion)
     return markingVersion != m_markingVersion;
 }
 
+ALWAYS_INLINE MarkedBlock::MarksWithDependency MarkedBlock::areMarksStaleWithDependency(HeapVersion markingVersion)
+{
+    auto consumed = consumeLoad(&m_markingVersion);
+    MarksWithDependency ret;
+    ret.areStale = consumed.value != markingVersion;
+    ret.dependency = consumed.dependency;
+    return ret;
+}
+
 inline void MarkedBlock::aboutToMark(HeapVersion markingVersion)
 {
     if (UNLIKELY(areMarksStale(markingVersion)))
@@ -499,10 +513,10 @@ inline bool MarkedBlock::isMarked(HeapVersion markingVersion, const void* p)
 
 inline bool MarkedBlock::isMarkedConcurrently(HeapVersion markingVersion, const void* p)
 {
-    if (areMarksStale(markingVersion))
+    auto marksWithDependency = areMarksStaleWithDependency(markingVersion);
+    if (marksWithDependency.areStale)
         return false;
-    WTF::loadLoadFence();
-    return m_marks.get(atomNumber(p));
+    return m_marks.get(atomNumber(p) + marksWithDependency.dependency);
 }
 
 inline bool MarkedBlock::testAndSetMarked(const void* p)
index dca468f..8d6e894 100644 (file)
@@ -1,5 +1,49 @@
 2016-09-27  JF Bastien  <jfbastien@apple.com>
 
+        Speed up Heap::isMarkedConcurrently
+        https://bugs.webkit.org/show_bug.cgi?id=162095
+
+        Reviewed by Filip Pizlo.
+
+        Heap::isMarkedConcurrently had a load-load fence which is expensive on weak memory ISAs such as ARM.
+
+        This patch is fairly perf-neutral overall, but the GC's instrumentation reports:
+          GC:Eden is 93% average runtime after change
+          GC:Full is 76% average runtime after change
+
+        The fence was there because:
+         1. If the read of m_markingVersion in MarkedBlock::areMarksStale isn't what's expected then;
+         2. The read of m_marks in MarkedBlock::isMarked needs to observe the value that was stored *before* m_markingVersion was stored.
+
+        This ordering isn't guaranteed on ARM, which has a weak memory model.
+
+        There are 3 ways to guarantee this ordering:
+         A. Use a barrier instruction.
+         B. Use a load-acquire (new in ARMv8).
+         C. use ARM's address dependency rule, which C++ calls memory_order_consume.
+
+        In general:
+         A. is slow but orders all of memory in an intuitive manner.
+         B. is faster-ish and has the same property-ish.
+         C. should be faster still, but *only orders dependent loads*. This last part is critical! Consume isn't an all-out replacement for acquire (acquire is rather a superset of consume).
+
+        ARM explains the address dependency rule in their document "barrier litmus tests and cookbook":
+
+        > *Resolving by the use of barriers and address dependency*
+        >
+        > There is a rule within the ARM architecture that:
+        > Where the value returned by a read is used to compute the virtual address of a subsequent read or write (this is known as an address dependency), then these two memory accesses will be observed in program order. An address dependency exists even if the value read by the first read has no effect in changing the virtual address (as might be the case if the value returned is masked off before it is used, or if it had no effect on changing a predicted address value).
+        > This restriction applies only when the data value returned from one read is used as a data value to calculate the address of a subsequent read or write. This does not apply if the data value returned from one read is used to determine the condition code flags, and the values of the flags are used for condition code evaluation to determine the address of a subsequent reads, either through conditional execution or the evaluation of a branch. This is known as a control dependency.
+        > Where both a control and address dependency exist, the ordering behaviour is consistent with the address dependency.
+
+        C++'s memory_order_consume is unfortunately unimplemented by C++ compilers, and maybe unimplementable as spec'd. I'm working with interested folks in the committee to fix this situation: http://wg21.link/p0190r2
+
+        * wtf/Atomics.h:
+        (WTF::zeroWithConsumeDependency): a 0 which carries a dependency
+        (WTF::consumeLoad): pixie magic
+
+2016-09-27  JF Bastien  <jfbastien@apple.com>
+
         Atomics.h on Windows: remove seq_cst hack
         https://bugs.webkit.org/show_bug.cgi?id=162022
 
index 73fe937..a434bc2 100644 (file)
@@ -168,8 +168,121 @@ inline void memoryBarrierBeforeUnlock() { std::atomic_thread_fence(std::memory_o
 
 #endif
 
+typedef size_t ConsumeDependency;
+
+template <typename T, typename std::enable_if<sizeof(T) == 8>::type* = nullptr>
+ALWAYS_INLINE ConsumeDependency zeroWithConsumeDependency(T value)
+{
+    uint64_t dependency;
+    uint64_t copy = bitwise_cast<uint64_t>(value);
+#if CPU(ARM64)
+    // Create a magical zero value through inline assembly, whose computation
+    // isn't visible to the optimizer. This zero is then usable as an offset in
+    // further address computations: adding zero does nothing, but the compiler
+    // doesn't know it. It's magical because it creates an address dependency
+    // from the load of `location` to the uses of the dependency, which triggers
+    // the ARM ISA's address dependency rule, a.k.a. the mythical C++ consume
+    // ordering. This forces weak memory order CPUs to observe `location` and
+    // dependent loads in their store order without the reader using a barrier
+    // or an acquire load.
+    asm volatile("eor %x[dependency], %x[in], %x[in]"
+                 : [dependency] "=r"(dependency)
+                 : [in] "r"(copy)
+                 // Lie about touching memory. Not strictly needed, but is
+                 // likely to avoid unwanted load/store motion.
+                 : "memory");
+#elif CPU(ARM)
+    asm volatile("eor %[dependency], %[in], %[in]"
+                 : [dependency] "=r"(dependency)
+                 : [in] "r"(copy)
+                 : "memory");
+#else
+    // No dependency is needed for this architecture.
+    loadLoadFence();
+    dependency = 0;
+    (void)copy;
+#endif
+    return static_cast<ConsumeDependency>(dependency);
+}
+
+template <typename T, typename std::enable_if<sizeof(T) == 4>::type* = nullptr>
+ALWAYS_INLINE ConsumeDependency zeroWithConsumeDependency(T value)
+{
+    uint32_t dependency;
+    uint32_t copy = bitwise_cast<uint32_t>(value);
+#if CPU(ARM64)
+    asm volatile("eor %w[dependency], %w[in], %w[in]"
+                 : [dependency] "=r"(dependency)
+                 : [in] "r"(copy)
+                 : "memory");
+#elif CPU(ARM)
+    asm volatile("eor %[dependency], %[in], %[in]"
+                 : [dependency] "=r"(dependency)
+                 : [in] "r"(copy)
+                 : "memory");
+#else
+    loadLoadFence();
+    dependency = 0;
+    (void)copy;
+#endif
+    return static_cast<ConsumeDependency>(dependency);
+}
+
+template <typename T, typename std::enable_if<sizeof(T) == 2>::type* = nullptr>
+ALWAYS_INLINE ConsumeDependency zeroWithConsumeDependency(T value)
+{
+    uint16_t copy = bitwise_cast<uint16_t>(value);
+    return zeroWithConsumeDependency(static_cast<size_t>(copy));
+}
+
+template <typename T, typename std::enable_if<sizeof(T) == 1>::type* = nullptr>
+ALWAYS_INLINE ConsumeDependency zeroWithConsumeDependency(T value)
+{
+    uint8_t copy = bitwise_cast<uint8_t>(value);
+    return zeroWithConsumeDependency(static_cast<size_t>(copy));
+}
+
+template <typename T>
+struct Consumed {
+    T value;
+    ConsumeDependency dependency;
+};
+
+// Consume load, returning the loaded `value` at `location` and a dependent-zero
+// which creates an address dependency from the `location`.
+//
+// Usage notes:
+//
+//  * Regarding control dependencies: merely branching based on `value` or
+//    `dependency` isn't sufficient to impose a dependency ordering: you must
+//    use `dependency` in the address computation of subsequent loads which
+//    should observe the store order w.r.t. `location`.
+// * Regarding memory ordering: consume load orders the `location` load with
+//   susequent dependent loads *only*. It says nothing about ordering of other
+//   loads!
+//
+// Caveat emptor.
+template <typename T>
+ALWAYS_INLINE auto consumeLoad(const T* location)
+{
+    typedef typename std::remove_cv<T>::type Returned;
+    Consumed<Returned> ret { };
+    // Force the read of `location` to occur exactly once and without fusing or
+    // forwarding using volatile. This is important because the compiler could
+    // otherwise rematerialize or find equivalent loads, or simply forward from
+    // a previous one, and lose the dependency we're trying so hard to
+    // create. Prevent tearing by using an atomic, but let it move around by
+    // using relaxed. We have at least a memory fence after this which prevents
+    // the load from moving too much.
+    ret.value = reinterpret_cast<const volatile std::atomic<Returned>*>(location)->load(std::memory_order_relaxed);
+    ret.dependency = zeroWithConsumeDependency(ret.value);
+    return ret;
+}
+
 } // namespace WTF
 
 using WTF::Atomic;
+using WTF::ConsumeDependency;
+using WTF::consumeLoad;
 
 #endif // Atomics_h
index 5f8cb80..61a4dfd 100644 (file)
@@ -1,3 +1,16 @@
+2016-09-27  JF Bastien  <jfbastien@apple.com>
+
+        Speed up Heap::isMarkedConcurrently
+        https://bugs.webkit.org/show_bug.cgi?id=162095
+
+        Reviewed by Filip Pizlo.
+
+        * TestWebKitAPI/CMakeLists.txt:
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WTF/Consume.cpp: Added.
+        (testConsume):
+        (TestWebKitAPI::TEST):
+
 2016-09-26  Alex Christensen  <achristensen@webkit.org>
 
         Implement URLParser::syntaxViolation
index 2da4c8d..fa38316 100644 (file)
@@ -42,6 +42,7 @@ set(TestWTF_SOURCES
     ${TESTWEBKITAPI_DIR}/TestsController.cpp
     ${TESTWEBKITAPI_DIR}/Tests/WTF/AtomicString.cpp
     ${TESTWEBKITAPI_DIR}/Tests/WTF/BloomFilter.cpp
+    ${TESTWEBKITAPI_DIR}/Tests/WTF/Consume.cpp
     ${TESTWEBKITAPI_DIR}/Tests/WTF/CrossThreadTask.cpp
     ${TESTWEBKITAPI_DIR}/Tests/WTF/CString.cpp
     ${TESTWEBKITAPI_DIR}/Tests/WTF/CheckedArithmeticOperations.cpp
index 0f1aa05..d170671 100644 (file)
                A1DF74321C41B65800A2F4D0 /* AlwaysRevalidatedURLSchemes.mm in Sources */ = {isa = PBXBuildFile; fileRef = A1DF74301C41B65800A2F4D0 /* AlwaysRevalidatedURLSchemes.mm */; };
                A57A34F216AF6B2B00C2501F /* PageVisibilityStateWithWindowChanges.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = A57A34F116AF69E200C2501F /* PageVisibilityStateWithWindowChanges.html */; };
                A5E2027515B21F6E00C13E14 /* WindowlessWebViewWithMedia.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = A5E2027015B2180600C13E14 /* WindowlessWebViewWithMedia.html */; };
+               ADCEBBA61D9AE229002E283A /* Consume.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ADCEBBA51D99D4CF002E283A /* Consume.cpp */; };
                B55AD1D5179F3B3000AC1494 /* PreventImageLoadWithAutoResizing_Bundle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B55AD1D3179F3ABF00AC1494 /* PreventImageLoadWithAutoResizing_Bundle.cpp */; };
                B55F11B71517D03300915916 /* attributedStringCustomFont.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = B55F11B01517A2C400915916 /* attributedStringCustomFont.html */; };
                B55F11BE15191A0600915916 /* Ahem.ttf in Copy Resources */ = {isa = PBXBuildFile; fileRef = B55F11B9151916E600915916 /* Ahem.ttf */; };
                A5E2027015B2180600C13E14 /* WindowlessWebViewWithMedia.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = WindowlessWebViewWithMedia.html; sourceTree = "<group>"; };
                A5E2027215B2181900C13E14 /* WindowlessWebViewWithMedia.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WindowlessWebViewWithMedia.mm; sourceTree = "<group>"; };
                A7A966DA140ECCC8005EF9B4 /* CheckedArithmeticOperations.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CheckedArithmeticOperations.cpp; sourceTree = "<group>"; };
+               ADCEBBA51D99D4CF002E283A /* Consume.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Consume.cpp; sourceTree = "<group>"; };
                B4039F9C15E6D8B3007255D6 /* MathExtras.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MathExtras.cpp; sourceTree = "<group>"; };
                B55AD1D1179F336600AC1494 /* PreventImageLoadWithAutoResizing.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = PreventImageLoadWithAutoResizing.mm; path = WebKit2ObjC/PreventImageLoadWithAutoResizing.mm; sourceTree = "<group>"; };
                B55AD1D3179F3ABF00AC1494 /* PreventImageLoadWithAutoResizing_Bundle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PreventImageLoadWithAutoResizing_Bundle.cpp; path = WebKit2ObjC/PreventImageLoadWithAutoResizing_Bundle.cpp; sourceTree = "<group>"; };
                                E40019301ACE9B5C001B0A2A /* BloomFilter.cpp */,
                                A7A966DA140ECCC8005EF9B4 /* CheckedArithmeticOperations.cpp */,
                                0FEAE3671B7D19CB00CE17F2 /* Condition.cpp */,
+                               ADCEBBA51D99D4CF002E283A /* Consume.cpp */,
                                51714EB91D087416004723C4 /* CrossThreadTask.cpp */,
                                26A2C72E15E2E73C005B1A14 /* CString.cpp */,
                                7AA021BA1AB09EA70052953F /* DateMath.cpp */,
                                7C83DEF61D0A590C00FEBCF3 /* MetaAllocator.cpp in Sources */,
                                7C83DEFE1D0A590C00FEBCF3 /* NakedPtr.cpp in Sources */,
                                7C83DF011D0A590C00FEBCF3 /* Optional.cpp in Sources */,
+                               ADCEBBA61D9AE229002E283A /* Consume.cpp in Sources */,
                                7C83DF021D0A590C00FEBCF3 /* OSObjectPtr.cpp in Sources */,
                                7C83DF591D0A590C00FEBCF3 /* ParkingLot.cpp in Sources */,
                                7C83DF131D0A590C00FEBCF3 /* RedBlackTree.cpp in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/WTF/Consume.cpp b/Tools/TestWebKitAPI/Tests/WTF/Consume.cpp
new file mode 100644 (file)
index 0000000..e1a5d7a
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#include <thread>
+#include <wtf/Atomics.h>
+
+template <typename T>
+NEVER_INLINE auto testConsume(const T* location)
+{
+    WTF::compilerFence(); // Paranoid testing.
+    auto ret = WTF::consumeLoad(location);
+    WTF::compilerFence(); // Paranoid testing.
+    return ret;
+}
+
+namespace TestWebKitAPI {
+
+TEST(WTF, Consumei8)
+{
+    uint8_t i8 = 42;
+    auto i8_consumed = testConsume(&i8);
+    ASSERT_EQ(i8_consumed.value, 42u);
+    ASSERT_EQ(i8_consumed.dependency, 0u);
+}
+
+TEST(WTF, Consumei16)
+{
+    uint16_t i16 = 42;
+    auto i16_consumed = testConsume(&i16);
+    ASSERT_EQ(i16_consumed.value, 42u);
+    ASSERT_EQ(i16_consumed.dependency, 0u);
+}
+
+TEST(WTF, Consumei32)
+{
+    uint32_t i32 = 42;
+    auto i32_consumed = testConsume(&i32);
+    ASSERT_EQ(i32_consumed.value, 42u);
+    ASSERT_EQ(i32_consumed.dependency, 0u);
+}
+
+TEST(WTF, Consumei64)
+{
+    uint64_t i64 = 42;
+    auto i64_consumed = testConsume(&i64);
+    ASSERT_EQ(i64_consumed.value, 42u);
+    ASSERT_EQ(i64_consumed.dependency, 0u);
+}
+
+TEST(WTF, Consumef32)
+{
+    float f32 = 42.f;
+    auto f32_consumed = testConsume(&f32);
+    ASSERT_EQ(f32_consumed.value, 42.f);
+    ASSERT_EQ(f32_consumed.dependency, 0u);
+}
+
+TEST(WTF, Consumef64)
+{
+    double f64 = 42.;
+    auto f64_consumed = testConsume(&f64);
+    ASSERT_EQ(f64_consumed.value, 42.);
+    ASSERT_EQ(f64_consumed.dependency, 0u);
+}
+
+static int* global;
+
+TEST(WTF, ConsumeGlobalPtr)
+{
+    auto* ptr = &global;
+    auto ptr_consumed = testConsume(&ptr);
+    ASSERT_EQ(ptr_consumed.value, &global);
+    ASSERT_EQ(ptr_consumed.dependency, 0u);
+}
+
+static int* globalArray[128];
+
+TEST(WTF, ConsumeGlobalArrayPtr)
+{
+    auto* ptr = &globalArray[64];
+    auto ptr_consumed = testConsume(&ptr);
+    ASSERT_EQ(ptr_consumed.value, &globalArray[64]);
+    ASSERT_EQ(ptr_consumed.dependency, 0u);
+}
+
+TEST(WTF, ConsumeStackPtr)
+{
+    char* hello = nullptr;
+    auto* stack = &hello;
+    auto stack_consumed = testConsume(&stack);
+    ASSERT_EQ(stack_consumed.value, &hello);
+    ASSERT_EQ(stack_consumed.dependency, 0u);
+}
+
+TEST(WTF, ConsumeWithThread)
+{
+    bool ready = false;
+    constexpr size_t num = 1024;
+    uint32_t* vec = new uint32_t[num];
+    std::thread t([&]() {
+        for (size_t i = 0; i != num; ++i)
+            vec[i] = i * 2;
+        WTF::storeStoreFence();
+        ready = true;
+    });
+    do {
+        auto stack_consumed = testConsume(&ready);
+        if (stack_consumed.value) {
+            for (size_t i = 0; i != num; ++i)
+                ASSERT_EQ(vec[i + stack_consumed.dependency], i * 2);
+            break;
+        }
+    } while (true);
+    t.join();
+    delete[] vec;
+}
+}