2017-08-31 04:32:54 +00:00
//: Continuations are a powerful primitive for constructing advanced kinds of
//: control *policies* like back-tracking.
//:
//: In Mu, continuations are first-class and delimited, and are constructed
//: out of two primitives:
//:
//: * 'call-with-continuation-mark' marks the top of the call stack and then
2017-11-04 00:56:35 +00:00
//: calls the provided recipe.
2017-08-31 04:32:54 +00:00
//: * 'return-continuation-until-mark' copies the top of the stack
//: until the mark, and returns it as the result of
2017-11-03 07:40:05 +00:00
//: 'call-with-continuation-mark' (which might be a distant ancestor on the
//: call stack; intervening calls don't return)
2017-08-31 04:32:54 +00:00
//:
//: The resulting slice of the stack can now be called just like a regular
2017-11-04 00:56:35 +00:00
//: recipe.
2017-11-03 07:40:05 +00:00
//:
//: See the example programs continuation*.mu to get a sense for the
//: possibilities.
//:
//: Refinements:
//: * You can call a single continuation multiple times, and it will preserve
//: the state of its local variables at each stack frame between calls.
//: The stack frames of a continuation are not destroyed until the
2017-11-04 00:56:35 +00:00
//: continuation goes out of scope. See continuation2.mu.
2017-11-03 07:40:05 +00:00
//: * 'return-continuation-until-mark' doesn't consume the mark, so you can
//: return multiple continuations based on a single mark. In combination
//: with the fact that 'return-continuation-until-mark' can return from
//: regular calls, just as long as there was an earlier call to
//: 'call-with-continuation-mark', this gives us a way to create resumable
2017-11-04 00:56:35 +00:00
//: recipes. See continuation3.mu.
2017-11-05 18:52:33 +00:00
//: * 'return-continuation-until-mark' can take ingredients to return just
//: like other 'return' instructions. It just implicitly also returns a
//: continuation as the first result. See continuation4.mu.
2017-11-06 09:28:53 +00:00
//: * Conversely, you can pass ingredients to a continuation when calling it,
//: to make it available to products of 'return-continuation-until-mark'.
2017-11-06 10:35:54 +00:00
//: See continuation5.mu.
2018-03-16 05:31:13 +00:00
//: * There can be multiple continuation marks on the stack at once;
//: 'call-with-continuation-mark' and 'return-continuation-until-mark' both
//: need to pass in a tag to coordinate on the correct mark. This allows us
//: to save multiple continuations for different purposes (say if one is
//: for exceptions) with overlapping stack frames. See exception.mu.
2017-11-06 09:28:53 +00:00
//:
//: Inspired by James and Sabry, "Yield: Mainstream delimited continuations",
//: Workshop on the Theory and Practice of Delimited Continuations, 2011.
//: https://www.cs.indiana.edu/~sabry/papers/yield.pdf
2017-11-03 07:40:05 +00:00
//:
//: Caveats:
//: * At the moment we can't statically type-check continuations. So we raise
//: runtime errors for a call that doesn't return a continuation when the
//: caller expects, or one that returns a continuation when the caller
//: doesn't expect it. This shouldn't cause memory corruption, though.
//: There should still be no way to lookup addresses that aren't allocated.
2017-08-31 04:32:54 +00:00
: ( before " End Mu Types Initialization " )
type_ordinal continuation = Type_ordinal [ " continuation " ] = Next_type_ordinal + + ;
Type [ continuation ] . name = " continuation " ;
//: A continuation can be called like a recipe.
: ( before " End is_mu_recipe Atom Cases(r) " )
if ( r . type - > name = = " continuation " ) return true ;
//: However, it can't be type-checked like most recipes. Pretend it's like a
//: header-less recipe.
: ( after " Begin Reagent->Recipe(r, recipe_header) " )
if ( r . type - > atom & & r . type - > name = = " continuation " ) {
result_header . has_header = false ;
return result_header ;
}
: ( scenario delimited_continuation )
recipe main [
2017-12-15 08:15:47 +00:00
1 : continuation < - call - with - continuation - mark 233 / mark , f , 77 # 77 is an argument to f
2018-06-18 02:53:52 +00:00
2 : num < - copy 5
2017-08-31 04:32:54 +00:00
{
2018-06-18 02:53:52 +00:00
2 : num < - call 1 : continuation , 2 : num # jump to ' return - continuation - until - mark ' below
3 : bool < - greater - or - equal 2 : num , 8
break - if 3 : bool
2017-08-31 04:32:54 +00:00
loop
}
]
recipe f [
2018-06-18 02:53:52 +00:00
11 : num < - next - ingredient
12 : num < - g 11 : num
return 12 : num
2017-08-31 04:32:54 +00:00
]
recipe g [
2018-06-18 02:53:52 +00:00
21 : num < - next - ingredient
22 : num < - return - continuation - until - mark 233 / mark
23 : num < - add 22 : num , 1
return 23 : num
2017-08-31 04:32:54 +00:00
]
2017-09-14 03:32:51 +00:00
# first call of 'g' executes the part before return-continuation-until-mark
2017-08-31 04:32:54 +00:00
+ mem : storing 77 in location 21
+ run : { 2 : " number " } < - copy { 5 : " literal " }
+ mem : storing 5 in location 2
2017-09-14 03:32:51 +00:00
# calls of the continuation execute the part after return-continuation-until-mark
2017-08-31 04:32:54 +00:00
+ run : { 2 : " number " } < - call { 1 : " continuation " } , { 2 : " number " }
+ mem : storing 5 in location 22
+ mem : storing 6 in location 2
+ run : { 2 : " number " } < - call { 1 : " continuation " } , { 2 : " number " }
+ mem : storing 6 in location 22
+ mem : storing 7 in location 2
+ run : { 2 : " number " } < - call { 1 : " continuation " } , { 2 : " number " }
+ mem : storing 7 in location 22
+ mem : storing 8 in location 2
2017-09-14 03:32:51 +00:00
# first call of 'g' does not execute the part after return-continuation-until-mark
2017-08-31 04:32:54 +00:00
- mem : storing 77 in location 22
2017-09-14 03:32:51 +00:00
# calls of the continuation don't execute the part before return-continuation-until-mark
2017-08-31 04:32:54 +00:00
- mem : storing 5 in location 21
- mem : storing 6 in location 21
- mem : storing 7 in location 21
# termination
- mem : storing 9 in location 2
: ( before " End call Fields " )
2018-03-16 04:54:53 +00:00
int continuation_mark_tag ;
2017-08-31 04:32:54 +00:00
: ( before " End call Constructor " )
2018-03-16 04:54:53 +00:00
continuation_mark_tag = 0 ;
2017-08-31 04:32:54 +00:00
: ( before " End Primitive Recipe Declarations " )
CALL_WITH_CONTINUATION_MARK ,
: ( before " End Primitive Recipe Numbers " )
Recipe_ordinal [ " call-with-continuation-mark " ] = CALL_WITH_CONTINUATION_MARK ;
: ( before " End Primitive Recipe Checks " )
case CALL_WITH_CONTINUATION_MARK : {
2018-02-21 06:09:12 +00:00
if ( SIZE ( inst . ingredients ) < 2 ) {
raise < < maybe ( get ( Recipe , r ) . name ) < < " ' " < < to_original_string ( inst ) < < " ' requires at least two ingredients: a mark number and a recipe to call \n " < < end ( ) ;
}
2017-08-31 04:32:54 +00:00
break ;
}
: ( before " End Primitive Recipe Implementations " )
case CALL_WITH_CONTINUATION_MARK : {
2017-09-01 08:50:32 +00:00
// like call, but mark the current call as a 'base of continuation' call
// before pushing the next one on it
2017-08-31 04:32:54 +00:00
if ( Trace_stream ) {
+ + Trace_stream - > callstack_depth ;
trace ( " trace " ) < < " delimited continuation; incrementing callstack depth to " < < Trace_stream - > callstack_depth < < end ( ) ;
assert ( Trace_stream - > callstack_depth < 9000 ) ; // 9998-101 plus cushion
}
2018-03-16 05:31:13 +00:00
instruction /*copy*/ caller_instruction = current_instruction ( ) ;
2018-03-16 04:54:53 +00:00
Current_routine - > calls . front ( ) . continuation_mark_tag = current_instruction ( ) . ingredients . at ( 0 ) . value ;
2018-03-16 07:53:33 +00:00
Current_routine - > calls . push_front ( call ( ingredients . at ( 1 ) . at ( 0 ) ) ) ;
2018-03-16 05:31:13 +00:00
// drop the mark
caller_instruction . ingredients . erase ( caller_instruction . ingredients . begin ( ) ) ;
ingredients . erase ( ingredients . begin ( ) ) ;
// drop the callee
caller_instruction . ingredients . erase ( caller_instruction . ingredients . begin ( ) ) ;
ingredients . erase ( ingredients . begin ( ) ) ;
2017-08-31 04:32:54 +00:00
finish_call_housekeeping ( caller_instruction , ingredients ) ;
continue ;
}
2018-03-16 05:31:13 +00:00
: ( scenario next_ingredient_inside_continuation )
recipe main [
2018-06-17 07:05:38 +00:00
call - with - continuation - mark 233 / mark , f , true
2018-03-16 05:31:13 +00:00
]
recipe f [
10 : bool < - next - input
]
+ mem : storing 1 in location 10
2018-03-16 07:53:33 +00:00
: ( scenario delimited_continuation_out_of_recipe_variable )
recipe main [
x : recipe < - copy f
2018-06-17 07:05:38 +00:00
call - with - continuation - mark 233 / mark , x , true
2018-03-16 07:53:33 +00:00
]
recipe f [
10 : bool < - next - input
]
+ mem : storing 1 in location 10
2017-08-31 04:32:54 +00:00
//: save the slice of current call stack until the 'call-with-continuation-mark'
//: call, and return it as the result.
//: todo: implement delimited continuations in Mu's memory
2017-11-19 12:23:31 +00:00
: ( before " End Types " )
struct delimited_continuation {
call_stack frames ;
int nrefs ;
delimited_continuation ( call_stack : : iterator begin , call_stack : : iterator end ) : frames ( call_stack ( begin , end ) ) , nrefs ( 0 ) { }
} ;
2017-08-31 04:32:54 +00:00
: ( before " End Globals " )
2017-11-19 12:23:31 +00:00
map < long long int , delimited_continuation > Delimited_continuation ;
2017-11-03 07:40:05 +00:00
long long int Next_delimited_continuation_id = 1 ; // 0 is null just like an address
2017-08-31 04:32:54 +00:00
: ( before " End Reset " )
Delimited_continuation . clear ( ) ;
2017-11-03 07:40:05 +00:00
Next_delimited_continuation_id = 1 ;
2017-08-31 04:32:54 +00:00
: ( before " End Primitive Recipe Declarations " )
RETURN_CONTINUATION_UNTIL_MARK ,
: ( before " End Primitive Recipe Numbers " )
Recipe_ordinal [ " return-continuation-until-mark " ] = RETURN_CONTINUATION_UNTIL_MARK ;
: ( before " End Primitive Recipe Checks " )
case RETURN_CONTINUATION_UNTIL_MARK : {
2018-03-22 04:52:53 +00:00
if ( inst . ingredients . empty ( ) ) {
raise < < maybe ( get ( Recipe , r ) . name ) < < " ' " < < to_original_string ( inst ) < < " ' requires at least one ingredient: a mark tag (number) \n " < < end ( ) ;
}
2017-08-31 04:32:54 +00:00
break ;
}
: ( before " End Primitive Recipe Implementations " )
case RETURN_CONTINUATION_UNTIL_MARK : {
2017-12-08 00:04:47 +00:00
// I don't know how to think about next-ingredient in combination with
// continuations, so seems cleaner to just kill it. Functions have to read
// their inputs before ever returning a continuation.
2017-08-31 04:32:54 +00:00
Current_routine - > calls . front ( ) . ingredient_atoms . clear ( ) ;
Current_routine - > calls . front ( ) . next_ingredient_to_process = 0 ;
// copy the current call stack until the most recent marked call
2017-12-15 08:15:47 +00:00
call_stack : : iterator find_base_of_continuation ( call_stack & , int ) ; // manual prototype containing '::'
2018-03-16 04:54:53 +00:00
call_stack : : iterator base = find_base_of_continuation ( Current_routine - > calls , /*mark tag*/ current_instruction ( ) . ingredients . at ( 0 ) . value ) ;
2017-08-31 04:32:54 +00:00
if ( base = = Current_routine - > calls . end ( ) ) {
2018-03-16 07:53:33 +00:00
raise < < maybe ( current_recipe_name ( ) ) < < " couldn't find a 'call-with-continuation-mark' to return to with tag " < < current_instruction ( ) . ingredients . at ( 0 ) . original_string < < ' \n ' < < end ( ) ;
2017-09-01 08:50:32 +00:00
raise < < maybe ( current_recipe_name ( ) ) < < " call stack: \n " < < end ( ) ;
for ( call_stack : : iterator p = Current_routine - > calls . begin ( ) ; p ! = Current_routine - > calls . end ( ) ; + + p )
raise < < maybe ( current_recipe_name ( ) ) < < " " < < get ( Recipe , p - > running_recipe ) . name < < ' \n ' < < end ( ) ;
2017-08-31 04:32:54 +00:00
break ;
}
2017-11-03 07:40:05 +00:00
trace ( " run " ) < < " creating continuation " < < Next_delimited_continuation_id < < end ( ) ;
2017-11-19 12:23:31 +00:00
put ( Delimited_continuation , Next_delimited_continuation_id , delimited_continuation ( Current_routine - > calls . begin ( ) , base ) ) ;
2017-08-31 04:32:54 +00:00
while ( Current_routine - > calls . begin ( ) ! = base ) {
if ( Trace_stream ) {
- - Trace_stream - > callstack_depth ;
assert ( Trace_stream - > callstack_depth > = 0 ) ;
}
Current_routine - > calls . pop_front ( ) ;
}
// return it as the result of the marked call
products . resize ( 1 ) ;
products . at ( 0 ) . push_back ( Next_delimited_continuation_id ) ;
2017-11-05 09:47:03 +00:00
// return any other ingredients passed in
2018-03-16 04:54:53 +00:00
copy ( /*skip mark tag*/ + + ingredients . begin ( ) , ingredients . end ( ) , inserter ( products , products . end ( ) ) ) ;
2017-08-31 04:32:54 +00:00
+ + Next_delimited_continuation_id ;
break ; // continue to process rest of marked call
}
: ( code )
2018-03-16 04:54:53 +00:00
call_stack : : iterator find_base_of_continuation ( call_stack & c , int mark_tag ) {
2017-08-31 04:32:54 +00:00
for ( call_stack : : iterator p = c . begin ( ) ; p ! = c . end ( ) ; + + p )
2018-03-16 04:54:53 +00:00
if ( p - > continuation_mark_tag = = mark_tag ) return p ;
2017-08-31 04:32:54 +00:00
return c . end ( ) ;
}
//: overload 'call' for continuations
: ( after " Begin Call " )
2017-11-19 10:35:43 +00:00
if ( is_mu_continuation ( current_instruction ( ) . ingredients . at ( 0 ) ) ) {
2017-09-01 09:23:45 +00:00
// copy multiple calls on to current call stack
assert ( scalar ( ingredients . at ( 0 ) ) ) ;
2017-11-03 07:40:05 +00:00
trace ( " run " ) < < " calling continuation " < < ingredients . at ( 0 ) . at ( 0 ) < < end ( ) ;
2017-11-19 11:18:11 +00:00
if ( ! contains_key ( Delimited_continuation , ingredients . at ( 0 ) . at ( 0 ) ) )
2017-09-01 09:23:45 +00:00
raise < < maybe ( current_recipe_name ( ) ) < < " no such delimited continuation " < < current_instruction ( ) . ingredients . at ( 0 ) . original_string < < ' \n ' < < end ( ) ;
2017-11-19 12:23:31 +00:00
const call_stack & new_frames = get ( Delimited_continuation , ingredients . at ( 0 ) . at ( 0 ) ) . frames ;
2018-01-03 08:31:10 +00:00
for ( call_stack : : const_reverse_iterator p = new_frames . rbegin ( ) ; p ! = new_frames . rend ( ) ; + + p )
2017-09-01 09:23:45 +00:00
Current_routine - > calls . push_front ( * p ) ;
if ( Trace_stream ) {
2017-11-19 11:18:11 +00:00
Trace_stream - > callstack_depth + = SIZE ( new_frames ) ;
2017-09-01 09:23:45 +00:00
trace ( " trace " ) < < " calling delimited continuation; growing callstack depth to " < < Trace_stream - > callstack_depth < < end ( ) ;
assert ( Trace_stream - > callstack_depth < 9000 ) ; // 9998-101 plus cushion
2017-08-31 04:32:54 +00:00
}
2017-11-20 06:42:06 +00:00
// no call housekeeping; continuations don't support next-ingredient
copy ( /*drop continuation*/ + + ingredients . begin ( ) , ingredients . end ( ) , inserter ( products , products . begin ( ) ) ) ;
2017-11-06 09:12:42 +00:00
break ; // record results of resuming 'return-continuation-until-mark' instruction
2017-09-01 09:23:45 +00:00
}
2017-11-03 07:40:05 +00:00
2017-11-19 12:18:31 +00:00
: ( scenario continuations_can_return_values )
def main [
local - scope
2017-12-15 08:15:47 +00:00
k : continuation , 1 : num / raw < - call - with - continuation - mark 233 / mark , f
2017-11-19 12:18:31 +00:00
]
def f [
local - scope
g
]
def g [
local - scope
2017-12-15 08:15:47 +00:00
return - continuation - until - mark 233 / mark , 34
2017-11-19 12:18:31 +00:00
stash [ continuation called ]
]
+ mem : storing 34 in location 1
2017-12-15 08:15:47 +00:00
: ( scenario continuations_continue_to_matching_mark )
def main [
local - scope
k : continuation , 1 : num / raw < - call - with - continuation - mark 233 / mark , f
add 1 , 1
]
def f [
local - scope
k2 : continuation < - call - with - continuation - mark 234 / mark , g
add 2 , 2
]
def g [
local - scope
return - continuation - until - mark 233 / mark , 34
stash [ continuation called ]
]
+ run : add { 1 : " literal " } , { 1 : " literal " }
- run : add { 2 : " literal " } , { 2 : " literal " }
2017-12-07 23:59:48 +00:00
//: Allow shape-shifting recipes to return continuations.
: ( scenario call_shape_shifting_recipe_with_continuation_mark )
def main [
2017-12-15 08:15:47 +00:00
1 : num < - call - with - continuation - mark 233 / mark , f , 34
2017-12-07 23:59:48 +00:00
]
def f x : _elem - > y : _elem [
local - scope
load - ingredients
y < - copy x
]
+ mem : storing 34 in location 1
: ( before " End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases " )
2017-12-15 08:15:47 +00:00
if ( inst . name = = " call-with-continuation-mark " ) {
if ( SIZE ( inst . ingredients ) > 1 & & is_recipe_literal ( inst . ingredients . at ( /*skip mark*/ 1 ) ) ) {
2017-12-10 11:44:49 +00:00
resolve_indirect_continuation_call ( r , index , inst , caller_recipe ) ;
2017-12-07 23:59:48 +00:00
return ;
2017-12-15 08:15:47 +00:00
}
2017-12-07 23:59:48 +00:00
}
2017-12-10 11:44:49 +00:00
: ( code )
void resolve_indirect_continuation_call ( const recipe_ordinal r , int index , instruction & inst , const recipe & caller_recipe ) {
instruction inst2 ;
2017-12-15 08:15:47 +00:00
inst2 . name = inst . ingredients . at ( /*skip mark*/ 1 ) . name ;
for ( int i = /*skip mark and recipe*/ 2 ; i < SIZE ( inst . ingredients ) ; + + i )
2017-12-10 11:44:49 +00:00
inst2 . ingredients . push_back ( inst . ingredients . at ( i ) ) ;
for ( int i = /*skip continuation*/ 1 ; i < SIZE ( inst . products ) ; + + i )
inst2 . products . push_back ( inst . products . at ( i ) ) ;
resolve_ambiguous_call ( r , index , inst2 , caller_recipe ) ;
2017-12-15 08:15:47 +00:00
inst . ingredients . at ( /*skip mark*/ 1 ) . name = inst2 . name ;
inst . ingredients . at ( /*skip mark*/ 1 ) . set_value ( get ( Recipe_ordinal , inst2 . name ) ) ;
2017-12-10 11:44:49 +00:00
}
2017-12-07 23:59:48 +00:00
2017-12-08 00:01:43 +00:00
: ( scenario call_shape_shifting_recipe_with_continuation_mark_and_no_outputs )
def main [
2017-12-15 08:15:47 +00:00
1 : continuation < - call - with - continuation - mark 233 / mark , f , 34
2017-12-08 00:01:43 +00:00
]
def f x : _elem [
local - scope
load - ingredients
2017-12-15 08:15:47 +00:00
return - continuation - until - mark 233 / mark
2017-12-08 00:01:43 +00:00
]
$ error : 0
2017-12-10 11:44:49 +00:00
: ( scenario continuation1 )
def main [
local - scope
2017-12-15 08:15:47 +00:00
k : continuation < - call - with - continuation - mark 233 / mark , create - yielder
2017-12-10 11:44:49 +00:00
10 : num / raw < - call k
]
def create - yielder - > n : num [
local - scope
load - inputs
2017-12-15 08:15:47 +00:00
return - continuation - until - mark 233 / mark
2017-12-10 11:44:49 +00:00
return 1
]
+ mem : storing 1 in location 10
$ error : 0
2017-11-03 07:40:05 +00:00
: ( code )
bool is_mu_continuation ( reagent /*copy*/ x ) {
canonize_type ( x ) ;
return x . type & & x . type - > atom & & x . type - > value = = get ( Type_ordinal , " continuation " ) ;
}
2017-11-19 12:23:31 +00:00
2017-12-07 21:45:01 +00:00
// helper for debugging
void dump ( const int continuation_id ) {
if ( ! contains_key ( Delimited_continuation , continuation_id ) ) {
raise < < " missing delimited continuation: " < < continuation_id < < ' \n ' < < end ( ) ;
return ;
}
delimited_continuation & curr = get ( Delimited_continuation , continuation_id ) ;
dump ( curr . frames ) ;
}