Commit 0309686b authored by fpizlo@apple.com's avatar fpizlo@apple.com

Stores to local captured variables should be intercepted

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

Source/JavaScriptCore: 

Reviewed by Mark Hahnenberg.
        
Previously, in bytecode, you could assign to a captured variable just as you would
assign to any other kind of variable. This complicates closure variable constant
inference because we don't have any place where we can intercept stores to captured
variables in the LLInt.
        
This patch institutes a policy that only certain instructions can store to captured
variables. If you interpret those instructions and you are required to notifyWrite()
then you need to check if the relevant variable is captured. Those instructions are
tracked in CodeBlock.cpp's VerifyCapturedDef. The main one is simply op_captured_mov.
In the future, we'll probably modify those instructions to have a pointer directly to
the VariableWatchpointSet; but for now we just introduce the captured instructions as
placeholders.
        
In order to validate that the placeholders are inserted correctly, this patch improves
the CodeBlock validation to be able to inspect every def in the bytecode. To do that,
this patch refactors the liveness analysis' use/def calculator to be reusable; it now
takes a functor for each use or def.
        
In the process of refactoring the liveness analysis, I noticed that op_enter was
claiming to def all callee registers. That's wrong; it only defs the non-temporary
variables. Making that change revealed preexisting bugs in the liveness analysis, since
now the validator would pick up cases where the bytecode claimed to use a temporary and
the def calculator never noticed the definition (or the converse - where the bytecode
was actually not using a temporary but the liveness analysis thought that it was a
use). This patch fixes a few of those bugs.

* GNUmakefile.list.am:
* JavaScriptCore.vcxproj/JavaScriptCore.vcxproj:
* JavaScriptCore.xcodeproj/project.pbxproj:
* bytecode/BytecodeLivenessAnalysis.cpp:
(JSC::stepOverInstruction):
* bytecode/BytecodeUseDef.h: Added.
(JSC::computeUsesForBytecodeOffset):
(JSC::computeDefsForBytecodeOffset):
* bytecode/CodeBlock.cpp:
(JSC::CodeBlock::dumpBytecode):
(JSC::CodeBlock::isCaptured):
(JSC::CodeBlock::validate):
* bytecode/CodeBlock.h:
* bytecode/Opcode.h:
(JSC::padOpcodeName):
* bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::BytecodeGenerator):
(JSC::BytecodeGenerator::resolveCallee):
(JSC::BytecodeGenerator::emitMove):
(JSC::BytecodeGenerator::isCaptured):
(JSC::BytecodeGenerator::local):
(JSC::BytecodeGenerator::constLocal):
(JSC::BytecodeGenerator::emitNewFunction):
(JSC::BytecodeGenerator::emitLazyNewFunction):
(JSC::BytecodeGenerator::emitNewFunctionInternal):
* bytecompiler/BytecodeGenerator.h:
(JSC::Local::Local):
(JSC::Local::isCaptured):
(JSC::Local::captureMode):
(JSC::BytecodeGenerator::captureMode):
(JSC::BytecodeGenerator::emitNode):
(JSC::BytecodeGenerator::pushOptimisedForIn):
* bytecompiler/NodesCodegen.cpp:
(JSC::PostfixNode::emitResolve):
(JSC::PrefixNode::emitResolve):
(JSC::ReadModifyResolveNode::emitBytecode):
(JSC::AssignResolveNode::emitBytecode):
(JSC::ConstDeclNode::emitCodeSingle):
(JSC::ForInNode::emitBytecode):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::parseBlock):
* dfg/DFGCapabilities.cpp:
(JSC::DFG::capabilityLevel):
* jit/JIT.cpp:
(JSC::JIT::privateCompileMainPass):
* llint/LowLevelInterpreter32_64.asm:
* llint/LowLevelInterpreter64.asm:
* runtime/SymbolTable.h:
(JSC::SymbolTable::isCaptured):

LayoutTests: 

Reviewed by Mark Hahnenberg.

* js/regress/captured-assignments-expected.txt: Added.
* js/regress/captured-assignments.html: Added.
* js/regress/script-tests/captured-assignments.js: Added.



