From 4be3e757348fc393cb93dbf8f9fc81ea6d57a408 Mon Sep 17 00:00:00 2001 From: Lars Knoll Date: Tue, 12 Feb 2013 16:23:52 +0100 Subject: [PATCH] Implement a first iteration of the fast property lookup scheme 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 --- src/v4/qmljs_environment.cpp | 7 ++++ src/v4/qmljs_environment.h | 2 + src/v4/qmljs_runtime.cpp | 92 ++++++++++++++++++++++++++++++++++++++++++++ src/v4/qmljs_runtime.h | 5 +++ src/v4/qv4functionobject.cpp | 3 +- src/v4/qv4functionobject.h | 13 ++++++- src/v4/qv4globalobject.cpp | 8 ++-- src/v4/qv4isel_masm.cpp | 52 ++++++++++++++++++++++--- src/v4/qv4isel_masm_p.h | 3 ++ src/v4/qv4isel_p.cpp | 1 + src/v4/qv4isel_p.h | 4 ++ src/v4/qv4object.cpp | 25 ++++++++++++ src/v4/qv4object.h | 3 ++ tests/fact.2.js | 2 +- tests/property_lookup.js | 9 +++++ tools/v4/main.cpp | 1 + 16 files changed, 217 insertions(+), 13 deletions(-) create mode 100644 tests/property_lookup.js diff --git a/src/v4/qmljs_environment.cpp b/src/v4/qmljs_environment.cpp index 76d0833..b1339b6 100644 --- a/src/v4/qmljs_environment.cpp +++ b/src/v4/qmljs_environment.cpp @@ -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(this + 1); std::fill(locals, locals + function->varCount, Value::undefinedValue()); diff --git a/src/v4/qmljs_environment.h b/src/v4/qmljs_environment.h index 5dc4a56..3c4e1fb 100644 --- a/src/v4/qmljs_environment.h +++ b/src/v4/qmljs_environment.h @@ -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; diff --git a/src/v4/qmljs_runtime.cpp b/src/v4/qmljs_runtime.cpp index 1fcdf02..4c32b85 100644 --- a/src/v4/qmljs_runtime.cpp +++ b/src/v4/qmljs_runtime.cpp @@ -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; diff --git a/src/v4/qmljs_runtime.h b/src/v4/qmljs_runtime.h index 2742692..b2efc15 100644 --- a/src/v4/qmljs_runtime.h +++ b/src/v4/qmljs_runtime.h @@ -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); diff --git a/src/v4/qv4functionobject.cpp b/src/v4/qv4functionobject.cpp index 67ba487..94f923a 100644 --- a/src/v4/qv4functionobject.cpp +++ b/src/v4/qv4functionobject.cpp @@ -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); diff --git a/src/v4/qv4functionobject.h b/src/v4/qv4functionobject.h index 87fb696..9a49674 100644 --- a/src/v4/qv4functionobject.h +++ b/src/v4/qv4functionobject.h @@ -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 generatedValues; QVector 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(); diff --git a/src/v4/qv4globalobject.cpp b/src/v4/qv4globalobject.cpp index 2d7bb71..8a0f779 100644 --- a/src/v4/qv4globalobject.cpp +++ b/src/v4/qv4globalobject.cpp @@ -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(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 isel(ctx->engine->iselFactory->create(vm, &module)); + if (inheritContext) + isel->setUseFastLookups(false); if (globalIRCode) globalCode = isel->vmFunction(globalIRCode); } diff --git a/src/v4/qv4isel_masm.cpp b/src/v4/qv4isel_masm.cpp index 24a51a9..7e74e07 100644 --- a/src/v4/qv4isel_masm.cpp +++ b/src/v4/qv4isel_masm.cpp @@ -383,8 +383,10 @@ InstructionSelection::~InstructionSelection() void InstructionSelection::run(VM::Function *vmFunction, IR::Function *function) { + QVector 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; +} diff --git a/src/v4/qv4isel_masm_p.h b/src/v4/qv4isel_masm_p.h index aaa0866..f20ffd9 100644 --- a/src/v4/qv4isel_masm_p.h +++ b/src/v4/qv4isel_masm_p.h @@ -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 _lookups; Assembler* _as; }; diff --git a/src/v4/qv4isel_p.cpp b/src/v4/qv4isel_p.cpp index 05e7739..6a050b3 100644 --- a/src/v4/qv4isel_p.cpp +++ b/src/v4/qv4isel_p.cpp @@ -18,6 +18,7 @@ using namespace QQmlJS::IR; EvalInstructionSelection::EvalInstructionSelection(VM::ExecutionEngine *engine, Module *module) : _engine(engine) + , useFastLookups(true) { assert(engine); assert(module); diff --git a/src/v4/qv4isel_p.h b/src/v4/qv4isel_p.h index d097f91..5ec50b3 100644 --- a/src/v4/qv4isel_p.h +++ b/src/v4/qv4isel_p.h @@ -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 _irToVM; +protected: + bool useFastLookups; }; class Q_V4_EXPORT EvalISelFactory diff --git a/src/v4/qv4object.cpp b/src/v4/qv4object.cpp index 25a453f..a6259f2 100644 --- a/src/v4/qv4object.cpp +++ b/src/v4/qv4object.cpp @@ -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; diff --git a/src/v4/qv4object.h b/src/v4/qv4object.h index 964051f..6991a39 100644 --- a/src/v4/qv4object.h +++ b/src/v4/qv4object.h @@ -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); diff --git a/tests/fact.2.js b/tests/fact.2.js index c0f087e..d8f750b 100644 --- a/tests/fact.2.js +++ b/tests/fact.2.js @@ -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 index 0000000..ee45b65 --- /dev/null +++ b/tests/property_lookup.js @@ -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(); diff --git a/tools/v4/main.cpp b/tools/v4/main.cpp index 2516386..01d4f8f 100644 --- a/tools/v4/main.cpp +++ b/tools/v4/main.cpp @@ -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); -- 2.7.4