digitalmars.D - Feature Request: Change the delegate type to void[] data instead
- downs (9/15) Oct 03 2007 This could be made backwards compatible by the introduction of a void
- Gregor Richards (3/3) Oct 03 2007 Downside #2: Calculating that range requires at best n pointer lookups
- downs (5/9) Oct 03 2007 I believe that most delegates have a depth of one, so this isn't much of
- Gregor Richards (8/18) Oct 03 2007 The delegate is created as soon as you do &foo. After that point,
- downs (5/26) Oct 03 2007 Damn, you're right. How much of a problem is this?
- Rioshin an'Harthen (13/19) Oct 04 2007 I have a small proposal for the syntax of delegates storing the extra
- BCS (8/14) Oct 03 2007 how will that more stack data into ram? If the delegate gets an array of...
- downs (13/34) Oct 03 2007 You misunderstand.
- BCS (30/66) Oct 04 2007 |class Bar { int opApply( int delegate(inout int) dg) {...} }
- Daniel Keep (34/53) Oct 04 2007 What about this? Leave delegates as they are, but introduce a new
- Christopher Wright (2/26) Oct 04 2007 I like it. Now just fix bug 854 and you're all set.
- Daniel Keep (4/31) Oct 04 2007 I think 854 is something of a side-concern, really. :3
This could be made backwards compatible by the introduction of a void *ptr() { return data.ptr; } If the delegate is of a method, data references the object's data. If the delegate is an inline function or nested function, data references *the stack space in all the superior functions that it uses*. Disadvantages: Delegates take size_t.sizeof bytes more space in RAM. Advantages: I'll let the following function speak for itself.R delegate(T) close(R, T...)(R delegate(T) dg) { typeof(dg) res=dg; res.data=res.data.dup; // eventually moves area of stack into RAM return res; }What do you think? --downs
Oct 03 2007
where n is the depth of the delegate. Creating delegates right now is free. - Gregor Richards
Oct 03 2007
Gregor Richards wrote:where n is the depth of the delegate. Creating delegates right now is free. - Gregor RichardsI believe that most delegates have a depth of one, so this isn't much of a problem. Alternatively, just set the array pointer to null initially and have the compiler fill it in when requested. --downs
Oct 03 2007
downs wrote:Gregor Richards wrote:The delegate is created as soon as you do &foo. After that point, whatever receives &foo doesn't know anything about it, so it doesn't know how many pointers it would have to jump through to get the right depth. So, you'd need to either accept that creating delegates incurs n cost (something that's fine by me since n is actually a constant), or have a special syntax for delegates which have the extra information. - Gregor Richardswhere n is the depth of the delegate. Creating delegates right now is free. - Gregor RichardsI believe that most delegates have a depth of one, so this isn't much of a problem. Alternatively, just set the array pointer to null initially and have the compiler fill it in when requested. --downs
Oct 03 2007
Gregor Richards wrote:downs wrote:Damn, you're right. How much of a problem is this? Note that the cost only occurs if the delegate actually uses outside variables. --downsGregor Richards wrote:The delegate is created as soon as you do &foo. After that point, whatever receives &foo doesn't know anything about it, so it doesn't know how many pointers it would have to jump through to get the right depth. So, you'd need to either accept that creating delegates incurs n cost (something that's fine by me since n is actually a constant), or have a special syntax for delegates which have the extra information. - Gregor Richardswhere n is the depth of the delegate. Creating delegates right now is free. - Gregor RichardsI believe that most delegates have a depth of one, so this isn't much of a problem. Alternatively, just set the array pointer to null initially and have the compiler fill it in when requested. --downs
Oct 03 2007
"Gregor Richards" <Richards codu.org> kirjoitti viestissä news:fe1t1b$kvn$1 digitalmars.com...The delegate is created as soon as you do &foo. After that point, whatever receives &foo doesn't know anything about it, so it doesn't know how many pointers it would have to jump through to get the right depth. So, you'd need to either accept that creating delegates incurs n cost (something that's fine by me since n is actually a constant), or have a special syntax for delegates which have the extra information.I have a small proposal for the syntax of delegates storing the extra information. Currently we define a delegate with something akin to R delegate(T) dg = &foo; Now, we want a syntax for delegates storing the stack context for the superior functions. According to the quote above, this looks like a case of needing extra dereferencing when used, so should be visible in declaring the delegate and assigning to it. Thus, I came up with the following: R delegate(T) &dg = &&foo; where the extra ampersands - kind of like in C(++) - show that there's another step or multiple steps to (de)reference.
Oct 04 2007
Reply to Downs,how will that more stack data into ram? If the delegate gets an array of pointers to stack frames (or objects) then what you get is a copy of the pointers that still point into the stack (or objects). If what you have is a slice of the stack, then what about the next function out? you will either still have a reference to another stack frame, or won't have access to it at all. You might get something with a void[][], but then you need to do an alloc on each delegate creation, not to mention Gregor's issue.R delegate(T) close(R, T...)(R delegate(T) dg) { typeof(dg) res=dg; res.data=res.data.dup; // eventually moves area of stack into RAM return res; }
Oct 03 2007
BCS wrote:Reply to Downs,You misunderstand. void[] is not an array of void pointers, but a continuous segment of memory; that being, all the stack the method does access.how will that more stack data into ram? If the delegate gets an array of pointers to stack frames (or objects) then what you get is a copy of the pointers that still point into the stack (or objects).R delegate(T) close(R, T...)(R delegate(T) dg) { typeof(dg) res=dg; res.data=res.data.dup; // eventually moves area of stack into RAM return res; }If what you have is a slice of the stack, then what about the next function out? you will either still have a reference to another stack frame, or won't have access to it at all.Not true. The compiler can determine at compile-time which stack frames the function does access. It can then use that data to calculate the pointer (as an offset to the SP) and length of the array.You might get something with a void[][], but then you need to do an alloc on each delegate creation, not to mention Gregor's issue.Yeah, see above. A void[] is sufficient. And Gregor's issue is more of a caveat - I do not believe the cost overhead of delegate _creation_ to be a significant problem. Thanks for your feedback :) --downs
Oct 03 2007
Reply to Downs,BCS wrote:|class Bar { int opApply( int delegate(inout int) dg) {...} } | |void Fun(Bar b) | { | int i; | foreach(inout j ; b) | { | i+=j; | h=0 | int nest(inout int k) | { | i+=k; | h-=k; | return 0; | } | b.opApply(&nest) | } |} There are three nested function here. Fun, the foreach body and the nest function when the nest function is turned into a delegate in the opApply function, the stack looks like this Fun Bar.opApply <<<<< Fun.foreachBody_1 potentially, I could add code later that would put an even bigger (or smaller) chunk of data between the Fun stack frame and the foreach body stack frame. In general this can't be detected at compile time because I could do this by deriving a class from Bar.Reply to Downs,You misunderstand. void[] is not an array of void pointers, but a continuous segment of memory; that being, all the stack the method does access.how will that more stack data into ram? If the delegate gets an array of pointers to stack frames (or objects) then what you get is a copy of the pointers that still point into the stack (or objects).R delegate(T) close(R, T...)(R delegate(T) dg) { typeof(dg) res=dg; res.data=res.data.dup; // eventually moves area of stack into RAM return res; }If what you have is a slice of the stack, then what about the next function out? you will either still have a reference to another stack frame, or won't have access to it at all.Not true. The compiler can determine at compile-time which stack frames the function does access. It can then use that data to calculate the pointer (as an offset to the SP) and length of the array.You might get something with a void[][], but then you need to do an alloc on each delegate creation, not to mention Gregor's issue.Yeah, see above. A void[] is sufficient. And Gregor's issue is more of a caveat - I do not believe the cost overhead of delegate _creation_ to be a significant problem. Thanks for your feedback :) --downs
Oct 04 2007
BCS wrote:Reply to Downs,Again, you're missing something. The void[] data property I'm proposing does not contain the complete stack of the delegate. Since part of that stack (the delegate's) doesn't even EXIST when the delegate is created, this would be rather pointless. Instead, data references the stack area _outside_ of the delegate that the delegate uses. So in this case, that would be Fun, directly followed by its foreach body; two parts of the stack which indeed form a continuous area. I hope that clears things up. --downsBCS wrote:|class Bar { int opApply( int delegate(inout int) dg) {...} } | |void Fun(Bar b) | { | int i; | foreach(inout j ; b) | { | i+=j; | h=0 | int nest(inout int k) | { | i+=k; | h-=k; | return 0; | } | b.opApply(&nest) | } |} There are three nested function here. Fun, the foreach body and the nest function when the nest function is turned into a delegate in the opApply function, the stack looks like this Fun Bar.opApply <<<<< Fun.foreachBody_1 potentially, I could add code later that would put an even bigger (or smaller) chunk of data between the Fun stack frame and the foreach body stack frame. In general this can't be detected at compile time because I could do this by deriving a class from Bar.Reply to Downs,You misunderstand. void[] is not an array of void pointers, but a continuous segment of memory; that being, all the stack the method does access.how will that more stack data into ram? If the delegate gets an array of pointers to stack frames (or objects) then what you get is a copy of the pointers that still point into the stack (or objects).R delegate(T) close(R, T...)(R delegate(T) dg) { typeof(dg) res=dg; res.data=res.data.dup; // eventually moves area of stack into RAM return res; }If what you have is a slice of the stack, then what about the next function out? you will either still have a reference to another stack frame, or won't have access to it at all.Not true. The compiler can determine at compile-time which stack frames the function does access. It can then use that data to calculate the pointer (as an offset to the SP) and length of the array.You might get something with a void[][], but then you need to do an alloc on each delegate creation, not to mention Gregor's issue.Yeah, see above. A void[] is sufficient. And Gregor's issue is more of a caveat - I do not believe the cost overhead of delegate _creation_ to be a significant problem. Thanks for your feedback :) --downs
Oct 04 2007
Reply to Downs,BCS wrote:In between the Fun frame and the foreach frame is the opApply frame (or several frames). If you keep nesting function and passing delegate to arbitrary code to be called deeper in the stack, sooner or later, you get a gap of unknown size.Fun Bar.opApply <<<<< Fun.foreachBody_1Again, you're missing something. The void[] data property I'm proposing does not contain the complete stack of the delegate. Since part of that stack (the delegate's) doesn't even EXIST when the delegate is created, this would be rather pointless. Instead, data references the stack area _outside_ of the delegate that the delegate uses. So in this case, that would be Fun, directly followed by its foreach body; two parts of the stack which indeed form a continuous area. I hope that clears things up. --downs
Oct 04 2007
BCS wrote:Reply to Downs,That is correct. And since the void[] data and the immediate stack space of the function don't need to have any direct relationship, it isn't a problem. The stack space of the function is accessed via the SP or an equivalent register. The outer stack area is accessed via (currently) void *ptr or (proposed) void[] data. The two are separate areas. I don't see the problem. --downsBCS wrote:In between the Fun frame and the foreach frame is the opApply frame (or several frames). If you keep nesting function and passing delegate to arbitrary code to be called deeper in the stack, sooner or later, you get a gap of unknown size.Fun Bar.opApply <<<<< Fun.foreachBody_1Again, you're missing something. The void[] data property I'm proposing does not contain the complete stack of the delegate. Since part of that stack (the delegate's) doesn't even EXIST when the delegate is created, this would be rather pointless. Instead, data references the stack area _outside_ of the delegate that the delegate uses. So in this case, that would be Fun, directly followed by its foreach body; two parts of the stack which indeed form a continuous area. I hope that clears things up. --downs
Oct 04 2007
Reply to Downs,BCS wrote:but from within the delegate the delegates frame is accessed with the SP, the foreach body's frame is accessed with the void *ptr or void[] data. But what is the outermost frame accessed with? Currently I think it is accessed with chained indirection (look in my outer function for his outer function's pointer, then use that pointer to find the next one etc.). This would still work with your proposal, but it gets all the issues of deep vs. shallow copies, which kinda defeats the point of the proposal. If you don't see my point for this case, consider this; the accusable scope of a nested function can contain an arbitrary number of non adjacent stack frames simply by passing callbacks to other function where the callback does the same. This can be done to as many levels as you wish.Reply to Downs,That is correct. And since the void[] data and the immediate stack space of the function don't need to have any direct relationship, it isn't a problem. The stack space of the function is accessed via the SP or an equivalent register. The outer stack area is accessed via (currently) void *ptr or (proposed) void[] data. The two are separate areas. I don't see the problem. --downsBCS wrote:In between the Fun frame and the foreach frame is the opApply frame (or several frames). If you keep nesting function and passing delegate to arbitrary code to be called deeper in the stack, sooner or later, you get a gap of unknown size.Fun Bar.opApply <<<<< Fun.foreachBody_1Again, you're missing something. The void[] data property I'm proposing does not contain the complete stack of the delegate. Since part of that stack (the delegate's) doesn't even EXIST when the delegate is created, this would be rather pointless. Instead, data references the stack area _outside_ of the delegate that the delegate uses. So in this case, that would be Fun, directly followed by its foreach body; two parts of the stack which indeed form a continuous area. I hope that clears things up. --downs
Oct 04 2007
BCS wrote:Reply to Downs,Yes. The idea is, since chained indirection only accesses consecutive stack frames anyway, void[] data contains _all_ the *outer* stack frames which the function *does* access, information which is definitely known at compile time. Since their size is also known, we can do away with chained indirection as well and simply use a fixed index in data.BCS wrote:but from within the delegate the delegates frame is accessed with the SP, the foreach body's frame is accessed with the void *ptr or void[] data. But what is the outermost frame accessed with? Currently I think it is accessed with chained indirection (look in my outer function for his outer function's pointer, then use that pointer to find the next one etc.). This would still work with your proposal, but it gets all the issues of deep vs. shallow copies, which kinda defeats the point of the proposal.Reply to Downs,That is correct. And since the void[] data and the immediate stack space of the function don't need to have any direct relationship, it isn't a problem. The stack space of the function is accessed via the SP or an equivalent register. The outer stack area is accessed via (currently) void *ptr or (proposed) void[] data. The two are separate areas. I don't see the problem. --downsBCS wrote:In between the Fun frame and the foreach frame is the opApply frame (or several frames). If you keep nesting function and passing delegate to arbitrary code to be called deeper in the stack, sooner or later, you get a gap of unknown size.Fun Bar.opApply <<<<< Fun.foreachBody_1Again, you're missing something. The void[] data property I'm proposing does not contain the complete stack of the delegate. Since part of that stack (the delegate's) doesn't even EXIST when the delegate is created, this would be rather pointless. Instead, data references the stack area _outside_ of the delegate that the delegate uses. So in this case, that would be Fun, directly followed by its foreach body; two parts of the stack which indeed form a continuous area. I hope that clears things up. --downsIf you don't see my point for this case, consider this; the accessable (sp.) scope of a nested function can contain an arbitrary number of non adjacent stack frames simply by passing callbacks to other function where the callback does the same.This is simply false. The stack space of other passed-in functions is not directly accessible from the current function. Thus, it is entirely irrelevant to this debate. How to access this space is the problem of the passed-in functions alone. --downs
Oct 04 2007
Reply to Downs,Yes. The idea is, since chained indirection only accesses consecutive stack frames anyway, void[] data contains _all_ the *outer* stack framesmy assertion is that, in general, the outer stack frames are *NOT consecutive* consider this code: |Foo(void delegate() dg) |{ | dg(); |} | |void Bar() |{ | int i; | Foo({ | int j; | Foo({ | int k; | Foo({ | int l; | Foo({ | int m; | Foo({ | int n = 1; | Foo({ | i += j += k += l += m += n; | }); | }); | }); | }); | }); | }); |} when the most nested delegate gets called it accesses six non-consecutive stack frames (Bar calls Foo calls delegate calls Foo ..., each delegate stack frame is seperated by a Foo stack frame) If the code for Foo is not known at compile time then their is no way to know where the outer stack frames are short of looking through a chain of pointers. To better illustrate this consider where Foo is this: |Foo(void delegate() dg) |{ | dg(); | int | dg(); |} with the second call in Foo, the offset of the called stack frame (the next more nested delegate) to the calling stack frame (the next delegate out) is different than for the first call. Without knowing which time you are being called you can't find the outer stack frames without a pointer chain to them.The stack space of other passed-in functions is not directly accessible from the current function.I don't follow that. It looks like I must have been unclear with what I was saying. The above section reiterates what I was trying to say.--downs
Oct 04 2007
Okay, I see your point. You are right. Thanks. Damnit. --downs
Oct 04 2007
Reply to Downs,Okay, I see your point. You are right. Thanks. Damnit. --downsDon't feel to bad, I was having a hard time keeping it all straight in my head.
Oct 04 2007
downs wrote:This could be made backwards compatible by the introduction of a void *ptr() { return data.ptr; } If the delegate is of a method, data references the object's data. If the delegate is an inline function or nested function, data references *the stack space in all the superior functions that it uses*. Disadvantages: Delegates take size_t.sizeof bytes more space in RAM. Advantages: I'll let the following function speak for itself.What about this? Leave delegates as they are, but introduce a new closure class of types. In effect, these would be: struct closure(returnT, argT...) { void* func; void[] data; returnT delegate(argT) opImplicitCast() { returnT delegate(argT) result; result.func = this.func; result.ptr = this.data.ptr; return result; } typeof(*this) dup() { typeof(*this) result; result.func = this.func; result.data = this.data.dup; return result; } } And appropriate opCall overloads, yadda yadda. Now, instead of delegate literals, you have *closure* literals, and taking the address of a non-globally scoped function will result in a closure. The implicit cast allows closures to be transparently used as delegates instead, so all existing code that deals with delegates explicitly should continue to work. I think the important thing is that it's quite likely the *only* piece of code that cares about the full slice of the stack is the code where the delegate was created; other things likely just want to call the delegate, so they probably don't care *how* it works, so long as it does. Plus, this means we get to keep our 8-byte delegates. :) -- DanielR delegate(T) close(R, T...)(R delegate(T) dg) { typeof(dg) res=dg; res.data=res.data.dup; // eventually moves area of stack into RAM return res; }What do you think? --downs
Oct 04 2007
Daniel Keep wrote:What about this? Leave delegates as they are, but introduce a new closure class of types. In effect, these would be: struct closure(returnT, argT...) { void* func; void[] data; returnT delegate(argT) opImplicitCast() { returnT delegate(argT) result; result.func = this.func; result.ptr = this.data.ptr; return result; } typeof(*this) dup() { typeof(*this) result; result.func = this.func; result.data = this.data.dup; return result; } }I like it. Now just fix bug 854 and you're all set.
Oct 04 2007
Christopher Wright wrote:Daniel Keep wrote:I think 854 is something of a side-concern, really. :3 Incidentally, I'd completely forgotten about that one... -- DanielWhat about this? Leave delegates as they are, but introduce a new closure class of types. In effect, these would be: struct closure(returnT, argT...) { void* func; void[] data; returnT delegate(argT) opImplicitCast() { returnT delegate(argT) result; result.func = this.func; result.ptr = this.data.ptr; return result; } typeof(*this) dup() { typeof(*this) result; result.func = this.func; result.data = this.data.dup; return result; } }I like it. Now just fix bug 854 and you're all set.
Oct 04 2007