From bea371d215b0a4096186359c2a934cd0bfc45129 Mon Sep 17 00:00:00 2001 From: Brendan Eich Date: Thu, 28 Aug 2008 00:18:05 -0700 Subject: [PATCH] Record JSOP_IN (452563, r=gal). --- js/src/builtins.tbl | 1 + js/src/jsbuiltins.cpp | 16 +++++++++ js/src/jsinterp.cpp | 40 +++++++++++---------- js/src/jstracer.cpp | 83 +++++++++++++++++++++++++++++++++++++++++-- js/src/trace-test.js | 26 ++++++++++++++ 5 files changed, 144 insertions(+), 22 deletions(-) diff --git a/js/src/builtins.tbl b/js/src/builtins.tbl index e649f5e4f19..d6e82cb382d 100644 --- a/js/src/builtins.tbl +++ b/js/src/builtins.tbl @@ -76,6 +76,7 @@ BUILTIN2(CloseIterator, LO, LO, LO, bool, JSContext*, jsval, 0, BUILTIN2(CallTree, LO, LO, P, nanojit::GuardRecord*, avmplus::InterpState*, nanojit::Fragment*, 0, 0) BUILTIN2(FastNewObject, LO, LO, P, JSObject*, JSContext*, JSObject*, 0, 0) BUILTIN3(AddProperty, LO, LO, LO, LO, bool, JSContext*, JSObject*, JSScopeProperty*, 0, 0) +BUILTIN3(HasNamedProperty, LO, LO, LO, LO, bool, JSContext*, JSObject*, JSString*, 0, 0) BUILTIN3(CallGetter, LO, LO, LO, P, jsval, JSContext*, JSObject*, JSScopeProperty*, 0, 0) BUILTIN2(TypeOfObject, LO, LO, P, JSString*, JSContext*, JSObject*, 1, 1) BUILTIN2(TypeOfBoolean, LO, LO, P, JSString*, JSContext*, jsint, 1, 1) diff --git a/js/src/jsbuiltins.cpp b/js/src/jsbuiltins.cpp index 16bb314bfb4..3d94b5d9c23 100644 --- a/js/src/jsbuiltins.cpp +++ b/js/src/jsbuiltins.cpp @@ -530,6 +530,22 @@ js_AddProperty(JSContext* cx, JSObject* obj, JSScopeProperty* sprop) return false; } +bool FASTCALL +js_HasNamedProperty(JSContext* cx, JSObject* obj, JSString* idstr) +{ + jsid id; + if (!js_ValueToStringId(cx, STRING_TO_JSVAL(idstr), &id)) + return JSVAL_ERROR_COOKIE; + + JSObject* obj2; + JSProperty* prop; + if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop)) + return JSVAL_TO_BOOLEAN(JSVAL_VOID); + if (prop) + OBJ_DROP_PROPERTY(cx, obj2, prop); + return prop != NULL; +} + jsval FASTCALL js_CallGetter(JSContext* cx, JSObject* obj, JSScopeProperty* sprop) { diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index 950357f69b2..faff2793c7e 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -3127,6 +3127,23 @@ js_Interpret(JSContext *cx) } \ JS_END_MACRO +#define TRY_BRANCH_AFTER_COND(cond,spdec) \ + JS_BEGIN_MACRO \ + uintN diff_; \ + JS_ASSERT(js_CodeSpec[op].length == 1); \ + diff_ = (uintN) regs.pc[1] - (uintN) JSOP_IFEQ; \ + if (diff_ <= 1) { \ + regs.sp -= spdec; \ + if (cond == (diff_ != 0)) { \ + ++regs.pc; \ + len = GET_JUMP_OFFSET(regs.pc); \ + BRANCH(len); \ + } \ + len = 1 + JSOP_IFEQ_LENGTH; \ + DO_NEXT_OP(len); \ + } \ + JS_END_MACRO + BEGIN_CASE(JSOP_IN) rval = FETCH_OPND(-1); if (JSVAL_IS_PRIMITIVE(rval)) { @@ -3137,10 +3154,12 @@ js_Interpret(JSContext *cx) FETCH_ELEMENT_ID(obj, -2, id); if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop)) goto error; - regs.sp--; - STORE_OPND(-1, BOOLEAN_TO_JSVAL(prop != NULL)); + cond = prop != NULL; if (prop) OBJ_DROP_PROPERTY(cx, obj2, prop); + TRY_BRANCH_AFTER_COND(cond, 2); + regs.sp--; + STORE_OPND(-1, BOOLEAN_TO_JSVAL(cond)); END_CASE(JSOP_IN) BEGIN_CASE(JSOP_ITER) @@ -3484,23 +3503,6 @@ js_Interpret(JSContext *cx) BITWISE_OP(&); END_CASE(JSOP_BITAND) -#define TRY_BRANCH_AFTER_COND(cond,spdec) \ - JS_BEGIN_MACRO \ - uintN diff_; \ - JS_ASSERT(js_CodeSpec[op].length == 1); \ - diff_ = (uintN) regs.pc[1] - (uintN) JSOP_IFEQ; \ - if (diff_ <= 1) { \ - regs.sp -= spdec; \ - if (cond == (diff_ != 0)) { \ - ++regs.pc; \ - len = GET_JUMP_OFFSET(regs.pc); \ - BRANCH(len); \ - } \ - len = 1 + JSOP_IFEQ_LENGTH; \ - DO_NEXT_OP(len); \ - } \ - JS_END_MACRO - #define RELATIONAL_OP(OP) \ JS_BEGIN_MACRO \ rval = FETCH_OPND(-1); \ diff --git a/js/src/jstracer.cpp b/js/src/jstracer.cpp index 2a9c51e3488..7cf8491f1ed 100644 --- a/js/src/jstracer.cpp +++ b/js/src/jstracer.cpp @@ -2420,7 +2420,9 @@ TraceRecorder::ifop() jsdouble d = asNumber(v); jsdpun u; u.d = 0; - guard((d == 0 || JSDOUBLE_IS_NaN(d)), lir->ins2(LIR_feq, get(&v), lir->insImmq(u.u64)), BRANCH_EXIT); + guard(d == 0 || JSDOUBLE_IS_NaN(d), + lir->ins2(LIR_feq, get(&v), lir->insImmq(u.u64)), + BRANCH_EXIT); } else if (JSVAL_IS_STRING(v)) { guard(JSSTRING_LENGTH(JSVAL_TO_STRING(v)) == 0, lir->ins_eq0(lir->ins2(LIR_piand, @@ -3782,7 +3784,7 @@ TraceRecorder::record_JSOP_SETPROP() JSObject* obj = JSVAL_TO_OBJECT(l); if (obj->map->ops->setProperty != js_SetProperty) - ABORT_TRACE("non-native setProperty"); + ABORT_TRACE("non-native JSObjectOps::setProperty"); LIns* obj_ins = get(&l); @@ -4881,7 +4883,82 @@ TraceRecorder::record_JSOP_THROW() bool TraceRecorder::record_JSOP_IN() { - return false; + jsval& rval = stackval(-1); + if (JSVAL_IS_PRIMITIVE(rval)) + ABORT_TRACE("JSOP_IN on non-object right operand"); + + jsval& lval = stackval(-2); + if (!JSVAL_IS_PRIMITIVE(lval)) + ABORT_TRACE("JSOP_IN on E4X QName left operand"); + + jsid id; + if (JSVAL_IS_INT(lval)) { + id = INT_JSVAL_TO_JSID(lval); + } else { + if (!js_ValueToStringId(cx, lval, &id)) + ABORT_TRACE("OOM under js_ValueToStringId in JSOP_IN"); + lval = ID_TO_VALUE(id); + } + + // Expect what we see at trace recording time (hit or miss) to be the same + // when executing the trace. Use a builtin helper for named properties, as + // forInLoop does. First, handle indexes in dense arrays as a special case. + JSObject* obj = JSVAL_TO_OBJECT(rval); + LIns* obj_ins = get(&rval); + + bool cond; + LIns* x; + do { + if (guardDenseArray(obj, obj_ins)) { + if (JSVAL_IS_INT(lval)) { + jsint idx = JSVAL_TO_INT(lval); + LIns* idx_ins = f2i(get(&lval)); + LIns* dslots_ins = lir->insLoad(LIR_ldp, obj_ins, offsetof(JSObject, dslots)); + if (!guardDenseArrayIndex(obj, idx, obj_ins, dslots_ins, idx_ins)) + ABORT_TRACE("dense array index out of bounds"); + + cond = obj->dslots[idx] != JSVAL_HOLE; + x = lir->ins_eq0(lir->ins2(LIR_eq, + lir->insLoad(LIR_ldp, dslots_ins, idx * sizeof(jsval)), + INS_CONST(JSVAL_HOLE))); + break; + } + + // Not an index id, but a dense array -- go up to the proto. */ + obj = STOBJ_GET_PROTO(obj); + obj_ins = stobj_get_fslot(obj_ins, JSSLOT_PROTO); + } else { + if (JSVAL_IS_INT(id)) + ABORT_TRACE("INT in OBJ where OBJ is not a dense array"); + } + + JSObject* obj2; + JSProperty* prop; + if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop)) + ABORT_TRACE("OBJ_LOOKUP_PROPERTY failed in JSOP_IN"); + + cond = prop != NULL; + if (prop) + OBJ_DROP_PROPERTY(cx, obj2, prop); + + LIns* args[] = { get(&lval), obj_ins, cx_ins }; + x = lir->insCall(F_HasNamedProperty, args); + guard(false, lir->ins2i(LIR_eq, x, 2), OOM_EXIT); + x = lir->ins2i(LIR_eq, x, 1); + } while (0); + + /* The interpreter fuses comparisons and the following branch, + so we have to do that here as well. */ + if (cx->fp->regs->pc[1] == JSOP_IFEQ || cx->fp->regs->pc[1] == JSOP_IFNE) + guard(cond, x, BRANCH_EXIT); + + /* We update the stack after the guard. This is safe since + the guard bails out at the comparison and the interpreter + will this re-execute the comparison. This way the + value of the condition doesn't have to be calculated and + saved on the stack in most cases. */ + set(&lval, x); + return true; } bool diff --git a/js/src/trace-test.js b/js/src/trace-test.js index ba4bd6ff1b8..2faf567bb15 100644 --- a/js/src/trace-test.js +++ b/js/src/trace-test.js @@ -849,6 +849,32 @@ function forVarInWith() { forVarInWith.expected = "pqrst"; test(forVarInWith); +function inObjectTest() { + var o = {p: 1, q: 2, r: 3, s: 4, t: 5}; + var r = 0; + for (var i in o) { + if (!(i in o)) + break; + if ((i + i) in o) + break; + ++r; + } + return r; +} +inObjectTest.expected = 5; +test(inObjectTest); + +function inArrayTest() { + var a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + for (var i = 0; i < a.length; i++) { + if (!(i in a)) + break; + } + return i; +} +inArrayTest.expected = 10; +test(inArrayTest); + /* Keep these at the end so that we can see the summary after the trace-debug spew. */ print("\npassed:", passes.length && passes.join(",")); print("\nFAILED:", fails.length && fails.join(","));