diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog index 71bebe93122e20aec1bc461a2143d1009fd35fe3..0f55b7c5f8a7fad0cb5af645d295806ff349e27b 100644 --- a/LayoutTests/ChangeLog +++ b/LayoutTests/ChangeLog @@ -1,3 +1,14 @@ +2013-12-01 Filip Pizlo + + 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 [MediaStream] Update layout tests to newer spec. diff --git a/LayoutTests/js/regress/captured-assignments-expected.txt b/LayoutTests/js/regress/captured-assignments-expected.txt new file mode 100644 index 0000000000000000000000000000000000000000..85b09ce13df609bd329ed048a88f2ae25ccaf182 --- /dev/null +++ b/LayoutTests/js/regress/captured-assignments-expected.txt @@ -0,0 +1,10 @@ +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 + diff --git a/LayoutTests/js/regress/captured-assignments.html b/LayoutTests/js/regress/captured-assignments.html new file mode 100644 index 0000000000000000000000000000000000000000..f7c13059e50a76ac2a2610e191c7db86c53b82e9 --- /dev/null +++ b/LayoutTests/js/regress/captured-assignments.html @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/LayoutTests/js/regress/script-tests/captured-assignments.js b/LayoutTests/js/regress/script-tests/captured-assignments.js new file mode 100644 index 0000000000000000000000000000000000000000..4319c7a039cbc652a779736f165461ad33e97644 --- /dev/null +++ b/LayoutTests/js/regress/script-tests/captured-assignments.js @@ -0,0 +1,22 @@ +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"; +} diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog index db1c3ef5b8bb941d4784318859f1c70f8d2bc77d..b443be204c5c4eddd924b308555b74c7e05bc513 100644 --- a/Source/JavaScriptCore/ChangeLog +++ b/Source/JavaScriptCore/ChangeLog @@ -1,3 +1,86 @@ +2013-12-01 Filip Pizlo + + 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 Instead of watchpointing activation allocation, we should watchpoint entry into functions that have captured variables diff --git a/Source/JavaScriptCore/GNUmakefile.list.am b/Source/JavaScriptCore/GNUmakefile.list.am index c06132da6160ac5f9b730653d4b6effb9076c2ae..11bd095cca089abbee162cc336441c796a3f1f7c 100644 --- a/Source/JavaScriptCore/GNUmakefile.list.am +++ b/Source/JavaScriptCore/GNUmakefile.list.am @@ -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 \ diff --git a/Source/JavaScriptCore/JavaScriptCore.vcxproj/JavaScriptCore.vcxproj b/Source/JavaScriptCore/JavaScriptCore.vcxproj/JavaScriptCore.vcxproj index 635a54ded774b131feae22f31703eef7305b7aa9..c36a879113c780192b7f4ee9f1021f77e2696168 100644 --- a/Source/JavaScriptCore/JavaScriptCore.vcxproj/JavaScriptCore.vcxproj +++ b/Source/JavaScriptCore/JavaScriptCore.vcxproj/JavaScriptCore.vcxproj @@ -751,6 +751,7 @@ + diff --git a/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj b/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj index 5711d4e7e5582462bdc1a0cfe40c35c733494e52..665855f52b5b07a1e26d36e871c93eb40b65a9ea 100644 --- a/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj +++ b/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj @@ -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 = ""; }; 0F8364B5164B0C0E0053329A /* DFGBranchDirection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DFGBranchDirection.h; path = dfg/DFGBranchDirection.h; sourceTree = ""; }; 0F85A31E16AB76AE0077571E /* DFGVariadicFunction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DFGVariadicFunction.h; path = dfg/DFGVariadicFunction.h; sourceTree = ""; }; + 0F885E101849A3BE00F1E3FA /* BytecodeUseDef.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BytecodeUseDef.h; sourceTree = ""; }; 0F8F2B93172E049E007DBDA5 /* FTLLink.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = FTLLink.cpp; path = ftl/FTLLink.cpp; sourceTree = ""; }; 0F8F2B94172E049E007DBDA5 /* FTLLink.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FTLLink.h; path = ftl/FTLLink.h; sourceTree = ""; }; 0F8F2B97172F04FD007DBDA5 /* DFGDesiredIdentifiers.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = DFGDesiredIdentifiers.cpp; path = dfg/DFGDesiredIdentifiers.cpp; sourceTree = ""; }; @@ -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 */, diff --git a/Source/JavaScriptCore/bytecode/BytecodeLivenessAnalysis.cpp b/Source/JavaScriptCore/bytecode/BytecodeLivenessAnalysis.cpp index 28190cec44d1e47521c1677fa1107e69832a4f6e..926334c444c9c2f3dfcc95012cd0a4a03104d2d9 100644 --- a/Source/JavaScriptCore/bytecode/BytecodeLivenessAnalysis.cpp +++ b/Source/JavaScriptCore/bytecode/BytecodeLivenessAnalysis.cpp @@ -27,6 +27,7 @@ #include "BytecodeLivenessAnalysis.h" #include "BytecodeLivenessAnalysisInlines.h" +#include "BytecodeUseDef.h" #include "CodeBlock.h" #include "FullBytecodeLiveness.h" #include "PreciseJumpTargets.h" @@ -67,426 +68,26 @@ static void setForOperand(CodeBlock* codeBlock, FastBitVector& bits, int operand bits.set(virtualReg.toLocal() - codeBlock->captureCount()); } -static void computeUsesForBytecodeOffset(CodeBlock* codeBlock, unsigned bytecodeOffset, FastBitVector& uses) -{ - Interpreter* interpreter = codeBlock->vm()->interpreter; - Instruction* instructionsBegin = codeBlock->instructions().begin(); - Instruction* instruction = &instructionsBegin[bytecodeOffset]; - OpcodeID opcodeID = interpreter->getOpcodeID(instruction->u.opcode); - switch (opcodeID) { - // No uses. - case op_new_regexp: - case op_new_array_buffer: - case op_throw_static_error: - case op_debug: - case op_resolve_scope: - case op_pop_scope: - case op_jneq_ptr: - case op_new_func_exp: - case op_loop_hint: - case op_jmp: - case op_new_object: - case op_init_lazy_reg: - case op_get_callee: - case op_enter: - case op_touch_entry: - case op_catch: - return; - // First argument. - case op_new_func: - case op_create_activation: - case op_create_arguments: - case op_to_this: - case op_tear_off_activation: - case op_profile_will_call: - case op_profile_did_call: - case op_throw: - case op_push_with_scope: - case op_end: - case op_ret: - case op_jtrue: - case op_jfalse: - case op_jeq_null: - case op_jneq_null: - case op_dec: - case op_inc: { - if (isValidRegisterForLiveness(codeBlock, instruction[1].u.operand)) - setForOperand(codeBlock, uses, instruction[1].u.operand); - return; - } - // First and second arguments. - case op_del_by_id: - case op_ret_object_or_this: - case op_jlesseq: - case op_jgreater: - case op_jgreatereq: - case op_jnless: - case op_jnlesseq: - case op_jngreater: - case op_jngreatereq: - case op_jless: { - if (isValidRegisterForLiveness(codeBlock, instruction[1].u.operand)) - setForOperand(codeBlock, uses, instruction[1].u.operand); - if (isValidRegisterForLiveness(codeBlock, instruction[2].u.operand)) - setForOperand(codeBlock, uses, instruction[2].u.operand); - return; - } - // First, second, and third arguments. - case op_del_by_val: - case op_put_by_val_direct: - case op_put_by_val: { - if (isValidRegisterForLiveness(codeBlock, instruction[1].u.operand)) - setForOperand(codeBlock, uses, instruction[1].u.operand); - if (isValidRegisterForLiveness(codeBlock, instruction[2].u.operand)) - setForOperand(codeBlock, uses, instruction[2].u.operand); - if (isValidRegisterForLiveness(codeBlock, instruction[3].u.operand)) - setForOperand(codeBlock, uses, instruction[3].u.operand); - return; - } - // First and third arguments. - case op_put_by_index: - case op_put_by_id_replace: - case op_put_by_id_transition: - case op_put_by_id_transition_direct: - case op_put_by_id_transition_direct_out_of_line: - case op_put_by_id_transition_normal: - case op_put_by_id_transition_normal_out_of_line: - case op_put_by_id_generic: - case op_put_by_id_out_of_line: - case op_put_by_id: - case op_put_to_scope: { - if (isValidRegisterForLiveness(codeBlock, instruction[1].u.operand)) - setForOperand(codeBlock, uses, instruction[1].u.operand); - if (isValidRegisterForLiveness(codeBlock, instruction[3].u.operand)) - setForOperand(codeBlock, uses, instruction[3].u.operand); - return; - } - // First, third, and fourth arguments. - case op_put_getter_setter: { - if (isValidRegisterForLiveness(codeBlock, instruction[1].u.operand)) - setForOperand(codeBlock, uses, instruction[1].u.operand); - if (isValidRegisterForLiveness(codeBlock, instruction[3].u.operand)) - setForOperand(codeBlock, uses, instruction[3].u.operand); - if (isValidRegisterForLiveness(codeBlock, instruction[4].u.operand)) - setForOperand(codeBlock, uses, instruction[4].u.operand); - return; - } - // Second argument. - case op_init_global_const_nop: - case op_init_global_const: - case op_push_name_scope: - case op_get_from_scope: - case op_to_primitive: - case op_get_by_id: - case op_get_by_id_out_of_line: - case op_get_by_id_self: - case op_get_by_id_proto: - case op_get_by_id_chain: - case op_get_by_id_getter_self: - case op_get_by_id_getter_proto: - case op_get_by_id_getter_chain: - case op_get_by_id_custom_self: - case op_get_by_id_custom_proto: - case op_get_by_id_custom_chain: - case op_get_by_id_generic: - case op_get_array_length: - case op_get_string_length: - case op_get_arguments_length: - case op_typeof: - case op_is_undefined: - case op_is_boolean: - case op_is_number: - case op_is_string: - case op_is_object: - case op_is_function: - case op_to_number: - case op_negate: - case op_neq_null: - case op_eq_null: - case op_not: - case op_mov: - case op_new_array_with_size: - case op_create_this: { - if (isValidRegisterForLiveness(codeBlock, instruction[2].u.operand)) - setForOperand(codeBlock, uses, instruction[2].u.operand); - return; - } - // Second and third arguments. - case op_get_by_val: - case op_get_argument_by_val: - case op_in: - case op_instanceof: - case op_check_has_instance: - case op_add: - case op_mul: - case op_div: - case op_mod: - case op_sub: - case op_lshift: - case op_rshift: - case op_urshift: - case op_bitand: - case op_bitxor: - case op_bitor: - case op_less: - case op_lesseq: - case op_greater: - case op_greatereq: - case op_nstricteq: - case op_stricteq: - case op_neq: - case op_eq: { - if (isValidRegisterForLiveness(codeBlock, instruction[2].u.operand)) - setForOperand(codeBlock, uses, instruction[2].u.operand); - if (isValidRegisterForLiveness(codeBlock, instruction[3].u.operand)) - setForOperand(codeBlock, uses, instruction[3].u.operand); - return; - } - // Second, third, and fourth arguments. - case op_call_varargs: - case op_get_pnames: { - if (isValidRegisterForLiveness(codeBlock, instruction[2].u.operand)) - setForOperand(codeBlock, uses, instruction[2].u.operand); - if (isValidRegisterForLiveness(codeBlock, instruction[3].u.operand)) - setForOperand(codeBlock, uses, instruction[3].u.operand); - if (isValidRegisterForLiveness(codeBlock, instruction[4].u.operand)) - setForOperand(codeBlock, uses, instruction[4].u.operand); - return; - } - // Second, third, fourth, and fifth arguments. - case op_next_pname: { - if (isValidRegisterForLiveness(codeBlock, instruction[2].u.operand)) - setForOperand(codeBlock, uses, instruction[2].u.operand); - if (isValidRegisterForLiveness(codeBlock, instruction[3].u.operand)) - setForOperand(codeBlock, uses, instruction[3].u.operand); - if (isValidRegisterForLiveness(codeBlock, instruction[4].u.operand)) - setForOperand(codeBlock, uses, instruction[4].u.operand); - if (isValidRegisterForLiveness(codeBlock, instruction[5].u.operand)) - setForOperand(codeBlock, uses, instruction[5].u.operand); - return; - } - // Second, third, fourth, fifth, and sixth arguments. - case op_get_by_pname: { - if (isValidRegisterForLiveness(codeBlock, instruction[2].u.operand)) - setForOperand(codeBlock, uses, instruction[2].u.operand); - if (isValidRegisterForLiveness(codeBlock, instruction[3].u.operand)) - setForOperand(codeBlock, uses, instruction[3].u.operand); - if (isValidRegisterForLiveness(codeBlock, instruction[4].u.operand)) - setForOperand(codeBlock, uses, instruction[4].u.operand); - if (isValidRegisterForLiveness(codeBlock, instruction[5].u.operand)) - setForOperand(codeBlock, uses, instruction[5].u.operand); - if (isValidRegisterForLiveness(codeBlock, instruction[6].u.operand)) - setForOperand(codeBlock, uses, instruction[6].u.operand); - return; - } - // Third argument. - case op_switch_string: - case op_switch_char: - case op_switch_imm: { - if (isValidRegisterForLiveness(codeBlock, instruction[3].u.operand)) - setForOperand(codeBlock, uses, instruction[3].u.operand); - return; - } - // Variable number of arguments. - case op_new_array: - case op_strcat: { - int base = instruction[2].u.operand; - int count = instruction[3].u.operand; - for (int i = 0; i < count; i++) { - if (isValidRegisterForLiveness(codeBlock, base - i)) - setForOperand(codeBlock, uses, base - i); - } - return; - } - // Crazy stuff for calls. - case op_construct: - case op_call_eval: - case op_call: { - if (isValidRegisterForLiveness(codeBlock, instruction[2].u.operand)) - setForOperand(codeBlock, uses, instruction[2].u.operand); - int argCount = instruction[3].u.operand; - int registerOffset = -instruction[4].u.operand; - int lastArg = registerOffset + CallFrame::thisArgumentOffset(); - for (int i = opcodeID == op_construct ? 1 : 0; i < argCount; i++) { - if (isValidRegisterForLiveness(codeBlock, lastArg + i)) - setForOperand(codeBlock, uses, lastArg + i); - } - return; - } - // Special stuff for tear off arguments. - case op_tear_off_arguments: { - if (isValidRegisterForLiveness(codeBlock, instruction[1].u.operand)) - setForOperand(codeBlock, uses, instruction[1].u.operand); - if (isValidRegisterForLiveness(codeBlock, unmodifiedArgumentsRegister(VirtualRegister(instruction[1].u.operand)).offset())) - setForOperand(codeBlock, uses, unmodifiedArgumentsRegister(VirtualRegister(instruction[1].u.operand)).offset()); - if (isValidRegisterForLiveness(codeBlock, instruction[2].u.operand)) - setForOperand(codeBlock, uses, instruction[2].u.operand); - return; - } - default: - RELEASE_ASSERT_NOT_REACHED(); - break; - } -} +namespace { -static void computeDefsForBytecodeOffset(CodeBlock* codeBlock, unsigned bytecodeOffset, FastBitVector& defs) -{ - Interpreter* interpreter = codeBlock->vm()->interpreter; - Instruction* instructionsBegin = codeBlock->instructions().begin(); - Instruction* instruction = &instructionsBegin[bytecodeOffset]; - OpcodeID opcodeID = interpreter->getOpcodeID(instruction->u.opcode); - switch (opcodeID) { - // These don't define anything. - case op_init_global_const: - case op_init_global_const_nop: - case op_push_name_scope: - case op_push_with_scope: - case op_put_to_scope: - case op_pop_scope: - case op_end: - case op_profile_will_call: - case op_profile_did_call: - case op_throw: - case op_throw_static_error: - case op_debug: - case op_ret: - case op_ret_object_or_this: - case op_jmp: - case op_jtrue: - case op_jfalse: - case op_jeq_null: - case op_jneq_null: - case op_jneq_ptr: - case op_jless: - case op_jlesseq: - case op_jgreater: - case op_jgreatereq: - case op_jnless: - case op_jnlesseq: - case op_jngreater: - case op_jngreatereq: - case op_loop_hint: - case op_switch_imm: - case op_switch_char: - case op_switch_string: - case op_put_by_id: - case op_put_by_id_out_of_line: - case op_put_by_id_replace: - case op_put_by_id_transition: - case op_put_by_id_transition_direct: - case op_put_by_id_transition_direct_out_of_line: - case op_put_by_id_transition_normal: - case op_put_by_id_transition_normal_out_of_line: - case op_put_by_id_generic: - case op_put_getter_setter: - case op_put_by_val: - case op_put_by_val_direct: - case op_put_by_index: - case op_del_by_id: - case op_del_by_val: - case op_touch_entry: -#define LLINT_HELPER_OPCODES(opcode, length) case opcode: - FOR_EACH_LLINT_OPCODE_EXTENSION(LLINT_HELPER_OPCODES); -#undef LLINT_HELPER_OPCODES - return; - // These all have a single destination for the first argument. - case op_next_pname: - case op_get_pnames: - case op_resolve_scope: - case op_strcat: - case op_tear_off_activation: - case op_to_primitive: - case op_catch: - case op_create_this: - case op_new_array: - case op_new_array_buffer: - case op_new_array_with_size: - case op_new_regexp: - case op_new_func: - case op_new_func_exp: - case op_call_varargs: - case op_get_from_scope: - case op_call: - case op_call_eval: - case op_construct: - case op_get_by_id: - case op_get_by_id_out_of_line: - case op_get_by_id_self: - case op_get_by_id_proto: - case op_get_by_id_chain: - case op_get_by_id_getter_self: - case op_get_by_id_getter_proto: - case op_get_by_id_getter_chain: - case op_get_by_id_custom_self: - case op_get_by_id_custom_proto: - case op_get_by_id_custom_chain: - case op_get_by_id_generic: - case op_get_array_length: - case op_get_string_length: - case op_check_has_instance: - case op_instanceof: - case op_get_by_val: - case op_get_argument_by_val: - case op_get_by_pname: - case op_get_arguments_length: - case op_typeof: - case op_is_undefined: - case op_is_boolean: - case op_is_number: - case op_is_string: - case op_is_object: - case op_is_function: - case op_in: - case op_to_number: - case op_negate: - case op_add: - case op_mul: - case op_div: - case op_mod: - case op_sub: - case op_lshift: - case op_rshift: - case op_urshift: - case op_bitand: - case op_bitxor: - case op_bitor: - case op_inc: - case op_dec: - case op_eq: - case op_neq: - case op_stricteq: - case op_nstricteq: - case op_less: - case op_lesseq: - case op_greater: - case op_greatereq: - case op_neq_null: - case op_eq_null: - case op_not: - case op_mov: - case op_new_object: - case op_to_this: - case op_get_callee: - case op_init_lazy_reg: - case op_create_activation: - case op_create_arguments: { - if (isValidRegisterForLiveness(codeBlock, instruction[1].u.operand)) - setForOperand(codeBlock, defs, instruction[1].u.operand); - return; +class SetBit { +public: + SetBit(FastBitVector& bits) + : m_bits(bits) + { } - case op_tear_off_arguments: { - int operand = unmodifiedArgumentsRegister( - VirtualRegister(instruction[1].u.operand)).offset(); + + void operator()(CodeBlock* codeBlock, Instruction*, OpcodeID, int operand) + { if (isValidRegisterForLiveness(codeBlock, operand)) - setForOperand(codeBlock, defs, operand); - return; + setForOperand(codeBlock, m_bits, operand); } - case op_enter: { - defs.setAll(); - return; - } } - RELEASE_ASSERT_NOT_REACHED(); -} + +private: + FastBitVector& m_bits; +}; + +} // anonymous namespace static unsigned getLeaderOffsetForBasicBlock(RefPtr* basicBlock) { @@ -537,8 +138,10 @@ static void stepOverInstruction(CodeBlock* codeBlock, Vector +void computeUsesForBytecodeOffset( + CodeBlock* codeBlock, unsigned bytecodeOffset, Functor& functor) +{ + Interpreter* interpreter = codeBlock->vm()->interpreter; + Instruction* instructionsBegin = codeBlock->instructions().begin(); + Instruction* instruction = &instructionsBegin[bytecodeOffset]; + OpcodeID opcodeID = interpreter->getOpcodeID(instruction->u.opcode); + switch (opcodeID) { + // No uses. + case op_new_regexp: + case op_new_array_buffer: + case op_throw_static_error: + case op_debug: + case op_resolve_scope: + case op_pop_scope: + case op_jneq_ptr: + case op_new_func_exp: + case op_loop_hint: + case op_jmp: + case op_new_object: + case op_init_lazy_reg: + case op_get_callee: + case op_enter: + case op_catch: + case op_touch_entry: + return; + case op_new_func: + case op_new_captured_func: + case op_create_activation: + case op_create_arguments: + case op_to_this: + case op_tear_off_activation: + case op_profile_will_call: + case op_profile_did_call: + case op_throw: + case op_push_with_scope: + case op_end: + case op_ret: + case op_jtrue: + case op_jfalse: + case op_jeq_null: + case op_jneq_null: + case op_dec: + case op_inc: { + functor(codeBlock, instruction, opcodeID, instruction[1].u.operand); + return; + } + case op_ret_object_or_this: + case op_jlesseq: + case op_jgreater: + case op_jgreatereq: + case op_jnless: + case op_jnlesseq: + case op_jngreater: + case op_jngreatereq: + case op_jless: { + functor(codeBlock, instruction, opcodeID, instruction[1].u.operand); + functor(codeBlock, instruction, opcodeID, instruction[2].u.operand); + return; + } + case op_put_by_val_direct: + case op_put_by_val: { + functor(codeBlock, instruction, opcodeID, instruction[1].u.operand); + functor(codeBlock, instruction, opcodeID, instruction[2].u.operand); + functor(codeBlock, instruction, opcodeID, instruction[3].u.operand); + return; + } + case op_put_by_index: + case op_put_by_id_replace: + case op_put_by_id_transition: + case op_put_by_id_transition_direct: + case op_put_by_id_transition_direct_out_of_line: + case op_put_by_id_transition_normal: + case op_put_by_id_transition_normal_out_of_line: + case op_put_by_id_generic: + case op_put_by_id_out_of_line: + case op_put_by_id: + case op_put_to_scope: { + functor(codeBlock, instruction, opcodeID, instruction[1].u.operand); + functor(codeBlock, instruction, opcodeID, instruction[3].u.operand); + return; + } + case op_put_getter_setter: { + functor(codeBlock, instruction, opcodeID, instruction[1].u.operand); + functor(codeBlock, instruction, opcodeID, instruction[3].u.operand); + functor(codeBlock, instruction, opcodeID, instruction[4].u.operand); + return; + } + case op_init_global_const_nop: + case op_init_global_const: + case op_push_name_scope: + case op_get_from_scope: + case op_to_primitive: + case op_get_by_id: + case op_get_by_id_out_of_line: + case op_get_by_id_self: + case op_get_by_id_proto: + case op_get_by_id_chain: + case op_get_by_id_getter_self: + case op_get_by_id_getter_proto: + case op_get_by_id_getter_chain: + case op_get_by_id_custom_self: + case op_get_by_id_custom_proto: + case op_get_by_id_custom_chain: + case op_get_by_id_generic: + case op_get_array_length: + case op_get_string_length: + case op_get_arguments_length: + case op_typeof: + case op_is_undefined: + case op_is_boolean: + case op_is_number: + case op_is_string: + case op_is_object: + case op_is_function: + case op_to_number: + case op_negate: + case op_neq_null: + case op_eq_null: + case op_not: + case op_mov: + case op_captured_mov: + case op_new_array_with_size: + case op_create_this: + case op_get_pnames: + case op_del_by_id: { + functor(codeBlock, instruction, opcodeID, instruction[2].u.operand); + return; + } + case op_get_by_val: + case op_get_argument_by_val: + case op_in: + case op_instanceof: + case op_check_has_instance: + case op_add: + case op_mul: + case op_div: + case op_mod: + case op_sub: + case op_lshift: + case op_rshift: + case op_urshift: + case op_bitand: + case op_bitxor: + case op_bitor: + case op_less: + case op_lesseq: + case op_greater: + case op_greatereq: + case op_nstricteq: + case op_stricteq: + case op_neq: + case op_eq: + case op_del_by_val: { + functor(codeBlock, instruction, opcodeID, instruction[2].u.operand); + functor(codeBlock, instruction, opcodeID, instruction[3].u.operand); + return; + } + case op_call_varargs: { + functor(codeBlock, instruction, opcodeID, instruction[2].u.operand); + functor(codeBlock, instruction, opcodeID, instruction[3].u.operand); + functor(codeBlock, instruction, opcodeID, instruction[4].u.operand); + return; + } + case op_next_pname: { + functor(codeBlock, instruction, opcodeID, instruction[2].u.operand); + functor(codeBlock, instruction, opcodeID, instruction[3].u.operand); + functor(codeBlock, instruction, opcodeID, instruction[4].u.operand); + functor(codeBlock, instruction, opcodeID, instruction[5].u.operand); + return; + } + case op_get_by_pname: { + functor(codeBlock, instruction, opcodeID, instruction[2].u.operand); + functor(codeBlock, instruction, opcodeID, instruction[3].u.operand); + functor(codeBlock, instruction, opcodeID, instruction[4].u.operand); + functor(codeBlock, instruction, opcodeID, instruction[5].u.operand); + functor(codeBlock, instruction, opcodeID, instruction[6].u.operand); + return; + } + case op_switch_string: + case op_switch_char: + case op_switch_imm: { + functor(codeBlock, instruction, opcodeID, instruction[3].u.operand); + return; + } + case op_new_array: + case op_strcat: { + int base = instruction[2].u.operand; + int count = instruction[3].u.operand; + for (int i = 0; i < count; i++) + functor(codeBlock, instruction, opcodeID, base - i); + return; + } + case op_construct: + case op_call_eval: + case op_call: { + functor(codeBlock, instruction, opcodeID, instruction[2].u.operand); + int argCount = instruction[3].u.operand; + int registerOffset = -instruction[4].u.operand; + int lastArg = registerOffset + CallFrame::thisArgumentOffset(); + for (int i = opcodeID == op_construct ? 1 : 0; i < argCount; i++) + functor(codeBlock, instruction, opcodeID, lastArg + i); + return; + } + case op_tear_off_arguments: { + functor(codeBlock, instruction, opcodeID, instruction[1].u.operand); + functor(codeBlock, instruction, opcodeID, unmodifiedArgumentsRegister(VirtualRegister(instruction[1].u.operand)).offset()); + functor(codeBlock, instruction, opcodeID, instruction[2].u.operand); + return; + } + default: + RELEASE_ASSERT_NOT_REACHED(); + break; + } +} + +template +void computeDefsForBytecodeOffset(CodeBlock* codeBlock, unsigned bytecodeOffset, Functor& functor) +{ + Interpreter* interpreter = codeBlock->vm()->interpreter; + Instruction* instructionsBegin = codeBlock->instructions().begin(); + Instruction* instruction = &instructionsBegin[bytecodeOffset]; + OpcodeID opcodeID = interpreter->getOpcodeID(instruction->u.opcode); + switch (opcodeID) { + // These don't define anything. + case op_init_global_const: + case op_init_global_const_nop: + case op_push_name_scope: + case op_push_with_scope: + case op_put_to_scope: + case op_pop_scope: + case op_end: + case op_profile_will_call: + case op_profile_did_call: + case op_throw: + case op_throw_static_error: + case op_debug: + case op_ret: + case op_ret_object_or_this: + case op_jmp: + case op_jtrue: + case op_jfalse: + case op_jeq_null: + case op_jneq_null: + case op_jneq_ptr: + case op_jless: + case op_jlesseq: + case op_jgreater: + case op_jgreatereq: + case op_jnless: + case op_jnlesseq: + case op_jngreater: + case op_jngreatereq: + case op_loop_hint: + case op_switch_imm: + case op_switch_char: + case op_switch_string: + case op_put_by_id: + case op_put_by_id_out_of_line: + case op_put_by_id_replace: + case op_put_by_id_transition: + case op_put_by_id_transition_direct: + case op_put_by_id_transition_direct_out_of_line: + case op_put_by_id_transition_normal: + case op_put_by_id_transition_normal_out_of_line: + case op_put_by_id_generic: + case op_put_getter_setter: + case op_put_by_val: + case op_put_by_val_direct: + case op_put_by_index: + case op_tear_off_arguments: + case op_touch_entry: +#define LLINT_HELPER_OPCODES(opcode, length) case opcode: + FOR_EACH_LLINT_OPCODE_EXTENSION(LLINT_HELPER_OPCODES); +#undef LLINT_HELPER_OPCODES + return; + // These all have a single destination for the first argument. + case op_next_pname: + case op_resolve_scope: + case op_strcat: + case op_tear_off_activation: + case op_to_primitive: + case op_catch: + case op_create_this: + case op_new_array: + case op_new_array_buffer: + case op_new_array_with_size: + case op_new_regexp: + case op_new_func: + case op_new_captured_func: + case op_new_func_exp: + case op_call_varargs: + case op_get_from_scope: + case op_call: + case op_call_eval: + case op_construct: + case op_get_by_id: + case op_get_by_id_out_of_line: + case op_get_by_id_self: + case op_get_by_id_proto: + case op_get_by_id_chain: + case op_get_by_id_getter_self: + case op_get_by_id_getter_proto: + case op_get_by_id_getter_chain: + case op_get_by_id_custom_self: + case op_get_by_id_custom_proto: + case op_get_by_id_custom_chain: + case op_get_by_id_generic: + case op_get_array_length: + case op_get_string_length: + case op_check_has_instance: + case op_instanceof: + case op_get_by_val: + case op_get_argument_by_val: + case op_get_by_pname: + case op_get_arguments_length: + case op_typeof: + case op_is_undefined: + case op_is_boolean: + case op_is_number: + case op_is_string: + case op_is_object: + case op_is_function: + case op_in: + case op_to_number: + case op_negate: + case op_add: + case op_mul: + case op_div: + case op_mod: + case op_sub: + case op_lshift: + case op_rshift: + case op_urshift: + case op_bitand: + case op_bitxor: + case op_bitor: + case op_inc: + case op_dec: + case op_eq: + case op_neq: + case op_stricteq: + case op_nstricteq: + case op_less: + case op_lesseq: + case op_greater: + case op_greatereq: + case op_neq_null: + case op_eq_null: + case op_not: + case op_mov: + case op_captured_mov: + case op_new_object: + case op_to_this: + case op_get_callee: + case op_init_lazy_reg: + case op_create_activation: + case op_create_arguments: + case op_del_by_id: + case op_del_by_val: { + functor(codeBlock, instruction, opcodeID, instruction[1].u.operand); + return; + } + case op_get_pnames: { + functor(codeBlock, instruction, opcodeID, instruction[1].u.operand); + functor(codeBlock, instruction, opcodeID, instruction[3].u.operand); + functor(codeBlock, instruction, opcodeID, instruction[4].u.operand); + return; + } + case op_enter: { + for (unsigned i = codeBlock->m_numVars; i--;) + functor(codeBlock, instruction, opcodeID, virtualRegisterForLocal(i).offset()); + return; + } } +} + +} // namespace JSC + +#endif // BytecodeUseDef_h + diff --git a/Source/JavaScriptCore/bytecode/CodeBlock.cpp b/Source/JavaScriptCore/bytecode/CodeBlock.cpp index eceff1d8f30a27cfc870e5bd2c522b15e36ac032..cb80548636533ddfe132cac15065450cd5ea9274 100644 --- a/Source/JavaScriptCore/bytecode/CodeBlock.cpp +++ b/Source/JavaScriptCore/bytecode/CodeBlock.cpp @@ -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 ? "" : ""); 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 ? "" : ""); + 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() diff --git a/Source/JavaScriptCore/bytecode/CodeBlock.h b/Source/JavaScriptCore/bytecode/CodeBlock.h index f5a4f50830aeaa4b8b43dc39cebd8a03cbda4e99..99f1804d2da8500dbc10ee24a7e382177b43ae32 100644 --- a/Source/JavaScriptCore/bytecode/CodeBlock.h +++ b/Source/JavaScriptCore/bytecode/CodeBlock.h @@ -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&); diff --git a/Source/JavaScriptCore/bytecode/Opcode.h b/Source/JavaScriptCore/bytecode/Opcode.h index 97af6bd2065297551df359ea5ac7945348abe93c..347f2bd517231d408b9e1f39433c3e05aa178a49 100644 --- a/Source/JavaScriptCore/bytecode/Opcode.h +++ b/Source/JavaScriptCore/bytecode/Opcode.h @@ -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 */ \ diff --git a/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp b/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp index 07a6e49833654e4e4c4ddc76de6cd098baba50d7..a22b17a4a94f30f5fc388dc9fa80d432198253a6 100644 --- a/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp +++ b/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp @@ -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(®isterFor(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(®isterFor(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); diff --git a/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h b/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h index 8c31271037f547a4c983204d2e16fce3e29b5ed7..71404c9992efc03a5bcd98e6037bb1c51c271de9 100644 --- a/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h +++ b/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h @@ -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); } diff --git a/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp b/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp index d62b6d9f4c0f0278a253c9460b63d33a27fced1a..2e5fb4551ec10f6ff918741c0b6f2fb204ab63ee 100644 --- a/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp +++ b/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp @@ -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 tempDst = generator.finalDestination(dst); + ASSERT(dst != localReg); + RefPtr 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 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 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 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()) { + if (Local local = generator.constLocal(m_ident)) { if (!m_init) - return local; + return local.get(); - return generator.emitNode(local, m_init); + if (local.isCaptured()) { + RefPtr tempDst = generator.newTemporary(); + generator.emitNode(tempDst.get(), m_init); + return generator.emitMove(local.get(), tempDst.get()); + } + + return generator.emitNode(local.get(), m_init); } RefPtr value = m_init ? generator.emitNode(m_init) : generator.emitLoad(0, jsUndefined()); @@ -1730,8 +1762,7 @@ void ForInNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst) if (m_lexpr->isResolveNode()) { const Identifier& ident = static_cast(m_lexpr)->identifier(); Local local = generator.local(ident); - propertyName = local.get(); - if (!propertyName) { + if (!local.get()) { propertyName = generator.newTemporary(); RefPtr protect = propertyName; if (generator.isStrictMode()) @@ -1740,8 +1771,10 @@ void ForInNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst) generator.emitExpressionInfo(divot(), divotStart(), divotEnd()); generator.emitPutToScope(scope, ident, propertyName, generator.isStrictMode() ? ThrowIfNotFound : DoNotThrowIfNotFound); } else { - expectedSubscript = generator.emitMove(generator.newTemporary(), propertyName); - generator.pushOptimisedForIn(expectedSubscript.get(), iter.get(), i.get(), propertyName); + expectedSubscript = generator.newTemporary(); + propertyName = expectedSubscript.get(); + generator.emitMove(local.get(), propertyName); + generator.pushOptimisedForIn(expectedSubscript.get(), iter.get(), i.get(), local.get()); optimizedForinAccess = true; } } else if (m_lexpr->isDotAccessorNode()) { @@ -1771,7 +1804,7 @@ void ForInNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst) Identifier ident = simpleBinding->boundProperty(); Local local = generator.local(ident); propertyName = local.get(); - if (!propertyName) + if (!propertyName || local.isCaptured()) goto genericBinding; expectedSubscript = generator.emitMove(generator.newTemporary(), propertyName); generator.pushOptimisedForIn(expectedSubscript.get(), iter.get(), i.get(), propertyName); diff --git a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp index 7807686be4081179ca41c2b92c2fbcd1fe808a8a..2f66a1503360c509471fed280e0ac51fe4a045c1 100644 --- a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp +++ b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp @@ -2146,7 +2146,8 @@ bool ByteCodeParser::parseBlock(unsigned limit) addToGraph(Breakpoint); NEXT_OPCODE(op_debug); #endif - case op_mov: { + case op_mov: + case op_captured_mov: { Node* op = get(VirtualRegister(currentInstruction[2].u.operand)); set(VirtualRegister(currentInstruction[1].u.operand), op); NEXT_OPCODE(op_mov); @@ -3226,7 +3227,8 @@ bool ByteCodeParser::parseBlock(unsigned limit) NEXT_OPCODE(op_get_argument_by_val); } - case op_new_func: { + case op_new_func: + case op_new_captured_func: { if (!currentInstruction[3].u.operand) { set(VirtualRegister(currentInstruction[1].u.operand), addToGraph(NewFunctionNoCheck, OpInfo(currentInstruction[2].u.operand))); diff --git a/Source/JavaScriptCore/dfg/DFGCapabilities.cpp b/Source/JavaScriptCore/dfg/DFGCapabilities.cpp index ccc78a8cf0cebee9668bd2b693097976cae5fba8..2d3c7ebb09197ad64c7198feca89a31b81eabd6e 100644 --- a/Source/JavaScriptCore/dfg/DFGCapabilities.cpp +++ b/Source/JavaScriptCore/dfg/DFGCapabilities.cpp @@ -103,6 +103,7 @@ CapabilityLevel capabilityLevel(OpcodeID opcodeID, CodeBlock* codeBlock, Instruc case op_debug: #endif case op_mov: + case op_captured_mov: case op_check_has_instance: case op_instanceof: case op_is_undefined: @@ -202,6 +203,7 @@ CapabilityLevel capabilityLevel(OpcodeID opcodeID, CodeBlock* codeBlock, Instruc case op_create_activation: case op_tear_off_activation: case op_new_func: + case op_new_captured_func: case op_new_func_exp: case op_switch_string: // Don't inline because we don't want to copy string tables in the concurrent JIT. return CanCompile; diff --git a/Source/JavaScriptCore/jit/JIT.cpp b/Source/JavaScriptCore/jit/JIT.cpp index e72214c94045354b9c1a10406c0c8e22ba6f4e1f..791ea0013effd292b2f02177cb30db7417838b81 100644 --- a/Source/JavaScriptCore/jit/JIT.cpp +++ b/Source/JavaScriptCore/jit/JIT.cpp @@ -226,6 +226,7 @@ void JIT::privateCompileMainPass() DEFINE_OP(op_loop_hint) DEFINE_OP(op_lshift) DEFINE_OP(op_mod) + case op_captured_mov: DEFINE_OP(op_mov) DEFINE_OP(op_mul) DEFINE_OP(op_negate) @@ -234,6 +235,7 @@ void JIT::privateCompileMainPass() DEFINE_OP(op_new_array) DEFINE_OP(op_new_array_with_size) DEFINE_OP(op_new_array_buffer) + case op_new_captured_func: DEFINE_OP(op_new_func) DEFINE_OP(op_new_func_exp) DEFINE_OP(op_new_object) diff --git a/Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm b/Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm index a7cafbe6f76c61a1bc87d5eead3b0d7a740f0556..48970ea0e9e05e5cf9cef8e21eeb96121c2ac763 100644 --- a/Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm +++ b/Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm @@ -542,6 +542,16 @@ _llint_op_mov: dispatch(3) +_llint_op_captured_mov: + traceExecution() + loadi 8[PC], t1 + loadi 4[PC], t0 + loadConstantOrVariable(t1, t2, t3) + storei t2, TagOffset[cfr, t0, 8] + storei t3, PayloadOffset[cfr, t0, 8] + dispatch(3) + + _llint_op_not: traceExecution() loadi 8[PC], t0 @@ -1662,6 +1672,17 @@ _llint_op_new_func: dispatch(4) +_llint_op_new_captured_func: + traceExecution() + btiz 12[PC], .opNewCapturedFuncUnchecked + loadi 4[PC], t1 + bineq TagOffset[cfr, t1, 8], EmptyValueTag, .opNewCapturedFuncDone +.opNewCapturedFuncUnchecked: + callSlowPath(_llint_slow_path_new_func) +.opNewCapturedFuncDone: + dispatch(4) + + macro arrayProfileForCall() if VALUE_PROFILER loadi 16[PC], t3 diff --git a/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm b/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm index a06e9a2337c4b51adac64be42e6a354c062afd63..fdbec8dc3fa7c9ba82a8d9018acaddf15c20871f 100644 --- a/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm +++ b/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm @@ -418,6 +418,15 @@ _llint_op_mov: dispatch(3) +_llint_op_captured_mov: + traceExecution() + loadisFromInstruction(2, t1) + loadisFromInstruction(1, t0) + loadConstantOrVariable(t1, t2) + storeq t2, [cfr, t0, 8] + dispatch(3) + + _llint_op_not: traceExecution() loadisFromInstruction(2, t0) @@ -1514,6 +1523,18 @@ _llint_op_new_func: dispatch(4) +_llint_op_new_captured_func: + traceExecution() + loadisFromInstruction(3, t2) + btiz t2, .opNewCapturedFuncUnchecked + loadisFromInstruction(1, t1) + btqnz [cfr, t1, 8], .opNewCapturedFuncDone +.opNewCapturedFuncUnchecked: + callSlowPath(_llint_slow_path_new_func) +.opNewCapturedFuncDone: + dispatch(4) + + macro arrayProfileForCall() if VALUE_PROFILER loadisFromInstruction(4, t3) diff --git a/Source/JavaScriptCore/runtime/SymbolTable.h b/Source/JavaScriptCore/runtime/SymbolTable.h index 9a92146b3d48bebdf270bfff2eb9222be837242a..0a15f9da17d1b240db11e71745220b2f13404e1e 100644 --- a/Source/JavaScriptCore/runtime/SymbolTable.h +++ b/Source/JavaScriptCore/runtime/SymbolTable.h @@ -453,6 +453,11 @@ public: void setCaptureEnd(int captureEnd) { m_captureEnd = captureEnd; } int captureCount() const { return -(m_captureEnd - m_captureStart); } + + bool isCaptured(int operand) + { + return operand <= captureStart() && operand > captureEnd(); + } int parameterCount() { return m_parameterCountIncludingThis - 1; } int parameterCountIncludingThis() { return m_parameterCountIncludingThis; }