[Development] Redesigning QML value types
Ulf Hermann
ulf.hermann at qt.io
Mon Sep 26 12:53:38 CEST 2022
Hi,
after some more experimentation, I've realized the problem is actually
of larger dimension. Since function signatures are ignored when
interpreting or running JIT-compiled QML code, you already get divergent
behavior without all the value type write back problems. Take for
example the following function:
function getLength(a: string) : int { return a.length }
Now this function is nicely compilable to C++. If it's caller is
compiled to C++, we could enforce the signature at compile time. Suppose
the caller is _not_ compiled to C++, though. The caller does
getLength(b) where b is of a value type with a "length" property. When
getLength is AOT-compiled, the this coerces b into a string (most likely
"[object Object]", but you can have a custom toString() method) and gets
the length of that. If it's interpreted, the type annotations is
ignored, and getLength will get the "length" property of a.
You may argue that the coercion should fail in the case where
getLength() is AOT-compiled, but since at the call site we're deep in
JavaScript land, that would be fairly incosistent. When calling
C++-defined methods, we can coerce everything to string.
(OK, the coercion rules when calling a QObject method are already
different from what we do when calling a typed JS method ... but let's
solve that another time.)
So, the clean solution would be to coerce all arguments into their
declared types even when interpreting or running JIT-compiled code.
Now, the type coercion is not free. If you call from one AOT-compiled
function into a different one, we don't have to do it because the
AOT-compiled functions are already using the C++ types. However, when
calling from an interpreted or JIT'ed function into an AOT-compiled one,
there is some overhead. Most of the time it's well worth it because the
actual function then runs much faster.
Considering this, I don't want to force the extra call overhead on calls
to interpreted or JIT'ed functions, as that would actually be slower. It
would discourage people from using the type annotations.
Furthermore, as shown above, enforcing the types would introduce subtle
behavior changes into code that's not compiled to C++. As the
compilation to C++ envolves a number of other changes to your
application (such as using qt_add_qml_module), we can probably live with
a slight difference in behavior between interpreted and AOT-compiled
code. It would be somewhat worse to introduce deliberate behavior
changes to code completely unaffected by any of this.
You should be _able_ to enforce the same behavior in either execution
mode, though. Therefore, my current idea is to introduce a pragma
FunctionSignatureBehavior that you can set to "Enforced" or "Ignored".
If explicitly enforced, the engine would coerce the arguments even when
calling an interpreted or JIT'ed function. If explicitly ignored,
qmlcachegen and qmlsc would refuse to compile any functions and calls to
QML-defined function in the component to C++. (they would still compile
bindings and signal handlers that at most call C++-defined functions,
though)
You can see my current work in progress at
https://codereview.qt-project.org/c/qt/qtdeclarative/+/434663
regards,
ULf
More information about the Development
mailing list