Re: [Development] Redesigning QML value types

2022-09-26 Thread Ulf Hermann

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

2022-09-21 Thread Ulf Hermann

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

2022-09-21 Thread Ulf Hermann

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

2022-09-21 Thread Konstantin Ritt
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

2022-09-21 Thread 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


Re: [Development] Redesigning QML value types

2022-09-21 Thread Yuya Nishihara
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

2022-09-21 Thread Shawn Rutledge

> 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

2022-09-21 Thread Nils Jeisecke via Development
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

2022-09-21 Thread Ulf Hermann
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

2022-09-21 Thread Volker Hilsheimer
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

2022-09-21 Thread Richard Gustavsen
> 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

2022-09-20 Thread Ulf Hermann
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