Commit 33961712 authored by fpizlo@apple.com's avatar fpizlo@apple.com

Infer constant global variables

https://bugs.webkit.org/show_bug.cgi?id=124464

Source/JavaScriptCore: 

Reviewed by Sam Weinig.
        
All global variables that are candidates for watchpoint-based constant inference (i.e.
not 'const' variables) will now have WatchpointSet's associated with them and those
are used to drive the inference by tracking three states of each variable:
        
Uninitialized: the variable's value is Undefined and the WatchpointSet state is
    ClearWatchpoint.
        
Initialized: the variable's value was set to something (could even be explicitly set
    to Undefined) and the WatchpointSet state is IsWatching.
        
Invalidated: the variable's value was set to something else (could even be the same
    thing as before but the point is that a put operation did execute again) and the
    WatchpointSet is IsInvalidated.
        
If the compiler tries to compile a GetGlobalVar and the WatchpointSet state is
IsWatching, then the current value of the variable can be folded in place of the get,
and a watchpoint on the variable can be registered.
        
We handle race conditions between the mutator and compiler by mandating that:
        
- The mutator changes the WatchpointSet state after executing the put.
        
- There is no opportunity to install code or call functions between when the mutator
  executes a put and changes the WatchpointSet state.
        
- The compiler checks the WatchpointSet state prior to reading the value.
        
The concrete algorithm used by the mutator is:
        
    1. Store the new value into the variable.
    --- Execute a store-store fence.
    2. Bump the state (ClearWatchpoing becomes IsWatching, IsWatching becomes
       IsInvalidated); the IsWatching->IsInvalidated transition may end up firing
       watchpoints.
        
The concrete algorithm that the compiler uses is:
        
    1. Load the state. If it's *not* IsWatching, then give up on constant inference.
    --- Execute a load-load fence.
    2. Load the value of the variable and use that for folding, while also registering
       a DesiredWatchpoint. The various parts of this step can be done in any order.
        
The desired watchpoint registration will fail if the watchpoint set is already
invalidated. Now consider the following interesting interleavings:
        
Uninitialized->M1->M2->C1->C2: Compiler sees IsWatching because of the mutator's store
    operation, and the variable is folded. The fencing ensures that C2 sees the value
    stored in M1 - i.e. we fold on the value that will actually be watchpointed. If
    before the compilation is installed the mutator executes another store then we
    will be sure that it will be a complete sequence of M1+M2 since compilations get
    installed at safepoints and never "in the middle" of a put_to_scope. Hence that
    compilation installation will be invalidated. If the M1+M2 sequence happens after
    the code is installed, then the code will be invalidated by triggering a jettison.
        
Uninitialized->M1->C1->C2->M2: Compiler sees Uninitialized and will not fold. This is
    a sensible outcome since if the compiler read the variable's value, it would have
    seen Undefined.
        
Uninitialized->C1->C2->M1->M2: Compiler sees Uninitialized and will not fold.
Uninitialized->C1->M1->C2->M2: Compiler sees Uninitialized and will not fold.
Uninitialized->C1->M1->M2->C2: Compiler sees Uninitialized and will not fold.
Uninitialized->M1->C1->M2->C2: Compiler sees Uninitialized and will not fold.
        
IsWatched->M1->M2->C1->C2: Compiler sees IsInvalidated and will not fold.
        
IsWatched->M1->C1->C2->M2: Compiler will fold, but will also register a desired
    watchpoint, and that watchpoint will get invalidated before the code is installed.
        
IsWatched->M1->C1->M2->C2: As above, will fold but the code will get invalidated.
IsWatched->C1->C2->M1->M2: As above, will fold but the code will get invalidated.
IsWatched->C1->M1->C2->M2: As above, will fold but the code will get invalidated.
IsWatched->C1->M1->M2->C2: As above, will fold but the code will get invalidated.
        
