Re: Multiple return values...
On 16 March 2012 02:26, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: On 3/15/12 5:44 PM, foobar wrote: On Thursday, 15 March 2012 at 18:23:57 UTC, Andrei Alexandrescu wrote: I understand how the draw of reaching for new syntax is extremely alluring. I used to fall for it much more often, but over years I have hardened myself to resist it, and I think that made me a better language designer. In this stage of D, I think we should all have an understanding that adding syntax is not a win. Instead, it is an acknowledgment that the existing language, for all its might, is unable to express the desired abstraction. This is sometimes fine (such as in the case of introducing multiple new symbols from one tuple), but generally the question is not why couldn't we add syntax X? but instead why should we add syntax X? Andrei I agree that this is an acknowledgement of the current language's inability to express the abstraction. That's why many people ask for it to be added in the first place. We should add this syntax because it *is* impossible ATM to implement the required abstraction. I think this is a reasonable request: (auto a, b) = fun(); --- static assert(fun().length == 2); auto __t = fun(); auto a = __t[0]; auto b = __t[1]; To express the idiom in the example, the programmer needs to do the expansion by hand, which is verbose and introduces unnecessary symbols. So the language as is has an expressiveness problem (albeit not a pernicious one) that is nice to solve. Andrei++ This could also be done for arrays too. (int a, b) = arr[]; - static assert(arr.length == 2); int a = arr[0]; int b = arr[1]; Or possibly a use in variadic templates, which could make D act more like how some scripting languages work. void conn(T ...)(T args) { // vars get default init if value wasn't passed to function. (string server, port, username, password) = args[]; } -- Iain Buclaw *(p e ? p++ : p) = (c 0x0f) + '0';
Re: Multiple return values...
On 15 March 2012 17:51, Manu turkey...@gmail.com wrote: On 15 March 2012 19:05, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: On 3/15/12 11:30 AM, Manu wrote: On 15 March 2012 17:32, Andrei Alexandrescu seewebsiteforem...@erdani.org mailto:seewebsiteforem...@erdani.org wrote: One note - the code is really ingenious, but I still prefer swap() in this case. It's more concise and does less work in the general case. swap(a[i + k], a[j + j]); only computes the indexing once (in source, too). It all still feels to me like a generally ugly hack around the original example: a,b = b,a; It's a function call. Why is a function call a ugly hack? Because now we're involving libraries to perform a trivial assignment. Question, what benefits would (a, b = b, a) bring over swap(a, b) if it was included? -- Iain Buclaw *(p e ? p++ : p) = (c 0x0f) + '0';
Re: Multiple return values...
On 03/16/12 01:03, H. S. Teoh wrote: On Fri, Mar 16, 2012 at 12:11:44AM +0100, Simen Kjærås wrote: On Thu, 15 Mar 2012 23:44:09 +0100, foobar f...@bar.com wrote: Is swap usually inlined by the compiler? This is the body of swap for simple types: auto tmp = lhs; lhs = rhs; rhs = tmp; For more complex types: ubyte[T.sizeof] t = void; auto a = (cast(ubyte*) lhs)[0 .. T.sizeof]; auto b = (cast(ubyte*) rhs)[0 .. T.sizeof]; t[] = a[]; a[] = b[]; b[] = t[]; So yeah. If that's not inlined, something's wrong. Ideally, though, a swap of large objects should be a single loop of xchg instructions (in x86 anyway, xchg may not exist on other architectures). Unless dmd is much more clever than I thought, the above code would generate 3 loops, which uses more memory and is (probably) slower. x86 xchg w/ mem ref implies lock, plus you'd have to load and store one of the operands anyway. artur
Re: Multiple return values...
On 16 March 2012 04:26, Andrei Alexandrescu seewebsiteforem...@erdani.orgwrote: A good design should strive to provide general features instead of special cases (E.g. swap is limited to the 2-tuple case). Also, why force an overhead of a function call on such a basic feature as assignment? Is swap usually inlined by the compiler? Sorry, this is grasping at straws. 1. swap can be easily generalized to take any number of arguments. I'm very happy that's possible, we've expended great efforts on making variadic functions as powerful as they are. But nobody asked for swap with many arguments until now. Which segues into... 2. When was the last time you needed to swap arbitrary numbers of elements, and so badly and frequently, you needed a new language feature for that? This is called a swizzle. And constantly comes up when dealing with x,y,z,w, or r,g,b,a. It could just as easily be expressed this way: a,b = tuple(b,a); // swap r,g,b,a = tuple(a,r,g,b); // swizzle At which point this multi assignment boils down to the exact same question of syntax as MRV return assignment. I don't really distinguish this swap/swizzle from MRV. It all comes back to the return assignment syntax. 3. Function overhead is solved by inlining, not by adding new features. That improves all functions, not only swap. Requiring inlining to make it efficient is not enough. The proposed MRV ABI would solve this not only for inlines, but for all multi-assignment type constructs, including distant function calls.
Re: Multiple return values...
On 3/16/12 6:29 AM, Manu wrote: On 16 March 2012 04:26, Andrei Alexandrescu seewebsiteforem...@erdani.org mailto:seewebsiteforem...@erdani.org wrote: A good design should strive to provide general features instead of special cases (E.g. swap is limited to the 2-tuple case). Also, why force an overhead of a function call on such a basic feature as assignment? Is swap usually inlined by the compiler? Sorry, this is grasping at straws. 1. swap can be easily generalized to take any number of arguments. I'm very happy that's possible, we've expended great efforts on making variadic functions as powerful as they are. But nobody asked for swap with many arguments until now. Which segues into... 2. When was the last time you needed to swap arbitrary numbers of elements, and so badly and frequently, you needed a new language feature for that? This is called a swizzle. And constantly comes up when dealing with x,y,z,w, or r,g,b,a. It could just as easily be expressed this way: a,b = tuple(b,a); // swap r,g,b,a = tuple(a,r,g,b); // swizzle At which point this multi assignment boils down to the exact same question of syntax as MRV return assignment. I don't really distinguish this swap/swizzle from MRV. It all comes back to the return assignment syntax. Actually, as has been mentioned, swizzling can be done very nicely inside the language. 3. Function overhead is solved by inlining, not by adding new features. That improves all functions, not only swap. Requiring inlining to make it efficient is not enough. The proposed MRV ABI would solve this not only for inlines, but for all multi-assignment type constructs, including distant function calls. If changing the ABI is on the table, I wonder whether we can improve it for all structures, not only for multiple return types. Overall I agree that defining a specialized ABI for leaving multiple values on the stack would be marginally more efficient, but (a) I don't know by how much, and (b) I don't know whether it's worth changing the language. Andrei
Re: Multiple return values...
On 16 March 2012 16:37, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: On 3/16/12 6:29 AM, Manu wrote: On 16 March 2012 04:26, Andrei Alexandrescu seewebsiteforem...@erdani.org mailto:seewebsiteforem...@erdani.org wrote: A good design should strive to provide general features instead of special cases (E.g. swap is limited to the 2-tuple case). Also, why force an overhead of a function call on such a basic feature as assignment? Is swap usually inlined by the compiler? Sorry, this is grasping at straws. 1. swap can be easily generalized to take any number of arguments. I'm very happy that's possible, we've expended great efforts on making variadic functions as powerful as they are. But nobody asked for swap with many arguments until now. Which segues into... 2. When was the last time you needed to swap arbitrary numbers of elements, and so badly and frequently, you needed a new language feature for that? This is called a swizzle. And constantly comes up when dealing with x,y,z,w, or r,g,b,a. It could just as easily be expressed this way: a,b = tuple(b,a); // swap r,g,b,a = tuple(a,r,g,b); // swizzle At which point this multi assignment boils down to the exact same question of syntax as MRV return assignment. I don't really distinguish this swap/swizzle from MRV. It all comes back to the return assignment syntax. Actually, as has been mentioned, swizzling can be done very nicely inside the language. 3. Function overhead is solved by inlining, not by adding new features. That improves all functions, not only swap. Requiring inlining to make it efficient is not enough. The proposed MRV ABI would solve this not only for inlines, but for all multi-assignment type constructs, including distant function calls. If changing the ABI is on the table, I wonder whether we can improve it for all structures, not only for multiple return types. Overall I agree that defining a specialized ABI for leaving multiple values on the stack would be marginally more efficient, but (a) I don't know by how much, and (b) I don't know whether it's worth changing the language. Andrei If you were to forget all about MRV for a brief moment, the change request being proposed here is to return *all* structures (including delegates, complex types and vectors) in registers if at all possible even if the underlying ABI default is to return it in memory. -- Iain Buclaw *(p e ? p++ : p) = (c 0x0f) + '0';
Re: Multiple return values...
On 3/16/12 3:48 AM, Iain Buclaw wrote: This could also be done for arrays too. (int a, b) = arr[]; - static assert(arr.length == 2); int a = arr[0]; int b = arr[1]; Yah, the rewrite is purely syntactic, not tuple-specific. That's a major thing I like about it. Or possibly a use in variadic templates, which could make D act more like how some scripting languages work. void conn(T ...)(T args) { // vars get default init if value wasn't passed to function. (string server, port, username, password) = args[]; } I fear this is a bit error-prone. Andrei
Re: Multiple return values...
On 3/16/12 11:50 AM, Iain Buclaw wrote: If you were to forget all about MRV for a brief moment, the change request being proposed here is to return *all* structures (including delegates, complex types and vectors) in registers if at all possible even if the underlying ABI default is to return it in memory. That sounds great. Must have missed this point, this is a weird discussion in which one sentence is about machine code and the next about syntax. Andrei
Re: Multiple return values...
On 16 March 2012 16:53, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: On 3/16/12 11:50 AM, Iain Buclaw wrote: If you were to forget all about MRV for a brief moment, the change request being proposed here is to return *all* structures (including delegates, complex types and vectors) in registers if at all possible even if the underlying ABI default is to return it in memory. That sounds great. Must have missed this point, this is a weird discussion in which one sentence is about machine code and the next about syntax. Andrei Indeed it is. Though in all honesty, I'm not sure how we can address this. X86 and X86_64 architectures already return small structures (less or equal to than 8 bytes iirc) in registers as an optimisation trick if the function is private/static, and optimisations are of course turned on. Implementing this for other architectures would require explicitly patching each backend architecture, which is simply not feasible to do, especially when such patches may likely get rejected (A 'reg_return' attribute for ARM has been submitted before back in 2007, but has never been accepted despite after several revisions). -- Iain Buclaw *(p e ? p++ : p) = (c 0x0f) + '0';
Re: Multiple return values...
On 16 March 2012 18:37, Andrei Alexandrescu seewebsiteforem...@erdani.orgwrote: Actually, as has been mentioned, swizzling can be done very nicely inside the language. How? The only example I saw in this thread was your from()/to() approach, which Timon said didn't actually work...? On 3/16/12 6:29 AM, Manu wrote: Requiring inlining to make it efficient is not enough. The proposed MRV ABI would solve this not only for inlines, but for all multi-assignment type constructs, including distant function calls. If changing the ABI is on the table, I wonder whether we can improve it for all structures, not only for multiple return types. Overall I agree that defining a specialized ABI for leaving multiple values on the stack would be marginally more efficient, but (a) I don't know by how much, and (b) I don't know whether it's worth changing the language. It might be that this technology is simpler to implement as applied to all structs (which would include tuples). That way it may additionally address the current problem with slices and delegates. If it applies too all structs, there probably needs to be a cut-off heuristic though, since explicit structs would tend to be a lot bigger than a few MRV values, and named return values is probably better in that context. Implementation details would reveal themselves when trying to write the code I think, ie, where to draw the line (structure size/recursion, etc). (a) 'Marginally' spread across the entire program eventually adds up to a lot, especially if it finds its self in hot loops. C/C++ compilers have extremely aggressive low level optimisers. There is obviously someone who agrees that that effort and reduction in compiler performance is worthwhile, or they wouldn't do it... In terms of benchmarks (the quantity of value you are looking for), it probably won't make so much difference to existing code, because people avoid returning structs by value like the plague It's just something you simply don't do. But if it were a real feature that were reliable, I can see it quickly replace returning through references passed in the parameter list (a horrible concept conceptually, effectively a hack), and it would start to show its value as more and more code were written that way. We free up an argument register in the call where we would have previously passed the ref/pointer, and the return doesn't produce a memory write+read (an LHS hazard). This is a good few opcodes, memory access, and elimination of a major architectural hazard. Repeat this throughout your code base... the only reason it's not in C, is because the ABI far too engraved and can never be changed. (b) Is it a change to the language? Just a change to the ABI with respect to returning a tuple. The change to the language is the unpack syntax, which should be argued on a completely separate basis.
Re: Multiple return values...
On 16 March 2012 18:53, Andrei Alexandrescu seewebsiteforem...@erdani.orgwrote: On 3/16/12 11:50 AM, Iain Buclaw wrote: If you were to forget all about MRV for a brief moment, the change request being proposed here is to return *all* structures (including delegates, complex types and vectors) in registers if at all possible even if the underlying ABI default is to return it in memory. That sounds great. Must have missed this point, this is a weird discussion in which one sentence is about machine code and the next about syntax. Haha, it's true, there are 2 completely distinct, but intrinsically connected problems being debated here :)
Re: Multiple return values...
On 16 March 2012 19:13, Iain Buclaw ibuc...@ubuntu.com wrote: On 16 March 2012 16:53, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: On 3/16/12 11:50 AM, Iain Buclaw wrote: If you were to forget all about MRV for a brief moment, the change request being proposed here is to return *all* structures (including delegates, complex types and vectors) in registers if at all possible even if the underlying ABI default is to return it in memory. That sounds great. Must have missed this point, this is a weird discussion in which one sentence is about machine code and the next about syntax. Andrei Indeed it is. Though in all honesty, I'm not sure how we can address this. X86 and X86_64 architectures already return small structures (less or equal to than 8 bytes iirc) in registers as an optimisation trick if the function is private/static, and optimisations are of course turned on. It's good for a pair of ints, but still inefficient for a struct with smaller data types, each of which could occupy their own register, avoiding the pack and unpack before and after the return... Implementing this for other architectures would require explicitly patching each backend architecture, which is simply not feasible to do, especially when such patches may likely get rejected (A 'reg_return' attribute for ARM has been submitted before back in 2007, but has never been accepted despite after several revisions). Brutal. LLVM supports this efficiently out of the box. It'd be interesting to have the GCC people weigh in on this argument actually. I wonder if they'd see the merit here... ie, presenting it as a fundamental language feature, it has a potential of offering a lot more value than it would applied to C.
Re: Multiple return values...
On 03/16/2012 06:33 PM, Manu wrote: On 16 March 2012 18:37, Andrei Alexandrescu seewebsiteforem...@erdani.org mailto:seewebsiteforem...@erdani.org wrote: Actually, as has been mentioned, swizzling can be done very nicely inside the language. How? Use opDispatch. a = a.yxwz; The only example I saw in this thread was your from()/to() approach, which Timon said didn't actually work...? It does work for assigning to simple variables, but not for assigning to more general expressions. ... In terms of benchmarks (the quantity of value you are looking for), it probably won't make so much difference to existing code, because people avoid returning structs by value like the plague It's just something you simply don't do. But if it were a real feature that were reliable, I can see it quickly replace returning through references passed in the parameter list (a horrible concept conceptually, effectively a hack), DMD does that for you (Walter is the inventor of NRVO).
Re: Multiple return values...
On 16 March 2012 19:53, Timon Gehr timon.g...@gmx.ch wrote: On 03/16/2012 06:33 PM, Manu wrote: On 16 March 2012 18:37, Andrei Alexandrescu seewebsiteforem...@erdani.org mailto:SeeWebsiteForEmail@**erdani.orgseewebsiteforem...@erdani.org wrote: Actually, as has been mentioned, swizzling can be done very nicely inside the language. How? Use opDispatch. a = a.yxwz; The simplest possible example (I've done this is std.simd)... but if I have a few different loosely related things? My personal most frequent problem case is collision/physics. pos, 't', velocity, intersectionFlag, intersection target pointer, etc.. lots of loosely related stuff, always results in inefficient function calls in some of the hottest code in the engine. DMD does that for you (Walter is the inventor of NRVO). Which is awesome for returning larger structs, but not good for returning just a couple of unrelated things that will persist as locals in the calling function.
Re: Multiple return values...
On Fri, 16 Mar 2012 03:26:55 +0100, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: I think this is a reasonable request: (auto a, b) = fun(); --- static assert(fun().length == 2); auto __t = fun(); auto a = __t[0]; auto b = __t[1]; That would be nice. As was mentioned in a later post, this works for static arrays, too. Now, a pattern that our productive friend bearophile posted earlier was this: int[] foo(); (auto a, b) = foo(); --- auto __t = foo(); assert( __t.length 0 ); auto a = __t[0]; auto b = __t[1..$]; Is this something we might also want?
Re: Multiple return values...
Simen K.: Now, a pattern that our productive friend bearophile posted earlier was this: int[] foo(); (auto a, b) = foo(); --- auto __t = foo(); assert( __t.length 0 ); auto a = __t[0]; auto b = __t[1..$]; Is this something we might also want? That's not a good idea, because you don't know much about what 'b' contains. Time ago I have written an example vaguely like that, but it was in the Python3 version. So a more similar translation is: auto (a, $b) = foo(); The $ means: 'unpack all the remaining items inside the b tuple'. A similar syntax is cute to have, but it's not that important because it's not a very common need. And if people think it's so important, then it's possible to add it later (it's a purely incremental feature over the basic tuple unpacking syntax). What's more commonly userful instead is tuple unpacking in foreach (this isn't the final syntax, it's just an idea) (Hara is willing to support this first one): void spam((int x, int y)) {} auto bar = [tuple(1,2,), tuple(3,4)]; foreach (tuple(x, y); bar) {} And in function signatures (this is not the same as using tupleof at the call point): void spam(tuple(int x, int y)) {} spam(tuple(1,2)) Bye, bearophile
Re: Multiple return values...
On 14 March 2012 22:06, Derek Parnell ddparn...@bigpond.com wrote: On Thu, 15 Mar 2012 08:52:26 +1100, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: On 3/14/12 3:00 PM, Simen Kjærås wrote: template to(T...) { alias T to; } auto from(T...)(T t) { struct Result { T t; alias t this; } return Result( t ); } void main( ) { int a = 3; int b = 4; to!(a, b) = from(b, a); assert( a == 4 ); assert( b == 3 ); } I got reborn inside a little when seeing this code. Congratulations! And I died a little ... I have no idea what this code is doing. What is the generated code produced by this and why? I'd like to break the nexus between science and magic here. -- Derek It gets expanded more or less to this: // auto from(T...)(T t) Result from (int a, int b) { return {.field0 = a, .field1 = b}; } void main() { int a = 3; int b = 4; // to!(b, a) = from(a, b) ref Result __tup = from(a, b), a = __tup-field0, b = __tup-field1; } Regards -- Iain Buclaw *(p e ? p++ : p) = (c 0x0f) + '0';
Re: Multiple return values...
On 03/14/2012 11:06 PM, Derek Parnell wrote: On Thu, 15 Mar 2012 08:52:26 +1100, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: On 3/14/12 3:00 PM, Simen Kjærås wrote: template to(T...) { alias T to; } auto from(T...)(T t) { struct Result { T t; alias t this; } return Result( t ); } void main( ) { int a = 3; int b = 4; to!(a, b) = from(b, a); assert( a == 4 ); assert( b == 3 ); } I got reborn inside a little when seeing this code. Congratulations! And I died a little ... I have no idea what this code is doing. What is the generated code produced by this and why? I'd like to break the nexus between science and magic here. // this is used to access language built-in tuples template to(T...) { alias T to; } // this builds a struct akin to std.typecons.tuple // it contains the function parameters as fields t[0], t[1],... auto from(T...)(T t) { struct Result { T t; alias t this; } return Result( t ); } // to!(a,b) creates a tuple containing aliases to a and b to!(a,b) = from(b,a); // cannot assign Result to two fields to!(a,b) = from(b,a).t; // try alias this auto __tmp = from(b,a).t; // expand tuple assign to multiple assignment a[0] = __tmp[0], a[1] = __tmp[1];
Re: Multiple return values...
On Thu, 15 Mar 2012 19:23:57 +1100, Timon Gehr timon.g...@gmx.ch wrote: I'd like to break the nexus between science and magic here. // this is used to access language built-in tuples template to(T...) { alias T to; } // this builds a struct akin to std.typecons.tuple // it contains the function parameters as fields t[0], t[1],... auto from(T...)(T t) { struct Result { T t; alias t this; } return Result( t ); } // to!(a,b) creates a tuple containing aliases to a and b to!(a,b) = from(b,a); // cannot assign Result to two fields to!(a,b) = from(b,a).t; // try alias this auto __tmp = from(b,a).t; // expand tuple assign to multiple assignment a[0] = __tmp[0], a[1] = __tmp[1]; Thanks, but I was hoping more for an explanation in English. Are you saying that the generated code is something like ... struct __tmpS { int Fa, int Fb }; __tempS __tmp; __tmp.Fa = a; __tmp.Fb = b; a = __tmp.Fb; b = __tmp.Fa; -- Derek Parnell Melbourne, Australia
Re: Multiple return values...
On 15 March 2012 09:52, Derek ddparn...@bigpond.com wrote: On Thu, 15 Mar 2012 19:23:57 +1100, Timon Gehr timon.g...@gmx.ch wrote: I'd like to break the nexus between science and magic here. // this is used to access language built-in tuples template to(T...) { alias T to; } // this builds a struct akin to std.typecons.tuple // it contains the function parameters as fields t[0], t[1],... auto from(T...)(T t) { struct Result { T t; alias t this; } return Result( t ); } // to!(a,b) creates a tuple containing aliases to a and b to!(a,b) = from(b,a); // cannot assign Result to two fields to!(a,b) = from(b,a).t; // try alias this auto __tmp = from(b,a).t; // expand tuple assign to multiple assignment a[0] = __tmp[0], a[1] = __tmp[1]; Thanks, but I was hoping more for an explanation in English. Are you saying that the generated code is something like ... struct __tmpS { int Fa, int Fb }; __tempS __tmp; __tmp.Fa = a; __tmp.Fb = b; a = __tmp.Fb; b = __tmp.Fa; In effect, yes. Given that the call to from() is inlined. :-) In GDC, you have a -fdump-tree-original switch that dumps a debug representation (that just so happens to look C-like) of the AST of the code to a file. You could use this to unravel some of the magic going on under the covers. ;-) -- Iain Buclaw *(p e ? p++ : p) = (c 0x0f) + '0';
Re: Multiple return values...
On 3/15/12 5:14 AM, Iain Buclaw wrote: On 15 March 2012 09:52, Derekddparn...@bigpond.com wrote: Are you saying that the generated code is something like ... struct __tmpS { int Fa, int Fb }; __tempS __tmp; __tmp.Fa = a; __tmp.Fb = b; a = __tmp.Fb; b = __tmp.Fa; In effect, yes. Given that the call to from() is inlined. :-) In GDC, you have a -fdump-tree-original switch that dumps a debug representation (that just so happens to look C-like) of the AST of the code to a file. You could use this to unravel some of the magic going on under the covers. ;-) One note - the code is really ingenious, but I still prefer swap() in this case. It's more concise and does less work in the general case. swap(a[i + k], a[j + j]); only computes the indexing once (in source, too). Andrei
Re: Multiple return values...
On 15 March 2012 17:32, Andrei Alexandrescu seewebsiteforem...@erdani.orgwrote: On 3/15/12 5:14 AM, Iain Buclaw wrote: On 15 March 2012 09:52, Derekddparn...@bigpond.com wrote: Are you saying that the generated code is something like ... struct __tmpS { int Fa, int Fb }; __tempS __tmp; __tmp.Fa = a; __tmp.Fb = b; a = __tmp.Fb; b = __tmp.Fa; In effect, yes. Given that the call to from() is inlined. :-) In GDC, you have a -fdump-tree-original switch that dumps a debug representation (that just so happens to look C-like) of the AST of the code to a file. You could use this to unravel some of the magic going on under the covers. ;-) One note - the code is really ingenious, but I still prefer swap() in this case. It's more concise and does less work in the general case. swap(a[i + k], a[j + j]); only computes the indexing once (in source, too). It all still feels to me like a generally ugly hack around the original example: a,b = b,a; What is the significant importance of the existing coma operator? What does it offer that couldn't possibly be achieved any other way?
Re: Multiple return values...
On 3/15/12 11:30 AM, Manu wrote: On 15 March 2012 17:32, Andrei Alexandrescu seewebsiteforem...@erdani.org mailto:seewebsiteforem...@erdani.org wrote: One note - the code is really ingenious, but I still prefer swap() in this case. It's more concise and does less work in the general case. swap(a[i + k], a[j + j]); only computes the indexing once (in source, too). It all still feels to me like a generally ugly hack around the original example: a,b = b,a; It's a function call. Why is a function call a ugly hack? It's also more verbose. Compare swap(a[i + k], a[i + j]); with a[i + k], a[i + j] = a[i + j], a[i + k]; I mean one could even easily miss a typo in there. There's also semantic issues to be defined. What is the order of evaluation in f(), g() = h(), k(); ? All of this adds dead weight to the language. We shouldn't reach to new syntax for every single little thing. Andrei
Re: Multiple return values...
On 03/15/2012 04:32 PM, Andrei Alexandrescu wrote: One note - the code is really ingenious, but I still prefer swap() in this case. It's more concise and does less work in the general case. swap(a[i + k], a[j + j]); only computes the indexing once (in source, too). In the general case, the alias tuple approach does less work, because it does not work. to!(a[i+k], a[j+j]) = from(a[j+j],a[i+k]); is illegal.
Re: Multiple return values...
On 15 March 2012 19:05, Andrei Alexandrescu seewebsiteforem...@erdani.orgwrote: On 3/15/12 11:30 AM, Manu wrote: On 15 March 2012 17:32, Andrei Alexandrescu seewebsiteforem...@erdani.org mailto:SeeWebsiteForEmail@**erdani.orgseewebsiteforem...@erdani.org wrote: One note - the code is really ingenious, but I still prefer swap() in this case. It's more concise and does less work in the general case. swap(a[i + k], a[j + j]); only computes the indexing once (in source, too). It all still feels to me like a generally ugly hack around the original example: a,b = b,a; It's a function call. Why is a function call a ugly hack? Because now we're involving libraries to perform a trivial assignment. It's also more verbose. Compare swap(a[i + k], a[i + j]); with a[i + k], a[i + j] = a[i + j], a[i + k]; I mean one could even easily miss a typo in there. That's fair, and in that case you might want to call such a function. But I think that's very contrived. I can't remember the last time I ever wanted to perform a swap on anything other than stack primitives. Also I think this (and perhaps more so MRV) will find a very major use case in maths code, and maths code tends to revolve around simple (often single letter) maths-y type variable names. There's also semantic issues to be defined. What is the order of evaluation in f(), g() = h(), k(); ? Left to right, clearly. Keep it simple. All of this adds dead weight to the language. We shouldn't reach to new syntax for every single little thing. MRV is not 'every single little thing'. I'd say it's the single major checklist item that D just doesn't tick (and perhaps user attributes, although there's no resistance there, it's just not done yet). I get the impression I'm not the only one here that feels that way too. I'm perhaps just louder, and more annoying :)
Re: Multiple return values...
On 3/15/12 12:51 PM, Manu wrote: On 15 March 2012 19:05, Andrei Alexandrescu It's a function call. Why is a function call a ugly hack? Because now we're involving libraries to perform a trivial assignment. It's not a trivial assignment, it's swapping two pieces of data. This matter has aliasing and ordering implications. Most languages don't make swapping a primitive. It's also more verbose. Compare swap(a[i + k], a[i + j]); with a[i + k], a[i + j] = a[i + j], a[i + k]; I mean one could even easily miss a typo in there. That's fair, and in that case you might want to call such a function. But I think that's very contrived. How is it contrived? I can't figure for the life of me how to swap a and b call swap(a, b) is contrived. I mean swapping is even _called_ swap. So if I can name it as a function and with a suggestive name, and if the function is useful, why do we _also_ need a special syntax? I can't remember the last time I ever wanted to perform a swap on anything other than stack primitives. I think that's an exaggeration. swap() is very often (almost always?) called with expressions, see e.g. std.algorithm. Also I think this (and perhaps more so MRV) will find a very major use case in maths code, and maths code tends to revolve around simple (often single letter) maths-y type variable names. I am confident your math code will not look any worse with swap(a, b). There's also semantic issues to be defined. What is the order of evaluation in f(), g() = h(), k(); ? Left to right, clearly. Keep it simple. Problem is, that and the related issues need to go in the manual. All of this adds dead weight to the language. We shouldn't reach to new syntax for every single little thing. MRV is not 'every single little thing'. This discussion is about swap. I'd say it's the single major checklist item that D just doesn't tick (and perhaps user attributes, although there's no resistance there, it's just not done yet). I get the impression I'm not the only one here that feels that way too. I understand how the draw of reaching for new syntax is extremely alluring. I used to fall for it much more often, but over years I have hardened myself to resist it, and I think that made me a better language designer. In this stage of D, I think we should all have an understanding that adding syntax is not a win. Instead, it is an acknowledgment that the existing language, for all its might, is unable to express the desired abstraction. This is sometimes fine (such as in the case of introducing multiple new symbols from one tuple), but generally the question is not why couldn't we add syntax X? but instead why should we add syntax X? Andrei
Re: Multiple return values...
On Thursday, 15 March 2012 at 17:05:00 UTC, Andrei Alexandrescu wrote: On 3/15/12 11:30 AM, Manu wrote: On 15 March 2012 17:32, Andrei Alexandrescu seewebsiteforem...@erdani.org mailto:seewebsiteforem...@erdani.org wrote: One note - the code is really ingenious, but I still prefer swap() in this case. It's more concise and does less work in the general case. swap(a[i + k], a[j + j]); only computes the indexing once (in source, too). It all still feels to me like a generally ugly hack around the original example: a,b = b,a; It's a function call. Why is a function call a ugly hack? It's also more verbose. Compare swap(a[i + k], a[i + j]); with a[i + k], a[i + j] = a[i + j], a[i + k]; I mean one could even easily miss a typo in there. There's also semantic issues to be defined. What is the order of evaluation in f(), g() = h(), k(); ? All of this adds dead weight to the language. We shouldn't reach to new syntax for every single little thing. Andrei even if swap is preferable here (IMO the syntax improvement here is negligible) it is still a special case. In the general case Ruby allows e.g: a, b, c, d = b, d, a, c How would you do this with swap? The order of evaluation is an orthogonal issue to the syntax. The compiler calculates n functions to produce n values so we could leave the order undefined. Some FP languages define order of evaluation (e.g. left to right) but IMO this is more suitable for older PCs with serial execution (single processor). It's worth considering for current and future multi/many-core PCs to instead evaluate the tuple concurrently. or perhaps provide both options, e.g. f(), g() // left to right @parallel f(), g() // separate tasks/threads Or perhaps even switch the default to make the parallel order the default and mark the former with a @serial annotation.
Re: Multiple return values...
On Thursday, 15 March 2012 at 18:23:57 UTC, Andrei Alexandrescu wrote: I understand how the draw of reaching for new syntax is extremely alluring. I used to fall for it much more often, but over years I have hardened myself to resist it, and I think that made me a better language designer. In this stage of D, I think we should all have an understanding that adding syntax is not a win. Instead, it is an acknowledgment that the existing language, for all its might, is unable to express the desired abstraction. This is sometimes fine (such as in the case of introducing multiple new symbols from one tuple), but generally the question is not why couldn't we add syntax X? but instead why should we add syntax X? Andrei I agree that this is an acknowledgement of the current language's inability to express the abstraction. That's why many people ask for it to be added in the first place. We should add this syntax because it *is* impossible ATM to implement the required abstraction. A good design should strive to provide general features instead of special cases (E.g. swap is limited to the 2-tuple case). Also, why force an overhead of a function call on such a basic feature as assignment? Is swap usually inlined by the compiler?
Re: Multiple return values...
Andrei Alexandrescu: generally the question is not why couldn't we add syntax X? but instead why should we add syntax X? You are right. Tuples are a basic data structure, some other language constructs are built on them (swapping items is just one of their purposes, then there is multiple return values). They are not necessary, so if you don't want to add the syntax sugar we'll do with the libray Tuple we have. But so far I have not seen ways to replace that syntax sugar with with something else. Bye, bearophile
Re: Multiple return values...
On Thu, 15 Mar 2012 23:44:09 +0100, foobar f...@bar.com wrote: Is swap usually inlined by the compiler? This is the body of swap for simple types: auto tmp = lhs; lhs = rhs; rhs = tmp; For more complex types: ubyte[T.sizeof] t = void; auto a = (cast(ubyte*) lhs)[0 .. T.sizeof]; auto b = (cast(ubyte*) rhs)[0 .. T.sizeof]; t[] = a[]; a[] = b[]; b[] = t[]; So yeah. If that's not inlined, something's wrong.
Re: Multiple return values...
On Fri, Mar 16, 2012 at 12:11:44AM +0100, Simen Kjærås wrote: On Thu, 15 Mar 2012 23:44:09 +0100, foobar f...@bar.com wrote: Is swap usually inlined by the compiler? This is the body of swap for simple types: auto tmp = lhs; lhs = rhs; rhs = tmp; For more complex types: ubyte[T.sizeof] t = void; auto a = (cast(ubyte*) lhs)[0 .. T.sizeof]; auto b = (cast(ubyte*) rhs)[0 .. T.sizeof]; t[] = a[]; a[] = b[]; b[] = t[]; So yeah. If that's not inlined, something's wrong. Ideally, though, a swap of large objects should be a single loop of xchg instructions (in x86 anyway, xchg may not exist on other architectures). Unless dmd is much more clever than I thought, the above code would generate 3 loops, which uses more memory and is (probably) slower. T -- Never ascribe to malice that which is adequately explained by incompetence. -- Napoleon Bonaparte
Re: Multiple return values...
On 3/15/12 5:44 PM, foobar wrote: On Thursday, 15 March 2012 at 18:23:57 UTC, Andrei Alexandrescu wrote: I understand how the draw of reaching for new syntax is extremely alluring. I used to fall for it much more often, but over years I have hardened myself to resist it, and I think that made me a better language designer. In this stage of D, I think we should all have an understanding that adding syntax is not a win. Instead, it is an acknowledgment that the existing language, for all its might, is unable to express the desired abstraction. This is sometimes fine (such as in the case of introducing multiple new symbols from one tuple), but generally the question is not why couldn't we add syntax X? but instead why should we add syntax X? Andrei I agree that this is an acknowledgement of the current language's inability to express the abstraction. That's why many people ask for it to be added in the first place. We should add this syntax because it *is* impossible ATM to implement the required abstraction. I think this is a reasonable request: (auto a, b) = fun(); --- static assert(fun().length == 2); auto __t = fun(); auto a = __t[0]; auto b = __t[1]; To express the idiom in the example, the programmer needs to do the expansion by hand, which is verbose and introduces unnecessary symbols. So the language as is has an expressiveness problem (albeit not a pernicious one) that is nice to solve. It doesn't seem to me this is a reasonable request: a, b = b, a; --- auto __t0 = b; auto __t1 = a; a = __t1; b = __t0; This is because the alternative swap(a, b) is not only competitive, but arguably better at least in some respects. A good design should strive to provide general features instead of special cases (E.g. swap is limited to the 2-tuple case). Also, why force an overhead of a function call on such a basic feature as assignment? Is swap usually inlined by the compiler? Sorry, this is grasping at straws. 1. swap can be easily generalized to take any number of arguments. I'm very happy that's possible, we've expended great efforts on making variadic functions as powerful as they are. But nobody asked for swap with many arguments until now. Which segues into... 2. When was the last time you needed to swap arbitrary numbers of elements, and so badly and frequently, you needed a new language feature for that? 3. Function overhead is solved by inlining, not by adding new features. That improves all functions, not only swap. Andrei
Re: Multiple return values...
2. When was the last time you needed to swap arbitrary numbers of elements, and so badly and frequently, you needed a new language feature for that? Andrei I would like to point out Boscop's vector swizzling tutorial that he made recently as evidence that not only do you not need it that frequently, it is fairly easy to implement a nice syntax for it in D, no language changes needed. -- James Miller
Re: Multiple return values...
On Fri, Mar 16, 2012 at 04:46:52PM +1300, James Miller wrote: 2. When was the last time you needed to swap arbitrary numbers of elements, and so badly and frequently, you needed a new language feature for that? Andrei I would like to point out Boscop's vector swizzling tutorial that he made recently as evidence that not only do you not need it that frequently, it is fairly easy to implement a nice syntax for it in D, no language changes needed. [...] Yeah, using opDispatch + CTFE to implement things like: int[4] v1; auto v2 = v1.zxyw; // or *any* permutation of wxyz is pretty amazing. You get syntax more compact than any new language syntax can offer, *within* the confines of the current language. T -- Public parking: euphemism for paid parking. -- Flora
Re: Tuple unpacking syntax [Was: Re: Multiple return values...]
I'm encouraged to see that every person in this thread so far seems to feel the same way as me regarding the syntax. On 14 March 2012 05:25, Derek Parnell ddparn...@bigpond.com wrote: On Wed, 14 Mar 2012 13:33:18 +1100, Kevin Cox kevincox...@gmail.com wrote: (int i,,float f) = intBoringFloat(); For what its worth, the Euphoria Programming Language uses ? to signify ignored values. Yeah I tend to agree with that logic. I was quite liking the '_' that someone suggested much earlier in the thread: «x, _, int y» = intBoringFloat (); // Note: it seems to be unclear which brackets to use ;) Hey, the Japanese have some really cool brackets! 「x, _, int y」 = ... 〖x, _, int y〗 = ... 〘x, _, int y〙 = ... Tight! ;)
Re: Tuple unpacking syntax [Was: Re: Multiple return values...]
On Wednesday, 14 March 2012 at 02:33:29 UTC, Kevin Cox wrote: Kind of unrelated but I think that it is important to have a way to ignore values also. Leaving them bank would sufice. (int i,,float f) = intBoringFloat(); or (int i, null, float f) = intBoringFloat(); or (int i, void, float f) = intBoringFloat();
Re: Tuple unpacking syntax [Was: Re: Multiple return values...]
On Wed, 14 Mar 2012 03:52:55 -0500, Manu turkey...@gmail.com wrote: I'm encouraged to see that every person in this thread so far seems to feel the same way as me regarding the syntax. On 14 March 2012 05:25, Derek Parnell ddparn...@bigpond.com wrote: On Wed, 14 Mar 2012 13:33:18 +1100, Kevin Cox kevincox...@gmail.com wrote: (int i,,float f) = intBoringFloat(); For what its worth, the Euphoria Programming Language uses ? to signify ignored values. Yeah I tend to agree with that logic. I was quite liking the '_' that someone suggested much earlier in the thread: «x, _, int y» = intBoringFloat (); // Note: it seems to be unclear which brackets to use ;) Hey, the Japanese have some really cool brackets! 「x, _, int y」 = ... 〖x, _, int y〗 = ... 〘x, _, int y〙 = ... Tight! ;) UTF math has a bunch of cool brackets as well ⟦ x, _, int y ⟧ ⦃ x, _, int y ⦄ ⦅ x, _, int y ⦆ ⦇ x, _, int y ⦈ ⦉ x, _, int y ⦊ But there's a reason we use /// instead of ⫻; we shouldn't require custom keyboard mappings in order to program efficiently in D.
Re: Multiple return values...
On 3/13/12 6:12 PM, Andrei Alexandrescu wrote: On 3/13/12 2:57 PM, Manu wrote: And you think that's more readable and intuitive than: (v1, v2, v3) = fun(); ? Yes (e.g. when I see the commas my mind starts running in all directions because that's valid code nowadays that ignores v1 and v2 and keeps v3 as an lvalue). Who uses that, except code generators? I'd like D to deprecate the comma so it can be used for other things, like tuple assignment. Let me put it another way: I don't see one syntax over another a deal maker or deal breaker. At all. Well, it's sad that syntax is not very important in D. If you have to write less code there will be less chance for bugs and it will be more understandable (unless you obfuscate the code, obviously). Here's what you can do in Ruby: a = 1 b = 2 # Swap the contents a, b = b, a Can you do something like that with templates in D, with a nice syntax?
Re: Multiple return values...
On 3/14/12 2:02 PM, Ary Manzana wrote: On 3/13/12 6:12 PM, Andrei Alexandrescu wrote: On 3/13/12 2:57 PM, Manu wrote: And you think that's more readable and intuitive than: (v1, v2, v3) = fun(); ? Yes (e.g. when I see the commas my mind starts running in all directions because that's valid code nowadays that ignores v1 and v2 and keeps v3 as an lvalue). Who uses that, except code generators? I'd like D to deprecate the comma so it can be used for other things, like tuple assignment. I'd like that, too, but we need to worry about breaking code, too. Let me put it another way: I don't see one syntax over another a deal maker or deal breaker. At all. Well, it's sad that syntax is not very important in D. That's an undue generalization of what I said. If you have to write less code there will be less chance for bugs and it will be more understandable (unless you obfuscate the code, obviously). I agree. Here's what you can do in Ruby: a = 1 b = 2 # Swap the contents a, b = b, a Can you do something like that with templates in D, with a nice syntax? swap(a, b) Andrei
Re: Multiple return values...
On Wed, 14 Mar 2012 20:02:50 +0100, Ary Manzana a...@esperanto.org.ar wrote: Here's what you can do in Ruby: a = 1 b = 2 # Swap the contents a, b = b, a Can you do something like that with templates in D, with a nice syntax? template to(T...) { alias T to; } auto from(T...)(T t) { struct Result { T t; alias t this; } return Result( t ); } void main( ) { int a = 3; int b = 4; to!(a, b) = from(b, a); assert( a == 4 ); assert( b == 3 ); }
Re: Multiple return values...
On 3/14/12 5:00 PM, Simen Kjærås wrote: On Wed, 14 Mar 2012 20:02:50 +0100, Ary Manzana a...@esperanto.org.ar wrote: Here's what you can do in Ruby: a = 1 b = 2 # Swap the contents a, b = b, a Can you do something like that with templates in D, with a nice syntax? template to(T...) { alias T to; } auto from(T...)(T t) { struct Result { T t; alias t this; } return Result( t ); } void main( ) { int a = 3; int b = 4; to!(a, b) = from(b, a); assert( a == 4 ); assert( b == 3 ); } Awesome! :-)
Re: Multiple return values...
On 14 March 2012 22:10, Ary Manzana a...@esperanto.org.ar wrote: On 3/14/12 5:00 PM, Simen Kjærås wrote: On Wed, 14 Mar 2012 20:02:50 +0100, Ary Manzana a...@esperanto.org.ar wrote: Here's what you can do in Ruby: a = 1 b = 2 # Swap the contents a, b = b, a Can you do something like that with templates in D, with a nice syntax? template to(T...) { alias T to; } auto from(T...)(T t) { struct Result { T t; alias t this; } return Result( t ); } void main( ) { int a = 3; int b = 4; to!(a, b) = from(b, a); assert( a == 4 ); assert( b == 3 ); } Awesome! :-) Mmmm, I still kinda like the ruby way. I agree, the coma operator is a serious liability in D. Multi assignments without any other rubbish around it are useful in a whole bunch of of contexts. How much would really break if coma was deprecated? Is it used any more than C++ coma? (Most C++ programs wouldn't even know if the coma operator were removed)
Re: Tuple unpacking syntax [Was: Re: Multiple return values...]
On 14 March 2012 15:17, Robert Jacques sandf...@jhu.edu wrote: But there's a reason we use /// instead of ⫻; we shouldn't require custom keyboard mappings in order to program efficiently in D. Hold that thought, I think you're missing a major franchising opportunity right there... D branded 'pro-codah' keyboards! Nice! :P
Re: Multiple return values...
On 3/14/12 3:00 PM, Simen Kjærås wrote: template to(T...) { alias T to; } auto from(T...)(T t) { struct Result { T t; alias t this; } return Result( t ); } void main( ) { int a = 3; int b = 4; to!(a, b) = from(b, a); assert( a == 4 ); assert( b == 3 ); } I got reborn inside a little when seeing this code. Congratulations! Andrei
Re: Multiple return values...
On Thu, 15 Mar 2012 08:52:26 +1100, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: On 3/14/12 3:00 PM, Simen Kjærås wrote: template to(T...) { alias T to; } auto from(T...)(T t) { struct Result { T t; alias t this; } return Result( t ); } void main( ) { int a = 3; int b = 4; to!(a, b) = from(b, a); assert( a == 4 ); assert( b == 3 ); } I got reborn inside a little when seeing this code. Congratulations! And I died a little ... I have no idea what this code is doing. What is the generated code produced by this and why? I'd like to break the nexus between science and magic here. -- Derek
Re: Multiple return values...
On Wed, 14 Mar 2012 22:52:26 +0100, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: On 3/14/12 3:00 PM, Simen Kjærås wrote: template to(T...) { alias T to; } auto from(T...)(T t) { struct Result { T t; alias t this; } return Result( t ); } void main( ) { int a = 3; int b = 4; to!(a, b) = from(b, a); assert( a == 4 ); assert( b == 3 ); } I got reborn inside a little when seeing this code. Congratulations! Andrei Thank you, and you're welcome.
Re: Tuple unpacking syntax [Was: Re: Multiple return values...]
On Wednesday, 14 March 2012 at 13:17:47 UTC, Robert Jacques wrote: snip But there's a reason we use /// instead of ⫻; we shouldn't require custom keyboard mappings in order to program efficiently in D. Aren't we supposed to be moving towards more natural interfaces in computing? I'm sure that once the touch interface is perfected for text input it would be trivial to have a coding keyboard or a D keyboard. The keyboard mappings would simply come bundled with the compiler. The current text based programming is quite limiting considering that we actually deal with a tree of tokens. IDEs already manipulate code at the AST level in order to enable refactoring. The next logical step would be to eliminate the text form all together and store code at the AST level, thus avoiding lexing/parsing overhead and the limits of text based representation. e.g. subtextual.org has cool ideas how to design such a future non textual language. For example, representing Boolean logic in a table instead of arbitrary nested if statements.
Re: Multiple return values...
On 13 March 2012 06:45, Andrei Alexandrescu seewebsiteforem...@erdani.orgwrote: You see, at this point I have no idea what to believe anymore. You argued very strongly from the position of one whose life depends on efficiency. Here and there you'd mix some remark about syntax, and I'd like whaa?... but generally discounted it as distraction from the main point, which was that all you must do is f(g()) where the body of g() is insignificantly small, which makes the cost of passing arguments around absolutely paramount. And now you come with this completely opposite viewpoint in which the syntax is paramount and urgent, whereas codegen is like let's leave it for later. I really am confused. Okay sorry, let me clarify. My own personal stance is unchanged, but I appreciate your assertion of priorities and I relent :) This topic has meandered between 2 distinct threads, syntax and abi, and I feel strongly about both, so maybe my personal sense of priority comes across wrong as I'm discussing one topic or the other. Trying to see it from a practicality standpoint, there is a pull request there which would seem like a near-complete implementation of the syntax, so that's a much easier/smaller step than messing with the ABI. Also, the syntax element of the feature will benefit far more people, and more immediately. Note, I still find myself wanting this feature, at least syntactically, every other day (my motivation starting the thread initially). But for my purposes (simd math library currently) it wouldn't do for it to be inefficient. At least the promise of an efficient implementation down the road is needed to make use of it. I think I feel a sense of urgency towards the ABI aspect because it is a breaking change, and I suspect the longer anything like that is left, the less likely/more risky it becomes. If it gets delayed for 6-12 months, are you honestly more or less likely to say it's a good idea to fiddle with the ABI? I am sold on the Tuple approach now, so that's a big discussion that can be dismissed. I think it was as a result of realising this that the ABI became of higher importance in my mind, since I agree, workable syntax is technically possible already (although ugly/verbose). You don't see the immediate value in a convenient MRV syntax? It would improve code clarity in many places, and allow the code to also be more efficient down the road. I see value in Kenji's related diff, but not in adding syntax to e.g. return (int, int). But we want to make sure we address the matter holistically (for example: is Kenji's diff enough, or do we need to worry about assignment too?). The worst strategy in chess is to move a piece and then start analyzing the new situation on the board. Shall we discuss the shortcomings of his implementation? Can someone demonstrate the details of his implementation? From the little examples up in the thread, it looked like you could only declare new variables inline, but not assign out to existing ones. I'd say this needs to be added too, and perhaps that will throw the whole design into turmoil? ;)
Re: Multiple return values...
On 13 March 2012 09:12, Manu turkey...@gmail.com wrote: On 13 March 2012 06:45, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: You see, at this point I have no idea what to believe anymore. You argued very strongly from the position of one whose life depends on efficiency. Here and there you'd mix some remark about syntax, and I'd like whaa?... but generally discounted it as distraction from the main point, which was that all you must do is f(g()) where the body of g() is insignificantly small, which makes the cost of passing arguments around absolutely paramount. And now you come with this completely opposite viewpoint in which the syntax is paramount and urgent, whereas codegen is like let's leave it for later. I really am confused. Okay sorry, let me clarify. My own personal stance is unchanged, but I appreciate your assertion of priorities and I relent :) This topic has meandered between 2 distinct threads, syntax and abi, and I feel strongly about both, so maybe my personal sense of priority comes across wrong as I'm discussing one topic or the other. Trying to see it from a practicality standpoint, there is a pull request there which would seem like a near-complete implementation of the syntax, so that's a much easier/smaller step than messing with the ABI. Also, the syntax element of the feature will benefit far more people, and more immediately. Note, I still find myself wanting this feature, at least syntactically, every other day (my motivation starting the thread initially). But for my purposes (simd math library currently) it wouldn't do for it to be inefficient. At least the promise of an efficient implementation down the road is needed to make use of it. I think I feel a sense of urgency towards the ABI aspect because it is a breaking change, and I suspect the longer anything like that is left, the less likely/more risky it becomes. If it gets delayed for 6-12 months, are you honestly more or less likely to say it's a good idea to fiddle with the ABI? I am sold on the Tuple approach now, so that's a big discussion that can be dismissed. I think it was as a result of realising this that the ABI became of higher importance in my mind, since I agree, workable syntax is technically possible already (although ugly/verbose). What about alternative optimisations for MRV, rather than stating that it should always be returned in registers where possible (and breaking ABI on all target platforms). What about, for example, using named return value optimisation in this case to help improve the cost of returning on non-x86 architectures. Just throwing random thoughts out there. -- Iain Buclaw *(p e ? p++ : p) = (c 0x0f) + '0';
Re: Multiple return values...
On 13 March 2012 13:27, Iain Buclaw ibuc...@ubuntu.com wrote: What about alternative optimisations for MRV, rather than stating that it should always be returned in registers where possible (and breaking ABI on all target platforms). What about, for example, using named return value optimisation in this case to help improve the cost of returning on non-x86 architectures. Just throwing random thoughts out there. What difference would that actually make? The effect is still the same, unless perhaps you were returning directly into some output structure, that might be a win in that case (but that's the opposite of what MRV is actually for). Definitely no good for slices, and it doesn't help calls, only returns. The non-x86 platforms don't only suffer from return values, they suffer passing TO functions as well. So currently they take the hit on both sides. Slices are fundamental to the language feature, they need to be efficient :/
Re: Multiple return values...
On 3/13/12 4:12 AM, Manu wrote: I think I feel a sense of urgency towards the ABI aspect because it is a breaking change, and I suspect the longer anything like that is left, the less likely/more risky it becomes. If it gets delayed for 6-12 months, are you honestly more or less likely to say it's a good idea to fiddle with the ABI? I think Walter could answer that. I am sold on the Tuple approach now, so that's a big discussion that can be dismissed. Great! Shall we discuss the shortcomings of his implementation? Can someone demonstrate the details of his implementation? From the little examples up in the thread, it looked like you could only declare new variables inline, but not assign out to existing ones. I'd say this needs to be added too, and perhaps that will throw the whole design into turmoil? ;) I thought more about it and we should be fine with two functions (untested): enum Skip {}; @property ref Skip skip() { static __gshared Skip result; return result; } void scatter(T, U...)(auto ref T source, ref U targets) { assert(source.length == targets.length); foreach (i, ref target; targets) { static if (is(typeof(target) != Skip)) { target = source[i]; } } } void gather(T, U...)(ref T target, auto ref U sources) { assert(target.length == sources.length); foreach (i, source; sources) { static if (is(typeof(source) != Skip)) { target[i] = source; } } } Usage: auto t = tuple(1, hi, 2.3); int a; string b; double c; t.scatter(a, b, skip); // assigns a and b from tuple b = !; ++c; t.gather(skip, b, c); // assigns tuple from variables b and c Andrei
Re: Multiple return values...
On 13 March 2012 16:44, Andrei Alexandrescu seewebsiteforem...@erdani.orgwrote: I thought more about it and we should be fine with two functions (untested): enum Skip {}; @property ref Skip skip() { static __gshared Skip result; return result; } void scatter(T, U...)(auto ref T source, ref U targets) { assert(source.length == targets.length); foreach (i, ref target; targets) { static if (is(typeof(target) != Skip)) { target = source[i]; } } } void gather(T, U...)(ref T target, auto ref U sources) { assert(target.length == sources.length); foreach (i, source; sources) { static if (is(typeof(source) != Skip)) { target[i] = source; } } } Usage: auto t = tuple(1, hi, 2.3); int a; string b; double c; t.scatter(a, b, skip); // assigns a and b from tuple b = !; ++c; t.gather(skip, b, c); // assigns tuple from variables b and c Well, that 'works' :) .. Is that a proposal for a 'final' syntax, or something to work with in the mean time? I said I've come to accept the Tuple *implementation*, but I'm absolutely not ready to accept the syntax baggage ;) I'd really rather see something that actually looks like a language feature in its final manifestation. Is natural and convenient to read and type. float t; ... (myStruct.pos, t, _, int err) = intersectThings(); Or something to this effect. That's about as clear and concise as it gets for my money.
Re: Multiple return values...
On 3/13/12 10:48 AM, Manu wrote: float t; ... (myStruct.pos, t, _, int err) = intersectThings(); I actually find the scatter syntax better than this. Anyway, I hope you'll agree there's not much difference pragmatically. Andrei
Re: Multiple return values...
On 13 March 2012 18:07, Andrei Alexandrescu seewebsiteforem...@erdani.orgwrote: On 3/13/12 10:48 AM, Manu wrote: float t; ... (myStruct.pos, t, _, int err) = intersectThings(); I actually find the scatter syntax better than this. Anyway, I hope you'll agree there's not much difference pragmatically. There's a few finicky differences. I'm still of the understanding (and I may be wrong, still mystified by some of D's more complicated template syntax) that once you give the returned tuple a name, it is structurally bound to the stack. At that point, passing any member by-ref to any function must conservatively commit the entire tuple to the stack. This behaviour won't be intuitive to most users, and can be easily avoided; by obscuring the Tuple from user visibility, they can only access the returned values through their independant output assignments, which guarantees the independence of each returned item. Syntactically, scatter can't declare new variables inline (?), it also uses additional lines of code (1 + as many variables as you need to declare), which is very disruptive to flow. Maths-y code should be un-cluttered and read sequentially. Having to put extra lines in to munge un-related things really ruins the code IMO ('t' is of no consequence to the user, pollutes their namespace, gets in the way with extra lines, etc). What people want from MRV is to capture the returned values independently. If I *wanted* to capture the returned Tuple (the extremely rare case), I'd rather do that explicitly, something like this: auto t = tuple(mrvFunc()); scatter/gather is nice and simple, I'll take it in the mean time, but I think it would be a shame for it to stop there longer term... That said though, it's all still nothing to me without at least a promise on the ABI :) .. And I feel that should ideally come in the form of a language policy/promise that this feature will be 'efficient' (or at very least, not *inefficient* as it is now), and leave it to compiler implementations to concur with that promise, ie, failing to be 'standards' compliant if they fail to do so.
Re: Multiple return values...
On 3/13/12 12:02 PM, Manu wrote: There's a few finicky differences. I'm still of the understanding (and I may be wrong, still mystified by some of D's more complicated template syntax) that once you give the returned tuple a name, it is structurally bound to the stack. At that point, passing any member by-ref to any function must conservatively commit the entire tuple to the stack. This behaviour won't be intuitive to most users, and can be easily avoided; by obscuring the Tuple from user visibility, they can only access the returned values through their independant output assignments, which guarantees the independence of each returned item. Here we go moving the goalposts again. Syntactically, scatter can't declare new variables inline (?), it also uses additional lines of code (1 + as many variables as you need to declare), which is very disruptive to flow. This is in addition to Kenji's change. What people want from MRV is to capture the returned values independently. If I /wanted/ to capture the returned Tuple (the extremely rare case), I'd rather do that explicitly, something like this: auto t = tuple(mrvFunc()); No. Tuple stays together by default and is expanded explicitly. This is not negotiable. scatter/gather is nice and simple, I'll take it in the mean time, but I think it would be a shame for it to stop there longer term... That said though, it's all still nothing to me without at least a promise on the ABI :) .. And I feel that should ideally come in the form of a language policy/promise that this feature will be 'efficient' (or at very least, not /inefficient/ as it is now), and leave it to compiler implementations to concur with that promise, ie, failing to be 'standards' compliant if they fail to do so. This is not for me to promise. Andrei
Re: Multiple return values...
On 13 March 2012 19:25, Andrei Alexandrescu seewebsiteforem...@erdani.orgwrote: On 3/13/12 12:02 PM, Manu wrote: There's a few finicky differences. I'm still of the understanding (and I may be wrong, still mystified by some of D's more complicated template syntax) that once you give the returned tuple a name, it is structurally bound to the stack. At that point, passing any member by-ref to any function must conservatively commit the entire tuple to the stack. This behaviour won't be intuitive to most users, and can be easily avoided; by obscuring the Tuple from user visibility, they can only access the returned values through their independant output assignments, which guarantees the independence of each returned item. Here we go moving the goalposts again. I don't see how? I'm just saying that I don't think they are pragmatically identical. Syntactically, scatter can't declare new variables inline (?), it also uses additional lines of code (1 + as many variables as you need to declare), which is very disruptive to flow. This is in addition to Kenji's change. What value does it add over Kenji's change? Is this because Kenji's change is unable to perform direct to existing variables? My understanding from early in the thread was that Kenji's change hides the returned tuple, and performs a convenient unpack. How can you perform a scatter if the tuple instance is no longer visible? What people want from MRV is to capture the returned values independently. If I /wanted/ to capture the returned Tuple (the extremely rare case), I'd rather do that explicitly, something like this: auto t = tuple(mrvFunc()); No. Tuple stays together by default and is expanded explicitly. This is not negotiable. Then I think you commit to polluting the common case with wordy redundant noise. Why is it so important? If it were expanded by default, all you need to do it put a tuple constructor around it to wrap it up again. It creates semantic multi-assignment problems I suspect? This is what I reckon needs to be addressed to make the implementation really nice. scatter/gather is nice and simple, I'll take it in the mean time, but I think it would be a shame for it to stop there longer term... That said though, it's all still nothing to me without at least a promise on the ABI :) .. And I feel that should ideally come in the form of a language policy/promise that this feature will be 'efficient' (or at very least, not /inefficient/ as it is now), and leave it to compiler implementations to concur with that promise, ie, failing to be 'standards' compliant if they fail to do so. This is not for me to promise. Sure, but it'd be good to get a weigh in on that issue from Walter, and others, Iain?
Re: Multiple return values...
On Tue, Mar 13, 2012 at 9:07 AM, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: On 3/13/12 10:48 AM, Manu wrote: float t; ... (myStruct.pos, t, _, int err) = intersectThings(); This can be checked at compile time. The D compiler can check that the number of arguments and the types match. I actually find the scatter syntax better than this. Anyway, I hope you'll agree there's not much difference pragmatically. Correct if I am wrong but the scatter and gather functions cannot check that the number of arguments and their type match at compile time. Andrei
Re: Multiple return values...
On 3/13/12 2:07 PM, Jose Armando Garcia wrote: On Tue, Mar 13, 2012 at 9:07 AM, Andrei Alexandrescu seewebsiteforem...@erdani.org wrote: On 3/13/12 10:48 AM, Manu wrote: float t; ... (myStruct.pos, t, _, int err) = intersectThings(); This can be checked at compile time. The D compiler can check that the number of arguments and the types match. scatter() can also be compile-time checked. I left that to a runtime assert for more flexibility, but probably more checking is better particularly because skip allows skipping some values. I actually find the scatter syntax better than this. Anyway, I hope you'll agree there's not much difference pragmatically. Correct if I am wrong but the scatter and gather functions cannot check that the number of arguments and their type match at compile time. Just replace the two assert()s with static assert or a template constraint. Andrei
Re: Multiple return values...
On 3/13/12 1:20 PM, Manu wrote: What value does it add over Kenji's change? Is this because Kenji's change is unable to perform direct to existing variables? Yes. My understanding from early in the thread was that Kenji's change hides the returned tuple, and performs a convenient unpack. How can you perform a scatter if the tuple instance is no longer visible? If I understand you correctly, you just say fun().scatter(v1, v2, v3). Andrei
Re: Multiple return values...
On 13 March 2012 21:40, Andrei Alexandrescu seewebsiteforem...@erdani.orgwrote: On 3/13/12 1:20 PM, Manu wrote: What value does it add over Kenji's change? Is this because Kenji's change is unable to perform direct to existing variables? Yes. My understanding from early in the thread was that Kenji's change hides the returned tuple, and performs a convenient unpack. How can you perform a scatter if the tuple instance is no longer visible? If I understand you correctly, you just say fun().scatter(v1, v2, v3). Ah okay, I see. And you think that's more readable and intuitive than: (v1, v2, v3) = fun(); ?
Re: Multiple return values...
On 3/13/12 2:57 PM, Manu wrote: And you think that's more readable and intuitive than: (v1, v2, v3) = fun(); ? Yes (e.g. when I see the commas my mind starts running in all directions because that's valid code nowadays that ignores v1 and v2 and keeps v3 as an lvalue). Let me put it another way: I don't see one syntax over another a deal maker or deal breaker. At all. Andrei
Tuple unpacking syntax [Was: Re: Multiple return values...]
Andrei Alexandrescu: Let me put it another way: I don't see one syntax over another a deal maker or deal breaker. At all. I am usually able to follow threads, but this time I am a bit lost (this discussion has mixed very different topics like ABIs, implementation efficiency of tuples and typetuples, a hypothetical not-really-a-tuple third-kind of tuple, built-in syntax, library implementation code, etc). Is someone able and willing to summarize the current situation of this discussion? From the last weeks (more like months) of intensive usage of functional-style D code I really think D will really enjoy an unpacking syntax for std.typecons.Tuples. Recently I have written a long post about this: http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.Darticle_id=159627 The proposed syntax is quite simple, it's mostly implemented in a patch. The person that has written the patch says he's willing to add the missing part, for foreach looping (see below). Walter is waiting for something, to find problems. One raw solution for this impasse is to apply the patch as an experiment and then look for problems. Practical usages helps thinking, sometimes. In this thread I have seen something regarding problems of the patch on code like this, that has one already defined variable: Tuple!(int,int) foo() { return tuple(1, 2); } int x; (x, int y) = foo(); If such code is a problem for the patch, then I am sure the patch author will improve it. Now I am seeing a scatter()/gather library implementation. In that post of mine there examples like: alias Tuple!(CTable, string, int, int) Four; GrowableCircularQueue!Four open; // ... while (open.length) { immutable item = open.pop(); immutable CTable cur = item[0]; immutable string cSol = item[1]; immutable int x = item[2]; immutable int y = item[3]; With a tuple unpacking syntax becomes: while (open.length) { immutable (cur, cSol, x, y) = open.pop(); Are those scatter/gather designed to solve this very very simple problem? How do you unpack into immutables? I don't understand. The missing part in the DMD patch is for something like this (but the exact syntax for this is not yet decided): foreach (auto (x, y); [tuple(1,2), tuple(3,4)]) {...} How do you do this with library code? So I think we should put this thread back on the rails. Library implementations are not enough here. I suggest to start discussing about what's wrong in the proposed D syntax patch, solve the problems Walter has with it. Bye, bearophile
Re: Tuple unpacking syntax [Was: Re: Multiple return values...]
Maybe [x, y] = func(); ?
Re: Tuple unpacking syntax [Was: Re: Multiple return values...]
On Tuesday, 13 March 2012 at 22:26:14 UTC, bearophile wrote: Andrei Alexandrescu: Let me put it another way: I don't see one syntax over another a deal maker or deal breaker. At all. I am usually able to follow threads, but this time I am a bit lost (this discussion has mixed very different topics like ABIs, implementation efficiency of tuples and typetuples, a hypothetical not-really-a-tuple third-kind of tuple, built-in syntax, library implementation code, etc). Is someone able and willing to summarize the current situation of this discussion? [snip] So I think we should put this thread back on the rails. Library implementations are not enough here. I suggest to start discussing about what's wrong in the proposed D syntax patch, solve the problems Walter has with it. Bye, bearophile Yeap, I'm confused as well. D's tuple support seems to be completely messed up. This reminds me - what was the semantic problem with the auto unpacking in a function's parameter list? Personally, I think D ought to learn from the experts on this. Take a look at how FP languages implement this. E.g take a look at ML or Haskell for pointers.
Re: Multiple return values...
Shall we discuss the shortcomings of his implementation? Can someone demonstrate the details of his implementation? From the little examples up in the thread, it looked like you could only declare new variables inline, but not assign out to existing ones. I'd say this needs to be added too, and perhaps that will throw the whole design into turmoil? ;) We already have very flexible assignments to existing variables. import std.typecons, std.typetuple, std.stdio; Tuple!(int, int) foo() { return typeof(return)(1, 2); } void main() { int a, b; TypeTuple!(a, b) = foo(); writeln(a, b); TypeTuple!(a, b) = tuple(0, 1, 2)[0 .. 2]; writeln(a, b); Tuple!(int, int) t; t[] = TypeTuple!(a, b); writeln(t); TypeTuple!(a, b) = tuple(t[1], t[0]); writeln(a, b); }
Re: Tuple unpacking syntax [Was: Re: Multiple return values...]
On Wed, 14 Mar 2012 13:33:18 +1100, Kevin Cox kevincox...@gmail.com wrote: Kind of unrelated but I think that it is important to have a way to ignore values also. Leaving them bank would sufice. (int i,,float f) = intBoringFloat(); For what its worth, the Euphoria Programming Language uses ? to signify ignored values. eg. integer i atomf {i, ?, f} = intBoringFloat() We felt that leaving a blank could be a source of human error (can be missed when reading code) and also a result of human error (accidentally put in a double comma). Using an unusual glyph to signify omission was the compromise we came up with. -- Derek Parnell
Re: Multiple return values...
On 03/12/2012 05:01 AM, Robert Jacques wrote: On Sun, 11 Mar 2012 21:49:52 -0500, Mantis mail.mantis...@gmail.com wrote: 12.03.2012 4:00, Robert Jacques пишет: On Sun, 11 Mar 2012 18:15:31 -0500, Timon Gehr timon.g...@gmx.ch wrote: On 03/11/2012 11:58 PM, Robert Jacques wrote: Manu was arguing that MRV were somehow special and had mystical optimization potential. That's simply not true. Not exactly mystical, but it is certainly there. void main(){ auto a = foo(); // MRV/struct return bar(a.x); // defined in a different compilation unit } struct return has to write out the whole struct on the stack because of layout guarantees, probably making the optimized struct return calling convention somewhat slower for this case. The same does not hold for MRV. The layout of the struct only has to exist _when_ the address is taken. Before that, the compiler/language/optimizer is free to (and does) do whatever it want. Besides, in your example only the address of a field is taken, the compiler will optimize away all the other pieces a (dead variable elimination). That's the point of discussion. Fields of structure may not be optimized away, because they are not independent variables. In D you have unchecked pointer-to-pointer casts, and results of these casts should depend on target architecture, not on optimizer implementation. At particular, if such optimizations are allowed, some C API will no longer be accessible from D. Unused fields of a structure are optimized away _today_. Unless a piece of code takes the address of the struct, all of the fields are treated as independent variables. The only point I was trying to make is that the 'unless' part does not apply to MRV.
Re: Multiple return values...
On 12 March 2012 04:44, Andrei Alexandrescu seewebsiteforem...@erdani.orgwrote: On 3/11/12 6:30 PM, Manu wrote: D should define an MRV ABI which is precisely the ABI for passing multiple args TO a function, but in reverse, for any given architecture. This also has the lovely side effect of guaranteeing correct argument placement for chain-called functions. I'm quoting this because it is the tersest and clearest expression of the actual request. It's a nice feature to have, but so are many others. I don't know what it would cost to implement (my guess is: high), and how large the benefits would be in various projects. Is this basically like saying it'll never happen? There is already a pending pull request implementing the syntax, that addresses half of the feature straight up.. codegen can come later, I agreed earlier that it is of lesser importance. You don't see the immediate value in a convenient MRV syntax? It would improve code clarity in many places, and allow the code to also be more efficient down the road. D seems rather feature-complete. What many other major features are on the cards if I may ask?
Re: Multiple return values...
On 12 March 2012 04:00, Robert Jacques sandf...@jhu.edu wrote: On Sun, 11 Mar 2012 18:15:31 -0500, Timon Gehr timon.g...@gmx.ch wrote: On 03/11/2012 11:58 PM, Robert Jacques wrote: Manu was arguing that MRV were somehow special and had mystical optimization potential. That's simply not true. Not exactly mystical, but it is certainly there. void main(){ auto a = foo(); // MRV/struct return bar(a.x); // defined in a different compilation unit } struct return has to write out the whole struct on the stack because of layout guarantees, probably making the optimized struct return calling convention somewhat slower for this case. The same does not hold for MRV. The layout of the struct only has to exist _when_ the address is taken. Before that, the compiler/language/optimizer is free to (and does) do whatever it want. Besides, in your example only the address of a field is taken, the compiler will optimize away all the other pieces a (dead variable elimination). No, it can't. That's the point. It must preserve the struct in case you fiddle with the pointer. Taking the pointer is explicit in this case, but if you passed anything in the struct to another function by ref, you've setup the same scenario. Wait, ARM?! That's really cool. However, as far as I know, D on ARM is very experimental. Having an experimental compiler not eak out every last cycle is not something that should be unexpected. That said, I'm not sure what point you were trying to make, aside from backend quality-of-implementation issues. I think bringing these issues up is important, but they are tangent to the language changes you're asking for. This is using GCC's backend which is not really experimental, it has decades of field use. The point here is that we are seeing the effect of the C ABI applied directly to this problem, and it's completely un-workable. I'm trying to show that D needs to declare something of an ABI promise when applied to this problem if it is to be a useful+efficient feature. Again, C can't express this problem, and we won't get any value from of the C ABI to make this contruct efficient, but a very simple and efficient solution does exist. Why should D place this constraint on future compilers? D currently only specifies the ABI for x86. I'm fairly sure it would follow the best practices for each of the other architecture, but none of them have been established yet. Constraint? Perhaps you mean 'liberation'... The x86 ABI is not a *best* practise by a long shot. It is only banking on a traditional x86 trick for small structs. I'm was giving you an example that seemed to satisfy your complaints. An no, actually it can't return in those registers at zero cost. There is a reason why we don't use all the registers to both pass and return arguments: we need some registers free to work on them both before and after the call. D should define an MRV ABI which is precisely the ABI for passing multiple args TO a function, but in reverse, for any given architecture. .. I've never said anything about using ALL the registers, I say to use all the ARGUMENT registers. On x64, that is 4 GPR regs, and 4 XMM regs. I know Go has MRV. What does its ABI look like? What does ARM prefer? I'd recommend citing some papers or a compiler or something. Otherwise, it looks like you're ignoring the wisdom of the masses or simply ignorant. I don't have a Go toolchain, do you wanna run my tests above? Are you suggesting I have no idea what I'm talking about with respect to efficient calling conventions? The very fastest way is to return in the registers designed for the job. This is true for x64, ARM, everything. What to do when you exceed the argument register limit is a question for each architecture, but I maintain it should behave exactly as it does when calling a function, this way you create the possibility of super-efficient chain-calls. LLVM has support for MRV how I describe: The biggest change in LLVM 2.3 is Multiple Return Value (MRV) support. MRVs allow LLVM IR to directly represent functions that return multiple values without having to pass them by reference in the LLVM IR. This allows a front-end to generate more efficient code, *as MRVs are generally returned in registers if a target supports them*. See the LLVM IR Referencehttp://llvm.org/releases/2.3/docs/LangRef.html#i_getresult for more details. MRVs are fully supported in the LLVM IR, but are not yet fully supported in on all targets. However, it is generally safe to return up to 2 values from a function: most targets should be able to handle at least that. MRV support is a critical requirement for X86-64 ABI support, as X86-64 requires the ability to return multiple registers from functions, and we use MRVs to accomplish this *in a direct way*. In this case, if we have the expression defined in the language (the other guys have convinced me we do, via tuples), it's conceivable the front end could present it
Re: Multiple return values...
12.03.2012 6:01, Robert Jacques пишет: On Sun, 11 Mar 2012 21:49:52 -0500, Mantis mail.mantis...@gmail.com wrote: [...] That's the point of discussion. Fields of structure may not be optimized away, because they are not independent variables. In D you have unchecked pointer-to-pointer casts, and results of these casts should depend on target architecture, not on optimizer implementation. At particular, if such optimizations are allowed, some C API will no longer be accessible from D. Unused fields of a structure are optimized away _today_. Unless a piece of code takes the address of the struct, all of the fields are treated as independent variables. I can't confirm: http://pastebin.com/YgBULGfe Prints 42\n3.14\n, compiled with dmd -release on windows x86. How exactly did you find out that such optimization is performed?
Re: Multiple return values...
So, function with MRV is basically the function that returns Tuple where one can specify return convention? --- auto fun() { return(Windows) tuple(1, 2.0f); } (int x, float y) = fun(); ---
Re: Multiple return values...
On 12 March 2012 01:37, Andrew Wiley wiley.andre...@gmail.com wrote: On Sun, Mar 11, 2012 at 7:44 PM, Manu turkey...@gmail.com wrote: On 12 March 2012 00:58, Robert Jacques sandf...@jhu.edu wrote: That's an argument for using the right register for the job. And we can / will be doing this on x86-64, as other compilers have already done. Manu was arguing that MRV were somehow special and had mystical optimization potential. That's simply not true. Here's some tests for you: // first test that the argument registers allocate as expected... int gprtest(int x, int y, int z) { return x+y+z; } Perfect, ints pass in register sequence, return in r0, no memory access add r0, r0, r1 add r0, r0, r2 bx lr float fptest(float x, float y, float z) { return x+y+z; } Same for floats fadds s0, s0, s1 fadds s0, s0, s2 bx lr // Some MRV tests... auto mrv1(int x, int z) { return Tuple!(int, int)(x, z); } Simple case, 2 ints FAIL, stores the 2 arguments it received in regs straight to output struct pointer supplied stmia r0, {r1, r2} bx lr auto mrv2(int x, float y, byte z) { return Tuple!(int, float, byte)(x, y, z); } Different typed things EPIC FAIL stmfd sp!, {r4, r5} mov ip, #0 sub sp, sp, #24 mov r4, r2 str ip, [sp, #12] str ip, [sp, #20] ldr r2, .L27 add ip, sp, #24 mov r3, r0 mov r5, r1 str r2, [sp, #16] @ float ldmdb ip, {r0, r1, r2} stmia r3, {r0, r1, r2} fsts s0, [r3, #4] stmia sp, {r0, r1, r2} str r5, [r3, #0] strb r4, [r3, #8] mov r0, r3 add sp, sp, #24 ldmfd sp!, {r4, r5} bx lr auto range(int *p) { return p[0..1]; } Range SURPRISE FAIL, even a range is returned as a struct! O_O mov r2, #1 str r2, [r0, #0] str r1, [r0, #4] bx lr So the D ABI is a complete shambles on ARM! Unsurprisingly, it all just follows the return struct by-val ABI, which is to write it to the stack unconditionally. And sadly, it even thinks the internal types like range+delegate are just a struct by-val, and completely ruins those! Let's try again with x86... auto mrv1(int x, int z) { return Tuple!(int, int)(x, z); } Returns in eax/edx as expected movl 4(%esp), %eax movl 8(%esp), %edx auto mrv2(int x, float y, int z) { return Tuple!(int, float, int)(x, y, z); } FAIL! All written to a struct rather than returning in eax,edx,st0 .. This is C ABI baggage, D can do better. movl 4(%esp), %eax movl 8(%esp), %edx movl %edx, (%eax) movl 12(%esp), %edx movl %edx, 4(%eax) movl 16(%esp), %edx movl %edx, 8(%eax) ret $4 auto range(int *p) { return p[0..1]; } Obviously, the small struct optimisation allows this to work properly movl $1, %eax movl 4(%esp), %edx ret All that said, x86 isn't a good test case, since all args are ALWAYS passed on the stack. x64 would be a much better test since it actually has arg registers, but I'm on windows, so no x64 for me... I assume this is with GDC? Pretty sure GDC doesn't match D's official ABI anyway because Iain didn't want to try to push D ABI support into GCC along with GDC. You're probably still right, but be aware that GDC is a bit different here. Well, this is taken off the D ABI documentation. The extern (C) and extern (D) calling convention matches the C calling convention used by the supported C compiler on the host system. Except that the extern (D) calling convention for Windows x86 is described here. Examining it in a literal sense, I can say that GDC is compliant with the spec (except on x86 Windows, which it uses stdcall rather than the calling convention described on the page). -- Iain Buclaw *(p e ? p++ : p) = (c 0x0f) + '0';
Re: Multiple return values...
On 12 March 2012 00:44, Manu turkey...@gmail.com wrote: On 12 March 2012 00:58, Robert Jacques sandf...@jhu.edu wrote: That's an argument for using the right register for the job. And we can / will be doing this on x86-64, as other compilers have already done. Manu was arguing that MRV were somehow special and had mystical optimization potential. That's simply not true. Here's some tests for you: // first test that the argument registers allocate as expected... int gprtest(int x, int y, int z) { return x+y+z; } Perfect, ints pass in register sequence, return in r0, no memory access add r0, r0, r1 add r0, r0, r2 bx lr float fptest(float x, float y, float z) { return x+y+z; } Same for floats fadds s0, s0, s1 fadds s0, s0, s2 bx lr // Some MRV tests... auto mrv1(int x, int z) { return Tuple!(int, int)(x, z); } Simple case, 2 ints FAIL, stores the 2 arguments it received in regs straight to output struct pointer supplied stmia r0, {r1, r2} bx lr auto mrv2(int x, float y, byte z) { return Tuple!(int, float, byte)(x, y, z); } Different typed things EPIC FAIL stmfd sp!, {r4, r5} mov ip, #0 sub sp, sp, #24 mov r4, r2 str ip, [sp, #12] str ip, [sp, #20] ldr r2, .L27 add ip, sp, #24 mov r3, r0 mov r5, r1 str r2, [sp, #16] @ float ldmdb ip, {r0, r1, r2} stmia r3, {r0, r1, r2} fsts s0, [r3, #4] stmia sp, {r0, r1, r2} str r5, [r3, #0] strb r4, [r3, #8] mov r0, r3 add sp, sp, #24 ldmfd sp!, {r4, r5} bx lr auto range(int *p) { return p[0..1]; } Range SURPRISE FAIL, even a range is returned as a struct! O_O mov r2, #1 str r2, [r0, #0] str r1, [r0, #4] bx lr So the D ABI is a complete shambles on ARM! Unsurprisingly, it all just follows the return struct by-val ABI, which is to write it to the stack unconditionally. And sadly, it even thinks the internal types like range+delegate are just a struct by-val, and completely ruins those! Let's try again with x86... auto mrv1(int x, int z) { return Tuple!(int, int)(x, z); } Returns in eax/edx as expected movl 4(%esp), %eax movl 8(%esp), %edx auto mrv2(int x, float y, int z) { return Tuple!(int, float, int)(x, y, z); } FAIL! All written to a struct rather than returning in eax,edx,st0 .. This is C ABI baggage, D can do better. movl 4(%esp), %eax movl 8(%esp), %edx movl %edx, (%eax) movl 12(%esp), %edx movl %edx, 4(%eax) movl 16(%esp), %edx movl %edx, 8(%eax) ret $4 auto range(int *p) { return p[0..1]; } Obviously, the small struct optimisation allows this to work properly movl $1, %eax movl 4(%esp), %edx ret All that said, x86 isn't a good test case, since all args are ALWAYS passed on the stack. x64 would be a much better test since it actually has arg registers, but I'm on windows, so no x64 for me... What compiler flags are you using here? For x86, I would have thought that small structs ( 8 bytes) would be passed back in registers... only speculating though - will need to see what codegen is being built from the D code provided to be sure. -- Iain Buclaw *(p e ? p++ : p) = (c 0x0f) + '0';
Re: Multiple return values...
On 12 March 2012 19:03, Iain Buclaw ibuc...@ubuntu.com wrote: On 12 March 2012 00:44, Manu turkey...@gmail.com wrote: On 12 March 2012 00:58, Robert Jacques sandf...@jhu.edu wrote: That's an argument for using the right register for the job. And we can / will be doing this on x86-64, as other compilers have already done. Manu was arguing that MRV were somehow special and had mystical optimization potential. That's simply not true. Here's some tests for you: // first test that the argument registers allocate as expected... int gprtest(int x, int y, int z) { return x+y+z; } Perfect, ints pass in register sequence, return in r0, no memory access add r0, r0, r1 add r0, r0, r2 bx lr float fptest(float x, float y, float z) { return x+y+z; } Same for floats fadds s0, s0, s1 fadds s0, s0, s2 bx lr // Some MRV tests... auto mrv1(int x, int z) { return Tuple!(int, int)(x, z); } Simple case, 2 ints FAIL, stores the 2 arguments it received in regs straight to output struct pointer supplied stmia r0, {r1, r2} bx lr auto mrv2(int x, float y, byte z) { return Tuple!(int, float, byte)(x, y, z); } Different typed things EPIC FAIL stmfd sp!, {r4, r5} mov ip, #0 sub sp, sp, #24 mov r4, r2 str ip, [sp, #12] str ip, [sp, #20] ldr r2, .L27 add ip, sp, #24 mov r3, r0 mov r5, r1 str r2, [sp, #16] @ float ldmdb ip, {r0, r1, r2} stmia r3, {r0, r1, r2} fsts s0, [r3, #4] stmia sp, {r0, r1, r2} str r5, [r3, #0] strb r4, [r3, #8] mov r0, r3 add sp, sp, #24 ldmfd sp!, {r4, r5} bx lr auto range(int *p) { return p[0..1]; } Range SURPRISE FAIL, even a range is returned as a struct! O_O mov r2, #1 str r2, [r0, #0] str r1, [r0, #4] bx lr So the D ABI is a complete shambles on ARM! Unsurprisingly, it all just follows the return struct by-val ABI, which is to write it to the stack unconditionally. And sadly, it even thinks the internal types like range+delegate are just a struct by-val, and completely ruins those! Let's try again with x86... auto mrv1(int x, int z) { return Tuple!(int, int)(x, z); } Returns in eax/edx as expected movl 4(%esp), %eax movl 8(%esp), %edx auto mrv2(int x, float y, int z) { return Tuple!(int, float, int)(x, y, z); } FAIL! All written to a struct rather than returning in eax,edx,st0 .. This is C ABI baggage, D can do better. movl 4(%esp), %eax movl 8(%esp), %edx movl %edx, (%eax) movl 12(%esp), %edx movl %edx, 4(%eax) movl 16(%esp), %edx movl %edx, 8(%eax) ret $4 auto range(int *p) { return p[0..1]; } Obviously, the small struct optimisation allows this to work properly movl $1, %eax movl 4(%esp), %edx ret All that said, x86 isn't a good test case, since all args are ALWAYS passed on the stack. x64 would be a much better test since it actually has arg registers, but I'm on windows, so no x64 for me... What compiler flags are you using here? For x86, I would have thought that small structs ( 8 bytes) would be passed back in registers... only speculating though - will need to see what codegen is being built from the D code provided to be sure. -S -O2 -msse2 And as expected, 8byte structs were returned packed in registers from my examples above. That's a traditional x86 ABI hack which conveniently allows delegates+ranges to work well on x86, but as you can see, they're proper broken on other architectures.
Re: Multiple return values...
On 12 March 2012 17:22, Manu turkey...@gmail.com wrote: On 12 March 2012 19:03, Iain Buclaw ibuc...@ubuntu.com wrote: On 12 March 2012 00:44, Manu turkey...@gmail.com wrote: On 12 March 2012 00:58, Robert Jacques sandf...@jhu.edu wrote: That's an argument for using the right register for the job. And we can / will be doing this on x86-64, as other compilers have already done. Manu was arguing that MRV were somehow special and had mystical optimization potential. That's simply not true. Here's some tests for you: // first test that the argument registers allocate as expected... int gprtest(int x, int y, int z) { return x+y+z; } Perfect, ints pass in register sequence, return in r0, no memory access add r0, r0, r1 add r0, r0, r2 bx lr float fptest(float x, float y, float z) { return x+y+z; } Same for floats fadds s0, s0, s1 fadds s0, s0, s2 bx lr // Some MRV tests... auto mrv1(int x, int z) { return Tuple!(int, int)(x, z); } Simple case, 2 ints FAIL, stores the 2 arguments it received in regs straight to output struct pointer supplied stmia r0, {r1, r2} bx lr auto mrv2(int x, float y, byte z) { return Tuple!(int, float, byte)(x, y, z); } Different typed things EPIC FAIL stmfd sp!, {r4, r5} mov ip, #0 sub sp, sp, #24 mov r4, r2 str ip, [sp, #12] str ip, [sp, #20] ldr r2, .L27 add ip, sp, #24 mov r3, r0 mov r5, r1 str r2, [sp, #16] @ float ldmdb ip, {r0, r1, r2} stmia r3, {r0, r1, r2} fsts s0, [r3, #4] stmia sp, {r0, r1, r2} str r5, [r3, #0] strb r4, [r3, #8] mov r0, r3 add sp, sp, #24 ldmfd sp!, {r4, r5} bx lr auto range(int *p) { return p[0..1]; } Range SURPRISE FAIL, even a range is returned as a struct! O_O mov r2, #1 str r2, [r0, #0] str r1, [r0, #4] bx lr So the D ABI is a complete shambles on ARM! Unsurprisingly, it all just follows the return struct by-val ABI, which is to write it to the stack unconditionally. And sadly, it even thinks the internal types like range+delegate are just a struct by-val, and completely ruins those! Let's try again with x86... auto mrv1(int x, int z) { return Tuple!(int, int)(x, z); } Returns in eax/edx as expected movl 4(%esp), %eax movl 8(%esp), %edx auto mrv2(int x, float y, int z) { return Tuple!(int, float, int)(x, y, z); } FAIL! All written to a struct rather than returning in eax,edx,st0 .. This is C ABI baggage, D can do better. movl 4(%esp), %eax movl 8(%esp), %edx movl %edx, (%eax) movl 12(%esp), %edx movl %edx, 4(%eax) movl 16(%esp), %edx movl %edx, 8(%eax) ret $4 auto range(int *p) { return p[0..1]; } Obviously, the small struct optimisation allows this to work properly movl $1, %eax movl 4(%esp), %edx ret All that said, x86 isn't a good test case, since all args are ALWAYS passed on the stack. x64 would be a much better test since it actually has arg registers, but I'm on windows, so no x64 for me... What compiler flags are you using here? For x86, I would have thought that small structs ( 8 bytes) would be passed back in registers... only speculating though - will need to see what codegen is being built from the D code provided to be sure. -S -O2 -msse2 And as expected, 8byte structs were returned packed in registers from my examples above. That's a traditional x86 ABI hack which conveniently allows delegates+ranges to work well on x86, but as you can see, they're proper broken on other architectures. OK, -msse2 is not an ARM target option. :~) Looking around, the Procedure Call Standard for the ARM Architecture specifically says (section 5.4: Result Return): A Composite Type not larger than 4 bytes is returned in R0. A Composite Type larger than 4 bytes ... is stored in memory at an address passed as an extra argument when the function was called ... Feel free to correct me if that document is slightly out of date. -- Iain Buclaw *(p e ? p++ : p) = (c 0x0f) + '0';
Re: Multiple return values...
On 12 March 2012 17:49, Iain Buclaw ibuc...@ubuntu.com wrote: On 12 March 2012 17:22, Manu turkey...@gmail.com wrote: On 12 March 2012 19:03, Iain Buclaw ibuc...@ubuntu.com wrote: On 12 March 2012 00:44, Manu turkey...@gmail.com wrote: On 12 March 2012 00:58, Robert Jacques sandf...@jhu.edu wrote: That's an argument for using the right register for the job. And we can / will be doing this on x86-64, as other compilers have already done. Manu was arguing that MRV were somehow special and had mystical optimization potential. That's simply not true. Here's some tests for you: // first test that the argument registers allocate as expected... int gprtest(int x, int y, int z) { return x+y+z; } Perfect, ints pass in register sequence, return in r0, no memory access add r0, r0, r1 add r0, r0, r2 bx lr float fptest(float x, float y, float z) { return x+y+z; } Same for floats fadds s0, s0, s1 fadds s0, s0, s2 bx lr // Some MRV tests... auto mrv1(int x, int z) { return Tuple!(int, int)(x, z); } Simple case, 2 ints FAIL, stores the 2 arguments it received in regs straight to output struct pointer supplied stmia r0, {r1, r2} bx lr auto mrv2(int x, float y, byte z) { return Tuple!(int, float, byte)(x, y, z); } Different typed things EPIC FAIL stmfd sp!, {r4, r5} mov ip, #0 sub sp, sp, #24 mov r4, r2 str ip, [sp, #12] str ip, [sp, #20] ldr r2, .L27 add ip, sp, #24 mov r3, r0 mov r5, r1 str r2, [sp, #16] @ float ldmdb ip, {r0, r1, r2} stmia r3, {r0, r1, r2} fsts s0, [r3, #4] stmia sp, {r0, r1, r2} str r5, [r3, #0] strb r4, [r3, #8] mov r0, r3 add sp, sp, #24 ldmfd sp!, {r4, r5} bx lr auto range(int *p) { return p[0..1]; } Range SURPRISE FAIL, even a range is returned as a struct! O_O mov r2, #1 str r2, [r0, #0] str r1, [r0, #4] bx lr So the D ABI is a complete shambles on ARM! Unsurprisingly, it all just follows the return struct by-val ABI, which is to write it to the stack unconditionally. And sadly, it even thinks the internal types like range+delegate are just a struct by-val, and completely ruins those! Let's try again with x86... auto mrv1(int x, int z) { return Tuple!(int, int)(x, z); } Returns in eax/edx as expected movl 4(%esp), %eax movl 8(%esp), %edx auto mrv2(int x, float y, int z) { return Tuple!(int, float, int)(x, y, z); } FAIL! All written to a struct rather than returning in eax,edx,st0 .. This is C ABI baggage, D can do better. movl 4(%esp), %eax movl 8(%esp), %edx movl %edx, (%eax) movl 12(%esp), %edx movl %edx, 4(%eax) movl 16(%esp), %edx movl %edx, 8(%eax) ret $4 auto range(int *p) { return p[0..1]; } Obviously, the small struct optimisation allows this to work properly movl $1, %eax movl 4(%esp), %edx ret All that said, x86 isn't a good test case, since all args are ALWAYS passed on the stack. x64 would be a much better test since it actually has arg registers, but I'm on windows, so no x64 for me... What compiler flags are you using here? For x86, I would have thought that small structs ( 8 bytes) would be passed back in registers... only speculating though - will need to see what codegen is being built from the D code provided to be sure. -S -O2 -msse2 And as expected, 8byte structs were returned packed in registers from my examples above. That's a traditional x86 ABI hack which conveniently allows delegates+ranges to work well on x86, but as you can see, they're proper broken on other architectures. OK, -msse2 is not an ARM target option. :~) Looking around, the Procedure Call Standard for the ARM Architecture specifically says (section 5.4: Result Return): A Composite Type not larger than 4 bytes is returned in R0. A Composite Type larger than 4 bytes ... is stored in memory at an address passed as an extra argument when the function was called ... Feel free to correct me if that document is slightly out of date. -- Iain Buclaw *(p e ? p++ : p) = (c 0x0f) + '0'; Link: http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042d/IHI0042D_aapcs.pdf -- Iain Buclaw *(p e ? p++ : p) = (c 0x0f) + '0';
Re: Multiple return values...
On 12 March 2012 19:49, Iain Buclaw ibuc...@ubuntu.com wrote: OK, -msse2 is not an ARM target option. :~) Oh sorry, I thought you were asking about the x86 codegen ;) I used -S -O2 -float-abi=hard Looking around, the Procedure Call Standard for the ARM Architecture specifically says (section 5.4: Result Return): A Composite Type not larger than 4 bytes is returned in R0. A Composite Type larger than 4 bytes ... is stored in memory at an address passed as an extra argument when the function was called ... Indeed, x86 is the only architecture I know which has this magic 8byte packing. Every other architecture will be just as bad as ARM by the standard C ABI. Something needs to be done about delegates and ranges at the very least, it would seen GDC just see's these as 8 byte structs being passed around by value, and only x86 has a hack to improve this. Does GDC understand MRV internally? I know LLVM does at least, but I couldn't find info about GDC. Feel free to correct me if that document is slightly out of date. Document? :)
Re: Multiple return values...
On 12 March 2012 17:59, Manu turkey...@gmail.com wrote: On 12 March 2012 19:49, Iain Buclaw ibuc...@ubuntu.com wrote: OK, -msse2 is not an ARM target option. :~) Oh sorry, I thought you were asking about the x86 codegen ;) I used -S -O2 -float-abi=hard Looking around, the Procedure Call Standard for the ARM Architecture specifically says (section 5.4: Result Return): A Composite Type not larger than 4 bytes is returned in R0. A Composite Type larger than 4 bytes ... is stored in memory at an address passed as an extra argument when the function was called ... Indeed, x86 is the only architecture I know which has this magic 8byte packing. Every other architecture will be just as bad as ARM by the standard C ABI. Something needs to be done about delegates and ranges at the very least, it would seen GDC just see's these as 8 byte structs being passed around by value, and only x86 has a hack to improve this. Does GDC understand MRV internally? I know LLVM does at least, but I couldn't find info about GDC. It does not. Feel free to correct me if that document is slightly out of date. Document? :) Link: http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042d/IHI0042D_aapcs.pdf -- Iain Buclaw *(p e ? p++ : p) = (c 0x0f) + '0';
Re: Multiple return values...
On Mon, 12 Mar 2012 02:15:55 -0500, Timon Gehr timon.g...@gmx.ch wrote: On 03/12/2012 05:01 AM, Robert Jacques wrote: On Sun, 11 Mar 2012 21:49:52 -0500, Mantis mail.mantis...@gmail.com wrote: 12.03.2012 4:00, Robert Jacques пишет: On Sun, 11 Mar 2012 18:15:31 -0500, Timon Gehr timon.g...@gmx.ch wrote: On 03/11/2012 11:58 PM, Robert Jacques wrote: Manu was arguing that MRV were somehow special and had mystical optimization potential. That's simply not true. Not exactly mystical, but it is certainly there. void main(){ auto a = foo(); // MRV/struct return bar(a.x); // defined in a different compilation unit } struct return has to write out the whole struct on the stack because of layout guarantees, probably making the optimized struct return calling convention somewhat slower for this case. The same does not hold for MRV. The layout of the struct only has to exist _when_ the address is taken. Before that, the compiler/language/optimizer is free to (and does) do whatever it want. Besides, in your example only the address of a field is taken, the compiler will optimize away all the other pieces a (dead variable elimination). That's the point of discussion. Fields of structure may not be optimized away, because they are not independent variables. In D you have unchecked pointer-to-pointer casts, and results of these casts should depend on target architecture, not on optimizer implementation. At particular, if such optimizations are allowed, some C API will no longer be accessible from D. Unused fields of a structure are optimized away _today_. Unless a piece of code takes the address of the struct, all of the fields are treated as independent variables. The only point I was trying to make is that the 'unless' part does not apply to MRV. True. But the principal argument is that MRV implemented via structs is somehow less efficient than 'real' MRV. MRV structs would never have their address taken and thus the 'unless' clause never happens.
Re: Multiple return values...
On Mon, 12 Mar 2012 04:46:45 -0500, Mantis mail.mantis...@gmail.com wrote: 12.03.2012 6:01, Robert Jacques пишет: On Sun, 11 Mar 2012 21:49:52 -0500, Mantis mail.mantis...@gmail.com wrote: [...] That's the point of discussion. Fields of structure may not be optimized away, because they are not independent variables. In D you have unchecked pointer-to-pointer casts, and results of these casts should depend on target architecture, not on optimizer implementation. At particular, if such optimizations are allowed, some C API will no longer be accessible from D. Unused fields of a structure are optimized away _today_. Unless a piece of code takes the address of the struct, all of the fields are treated as independent variables. I can't confirm: http://pastebin.com/YgBULGfe Prints 42\n3.14\n, compiled with dmd -release on windows x86. How exactly did you find out that such optimization is performed? We are referring to values on the stack; unless inlined, the returning function always has to return all values for both structs and MRV. So, by 'optimized away' I'm referring to the ability of the optimizer to not keep values around, if that's more efficient. So to test it, you'd probably want to include enough operations for the optimizer not want to do this. Second, taking the address of a field might be tripping the optimizer up, so try looking at the stack directly. I honestly don't know if DMD does this particular optimization as the x86 stack is cheap and out of order chips (i.e. x86) thrive on independent assignments, but it's a big and very visible feature of NVCC, a GPU C/C++ compiler.
Re: Multiple return values...
On Mon, 12 Mar 2012 04:25:54 -0500, Manu turkey...@gmail.com wrote: On 12 March 2012 04:00, Robert Jacques sandf...@jhu.edu wrote: On Sun, 11 Mar 2012 18:15:31 -0500, Timon Gehr timon.g...@gmx.ch wrote: On 03/11/2012 11:58 PM, Robert Jacques wrote: Manu was arguing that MRV were somehow special and had mystical optimization potential. That's simply not true. Not exactly mystical, but it is certainly there. void main(){ auto a = foo(); // MRV/struct return bar(a.x); // defined in a different compilation unit } struct return has to write out the whole struct on the stack because of layout guarantees, probably making the optimized struct return calling convention somewhat slower for this case. The same does not hold for MRV. The layout of the struct only has to exist _when_ the address is taken. Before that, the compiler/language/optimizer is free to (and does) do whatever it want. Besides, in your example only the address of a field is taken, the compiler will optimize away all the other pieces a (dead variable elimination). No, it can't. That's the point. It must preserve the struct in case you fiddle with the pointer. Taking the pointer is explicit in this case, but if you passed anything in the struct to another function by ref, you've setup the same scenario. Okay, to be clear about things, once a struct is returned the optimizer can do anything to it wants. Certain compilers are extremely aggressive about this because on their hardware it matters. C and C++ compilers do this today, so yes, compilers can. Wait, ARM?! That's really cool. However, as far as I know, D on ARM is very experimental. Having an experimental compiler not eak out every last cycle is not something that should be unexpected. That said, I'm not sure what point you were trying to make, aside from backend quality-of-implementation issues. I think bringing these issues up is important, but they are tangent to the language changes you're asking for. This is using GCC's backend which is not really experimental, it has decades of field use. The point here is that we are seeing the effect of the C ABI applied directly to this problem, and it's completely un-workable. I'm trying to show that D needs to declare something of an ABI promise when applied to this problem if it is to be a useful+efficient feature. Again, C can't express this problem, and we won't get any value from of the C ABI to make this contruct efficient, but a very simple and efficient solution does exist. GCC is very large collection of things and its backend has a general reputation of being second place to the commercial vendors by a decent margin (25+%) and I think also to LLVM. I was more referring to GDC's mapping to the GCC arm backend and the associated runtime issues, etc. As for a simple and efficient solution existing: show me and academic paper or compiler that gets it right. Then show me the study on a large codebase that its actually more efficient. Then we will listen. Until then, I'm liable to trust existing wisdom. Why should D place this constraint on future compilers? D currently only specifies the ABI for x86. I'm fairly sure it would follow the best practices for each of the other architecture, but none of them have been established yet. Constraint? Perhaps you mean 'liberation'... The x86 ABI is not a *best* practise by a long shot. It is only banking on a traditional x86 trick for small structs. Let us assume for a moment that the x86 design is good for x86, but terrible for ARM and vice versa. Why should either backend do something subpar for the other. Generating code for a IOE CPU vs OOE CPU vs a stack machine vs a register machine are all very different operations and the backend should have the liberation to do whatever is best. I'm was giving you an example that seemed to satisfy your complaints. An no, actually it can't return in those registers at zero cost. There is a reason why we don't use all the registers to both pass and return arguments: we need some registers free to work on them both before and after the call. D should define an MRV ABI which is precisely the ABI for passing multiple args TO a function, but in reverse, for any given architecture. .. I've never said anything about using ALL the registers, I say to use all the ARGUMENT registers. On x64, that is 4 GPR regs, and 4 XMM regs. The point is that increasing the number of return registers isn't free and that simply matching the best number of argument registers is not, ipso facto ideal. I know Go has MRV. What does its ABI look like? What does ARM prefer? I'd recommend citing some papers or a compiler or something. Otherwise, it looks like you're ignoring the wisdom of the masses or simply ignorant. I don't have a Go toolchain, do you wanna run my tests above? Are you suggesting I have no idea what I'm talking about with respect to efficient calling conventions? The very fastest way is to return in
Re: Multiple return values...
Is this basically like saying it'll never happen? There is already a pending pull request implementing the syntax, that addresses half of the feature straight up.. codegen can come later, I agreed earlier that it is of lesser importance. You don't see the immediate value in a convenient MRV syntax? It would improve code clarity in many places, and allow the code to also be more efficient down the road. The tuple unpacking feature has nothing to do with MRV. Please don't conflate them, it creates a lot of confusion. Using registers to full extend look really nice but there are some reasons MRV is not going to happen any time soon. - Departing from platform ABI's will put us on an isle where we need our own compiler backends, debuggers and maybe even linkers and OSes. - Your favorite compiler should be great at inlining so chained function calls could have ZERO overhead passing return values. - It is not very efficient to combine MRV with tuples that have a contiguous memory layout. Instead of in-place NRVO this would be 'callee stack-registers-caller stack'.
Re: Multiple return values...
On 3/12/12 3:37 AM, Manu wrote: On 12 March 2012 04:44, Andrei Alexandrescu seewebsiteforem...@erdani.org mailto:seewebsiteforem...@erdani.org wrote: On 3/11/12 6:30 PM, Manu wrote: D should define an MRV ABI which is precisely the ABI for passing multiple args TO a function, but in reverse, for any given architecture. This also has the lovely side effect of guaranteeing correct argument placement for chain-called functions. I'm quoting this because it is the tersest and clearest expression of the actual request. It's a nice feature to have, but so are many others. I don't know what it would cost to implement (my guess is: high), and how large the benefits would be in various projects. Is this basically like saying it'll never happen? Not at all. All I'm saying is we need to have a sense of priority. Right now everything that comes up has absolute priority, but somehow shouldn't make the previous hot topics enjoy the same level. Getting excited is easy. Lucidly analyzing and prioritizing is difficult. There is already a pending pull request implementing the syntax, that addresses half of the feature straight up.. codegen can come later, I agreed earlier that it is of lesser importance. You see, at this point I have no idea what to believe anymore. You argued very strongly from the position of one whose life depends on efficiency. Here and there you'd mix some remark about syntax, and I'd like whaa?... but generally discounted it as distraction from the main point, which was that all you must do is f(g()) where the body of g() is insignificantly small, which makes the cost of passing arguments around absolutely paramount. And now you come with this completely opposite viewpoint in which the syntax is paramount and urgent, whereas codegen is like let's leave it for later. I really am confused. You don't see the immediate value in a convenient MRV syntax? It would improve code clarity in many places, and allow the code to also be more efficient down the road. I see value in Kenji's related diff, but not in adding syntax to e.g. return (int, int). But we want to make sure we address the matter holistically (for example: is Kenji's diff enough, or do we need to worry about assignment too?). The worst strategy in chess is to move a piece and then start analyzing the new situation on the board. D seems rather feature-complete. What many other major features are on the cards if I may ask? You mean designing new features? Not a lot, but this is a moot point anyway because the work for scatter initialization has already been done. Andrei
Re: Multiple return values...
On 11 March 2012 03:45, Robert Jacques sandf...@jhu.edu wrote: On Sat, 10 Mar 2012 19:27:05 -0600, Manu turkey...@gmail.com wrote: On 11 March 2012 00:25, Sean Cavanaugh worksonmymach...@gmail.com wrote: On 3/10/2012 4:37 AM, Manu wrote: If I pass a structure TO a function by value, I know what happens, a copy is written to the stack which the function expects to find there. This is only true if the compiler is forced to use the ABI, when inlining is impossible, or the type being passed is too complex. Structs of pods, most compilers do magical things to provided you don't actively work against the code gen (virtual methods, dllexports etc), too many separate .obj units in C++ etc. Talking about the typical case here, obviously not inlined, calling through the ABI, struct may be simple, just 2-3 values, can you show me a case where C++ is able to to anything particularly fancy? I've never seen the compiler do wildly unexpected things unless whole program optimisation is enabled, which I don't imagine D will be able to support any time real soon? ...and even then, relying on WPO for the language to generate good code in certain circumstances is a really really bad idea. This makes the task of implementing an efficient compiler for the language extremely difficult. In most cases, making concise expression of the operation you want to perform possible in the language will generate better results anyway, without depending on an aggressive optimiser. It will also make the code more explicit and readable. Manu, please go read the D ABI (http://dlang.org/abi.html). Remember, your example of returning two values using Tuple vs 'real' MRV? The D ABI states that those values will be returned via registers. Returning something larger? Then the NVRO kicks in which gives you a zero copy approach. On x86-64 these limits are different, since you have more registers to play with, but the concept is the same. In fact, returning arguments has always been more efficient than passing arguments. Please go read my prior posts. This has absolutely no bearing on what I'm talking about. In fact, it fuels my argument in some cases. That said, I've read it, and it scares the hell out of me. D states that a small struct may be returned in up to 2 registers (8 bytes/16 bytes), I suspect this is a hack introduced specifically to make ranges efficient? If it is not simply a struct of 2 register sized things, they get packed into a magic 8-16 byte struct implicitly for return, and this makes my brain explode. If I wanted to perform a bunch of shifts, or's, and and's until it's snugly packed into a 8-16 byte block before returning, I'd do that explicitly... I DON'T want to do that however, and I *really* don't want the language doing that for me. If I want to return 4 byte values, they should be returned in 4 registers, not packed together into a 32bit word and returned in one. Also, if there are mixed ints, floats, vectors, even other structs, none of it works; what if there is float data? Swapping registers now? That's one of the worst penalties there is.
Re: Multiple return values...
On 11 March 2012 04:35, Sean Cavanaugh worksonmymach...@gmail.com wrote: On 3/10/2012 8:08 PM, Mantis wrote: Tuple!(float, float) callee() { do something to achieve result in st0,st1 fst st0, st1 into stack load stack values into EAX, EDX ret } void caller() { call callee() push EAX, EDX into a stack fld stack values into st0, st1 do something with st0, st1 } As opposed to: Tuple!(float, float) callee() { do something to achieve result in st0,st1 ret } void caller() { call callee() do something with st0, st1 } Is there something I miss here? Yes, the fact the FPU stack is deprecated :) Don't dismiss the point, the same still stands true with XMM regs.
Re: Multiple return values...
On 11 March 2012 05:04, Andrei Alexandrescu seewebsiteforem...@erdani.orgwrote: On 3/10/12 4:37 AM, Manu wrote: I still fundamentally see a clear divide between a Tuple, which is a deliberately structured association of multiple values, and 'multiple return values' which is an explicit non-association of multiple returned things. There is no difference. A Tuple is a product type, exactly like superposing arbitrary values together. So you're saying that whatever I think about the implementation of Tuple is wrong? It is not actually a structured type? You need to address that principle before I can begin to accept the idea of abusing a structured tuple as a substitute for this important language feature. My analogy is the function argument list. You don't think of the arguments you pass to a function as a Tuple (is it implemented internally in this way? if so, it is well hidden, and perhaps similar magic can be done...) You pass, TO a function, multiple un-associated values. This analogy is tenuous for D because functions are defined to return one type, e.g. typeof(fun(args)) is defined. Once we get into disallowing that for certain functions, we're looking at major language changes for little benefit. I don't know how 'major' they'd really work out to be. It's not a paradigm buster. There are some details, but I don't even think it would need to be a breaking change to the language (maybe some very subtle tweaks). Can you quantify 'little' benefit? I started this thread because I have found myself wishing for this feature every other day. It would be of huge value, and many others seem to agree. D has it all, there are so many features in D which make it feel like a modern language, but this is a missed(/rejected?) opportunity. It's a natural thing to want to ask a computer to do, but we're mentally trained into the returns-a-single-thing model from C and whatever and apparently it was never considered at the initial design stage, but apart from the expressive side, more importantly in D's case as a native language, it is an opportunity to implement an important low level feature that no other language offers me; an efficient multi-return syntax+ABI. In the tight loops of every program I've ever written, I can't recall a time when I haven't had a function that needs to return multiple things, and C/C++ has always forced me into unnecessary memory access to express this (ref parameters). For the first time in language/compiler history, D would finally be able to eliminate the last of the redundant memory accesses in my hottest code in a portable way, at a language/expression level. Are you saying it's impossible, that you don't think it should be done, or it's already solved? Is it that you don't see the value in it? Is that what I need to convince you of? You haven't argued with any of the other points I raised, which leads me to suspect you either see my points, or you think the entire premise of my rant is wrong... if so, can you show how, and present a solution that's workable within existing constructs that meets my criteria without adding other implicit/logical baggage? Nobody has acknowledged or disputed the majority of my points :/ D is a native language, that's it's most attractive feature, and while I appreciate all the high-level correct-ness D offers, it still needs to get the low level right and ideally improve on existing offerings in meaningful ways to motivate new users to switch. This is a big un-checked checkbox for me and my colleagues, and I'd wager any low level realtime programmer out there who has had to struggle with the codegen in their inner loops.. doubly so if they ever work on non-x86 systems since the penalties are so much greater.
Re: Multiple return values...
On 11 March 2012 05:04, Andrei Alexandrescu seewebsiteforem...@erdani.orgwrote: This analogy is tenuous for D because functions are defined to return one type, e.g. typeof(fun(args)) is defined. Once we get into disallowing that for certain functions, we're looking at major language changes for little benefit. I do appreciate this point though... Is it currently possible to enumerate a functions argument list? How do you express that? The problems seem very similar to me.
Re: Multiple return values...
On 03/11/2012 01:30 PM, Manu wrote: On 11 March 2012 05:04, Andrei Alexandrescu seewebsiteforem...@erdani.org mailto:seewebsiteforem...@erdani.org wrote: This analogy is tenuous for D because functions are defined to return one type, e.g. typeof(fun(args)) is defined. Once we get into disallowing that for certain functions, we're looking at major language changes for little benefit. I do appreciate this point though... It is not a valid concern. typeof(fun(args)) would just be a tuple of types. Is it currently possible to enumerate a functions argument list? Yes it is. How do you express that? The problems seem very similar to me. Language built-in tuples, of course. See std.traits.ParameterTypeTuple. It is extremely obvious how multiple return values should work.
Re: Multiple return values...
On 03/11/2012 12:50 PM, Manu wrote: Nobody has acknowledged or disputed the majority of my points :/ I agree with the majority of your points.
Re: Multiple return values...
On Sunday, 11 March 2012 at 03:04:38 UTC, Andrei Alexandrescu wrote: This analogy is tenuous for D because functions are defined to return one type, e.g. typeof(fun(args)) is defined. Once we get into disallowing that for certain functions, we're looking at major language changes for little benefit. TypeTuple!(ReturnType1, ReturnType2)? David
Re: Multiple return values...
On 11 March 2012 14:56, Timon Gehr timon.g...@gmx.ch wrote: On 03/11/2012 12:50 PM, Manu wrote: Nobody has acknowledged or disputed the majority of my points :/ I agree with the majority of your points. Cool, well that's encouraging :) I can't really argue the implementation details, all I can do is assert criteria/requirements as I see them. So what was the perceived issue with the pull request you mentioned in an earlier post? I presume it only implemented the syntax, and not the ABI bits? On 11 March 2012 15:08, David Nadlinger s...@klickverbot.at wrote: On Sunday, 11 March 2012 at 03:04:38 UTC, Andrei Alexandrescu wrote: This analogy is tenuous for D because functions are defined to return one type, e.g. typeof(fun(args)) is defined. Once we get into disallowing that for certain functions, we're looking at major language changes for little benefit. TypeTuple!(ReturnType1, ReturnType2)? Right, well I'm glad I'm not the only one :) I figured this must have some important implication that I totally missed...
Re: Multiple return values...
On 03/11/2012 02:23 PM, Manu wrote: On 11 March 2012 14:56, Timon Gehr timon.g...@gmx.ch mailto:timon.g...@gmx.ch wrote: On 03/11/2012 12:50 PM, Manu wrote: Nobody has acknowledged or disputed the majority of my points :/ I agree with the majority of your points. Cool, well that's encouraging :) I can't really argue the implementation details, all I can do is assert criteria/requirements as I see them. So what was the perceived issue with the pull request you mentioned in an earlier post? This is the pull request. https://github.com/D-Programming-Language/dmd/pull/341 I think the issue with it is that there are no obvious issues with it. I presume it only implemented the syntax, and not the ABI bits? Exactly. It implements the assignment from built-in/library tuples to multiple newly declared variables, but it does not implement multiple return values.
Re: Multiple return values...
On 03/11/12 02:27, Manu wrote: I've never seen the compiler do wildly unexpected things unless whole program optimisation is enabled The compiler can only ignore the ABI when it knows it sees the whole picture, ie whole program, or whole unit in case of private/local functions. which I don't imagine D will be able to support any time real soon? Umm, GDC supports WPO, both as LTO and std WPO, though i don't remember if i ever tried the latter. In fact, until GDC learns cross-module inlining, using these modes is the only way to make the compiler generate sane code, w/o reverting to turning all D functions into templates or compiling all sources together (at which point you could just as well turn on WPO...). Also, unit-at-a-time, which is on by default, can help. It (gdc) still has a few LTO bugs which sometimes cause trouble, but often can be worked around. And you may need to use the *.d runtime files (instead of *.di) etc. ...and even then, relying on WPO for the language to generate good code in certain circumstances is a really really bad idea. This makes the task of implementing an efficient compiler for the language extremely difficult. There is a reason why i never even considered trying out a non-gcc-based D compiler... [1] For a language like D, WPO in some form is almost required; w/o it you'd need to always think about how the compiler will treat your code; which, while possible, would often result in more unnatural and tricky solutions. (In C/C++ there's a *.h/*.c split, which helps mitigate the problem) artur [1] LLVM is possibly an option too, but i haven't yet found the time to investigate.
Re: Multiple return values...
On 11 March 2012 15:35, Timon Gehr timon.g...@gmx.ch wrote: On 03/11/2012 02:23 PM, Manu wrote: On 11 March 2012 14:56, Timon Gehr timon.g...@gmx.ch mailto:timon.g...@gmx.ch wrote: On 03/11/2012 12:50 PM, Manu wrote: Nobody has acknowledged or disputed the majority of my points :/ I agree with the majority of your points. Cool, well that's encouraging :) I can't really argue the implementation details, all I can do is assert criteria/requirements as I see them. So what was the perceived issue with the pull request you mentioned in an earlier post? This is the pull request. https://github.com/D-**Programming-Language/dmd/pull/**341https://github.com/D-Programming-Language/dmd/pull/341 I think the issue with it is that there are no obvious issues with it. I presume it only implemented the syntax, and not the ABI bits? Exactly. It implements the assignment from built-in/library tuples to multiple newly declared variables, but it does not implement multiple return values. How does the syntax work precisely? Is it possible to do each of those things I mentioned earlier; assign to existing locals, ignore individual return values, etc? How would the language distinguish from this implementation of implicitly building/returning/unpacking a tuple, and from explicit intent to do so structurally? Does the syntax give the code generation enough information to distinguish multiple-return from struct byval?
Re: Multiple return values...
On 11 March 2012 16:01, Artur Skawina art.08...@gmail.com wrote: which I don't imagine D will be able to support any time real soon? In fact, until GDC learns cross-module inlining, using these modes is the only way to make the compiler generate sane code Yeah I'm very concerned by this currently. But it seems to we a well known issue with a plan/intent to fix it right? (Side note: I'd still really like an explicit @forceinline attribute) ...and even then, relying on WPO for the language to generate good code in certain circumstances is a really really bad idea. This makes the task of implementing an efficient compiler for the language extremely difficult. There is a reason why i never even considered trying out a non-gcc-based D compiler... [1] For a language like D, WPO in some form is almost required; w/o it you'd need to always think about how the compiler will treat your code; which, while possible, would often result in more unnatural and tricky solutions. (In C/C++ there's a *.h/*.c split, which helps mitigate the problem) think about how the compiler will treat your code ... 'unnatural' and tricky solutions ... Outside of cross module inlining, what are some other common problem cases? C/C++ .h files only really simplify 2 things, inlining, and templates, but I can't see how the situation is any different in D? My understanding is that templates depends on .d/.di files being present during compilation? So why doesn't D do inlining the same way? If I declare some inline function in a .d/.di file, surely it should be capable of inlining? C/C++ can't inline something from a foreign object either without a definition in a header...
Re: Multiple return values...
On 03/11/12 15:59, Manu wrote: On 11 March 2012 16:01, Artur Skawina art.08...@gmail.com mailto:art.08...@gmail.com wrote: which I don't imagine D will be able to support any time real soon? In fact, until GDC learns cross-module inlining, using these modes is the only way to make the compiler generate sane code Yeah I'm very concerned by this currently. But it seems to we a well known issue with a plan/intent to fix it right? (Side note: I'd still really like an explicit @forceinline attribute) Just '@forceinline' is IMHO too specific and not enough -- because you also want @noinline, @noclone, @flatten, @cold and more of theses kind of /generic/ attributes, not to mention the target-specific ones. So a way to specify function (and data) attributes is needed, together with a way to bundle them. Maybe something like alias @attr(noinline, noclone, cold, nothrow) @errorpath;. ...and even then, relying on WPO for the language to generate good code in certain circumstances is a really really bad idea. This makes the task of implementing an efficient compiler for the language extremely difficult. There is a reason why i never even considered trying out a non-gcc-based D compiler... [1] For a language like D, WPO in some form is almost required; w/o it you'd need to always think about how the compiler will treat your code; which, while possible, would often result in more unnatural and tricky solutions. (In C/C++ there's a *.h/*.c split, which helps mitigate the problem) think about how the compiler will treat your code ... 'unnatural' and tricky solutions ... Outside of cross module inlining, what are some other common problem cases? C/C++ .h files only really simplify 2 things, inlining, and templates, but I can't see how the situation is any different in D? Traditionally, the pre-WPO way, the coder would put the things expected to become part of the caller in header files. In D, header files are unnecessary, other than for the binary-lib-interface case. And currently even if you duplicate part of the code in a *.di file, it doesn't really change anything, other than preventing some optimizations like inlining or cloning (for the omitted parts). If you have a complex data type which exports eg a range, you may want just '.front' etc to be inlined, and creating a *.di file just for this purpose shouldn't be required. Template definitions need to be visible to the caller/user too - having to place them all in a *.di file would not be a good solution - in many cases most of the code would move there... Other than that, there is devirtualization - D does not have 'virtual'; the compiler can still do it and even inline the virtual methods, but it needs to know that nothing overrides the functions - impossible w/o WPO for public non-final classes. [1] My understanding is that templates depends on .d/.di files being present during compilation? So why doesn't D do inlining the same way? If I declare some inline function in a .d/.di file, surely it should be capable of inlining? C/C++ can't inline something from a foreign object either without a definition in a header... It's the same for D, I just don't think *.di files should be necessary for _internal_ cross-module interfaces, or when the full source is available anyway. Currently, cross-module inlining is a problem for GDC, but even when that's fixed, WPO will help things like devirtualization or changing calling conventions (gcc does it already, but i don't know if it clones non-local functions just for this, when not in WPO mode). artur [1] BTW, this means that a @virtual attribute might be needed - to prevent devirtualizing methods for classes used by plugins or other dynamically loaded code.
Re: Multiple return values...
On Sun, 11 Mar 2012 05:57:05 -0500, Manu turkey...@gmail.com wrote: On 11 March 2012 04:35, Sean Cavanaugh worksonmymach...@gmail.com wrote: On 3/10/2012 8:08 PM, Mantis wrote: Tuple!(float, float) callee() { do something to achieve result in st0,st1 fst st0, st1 into stack load stack values into EAX, EDX ret } void caller() { call callee() push EAX, EDX into a stack fld stack values into st0, st1 do something with st0, st1 } As opposed to: Tuple!(float, float) callee() { do something to achieve result in st0,st1 ret } void caller() { call callee() do something with st0, st1 } Is there something I miss here? Yes, the fact the FPU stack is deprecated :) Don't dismiss the point, the same still stands true with XMM regs. And Walter has talked about using the XMM registers to return floating point data for exactly this reason. But those optimization apply to all structs and all data types. There's nothing special about MRV. It has to return a set of data in a structured manner; this is identical to the case of returning a struct.
Re: Multiple return values...
On 11 March 2012 18:50, Robert Jacques sandf...@jhu.edu wrote: On Sun, 11 Mar 2012 05:57:05 -0500, Manu turkey...@gmail.com wrote: On 11 March 2012 04:35, Sean Cavanaugh worksonmymach...@gmail.com wrote: On 3/10/2012 8:08 PM, Mantis wrote: Tuple!(float, float) callee() { do something to achieve result in st0,st1 fst st0, st1 into stack load stack values into EAX, EDX ret } void caller() { call callee() push EAX, EDX into a stack fld stack values into st0, st1 do something with st0, st1 } As opposed to: Tuple!(float, float) callee() { do something to achieve result in st0,st1 ret } void caller() { call callee() do something with st0, st1 } Is there something I miss here? Yes, the fact the FPU stack is deprecated :) Don't dismiss the point, the same still stands true with XMM regs. And Walter has talked about using the XMM registers to return floating point data for exactly this reason. But those optimization apply to all structs and all data types. There's nothing special about MRV. It has to return a set of data in a structured manner; this is identical to the case of returning a struct. Well you can't build these sorts of tightly packed structs in XMM registers... and even for the GPR's, it's very inefficient to do so. I haven't done tests, but I suspect this feature is probably a de-optimisation in all cases other than an int-pair struct (ranges, delegates? I suspect that's why this was invented). Structure packing/unpacking code will likely be slower than a store/load. We just need proper multiple return values, then this can go away. And the earlier any ABI breaking changes are implemented, the better, while there are still few(/none?) closed source, binary distributed D libraries.
Re: Multiple return values...
On 11 March 2012 18:45, Artur Skawina art.08...@gmail.com wrote: Other than that, there is devirtualization - D does not have 'virtual'; the compiler can still do it and even inline the virtual methods, but it needs to know that nothing overrides the functions - impossible w/o WPO for public non-final classes. [1] Oh don't get me started! (...oops, too late!) This is my single greatest complaint about D, and if I were to adopt D professionally, I would almost certainly fork the language and fix this. virtual-by-default seems like the single biggest and most harmful *mistake*in the whole language. Defaulting a feature that introduces very costly damage that ONLY a WPO pass can possibly un-do can only be described as insanity. Choosing to do this intentionally... does DMD have a powerful WPO? _ I genuinely fear for this decision. Junior programmers will write really slow code unknowingly, and it's too easy for anyone to simply forget to declare 'final'. Seniors will spend god only knows how much time (late nights?) trawling through the codebase verifying non-virtuals, and marking them final, time you probably can't afford when crunching to ship a product. Add to that the fact that *validating* a method is not overridden anywhere is NOT a trivial task. Being able to do this with confidence will be tedious and waste unimaginable amounts of time and frustration... and why? What was the harm in explicit virtual? I expect explicit 'final' will surely overwhelm virtuals in number too when considering keyword clutter (trivial accessors, properties, etc make up the majority of methods) :/ If I were evaluating D to use commercially, this would be the biggest red flag to consider. The time/efficiency costs could potentially be very high. My understanding is that templates depends on .d/.di files being present during compilation? So why doesn't D do inlining the same way? If I declare some inline function in a .d/.di file, surely it should be capable of inlining? C/C++ can't inline something from a foreign object either without a definition in a header... It's the same for D, I just don't think *.di files should be necessary for _internal_ cross-module interfaces, or when the full source is available anyway. Currently, cross-module inlining is a problem for GDC, but even when that's fixed, WPO will help things like devirtualization or changing calling conventions (gcc does it already, but i don't know if it clones non-local functions just for this, when not in WPO mode). So assuming that most libraries will be closed source, what's the implication here for inlines and tempaltes long term? It sounds no different than .h files. Is there a plan to be able to store that metadata inside lib/object files somehow? (I assume not, if there was, why need .di files at all)
Re: Multiple return values...
On 3/11/12 7:52 AM, Timon Gehr wrote: It is extremely obvious how multiple return values should work. (int, int) fun(); writeln(fun()); auto a = fun(); writeln(a); What should happen? Andrei