From 8f95b6fa484d5c76896e607e28db963860575485 Mon Sep 17 00:00:00 2001 From: Saket Patel Date: Sat, 12 Jul 2025 21:31:42 +0530 Subject: [PATCH 1/2] feat: introduce jank lib with static runtime - Also allows user to choose type of runtime while ahead of compiling their projects --- compiler+runtime/CMakeLists.txt | 86 ++++++++++- .../include/cpp/jank/aot/processor.hpp | 1 + .../include/cpp/jank/runtime/context.hpp | 2 + .../src/cpp/jank/aot/processor.cpp | 6 +- .../src/cpp/jank/evaluate_static_rt.cpp | 135 ++++++++++++++++++ .../src/cpp/jank/runtime/context.cpp | 26 +++- .../src/cpp/jank/runtime/module/loader.cpp | 28 ++++ compiler+runtime/src/cpp/jank/util/cli.cpp | 10 +- .../test/bash/ahead-of-time/pass-test | 53 ++++++- .../single-jank-module/core_with_eval.jank | 4 + 10 files changed, 332 insertions(+), 19 deletions(-) create mode 100644 compiler+runtime/src/cpp/jank/evaluate_static_rt.cpp create mode 100644 compiler+runtime/test/bash/ahead-of-time/src/single-jank-module/core_with_eval.jank diff --git a/compiler+runtime/CMakeLists.txt b/compiler+runtime/CMakeLists.txt index 0e7291a55..8db4f3701 100644 --- a/compiler+runtime/CMakeLists.txt +++ b/compiler+runtime/CMakeLists.txt @@ -153,8 +153,7 @@ endfunction() # remains a static lib, since these symbols need to be accessible in the # compiler's runtime by the JIT compiler. -add_library( - jank_lib STATIC +set(JANK_LIBRARY_COMMON_SOURCES src/cpp/jtl/panic.cpp src/cpp/jtl/assert.cpp src/cpp/jank/c_api.cpp @@ -273,16 +272,24 @@ add_library( src/cpp/jank/analyze/expr/case.cpp src/cpp/jank/analyze/local_frame.cpp src/cpp/jank/analyze/step/force_boxed.cpp + + # Native module sources. + src/cpp/clojure/core_native.cpp + src/cpp/clojure/string_native.cpp + src/cpp/jank/perf_native.cpp +) + +add_library( + jank_lib STATIC + ${JANK_LIBRARY_COMMON_SOURCES} + src/cpp/jank/evaluate.cpp src/cpp/jank/codegen/llvm_processor.cpp src/cpp/jank/jit/processor.cpp src/cpp/jank/aot/processor.cpp # Native module sources. - src/cpp/clojure/core_native.cpp - src/cpp/clojure/string_native.cpp src/cpp/jank/compiler_native.cpp - src/cpp/jank/perf_native.cpp ) set_property(TARGET jank_lib PROPERTY OUTPUT_NAME jank) @@ -363,6 +370,75 @@ target_link_options(jank_lib PRIVATE ${jank_linker_flags}) set_target_properties(jank_lib PROPERTIES ENABLE_EXPORTS 1) # ---- libjank.a ---- +# ---- libjank_static_rt.a ---- +add_library( + jank_lib_static_rt STATIC + ${JANK_LIBRARY_COMMON_SOURCES} + + src/cpp/jank/evaluate_static_rt.cpp +) + +set_property(TARGET jank_lib_static_rt PROPERTY OUTPUT_NAME jank_static_rt) + +target_compile_features(jank_lib_static_rt PUBLIC ${jank_cxx_standard}) +target_compile_options(jank_lib_static_rt PUBLIC ${jank_common_compiler_flags} ${jank_aot_compiler_flags}) + +target_include_directories( + jank_lib_static_rt + PUBLIC + "$" +) +target_include_directories( + jank_lib_static_rt + SYSTEM + PUBLIC + ${BDWGC_INCLUDE_DIR} + "$" + "$" + "$" + "$" + "$" + "$" + "$" + "$" + "$" + "$" + "$" +) + +target_link_libraries( + jank_lib_static_rt PRIVATE + ${BDWGC_LIBRARIES} + libzippp::libzippp + cpptrace::cpptrace + # clang + # clang-cpp + # LLVM + ftxui::screen ftxui::dom + OpenSSL::Crypto + Boost::multiprecision +) + +# Build a string of all of our JIT compilation flags so we can load them up in the runtime. +set(jank_jit_compile_flags_list ${jank_common_compiler_flags} ${jank_jit_compiler_flags}) +list(JOIN jank_jit_compile_flags_list " " jank_jit_compile_flags_str) + +set(jank_deps_library_dirs ${LLVM_LIBRARY_DIRS}) + +target_compile_options( + jank_lib_static_rt + PUBLIC + -DJANK_VERSION="${jank_version}" + -DJANK_JIT_FLAGS="${jank_jit_compile_flags_str}" + -DJANK_CLANG_PREFIX="${CLANG_INSTALL_PREFIX}" + -DJANK_STATIC_RUNTIME +) +target_link_options(jank_lib_static_rt PRIVATE ${jank_linker_flags}) + +# Symbol exporting for JIT. +set_target_properties(jank_lib_static_rt PROPERTIES ENABLE_EXPORTS 1) +# ---- libjank_static_rt.a ---- + # ---- libnanobench.a ---- # nanobench uses a single header for both .hpp and .cpp inclusion, based on # whether a define has been set. This doesn't work with jank's pre-compiled diff --git a/compiler+runtime/include/cpp/jank/aot/processor.hpp b/compiler+runtime/include/cpp/jank/aot/processor.hpp index 831254ce2..4eb9ab48f 100644 --- a/compiler+runtime/include/cpp/jank/aot/processor.hpp +++ b/compiler+runtime/include/cpp/jank/aot/processor.hpp @@ -22,6 +22,7 @@ namespace jank::aot native_vector define_macros; native_vector libs; + jtl::immutable_string runtime; jtl::immutable_string output_filename; }; diff --git a/compiler+runtime/include/cpp/jank/runtime/context.hpp b/compiler+runtime/include/cpp/jank/runtime/context.hpp index 74cc38c0d..54b3867a4 100644 --- a/compiler+runtime/include/cpp/jank/runtime/context.hpp +++ b/compiler+runtime/include/cpp/jank/runtime/context.hpp @@ -136,7 +136,9 @@ namespace jank::runtime * of previous code. This is essential for REPL use. */ /* TODO: This needs to be synchronized. */ analyze::processor an_prc{ *this }; +#ifndef JANK_STATIC_RUNTIME jit::processor jit_prc; +#endif /* TODO: This needs to be a dynamic var. */ native_unordered_map> module_dependencies; diff --git a/compiler+runtime/src/cpp/jank/aot/processor.cpp b/compiler+runtime/src/cpp/jank/aot/processor.cpp index cd2ad52b5..80981541a 100644 --- a/compiler+runtime/src/cpp/jank/aot/processor.cpp +++ b/compiler+runtime/src/cpp/jank/aot/processor.cpp @@ -38,6 +38,7 @@ namespace jank::aot , library_dirs{ opts.library_dirs } , define_macros{ opts.define_macros } , libs{ opts.libs } + , runtime{ opts.target_runtime } , output_filename(opts.output_filename) { } @@ -70,8 +71,6 @@ extern "C" jank_object_ref jank_call0(jank_object_ref f); sb(R"( extern "C" jank_object_ref jank_load_clojure_core_native(); extern "C" jank_object_ref jank_load_clojure_string_native(); -extern "C" jank_object_ref jank_load_jank_compiler_native(); -extern "C" jank_object_ref jank_load_clojure_core(); extern "C" jank_object_ref jank_var_intern_c(char const *, char const *); extern "C" jank_object_ref jank_deref(jank_object_ref); extern "C" jank_object_ref jank_call2(jank_object_ref, jank_object_ref, jank_object_ref); @@ -94,7 +93,6 @@ int main(int argc, const char** argv) auto const fn{ [](int const argc, char const **argv) { jank_load_clojure_core_native(); jank_load_clojure_string_native(); - jank_load_jank_compiler_native(); )"); @@ -203,7 +201,7 @@ int main(int argc, const char** argv) compiler_args.push_back(strdup(util::format("-L{}", library_dir).c_str())); } - for(auto const &lib : { "-ljank", + for(auto const &lib : { runtime == "static" ? "-ljank_static_rt" : "-ljank", /* Default libraries that jank depends on. */ "-lfolly", "-lgc", diff --git a/compiler+runtime/src/cpp/jank/evaluate_static_rt.cpp b/compiler+runtime/src/cpp/jank/evaluate_static_rt.cpp new file mode 100644 index 000000000..7143f2c47 --- /dev/null +++ b/compiler+runtime/src/cpp/jank/evaluate_static_rt.cpp @@ -0,0 +1,135 @@ +#include + +namespace jank::evaluate +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" + + analyze::expr::function_ref wrap_expression( + analyze::expression_ref const expr, + jtl::immutable_string const &name, + native_vector + params) // NOLINT(performance-unnecessary-value-param): This is a dummy implementation for static runtime + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + analyze::expr::function_ref wrap_expressions(native_vector const &exprs, + analyze::processor const &an_prc, + jtl::immutable_string const &name) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expression_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expr::def_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expr::var_deref_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expr::var_ref_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expr::call_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expr::primitive_literal_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expr::list_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expr::vector_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expr::map_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expr::set_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expr::local_reference_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expr::function_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expr::recur_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expr::recursion_reference_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expr::named_recursion_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expr::let_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expr::letfn_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expr::do_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expr::if_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expr::throw_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expr::try_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + + runtime::object_ref eval(analyze::expr::case_ref) + { + throw std::runtime_error{ "Eval disabled in static runtime." }; + } + +#pragma clang diagnostic pop +} diff --git a/compiler+runtime/src/cpp/jank/runtime/context.cpp b/compiler+runtime/src/cpp/jank/runtime/context.cpp index a3ba1349d..2e4109f34 100644 --- a/compiler+runtime/src/cpp/jank/runtime/context.cpp +++ b/compiler+runtime/src/cpp/jank/runtime/context.cpp @@ -39,10 +39,14 @@ namespace jank::runtime } context::context(util::cli::options const &opts) - : jit_prc{ opts } - , binary_cache_dir{ util::binary_cache_dir(opts.optimization_level, - opts.include_dirs, - opts.define_macros) } + : +#ifndef JANK_STATIC_RUNTIME + jit_prc{ opts } + , +#endif + binary_cache_dir{ + util::binary_cache_dir(opts.optimization_level, opts.include_dirs, opts.define_macros) + } , module_loader{ *this, opts.module_path } { auto const core(intern_ns(make_box("clojure.core"))); @@ -160,8 +164,12 @@ namespace jank::runtime return eval_string(file.expect_ok().view()); } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" object_ref context::eval_string(native_persistent_string_view const &code) +#pragma clang diagnostic pop { +#ifndef JANK_STATIC_RUNTIME profile::timer const timer{ "rt eval_string" }; read::lex::processor l_prc{ code }; read::parse::processor p_prc{ l_prc.begin(), l_prc.end() }; @@ -196,10 +204,17 @@ namespace jank::runtime } return ret; +#else + throw std::runtime_error{ "Eval disabled in static runtime." }; +#endif } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" void context::eval_cpp_string(native_persistent_string_view const &code) const +#pragma clang diagnostic pop { +#ifndef JANK_STATIC_RUNTIME profile::timer const timer{ "rt eval_cpp_string" }; /* TODO: Handle all the errors here to avoid exceptions. Also, return a message that @@ -215,6 +230,9 @@ namespace jank::runtime } auto err(jit_prc.interpreter->Execute(partial_tu)); +#else + throw std::runtime_error{ "Eval disabled in static runtime." }; +#endif } object_ref context::read_string(native_persistent_string_view const &code) diff --git a/compiler+runtime/src/cpp/jank/runtime/module/loader.cpp b/compiler+runtime/src/cpp/jank/runtime/module/loader.cpp index 020d4514c..d5e382cd0 100644 --- a/compiler+runtime/src/cpp/jank/runtime/module/loader.cpp +++ b/compiler+runtime/src/cpp/jank/runtime/module/loader.cpp @@ -612,8 +612,12 @@ namespace jank::runtime::module util::println("Loading module {} from {}", module, path); } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" jtl::string_result loader::load(jtl::immutable_string const &module, origin const ori) +#pragma clang diagnostic pop { +#ifndef JANK_STATIC_RUNTIME if(ori != origin::source && loader::is_loaded(module)) { return ok(); @@ -659,11 +663,18 @@ namespace jank::runtime::module locked_ordered_modules->push_back(module); } return ok(); +#else + return err("Load or eval not allowed in static runtime"); +#endif } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" jtl::string_result loader::load_o(jtl::immutable_string const &module, file_entry const &entry) const +#pragma clang diagnostic pop { +#ifndef JANK_STATIC_RUNTIME profile::timer const timer{ util::format("load object {}", module) }; /* While loading an object, if the main ns loading symbol exists, then @@ -695,11 +706,18 @@ namespace jank::runtime::module load(); return ok(); +#else + return err("Can't load file in static runtime"); +#endif } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" jtl::string_result loader::load_cpp(jtl::immutable_string const &module, file_entry const &entry) const +#pragma clang diagnostic pop { +#ifndef JANK_STATIC_RUNTIME if(entry.archive_path.is_some()) { visit_jar_entry(entry, [&](auto const &zip_entry) { @@ -725,10 +743,17 @@ namespace jank::runtime::module load(); return ok(); +#else + return err("Load or eval not allowed in static runtime"); +#endif } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" jtl::string_result loader::load_jank(file_entry const &entry) const +#pragma clang diagnostic pop { +#ifndef JANK_STATIC_RUNTIME if(entry.archive_path.is_some()) { visit_jar_entry(entry, [&](auto const &zip_entry) { @@ -747,6 +772,9 @@ namespace jank::runtime::module } return ok(); +#else + return err("Load or eval not allowed in static runtime"); +#endif } jtl::string_result loader::load_cljc(file_entry const &entry) const diff --git a/compiler+runtime/src/cpp/jank/util/cli.cpp b/compiler+runtime/src/cpp/jank/util/cli.cpp index 5ce24c5bd..b74f1cc11 100644 --- a/compiler+runtime/src/cpp/jank/util/cli.cpp +++ b/compiler+runtime/src/cpp/jank/util/cli.cpp @@ -54,11 +54,9 @@ namespace jank::util::cli /* Compile module subcommand. */ auto &cli_compile_module( - *cli.add_subcommand("compile-module", "Compile a file and its dependencies.")); + *cli.add_subcommand("compile-module", + "Compile a file and its dependencies into object files.")); cli_compile_module.fallthrough(); - cli_compile_module - .add_option("--runtime", opts.target_runtime, "The runtime of the compiled program.") - ->check(CLI::IsMember({ "dynamic", "static" })); cli_compile_module .add_option("module", opts.target_module, "Module to compile (must be on the module path).") ->required(); @@ -86,6 +84,10 @@ namespace jank::util::cli "compile", "Ahead of time compile project with entrypoint module containing -main.")); cli_compile.fallthrough(); + cli_compile + .add_option("--runtime", opts.target_runtime, "The runtime of the compiled program.") + ->default_val("dynamic") + ->check(CLI::IsMember({ "dynamic", "static" })); cli_compile.add_option("-o", opts.output_filename, "Output executable name.") ->default_val("a.out"); cli_compile.add_option("module", opts.target_module, "The entrypoint module.")->required(); diff --git a/compiler+runtime/test/bash/ahead-of-time/pass-test b/compiler+runtime/test/bash/ahead-of-time/pass-test index 0a9da51b6..7e72f3f5d 100755 --- a/compiler+runtime/test/bash/ahead-of-time/pass-test +++ b/compiler+runtime/test/bash/ahead-of-time/pass-test @@ -58,13 +58,14 @@ (defn compile-command [module-path main-module {:keys [include-headers? optimization-flag - output-file] + output-file + static-runtime?] :or {optimization-flag "-O0" output-file "cli"}}] (str jank-exe " " optimization-flag " --module-path=" module-path " " (when include-headers? (get-headers)) - " compile " main-module " -o " output-file)) + " compile " main-module (when static-runtime? " --runtime static ") " -o " output-file)) (use-fixtures :each @@ -101,6 +102,54 @@ (proc/sh {:dir dir}) :out)))))))) +(deftest aot-single-jank-module-static-runtime-succeeds + (let [alias-name "single-jank-module" + module-path (module-path alias-name) + main-module "main-with-args" + args " foo bar baz" + expected-output (slurp (str "expected-output/" alias-name "/" main-module)) + compile-command (compile-command module-path main-module {:static-runtime? true}) + cli-path (delay (find-binary {}))] + (testing (str alias-name " & " main-module) + (println "Compile command: " compile-command) + (is (= 0 (->> compile-command + (proc/sh {:out *out* + :err *out*}) + :exit))) + (is (= expected-output (-> @cli-path (str args) + proc/sh :out)))))) + +(deftest aot-single-jank-module-static-runtime-succeeds + (let [alias-name "single-jank-module" + module-path (module-path alias-name) + main-module "main-with-args" + args " foo bar baz" + expected-output (slurp (str "expected-output/" alias-name "/" main-module)) + compile-command (compile-command module-path main-module {:static-runtime? true}) + cli-path (delay (find-binary {}))] + (testing (str alias-name " & " main-module) + (println "Compile command: " compile-command) + (is (= 0 (->> compile-command + (proc/sh {:out *out* + :err *out*}) + :exit))) + (is (= expected-output (-> @cli-path (str args) + proc/sh :out)))))) + +(deftest aot-single-jank-module-static-runtime-eval-fails + (let [alias-name "single-jank-module" + module-path (module-path alias-name) + main-module "core-with-eval" + compile-command (compile-command module-path main-module {:static-runtime? true}) + cli-path (delay (find-binary {}))] + (testing (str alias-name " & " main-module) + (println "Compile command: " compile-command) + (is (= 0 (->> compile-command + (proc/sh {:out *out* + :err *out*}) + :exit))) + (is (not= 0 (-> @cli-path proc/sh :exit)))))) + (deftest aot-multiple-jank-modules (let [alias-name "only-jank-modules" module-path (module-path alias-name) diff --git a/compiler+runtime/test/bash/ahead-of-time/src/single-jank-module/core_with_eval.jank b/compiler+runtime/test/bash/ahead-of-time/src/single-jank-module/core_with_eval.jank new file mode 100644 index 000000000..e09e58bdc --- /dev/null +++ b/compiler+runtime/test/bash/ahead-of-time/src/single-jank-module/core_with_eval.jank @@ -0,0 +1,4 @@ +(ns core-with-eval) + +(defn -main [] + (eval '(println "Hello world"))) From 7d6a12fa0b6471311ca760e37c8cbf7ee56657ef Mon Sep 17 00:00:00 2001 From: Saket Patel Date: Sat, 12 Jul 2025 21:36:09 +0530 Subject: [PATCH 2/2] chore: remove duplicate test --- .../test/bash/ahead-of-time/pass-test | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/compiler+runtime/test/bash/ahead-of-time/pass-test b/compiler+runtime/test/bash/ahead-of-time/pass-test index 7e72f3f5d..c3e619263 100755 --- a/compiler+runtime/test/bash/ahead-of-time/pass-test +++ b/compiler+runtime/test/bash/ahead-of-time/pass-test @@ -119,23 +119,6 @@ (is (= expected-output (-> @cli-path (str args) proc/sh :out)))))) -(deftest aot-single-jank-module-static-runtime-succeeds - (let [alias-name "single-jank-module" - module-path (module-path alias-name) - main-module "main-with-args" - args " foo bar baz" - expected-output (slurp (str "expected-output/" alias-name "/" main-module)) - compile-command (compile-command module-path main-module {:static-runtime? true}) - cli-path (delay (find-binary {}))] - (testing (str alias-name " & " main-module) - (println "Compile command: " compile-command) - (is (= 0 (->> compile-command - (proc/sh {:out *out* - :err *out*}) - :exit))) - (is (= expected-output (-> @cli-path (str args) - proc/sh :out)))))) - (deftest aot-single-jank-module-static-runtime-eval-fails (let [alias-name "single-jank-module" module-path (module-path alias-name)