QmlCompiler: Resolve types for calls in init step

Now that resolving the types can be more expensive, especially for
composite types, we should be careful not to do it too often. Most of
the cases where we have to resolve types are for lookups. Most of the
lookups only resolve types in their "init" step, which ideally is only
performed once in the application life time.

However, so far calls had to pass the types of arguments and return
values in the actual "lookup" step, which causes the resolution to
happen on every call.

This change moves those type resolutions to the init step. We can do
this because:

1. Regular typed method calls are calls to a specific overload with
   specific types. The method itself already has the list of types and
   we can just remember which one that is. Then we don't need to pass
   the types.

2. Calls to shadowable methods are all-QVariant typed. The only thing
   we need to know is the number of arguments. Then we can construct
   the list of types in the lookup itself.

We can remember which one of those we're dealing with by adding further
"AsVariant" lookup functions. For the case of non-property-cached
regular methods we also need a new "Fallback" lookup like we already
have it for properties.

Change-Id: I74a3729131d6a5ea0ad79e276965a5167cd609be
Reviewed-by: Olivier De Cannière <olivier.decanniere@qt.io>
This commit is contained in:
Ulf Hermann 2024-10-02 10:31:54 +02:00
parent 6911a76f43
commit a741271dd5
10 changed files with 650 additions and 90 deletions

View File

@ -40,3 +40,62 @@ bool QQmlPrivate::AOTCompiledContext::getEnumLookup(uint index, int *target) con
return true;
}
#endif
#if QT_QML_REMOVED_SINCE(6, 9)
#include <QtQml/qqmlprivate.h>
#include <QtQml/private/qv4executablecompilationunit_p.h>
#include <QtQml/private/qv4lookup_p.h>
#include <QtQml/private/qv4qobjectwrapper_p.h>
bool QQmlPrivate::AOTCompiledContext::callObjectPropertyLookup(
uint index, QObject *object, void **args, const QMetaType *types, int argc) const
{
QV4::Lookup *l = compilationUnit->runtimeLookups + index;
QV4::Scope scope(engine->handle());
QV4::ScopedValue thisObject(scope, QV4::QObjectWrapper::wrap(scope.engine, object));
QV4::ScopedFunctionObject function(scope, l->getter(l, engine->handle(), thisObject));
if (!function) {
scope.engine->throwTypeError(
QStringLiteral("Property '%1' of object [object Object] is not a function")
.arg(compilationUnit->runtimeStrings[l->nameIndex]->toQString()));
return false;
}
function->call(object, args, types, argc);
return !scope.hasException();
}
void QQmlPrivate::AOTCompiledContext::initCallObjectPropertyLookup(uint index) const
{
Q_UNUSED(index);
Q_ASSERT(engine->hasError());
engine->handle()->amendException();
}
bool QQmlPrivate::AOTCompiledContext::callQmlContextPropertyLookup(
uint index, void **args, const QMetaType *types, int argc) const
{
QV4::Lookup *l = compilationUnit->runtimeLookups + index;
QV4::Scope scope(engine->handle());
QV4::ScopedValue thisObject(scope);
QV4::ScopedFunctionObject function(
scope, l->qmlContextPropertyGetter(l, scope.engine, thisObject));
if (!function) {
scope.engine->throwTypeError(
QStringLiteral("Property '%1' of object [null] is not a function").arg(
compilationUnit->runtimeStrings[l->nameIndex]->toQString()));
return false;
}
function->call(qmlScopeObject, args, types, argc);
return !scope.hasException();
}
void QQmlPrivate::AOTCompiledContext::initCallQmlContextPropertyLookup(uint index) const
{
Q_UNUSED(index);
Q_ASSERT(engine->hasError());
engine->handle()->amendException();
}
#endif

View File

