diff --git a/cli/main.cpp b/cli/main.cpp index d7aa2e1736..37e23c2157 100644 --- a/cli/main.cpp +++ b/cli/main.cpp @@ -1,16 +1,15 @@ #include #include -#include #include #include #include #include "thorin/config.h" -#include "thorin/dialects.h" #include "thorin/driver.h" #include "thorin/be/dot/dot.h" +#include "thorin/be/h/bootstrap.h" #include "thorin/fe/parser.h" #include "thorin/pass/optimize.h" #include "thorin/pass/pass.h" @@ -30,10 +29,10 @@ int main(int argc, char** argv) { Driver driver; bool show_help = false; bool show_version = false; - bool list_dialect_paths = false; + bool list_search_paths = false; std::string input, prefix; std::string clang = sys::find_cmd("clang"); - std::vector plugins, dialect_paths; + std::vector plugins, search_paths; std::vector breakpoints; std::array output; int verbose = 0; @@ -45,14 +44,14 @@ int main(int argc, char** argv) { auto cli = lyra::cli() | lyra::help(show_help) | lyra::opt(show_version )["-v"]["--version" ]("Display version info and exit.") - | lyra::opt(list_dialect_paths )["-l"]["--list-dialect-paths"]("List search paths in order and exit.") + | lyra::opt(list_search_paths )["-l"]["--list-search-paths" ]("List search paths in order and exit.") | lyra::opt(clang, "clang" )["-c"]["--clang" ]("Path to clang executable (default: '" THORIN_WHICH " clang').") - | lyra::opt(plugins, "dialect")["-d"]["--dialect" ]("Dynamically load dialect [WIP].") - | lyra::opt(dialect_paths, "path" )["-D"]["--dialect-path" ]("Path to search dialects in.") + | lyra::opt(plugins, "dialect")["-d"]["--dialect" ]("Dynamically load plugin.") + | lyra::opt(search_paths, "path" )["-D"]["--dialect-path" ]("Path to search for plugins.") | lyra::opt(inc_verbose )["-V"]["--verbose" ]("Verbose mode. Multiple -V options increase the verbosity. The maximum is 4.").cardinality(0, 4) | lyra::opt(opt, "level" )["-O"]["--optimize" ]("Optimization level (default: 2).") | lyra::opt(output[Dot ], "file" ) ["--output-dot" ]("Emits the Thorin program as a graph using Graphviz' DOT language.") - | lyra::opt(output[H ], "file" ) ["--output-h" ]("Emits a header file to be used to interface with a dialect in C++.") + | lyra::opt(output[H ], "file" ) ["--output-h" ]("Emits a header file to be used to interface with a plugin in C++.") | lyra::opt(output[LL ], "file" ) ["--output-ll" ]("Compiles the Thorin program to LLVM.") | lyra::opt(output[Md ], "file" ) ["--output-md" ]("Emits the input formatted as Markdown.") | lyra::opt(output[Thorin], "file" )["-o"]["--output-thorin" ]("Emits the Thorin program again.") @@ -80,10 +79,11 @@ int main(int argc, char** argv) { std::exit(EXIT_SUCCESS); } - for (auto&& path : dialect_paths) driver.add_search_path(path); + for (auto&& path : search_paths) driver.add_search_path(path); - if (list_dialect_paths) { - for (auto&& path : driver.search_paths()) std::cout << path << std::endl; + if (list_search_paths) { + for (auto&& path : driver.search_paths() | std::views::drop(1)) // skip first empty path + std::cout << path << std::endl; std::exit(EXIT_SUCCESS); } @@ -107,8 +107,9 @@ int main(int argc, char** argv) { } } - // we always need core and mem, as long as we are not in bootstrap mode.. - if (!os[H]) plugins.insert(plugins.end(), {"core", "mem", "compile", "opt"}); + // we always need core and mem, as long as we are not in bootstrap mode + flags.bootstrap = os[H]; + if (!flags.bootstrap) plugins.insert(plugins.end(), {"core", "mem", "compile", "opt"}); if (!plugins.empty()) for (const auto& plugin : plugins) driver.load(plugin); @@ -117,22 +118,16 @@ int main(int argc, char** argv) { if (input[0] == '-' || input.substr(0, 2) == "--") throw std::invalid_argument("error: unknown option " + input); - std::ifstream ifs(input); - if (!ifs) { - errln("error: cannot read file '{}'", input); - return EXIT_FAILURE; - } - - for (const auto& plugin : plugins) fe::Parser::import_module(world, world.sym(plugin)); - - auto sym = world.sym(std::move(input)); - world.set(sym); - fe::Parser parser(world, sym, ifs, os[Md]); - parser.parse_module(); + auto path = fs::path(input); + world.set(path.filename().replace_extension().string()); + auto parser = fe::Parser(world); + parser.import(input, os[Md]); - if (os[H]) { - parser.bootstrap(*os[H]); + if (auto h = os[H]) { + bootstrap(driver, world.sym(fs::path{path}.filename().replace_extension().string()), *h); opt = std::min(opt, 1); + } else { + parser.import("opt"); } // clang-format off diff --git a/cmake/Thorin.cmake b/cmake/Thorin.cmake index a40b288954..a4b72df919 100644 --- a/cmake/Thorin.cmake +++ b/cmake/Thorin.cmake @@ -101,7 +101,7 @@ function(add_thorin_dialect) OUTPUT ${DIALECT_MD} ${DIALECT_H} COMMAND $ ${THORIN_FILE_LIB_DIR} -D ${THORIN_LIB_DIR} --output-h ${DIALECT_H} --output-md ${DIALECT_MD} DEPENDS ${THORIN_TARGET_NAMESPACE}thorin internal_thorin_${DIALECT}_thorin ${THORIN_FILE_LIB_DIR} - COMMENT "Bootstrapping Thorin dialect '${DIALECT}' from '${THORIN_FILE}'" + COMMENT "Bootstrapping Thorin plugin '${DIALECT}' from '${THORIN_FILE}'" ) add_custom_target(${DIALECT} DEPENDS ${DIALECT_H}) diff --git a/dialects/affine/affine.cpp b/dialects/affine/affine.cpp index 10afc9fc26..d41ab1b0ba 100644 --- a/dialects/affine/affine.cpp +++ b/dialects/affine/affine.cpp @@ -10,7 +10,7 @@ using namespace thorin; -extern "C" THORIN_EXPORT thorin::DialectInfo thorin_get_dialect_info() { +extern "C" THORIN_EXPORT Plugin thorin_get_plugin() { return {"affine", [](Passes& passes) { register_pass(passes); }, nullptr, nullptr}; } diff --git a/dialects/autodiff/autodiff.cpp b/dialects/autodiff/autodiff.cpp index 64c9a98503..53d136163b 100644 --- a/dialects/autodiff/autodiff.cpp +++ b/dialects/autodiff/autodiff.cpp @@ -12,7 +12,7 @@ using namespace thorin; -extern "C" THORIN_EXPORT thorin::DialectInfo thorin_get_dialect_info() { +extern "C" THORIN_EXPORT Plugin thorin_get_plugin() { return {"autodiff", [](Passes& passes) { register_pass(passes); diff --git a/dialects/clos/clos.cpp b/dialects/clos/clos.cpp index e2fea7be10..cff89cb00e 100644 --- a/dialects/clos/clos.cpp +++ b/dialects/clos/clos.cpp @@ -7,8 +7,6 @@ #include #include -#include "thorin/dialects.h" - #include "dialects/clos/pass/fp/lower_typed_clos_prep.h" #include "dialects/clos/pass/rw/branch_clos_elim.h" #include "dialects/clos/pass/rw/clos2sjlj.h" @@ -137,7 +135,7 @@ Ref ctype(World& w, Defs doms, Ref env_type) { using namespace thorin; -extern "C" THORIN_EXPORT DialectInfo thorin_get_dialect_info() { +extern "C" THORIN_EXPORT Plugin thorin_get_plugin() { return {"clos", [](Passes& passes) { register_pass(passes, nullptr); diff --git a/dialects/clos/pass/rw/phase_wrapper.h b/dialects/clos/pass/rw/phase_wrapper.h index df6003b496..0a99e387a4 100644 --- a/dialects/clos/pass/rw/phase_wrapper.h +++ b/dialects/clos/pass/rw/phase_wrapper.h @@ -3,8 +3,6 @@ #include #include -#include "thorin/dialects.h" - #include "dialects/clos/phase/clos_conv.h" #include "dialects/clos/phase/lower_typed_clos.h" diff --git a/dialects/compile/compile.cpp b/dialects/compile/compile.cpp index 975adc9d75..c6c94b7292 100644 --- a/dialects/compile/compile.cpp +++ b/dialects/compile/compile.cpp @@ -1,7 +1,6 @@ #include "dialects/compile/compile.h" #include -#include #include #include "thorin/pass/fp/beta_red.h" @@ -35,40 +34,40 @@ void add_passes(World& world, PipelineBuilder& builder, Passes& passes, DefVec& builder.end_pass_phase(); } -extern "C" THORIN_EXPORT thorin::DialectInfo thorin_get_dialect_info() { +extern "C" THORIN_EXPORT thorin::Plugin thorin_get_plugin() { return {"compile", [](Passes& passes) { auto debug_phase_flag = flags_t(Axiom::Base); - passes[debug_phase_flag] = [](World& world, PipelineBuilder& builder, const Def* app) { + assert_emplace(passes, debug_phase_flag, [](World& world, PipelineBuilder& builder, const Def* app) { world.DLOG("Generate debug_phase: {}", app); int level = (int)(app->as()->arg(0)->as()->get()); world.DLOG(" Level: {}", level); builder.add_phase(level); - }; + }); - passes[flags_t(Axiom::Base)] = + assert_emplace(passes, flags_t(Axiom::Base), [&](World& world, PipelineBuilder& builder, const Def* app) { auto pass_array = app->as()->arg()->projs(); DefVec pass_list; for (auto pass : pass_array) pass_list.push_back(pass); add_passes(world, builder, passes, pass_list); - }; + }); - passes[flags_t(Axiom::Base)] = + assert_emplace(passes, flags_t(Axiom::Base), [&](World& world, PipelineBuilder& builder, const Def* app) { auto phase_array = app->as()->arg()->projs(); DefVec phase_list; for (auto phase : phase_array) phase_list.push_back(phase); add_phases(phase_list, world, passes, builder); - }; + }); - passes[flags_t(Axiom::Base)] = [&](World& world, PipelineBuilder& builder, + assert_emplace(passes, flags_t(Axiom::Base), [&](World& world, PipelineBuilder& builder, const Def* app) { auto [ax, phases] = collect_args(app); add_phases(phases, world, passes, builder); - }; - passes[flags_t(Axiom::Base)] = - [](World&, PipelineBuilder& builder, const Def* def) { builder.def2pass(def, nullptr); }; + }); + assert_emplace(passes, flags_t(Axiom::Base), + [](World&, PipelineBuilder& builder, const Def* def) { builder.def2pass(def, nullptr); }); register_pass(passes); register_pass(passes); diff --git a/dialects/core/core.cpp b/dialects/core/core.cpp index 2b0f0164cf..248597213d 100644 --- a/dialects/core/core.cpp +++ b/dialects/core/core.cpp @@ -3,13 +3,11 @@ #include #include -#include "thorin/dialects.h" - #include "dialects/core/be/ll/ll.h" using namespace thorin; -extern "C" THORIN_EXPORT DialectInfo thorin_get_dialect_info() { +extern "C" THORIN_EXPORT Plugin thorin_get_plugin() { return {"core", nullptr, [](Backends& backends) { backends["ll"] = &ll::emit; }, [](Normalizers& normalizers) { core::register_normalizers(normalizers); }}; } diff --git a/dialects/demo/demo.cpp b/dialects/demo/demo.cpp index 7471ef2ca7..12f3aca9e3 100644 --- a/dialects/demo/demo.cpp +++ b/dialects/demo/demo.cpp @@ -1,7 +1,6 @@ #include "dialects/demo/demo.h" -#include -#include +#include #include using namespace thorin; @@ -9,6 +8,6 @@ using namespace thorin; /// heart of the dialect /// registers passes in the different optimization phases /// as well as normalizers for the axioms -extern "C" THORIN_EXPORT thorin::DialectInfo thorin_get_dialect_info() { +extern "C" THORIN_EXPORT Plugin thorin_get_plugin() { return {"demo", nullptr, nullptr, [](Normalizers& normalizers) { demo::register_normalizers(normalizers); }}; } diff --git a/dialects/direct/direct.cpp b/dialects/direct/direct.cpp index 58a7bec96e..de69c56973 100644 --- a/dialects/direct/direct.cpp +++ b/dialects/direct/direct.cpp @@ -1,6 +1,6 @@ #include "dialects/direct/direct.h" -#include +#include #include #include "dialects/direct/passes/cps2ds.h" @@ -8,7 +8,7 @@ using namespace thorin; -extern "C" THORIN_EXPORT thorin::DialectInfo thorin_get_dialect_info() { +extern "C" THORIN_EXPORT Plugin thorin_get_plugin() { return {"direct", [](Passes& passes) { register_pass(passes); diff --git a/dialects/math/math.cpp b/dialects/math/math.cpp index 326c17d156..03221bdba0 100644 --- a/dialects/math/math.cpp +++ b/dialects/math/math.cpp @@ -3,11 +3,9 @@ #include #include -#include "thorin/dialects.h" - using namespace thorin; -extern "C" THORIN_EXPORT DialectInfo thorin_get_dialect_info() { +extern "C" THORIN_EXPORT Plugin thorin_get_plugin() { return {"math", nullptr, [](Backends&) {}, [](Normalizers& normalizers) { math::register_normalizers(normalizers); }}; } diff --git a/dialects/mem/mem.cpp b/dialects/mem/mem.cpp index 861947ac93..6088bfc27c 100644 --- a/dialects/mem/mem.cpp +++ b/dialects/mem/mem.cpp @@ -1,7 +1,6 @@ #include "dialects/mem/mem.h" #include -#include #include #include #include @@ -22,7 +21,7 @@ using namespace thorin; -extern "C" THORIN_EXPORT DialectInfo thorin_get_dialect_info() { +extern "C" THORIN_EXPORT Plugin thorin_get_plugin() { return {"mem", [](Passes& passes) { register_pass_with_arg(passes); diff --git a/dialects/opt/opt.cpp b/dialects/opt/opt.cpp index a91cb36e33..147880d3cc 100644 --- a/dialects/opt/opt.cpp +++ b/dialects/opt/opt.cpp @@ -10,7 +10,7 @@ using namespace thorin; -extern "C" THORIN_EXPORT thorin::DialectInfo thorin_get_dialect_info() { +extern "C" THORIN_EXPORT Plugin thorin_get_plugin() { return {"opt", [](Passes& passes) { passes[flags_t(Axiom::Base)] = [&](World& world, PipelineBuilder& builder, @@ -20,16 +20,14 @@ extern "C" THORIN_EXPORT thorin::DialectInfo thorin_get_dialect_info() { auto dialect_axiom = args[1]->as(); auto then_phase = args[2]; auto else_phase = args[3]; - world.DLOG("dialect_phase for: {}", dialect_axiom->sym()); + auto name = dialect_axiom->sym(); // name has the form %opt.tag + auto [_, tag, __] = Axiom::split(world, name); // where tag = [dialect]_dialect + auto dialect = driver.sym(tag->substr(0, tag->find('_'))); // we want to extract the dialect + bool is_loaded = driver.is_loaded(dialect); - // name has the form %opt.tag where tag = [dialect]_dialect - // we want to extract the dialect part - auto name = dialect_axiom->sym(); - auto [_, tag, __] = Axiom::split(world, name); assert(tag->find('_') != std::string_view::npos && "dialect_phase: invalid dialect name"); - auto dialect = driver.sym(tag->substr(0, tag->find('_'))); + world.DLOG("dialect_phase for: {}", dialect_axiom->sym()); world.DLOG("dialect: {}", dialect); - bool is_loaded = driver.plugin(dialect); world.DLOG("contained: {}", is_loaded); compile::handle_optimization_part(is_loaded ? then_phase : else_phase, world, passes, builder); diff --git a/dialects/refly/refly.cpp b/dialects/refly/refly.cpp index 898d6fae9c..9fb4e52a39 100644 --- a/dialects/refly/refly.cpp +++ b/dialects/refly/refly.cpp @@ -1,7 +1,6 @@ #include "dialects/refly/refly.h" #include -#include #include #include @@ -12,7 +11,7 @@ using namespace thorin; /// heart of the dialect /// registers passes in the different optimization phases /// as well as normalizers for the axioms -extern "C" THORIN_EXPORT thorin::DialectInfo thorin_get_dialect_info() { +extern "C" THORIN_EXPORT Plugin thorin_get_plugin() { return {"refly", [](Passes& passes) { register_pass(passes); }, nullptr, [](Normalizers& normalizers) { refly::register_normalizers(normalizers); }}; } diff --git a/docs/cli.md b/docs/cli.md index cbd94e271d..6994d0ada3 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -6,6 +6,14 @@ \include "cli-help.inc" +In addition, you can specify more search paths using the environment variable `THORIN_PLUGIN_PATH`. +Thorin will look for plugins in this priority: +1. The current working directory. +2. All paths specified via `-D` (in the given order). +3. All paths specified in the environment variable `THORIN_PLUGIN_PATH` (in the given order). +4. `path/to/thorin.exe/../../lib/thorin` +5. `CMAKE_INSTALL_PREFIX/lib/thorin` + ## Debugging Features {#clidebug} * You can increase the log level with `-V`. diff --git a/docs/langref.md b/docs/langref.md index d2082faff2..dec8a9a3e3 100644 --- a/docs/langref.md +++ b/docs/langref.md @@ -33,33 +33,34 @@ In addition you can use `⟨`, `⟩`, `⟪`, and `⟫` as an alternative for ` In addition the following keywords are *terminals*: -| Terminal | Comment | -|-------------|---------------------------------------------------| -| `.ax` | axiom | -| `.let` | let expression | -| `.Pi` | nominal thorin::Pi | -| `.con` | [continuation](@ref thorin::Lam) (declaration) | -| `.fun` | [function](@ref thorin::Lam) (declaration - TODO) | -| `.lam` | [lambda](@ref thorin::Lam) (declaration) | -| `.cn` | [continuation](@ref thorin::Lam) (expression) | -| `.fn` | [function](@ref thorin::Lam) (expression - TODO) | -| `.cn` | [lambda](@ref thorin::Lam) (expression) | -| `.Arr` | nominal thorin::Arr | -| `.pack` | nominal thorin::Pack | -| `.Sigma` | nominal thorin::Sigma | -| `.def` | nominal definition | -| `.extern` | marks nominal as external | -| `.ins` | thorin::Insert expression | -| `.insert` | alias for `.ins` | -| `.module` | starts a module | -| `.import` | imports a dialect | -| `.Nat` | thorin::Nat | -| `.Idx` | thorin::Idx | -| `.Bool` | alias for `.Idx 2` | -| `.ff` | alias for `0₂` | -| `.tt` | alias for `1₂` | -| `.Type` | thorin::Type | -| `.Univ` | thorin::Univ | +| Terminal | Comment | +|-----------|-----------------------------------------------------------| +| `.ax` | axiom | +| `.let` | let expression | +| `.Pi` | nominal thorin::Pi | +| `.con` | [continuation](@ref thorin::Lam) (declaration) | +| `.fun` | [function](@ref thorin::Lam) (declaration - TODO) | +| `.lam` | [lambda](@ref thorin::Lam) (declaration) | +| `.cn` | [continuation](@ref thorin::Lam) (expression) | +| `.fn` | [function](@ref thorin::Lam) (expression - TODO) | +| `.cn` | [lambda](@ref thorin::Lam) (expression) | +| `.Arr` | nominal thorin::Arr | +| `.pack` | nominal thorin::Pack | +| `.Sigma` | nominal thorin::Sigma | +| `.def` | nominal definition | +| `.extern` | marks nominal as external | +| `.ins` | thorin::Insert expression | +| `.insert` | alias for `.ins` | +| `.module` | starts a module | +| `.import` | imports another Thorin file | +| `.plugin` | like `.import` and additionally loads the compiler plugin | +| `.Nat` | thorin::Nat | +| `.Idx` | thorin::Idx | +| `.Bool` | alias for `.Idx 2` | +| `.ff` | alias for `0₂` | +| `.tt` | alias for `1₂` | +| `.Type` | thorin::Type | +| `.Univ` | thorin::Univ | All keywords start with a `.` to prevent name clashes with identifiers. diff --git a/gtest/lexer.cpp b/gtest/lexer.cpp index 677276b699..a66335525f 100644 --- a/gtest/lexer.cpp +++ b/gtest/lexer.cpp @@ -14,7 +14,7 @@ using namespace thorin::fe; TEST(Lexer, Toks) { Driver driver; std::istringstream is("{ } ( ) [ ] ‹ › « » : , . .lam .Pi λ Π"); - Lexer lexer(driver.world(), driver.sym(""), is); + Lexer lexer(driver.world(), is); EXPECT_TRUE(lexer.lex().isa(Tok::Tag::D_brace_l)); EXPECT_TRUE(lexer.lex().isa(Tok::Tag::D_brace_r)); @@ -38,9 +38,8 @@ TEST(Lexer, Toks) { TEST(Lexer, Loc) { Driver driver; - auto sym = driver.sym(""); std::istringstream is(" test abc def if \nwhile λ foo "); - Lexer lexer(driver.world(), sym, is); + Lexer lexer(driver.world(), is); auto t1 = lexer.lex(); auto t2 = lexer.lex(); auto t3 = lexer.lex(); @@ -52,35 +51,35 @@ TEST(Lexer, Loc) { EXPECT_EQ(fmt("{} {} {} {} {} {} {} {}", t1, t2, t3, t4, t5, t6, t7, t8), "test abc def if while λ foo "); // clang-format off - EXPECT_EQ(t1.loc(), Loc(sym, {1, 2}, {1, 5})); - EXPECT_EQ(t2.loc(), Loc(sym, {1, 8}, {1, 10})); - EXPECT_EQ(t3.loc(), Loc(sym, {1, 15}, {1, 17})); - EXPECT_EQ(t4.loc(), Loc(sym, {1, 19}, {1, 20})); - EXPECT_EQ(t5.loc(), Loc(sym, {2, 1}, {2, 5})); - EXPECT_EQ(t6.loc(), Loc(sym, {2, 7}, {2, 7})); - EXPECT_EQ(t7.loc(), Loc(sym, {2, 9}, {2, 11})); - EXPECT_EQ(t8.loc(), Loc(sym, {2, 14}, {2, 14})); + EXPECT_EQ(t1.loc(), Loc({1, 2}, {1, 5})); + EXPECT_EQ(t2.loc(), Loc({1, 8}, {1, 10})); + EXPECT_EQ(t3.loc(), Loc({1, 15}, {1, 17})); + EXPECT_EQ(t4.loc(), Loc({1, 19}, {1, 20})); + EXPECT_EQ(t5.loc(), Loc({2, 1}, {2, 5})); + EXPECT_EQ(t6.loc(), Loc({2, 7}, {2, 7})); + EXPECT_EQ(t7.loc(), Loc({2, 9}, {2, 11})); + EXPECT_EQ(t8.loc(), Loc({2, 14}, {2, 14})); // clang-format on } TEST(Lexer, Errors) { Driver driver; std::istringstream is1("asdf \xc0\xc0"); - Lexer l1(driver.world(), driver.sym(""), is1); + Lexer l1(driver.world(), is1); l1.lex(); EXPECT_ANY_THROW(l1.lex()); std::istringstream is2("foo \xaa"); - Lexer l2(driver.world(), driver.sym(""), is2); + Lexer l2(driver.world(), is2); l2.lex(); EXPECT_ANY_THROW(l2.lex()); std::istringstream is3("+"); - Lexer l3(driver.world(), driver.sym(""), is3); + Lexer l3(driver.world(), is3); EXPECT_ANY_THROW(l3.lex()); std::istringstream is4("-"); - Lexer l4(driver.world(), driver.sym(""), is4); + Lexer l4(driver.world(), is4); EXPECT_ANY_THROW(l4.lex()); } @@ -88,7 +87,7 @@ TEST(Lexer, Eof) { Driver driver; std::istringstream is(""); - Lexer lexer(driver.world(), driver.sym(""), is); + Lexer lexer(driver.world(), is); for (int i = 0; i < 10; i++) EXPECT_TRUE(lexer.lex().isa(Tok::Tag::M_eof)); } @@ -109,7 +108,7 @@ TEST_P(Real, sign) { } std::istringstream is(s); - Lexer lexer(w, w.sym(""), is); + Lexer lexer(w, is); auto tag = lexer.lex(); EXPECT_TRUE(tag.isa(Tok::Tag::L_r)); @@ -128,11 +127,11 @@ TEST_P(Real, sign) { // clang-format on std::istringstream is1("0x2.34"); - Lexer l1(w, w.sym(""), is1); + Lexer l1(w, is1); EXPECT_ANY_THROW(l1.lex()); std::istringstream is2("2.34e"); - Lexer l2(w, w.sym(""), is2); + Lexer l2(w, is2); EXPECT_ANY_THROW(l2.lex()); } diff --git a/gtest/restricted_dep_types.cpp b/gtest/restricted_dep_types.cpp index c03c327069..c300480d45 100644 --- a/gtest/restricted_dep_types.cpp +++ b/gtest/restricted_dep_types.cpp @@ -7,7 +7,6 @@ #include #include "thorin/def.h" -#include "thorin/dialects.h" #include "thorin/driver.h" #include "thorin/fe/parser.h" @@ -17,7 +16,6 @@ #include "thorin/pass/optimize.h" #include "thorin/pass/pass.h" #include "thorin/pass/pipelinebuilder.h" -#include "thorin/util/sys.h" #include "dialects/compile/compile.h" #include "dialects/core/core.h" @@ -31,11 +29,9 @@ using namespace thorin; TEST(RestrictedDependentTypes, join_singleton) { auto test_on_world = [](auto test) { Driver driver; - World& w = driver.world(); - - auto dialects = {"compile", "mem", "core", "math"}; - for (auto dialect : dialects) driver.load(dialect); - for (auto dialect : dialects) fe::Parser::import_module(w, w.sym(dialect)); + World& w = driver.world(); + auto parser = fe::Parser(w); + for (auto dialect : {"compile", "mem", "core", "math"}) parser.plugin(dialect); auto i32_t = w.type_int(32); auto i64_t = w.type_int(64); @@ -223,11 +219,9 @@ TEST(RestrictedDependentTypes, join_singleton) { TEST(RestrictedDependentTypes, ll) { Driver driver; - World& w = driver.world(); - - auto dialects = {"compile", "mem", "core", "math"}; - for (auto dialect : dialects) driver.load(dialect); - for (auto dialect : dialects) fe::Parser::import_module(w, w.sym(dialect)); + World& w = driver.world(); + auto parser = fe::Parser(w); + for (auto dialect : {"compile", "mem", "core", "math"}) parser.plugin(dialect); auto mem_t = mem::type_mem(w); auto i32_t = w.type_int(32); diff --git a/gtest/test.cpp b/gtest/test.cpp index 5e9ad80158..35b125adf2 100644 --- a/gtest/test.cpp +++ b/gtest/test.cpp @@ -2,11 +2,12 @@ #include #include +#include #include "thorin/driver.h" +#include "thorin/rewrite.h" #include "thorin/fe/parser.h" -#include "thorin/util/sys.h" #include "dialects/core/core.h" #include "helpers.h" @@ -15,35 +16,23 @@ using namespace thorin; TEST(Zip, fold) { Driver driver; - World& w = driver.world(); - - driver.load("core"); - fe::Parser::import_module(w, w.sym("core")); - - auto zip = w.ax(); - // clang-format off - auto a = w.tuple({w.tuple({w.lit_idx( 0), w.lit_idx( 1), w.lit_idx( 2)}), - w.tuple({w.lit_idx( 3), w.lit_idx( 4), w.lit_idx( 5)})}); - - auto b = w.tuple({w.tuple({w.lit_idx( 6), w.lit_idx( 7), w.lit_idx( 8)}), - w.tuple({w.lit_idx( 9), w.lit_idx(10), w.lit_idx(11)})}); - - auto c = w.tuple({w.tuple({w.lit_idx( 6), w.lit_idx( 8), w.lit_idx(10)}), - w.tuple({w.lit_idx(12), w.lit_idx(14), w.lit_idx(16)})}); - - auto f = w.app(w.app(w.ax(core::wrap::add), w.lit_nat(Idx::bitwidth2size(32))), w.lit_nat_0()); - auto i32_t = w.type_int(32); - auto res = w.app(w.app(w.app(zip, {/*r*/w.lit_nat(2), /*s*/w.tuple({w.lit_nat(2), w.lit_nat(3)})}), - {/*n_i*/ w.lit_nat(2), /*Is*/w.pack(2, i32_t), /*n_o*/w.lit_nat(1), /*Os*/i32_t, f}), - {a, b}); - // clang-format on - EXPECT_TRUE(res->is_term()); - EXPECT_TRUE(!zip->is_term()); - EXPECT_TRUE(!res->type()->is_term()); - EXPECT_TRUE(!zip->type()->is_term()); - - res->dump(0); - EXPECT_EQ(c, res); + World& w = driver.world(); + auto parser = fe::Parser(w); + + std::istringstream iss(".plugin core;" + ".let _32 = 4294967296;" + ".let I32 = .Idx _32;" + ".let a = ((0:I32, 1:I32, 2:I32), ( 3:I32, 4:I32, 5:I32));" + ".let b = ((6:I32, 7:I32, 8:I32), ( 9:I32, 10:I32, 11:I32));" + ".let c = ((6:I32, 8:I32, 10:I32), (12:I32, 14:I32, 16:I32));" + ".let r = %core.zip (2, (2, 3)) (2, (I32, I32), 1, I32, %core.wrap.add 0) (a, b);"); + parser.import(iss); + auto c = parser.scopes().find({Loc(), driver.sym("c")}); + auto r = parser.scopes().find({Loc(), driver.sym("r")}); + + EXPECT_TRUE(r->is_term()); + EXPECT_TRUE(!r->type()->is_term()); + EXPECT_EQ(c, r); } TEST(World, simplify_one_tuple) { @@ -97,10 +86,9 @@ TEST(Axiom, split) { TEST(trait, idx) { Driver driver; - World& w = driver.world(); - - driver.load("core"); - fe::Parser::import_module(w, w.sym("core")); + World& w = driver.world(); + auto parser = fe::Parser(w); + parser.plugin("core"); EXPECT_EQ(as_lit(op(core::trait::size, w.type_idx(0x0000'0000'0000'00FF_n))), 1); EXPECT_EQ(as_lit(op(core::trait::size, w.type_idx(0x0000'0000'0000'0100_n))), 1); diff --git a/lit/clos/array.thorin b/lit/clos/array.thorin index 9ef82233e3..8e96440e1a 100644 --- a/lit/clos/array.thorin +++ b/lit/clos/array.thorin @@ -1,10 +1,8 @@ // RUN: rm -f %t.ll ; \ -// RUN: %thorin -d clos %s --output-ll %t.ll -o - - -.import mem; -.import core; -.import clos; +// RUN: %thorin %s --output-ll %t.ll -o - +.plugin core; +.plugin clos; .let I32 = .Idx 4294967296; diff --git a/lit/math/exp.thorin b/lit/math/exp.thorin index 462f95b4ac..6a6cd4e5a9 100644 --- a/lit/math/exp.thorin +++ b/lit/math/exp.thorin @@ -3,9 +3,9 @@ // RUN: clang++ %t.ll -o %t -Wno-override-module // RUN: %t foo bar; test $? -eq 64 -.import math; -.import mem; -.import core; +.plugin math; +.plugin mem; +.plugin core; .let _32 = 4294967296; .let I32 = .Idx _32; diff --git a/lit/math/infer.thorin b/lit/math/infer.thorin index 349bd5ff9b..995c162cdf 100644 --- a/lit/math/infer.thorin +++ b/lit/math/infer.thorin @@ -1,5 +1,5 @@ // RUN: %thorin %s -o - -.import math; +.plugin math; .con .extern foo(pe: «2; .Nat», f: %math.F pe, return: .Cn %math.F pe) = return (%math.arith.mul 0 (f, f)); diff --git a/lit/math/tri.thorin b/lit/math/tri.thorin index f04d8ab22d..db580c7e62 100644 --- a/lit/math/tri.thorin +++ b/lit/math/tri.thorin @@ -3,9 +3,9 @@ // RUN: clang++ %t.ll -o %t -Wno-override-module // RUN: %t foo; test $? -eq 98 -.import math; -.import mem; -.import core; +.plugin math; +.plugin mem; +.plugin core; .let _32 = 4294967296; .let I32 = .Idx _32; diff --git a/thorin/CMakeLists.txt b/thorin/CMakeLists.txt index e3f4542881..1e9c058078 100644 --- a/thorin/CMakeLists.txt +++ b/thorin/CMakeLists.txt @@ -5,8 +5,7 @@ add_library(libthorin check.h def.cpp def.h - dialects.cpp - dialects.h + plugin.h dump.cpp driver.cpp driver.h @@ -37,8 +36,8 @@ add_library(libthorin be/emitter.h be/dot/dot.cpp be/dot/dot.h - be/h/bootstrapper.cpp - be/h/bootstrapper.h + be/h/bootstrap.cpp + be/h/bootstrap.h fe/ast.cpp fe/ast.h fe/lexer.cpp diff --git a/thorin/analyses/schedule.cpp b/thorin/analyses/schedule.cpp index 732d0abe0d..4eb51b9719 100644 --- a/thorin/analyses/schedule.cpp +++ b/thorin/analyses/schedule.cpp @@ -20,16 +20,14 @@ Scheduler::Scheduler(const Scope& s) auto enqueue = [&](const Def* def, size_t i, const Def* op) { if (scope().bound(op)) { - auto [_, ins] = def2uses_[op].emplace(def, i); - assert_unused(ins); + assert_emplace(def2uses_[op], def, i); if (auto [_, ins] = done.emplace(op); ins) queue.push(op); } }; for (auto n : cfg().reverse_post_order()) { queue.push(n->nom()); - auto p = done.emplace(n->nom()); - assert_unused(p.second); + assert_emplace(done, n->nom()); } while (!queue.empty()) { diff --git a/thorin/axiom.cpp b/thorin/axiom.cpp index 7cefe5a5e0..df216b98ea 100644 --- a/thorin/axiom.cpp +++ b/thorin/axiom.cpp @@ -6,8 +6,8 @@ using namespace std::literals; namespace thorin { -Axiom::Axiom(NormalizeFn normalizer, u8 curry, u8 trip, const Def* type, dialect_t dialect, tag_t tag, sub_t sub) - : Def(Node, type, Defs{}, dialect | (flags_t(tag) << 8_u64) | flags_t(sub)) { +Axiom::Axiom(NormalizeFn normalizer, u8 curry, u8 trip, const Def* type, plugin_t plugin, tag_t tag, sub_t sub) + : Def(Node, type, Defs{}, plugin | (flags_t(tag) << 8_u64) | flags_t(sub)) { normalizer_ = normalizer; curry_ = curry; trip_ = trip; @@ -43,12 +43,12 @@ std::tuple Axiom::get(const Def* def) { return {nullptr, 0, 0}; } -std::optional Axiom::mangle(Sym s) { +std::optional Axiom::mangle(Sym s) { auto n = s->size(); - if (n > Max_Dialect_Size) return {}; + if (n > Max_Plugin_Size) return {}; u64 result = 0; - for (size_t i = 0; i != Max_Dialect_Size; ++i) { + for (size_t i = 0; i != Max_Plugin_Size; ++i) { u64 u = '\0'; if (i < n) { @@ -72,9 +72,9 @@ std::optional Axiom::mangle(Sym s) { return result << 16_u64; } -Sym Axiom::demangle(World& world, dialect_t u) { +Sym Axiom::demangle(World& world, plugin_t u) { std::string result; - for (size_t i = 0; i != Max_Dialect_Size; ++i) { + for (size_t i = 0; i != Max_Plugin_Size; ++i) { u64 c = (u & 0xfc00000000000000_u64) >> 58_u64; if (c == 0) { return world.sym(result); @@ -102,17 +102,17 @@ std::array Axiom::split(World& world, Sym s) { auto dot = sv.find('.'); if (dot == std::string_view::npos) return {}; - auto dialect = world.sym(subview(sv, 0, dot)); - if (!mangle(dialect)) return {}; + auto plugin = world.sym(subview(sv, 0, dot)); + if (!mangle(plugin)) return {}; auto tag = subview(sv, dot + 1); if (auto dot = tag.find('.'); dot != std::string_view::npos) { auto sub = world.sym(subview(tag, dot + 1)); tag = subview(tag, 0, dot); - return {dialect, world.sym(tag), sub}; + return {plugin, world.sym(tag), sub}; } - return {dialect, world.sym(tag), {}}; + return {plugin, world.sym(tag), {}}; } } // namespace thorin diff --git a/thorin/axiom.h b/thorin/axiom.h index 5a5841aa6e..4567054e96 100644 --- a/thorin/axiom.h +++ b/thorin/axiom.h @@ -8,9 +8,23 @@ namespace thorin { class Axiom : public Def { private: - Axiom(NormalizeFn, u8 curry, u8 trip, const Def* type, dialect_t, tag_t, sub_t); + Axiom(NormalizeFn, u8 curry, u8 trip, const Def* type, plugin_t, tag_t, sub_t); public: + struct Info { + Info(Sym plugin, Sym tag, flags_t tag_id) + : plugin(plugin) + , tag(tag) + , tag_id(tag_id) {} + + Sym plugin; + Sym tag; + flags_t tag_id; + std::deque> subs; ///< List of subs which is a list of aliases. + Sym normalizer; + bool pi = false; + }; + /// @name normalization ///@{ /// For a curried App of an Axiom, you only want to trigger normalization at specific spots. @@ -46,15 +60,15 @@ class Axiom : public Def { ///@{ /// Anatomy of an Axiom name: /// ``` - /// %dialect.tag.sub + /// %plugin.tag.sub /// | 48 | 8 | 8 | <-- Number of bits per field. /// ``` /// * Def::name() retrieves the full name as Sym. /// * Def::flags() retrieves the full name as Axiom::mangle%d 64-bit integer. - /// Yields the `dialect` part of the name as integer. + /// Yields the `plugin` part of the name as integer. /// It consists of 48 relevant bits that are returned in the highest 6 bytes of a 64-bit integer. - dialect_t dialect() const { return flags() & Global_Dialect; } + plugin_t plugin() const { return flags() & Global_Plugin; } /// Yields the `tag` part of the name as integer. tag_t tag() const { return tag_t((flags() & 0x0000'0000'0000'ff00_u64) >> 8_u64); } @@ -62,14 +76,14 @@ class Axiom : public Def { /// Yields the `sub` part of the name as integer. sub_t sub() const { return sub_t(flags() & 0x0000'0000'0000'00ff_u64); } - /// Includes Axiom::dialect() and Axiom::tag() but **not** Axiom::sub. + /// Includes Axiom::plugin() and Axiom::tag() but **not** Axiom::sub. flags_t base() const { return flags() & ~0xff_u64; } ///@} - /// @name Mangling Dialect Name + /// @name Mangling plugin Name ///@{ - static constexpr size_t Max_Dialect_Size = 8; - static constexpr dialect_t Global_Dialect = 0xffff'ffff'ffff'0000_u64; + static constexpr size_t Max_Plugin_Size = 8; + static constexpr plugin_t Global_Plugin = 0xffff'ffff'ffff'0000_u64; /// Mangles @p s into a dense 48-bit representation. /// The layout is as follows: @@ -79,7 +93,7 @@ class Axiom : public Def { /// Char67Char66Char65Char64Char63Char62Char61Char60|---reserved---| /// ``` /// The `reserved` part is used for the Axiom::tag and the Axiom::sub. - /// Each `Char6x` is 6-bit wide and hence a dialect name has at most Axiom::Max_Dialect_Size = 8 chars. + /// Each `Char6x` is 6-bit wide and hence a plugin name has at most Axiom::Max_Plugin_Size = 8 chars. /// It uses this encoding: /// | `Char6` | ASCII | /// |---------|---------| @@ -89,11 +103,11 @@ class Axiom : public Def { /// | 54-63: | `0`-`9` | /// The 0 is special and marks the end of the name if the name has less than 8 chars. /// @returns `std::nullopt` if encoding is not possible. - static std::optional mangle(Sym s); + static std::optional mangle(Sym s); /// Reverts an Axiom::mangle%d string to a Sym. /// Ignores lower 16-bit of @p u. - static Sym demangle(World&, dialect_t u); + static Sym demangle(World&, plugin_t u); static std::array split(World&, Sym); ///@} @@ -146,11 +160,11 @@ class Match { /// @name Axiom name ///@{ - auto dialect() const { return axiom()->dialect(); } ///< @sa Axiom::dialect. - auto tag() const { return axiom()->tag(); } ///< @sa Axiom::tag. - auto sub() const { return axiom()->sub(); } ///< @sa Axiom::sub. - auto base() const { return axiom()->sub(); } ///< @sa Axiom::base. - auto id() const { return Id(axiom()->flags()); } ///< Axiom::flags cast to @p Id. + auto plugin() const { return axiom()->plugin(); } ///< @sa Axiom::plugin. + auto tag() const { return axiom()->tag(); } ///< @sa Axiom::tag. + auto sub() const { return axiom()->sub(); } ///< @sa Axiom::sub. + auto base() const { return axiom()->sub(); } ///< @sa Axiom::base. + auto id() const { return Id(axiom()->flags()); } ///< Axiom::flags cast to @p Id. ///@} private: diff --git a/thorin/be/h/bootstrapper.cpp b/thorin/be/h/bootstrap.cpp similarity index 72% rename from thorin/be/h/bootstrapper.cpp rename to thorin/be/h/bootstrap.cpp index cd0c26700d..04b56daf99 100644 --- a/thorin/be/h/bootstrapper.cpp +++ b/thorin/be/h/bootstrap.cpp @@ -1,36 +1,42 @@ -#include "thorin/be/h/bootstrapper.h" +#include "thorin/be/h/bootstrap.h" #include #include #include "thorin/axiom.h" +#include "thorin/driver.h" -namespace thorin::h { +namespace thorin { -void Bootstrapper::emit(std::ostream& h) { +void bootstrap(Driver& driver, Sym plugin, std::ostream& h) { + Tab tab; tab.print(h, "#pragma once\n\n"); tab.print(h, "#include \"thorin/axiom.h\"\n" - "#include \"thorin/dialects.h\"\n\n"); + "#include \"thorin/plugin.h\"\n\n"); - tab.print(h, "namespace thorin {{\nnamespace {} {{\n\n", dialect_); + tab.print(h, "namespace thorin {{\nnamespace {} {{\n\n", plugin); - dialect_t dialect_id = *Axiom::mangle(dialect_); + plugin_t plugin_id = *Axiom::mangle(plugin); std::vector normalizers, outer_namespace; - tab.print(h << std::hex, "static constexpr dialect_t Dialect_Id = 0x{};\n\n", dialect_id); + tab.print(h << std::hex, "static constexpr plugin_t Plugin_Id = 0x{};\n\n", plugin_id); + + auto& infos = driver.plugin2axiom_infos(plugin); // clang-format off - for (const auto& [key, ax] : axioms) { + for (const auto& [key, ax] : infos) { + if (ax.plugin != plugin) continue; // this is from an import + tab.print(h, "#ifdef DOXYGEN // see https://github.com/doxygen/doxygen/issues/9668\n"); tab.print(h, "enum {} : flags_t {{\n", ax.tag); tab.print(h, "#else\n"); tab.print(h, "enum class {} : flags_t {{\n", ax.tag); tab.print(h, "#endif\n"); ++tab; - flags_t ax_id = dialect_id | (ax.tag_id << 8u); + flags_t ax_id = plugin_id | (ax.tag_id << 8u); auto& os = outer_namespace.emplace_back(); - print(os << std::hex, "template<> constexpr flags_t Axiom::Base<{}::{}> = 0x{};\n", dialect_, ax.tag, ax_id); + print(os << std::hex, "template<> constexpr flags_t Axiom::Base<{}::{}> = 0x{};\n", plugin, ax.tag, ax_id); if (auto& subs = ax.subs; !subs.empty()) { for (const auto& aliases : subs) { @@ -57,7 +63,7 @@ void Bootstrapper::emit(std::ostream& h) { tab.print(h, "inline flags_t operator|({} lhs, {} rhs) {{ return static_cast(lhs) | static_cast(rhs); }}\n\n", ax.tag, ax.tag); } - print(outer_namespace.emplace_back(), "template<> constexpr size_t Axiom::Num<{}::{}> = {};\n", dialect_, ax.tag, ax.subs.size()); + print(outer_namespace.emplace_back(), "template<> constexpr size_t Axiom::Num<{}::{}> = {};\n", plugin, ax.tag, ax.subs.size()); if (ax.normalizer) { if (auto& subs = ax.subs; !subs.empty()) { @@ -71,7 +77,7 @@ void Bootstrapper::emit(std::ostream& h) { if (!normalizers.empty()) { tab.print(h, "void register_normalizers(Normalizers& normalizers);\n\n"); - tab.print(h, "#define THORIN_{}_NORMALIZER_IMPL \\\n", dialect_); + tab.print(h, "#define THORIN_{}_NORMALIZER_IMPL \\\n", plugin); ++tab; tab.print(h, "void register_normalizers(Normalizers& normalizers) {{\\\n"); ++tab; @@ -81,19 +87,18 @@ void Bootstrapper::emit(std::ostream& h) { --tab; } - tab.print(h, "}} // namespace {}\n\n", dialect_); + tab.print(h, "}} // namespace {}\n\n", plugin); for (const auto& line : outer_namespace) tab.print(h, "{}", line.str()); tab.print(h, "\n"); - if (std::ranges::any_of(axioms, [](const auto& ax) { return !ax.second.pi; })) { - for (const auto& [tag, ax] : axioms) { - if (ax.pi) continue; - tab.print(h, "template<> struct Axiom::Match<{}::{}> {{ using type = Axiom; }};\n", ax.dialect, ax.tag); - } + // emit helpers for non-function axiom + for (const auto& [tag, ax] : infos) { + if (ax.pi || ax.plugin != plugin) continue; // from function or other plugin? + tab.print(h, "template<> struct Axiom::Match<{}::{}> {{ using type = Axiom; }};\n", ax.plugin, ax.tag); } tab.print(h, "}} // namespace thorin\n"); } -} // namespace thorin::h +} // namespace thorin diff --git a/thorin/be/h/bootstrap.h b/thorin/be/h/bootstrap.h new file mode 100644 index 0000000000..f88ffa2d6b --- /dev/null +++ b/thorin/be/h/bootstrap.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include +#include + +#include "thorin/util/print.h" +#include "thorin/util/sym.h" +#include "thorin/util/types.h" + +namespace thorin { + +class Driver; + +void bootstrap(Driver&, Sym, std::ostream&); + +} // namespace thorin diff --git a/thorin/be/h/bootstrapper.h b/thorin/be/h/bootstrapper.h deleted file mode 100644 index 2d1f789267..0000000000 --- a/thorin/be/h/bootstrapper.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "thorin/util/print.h" -#include "thorin/util/sym.h" -#include "thorin/util/types.h" - -namespace thorin::h { - -struct AxiomInfo { - flags_t tag_id; - Sym dialect; - Sym tag; - std::deque> subs; - Sym normalizer; - bool pi; -}; - -class Bootstrapper { -public: - Bootstrapper(Sym dialect) - : dialect_(dialect) {} - - void emit(std::ostream&); - Sym dialect() const { return dialect_; } - - SymMap axioms; - Tab tab; - -private: - Sym dialect_; -}; - -} // namespace thorin::h diff --git a/thorin/def.cpp b/thorin/def.cpp index 6a62120605..6268d1c4a1 100644 --- a/thorin/def.cpp +++ b/thorin/def.cpp @@ -104,7 +104,7 @@ Ref Var ::rebuild_(World& w, Ref t, Defs o) const { return w.var(t, o[0]->a Ref Vel ::rebuild_(World& w, Ref t, Defs o) const { return w.vel(t, o[0]); } Ref Axiom ::rebuild_(World& w, Ref t, Defs ) const { - if (&w != &world()) return w.axiom(normalizer(), curry(), trip(), t, dialect(), tag(), sub()); + if (&w != &world()) return w.axiom(normalizer(), curry(), trip(), t, plugin(), tag(), sub()); assert(w.checker().equiv(t, type())); return this; } diff --git a/thorin/dialects.cpp b/thorin/dialects.cpp deleted file mode 100644 index 480e14d5af..0000000000 --- a/thorin/dialects.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "thorin/dialects.h" - -#include - -#include -#include -#include - -#include "thorin/config.h" -#include "thorin/driver.h" - -#include "thorin/pass/pass.h" -#include "thorin/util/dl.h" -#include "thorin/util/sys.h" - -namespace thorin { - -Dialect::Dialect(const std::string& plugin_path, Handle&& handle) - : plugin_path_(plugin_path) - , handle_(std::move(handle)) { - auto get_info = - reinterpret_cast(dl::get(this->handle(), "thorin_get_dialect_info")); - if (!get_info) err("plugin has no 'thorin_get_dialect_info()'"); - info_ = get_info(); -} - -} // namespace thorin diff --git a/thorin/dialects.h b/thorin/dialects.h deleted file mode 100644 index b33df75fc8..0000000000 --- a/thorin/dialects.h +++ /dev/null @@ -1,83 +0,0 @@ -#pragma once - -#include -#include -#include - -#include -#include - -#include "thorin/def.h" - -namespace thorin { - -class PipelineBuilder; - -/// `axiom ↦ (pipeline part) × (axiom application) → ()`
-/// The function should inspect App%lication to construct the Pass/Phase and add it to the pipeline. -using Passes = absl::flat_hash_map>; -using Backends = absl::btree_map; -using Normalizers = absl::flat_hash_map; - -extern "C" { -/// Basic info and registration function pointer to be returned from a dialect plugin. -/// Use \ref Dialect to load such a plugin. -struct DialectInfo { - /// Name of the plugin - const char* plugin_name; - - /// Callback for registering the dialects' callbacks for the pipeline extension points. - void (*register_passes)(Passes& passes); - - /// Callback for registering the mapping from backend names to emission functions in the given \a backends map. - void (*register_backends)(Backends& backends); - - /// Callback for registering the mapping from axiom ids to normalizer functions in the given \a normalizers map. - void (*register_normalizers)(Normalizers& normalizers); -}; -} - -/// To be implemented and exported by the dialect plugins. -/// Shall return a filled DialectInfo. -extern "C" THORIN_EXPORT thorin::DialectInfo thorin_get_dialect_info(); - -/// A thorin dialect. -/// This is used to load and manage a thorin dialect. -/// -/// A plugin implementor should implement \ref thorin_get_dialect_info and \ref DialectInfo. -class Dialect { -public: - using Handle = std::unique_ptr; - - /// Name of the dialect. - std::string name() const { return info_.plugin_name; } - - /// Shared object handle. Can be used with the functions from \ref dl. - void* handle() { return handle_.get(); } - - /// Registers callbacks in the \a builder that extend the exposed PassMan's. - void register_passes(Passes& passes) const { - if (info_.register_passes) info_.register_passes(passes); - } - - /// Registers the mapping from backend names to emission functions in the given \a backends map. - void register_backends(Backends& backends) const { - if (info_.register_backends) info_.register_backends(backends); - } - - /// Registers the mapping from axiom ids to normalizer functions in the given \a normalizers map. - void register_normalizers(Normalizers& normalizers) const { - if (info_.register_normalizers) info_.register_normalizers(normalizers); - } - -private: - explicit Dialect(const std::string& plugin_path, Handle&&); - - DialectInfo info_; - std::string plugin_path_; - std::unique_ptr handle_; - - friend class Driver; -}; - -} // namespace thorin diff --git a/thorin/driver.cpp b/thorin/driver.cpp index 2944a0e5f6..4ce1517950 100644 --- a/thorin/driver.cpp +++ b/thorin/driver.cpp @@ -1,6 +1,8 @@ #include "thorin/driver.h" +#include "thorin/plugin.h" #include "thorin/util/dl.h" +#include "thorin/util/sys.h" namespace thorin { @@ -12,12 +14,12 @@ static std::vector get_plugin_name_variants(std::string_view name) { } Driver::Driver() - : log_(*this) - , world_(this) { - add_search_path(fs::current_path()); + : world_(this) { + // prepend empty path + search_paths_.emplace_front(fs::path{}); // paths from env - if (auto env_path = std::getenv("THORIN_DIALECT_PATH")) { + if (auto env_path = std::getenv("THORIN_PLUGIN_PATH")) { std::stringstream env_path_stream{env_path}; std::string sub_path; while (std::getline(env_path_stream, sub_path, ':')) add_search_path(sub_path); @@ -32,27 +34,33 @@ Driver::Driver() add_search_path(std::move(install_path)); } - // all other user paths take precedence and are inserted before above fallbacks - insert_ = search_paths_.begin(); + // all other user paths are placed just behind the first path (the empty path) + insert_ = ++search_paths_.begin(); +} + +const fs::path* Driver::add_import(fs::path path, Sym sym) { + for (const auto& [p, _] : imports_) { + if (fs::equivalent(p, path)) return nullptr; + } + + imports_.emplace_back(std::pair(std::move(path), sym)); + return &imports_.back().first; } void Driver::load(Sym name) { ILOG("loading plugin: '{}'", name); - if (plugin(name)) { + if (is_loaded(name)) { WLOG("plugin '{}' already loaded", name); return; } - Dialect::Handle handle{nullptr, dl::close}; - auto plugin_path = *name; + Plugin::Handle handle{nullptr, dl::close}; if (auto path = fs::path{*name}; path.is_absolute() && fs::is_regular_file(path)) handle.reset(dl::open(*name)); if (!handle) { for (const auto& path : search_paths()) { for (auto name_variants = get_plugin_name_variants(name); const auto& name_variant : name_variants) { auto full_path = path / name_variant; - plugin_path = full_path.string(); - std::error_code ignore; if (bool reg_file = fs::is_regular_file(full_path, ignore); reg_file && !ignore) { auto path_str = full_path.string(); @@ -65,12 +73,24 @@ void Driver::load(Sym name) { if (!handle) err("cannot open plugin '{}'", name); - auto [i, ins] = plugins_.emplace(name, Dialect{plugin_path, std::move(handle)}); - assert_unused(ins); - auto& plugin = i->second; - plugin.register_passes(passes_); - plugin.register_backends(backends_); - plugin.register_normalizers(normalizers_); + if (auto get_info = reinterpret_cast(dl::get(handle.get(), "thorin_get_plugin"))) { + assert_emplace(plugins_, name, std::move(handle)); + auto info = get_info(); + if (auto reg = info.register_passes) reg(passes_); + if (auto reg = info.register_normalizers) reg(normalizers_); + if (auto reg = info.register_backends) reg(backends_); + } else { + err("plugin has no 'thorin_get_plugin()'"); + } +} + +std::pair Driver::axiom2info(Sym sym, Sym plugin, Sym tag, Loc loc) { + auto& infos = plugin2axiom_infos_[plugin]; + if (infos.size() > std::numeric_limits::max()) + err(loc, "exceeded maxinum number of axioms in current plugin"); + + auto [it, is_new] = infos.emplace(sym, Axiom::Info{plugin, tag, infos.size()}); + return {it->second, is_new}; } } // namespace thorin diff --git a/thorin/driver.h b/thorin/driver.h index c2c38b53b6..e9d1bbe279 100644 --- a/thorin/driver.h +++ b/thorin/driver.h @@ -2,12 +2,13 @@ #include -#include "thorin/dialects.h" #include "thorin/flags.h" +#include "thorin/plugin.h" #include "thorin/world.h" #include "thorin/util/log.h" -#include "thorin/util/sys.h" + +#include "absl/container/node_hash_map.h" namespace thorin { @@ -24,48 +25,68 @@ class Driver : public SymPool { World& world() { return world_; } ///@} - /// @name search paths and dialect loading + /// @name manage search paths ///@{ - void add_search_path(fs::path path) { - if (fs::exists(path) && fs::is_directory(path)) search_paths_.insert(insert_, fs::absolute(std::move(path))); - } - - /// Search paths for dialect plugins are in the following order: - /// 1. All further user-specified paths via Driver::add_search_path; paths added first will also be searched first. - /// 2. Current working directory. - /// 3. All paths specified in the environment variable `THORIN_DIALECT_PATH`. + /// Search paths for plugins are in the following order: + /// 1. The empty path. Used as prefix to look into current working directory without resorting to an absolute path. + /// 2. All further user-specified paths via Driver::add_search_path; paths added first will also be searched first. + /// 3. All paths specified in the environment variable `THORIN_PLUGIN_PATH`. /// 4. `path/to/thorin.exe/../../lib/thorin` /// 5. `CMAKE_INSTALL_PREFIX/lib/thorin` const auto& search_paths() const { return search_paths_; } + void add_search_path(fs::path path) { + if (fs::exists(path) && fs::is_directory(path)) search_paths_.insert(insert_, std::move(path)); + } + ///@} - /// Finds and loads a shared object file that implements the Thorin dialect @p name. + /// @name manage imports + ///@{ + /// This is a list of pairs where each pair contains: + /// 1. The `fs::path` used during import, + /// 2. The name as Sym%bol used in the `.import` directive or in Parser::import. + const auto& imports() { return imports_; } + /// Yields a `fs::path*` if not already added that you can use in Loc%ation; returns `nullptr` otherwise. + const fs::path* add_import(fs::path, Sym); + ///@} + + /// @name load plugin + ///@{ + /// Finds and loads a shared object file that implements the Thorin Plugin @p name. /// If \a name is an absolute path to a `.so`/`.dll` file, this is used. /// Otherwise, "name", "libthorin_name.so" (Linux, Mac), "thorin_name.dll" (Win) /// are searched for in Driver::search_paths(). void load(Sym name); void load(std::string name) { return load(sym(std::move(name))); } + bool is_loaded(Sym sym) const { return lookup(plugins_, sym); } ///@} /// @name manage plugins ///@{ /// All these lookups yield `nullptr` if the key has not been found. - auto plugin(Sym sym) const { return lookup(plugins_, sym); } auto pass(flags_t flags) { return lookup(passes_, flags); } auto normalizer(flags_t flags) const { return lookup(normalizers_, flags); } - auto normalizer(dialect_t d, tag_t t, sub_t s) const { return normalizer(d | flags_t(t << 8u) | s); } + auto normalizer(plugin_t d, tag_t t, sub_t s) const { return normalizer(d | flags_t(t << 8u) | s); } auto backend(std::string_view name) { return lookup(backends_, name); } ///@} + /// @name manage Axiom::Info + ///@{ + const auto& plugin2axiom_infos(Sym plugin) { return plugin2axiom_infos_[plugin]; } + std::pair axiom2info(Sym sym, Sym plugin, Sym tag, Loc loc); + ///@} + private: Flags flags_; Log log_; World world_; std::list search_paths_; std::list::iterator insert_ = search_paths_.end(); - absl::node_hash_map plugins_; + absl::node_hash_map plugins_; Backends backends_; Passes passes_; Normalizers normalizers_; + std::deque> imports_; + SymMap> plugin2axiom_infos_; }; } // namespace thorin diff --git a/thorin/dump.cpp b/thorin/dump.cpp index 6960ec49f0..4548587f42 100644 --- a/thorin/dump.cpp +++ b/thorin/dump.cpp @@ -1,6 +1,6 @@ #include -#include "thorin/world.h" +#include "thorin/driver.h" #include "thorin/analyses/deptree.h" #include "thorin/fe/tok.h" @@ -382,7 +382,8 @@ void World::dump(std::ostream& os) { auto dep = DepTree(*this); auto dumper = Dumper(os, &dep); - for (const auto& import : imported()) print(os, ".import {};\n", import); + for (auto [_, name] : driver().imports()) + print(os, ".{} {};\n", driver().is_loaded(name) ? "plugin" : "import", name); dumper.recurse(dep.root()); } diff --git a/thorin/fe/ast.cpp b/thorin/fe/ast.cpp index a70fffbe93..e6aa7c5057 100644 --- a/thorin/fe/ast.cpp +++ b/thorin/fe/ast.cpp @@ -43,10 +43,9 @@ const Def* TuplePtrn::type(World& world, Def2Fields& def2fields) const { assert(ptrns().size() > 0); - auto type = world.umax(ops); - auto sigma = world.nom_sigma(type, n)->set(loc(), sym()); - auto [_, ins] = def2fields.emplace(sigma, Array(n, [&](size_t i) { return ptrn(i)->sym(); })); - assert(ins); + auto type = world.umax(ops); + auto sigma = world.nom_sigma(type, n)->set(loc(), sym()); + assert_emplace(def2fields, sigma, Array(n, [&](size_t i) { return ptrn(i)->sym(); })); sigma->set(0, ops[0]); for (size_t i = 1; i != n; ++i) { diff --git a/thorin/fe/lexer.cpp b/thorin/fe/lexer.cpp index 073eb233ee..284c596161 100644 --- a/thorin/fe/lexer.cpp +++ b/thorin/fe/lexer.cpp @@ -9,8 +9,8 @@ namespace thorin::fe { static bool issign(char32_t i) { return i == '+' || i == '-'; } static bool issubscsr(char32_t i) { return U'₀' <= i && i <= U'₉'; } -Lexer::Lexer(World& world, Sym file, std::istream& istream, std::ostream* md /*= nullptr*/) - : Super(file, istream) +Lexer::Lexer(World& world, std::istream& istream, const fs::path* path /*= nullptr*/, std::ostream* md /*= nullptr*/) + : Super(istream, path) , world_(world) , md_(md) { #define CODE(t, str) keywords_[world.sym(str)] = Tok::Tag::t; @@ -148,11 +148,11 @@ Tok Lexer::lex() { continue; } - err({loc_.file, ahead().pos}, "invalid input char '/'; maybe you wanted to start a comment?"); + err({loc_.path, ahead().pos}, "invalid input char '/'; maybe you wanted to start a comment?"); continue; } - err({loc_.file, ahead().pos}, "invalid input char '{}'", (char)ahead()); + err({loc_.path, ahead().pos}, "invalid input char '{}'", (char)ahead()); next(); } } diff --git a/thorin/fe/lexer.h b/thorin/fe/lexer.h index a3fa48ca16..9fda5b9f2e 100644 --- a/thorin/fe/lexer.h +++ b/thorin/fe/lexer.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include "thorin/fe/tok.h" @@ -17,15 +19,15 @@ class Lexer : public utf8::Lexer<3> { public: /// Creates a lexer to read Thorin files (see [Lexical Structure](@ref lex)). /// If @p md is not `nullptr`, a Markdown output will be generated. - Lexer(World& world, Sym file, std::istream& istream, std::ostream* md = nullptr); + Lexer(World& world, std::istream& istream, const fs::path* path = nullptr, std::ostream* md = nullptr); World& world() { return world_; } - Sym file() const { return loc_.file; } + const fs::path* path() const { return loc_.path; } Loc loc() const { return loc_; } Tok lex(); private: - Ahead next() override { + Char next() override { auto res = Super::next(); if (md_ && out_) { if (res.c32 == utf8::EoF) { @@ -55,7 +57,7 @@ class Lexer : public utf8::Lexer<3> { std::ostream* md_; bool out_ = true; SymMap keywords_; - std::optional cache_; + std::optional cache_ = std::nullopt; }; } // namespace thorin::fe diff --git a/thorin/fe/parser.cpp b/thorin/fe/parser.cpp index d1c29c9be4..13126fc0fd 100644 --- a/thorin/fe/parser.cpp +++ b/thorin/fe/parser.cpp @@ -1,12 +1,12 @@ #include "thorin/fe/parser.h" +#include #include #include #include #include "thorin/check.h" #include "thorin/def.h" -#include "thorin/dialects.h" #include "thorin/driver.h" #include "thorin/rewrite.h" @@ -31,24 +31,15 @@ using namespace std::string_literals; namespace thorin::fe { -Parser::Parser(World& world, Sym file, std::istream& istream, std::ostream* md) - : lexer_(world, file, istream, md) - , prev_(lexer_.loc()) - , bootstrapper_(world.sym(fs::path{*file}.filename().replace_extension("").string())) - , anonymous_(world.sym("_")) { - for (size_t i = 0; i != Max_Ahead; ++i) lex(); - prev_ = Loc(file, {1, 1}, {1, 1}); -} - /* * helpers */ Tok Parser::lex() { auto result = ahead(); - prev_ = ahead_[0].loc(); - for (size_t i = 0; i < Max_Ahead - 1; ++i) ahead_[i] = ahead_[i + 1]; - ahead_.back() = lexer_.lex(); + prev() = result.loc(); + for (size_t i = 0; i < Max_Ahead - 1; ++i) ahead(i) = ahead(i + 1); + ahead(Max_Ahead - 1) = lexer().lex(); return result; } @@ -74,39 +65,56 @@ void Parser::syntax_err(std::string_view what, const Tok& tok, std::string_view * entry points */ -Parser Parser::import_module(World& world, Sym name) { - auto file_name = *name + ".thorin"; +void Parser::parse_module() { + while (true) + if (ahead().tag() == Tok::Tag::K_import) + parse_import(); + else if (ahead().tag() == Tok::Tag::K_plugin) + parse_plugin(); + else + break; + + parse_decls({}); + expect(Tok::Tag::M_eof, "module"); +}; - std::string input_path{}; - for (const auto& path : world.driver().search_paths()) { - auto full_path = path / file_name; +void Parser::import(fs::path name, std::ostream* md) { + world().VLOG("import: {}", name); + auto filename = name; + filename.replace_extension("thorin"); // TODO error cases + fs::path rel_path; + for (const auto& path : driver().search_paths()) { + rel_path = path / filename; std::error_code ignore; - if (bool reg_file = fs::is_regular_file(full_path, ignore); reg_file && !ignore) { - input_path = full_path.string(); - break; - } + if (bool reg_file = fs::is_regular_file(rel_path, ignore); reg_file && !ignore) break; } - std::ifstream ifs(input_path); - - if (!ifs) throw std::runtime_error("could not find file '" + file_name + "'"); - thorin::fe::Parser parser(world, world.sym(input_path), ifs); - parser.parse_module(); + if (auto path = driver().add_import(std::move(rel_path), world().sym(name.string()))) { + auto ifs = std::ifstream(*path); + import(ifs, path, md); + } +} - world.add_imported(name); +void Parser::import(std::istream& is, const fs::path* path, std::ostream* md) { + world().VLOG("reading: {}", path ? path->string() : ""s); + if (!is) err("error: cannot read file '{}'", *path); - return parser; -} + lexers_.emplace(world(), is, path, md); + auto state = state_; -void Parser::bootstrap(std::ostream& h) { bootstrapper_.emit(h); } + for (size_t i = 0; i != Max_Ahead; ++i) ahead(i) = lexer().lex(); + prev() = Loc(path, {1, 1}); -void Parser::parse_module() { - while (ahead().tag() == Tok::Tag::K_import) parse_import(); + parse_module(); + state_ = state; + lexers_.pop(); +} - parse_decls({}); - expect(Tok::Tag::M_eof, "module"); -}; +void Parser::plugin(fs::path name) { + if (!driver().flags().bootstrap) driver().load(name.string()); + import(name); +} /* * misc @@ -116,21 +124,20 @@ void Parser::parse_import() { eat(Tok::Tag::K_import); auto name = expect(Tok::Tag::M_id, "import name"); expect(Tok::Tag::T_semicolon, "end of import"); + import(*name.sym()); +} - if (auto [_, ins] = imported_.emplace(name.sym()); !ins) return; - - // search file and import - auto parser = Parser::import_module(world(), name.sym()); - scopes_.merge(parser.scopes_); - - // transitvely remember which files we transitively imported - imported_.merge(parser.imported_); +void Parser::parse_plugin() { + eat(Tok::Tag::K_plugin); + auto name = expect(Tok::Tag::M_id, "plugin name"); + expect(Tok::Tag::T_semicolon, "end of import"); + plugin(*name.sym()); } Dbg Parser::parse_sym(std::string_view ctxt) { if (auto id = accept(Tok::Tag::M_id)) return {id->dbg()}; syntax_err("identifier", ctxt); - return {prev_, world().sym("")}; + return {prev(), world().sym("")}; } Ref Parser::parse_type_ascr(std::string_view ctxt) { @@ -266,7 +273,7 @@ Ref Parser::parse_var() { eat(Tok::Tag::T_at); auto dbg = parse_sym("variable"); auto nom = scopes_.find(dbg)->isa_nom(); - if (!nom) err(prev_, "variable must reference a nominal"); + if (!nom) err(prev(), "variable must reference a nominal"); return nom->var()->set(dbg); } @@ -409,8 +416,8 @@ Ref Parser::parse_lit() { if (lit.tag() == Tok::Tag::T_bot) return world().bot(world().type())->set(track.loc()); if (lit.tag() == Tok::Tag::T_top) return world().top(world().type())->set(track.loc()); - if (lit.tag() == Tok::Tag::L_s) err(prev_, ".Nat literal specified as signed but must be unsigned"); - if (lit.tag() == Tok::Tag::L_r) err(prev_, ".Nat literal specified as floating-point but must be unsigned"); + if (lit.tag() == Tok::Tag::L_s) err(prev(), ".Nat literal specified as signed but must be unsigned"); + if (lit.tag() == Tok::Tag::L_r) err(prev(), ".Nat literal specified as floating-point but must be unsigned"); return world().lit_nat(lit.u())->set(track.loc()); } @@ -578,28 +585,18 @@ Ref Parser::parse_decls(std::string_view ctxt) { void Parser::parse_ax() { auto track = tracker(); eat(Tok::Tag::K_ax); - auto ax = expect(Tok::Tag::M_ax, "name of an axiom"); - auto [dialect, tag, sub] = Axiom::split(world(), ax.sym()); + auto ax = expect(Tok::Tag::M_ax, "name of an axiom"); + auto [plugin, tag, sub] = Axiom::split(world(), ax.sym()); + auto&& [info, is_new] = driver().axiom2info(ax.sym(), plugin, tag, ax.loc()); - if (!dialect) err(ax.loc(), "invalid axiom name '{}'", ax); + if (!plugin) err(ax.loc(), "invalid axiom name '{}'", ax); if (sub) err(ax.loc(), "definition of axiom '{}' must not have sub in tag name", ax); - auto [it, is_new] = bootstrapper_.axioms.emplace(ax.sym(), h::AxiomInfo{}); - auto& [key, info] = *it; - if (is_new) { - info.dialect = dialect; - info.tag = tag; - info.tag_id = bootstrapper_.axioms.size() - 1; - } - - if (dialect != bootstrapper_.dialect()) { - // TODO - // err(ax.loc(), "axiom name `{}` implies a dialect name of `{}` but input file is named `{}`", ax, - // info.dialect, lexer_.file()); - } - - if (bootstrapper_.axioms.size() >= std::numeric_limits::max()) - err(ax.loc(), "exceeded maxinum number of axioms in current dialect"); + // if (plugin != bootstrapper_.plugin()) { + // TODO + // err(ax.loc(), "axiom name `{}` implies a plugin name of `{}` but input file is named `{}`", ax, + // info.plugin, lexer_.file()); + //} std::deque> new_subs; if (ahead().isa(Tok::Tag::D_paren_l)) { @@ -617,7 +614,7 @@ void Parser::parse_ax() { if (!is_new && new_subs.empty() && !info.subs.empty()) err(ax.loc(), "redeclaration of axiom '{}' without specifying new subs", ax); else if (!is_new && !new_subs.empty() && info.subs.empty()) - err(ax.loc(), "cannot extend subs of axiom '{}' which does not have subs", ax); + err(ax.loc(), "cannot extend subs of axiom '{}' because it was declared as a subless axiom", ax); auto type = parse_type_ascr("type ascription of an axiom"); if (!is_new && info.pi != (type->isa() != nullptr)) @@ -639,21 +636,21 @@ void Parser::parse_ax() { if (accept(Tok::Tag::T_comma)) trip = expect(Tok::Tag::L_u, "trip count for axiom").u(); - dialect_t d = *Axiom::mangle(dialect); - tag_t t = info.tag_id; - sub_t s = info.subs.size(); + plugin_t p = *Axiom::mangle(plugin); + tag_t t = info.tag_id; + sub_t s = info.subs.size(); if (new_subs.empty()) { - auto norm = driver().normalizer(d, t, 0); - auto axiom = world().axiom(norm, curry, trip, type, d, t, 0)->set(ax.loc(), ax.sym()); + auto norm = driver().normalizer(p, t, 0); + auto axiom = world().axiom(norm, curry, trip, type, p, t, 0)->set(ax.loc(), ax.sym()); scopes_.bind(ax.dbg(), axiom); } else { for (const auto& sub : new_subs) { auto name = world().sym(*ax.sym() + "."s + *sub.front()); - auto norm = driver().normalizer(d, t, s); - auto axiom = world().axiom(norm, curry, trip, type, d, t, s)->set(track.loc(), name); + auto norm = driver().normalizer(p, t, s); + auto axiom = world().axiom(norm, curry, trip, type, p, t, s)->set(track.loc(), name); for (auto& alias : sub) { auto sym = world().sym(*ax.sym() + "."s + *alias); - scopes_.bind({prev_, sym}, axiom); + scopes_.bind({prev(), sym}, axiom); } ++s; } @@ -723,7 +720,7 @@ Lam* Parser::parse_lam(bool decl) { bool is_cn = tok.isa(Tok::Tag::K_cn) || tok.isa(Tok::Tag::K_con); auto prec = is_cn ? Tok::Prec::Bot : Tok::Prec::Pi; bool external = decl && accept(Tok::Tag::K_extern).has_value(); - auto dbg = decl ? parse_sym("nominal lambda") : Dbg{prev_, anonymous_}; + auto dbg = decl ? parse_sym("nominal lambda") : Dbg{prev(), anonymous_}; auto outer = scopes_.curr(); scopes_.push(); @@ -783,7 +780,7 @@ Lam* Parser::parse_lam(bool decl) { auto body = accept(Tok::Tag::T_assign) ? parse_decls("body of a lambda") : nullptr; if (!body) { - if (!decl) err(prev_, "body of a lambda expression is mandatory"); + if (!decl) err(prev(), "body of a lambda expression is mandatory"); // TODO error message if filter is non .ff funs.back().second->unset(0); } @@ -816,14 +813,14 @@ void Parser::parse_def(Dbg dbg /*= {}*/) { if (ahead().isa(Tok::Tag::D_brace_l)) { scopes_.push(); parse_list("nominal definition", Tok::Tag::D_brace_l, [&]() { - if (i == n) err(prev_, "too many operands"); + if (i == n) err(prev(), "too many operands"); nom->set(i++, parse_decls("operand of a nominal")); }); scopes_.pop(); } else if (n - i == 1) { nom->set(i, parse_decls("operand of a nominal")); } else { - err(prev_, "expected operands for nominal definition"); + err(prev(), "expected operands for nominal definition"); } expect(Tok::Tag::T_semicolon, "end of a nominal definition"); diff --git a/thorin/fe/parser.h b/thorin/fe/parser.h index 7f399328c4..5f4c83ce25 100644 --- a/thorin/fe/parser.h +++ b/thorin/fe/parser.h @@ -2,7 +2,6 @@ #include "thorin/driver.h" -#include "thorin/be/h/bootstrapper.h" #include "thorin/fe/ast.h" #include "thorin/fe/lexer.h" #include "thorin/fe/scopes.h" @@ -29,17 +28,16 @@ namespace thorin::fe { /// * If default argument is **provided** we have the same behavior as in 2. class Parser { public: - Parser(World&, Sym file, std::istream&, std::ostream* md = nullptr); + Parser(World& world) + : world_(world) + , anonymous_(world.sym("_")) {} - World& world() { return lexer_.world(); } + World& world() { return world_; } Driver& driver() { return world().driver(); } - - /// @name entry points - ///@{ - static Parser import_module(World&, Sym); - void parse_module(); - void bootstrap(std::ostream&); - ///@} + void import(fs::path, std::ostream* md = nullptr); + void import(std::istream&, const fs::path* = nullptr, std::ostream* md = nullptr); + void plugin(fs::path); + const Scopes& scopes() const { return scopes_; } private: /// @name Tracker @@ -51,7 +49,7 @@ class Parser { : parser_(parser) , pos_(pos) {} - Loc loc() const { return {parser_.prev_.file, pos_, parser_.prev_.finis}; } + Loc loc() const { return {parser_.prev().path, pos_, parser_.prev().finis}; } Dbg dbg(Sym sym) const { return {loc(), sym}; } private: @@ -59,8 +57,48 @@ class Parser { Pos pos_; }; - /// @name misc parsing helpers + Loc& prev() { return state_.prev; } + + /// Factory method to build a Parser::Tracker. + Tracker tracker() { return Tracker(*this, ahead().loc().begin); } + ///@} + + /// @name get next token + ///@{ + /// Get lookahead. + Tok& ahead(size_t i = 0) { + assert(i < Max_Ahead); + return state_.ahead[i]; + } + + Lexer& lexer() { return lexers_.top(); } + bool main() const { return lexers_.size() == 1; } + + /// Invoke Lexer to retrieve next Tok%en. + Tok lex(); + + /// If Parser::ahead() is a @p tag, Parser::lex(), and return `true`. + std::optional accept(Tok::Tag tag); + + /// Parser::lex Parser::ahead() which must be a @p tag. + /// Issue err%or with @p ctxt otherwise. + Tok expect(Tok::Tag tag, std::string_view ctxt); + + /// Consume Parser::ahead which must be a @p tag; asserts otherwise. + Tok eat([[maybe_unused]] Tok::Tag tag) { + assert(tag == ahead().tag() && "internal parser error"); + return lex(); + } + ///@} + + /// @name parse misc ///@{ + void parse_module(); + Dbg parse_sym(std::string_view ctxt = {}); + void parse_import(); + void parse_plugin(); + Ref parse_type_ascr(std::string_view ctxt); + template void parse_list(std::string ctxt, Tok::Tag delim_l, F f, Tok::Tag sep = Tok::Tag::T_comma) { expect(delim_l, ctxt); @@ -70,12 +108,9 @@ class Parser { } expect(delim_r, std::string("closing delimiter of a ") + ctxt); } - Dbg parse_sym(std::string_view ctxt = {}); - void parse_import(); - Ref parse_type_ascr(std::string_view ctxt); ///@} - /// @name exprs + /// @name parse exprs ///@{ Ref parse_expr(std::string_view ctxt, Tok::Prec = Tok::Prec::Bot); Ref parse_primary_expr(std::string_view ctxt); @@ -83,7 +118,7 @@ class Parser { Ref parse_extract(Tracker, const Def*, Tok::Prec); ///@} - /// @name primary exprs + /// @name parse primary exprs ///@{ Ref parse_Cn(); Ref parse_arr(); @@ -99,7 +134,7 @@ class Parser { Lam* parse_lam(bool decl = false); ///@} - /// @name ptrns + /// @name parse ptrns ///@{ /// Depending on @p tag, this parses a `()`-style (Tok::Tag::D_paren_l) or `[]`-style (Tok::Tag::D_brckt_l) Ptrn. @@ -107,7 +142,7 @@ class Parser { std::unique_ptr parse_tuple_ptrn(Tracker, bool, Sym); ///@} - /// @name decls + /// @name parse decls ///@{ Ref parse_decls(std::string_view ctxt); void parse_ax(); @@ -118,35 +153,6 @@ class Parser { void parse_def(Dbg dbg = {}); ///@} - /// @name get next Tok and manage Location - ///@{ - - /// Factory method to build a Parser::Tracker. - Tracker tracker() { return Tracker(*this, ahead().loc().begin); } - - /// Get lookahead. - Tok ahead(size_t i = 0) const { - assert(i < Max_Ahead); - return ahead_[i]; - } - - /// Invoke Lexer to retrieve next Tok%en. - Tok lex(); - - /// If Parser::ahead() is a @p tag, Parser::lex(), and return `true`. - std::optional accept(Tok::Tag tag); - - /// Parser::lex Parser::ahead() which must be a @p tag. - /// Issue err%or with @p ctxt otherwise. - Tok expect(Tok::Tag tag, std::string_view ctxt); - - /// Consume Parser::ahead which must be a @p tag; asserts otherwise. - Tok eat([[maybe_unused]] Tok::Tag tag) { - assert(tag == ahead().tag() && "internal parser error"); - return lex(); - } - ///@} - /// @name error messages ///@{ /// Issue an error message of the form: @@ -157,15 +163,17 @@ class Parser { [[noreturn]] void syntax_err(std::string_view what, std::string_view ctxt) { syntax_err(what, ahead(), ctxt); } ///@} - Lexer lexer_; + static constexpr size_t Max_Ahead = 2; ///< maximum lookahead + using Ahead = std::array; + + World& world_; + std::stack lexers_; + struct { + Loc prev; + Ahead ahead; ///< SLL look ahead + } state_; Scopes scopes_; Def2Fields def2fields_; - Loc prev_; - std::string dialect_; - static constexpr size_t Max_Ahead = 2; ///< maximum lookahead - std::array ahead_; ///< SLL look ahead - SymSet imported_; - h::Bootstrapper bootstrapper_; Sym anonymous_; }; diff --git a/thorin/fe/scopes.cpp b/thorin/fe/scopes.cpp index 2a7acaaea1..8249a38ca2 100644 --- a/thorin/fe/scopes.cpp +++ b/thorin/fe/scopes.cpp @@ -32,9 +32,4 @@ void Scopes::bind(Scope* scope, Dbg dbg, const Def* def, bool rebind) { } } -void Scopes::merge(Scopes& other) { - assert(scopes_.size() == 1 && other.scopes_.size() == 1); - scopes_.front().merge(other.scopes_.front()); -} - } // namespace thorin::fe diff --git a/thorin/fe/scopes.h b/thorin/fe/scopes.h index 509ab1fa08..a7244d3626 100644 --- a/thorin/fe/scopes.h +++ b/thorin/fe/scopes.h @@ -22,7 +22,6 @@ class Scopes { const Def* find(Dbg) const; void bind(Scope*, Dbg, const Def*, bool rebind = false); void bind(Dbg dbg, const Def* def, bool rebind = false) { bind(&scopes_.back(), dbg, def, rebind); } - void merge(Scopes&); void swap(Scope& other) { std::swap(scopes_.back(), other); } private: diff --git a/thorin/fe/tok.h b/thorin/fe/tok.h index a6ea5a38eb..0d27fa8732 100644 --- a/thorin/fe/tok.h +++ b/thorin/fe/tok.h @@ -15,6 +15,7 @@ namespace fe { #define THORIN_KEY(m) \ m(K_module, ".module") \ m(K_import, ".import") \ + m(K_plugin, ".plugin") \ m(K_ax, ".ax" ) \ m(K_def, ".def" ) \ m(K_let, ".let" ) \ diff --git a/thorin/flags.h b/thorin/flags.h index 4206085a74..aeaed5a4b1 100644 --- a/thorin/flags.h +++ b/thorin/flags.h @@ -9,6 +9,7 @@ struct Flags { int dump_gid = 0; bool dump_recursive = false; bool disable_type_checking = false; // TODO implement this flag + bool bootstrap = false; #if THORIN_ENABLE_CHECKS bool reeval_breakpoints = false; bool trace_gids = false; diff --git a/thorin/pass/fp/eta_exp.cpp b/thorin/pass/fp/eta_exp.cpp index 8b924e6b2c..5b4a778a55 100644 --- a/thorin/pass/fp/eta_exp.cpp +++ b/thorin/pass/fp/eta_exp.cpp @@ -7,8 +7,8 @@ using namespace std::literals; namespace thorin { Lam* EtaExp::new2old(Lam* new_lam) { - if (auto i = new2old_.find(new_lam); i != new2old_.end()) { - auto root = new2old(i->second); // path compression + if (auto old = lookup(new2old_, new_lam)) { + auto root = new2old(old); // path compression assert(root != new_lam); new2old_[new_lam] = root; return root; @@ -19,20 +19,18 @@ Lam* EtaExp::new2old(Lam* new_lam) { const Def* EtaExp::rewrite(const Def* def) { if (std::ranges::none_of(def->ops(), [](const Def* def) { return def->isa(); })) return def; - if (auto i = old2new().find(def); i != old2new().end()) return i->second; + if (auto n = lookup(old2new(), def)) return n; auto& [_, new_ops] = *def2new_ops_.emplace(def, def->ops()).first; for (size_t i = 0, e = def->num_ops(); i != e; ++i) { if (auto lam = def->op(i)->isa_nom(); lam && lam->is_set()) { if (isa_callee(def, i)) { - if (auto it = exp2orig_.find(lam); it != exp2orig_.end()) new_ops[i] = it->second; - } else { - if (expand_.contains(lam)) { - if (new_ops[i] == lam) new_ops[i] = eta_exp(lam); - } else if (auto it = exp2orig_.find(lam); it != exp2orig_.end()) { - if (new_ops[i] == lam) new_ops[i] = eta_exp(it->second); - } + if (auto orig = lookup(exp2orig_, lam)) new_ops[i] = orig; + } else if (expand_.contains(lam)) { + if (new_ops[i] == lam) new_ops[i] = eta_exp(lam); + } else if (auto orig = lookup(exp2orig_, lam)) { + if (new_ops[i] == lam) new_ops[i] = eta_exp(orig); } } } diff --git a/thorin/pass/optimize.cpp b/thorin/pass/optimize.cpp index daff820da4..e250132cb3 100644 --- a/thorin/pass/optimize.cpp +++ b/thorin/pass/optimize.cpp @@ -49,8 +49,8 @@ void optimize(World& world) { world.DLOG("compilation using {} : {}", compilation, compilation->type()); // We can not directly access compile axioms here. - // But the compile dialect has not the necessary communication pipeline. - // Therefore, we register the handlers and let the compile dialect call them. + // But the compile plugin has not the necessary communication pipeline. + // Therefore, we register the handlers and let the compile plugin call them. PipelineBuilder pipe_builder(world); auto pipeline = compilation->as()->body(); diff --git a/thorin/pass/pipelinebuilder.cpp b/thorin/pass/pipelinebuilder.cpp index 15e2c03bf2..82eaaab9ce 100644 --- a/thorin/pass/pipelinebuilder.cpp +++ b/thorin/pass/pipelinebuilder.cpp @@ -1,7 +1,7 @@ #include "thorin/pass/pipelinebuilder.h" #include "thorin/def.h" -#include "thorin/dialects.h" +#include "thorin/plugin.h" #include "thorin/lattice.h" #include "thorin/pass/fp/beta_red.h" diff --git a/thorin/pass/pipelinebuilder.h b/thorin/pass/pipelinebuilder.h index 15032912c0..f63e9a091a 100644 --- a/thorin/pass/pipelinebuilder.h +++ b/thorin/pass/pipelinebuilder.h @@ -1,6 +1,6 @@ #pragma once -#include "thorin/dialects.h" +#include "thorin/plugin.h" #include "thorin/world.h" #include "thorin/pass/optimize.h" @@ -47,28 +47,28 @@ class PipelineBuilder { template void register_pass(Passes& passes, CArgs&&... args) { - passes[flags_t(Axiom::Base)] = [... args = std::forward(args)](World&, PipelineBuilder& builder, + assert_emplace(passes, flags_t(Axiom::Base), [... args = std::forward(args)](World&, PipelineBuilder& builder, const Def* app) { builder.add_pass

