From e17744ead95352e00f6350879c9918d0ea48a7e6 Mon Sep 17 00:00:00 2001 From: jeaye Date: Tue, 10 Oct 2023 00:08:08 -0700 Subject: [PATCH] Add module loading and writing This involves compiling namespaces to .cpp files within the class path and then loading them up again. Namespaces are written to their own file, with a special `__init` file also written. The `__init` file needs to be loaded prior to the namespace file being loaded, since it contains all of the dependencies. Any functions created within the namespace are nested modules, using the same `foo.bar$spam` syntax as the JVM. They're all written to individual files, same as with Clojure, and they're loaded using the `__init` module. Having this allows us to load `clojure.core` from pre-compiled .cpp files, rather than from jank source. The startup time of the compiler, when loading `clojure.core` this way, drops from 8.7s to 3.7s, which means it's more than twice as fast now. We can drop this further by compiling the .cpp files to LLVM IR files, or, even further, to object files or pre-compiled modules (PCMs). The framework for this is present, but Cling doesn't actually have an interface to load either of those, so some more intricate work will be needed. I'm going to stick with the .cpp files for now and flesh out the rest of the module loading. This bout of work is not primarily focused on performance gains. --- include/cpp/jank/codegen/processor.hpp | 18 +- include/cpp/jank/detail/to_runtime_data.hpp | 5 + include/cpp/jank/evaluate.hpp | 5 + include/cpp/jank/jit/processor.hpp | 9 +- include/cpp/jank/result.hpp | 2 + include/cpp/jank/runtime/context.hpp | 31 ++- include/cpp/jank/runtime/module/loader.hpp | 33 ++- include/cpp/jank/runtime/obj/jit_function.hpp | 2 - include/cpp/jank/type.hpp | 3 + src/cpp/jank/analyze/processor.cpp | 41 +++- src/cpp/jank/codegen/processor.cpp | 124 ++++++++-- src/cpp/jank/evaluate.cpp | 58 ++++- src/cpp/jank/jit/processor.cpp | 19 +- src/cpp/jank/runtime/context.cpp | 136 +++++++++-- src/cpp/jank/runtime/module/loader.cpp | 223 ++++++++++++++++-- src/cpp/main.cpp | 18 +- src/jank/clojure/core.jank | 18 ++ test/cpp/jank/analyze/box.cpp | 69 +++--- test/cpp/jank/jit/processor.cpp | 5 +- 19 files changed, 681 insertions(+), 138 deletions(-) diff --git a/include/cpp/jank/codegen/processor.hpp b/include/cpp/jank/codegen/processor.hpp index 29d8f11c2..a21127874 100644 --- a/include/cpp/jank/codegen/processor.hpp +++ b/include/cpp/jank/codegen/processor.hpp @@ -43,6 +43,13 @@ namespace jank::codegen native_string unboxed_name; }; + enum class compilation_target + { + ns, + function, + repl + }; + /* Codegen processors render a single function expression to a C++ functor. REPL expressions * are wrapped in a nullary functor. These functors nest arbitrarily, if an expression has more * fn values of its own, each one rendered with its own codegen processor. */ @@ -52,12 +59,16 @@ namespace jank::codegen processor ( runtime::context &rt_ctx, - analyze::expression_ptr const &expr + analyze::expression_ptr const &expr, + native_string_view const &module, + compilation_target target ); processor ( runtime::context &rt_ctx, - analyze::expr::function const &expr + analyze::expr::function const &expr, + native_string_view const &module, + compilation_target target ); processor(processor const &) = delete; processor(processor &&) noexcept = default; @@ -159,6 +170,7 @@ namespace jank::codegen void build_footer(); native_string expression_str(bool box_needed, bool const auto_call); + native_string module_init_str(native_string_view const &module); void format_elided_var ( @@ -191,7 +203,9 @@ namespace jank::codegen /* This is stored just to keep the expression alive. */ analyze::expression_ptr root_expr{}; analyze::expr::function const &root_fn; + native_string module; + compilation_target target{}; runtime::obj::symbol struct_name; fmt::memory_buffer header_buffer; fmt::memory_buffer body_buffer; diff --git a/include/cpp/jank/detail/to_runtime_data.hpp b/include/cpp/jank/detail/to_runtime_data.hpp index bf375f716..3e58b4be1 100644 --- a/include/cpp/jank/detail/to_runtime_data.hpp +++ b/include/cpp/jank/detail/to_runtime_data.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include namespace jank::detail @@ -26,6 +28,9 @@ namespace jank::detail inline runtime::object_ptr to_runtime_data(runtime::obj::symbol const &d) { return make_box(d); } + inline runtime::object_ptr to_runtime_data(boost::filesystem::path const &p) + { return make_box(p.string()); } + template runtime::object_ptr to_runtime_data(native_unordered_map const &m) { diff --git a/include/cpp/jank/evaluate.hpp b/include/cpp/jank/evaluate.hpp index 0b03c15d6..de6a68aca 100644 --- a/include/cpp/jank/evaluate.hpp +++ b/include/cpp/jank/evaluate.hpp @@ -5,6 +5,11 @@ namespace jank::evaluate { analyze::expr::function wrap_expression(analyze::expression_ptr const expr); + analyze::expr::function wrap_expressions + ( + native_vector const &exprs, + analyze::processor const &an_prc + ); runtime::object_ptr eval ( diff --git a/include/cpp/jank/jit/processor.hpp b/include/cpp/jank/jit/processor.hpp index 689819b33..d02b16071 100644 --- a/include/cpp/jank/jit/processor.hpp +++ b/include/cpp/jank/jit/processor.hpp @@ -5,17 +5,18 @@ #include #include -#include #include +namespace jank::runtime +{ struct context; } + namespace jank::jit { struct processor { - processor(); + processor(runtime::context &rt_ctx); - result, native_string> eval - (runtime::context &rt_ctx, codegen::processor &cg_prc) const; + result, native_string> eval(codegen::processor &cg_prc) const; void eval_string(native_string const &s) const; std::unique_ptr interpreter; diff --git a/include/cpp/jank/result.hpp b/include/cpp/jank/result.hpp index 8f3332070..ac8083cc0 100644 --- a/include/cpp/jank/result.hpp +++ b/include/cpp/jank/result.hpp @@ -182,6 +182,8 @@ namespace jank std::cout << "error: expected ok result, but found: " << err << std::endl; throw err; } + void expect_ok() const + { assert_ok(); } E const& expect_err() const { return boost::get(data); } diff --git a/include/cpp/jank/runtime/context.hpp b/include/cpp/jank/runtime/context.hpp index 2504d4659..80a7788a3 100644 --- a/include/cpp/jank/runtime/context.hpp +++ b/include/cpp/jank/runtime/context.hpp @@ -7,9 +7,11 @@ #include #include +#include #include #include #include +#include namespace jank::jit { struct processor; } @@ -18,7 +20,7 @@ namespace jank::runtime { struct context { - context(); + context(option const &class_path = none); context(context const&); context(context &&) = delete; @@ -41,10 +43,19 @@ namespace jank::runtime static object_ptr print(object_ptr o, object_ptr more); static object_ptr println(object_ptr more); - void eval_prelude(jit::processor const &); - object_ptr eval_file(native_string_view const &path, jit::processor const &); - object_ptr eval_string(native_string_view const &code, jit::processor const &); - native_vector analyze_string(native_string_view const &code, jit::processor const &jit_prc, native_bool const eval = true); + void eval_prelude(); + object_ptr eval_file(native_string_view const &path); + object_ptr eval_string(native_string_view const &code); + native_vector analyze_string(native_string_view const &code, native_bool const eval = true); + + /* Finds the specified module on the class path and loads it. If + * the module is already loaded, nothing is done. */ + result load_module(native_string_view const &module); + + /* Does all the same work as load_module, but also writes compiled files to the file system. */ + result compile_module(native_string_view const &module); + + void write_module(native_string_view const &module, native_string_view const &contents) const; /* Generates a unique name for use with anything from codgen structs, * lifted vars, to shadowed locals. */ @@ -74,6 +85,14 @@ namespace jank::runtime /* The analyze processor is reused across evaluations so we can keep the semantic information * of previous code. This is essential for REPL use. */ /* TODO: This needs to be synchronized. */ - jank::analyze::processor an_prc{ *this }; + analyze::processor an_prc{ *this }; + jit::processor jit_prc; + /* TODO: This needs to be a dynamic var. */ + bool compiling{}; + /* TODO: This needs to be a dynamic var. */ + native_string_view current_module; + native_unordered_map> module_dependencies; + native_string output_dir{ "classes" }; + module::loader module_loader; }; } diff --git a/include/cpp/jank/runtime/module/loader.hpp b/include/cpp/jank/runtime/module/loader.hpp index be5000162..67271b11d 100644 --- a/include/cpp/jank/runtime/module/loader.hpp +++ b/include/cpp/jank/runtime/module/loader.hpp @@ -2,6 +2,12 @@ #include +namespace jank::runtime +{ struct context; } + +namespace jank::jit +{ struct processor; } + namespace jank::runtime::module { struct file_entry @@ -12,9 +18,16 @@ namespace jank::runtime::module option archive_path; /* If there's an archive path, this path is within the archive. Otherwise, it's the * filesystem path. */ - boost::filesystem::path path; + native_string path; }; + native_string path_to_module(boost::filesystem::path const &path); + boost::filesystem::path module_to_path(native_string_view const &module); + native_string module_to_native_ns(native_string_view const &module); + native_string nest_module(native_string const &module, native_string const &sub); + native_string nest_native_ns(native_string const &native_ns, native_string const &end); + native_bool is_nested_module(native_string const &module); + struct loader { /* A module entry represents one or more files on the classpath which prove that module. @@ -29,10 +42,10 @@ namespace jank::runtime::module * subsequent matches are ignored. */ struct entry { + option pcm; + option cpp; option jank; option cljc; - option cpp; - option pcm; }; /* These separators match what the JVM does on each system. */ @@ -42,13 +55,23 @@ namespace jank::runtime::module static constexpr char module_separator{ ':' }; #endif - loader() = default; - loader(native_string_view const &paths); + loader(context &rt_ctx, native_string_view const &paths); + + native_bool is_loaded(native_string_view const &) const; + result load_ns(native_string_view const &module); + result load(native_string_view const &module); + result load_pcm(file_entry const &entry); + result load_cpp(file_entry const &entry); + result load_jank(file_entry const &entry); + result load_cljc(file_entry const &entry); object_ptr to_runtime_data() const; + context &rt_ctx; + native_string paths; /* This maps module strings to entries. Module strings are like fully qualified Java * class names. */ native_unordered_map entries; + native_set loaded; }; } diff --git a/include/cpp/jank/runtime/obj/jit_function.hpp b/include/cpp/jank/runtime/obj/jit_function.hpp index 2dc15eace..495850a04 100644 --- a/include/cpp/jank/runtime/obj/jit_function.hpp +++ b/include/cpp/jank/runtime/obj/jit_function.hpp @@ -19,7 +19,6 @@ namespace jank::runtime static_object() = default; static_object(static_object &&) = default; static_object(static_object const &) = default; - static_object(object &&base); static_object(object_ptr const fn, object_ptr const start); /* behavior::objectable */ @@ -31,7 +30,6 @@ namespace jank::runtime /* behavior::metadatable */ object_ptr with_meta(object_ptr m); - /* TODO: Doesn't have an offset of 0. */ object base{ object_type::jit_function }; behavior::callable_ptr data{}; option meta; diff --git a/include/cpp/jank/type.hpp b/include/cpp/jank/type.hpp index dc17f511e..abef9f62e 100644 --- a/include/cpp/jank/type.hpp +++ b/include/cpp/jank/type.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -27,6 +28,8 @@ namespace jank using native_vector = folly::fbvector>; template using native_map = std::map>>; + template + using native_set = std::set, native_allocator>; /* TODO: Try out unordered_flat_map once vcpkg has boost 1.81.0. */ template diff --git a/src/cpp/jank/analyze/processor.cpp b/src/cpp/jank/analyze/processor.cpp index 396cfd9fb..033bc209a 100644 --- a/src/cpp/jank/analyze/processor.cpp +++ b/src/cpp/jank/analyze/processor.cpp @@ -350,6 +350,7 @@ namespace jank::analyze } else { name = runtime::context::unique_string("fn"); } + name = runtime::munge(name); native_vector> arities; @@ -421,15 +422,41 @@ namespace jank::analyze } } - return make_box + auto ret ( - expr::function - { - expression_base{ {}, expr_type, current_frame }, - name, - std::move(arities) - } + make_box + ( + expr::function + { + expression_base{ {}, expr_type, current_frame }, + name, + std::move(arities) + } + ) ); + + if(rt_ctx.compiling) + { + /* Register this module as a dependency of the current module so we can generate + * code to load it. */ + auto const &ns_sym(make_box("clojure.core/*ns*")); + auto const &ns_var(rt_ctx.find_var(ns_sym).unwrap()); + auto const module + ( + runtime::module::nest_module + ( + runtime::detail::to_string(ns_var->get_root()), + runtime::munge(name) + ) + ); + rt_ctx.module_dependencies[rt_ctx.current_module].emplace_back(module); + fmt::println("module dep {} -> {}", rt_ctx.current_module, module); + + codegen::processor cg_prc{ rt_ctx, ret, module, codegen::compilation_target::function }; + rt_ctx.write_module(module, cg_prc.declaration_str()); + } + + return ret; } processor::expression_result processor::analyze_recur diff --git a/src/cpp/jank/codegen/processor.cpp b/src/cpp/jank/codegen/processor.cpp index 9e3527b98..6d5a22c15 100644 --- a/src/cpp/jank/codegen/processor.cpp +++ b/src/cpp/jank/codegen/processor.cpp @@ -246,21 +246,29 @@ namespace jank::codegen processor::processor ( runtime::context &rt_ctx, - analyze::expression_ptr const &expr + analyze::expression_ptr const &expr, + native_string_view const &module, + compilation_target const target ) : rt_ctx{ rt_ctx }, root_expr{ expr }, root_fn{ boost::get>(expr->data) }, + module{ module }, + target{ target }, struct_name{ root_fn.name } { assert(root_fn.frame.data); } processor::processor ( runtime::context &rt_ctx, - analyze::expr::function const &expr + analyze::expr::function const &expr, + native_string_view const &module, + compilation_target const target ) : rt_ctx{ rt_ctx }, root_fn{ expr }, + module{ module }, + target{ target }, struct_name{ root_fn.name } { assert(root_fn.frame.data); } @@ -814,10 +822,22 @@ namespace jank::codegen ) { /* Since each codegen proc handles one callable struct, we create a new one for this fn. */ - processor prc{ rt_ctx, expr }; + processor prc + { + rt_ctx, + expr, + runtime::module::nest_module(module, runtime::munge(expr.name)), + rt_ctx.compiling ? compilation_target::function : compilation_target::repl + }; + + + /* If we're compiling, we'll create a separate file for this. */ + if(target != compilation_target::ns) + { + auto header_inserter(std::back_inserter(header_buffer)); + fmt::format_to(header_inserter, "{}", prc.declaration_str()); + } - auto header_inserter(std::back_inserter(header_buffer)); - fmt::format_to(header_inserter, "{}", prc.declaration_str()); switch(expr.expr_type) { case analyze::expression_type::statement: @@ -1011,7 +1031,7 @@ namespace jank::codegen interpolated_chunk_tmps.emplace_back(gen(*chunk_expr, fn_arity, true).unwrap()); } - fmt::format_to(inserter, "object_ptr {};", ret_tmp); + fmt::format_to(inserter, "object_ptr {}{{ obj::nil::nil_const() }};", ret_tmp); fmt::format_to(inserter, "{{ object_ptr __value{{ obj::nil::nil_const() }};"); size_t interpolated_chunk_it{}; for(auto const &chunk : expr.chunks) @@ -1055,6 +1075,21 @@ namespace jank::codegen void processor::build_header() { auto inserter(std::back_inserter(header_buffer)); + + /* TODO: We don't want this for nested modules, but we do if they're in their own file. + * Do we need three module compilation targets? Top-level, nested, local? + * + * Local fns are within a struct already, so we can't enter the ns again. */ + if(!runtime::module::is_nested_module(module)) + { + fmt::format_to + ( + inserter, + "namespace {} {{", + runtime::module::module_to_native_ns(module) + ); + } + fmt::format_to ( inserter, @@ -1263,11 +1298,19 @@ namespace jank::codegen void processor::build_footer() { auto inserter(std::back_inserter(footer_buffer)); + + /* Struct. */ fmt::format_to(inserter, "}};"); + + /* Namespace. */ + if(!runtime::module::is_nested_module(module)) + { fmt::format_to(inserter, "}}"); } } native_string processor::expression_str(bool const box_needed, bool const auto_call) { + auto const module_ns(runtime::module::module_to_native_ns(module)); + if(!generated_expression) { auto inserter(std::back_inserter(expression_buffer)); @@ -1282,11 +1325,10 @@ namespace jank::codegen ( inserter, R"( - {0} {1}{{ *reinterpret_cast({2}) + {0} {1}{{ __rt_ctx )", - runtime::munge(struct_name.name), - tmp_name, - fmt::ptr(&rt_ctx) + runtime::module::nest_native_ns(module_ns, runtime::munge(struct_name.name)), + tmp_name ); for(auto const &arity : root_fn.arities) @@ -1316,9 +1358,8 @@ namespace jank::codegen fmt::format_to ( inserter, - "jank::make_box<{0}>(std::ref(*reinterpret_cast({1}))", - runtime::munge(struct_name.name), - fmt::ptr(&rt_ctx) + "jank::make_box<{0}>(__rt_ctx", + runtime::module::nest_native_ns(module_ns, runtime::munge(struct_name.name)) ); } else @@ -1326,9 +1367,8 @@ namespace jank::codegen fmt::format_to ( inserter, - "{0}{{ std::ref(*reinterpret_cast({1}))", - runtime::munge(struct_name.name), - fmt::ptr(&rt_ctx) + "{0}{{ rt_ctx", + runtime::module::nest_native_ns(module_ns, runtime::munge(struct_name.name)) ); close = "}"; } @@ -1353,4 +1393,56 @@ namespace jank::codegen } return { expression_buffer.data(), expression_buffer.size() }; } + + native_string processor::module_init_str(native_string_view const &module) + { + fmt::memory_buffer module_buffer; + auto inserter(std::back_inserter(module_buffer)); + + fmt::format_to + ( + inserter, + "namespace {} {{", + runtime::module::module_to_native_ns(module) + ); + + fmt::format_to + ( + inserter, + R"( + struct __ns__init + {{ + )" + ); + + fmt::format_to(inserter, "static void __init(){{"); + fmt::format_to(inserter, "constexpr auto const deps(jank::util::make_array("); + bool needs_comma{}; + for(auto const &dep : rt_ctx.module_dependencies[module]) + { + if(needs_comma) + { fmt::format_to(inserter, ", "); } + fmt::format_to(inserter, "\"{}\"", dep); + needs_comma = true; + } + fmt::format_to(inserter, "));"); + + fmt::format_to(inserter, "for(auto const &dep : deps){{"); + fmt::format_to(inserter, "__rt_ctx.load_module(dep);"); + fmt::format_to(inserter, "}}"); + + /* __init fn */ + fmt::format_to(inserter, "}}"); + + /* Struct */ + fmt::format_to(inserter, "}};"); + + /* Namespace */ + fmt::format_to(inserter, "}}"); + + native_string ret; + ret.reserve(module_buffer.size()); + ret += native_string_view{ module_buffer.data(), module_buffer.size() }; + return ret; + } } diff --git a/src/cpp/jank/evaluate.cpp b/src/cpp/jank/evaluate.cpp index b1aafccb4..7407bc3ec 100644 --- a/src/cpp/jank/evaluate.cpp +++ b/src/cpp/jank/evaluate.cpp @@ -11,9 +11,6 @@ namespace jank::evaluate { - template - concept has_frame = requires(T const *t){ t->frame; }; - /* Some expressions don't make sense to eval outright and aren't fns that can be JIT compiled. * For those, we wrap them in a fn expression and then JIT compile and call them. * @@ -39,10 +36,50 @@ namespace jank::evaluate wrapper.arities.emplace_back(std::move(arity)); wrapper.frame = expr.frame; + wrapper.name = runtime::context::unique_string("repl_fn"); return wrapper; } + analyze::expr::function wrap_expressions + ( + native_vector const &exprs, + analyze::processor const &an_prc + ) + { + if(exprs.empty()) + { + return wrap_expression + ( + analyze::expr::primitive_literal + { + analyze::expression_base + { {}, analyze::expression_type::return_statement, an_prc.root_frame, true }, + runtime::obj::nil::nil_const() + } + ); + } + else + { + /* We'll cheat a little and build a fn using just the first expression. Then we can just + * add the rest. I'd rather do this than duplicate all of the wrapping logic. */ + auto ret(wrap_expression(exprs[0])); + auto &body(ret.arities[0].body.body); + /* We normally wrap one expression, which is a return statement, but we'll be potentially + * adding more, so let's not make assumptions yet. */ + body[0]->get_base()->expr_type = analyze::expression_type::statement; + + for(auto const &expr : exprs) + { body.emplace_back(expr); } + + /* Finally, mark the last body item as our return. */ + auto const last_body_index(body.size() - 1); + body[last_body_index]->get_base()->expr_type = analyze::expression_type::return_statement; + + return ret; + } + } + analyze::expr::function wrap_expression(analyze::expression_ptr const expr) { return boost::apply_visitor @@ -238,8 +275,17 @@ namespace jank::evaluate analyze::expr::function const &expr ) { - jank::codegen::processor cg_prc{ rt_ctx, expr }; - return jit_prc.eval(rt_ctx, cg_prc).expect_ok().unwrap(); + auto const &module + ( + runtime::module::nest_module + ( + runtime::expect_object + (rt_ctx.intern_var("clojure.core", "*ns*").expect_ok()->get_root())->to_string(), + runtime::munge(expr.name) + ) + ); + codegen::processor cg_prc{ rt_ctx, expr, module, codegen::compilation_target::repl }; + return jit_prc.eval(cg_prc).expect_ok().unwrap(); } runtime::object_ptr eval @@ -258,7 +304,7 @@ namespace jank::evaluate analyze::expr::do_ const &expr ) { - runtime::object_ptr ret{}; + runtime::object_ptr ret{ runtime::obj::nil::nil_const() }; for(auto const &form : expr.body) { ret = eval(rt_ctx, jit_prc, form); } return ret; diff --git a/src/cpp/jank/jit/processor.cpp b/src/cpp/jank/jit/processor.cpp index 3c9b81a66..ae8684a24 100644 --- a/src/cpp/jank/jit/processor.cpp +++ b/src/cpp/jank/jit/processor.cpp @@ -54,7 +54,7 @@ namespace jank::jit return JANK_CLING_BUILD_DIR; } - processor::processor() + processor::processor(runtime::context &rt_ctx) { /* TODO: Pass this into each fn below so we only do this once on startup. */ auto const jank_path(jank::util::process_location().unwrap().parent_path()); @@ -91,10 +91,18 @@ namespace jank::jit ) ); interpreter = std::make_unique(args.size(), args.data(), llvm_resource_path_str.c_str()); + + eval_string + ( + fmt::format + ( + "auto &__rt_ctx(*reinterpret_cast({}));", + fmt::ptr(&rt_ctx) + ) + ); } - result, native_string> processor::eval - (runtime::context &, codegen::processor &cg_prc) const + result, native_string> processor::eval(codegen::processor &cg_prc) const { /* TODO: Improve Cling to accept string_views instead. */ auto const str(cg_prc.declaration_str()); @@ -118,5 +126,8 @@ namespace jank::jit } void processor::eval_string(native_string const &s) const - { interpreter->process(static_cast(s)); } + { + //fmt::println("JIT eval string {}", s); + interpreter->process(static_cast(s)); + } } diff --git a/src/cpp/jank/runtime/context.cpp b/src/cpp/jank/runtime/context.cpp index d397752ec..b1d2c2184 100644 --- a/src/cpp/jank/runtime/context.cpp +++ b/src/cpp/jank/runtime/context.cpp @@ -1,5 +1,7 @@ #include +#include + #include #include #include @@ -17,18 +19,20 @@ namespace jank::runtime { - context::context() + context::context(option const &class_path) + : jit_prc{ *this } + , module_loader{ *this, class_path.unwrap_or("") } { auto &t_state(get_thread_state()); - auto const core(intern_ns(jank::make_box("clojure.core"))); + auto const core(intern_ns(make_box("clojure.core"))); { auto const locked_core_vars(core->vars.wlock()); - auto const ns_sym(jank::make_box("clojure.core/*ns*")); - auto const ns_res(locked_core_vars->insert({ns_sym, jank::make_box(core, ns_sym, core)})); + auto const ns_sym(make_box("clojure.core/*ns*")); + auto const ns_res(locked_core_vars->insert({ns_sym, make_box(core, ns_sym, core)})); t_state.current_ns = ns_res.first->second; } - auto const in_ns_sym(jank::make_box("clojure.core/in-ns")); + auto const in_ns_sym(make_box("clojure.core/in-ns")); std::function in_ns_fn ( [this](object_ptr const sym) @@ -52,13 +56,13 @@ namespace jank::runtime sym ); } - ); + ); auto in_ns_var(intern_var(in_ns_sym).expect_ok()); in_ns_var->set_root(make_box(in_ns_fn)); t_state.in_ns = in_ns_var; /* TODO: Remove this once it can be defined in jank. */ - auto const assert_sym(jank::make_box("clojure.core/assert")); + auto const assert_sym(make_box("clojure.core/assert")); std::function assert_fn ( [](object_ptr const o) @@ -71,14 +75,19 @@ namespace jank::runtime intern_var(assert_sym).expect_ok()->set_root(make_box(assert_fn)); /* TODO: Remove this once it can be defined in jank. */ - auto const seq_sym(jank::make_box("clojure.core/seq")); + auto const seq_sym(make_box("clojure.core/seq")); intern_var(seq_sym).expect_ok()->set_root(make_box(static_cast(&seq))); - auto const fresh_seq_sym(jank::make_box("clojure.core/fresh-seq")); + auto const fresh_seq_sym(make_box("clojure.core/fresh-seq")); intern_var(fresh_seq_sym).expect_ok()->set_root(make_box(&fresh_seq)); } context::context(context const &ctx) + : jit_prc{ *this } + , current_module{ ctx.current_module } + , module_dependencies{ ctx.module_dependencies } + , output_dir{ ctx.output_dir } + , module_loader{ *this, ctx.module_loader.paths } { auto ns_lock(namespaces.wlock()); for(auto const &ns : *ctx.namespaces.rlock()) @@ -94,7 +103,7 @@ namespace jank::runtime { auto const t_state(get_thread_state()); auto const current_ns(expect_object(t_state.current_ns->get_root())); - qualified_sym = jank::make_box(current_ns->name->name, sym->name); + qualified_sym = make_box(current_ns->name->name, sym->name); } return qualified_sym; } @@ -107,7 +116,7 @@ namespace jank::runtime ns_ptr ns{}; { auto const locked_namespaces(namespaces.rlock()); - auto const found(locked_namespaces->find(jank::make_box("", sym->ns))); + auto const found(locked_namespaces->find(make_box("", sym->ns))); if(found == locked_namespaces->end()) { return none; } ns = found->second; @@ -127,7 +136,7 @@ namespace jank::runtime auto const t_state(get_thread_state()); auto const current_ns(expect_object(t_state.current_ns->get_root())); auto const locked_vars(current_ns->vars.rlock()); - auto const qualified_sym(jank::make_box(current_ns->name->name, sym->name)); + auto const qualified_sym(make_box(current_ns->name->name, sym->name)); auto const found(locked_vars->find(qualified_sym)); if(found == locked_vars->end()) { return none; } @@ -141,38 +150,63 @@ namespace jank::runtime return none; } - void context::eval_prelude(jit::processor const &jit_prc) + /* TODO: Use module loader. */ + void context::eval_prelude() { auto const jank_path(jank::util::process_location().unwrap().parent_path()); auto const src_path(jank_path / "../src/jank/clojure/core.jank"); - eval_file(src_path.string(), jit_prc); + eval_file(src_path.string()); } - object_ptr context::eval_file(native_string_view const &path, jit::processor const &jit_prc) + object_ptr context::eval_file(native_string_view const &path) { auto const file(util::map_file(path)); if(file.is_err()) { throw std::runtime_error{ fmt::format("unable to map file {} due to error: {}", path, file.expect_err()) }; } - return eval_string({ file.expect_ok().head, file.expect_ok().size }, jit_prc); + return eval_string({ file.expect_ok().head, file.expect_ok().size }); } - object_ptr context::eval_string(native_string_view const &code, jit::processor const &jit_prc) + object_ptr context::eval_string(native_string_view const &code) { read::lex::processor l_prc{ code }; read::parse::processor p_prc{ *this, l_prc.begin(), l_prc.end() }; object_ptr ret{ obj::nil::nil_const() }; + native_vector exprs{}; for(auto const &form : p_prc) { auto const expr(an_prc.analyze(form.expect_ok(), analyze::expression_type::statement)); ret = evaluate::eval(*this, jit_prc, expr.expect_ok()); + exprs.emplace_back(expr.expect_ok()); + } + + if(compiling) + { + auto wrapped_exprs(evaluate::wrap_expressions(exprs, an_prc)); + wrapped_exprs.name = "__ns"; + auto const &module + ( + expect_object + (intern_var("clojure.core", "*ns*").expect_ok()->get_root())->to_string() + ); + codegen::processor cg_prc{ *this, wrapped_exprs, module, codegen::compilation_target::ns }; + write_module + ( + current_module, + cg_prc.declaration_str() + ); + write_module + ( + fmt::format("{}__init", current_module), + cg_prc.module_init_str(current_module) + ); } assert(ret); return ret; } - native_vector context::analyze_string(native_string_view const &code, jit::processor const &jit_prc, native_bool const eval) + native_vector context::analyze_string(native_string_view const &code, native_bool const eval) { read::lex::processor l_prc{ code }; read::parse::processor p_prc{ *this, l_prc.begin(), l_prc.end() }; @@ -183,18 +217,72 @@ namespace jank::runtime auto const expr(an_prc.analyze(form.expect_ok(), analyze::expression_type::statement)); if(eval) { evaluate::eval(*this, jit_prc, expr.expect_ok()); } - ret.push_back(expr.expect_ok()); + ret.emplace_back(expr.expect_ok()); } return ret; } + result context::load_module(native_string_view const &module) + { + /* TODO: Support reloading. */ + if(module_loader.is_loaded(module) && !compiling) + { + fmt::println("Module already loaded: {}", module); + return ok(); + } + + try + { + /* TODO: Get a result from this and forward errors. */ + if(module.find('$') == native_string::npos) + { module_loader.load_ns(module); } + else + { module_loader.load(module); } + return ok(); + } + catch(std::exception const &e) + { return err(e.what()); } + catch(object_ptr const &e) + { return err(detail::to_string(e)); } + } + + result context::compile_module(native_string_view const &module) + { + module_dependencies.clear(); + + /* TODO: When `compiling` is a dynamic var, we need to push and finally pop here. */ + auto const prev_compiling(compiling); + auto const prev_module(current_module); + compiling = true; + current_module = module; + + auto res(load_module(module)); + + compiling = prev_compiling; + current_module = prev_module; + return res; + } + + void context::write_module(native_string_view const &module, native_string_view const &contents) const + { + boost::filesystem::path const dir{ output_dir }; + if(!boost::filesystem::exists(dir)) + { boost::filesystem::create_directories(dir); } + + { + std::ofstream ofs{ fmt::format("{}/{}.cpp", module::module_to_path(output_dir).string(), module) }; + ofs << contents; + ofs.flush(); + } + } + native_string context::unique_string() { return unique_string("gen"); } native_string context::unique_string(native_string_view const &prefix) { static std::atomic_size_t index{ 1 }; - return prefix.data() + std::to_string(index++); + return fmt::format(FMT_COMPILE("{}_{}"), prefix.data(), index++); } obj::symbol context::unique_symbol() { return unique_symbol("gen"); } @@ -226,20 +314,20 @@ namespace jank::runtime if(found != locked_namespaces->end()) { return found->second; } - auto const result(locked_namespaces->emplace(sym, jank::make_box(sym, *this))); + auto const result(locked_namespaces->emplace(sym, make_box(sym, *this))); return result.first->second; } result context::intern_var (native_string const &ns, native_string const &name) - { return intern_var(jank::make_box(ns, name)); } + { return intern_var(make_box(ns, name)); } result context::intern_var(obj::symbol_ptr const &qualified_sym) { if(qualified_sym->ns.empty()) { return err("can't intern var; sym isn't qualified"); } auto locked_namespaces(namespaces.wlock()); - auto const found_ns(locked_namespaces->find(jank::make_box(qualified_sym->ns))); + auto const found_ns(locked_namespaces->find(make_box(qualified_sym->ns))); if(found_ns == locked_namespaces->end()) { return err("can't intern var; namespace doesn't exist"); } @@ -250,7 +338,7 @@ namespace jank::runtime { return ok(found_var->second); } auto const ns_res - (locked_vars->insert({qualified_sym, jank::make_box(found_ns->second, qualified_sym)})); + (locked_vars->insert({qualified_sym, make_box(found_ns->second, qualified_sym)})); return ok(ns_res.first->second); } diff --git a/src/cpp/jank/runtime/module/loader.cpp b/src/cpp/jank/runtime/module/loader.cpp index 23bd274c6..f075747d3 100644 --- a/src/cpp/jank/runtime/module/loader.cpp +++ b/src/cpp/jank/runtime/module/loader.cpp @@ -4,6 +4,8 @@ #include +#include +#include #include namespace jank::runtime::module @@ -11,20 +13,90 @@ namespace jank::runtime::module /* This turns `foo_bar/spam/meow.cljc` into `foo-bar.spam.meow`. */ native_string path_to_module(boost::filesystem::path const &path) { - static std::regex const underscore{ "_" }; + //static std::regex const underscore{ "_" }; static std::regex const slash{ "/" }; auto const &s(path.string()); std::string ret{ s, 0, s.size() - path.extension().size() }; - ret = std::regex_replace(ret, underscore, "-"); + //ret = std::regex_replace(ret, underscore, "-"); ret = std::regex_replace(ret, slash, "."); return ret; } - void register_entry(native_unordered_map &entries, file_entry const &entry) + boost::filesystem::path module_to_path(native_string_view const &module) + { + static std::regex const dash{ "-" }; + static std::regex const dot{ "\\." }; + + std::string ret{ module }; + ret = std::regex_replace(ret, dash, "_"); + ret = std::regex_replace(ret, dot, "/"); + + return ret; + } + + native_string module_to_native_ns(native_string_view const &module) + { + static std::regex const dash{ "-" }; + static std::regex const dot{ "\\." }; + static std::regex const last_module{ "\\$[a-zA-Z_0-9]+$" }; + static std::regex const dollar{ "\\$" }; + + std::string ret{ module }; + ret = std::regex_replace(ret, dash, "_"); + ret = std::regex_replace(ret, dot, "::"); + ret = std::regex_replace(ret, last_module, ""); + ret = std::regex_replace(ret, dollar, "::"); + + return ret; + } + + native_string nest_module(native_string const &module, native_string const &sub) + { + assert(!module.empty()); + assert(!sub.empty()); + return module + "$" + sub; + } + + native_string nest_native_ns(native_string const &native_ns, native_string const &end) + { + assert(!native_ns.empty()); + assert(!end.empty()); + return fmt::format("::{}::{}", native_ns, end); + } + + /* If it has two or more occurences of $, it's nested. */ + native_bool is_nested_module(native_string const &module) + { return module.find_first_of('$') != module.find_last_of('$'); } + + template + void visit_jar_entry(file_entry const &entry, F const &fn) + { + auto const &path(entry.archive_path.unwrap()); + libzippp::ZipArchive zf{ std::string{ path } }; + auto success(zf.open(libzippp::ZipArchive::ReadOnly)); + if(!success) + { throw std::runtime_error{ fmt::format("Failed to open jar on classpath: {}", path) }; } + + auto const &zip_entry(zf.getEntry(std::string{ entry.path })); + fn(zip_entry.readAsText()); + } + + void register_entry + ( + native_unordered_map &entries, + boost::filesystem::path const &resource_path, + file_entry const &entry + ) { boost::filesystem::path p{ entry.path }; + /* We need the file path relative to the class path, since the class + * path portion is not included in part of the module name. For example, + * the file may live in `src/jank/clojure/core.jank` but the module + * should be `clojure.core`, not `src.jank.clojure.core`. */ + auto const &module_path(p.lexically_relative(resource_path)); + auto const ext(p.extension().string()); bool registered{}; if(ext == ".jank") @@ -32,7 +104,7 @@ namespace jank::runtime::module registered = true; loader::entry e; e.jank = entry; - auto res(entries.insert({ path_to_module(p), std::move(e) })); + auto res(entries.insert({ path_to_module(module_path), std::move(e) })); if(!res.second) { res.first->second.jank = entry; } } @@ -41,7 +113,7 @@ namespace jank::runtime::module registered = true; loader::entry e; e.cljc = entry; - auto res(entries.insert({ path_to_module(p), std::move(e) })); + auto res(entries.insert({ path_to_module(module_path), std::move(e) })); if(!res.second) { res.first->second.cljc = entry; } } @@ -50,7 +122,7 @@ namespace jank::runtime::module registered = true; loader::entry e; e.cpp = entry; - auto res(entries.insert({ path_to_module(p), std::move(e) })); + auto res(entries.insert({ path_to_module(module_path), std::move(e) })); if(!res.second) { res.first->second.cpp = entry; } } @@ -59,13 +131,15 @@ namespace jank::runtime::module registered = true; loader::entry e; e.pcm = entry; - auto res(entries.insert({ path_to_module(p), std::move(e) })); + auto res(entries.insert({ path_to_module(module_path), std::move(e) })); if(!res.second) { res.first->second.pcm = entry; } } if(registered) - { fmt::println("register_entry {} {} {}", entry.archive_path, entry.path.string(), path_to_module(p)); } + { + //fmt::println("register_entry {} {} {}", entry.archive_path, entry.path, path_to_module(module_path)); + } } void register_directory(native_unordered_map &entries, boost::filesystem::path const &path) @@ -73,13 +147,12 @@ namespace jank::runtime::module for(auto const &f : boost::filesystem::recursive_directory_iterator{ path }) { if(boost::filesystem::is_regular_file(f)) - { register_entry(entries, file_entry{ none, f.path() }); } + { register_entry(entries, path, file_entry{ none, f.path().string() }); } } } void register_jar(native_unordered_map &entries, native_string_view const &path) { - fmt::println("register_jar {}", path); libzippp::ZipArchive zf{ std::string{ path } }; auto success(zf.open(libzippp::ZipArchive::ReadOnly)); if(!success) @@ -93,26 +166,34 @@ namespace jank::runtime::module { auto const &name(entry.getName()); if(!entry.isDirectory()) - { register_entry(entries, { path, name }); } + { register_entry(entries, "", { path, name }); } } } void register_path(native_unordered_map &entries, native_string_view const &path) { - fmt::println("register_path {}", path); + /* It's entirely possible to have empty entries in the classpath, mainly due to lazy string + * concatenation. We just ignore them. This means something like "::::" is valid. */ + if(path.empty() || !boost::filesystem::exists(path)) + { return; } - boost::filesystem::path p{ path }; + boost::filesystem::path p{ boost::filesystem::canonical(path).lexically_normal() }; if(boost::filesystem::is_directory(p)) { register_directory(entries, p); } else if(p.extension().string() == ".jar") { register_jar(entries, path); } + /* If it's not a JAR or a directory, we just add it as a direct file entry. I don't think the + * JVM supports this, but I like that it allows us to put specific files in the path. */ else - { register_entry(entries, { none, p }); } + { register_entry(entries, "", { none, p.string() }); } } - loader::loader(native_string_view const &paths) + loader::loader(context &rt_ctx, native_string_view const &ps) + : rt_ctx{ rt_ctx }, paths{ ps } { - fmt::println("BINGBONG loader: {}", paths); + auto const jank_path(jank::util::process_location().unwrap().parent_path()); + paths += fmt::format(":{}", (jank_path / "../src/jank").string()); + paths += fmt::format(":{}", rt_ctx.output_dir); size_t start{}; size_t i{ paths.find(module_separator, start) }; @@ -140,10 +221,118 @@ namespace jank::runtime::module ( make_box("__type"), make_box("module::file_entry"), make_box("archive_path"), jank::detail::to_runtime_data(archive_path), - make_box("path"), make_box(path.string()) + make_box("path"), make_box(path) ); } + native_bool loader::is_loaded(native_string_view const &module) const + { return loaded.contains(module); } + + result loader::load_ns(native_string_view const &module) + { + bool const compiling{ rt_ctx.compiling }; + native_bool const needs_init{ !compiling && entries.contains(fmt::format("{}__init", module)) }; + if(needs_init) + { + auto ret(load(fmt::format("{}__init", module))); + if(ret.is_err()) + { return ret; } + rt_ctx.jit_prc.eval_string + ( + fmt::format + ( + "{}::__ns__init::__init();", + runtime::module::module_to_native_ns(module) + ) + ); + } + + auto ret(load(module)); + if(ret.is_err()) + { return ret; } + + if(needs_init) + { + rt_ctx.jit_prc.eval_string + ( + fmt::format + ( + "{}::__ns{{ __rt_ctx }}.call();", + runtime::module::module_to_native_ns(module) + ) + ); + } + + return ok(); + } + + result loader::load(native_string_view const &module) + { + auto const &entry(entries.find(module)); + if(entry == entries.end()) + { return err(fmt::format("ICE: unable to find module: {}", module)); } + + if(entry->second.pcm.is_some()) + { return load_pcm(entry->second.pcm.unwrap()); } + else if(entry->second.cpp.is_some()) + { return load_cpp(entry->second.cpp.unwrap()); } + else if(entry->second.jank.is_some()) + { return load_jank(entry->second.jank.unwrap()); } + else if(entry->second.cljc.is_some()) + { return load_cljc(entry->second.cljc.unwrap()); } + else + { return err(fmt::format("ICE: no sources for registered module: {}", module)); } + + loaded.emplace(module); + + return ok(); + } + + result loader::load_pcm(file_entry const &) + { return err("Not yet implemented: PCM loading"); } + + result loader::load_cpp(file_entry const &entry) + { + if(entry.archive_path.is_some()) + { + visit_jar_entry + ( + entry, + [&](auto const &str) + { rt_ctx.jit_prc.eval_string(str); } + ); + } + else + { + auto const file(util::map_file(entry.path)); + if(file.is_err()) + { return err(fmt::format("unable to map file {} due to error: {}", entry.path, file.expect_err())); } + rt_ctx.jit_prc.eval_string({ file.expect_ok().head, file.expect_ok().size }); + } + + return ok(); + } + + result loader::load_jank(file_entry const &entry) + { + if(entry.archive_path.is_some()) + { + visit_jar_entry + ( + entry, + [&](auto const &str) + { rt_ctx.eval_string(str); } + ); + } + else + { rt_ctx.eval_file(entry.path); } + + return ok(); + } + + result loader::load_cljc(file_entry const &) + { return err("Not yet implemented: CLJC loading"); } + object_ptr loader::to_runtime_data() const { runtime::object_ptr entry_maps(make_box()); diff --git a/src/cpp/main.cpp b/src/cpp/main.cpp index 86e445e1f..dd76715fa 100644 --- a/src/cpp/main.cpp +++ b/src/cpp/main.cpp @@ -1,8 +1,12 @@ #include #include +#include + #include #include +#include +#include #include @@ -17,6 +21,12 @@ int main(int const argc, char const **argv) { + /* TODO: Arg parsing: + * 1. Optimization level + * 2. Classpath + * 3. Compilation directory + * 4. Starting a repl + */ if(argc < 2) { // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) @@ -32,12 +42,12 @@ int main(int const argc, char const **argv) // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) char const *file{ argv[1] }; - jank::runtime::context rt_ctx; - jank::jit::processor jit_prc; + jank::runtime::context rt_ctx{ }; try { - rt_ctx.eval_prelude(jit_prc); + //rt_ctx.eval_prelude(); + rt_ctx.load_module("clojure.core").expect_ok(); //{ // auto const mfile(jank::util::map_file(file)); @@ -61,7 +71,7 @@ int main(int const argc, char const **argv) // return 0; //} - std::cout << jank::runtime::detail::to_string(rt_ctx.eval_file(file, jit_prc)) << std::endl; + std::cout << jank::runtime::detail::to_string(rt_ctx.eval_file(file)) << std::endl; } catch(std::exception const &e) { fmt::print("Exception: {}", e.what()); } diff --git a/src/jank/clojure/core.jank b/src/jank/clojure/core.jank index 488850c4b..7acd0be38 100644 --- a/src/jank/clojure/core.jank +++ b/src/jank/clojure/core.jank @@ -863,3 +863,21 @@ (native/raw "__value = make_box(std::pow(runtime::detail::to_real(#{ x }#), runtime::detail::to_real(#{ y }#)));")) (reset-meta! (var pow) {:arities {2 {:supports-unboxed-input? true :unboxed-output? true}}}) + +; Namespaces (again). + +; TODO: Handle multiple paths +; TODO: Handle relative paths +(defn load [path] + (native/raw "__rt_ctx.load_module(runtime::detail::to_string(#{ path }#)).expect_ok();")) + +(defn compile [path] + (native/raw "__rt_ctx.compile_module(runtime::detail::to_string(#{ path }#)).expect_ok();")) + +; TODO: Handle multiple args +; TODO: Handle vectors with flags +(defn require [sym] + (native/raw "auto const &s(runtime::detail::to_string(#{ sym }#)); + __rt_ctx.load_module(s).expect_ok() ;")) + +(println "Bottom of clojure.core") diff --git a/test/cpp/jank/analyze/box.cpp b/test/cpp/jank/analyze/box.cpp index 54e7f4e89..556e8a4a2 100644 --- a/test/cpp/jank/analyze/box.cpp +++ b/test/cpp/jank/analyze/box.cpp @@ -14,16 +14,15 @@ namespace jank::analyze TEST_CASE("Unboxed local") { - jit::processor jit_prc; runtime::context rt_ctx; SUBCASE("No boxed usage") { - auto const res(rt_ctx.analyze_string("(let* [a 1 b a])", jit_prc)); + auto const res(rt_ctx.analyze_string("(let* [a 1 b a])")); CHECK_EQ(res.size(), 1); auto const map(res[0]->to_runtime_data()); - auto const a_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 0 0])", jit_prc))); + auto const a_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 0 0])"))); CHECK(equal(runtime::get(a_binding, make_box("needs_box")), make_box(false))); CHECK(equal(runtime::get(a_binding, make_box("has_boxed_usage")), make_box(false))); @@ -32,21 +31,20 @@ namespace jank::analyze SUBCASE("Unboxed arithmetic") { - jit::processor jit_prc; runtime::context rt_ctx; - rt_ctx.eval_prelude(jit_prc); + rt_ctx.load_module("clojure.core"); - auto const res(rt_ctx.analyze_string("(let* [a 1 b (* a 2.0) c (/ b 2.0)])", jit_prc)); + auto const res(rt_ctx.analyze_string("(let* [a 1 b (* a 2.0) c (/ b 2.0)])")); CHECK_EQ(res.size(), 1); auto const map(res[0]->to_runtime_data()); - auto const b_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 1 0])", jit_prc))); + auto const b_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 1 0])"))); CHECK(equal(runtime::get(b_binding, make_box("needs_box")), make_box(false))); CHECK(equal(runtime::get(b_binding, make_box("has_boxed_usage")), make_box(false))); CHECK(equal(runtime::get(b_binding, make_box("has_unboxed_usage")), make_box(true))); - auto const c_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 2 0])", jit_prc))); + auto const c_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 2 0])"))); CHECK(equal(runtime::get(c_binding, make_box("needs_box")), make_box(false))); CHECK(equal(runtime::get(c_binding, make_box("has_boxed_usage")), make_box(false))); CHECK(equal(runtime::get(c_binding, make_box("has_unboxed_usage")), make_box(false))); @@ -54,11 +52,11 @@ namespace jank::analyze SUBCASE("Boxed usage") { - auto const res(rt_ctx.analyze_string("(let* [a 1 b a] a)", jit_prc)); + auto const res(rt_ctx.analyze_string("(let* [a 1 b a] a)")); CHECK_EQ(res.size(), 1); auto const map(res[0]->to_runtime_data()); - auto const a_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 0 0])", jit_prc))); + auto const a_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 0 0])"))); CHECK(equal(runtime::get(a_binding, make_box("needs_box")), make_box(false))); CHECK(equal(runtime::get(a_binding, make_box("has_boxed_usage")), make_box(true))); @@ -67,18 +65,18 @@ namespace jank::analyze SUBCASE("Captured, no unboxed usage") { - auto const res(rt_ctx.analyze_string("(let* [a 1 f (fn* [] a)] (f))", jit_prc, false)); + auto const res(rt_ctx.analyze_string("(let* [a 1 f (fn* [] a)] (f))", false)); CHECK_EQ(res.size(), 1); auto const map(res[0]->to_runtime_data()); - auto const a_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 0 0])", jit_prc))); + auto const a_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 0 0])"))); CHECK(equal(runtime::get(a_binding, make_box("needs_box")), make_box(false))); CHECK(equal(runtime::get(a_binding, make_box("has_boxed_usage")), make_box(true))); CHECK(equal(runtime::get(a_binding, make_box("has_unboxed_usage")), make_box(false))); - auto const f_fn(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 1 1])", jit_prc))); - auto const captured_a_binding(runtime::get_in(f_fn, rt_ctx.eval_string(R"(["arities" 0 "body" "body" 0 "binding"])", jit_prc))); + auto const f_fn(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 1 1])"))); + auto const captured_a_binding(runtime::get_in(f_fn, rt_ctx.eval_string(R"(["arities" 0 "body" "body" 0 "binding"])"))); CHECK(equal(runtime::get(captured_a_binding, make_box("needs_box")), make_box(true))); CHECK(equal(runtime::get(captured_a_binding, make_box("has_boxed_usage")), make_box(true))); CHECK(equal(runtime::get(captured_a_binding, make_box("has_unboxed_usage")), make_box(false))); @@ -86,22 +84,21 @@ namespace jank::analyze SUBCASE("Captured, unboxed arithmetic usage") { - jit::processor jit_prc; runtime::context rt_ctx; - rt_ctx.eval_prelude(jit_prc); + rt_ctx.load_module("clojure.core"); - auto const res(rt_ctx.analyze_string("(let* [a 1 b (+ a 1.0) f (fn* [] a)] (f))", jit_prc)); + auto const res(rt_ctx.analyze_string("(let* [a 1 b (+ a 1.0) f (fn* [] a)] (f))")); CHECK_EQ(res.size(), 1); auto const map(res[0]->to_runtime_data()); - auto const a_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 0 0])", jit_prc))); + auto const a_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 0 0])"))); CHECK(equal(runtime::get(a_binding, make_box("needs_box")), make_box(false))); CHECK(equal(runtime::get(a_binding, make_box("has_boxed_usage")), make_box(true))); CHECK(equal(runtime::get(a_binding, make_box("has_unboxed_usage")), make_box(true))); - auto const f_fn(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 2 1])", jit_prc))); - auto const captured_a_binding(runtime::get_in(f_fn, rt_ctx.eval_string(R"(["arities" 0 "body" "body" 0 "binding"])", jit_prc))); + auto const f_fn(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 2 1])"))); + auto const captured_a_binding(runtime::get_in(f_fn, rt_ctx.eval_string(R"(["arities" 0 "body" "body" 0 "binding"])"))); CHECK(equal(runtime::get(captured_a_binding, make_box("needs_box")), make_box(true))); CHECK(equal(runtime::get(captured_a_binding, make_box("has_boxed_usage")), make_box(true))); CHECK(equal(runtime::get(captured_a_binding, make_box("has_unboxed_usage")), make_box(false))); @@ -109,9 +106,8 @@ namespace jank::analyze SUBCASE("Sub-expression of a boxed call which doesn't require boxed inputs") { - jit::processor jit_prc; runtime::context rt_ctx; - rt_ctx.eval_prelude(jit_prc); + rt_ctx.load_module("clojure.core"); auto const res ( @@ -122,20 +118,19 @@ namespace jank::analyze r2 (* r r)] (* (+ r2 (- 1.0 r2)) (pow (- 1.0 r) 5.0))) - )", - jit_prc + )" ) ); CHECK_EQ(res.size(), 1); auto const map(res[0]->to_runtime_data()); - auto const r_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 0 0])", jit_prc))); + auto const r_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 0 0])"))); CHECK(equal(runtime::get(r_binding, make_box("needs_box")), make_box(false))); CHECK(equal(runtime::get(r_binding, make_box("has_boxed_usage")), make_box(false))); CHECK(equal(runtime::get(r_binding, make_box("has_unboxed_usage")), make_box(true))); - auto const r2_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 1 0])", jit_prc))); + auto const r2_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 1 0])"))); CHECK(equal(runtime::get(r2_binding, make_box("needs_box")), make_box(false))); CHECK(equal(runtime::get(r2_binding, make_box("has_boxed_usage")), make_box(false))); CHECK(equal(runtime::get(r2_binding, make_box("has_unboxed_usage")), make_box(true))); @@ -152,22 +147,21 @@ namespace jank::analyze (fn* [] (fn* [] a))) - )", - jit_prc + )" ) ); CHECK_EQ(res.size(), 1); auto const map(res[0]->to_runtime_data()); - auto const a_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 0 0])", jit_prc))); + auto const a_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 0 0])"))); CHECK(equal(runtime::get(a_binding, make_box("needs_box")), make_box(false))); CHECK(equal(runtime::get(a_binding, make_box("has_boxed_usage")), make_box(true))); CHECK(equal(runtime::get(a_binding, make_box("has_unboxed_usage")), make_box(false))); - auto const first_fn(runtime::get_in(map, rt_ctx.eval_string(R"(["body" "body" 0])", jit_prc))); - auto const second_fn(runtime::get_in(first_fn, rt_ctx.eval_string(R"(["arities" 0 "body" "body" 0])", jit_prc))); - auto const captured_a_binding(runtime::get_in(second_fn, rt_ctx.eval_string(R"(["arities" 0 "body" "body" 0 "binding"])", jit_prc))); + auto const first_fn(runtime::get_in(map, rt_ctx.eval_string(R"(["body" "body" 0])"))); + auto const second_fn(runtime::get_in(first_fn, rt_ctx.eval_string(R"(["arities" 0 "body" "body" 0])"))); + auto const captured_a_binding(runtime::get_in(second_fn, rt_ctx.eval_string(R"(["arities" 0 "body" "body" 0 "binding"])"))); CHECK(equal(runtime::get(captured_a_binding, make_box("needs_box")), make_box(true))); CHECK(equal(runtime::get(captured_a_binding, make_box("has_boxed_usage")), make_box(true))); CHECK(equal(runtime::get(captured_a_binding, make_box("has_unboxed_usage")), make_box(false))); @@ -176,22 +170,21 @@ namespace jank::analyze TEST_CASE("Boxed local") { - jit::processor jit_prc; runtime::context rt_ctx; SUBCASE("Boxed usage") { - auto const res(rt_ctx.analyze_string("(let* [f (fn* [] 1) a (f)] a)", jit_prc)); + auto const res(rt_ctx.analyze_string("(let* [f (fn* [] 1) a (f)] a)")); CHECK_EQ(res.size(), 1); auto const map(res[0]->to_runtime_data()); - auto const f_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 1 1 "source_expr" "binding"])", jit_prc))); + auto const f_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 1 1 "source_expr" "binding"])"))); CHECK(equal(runtime::get(f_binding, make_box("needs_box")), make_box(true))); CHECK(equal(runtime::get(f_binding, make_box("has_boxed_usage")), make_box(true))); CHECK(equal(runtime::get(f_binding, make_box("has_unboxed_usage")), make_box(false))); - auto const a_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 1 0])", jit_prc))); + auto const a_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 1 0])"))); CHECK(equal(runtime::get(a_binding, make_box("needs_box")), make_box(true))); CHECK(equal(runtime::get(a_binding, make_box("has_boxed_usage")), make_box(true))); CHECK(equal(runtime::get(a_binding, make_box("has_unboxed_usage")), make_box(false))); @@ -199,11 +192,11 @@ namespace jank::analyze SUBCASE("Re-binding") { - auto const res(rt_ctx.analyze_string("(let* [f (fn* [] 1) a f] a)", jit_prc)); + auto const res(rt_ctx.analyze_string("(let* [f (fn* [] 1) a f] a)")); CHECK_EQ(res.size(), 1); auto const map(res[0]->to_runtime_data()); - auto const a_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 1 0])", jit_prc))); + auto const a_binding(runtime::get_in(map, rt_ctx.eval_string(R"(["pairs" 1 0])"))); CHECK(equal(runtime::get(a_binding, make_box("needs_box")), make_box(true))); CHECK(equal(runtime::get(a_binding, make_box("has_boxed_usage")), make_box(true))); diff --git a/test/cpp/jank/jit/processor.cpp b/test/cpp/jank/jit/processor.cpp index 44c55015e..f387a6f58 100644 --- a/test/cpp/jank/jit/processor.cpp +++ b/test/cpp/jank/jit/processor.cpp @@ -32,10 +32,9 @@ namespace jank::jit TEST_CASE("Files") { - jit::processor jit_prc; runtime::context rt_ctx; auto const cardinal_result(rt_ctx.intern_keyword(runtime::obj::symbol{ "", "success" }, true)); - rt_ctx.eval_prelude(jit_prc); + rt_ctx.load_module("clojure.core"); size_t test_count{}; /* The functionality I want here is too complex for doctest to handle. Output should be @@ -85,7 +84,7 @@ namespace jank::jit } }; - auto const result(test_rt_ctx.eval_file(dir_entry.path().string(), jit_prc)); + auto const result(test_rt_ctx.eval_file(dir_entry.path().string())); if(!expect_success) { failures.push_back