Title: [184462] releases/WebKitGTK/webkit-2.4/Source
Revision
184462
Author
[email protected]
Date
2015-05-18 05:02:56 -0700 (Mon, 18 May 2015)

Log Message

Merge r181305 - 8-bit version of weakCompareAndSwap() can cause an infinite loop.
https://webkit.org/b/142513>

Reviewed by Filip Pizlo.

Source/_javascript_Core:

Added a test that exercises the 8-bit CAS from multiple threads.  The threads
will contend to set bits in a large array of bytes using the CAS function.

* API/tests/CompareAndSwapTest.cpp: Added.
(Bitmap::Bitmap):
(Bitmap::numBits):
(Bitmap::clearAll):
(Bitmap::concurrentTestAndSet):
(setBitThreadFunc):
(testCompareAndSwap):
* API/tests/testapi.c:
(main):
* _javascript_Core.vcxproj/testapi/testapi.vcxproj:
* _javascript_Core.vcxproj/testapi/testapi.vcxproj.filters:
* _javascript_Core.xcodeproj/project.pbxproj:

Source/WTF:

Presently, Bitmap::concurrentTestAndSet() uses the 8-bit version of
weakCompareAndSwap() (which compares and swaps an uint8_t value).
Bitmap::concurrentTestAndSet() has a loop that checks if a bit in the
byte of interest has been set.  If not, it will call the 8-bit CAS
function to set the bit.

Under the covers, for ARM, the 8-bit CAS function actually works with a
32-bit CAS.  The 8-bit CAS will first fetch the 32-bit value in memory
that should contain the 8-bit value, and check if it contains the
expected byte.  If the value in memory doesn't have the expected byte,
it will return early to its caller.  The expectation is that the caller
will reload the byte from memory and call the 8-bit CAS again.

Unfortunately, this code path that returns early does not have a
compiler fence.  Without a compiler fence, the C++ compiler can
optimize away the reloading of the expected byte value, leaving it
unchanged.  As a result, we'll have a infinite loop here that checks a
value that will never change, and the loop will not terminate until the
value changes.

The fix is to eliminate the early return check in the 8-bit CAS, and
have it always call down to the 32-bit CAS.  The 32-bit CAS has a
compiler fence which will prevent this issue.

* wtf/Atomics.h:
(WTF::weakCompareAndSwap):

Modified Paths

Added Paths

Diff

Added: releases/WebKitGTK/webkit-2.4/Source/_javascript_Core/API/tests/CompareAndSwapTest.cpp (0 => 184462)