Note that this kind of reasoning shows why having the mutator first bump the state and
then store the new value would be wrong. If we had done that (M1 = bump state, M2 =
execute put) then we could have the following deadly interleavings:
        
Uninitialized->M1->C1->C2->M2:
Uninitialized->M1->C1->M2->C2: Mutator bumps the state to IsWatched and then the
    compiler folds Undefined, since M2 hasn't executed yet. Although C2 will set the
    watchpoint, M1 didn't notify it - it mearly initiated watching. M2 then stores a
    value other than Undefined, and you're toast.
        
You could fix this sort of thing by making the Desired Watchpoints machinery more
sophisticated, for example having it track the value that was folded; if the global
variable's value was later found to be different then we could invalidate the
compilation. You could also fix it by having the compiler also check that the value of
the variable is not Undefined before folding. While those all sound great, I decided
to instead just use the right interleaving since that results in less code and feels
more intuitive.
        
This is a 0.5% speed-up on SunSpider, mostly due to a 20% speed-up on math-cordic.
It's a 0.6% slow-down on LongSpider, mostly due to a 25% slow-down on 3d-cube. This is
because 3d-cube takes global variable assignment slow paths very often. Note that this
3d-cube slow-down doesn't manifest as much in SunSpider (only 6% there). This patch is
also a 1.5% speed-up on V8v7 and a 2.8% speed-up on Octane v1, mostly due to deltablue
(3.7%), richards (4%), and mandreel (26%). This is a 2% speed-up on Kraken, mostly due
to a 17.5% speed-up on imaging-gaussian-blur. Something that really illustrates the
slam-dunk-itude of this patch is the wide range of speed-ups on JSRegress. Casual JS
programming often leads to global-var-based idioms and those variables tend to be
assigned once, leading to excellent constant folding opportunities in an optimizing
JIT. This is very evident in the speed-ups on JSRegress.

