Implement a first iteration of the fast property lookup scheme
authorLars Knoll <lars.knoll@digia.com>
Tue, 12 Feb 2013 15:23:52 +0000 (16:23 +0100)
committerSimon Hausmann <simon.hausmann@digia.com>
Tue, 12 Feb 2013 21:48:19 +0000 (22:48 +0100)
Fast lookups still require a function call, and will only work
for properties defined on the object itself. Properties of the
prototype will still be slow.

Change-Id: I07c601998d312b1bd8e9977708d3375bf72df3e3
Reviewed-by: Simon Hausmann <simon.hausmann@digia.com>
16 files changed:
src/v4/qmljs_environment.cpp
src/v4/qmljs_environment.h
src/v4/qmljs_runtime.cpp
src/v4/qmljs_runtime.h
src/v4/qv4functionobject.cpp
src/v4/qv4functionobject.h
src/v4/qv4globalobject.cpp
src/v4/qv4isel_masm.cpp
src/v4/qv4isel_masm_p.h
src/v4/qv4isel_p.cpp
src/v4/qv4isel_p.h
src/v4/qv4object.cpp
src/v4/qv4object.h
tests/fact.2.js
tests/property_lookup.js [new file with mode: 0644]
tools/v4/main.cpp

index 76d0833..b1339b6 100644 (file)
@@ -217,6 +217,8 @@ void ExecutionContext::init(ExecutionEngine *eng)
     thisObject = eng->globalObject;
 
     function = 0;
+    lookups = 0;
+
     arguments = 0;
     argumentCount = 0;
     locals = 0;
@@ -235,6 +237,8 @@ void ExecutionContext::init(ExecutionContext *p, Object *with)
     thisObject = p->thisObject;
 
     function = 0;
+    lookups = parent->lookups;
+
     arguments = 0;
     argumentCount = 0;
     locals = 0;
@@ -504,6 +508,9 @@ void ExecutionContext::initCallContext(ExecutionContext *parent)
 
     strictMode = function->strictMode;
 
