digitalmars.D - Dynamic closure vs static closure
- Frank Benoit (23/23) Oct 25 2008 It is great to hear that this issue is getting solved.
- Frank Benoit (3/29) Oct 25 2008 void foo( scope void delegate() dg ){
- Bill Baxter (9/32) Oct 25 2008 Hmm, this makes it pretty clear I don't know what I'm talking about
- KennyTM~ (4/32) Oct 25 2008 It should done on the calling site (the point where delegates are
- Bill Baxter (13/46) Oct 25 2008 The problem is this
- KennyTM~ (62/111) Oct 25 2008 OK. Then both the call site and the function need the "scope", since the...
- Yigal Chripun (35/120) Oct 25 2008 I probably don't understand something here, but why does the delegate
- Jarrett Billingsley (7/11) Oct 25 2008 It would start to become untenable, but I would imagine closures would
- Yigal Chripun (8/20) Oct 25 2008 OK, But my question is why do you need that separation in the first
- Jarrett Billingsley (16/36) Oct 25 2008 Did you see the thread on performance?
- Yigal Chripun (7/46) Oct 26 2008 I've read that thread and understand the benefits of D1 style delegates,
- KennyTM~ (49/76) Oct 27 2008 The scope solution is very good since whether the frame is allocated on
- KennyTM~ (19/52) Oct 25 2008 -- snip --
- Johan Granberg (5/56) Oct 25 2008 What about the case of storing a bunch of delegates in a list (thats ret...
- Robert Fraser (4/32) Oct 25 2008 What if heap delegates were implicitly castable to stack delegates? That...
- Steven Schveighoffer (37/60) Oct 26 2008 I think possibly, there cannot be enforcement of this.
It is great to hear that this issue is getting solved. How will be the now syntax? I wonder if the distinction between dynamic/static closure shall be done on the calling site, or the called site. void foo( void delegate() dg ){ } // -or- void foo2( void delegate() dg ){ } void bar(){ int i; foo({ i++; }); // -or- foo( scope { i++; }); } Because I think, the foo method/function signature has to define if the delegate is escaping or not. The caller might not know it. If the signature defines this, the compiler can check that and give more safety.
Oct 25 2008
Frank Benoit schrieb:It is great to hear that this issue is getting solved. How will be the new syntax? I wonder if the distinction between dynamic/static closure shall be done on the calling site, or the called site. void foo( void delegate() dg ){ } // -or-void foo( scope void delegate() dg ){ }void bar(){ int i; foo({ i++; }); // -or- foo( scope { i++; }); } Because I think, the foo method/function signature has to define if the delegate is escaping or not. The caller might not know it. If the signature defines this, the compiler can check that and give more safety.
Oct 25 2008
On Sat, Oct 25, 2008 at 5:49 PM, Frank Benoit <keinfarbton googlemail.com> wrote:It is great to hear that this issue is getting solved. How will be the now syntax? I wonder if the distinction between dynamic/static closure shall be done on the calling site, or the called site. void foo( void delegate() dg ){ } // -or- void foo2( void delegate() dg ){ } void bar(){ int i; foo({ i++; }); // -or- foo( scope { i++; }); } Because I think, the foo method/function signature has to define if the delegate is escaping or not. The caller might not know it. If the signature defines this, the compiler can check that and give more safety.Hmm, this makes it pretty clear I don't know what I'm talking about when it comes to the wheres and hows and whys of dynamic closure creation in D2. Sorry for the noise. (please forgive me superdan!) What I can say for sure, is that D1 has been working pretty well for me, so I just hope the solution doesn't involve a much more cumbersome syntax for things that used to work fine in D1. --bb
Oct 25 2008
Frank Benoit wrote:It is great to hear that this issue is getting solved. How will be the now syntax? I wonder if the distinction between dynamic/static closure shall be done on the calling site, or the called site. void foo( void delegate() dg ){ } // -or- void foo2( scope void delegate() dg ){ } void bar(){ int i; foo({ i++; }); // -or- foo( scope { i++; }); } Because I think, the foo method/function signature has to define if the delegate is escaping or not. The caller might not know it. If the signature defines this, the compiler can check that and give more safety.It should done on the calling site (the point where delegates are created). Why the called function need to know if the delegate is a closure or not? What they can do is just call the delegate.
Oct 25 2008
On Sat, Oct 25, 2008 at 6:30 PM, KennyTM~ <kennytm gmail.com> wrote:Frank Benoit wrote:The problem is this { float x = 3.14; bar( float delegate (float y){ return x*y ; } ); // this delegate need to be allocated? } If bar is going to hold onto the delegate beyond the end of the scope, then allocation is needed. The author of bar() may be in a better position to judge that than the caller of bar(). But I'm not sure if that's the only kind of situation to be concerned about. Are there other cases where only caller knows which it should be? --bbIt is great to hear that this issue is getting solved. How will be the now syntax? I wonder if the distinction between dynamic/static closure shall be done on the calling site, or the called site. void foo( void delegate() dg ){ } // -or- void foo2( scope void delegate() dg ){ } void bar(){ int i; foo({ i++; }); // -or- foo( scope { i++; }); } Because I think, the foo method/function signature has to define if the delegate is escaping or not. The caller might not know it. If the signature defines this, the compiler can check that and give more safety.It should done on the calling site (the point where delegates are created). Why the called function need to know if the delegate is a closure or not? What they can do is just call the delegate.
Oct 25 2008
Bill Baxter wrote:On Sat, Oct 25, 2008 at 6:30 PM, KennyTM~ <kennytm gmail.com> wrote:OK. Then both the call site and the function need the "scope", since the function signature alone is surely insufficient to determine if the delegate is closure or not. Since filling the parameter is just a cloning process, this can be reduced to asking if <delegate-type> f = g; will produce something weird, when one side is a closure, etc. The simple solution is to make the delegates on stack a super type of delegates on heap. (Don't mind the "static" keyword yet. I followed the name "static closure" described somewhere else. I agree "static" is too overloaded.) -------- alias float delegate(in float) FloatDelegate; // by default delegates are on heap for access safety. FloatDelegate g, sin_pi; // but I can declare it only needs to live on stack. // i.e. the parameters won't jump out of its scope. static(FloatDelegate) h; // require allocation on heap. (right? right?) void store (FloatDelegate f) { .g = f; } // don't require to allocation on heap float evaluate_at (static(FloatDelegate) f, in float x) { return f(x); } // don't just deal with parameters... the return values need to be considered as well. FloatDelegate square (FloatDelegate f) { return float delegate(in float x) { return f(x)*f(x); } } FloatDelegate plus_zero_of (static(FloatDelegate) f) { auto at_zero = f(0); return float delegate(in float x) { return x + at_zero; }; } static(FloatDelegate) get_zero_function () { return static float delegate(in float) { return 0; }; } { float pi = 3.1415926535f; // don't require allocation on heap. // I know this, therefore the static in front. auto cos_pi = static float delegate(in float y){ return cos(pi * y); }; auto tan_pi = static float delegate(in float y){ return tan(pi * y); }; // delegate on stack set to another delegate on stack: OK. writefln(evaluate_at(tan_pi, 0.2f)); // require allocation on heap sin_pi = float delegate(float y) { return sin(pi * y); } // Oops, cos_pi is on stack, but the func sig requires on heap. // should generate error. //store(cos_pi); } // sin_pi already on heap, although the func sig said can be on stack. // should _not_ generate error. writefln(evaluate_at(sin_pi, 0.4f)); // g can now stay on stack, although its type is a closure. g = get_zero_function(); // also ok. h = get_zero_function(); // this should fail. h = g;Frank Benoit wrote:The problem is this { float x = 3.14; bar( float delegate (float y){ return x*y ; } ); // this delegate need to be allocated? } If bar is going to hold onto the delegate beyond the end of the scope, then allocation is needed. The author of bar() may be in a better position to judge that than the caller of bar(). But I'm not sure if that's the only kind of situation to be concerned about. Are there other cases where only caller knows which it should be? --bbIt is great to hear that this issue is getting solved. How will be the now syntax? I wonder if the distinction between dynamic/static closure shall be done on the calling site, or the called site. void foo( void delegate() dg ){ } // -or- void foo2( scope void delegate() dg ){ } void bar(){ int i; foo({ i++; }); // -or- foo( scope { i++; }); } Because I think, the foo method/function signature has to define if the delegate is escaping or not. The caller might not know it. If the signature defines this, the compiler can check that and give more safety.It should done on the calling site (the point where delegates are created). Why the called function need to know if the delegate is a closure or not? What they can do is just call the delegate.
Oct 25 2008
I probably don't understand something here, but why does the delegate itself need to know anything about allocation? see my comments in the body of your message. KennyTM~ wrote:OK. Then both the call site and the function need the "scope", since the function signature alone is surely insufficient to determine if the delegate is closure or not. Since filling the parameter is just a cloning process, this can be reduced to asking if <delegate-type> f = g; will produce something weird, when one side is a closure, etc. The simple solution is to make the delegates on stack a super type of delegates on heap. (Don't mind the "static" keyword yet. I followed the name "static closure" described somewhere else. I agree "static" is too overloaded.) -------- alias float delegate(in float) FloatDelegate; // by default delegates are on heap for access safety. FloatDelegate g, sin_pi; // but I can declare it only needs to live on stack. // i.e. the parameters won't jump out of its scope. static(FloatDelegate) h;change the above to: scope FloatDelegate h; // note: *same* type for dg// require allocation on heap. (right? right?) void store (FloatDelegate f) { .g = f; }the above will heap allocate.// don't require to allocation on heap float evaluate_at (static(FloatDelegate) f, in float x) { return f(x); }float evaluate_at (FloatDelegate f, in float x) { return f(x); }// don't just deal with parameters... the return values need to be considered as well. FloatDelegate square (FloatDelegate f) { return float delegate(in float x) { return f(x)*f(x); } }ok. you'll get heap alloc.FloatDelegate plus_zero_of (static(FloatDelegate) f) { auto at_zero = f(0); return float delegate(in float x) { return x + at_zero; }; }FloatDelegate plus_zero_of (scope FloatDelegate f) { auto at_zero = f(0); return float delegate(in float x) { return x + at_zero; }; }static(FloatDelegate) get_zero_function () { return static float delegate(in float) { return 0; }; }the above seems wrong to me. it's the same as returning scope class instances..just define: FloatDelegate get_zero_function () { return float delegate(in float) { return 0; }; } and use: scope dg1 = get_zero_function(); // this'll prevent heap alloc{ float pi = 3.1415926535f; // don't require allocation on heap. // I know this, therefore the static in front. auto cos_pi = static float delegate(in float y){ return cos(pi * y); }; auto tan_pi = static float delegate(in float y){ return tan(pi * y); };rewrite as: auto cos_pi = scope float delegate(in float y){ return cos(pi * y); }; auto tan_pi = scope float delegate(in float y){ return tan(pi * y); };// delegate on stack set to another delegate on stack: OK. writefln(evaluate_at(tan_pi, 0.2f));doesn't matter what "kind" of delegate tan_pi is.// require allocation on heap sin_pi = float delegate(float y) { return sin(pi * y); } // Oops, cos_pi is on stack, but the func sig requires on heap. // should generate error. //store(cos_pi);either error because you assign a scope object to non-scope or perhaps better alternatives: a. you use: store(cos_pi.dup); // manually alloc on heap b. not an error. *compiler* automatically allocates a copy on heap.} // sin_pi already on heap, although the func sig said can be on stack. // should _not_ generate error. writefln(evaluate_at(sin_pi, 0.4f)); // g can now stay on stack, although its type is a closure. g = get_zero_function(); // also ok. h = get_zero_function(); // this should fail. h = g;anyway, I think you got my intention by now. I really don't want to have 3 kinds of function types. I think the already exsisting separation between function pointers and delegates could be handled better by adding an implicit cast.
Oct 25 2008
On Sat, Oct 25, 2008 at 9:30 AM, Yigal Chripun <yigal100 gmail.com> wrote:anyway, I think you got my intention by now. I really don't want to have 3 kinds of function types. I think the already exsisting separation between function pointers and delegates could be handled better by adding an implicit cast.It would start to become untenable, but I would imagine closures would be implicitly convertible to scope delegates, since in effect they are a subtype, and anywhere a scope delegate could be used, a closure could be used as well. Add to that implicit conversion from functions to delegates using thunks and bam, you could have a function take one type and it could accept all three.
Oct 25 2008
Jarrett Billingsley wrote:On Sat, Oct 25, 2008 at 9:30 AM, Yigal Chripun <yigal100 gmail.com> wrote:OK, But my question is why do you need that separation in the first place? to me it seems an unnecessary distinction between scope delegates and "regular" delegates. regarding functions and delegates - I must be missing something but why do you need a thunk? just allocate a delegate, assign its function pointer to the function and leave the "context" pointer null. isn't that enough?anyway, I think you got my intention by now. I really don't want to have 3 kinds of function types. I think the already existing separation between function pointers and delegates could be handled better by adding an implicit cast.It would start to become untenable, but I would imagine closures would be implicitly convertible to scope delegates, since in effect they are a subtype, and anywhere a scope delegate could be used, a closure could be used as well. Add to that implicit conversion from functions to delegates using thunks and bam, you could have a function take one type and it could accept all three.
Oct 25 2008
On Sat, Oct 25, 2008 at 5:08 PM, Yigal Chripun <yigal100 gmail.com> wrote:Jarrett Billingsley wrote:Did you see the thread on performance? D1's nested functions are great because they're very efficient. D2's "allocate everything on the heap just in case" is, in most cases, completely unnecessary, and performancewise it's terrible too. Yes, with a perfect compiler, it would be able to statically ensure that a delegate doesn't need to be allocated on the heap; but since we have separate compilation and use ancient object formats and linkers that don't understand such analysis, it can't be done.On Sat, Oct 25, 2008 at 9:30 AM, Yigal Chripun <yigal100 gmail.com> wrote:OK, But my question is why do you need that separation in the first place? to me it seems an unnecessary distinction between scope delegates and "regular" delegates.anyway, I think you got my intention by now. I really don't want to have 3 kinds of function types. I think the already existing separation between function pointers and delegates could be handled better by adding an implicit cast.It would start to become untenable, but I would imagine closures would be implicitly convertible to scope delegates, since in effect they are a subtype, and anywhere a scope delegate could be used, a closure could be used as well. Add to that implicit conversion from functions to delegates using thunks and bam, you could have a function take one type and it could accept all three.regarding functions and delegates - I must be missing something but why do you need a thunk? just allocate a delegate, assign its function pointer to the function and leave the "context" pointer null. isn't that enough?No. Functions and delegates have similar, but slightly different calling conventions. Translation from one to the other requires a bit of register/stack shuffling. Implicit conversion from function to delegate only requires a single statically allocated thunk for each set of parameter types; the delegate would have the thunk as the funcptr and the original function as the ptr. The thunk would then be able to shift the params around and call the real function.
Oct 25 2008
Jarrett Billingsley wrote:On Sat, Oct 25, 2008 at 5:08 PM, Yigal Chripun <yigal100 gmail.com> wrote:I've read that thread and understand the benefits of D1 style delegates, performance wise. What I meant to say was Why do we need two separate *types* for that? both styles can be used with one type and with a scope modifier as I replied to Kenny.Jarrett Billingsley wrote:Did you see the thread on performance?On Sat, Oct 25, 2008 at 9:30 AM, Yigal Chripun <yigal100 gmail.com> wrote:OK, But my question is why do you need that separation in the first place? to me it seems an unnecessary distinction between scope delegates and "regular" delegates.anyway, I think you got my intention by now. I really don't want to have 3 kinds of function types. I think the already existing separation between function pointers and delegates could be handled better by adding an implicit cast.It would start to become untenable, but I would imagine closures would be implicitly convertible to scope delegates, since in effect they are a subtype, and anywhere a scope delegate could be used, a closure could be used as well. Add to that implicit conversion from functions to delegates using thunks and bam, you could have a function take one type and it could accept all three.D1's nested functions are great because they're very efficient. D2's "allocate everything on the heap just in case" is, in most cases, completely unnecessary, and performancewise it's terrible too. Yes, with a perfect compiler, it would be able to statically ensure that a delegate doesn't need to be allocated on the heap; but since we have separate compilation and use ancient object formats and linkers that don't understand such analysis, it can't be done.I see. Thanks for explaining this.regarding functions and delegates - I must be missing something but why do you need a thunk? just allocate a delegate, assign its function pointer to the function and leave the "context" pointer null. isn't that enough?No. Functions and delegates have similar, but slightly different calling conventions. Translation from one to the other requires a bit of register/stack shuffling. Implicit conversion from function to delegate only requires a single statically allocated thunk for each set of parameter types; the delegate would have the thunk as the funcptr and the original function as the ptr. The thunk would then be able to shift the params around and call the real function.
Oct 26 2008
Yigal Chripun wrote:Jarrett Billingsley wrote:The scope solution is very good since whether the frame is allocated on heap or on stack is determined only at the point of declaring the DG. After the declaration a closure is no different from a delegate -- both has the same structure {ptr, funcptr} and even the calling convention is the same. The two are indistinguishable after declaration. But you'll need two types if you want to distinguish them. Such as to enforce a non-scope constraint: // f1's ptr can be on stack or on heap. void T1 (scope delegate f1) { ... } // f2's ptr must be on heap. void T2 (delegate f2) { ... } ... { ... scope f4 = delegate { ... }; // frame allocated on stack. ... { ... T2(f4); // T2: Hi, f4, is your .ptr on stack or on heap? // For safety, only (.ptr)s on heap are allowed. // f4: I... don't know. Just let me in? I'll behave. ... } } ... // later on an access violation/stack overflow appears mysteriously. Solutions I can think of :- (a) Use some algorithm to check if the .ptr is on heap or on stack (indeed the address for them are significantly different); (b) Add a field to indicate if f4 is allocated on stack or on heap; (c) The “scope” keyword for delegates do nothing, let the programmer take the risk (current behavior); (d) Automatically .dup the frame when a delegate is passed into an argument without “scope”. Problems for each solution :- (a) is probably not reliable (is it? can the GC help?), and DG & CL now behaves like 2 different types. (b) is effectively adding a new type. (c) is probably unsafe. (d) is slow. The move I'd take for now would be (c) if no new types are wanted, because it's the current behavior and if you want significant performance out of nowhere there should be some risk trade off. The most urgent issue it seems right now is to restore the performance of delegates to D1 level. If (a) can be done reliably and quickly than I vote for (a). There could be other solutions as well I don't know yet.On Sat, Oct 25, 2008 at 5:08 PM, Yigal Chripun <yigal100 gmail.com> wrote:I've read that thread and understand the benefits of D1 style delegates, performance wise. What I meant to say was Why do we need two separate *types* for that? both styles can be used with one type and with a scope modifier as I replied to Kenny.Jarrett Billingsley wrote:Did you see the thread on performance?On Sat, Oct 25, 2008 at 9:30 AM, Yigal Chripun <yigal100 gmail.com> wrote:OK, But my question is why do you need that separation in the first place? to me it seems an unnecessary distinction between scope delegates and "regular" delegates.anyway, I think you got my intention by now. I really don't want to have 3 kinds of function types. I think the already existing separation between function pointers and delegates could be handled better by adding an implicit cast.It would start to become untenable, but I would imagine closures would be implicitly convertible to scope delegates, since in effect they are a subtype, and anywhere a scope delegate could be used, a closure could be used as well. Add to that implicit conversion from functions to delegates using thunks and bam, you could have a function take one type and it could accept all three.-- snip --
Oct 27 2008
KennyTM~ wrote:Bill Baxter wrote:-- snip --OK. Then both the call site and the function need the "scope", since the function signature alone is surely insufficient to determine if the delegate is closure or not. Since filling the parameter is just a cloning process, this can be reduced to asking if <delegate-type> f = g; will produce something weird, when one side is a closure, etc. The simple solution is to make the delegates on stack a super type of delegates on heap. (Don't mind the "static" keyword yet. I followed the name "static closure" described somewhere else. I agree "static" is too overloaded.) ---------- snip --// Oops, cos_pi is on stack, but the func sig requires on heap. // should generate error. //store(cos_pi); } // sin_pi already on heap, although the func sig said can be on stack. // should _not_ generate error. writefln(evaluate_at(sin_pi, 0.4f));-- snip -- Oops I caught myself. According to this type system store(cos_pi) should be correct while evaluate_at(sin_pi, 0.4f) will generate an error. It's ridiculous. This can be fixed by switching the role of inheritance of delegates and closures, i.e. Delegate dg = Closure // Valid. Closure cl = Delegate // Invalid. Along with (let's get rid of all of them all together...): StaticDelegate dg = FuncPtr // Valid. Closure cl = FuncPtr // Valid. FuncPtr fp = Delegate // Invalid. FuncPtr fp = Closure // Invalid. i.e. FuncPtr ---safe-assign---> Closure ---safe-assign---> Delegate. (Delegate = Static closure, Closure = Dynamic closure) I'll post my reasoning later, if one finds it unclear...
Oct 25 2008
Bill Baxter wrote:On Sat, Oct 25, 2008 at 6:30 PM, KennyTM~ <kennytm gmail.com> wrote:What about the case of storing a bunch of delegates in a list (thats retaining them) that has a lifetime shorter than the function context? In this case it would be more efficient to store stack delegates even when some code keeps a reference. I don't believe that there is a non-problematic way to determine if a delegate has to be on the heap or not. I suggest that this decision is left to the programmer by making heap allocation the default and allowing the calling side to optimize by adding some keyword, for example scope. For the case of calling library code the programmers will have to rely on documentation.Frank Benoit wrote:The problem is this { float x = 3.14; bar( float delegate (float y){ return x*y ; } ); // this delegate need to be allocated? } If bar is going to hold onto the delegate beyond the end of the scope, then allocation is needed. The author of bar() may be in a better position to judge that than the caller of bar(). But I'm not sure if that's the only kind of situation to be concerned about. Are there other cases where only caller knows which it should be? --bbIt is great to hear that this issue is getting solved. How will be the now syntax? I wonder if the distinction between dynamic/static closure shall be done on the calling site, or the called site. void foo( void delegate() dg ){ } // -or- void foo2( scope void delegate() dg ){ } void bar(){ int i; foo({ i++; }); // -or- foo( scope { i++; }); } Because I think, the foo method/function signature has to define if the delegate is escaping or not. The caller might not know it. If the signature defines this, the compiler can check that and give more safety.It should done on the calling site (the point where delegates are created). Why the called function need to know if the delegate is a closure or not? What they can do is just call the delegate.
Oct 25 2008
Frank Benoit wrote:It is great to hear that this issue is getting solved. How will be the now syntax? I wonder if the distinction between dynamic/static closure shall be done on the calling site, or the called site. void foo( void delegate() dg ){ } // -or- void foo2( void delegate() dg ){ } void bar(){ int i; foo({ i++; }); // -or- foo( scope { i++; }); } Because I think, the foo method/function signature has to define if the delegate is escaping or not. The caller might not know it. If the signature defines this, the compiler can check that and give more safety.What if heap delegates were implicitly castable to stack delegates? That way, things that needed heap delegates could demand them, and things that didn't care would be OK either way?
Oct 25 2008
"Frank Benoit" wroteIt is great to hear that this issue is getting solved. How will be the now syntax? I wonder if the distinction between dynamic/static closure shall be done on the calling site, or the called site. void foo( void delegate() dg ){ } // -or- void foo2( void delegate() dg ){ } void bar(){ int i; foo({ i++; }); // -or- foo( scope { i++; }); } Because I think, the foo method/function signature has to define if the delegate is escaping or not. The caller might not know it. If the signature defines this, the compiler can check that and give more safety.I think possibly, there cannot be enforcement of this. Here is a case, where the constructor needs to be able to take both types (scope and heap), and the compiler will not be able to which to use without semantic analysis. class DelegateCaller { private delegate int _foo(); this(int delegate() foo) { _foo = foo; } int callit() { return _foo();} } int f1() { int x() { return 5; } auto dc = new DelegateCaller(&x); // should allocate on stack return dc.callit() * dc.callit(); } DelegateCaller f2() { int x() { return 5;} auto dc = new DelegateCaller(&x); // allocate on heap return dc; } So the author of DelegateCaller cannot require a heap delegate, even though it's possible the act of creating a DelegateCaller can cause an escape. Note that the first two lines of each function are identical, so the compiler needs to do a semantic analysis of whether the use of a delegate requires a heap allocation. It would be even more sticky with multiple function calls, or functions that call eachother. The only thing I can think of is to leave it up to the caller to decide whether he thinks the delegate should be heap-allocated, and the author of the function should document how it will use the delegate. Fortunately, this is not a huge deal, as most of the time a developer expects a stack-allocated delegate (scope). So make scope the default and allow some syntax to let the author define that a function call warrants allocating the frame on the heap. -Steve
Oct 26 2008