* assembler/ARM64Assembler.h:
(JSC::ARM64Assembler::dmbSY):
* assembler/ARMv7Assembler.h:
(JSC::ARMv7Assembler::dmbSY):
* assembler/MacroAssemblerARM64.h:
(JSC::MacroAssemblerARM64::memfence):
* assembler/MacroAssemblerARMv7.h:
(JSC::MacroAssemblerARMv7::load8):
(JSC::MacroAssemblerARMv7::memfence):
* assembler/MacroAssemblerX86.h:
(JSC::MacroAssemblerX86::load8):
(JSC::MacroAssemblerX86::store8):
* assembler/MacroAssemblerX86Common.h:
(JSC::MacroAssemblerX86Common::getUnusedRegister):
(JSC::MacroAssemblerX86Common::store8):
(JSC::MacroAssemblerX86Common::memoryFence):
* assembler/MacroAssemblerX86_64.h:
(JSC::MacroAssemblerX86_64::load8):
(JSC::MacroAssemblerX86_64::store8):
* assembler/X86Assembler.h:
(JSC::X86Assembler::movb_rm):
(JSC::X86Assembler::movzbl_mr):
(JSC::X86Assembler::mfence):
(JSC::X86Assembler::X86InstructionFormatter::threeByteOp):
(JSC::X86Assembler::X86InstructionFormatter::oneByteOp8):
* bytecode/CodeBlock.cpp:
(JSC::CodeBlock::CodeBlock):
* bytecode/Watchpoint.cpp:
(JSC::WatchpointSet::WatchpointSet):
(JSC::WatchpointSet::add):
(JSC::WatchpointSet::notifyWriteSlow):
* bytecode/Watchpoint.h:
(JSC::WatchpointSet::state):
(JSC::WatchpointSet::isStillValid):
(JSC::WatchpointSet::addressOfSetIsNotEmpty):
* dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::::executeEffects):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::getJSConstantForValue):
(JSC::DFG::ByteCodeParser::getJSConstant):
(JSC::DFG::ByteCodeParser::parseBlock):
* dfg/DFGClobberize.h:
(JSC::DFG::clobberize):
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
* dfg/DFGNode.h:
(JSC::DFG::Node::isStronglyProvedConstantIn):
(JSC::DFG::Node::hasIdentifierNumberForCheck):
(JSC::DFG::Node::hasRegisterPointer):
* dfg/DFGNodeFlags.h:
* dfg/DFGNodeType.h:
* dfg/DFGOperations.cpp:
* dfg/DFGOperations.h:
* dfg/DFGPredictionPropagationPhase.cpp:
(JSC::DFG::PredictionPropagationPhase::propagate):
* dfg/DFGSafeToExecute.h:
(JSC::DFG::safeToExecute):
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compileNotifyPutGlobalVar):
* dfg/DFGSpeculativeJIT.h:
(JSC::DFG::SpeculativeJIT::callOperation):
* dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* ftl/FTLAbbreviatedTypes.h:
* ftl/FTLAbbreviations.h:
(JSC::FTL::buildFence):
* ftl/FTLCapabilities.cpp:
(JSC::FTL::canCompile):
* ftl/FTLIntrinsicRepository.h:
* ftl/FTLLowerDFGToLLVM.cpp:
(JSC::FTL::LowerDFGToLLVM::compileNode):
(JSC::FTL::LowerDFGToLLVM::compileNotifyPutGlobalVar):
* ftl/FTLOutput.h:
(JSC::FTL::Output::fence):
* jit/JIT.h:
* jit/JITOperations.h:
* jit/JITPropertyAccess.cpp:
(JSC::JIT::emitPutGlobalVar):
(JSC::JIT::emit_op_put_to_scope):
(JSC::JIT::emitSlow_op_put_to_scope):
* jit/JITPropertyAccess32_64.cpp:
(JSC::JIT::emitPutGlobalVar):
(JSC::JIT::emit_op_put_to_scope):
(JSC::JIT::emitSlow_op_put_to_scope):
* llint/LowLevelInterpreter32_64.asm:
* llint/LowLevelInterpreter64.asm:
* llvm/LLVMAPIFunctions.h:
* offlineasm/arm.rb:
* offlineasm/arm64.rb:
* offlineasm/cloop.rb:
* offlineasm/instructions.rb:
* offlineasm/x86.rb:
* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::addGlobalVar):
(JSC::JSGlobalObject::addFunction):
* runtime/JSGlobalObject.h:
(JSC::JSGlobalObject::addVar):
(JSC::JSGlobalObject::addConst):
* runtime/JSScope.cpp:
(JSC::abstractAccess):
* runtime/JSSymbolTableObject.h:
(JSC::symbolTablePut):
(JSC::symbolTablePutWithAttributes):
* runtime/SymbolTable.cpp:
(JSC::SymbolTableEntry::couldBeWatched):
(JSC::SymbolTableEntry::prepareToWatch):
(JSC::SymbolTableEntry::notifyWriteSlow):
* runtime/SymbolTable.h:

LayoutTests: 

Reviewed by Sam Weinig.

* js/regress/global-var-const-infer-expected.txt: Added.
* js/regress/global-var-const-infer-fire-from-opt-expected.txt: Added.
* js/regress/global-var-const-infer-fire-from-opt.html: Added.
* js/regress/global-var-const-infer.html: Added.
* js/regress/script-tests/global-var-const-infer-fire-from-opt.js: Added.
(foo):
(setA):
(setB):
(check):
* js/regress/script-tests/global-var-const-infer.js: Added.
(foo):
(check):



