PLUGIN coroutine lang: "C++" version: "1.0" date: "2022-02-19" author: "Julien BRUGUIER" maintainer: "Julien BRUGUIER " synopsis: "An implementation of coroutines for SVM." description: %{ This plugin enables the support of coroutines. Several coroutines can be created and used at a given point in programs. .P The plugin uses processor flags starting with 'coroutine:' (by default) to handle the coroutines and all flags with this prefix shall not be used in applications using coroutines. This prefix can be changed through the -p plugin option. %} example: "Fibonacci generator" %{ .nf #!===SVMBIN=== LOG PLUGIN "svmint.so" PLUGIN "svmcom.so" PLUGIN "===PLUGINLIB===" ARGUMENT INT nb PROCESS "fibonacci" CODE "main" INLINE :memory (PTR, INT)/p, INT/i [ 0 ] -> i :coroutine.call "fib" "fib" p :label loop :coroutine.run "fib" :com.message @(p/1) :shift &i :goto loop :when @&i IN &0*@&nb :coroutine.return "fib" :shutdown :label fib :memory INT, INT -> &P 0 -> (P/1) :coroutine.yield :shift (P/1) :coroutine.yield [ 0 , 1 ] -> @&P :label loop_fib :int.add @&P -> (P/1) @(@&P/1) -> (@&P/0) @(P/1) -> (@&P/1) :coroutine.yield :goto loop_fib END MEMORY nb END .fi %} example: "A complete example" %{ .nf #!===SVMBIN=== LOG PLUGIN "svmint.so" PLUGIN "svmcom.so" PLUGIN "===PLUGINLIB===" ARGUMENT INT nb PROCESS "coroutine" CODE "main" INLINE :memory INT/i, INT*@&nb/t 0 -> &i :label init :int.sub @&nb @&i -> (t/@&i) :shift &i :goto init :when @&i IN t :memory (PTR, PTR, STR, INT, INT)/p [ , t , "sum" , 0 , ] -> p :call accumulate p :com.message "Result=" @(p/4) :com.message "Explanation:" :coroutine.call "accumulate" "accumulate" p :memory (PTR, PTR, STR)/pp [ , p , ] -> pp :coroutine.call "parity" "parity" pp :label loop :coroutine.run "parity" :shutdown :unless (@&p/5) DEFINED :com.message @(@&p/5) " (" @(pp/2) ")" :goto loop :label accumulate # PTR, PTR:array of int, STR/SYM:function, INT:start, INT:result :memory INT, INT, PTR, INT, INT, INT -> &P [ @(P/3) , 0 ] -> &@&P*2 :label accumulate_loop @&@&P -> (@&P/3) @(@(P/1)/@(@&P/1)) -> (@&P/4) :call @(P/2) (&@&P+2)*4 @(@&P/5) -> &@&P :shift (@&P/1) :goto accumulate_loop :when @(@&P/1) IN @(P/1) @&@&P -> (P/4) :return :label sum # PTR, INT:previous, INT:value, INT:result :int.add @(P/1) @(P/2) -> (P/3) :coroutine.yield :return :label parity # PTR, PTR:function_params, STR:result :memory INT, BLN -> &P :label parity_loop :coroutine.run "accumulate" :return :unless (@&@(P/1)/5) DEFINED :int.mod @(@&@(P/1)/5) 2 -> &@&P :int.cmp @&@&P = 0 -> (@&P/1) :call even P :when @(@&P/1) TRUE :call odd P :unless @(@&P/1) TRUE :coroutine.yield :goto parity_loop :label even "even" -> (P/2) :return :label odd "odd" -> (P/2) :return END MEMORY nb END .fi %} code: %{ namespace Coroutine { static std::string _prefix = "coroutine:"; std::string flag(const std::string& name) { return _prefix+name; } std::pair stack(const void *svm, SVM_Kernel kernel, const std::string& name) { std::string flag = Coroutine::flag(name); auto level_begin = ::svm_processor_returnstack_find_flag__raw(svm,kernel,flag.c_str(),0); if(level_begin==::svm_processor_returnstack_get_size(svm,kernel)) { std::ostringstream oss; oss << "Coroutine " << name << " does not exist."; ERROR_INTERNAL(FAILURE,oss.str().c_str()); } auto level_end = level_begin; auto level = level_begin; do { level_end = level+1; level = ::svm_processor_returnstack_find_flag__raw(svm,kernel,flag.c_str(),level_end); } while(level!=::svm_processor_returnstack_get_size(svm,kernel)); return std::make_pair(level_begin,level_end); } } %} initialisation: %{ SVM_Value_String rp = ::svm_plugin_get_option(svm,CONST_PEP(coroutine,prefix)); if(not ::svm_value_state_is_null(svm,rp)) { SVM_String sp = ::svm_value_string_get(svm,rp); Coroutine::_prefix = RAW_STRING(sp); } %} DEFINE OPTION coroutine.prefix -p STR help: "Changes the coroutines flag prefix." INSTRUCTION coroutine.call STR:name [ STR SYM ]:function PTR:parameters %{ SVM_Kernel kernel = ::svm_kernel_get_current(svm); SVM_String raw_name = ARGV_VALUE(0,string); std::string name(raw_name.string,raw_name.size); std::string flag = Coroutine::flag(name); auto level = ::svm_processor_returnstack_find_flag__raw(svm,kernel,flag.c_str(),0); if(level!=::svm_processor_returnstack_get_size(svm,kernel)) { std::ostringstream oss; oss << "Coroutine " << name << " already exists."; ERROR_INTERNAL(FAILURE,oss.str().c_str()); } SVM_Value function = ::svm_parameter_value_get(svm,argv[1]); if(::svm_value_type_is_string(svm,function)) { SVM_Address address = ::svm_code_label_get_address(svm,::svm_processor_get_currentcode(svm,kernel),function); ::svm_processor_call_local(svm,kernel,address,::svm_parameter_value_get(svm,argv[2])); } else { ::svm_processor_call_global(svm,kernel,function,::svm_parameter_value_get(svm,argv[2])); } ::svm_processor_set_flag__raw(svm,kernel,flag.c_str(),CASCADE); ::svm_processor_returnstack_swap_level(svm,kernel,0); ::svm_processor_returnstack_move_level(svm,kernel,0,1,::svm_processor_returnstack_get_size(svm,kernel)-1); %} help: %{ Create a named coroutine. The coroutine is placed in the background. .P This instruction raises a FAILURE interruption when a coroutine with the same name already exists. %} INSTRUCTION coroutine.run STR:name %{ SVM_Kernel kernel = ::svm_kernel_get_current(svm); SVM_String raw_name = ARGV_VALUE(0,string); std::string name(raw_name.string,raw_name.size); std::string flag = Coroutine::flag(name); auto coroutine = Coroutine::stack(svm,kernel,name); ::svm_processor_returnstack_swap_level(svm,kernel,coroutine.first); ::svm_processor_returnstack_move_level(svm,kernel,coroutine.first,coroutine.first+1,0); ::svm_processor_returnstack_move_level(svm,kernel,coroutine.first+1,coroutine.second,0); %} help: %{ Resume execution of the named coroutine. .P This instruction raises a FAILURE interruption when a coroutine with the given name does not exist. %} INSTRUCTION coroutine.yield %{ SVM_Kernel kernel = ::svm_kernel_get_current(svm); std::string flag; for(SVM_Value_String *it = ::svm_processor_list_flag(svm,kernel) ; *it ; ++it) { SVM_String raw_f = ::svm_value_string_get(svm,*it); std::string f(raw_f.string,raw_f.size); if(f.substr(0,Coroutine::_prefix.size())==Coroutine::_prefix) { flag = f; break; } } if(not flag.empty()) { ssize_t level_end = -1; auto level = level_end; do { level_end = level+1; level = ::svm_processor_returnstack_find_flag__raw(svm,kernel,flag.c_str(),level_end); } while(level!=::svm_processor_returnstack_get_size(svm,kernel)); ::svm_processor_returnstack_move_level(svm,kernel,0,level_end,::svm_processor_returnstack_get_size(svm,kernel)-level_end); ::svm_processor_returnstack_swap_level(svm,kernel,0); ::svm_processor_returnstack_move_level(svm,kernel,0,1,::svm_processor_returnstack_get_size(svm,kernel)-level_end-1); } %} help: %{ Pause execution of the current coroutine and resume execution of its caller. .P This instruction does nothing when called outside a coroutine. %} INSTRUCTION coroutine.return STR:name %{ SVM_Kernel kernel = ::svm_kernel_get_current(svm); SVM_String raw_name = ARGV_VALUE(0,string); std::string name(raw_name.string,raw_name.size); std::string flag = Coroutine::flag(name); for(SVM_Value_String *it = ::svm_processor_list_flag(svm,kernel) ; *it ; ++it) { SVM_String raw_f = ::svm_value_string_get(svm,*it); std::string f(raw_f.string,raw_f.size); if(f==flag) { ERROR_INTERNAL(FAILURE,"Cannot return a coroutine from itself."); } } auto coroutine = Coroutine::stack(svm,kernel,name); ::svm_processor_returnstack_swap_level(svm,kernel,coroutine.first); ::svm_processor_returnstack_move_level(svm,kernel,coroutine.first,coroutine.first+1,0); ::svm_processor_returnstack_move_level(svm,kernel,coroutine.first+1,coroutine.second,0); for(size_t i=0 ; i<(coroutine.second-coroutine.first) ; ++i) { ::svm_processor_return(svm,kernel); } %} help: %{ Destroy the named coroutine in the background. .P This instruction raises a FAILURE interruption when a coroutine with the given name does not exist, or when this instruction attempts to return the coroutine from itself. .P A coroutine can return by itself using the return instruction. Any subsequent call to such coroutine will fail after it returns. %}