+    if (function->function)
+        lookups = function->function->lookups;
+
     if (function->varCount) {
         locals = reinterpret_cast<Value *>(this + 1);
         std::fill(locals, locals + function->varCount, Value::undefinedValue());
index 5dc4a56..3c4e1fb 100644 (file)
@@ -54,6 +54,7 @@ struct Object;
 struct ExecutionEngine;
 struct ExecutionContext;
 struct DeclarativeEnvironment;
+struct Lookup;
 
 struct Q_V4_EXPORT DiagnosticMessage
 {
@@ -81,6 +82,7 @@ struct ExecutionContext
     Value thisObject;
 
     FunctionObject *function;
+    Lookup *lookups;
 
     Value *arguments;
     unsigned int argumentCount;
index 1fcdf02..4c32b85 100644 (file)
@@ -676,6 +676,61 @@ Value __qmljs_get_activation_property(ExecutionContext *ctx, String *name)
     return ctx->getProperty(name);
 }
 
+Value __qmljs_get_property_lookup(ExecutionContext *ctx, Value object, int lookupIndex)
+{
+    Lookup *l = ctx->lookups + lookupIndex;
+    if (Object *o = object.asObject()) {
+        if (o->internalClass == l->internalClass)
+            return o->getValue(ctx, o->memberData + l->index);
+
+        uint idx = o->internalClass->find(l->name);
+        if (idx < UINT_MAX) {
+            l->internalClass = o->internalClass;
+            l->index = idx;
+            return o->getValue(ctx, o->memberData + idx);
+        }
+
+        return object.objectValue()->__get__(ctx, l->name);
+    } else if (object.isString() && l->name->isEqualTo(ctx->engine->id_length)) {
+        return Value::fromInt32(object.stringValue()->toQString().length());
+    } else {
+        object = __qmljs_to_object(object, ctx);
+
+        if (object.isObject()) {
+            return object.objectValue()->__get__(ctx, l->name);
+        } else {
+            ctx->throwTypeError();
+            return Value::undefinedValue();
+        }
+    }
+}
+
+void __qmljs_set_property_lookup(ExecutionContext *ctx, Value object, int lookupIndex, Value value)
+{
+    if (! object.isObject())
+        object = __qmljs_to_object(object, ctx);
+
+    Object *o = object.objectValue();
+    Lookup *l = ctx->lookups + lookupIndex;
+
+    if (l->index != ArrayObject::LengthPropertyIndex || !o->isArrayObject()) {
+        if (o->internalClass == l->internalClass) {
+            o->putValue(ctx, o->memberData + l->index, value);
+            return;
+        }
+
+        uint idx = o->internalClass->find(l->name);
+        if (idx < UINT_MAX) {
+            l->internalClass = o->internalClass;
+            l->index = idx;
+            return o->putValue(ctx, o->memberData + idx, value);
+        }
+    }
+
+    o->__put__(ctx, l->name, value);
+}
+
+
 Value __qmljs_get_thisObject(ExecutionContext *ctx)
 {
     return ctx->thisObject;
@@ -770,6 +825,43 @@ Value __qmljs_call_property(ExecutionContext *context, Value thisObject, String
     return o->call(context, thisObject, args, argc);
 }
 
+Value __qmljs_call_property_lookup(ExecutionContext *context, Value thisObject, uint index, Value *args, int argc)
+{
+    Lookup *l = context->lookups + index;
+
+    Object *baseObject;
+    if (thisObject.isString()) {
+        baseObject = context->engine->stringPrototype;
+    } else {
+        if (!thisObject.isObject())
+            thisObject = __qmljs_to_object(thisObject, context);
+
+       assert(thisObject.isObject());
+       baseObject = thisObject.objectValue();
+    }
+
+
+    Value func;
+
+    if (baseObject->internalClass == l->internalClass) {
+        func = baseObject->getValue(context, baseObject->memberData + l->index);
+    } else {
+        uint idx = baseObject->internalClass->find(l->name);
+        if (idx < UINT_MAX) {
+            l->internalClass = baseObject->internalClass;
+            l->index = idx;
+            func = baseObject->getValue(context, baseObject->memberData + idx);
+        } else {
+            func = baseObject->__get__(context, l->name);
+        }
+    }
+    FunctionObject *o = func.asFunctionObject();
+    if (!o)
+        context->throwTypeError();
+
+    return o->call(context, thisObject, args, argc);
+}
+
 Value __qmljs_call_element(ExecutionContext *context, Value that, Value index, Value *args, int argc)
 {
     Value thisObject = that;
index 2742692..b2efc15 100644 (file)
@@ -93,6 +93,7 @@ extern "C" {
 // context
 Value __qmljs_call_activation_property(ExecutionContext *, String *name, Value *args, int argc);
 Value __qmljs_call_property(ExecutionContext *context, Value that, String *name, Value *args, int argc);
+Value __qmljs_call_property_lookup(ExecutionContext *context, Value thisObject, uint index, Value *args, int argc);
 Value __qmljs_call_element(ExecutionContext *context, Value that, Value index, Value *args, int argc);
 Value __qmljs_call_value(ExecutionContext *context, Value thisObject, Value func, Value *args, int argc);
 
@@ -168,6 +169,10 @@ void __qmljs_set_property(ExecutionContext *ctx, Value object, String *name, Val
 Value __qmljs_get_property(ExecutionContext *ctx, Value object, String *name);
 Value __qmljs_get_activation_property(ExecutionContext *ctx, String *name);
 
+Value __qmljs_get_property_lookup(ExecutionContext *ctx, Value object, int lookupIndex);
+void __qmljs_set_property_lookup(ExecutionContext *ctx, Value object, int lookupIndex, Value value);
+
+
 Value __qmljs_get_element(ExecutionContext *ctx, Value object, Value index);
 void __qmljs_set_element(ExecutionContext *ctx, Value object, Value index, Value value);
 
index 67ba487..94f923a 100644 (file)
@@ -92,6 +92,7 @@ FunctionObject::FunctionObject(ExecutionContext *scope)
     , varList(0)
     , formalParameterCount(0)
     , varCount(0)
+    , function(0)
 {
     prototype = scope->engine->functionPrototype;
 
@@ -339,8 +340,8 @@ Value FunctionPrototype::method_bind(ExecutionContext *ctx)
 
 ScriptFunction::ScriptFunction(ExecutionContext *scope, Function *function)
     : FunctionObject(scope)
-    , function(function)
 {
+    this->function = function;
     assert(function);
     assert(function->code);
 
index 87fb696..9a49674 100644 (file)
@@ -100,6 +100,13 @@ struct ReferenceErrorPrototype;
 struct SyntaxErrorPrototype;
 struct TypeErrorPrototype;
 struct URIErrorPrototype;
+struct InternalClass;
+
+struct Lookup {
+    InternalClass *internalClass;
+    uint index;
+    String *name;
+};
 
 struct Function {
     String *name;
@@ -113,6 +120,8 @@ struct Function {
     QVector<Value> generatedValues;
     QVector<String *> identifiers;
 
+    Lookup *lookups;
+
     bool hasNestedFunctions  : 1;
     bool hasDirectEval       : 1;
     bool usesArgumentsObject : 1;
@@ -122,6 +131,7 @@ struct Function {
         : name(name)
         , code(0)
         , codeData(0)
+        , lookups(0)
         , hasNestedFunctions(0)
         , hasDirectEval(false)
         , usesArgumentsObject(false)
@@ -141,6 +151,7 @@ struct Q_V4_EXPORT FunctionObject: Object {
     String * const *varList;
     unsigned int formalParameterCount;
     unsigned int varCount;
+    VM::Function *function;
 
     FunctionObject(ExecutionContext *scope);
 
@@ -196,8 +207,6 @@ struct BuiltinFunction: FunctionObject {
 };
 
 struct ScriptFunction: FunctionObject {
-    VM::Function *function;
-
     ScriptFunction(ExecutionContext *scope, VM::Function *function);
     virtual ~ScriptFunction();
 
index 2d7bb71..8a0f779 100644 (file)
@@ -375,15 +375,15 @@ Value EvalFunction::evalCall(ExecutionContext *context, Value /*thisObject*/, Va
 
     bool strict = f->isStrict || (directCall && context->strictMode);
 
-    uint size = requiredMemoryForExecutionContect(this, argc);
+    uint size = requiredMemoryForExecutionContect(this, 0);
     ExecutionContext *k = static_cast<ExecutionContext *>(alloca(size));
 
     if (strict) {
         ctx = k;
         ctx->thisObject = directCall ? context->thisObject : context->engine->globalObject;
         ctx->function = this;
-        ctx->arguments = args;
-        ctx->argumentCount = argc;
+        ctx->arguments = 0;
+        ctx->argumentCount = 0;
         ctx->initCallContext(context);
     }
 
@@ -472,6 +472,8 @@ QQmlJS::VM::Function *EvalFunction::parseSource(QQmlJS::VM::ExecutionContext *ct
             Codegen cg(ctx, strictMode);
             IR::Function *globalIRCode = cg(fileName, program, &module, mode, inheritedLocals);
             QScopedPointer<EvalInstructionSelection> isel(ctx->engine->iselFactory->create(vm, &module));
+            if (inheritContext)
+                isel->setUseFastLookups(false);
             if (globalIRCode)
                 globalCode = isel->vmFunction(globalIRCode);
         }
index 24a51a9..7e74e07 100644 (file)
@@ -383,8 +383,10 @@ InstructionSelection::~InstructionSelection()
 
 void InstructionSelection::run(VM::Function *vmFunction, IR::Function *function)
 {
+    QVector<Lookup> lookups;
     qSwap(_function, function);
     qSwap(_vmFunction, vmFunction);
+    qSwap(_lookups, lookups);
     Assembler* oldAssembler = _as;
     _as = new Assembler(_function);
 
@@ -428,8 +430,14 @@ void InstructionSelection::run(VM::Function *vmFunction, IR::Function *function)
 
     _as->link(_vmFunction);
 
+    if (_lookups.size()) {
+        _vmFunction->lookups = new Lookup[_lookups.size()];
+        memcpy(_vmFunction->lookups, _lookups.constData(), _lookups.size()*sizeof(Lookup));
+    }
+
     qSwap(_vmFunction, vmFunction);
     qSwap(_function, function);
+    qSwap(_lookups, lookups);
     delete _as;
     _as = oldAssembler;
 }
@@ -643,12 +651,24 @@ void InstructionSelection::initClosure(IR::Closure *closure, IR::Temp *target)
 
 void InstructionSelection::getProperty(IR::Temp *base, const QString &name, IR::Temp *target)
 {
-    generateFunctionCall(target, __qmljs_get_property, Assembler::ContextRegister, base, identifier(name));
+    if (useFastLookups) {
+        VM::String *s = identifier(name);
+        uint index = addLookup(s);
+        generateFunctionCall(target, __qmljs_get_property_lookup, Assembler::ContextRegister, base, Assembler::TrustedImm32(index));
+    } else {
+        generateFunctionCall(target, __qmljs_get_property, Assembler::ContextRegister, base, identifier(name));
+    }
 }
 
 void InstructionSelection::setProperty(IR::Expr *source, IR::Temp *targetBase, const QString &targetName)
 {
-    generateFunctionCall(Assembler::Void, __qmljs_set_property, Assembler::ContextRegister, targetBase, identifier(targetName), source);
+    if (useFastLookups) {
+        VM::String *s = identifier(targetName);
+        uint index = addLookup(s);
+        generateFunctionCall(Assembler::Void, __qmljs_set_property_lookup, Assembler::ContextRegister, targetBase, Assembler::TrustedImm32(index), source);
+    } else {
+        generateFunctionCall(Assembler::Void, __qmljs_set_property, Assembler::ContextRegister, targetBase, identifier(targetName), source);
+    }
 }
 
 void InstructionSelection::getElement(IR::Temp *base, IR::Temp *index, IR::Temp *target)
@@ -778,10 +798,20 @@ void InstructionSelection::callProperty(IR::Temp *base, const QString &name,
     assert(base != 0);
 
     int argc = prepareVariableArguments(args);
-    generateFunctionCall(result, __qmljs_call_property,
-                         Assembler::ContextRegister, base, identifier(name),
-                         baseAddressForCallArguments(),
-                         Assembler::TrustedImm32(argc));
+    VM::String *s = identifier(name);
+
+    if (useFastLookups) {
+        uint index = addLookup(s);
+        generateFunctionCall(result, __qmljs_call_property_lookup,
+                             Assembler::ContextRegister, base, Assembler::TrustedImm32(index),
+                             baseAddressForCallArguments(),
+                             Assembler::TrustedImm32(argc));
+    } else {
+        generateFunctionCall(result, __qmljs_call_property,
+                             Assembler::ContextRegister, base, s,
+                             baseAddressForCallArguments(),
+                             Assembler::TrustedImm32(argc));
+    }
 }
 
 void InstructionSelection::callSubscript(IR::Temp *base, IR::Temp *index, IR::ExprList *args, IR::Temp *result)
@@ -935,3 +965,13 @@ void InstructionSelection::callRuntimeMethodImp(IR::Temp *result, const char* na
 }
 
 
+uint InstructionSelection::addLookup(VM::String *name)
+{
+    uint index = (uint)_lookups.size();
+    VM::Lookup l;
+    l.internalClass = 0;
+    l.index = 0;
+    l.name = name;
+    _lookups.append(l);
+    return index;
+}
index aaa0866..f20ffd9 100644 (file)
@@ -778,9 +778,12 @@ private:
 #define callRuntimeMethod(result, function, ...) \
     callRuntimeMethodImp(result, isel_stringIfy(function), function, __VA_ARGS__)
 
+    uint addLookup(VM::String *name);
+
     IR::BasicBlock *_block;
     IR::Function* _function;
     VM::Function* _vmFunction;
+    QVector<VM::Lookup> _lookups;
     Assembler* _as;
 };
 
index 05e7739..6a050b3 100644 (file)
@@ -18,6 +18,7 @@ using namespace QQmlJS::IR;
 
 EvalInstructionSelection::EvalInstructionSelection(VM::ExecutionEngine *engine, Module *module)
     : _engine(engine)
+    , useFastLookups(true)
 {
     assert(engine);
     assert(module);
index d097f91..5ec50b3 100644 (file)
@@ -51,6 +51,8 @@ public:
 
     VM::Function *vmFunction(IR::Function *f);
 
+    void setUseFastLookups(bool b) { useFastLookups = b; }
+
 protected:
     VM::Function *createFunctionMapping(VM::ExecutionEngine *engine, IR::Function *irFunction);
     VM::ExecutionEngine *engine() const { return _engine; }
@@ -59,6 +61,8 @@ protected:
 private:
     VM::ExecutionEngine *_engine;
     QHash<IR::Function *, VM::Function *> _irToVM;
+protected:
+    bool useFastLookups;
 };
 
 class Q_V4_EXPORT EvalISelFactory
index 25a453f..a6259f2 100644 (file)
@@ -89,6 +89,7 @@ void Object::__put__(ExecutionContext *ctx, const QString &name, const Value &va
 
 Value Object::getValue(ExecutionContext *ctx, const PropertyDescriptor *p) const
 {
+    assert(p->type != PropertyDescriptor::Generic);
     if (p->isData())
         return p->value;
     if (!p->get)
@@ -112,6 +113,30 @@ Value Object::getValueChecked(ExecutionContext *ctx, const PropertyDescriptor *p
     return getValue(ctx, p);
 }
 
+void Object::putValue(ExecutionContext *ctx, PropertyDescriptor *pd, Value value)
+{
+    if (pd->isAccessor()) {
+            if (pd->set) {
+                Value args[1];
+                args[0] = value;
+                pd->set->call(ctx, Value::fromObject(this), args, 1);
+                return;
+            }
+            goto reject;
+    }
+
+    if (!pd->isWritable())
+        goto reject;
+
+    pd->value = value;
+    return;
+
+  reject:
+    if (ctx->strictMode)
+        __qmljs_throw_type_error(ctx);
+
+}
+
 void Object::inplaceBinOp(Value rhs, String *name, BinOp op, ExecutionContext *ctx)
 {
     bool hasProperty = false;
index 964051f..6991a39 100644 (file)
@@ -68,6 +68,7 @@ namespace VM {
 
 struct Value;
 struct Function;
+struct Lookup;
 struct Object;
 struct ObjectIterator;
 struct BooleanObject;
@@ -147,6 +148,8 @@ struct Q_V4_EXPORT Object: Managed {
     Value getValueChecked(ExecutionContext *ctx, const PropertyDescriptor *p) const;
     Value getValueChecked(ExecutionContext *ctx, const PropertyDescriptor *p, bool *exists) const;
 
+    void putValue(ExecutionContext *ctx, PropertyDescriptor *pd, Value value);
+
     void inplaceBinOp(Value rhs, String *name, BinOp op, ExecutionContext *ctx);
     void inplaceBinOp(Value rhs, Value index, BinOp op, ExecutionContext *ctx);
 
index c0f087e..d8f750b 100644 (file)
@@ -3,6 +3,6 @@ function fact(n) {
     return n > 1 ? n * fact(n - 1) : 1
 }
 
-for (var i = 0; i < 10000; i = i + 1)
+for (var i = 0; i < 1000000; i = i + 1)
     fact(12)
 
diff --git a/tests/property_lookup.js b/tests/property_lookup.js
new file mode 100644 (file)
index 0000000..ee45b65
--- /dev/null
@@ -0,0 +1,9 @@
+function foo() {
+    var obj = { x: 10 }
+
+    for (var i = 0; i < 1000000; ++i) {
+        var y = obj.x;
+        obj.x = y;
+    }
+}
+foo();
index 2516386..01d4f8f 100644 (file)
@@ -400,6 +400,7 @@ int main(int argc, char *argv[])
                 vm.globalCode = f;
 
                 ctx->strictMode = f->isStrict;
+                ctx->lookups = f->lookups;
                 if (debugger)
                     debugger->aboutToCall(0, ctx);
                 QQmlJS::VM::Value result = f->code(ctx, f->codeData);