def convert_args_to_waldo(method_node,sequence_local=False): ''' In many cases, am not passing WaldoObjects to arguments being called. (Examples: when a programmer uses a non-reference type that is not external.) Currently, the compiler's invariant however is that all variables should be Waldo objects. As a result, for each argument passed in, we check if it is a Waldo object. If it is not, we turn it into one, (depending on the type it has). Additionally, method caller always passes in the reference to a Waldo object when the method is called with a variable. For example, a = 5; some_method(a); Actually passes the reference to a into the called function. This is fine if a is a reference type (list, map, user struct, function object) or the signature of some_method takes in an external. However, it is not okay for non-external value types. These should be passed by value. For each non-external value type, tell turn_into_waldo_var to force a copy of the variable. @param {bool} sequence_local --- If sequence_local is True, that means that the data must be copied into a peered vriable, regardless of whether or not they are a reference type. ''' arg_node_index = get_arg_index_from_func_node_label(method_node.label) func_decl_arglist_node = method_node.children[arg_node_index] converted_args_string = '' # need to emit two sections of code, decide which to run based on # whether this was an endpoint call or not. The reason that we # make this distinction is that non-external maps, lists, and user # structs passed across endpoint call boundaries are copied, # rather than used by reference. (Across non-endpoint method call # boundaries, they're used by reference.) converted_args_string = 'if _context.check_and_set_from_endpoint_call_false():\n' converted_args_string += emit_utils.indent_str( convert_args_helper(func_decl_arglist_node,sequence_local,True) + '\npass\n') converted_args_string += '\nelse:\n' converted_args_string += emit_utils.indent_str( convert_args_helper(func_decl_arglist_node,sequence_local,False) + '\npass\n') converted_args_string += '\n' return converted_args_string
def emit_single_endpoint(endpoint_name,ast_root,fdep_dict,emit_ctx): ''' @param {String} endpoint_name --- The name of the endpoint we are currently emitting. For all other params + return, @see emit_endpoints Will produce something like the following: def SingleSide(_waldo_classes,_host_uuid,_conn_obj): class _SingleSide (_waldo_classes["Endpoint"]): def __init__(self,_waldo_classes,_host_uuid,_conn_obj): ... return _SingleSide(_waldo_classes,_host_uuid,_conn_obj) The reason that we wrap the class in a function instead of just directly providing the class is so that we do not have to statically link the generated file to any library. Can take in an argument that dynamically tells us how to make the Endpoint inherit from the hidden Waldo _Endpoint. ''' endpoint_factory_func = ( 'def %s (_waldo_classes,_host_uuid,_conn_obj,*args):\n' % endpoint_name) endpoint_header = 'class _%s (_waldo_classes["Endpoint"]):\n' % ( endpoint_name) endpoint_body = emit_endpoint_body( endpoint_name,ast_root,fdep_dict,emit_ctx) endpoint_class = endpoint_header + emit_utils.indent_str(endpoint_body) endpoint_factory_func += emit_utils.indent_str(endpoint_class) endpoint_factory_func += emit_utils.indent_str( 'return _%s(_waldo_classes,_host_uuid,_conn_obj,*args)\n' % endpoint_name) return endpoint_factory_func
def emit_message_receive( message_receive_node,next_to_call_node,endpoint_name,ast_root, fdep_dict,emit_ctx): ''' @param {AstNode or None} next_to_call_node --- If AstNode, then has label AST_MESSAGE_RECEIVE_FUNCTION. If None, means that this is the last message receive node in sequence and should tell other side to issue a block call with target None. ''' msg_recv_node_name_node = message_receive_node.children[1] msg_recv_name = msg_recv_node_name_node.value emit_ctx.in_message_receive = True emit_ctx.message_seq_return_txt = '\nreturn ' msg_receive_txt = emit_private_method_interface( message_receive_node,endpoint_name,ast_root,fdep_dict,emit_ctx, lib_util.partner_endpoint_msg_call_func_name) msg_receive_txt += '\n' emit_ctx.in_message_receive = False emit_ctx.message_seq_return_txt = '' ## Ends by telling the opposite side what to do next next_to_call_txt = 'None' if next_to_call_node != None: # the name of the next sequence block to execute, as it # appears in the source Waldo text, ie, before mangling. next_sequence_block_src_name = next_to_call_node.children[1].value # note that we must wait on receiving a response from the other # side before we can continue on, or the original sender may # return too early. We should not wait on the last message that we # send however, because, we do not expect any response to it. # (Ie, if next_to_call_txt == 'None', then do not wait.) next_to_call_txt = '"' + lib_util.partner_endpoint_msg_call_func_name( next_sequence_block_src_name) + '"' next_sequence_txt = ''' _context.hide_partner_call( self,_active_event,%s,False) ''' % (next_to_call_txt) msg_receive_txt += emit_utils.indent_str(next_sequence_txt) return msg_receive_txt
def emit_endpoint_init( endpoint_name,ast_root,fdep_dict,emit_ctx): ''' For params and return, @see emit_single_endpoint ''' oncreate_argument_string = '' oncreate_node = get_oncreate_node(endpoint_name,ast_root) if oncreate_node != None: oncreate_arg_names = get_method_arg_names(oncreate_node) oncreate_argument_string = reduce ( lambda x, y : x + ',' + y, oncreate_arg_names,'') init_header = ( 'def __init__(self,_waldo_classes,_host_uuid,_conn_obj%s):\n' % oncreate_argument_string) # create endpoint and peered variable store # should initialize variable store before entering into _Endpoint # super class initializer: super class initializer registers the # _Endpoint with connection object. endpoint_global_and_peered_variable_store_load_txt = ( emit_endpoint_global_and_peered_variable_store( endpoint_name,'_host_uuid',ast_root,fdep_dict,emit_ctx)) # actually initialize super class init_body = ''' # a little ugly in that need to pre-initialize _host_uuid, because # code used for initializing variable store may rely on it. (Eg., if # initializing nested lists.) self._waldo_classes = _waldo_classes self._host_uuid = _host_uuid self._global_var_store = %s(_host_uuid) %s %s.__init__(self,_waldo_classes,_host_uuid,_conn_obj,self._global_var_store) ''' % (emit_utils.library_transform('VariableStore'), endpoint_global_and_peered_variable_store_load_txt, emit_utils.library_transform('Endpoint')) # emit call to oncreate method init_body += emit_oncreate_call(endpoint_name,ast_root) return init_header + emit_utils.indent_str(init_body)
def emit_message_receive( message_receive_node,next_to_call_node,endpoint_name,ast_root, fdep_dict,emit_ctx): ''' @param {AstNode or None} next_to_call_node --- If AstNode, then has label AST_MESSAGE_RECEIVE_FUNCTION. If None, means that this is the last message receive node in sequence and should tell other side to issue a block call with target None. ''' msg_recv_node_name_node = message_receive_node.children[1] msg_recv_name = msg_recv_node_name_node.value emit_ctx.in_message_receive = True emit_ctx.message_seq_return_txt = '\nreturn ' msg_receive_txt = emit_private_method_interface( message_receive_node,endpoint_name,ast_root,fdep_dict,emit_ctx, lib_util.partner_endpoint_msg_call_func_name) msg_receive_txt += '\n' emit_ctx.in_message_receive = False emit_ctx.message_seq_return_txt = '' ## Ends by telling the opposite side what to do next next_to_call_txt = 'None' if next_to_call_node != None: # the name of the next sequence block to execute, as it # appears in the source Waldo text, ie, before mangling. next_sequence_block_src_name = next_to_call_node.children[1].value next_to_call_txt = '"' + lib_util.partner_endpoint_msg_call_func_name( next_sequence_block_src_name) + '"' # note that we must wait on receiving a response from the other # side before we can continue on, or the original sender may # return too early. We should not wait on the last message that we # send however, because, we do not expect any response to it. # (Ie, if next_to_call_txt == 'None', then do not wait.) next_sequence_txt = ''' _threadsafe_queue = %s.Queue() _active_event.issue_partner_sequence_block_call( _context,%s,_threadsafe_queue,False) # must wait on the result of the call before returning if %s != None: # means that we have another sequence item to execute next _queue_elem = _threadsafe_queue.get() ''' % (emit_utils.library_transform('Queue'), next_to_call_txt, next_to_call_txt) next_sequence_txt += ''' if isinstance(_queue_elem,%s): # back everything out raise %s() _context.set_to_reply_with(_queue_elem.reply_with_msg_field) # apply changes to sequence variables. Note: that # the system has already applied deltas for global data. _context.sequence_local_store.incorporate_deltas( _active_event,_queue_elem.sequence_local_var_store_deltas) # send more messages _to_exec_next = _queue_elem.to_exec_next_name_msg_field if _to_exec_next != None: # means that we do not have any additional functions to exec _to_exec = getattr(self,_to_exec_next) _to_exec(_active_event,_context) ''' % (emit_utils.library_transform('BackoutBeforeReceiveMessageResult'), emit_utils.library_transform('BackoutException')) msg_receive_txt += emit_utils.indent_str(next_sequence_txt) return msg_receive_txt
def emit_message_send( message_send_node,next_to_call_node,seq_globals_node, endpoint_name,ast_root, fdep_dict,emit_ctx): ''' @param {AstNode} seq_globals_node --- Has label AST_MESSAGE_SEQUENCE_GLOBALS....In message send, we initialize the values of all sequence global variables. Use this to do that. ''' method_arg_names = get_method_arg_names(message_send_node) seq_local_init_prefix = ''' _first_msg = False if not _context.set_msg_send_initialized_bit_true(): # we must load all arguments into sequence local data and perform # initialization on sequence local data....start by loading # arguments into sequence local data # below tells the message send that it must serialize and # send all sequence local data. _first_msg = True ''' seq_local_init_prefix += emit_utils.indent_str( convert_args_to_waldo(message_send_node,True)) # now emit the sequence global initializations and declarations # (this will also emit for the return nodes). seq_local_init_prefix += emit_utils.indent_str(emit_statement.emit_statement( seq_globals_node,endpoint_name,ast_root,fdep_dict,emit_ctx)) seq_local_init_prefix += emit_utils.indent_str('\npass\n') seq_local_init_prefix += '\n' # when message send ends, it must grab the sequence local data # requested to return. To control for jumps, any time we jump, we # take whatever text is in emit_ctx's message_seq_return_txt and # insert it. (For a message send function, this will return # sequence local data. For a message receive function, this will # just be a return.) This way, can insure that do not continue # executing after jump. return_var_name_nodes = get_message_send_return_var_names( message_send_node) msg_send_return_txt = '\nreturn ' for counter in range(0,len(return_var_name_nodes)): var_name_node = return_var_name_nodes[counter] msg_send_return_txt += ( '_context.sequence_local_store.get_var_if_exists("%s")' % var_name_node) if counter != (len(return_var_name_nodes) -1): msg_send_return_txt += ',' emit_ctx.in_message_send = True emit_ctx.message_seq_return_txt = msg_send_return_txt # a message send function should look the same as a private # internal method (if we name it a little # differently...name_mangler arg takes care of this; and we # keep track of the return statement to issue on jump calls) msg_send_txt = emit_private_method_interface( message_send_node,endpoint_name,ast_root,fdep_dict,emit_ctx, lib_util.partner_endpoint_msg_call_func_name,seq_local_init_prefix) # issue call for what to call next msg_send_txt += '\n' msg_send_txt += emit_utils.indent_str( emit_message_node_what_to_call_next(next_to_call_node,emit_ctx)) # takes care of fall-through return (ie, message send has # completed) msg_send_txt += emit_utils.indent_str(msg_send_return_txt) msg_send_txt += '\n' emit_ctx.in_message_send = False emit_ctx.message_seq_return_txt = '' return msg_send_txt
def emit_public_method_interface( public_method_node,endpoint_name,ast_root,fdep_dict,emit_ctx): ''' @param {AstNode} public_method_node --- An AstNode with label AST_PUBLIC_FUNCTION ''' method_name_node = public_method_node.children[0] method_name = method_name_node.value method_arg_names = get_method_arg_names(public_method_node) # turns the array of argnames above into a single string of csv # arg names comma_sep_arg_names = reduce ( lambda x, y : x + ',' + y, method_arg_names,'') public_header = ''' def %s(self%s): ''' % (method_name, comma_sep_arg_names) # wait until ready initialization for node has completed before # continuing public_body = ''' # ensure that both sides have completed their onCreate calls # before continuing self._block_ready() ''' #### Deep copy non-external args # non_ext_arg_names is an array of strings non_ext_arg_names = get_non_external_arg_names_from_func_node( public_method_node) # do not need to copy arguments in: each function call does so on # its own. # Each element in this list is an index for a return parameter # that should be de-waldo-ified before returning. We pass this # argument to the internal function that the internal function # knows which returns need to be de-waldo-ified before being # returned and which do not. list_return_external_positions = ( get_external_return_positions_from_func_node(public_method_node)) #### create a root event + ctx for event, call internal, and reurn internal_method_name = lib_util.endpoint_call_func_name(method_name) public_body += ''' while True: # FIXME: currently using infinite retry _root_event = self._act_event_map.create_root_event() _ctx = %s( self._global_var_store, # not using sequence local store %s(self._host_uuid)) # call internal function... note True as last param tells internal # version of function that it needs to de-waldo-ify all return # arguments (while inside transaction) so that this method may # return them....if it were false, might just get back refrences # to Waldo variables, and de-waldo-ifying them outside of the # transaction might return over-written/inconsistent values. _to_return = self.%s(_root_event,_ctx %s,%s) # try committing root event _root_event.request_commit() _commit_resp = _root_event.event_complete_queue.get() if isinstance(_commit_resp,%s): # means it isn't a backout message: we're done return _to_return ''' % (emit_utils.library_transform('ExecutingEventContext'), emit_utils.library_transform('VariableStore'), internal_method_name, comma_sep_arg_names, str(list_return_external_positions), emit_utils.library_transform('CompleteRootCallResult')) return public_header + emit_utils.indent_str(public_body)
def emit_private_method_interface( method_node,endpoint_name,ast_root,fdep_dict,emit_ctx, name_mangler=lib_util.endpoint_call_func_name,prefix=None): ''' @param {AstNode} method_node --- Either a public method node or a private method node. If it's a public method, then we emit the internal method that gets called from the public interface of the method. We emit private versions of public methods for two reasons: 1: The public versions of the methods do some very basic bookkeeping (copy args in, check for backout and retry, etc.) 2: When issuing an endpoint call, endpoint call gets issued to private part of function, which does most of the work. @param {Function} name_mangler --- Takes in a string and returns a string. The compiler uses different internal names for functions than appear in the Waldo source text. This is so that users are less likely to accidentally call into unsafe functions. This function should translate the name of the function from the source to an internal function. @param {String or None} prefix --- If we are emitting a message send function, there are some initialization operations that it must do. Instead of copying the function arguments, we use the string provided in prefix. Also can be called to emit a message send or message receive function. ''' name_node_index = 0 if emit_utils.is_message_sequence_node(method_node): name_node_index = 1 method_name_node = method_node.children[name_node_index] src_method_name = method_name_node.value internal_method_name = name_mangler(src_method_name) # When returning, check if it was a call from an outside-Waldo # function into this function... if it was (ie, # _returning_to_public_ext_array is not None), then we must # de-waldo-ify the return values before returning. The values of # _returning_to_public_ext_array correspond to which return values # should be returned as externals (ie, we do not de-waldo-ify # them). method_arg_names = ['_returning_to_public_ext_array=None'] if method_node.label == AST_MESSAGE_SEND_SEQUENCE_FUNCTION: # will fill in the default value of None in reduce method_arg_names = ['_returning_to_public_ext_array'] if method_node.label != AST_MESSAGE_RECEIVE_SEQUENCE_FUNCTION: # message receives take no arguments method_arg_names = get_method_arg_names(method_node) + method_arg_names if method_node.label != AST_MESSAGE_SEND_SEQUENCE_FUNCTION: comma_sep_arg_names = reduce ( lambda x, y : x + ',' + y, method_arg_names,'') else: # provide default values for all argument sequence local data. # That way, jumps are easier (we do not have to match # of # arguments when jumping). comma_sep_arg_names = reduce ( lambda x, y : x + ',' + y + '=None', method_arg_names,'') private_header = ''' def %s(self,_active_event,_context%s): ''' % (internal_method_name, comma_sep_arg_names) # actually emit body of function private_body = '\n' if method_node.label != AST_MESSAGE_RECEIVE_SEQUENCE_FUNCTION: if prefix == None: private_body = convert_args_to_waldo(method_node) else: # we are in a message send function: this means that # instead of copying in args, we must test if we've # already been initialized (to handle jumps properly). # Similarly, we must initialize other sequence global # data. private_body = prefix method_body_node = get_method_body_node_from_method_node(method_node) emitted_something = False for statement_node in method_body_node.children: emitted_something = True private_body += emit_statement.emit_statement( statement_node,endpoint_name,ast_root,fdep_dict,emit_ctx) private_body += '\n' if private_body.strip() == '': # in case of empty functions private_body += 'pass\n' return private_header + emit_utils.indent_str(private_body)