git-svn-id: http://svn.webkit.org/repository/webkit/trunk@159545 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent b4df66ee
2013-11-19 Filip Pizlo <fpizlo@apple.com>
Infer constant global variables
https://bugs.webkit.org/show_bug.cgi?id=124464
Reviewed by Sam Weinig.
* js/regress/global-var-const-infer-expected.txt: Added.
* js/regress/global-var-const-infer-fire-from-opt-expected.txt: Added.
* js/regress/global-var-const-infer-fire-from-opt.html: Added.
* js/regress/global-var-const-infer.html: Added.
* js/regress/script-tests/global-var-const-infer-fire-from-opt.js: Added.
(foo):
(setA):
(setB):
(check):
* js/regress/script-tests/global-var-const-infer.js: Added.
(foo):
(check):
2013-11-19 Sun-woo Nam <sunny.nam@samsung.com>
[EFL] Layout tests need to be rebaselined.
......
JSRegress/global-var-const-infer
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS no exception thrown
PASS successfullyParsed is true
TEST COMPLETE
JSRegress/global-var-const-infer-fire-from-opt
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS no exception thrown
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<script src="../resources/js-test-pre.js"></script>
</head>
<body>
<script src="resources/regress-pre.js"></script>
<script src="script-tests/global-var-const-infer-fire-from-opt.js"></script>
<script src="resources/regress-post.js"></script>
<script src="../resources/js-test-post.js"></script>
</body>
</html>
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<script src="../resources/js-test-pre.js"></script>
</head>
<body>
<script src="resources/regress-pre.js"></script>
<script src="script-tests/global-var-const-infer.js"></script>
<script src="resources/regress-post.js"></script>
<script src="../resources/js-test-post.js"></script>
</body>
</html>
function foo() {
return a + b;
}
noInline(foo);
var a;
var b;
function setA(p, value) {
if (p)
a = value;
}
function setB(p, value) {
if (p)
b = value;
}
noInline(setA);
noInline(setB);
setA(true, 4);
setB(true, 5);
for (var i = 0; i < 1000; ++i) {
setA(false, 42);
setB(false, 42);
}
function check(actual, expected) {
if (actual == expected)
return;
throw "Error: expected " + expected + " but got " + actual;
}
for (var i = 0; i < 100; ++i)
check(foo(), 9);
setA(true, 6);
for (var i = 0; i < 1000; ++i)
check(foo(), 11);
setB(true, 7);
for (var i = 0; i < 10000; ++i)
check(foo(), 13);
function foo() {
return a + b;
}
noInline(foo);
var a = 4;
var b = 5;
function check(actual, expected) {
if (actual == expected)
return;
throw "Error: expected " + expected + " but got " + actual;
}
for (var i = 0; i < 100; ++i)
check(foo(), 9);
a = 6;
for (var i = 0; i < 1000; ++i)
check(foo(), 11);
b = 7;
for (var i = 0; i < 10000; ++i)
check(foo(), 13);
This diff is collapsed.
......@@ -1595,6 +1595,11 @@ public:
{
insn(nopPseudo());
}
ALWAYS_INLINE void dmbSY()
{
insn(0xd5033fbf);
}
template<int datasize>
ALWAYS_INLINE void orn(RegisterID rd, RegisterID rn, RegisterID rm)
......
......@@ -713,6 +713,7 @@ private:
OP_MOVT = 0xF2C0,
OP_UBFX_T1 = 0xF3C0,
OP_NOP_T2a = 0xF3AF,
OP_DMB_SY_T2a = 0xF3BF,
OP_STRB_imm_T3 = 0xF800,
OP_STRB_reg_T2 = 0xF800,
OP_LDRB_imm_T3 = 0xF810,
......@@ -769,6 +770,7 @@ private:
OP_VCVTSD_T1b = 0x0A40,
OP_VCVTDS_T1b = 0x0A40,
OP_NOP_T2b = 0x8000,
OP_DMB_SY_T2a = 0x8F5F,
OP_B_T3b = 0x8000,
OP_B_T4b = 0x9000,
} OpcodeID2;
......@@ -1980,6 +1982,11 @@ public:
{
m_formatter.twoWordOp16Op16(OP_NOP_T2a, OP_NOP_T2b);
}
void dmbSY()
{
m_formatter.twoWordOp16Op16(OP_DMB_SY_T2a, OP_DMB_SY_T2b);
}
AssemblerLabel labelIgnoringWatchpoints()
{
......
......@@ -2255,6 +2255,11 @@ public:
{
m_assembler.nop();
}
void memfence()
{
m_assembler.dmbSY();
}
// Misc helper functions.
......
......@@ -652,6 +652,12 @@ public:
load8Signed(setupArmAddress(address), dest);
}
void load8(const void* address, RegisterID dest)
{
move(TrustedImmPtr(address), dest);
load8(dest, dest);
}
DataLabel32 load32WithAddressOffsetPatch(Address address, RegisterID dest)
{
DataLabel32 label = moveWithPatch(TrustedImm32(address.offset), dataTempRegister);
......@@ -1231,6 +1237,11 @@ public:
m_assembler.nop();
}
void memfence()
{
m_assembler.dmbSY();
}
static void replaceWithJump(CodeLocationLabel instructionStart, CodeLocationLabel destination)
{
ARMv7Assembler::replaceWithJump(instructionStart.dataLocation(), destination.dataLocation());
......
......@@ -47,6 +47,7 @@ public:
using MacroAssemblerX86Common::sub32;
using MacroAssemblerX86Common::or32;
using MacroAssemblerX86Common::load32;
using MacroAssemblerX86Common::load8;
using MacroAssemblerX86Common::store32;
using MacroAssemblerX86Common::store8;
using MacroAssemblerX86Common::branch32;
......@@ -104,6 +105,11 @@ public:
{
m_assembler.movl_mr(address, dest);
}
void load8(const void* address, RegisterID dest)
{
m_assembler.movzbl_mr(address, dest);
}
ConvertibleLoadLabel convertibleLoadPtr(Address address, RegisterID dest)
{
......@@ -138,6 +144,11 @@ public:
{
m_assembler.movl_rm(src, address);
}
void store8(RegisterID src, void* address)
{
m_assembler.movb_rm(src, address);
}
void store8(TrustedImm32 imm, void* address)
{
......
......@@ -606,6 +606,15 @@ public:
return X86Registers::ecx;
}
static ALWAYS_INLINE RegisterID getUnusedRegister(Address address)
{
if (address.base != X86Registers::eax)
return X86Registers::eax;
ASSERT(address.base != X86Registers::edx);
return X86Registers::edx;
}
void store8(RegisterID src, BaseIndex address)
{
#if CPU(X86)
......@@ -624,6 +633,25 @@ public:
#endif
m_assembler.movb_rm(src, address.offset, address.base, address.index, address.scale);
}
void store8(RegisterID src, Address address)
{
#if CPU(X86)
// On 32-bit x86 we can only store from the first 4 registers;
// esp..edi are mapped to the 'h' registers!
if (src >= 4) {
// Pick a temporary register.
RegisterID temp = getUnusedRegister(address);
// Swap to the temporary register to perform the store.
swap(src, temp);
m_assembler.movb_rm(temp, address.offset, address.base);
swap(src, temp);
return;
}
#endif
m_assembler.movb_rm(src, address.offset, address.base);
}
void store16(RegisterID src, BaseIndex address)
{
......@@ -1419,6 +1447,11 @@ public:
{
m_assembler.nop();
}
void memoryFence()
{
m_assembler.mfence();
}
static void replaceWithJump(CodeLocationLabel instructionStart, CodeLocationLabel destination)
{
......
......@@ -47,6 +47,7 @@ public:
using MacroAssemblerX86Common::branchAdd32;
using MacroAssemblerX86Common::or32;
using MacroAssemblerX86Common::sub32;
using MacroAssemblerX86Common::load8;
using MacroAssemblerX86Common::load32;
using MacroAssemblerX86Common::store32;
using MacroAssemblerX86Common::store8;
......@@ -91,6 +92,12 @@ public:
move(TrustedImmPtr(address.m_ptr), scratchRegister);
sub32(imm, Address(scratchRegister));
}
void load8(const void* address, RegisterID dest)
{
move(TrustedImmPtr(address), dest);
load8(dest, dest);
}
void load32(const void* address, RegisterID dest)
{
......@@ -126,6 +133,12 @@ public:
store8(imm, Address(scratchRegister));
}
void store8(RegisterID reg, void* address)
{
move(TrustedImmPtr(address), scratchRegister);
store8(reg, Address(scratchRegister));
}
Call call()
{
DataLabelPtr label = moveWithPatch(TrustedImmPtr(0), scratchRegister);
......
......@@ -267,6 +267,7 @@ private:
OP2_MOVD_EdVd = 0x7E,
OP2_JCC_rel32 = 0x80,
OP_SETCC = 0x90,
OP2_3BYTE_ESCAPE = 0xAE,
OP2_IMUL_GvEv = 0xAF,
OP2_MOVZX_GvEb = 0xB6,
OP2_MOVSX_GvEb = 0xBE,
......@@ -277,6 +278,10 @@ private:
OP2_PSRLQ_UdqIb = 0x73,
OP2_POR_VdqWdq = 0XEB,
} TwoByteOpcodeID;
typedef enum {
OP3_MFENCE = 0xF0,
} ThreeByteOpcodeID;
TwoByteOpcodeID jccRel32(Condition cond)
{
......@@ -1302,6 +1307,18 @@ public:
m_formatter.oneByteOp(OP_GROUP11_EvIb, GROUP11_MOV, base, index, scale, offset);
m_formatter.immediate8(imm);
}
#if !CPU(X86_64)
void movb_rm(RegisterID src, const void* addr)
{
m_formatter.oneByteOp(OP_MOV_EbGb, src, addr);
}
#endif
void movb_rm(RegisterID src, int offset, RegisterID base)
{
m_formatter.oneByteOp8(OP_MOV_EbGb, src, base, offset);
}
void movb_rm(RegisterID src, int offset, RegisterID base, RegisterID index, int scale)
{
......@@ -1449,6 +1466,13 @@ public:
m_formatter.twoByteOp(OP2_MOVZX_GvEb, dst, base, index, scale, offset);
}
#if !CPU(X86_64)
void movzbl_mr(const void* address, RegisterID dst)
{
m_formatter.twoByteOp(OP2_MOVZX_GvEb, dst, address);
}
#endif
void movsbl_mr(int offset, RegisterID base, RegisterID dst)
{
m_formatter.twoByteOp(OP2_MOVSX_GvEb, dst, base, offset);
......@@ -1873,7 +1897,7 @@ public:
m_formatter.prefix(PRE_SSE_F2);
m_formatter.twoByteOp(OP2_SQRTSD_VsdWsd, (RegisterID)dst, (RegisterID)src);
}
// Misc instructions:
void int3()
......@@ -1890,6 +1914,11 @@ public:
{
m_formatter.prefix(PRE_PREDICT_BRANCH_NOT_TAKEN);
}
void mfence()
{
m_formatter.threeByteOp(OP3_MFENCE);
}
// Assembler admin methods:
......@@ -2301,6 +2330,14 @@ private:
}
#endif
void threeByteOp(ThreeByteOpcodeID opcode)
{
m_buffer.ensureSpace(maxInstructionSize);
m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE);
m_buffer.putByteUnchecked(OP2_3BYTE_ESCAPE);
m_buffer.putByteUnchecked(opcode);
}
#if CPU(X86_64)
// Quad-word-sized operands:
//
......@@ -2413,6 +2450,14 @@ private:
registerModRM(reg, rm);
}
void oneByteOp8(OneByteOpcodeID opcode, int reg, RegisterID base, int offset)
{
m_buffer.ensureSpace(maxInstructionSize);
emitRexIf(byteRegRequiresRex(reg) || byteRegRequiresRex(base), reg, 0, base);
m_buffer.putByteUnchecked(opcode);
memoryModRM(reg, base, offset);
}
void oneByteOp8(OneByteOpcodeID opcode, int reg, RegisterID base, RegisterID index, int scale, int offset)
{
m_buffer.ensureSpace(maxInstructionSize);
......
......@@ -1841,9 +1841,6 @@ CodeBlock::CodeBlock(ScriptExecutable* ownerExecutable, UnlinkedCodeBlock* unlin
if (entry.isNull())
break;
// It's likely that we'll write to this var, so notify now and avoid the overhead of doing so at runtime.
entry.notifyWrite();
instructions[i + 0] = vm()->interpreter->getOpcode(op_init_global_const);
instructions[i + 1] = &m_globalObject->registerAt(entry.getIndex());
break;
......
......@@ -40,6 +40,7 @@ Watchpoint::~Watchpoint()
WatchpointSet::WatchpointSet(WatchpointState state)
: m_state(state)
, m_setIsNotEmpty(false)
{
}
......@@ -60,6 +61,7 @@ void WatchpointSet::add(Watchpoint* watchpoint)
if (!watchpoint)
return;
m_set.push(watchpoint);
m_setIsNotEmpty = true;
m_state = IsWatched;
}
......@@ -67,6 +69,7 @@ void WatchpointSet::fireAllSlow()
{
ASSERT(state() == IsWatched);
WTF::storeStoreFence();
fireAllWatchpoints();
m_state = IsInvalidated;
WTF::storeStoreFence();
......
......@@ -59,7 +59,18 @@ public:
WatchpointSet(WatchpointState);
~WatchpointSet(); // Note that this will not fire any of the watchpoints; if you need to know when a WatchpointSet dies then you need a separate mechanism for this.
WatchpointState state() const { return static_cast<WatchpointState>(m_state); }
// It is safe to call this from another thread. It may return an old
// state. Guarantees that if *first* read the state() of the thing being
// watched and it returned IsWatched and *second* you actually read its
// value then it's safe to assume that if the state being watched changes
// then also the watchpoint state() will change to IsInvalidated.
WatchpointState state() const
{
WTF::loadLoadFence();
WatchpointState result = static_cast<WatchpointState>(m_state);
WTF::loadLoadFence();
return result;
}
// It is safe to call this from another thread. It may return true
// even if the set actually had been invalidated, but that ought to happen
......@@ -69,7 +80,6 @@ public:
// issuing a load-load fence prior to querying the state.
bool isStillValid() const
{
WTF::loadLoadFence();
return state() != IsInvalidated;
}
// Like isStillValid(), may be called from another thread.
......@@ -97,8 +107,17 @@ public:
return;
fireAllSlow();
}
void notifyWrite()
{
if (state() == ClearWatchpoint)
startWatching();
else
fireAll();
}
int8_t* addressOfState() { return &m_state; }
int8_t* addressOfSetIsNotEmpty() { return &m_setIsNotEmpty; }
JS_EXPORT_PRIVATE void fireAllSlow(); // Call only if you've checked isWatched.
......@@ -109,6 +128,7 @@ private:
SentinelLinkedList<Watchpoint, BasicRawSentinelNode<Watchpoint>> m_set;
int8_t m_state;
int8_t m_setIsNotEmpty;
};
// InlineWatchpointSet is a low-overhead, non-copyable watchpoint set in which
......
......@@ -1511,6 +1511,7 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
break;
case PutGlobalVar:
case NotifyPutGlobalVar:
break;
case CheckHasInstance:
......
......@@ -516,7 +516,7 @@ private:
// constant folding. I.e. creating constants using this if we had constant
// field inference would be a bad idea, since the bytecode parser's folding
// doesn't handle liveness preservation.
Node* getJSConstantForValue(JSValue constantValue)
Node* getJSConstantForValue(JSValue constantValue, NodeFlags flags = NodeIsStaticConstant)
{
unsigned constantIndex;
if (!m_codeBlock->findConstant(constantValue, constantIndex)) {
......@@ -526,16 +526,17 @@ private:
ASSERT(m_constants.size() == m_codeBlock->numberOfConstantRegisters());
return getJSConstant(constantIndex);
return getJSConstant(constantIndex, flags);
}
Node* getJSConstant(unsigned constant)
Node* getJSConstant(unsigned constant, NodeFlags flags = NodeIsStaticConstant)
{
Node* node = m_constants[constant].asJSValue;
if (node)
return node;
Node* result = addToGraph(JSConstant, OpInfo(constant));
result->mergeFlags(flags);
m_constants[constant].asJSValue = result;
return result;
}
......@@ -3100,7 +3101,10 @@ bool ByteCodeParser::parseBlock(unsigned limit)
addToGraph(GlobalVarWatchpoint, OpInfo(operand), OpInfo(identifierNumber));
JSValue specificValue = globalObject->registerAt(entry.getIndex()).get();
set(VirtualRegister(dst), cellConstant(specificValue.asCell()));
if (specificValue.isCell())
set(VirtualRegister(dst), cellConstant(specificValue.asCell()));
else
set(VirtualRegister(dst), getJSConstantForValue(specificValue, 0));
break;
}
case ClosureVar:
......@@ -3123,12 +3127,13 @@ bool ByteCodeParser::parseBlock(unsigned limit)
ResolveType resolveType = ResolveModeAndType(currentInstruction[4].u.operand).type();
StringImpl* uid = m_graph.identifiers()[identifierNumber];
Structure* structure;
Structure* structure = 0;
WatchpointSet* watchpoints = 0;
uintptr_t operand;
{
ConcurrentJITLocker locker(m_inlineStackTop->m_profiledBlock->m_lock);
if (resolveType == GlobalVar || resolveType == GlobalVarWithVarInjectionChecks)
structure = 0;
watchpoints = currentInstruction[5].u.watchpointSet;
else
structure = currentInstruction[5].u.structure.get();
operand = reinterpret_cast<uintptr_t>(currentInstruction[6].u.pointer);
......@@ -3153,10 +3158,11 @@ bool ByteCodeParser::parseBlock(unsigned limit)
}
case GlobalVar:
case GlobalVarWithVarInjectionChecks: {
addToGraph(Phantom, get(VirtualRegister(scope)));
SymbolTableEntry entry = globalObject->symbolTable()->get(uid);
ASSERT(!entry.couldBeWatched() || !m_graph.watchpoints().isStillValid(entry.watchpointSet()));
ASSERT(watchpoints == entry.watchpointSet());
addToGraph(PutGlobalVar, OpInfo(operand), get(VirtualRegister(value)));
if (watchpoints->state() != IsInvalidated)
addToGraph(NotifyPutGlobalVar, OpInfo(operand), OpInfo(identifierNumber));
// Keep scope alive until after put.
addToGraph(Phantom, get(VirtualRegister(scope)));
break;
......
......@@ -142,6 +142,10 @@ void clobberize(Graph& graph, Node* node, ReadFunctor& read, WriteFunctor& write
case InvalidationPoint:
write(SideState);
return;
case NotifyPutGlobalVar:
write(Watchpoint_fire);
return;
case CreateActivation:
case CreateArguments:
......
......@@ -903,6 +903,7 @@ private:
case GetClosureVar:
case GetGlobalVar:
case PutGlobalVar:
case NotifyPutGlobalVar:
case GlobalVarWatchpoint:
case VarInjectionWatchpoint:
case AllocationProfileWatchpoint:
......
......@@ -359,7 +359,8 @@ struct Node {