Re: [Development] Redesigning QML value types
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 ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] Redesigning QML value types
This becomes more and more non-intuitive. let a: font = f // copy f because a is value-typed? That syntax doesn't exist in QML. You can only type-annotate function arguments and return types, but not locals. let b = a // copy because a is typed, and b's type is inferred from a? That statement by itself is a no-op in JS. It just adds another name to the value referred to by a. Here you have one of the reasons why I'm hesitant to add further syntax like you propose above. I would also have to add extra byte code instructions, invent semantics for them, and figure out how that would interact with the ECMAScript standard. let c = f // ref because f is JS-ish type Here we just load f into a local and form a value type reference. That's the only thing you can do when loading a value type from a property. function foo(arg) { return arg } let d = foo(b) // ref because f is JS-ish type ? or copy because a is typed, and b's type is inferred from a? Here we're indeed not in Kansas anymore. let b = d // ref? Well, it's wicked. Let's never allow typed locals! ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] Redesigning QML value types
Should there be a way to pass by reference even when using type annotations? Providing a way to pass by reference for type annotated functions will probably in people expecting such code to be compiled to C++. I would only provide such syntax if qmlcachegen and qmlsc can deal with it. If it's of high priority, we can go back to the idea of chained wrappers as representation for value type references, but currently I don't think it's the most important thing to do. Could the language server tell us (so the editor can provide hover feedback or something) what’s passed by value and what by reference? I think now my understanding has been wrong a lot of the time, and may be in the future too. Oh, I would actually be interested in your past assumptions on this. In general, it would be easy for the language server to see something like "this is a non-trivial value type pass as a (non-)annotated argument". I don't know off the top of my head if the language server protocol has any facilities for what you are envisioning. In C++ some people are nagging constantly about using const-refs everywhere. But here it looks like you are in favor of passing by value. (We are supposed to use type annotations more, right? And then we get copying?) The copying may be an issue. Mind that whenever you read a value type from a property using a getter you already copy once and every time you write it back you copy again. Therefore, I generally assume value types are easy to copy, and we can afford another copy when passing a value type as function argument. However, qmlcachegen and qmlsc are in fact rather clever about this. They will only perform the copy if you are going to modify the argument. Otherwise they will take a reference instead. Of course if you're calling from interpreted code into compiled code or vice versa we do have to copy. I mostly think of our JS engine as being something to minimize the use of, at least in nontrivial applications. (Yeah writing single-qml-file apps is fun; only then do I actually want to use JS in my QML.) If we can never leave out the runtime, even when using the compiler or even when we manage to write pure-declarative QML, then it should be kept as small and simple as possible. Sometimes small size gives you much more speed than you expect, just by fitting better into CPU cache. (And if we were free to pick a tried-and-true small language, it could be even smaller.) So I don’t know how much it’s worth changing things for performance. QML is declarative first, so I tend to think optimizing the size and creation time of the objects that are declared is more important than compiling the little bits of imperative code that you shouldn’t write in the first place. I agree to some extent. The main question here is how value types are supposed to work and one problem is that the behavior of the JS engine and the compiled code differ. A solution would indeed be to just drop support for compiling functions to C++ and keep the rest as it is. Since that support already exists, I would expect some backlash, though. Yet, indeed I'm very cautious about what features I implement in the compilers. If we can pass Q_GADGETs by reference (properties and signal arguments)… you know I wanted that for a few years, but I thought it’s supposed to be incompatible with having them as value types. But if value types are often passed by reference anyway, why is it again that I can’t emit a QMouseEvent and let the QML user set the accepted flag? (As ugly as that is) Signals auto-detach value types. Remember that signals have always been typed in QML. Therefore, when sending a signal, the parameters are transformed into their C++ equivalents and the back reference to the property is lost. Even in C++, sending references through signals is at least somewhat adventurous. You might, however, define a value type that simply holds a pointer to a QMouseEvent and has a Q_INVOKABLE setAccepted() method. That thing you can send and call wherever you like. You just have to take care that the pointer stays valid. ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] Redesigning QML value types
This becomes more and more non-intuitive. let a: font = f // copy f because a is value-typed? let b = a// copy because a is typed, and b's type is inferred from a? let c = f// ref because f is JS-ish type function foo(arg) { return arg } let d = foo(b) // ref because f is JS-ish type ? or copy because a is typed, and b's type is inferred from a? let b = d // ref? Regards, Konstantin ср, 21 сент. 2022 г. в 19:45, Ulf Hermann : > > Generally I feel that all the gritty details in what to do and what > > not do in qml to have efficient, compiled code, are more and more > > confusing. There is so much to consider and basically all the > > documentation about this is hidden in Qt blog posts. > > Well, yes. All of this is 10 years late and we have to shoehorn it into > an existing language with compatibility promises. So, some inconsistency > cannot be avoided. I'm doing what I can to avoid confusion, though. The > blog posts are in fact being transformed into regular documentation. It > takes some time, though. > > > Anyway I'd just like to note that, as far as I can remember, type > > annotation are currently not supported in lambdas (=> syntax). > > Indeed not. Type annotations are mostly meant for methods of QML types, > which cannot be phrased as arrow functions. The only other place where > they might be beneficial is in inner functions inside bindings or > methods. The compilers cannot generate efficient code to call those, > yet. Therefore it doesn't make much sense to annotate them, yet. We'll > get back to that when we get there. Help is always welcome, by the way. > > regards, > Ulf > ___ > Development mailing list > Development@qt-project.org > https://lists.qt-project.org/listinfo/development > ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] Redesigning QML value types
Generally I feel that all the gritty details in what to do and what not do in qml to have efficient, compiled code, are more and more confusing. There is so much to consider and basically all the documentation about this is hidden in Qt blog posts. Well, yes. All of this is 10 years late and we have to shoehorn it into an existing language with compatibility promises. So, some inconsistency cannot be avoided. I'm doing what I can to avoid confusion, though. The blog posts are in fact being transformed into regular documentation. It takes some time, though. Anyway I'd just like to note that, as far as I can remember, type annotation are currently not supported in lambdas (=> syntax). Indeed not. Type annotations are mostly meant for methods of QML types, which cannot be phrased as arrow functions. The only other place where they might be beneficial is in inner functions inside bindings or methods. The compilers cannot generate efficient code to call those, yet. Therefore it doesn't make much sense to annotate them, yet. We'll get back to that when we get there. Help is always welcome, by the way. regards, Ulf ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] Redesigning QML value types
On Wed, 21 Sep 2022 14:59:08 +0200, Ulf Hermann wrote: > If a function has type annotations, we pass its arguments and return > value by value (if they are value types). Otherwise we pass by > reference. Inside a function, everything is a reference. > > This makes some intuitive sense: The type annotations are QML types, not > JavaScript types. You might expect QML _value_ types to be passed by > value. As long as the arguments and return values (or indeed the locals > on the stack) are untyped, we're firmly in JavaScript land. I like this idea in general in that it's explicit and backward compatible. Following this type annotation rule, maybe we'll need to consider about explicitly-value-typed vs JS-ish variable? let a: font = f // copy f because a is value-typed? let b = a// copy because a is typed, and b's type is inferred from a? let c = f// ref because f is JS-ish type And it's probably nice to provide an explicit copy/clone function (like the proposed detach()) if untyped value isn't copied by default: let v1 = vec3d.explicit_copy_func(); v1.x = whatever; // don't wanna mutate the original vector Regards, ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] Redesigning QML value types
> On 2022 Sep 21, at 14:59, Ulf Hermann wrote: > > Thanks for the feedback! I've thought about it some more and done some > experiments and I think we can solve this in a way that makes everyone happy: > > If a function has type annotations, we pass its arguments and return value by > value (if they are value types). Otherwise we pass by reference. Inside a > function, everything is a reference. Should there be a way to pass by reference even when using type annotations? Could the language server tell us (so the editor can provide hover feedback or something) what’s passed by value and what by reference? I think now my understanding has been wrong a lot of the time, and may be in the future too. In C++ some people are nagging constantly about using const-refs everywhere. But here it looks like you are in favor of passing by value. (We are supposed to use type annotations more, right? And then we get copying?) > This makes some intuitive sense: The type annotations are QML types, not > JavaScript types. You might expect QML _value_ types to be passed by value. > As long as the arguments and return values (or indeed the locals on the > stack) are untyped, we're firmly in JavaScript land. > > You also get to keep the convenience of "o.a = 10" and similar constructions > this way, and we can seemlessly extend that to more deeply or differently > nested structures. > > Remember that qmlsc and qmlcachegen only compile typed functions to C++ and > the generated code will also only call typed functions. This means, with the > above proposal, our AOT-compiled functions don't have to pass value type > references as arguments or return types. Therefore, we can leave the > compilers as they are. Otherwise, if we had to pass value type references > instead of actual value types between our AOT-compiled functions, their > signatures would have to change. That would have user-visible effects on the > metaobjects. Plus, if we were to return value type references from functions, > we'd have to allocate at least some of those value types on the heap, and > garbage collect them at some point. I feel we really shouldn't go there. > > The downside is that we have to swallow a behavior change in interpreted > code: So far the QML engine passes everything by reference and now it will > have to detach the arguments and return values when calling or returning from > a typed function. However, since the primary reason to annotate your > functions is to get them compiled to C++, and since qmlsc and qmlcachegen > already generate code that behaves the new way, this may not be all that hard. I mostly think of our JS engine as being something to minimize the use of, at least in nontrivial applications. (Yeah writing single-qml-file apps is fun; only then do I actually want to use JS in my QML.) If we can never leave out the runtime, even when using the compiler or even when we manage to write pure-declarative QML, then it should be kept as small and simple as possible. Sometimes small size gives you much more speed than you expect, just by fitting better into CPU cache. (And if we were free to pick a tried-and-true small language, it could be even smaller.) So I don’t know how much it’s worth changing things for performance. QML is declarative first, so I tend to think optimizing the size and creation time of the objects that are declared is more important than compiling the little bits of imperative code that you shouldn’t write in the first place. Being able to consistently set properties on nested value types and value types in lists, in-place, sounds like a good thing to get working. If we can pass Q_GADGETs by reference (properties and signal arguments)… you know I wanted that for a few years, but I thought it’s supposed to be incompatible with having them as value types. But if value types are often passed by reference anyway, why is it again that I can’t emit a QMouseEvent and let the QML user set the accepted flag? (As ugly as that is) ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] Redesigning QML value types
Hi, Generally I feel that all the gritty details in what to do and what not do in qml to have efficient, compiled code, are more and more confusing. There is so much to consider and basically all the documentation about this is hidden in Qt blog posts. Anyway I'd just like to note that, as far as I can remember, type annotation are currently not supported in lambdas (=> syntax). Please correct if I'm wrong. Nils ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] Redesigning QML value types
Thanks for the feedback! I've thought about it some more and done some experiments and I think we can solve this in a way that makes everyone happy: If a function has type annotations, we pass its arguments and return value by value (if they are value types). Otherwise we pass by reference. Inside a function, everything is a reference. This makes some intuitive sense: The type annotations are QML types, not JavaScript types. You might expect QML _value_ types to be passed by value. As long as the arguments and return values (or indeed the locals on the stack) are untyped, we're firmly in JavaScript land. You also get to keep the convenience of "o.a = 10" and similar constructions this way, and we can seemlessly extend that to more deeply or differently nested structures. Remember that qmlsc and qmlcachegen only compile typed functions to C++ and the generated code will also only call typed functions. This means, with the above proposal, our AOT-compiled functions don't have to pass value type references as arguments or return types. Therefore, we can leave the compilers as they are. Otherwise, if we had to pass value type references instead of actual value types between our AOT-compiled functions, their signatures would have to change. That would have user-visible effects on the metaobjects. Plus, if we were to return value type references from functions, we'd have to allocate at least some of those value types on the heap, and garbage collect them at some point. I feel we really shouldn't go there. The downside is that we have to swallow a behavior change in interpreted code: So far the QML engine passes everything by reference and now it will have to detach the arguments and return values when calling or returning from a typed function. However, since the primary reason to annotate your functions is to get them compiled to C++, and since qmlsc and qmlcachegen already generate code that behaves the new way, this may not be all that hard. best regards, Ulf ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] Redesigning QML value types
Thanks for writing this up, Ulf! Working on porting Qt Location to Qt 6 right now, I’m looking very much forward to the improved support for value types :) > On 20 Sep 2022, at 18:13, Ulf Hermann wrote: > > Hi, > > I'm currently trying to transform QML value types, such as font, rect, > size, etc, into something less random and more predictable. On that > occasion I'm wondering what semantics people actually expect from value > types. […] > 1. Value types are passed by value > — […] > However, since we've never implemented value type references for lists, > lists of value types behave exactly this way. The following does in fact > not modify the list, but only a copy of the object at index 10: > > property list fonts: [ ... ] > Component.onCompleted: fonts[10].pointSize = 33 > > The same holds for nested value types. Assume a value type "outer" that > has a property "a" of another value type, which has an integer property > "b". The following does not write back: > > property outer o > Component.onCompleted: o.a.b = 10 Not a fan. Within a scope, or at least within a statement, operating on copies is unexpected and confusing. Esp with nested lists of structured value types, ie. o.a[1].b[10] = 10 That turns into a lot of code when write-back has to be done explicitly. > 2. Everything is a reference > > > JavaScript doesn't really have value types. The doEvil() function thus > receives a reference to the original 'f', not a copy. The modification > of pointSize is written back to the original property. > > With font you might still wrap your head around it because font "looks > like" an object with all its properties and internal logic. However, you > can also pass a point or a rect around through various functions, and > whenever you modify it, the result would be written back to the original > place where it was retrieved from, possibly in a different file half an > hour ago. I personally find that rather confusing. Agree. It might be more natural for a Python, Ruby, or JavaScript developer, but I’d much prefer functions not to have implicit side effects by default. With EcmaScript 6 we can use destructuring assignment syntax to work with functions that return multiple values through an array or object, which makes it reasonably convenient to return multiple values from a function. > A middle ground would be that value types are passed by value when > calling or returning from a function, but are treated as references and > written back on modification inside a function. Inside the same function > qmlsc and qmlcachegen could keep track of where a value was loaded from > and write it back when it's modified. I've played with the concept a > bit, and it's possible to support nested value types and lists of value > types this way. All the writing back would still be inefficient, though. I like that approach, it’s easily defined when the value is copied, and I think it does the right thing in most cases. Taking this one step further, could we treat lvalues as references: // o.a.b gets modified o.a.b = 10 But always copy rvalues: // o.a.b does not get modified var old_b = o.a.b // copy of rvalue old_b = 15 // call_function gets a copy of o.a.b, which then gets modified explicitly o.a.b = call_function(o.a.b) ? > In addition to whatever course is chosen, we might add a detach() method > on the JavaScript prototype used for all value types. With that you > could intentionally detach a value from the property it was loaded from. Makes sense. It would solve the problem that modifying several attributes of a value would require many (slow) write-backs. Volker ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
Re: [Development] Redesigning QML value types
> 1. Value types are passed by value > 2. Everything is a reference Most languages have a way to specify this in the signature. Swift has the “inout” keyword, c# has “ref”. Have you considered this as an option as well? BR, Richard ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development
[Development] Redesigning QML value types
Hi, I'm currently trying to transform QML value types, such as font, rect, size, etc, into something less random and more predictable. On that occasion I'm wondering what semantics people actually expect from value types. One thing you have to know in advance is that QML has this internal concept of "value type references". A value type reference is object of a value type combined with a sort of pointer to the property it was retrieved from. Whenever the value type object is modified, it is subsequently written back to its original property. This mechanism is mildly inefficient, as you may imagine. Yet, we use it all over the place. Now, consider the following snippet of QML: import QtQuick QtObject { property font f function doEvil(ff: font) { ff.pointSize = 22 } Component.onCompleted: doEvil(f) } Assume the default pointSize of a font object is 0. What pointSize should the member 'f' of the created object receive here? I see two ways of looking at this: 1. Value types are passed by value -- The doEvil() function then receives a copy of the font and the original font is never modified. Its pointSize stays 0. If this is the way it should behave, you get to tell me what the following should do, though: Component.onCompleted: { var ff = f; ff.pointSize = 22; } In JavaScript, this is indistinguishable from simply "f.pointSize = 22". So, strictly speaking, the latter would have to modify a copy of 'f', not 'f' itself. That, however, would make working with value types somewhat more complicated. You'd have to explicitly write them back after modification. On the plus side, you could modify several properties in a row and only write the value back once. Since value types have been writing themselves back for about 10 years now, we'd need some compatibility mechanism to phase such a behavior in, over several versions of Qt. And I'm sure someone would hate it. However, since we've never implemented value type references for lists, lists of value types behave exactly this way. The following does in fact not modify the list, but only a copy of the object at index 10: property list fonts: [ ... ] Component.onCompleted: fonts[10].pointSize = 33 The same holds for nested value types. Assume a value type "outer" that has a property "a" of another value type, which has an integer property "b". The following does not write back: property outer o Component.onCompleted: o.a.b = 10 2. Everything is a reference JavaScript doesn't really have value types. The doEvil() function thus receives a reference to the original 'f', not a copy. The modification of pointSize is written back to the original property. With font you might still wrap your head around it because font "looks like" an object with all its properties and internal logic. However, you can also pass a point or a rect around through various functions, and whenever you modify it, the result would be written back to the original place where it was retrieved from, possibly in a different file half an hour ago. I personally find that rather confusing. Another drawback of this is that it would be rather hard to compile a function that deals with value types to C++. In C++, value types don't have a back reference to the property they were loaded from. A QML font is just a QFont and nothing else. This assumption allows qmlcachegen and qmlsc to generate efficient code. Yet, as you may notice by toggling QV4_FORCE_INTERPRETER on the QML program above, they already mess this up today (and no one has noticed). So, the consequence of going with "Everything is a reference" would be that we couldn't compile any function to C++ that passes a value type to a JavaScript function or uses a value type returned from a JavaScript function. A middle ground would be that value types are passed by value when calling or returning from a function, but are treated as references and written back on modification inside a function. Inside the same function qmlsc and qmlcachegen could keep track of where a value was loaded from and write it back when it's modified. I've played with the concept a bit, and it's possible to support nested value types and lists of value types this way. All the writing back would still be inefficient, though. In addition to whatever course is chosen, we might add a detach() method on the JavaScript prototype used for all value types. With that you could intentionally detach a value from the property it was loaded from. regards, Ulf ___ Development mailing list Development@qt-project.org https://lists.qt-project.org/listinfo/development