--- releases/WebKitGTK/webkit-2.4/Source/_javascript_Core/API/tests/CompareAndSwapTest.cpp	                        (rev 0)
+++ releases/WebKitGTK/webkit-2.4/Source/_javascript_Core/API/tests/CompareAndSwapTest.cpp	2015-05-18 12:02:56 UTC (rev 184462)
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2015 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. AND ITS CONTRIBUTORS ``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 ITS 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 <stdio.h>
+#include <thread>
+#include <wtf/Atomics.h>
+
+// Regression test for webkit.org/b/142513
+extern "C" void testCompareAndSwap();
+
+class Bitmap {
+public:
+    Bitmap() { clearAll(); }
+
+    inline void clearAll();
+    inline bool concurrentTestAndSet(size_t n);
+    inline size_t numBits() const { return words * wordSize; }
+
+private:
+    static const size_t Size = 4096*10;
+
+    static const unsigned wordSize = sizeof(uint8_t) * 8;
+    static const unsigned words = (Size + wordSize - 1) / wordSize;
+    static const uint8_t _one_ = 1;
+
+    uint8_t bits[words];
+};
+
+inline void Bitmap::clearAll()
+{
+    memset(&bits, 0, sizeof(bits));
+}
+
+inline bool Bitmap::concurrentTestAndSet(size_t n)
+{
+    uint8_t mask = one << (n % wordSize);
+    size_t index = n / wordSize;
+    uint8_t* wordPtr = &bits[index];
+    uint8_t oldValue;
+    do {
+        oldValue = *wordPtr;
+        if (oldValue & mask)
+            return true;
+    } while (!WTF::weakCompareAndSwap(wordPtr, oldValue, oldValue | mask));
+    return false;
+}
+
+struct Data {
+    Bitmap* bitmap;
+    int id;
+    int numThreads;
+};
+
+static void* setBitThreadFunc(void* p)
+{
+    Data* data = ""
+    Bitmap* bitmap = data->bitmap;
+    size_t numBits = bitmap->numBits();
+
+    // The computed start index here is heuristic that seems to maximize (anecdotally)
+    // the chance for the CAS issue to manifest.
+    size_t start = (numBits * (data->numThreads - data->id)) / data->numThreads;
+
+    printf("   started Thread %d\n", data->id);
+    for (size_t i = start; i < numBits; i++)
+        while (!bitmap->concurrentTestAndSet(i)) { }
+    for (size_t i = 0; i < start; i++)
+        while (!bitmap->concurrentTestAndSet(i)) { }
+
+    printf("   finished Thread %d\n", data->id);
+    pthread_exit(nullptr);
+}
+
+void testCompareAndSwap()
+{
+    Bitmap bitmap;
+    const int numThreads = 5;
+    pthread_t threadIDs[numThreads];
+    Data data[numThreads];
+    
+    printf("Starting %d threads for CompareAndSwap test.  Test should complete without hanging.\n", numThreads);
+    for (int i = 0; i < numThreads; i++) {
+        data[i].bitmap = &bitmap;
+        data[i].id = i;
+        data[i].numThreads = numThreads;
+        pthread_create(&threadIDs[i], 0, &setBitThreadFunc, &data[i]);
+    }
+
+    printf("Waiting for %d threads to join\n", numThreads);
+    for (int i = 0; i < numThreads; i++)
+        pthread_join(threadIDs[i], nullptr);
+
+    printf("PASS: CompareAndSwap test completed without a hang\n");
+}

Modified: releases/WebKitGTK/webkit-2.4/Source/_javascript_Core/API/tests/testapi.c (184461 => 184462)


--- releases/WebKitGTK/webkit-2.4/Source/_javascript_Core/API/tests/testapi.c	2015-05-18 11:29:26 UTC (rev 184461)
+++ releases/WebKitGTK/webkit-2.4/Source/_javascript_Core/API/tests/testapi.c	2015-05-18 12:02:56 UTC (rev 184462)
@@ -46,6 +46,7 @@
 #if JSC_OBJC_API_ENABLED
 void testObjectiveCAPI(void);
 #endif
+void testCompareAndSwap();
 
 extern void JSSynchronousGarbageCollectForDebugging(JSContextRef);
 
@@ -1174,6 +1175,8 @@
     ::SetErrorMode(0);
 #endif
 
+    testCompareAndSwap();
+
 #if JSC_OBJC_API_ENABLED
     testObjectiveCAPI();
 #endif

Modified: releases/WebKitGTK/webkit-2.4/Source/_javascript_Core/ChangeLog (184461 => 184462)


--- releases/WebKitGTK/webkit-2.4/Source/_javascript_Core/ChangeLog	2015-05-18 11:29:26 UTC (rev 184461)
+++ releases/WebKitGTK/webkit-2.4/Source/_javascript_Core/ChangeLog	2015-05-18 12:02:56 UTC (rev 184462)
@@ -1,3 +1,26 @@
+2015-03-09  Mark Lam  <[email protected]>
+
+        8-bit version of weakCompareAndSwap() can cause an infinite loop.
+        https://webkit.org/b/142513>
+
+        Reviewed by Filip Pizlo.
+
+        Added a test that exercises the 8-bit CAS from multiple threads.  The threads
+        will contend to set bits in a large array of bytes using the CAS function.
+
+        * API/tests/CompareAndSwapTest.cpp: Added.
+        (Bitmap::Bitmap):
+        (Bitmap::numBits):
+        (Bitmap::clearAll):
+        (Bitmap::concurrentTestAndSet):
+        (setBitThreadFunc):
+        (testCompareAndSwap):
+        * API/tests/testapi.c:
+        (main):
+        * _javascript_Core.vcxproj/testapi/testapi.vcxproj:
+        * _javascript_Core.vcxproj/testapi/testapi.vcxproj.filters:
+        * _javascript_Core.xcodeproj/project.pbxproj:
+
 2014-12-04  Oliver Hunt  <[email protected]>
 
         Serialization of MapData object provides unsafe access to internal types

