diff --git a/js/src/jit-test/tests/atomics/mutual-exclusion.js b/js/src/jit-test/tests/atomics/mutual-exclusion.js new file mode 100644 index 000000000000..d71fb05485de --- /dev/null +++ b/js/src/jit-test/tests/atomics/mutual-exclusion.js @@ -0,0 +1,91 @@ +// Let a few threads hammer on memory with atomics to provoke errors +// in exclusion work. This test is not 100% fail-safe: the test may +// pass despite a bug, but this is unlikely. + +if (!(this.SharedArrayBuffer && this.getSharedArrayBuffer && this.setSharedArrayBuffer && this.evalInWorker)) + quit(0); + +try { + // This will fail with --no-threads. + evalInWorker("37"); +} +catch (e) { + quit(0); +} + +// Map an Int32Array on shared memory. The first location is used as +// a counter, each worker counts up on exit and the main thread will +// wait until the counter reaches the number of workers. The other +// elements are contended accumulators where we count up and down very +// rapidly and for a long time, any failure in mutual exclusion should +// lead to errors in the result. (For example, the test fails almost +// immediately when I disable simulation of mutual exclusion in the +// ARM simulator.) + +const numWorkers = 4; // You're not meant to change this +const iterCount = 255; // Nor this +const sabLength = 1024; // Nor this + +const oddResult = (function () { + var v = 0; + for ( var j=0 ; j < numWorkers ; j++ ) + v |= (iterCount << (8 * j)); + return v; +})(); + +const evenResult = 0; + +const sab = new SharedArrayBuffer(sabLength); + +setSharedArrayBuffer(sab); + +const iab = new SharedInt32Array(sab); + +function testRun(limit) { + console.log("Limit = " + limit); + + // Fork off workers to hammer on memory. + for ( var i=0 ; i < numWorkers ; i++ ) { + evalInWorker(` + const iab = new SharedInt32Array(getSharedArrayBuffer()); + const v = 1 << (8 * ${i}); + for ( var i=0 ; i < ${limit} ; i++ ) { + for ( var k=0 ; k < ${iterCount} ; k++ ) { + if (i & 1) { + for ( var j=1 ; j < iab.length ; j++ ) + Atomics.sub(iab, j, v); + } + else { + for ( var j=1 ; j < iab.length ; j++ ) + Atomics.add(iab, j, v); + } + } + } + Atomics.add(iab, 0, 1); + `); + } + + // Wait... + while (Atomics.load(iab, 0) != numWorkers) + ; + Atomics.store(iab, 0, 0); + + // Check the results and clear the array again. + const v = (limit & 1) ? oddResult : evenResult; + for ( var i=1 ; i < iab.length ; i++ ) { + assertEq(iab[i], v); + iab[i] = 0; + } +} + +// Under some configurations the test can take a while to run (and may +// saturate the CPU since it runs four workers); try not to time out. + +var then = new Date(); +testRun(1); +if (new Date() - then < 20000) { + testRun(2); + if (new Date() - then < 30000) { + testRun(3); + } +} diff --git a/js/src/jit/arm/Simulator-arm.cpp b/js/src/jit/arm/Simulator-arm.cpp index d369e759b828..9fa0fba4562e 100644 --- a/js/src/jit/arm/Simulator-arm.cpp +++ b/js/src/jit/arm/Simulator-arm.cpp @@ -38,7 +38,9 @@ #include "asmjs/AsmJSValidate.h" #include "jit/arm/Assembler-arm.h" #include "jit/arm/disasm/Constants-arm.h" +#include "jit/AtomicOperations.h" #include "vm/Runtime.h" +#include "vm/SharedMem.h" extern "C" { @@ -1127,6 +1129,8 @@ Simulator::Simulator() cacheLockHolder_ = nullptr; #endif redirection_ = nullptr; + exclusiveMonitorHeld_ = false; + exclusiveMonitor_ = 0; } bool @@ -1478,6 +1482,27 @@ Simulator::setCallResult(int64_t res) set_register(r1, static_cast(res >> 32)); } +void +Simulator::exclusiveMonitorSet(uint64_t value) +{ + exclusiveMonitor_ = value; + exclusiveMonitorHeld_ = true; +} + +uint64_t +Simulator::exclusiveMonitorGetAndClear(bool* held) +{ + *held = exclusiveMonitorHeld_; + exclusiveMonitorHeld_ = false; + return *held ? exclusiveMonitor_ : 0; +} + +void +Simulator::exclusiveMonitorClear() +{ + exclusiveMonitorHeld_ = false; +} + int Simulator::readW(int32_t addr, SimInstruction* instr) { @@ -1504,14 +1529,66 @@ Simulator::writeW(int32_t addr, int value, SimInstruction* instr) } } +// For the time being, define Relaxed operations in terms of SeqCst +// operations - we don't yet need Relaxed operations anywhere else in +// the system, and the distinction is not important to the simulation +// at the level where we're operating. + +template +static +T loadRelaxed(SharedMem addr) +{ + return AtomicOperations::loadSeqCst(addr); +} + +template +static +T compareExchangeRelaxed(SharedMem addr, T oldval, T newval) +{ + return AtomicOperations::compareExchangeSeqCst(addr, oldval, newval); +} + +int +Simulator::readExW(int32_t addr, SimInstruction* instr) +{ + // The regexp engine emits unaligned loads, so we don't check for them here + // like most of the other methods do. + if ((addr & 3) == 0 || !HasAlignmentFault()) { + SharedMem ptr = SharedMem::shared(reinterpret_cast(addr)); + int32_t value = loadRelaxed(ptr); + exclusiveMonitorSet(value); + return value; + } else { + printf("Unaligned write at 0x%08x, pc=%p\n", addr, instr); + MOZ_CRASH(); + } +} + +int32_t +Simulator::writeExW(int32_t addr, int value, SimInstruction* instr) +{ + if ((addr & 3) == 0) { + SharedMem ptr = SharedMem::shared(reinterpret_cast(addr)); + bool held; + int32_t expected = int32_t(exclusiveMonitorGetAndClear(&held)); + if (!held) + return 1; + int32_t old = compareExchangeRelaxed(ptr, expected, int32_t(value)); + return old != expected; + } else { + printf("Unaligned write at 0x%08x, pc=%p\n", addr, instr); + MOZ_CRASH(); + } +} + uint16_t Simulator::readHU(int32_t addr, SimInstruction* instr) { // The regexp engine emits unaligned loads, so we don't check for them here // like most of the other methods do. if ((addr & 1) == 0 || !HasAlignmentFault()) { - uint16_t* ptr = reinterpret_cast(addr); - return *ptr; + uint16_t* ptr = reinterpret_cast(addr); + return *ptr; } printf("Unaligned unsigned halfword read at 0x%08x, pc=%p\n", addr, instr); MOZ_CRASH(); @@ -1554,6 +1631,39 @@ Simulator::writeH(int32_t addr, int16_t value, SimInstruction* instr) } } +uint16_t +Simulator::readExHU(int32_t addr, SimInstruction* instr) +{ + // The regexp engine emits unaligned loads, so we don't check for them here + // like most of the other methods do. + if ((addr & 1) == 0 || !HasAlignmentFault()) { + SharedMem ptr = SharedMem::shared(reinterpret_cast(addr)); + uint16_t value = loadRelaxed(ptr); + exclusiveMonitorSet(value); + return value; + } + printf("Unaligned atomic unsigned halfword read at 0x%08x, pc=%p\n", addr, instr); + MOZ_CRASH(); + return 0; +} + +int32_t +Simulator::writeExH(int32_t addr, uint16_t value, SimInstruction* instr) +{ + if ((addr & 1) == 0) { + SharedMem ptr = SharedMem::shared(reinterpret_cast(addr)); + bool held; + uint16_t expected = uint16_t(exclusiveMonitorGetAndClear(&held)); + if (!held) + return 1; + uint16_t old = compareExchangeRelaxed(ptr, expected, value); + return old != expected; + } else { + printf("Unaligned atomic unsigned halfword write at 0x%08x, pc=%p\n", addr, instr); + MOZ_CRASH(); + } +} + uint8_t Simulator::readBU(int32_t addr) { @@ -1561,6 +1671,27 @@ Simulator::readBU(int32_t addr) return *ptr; } +uint8_t +Simulator::readExBU(int32_t addr) +{ + SharedMem ptr = SharedMem::shared(reinterpret_cast(addr)); + uint8_t value = loadRelaxed(ptr); + exclusiveMonitorSet(value); + return value; +} + +int32_t +Simulator::writeExB(int32_t addr, uint8_t value) +{ + SharedMem ptr = SharedMem::shared(reinterpret_cast(addr)); + bool held; + uint8_t expected = uint8_t(exclusiveMonitorGetAndClear(&held)); + if (!held) + return 1; + uint8_t old = compareExchangeRelaxed(ptr, expected, value); + return old != expected; +} + int8_t Simulator::readB(int32_t addr) { @@ -1607,6 +1738,49 @@ Simulator::writeDW(int32_t addr, int32_t value1, int32_t value2) } } +int32_t +Simulator::readExDW(int32_t addr, int32_t* hibits) +{ +#if defined(__clang__) && defined(__i386) + // This is OK for now, we don't yet generate LDREXD. + MOZ_CRASH("Unimplemented - 8-byte atomics are unsupported in Clang on i386"); +#else + if ((addr & 3) == 0) { + SharedMem ptr = SharedMem::shared(reinterpret_cast(addr)); + uint64_t value = loadRelaxed(ptr); + exclusiveMonitorSet(value); + *hibits = int32_t(value); + return int32_t(value >> 32); + } + printf("Unaligned read at 0x%08x\n", addr); + MOZ_CRASH(); + return 0; +#endif +} + +int32_t +Simulator::writeExDW(int32_t addr, int32_t value1, int32_t value2) +{ +#if defined(__clang__) && defined(__i386) + // This is OK for now, we don't yet generate STREXD. + MOZ_CRASH("Unimplemented - 8-byte atomics are unsupported in Clang on i386"); +#else + if ((addr & 3) == 0) { + SharedMem ptr = SharedMem::shared(reinterpret_cast(addr)); + uint64_t value = (uint64_t(value1) << 32) | uint32_t(value2); + bool held; + uint64_t expected = exclusiveMonitorGetAndClear(&held); + if (!held) + return 1; + uint64_t old = compareExchangeRelaxed(ptr, expected, value); + return old != expected; + } else { + printf("Unaligned write at 0x%08x\n", addr); + MOZ_CRASH(); + } +#endif +} + uintptr_t Simulator::stackLimit() const { @@ -2554,33 +2728,27 @@ Simulator::decodeType01(SimInstruction* instr) } else { if (instr->bits(disasm::ExclusiveOpHi, disasm::ExclusiveOpLo) == disasm::ExclusiveOpcode) { // Load-exclusive / store-exclusive. - // - // Bare-bones simulation: the store always succeeds, and we - // do not execute any fences. Also, we allow readDW and - // writeDW to split the memory transaction. - // - // The next step up would involve remembering the value - // that was read with load-exclusive so that we could use - // compareExchange for the store-exclusive, and to - // implement atomic doubleword read and write. - // - // Also see DMB/DSB/ISB below. if (instr->bit(disasm::ExclusiveLoad)) { int rn = instr->rnValue(); int rt = instr->rtValue(); int32_t address = get_register(rn); switch (instr->bits(disasm::ExclusiveSizeHi, disasm::ExclusiveSizeLo)) { case disasm::ExclusiveWord: - set_register(rt, readW(address, instr)); + set_register(rt, readExW(address, instr)); break; - case disasm::ExclusiveDouble: - set_dw_register(rt, readDW(address)); + case disasm::ExclusiveDouble: { + MOZ_ASSERT((rt % 2) == 0); + int32_t hibits; + int32_t lobits = readExDW(address, &hibits); + set_register(rt, lobits); + set_register(rt+1, hibits); break; + } case disasm::ExclusiveByte: - set_register(rt, readBU(address)); + set_register(rt, readExBU(address)); break; case disasm::ExclusiveHalf: - set_register(rt, readHU(address, instr)); + set_register(rt, readExHU(address, instr)); break; } } else { @@ -2589,24 +2757,25 @@ Simulator::decodeType01(SimInstruction* instr) int rt = instr->bits(3,0); int32_t address = get_register(rn); int32_t value = get_register(rt); + int32_t result = 0; switch (instr->bits(disasm::ExclusiveSizeHi, disasm::ExclusiveSizeLo)) { case disasm::ExclusiveWord: - writeW(address, value, instr); + result = writeExW(address, value, instr); break; case disasm::ExclusiveDouble: { - MOZ_ASSERT((rt % 2) == 0); - int32_t value2 = get_register(rt+1); - writeDW(address, value, value2); - break; + MOZ_ASSERT((rt % 2) == 0); + int32_t value2 = get_register(rt+1); + result = writeExDW(address, value, value2); + break; } case disasm::ExclusiveByte: - writeB(address, (uint8_t)value); + result = writeExB(address, (uint8_t)value); break; case disasm::ExclusiveHalf: - writeH(address, (uint16_t)value, instr); + result = writeExH(address, (uint16_t)value, instr); break; } - set_register(rd, 0); + set_register(rd, result); } } else { MOZ_CRASH(); // Not used atm @@ -3329,12 +3498,15 @@ Simulator::decodeType7CoprocessorIns(SimInstruction* instr) int CRn = instr->bits(19,16); int CRm = instr->bits(3,0); if (opc1 == 0 && opc2 == 4 && CRn == 7 && CRm == 10) { - // ARMv6 DSB instruction - do nothing now, see comments above + // ARMv6 DSB instruction. We do not use DSB. + MOZ_CRASH("DSB not implemented"); } else if (opc1 == 0 && opc2 == 5 && CRn == 7 && CRm == 10) { - // ARMv6 DMB instruction - do nothing now, see comments above + // ARMv6 DMB instruction. + AtomicOperations::fenceSeqCst(); } else if (opc1 == 0 && opc2 == 4 && CRn == 7 && CRm == 5) { - // ARMv6 ISB instruction - do nothing now, see comments above + // ARMv6 ISB instruction. We do not use ISB. + MOZ_CRASH("ISB not implemented"); } else { MOZ_CRASH(); @@ -4156,16 +4328,16 @@ Simulator::decodeSpecialCondition(SimInstruction* instr) break; case 0xA: if (instr->bits(31,20) == 0xf57) { - // Minimal simulation: do nothing. - // - // If/when we upgrade load-exclusive and store-exclusive (above) to - // do something useful concurrency-wise, we should also upgrade - // these instructions. switch (instr->bits(7,4)) { case 5: // DMB - case 4: // DSB - case 6: // ISB + AtomicOperations::fenceSeqCst(); break; + case 4: // DSB + // We do not use DSB. + MOZ_CRASH("DSB unimplemented"); + case 6: // ISB + // We do not use ISB. + MOZ_CRASH("ISB unimplemented"); default: MOZ_CRASH(); } diff --git a/js/src/jit/arm/Simulator-arm.h b/js/src/jit/arm/Simulator-arm.h index f6785a4138b8..5f0f865b0942 100644 --- a/js/src/jit/arm/Simulator-arm.h +++ b/js/src/jit/arm/Simulator-arm.h @@ -248,18 +248,30 @@ class Simulator inline void writeB(int32_t addr, uint8_t value); inline void writeB(int32_t addr, int8_t value); + inline uint8_t readExBU(int32_t addr); + inline int32_t writeExB(int32_t addr, uint8_t value); + inline uint16_t readHU(int32_t addr, SimInstruction* instr); inline int16_t readH(int32_t addr, SimInstruction* instr); // Note: Overloaded on the sign of the value. inline void writeH(int32_t addr, uint16_t value, SimInstruction* instr); inline void writeH(int32_t addr, int16_t value, SimInstruction* instr); + inline uint16_t readExHU(int32_t addr, SimInstruction* instr); + inline int32_t writeExH(int32_t addr, uint16_t value, SimInstruction* instr); + inline int readW(int32_t addr, SimInstruction* instr); inline void writeW(int32_t addr, int value, SimInstruction* instr); + inline int readExW(int32_t addr, SimInstruction* instr); + inline int writeExW(int32_t addr, int value, SimInstruction* instr); + int32_t* readDW(int32_t addr); void writeDW(int32_t addr, int32_t value1, int32_t value2); + int32_t readExDW(int32_t addr, int32_t* hibits); + int32_t writeExDW(int32_t addr, int32_t value1, int32_t value2); + // Executing is handled based on the instruction type. // Both type 0 and type 1 rolled into one. void decodeType01(SimInstruction* instr); @@ -432,6 +444,15 @@ class Simulator MOZ_ASSERT(cacheLockHolder_); redirection_ = redirection; } + + private: + // Exclusive access monitor + void exclusiveMonitorSet(uint64_t value); + uint64_t exclusiveMonitorGetAndClear(bool* held); + void exclusiveMonitorClear(); + + bool exclusiveMonitorHeld_; + uint64_t exclusiveMonitor_; }; #define JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, onerror) \