git-svn-id: http://svn.webkit.org/repository/webkit/trunk@159943 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent a4ea0663
2013-12-01 Filip Pizlo <fpizlo@apple.com>
Stores to local captured variables should be intercepted
https://bugs.webkit.org/show_bug.cgi?id=124883
Reviewed by Mark Hahnenberg.
* js/regress/captured-assignments-expected.txt: Added.
* js/regress/captured-assignments.html: Added.
* js/regress/script-tests/captured-assignments.js: Added.
2013-12-02 Lauro Neto <lauro.neto@openbossa.org>
[MediaStream] Update layout tests to newer spec.
......
JSRegress/captured-assignments
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/captured-assignments.js"></script>
<script src="resources/regress-post.js"></script>
<script src="../../resources/js-test-post.js"></script>
</body>
</html>
function foo() {
var x = 0;
++x;
for (var x in [1, 2, 3]) {
}
x = 1;
x++;
var y = x++;
var z = y++;
z = x++;
x += 2;
z -= 1;
x *= 2;
z <<= 1;
x |= 5;
return function() { return x++ + z; }
}
for (var i = 0; i < 100; ++i) {
if (foo()() != 17)
throw "Error";
}
2013-12-01 Filip Pizlo <fpizlo@apple.com>
Stores to local captured variables should be intercepted
https://bugs.webkit.org/show_bug.cgi?id=124883
Reviewed by Mark Hahnenberg.
Previously, in bytecode, you could assign to a captured variable just as you would
assign to any other kind of variable. This complicates closure variable constant
inference because we don't have any place where we can intercept stores to captured
variables in the LLInt.
This patch institutes a policy that only certain instructions can store to captured
variables. If you interpret those instructions and you are required to notifyWrite()
then you need to check if the relevant variable is captured. Those instructions are
tracked in CodeBlock.cpp's VerifyCapturedDef. The main one is simply op_captured_mov.
In the future, we'll probably modify those instructions to have a pointer directly to
the VariableWatchpointSet; but for now we just introduce the captured instructions as
placeholders.
In order to validate that the placeholders are inserted correctly, this patch improves
the CodeBlock validation to be able to inspect every def in the bytecode. To do that,
this patch refactors the liveness analysis' use/def calculator to be reusable; it now
takes a functor for each use or def.
In the process of refactoring the liveness analysis, I noticed that op_enter was
claiming to def all callee registers. That's wrong; it only defs the non-temporary
variables. Making that change revealed preexisting bugs in the liveness analysis, since
now the validator would pick up cases where the bytecode claimed to use a temporary and
the def calculator never noticed the definition (or the converse - where the bytecode
was actually not using a temporary but the liveness analysis thought that it was a
use). This patch fixes a few of those bugs.
* GNUmakefile.list.am:
* JavaScriptCore.vcxproj/JavaScriptCore.vcxproj:
* JavaScriptCore.xcodeproj/project.pbxproj:
* bytecode/BytecodeLivenessAnalysis.cpp:
(JSC::stepOverInstruction):
* bytecode/BytecodeUseDef.h: Added.
(JSC::computeUsesForBytecodeOffset):
(JSC::computeDefsForBytecodeOffset):
* bytecode/CodeBlock.cpp:
(JSC::CodeBlock::dumpBytecode):
(JSC::CodeBlock::isCaptured):
(JSC::CodeBlock::validate):
* bytecode/CodeBlock.h:
* bytecode/Opcode.h:
(JSC::padOpcodeName):
* bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::BytecodeGenerator):
(JSC::BytecodeGenerator::resolveCallee):
(JSC::BytecodeGenerator::emitMove):
(JSC::BytecodeGenerator::isCaptured):
(JSC::BytecodeGenerator::local):
(JSC::BytecodeGenerator::constLocal):
(JSC::BytecodeGenerator::emitNewFunction):
(JSC::BytecodeGenerator::emitLazyNewFunction):
(JSC::BytecodeGenerator::emitNewFunctionInternal):
* bytecompiler/BytecodeGenerator.h:
(JSC::Local::Local):
(JSC::Local::isCaptured):
(JSC::Local::captureMode):
(JSC::BytecodeGenerator::captureMode):
(JSC::BytecodeGenerator::emitNode):
(JSC::BytecodeGenerator::pushOptimisedForIn):
* bytecompiler/NodesCodegen.cpp:
(JSC::PostfixNode::emitResolve):
(JSC::PrefixNode::emitResolve):
(JSC::ReadModifyResolveNode::emitBytecode):
(JSC::AssignResolveNode::emitBytecode):
(JSC::ConstDeclNode::emitCodeSingle):
(JSC::ForInNode::emitBytecode):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::parseBlock):
* dfg/DFGCapabilities.cpp:
(JSC::DFG::capabilityLevel):
* jit/JIT.cpp:
(JSC::JIT::privateCompileMainPass):
* llint/LowLevelInterpreter32_64.asm:
* llint/LowLevelInterpreter64.asm:
* runtime/SymbolTable.h:
(JSC::SymbolTable::isCaptured):
2013-12-02 Filip Pizlo <fpizlo@apple.com>
Instead of watchpointing activation allocation, we should watchpoint entry into functions that have captured variables
......
......@@ -102,10 +102,11 @@ javascriptcore_sources += \
Source/JavaScriptCore/bytecode/ByValInfo.h \
Source/JavaScriptCore/bytecode/BytecodeBasicBlock.cpp \
Source/JavaScriptCore/bytecode/BytecodeBasicBlock.h \
Source/JavaScriptCore/bytecode/BytecodeConventions.h \
Source/JavaScriptCore/bytecode/BytecodeLivenessAnalysis.cpp \
Source/JavaScriptCore/bytecode/BytecodeLivenessAnalysis.h \
Source/JavaScriptCore/bytecode/BytecodeLivenessAnalysisInlines.h \
Source/JavaScriptCore/bytecode/BytecodeConventions.h \
Source/JavaScriptCore/bytecode/BytecodeUseDef.h \
Source/JavaScriptCore/bytecode/CallLinkInfo.cpp \
Source/JavaScriptCore/bytecode/CallLinkInfo.h \
Source/JavaScriptCore/bytecode/CallLinkStatus.cpp \
......
......@@ -751,6 +751,7 @@
<ClInclude Include="..\bytecode\BytecodeBasicBlock.h" />
<ClInclude Include="..\bytecode\BytecodeLivenessAnalysis.h" />
<ClInclude Include="..\bytecode\BytecodeLivenessAnalysisInline.h" />
<ClInclude Include="..\bytecode\BytecodeUseDef.h" />
<ClInclude Include="..\bytecode\CallLinkInfo.h" />
<ClInclude Include="..\bytecode\CallLinkStatus.h" />
<ClInclude Include="..\bytecode\CallReturnOffsetToBytecodeOffset.h" />
......
......@@ -289,6 +289,7 @@
0F8335B71639C1E6001443B5 /* ArrayAllocationProfile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0F8335B41639C1E3001443B5 /* ArrayAllocationProfile.cpp */; };
0F8335B81639C1EA001443B5 /* ArrayAllocationProfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F8335B51639C1E3001443B5 /* ArrayAllocationProfile.h */; settings = {ATTRIBUTES = (Private, ); }; };
0F8364B7164B0C110053329A /* DFGBranchDirection.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F8364B5164B0C0E0053329A /* DFGBranchDirection.h */; settings = {ATTRIBUTES = (Private, ); }; };
0F885E111849A3BE00F1E3FA /* BytecodeUseDef.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F885E101849A3BE00F1E3FA /* BytecodeUseDef.h */; settings = {ATTRIBUTES = (Private, ); }; };
0F8F2B95172E04A0007DBDA5 /* FTLLink.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0F8F2B93172E049E007DBDA5 /* FTLLink.cpp */; };
0F8F2B96172E04A3007DBDA5 /* FTLLink.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F8F2B94172E049E007DBDA5 /* FTLLink.h */; settings = {ATTRIBUTES = (Private, ); }; };
0F8F2B99172F04FF007DBDA5 /* DFGDesiredIdentifiers.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0F8F2B97172F04FD007DBDA5 /* DFGDesiredIdentifiers.cpp */; };
......@@ -1583,6 +1584,7 @@
0F8335B51639C1E3001443B5 /* ArrayAllocationProfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArrayAllocationProfile.h; sourceTree = "<group>"; };
0F8364B5164B0C0E0053329A /* DFGBranchDirection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DFGBranchDirection.h; path = dfg/DFGBranchDirection.h; sourceTree = "<group>"; };
0F85A31E16AB76AE0077571E /* DFGVariadicFunction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DFGVariadicFunction.h; path = dfg/DFGVariadicFunction.h; sourceTree = "<group>"; };
0F885E101849A3BE00F1E3FA /* BytecodeUseDef.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BytecodeUseDef.h; sourceTree = "<group>"; };
0F8F2B93172E049E007DBDA5 /* FTLLink.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = FTLLink.cpp; path = ftl/FTLLink.cpp; sourceTree = "<group>"; };
0F8F2B94172E049E007DBDA5 /* FTLLink.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FTLLink.h; path = ftl/FTLLink.h; sourceTree = "<group>"; };
0F8F2B97172F04FD007DBDA5 /* DFGDesiredIdentifiers.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = DFGDesiredIdentifiers.cpp; path = dfg/DFGDesiredIdentifiers.cpp; sourceTree = "<group>"; };
......@@ -4058,6 +4060,7 @@
C2FCAE0E17A9C24E0034C735 /* BytecodeLivenessAnalysis.cpp */,
C2FCAE0F17A9C24E0034C735 /* BytecodeLivenessAnalysis.h */,
0F666EBE183566F900D017F1 /* BytecodeLivenessAnalysisInlines.h */,
0F885E101849A3BE00F1E3FA /* BytecodeUseDef.h */,
0F8023E91613832300A0BA45 /* ByValInfo.h */,
0F0B83AE14BCF71400885B4F /* CallLinkInfo.cpp */,
0F0B83AF14BCF71400885B4F /* CallLinkInfo.h */,
......@@ -4269,6 +4272,7 @@
BC3135640F302FA3003DFD3A /* DebuggerActivation.h in Headers */,
BC18C3FB0E16F5CD00B34460 /* DebuggerCallFrame.h in Headers */,
0F136D4D174AD69E0075B354 /* DeferGC.h in Headers */,
0F885E111849A3BE00F1E3FA /* BytecodeUseDef.h in Headers */,
0FC712DF17CD877C008CC93C /* DeferredCompilationCallback.h in Headers */,
A77A423E17A0BBFD00A8DB81 /* DFGAbstractHeap.h in Headers */,
A704D90317A0BAA8006BA554 /* DFGAbstractInterpreter.h in Headers */,
......
This diff is collapsed.
......@@ -31,6 +31,7 @@
#include "CodeBlock.h"
#include "BytecodeGenerator.h"
#include "BytecodeUseDef.h"
#include "CallLinkStatus.h"
#include "DFGCapabilities.h"
#include "DFGCommon.h"
......@@ -762,6 +763,13 @@ void CodeBlock::dumpBytecode(PrintStream& out, ExecState* exec, const Instructio
out.printf("%s, %s", registerName(r0).data(), registerName(r1).data());
break;
}
case op_captured_mov: {
int r0 = (++it)->u.operand;
int r1 = (++it)->u.operand;
printLocationAndOp(out, exec, location, it, "captured_mov");
out.printf("%s, %s", registerName(r0).data(), registerName(r1).data());
break;
}
case op_not: {
printUnaryOp(out, exec, location, it, "not");
break;
......@@ -1213,6 +1221,14 @@ void CodeBlock::dumpBytecode(PrintStream& out, ExecState* exec, const Instructio
out.printf("%s, f%d, %s", registerName(r0).data(), f0, shouldCheck ? "<Checked>" : "<Unchecked>");
break;
}
case op_new_captured_func: {
int r0 = (++it)->u.operand;
int f0 = (++it)->u.operand;
int shouldCheck = (++it)->u.operand;
printLocationAndOp(out, exec, location, it, "new_captured_func");
out.printf("%s, f%d, %s", registerName(r0).data(), f0, shouldCheck ? "<Checked>" : "<Unchecked>");
break;
}
case op_new_func_exp: {
int r0 = (++it)->u.operand;
int f0 = (++it)->u.operand;
......@@ -2472,8 +2488,7 @@ bool CodeBlock::isCaptured(VirtualRegister operand, InlineCallFrame* inlineCallF
if (!symbolTable())
return false;
return operand.offset() <= symbolTable()->captureStart()
&& operand.offset() > symbolTable()->captureEnd();
return symbolTable()->isCaptured(operand.offset());
}
int CodeBlock::framePointerOffsetToGetActivationRegisters(int machineCaptureStart)
......@@ -3430,6 +3445,48 @@ String CodeBlock::nameForRegister(VirtualRegister virtualRegister)
return "";
}
namespace {
struct VerifyCapturedDef {
void operator()(CodeBlock* codeBlock, Instruction* instruction, OpcodeID opcodeID, int operand)
{
unsigned bytecodeOffset = instruction - codeBlock->instructions().begin();
if (codeBlock->isConstantRegisterIndex(operand)) {
codeBlock->beginValidationDidFail();
dataLog(" At bc#", bytecodeOffset, " encountered a definition of a constant.\n");
codeBlock->endValidationDidFail();
return;
}
switch (opcodeID) {
case op_enter:
case op_captured_mov:
case op_init_lazy_reg:
case op_create_arguments:
case op_new_captured_func:
return;
default:
break;
}
VirtualRegister virtualReg(operand);
if (!virtualReg.isLocal())
return;
if (codeBlock->captureCount() && codeBlock->symbolTable()->isCaptured(operand)) {
codeBlock->beginValidationDidFail();
dataLog(" At bc#", bytecodeOffset, " encountered invalid assignment to captured variable loc", virtualReg.toLocal(), ".\n");
codeBlock->endValidationDidFail();
return;
}
return;
}
};
} // anonymous namespace
void CodeBlock::validate()
{
BytecodeLivenessAnalysis liveness(this); // Compute directly from scratch so it doesn't effect CodeBlock footprint.
......@@ -3467,6 +3524,16 @@ void CodeBlock::validate()
}
}
}
for (unsigned bytecodeOffset = 0; bytecodeOffset < instructions().size();) {
Instruction* currentInstruction = instructions().begin() + bytecodeOffset;
OpcodeID opcodeID = m_vm->interpreter->getOpcodeID(currentInstruction->u.opcode);
VerifyCapturedDef verifyCapturedDef;
computeDefsForBytecodeOffset(this, bytecodeOffset, verifyCapturedDef);
bytecodeOffset += opcodeLength(opcodeID);
}
}
void CodeBlock::beginValidationDidFail()
......
......@@ -930,7 +930,12 @@ public:
bool m_allTransitionsHaveBeenMarked; // Initialized and used on every GC.
bool m_didFailFTLCompilation;
// Internal methods for use by validation code. It would be private if it wasn't
// for the fact that we use it from anonymous namespaces.
void beginValidationDidFail();
NO_RETURN_DUE_TO_CRASH void endValidationDidFail();
protected:
virtual void visitWeakReferences(SlotVisitor&) OVERRIDE;
virtual void finalizeUnconditionally() OVERRIDE;
......@@ -1031,9 +1036,6 @@ private:
m_rareData = adoptPtr(new RareData);
}
void beginValidationDidFail();
NO_RETURN_DUE_TO_CRASH void endValidationDidFail();
#if ENABLE(JIT)
void resetStubInternal(RepatchBuffer&, StructureStubInfo&);
void resetStubDuringGCInternal(RepatchBuffer&, StructureStubInfo&);
......
......@@ -55,6 +55,7 @@ namespace JSC {
macro(op_new_array_buffer, 5) \
macro(op_new_regexp, 3) \
macro(op_mov, 3) \
macro(op_captured_mov, 3) \
\
macro(op_not, 3) \
macro(op_eq, 4) \
......@@ -154,6 +155,7 @@ namespace JSC {
macro(op_switch_string, 4) \
\
macro(op_new_func, 4) \
macro(op_new_captured_func, 4) \
macro(op_new_func_exp, 3) \
macro(op_call, 8) /* has value profiling */ \
macro(op_call_eval, 8) /* has value profiling */ \
......
......@@ -307,7 +307,7 @@ BytecodeGenerator::BytecodeGenerator(VM& vm, FunctionBodyNode* functionBody, Unl
instructions().append(m_activationRegister->index());
}
m_functions.add(ident.impl());
emitNewFunction(addVar(ident, false), function);
emitNewFunction(addVar(ident, false), IsCaptured, function);
}
}
for (size_t i = 0; i < varStack.size(); ++i) {
......@@ -335,7 +335,7 @@ BytecodeGenerator::BytecodeGenerator(VM& vm, FunctionBodyNode* functionBody, Unl
// Don't lazily create functions that override the name 'arguments'
// as this would complicate lazy instantiation of actual arguments.
if (!canLazilyCreateFunctions || ident == propertyNames().arguments)
emitNewFunction(reg.get(), function);
emitNewFunction(reg.get(), NotCaptured, function);
else {
emitInitLazyRegister(reg.get());
m_lazyFunctions.set(reg->virtualRegister().toLocal(), function);
......@@ -475,7 +475,7 @@ RegisterID* BytecodeGenerator::resolveCallee(FunctionBodyNode* functionBodyNode)
return &m_calleeRegister;
// Move the callee into the captured section of the stack.
return emitMove(addVar(), &m_calleeRegister);
return emitMove(addVar(), IsCaptured, &m_calleeRegister);
}
void BytecodeGenerator::addCallee(FunctionBodyNode* functionBodyNode, RegisterID* calleeRegister)
......@@ -1000,16 +1000,21 @@ unsigned BytecodeGenerator::addRegExp(RegExp* r)
return m_codeBlock->addRegExp(r);
}
RegisterID* BytecodeGenerator::emitMove(RegisterID* dst, RegisterID* src)
RegisterID* BytecodeGenerator::emitMove(RegisterID* dst, CaptureMode captureMode, RegisterID* src)
{
m_staticPropertyAnalyzer.mov(dst->index(), src->index());
emitOpcode(op_mov);
emitOpcode(captureMode == IsCaptured ? op_captured_mov : op_mov);
instructions().append(dst->index());
instructions().append(src->index());
return dst;
}
RegisterID* BytecodeGenerator::emitMove(RegisterID* dst, RegisterID* src)
{
return emitMove(dst, captureMode(dst->index()), src);
}
RegisterID* BytecodeGenerator::emitUnaryOp(OpcodeID opcodeID, RegisterID* dst, RegisterID* src)
{
emitOpcode(opcodeID);
......@@ -1160,11 +1165,16 @@ RegisterID* BytecodeGenerator::emitLoadGlobalObject(RegisterID* dst)
return m_globalObjectRegister;
}
bool BytecodeGenerator::isCaptured(int operand)
{
return m_symbolTable && m_symbolTable->isCaptured(operand);
}
Local BytecodeGenerator::local(const Identifier& property)
{
if (property == propertyNames().thisIdentifier)
return Local(thisRegister(), ReadOnly);
return Local(thisRegister(), ReadOnly, NotCaptured);
if (property == propertyNames().arguments)
createArgumentsIfNecessary();
......@@ -1176,7 +1186,7 @@ Local BytecodeGenerator::local(const Identifier& property)
return Local();
RegisterID* local = createLazyRegisterIfNecessary(&registerFor(entry.getIndex()));
return Local(local, entry.getAttributes());
return Local(local, entry.getAttributes(), captureMode(local->index()));
}
Local BytecodeGenerator::constLocal(const Identifier& property)
......@@ -1189,7 +1199,7 @@ Local BytecodeGenerator::constLocal(const Identifier& property)
return Local();
RegisterID* local = createLazyRegisterIfNecessary(&registerFor(entry.getIndex()));
return Local(local, entry.getAttributes());
return Local(local, entry.getAttributes(), captureMode(local->index()));
}
void BytecodeGenerator::emitCheckHasInstance(RegisterID* dst, RegisterID* value, RegisterID* base, Label* target)
......@@ -1550,9 +1560,9 @@ RegisterID* BytecodeGenerator::emitNewArray(RegisterID* dst, ElementNode* elemen
return dst;
}
RegisterID* BytecodeGenerator::emitNewFunction(RegisterID* dst, FunctionBodyNode* function)
RegisterID* BytecodeGenerator::emitNewFunction(RegisterID* dst, CaptureMode captureMode, FunctionBodyNode* function)
{
return emitNewFunctionInternal(dst, m_codeBlock->addFunctionDecl(makeFunction(function)), false);
return emitNewFunctionInternal(dst, captureMode, m_codeBlock->addFunctionDecl(makeFunction(function)), false);
}
RegisterID* BytecodeGenerator::emitLazyNewFunction(RegisterID* dst, FunctionBodyNode* function)
......@@ -1560,13 +1570,13 @@ RegisterID* BytecodeGenerator::emitLazyNewFunction(RegisterID* dst, FunctionBody
FunctionOffsetMap::AddResult ptr = m_functionOffsets.add(function, 0);
if (ptr.isNewEntry)
ptr.iterator->value = m_codeBlock->addFunctionDecl(makeFunction(function));
return emitNewFunctionInternal(dst, ptr.iterator->value, true);
return emitNewFunctionInternal(dst, NotCaptured, ptr.iterator->value, true);
}
RegisterID* BytecodeGenerator::emitNewFunctionInternal(RegisterID* dst, unsigned index, bool doNullCheck)
RegisterID* BytecodeGenerator::emitNewFunctionInternal(RegisterID* dst, CaptureMode captureMode, unsigned index, bool doNullCheck)
{
createActivationIfNecessary();
emitOpcode(op_new_func);
emitOpcode(captureMode == IsCaptured ? op_new_captured_func : op_new_func);
instructions().append(dst->index());
instructions().append(index);
instructions().append(doNullCheck);
......
......@@ -114,6 +114,11 @@ namespace JSC {
TryData* tryData;
};
enum CaptureMode {
NotCaptured,
IsCaptured
};
class Local {
public:
Local()
......@@ -122,9 +127,10 @@ namespace JSC {
{
}
Local(RegisterID* local, unsigned attributes)
Local(RegisterID* local, unsigned attributes, CaptureMode captureMode)
: m_local(local)
, m_attributes(attributes)
, m_isCaptured(captureMode == IsCaptured)
{
}
......@@ -133,10 +139,14 @@ namespace JSC {
RegisterID* get() { return m_local; }
bool isReadOnly() { return m_attributes & ReadOnly; }
bool isCaptured() { return m_isCaptured; }
CaptureMode captureMode() { return isCaptured() ? IsCaptured : NotCaptured; }
private:
RegisterID* m_local;
unsigned m_attributes;
bool m_isCaptured;
};
struct TryRange {
......@@ -171,6 +181,9 @@ namespace JSC {
bool willResolveToArguments(const Identifier&);
RegisterID* uncheckedRegisterForArguments();
bool isCaptured(int operand);
CaptureMode captureMode(int operand) { return isCaptured(operand) ? IsCaptured : NotCaptured; }
Local local(const Identifier&);
Local constLocal(const Identifier&);
......@@ -231,6 +244,8 @@ namespace JSC {
{
// Node::emitCode assumes that dst, if provided, is either a local or a referenced temporary.
ASSERT(!dst || dst == ignoredResult() || !dst->isTemporary() || dst->refCount());
// Should never store directly into a captured variable.
ASSERT(!dst || dst == ignoredResult() || !isCaptured(dst->index()));
if (!m_vm->isSafeToRecurse()) {
emitThrowExpressionTooDeepException();
return;
......@@ -247,6 +262,8 @@ namespace JSC {
{
// Node::emitCode assumes that dst, if provided, is either a local or a referenced temporary.
ASSERT(!dst || dst == ignoredResult() || !dst->isTemporary() || dst->refCount());
// Should never store directly into a captured variable.
ASSERT(!dst || dst == ignoredResult() || !isCaptured(dst->index()));
if (!m_vm->isSafeToRecurse())
return emitThrowExpressionTooDeepException();
return n->emitBytecode(*this, dst);
......@@ -329,12 +346,13 @@ namespace JSC {
RegisterID* emitNewObject(RegisterID* dst);
RegisterID* emitNewArray(RegisterID* dst, ElementNode*, unsigned length); // stops at first elision
RegisterID* emitNewFunction(RegisterID* dst, FunctionBodyNode* body);
RegisterID* emitNewFunction(RegisterID* dst, CaptureMode, FunctionBodyNode*);
RegisterID* emitLazyNewFunction(RegisterID* dst, FunctionBodyNode* body);
RegisterID* emitNewFunctionInternal(RegisterID* dst, unsigned index, bool shouldNullCheck);
RegisterID* emitNewFunctionInternal(RegisterID* dst, CaptureMode, unsigned index, bool shouldNullCheck);
RegisterID* emitNewFunctionExpression(RegisterID* dst, FuncExprNode* func);
RegisterID* emitNewRegExp(RegisterID* dst, RegExp*);
RegisterID* emitMove(RegisterID* dst, CaptureMode, RegisterID* src);
RegisterID* emitMove(RegisterID* dst, RegisterID* src);
RegisterID* emitToNumber(RegisterID* dst, RegisterID* src) { return emitUnaryOp(op_to_number, dst, src); }
......@@ -420,9 +438,9 @@ namespace JSC {
void pushFinallyContext(StatementNode* finallyBlock);
void popFinallyContext();
void pushOptimisedForIn(RegisterID* expectedBase, RegisterID* iter, RegisterID* index, RegisterID* propertyRegister)
void pushOptimisedForIn(RegisterID* expectedSubscript, RegisterID* iter, RegisterID* index, RegisterID* propertyRegister)
{
ForInContext context = { expectedBase, iter, index, propertyRegister };
ForInContext context = { expectedSubscript, iter, index, propertyRegister };
m_forInContextStack.append(context);
}
......
......@@ -673,11 +673,22 @@ RegisterID* PostfixNode::emitResolve(BytecodeGenerator& generator, RegisterID* d
const Identifier& ident = resolve->identifier();
if (Local local = generator.local(ident)) {
RegisterID* localReg = local.get();
if (local.isReadOnly()) {
generator.emitReadOnlyExceptionIfNeeded();
local = Local(generator.emitMove(generator.tempDestination(dst), local.get()), 0);
localReg = generator.emitMove(generator.tempDestination(dst), localReg);
}
return emitPostIncOrDec(generator, generator.finalDestination(dst), local.get(), m_operator);
if (local.isCaptured()) {
RefPtr<RegisterID> tempDst = generator.finalDestination(dst);
ASSERT(dst != localReg);
RefPtr<RegisterID> tempDstSrc = generator.newTemporary();
generator.emitToNumber(tempDst.get(), localReg);
generator.emitMove(tempDstSrc.get(), localReg);
emitIncOrDec(generator, tempDstSrc.get(), m_operator);
generator.emitMove(localReg, tempDstSrc.get());
return tempDst.get();
}
return emitPostIncOrDec(generator, generator.finalDestination(dst), localReg, m_operator);
}
generator.emitExpressionInfo(divot(), divotStart(), divotEnd());
......@@ -838,12 +849,20 @@ RegisterID* PrefixNode::emitResolve(BytecodeGenerator& generator, RegisterID* ds
const Identifier& ident = resolve->identifier();
if (Local local = generator.local(ident)) {
RegisterID* localReg = local.get();
if (local.isReadOnly()) {
generator.emitReadOnlyExceptionIfNeeded();
local = Local(generator.emitMove(generator.tempDestination(dst), local.get()), 0);
localReg = generator.emitMove(generator.tempDestination(dst), localReg);
}
emitIncOrDec(generator, local.get(), m_operator);
return generator.moveToDestinationIfNeeded(dst, local.get());
if (local.isCaptured()) {
RefPtr<RegisterID> tempDst = generator.tempDestination(dst);
generator.emitMove(tempDst.get(), localReg);
emitIncOrDec(generator, tempDst.get(), m_operator);
generator.emitMove(localReg, tempDst.get());
return generator.moveToDestinationIfNeeded(dst, tempDst.get());
}
emitIncOrDec(generator, localReg, m_operator);
return generator.moveToDestinationIfNeeded(dst, localReg);
}
generator.emitExpressionInfo(divot(), divotStart(), divotEnd());
......@@ -1327,7 +1346,8 @@ RegisterID* ReadModifyResolveNode::emitBytecode(BytecodeGenerator& generator, Re
return emitReadModifyAssignment(generator, generator.finalDestination(dst), local.get(), m_right, m_operator, OperandTypes(ResultType::unknownType(), m_right->resultDescriptor()));
}
if (generator.leftHandSideNeedsCopy(m_rightHasAssignments, m_right->isPure(generator))) {
if (local.isCaptured()
|| generator.leftHandSideNeedsCopy(m_rightHasAssignments, m_right->isPure(generator))) {
RefPtr<RegisterID> result = generator.newTemporary();
generator.emitMove(result.get(), local.get());
emitReadModifyAssignment(generator, result.get(), result.get(), m_right, m_operator, OperandTypes(ResultType::unknownType(), m_right->resultDescriptor()));
......@@ -1356,6 +1376,12 @@ RegisterID* AssignResolveNode::emitBytecode(BytecodeGenerator& generator, Regist
generator.emitReadOnlyExceptionIfNeeded();
return generator.emitNode(dst, m_right);
}
if (local.isCaptured()) {
RefPtr<RegisterID> tempDst = generator.tempDestination(dst);
generator.emitNode(tempDst.get(), m_right);
generator.emitMove(local.get(), tempDst.get());
return generator.moveToDestinationIfNeeded(dst, tempDst.get());
}
RegisterID* result = generator.emitNode(local.get(), m_right);
return generator.moveToDestinationIfNeeded(dst, result);
}
......@@ -1451,11 +1477,17 @@ RegisterID* CommaNode::emitBytecode(BytecodeGenerator& generator, RegisterID* ds
RegisterID* ConstDeclNode::emitCodeSingle(BytecodeGenerator& generator)
{
// FIXME: This code does not match the behavior of const in Firefox.
if (RegisterID* local = generator.constLocal(m_ident).get()) {