@ -450,6 +450,70 @@ ReturnedValue Lookup::getterQObjectMethod(Lookup *lookup, ExecutionEngine *engin
return QObjectWrapper::lookupMethodGetterImpl(lookup, engine, object, flags, revertLookup);
}
ReturnedValue Lookup::getterQObjectMethodAsVariant(
Lookup *lookup, ExecutionEngine *engine, const Value &object)
{
if (&Lookup::getterQObjectMethod == &Lookup::getterQObjectMethodAsVariant) {
// Certain compilers, e.g. MSVC, will "helpfully" deduplicate methods that are completely
// equal. As a result, the pointers are the same, which wreaks havoc on the logic that
// decides how to retrieve the property.
qFatal("Your C++ compiler is broken.");
}
// This getter marks the presence of a qobject method lookup with variant conversion.
// It only does anything with it when running AOT-compiled code.
return getterQObjectMethod(lookup, engine, object);
}
ReturnedValue Lookup::getterFallbackMethod(Lookup *l, ExecutionEngine *engine, const Value &object)
{
const auto revertLookup = [l, engine, &object]() {
l->getter = Lookup::getterGeneric;
return Lookup::getterGeneric(l, engine, object);
};
const Object *o = object.as<Object>();
if (!o || o->internalClass() != l->qobjectMethodLookup.ic)
return revertLookup();
const QObjectWrapper *This = static_cast<const QObjectWrapper *>(o);
QObject *qobj = This->d()->object();
if (QQmlData::wasDeleted(qobj))
return QV4::Encode::undefined();
Scope scope(engine);
ScopedString name(
scope,
engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[l->nameIndex]);
QV4::ScopedValue result(
scope, QObjectWrapper::getMethodFallback(
engine, This->d(), qobj, name,
l->forCall ? QObjectWrapper::NoFlag : QObjectWrapper::AttachMethods));
// In the general case we cannot rely on the method to exist or stay the same across calls.
// However, the AOT compiler can prove it in certain cases. For these, we store the method.
if (QObjectMethod *method = result->as<QV4::QObjectMethod>())
l->qobjectMethodLookup.method.set(engine, method->d());
return result->asReturnedValue();
}
ReturnedValue Lookup::getterFallbackMethodAsVariant(
Lookup *l, ExecutionEngine *engine, const Value &object)
{
if (&Lookup::getterFallbackMethod == &Lookup::getterFallbackMethodAsVariant) {
// Certain compilers, e.g. MSVC, will "helpfully" deduplicate methods that are completely
// equal. As a result, the pointers are the same, which wreaks havoc on the logic that
// decides how to retrieve the property.
qFatal("Your C++ compiler is broken.");
}
// This getter marks the presence of a fallback method lookup with variant conversion.
// It only does anything with it when running AOT-compiled code.
return getterFallbackMethod(l, engine, object);
}
ReturnedValue Lookup::primitiveGetterProto(Lookup *l, ExecutionEngine *engine, const Value &object)
{
// Otherwise we cannot trust the protoIds

View File

@ -185,6 +185,9 @@ struct Q_QML_EXPORT Lookup {
static ReturnedValue getterQObject(Lookup *l, ExecutionEngine *engine, const Value &object);
static ReturnedValue getterQObjectAsVariant(Lookup *l, ExecutionEngine *engine, const Value &object);
static ReturnedValue getterQObjectMethod(Lookup *l, ExecutionEngine *engine, const Value &object);
static ReturnedValue getterQObjectMethodAsVariant(Lookup *l, ExecutionEngine *engine, const Value &object);
static ReturnedValue getterFallbackMethod(Lookup *l, ExecutionEngine *engine, const Value &object);
static ReturnedValue getterFallbackMethodAsVariant(Lookup *l, ExecutionEngine *engine, const Value &object);
static ReturnedValue primitiveGetterProto(Lookup *l, ExecutionEngine *engine, const Value &object);
static ReturnedValue primitiveGetterAccessor(Lookup *l, ExecutionEngine *engine, const Value &object);
@ -226,6 +229,7 @@ struct Q_QML_EXPORT Lookup {
if (const QQmlPropertyCache *pc = qobjectLookup.propertyCache)
pc->release();
} else if (getter == getterQObjectMethod
|| getter == getterQObjectMethodAsVariant
|| getter == QQmlTypeWrapper::lookupSingletonMethod
|| qmlContextPropertyGetter == QQmlContextWrapper::lookupScopeObjectMethod
|| qmlContextPropertyGetter == QQmlContextWrapper::lookupContextObjectMethod) {

View File

@ -376,6 +376,18 @@ ReturnedValue QObjectWrapper::getProperty(
}
}
ReturnedValue QObjectWrapper::getMethodFallback(
ExecutionEngine *engine, Heap::Object *wrapper, QObject *qobject,
QV4::String *name, Flags flags)
{
QQmlPropertyData local;
const QQmlPropertyData *property = QQmlPropertyCache::property(
qobject, name, engine->callingQmlContext(), &local);
return property
? getProperty(engine, wrapper, qobject, property, flags)
: Encode::undefined();
}
static OptionalReturnedValue getDestroyOrToStringMethod(
ExecutionEngine *v4, String *name, Heap::Object *qobj, bool *hasProperty = nullptr)
{
@ -1101,13 +1113,13 @@ ReturnedValue QObjectWrapper::virtualResolveLookupGetter(const Object *object, E
}
if (!ddata || !ddata->propertyCache) {
QQmlPropertyData local;
const QQmlPropertyData *property = QQmlPropertyCache::property(
qobj, name, qmlContext, &local);
return property
? getProperty(engine, This->d(), qobj, property,
lookup->forCall ? NoFlag : AttachMethods)
: Encode::undefined();
QV4::ScopedValue result(scope, getMethodFallback(
engine, This->d(), qobj, name, lookup->forCall ? NoFlag : AttachMethods));
lookup->qobjectMethodLookup.ic.set(engine, object->internalClass());
if (QObjectMethod *method = result->as<QObjectMethod>())
lookup->qobjectMethodLookup.method.set(engine, method->d());
lookup->getter = Lookup::getterFallbackMethod;
return result->asReturnedValue();
}
const QQmlPropertyData *property = ddata->propertyCache->property(name.getPointer(), qobj, qmlContext);

View File

@ -177,6 +177,10 @@ struct Q_QML_EXPORT QObjectWrapper : public Object
ExecutionEngine *engine, Heap::Object *wrapper, QObject *object,
const QQmlPropertyData *property, Flags flags);
static ReturnedValue getMethodFallback(
ExecutionEngine *engine, Heap::Object *wrapper, QObject *object,
QV4::String *name, Flags flags);
static ReturnedValue virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup);
static ReturnedValue lookupAttached(Lookup *l, ExecutionEngine *engine, const Value &object);

View File

@ -17,6 +17,7 @@
#include <private/qqmltypemodule_p.h>
#include <private/qqmltypewrapper_p.h>
#include <private/qqmlvaluetypewrapper_p.h>
#include <private/qv4alloca_p.h>
#include <private/qv4dateobject_p.h>
#include <private/qv4errorobject_p.h>
#include <private/qv4identifiertable_p.h>
@ -986,6 +987,8 @@ void qmlRegisterTypeAndRevisions<QQmlTypeNotAvailable, void>(
qmlregister(TypeAndRevisionsRegistration, &type);
}
struct LookupNotInitialized {};
QObject *AOTCompiledContext::thisObject() const
{
return static_cast<QV4::MetaTypesStackFrame *>(engine->handle()->currentStackFrame)
@ -1295,8 +1298,8 @@ static ObjectPropertyResult resetFallbackProperty(
static bool isTypeCompatible(QMetaType lookupType, QMetaType propertyType)
{
if (!lookupType.isValid()) {
// If type is invalid, then the calling code depends on the lookup
if (lookupType == QMetaType::fromType<LookupNotInitialized>()) {
// If lookup is not initialized, then the calling code depends on the lookup
// to be set up in order to query the type, via lookupResultMetaType.
// We cannot verify the type in this case.
} else if ((lookupType.flags() & QMetaType::IsQmlList)
@ -1351,6 +1354,11 @@ static bool isTypeCompatible(QMetaType lookupType, QMetaType propertyType)
}
return false;
} else if (!propertyType.isValid()) {
// We cannot directly store void, but we can put it into QVariant or QJSPrimitiveValue
return !lookupType.isValid()
|| lookupType == QMetaType::fromType<QVariant>()
|| lookupType == QMetaType::fromType<QJSPrimitiveValue>();
} else if (propertyType != lookupType) {
return false;
}
@ -1564,25 +1572,40 @@ QMetaType AOTCompiledContext::lookupResultMetaType(uint index) const
|| l->getter == QV4::Lookup::getterQObject
|| l->getter == QV4::Lookup::getterQObjectAsVariant) {
return l->qobjectLookup.propertyData->propType();
} else if (l->getter == QV4::QQmlValueTypeWrapper::lookupGetter) {
}
if (l->getter == QV4::QQmlValueTypeWrapper::lookupGetter)
return QMetaType(l->qgadgetLookup.metaType);
} else if (l->getter == QV4::QQmlTypeWrapper::lookupEnumValue) {
if (l->getter == QV4::QQmlTypeWrapper::lookupEnumValue)
return QMetaType(l->qmlEnumValueLookup.metaType);
} else if (l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupIdObject
|| l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupType
|| l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupSingleton
|| l->getter == QV4::QObjectWrapper::lookupAttached) {
if (l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupIdObject
|| l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupType
|| l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupSingleton
|| l->getter == QV4::QObjectWrapper::lookupAttached) {
return QMetaType::fromType<QObject *>();
} else if (l->getter == QV4::Lookup::getterFallback
|| l->getter == QV4::Lookup::getterFallbackAsVariant
|| l->qmlContextPropertyGetter
}
if (l->getter == QV4::Lookup::getterFallback
|| l->getter == QV4::Lookup::getterFallbackAsVariant
|| l->qmlContextPropertyGetter
== QV4::QQmlContextWrapper::lookupScopeFallbackProperty) {
const QMetaObject *metaObject
= reinterpret_cast<const QMetaObject *>(l->qobjectFallbackLookup.metaObject - 1);
const int coreIndex = l->qobjectFallbackLookup.coreIndex;
return metaObject->property(coreIndex).metaType();
}
return QMetaType();
if (l->getter == QV4::Lookup::getterQObjectMethod
|| l->getter == QV4::Lookup::getterQObjectMethodAsVariant
|| l->getter == QV4::Lookup::getterFallbackMethod
|| l->getter == QV4::Lookup::getterFallbackMethodAsVariant
|| l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupScopeObjectMethod) {
return l->qobjectMethodLookup.propertyData->propType();
}
return QMetaType::fromType<LookupNotInitialized>();
}
static bool isUndefined(const void *value, QMetaType type)
@ -1609,7 +1632,7 @@ void AOTCompiledContext::storeNameSloppy(uint nameIndex, void *value, QMetaType
l.nameIndex = nameIndex;
l.forCall = false;
ObjectPropertyResult storeResult = ObjectPropertyResult::NeedsInit;
switch (initObjectLookup(this, &l, qmlScopeObject, QMetaType())) {
switch (initObjectLookup(this, &l, qmlScopeObject, QMetaType::fromType<LookupNotInitialized>())) {
case ObjectLookupResult::ObjectAsVariant:
case ObjectLookupResult::Object: {
const QMetaType propType = l.qobjectLookup.propertyData->propType();
@ -1749,30 +1772,364 @@ QDateTime AOTCompiledContext::constructDateTime(
year, month, day, hours, minutes, seconds, msecs, engine->handle()));
}
bool AOTCompiledContext::callQmlContextPropertyLookup(
uint index, void **args, const QMetaType *types, int argc) const
static QMetaType jsTypedFunctionArgument(
const QQmlType &type, const QV4::CompiledData::ParameterType &parameter)
{
return parameter.isList() ? type.qListTypeId() : type.typeId();
}
static bool callQObjectMethodWithTypes(
QV4::ExecutionEngine *engine, QV4::Lookup *l, QObject *thisObject, void **args,
QMetaType *types, int argc)
{
QV4::Scope scope(engine);
QV4::Scoped<QV4::QObjectMethod> function(scope, l->qobjectMethodLookup.method);
Q_ASSERT(function);
function->call(thisObject, args, types, argc);
return !scope.hasException();
}
static void setVariantGetter(QV4::Lookup *l)
{
if (l->getter == QV4::Lookup::getterQObjectMethod) {
l->getter = QV4::Lookup::getterQObjectMethodAsVariant;
return;
}
if (l->getter == QV4::Lookup::getterFallbackMethod) {
l->getter = QV4::Lookup::getterFallbackMethodAsVariant;
return;
}
if (l->getter == QV4::Lookup::getterQObject) {
l->getter = QV4::Lookup::getterQObjectAsVariant;
return;
}
if (l->getter == QV4::Lookup::getterFallback) {
l->getter = QV4::Lookup::getterFallbackAsVariant;
return;
}
}
static bool callQObjectMethodAsVariant(
QV4::ExecutionEngine *engine, QV4::Lookup *l,
QObject *thisObject, void **args, int argc)
{
// We need to re-fetch the method on every call because it can be shadowed.
QV4::Scope scope(engine);
QV4::ScopedValue wrappedObject(scope, QV4::QObjectWrapper::wrap(scope.engine, thisObject));
QV4::ScopedFunctionObject function(scope, l->getter(l, scope.engine, wrappedObject));
Q_ASSERT(function);
// The getter may have reset the lookup, but the method is still shadowable.
setVariantGetter(l);
Q_ALLOCA_VAR(QMetaType, types, (argc + 1) * sizeof(QMetaType));
std::fill(types, types + argc + 1, QMetaType::fromType<QVariant>());
function->call(thisObject, args, types, argc);
return !scope.hasException();
}
static bool callQObjectMethod(
QV4::ExecutionEngine *engine, QV4::Lookup *l,
QObject *thisObject, void **args, int argc)
{
Q_ALLOCA_VAR(QMetaType, types, (argc + 1) * sizeof(QMetaType));
const QMetaMethod method = l->qobjectMethodLookup.propertyData->metaMethod();
Q_ASSERT(argc == method.parameterCount());
types[0] = method.returnMetaType();
for (int i = 0; i < argc; ++i)
types[i + 1] = method.parameterMetaType(i);
return callQObjectMethodWithTypes(engine, l, thisObject, args, types, argc);
}
static bool callArrowFunction(
QV4::ExecutionEngine *engine, QV4::ArrowFunction *function,
QObject *thisObject, void **args, int argc)
{
QV4::Function *v4Function = function->function();
Q_ASSERT(v4Function);
Q_ASSERT(v4Function->nFormals == argc);
switch (v4Function->kind) {
case QV4::Function::AotCompiled: {
const auto *types = v4Function->aotCompiledFunction.types.data();
function->call(thisObject, args, types, argc);
return !engine->hasException;
}
case QV4::Function::JsTyped: {
const auto *compiledFunction = v4Function->compiledFunction;
const QV4::CompiledData::Parameter *formals
= v4Function->compiledFunction->formalsTable();
Q_ALLOCA_VAR(QMetaType, types, (argc + 1) * sizeof(QMetaType));
types[0] = jsTypedFunctionArgument(
v4Function->jsTypedFunction.types[0], compiledFunction->returnType);
for (qsizetype i = 0; i != argc; ++i) {
types[i + 1] = jsTypedFunctionArgument(
v4Function->jsTypedFunction.types[i + 1], formals[i].type);
}
function->call(thisObject, args, types, argc);
return !engine->hasException;
}
case QV4::Function::JsUntyped: {
// We can call untyped functions if we're not expecting a specific return value and don't
// have to pass any arguments. The compiler verifies this.
Q_ASSERT(argc == 0);
QMetaType variantType = QMetaType::fromType<QVariant>();
function->call(thisObject, args, &variantType, 0);
return !engine->hasException;
}
case QV4::Function::Eval:
break;
}
Q_UNREACHABLE_RETURN(false);
}
static bool callArrowFunctionAsVariant(
QV4::ExecutionEngine *engine, QV4::ArrowFunction *function,
QObject *thisObject, void **args, int argc)
{
QV4::Function *v4Function = function->function();
Q_ASSERT(v4Function);
switch (v4Function->kind) {
case QV4::Function::JsUntyped:
// We cannot assert anything here because the method can be shadowed.
// That's why we wrap everything in QVariant.
case QV4::Function::AotCompiled:
case QV4::Function::JsTyped: {
Q_ALLOCA_VAR(QMetaType, types, (argc + 1) * sizeof(QMetaType));
std::fill(types, types + argc + 1, QMetaType::fromType<QVariant>());
function->call(thisObject, args, types, argc);
return !engine->hasException;
}
case QV4::Function::Eval:
break;
}
Q_UNREACHABLE_RETURN(false);
}
bool AOTCompiledContext::callQmlContextPropertyLookup(uint index, void **args, int argc) const
{
QV4::Lookup *l = compilationUnit->runtimeLookups + index;
if (l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupScopeObjectMethod)
return callQObjectMethod(engine->handle(), l, qmlScopeObject, args, argc);
const auto doCall = [&](auto &&call) {
QV4::Scope scope(engine->handle());
QV4::ScopedValue undefined(scope);
QV4::Scoped<QV4::ArrowFunction> function(
scope, l->qmlContextPropertyGetter(l, scope.engine, undefined));
Q_ASSERT(function);
return call(scope.engine, function, qmlScopeObject, args, argc);
};
if (l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupScopeObjectProperty)
return doCall(&callArrowFunction);
return false;
}
enum MatchScore {
NoMatch = 0x0,
VariantMatch = 0x1,
VariantExactMatch = 0x2,
ExactMatch = 0x4,
// VariantMatch and ExactMatch for different arguments are incompatible because the ExactMatch
// tells us that the variant was not meant as a generic argument but rather as a concrete one.
IncompatibleMatch = VariantMatch | ExactMatch,
// If we're calling a scope method we know that it cannot be shadowed. Therefore an all-variant
// method matched by an all-variant call is fine.
ScopeAccepted = ExactMatch | VariantExactMatch,
// If we're calling an object method it may be shadowed. We cannot nail down an all-variant
// call to an all-variant method.
ObjectAccepted = ExactMatch,
};
Q_DECLARE_FLAGS(MatchScores, MatchScore);
static MatchScore overloadTypeMatch(QMetaType passed, QMetaType expected)
{
const bool isVariant = (passed == QMetaType::fromType<QVariant>());
if (isTypeCompatible(passed, expected))
return isVariant ? VariantExactMatch : ExactMatch;
if (isVariant)
return VariantMatch;
return NoMatch;
}
static MatchScore resolveQObjectMethodOverload(
QV4::QObjectMethod *method, QV4::Lookup *l, const QMetaType *types, int argc,
MatchScore acceptedScores)
{
Q_ASSERT(l->qobjectMethodLookup.method.get() == method->d());
const auto *d = method->d();
for (int i = 0, end = d->methodCount; i != end; ++i) {
const QMetaMethod metaMethod = d->methods[i].metaMethod();
if (metaMethod.parameterCount() != argc)
continue;
MatchScores finalScore = NoMatch;
if (!types[0].isValid()) {
if (argc == 0) {
// No arguments given and we're not interested in the return value:
// The overload with 0 arguments matches (but it may still be shadowable).
finalScore = VariantExactMatch;
}
} else {
const MatchScore score = overloadTypeMatch(types[0], metaMethod.returnMetaType());
if (score == NoMatch)
continue;
finalScore = score;
}
for (int j = 0; j < argc; ++j) {
const MatchScore score
= overloadTypeMatch(types[j + 1], metaMethod.parameterMetaType(j));
if (score == NoMatch) {
finalScore = NoMatch;
break;
}
finalScore.setFlag(score);
if (finalScore.testFlags(IncompatibleMatch)) {
finalScore = NoMatch;
break;
}
}
if (finalScore == NoMatch)
continue;
if (finalScore.testAnyFlags(acceptedScores)) {
l->qobjectMethodLookup.propertyData = d->methods + i;
return ExactMatch;
}
}
// No adjusting of the lookup's propertyData here. We re-fetch the method on every call.
// Furthermore, the first propertyData of the collection of possible overloads has the
// isOverridden flag we use to determine whether to invalidate a lookup. Therefore, we
// have to store that one if the method can be overridden (or shadowed).
return VariantMatch;
}
static inline bool allTypesAreVariant(const QMetaType *types, int argc)
{
for (int i = 0; i <= argc; ++i) { // Yes, i <= argc, because of return type
if (types[i] != QMetaType::fromType<QVariant>())
return false;
}
return true;
}
static bool isArrowFunctionVariantCall(
QV4::ArrowFunction *function, const QMetaType *types, int argc)
{
QV4::Function *v4Function = function->function();
Q_ASSERT(v4Function);
switch (v4Function->kind) {
case QV4::Function::AotCompiled: {
Q_ASSERT(argc + 1 == v4Function->aotCompiledFunction.types.size());
const QMetaType *parameterTypes = v4Function->aotCompiledFunction.types.data();
if (types[0].isValid() && !isTypeCompatible(types[0], parameterTypes[0])) {
Q_ASSERT(allTypesAreVariant(types, argc));
return true;
}
for (int i = 1; i <= argc; ++i) { // Yes, i <= argc, because of return type
if (!isTypeCompatible(types[i], parameterTypes[i])) {
Q_ASSERT(allTypesAreVariant(types, argc));
return true;
}
}
return false;
}
case QV4::Function::JsTyped: {
const auto *compiledFunction = v4Function->compiledFunction;
const QV4::CompiledData::Parameter *formals
= v4Function->compiledFunction->formalsTable();
if (types[0].isValid()
&& !isTypeCompatible(types[0], jsTypedFunctionArgument(
v4Function->jsTypedFunction.types[0], compiledFunction->returnType))) {
Q_ASSERT(allTypesAreVariant(types, argc));
return true;
}
for (int i = 1; i <= argc; ++i) { // Yes, i <= argc, because of return type
if (!isTypeCompatible(types[i], jsTypedFunctionArgument(
v4Function->jsTypedFunction.types[i], formals[i - 1].type))) {
Q_ASSERT(allTypesAreVariant(types, argc));
return true;
}
}
return false;
}
case QV4::Function::JsUntyped: {
// We can call untyped functions if we're not expecting a specific return value and don't
// have to pass any arguments. The compiler verifies this.
Q_ASSERT(v4Function->nFormals == 0);
Q_ASSERT(!types[0].isValid() || types[0] == QMetaType::fromType<QVariant>());
return types[0] == QMetaType::fromType<QVariant>();
}
case QV4::Function::Eval:
break;
}
Q_UNREACHABLE_RETURN(false);
}
void AOTCompiledContext::initCallQmlContextPropertyLookup(
uint index, const QMetaType *types, int argc) const
{
if (engine->hasError()) {
engine->handle()->amendException();
return;
}
QV4::Lookup *l = compilationUnit->runtimeLookups + index;
QV4::Scope scope(engine->handle());
QV4::ScopedValue thisObject(scope);
QV4::ScopedFunctionObject function(
scope, l->qmlContextPropertyGetter(l, scope.engine, thisObject));
if (!function) {
scope.engine->throwTypeError(
QStringLiteral("Property '%1' of object [null] is not a function").arg(
compilationUnit->runtimeStrings[l->nameIndex]->toQString()));
return false;
if (auto *method = function->as<QV4::QObjectMethod>()) {
Q_ASSERT(l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupScopeObjectMethod);
method->d()->ensureMethodsCache(qmlScopeObject->metaObject());
const auto match = resolveQObjectMethodOverload(method, l, types, argc, ScopeAccepted);
Q_ASSERT(match == ExactMatch);
return;
}
function->call(qmlScopeObject, args, types, argc);
return !scope.hasException();
}
if (function->as<QV4::ArrowFunction>()) {
// Can't have overloads of JavaScript functions.
Q_ASSERT(l->qmlContextPropertyGetter == QV4::QQmlContextWrapper::lookupScopeObjectProperty);
return;
}
void AOTCompiledContext::initCallQmlContextPropertyLookup(uint index) const
{
Q_UNUSED(index);
Q_ASSERT(engine->hasError());
engine->handle()->amendException();
scope.engine->throwTypeError(
QStringLiteral("Property '%1' of object [null] is not a function").arg(
compilationUnit->runtimeStrings[l->nameIndex]->toQString()));
}
bool AOTCompiledContext::loadContextIdLookup(uint index, void *target) const
@ -1833,28 +2190,76 @@ void AOTCompiledContext::initLoadContextIdLookup(uint index) const
}
bool AOTCompiledContext::callObjectPropertyLookup(
uint index, QObject *object, void **args, const QMetaType *types, int argc) const
uint index, QObject *object, void **args, int argc) const
{
QV4::Lookup *l = compilationUnit->runtimeLookups + index;
if (l->getter == QV4::Lookup::getterQObjectMethod
|| l->getter == QV4::Lookup::getterFallbackMethod) {
return callQObjectMethod(engine->handle(), l, object, args, argc);
}
if (l->getter == QV4::Lookup::getterQObjectMethodAsVariant
|| l->getter == QV4::Lookup::getterFallbackMethodAsVariant) {
return callQObjectMethodAsVariant(engine->handle(), l, object, args, argc);
}
const auto doCall = [&](auto &&call) {
// Here we always retrieve a fresh method via the getter. No need to re-init.
QV4::Scope scope(engine->handle());
QV4::ScopedValue thisObject(scope, QV4::QObjectWrapper::wrap(scope.engine, object));
QV4::Scoped<QV4::ArrowFunction> function(scope, l->getter(l, scope.engine, thisObject));
Q_ASSERT(function);
return call(scope.engine, function, qmlScopeObject, args, argc);
};
if (l->getter == QV4::Lookup::getterQObject
|| l->getter == QV4::Lookup::getterFallback) {
return doCall(&callArrowFunction);
}
if (l->getter == QV4::Lookup::getterQObjectAsVariant
|| l->getter == QV4::Lookup::getterFallbackAsVariant) {
const bool result = doCall(&callArrowFunctionAsVariant);
// The getter may have reset the lookup, but the method is still shadowable.
setVariantGetter(l);
return result;
}
return false;
}
void AOTCompiledContext::initCallObjectPropertyLookup(
uint index, QObject *object, const QMetaType *types, int argc) const
{
if (engine->hasError()) {
engine->handle()->amendException();
return;
}
QV4::Lookup *l = compilationUnit->runtimeLookups + index;
QV4::Scope scope(engine->handle());
QV4::ScopedValue thisObject(scope, QV4::QObjectWrapper::wrap(scope.engine, object));
QV4::ScopedFunctionObject function(scope, l->getter(l, engine->handle(), thisObject));
if (!function) {
scope.engine->throwTypeError(
QStringLiteral("Property '%1' of object [object Object] is not a function")
.arg(compilationUnit->runtimeStrings[l->nameIndex]->toQString()));
return false;
QV4::ScopedFunctionObject function(scope, l->getter(l, scope.engine, thisObject));
if (auto *method = function->as<QV4::QObjectMethod>()) {
method->d()->ensureMethodsCache(object->metaObject());
if (resolveQObjectMethodOverload(method, l, types, argc, ObjectAccepted) == VariantMatch)
setVariantGetter(l);
return;
}
function->call(object, args, types, argc);
return !scope.hasException();
}
if (QV4::ArrowFunction *arrowFunction = function->as<QV4::ArrowFunction>()) {
// Can't have overloads of JavaScript functions.
if (isArrowFunctionVariantCall(arrowFunction, types, argc))
setVariantGetter(l);
return;
}
void AOTCompiledContext::initCallObjectPropertyLookup(uint index) const
{
Q_UNUSED(index);
Q_ASSERT(engine->hasError());
engine->handle()->amendException();
scope.engine->throwTypeError(
QStringLiteral("Property '%1' of object [object Object] is not a function")
.arg(compilationUnit->runtimeStrings[l->nameIndex]->toQString()));
}
bool AOTCompiledContext::callGlobalLookup(

View File

@ -687,16 +687,27 @@ namespace QQmlPrivate
// exception is present after the initialization there is no way to carry out the lookup and
// the exception should be propagated. If not, the original lookup can be tried again.
bool callQmlContextPropertyLookup(uint index, void **args, int argc) const;
void initCallQmlContextPropertyLookup(uint index, const QMetaType *types, int argc) const;
#if QT_QML_REMOVED_SINCE(6, 9)
bool callQmlContextPropertyLookup(
uint index, void **args, const QMetaType *types, int argc) const;
void initCallQmlContextPropertyLookup(uint index) const;
#endif
bool loadContextIdLookup(uint index, void *target) const;
void initLoadContextIdLookup(uint index) const;
bool callObjectPropertyLookup(uint index, QObject *object,
void **args, const QMetaType *types, int argc) const;
bool callObjectPropertyLookup(uint index, QObject *object, void **args, int argc) const;
void initCallObjectPropertyLookup(
uint index, QObject *object, const QMetaType *types, int argc) const;
#if QT_QML_REMOVED_SINCE(6, 9)
bool callObjectPropertyLookup(
uint index, QObject *object, void **args, const QMetaType *types, int argc) const;
void initCallObjectPropertyLookup(uint index) const;
#endif
bool callGlobalLookup(uint index, void **args, const QMetaType *types, int argc) const;
void initCallGlobalLookup(uint index) const;

View File

@ -1694,7 +1694,9 @@ void QQmlJSCodeGenerator::generate_Resume(int)
BYTECODE_UNIMPLEMENTED();
}
QString QQmlJSCodeGenerator::argumentsList(int argc, int argv, QString *outVar)
QString QQmlJSCodeGenerator::initAndCall(
int argc, int argv, const QString &callMethodTemplate, const QString &initMethodTemplate,
QString *outVar)
{
QString types;
QString args;
@ -1708,12 +1710,6 @@ QString QQmlJSCodeGenerator::argumentsList(int argc, int argv, QString *outVar)
*outVar = u"callResult"_s;
const QQmlJSScope::ConstPtr outType = m_state.accumulatorOut().storedType();
m_body += outType->augmentedInternalName() + u' ' + *outVar;
if (!m_typeResolver->registerContains(m_state.accumulatorOut(), outType)) {
if (m_typeResolver->equals(outType, m_typeResolver->varType())
|| m_typeResolver->equals(outType, m_typeResolver->jsPrimitiveType())) {
m_body += u'(' + metaType(m_state.accumulatorOut().containedType()) + u')';
}
}
m_body += u";\n";
args = contentPointer(m_state.accumulatorOut(), *outVar);
@ -1727,8 +1723,14 @@ QString QQmlJSCodeGenerator::argumentsList(int argc, int argv, QString *outVar)
types += u", "_s + contentType(content, var);
}
return u"void *args[] = { "_s + args + u" };\n"_s
+ u"const QMetaType types[] = { "_s + types + u" };\n"_s;
return u"const auto doCall = [&]() {\n"_s
+ u" void *args[] = {" + args + u"};\n"_s
+ u" return aotContext->"_s + callMethodTemplate.arg(u"args"_s).arg(argc) + u";\n"
+ u"};\n"_s
+ u"const auto doInit = [&]() {\n"_s
+ u" QMetaType types[] = {" + types + u"};\n"_s
+ u" aotContext->"_s + initMethodTemplate.arg(u"types"_s).arg(argc) + u";\n"
+ u"};\n"_s;
}
void QQmlJSCodeGenerator::generateMoveOutVar(const QString &outVar)
@ -2238,18 +2240,17 @@ void QQmlJSCodeGenerator::generate_CallPropertyLookup(int index, int base, int a
scope, baseType, registerVariable(base),
u"Cannot call method '%1' of %2"_s.arg(name));
const QString indexString = QString::number(index);
m_body += u"{\n"_s;
QString outVar;
m_body += argumentsList(argc, argv, &outVar);
const QString lookup = u"aotContext->callObjectPropertyLookup("_s + indexString
+ u", "_s + inputPointer
+ u", args, types, "_s + QString::number(argc) + u')';
const QString initialization = u"aotContext->initCallObjectPropertyLookup("_s
+ indexString + u')';
generateLookup(lookup, initialization);
m_body += initAndCall(
argc, argv, u"callObjectPropertyLookup(%1, %2, %3, %4)"_s.arg(index).arg(inputPointer),
u"initCallObjectPropertyLookup(%1, %2, %3, %4)"_s.arg(index).arg(inputPointer),
&outVar);
const QString lookup = u"doCall()"_s;
const QString initialization = u"doInit()"_s;
const QString preparation = getLookupPreparation(m_state.accumulatorOut(), outVar, index);
generateLookup(lookup, initialization, preparation);
generateMoveOutVar(outVar);
m_body += u"}\n"_s;
@ -2295,16 +2296,16 @@ void QQmlJSCodeGenerator::generate_CallQmlContextPropertyLookup(int index, int a
AccumulatorConverter registers(this);
const QString indexString = QString::number(index);
m_body += u"{\n"_s;
QString outVar;
m_body += argumentsList(argc, argv, &outVar);
const QString lookup = u"aotContext->callQmlContextPropertyLookup("_s + indexString
+ u", args, types, "_s + QString::number(argc) + u')';
const QString initialization = u"aotContext->initCallQmlContextPropertyLookup("_s
+ indexString + u')';
generateLookup(lookup, initialization);
m_body += initAndCall(
argc, argv, u"callQmlContextPropertyLookup(%1, %2, %3)"_s.arg(index),
u"initCallQmlContextPropertyLookup(%1, %2, %3)"_s.arg(index), &outVar);
const QString lookup = u"doCall()"_s;
const QString initialization = u"doInit()"_s;
const QString preparation = getLookupPreparation(m_state.accumulatorOut(), outVar, index);
generateLookup(lookup, initialization, preparation);
generateMoveOutVar(outVar);
m_body += u"}\n"_s;
@ -2899,6 +2900,12 @@ QString QQmlJSCodeGenerator::getLookupPreparation(
return var + u" = QVariant(aotContext->lookupResultMetaType("_s
+ QString::number(lookup) + u"))"_s;
}
if (registerIsStoredIn(content, m_typeResolver->jsPrimitiveType())) {
return var + u" = QJSPrimitiveValue(aotContext->lookupResultMetaType("_s
+ QString::number(lookup) + u"))"_s;
}
// TODO: We could make sure they're compatible, for example QObject pointers.
return QString();
}

View File

@ -314,7 +314,10 @@ private:
QString eqIntExpression(int lhsConst);
QString argumentsList(int argc, int argv, QString *outVar);
QString initAndCall(
int argc, int argv, const QString &callMethodTemplate,
const QString &initMethodTemplate, QString *outVar);
QString castTargetName(const QQmlJSScope::ConstPtr &type) const;
bool inlineStringMethod(const QString &name, int base, int argc, int argv);

View File

@ -1160,21 +1160,12 @@ void tst_QmlCppCodegen::consoleTrace()
QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/consoleTrace.qml"_s));
QVERIFY2(!component.isError(), component.errorString().toUtf8());
#if !defined(QT_NO_DEBUG) || defined(QT_TEST_FORCE_INTERPRETER)
// All line numbers in debug mode or when interpreting
// We always get line numbers for the first call since we need to do the "init" step.
QTest::ignoreMessage(QtDebugMsg, R"(c (qrc:/qt/qml/TestTypes/consoleTrace.qml:6)
b (qrc:/qt/qml/TestTypes/consoleTrace.qml:5)
a (qrc:/qt/qml/TestTypes/consoleTrace.qml:4)
expression for onCompleted (qrc:/qt/qml/TestTypes/consoleTrace.qml:7))");
#else
// Only top-most line number otherwise
QTest::ignoreMessage(QtDebugMsg, R"(c (qrc:/qt/qml/TestTypes/consoleTrace.qml:6)
b (qrc:/qt/qml/TestTypes/consoleTrace.qml)
a (qrc:/qt/qml/TestTypes/consoleTrace.qml)
expression for onCompleted (qrc:/qt/qml/TestTypes/consoleTrace.qml))");
#endif
QScopedPointer<QObject> object(component.create());
QVERIFY(!object.isNull());