(app, args...); - }; + }); } template void register_phase(Passes& passes, CArgs&&... args) { - passes[flags_t(Axiom::Base)] = [... args = std::forward(args)](World&, PipelineBuilder& builder, + assert_emplace(passes, flags_t(Axiom::Base), [... args = std::forward(args)](World&, PipelineBuilder& builder, const Def*) { builder.add_phase

(args...); - }; + }); } template void register_pass_with_arg(Passes& passes) { - passes[flags_t(Axiom::Base)] = [](World& world, PipelineBuilder& builder, const Def* app) { + assert_emplace(passes, flags_t(Axiom::Base), [](World& world, PipelineBuilder& builder, const Def* app) { auto pass_arg = (Q*)(builder.pass(app->as()->arg())); world.DLOG("register using arg: {} of type {} for gid {}", pass_arg, typeid(Q).name(), app->as()->arg()->gid()); builder.add_pass

(app, pass_arg); - }; + }); } } // namespace thorin diff --git a/thorin/plugin.h b/thorin/plugin.h new file mode 100644 index 0000000000..6197fcd923 --- /dev/null +++ b/thorin/plugin.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include "thorin/config.h" +#include "thorin/def.h" + +namespace thorin { + +class PipelineBuilder; + +/// `axiom ↦ (pipeline part) × (axiom application) → ()`
+/// The function should inspect App%lication to construct the Pass/Phase and add it to the pipeline. +using Passes = absl::flat_hash_map>; +using Backends = absl::btree_map; +using Normalizers = absl::flat_hash_map; + +extern "C" { +/// Basic info and registration function pointer to be returned from a specific plugin. +/// Use Driver to load such a plugin. +struct Plugin { + using Handle = std::unique_ptr; + + /// Name of the Plugin. + const char* plugin_name; + + /// Callback for registering the Plugin's callbacks for the pipeline extension points. + void (*register_passes)(Passes& passes); + + /// Callback for registering the mapping from backend names to emission functions in the given \a backends map. + void (*register_backends)(Backends& backends); + + /// Callback for registering the mapping from axiom ids to normalizer functions in the given \a normalizers map. + void (*register_normalizers)(Normalizers& normalizers); +}; +} + +/// To be implemented and exported by a plugin; shall return a filled Plugin. +extern "C" THORIN_EXPORT thorin::Plugin thorin_get_plugin(); + +} // namespace thorin diff --git a/thorin/rewrite.cpp b/thorin/rewrite.cpp index d1368b3e11..9ef3b9804b 100644 --- a/thorin/rewrite.cpp +++ b/thorin/rewrite.cpp @@ -6,7 +6,7 @@ namespace thorin { -const Def* Rewriter::rewrite(Ref old_def) { +Ref Rewriter::rewrite(Ref old_def) { if (!old_def) return nullptr; if (old_def->isa()) return world().univ(); if (auto i = old2new_.find(old_def); i != old2new_.end()) return i->second; @@ -16,13 +16,13 @@ const Def* Rewriter::rewrite(Ref old_def) { return map(old_def, new_def); } -const Def* Rewriter::rewrite_structural(const Def* old_def) { +Ref Rewriter::rewrite_structural(Ref old_def) { auto new_type = rewrite(old_def->type()); DefArray new_ops(old_def->num_ops(), [&](auto i) { return rewrite(old_def->op(i)); }); return old_def->rebuild(world(), new_type, new_ops); } -const Def* Rewriter::rewrite_nom(Def* old_nom) { +Ref Rewriter::rewrite_nom(Def* old_nom) { auto new_type = rewrite(old_nom->type()); auto new_nom = old_nom->stub(world(), new_type); map(old_nom, new_nom); @@ -35,28 +35,26 @@ const Def* Rewriter::rewrite_nom(Def* old_nom) { return new_nom; } -const Def* rewrite(const Def* def, const Def* old_def, const Def* new_def, const Scope& scope) { +Ref rewrite(Ref def, Ref old_def, Ref new_def, const Scope& scope) { ScopeRewriter rewriter(def->world(), scope); rewriter.map(old_def, new_def); return rewriter.rewrite(def); } -const Def* rewrite(Def* nom, const Def* arg, size_t i, const Scope& scope) { - return rewrite(nom->op(i), nom->var(), arg, scope); -} +Ref rewrite(Def* nom, Ref arg, size_t i, const Scope& scope) { return rewrite(nom->op(i), nom->var(), arg, scope); } -const Def* rewrite(Def* nom, const Def* arg, size_t i) { +Ref rewrite(Def* nom, Ref arg, size_t i) { Scope scope(nom); return rewrite(nom, arg, i, scope); } -DefArray rewrite(Def* nom, const Def* arg, const Scope& scope) { +DefArray rewrite(Def* nom, Ref arg, const Scope& scope) { ScopeRewriter rewriter(nom->world(), scope); rewriter.map(nom->var(), arg); return DefArray(nom->num_ops(), [&](size_t i) { return rewriter.rewrite(nom->op(i)); }); } -DefArray rewrite(Def* nom, const Def* arg) { +DefArray rewrite(Def* nom, Ref arg) { Scope scope(nom); return rewrite(nom, arg, scope); } diff --git a/thorin/rewrite.h b/thorin/rewrite.h index 0c7930ef76..c98ae40bbc 100644 --- a/thorin/rewrite.h +++ b/thorin/rewrite.h @@ -17,10 +17,10 @@ class Rewriter { /// @name recursively rewrite old Defs ///@{ - const Def* map(const Def* old_def, const Def* new_def) { return old2new_[old_def] = new_def; } - virtual const Def* rewrite(Ref); - virtual const Def* rewrite_structural(const Def*); - virtual const Def* rewrite_nom(Def*); + Ref map(Ref old_def, Ref new_def) { return old2new_[old_def] = new_def; } + virtual Ref rewrite(Ref); + virtual Ref rewrite_structural(Ref); + virtual Ref rewrite_nom(Def*); ///@} private: @@ -28,6 +28,7 @@ class Rewriter { Def2Def old2new_; }; +/// Stops rewriting when leaving the Scope. class ScopeRewriter : public Rewriter { public: ScopeRewriter(World& world, const Scope& scope) @@ -36,7 +37,7 @@ class ScopeRewriter : public Rewriter { const Scope& scope() const { return scope_; } - const Def* rewrite(Ref old_def) override { + Ref rewrite(Ref old_def) override { if (!old_def || !scope().bound(old_def)) return old_def; return Rewriter::rewrite(old_def); } @@ -45,19 +46,31 @@ class ScopeRewriter : public Rewriter { const Scope& scope_; }; +class InferRewriter : public Rewriter { +public: + InferRewriter(World& world) + : Rewriter(world) {} + + Ref rewrite(Ref old_def) override { + if (old_def->isa_nom()) return old_def; + if (old_def->has_dep(Dep::Infer)) return Rewriter::rewrite(old_def); + return old_def; + } +}; + /// Rewrites @p def by mapping @p old_def to @p new_def while obeying @p scope. -const Def* rewrite(const Def* def, const Def* old_def, const Def* new_def, const Scope& scope); +Ref rewrite(Ref def, Ref old_def, Ref new_def, const Scope& scope); /// Rewrites @p nom's @p i^th op by substituting @p nom's @p Var with @p arg while obeying @p nom's @p scope. -const Def* rewrite(Def* nom, const Def* arg, size_t i); +Ref rewrite(Def* nom, Ref arg, size_t i); /// Same as above but uses @p scope as an optimization instead of computing a new Scope. -const Def* rewrite(Def* nom, const Def* arg, size_t i, const Scope& scope); +Ref rewrite(Def* nom, Ref arg, size_t i, const Scope& scope); /// Rewrites @p nom's ops by substituting @p nom's @p Var with @p arg while obeying @p nom's @p scope. -DefArray rewrite(Def* nom, const Def* arg); +DefArray rewrite(Def* nom, Ref arg); /// Same as above but uses @p scope as an optimization instead of computing a new Scope. -DefArray rewrite(Def* nom, const Def* arg, const Scope& scope); +DefArray rewrite(Def* nom, Ref arg, const Scope& scope); } // namespace thorin diff --git a/thorin/util/container.h b/thorin/util/container.h index 7b04a76cc7..672e8f57df 100644 --- a/thorin/util/container.h +++ b/thorin/util/container.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -8,6 +9,7 @@ #include #include +#include "thorin/util/assert.h" #include "thorin/util/hash.h" namespace thorin { @@ -115,14 +117,23 @@ template using GIDNodeSet = absl::node_hash_set auto lookup(const C& container, const K& key) { auto i = container.find(key); - if constexpr (std::is_pointer_v) { + if constexpr (std::is_pointer_v) return i != container.end() ? i->second : nullptr; - } else { + else return i != container.end() ? &i->second : nullptr; - } +} + +/// Invokes `emplace` on @p container, asserts that insertion actually happened, and returns the iterator. +template +auto assert_emplace(C& container, Args&&... args) { + auto [i, ins] = container.emplace(std::forward(args)...); + assert_unused(ins); + return i; } } // namespace thorin diff --git a/thorin/util/dl.cpp b/thorin/util/dl.cpp index e41a5f9b72..36951eb3d8 100644 --- a/thorin/util/dl.cpp +++ b/thorin/util/dl.cpp @@ -35,7 +35,7 @@ void* open(const std::string& file) { if (HMODULE handle = LoadLibraryA(file.c_str())) { return static_cast(handle); } else { - err("could not load dialect plugin '{}' due to error '{}'\n" + err("could not load plugin '{}' due to error '{}'\n" "see https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes\n", file, GetLastError()); } diff --git a/thorin/util/loc.cpp b/thorin/util/loc.cpp index 23fdb944f8..22e7bb345f 100644 --- a/thorin/util/loc.cpp +++ b/thorin/util/loc.cpp @@ -11,8 +11,8 @@ std::ostream& operator<<(std::ostream& os, const Pos pos) { } std::ostream& operator<<(std::ostream& os, const Loc loc) { - if (loc.begin) { - os << loc.file << ':' << loc.begin; + if (loc) { + os << (loc.path ? *loc.path : "") << ':' << loc.begin; if (loc.begin != loc.finis) os << '-' << loc.finis; return os; } diff --git a/thorin/util/loc.h b/thorin/util/loc.h index c563eb3e27..f5c82e1050 100644 --- a/thorin/util/loc.h +++ b/thorin/util/loc.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include @@ -8,6 +10,8 @@ namespace thorin { +namespace fs = std::filesystem; + struct Pos { Pos() = default; Pos(uint16_t row) @@ -23,21 +27,29 @@ struct Pos { uint16_t col = 0; }; +/// Loc%ation in a File. +/// @warning Loc::path is only a pointer and it is your job to guarantee +/// that the underlying `std::filesystem::path` outlives this Loc%ation. +/// In Thorin itself, the Driver takes care of this. struct Loc { Loc() = default; - Loc(Sym file, Pos begin, Pos finis) - : file(file) + Loc(const fs::path* path, Pos begin, Pos finis) + : path(path) , begin(begin) , finis(finis) {} - Loc(Sym file, Pos pos) + Loc(const fs::path* file, Pos pos) : Loc(file, pos, pos) {} + Loc(Pos begin, Pos finis) + : Loc(nullptr, begin, finis) {} + Loc(Pos pos) + : Loc(nullptr, pos, pos) {} - Loc anew_begin() const { return {file, begin, begin}; } - Loc anew_finis() const { return {file, finis, finis}; } + Loc anew_begin() const { return {path, begin, begin}; } + Loc anew_finis() const { return {path, finis, finis}; } explicit operator bool() const { return (bool)begin; } void dump(); - Sym file; + const fs::path* path = nullptr; Pos begin = {}; Pos finis = {}; ///< It's called `finis` because it refers to the **last** character within this Loc%ation. @@ -56,7 +68,8 @@ std::ostream& operator<<(std::ostream&, const Pos); std::ostream& operator<<(std::ostream&, const Loc); inline bool operator==(Pos p1, Pos p2) { return p1.row == p2.row && p1.col == p2.col; } -inline bool operator==(Loc l1, Loc l2) { return l1.begin == l2.begin && l1.finis == l2.finis && l1.file == l2.file; } +/// @note Loc::path is only checked via pointer equality. +inline bool operator==(Loc l1, Loc l2) { return l1.begin == l2.begin && l1.finis == l2.finis && l1.path == l2.path; } struct Dbg { Loc loc; diff --git a/thorin/util/log.h b/thorin/util/log.h index 9a6defc287..7be2ab3637 100644 --- a/thorin/util/log.h +++ b/thorin/util/log.h @@ -10,9 +10,6 @@ class Log { public: enum class Level { Error, Warn, Info, Verbose, Debug }; - Log(SymPool& sym_pool) - : sym_pool_(sym_pool) {} - /// @name getters ///@{ Level level() const { return max_level_; } @@ -49,7 +46,8 @@ class Log { } template void log(Level level, const char* file, uint16_t line, const char* fmt, Args&&... args) { - log(level, Loc(sym_pool_.sym(file), line), fmt, std::forward(args)...); + auto path = fs::path(file); + log(level, Loc(&path, line), fmt, std::forward(args)...); } ///@} @@ -64,7 +62,6 @@ class Log { private: std::ostream* ostream_ = nullptr; Level max_level_ = Level::Error; - SymPool& sym_pool_; }; // clang-format off diff --git a/thorin/util/print.h b/thorin/util/print.h index ad103875a9..eb4cb3bd22 100644 --- a/thorin/util/print.h +++ b/thorin/util/print.h @@ -23,11 +23,15 @@ struct Elem { }; namespace detail { -template + +template +concept Printable = requires(std::ostream& os, T a) { os << a; }; + +template concept Elemable = requires(T elem) { - elem.range; - elem.f; -}; + elem.range; + elem.f; + }; template std::ostream& range(std::ostream& os, const R& r, F f, const char* sep = ", ") { @@ -35,11 +39,10 @@ std::ostream& range(std::ostream& os, const R& r, F f, const char* sep = ", ") { for (const auto& elem : r) { for (auto i = cur_sep; *i != '\0'; ++i) os << *i; - if constexpr (std::is_invocable_v) { + if constexpr (std::is_invocable_v) std::invoke(f, os, elem); - } else { + else std::invoke(f, elem); - } cur_sep = sep; } return os; @@ -102,13 +105,16 @@ std::ostream& print(std::ostream& os, const char* s, T&& t, Args&&... args) { std::invoke(t); } else if constexpr (std::is_invocable_v) { std::invoke(t, os); + } else if constexpr (detail::Printable) { + os << t; } else if constexpr (detail::Elemable) { detail::range(os, t.range, t.f, spec.c_str()); } else if constexpr (std::ranges::range) { detail::range( os, t, [&](const auto& x) { os << x; }, spec.c_str()); } else { - os << t; + []() { static_assert(flag, "cannot print T t"); } + (); } ++s; // skip closing brace '}' diff --git a/thorin/util/sys.cpp b/thorin/util/sys.cpp index a46e2f47b6..001de9b506 100644 --- a/thorin/util/sys.cpp +++ b/thorin/util/sys.cpp @@ -21,6 +21,7 @@ #endif using namespace std::string_literals; +namespace fs = std::filesystem; namespace thorin::sys { diff --git a/thorin/util/types.h b/thorin/util/types.h index 041181afc6..80e5eddab3 100644 --- a/thorin/util/types.h +++ b/thorin/util/types.h @@ -75,12 +75,12 @@ template using w2u = typename w2u_::type; template using w2s = typename w2s_::type; template using w2f = typename w2f_::type; -using nat_t = u64; -using node_t = u8; -using flags_t = u64; -using dialect_t = u64; -using tag_t = u8; -using sub_t = u8; +using nat_t = u64; +using node_t = u8; +using flags_t = u64; +using plugin_t = u64; +using tag_t = u8; +using sub_t = u8; /// A `size_t` literal. Use `0_s` to disambiguate `0` from `nullptr`. constexpr size_t operator""_s(unsigned long long int i) { return size_t(i); } diff --git a/thorin/util/utf8.h b/thorin/util/utf8.h index ed46623953..75d1f70855 100644 --- a/thorin/util/utf8.h +++ b/thorin/util/utf8.h @@ -40,9 +40,9 @@ bool decode(std::ostream& os, char32_t c); template class Lexer { public: - Lexer(Sym file, std::istream& istream) + Lexer(std::istream& istream, const fs::path* path) : istream_(istream) - , loc_(file, {0, 0}) { + , loc_(path, {0, 0}) { ahead_.back().pos = {1, 0}; for (size_t i = 0; i != Max_Ahead; ++i) next(); accept(BOM); // eat utf-8 BOM if present @@ -50,9 +50,9 @@ class Lexer { virtual ~Lexer() {} protected: - struct Ahead { - Ahead() = default; - Ahead(char32_t c32, Pos pos) + struct Char { + Char() = default; + Char(char32_t c32, Pos pos) : c32(c32) , pos(pos) {} @@ -61,12 +61,12 @@ class Lexer { Pos pos; }; - Ahead ahead(size_t i = 0) const { + Char ahead(size_t i = 0) const { assert(i < Max_Ahead); return ahead_[i]; } - virtual Ahead next() { + virtual Char next() { auto result = ahead(); auto prev = ahead_.back().pos; @@ -107,7 +107,7 @@ class Lexer { std::istream& istream_; Loc loc_; std::string str_; - std::array ahead_; + std::array ahead_; }; } // namespace thorin::utf8 diff --git a/thorin/world.cpp b/thorin/world.cpp index d7b2ccea8e..8b74c4a792 100644 --- a/thorin/world.cpp +++ b/thorin/world.cpp @@ -124,7 +124,35 @@ Ref World::umax(DefArray ops) { return sort == Sort::Univ ? ldef : type(ldef); } +Ref World::iapp(Ref callee, Ref arg) { + while (auto pi = callee->type()->isa()) { + if (pi->implicit()) { + auto infer = nom_infer_entity(); + auto a = app(callee, infer); + callee = a; + } else { + // resolve Infers now if possible before normalizers are run + if (auto app = callee->isa(); app && app->curry() == 1) { + checker().assignable(callee->type()->as()->dom(), arg); + auto apps = decurry(app); + callee = apps.front()->callee(); + for (auto app : apps) callee = this->app(callee, refer(app->arg())); + } + break; + } + } + + return app(callee, arg); +} + Ref World::app(Ref callee, Ref arg) { + // try to eliminate Infers if present - TODO better place for this? + if (callee->has_dep(Dep::Infer) || arg->has_dep(Dep::Infer)) { + InferRewriter rw(*this); + callee = rw.rewrite(callee); + arg = rw.rewrite(arg); + } + auto pi = callee->type()->isa(); // (a, b)#i arg where a = A -> B; b = A -> B @@ -463,7 +491,6 @@ Ref World::test(Ref value, Ref probe, Ref match, Ref clash) { Ref World::singleton(Ref inner_type) { return unify(1, this->type<1>(), inner_type); } -// appends a suffix or an increasing number if the suffix already exists Sym World::append_suffix(Sym symbol, std::string suffix) { auto name = *symbol; @@ -484,31 +511,6 @@ Sym World::append_suffix(Sym symbol, std::string suffix) { return sym(std::move(name)); } -/* - * implicits - */ - -Ref World::iapp(Ref callee, Ref arg) { - while (auto pi = callee->type()->isa()) { - if (pi->implicit()) { - auto infer = nom_infer_entity(); - auto a = app(callee, infer); - callee = a; - } else { - // resolve Infers now if possible before normalizers are run - if (auto app = callee->isa(); app && app->curry() == 1) { - checker().assignable(callee->type()->as()->dom(), arg); - auto apps = decurry(app); - callee = apps.front()->callee(); - for (auto app : apps) callee = this->app(callee, refer(app->arg())); - } - break; - } - } - - return app(callee, arg); -} - /* * debugging */ diff --git a/thorin/world.h b/thorin/world.h index 9a83679bfe..314e8c04c2 100644 --- a/thorin/world.h +++ b/thorin/world.h @@ -47,7 +47,6 @@ class World { mutable bool frozen = false; } pod; - absl::btree_set imported_dialects; #if THORIN_ENABLE_CHECKS absl::flat_hash_set breakpoints; #endif @@ -56,7 +55,6 @@ class World { assert((!s1.pod.loc || !s2.pod.loc) && "Why is emit_loc() still set?"); // clang-format off swap(s1.pod, s2.pod); - swap(s1.imported_dialects, s2.imported_dialects); // clang-format on #if THORIN_ENABLE_CHECKS swap(s1.breakpoints, s2.breakpoints); @@ -88,10 +86,7 @@ class World { Sym name() const { return state_.pod.name; } void set(Sym name) { state_.pod.name = name; } - Sym append_suffix(Sym name, std::string suffix); - - void add_imported(Sym name) { state_.imported_dialects.emplace(name); } - const auto& imported() const { return state_.imported_dialects; } + void set(std::string_view name) { state_.pod.name = sym(name); } /// Manage global identifier - a unique number for each Def. u32 curr_gid() const { return state_.pod.curr_gid; } @@ -113,6 +108,8 @@ class World { Sym sym(std::string_view); Sym sym(const char*); Sym sym(std::string); + /// Appends a @p suffix or an increasing number if the suffix already exists. + Sym append_suffix(Sym name, std::string suffix); ///@} /// @name freeze @@ -155,20 +152,17 @@ class World { const auto& externals() const { return move_.externals; } bool empty() { return move_.externals.empty(); } void make_external(Def* def) { + assert(!def->external_); def->external_ = true; - assert(def->sym()); - move_.externals.emplace(def->sym(), def); // TODO enable assert again - // auto [i, ins] = move_.externals.emplace(name, def); - // assert((ins || (def == i->second)) && "two different externals registered with the same name"); + assert_emplace(move_.externals, def->sym(), def); } void make_internal(Def* def) { + assert(def->external_); def->external_ = false; - move_.externals.erase(def->sym()); - } - Def* lookup(Sym name) { - auto i = move_.externals.find(name); - return i != move_.externals.end() ? i->second : nullptr; + auto num = move_.externals.erase(def->sym()); + assert(num == 1); } + Def* lookup(Sym name) { return thorin::lookup(move_.externals, name); } ///@} /// @name Univ, Type, Var, Proxy, Infer @@ -208,31 +202,31 @@ class World { /// @name Axiom ///@{ - const Axiom* axiom(NormalizeFn n, u8 curry, u8 trip, Ref type, dialect_t d, tag_t t, sub_t s) { - auto ax = unify(0, n, curry, trip, type, d, t, s); + const Axiom* axiom(NormalizeFn n, u8 curry, u8 trip, Ref type, plugin_t p, tag_t t, sub_t s) { + auto ax = unify(0, n, curry, trip, type, p, t, s); return move_.axioms[ax->flags()] = ax; } - const Axiom* axiom(Ref type, dialect_t d, tag_t t, sub_t s) { return axiom(nullptr, 0, 0, type, d, t, s); } + const Axiom* axiom(Ref type, plugin_t p, tag_t t, sub_t s) { return axiom(nullptr, 0, 0, type, p, t, s); } /// Builds a fresh Axiom with descending Axiom::sub. /// This is useful during testing to come up with some entitiy of a specific type. - /// It uses the dialect Axiom::Global_Dialect and starts with `0` for Axiom::sub and counts up from there. + /// It uses the plugin Axiom::Global_Plugin and starts with `0` for Axiom::sub and counts up from there. /// The Axiom::tag is set to `0` and the Axiom::normalizer to `nullptr`. const Axiom* axiom(NormalizeFn n, u8 curry, u8 trip, Ref type) { - return axiom(n, curry, trip, type, Axiom::Global_Dialect, 0, state_.pod.curr_sub++); + return axiom(n, curry, trip, type, Axiom::Global_Plugin, 0, state_.pod.curr_sub++); } const Axiom* axiom(Ref type) { return axiom(nullptr, 0, 0, type); } ///< See above. - /// Get Axiom from a dialect. + /// Get Axiom from a plugin. /// Use this to get an Axiom via Axiom::id. template const Axiom* ax(Id id) { u64 flags = static_cast(id); if (auto i = move_.axioms.find(flags); i != move_.axioms.end()) return i->second; - err("Axiom with ID '{}' not found; demangled dialect name is '{}'", flags, Axiom::demangle(*this, flags)); + err("Axiom with ID '{}' not found; demangled plugin name is '{}'", flags, Axiom::demangle(*this, flags)); } - /// Get Axiom from a dialect. + /// Get Axiom from a plugin. /// Can be used to get an Axiom without sub-tags. /// E.g. use `w.ax();` to get the `%mem.M` Axiom. template @@ -499,8 +493,7 @@ class World { if (flags().trace_gids) outln("{}: {} - {}", def->node_name(), def->gid(), def->flags()); if (breakpoints().contains(def->gid())) thorin::breakpoint(); #endif - auto [_, ins] = move_.defs.emplace(def); - assert_unused(ins); + assert_emplace(move_.defs, def); return def; } ///@}