If an uncaught exception escapes from the entry-function, std::terminate is called.
Hello,
I have some questions about call/cc in proposed implementation
I think possibility put any arguments in continuation and access to them through template function it is lack of type safety, and isn't good idea to propose it in standard.
Why not specialize continuation with type of input and type of output?
And forward arguments of operator() to constructor of input type.
I rewrote continuation based on boost.context implementation.
Also I get rid of data_available/get_data... why not use optional?
What do you think about it?
https://gist.github.com/cNoNim/bb24a613406de30d2c856bc7df1ab424
Also I don't understand why:If an uncaught exception escapes from the entry-function, std::terminate is called.
We can catch exception in `run` function of `record` and rethrow in `context_exit`
On Tuesday, February 28, 2017 at 9:24:38 PM UTC-5, Oleg Ageev wrote:Hello,
I have some questions about call/cc in proposed implementation
I think possibility put any arguments in continuation and access to them through template function it is lack of type safety, and isn't good idea to propose it in standard.
Why not specialize continuation with type of input and type of output?
Because:
1) That would limit what you can do with continuations. As currently proposed, you can pass different parameters to different parts of continuations. Your idea fixes the interface at call/cc time.
2) P0534 is intended to be as low level as possible while still being functional. By specializing types, it would be higher level than strictly necessary.
I think this API can be used even with pure C.
And we can build anything ontop. call/cc can have some unwanted overhead. I'm sure that fcontext_t-related functions are the main values of Boost.Context library.
2017-03-01 8:48 GMT+01:00 Victor Dyachenko <victor.d...@gmail.com>:I think this API can be used even with pure C.because it's proposed for C++ - class std::continuation manages the execution context (the stack; one shot-continuation).
And we can build anything ontop. call/cc can have some unwanted overhead. I'm sure that fcontext_t-related functions are the main values of Boost.Context library.callcc() and continuation::operator() are only thin wrapper (only one- or two-lines of code)
1) That would limit what you can do with continuations. As currently proposed, you can pass different parameters to different parts of continuations. Your idea fixes the interface at call/cc time.
2) P0534 is intended to be as low level as possible while still being functional. By specializing types, it would be higher level than strictly necessary.
Use `optional` to do what? To take up more space? To force me to copy values when I don't have to?
For instance during the implementation of my coroutine and fiber libraries I encountered that initialization- and termination-phases were required. In those phases no data at all or data of different types are transferred.Imagine the type of the transferred data does not have an default-ctor - you have to pass an value each time the continuation is resumed. What if you can't produce one or you want to terminate the continuation.Of course you could use something like variant or optional, but you would pay each time for the overhead even in cases were you don't need variant/optional etc.
The switch mechanism proposed in P0534 is symmetric, e.g you have to explicitly specify the continuation that has to resumed next.To which continuation do you want to jump to in context_exit?
ontop_fcontext( t.fctx, rec, context_exit< Rec >);
2) P0534 is intended to be as low level as possible while still being functional. By specializing types, it would be higher level than strictly necessary.
low level API it's fcontext_t and several functions to context switching, continuation it's high level wrapper
On Wednesday, March 1, 2017 at 10:26:57 AM UTC+3, Oliver Kowalke wrote:For instance during the implementation of my coroutine and fiber libraries I encountered that initialization- and termination-phases were required. In those phases no data at all or data of different types are transferred.Imagine the type of the transferred data does not have an default-ctor - you have to pass an value each time the continuation is resumed. What if you can't produce one or you want to terminate the continuation.Of course you could use something like variant or optional, but you would pay each time for the overhead even in cases were you don't need variant/optional etc.also you can use custom type for wrap it and reduce overhead, or hide internals from use
The switch mechanism proposed in P0534 is symmetric, e.g you have to explicitly specify the continuation that has to resumed next.To which continuation do you want to jump to in context_exit?If I get you right `context_exit` executed ontop of caller context because in `context_entry` we have:and if we catch exception in continuation, we can rethrow it to caller context in context_exit
ontop_fcontext( t.fctx, rec, context_exit< Rec >);
or I don't understand some use cases?
if you throw an exception in the middle of your continuation and it it escapes the top-level continuation you have no guaranttee that the calling continuation is still valid (might resumed and termianted already)
BOOST_ASSERT( nullptr != t.fctx);
Transfer run(Transfer t)
{
Ctx from{ t };
auto recovery = from.t.fctx;
try
{
Ctx cc = std::invoke(f, std::move(from));
return { std::exchange(cc.t.fctx, nullptr), nullptr };
}
catch (ForcedUnwind const&)
{ throw; }
catch (...)
{
except = std::current_exception();
return { recovery, nullptr };
}
}
On Tuesday, February 28, 2017 at 9:24:38 PM UTC-5, Oleg Ageev wrote:Hello,
I have some questions about call/cc in proposed implementation
I think possibility put any arguments in continuation and access to them through template function it is lack of type safety, and isn't good idea to propose it in standard.
Why not specialize continuation with type of input and type of output?
Because:
1) That would limit what you can do with continuations. As currently proposed, you can pass different parameters to different parts of continuations. Your idea fixes the interface at call/cc time.
2) P0534 is intended to be as low level as possible while still being functional. By specializing types, it would be higher level than strictly necessary.
you can't use recovery after calling std::invloke() because during the execution of f the context associated with f.ctx/recovery might have been resumed (so recovery points somewhere in the stackframe) or the context was terminated before the exception is thrown out of f.
Transfer run(Transfer t)
{
Ctx from{ t };
try
{
Ctx cc = std::invoke(f, std::move(from));
return { std::exchange(cc.t.fctx, nullptr), nullptr };
}
catch (ForcedUnwind const&)
{ throw; }
catch (...)
{
except = std::current_exception();
return { std::exchange(from.t.fctx, nullptr), nullptr };
}
}
std::exception_ptr eptr;
try
{
auto C = Continuation<void, void>::callcc([](auto && yield)
{
int counter = 0;
do
{
if (counter == 10)
throw std::bad_function_call();
counter++;
} while (yield = yield());
return std::move(yield);
});
auto B = Continuation<void, int>::callcc([&C](auto && yield)
{
int ret = 0;
do
{
if (!(C = C())) break;
} while (yield = yield(ret++));
return std::move(yield);
});
int ent = 0;
while (B = B())
{
auto ret = B.get();
if (ret >= 100) break;
}
std::cout << "main: done" << std::endl;
std::cin.ignore();
}
catch (...)
{ eptr = std::current_exception(); }
std::exception_ptr eptr;
try
{
auto C = Continuation<void, void>::callcc([](auto && yield)
{
int counter = 0;
do
{
counter++;
} while (yield = yield());
return std::move(yield);
});
auto B = Continuation<void, int>::callcc([&C](auto && yield)
{
struct BAD { ~BAD() { throw std::bad_function_call(); } };
BAD bad;
int ret = 0;
do
{
if (!(C = C())) break;
} while (yield = yield(ret++));
return std::move(yield);
});
int ent = 0;
while (B = B())
{
auto ret = B.get();
if (ret >= 100) break;
}
std::cout << "main: done" << std::endl;
}
catch (...)
{
eptr = std::current_exception();
}
yes exception leaked if thrown by destructor
you have the same issue if the lambda moves the continuation to a local variable, takes it by value instead of reference, or pass it to another function by value, etc....
warning C4297: 'main::<lambda_47e6b9d8c7d076365105bb6276e58c85>::()::BAD::~BAD': function assumed not to throw an exception but does
note: destructor or deallocator has a (possibly implicit) non-throwing exception specification
--To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/a880928c-208a-4f30-8967-bf29fac87bf2%40isocpp.org.
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/F-8aMUBPMzc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
std::exception_ptr eptr;
try
{
auto C = Continuation<void, void>::callcc([](auto && yield)
{
int counter = 0;
do
{
if (counter == 10) throw std::bad_function_call();
counter++;
} while (yield = yield());
return std::move(yield);
});
auto B = Continuation<void, int>::callcc([C=std::move(C)](auto && yield) mutable
{
int ret = 0;
do
{
if (!(C = C())) break;
} while (yield = yield(ret++));
return std::move(yield);
});
int ent = 0;
while (B = B())
{
auto ret = B.get();
if (ret >= 100) break;
}
std::cout << "main: done" << std::endl;
}
catch (...)
{
eptr = std::current_exception();
}
--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/F-8aMUBPMzc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/b420b720-71e2-48ca-8a0f-288c1d76e607%40isocpp.org.
Transfer run(Transfer t)
{
Ctx from{ t };
auto recovery = from.t.fctx;
try
{
Ctx cc = std::invoke(f, std::move(from));
return { std::exchange(cc.t.fctx, nullptr), nullptr };
}
catch (ForcedUnwind const&)
{ throw; }
catch (...)
{
except = std::current_exception();
return { recovery, nullptr };
}
}
no matter what the code will not work - you have no guarantee that
'recovery' is still valid in the catch clause
as I explained the stack 'recovery' is pointing to might be invalidated during execution of 'f'
On Wednesday, March 1, 2017 at 3:01:25 AM UTC, Nicol Bolas wrote:
On Tuesday, February 28, 2017 at 9:24:38 PM UTC-5, Oleg Ageev wrote:Hello,
I have some questions about call/cc in proposed implementation
I think possibility put any arguments in continuation and access to them through template function it is lack of type safety, and isn't good idea to propose it in standard.
Why not specialize continuation with type of input and type of output?
Because:
1) That would limit what you can do with continuations. As currently proposed, you can pass different parameters to different parts of continuations. Your idea fixes the interface at call/cc time.
2) P0534 is intended to be as low level as possible while still being functional. By specializing types, it would be higher level than strictly necessary.
FWIW, I strongly disagree. Getting continuation correctly typed is important and any help the compiler can give when using such a mind-bending construct as a continuation is very welcome. In particular getting the typing of something like invoke_on_top is non trivial (it certainly took me a few tries).
So we need the safe mechanism, and we need it shipped with the system. But that should not be the only way; the lower-level form should still exist.
Now, maybe the type-safe version should be the default, so that if you want the lower-level, unsafe API, we can clearly see that safety is being sacrificed. So `callcc/continuation` would naturally be type-safe, with `unsafe_callcc/unsafe_continuation` being not type safe.
no matter what the code will not work - you have no guarantee that
'recovery' is still valid in the catch clause
as I explained the stack 'recovery' is pointing to might be invalidated during execution of 'f'
On Wednesday, March 1, 2017 at 3:38:13 PM UTC, Nicol Bolas wrote:
[...]So we need the safe mechanism, and we need it shipped with the system. But that should not be the only way; the lower-level form should still exist.
Now, maybe the type-safe version should be the default, so that if you want the lower-level, unsafe API, we can clearly see that safety is being sacrificed. So `callcc/continuation` would naturally be type-safe, with `unsafe_callcc/unsafe_continuation` being not type safe.
we are in agreement then, I just want to point out that, given a type safe std::continuation<T> implementation, the unsafe variant would simply be std::continuation<void*>
2017-03-01 16:48 GMT+01:00 Giovanni Piero Deretta <gpderetta@gmail.com>:On Wednesday, March 1, 2017 at 3:38:13 PM UTC, Nicol Bolas wrote:
[...]So we need the safe mechanism, and we need it shipped with the system. But that should not be the only way; the lower-level form should still exist.
Now, maybe the type-safe version should be the default, so that if you want the lower-level, unsafe API, we can clearly see that safety is being sacrificed. So `callcc/continuation` would naturally be type-safe, with `unsafe_callcc/unsafe_continuation` being not type safe.
we are in agreement then, I just want to point out that, given a type safe std::continuation<T> implementation, the unsafe variant would simply be std::continuation<void*>and this would require std::continuation<void> too - so we end up in three specialisations of std::continuation<>
I prefer a template-less std::continuation - it's more flexible.
// Example:class MyClass {void fun(std::continuation&& cont);};MyClass myObj;callcc(&MyClass::fun, myObj); // This is what you want to write, but callcc does not take method pointers.// So you may try this:callcc(std::function<void(MyClass&, std::continuation&&)>(&MyClass::fun), myObj); // Mismatched types as callcc gets the order wrong.// Instead you have to conjure up a lambda:callcc([&](std::continuation&& cont) { myObj.fun(std::move(cont); });
Hi.I do really like the general idea of callcc and continuation class. But I don't understand the need of data transfer at this low abstraction level.
After all, these are not threads, so there is no asynchronism to think about. To prove this point I implemented the classical generator pattern without using continuation's data transfer possibilities (see attached file). Sure enough, when the generator lambda calls yield(value) it stops processing until the main program calls operator++ again. So all this time the parameter to yield is valid on the generator's stack and available by reference to the main program. So while there are obviously needs to send data both ways for different patterns I fail to see why it must be a feature of the lowest level abstraction.
When I wrote the code for generator.h I also saw that the pattern `mContinuation = mContinuation()` seems to be mandatory. If you forget the assignment you loose the ability to continue the next time. While there may be cases where you want to assign the continuation returned from operator() to some other std::continuation object, wouldn't it be more appropriate to make simple things simple by letting operator() return void and automatically update its `this` to the new state before returning and then applications which really do need to move the continuation object elsewhere can do so afterwards. Again there are no threading issues so it should be safe. In the back of my head I think there is some exception safety issue at play here, but if so it needs to be explained more clearly.
A comment on the invoke_on_top_t functionality. Why add a special tag type just to be able to overload operator() to do another task when there is the possibility to add a regular method: std::continuation::invoke_on_top(args...).
Also it would be interesting to know when this rather strange functionality is useful.
An issue I stumbled upon during implementation was not being able to do callcc() on a method. I think an overload of callcc specifically for method pointers (in parallel with the std::function constructor set) will be needed. This is as the std::continuation&& parameter gets prepended to the args of callcc when calling the provided function. If the provided function is a std::function wrapping a method pointer its operator() will expect to get the 'this' of the object to call and instead it will get the std::continuation prepended by callcc!// Example:class MyClass {void fun(std::continuation&& cont);};MyClass myObj;callcc(&MyClass::fun, myObj); // This is what you want to write, but callcc does not take method pointers.// So you may try this:callcc(std::function<void(MyClass&, std::continuation&&)>(&MyClass::fun), myObj); // Mismatched types as callcc gets the order wrong.// Instead you have to conjure up a lambda:callcc([&](std::continuation&& cont) { myObj.fun(std::move(cont); });While the lambda solution works I think it is quite weak not to allow the first version.
[...]
Static checking between the parameters to `callcc` and the parameters to the function being invoked is one thing. But static checking between the parameters to `continuation::operator()` and to the receiving code is quite something else. It is perfectly reasonable to initiate a `callcc` with one set of parameters, but provide different parameters of different types at continuation time.
Also, it's important to recognize that the function which began the `callcc` operation may be doing generic boilerplate stuff: storing a promise, building a channel, etc. Its return value will in many cases be irrelevant. So it doesn't make sense to have `callcc` statically assume that the return value of that function is meaningful to the caller.
The fundamental difference between `std::function` and a continuation is that the former is intended to be a go-between and the latter is not.
The former is just a value type for storing and invoking any C++ callable. `callcc` has nothing to do with that; it's creating new functionality. And that new functionality has its own needs.
For example, guaranteed elision should still work through a `std::function` call, for both parameters and return values. But there's no way to make it work through a continuation; there has to be a copy/move of parameters into the continuation's stack, and a copy/move of return values from the continuation's stack.
[1] contrary to what P0534 claims, it describes delimited continuations; I'm not even sure you can have undelimited continuations when you have one-shot semantics.
2017-03-02 11:52 GMT+01:00 Giovanni Piero Deretta <gpde...@gmail.com>:
[1] contrary to what P0534 claims, it describes delimited continuations; I'm not even sure you can have undelimited continuations when you have one-shot semantics.undelimited continuations == no delimiter that tells you where the continuatio nends, e.g. if you call callcc() from main(), it is undelimited
Now, if I understand correctly, with shift/reset, shift finds the closest dynamically scoped reset in the callstack, while with callcc and yield, there is not a clear distinction between the prompt and the shift point as they are both continuations and they are statically scoped (on the other hand, asymmetric coroutines with an explicit thread local parent pointer more closely match shift/reset model).
But I don't understand the need of data transfer at this low abstraction level. After all, these are not threads, so there is no asynchronism to think about.
When I wrote the code for generator.h I also saw that the pattern `mContinuation = mContinuation()` seems to be mandatory. If you forget the assignment you loose the ability to continue the next time. While there may be cases where you want to assign the continuation returned from operator() to some other std::continuation object, wouldn't it be more appropriate to make simple things simple by letting operator() return void and automatically update its `this` to the new state before returning and then applications which really do need to move the continuation object elsewhere can do so afterwards.
A comment on the invoke_on_top_t functionality. Why add a special tag type just to be able to overload operator() to do another task when there is the possibility to add a regular method: std::continuation::invoke_on_top(args...).
Also it would be interesting to know when this rather strange functionality is useful.
An issue I stumbled upon during implementation was not being able to do callcc() on a method. I think an overload of callcc specifically for method pointers (in parallel with the std::function constructor set) will be needed. This is as the std::continuation&& parameter gets prepended to the args of callcc when calling the provided function. If the provided function is a std::function wrapping a method pointer its operator() will expect to get the 'this' of the object to call and instead it will get the std::continuation prepended by callcc!// Example:class MyClass {void fun(std::continuation&& cont);};MyClass myObj;callcc(&MyClass::fun, myObj); // This is what you want to write, but callcc does not take method pointers.// So you may try this:callcc(std::function<void(MyClass&, std::continuation&&)>(&MyClass::fun), myObj); // Mismatched types as callcc gets the order wrong.// Instead you have to conjure up a lambda:callcc([&](std::continuation&& cont) { myObj.fun(std::move(cont); });While the lambda solution works I think it is quite weak not to allow the first version.
An issue I stumbled upon during implementation was not being able to do callcc() on a method. I think an overload of callcc specifically for method pointers (in parallel with the std::function constructor set) will be needed. This is as the std::continuation&& parameter gets prepended to the args of callcc when calling the provided function. If the provided function is a std::function wrapping a method pointer its operator() will expect to get the 'this' of the object to call and instead it will get the std::continuation prepended by callcc!// Example:class MyClass {void fun(std::continuation&& cont);};MyClass myObj;callcc(&MyClass::fun, myObj); // This is what you want to write, but callcc does not take method pointers.// So you may try this:callcc(std::function<void(MyClass&, std::continuation&&)>(&MyClass::fun), myObj); // Mismatched types as callcc gets the order wrong.// Instead you have to conjure up a lambda:callcc([&](std::continuation&& cont) { myObj.fun(std::move(cont); });While the lambda solution works I think it is quite weak not to allow the first version.I'm too busy - so it didn't got the highest prio/keep in mind I do it at my freetime
…A comment on the invoke_on_top_t functionality. Why add a special tag type just to be able to overload operator() to do another task when there is the possibility to add a regular method: std::continuation::invoke_on_top(args...). Also it would be interesting to know when this rather strange functionality is useful.…
But I don't understand the need of data transfer at this low abstraction level. After all, these are not threads, so there is no asynchronism to think about.
P0534 is symmetric context switching, e.g. no coupling between caller and callee as it is for generators/coroutines.
When I wrote the code for generator.h I also saw that the pattern `mContinuation = mContinuation()` seems to be mandatory. If you forget the assignment you loose the ability to continue the next time. While there may be cases where you want to assign the continuation returned from operator() to some other std::continuation object, wouldn't it be more appropriate to make simple things simple by letting operator() return void and automatically update its `this` to the new state before returning and then applications which really do need to move the continuation object elsewhere can do so afterwards.I've had several variations of callcc() implemented and I got into troubles during the implementation of fibers (lightweight threads) using such a kind of call/cc.I would not consider to change the identity of a continuation while invoking its operator() a good idea - you don't know to which context your continuation points to (e.g. what it executes/does) after returning from continuation::operator().
A comment on the invoke_on_top_t functionality. Why add a special tag type just to be able to overload operator() to do another task when there is the possibility to add a regular method: std::continuation::invoke_on_top(args...).both is possible : because apply-operator does the context switch I'd like to use the same operator to do the context switch + invoking an function on the resumed context
Also it would be interesting to know when this rather strange functionality is useful.described in P0534:you can invoke a new function after resuming the context that throws an exception
for instance you want abort and unwind the stack - instead to check for abort each time the context is resumed you call invoke-ontop functionality to execute the throwing abort function only at the time point you know you want to abort the continuation/context
An issue I stumbled upon during implementation was not being able to do callcc() on a method. I think an overload of callcc specifically for method pointers (in parallel with the std::function constructor set) will be needed. This is as the std::continuation&& parameter gets prepended to the args of callcc when calling the provided function. If the provided function is a std::function wrapping a method pointer its operator() will expect to get the 'this' of the object to call and instead it will get the std::continuation prepended by callcc!// Example:class MyClass {void fun(std::continuation&& cont);};MyClass myObj;callcc(&MyClass::fun, myObj); // This is what you want to write, but callcc does not take method pointers.// So you may try this:callcc(std::function<void(MyClass&, std::continuation&&)>(&MyClass::fun), myObj); // Mismatched types as callcc gets the order wrong.// Instead you have to conjure up a lambda:callcc([&](std::continuation&& cont) { myObj.fun(std::move(cont); });While the lambda solution works I think it is quite weak not to allow the first version.I'm too busy - so it didn't got the highest prio/keep in mind I do it at my freetime
But I don't understand the need of data transfer at this low abstraction level. After all, these are not threads, so there is no asynchronism to think about.P0534 is symmetric context switching, e.g. no coupling between caller and callee as it is for generators/coroutines.Exactly, that's why I asked why data transfer is needed _on the call/cc level_. I showed in my example that you can do data transfer on top of this (at the generator level) with essentially no overhead. Giovanni pointed out that there may be the overhead of one register save/restore, and if this the performance gain you are after I think the price of a scary "void*" style interface (hidden behind an unsafe cast) is not worth it, and if the access is to be tested the performance cost is definitely more than a register save.
And you don't know the identity of the continuation returned from operator() in your case, what is the difference? The advantage of the in situ version is however obvious: You can't forget doing the assignment, and thereby leave the continuation object stale.
Also it would be interesting to know when this rather strange functionality is useful.described in P0534:you can invoke a new function after resuming the context that throws an exceptionIt is a bit hard to understsand this sentense.
Do you mean to resume a context after it has thrown an exception?
for instance you want abort and unwind the stack - instead to check for abort each time the context is resumed you call invoke-ontop functionality to execute the throwing abort function only at the time point you know you want to abort the continuation/contextI think you may be saying that once in a while one party injects code that throws if an abort flag is set into another piece of code on the other side of the continuation.
Or maybe you test the abort flag first and then injects a function which just throws an exception. Well that would be a way to get rid of the continuation and properly unwind its stack, but didn't you have a specific way of doing that, such as desrtroying the continuation object?
2017-03-04 15:27 GMT+01:00 Bengt Gustafsson <bengt.gu...@beamways.com>:But I don't understand the need of data transfer at this low abstraction level. After all, these are not threads, so there is no asynchronism to think about.P0534 is symmetric context switching, e.g. no coupling between caller and callee as it is for generators/coroutines.Exactly, that's why I asked why data transfer is needed _on the call/cc level_. I showed in my example that you can do data transfer on top of this (at the generator level) with essentially no overhead. Giovanni pointed out that there may be the overhead of one register save/restore, and if this the performance gain you are after I think the price of a scary "void*" style interface (hidden behind an unsafe cast) is not worth it, and if the access is to be tested the performance cost is definitely more than a register save.I don't see your point - accessing a register if verry fast (1 CPU cycle I guess).
The data transfer as proposed in P0534 (and used for coroutine and fiber implementation) does provide some kind of type-safety and no overhead if no data are transferred.
And you don't know the identity of the continuation returned from operator() in your case, what is the difference? The advantage of the in situ version is however obvious: You can't forget doing the assignment, and thereby leave the continuation object stale.Suppose you have contoinuations c1, c2:c1 = callcc( foo);
c2 = callcc( bar);- in main() you resume c1: c1();
- c1 executes foo
- in foo you resume c2: c2()
- c2 executes bar
- in bar you jump to main()
- that means you return from c1() in main()- if you call c1() in main() you do resume bar() instead of foo()
Also it would be interesting to know when this rather strange functionality is useful.described in P0534:you can invoke a new function after resuming the context that throws an exceptionIt is a bit hard to understsand this sentense.context switching is a bit mind twisitingDo you mean to resume a context after it has thrown an exception?no - resume the context and then, if you are in the resumed context, create a new stack frame and execute your supplied function (that might throw)
for instance you want abort and unwind the stack - instead to check for abort each time the context is resumed you call invoke-ontop functionality to execute the throwing abort function only at the time point you know you want to abort the continuation/contextI think you may be saying that once in a while one party injects code that throws if an abort flag is set into another piece of code on the other side of the continuation.yesOr maybe you test the abort flag first and then injects a function which just throws an exception. Well that would be a way to get rid of the continuation and properly unwind its stack, but didn't you have a specific way of doing that, such as desrtroying the continuation object?throwing an exeception was only one use case - infact if a suspended continuation has to be destroyed (instance goes out of scope) out of a deep callstack - the current implementation executes a function ontop of those continuation that throws a speciall exception that unwinds the stakc of the continuation and jumps back to the caller.
I don't see your point - accessing a register if verry fast (1 CPU cycle I guess).The point is that if the feature does not have a large performance gain compared to not having it, and if it does not avoid a complex synchronization problem then why have it? I don't see that you have shown any major performance or synchronization gain.
The data transfer as proposed in P0534 (and used for coroutine and fiber implementation) does provide some kind of type-safety and no overhead if no data are transferred.While ideas in the direction of my Generator class offers compile time type safety and negligable overhead even if used!
Not to mention a smaller API.
- in foo you resume c2: c2()ok, a bit odd but doable.
- in bar you jump to main()Unless you involve setjmp/longjmp I don't see a way fot this to happen
What can happen is that foo gives bar access to its stored continuation and bar uses this to transfer control to main after its c1() call.
And sure enough c1 now refers to bar after its second context switch. Yes this seems to be what you refer to.- that means you return from c1() in main()- if you call c1() in main() you do resume bar() instead of foo()Yes. This can happen if you store the continuations in global variables
Isn't this why we want to abstract the continuation level into higher level abstractions which are easier to understand and where these types ofoddities are easy to avoid.
The only gain with the assignment is as a reminder of "next time this continuation will continue executing somewhere else". But as that always happens and you can not make something else happen by NOT doing the assignment (except dropping a valuable asset) it seems rather error prone...
no - resume the context and then, if you are in the resumed context, create a new stack frame and execute your supplied function (that might throw)OK, I understand what it does, my question was why this is useful.
Yes, that is how I understood it. This is what would happen if you forget to assign the returned continuation to your variable, I assume ;-)
So the only use case for invoke_on_top() that you have produced is in fact equivalent to destroying the continuation object.
Given that you can't access data in underlying stack frames
continuation c;fun()c()
andc.invoke_on_top(fun);
Well, there may be some difference if fun throws,
Ok, maybe you only see this as a method of throwing an exception in the other stack,
I would have thought that the main use would be to allow a pool of fibers to be used for executing arbitrary code.
The main reason for not doing new callcc() calls each time would be to avoid having to allocate stacks each time.
If this is the use case it seems that the stack rather than the continuation is the object to preserve and pool.
2017-03-04 20:04 GMT+01:00 Bengt Gustafsson <bengt.gu...@beamways.com>:I don't see your point - accessing a register if verry fast (1 CPU cycle I guess).The point is that if the feature does not have a large performance gain compared to not having it, and if it does not avoid a complex synchronization problem then why have it? I don't see that you have shown any major performance or synchronization gain.I don't understand what you try to tell me
The data transfer as proposed in P0534 (and used for coroutine and fiber implementation) does provide some kind of type-safety and no overhead if no data are transferred.While ideas in the direction of my Generator class offers compile time type safety and negligable overhead even if used!
generators provide asynchronous context switching which is always less efficient than synchronous
Not to mention a smaller API.it's not
- in foo you resume c2: c2()ok, a bit odd but doable.why is it odd?
- in bar you jump to main()Unless you involve setjmp/longjmp I don't see a way fot this to happencallcc() does similiar things like setjmp/longjmp - actually callcc() creates a new stack and a new context and exchanges stack and instruction pointer
What can happen is that foo gives bar access to its stored continuation and bar uses this to transfer control to main after its c1() call.I don't get it
foo can not give its continuation to bar while foo is running
foo is supended by calling callcc() or continuation::operator() and the invocation of this functions generate a continuation of fooAnd sure enough c1 now refers to bar after its second context switch. Yes this seems to be what you refer to.- that means you return from c1() in main()- if you call c1() in main() you do resume bar() instead of foo()Yes. This can happen if you store the continuations in global variablesno, global vars are not needed
Isn't this why we want to abstract the continuation level into higher level abstractions which are easier to understand and where these types ofoddities are easy to avoid.I would say no - higher level astractions defined how continuations interact
The only gain with the assignment is as a reminder of "next time this continuation will continue executing somewhere else". But as that always happens and you can not make something else happen by NOT doing the assignment (except dropping a valuable asset) it seems rather error prone...I don't get it
no - resume the context and then, if you are in the resumed context, create a new stack frame and execute your supplied function (that might throw)OK, I understand what it does, my question was why this is useful.yes
Yes, that is how I understood it. This is what would happen if you forget to assign the returned continuation to your variable, I assume ;-)rightSo the only use case for invoke_on_top() that you have produced is in fact equivalent to destroying the continuation object.no
Given that you can't access data in underlying stack framesyou can pass data from the fucntion executed ontop of the context to the underlying stack frame
continuation c;fun()c()fun() is executed on the current stack (and in the current context - lets say main() for instance)c() resumes c, e.g switches execution to the stack and context c is related toandc.invoke_on_top(fun);resumes c and invokes fun on the stack c is related to == fun() is executed in the context of c
Well, there may be some difference if fun throws,if fun() throwsfun()c()-> c() is not executed; exeception is thrown in the context of main() for instance
c.invoke_on_top(fun);-> exeception is thrown inside c and will terminate c if not catchedOk, maybe you only see this as a method of throwing an exception in the other stack,no
I would have thought that the main use would be to allow a pool of fibers to be used for executing arbitrary code.you nixing up concepts - callcc() is not a fiber, but fibers can be implemented using callcc() (see boost.fiber as an example)
The main reason for not doing new callcc() calls each time would be to avoid having to allocate stacks each time.callcc() is factory function of continuations, it generates a new stack and context
If this is the use case it seems that the stack rather than the continuation is the object to preserve and pool.the stack is actually the continuation - if continuation::operator() is called the stack pointer (stored inside continuation) is assigned to the stack pointer and the instruction pointe ris poped from it
On Mar 3, 2017, at 7:36 AM, Bengt Gustafsson <bengt.gu...@beamways.com> wrote:I don't know what you mean by trampoline in this context. I checked wikipedia link but I didn't get it...
…
…
This aside, do you see a reason for this being a tag type and operator() rather than a method call?
…
…
I thought about this more and it seems that it may have to do with implementing a fiber pool. The invoked function is the body of the next fiber to execute on the stack of the continuation and the target of the ccllcc is just a function like this:
void fiber_base(continuation&& cont, bool& stop)
{
continuation c = std::move(cont);
while (!stop)
c = c();
}
then the party starting the fiber_base with callcc can invoke anything on this stack and when this anything returns the loop goes back to wait for the next task.
This idea is just speculation on my part, but it seems to show a use case (unless the stack itself is made a first class object which can be reused for multiple callcc calls which I would find more logical).
…
…
Den 2017-03-02 kl. 23:21, skrev Bryce Glover:
(snipped…)-- Bengt Gustafsson CEO, Beamways AB Westmansgatan 37 582 16 Linköping, Sweden +46 (705) 338259 Skype: benke_g www.beamways.com