www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Another feature/optimization request (also: macro proposal)

reply downs <default_357-line yahoo.de> writes:
Okay, this is more of a compiler than language feature request.

While writing the OpenGL API for dglut, I found myself repeatedly using the
following idiom:

 
 void glDoStuff(GLenum foo, int bar, void delegate()[] dgs...) {
   auto oldState=glGetStateFooBar(foo);
   scope(exit) glStateFooBar(foo, oldState);
   glStateFooBar(foo, bar);
   
   foreach (dg; dgs) dg();
 }
 

Seeing as this, worst-case, comes down to two function call layers, just to do some easier OpenGL stuff, I decided to test how this common situation gets inlined. For this, I dumped EBP during several parts of the code.
 
 import std.stdio;
 void test(void delegate()[] dgs...) {
         writefln("test: ", getESP());
         foreach (dg; dgs) dg();
 }
 
 void *getESP() {
         void *res;
         asm { mov res, EBP; }
         return res;
 }
 
 void main() {
         writefln("main: ", getESP());
         test({ writefln("dg: ", getESP()); }); 
 }
 

The result: DMD doesn't inline at all. GDC inlines test, but not the delegate. Because this might cause significant slowdowns, I'd like to request that the language spec guarantee the following: that in a case such as,
 auto foo={}; foo();
 // or
 auto bar=[{}];
 foreach (b; bar) b(); // foreach can get unrolled at compile-time because the
array is static

foo (and b) always get inlined. Note that I propose no kind of complex value tracking, just this one, simple case. The effect of this will be to place user-defined control structures taking delegate parameters on the same level as built-in statements. * ALTERNATIVE PROPOSAL * Another way to achieve this would be the introduction of a "block" type. This type would behave as follows: - When used in a parameter list, block behaves like typesafe variadic void delegate()[], with the following modifications: - All delegate and function *literals* that do not take parameters are implicitly converted to block. This conversion will make the following changes: - If the literal returns a value, this value is stored until the control flow re-enters the function in which they were declared. - At this point, the value is returned. - non-literal delegate() variables are also implicitly convertible to block. - Since the above modifications cannot be applied, the variables are treated as if they were wrapped in a literal. - Block variables can be called like void delegate() variables. This causes immediate inlining. - Lexically speaking, each block constitutes a scope. The above example, rewritten using block, would look something like this:
 void glDoStuff(GLenum foo, int bar, block b) {
   auto oldState=glGetStateFooBar(foo);
   scope(exit) glStateFooBar(foo, oldState);
   glStateFooBar(foo, bar);
   b(); // gets inlined
 }

Since blocks are evaluated completely at compile-time, features like block AST introspection or modification might eventually be added, thus tuning blocks into the foundation of macros. The obligatory: what do you think? :) -- downs
Jan 02 2008
next sibling parent downs <default_357-line yahoo.de> writes:
downs wrote:
 Since blocks are evaluated completely at compile-time, features like block AST
introspection or modification might eventually be added,
 thus tuning blocks into the foundation of macros.
 

Ahem. "turning". Sorry. --downs
Jan 02 2008
prev sibling next sibling parent janderson <askme me.com> writes:
downs wrote:
 Okay, this is more of a compiler than language feature request.
 
 While writing the OpenGL API for dglut, I found myself repeatedly using the
following idiom:
 
 void glDoStuff(GLenum foo, int bar, void delegate()[] dgs...) {
   auto oldState=glGetStateFooBar(foo);
   scope(exit) glStateFooBar(foo, oldState);
   glStateFooBar(foo, bar);
   
   foreach (dg; dgs) dg();
 }

Seeing as this, worst-case, comes down to two function call layers, just to do some easier OpenGL stuff, I decided to test how this common situation gets inlined. For this, I dumped EBP during several parts of the code.
 import std.stdio;
 void test(void delegate()[] dgs...) {
         writefln("test: ", getESP());
         foreach (dg; dgs) dg();
 }

 void *getESP() {
         void *res;
         asm { mov res, EBP; }
         return res;
 }

 void main() {
         writefln("main: ", getESP());
         test({ writefln("dg: ", getESP()); }); 
 }

