Skip to content

QML Engine Internals, Part 1: QML File Loading

In this series of blog posts, we’ll look under the hood of the QML engine and uncover some of its inner workings. The articles are based on the Qt5 version of QtQuick, QtQuick 2.0.

Most people know  that each element in a QML file is backed by a C++ class. When a QML file is loaded, the QML engine somehow creates one C++ object for all elements in the file. In this blog post, we’ll examine how the QML engine goes from reading a text file to having a complete tree of C++ objects. The Qt documentation on QML has extensive descriptions that cover how QML and C++ work together, a read that is well worth the time. In the blog series, I am assuming the parts covered by that documentation are understood.

The Example

Throughout this post, we’ll use an example that doesn’t do any useful or exciting, but covers some interesting parts of the QML functionality:

import QtQuick 2.0

Rectangle {
    id: root
    width: 360
    height: width + 50
    color: "lightsteelblue"
    property alias myWidth: root.width
    property int counter: 1

    function reactToClick() {
        root.counter++
    }

    Text {
        id: text
        text: qsTr("Hello World: " + counter)
        anchors.centerIn: parent
    }

    MouseArea {
        id: mouseArea
        anchors.fill: parent
        onClicked: {
            reactToClick()
        }
    }
}

This file has three elements in it, Rectangle, Text and MouseArea. These correspond to the C++ classes QQuickRectangle, QQuickText and QQuickMouseArea. Those classes are only exported to QML, the C++ versions are private and not available for users of Qt. The elements are drawn using a OpenGL scenegraph. Drawing and event handling are both orchestrated by QQuickView . The C++ object tree corresponds to the one in the QML file, which we can verify with KDAB’s Qt introspection tool, Gammaray:

As expected, the classes QQuickMouseArea and QQuickText show up in the object tree. But what is QQuickRectangle_QML_0? There is no C++ class with such a name in the Qt sources! We’ll come back to that in a later blog post, for now you can assume that an object of type QQuickRectangle was used.

Let’s go a step further and run the application in the QML profiler:

We see that a bunch of time is spent setting up the scene, which is the Creating phase. This is followed by a bit of Painting, which is what we would expect. But what is this Compiling phase? Is it creating machine code? Time to dig into the QML file loading code a bit deeper.

QML File Loading Steps

When loading a QML file, there are 3 distinct steps happening, which we’ll examine in the following sections:

  1. Parsing
  2. Compiling
  3. Creating

Parsing

First of all, the QML file is parsed, which is handled by QQmlScript::Parser (Doesn’t exist anymore). Most of the parser internals are auto-generated from a grammar file. The abstract syntax tree (AST) of our example looks like this:

This was generated by graphviz using a dotfile produced with the patch at http://www.kdab.com/~thomas/stuff/ast-graph.diff

The AST is quite low-level, therefore it is transformed into a higher level structure of Objects, Properties and Valuesin the next step. This is done using a visitor on the AST. At this level, Objects correspond to QML elements, and Property/Value pairs to QML properties and values like “color” and “lightsteelblue”. Even signal handlers like onClicked are just property/value pairs, in this case the value is the Javascript function body.

Compiling

Now in theory, the structure of Objects, Properties and Values would be enough to examine for creating the associated C++ objects and assigning the values to the properties there. However, the Objects, Properties and Values are still quite raw, and require some post-processing before the C++ objects can be created. This post-processing is done by QQmlCompiler, which explains what the compiling phase we saw in the QML profiler is about. The compiler creates an QQmlCompiledData object for the QML file. Examining QQmlCompiledData and creating C++ objects from it is quite a lot faster than examining the Objects, Properties and Values. When using a QML file multiple times, say for example a Button.qml that is used all over the place in other QML files, Button.qml would only be compiled once. The QQmlCompiledData for Button.qml is kept around, and each time a Button component is used, the C++ objects are created by examining the QQmlCompiledData. The later is the creating phase, which can be seen in the QML profiler output.

To sum up: Parsing a QML file and compiling it is only done once, after that the QQmlCompiledData object is used to quickly create the C++ objects.

Creating

I won’t go into details about QQmlCompiledData, but one thing might have caught your attention: The “QByteArray bytecode” member variable. The instructions to create C++ objects and to assign the correct values to its properties are actually compiled to bytecode, which is then later interpreted by a bytecode interpreter! The bytecode contains a bunch of instructions, and the rest of QQmlCompiledData is just used as auxiliary data when executing the instructions.

In the creating phase, the bytecode is interpreted by the class QQmlVME. Looking into QQmlVME::run(), the interpreter basically loops over all instructions contained in the bytecode, with a big switch statement for the different instruction types. By running the app with QML_COMPILER_DUMP=1, we can see the individual instructions of the bytecode:

