/*! * @file GoalFunctionForms.cpp * Utilities related to functions. */ #include "Goal.h" #include "GoalLambda.h" #include "util.h" #include "logger/Logger.h" /*! * Compile "inline", a form which makes a function call inline if possible, and errors otherwise. */ std::shared_ptr Goal::compile_inline(const Object& form, Object rest, std::shared_ptr env) { (void)env; auto args = goos.get_uneval_args(form, rest, 2); if (args.has_rest || args.unnamed_args.size() < 1 || !args.named_args.empty()) { throw_compile_error(form, "invalid inline"); } auto function_name = args.unnamed_args.front(); if (function_name.type != SYMBOL) { throw_compile_error(form, "invalid inline, must give a symbol"); } auto kv = inlineable_functions.find(function_name.as_symbol()); if (kv == inlineable_functions.end()) { throw_compile_error(form, "couldn't find definition to inline"); } if (kv->second->func && !kv->second->func->settings.allow_inline) { throw_compile_error(form, "not allowed to inline"); } return kv->second; } /*! * Get the preference to inline in the given environment - return false if no preference set. */ static bool get_inline_preference(std::shared_ptr env) { auto inline_env = get_parent_env_of_type(env); if (inline_env) { return inline_env->inline_preference; } else { return false; } } /*! * Compile a real x86 function call helper, */ std::shared_ptr Goal::compile_real_function_call(const Object& form, std::shared_ptr function, std::vector> args, std::shared_ptr env) { TypeSpec return_ts; if (function->type.ts_args.empty()) { // if the type system doesn't know what the function will return, just make it object. // the user is responsible for getting this right. return_ts = get_base_typespec("object"); // gLogger.log(MSG_WARN, "[Warning] Function call could not determine return type: %s\n", // const_cast(form).print().c_str()); // todo, should this be a warning? not a great thing if we don't know what a function will // return? } else { return_ts = function->type.ts_args.front(); } auto return_reg = env->alloc_reg(return_ts); for (auto& arg : args) { // note: this has to be done in here, because we might want to const prop across lexical envs. arg = resolve_to_gpr(arg, env); } // check arg count: if (!function->type.ts_args.empty()) { if (function->type.ts_args.size() - 1 != args.size()) { throw_compile_error(form, "invalid number of arguments to function call: got " + std::to_string(args.size()) + " and expected " + std::to_string(function->type.ts_args.size() - 1)); } for (uint32_t i = 0; i < args.size(); i++) { typecheck_base_only(form, function->type.ts_args.at(i + 1), args.at(i)->type, "function argument"); } } // set args (introducing a move here makes coloring more likely to be possible) std::vector> arg_outs; for (auto& arg : args) { arg_outs.push_back(env->alloc_reg(arg->type)); env->emit(make_unique(arg_outs.back(), arg)); } env->emit( make_unique(env->alloc_reg(function->type), function, return_reg, arg_outs)); return return_reg; } /*! * Compile a function or method call. This includes real function calls, inline function calls, * automatic inline function calls, immediate application of lambda, method calls of basics, and * method calls of structures. */ std::shared_ptr Goal::compile_function_or_method_call(const Object& form, std::shared_ptr env) { Object f = form; // get args in a list auto args = goos.get_uneval_args_no_rest(form, form, 9); // 8 args + function max auto uneval_head = args.unnamed_args.front(); auto head = get_none(); // will hold function object to call // determine if this call should be automatically inlined. // this logic will not trigger for a manually inlined call [using the (inline func) form] bool auto_inline = false; if (uneval_head.type == SYMBOL) { // we can only auto-inline the function if its name is explicit. // look it up: auto kv = inlineable_functions.find(as_symbol_obj(uneval_head)); if (kv != inlineable_functions.end()) { // it's inlinable. However, we do not always inline an inlinable function by default if (kv->second->func == nullptr || // only-inline, we must inline it as there is no code generated for it kv->second->func->settings .inline_by_default || // inline when possible, so we should inline (kv->second->func->settings.allow_inline && get_inline_preference(env))) { // inline is allowed, and we prefer it locally auto_inline = true; head = kv->second; } } } bool is_method_call = false; if (!auto_inline) { // if auto-inlining failed, we must get the thing to call in a different way. if (uneval_head.type == SYMBOL) { if (is_local_symbol(uneval_head, env) || symbol_types.find(as_symbol_obj(uneval_head)) != symbol_types.end()) { // the local environment (mlets, lexicals, constants, globals) defines this symbol. // this will "win" over a method name lookup, so we should compile as normal head = compile_error_guard(args.unnamed_args.front(), env); } else { // we don't think compiling the head give us a function, so it's either a method or an error is_method_call = true; } } else { // the head is some expression. Could be something like (inline my-func) or (-> obj // func-ptr-field) in either case, compile it - and it can't be a method call. head = compile_error_guard(args.unnamed_args.front(), env); } } if (!is_method_call) { // typecheck that we got a function auto f_type = get_base_typespec("function"); if (!head->type.typecheck_base_only(f_type, types)) { throw_compile_error( form, "function call head does not evaluate to a function! " + head->type.print()); } } // compile arguments std::vector> eval_args; for (uint32_t i = 1; i < args.unnamed_args.size(); i++) { auto intermediate = compile_error_guard(args.unnamed_args.at(i), env); eval_args.push_back(resolve_to_gpr_or_xmm(intermediate, env)); } // see if its an "immediate" application. This happens in three cases: // 1). the user directly puts a (lambda ...) form in the head (like with a (let) macro) // 2). the user used a (inline my-func) to grab the LambdaPlace of the function. // 3). the auto-inlining above looked up the LambdaPlace of an inlinable_function. // note that an inlineable function looked up by symbol or other way WILL NOT cast to a // LambdaPlace! so this cast will only succeed if the auto-inliner succeeded, or the user has // passed use explicitly a lambda either with the lambda form, or with the (inline ...) form. std::shared_ptr head_as_lambda = nullptr; if (!is_method_call) { head_as_lambda = std::dynamic_pointer_cast(head); } if (head_as_lambda) { // inline the function! // check args are ok if (head_as_lambda->lambda.params.size() != eval_args.size()) { throw_compile_error(form, "invalid argument count"); } // construct a lexical environment auto lexical_env = std::make_shared(); lexical_env->parent = env; std::shared_ptr compile_env = lexical_env; // if needed create a label env. // we don't want a separate label env with lets, but we do in other cases. if (auto_inline) { // TODO - this misses the case of (inline func)! compile_env = std::make_shared(lexical_env); } // check arg types if (!head->type.ts_args.empty()) { if (head->type.ts_args.size() - 1 != eval_args.size()) { throw_compile_error(form, "invalid number of arguments to function call (inline)"); } for (uint32_t i = 0; i < eval_args.size(); i++) { typecheck_base_only(form, head->type.ts_args.at(i + 1), eval_args.at(i)->type, "function (inline) argument"); } } // copy args... for (uint32_t i = 0; i < eval_args.size(); i++) { auto copy = env->alloc_reg(eval_args.at(i)->type); env->emit(make_unique(copy, eval_args.at(i))); lexical_env->vars[head_as_lambda->lambda.params.at(i).name] = copy; } // compile inline! bool first_thing = true; std::shared_ptr result = get_none(); for_each_in_list(head_as_lambda->lambda.body, [&](Object o) { result = compile_error_guard(o, compile_env); if (first_thing) { first_thing = false; lexical_env->settings.is_set = true; } }); // this doesn't require a return type. return result; } else { // not an inline call if (is_method_call) { // determine the method to call by looking at the type of first argument if (eval_args.empty()) { throw_compile_error(form, "0 argument method call is impossible to figure out"); } head = compile_get_method_of_object(eval_args.front(), symbol_string(uneval_head), env); } // convert the head to a GPR auto head_as_gpr = std::dynamic_pointer_cast(resolve_to_gpr(head, env)); if (head_as_gpr) { return compile_real_function_call(form, head_as_gpr, eval_args, env); } else { throw_compile_error(form, "can't figure out this function call!"); } } throw_compile_error(form, "call_function_or_method unreachable"); return get_none(); } std::shared_ptr Goal::compile_defmethod(const Object& form, Object rest, std::shared_ptr env) { auto args = goos.get_uneval_args(form, rest, 3); if (!args.named_args.empty() || args.unnamed_args.size() != 3) { throw_compile_error(form, "invalid defmethod"); } TypeSpec lambda_ts = get_base_typespec("function"); // temp return typespec lambda_ts.ts_args.push_back(get_base_typespec("none")); // temp for now auto place = std::make_shared(get_none()->type); // Build Lambda Object GoalLambda& lambda = place->lambda; // todo get the correct function type auto arg_name = args.unnamed_args.at(0); auto arg_type = args.unnamed_args.at(1); if (arg_name.type != SYMBOL) { throw_compile_error(form, "defmethod method name must be a symbol"); } if (arg_type.type != SYMBOL) { throw_compile_error(form, "defmethod type name must be a symbol"); } auto body = args.unnamed_args.at(2); if (body.type == EMPTY_LIST) { throw_compile_error(form, "defmethod had an empty body!"); } for_each_in_list(body, [&](Object o) { if (o.type == SYMBOL) { lambda.params.emplace_back(o.as_symbol()->name, get_base_typespec("object")); lambda_ts.ts_args.push_back(get_base_typespec("object")); } else { auto param_args = goos.get_uneval_args(o, o, 3); if (param_args.unnamed_args.size() >= 3 || param_args.unnamed_args.size() < 1 || param_args.has_rest || !param_args.named_args.empty()) { throw_compile_error(o, "invalid lambda parameter"); } GoalLambdaParam parm; if (param_args.unnamed_args.front().type != SYMBOL) { throw_compile_error(o, "invalid lambda parameter"); } parm.name = param_args.unnamed_args.front().as_symbol()->name; if (param_args.unnamed_args.size() >= 2) { parm.type = TypeSpec(param_args.unnamed_args[1], types); // todo improve } else { parm.type = get_base_typespec("object"); } // printf("set arg type to %s\n", parm.type.print().c_str()); if (param_args.unnamed_args.size() >= 3) { parm.default_value = param_args.unnamed_args[2]; parm.has_default = true; } lambda.params.push_back(parm); lambda_ts.ts_args.push_back(parm.type); } }); assert(lambda.params.size() + 1 == lambda_ts.ts_args.size()); if (!args.has_rest) { throw_compile_error(form, "lambda must have a body"); } // skip docstring if (args.rest.as_pair()->car.type == STRING && args.rest.as_pair()->cdr.type != EMPTY_LIST) { args.rest = args.rest.as_pair()->cdr; } lambda.body = args.rest; place->func = nullptr; auto new_func_env = std::make_shared(place->print()); new_func_env->method_of_type_name = arg_type.as_symbol()->name; new_func_env->parent = env; new_func_env->segment = MAIN_SEGMENT; // todo not this // set up arguments assert(lambda.params.size() < 8); // todo, this should be more graceful for (uint32_t i = 0; i < lambda.params.size(); i++) { RegConstraint constr; constr.instr_id = 0; constr.var_id = new_func_env->vars.size(); constr.ass.kind = REGISTER; constr.ass.reg_id = ARG_REGS[i]; new_func_env->params[lambda.params.at(i).name] = new_func_env->alloc_reg(lambda.params.at(i).type); new_func_env->constrain_reg(constr); } place->func = new_func_env; new_func_env->emit(make_unique(place)); auto return_reg = new_func_env->alloc_reg(get_none()->type); auto func_block_env = std::make_shared(new_func_env, "#f"); func_block_env->return_value = return_reg; auto label = std::make_shared