[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