Modified: releases/WebKitGTK/webkit-2.4/Source/WTF/ChangeLog (184461 => 184462)


--- releases/WebKitGTK/webkit-2.4/Source/WTF/ChangeLog	2015-05-18 11:29:26 UTC (rev 184461)
+++ releases/WebKitGTK/webkit-2.4/Source/WTF/ChangeLog	2015-05-18 12:02:56 UTC (rev 184462)
@@ -1,3 +1,37 @@
+2015-03-09  Mark Lam  <[email protected]>
+
+        8-bit version of weakCompareAndSwap() can cause an infinite loop.
+        https://webkit.org/b/142513>
+
+        Reviewed by Filip Pizlo.
+
+        Presently, Bitmap::concurrentTestAndSet() uses the 8-bit version of
+        weakCompareAndSwap() (which compares and swaps an uint8_t value).
+        Bitmap::concurrentTestAndSet() has a loop that checks if a bit in the
+        byte of interest has been set.  If not, it will call the 8-bit CAS
+        function to set the bit.
+
+        Under the covers, for ARM, the 8-bit CAS function actually works with a
+        32-bit CAS.  The 8-bit CAS will first fetch the 32-bit value in memory
+        that should contain the 8-bit value, and check if it contains the
+        expected byte.  If the value in memory doesn't have the expected byte,
+        it will return early to its caller.  The expectation is that the caller
+        will reload the byte from memory and call the 8-bit CAS again.
+
+        Unfortunately, this code path that returns early does not have a
+        compiler fence.  Without a compiler fence, the C++ compiler can
+        optimize away the reloading of the expected byte value, leaving it
+        unchanged.  As a result, we'll have a infinite loop here that checks a
+        value that will never change, and the loop will not terminate until the
+        value changes.
+
+        The fix is to eliminate the early return check in the 8-bit CAS, and
+        have it always call down to the 32-bit CAS.  The 32-bit CAS has a
+        compiler fence which will prevent this issue.
+
+        * wtf/Atomics.h:
+        (WTF::weakCompareAndSwap):
+
 2014-10-22  Byungseon Shin  <[email protected]>
 
         String(new Date(Mar 30 2014 01:00:00)) is wrong in CET

Modified: releases/WebKitGTK/webkit-2.4/Source/WTF/wtf/Atomics.h (184461 => 184462)


--- releases/WebKitGTK/webkit-2.4/Source/WTF/wtf/Atomics.h	2015-05-18 11:29:26 UTC (rev 184461)
+++ releases/WebKitGTK/webkit-2.4/Source/WTF/wtf/Atomics.h	2015-05-18 12:02:56 UTC (rev 184462)
@@ -292,16 +292,24 @@
     unsigned* alignedLocation = bitwise_cast<unsigned*>(alignedLocationValue);
     // Make sure that this load is always issued and never optimized away.
     unsigned oldAlignedValue = *const_cast<volatile unsigned*>(alignedLocation);
-    union {
-        uint8_t bytes[sizeof(unsigned)];
-        unsigned word;
-    } u;
-    u.word = oldAlignedValue;
-    if (u.bytes[locationOffset] != expected)
-        return false;
-    u.bytes[locationOffset] = newValue;
-    unsigned newAlignedValue = u.word;
-    return weakCompareAndSwap(alignedLocation, oldAlignedValue, newAlignedValue);
+
+    struct Splicer {
+        static unsigned splice(unsigned value, uint8_t byte, uintptr_t byteIndex)
+        {
+            union {
+                unsigned word;
+                uint8_t bytes[sizeof(unsigned)];
+            } u;
+            u.word = value;
+            u.bytes[byteIndex] = byte;
+            return u.word;
+        }
+    };
+    
+    unsigned expectedAlignedValue = Splicer::splice(oldAlignedValue, expected, locationOffset);
+    unsigned newAlignedValue = Splicer::splice(oldAlignedValue, newValue, locationOffset);
+
+    return weakCompareAndSwap(alignedLocation, expectedAlignedValue, newAlignedValue);
 #endif
 #else
     UNUSED_PARAM(location);
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to