The result: DMD doesn't inline at all. GDC inlines test, but not the delegate. Because this might cause significant slowdowns, I'd like to request that the language spec guarantee the following: that in a case such as,
 auto foo={}; foo();
 // or
 auto bar=[{}];
 foreach (b; bar) b(); // foreach can get unrolled at compile-time because the
array is static

foo (and b) always get inlined. Note that I propose no kind of complex value tracking, just this one, simple case. The effect of this will be to place user-defined control structures taking delegate parameters on the same level as built-in statements.

I think that would be best however I think it should be done with templates (I'm not sure if its already possible?): ie void glDoStuff(void delegate()[] dgs)(GLenum foo, int bar) { auto oldState=glGetStateFooBar(foo); scope(exit) glStateFooBar(foo, oldState); glStateFooBar(foo, bar); foreach (dg; dgs) dg(); }


-Joel
Jan 02 2008
prev sibling parent reply downs <default_357-line yahoo.de> writes:
downs wrote:
 
  * ALTERNATIVE PROPOSAL *
 

Let's modify this proposal a bit. First, let us introduce a specialization of the delegate type: dgliteral. It behaves like delegate in most ways, with the following exceptions: - A dgliteral that was created from a delegate literal - cannot be returned from a function, - is implicitly constant and - is always inlined on call. - A dgliteral that was created from a delegate variable behaves as if that delegate were wrapped as follows: { return dg(); } - Even though they nominally share the same type, a dgliteral's complete type encodes the literal used to create it, as well as its return type. This is also why they cannot be reassigned. Every dgliteral can be implicitly converted to a delegate that returns an int. This return value signals the type of scope exit: zero for normal, one for break, two for return. (dgliterals of course behave in the same way) ((alternatively, use symbolic constants for these)) The actual value "return"ed in the dgliteral is stored until control flow successfully re-enters the function in which the dgliteral was created. It is then returned from the surrounding function. The value returned may be queried and modified using the "ret" property. Every dgliteral passed as a parameter to a function behaves as if it were a dgliteral-type template value parameter of that function. This of course means the function cannot be inherited or used in interfaces. A recommended way around this limitation is generating code that behaves as if the dgliteral was a simple delegate, and fall back to this code when the precise function to be used cannot be determined (interface/non-final class usage). Every function that takes a dgliteral as a parameter must return an integer; this return value signals to the calling scope in the same way that the return value of dgliteral does. For illustration, here's an implementation of opApply using dgliteral: class ArrayWrapper(T) { T[] array; int opApply(int dgliteral(ref T) dl) { foreach (ref entry; array) { auto res=dg(entry); if (res==1) return 0; // only exit the foreach scope if (res==2) return 2; // return is transferred to the outside scope } return 0; // normal exit } } --downs
Jan 03 2008
parent reply downs <default_357-line yahoo.de> writes:
downs wrote:
 downs wrote:
  * ALTERNATIVE PROPOSAL *

Let's modify this proposal a bit.

In fact, let's go one step further. How about this? If a dgliteral is the last parameter to a function, say,
 int test(int x, int dgliteral() foo) {
   ...
 }

then the following syntax shall be allowed:
 test(5) { writefln("Yay!"); }

To demonstrate what this would allow, consider this:
 int forall(T)(T[] array, int dgliteral(T) dl) {
   foreach (entry; array) {
     auto res = dl(entry);
     if (res==1) return 0;
     if (res==2) return 2;
   }
   return 0;
 }


 forall([2, 3, 4, 5]) (int x) { writefln(x); }

Not quite foreach in software yet, but we're getting there :) IN FACT, while we're about it, let's warm up another old proposal: to consider (int x) writefln(x); a literal, that is, to allow omitting the braces for single-statement literals. I'm ready to argue that the benefits of this syntax in bringing literals in line with the behavior of most of D's other statements outweigh the drawbacks of forbidding the arising ambiguities, if there even are any. As usual, what do you think? --downs
Jan 03 2008
parent reply downs <default_357-line yahoo.de> writes:
I currently use the following code as part of dglut:
 glChecked("RenderToScreen", MatrixScope(init, renderScene(f),
img2.With(WithFlag(GL_BLEND, true, { ... }))));
 

With the proposed features, this could be rewritten as follows:
 glChecked("RenderToScreen") MatrixScope { init; renderScene(f); img2.With
WithFlag(GL_BLEND, true) { ... } }
 // without trailing delegates
 glChecked("RenderToScreen", { MatrixScope({ init; renderScene(f); img2.With({
WithFlag(GL_BLEND, true, { ... }); }); }); });

Now if this looks better is a matter of taste (personally I think it does). But with the proposed extension, the first would translate to four function-call levels of wrappers, while the second and third can be inlined completely. I don't think it should be necessary to decide for user-defined abstractions OR speed, and since the delegate inlining issue looks hard to fix, this seems to be the only way to use your own abstractions as well as take advantage of inlining. --downs
Jan 03 2008
parent reply "Bruce Adams" <tortoise_74 yeah.who.co.uk> writes:
On Thu, 03 Jan 2008 20:10:51 -0000, downs <default_357-line yahoo.de>  =

wrote:

 I currently use the following code as part of dglut:
 glChecked("RenderToScreen", MatrixScope(init, renderScene(f),  =


 img2.With(WithFlag(GL_BLEND, true, { ... }))));

With the proposed features, this could be rewritten as follows:
 glChecked("RenderToScreen") MatrixScope { init; renderScene(f);  =


 img2.With WithFlag(GL_BLEND, true) { ... } }
 // without trailing delegates
 glChecked("RenderToScreen", { MatrixScope({ init; renderScene(f);  =


 img2.With({ WithFlag(GL_BLEND, true, { ... }); }); }); });

Now if this looks better is a matter of taste (personally I think it =

 does). But with the proposed extension,
 the first would translate to four function-call levels of wrappers,  =

 while the second and third can be inlined completely.

 I don't think it should be necessary to decide for user-defined  =

 abstractions OR speed, and since the delegate inlining issue
 looks hard to fix, this seems to be the only way to use your own  =

 abstractions as well as take advantage of inlining.

  --downs

Can you honestly say either of those look particularly readable? Imagine= trying to explain it to a new recruit, particularly one unfamiliar with = D. This is why Python invented compulsory whitespace. How about just re-laying-out (is that a word?) your orginal as: glChecked("RenderToScreen", MatrixScope(init, renderScene(f), img2.With(WithFlag(GL_BLEND, true, { ... })))); If you must insist on a heavily nested functional style you could ask for improved laziness support and write: auto lazy cthulhu =3D WithFlag(GL_BLEND,true, {...}); auto lazy nylarathotep =3D img2.With(cthulhu); auto lazy yogshoggoth =3D MatrixScope(init, renderScene(f), nylarathotep); glChecked("RenderToScreen", yogshoggoth); Then if you further wish to please the great old ones you could add some comments between the lines to explain the code to noobs. Then if you're lucky you might get to be eaten first = (http://www.geocities.com/neverclan/c/cthulhu.html). Regards, Bruce.
Jan 03 2008
parent downs <default_357-line yahoo.de> writes:
Bruce Adams wrote:
 
 Can you honestly say either of those look particularly readable? Imagine
 trying to explain it to a new recruit, particularly one unfamiliar with D.
 This is why Python invented compulsory whitespace.
 
 [snip lots of helpful advice]

 Regards,
 
 Bruce.
 

I agree that neither of them is particularly readable. However, irregardless of the trailing delegate part of the proposal, it remains fact that the proposed dgliteral can be easily inlined, something the current delegate form will probably never have. Apart from that, personally I'd prefer trailing dgliterals if only because I have to type less braces :) but if I'd have to choose, I'd say they're the least important part of my set of proposals. Thanks for your answer though, --downs
Jan 03 2008