+2008-09-14 Cameron Zwarich <cwzwarich@uwaterloo.ca>
+
+ Reviewed by Maciej Stachowiak.
+
+ Bug 20827: the 'typeof' operator is slow
+ <https://bugs.webkit.org/show_bug.cgi?id=20827>
+
+ Optimize the 'typeof' operator when its result is compared to a constant
+ string.
+
+ This is a 5.5% speedup on the V8 Earley-Boyer test.
+
+ * VM/CTI.cpp:
+ (JSC::CTI::privateCompileMainPass):
+ * VM/CodeBlock.cpp:
+ (JSC::CodeBlock::dump):
+ * VM/CodeGenerator.cpp:
+ (JSC::CodeGenerator::emitEqualityOp):
+ * VM/CodeGenerator.h:
+ * VM/Machine.cpp:
+ (JSC::jsIsObjectType):
+ (JSC::jsIsFunctionType):
+ (JSC::Machine::privateExecute):
+ (JSC::Machine::cti_op_is_undefined):
+ (JSC::Machine::cti_op_is_boolean):
+ (JSC::Machine::cti_op_is_number):
+ (JSC::Machine::cti_op_is_string):
+ (JSC::Machine::cti_op_is_object):
+ (JSC::Machine::cti_op_is_function):
+ * VM/Machine.h:
+ * VM/Opcode.h:
+ * kjs/nodes.cpp:
+ (JSC::BinaryOpNode::emitCode):
+ (JSC::EqualNode::emitCode):
+ (JSC::StrictEqualNode::emitCode):
+ * kjs/nodes.h:
+
2008-09-14 Sam Weinig <sam@webkit.org>
Reviewed by Cameron Zwarich.
break; \
}
+#define CTI_COMPILE_UNARY_OP(name) \
+ case name: { \
+ emitGetPutArg(instruction[i + 2].u.operand, 0, X86::ecx); \
+ emitCall(i, Machine::cti_##name); \
+ emitPutResult(instruction[i + 1].u.operand); \
+ i += 3; \
+ break; \
+ }
+
#if ENABLE(SAMPLING_TOOL)
OpcodeID currentOpcodeID = static_cast<OpcodeID>(-1);
#endif
i += 1;
break;
}
- case op_typeof: {
- emitGetPutArg(instruction[i + 2].u.operand, 0, X86::ecx);
- emitCall(i, Machine::cti_op_typeof);
- emitPutResult(instruction[i + 1].u.operand);
- i += 3;
- break;
- }
+ CTI_COMPILE_UNARY_OP(op_typeof)
+ CTI_COMPILE_UNARY_OP(op_is_undefined)
+ CTI_COMPILE_UNARY_OP(op_is_boolean)
+ CTI_COMPILE_UNARY_OP(op_is_number)
+ CTI_COMPILE_UNARY_OP(op_is_string)
+ CTI_COMPILE_UNARY_OP(op_is_object)
+ CTI_COMPILE_UNARY_OP(op_is_function)
CTI_COMPILE_BINARY_OP(op_stricteq)
CTI_COMPILE_BINARY_OP(op_nstricteq)
case op_to_jsnumber: {
printUnaryOp(location, it, "typeof");
break;
}
+ case op_is_undefined: {
+ printUnaryOp(location, it, "is_undefined");
+ break;
+ }
+ case op_is_boolean: {
+ printUnaryOp(location, it, "is_boolean");
+ break;
+ }
+ case op_is_number: {
+ printUnaryOp(location, it, "is_number");
+ break;
+ }
+ case op_is_string: {
+ printUnaryOp(location, it, "is_string");
+ break;
+ }
+ case op_is_object: {
+ printUnaryOp(location, it, "is_object");
+ break;
+ }
+ case op_is_function: {
+ printUnaryOp(location, it, "is_function");
+ break;
+ }
case op_in: {
printBinaryOp(location, it, "in");
break;
return dst;
}
+RegisterID* CodeGenerator::emitEqualityOp(OpcodeID opcode, RegisterID* dst, RegisterID* src1, RegisterID* src2)
+{
+ if (m_lastOpcodeID == op_typeof) {
+ int dstIndex;
+ int srcIndex;
+
+ retrieveLastUnaryOp(dstIndex, srcIndex);
+
+ if (src1->index() == dstIndex
+ && src1->isTemporary()
+ && static_cast<unsigned>(src2->index()) < m_codeBlock->constantRegisters.size()
+ && m_codeBlock->constantRegisters[src2->index()].jsValue(globalExec())->isString()) {
+ const UString& value = static_cast<JSString*>(m_codeBlock->constantRegisters[src2->index()].jsValue(globalExec()))->value();
+ if (value == "undefined") {
+ rewindUnaryOp();
+ emitOpcode(op_is_undefined);
+ instructions().append(dst->index());
+ instructions().append(srcIndex);
+ return dst;
+ }
+ if (value == "boolean") {
+ rewindUnaryOp();
+ emitOpcode(op_is_boolean);
+ instructions().append(dst->index());
+ instructions().append(srcIndex);
+ return dst;
+ }
+ if (value == "number") {
+ rewindUnaryOp();
+ emitOpcode(op_is_number);
+ instructions().append(dst->index());
+ instructions().append(srcIndex);
+ return dst;
+ }
+ if (value == "string") {
+ rewindUnaryOp();
+ emitOpcode(op_is_string);
+ instructions().append(dst->index());
+ instructions().append(srcIndex);
+ return dst;
+ }
+ if (value == "object") {
+ rewindUnaryOp();
+ emitOpcode(op_is_object);
+ instructions().append(dst->index());
+ instructions().append(srcIndex);
+ return dst;
+ }
+ if (value == "function") {
+ rewindUnaryOp();
+ emitOpcode(op_is_function);
+ instructions().append(dst->index());
+ instructions().append(srcIndex);
+ return dst;
+ }
+ }
+ }
+
+ emitOpcode(opcode);
+ instructions().append(dst->index());
+ instructions().append(src1->index());
+ instructions().append(src2->index());
+ return dst;
+}
+
RegisterID* CodeGenerator::emitLoad(RegisterID* dst, bool b)
{
return emitLoad(dst, jsBoolean(b));
RegisterID* emitUnaryOp(OpcodeID, RegisterID* dst, RegisterID* src);
RegisterID* emitBinaryOp(OpcodeID, RegisterID* dst, RegisterID* src1, RegisterID* src2);
+ RegisterID* emitEqualityOp(OpcodeID, RegisterID* dst, RegisterID* src1, RegisterID* src2);
RegisterID* emitUnaryNoDstOp(OpcodeID, RegisterID* src);
RegisterID* emitNewObject(RegisterID* dst);
return jsNontrivialString(exec, "object");
}
+static bool jsIsObjectType(JSValue* v)
+{
+ if (JSImmediate::isImmediate(v))
+ return v->isNull();
+
+ JSType type = static_cast<JSCell*>(v)->structureID()->type();
+ if (type == NumberType || type == StringType)
+ return false;
+ if (type == ObjectType) {
+ if (static_cast<JSObject*>(v)->masqueradeAsUndefined())
+ return false;
+ CallData callData;
+ if (static_cast<JSObject*>(v)->getCallData(callData) != CallTypeNone)
+ return false;
+ }
+ return true;
+}
+
+static bool jsIsFunctionType(JSValue* v)
+{
+ if (v->isObject()) {
+ CallData callData;
+ if (static_cast<JSObject*>(v)->getCallData(callData) != CallTypeNone)
+ return true;
+ }
+ return false;
+}
+
static bool NEVER_INLINE resolve(ExecState* exec, Instruction* vPC, Register* r, ScopeChainNode* scopeChain, CodeBlock* codeBlock, JSValue*& exceptionValue)
{
int dst = (vPC + 1)->u.operand;
++vPC;
NEXT_OPCODE;
}
+ BEGIN_OPCODE(op_is_undefined) {
+ /* is_undefined dst(r) src(r)
+
+ Determines whether the type string for src according to
+ the ECMAScript rules is "undefined", and puts the result
+ in register dst.
+ */
+ int dst = (++vPC)->u.operand;
+ int src = (++vPC)->u.operand;
+ JSValue* v = r[src].jsValue(exec);
+ r[dst] = jsBoolean(v->isUndefined() || (v->isObject() && static_cast<JSObject*>(v)->masqueradeAsUndefined()));
+
+ ++vPC;
+ NEXT_OPCODE;
+ }
+ BEGIN_OPCODE(op_is_boolean) {
+ /* is_boolean dst(r) src(r)
+
+ Determines whether the type string for src according to
+ the ECMAScript rules is "boolean", and puts the result
+ in register dst.
+ */
+ int dst = (++vPC)->u.operand;
+ int src = (++vPC)->u.operand;
+ r[dst] = jsBoolean(r[src].jsValue(exec)->isBoolean());
+
+ ++vPC;
+ NEXT_OPCODE;
+ }
+ BEGIN_OPCODE(op_is_number) {
+ /* is_number dst(r) src(r)
+
+ Determines whether the type string for src according to
+ the ECMAScript rules is "number", and puts the result
+ in register dst.
+ */
+ int dst = (++vPC)->u.operand;
+ int src = (++vPC)->u.operand;
+ r[dst] = jsBoolean(r[src].jsValue(exec)->isNumber());
+
+ ++vPC;
+ NEXT_OPCODE;
+ }
+ BEGIN_OPCODE(op_is_string) {
+ /* is_string dst(r) src(r)
+
+ Determines whether the type string for src according to
+ the ECMAScript rules is "string", and puts the result
+ in register dst.
+ */
+ int dst = (++vPC)->u.operand;
+ int src = (++vPC)->u.operand;
+ r[dst] = jsBoolean(r[src].jsValue(exec)->isString());
+
+ ++vPC;
+ NEXT_OPCODE;
+ }
+ BEGIN_OPCODE(op_is_object) {
+ /* is_object dst(r) src(r)
+
+ Determines whether the type string for src according to
+ the ECMAScript rules is "object", and puts the result
+ in register dst.
+ */
+ int dst = (++vPC)->u.operand;
+ int src = (++vPC)->u.operand;
+ r[dst] = jsBoolean(jsIsObjectType(r[src].jsValue(exec)));
+
+ ++vPC;
+ NEXT_OPCODE;
+ }
+ BEGIN_OPCODE(op_is_function) {
+ /* is_function dst(r) src(r)
+
+ Determines whether the type string for src according to
+ the ECMAScript rules is "function", and puts the result
+ in register dst.
+ */
+ int dst = (++vPC)->u.operand;
+ int src = (++vPC)->u.operand;
+ r[dst] = jsBoolean(jsIsFunctionType(r[src].jsValue(exec)));
+
+ ++vPC;
+ NEXT_OPCODE;
+ }
BEGIN_OPCODE(op_in) {
/* in dst(r) property(r) base(r)
return jsTypeStringForValue(ARG_exec, ARG_src1);
}
+JSValue* Machine::cti_op_is_undefined(CTI_ARGS)
+{
+ JSValue* v = ARG_src1;
+ return jsBoolean(v->isUndefined() || (v->isObject() && static_cast<JSObject*>(v)->masqueradeAsUndefined()));
+}
+
+JSValue* Machine::cti_op_is_boolean(CTI_ARGS)
+{
+ return jsBoolean(ARG_src1->isBoolean());
+}
+
+JSValue* Machine::cti_op_is_number(CTI_ARGS)
+{
+ return jsBoolean(ARG_src1->isNumber());
+}
+
+JSValue* Machine::cti_op_is_string(CTI_ARGS)
+{
+ return jsBoolean(ARG_src1->isString());
+}
+
+JSValue* Machine::cti_op_is_object(CTI_ARGS)
+{
+ return jsBoolean(jsIsObjectType(ARG_src1));
+}
+
+JSValue* Machine::cti_op_is_function(CTI_ARGS)
+{
+ return jsBoolean(jsIsFunctionType(ARG_src1));
+}
+
JSValue* Machine::cti_op_stricteq(CTI_ARGS)
{
JSValue* src1 = ARG_src1;
static void SFX_CALL cti_op_push_scope(CTI_ARGS);
static void SFX_CALL cti_op_pop_scope(CTI_ARGS);
static JSValue* SFX_CALL cti_op_typeof(CTI_ARGS);
+ static JSValue* SFX_CALL cti_op_is_undefined(CTI_ARGS);
+ static JSValue* SFX_CALL cti_op_is_boolean(CTI_ARGS);
+ static JSValue* SFX_CALL cti_op_is_number(CTI_ARGS);
+ static JSValue* SFX_CALL cti_op_is_string(CTI_ARGS);
+ static JSValue* SFX_CALL cti_op_is_object(CTI_ARGS);
+ static JSValue* SFX_CALL cti_op_is_function(CTI_ARGS);
static JSValue* SFX_CALL cti_op_stricteq(CTI_ARGS);
static JSValue* SFX_CALL cti_op_nstricteq(CTI_ARGS);
static JSValue* SFX_CALL cti_op_to_jsnumber(CTI_ARGS);
\
macro(op_instanceof) \
macro(op_typeof) \
+ macro(op_is_undefined) \
+ macro(op_is_boolean) \
+ macro(op_is_number) \
+ macro(op_is_string) \
+ macro(op_is_object) \
+ macro(op_is_function) \
macro(op_in) \
\
macro(op_resolve) \
RegisterID* BinaryOpNode::emitCode(CodeGenerator& generator, RegisterID* dst)
{
OpcodeID opcode = this->opcode();
- if (opcode == op_eq || opcode == op_neq) {
+ if (opcode == op_neq) {
if (m_expr1->isNull() || m_expr2->isNull()) {
RefPtr<RegisterID> src = generator.emitNode(dst, m_expr1->isNull() ? m_expr2.get() : m_expr1.get());
- return generator.emitUnaryOp(opcode == op_eq ? op_eq_null : op_neq_null, generator.finalDestination(dst, src.get()), src.get());
+ return generator.emitUnaryOp(op_neq_null, generator.finalDestination(dst, src.get()), src.get());
}
}
return generator.emitBinaryOp(opcode, generator.finalDestination(dst, src1.get()), src1.get(), src2);
}
+RegisterID* EqualNode::emitCode(CodeGenerator& generator, RegisterID* dst)
+{
+ if (m_expr1->isNull() || m_expr2->isNull()) {
+ RefPtr<RegisterID> src = generator.emitNode(dst, m_expr1->isNull() ? m_expr2.get() : m_expr1.get());
+ return generator.emitUnaryOp(op_eq_null, generator.finalDestination(dst, src.get()), src.get());
+ }
+
+ RefPtr<RegisterID> src1 = generator.emitNodeForLeftHandSide(m_expr1.get(), m_rightHasAssignments, m_expr2->isPure(generator));
+ RegisterID* src2 = generator.emitNode(m_expr2.get());
+ return generator.emitEqualityOp(op_eq, generator.finalDestination(dst, src1.get()), src1.get(), src2);
+}
+
+RegisterID* StrictEqualNode::emitCode(CodeGenerator& generator, RegisterID* dst)
+{
+ RefPtr<RegisterID> src1 = generator.emitNodeForLeftHandSide(m_expr1.get(), m_rightHasAssignments, m_expr2->isPure(generator));
+ RegisterID* src2 = generator.emitNode(m_expr2.get());
+ return generator.emitEqualityOp(op_stricteq, generator.finalDestination(dst, src1.get()), src1.get(), src2);
+}
+
RegisterID* ReverseBinaryOpNode::emitCode(CodeGenerator& generator, RegisterID* dst)
{
RefPtr<RegisterID> src1 = generator.emitNodeForLeftHandSide(m_expr1.get(), m_rightHasAssignments, m_expr2->isPure(generator));
{
}
+ virtual RegisterID* emitCode(CodeGenerator&, RegisterID* = 0) JSC_FAST_CALL;
virtual OpcodeID opcode() const JSC_FAST_CALL { return op_eq; }
virtual void streamTo(SourceStream&) const JSC_FAST_CALL;
virtual Precedence precedence() const { return PrecEquality; }
{
}
+ virtual RegisterID* emitCode(CodeGenerator&, RegisterID* = 0) JSC_FAST_CALL;
virtual OpcodeID opcode() const JSC_FAST_CALL { return op_stricteq; }
virtual void streamTo(SourceStream&) const JSC_FAST_CALL;
virtual Precedence precedence() const { return PrecEquality; }
+2008-09-14 Cameron Zwarich <cwzwarich@uwaterloo.ca>
+
+ Reviewed by Maciej Stachowiak.
+
+ Test for a bug in a preliminary version of the patch for
+ Bug 20827: the 'typeof' operator is slow
+ <https://bugs.webkit.org/show_bug.cgi?id=20827>
+
+ * fast/js/resources/typeof-codegen-crash.js: Added.
+ * fast/js/typeof-codegen-crash-expected.txt: Added.
+ * fast/js/typeof-codegen-crash.html: Added.
+
2008-09-13 Dan Bernstein <mitz@apple.com>
Reviewed by Sam Weinig.
--- /dev/null
+description(
+"This test for a crash when optimizing expressions of the form 'typeof o == constant' where 'constant' is not a string."
+);
+
+var o = { };
+
+shouldBeFalse("typeof o == undefined");
+shouldBeFalse("typeof o == null");
+shouldBeFalse("typeof o == true");
+shouldBeFalse("typeof o == false");
+shouldBeFalse("typeof o == 1");
+shouldBeFalse("typeof o == 1.0");
+shouldBeFalse("typeof o == { }");
+
+successfullyParsed = true;
--- /dev/null
+This test for a crash when optimizing expressions of the form 'typeof o == constant' where 'constant' is not a string.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS typeof o == undefined is false
+PASS typeof o == null is false
+PASS typeof o == true is false
+PASS typeof o == false is false
+PASS typeof o == 1 is false
+PASS typeof o == 1.0 is false
+PASS typeof o == { } is false
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<link rel="stylesheet" href="resources/js-test-style.css">
+<script src="resources/js-test-pre.js"></script>
+</head>
+<body>
+<p id="description"></p>
+<div id="console"></div>
+<script src="resources/typeof-codegen-crash.js"></script>
+<script src="resources/js-test-post.js"></script>
+</body>
+</html>