def return_command_implementation(f: Function): with location(f"at {term.yellow(str(f.name))}", f.location): # pthread_mutex_lock(&nw_handler_lock); # took_lock = 1; generate_requires(not f.return_value.type.buffer or f.return_value.type.lifetime != Expr("AVA_CALL"), "Returned buffers must have a lifetime other than `call' (i.e., must be annotated with `ava_lifetime_static', `ava_lifetime_coupled', or `ava_lifetime_manual').") return f""" case {f.ret_id_spelling}: {{\ {timing_code_guest("before_unmarshal", str(f.name), f.generate_timing_code)} ava_is_in = 0; ava_is_out = 1; struct {f.ret_spelling}* __ret = (struct {f.ret_spelling}*)__cmd; assert(__ret->base.api_id == {f.api.number_spelling}); assert(__ret->base.command_size == sizeof(struct {f.ret_spelling}) && "Command size does not match ID. (Can be caused by incorrectly computed buffer sizes, especially using `strlen(s)` instead of `strlen(s)+1`)"); struct {f.call_record_spelling}* __local = (struct {f.call_record_spelling}*)ava_remove_call(&__ava_endpoint, __ret->__call_id); {{ {unpack_struct("__local", f.arguments, "->")} \ {unpack_struct("__local", f.logue_declarations, "->")} \ {unpack_struct("__ret", [f.return_value], "->", convert=get_buffer_expr) if not f.return_value.type.is_void else ""} \ {lines(copy_result_for_argument(a, "__local", "__ret") for a in f.arguments if a.type.contains_buffer)} {copy_result_for_argument(f.return_value, "__local", "__ret") if not f.return_value.type.is_void else ""}\ {lines(f.epilogue)} {lines(deallocate_managed_for_argument(a, "__local") for a in f.arguments)} }} {timing_code_guest("after_unmarshal", str(f.name), f.generate_timing_code)} __local->__call_complete = 1; if(__local->__handler_deallocate) {{ free(__local); }} break; }} """.strip()
def function_wrapper(f: Function) -> str: """ Generate a wrapper function for f which takes the arguments of the function, executes the "logues", and calls the function. :param f: A function. :return: A C static function definition. """ with location(f"at {term.yellow(str(f.name))}", f.location): if f.return_value.type.is_void: declare_ret = "" capture_ret = "" return_statement = "return;" else: declare_ret = f"{f.return_value.type.nonconst.attach_to(f.return_value.name)};" capture_ret = f"{f.return_value.name} = " return_statement = f"return {f.return_value.name};" if f.disable_native: # This works for both normal functions and callbacks because the # difference between the two is in the call, which is not emitted in # this case anyway. capture_ret = "" call_code = "" callback_unpack = "" elif not f.callback_decl: # Normal call call_code = f"""{f.name}({", ".join(a.name for a in f.real_arguments)})""" callback_unpack = "" else: # Indirect call (callback) try: userdata_arg, = [a for a in f.arguments if a.userdata] except ValueError: generate_requires( False, "ava_callback_decl function must have exactly one argument annotated with " "ava_userdata.") call_code = f"""__target_function({", ".join(a.name for a in f.real_arguments)})""" callback_unpack = f""" {f.type.attach_to("__target_function")}; __target_function = {f.type.cast_type(f"((struct ava_callback_user_data*){userdata_arg.name})->function_pointer")}; {userdata_arg.name} = ((struct ava_callback_user_data*){userdata_arg.name})->userdata; """ return f""" static {f.return_value.type.spelling} __wrapper_{f.name}({", ".join(a.declaration for a in f.arguments)}) {{ {callback_unpack}\ {lines(a.declaration + ";" for a in f.logue_declarations)}\ {lines(f.prologue)}\ {{ {declare_ret} {capture_ret}{call_code}; {lines(f.epilogue)} /* Report resources */ {lines(report_alloc_resources(arg) for arg in f.arguments)} {report_alloc_resources(f.return_value)} {report_consume_resources(f)} {return_statement} }} }} """.strip()
def attach_for_argument(arg: Argument, dest): """ Copy arg into dest attaching buffers as needed. :param arg: The argument to copy. :param dest: The destination CALL struct. :return: A series of C statements. """ alloc_list = AllocList(arg.function) def copy_for_value(values, type: Type, depth, argument, original_type=None, **other): if isinstance(type, ConditionalType): return Expr(type.predicate).if_then_else( copy_for_value(values, type.then_type, depth, argument, original_type=type.original_type, **other), copy_for_value(values, type.else_type, depth, argument, original_type=type.original_type, **other)) arg_value, cmd_value = values def attach_data(data): return attach_buffer(cmd_value, arg_value, data, type, arg.input, cmd=dest, original_type=original_type, expect_reply=True) def simple_buffer_case(): if not hasattr(type, "pointee"): return """abort_with_reason("Reached code to handle buffer in non-pointer type.");""" return (Expr(arg_value).not_equals("NULL") & (Expr(type.buffer) > 0)).if_then_else( attach_data(arg_value), f"{cmd_value} = NULL;") def buffer_case(): if not hasattr(type, "pointee"): return """abort_with_reason("Reached code to handle buffer in non-pointer type.");""" if not arg.input: return simple_buffer_case() tmp_name = f"__tmp_{arg.name}_{depth}" size_name = f"__size_{arg.name}_{depth}" loop = for_all_elements((arg_value, tmp_name), type, depth=depth, argument=argument, precomputed_size=size_name, original_type=original_type, **other) return (Expr(arg_value).not_equals("NULL") & (Expr(type.buffer) > 0)).if_then_else( f""" {allocate_tmp_buffer(tmp_name, size_name, type, alloc_list=alloc_list, original_type=original_type)} {loop} {attach_data(tmp_name)} """, f"{cmd_value} = NULL;" ) def default_case(): return Expr(not type.is_void).if_then_else( f"{cmd_value} = {arg_value};", """abort_with_reason("Reached code to handle void value.");""") if type.fields: return for_all_elements(values, type, depth=depth, argument=argument, original_type=original_type, **other) return type.is_simple_buffer(allow_handle=True).if_then_else( simple_buffer_case, Expr(type.transfer).equals("NW_BUFFER").if_then_else( buffer_case, default_case ) ).scope() with location(f"at {term.yellow(str(arg.name))}", arg.location): userdata_code = "" if arg.userdata and not arg.function.callback_decl: try: callback_arg, = [a for a in arg.function.arguments if a.type.transfer == "NW_CALLBACK"] except ValueError: generate_requires(False, "If ava_userdata is applied to an argument exactly one other argument " "must be annotated with ava_callback.") generate_requires([arg] == [a for a in arg.function.arguments if a.userdata], "Only one argument on a given function can be annotated with ava_userdata.") userdata_code = f""" if ({callback_arg.param_spelling} != NULL) {{ // TODO:MEMORYLEAK: This leaks 2*sizeof(void*) whenever a callback is transported. Should be fixable // with "coupled buffer" framework. struct ava_callback_user_data *__callback_data = malloc(sizeof(struct ava_callback_user_data)); __callback_data->userdata = {arg.param_spelling}; __callback_data->function_pointer = (void*){callback_arg.param_spelling}; {arg.param_spelling} = __callback_data; }} """ return comment_block( f"Input: {arg}", Expr(userdata_code).then( copy_for_value((arg.param_spelling, f"{dest}->{arg.param_spelling}"), arg.type, depth=0, argument=arg, name=arg.name, kernel=copy_for_value, only_complex_buffers=False, self_index=0)))