diff --git a/src/thorin/be/c/c.cpp b/src/thorin/be/c/c.cpp index 0dc8e9d71..724ca2e52 100644 --- a/src/thorin/be/c/c.cpp +++ b/src/thorin/be/c/c.cpp @@ -93,6 +93,7 @@ class CCodeGen : public thorin::Emitter std::string emit_constant(const Def*); std::string emit_bottom(const Type*); std::string emit_def(BB*, const Def*); + void emit_call(BB& bb, const Def* callee, ArrayRef); void emit_access(Stream&, const Type*, const Def*, const std::string_view& = "."); bool is_valid(const std::string& s) { return !s.empty(); } std::string emit_fun_head(Continuation*, bool = false); @@ -109,13 +110,18 @@ class CCodeGen : public thorin::Emitter std::string constructor_prefix(const Type*); std::string device_prefix(); Stream& emit_debug_info(Stream&, const Def*); + const Type* mangle_return_type(const ReturnType* return_type); bool get_interface(HlsInterface &interface, HlsInterface &gmem); + const Param* get_channel_read_output(Continuation*); template std::string emit_float(T, IsInfFn, IsNanFn); std::string array_name(const DefiniteArrayType*); std::string tuple_name(const TupleType*); + std::string closure_name(const ClosureType*); + std::string return_name(const ReturnType*); + std::string fn_name(const FnType*); Thorin& thorin_; ScopesForest forest_; @@ -549,17 +555,11 @@ static inline bool is_passed_via_buffer(const Param* param) { || param->type()->isa(); } -static inline const Type* ret_type(const FnType* fn_type) { - auto ret_fn_type = (*std::find_if( - fn_type->types().begin(), fn_type->types().end(), [] (const Type* op) { - return op->order() % 2 == 1; - }))->as(); - std::vector types; - for (auto op : ret_fn_type->types()) { - if (op->isa() || is_type_unit(op) || op->order() > 0) continue; - types.push_back(op); - } - return fn_type->world().tuple_type(types); +const Type* CCodeGen::mangle_return_type(const ReturnType* return_type) { + // treat non-returning calls as if they return nothing, for now + if(!return_type) + return world().unit_type(); + return return_type->mangle_for_codegen(); } static inline const Type* pointee_or_elem_type(const PtrType* ptr_type) { @@ -705,6 +705,44 @@ void CCodeGen::finalize(Continuation* cont) { func_impls_.fmt("{{\t\n{}{}{}\b\n}}\b\n", bb.head.str(), bb.body.str(), bb.tail.str()); } +const Param* CCodeGen::get_channel_read_output(Continuation* cont) { + size_t num_params = cont->num_params(); + size_t n = 0; + Array values(num_params); + for (auto param : cont->params()) { + if (!is_mem(param) && !is_unit(param)) { + values[n] = param; + n++; + } + } + return n == 1 ? values[0] : nullptr; +} + +void CCodeGen::emit_call(BB& bb, const Def* callee, ArrayRef args) { + if (auto cont = callee->isa_nom()) { + auto& scope = forest_.get_scope(entry_); + // Use goto syntax when calling within the local scope + // (and it's not recursion) + if (scope.contains(cont) && cont != entry_) { + assert(cont->num_params() == args.size()); + for (size_t i = 0, size = cont->num_params(); i != size; ++i) { + if (auto arg = args[i]; !arg.empty()) + bb.tail.fmt("p_{} = {};\n", cont->param(i)->unique_name(), arg); + } + bb.tail.fmt("goto {};", label_name(cont)); + return; + } + } + + auto ecallee = emit(callee); + if (auto closure_t = callee->type()->isa()) { + auto appended = concat(args, ecallee); + auto as_fnt = world().fn_type(concat(closure_t->types(), closure_t->as())); + bb.tail.fmt("(({}) {}.f)({, });", convert(as_fnt), ecallee, appended); + } else + bb.tail.fmt("{}({, });", ecallee, args); +} + void CCodeGen::emit_epilogue(Continuation* cont) { auto&& bb = cont2bb_[cont]; assert(cont->has_body()); @@ -716,7 +754,7 @@ void CCodeGen::emit_epilogue(Continuation* cont) { if ((lang_ == Lang::OpenCL || (lang_ == Lang::HLS && hls_top_scope)) && (cont->is_exported())) emit_fun_decl(cont); - if (body->callee() == entry_->ret_param()) { // return + if (body->callee()->type()->isa()) { // return std::vector values; std::vector types; @@ -727,17 +765,29 @@ void CCodeGen::emit_epilogue(Continuation* cont) { } } + std::string emitted_return; + switch (values.size()) { - case 0: bb.tail.fmt(lang_ == Lang::HLS ? "return void();" : "return;"); break; - case 1: bb.tail.fmt("return {};", values[0]); break; + case 0: emitted_return = lang_ == Lang::HLS ? "void()" : ""; break; + case 1: emitted_return = values[0]; break; default: auto tuple = convert(world().tuple_type(types)); bb.tail.fmt("{} ret_val;\n", tuple); for (size_t i = 0, e = types.size(); i != e; ++i) bb.tail.fmt("ret_val.e{} = {};\n", i, values[i]); - bb.tail.fmt("return ret_val;"); + emitted_return = "ret_val"; break; } + + if (body->callee() == entry_->ret_param()) { + // local return + if (emitted_return.empty()) + bb.tail.fmt("return;"); + else + bb.tail.fmt("return {};", emitted_return); + } else { + assert(false && "TODO: implement capturing returns"); + } } else if (body->callee() == world().branch()) { emit_unsafe(body->arg(0)); auto c = emit(body->arg(1)); @@ -828,93 +878,99 @@ void CCodeGen::emit_epilogue(Continuation* cont) { } else { THORIN_UNREACHABLE; } - } else if (auto callee = body->callee()->isa_nom()) { // function/closure call - auto ret_cont = (*std::find_if(body->args().begin(), body->args().end(), [] (const Def* arg) { - return arg->isa_nom(); - }))->as_nom(); + } else { // function/closure call + auto callee_type = body->callee()->type()->as(); + int ret_param = callee_type->ret_param_index(); + const Def* ret = nullptr; + if (ret_param >= 0) + ret = body->arg(ret_param); std::vector args; for (auto arg : body->args()) { - if (arg == ret_cont) continue; + if (arg == ret) continue; if (auto emitted_arg = emit_unsafe(arg); !emitted_arg.empty()) args.emplace_back(emitted_arg); } - size_t num_params = ret_cont->num_params(); - size_t n = 0; - Array values(num_params); - Array types(num_params); - for (auto param : ret_cont->params()) { - if (!is_mem(param) && !is_unit(param)) { - values[n] = param; - types[n] = param->type(); - n++; - } - } - - const Param* channel_read_result = n == 1 ? values[0] : nullptr; - bool channel_transaction = false, no_function_call = false; - auto name = (callee->is_exported() || callee->empty()) ? callee->name() : callee->unique_name(); - if (lang_ == Lang::OpenCL && use_channels_ && callee->is_channel()) { - auto [usage, _] = builtin_funcs_.emplace(callee, FuncMode::Read); - - if (name.find("write") != std::string::npos) { - usage->second = FuncMode::Write; - } else if (name.find("read") != std::string::npos) { - usage->second = FuncMode::Read; - assert(channel_read_result != nullptr); - args.emplace(args.begin(), emit(channel_read_result)); - } else THORIN_UNREACHABLE; - channel_transaction = true; - } else if (lang_ == Lang::HLS && callee->is_channel()) { - int i = 0; - for (auto arg : body->args()) { - if (!is_concrete(arg)) continue; - if (i == 0) - bb.tail.fmt("*{}", emit(arg)); - if (i == 1) { - if (name.find("write_channel") != std::string::npos) { - bb.tail.fmt(" << {};\n", emit(arg)); - } else THORIN_UNREACHABLE; - } - if (name.find("read_channel") != std::string::npos) { - bb.tail.fmt(" >> {};\n", emit(channel_read_result)); + if (auto known_callee = body->callee()->isa_nom()) { + auto name = (known_callee->is_exported() || known_callee->empty()) ? known_callee->name() : known_callee->unique_name(); + if (lang_ == Lang::OpenCL && use_channels_ && known_callee->is_channel()) { + auto [usage, _] = builtin_funcs_.emplace(known_callee, FuncMode::Read); + + if (name.find("write") != std::string::npos) { + usage->second = FuncMode::Write; + } else if (name.find("read") != std::string::npos) { + usage->second = FuncMode::Read; + auto channel_read_result = get_channel_read_output(ret->as_nom()->continuation()); + assert(channel_read_result != nullptr); + args.emplace(args.begin(), emit(channel_read_result)); + } else + THORIN_UNREACHABLE; + channel_transaction = true; + } else if (lang_ == Lang::HLS && known_callee->is_channel()) { + int i = 0; + for (auto arg: body->args()) { + if (!is_concrete(arg)) continue; + if (i == 0) + bb.tail.fmt("*{}", emit(arg)); + if (i == 1) { + if (name.find("write_channel") != std::string::npos) { + bb.tail.fmt(" << {};\n", emit(arg)); + } else + THORIN_UNREACHABLE; + } + if (name.find("read_channel") != std::string::npos) + bb.tail.fmt(" >> {};\n", emit(get_channel_read_output(ret->as_nom()->continuation()))); + i++; } - i++; + no_function_call = true; + //TODO: Check it + channel_transaction = true; } - no_function_call = true; - //TODO: Check it - channel_transaction = true; } // Do not store the result of `void` calls - auto ret_type = thorin::c::ret_type(callee->type()); + auto ret_type = mangle_return_type(callee_type->return_param_type()); if (!is_type_unit(ret_type) && !channel_transaction) bb.tail.fmt("{} ret_val = ", convert(ret_type)); - if (!no_function_call) - bb.tail.fmt("{}({, });\n", emit(callee), args); - - // Pass the result to the phi nodes of the return continuation - if (!is_type_unit(ret_type)) { - size_t i = 0; - for (auto param : ret_cont->params()) { - if (!is_concrete(param)) - continue; - if (ret_type->isa()) - bb.tail.fmt("p_{} = ret_val.e{};\n", param->unique_name(), i++); - else if ((lang_ == Lang::OpenCL && use_channels_) || (lang_ == Lang::HLS)) - bb.tail.fmt(" p_{} = {};\n", emit(channel_read_result), param->unique_name()); - else - bb.tail.fmt("p_{} = ret_val;\n", param->unique_name()); + if (!no_function_call) { + emit_call(bb, body->callee(), args); + } + + if (auto ret_pt = ret->isa()) { + // Pass the result to the phi nodes of the return continuation + if (!is_type_unit(ret_type)) { + size_t i = 0; + for (auto param: ret_pt->continuation()->params()) { + if (!is_concrete(param)) + continue; + bb.tail.fmt("\n"); + if (ret_type->isa()) + bb.tail.fmt("p_{} = ret_val.e{};", param->unique_name(), i++); + else if ((lang_ == Lang::OpenCL && use_channels_) || (lang_ == Lang::HLS)) + bb.tail.fmt(" p_{} = {};", emit(get_channel_read_output(ret_pt->continuation())), + param->unique_name()); + else + bb.tail.fmt("p_{} = ret_val;", param->unique_name()); + } } + + if (!hls_top_scope) { + bb.tail.fmt("\ngoto {};", label_name(ret_pt->continuation())); + } + } else if (ret && ret == entry_->ret_param()) { + // TODO: tail call annotations ? + if (!is_type_unit(ret_type)) { + bb.tail.fmt("\n"); + bb.tail.fmt("return ret_val;"); + } + } else { + assert(!ret); + // TODO: dummy return statements ? } - if (!hls_top_scope) - bb.tail.fmt("goto {};", label_name(ret_cont)); - } else { - THORIN_UNREACHABLE; } } @@ -1413,13 +1469,17 @@ std::string CCodeGen::emit_fun_head(Continuation* cont, bool is_proto) { } s.fmt("{} {}(", - convert(ret_type(cont->type())), + convert(mangle_return_type(cont->type()->return_param_type())), !world().is_external(cont) ? cont->unique_name() : cont->name()); // Emit and store all first-order params bool needs_comma = false; for (size_t i = 0, n = cont->num_params(); i < n; ++i) { auto param = cont->param(i); + if (lang_ == Lang::C99 && param->type()->isa()) { + defs_[param] = "&return_buf"; + continue; + } if (!is_concrete(param)) { defs_[param] = {}; continue; @@ -1613,6 +1673,18 @@ std::string CCodeGen::tuple_name(const TupleType* tuple_type) { return "tuple_" + std::to_string(tuple_type->gid()); } +std::string CCodeGen::fn_name(const FnType* fn_type) { + return "fn_" + std::to_string(fn_type->gid()); +} + +std::string CCodeGen::closure_name(const ClosureType* fn_type) { + return "closure_" + std::to_string(fn_type->gid()); +} + +std::string CCodeGen::return_name(const ReturnType* fn_type) { + return "return_" + std::to_string(fn_type->gid()); +} + //------------------------------------------------------------------------------ void CodeGen::emit_stream(std::ostream& stream) { diff --git a/src/thorin/be/llvm/llvm.cpp b/src/thorin/be/llvm/llvm.cpp index 4f7d80451..28f87458f 100644 --- a/src/thorin/be/llvm/llvm.cpp +++ b/src/thorin/be/llvm/llvm.cpp @@ -141,40 +141,46 @@ llvm::Type* CodeGen::convert(const Type* type) { case Node_ClosureType: case Node_FnType: { // extract "return" type, collect all other types - auto fn = type->as(); - llvm::Type* ret = nullptr; + auto tfn_type = type->as(); + llvm::Type* ret = llvm::Type::getVoidTy(context()); // top-level non-returning continuations should be void std::vector ops; - for (auto op : fn->types()) { + for (auto op : tfn_type->domain()) { if (op->isa() || op == world().unit_type()) continue; - auto fn = op->isa(); - if (fn && !op->isa()) { - assert(!ret && "only one 'return' supported"); - std::vector ret_types; - for (auto fn_op : fn->types()) { - if (fn_op->isa() || fn_op == world().unit_type()) continue; - ret_types.push_back(convert(fn_op)); - } - if (ret_types.size() == 0) ret = llvm::Type::getVoidTy(context()); - else if (ret_types.size() == 1) ret = ret_types.back(); - else ret = llvm::StructType::get(context(), ret_types); - } else - ops.push_back(convert(op)); + ops.push_back(convert(op)); } - if (!ret) { - ret = llvm::Type::getVoidTy(context()); + if (tfn_type->is_returning()) { + auto ret_ty = tfn_type->return_param_type(); + assert(ret_ty); + std::vector ret_types; + for (auto fn_op : ret_ty->types()) { + if (fn_op->isa() || fn_op == world().unit_type()) continue; + ret_types.push_back(convert(fn_op)); + } + if (ret_types.size() == 0) ret = llvm::Type::getVoidTy(context()); + else if (ret_types.size() == 1) ret = ret_types.back(); + else ret = llvm::StructType::get(context(), ret_types); } + assert(ret); + if (type->tag() == Node_FnType) { auto llvm_type = llvm::FunctionType::get(ret, ops, false); return types_[type] = llvm_type; } auto env_type = convert(Closure::environment_type(world())); - auto ptr_type = llvm::PointerType::get(context(), 0); + ops.push_back(env_type); + auto fn_type = llvm::FunctionType::get(ret, ops, false); + auto ptr_type = llvm::PointerType::get(fn_type, 0); llvm_type = llvm::StructType::get(context(), { ptr_type, env_type }); return types_[type] = llvm_type; } + case Node_ReturnType: { + auto ret_t = type->as(); + auto tuple_t = world().tuple_type({ ret_t->mangle_for_codegen(), world().definite_array_type(world().type_qu8(), 200) }); + return types_[type] = convert(world().ptr_type(tuple_t)); + } case Node_StructType: { auto struct_type = type->as(); @@ -427,6 +433,9 @@ llvm::Function* CodeGen::prepare(const Scope& scope) { discope_ = disub_program; } + has_alloca_ = false; + potential_tailcalls_.clear(); + return fct; } @@ -478,6 +487,10 @@ void CodeGen::finalize(const Scope&) { if (auto variant = def->isa(); variant && !variant->value()->has_dep(Dep::Param)) to_remove.push_back(def); } + if (!has_alloca_) for (auto call : potential_tailcalls_) { + call->setTailCall(true); + // call->setTailCallKind(llvm::CallInst::TCK_MustTail); + } for (auto& def : to_remove) defs_.erase(def); } @@ -522,13 +535,15 @@ llvm::CallInst* CodeGen::emit_call(llvm::IRBuilder<>& irbuilder, const Def* call } } return nullptr; + } else if (auto return_point = callee->isa()) { // for direct-style calls, just forward to the destination + return emit_call(irbuilder, return_point->continuation(), args); } else if (callee->isa()) { irbuilder.CreateUnreachable(); return nullptr; } else if (auto cont = callee->isa_nom(); cont && scope_->contains(cont) && cont != entry_) { assert(cont->is_basicblock()); size_t j = 0, i = 0; - for (auto t: cont->type()->types()) { + for (auto t: cont->type()->domain()) { i++; assert(t->order() == 0); if (t->isa() || t == world().unit_type()) @@ -601,7 +616,7 @@ void CodeGen::emit_epilogue(Continuation* continuation) { } } else if (auto callee = body->callee()->isa_nom(); callee && callee->is_intrinsic()) { auto args = emit_intrinsic(irbuilder, continuation); - call_instr = emit_call(irbuilder, body->arg(callee->ret_param()->index()), args); + call_instr = emit_call(irbuilder, body->arg(callee->type()->ret_param_index()), args); } else { // plain continuation call: we can just emit all the arguments std::vector args; @@ -617,11 +632,11 @@ void CodeGen::emit_epilogue(Continuation* continuation) { } call_instr = emit_call(irbuilder, body->callee(), args); - if (body->callee()->type()->as()->is_returning() && !body->callee()->isa()) { + if (auto codom = body->callee()->type()->as()->codomain()) { assert(call_instr && "returning calls always involve one of those"); assert(ret_arg && "we need a return argument too!"); - auto ret_args = split_values(irbuilder, ret_arg->type()->as()->types(), call_instr); + auto ret_args = split_values(irbuilder, *codom, call_instr); call_instr = emit_call(irbuilder, ret_arg, ret_args); } } @@ -629,7 +644,7 @@ void CodeGen::emit_epilogue(Continuation* continuation) { if (call_instr) { // we need to add a dummy return terminator if the last instruction is a call if (entry_->type()->is_returning()) { - auto entry_return_t = mangle_for_codegen(world(), entry_->ret_param()->type()->as()->types()); + auto entry_return_t = entry_->type()->return_param_type()->mangle_for_codegen(); if (entry_return_t != world().unit_type()) { irbuilder.CreateRet(llvm::UndefValue::get(convert(entry_return_t))); } else @@ -1071,6 +1086,7 @@ llvm::AllocaInst* CodeGen::emit_alloca(llvm::IRBuilder<>& irbuilder, llvm::Type* else alloca = new llvm::AllocaInst(type, layout.getAllocaAddrSpace(), nullptr, name, entry->getFirstNonPHIOrDbg()); alloca->setAlignment(layout.getABITypeAlign(type)); + has_alloca_ = true; return alloca; } @@ -1429,10 +1445,10 @@ llvm::Value* CodeGen::emit_reserve_shared(llvm::IRBuilder<>& irbuilder, const Co if (!body->arg(1)->isa()) world().edef(body->arg(1), "reserve_shared: couldn't extract memory size"); auto num_elems = body->arg(1)->as()->ps32_value(); - auto cont = body->arg(2)->as_nom(); - auto type = convert(cont->param(1)->type()); + auto cont_t = body->arg(2)->type()->as(); + auto type = convert(cont_t->domain()[1]); // construct array type - auto elem_type = cont->param(1)->type()->as()->pointee()->as()->elem_type(); + auto elem_type = cont_t->domain()[1]->as()->pointee()->as()->elem_type(); auto smem_type = this->convert(continuation->world().definite_array_type(elem_type, num_elems)); auto name = continuation->unique_name(); // NVVM doesn't allow '.' in global identifier diff --git a/src/thorin/be/llvm/llvm.h b/src/thorin/be/llvm/llvm.h index 7d5414331..da288c17f 100644 --- a/src/thorin/be/llvm/llvm.h +++ b/src/thorin/be/llvm/llvm.h @@ -137,6 +137,8 @@ class CodeGen : public thorin::CodeGen, public thorin::Emitter runtime_; + bool has_alloca_; + std::vector potential_tailcalls_; #if THORIN_ENABLE_RV std::vector> vec_todo_; #endif diff --git a/src/thorin/be/spirv/spirv.cpp b/src/thorin/be/spirv/spirv.cpp index 931546ea0..c3b262ffd 100644 --- a/src/thorin/be/spirv/spirv.cpp +++ b/src/thorin/be/spirv/spirv.cpp @@ -218,15 +218,9 @@ static bool is_return_block(thorin::Continuation* cont) { for (auto use : cont->copy_uses()) { if (use.def()->isa()) continue; // the block can have params - else if (auto app = use.def()->isa()) { - if (auto callee = app->callee()->isa_nom()) { - auto arg_index = use.index() - App::FirstArg; - auto ret_param = callee->ret_param(); - if (ret_param && arg_index == ret_param->index()) { - uses_as_ret_param++; - continue; - } - } + if (use.def()->isa()) { + uses_as_ret_param++; + continue; // the block can be returned to (once) } return false; // any other use disqualifies the block } @@ -336,15 +330,19 @@ Id CodeGen::emit_as_bb(thorin::Continuation* cont) { return cont2bb_[cont]->label; } -void CodeGen::emit_epilogue(Continuation* continuation) { - if (!continuation->has_body()) - return; - - BasicBlockBuilder* bb = cont2bb_[continuation]; +void CodeGen::emit_jump(BasicBlockBuilder* bb, const Def* to, std::vector args) { + if (auto ret_point = to->isa()) { + to = ret_point->continuation(); + } - // Handles the potential nuances of jumping to another continuation - auto jump_to_next_cont_with_args = [&](Continuation* succ, std::vector args) { - assert(succ->is_basicblock()); + if (to == entry_->ret_param()) { + switch (args.size()) { + case 0: bb->terminator.return_void(); break; + case 1: bb->terminator.return_value(args[0]); break; + default: bb->terminator.return_value(emit_composite(bb, builder_->current_fn_->fn_ret_type, args)); + } + } else if (auto succ = to->isa_nom(); succ && scope_->contains(succ)) { + // local BB jump BasicBlockBuilder* dstbb = cont2bb_[succ]; for (size_t i = 0, j = 0; i != succ->num_params(); ++i) { @@ -359,48 +357,37 @@ void CodeGen::emit_epilogue(Continuation* continuation) { defs_[param] = args[j]; } else { auto& phi = cont2bb_[succ]->phis_map[param]; - phi.preds.emplace_back(args[j], emit_as_bb(continuation)); + phi.preds.emplace_back(args[j], bb->label); } j++; } bb->terminator.branch(emit(succ)); - }; + } +} - auto& app = *continuation->body(); +void CodeGen::emit_epilogue(Continuation* continuation) { + if (!continuation->has_body()) + return; - if (app.callee() == entry_->ret_param()) { - std::vector values; + BasicBlockBuilder* bb = cont2bb_[continuation]; + auto& app = *continuation->body(); + // calls to intrinsics involve passing basic blocks, which aren't first-class values + // however this isn't encoded in their type (fn[...]), and establishing whether they are is hard + // instead we can just lazily emit the arguments and deal with the special-case control-flow intrinsics first + auto emit_args = [&]() { + std::vector args; for (auto arg : app.args()) { - assert(arg->order() == 0); if (!should_emit(arg->type())) { emit_unsafe(arg); continue; } - auto val = emit(arg); - values.emplace_back(val); + args.emplace_back(emit(arg)); } + return args; + }; - switch (values.size()) { - case 0: bb->terminator.return_void(); break; - case 1: bb->terminator.return_value(values[0]); break; - default: bb->terminator.return_value(emit_composite(bb, builder_->current_fn_->fn_ret_type, values)); - } - } else if (auto dst_cont = app.callee()->isa_nom(); dst_cont && dst_cont->is_basicblock()) { // ordinary jump - int index = -1; - for (auto& arg : app.args()) { - index++; - if (!should_emit(arg->type())) { - emit_unsafe(arg); - continue; - } - auto val = emit(arg); - auto* param = dst_cont->param(index); - auto& phi = cont2bb_[dst_cont]->phis_map[param]; - phi.preds.emplace_back(val, emit_as_bb(continuation)); - } - bb->terminator.branch(emit(dst_cont)); - } else if (app.callee() == world().branch()) { + if (app.callee() == world().branch()) { auto mem = app.arg(0); emit_unsafe(mem); @@ -428,31 +415,15 @@ void CodeGen::emit_epilogue(Continuation* continuation) { emit_unsafe(app.arg(0)); auto productions = emit_intrinsic(app, intrinsic, bb); - auto succ = app.args().back()->isa_nom(); - jump_to_next_cont_with_args(succ, productions); - } else { // function/closure call - // put all first-order args into an array - std::vector call_args; - const Def* ret_arg = nullptr; - for (auto arg : app.args()) { - if (arg->order() == 0) { - auto arg_type = arg->type(); - if (arg_type == world().unit_type() || arg_type == world().mem_type()) { - emit_unsafe(arg); - continue; - } - auto arg_val = emit(arg); - call_args.push_back(arg_val); - } else { - assert(!ret_arg); - ret_arg = arg; - } - } + emit_jump(bb, app.ret_arg(), productions); + } else if (auto codom = app.callee_type()->codomain()) { // function/closure call + auto args = emit_args(); Id call_result; if (auto called_continuation = app.callee()->isa_nom()) { + // TODO: fn calls auto ret_type = get_codom_type(called_continuation->type()); - call_result = bb->call(ret_type, emit(called_continuation), call_args); + call_result = bb->call(ret_type, emit(called_continuation), args); } else { // must be a closure THORIN_UNREACHABLE; @@ -462,35 +433,33 @@ void CodeGen::emit_epilogue(Continuation* continuation) { // call = irbuilder.CreateCall(irbuilder.CreateExtractValue(closure, 0), args); } - // must be call + continuation --- call + return has been removed by codegen_prepare - auto succ = ret_arg->isa_nom(); - - size_t real_params_count = 0; - const Param* last_param = nullptr; - for (auto param : succ->params()) { - if (!should_emit(param->type())) + // count the number of params the return point will have + size_t return_values_count = 0; + for (auto param_type : *codom) { + if (!should_emit(param_type)) continue; - last_param = param; - real_params_count++; + return_values_count++; } - std::vector args(real_params_count); - - if (real_params_count == 1) { - args[0] = call_result; - } else if (real_params_count > 1) { - for (size_t i = 0, j = 0; i != succ->num_params(); ++i) { - auto param = succ->param(i); - if (!should_emit(param->type())) + // map the single return value to the possibly multiple params of the return point + std::vector return_values(return_values_count); + if (return_values_count == 1) { + return_values[0] = call_result; + } else if (return_values_count > 1) { + for (size_t i = 0, j = 0; i != codom->size(); ++i) { + auto param_type = (*codom)[i]; + if (!should_emit(param_type)) continue; - args[j] = bb->extract(convert(param->type()).id, call_result, { (uint32_t) j }); + return_values[j] = bb->extract(convert(param_type).id, call_result, { (uint32_t) j }); j++; } - - bb->terminator.branch(emit(succ)); } - jump_to_next_cont_with_args(succ, args); + emit_jump(bb, app.ret_arg(), return_values); + } else { + // return, tailcall or BB call + auto args = emit_args(); + emit_jump(bb, app.callee(), args); } } @@ -522,22 +491,13 @@ Id CodeGen::emit_constant(const thorin::Def* def) { } } return constant; + } else if (auto rp = def->isa()) { + return emit(rp->continuation()); } assertf(false, "Incomplete emit(def) definition"); } -bool CodeGen::should_emit(const thorin::Type* type) { - if (type == world().mem_type()) - return false; - if (auto fn_t = type->isa()) - return fn_t->is_returning(); - auto converted = convert_maybe_void(type); - if (converted.id == builder_->declare_void_type()) - return false; - return true; -} - std::vector CodeGen::emit_args(Defs defs) { std::vector emitted; for (auto arg : defs) { diff --git a/src/thorin/be/spirv/spirv.h b/src/thorin/be/spirv/spirv.h index cae51c7c6..4402b1f5a 100644 --- a/src/thorin/be/spirv/spirv.h +++ b/src/thorin/be/spirv/spirv.h @@ -84,6 +84,8 @@ class CodeGen : public thorin::CodeGen, public thorin::Emitter); Id emit_ptr_bitcast(BasicBlockBuilder* bb, const PtrType* from, const PtrType* to, Id); + void emit_jump(BasicBlockBuilder* bb, const Def* to, std::vector args); + std::tuple, Id> get_dom_codom(const FnType* fn); Id get_codom_type(const FnType*); diff --git a/src/thorin/be/spirv/spirv_types.cpp b/src/thorin/be/spirv/spirv_types.cpp index c40f94406..aecb5b5e6 100644 --- a/src/thorin/be/spirv/spirv_types.cpp +++ b/src/thorin/be/spirv/spirv_types.cpp @@ -47,28 +47,41 @@ Id CodeGen::get_codom_type(const FnType* fn) { return codom; } +bool CodeGen::should_emit(const thorin::Type* type) { + if (type == world().mem_type()) + return false; + if (type->isa()) + return false; + auto converted = convert_maybe_void(type); + if (converted.id == builder_->declare_void_type()) + return false; + return true; +} + std::tuple, Id> CodeGen::get_dom_codom(const FnType* fn) { - Id ret = 0; std::vector ops; - for (auto op : fn->types()) { - auto fn_type = op->isa(); - if (fn_type && !op->isa()) { - assert(!ret && "only one 'return' supported"); - std::vector ret_types; - for (auto fn_op : fn_type->types()) { - if (!should_emit(fn_op)) - continue; - ret_types.push_back(fn_op); - } - if (ret_types.size() == 1) - ret = convert_maybe_void(ret_types.back()).id; - else - ret = convert_maybe_void(world().tuple_type(ret_types)).id; - } else if (!should_emit(op)) + for (auto op : fn->domain()) { + if (!should_emit(op)) continue; else ops.push_back(convert(op).id); } + + Id ret; + if (auto codom = fn->codomain()) { + std::vector ret_types; + for (auto fn_op : *codom) { + if (!should_emit(fn_op)) + continue; + ret_types.push_back(fn_op); + } + if (ret_types.size() == 1) + ret = convert_maybe_void(ret_types.back()).id; + else + ret = convert_maybe_void(world().tuple_type(ret_types)).id; + } else { + ret = convert_maybe_void(world().unit_type()).id; + } return std::make_tuple(ops, ret); } diff --git a/src/thorin/continuation.cpp b/src/thorin/continuation.cpp index 1632708b1..eb253af12 100644 --- a/src/thorin/continuation.cpp +++ b/src/thorin/continuation.cpp @@ -61,6 +61,22 @@ bool App::verify() const { //------------------------------------------------------------------------------ +ReturnPoint::ReturnPoint(thorin::World& world, const thorin::Continuation* destination, thorin::Debug dbg) : Def(world, Node_ReturnPoint, world.return_type(destination->type()->types()), {destination }, dbg) {} + +const Def* ReturnPoint::rebuild(thorin::World& world, const thorin::Type*, thorin::Defs nops) const { + auto def = nops.front(); + // TODO: have some kind of generalised mechanism to obtain the 'real' def + while (auto run = def->isa()) { + def = run->def(); + } + while (auto closure = def->isa()) { + def = closure->fn(); + } + return world.return_point(def->as()); +} + +//------------------------------------------------------------------------------ + Filter::Filter(World& world, const Defs defs, Debug dbg) : Def(world, Node_Filter, world.bottom_type(), defs, dbg) {} const Filter* Filter::cut(ArrayRef indices) const { @@ -145,14 +161,8 @@ const Param* Continuation::ret_param() const { return nullptr; default: break; } - const Param* result = nullptr; - for (auto param : params()) { - if (param->order() >= 1) { - assertf(is_intrinsic() || result == nullptr, "only one ret_param allowed"); - result = param; - } - } - return result; + int ret_param = type()->ret_param_index(); + return (ret_param > 0) ? param(ret_param) : nullptr; } void Continuation::destroy(const char* cause) { diff --git a/src/thorin/continuation.h b/src/thorin/continuation.h index bdedec64f..226a45d8f 100644 --- a/src/thorin/continuation.h +++ b/src/thorin/continuation.h @@ -66,9 +66,15 @@ class App : public Def { }; const Def* callee() const { return op(Ops::Callee); } + const FnType* callee_type() const { return callee()->type()->as(); } const Def* arg(size_t i) const { return op(Ops::FirstArg + i); } size_t num_args() const { return num_ops() - Ops::FirstArg; } const Defs args() const { return ops().skip_front(Ops::FirstArg); } + const Def* ret_arg() const { + if (auto index = callee_type()->ret_param_index(); index >= 0) + return arg(index); + return nullptr; + } const Def* rebuild(World&, const Type*, Defs) const override; Continuations using_continuations() const { @@ -86,6 +92,16 @@ class App : public Def { friend class World; }; +class ReturnPoint : public Def { +private: + ReturnPoint(World&, const Continuation* destination, Debug dbg); + +public: + const Def* rebuild(World&, const Type*, Defs) const override; + Continuation* continuation() const { return op(0)->as_nom(); } + friend class World; +}; + //------------------------------------------------------------------------------ enum class CC : uint8_t { @@ -147,7 +163,6 @@ class Continuation : public Def { private: Continuation(World&, const FnType* pi, const Attributes& attributes, Debug dbg); - virtual ~Continuation() { for (auto param : params()) delete param; } public: const FnType* type() const { return Def::type()->as(); } diff --git a/src/thorin/rec_stream.cpp b/src/thorin/rec_stream.cpp index 590693e31..96f2adfb1 100644 --- a/src/thorin/rec_stream.cpp +++ b/src/thorin/rec_stream.cpp @@ -195,6 +195,8 @@ Stream& Type::stream(Stream& s) const { return s.fmt("[{} x {}]", t->dim(), t->elem_type()); } else if (auto t = isa()) { return s.fmt("closure [{, }]", t->ops()); + } else if (auto t = isa()) { + return s.fmt("return[{, }]", t->ops()); } else if (auto t = isa()) { return s.fmt("fn[{, }]", t->ops()); } else if (auto t = isa()) { diff --git a/src/thorin/tables/nodetable.h b/src/thorin/tables/nodetable.h index 8c17d04dc..a295b2959 100644 --- a/src/thorin/tables/nodetable.h +++ b/src/thorin/tables/nodetable.h @@ -45,13 +45,15 @@ THORIN_NODE(Assembly, asm) THORIN_NODE(Param, param) THORIN_NODE(Filter, filter) + THORIN_NODE(App, app) + THORIN_NODE(ReturnPoint, return) // Type // PrimType THORIN_NODE(Star, star) - THORIN_NODE(App, app) THORIN_NODE(DefiniteArrayType, definite_array_type) THORIN_NODE(FnType, fn) THORIN_NODE(ClosureType, closure_type) + THORIN_NODE(ReturnType, return_type) THORIN_NODE(FrameType, frame) THORIN_NODE(IndefiniteArrayType, indefinite_array_type) THORIN_NODE(Lambda, lambda) diff --git a/src/thorin/transform/cleanup_world.cpp b/src/thorin/transform/cleanup_world.cpp index 65a860e48..139e48bd6 100644 --- a/src/thorin/transform/cleanup_world.cpp +++ b/src/thorin/transform/cleanup_world.cpp @@ -105,7 +105,8 @@ void Cleaner::eliminate_params() { if (ocontinuation->has_body() && !world().is_external(ocontinuation)) { auto obody = ocontinuation->body(); for (auto use : ocontinuation->uses()) { - if (use.index() != 0 || !use->isa_nom()) + bool is_call = use->isa_nom() && use.index() == App::Ops::Callee; + if (!is_call) goto next_continuation; } diff --git a/src/thorin/transform/codegen_prepare.cpp b/src/thorin/transform/codegen_prepare.cpp index e8218a571..78d380d27 100644 --- a/src/thorin/transform/codegen_prepare.cpp +++ b/src/thorin/transform/codegen_prepare.cpp @@ -7,28 +7,35 @@ namespace thorin { struct CodegenPrepare : public Rewriter { CodegenPrepare(World& src, World& dst) : Rewriter(src, dst) {} + DefMap wrappers; + Continuation* make_wrapper(const Def* old_return_param) { assert(old_return_param); + if (auto found = wrappers.lookup(old_return_param)) + return *found; auto npi = instantiate(old_return_param->type())->as(); npi = dst().fn_type(npi->types()); auto wrapper = dst().continuation(npi, old_return_param->debug()); + wrappers[old_return_param] = wrapper; return wrapper; } const Def* rewrite(const Def* odef) override { if (auto app = odef->isa()) { auto new_ops = Array(app->num_args(), [&](size_t i) -> const Def* { - auto oarg = app->arg(i); - if (auto oparam = oarg->isa()) { - if (oparam == oparam->continuation()->ret_param()) { - auto wrapped = make_wrapper(oarg); - insert(oarg, wrapped); - auto imported_param = instantiate(oparam->continuation())->as_nom()->ret_param(); + auto op = app->arg(i); + if (op->isa() && op->type()->isa()) { + // because wrappers bodies need the rewritten ret param, they need to be created lazily + // otherwise, rewriting the param would cause the whole scope to be rewritten first, before we get a chance to put anything in the map + auto wrapped = make_wrapper(op); + if (!(wrapped)->has_body()) { + auto imported_param = instantiate(app->arg(i)); wrapped->jump(imported_param, wrapped->params_as_defs(), imported_param->debug()); - return wrapped; } + return dst().return_point(wrapped); + } else { + return instantiate(app->arg(i)); } - return instantiate(app->arg(i)); }); return dst().app(instantiate(app->callee()), new_ops); } @@ -37,6 +44,7 @@ struct CodegenPrepare : public Rewriter { }; /// this pass makes sure the return param is only called directly, by eta-expanding any uses where it appears in another position +// TODO: this effectively prevents tail-calls, this shouldn't run if the backend supports tail-calls void codegen_prepare(Thorin& thorin) { thorin.world().VLOG("start codegen_prepare"); auto& src = thorin.world(); diff --git a/src/thorin/transform/flatten_tuples.cpp b/src/thorin/transform/flatten_tuples.cpp index 8187b1001..d29c4a4c1 100644 --- a/src/thorin/transform/flatten_tuples.cpp +++ b/src/thorin/transform/flatten_tuples.cpp @@ -20,7 +20,7 @@ static const Type* wrapped_type(const FnType* fn_type, size_t max_tuple_size) { nops.push_back(arg); } else nops.push_back(op); - } else if (auto op_fn_type = op->isa()) { + } else if (auto op_fn_type = op->isa(); op_fn_type && op_fn_type->tag() == NodeTag::Node_FnType) { nops.push_back(wrapped_type(op_fn_type, max_tuple_size)); } else { nops.push_back(op); @@ -98,7 +98,7 @@ static Continuation* wrap_def(Def2Def& wrapped, Def2Def& unwrapped, const Def* o call_args[i + 1] = world.tuple(tuple_args); } else call_args[i + 1] = new_cont->param(j++); - } else if (auto fn_type = op->isa()) { + } else if (auto fn_type = op->isa(); fn_type && fn_type->tag() == NodeTag::Node_FnType) { auto fn_param = new_cont->param(j++); // no need to unwrap if the types are identical if (fn_param->type() != op) @@ -149,7 +149,7 @@ static Continuation* unwrap_def(Def2Def& wrapped, Def2Def& unwrapped, const Def* call_args[j++] = world.extract(param, k); } else call_args[j++] = param; - } else if (auto fn_type = param->type()->isa()) { + } else if (auto fn_type = param->type()->isa(); fn_type && fn_type->tag() == NodeTag::Node_FnType) { auto new_fn_type = new_type->op(j - 1)->as(); // no need to wrap if the types are identical if (fn_type != new_fn_type) diff --git a/src/thorin/transform/importer.cpp b/src/thorin/transform/importer.cpp index 2bebb9d07..e3ebac57d 100644 --- a/src/thorin/transform/importer.cpp +++ b/src/thorin/transform/importer.cpp @@ -31,7 +31,7 @@ const Def* Importer::rewrite(const Def* const odef) { } else if (auto closure = odef->isa()) { bool only_called = true; for (auto use : closure->uses()) { - if (use.def()->isa() && use.index() == 0) + if (use.def()->isa() && use.index() == App::Ops::Callee) continue; only_called = false; break; @@ -95,7 +95,7 @@ const Def* Importer::rewrite(const Def* const odef) { // permute the arguments and call the parameter instead for (auto use : cont->copy_uses()) { auto uapp = use->isa(); - if (uapp && use.index() == 0) { + if (uapp && use.index() == App::Ops::Callee) { todo_ = true; has_calls = true; break; diff --git a/src/thorin/type.cpp b/src/thorin/type.cpp index 837d6ff2d..773914b20 100644 --- a/src/thorin/type.cpp +++ b/src/thorin/type.cpp @@ -55,6 +55,7 @@ const Type* BottomType ::rebuild(World& w, const Type* , Defs ) const const Type* ClosureType ::rebuild(World& w, const Type* , Defs o) const { return w.closure_type(defs2types(o)); } const Type* DefiniteArrayType ::rebuild(World& w, const Type* , Defs o) const { return w.definite_array_type(o[0]->as(), dim()); } const Type* FnType ::rebuild(World& w, const Type* , Defs o) const { return w.fn_type(defs2types(o)); } +const Type* ReturnType ::rebuild(World& w, const Type* t, Defs o) const { return w.return_type(defs2types(o)); } const Type* FrameType ::rebuild(World& w, const Type* , Defs ) const { return w.frame_type(); } const Type* IndefiniteArrayType::rebuild(World& w, const Type* , Defs o) const { return w.indefinite_array_type(o[0]->as()); } const Type* MemType ::rebuild(World& w, const Type* , Defs ) const { return w.mem_type(); } @@ -86,20 +87,51 @@ const VectorType* VectorType::scalarize() const { return world().prim_type(as()->primtype_tag()); } -bool FnType::is_returning() const { - bool ret = false; - for (auto op : ops()) { - switch (op->order()) { - case 1: - if (!ret) { - ret = true; - continue; - } - return false; - default: continue; +const ReturnType* FnType::return_param_type() const { + auto i = ret_param_index(); + if (i < 0) + return nullptr; + return op(i)->as(); +} + +const Type* ReturnType::mangle_for_codegen() const { + // treat non-returning calls as if they return nothing, for now + std::vector types; + for (auto op: this->types()) { + assert(op->order() == 0); + if (op->isa() || is_type_unit(op)) continue; + types.push_back(op); + } + return world().tuple_type(types); +} + +Array FnType::domain() const { + auto r = ret_param_index(); + Array dom(r < 0 ? num_ops() : num_ops() - 1); + int j = 0; + for (int i = 0; i < num_ops(); i++) { + if (i == r) continue; + dom[j++] = op(i)->as(); + } + return dom; +} + +std::optional> FnType::codomain() const { + if (auto ret_t = return_param_type()) + return std::make_optional(ret_t->domain()); + return std::nullopt; +} + +int FnType::ret_param_index() const { + int p = -1; + for (unsigned int i = num_ops() - 1; i < num_ops(); i--) { + if (op(i)->isa()) { + // this also does not work for schemes like exceptions etc where multiple 'returns' are valid + assert(p == -1 && "only one return parameter allowed"); + p = i; } } - return ret; + return p; } bool VariantType::has_payload() const { @@ -169,6 +201,7 @@ const PtrType* World::ptr_type(const Type* pointee, size_t length, AddrSpace add const FnType* World::fn_type(Types args) { return make(*this, types2defs(args), Node_FnType, Debug()); } const ClosureType* World::closure_type(Types args) { return make(*this, types2defs(args), Debug()); } +const ReturnType* World::return_type(Types args) { return make(*this, types2defs(args), Debug()); } const DefiniteArrayType* World::definite_array_type(const Type* elem, u64 dim) { return make(*this, elem, dim, Debug()); } const IndefiniteArrayType* World::indefinite_array_type(const Type* elem) { return make(*this, elem, Debug()); } diff --git a/src/thorin/type.h b/src/thorin/type.h index 8d69b4aa2..387ba143c 100644 --- a/src/thorin/type.h +++ b/src/thorin/type.h @@ -267,6 +267,8 @@ inline bool is_thin(const Type* type) { return type->isa() || type->isa() || is_type_unit(type); } +class ReturnType; + class FnType : public Type, public TypeOpsMixin { protected: FnType(World& world, Defs ops, NodeTag tag, Debug dbg) @@ -277,7 +279,12 @@ class FnType : public Type, public TypeOpsMixin { public: bool is_basicblock() const { return order() == 1; } - bool is_returning() const; + bool is_returning() const { return ret_param_index() >= 0; } + const ReturnType* return_param_type() const; + int ret_param_index() const; + + Array domain() const; + std::optional> codomain() const; private: const Type* rebuild(World&, const Type*, Defs) const override; @@ -304,6 +311,17 @@ class ClosureType : public FnType { friend class World; }; +class ReturnType : public FnType { +private: + ReturnType(World& world, Defs ops, Debug dbg) : FnType(world, ops, Node_ReturnType, dbg) {} + +public: + const Type* rebuild(World&, const Type*, Defs) const override; + const Type* mangle_for_codegen() const; + + friend class World; +}; + //------------------------------------------------------------------------------ class ArrayType : public Type, public TypeOpsMixin { diff --git a/src/thorin/world.cpp b/src/thorin/world.cpp index 5c3339685..06ea85a5e 100644 --- a/src/thorin/world.cpp +++ b/src/thorin/world.cpp @@ -1154,6 +1154,14 @@ const Filter* World::filter(const Defs defs, Debug dbg) { /// App node does its own folding during construction, and it only sets the ops once const App* World::app(const Def* callee, const Defs args, Debug dbg) { + while (true) { + if (auto ret = callee->isa()) { + callee = ret->continuation(); + continue; + } + break; + } + if (auto continuation = callee->isa()) { switch (continuation->intrinsic()) { // See also mangle::instantiate when modifying this. @@ -1185,14 +1193,32 @@ const App* World::app(const Def* callee, const Defs args, Debug dbg) { } } - Array ops(1 + args.size()); - ops[0] = callee; + Array ops(App::Ops::FirstArg + args.size()); + ops[App::Ops::Callee] = callee; for (size_t i = 0; i < args.size(); i++) - ops[i + 1] = args[i]; + ops[App::Ops::FirstArg + i] = args[i]; return cse(new App(*this, ops, dbg)); } +const Def* World::return_point(const thorin::Continuation* destination, thorin::Debug dbg) { + // We need a slightly different flavor of eta-conversion here + // regular eta-conversion will not turn `cont() { ret(...) }` into `ret` because the types wouldn't match + // but we're wrapping the cont here so we can do just that! + if (destination->has_body()) { + auto dbody = destination->body(); + assert(dbody->callee() != destination); + if (dbody->callee()->type()->isa() && dbody->args() == destination->params_as_defs()) { + Scope s((Continuation*) destination); + if (!s.contains(dbody->callee())) { + VLOG("simplify: return_point with continuation {} just returns to another one {}", destination->unique_name(), dbody->callee()); + return dbody->callee(); + } + } + } + return cse(new ReturnPoint(*this, destination, dbg)); +} + /* * misc */ @@ -1291,6 +1317,12 @@ const Def* World::cse_base(const Def* def) { return def; } +World::~World() { + for (const Def* def : data_.defs_) { + delete def; + } +} + /* * optimizations */ diff --git a/src/thorin/world.h b/src/thorin/world.h index 4b77d8072..26e746c3d 100644 --- a/src/thorin/world.h +++ b/src/thorin/world.h @@ -79,6 +79,8 @@ class World : public Streamable { state_ = other.state_; } + ~World(); + /// @name manage global identifier - a unique number for each Def //@{ //u32 cur_gid() const { return state_.cur_gid; } @@ -122,6 +124,7 @@ class World : public Streamable { const FnType* fn_type() { return fn_type({}); } ///< Returns an empty @p FnType. const FnType* fn_type(Types args); const ClosureType* closure_type(Types args); + const ReturnType* return_type(Types args); const DefiniteArrayType* definite_array_type(const Type* elem, u64 dim); const IndefiniteArrayType* indefinite_array_type(const Type* elem); @@ -273,6 +276,7 @@ class World : public Streamable { Continuation* match(const Type* type, size_t num_patterns); Continuation* end_scope() const { return data_.end_scope_; } const App* app(const Def* callee, const Defs args, Debug dbg = {}); + const Def* return_point(const Continuation* destination, Debug dbg = {}); const Filter* filter(const Defs, Debug dbg = {}); // getters