Index           Operation               Data1   Data2   Data3   Comments
-------------------------------------------------------------------------------
0               INIT                    4       3       0       0
1               INIT_V8_BINDING 0       17
2               CREATECPP                       0
3               STORE_META
4               SETID                   0                       "root"
5               BEGIN                   16
6               STORE_INTEGER           45      1
7               STORE_COLOR             41                      "ffb0c4de"
8               STORE_COMPILED_BINDING  10      2       0
9               STORE_DOUBLE            9       360
10              FETCH_QLIST             2
11              CREATE_SIMPLE           32
12              SETID                   1                       "text"
13              BEGIN                   16
14              STORE_V8_BINDING        43      0       0
15              FETCH                   19
16              STORE_COMPILED_BINDING  17      1       1
17              POP
18              STORE_OBJECT_QLIST
19              CREATE_SIMPLE           32
20              SETID                   2                       "mouseArea"
21              BEGIN                   16
22              STORE_SIGNAL            42      2
23              FETCH                   19
24              STORE_COMPILED_BINDING  16      0       1
25              POP
26              STORE_OBJECT_QLIST
27              POP_QLIST
28              SET_DEFAULT
29              DONE
-------------------------------------------------------------------------------

CREATE_SIMPLE is the most important instruction, it creates a C++ object, using a database of registered objects in QQmlMetaType. STORE_INTEGER is the instruction to write an integer value to a property. STORE_SIGNAL is used to create a bound signal handler. STORE_*_BINDING is used to create a property binding. More about bindings to come in the next blog post of this series. SETID obviously sets the identifier of an object, which is not an ordinary property.

The VME has a stack of objects, all instructions like STORE_* operate on the top object. FETCH puts a specific QObject on top of the stack, POP removes the top object. All of the instructions make heavy use of integer indices, for example the STORE_COLOR instruction writes to property 41, which is the property index of the target QObject’s meta object.

To sum up: Once a QML file is compiled, creating an instance of it is just a matter of executing the bytecode of the compiled data.

Conclusion

That is the end of this blog post, we have covered on how a QML file is parsed, processed, compiled and later how the objects are actually created by the VME. I hope you gained some interesting insights about the QML engine.

Stay tuned for the next blog post, in which we’ll examine how property bindings work.

Read Part 2…

 

FacebookTwitterLinkedInEmail

Categories: KDAB Blogs / KDAB on Qt / QML

14 thoughts on “QML Engine Internals, Part 1: QML File Loading”

  1. Really interesting post! I didn’t know the internals of QML, and they seem quite well thought. For me it’s been a surprise that there’s actually a step that compiles to bytecode. Keep the posts coming =)

  2. Very useful post. Thank you. 🙂

    It always bugged me a bit I do not know much about the underlyings.

  3. I have two minor questions:

    1) What does the “VME” abbreviate in the class name “QQmlVME”?
    2) Can you describe in few words (perhaps give an example) what the visitor does during the low to high-level transformation?

    1. Thomas McGuire

      1) I don’t know actually… I suspect something like Virtual Machine Environment

      2) The visitor basically iterates over all nodes in the AST and creates QQmlScript::Objects, Properties and Values for the nodes. The tree of Objects, Properties and Values is the high level structure.
      For example when the an UiObjectDefinition node is encountered, the visitor creates an Object for that, then processes all child AST nodes of the object. These child AST nodes might for example be property definitions, which would then be added as a Property to the parent Object.
      I suggest to step over the code in a debugger to see how it works in detail.

    1. Thomas McGuire

      QML has no support for precompiling. It sure would be nice, would remove the ‘compiling’ phase from startup.

      There was some talk about this feature, can’t remember where I read about that. The conclusion was that while it is technically a good idea and apparently not too hard to implement, just no one had time to add this feature yet.

  4. Hi
    The QML internals described neatly, which are not found at one place at Qt website.

    I notice that, there are many links in the article still pointing to QtDeclarative/..
    It would be nice to see if those broken links are fixed, just in case, if the internal things have already got changed(evolved), it would address that also.

    Thanks

    1. Thomas McGuire

      Right, there are a lot of outdated links on this page.
      I recommend browsing the code at http://code.woboq.org/qt5/qtdeclarative/.

      As for fixing the links: The content as a whole is quite outdated by now, as the QML engine evolved quickly. For example, the QML engine doesn’t have a VME anymore. Thus, the article needs a large rewrite,, or maybe a followup article. I don’t have plans for any of that at the moment though.

  5. @Thomas McGuire
    Hi, this post is very helpful to me.
    now we will use Qt QML for our APP(IOS), but we want to use iOS
    base widgets like UIView, UILabel, UIButton, so we just want to replace QQuickText to UILabel in object creating, i want to know if this is possible and how ? thanks very much.

    1. Thomas McGuire

      QML with another UI than QtQuick has been done before, for example Blackberry’s Cascades UI framework.

      So using native iOS controls with QML is in theory possible, however I am not aware of an existing project providing a QML API for iOS controls, so you’d have to write that API yourself. Recently, someone did exactly that for native Android controls, wrapping them in a QML API: http://achipa.blogspot.de/2014/11/qml-wrappers-for-native-android.html. So you could do the same for iOS and write a QML API. If you do so, please publish that project, I am sure others would find it useful too.

Leave a Reply

Your email address will not be published. Required fields are marked *