diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e87c092ac..06e4f89dd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -97,10 +97,11 @@ jobs: analyze: on ## Debug + sanitization - - name: Ubuntu - address sanitizer - os: ubuntu-24.04 - build_type: Debug - sanitize: address + # TODO: Fix this. GC issue: https://github.com/bdwgc/bdwgc/issues/772 + #- name: Ubuntu - address sanitizer + # os: ubuntu-24.04 + # build_type: Debug + # sanitize: address - name: Ubuntu - undefined behavior sanitizer os: ubuntu-24.04 diff --git a/.gitignore b/.gitignore index 66bce157b..454712591 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ a.out .jank-repl-history .envrc .direnv -/notes # Vim files /.ycm_extra_conf.py* diff --git a/.gitmodules b/.gitmodules index f4d195697..4bc396c22 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "compiler+runtime/third-party/folly"] path = compiler+runtime/third-party/folly url = https://github.com/jank-lang/folly.git -[submodule "compiler+runtime/third-party/bpptree"] - path = compiler+runtime/third-party/bpptree - url = https://github.com/jank-lang/BppTree.git [submodule "compiler+runtime/third-party/immer"] path = compiler+runtime/third-party/immer url = https://github.com/jank-lang/immer.git diff --git a/compiler+runtime/CMakeLists.txt b/compiler+runtime/CMakeLists.txt index d07be2b0d..f7a6009d2 100644 --- a/compiler+runtime/CMakeLists.txt +++ b/compiler+runtime/CMakeLists.txt @@ -42,6 +42,7 @@ option(jank_coverage "Enable code coverage measurement" OFF) option(jank_analyze "Enable static analysis" OFF) option(jank_test "Enable jank's test suite" OFF) option(jank_unity_build "Optimize translation unit compilation for the number of cores" OFF) +option(jank_debug_gc "Enable GC debug assertions" OFF) set(jank_sanitize "none" CACHE STRING "The type of Clang sanitization to use (or none)") set(jank_resource_dir "../lib/jank/${CMAKE_PROJECT_VERSION}" @@ -569,7 +570,6 @@ target_include_directories( PUBLIC "$" "$" - "$" "$" "$" "$" @@ -905,7 +905,7 @@ add_custom_command( DEPENDS ${CMAKE_BINARY_DIR}/jank-phase-1 ${CMAKE_SOURCE_DIR}/src/jank/clojure/core.jank ${jank_incremental_pch_flag} OUTPUT ${jank_core_libraries_flag} BYPRODUCTS ${jank_clojure_core_o} - COMMAND ${CMAKE_BINARY_DIR}/jank-phase-1 compile-module -o ${jank_clojure_core_o} clojure.core + COMMAND ${CMAKE_BINARY_DIR}/jank-phase-1 compile-module -O3 -o ${jank_clojure_core_o} clojure.core COMMAND touch ${jank_core_libraries_flag} ) add_custom_target( diff --git a/compiler+runtime/cmake/dependency/bdwgc.cmake b/compiler+runtime/cmake/dependency/bdwgc.cmake index fff5b9cba..0176e2692 100644 --- a/compiler+runtime/cmake/dependency/bdwgc.cmake +++ b/compiler+runtime/cmake/dependency/bdwgc.cmake @@ -4,18 +4,37 @@ set(BUILD_SHARED_LIBS_OLD ${BUILD_SHARED_LIBS}) set(CMAKE_CXX_CLANG_TIDY_OLD ${CMAKE_CXX_CLANG_TIDY}) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -w") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w") + + if(NOT APPLE) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DREDIRECT_MALLOC=GC_malloc_uncollectable -DREDIR_MALLOC_AND_LINUX_THREADS") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DREDIRECT_MALLOC=GC_malloc_uncollectable -DREDIR_MALLOC_AND_LINUX_THREADS") + endif() + set(BUILD_SHARED_LIBS OFF) set(CMAKE_CXX_CLANG_TIDY "") set(enable_cplusplus ON CACHE BOOL "Enable C++") set(build_cord OFF CACHE BOOL "Build cord") set(enable_docs OFF CACHE BOOL "Enable docs") + set(enable_threads ON CACHE BOOL "Enable multi-threading support") set(enable_large_config ON CACHE BOOL "Optimize for large heap or root set") set(enable_throw_bad_alloc_library ON CACHE BOOL "Enable C++ gctba library build") + set(enable_gc_debug OFF CACHE BOOL "Support for pointer back-tracing") + + if(jank_debug_gc) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DGC_ASSERTIONS") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DGC_ASSERTIONS") + set(enable_gc_debug ON CACHE BOOL "Support for pointer back-tracing") + endif() + add_subdirectory(third-party/bdwgc EXCLUDE_FROM_ALL) unset(enable_cplusplus) unset(build_cord) unset(enable_docs) + unset(enable_threads) + unset(enable_large_config) + unset(enable_throw_bad_alloc_library) + unset(enable_gc_debug) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS_OLD}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS_OLD}") set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_OLD}) diff --git a/compiler+runtime/cmake/install.cmake b/compiler+runtime/cmake/install.cmake index 9c7f7553c..70121d454 100644 --- a/compiler+runtime/cmake/install.cmake +++ b/compiler+runtime/cmake/install.cmake @@ -61,11 +61,6 @@ jank_glob_install_without_prefix( PATTERN "${CMAKE_SOURCE_DIR}/third-party/folly/folly/*.h" ) -jank_glob_install_without_prefix( - INPUT_PREFIX "${CMAKE_SOURCE_DIR}/third-party/bpptree/" - PATTERN "${CMAKE_SOURCE_DIR}/third-party/bpptree/include/*" -) - jank_glob_install_without_prefix( INPUT_PREFIX "${CMAKE_SOURCE_DIR}/third-party/immer/" OUTPUT_PREFIX "include/" @@ -82,12 +77,6 @@ jank_glob_install_without_prefix( PATTERN "${CMAKE_SOURCE_DIR}/third-party/ftxui/include/*" ) -jank_glob_install_without_prefix( - INPUT_PREFIX "${CMAKE_SOURCE_DIR}/third-party/libzippp/src/" - OUTPUT_PREFIX "include/" - PATTERN "${CMAKE_SOURCE_DIR}/third-party/libzippp/src/*" -) - jank_glob_install_without_prefix( INPUT_PREFIX "${CMAKE_SOURCE_DIR}/third-party/cpptrace/" PATTERN "${CMAKE_SOURCE_DIR}/third-party/cpptrace/include/*" diff --git a/compiler+runtime/cmake/summary.cmake b/compiler+runtime/cmake/summary.cmake index d5000b845..8477b6e01 100644 --- a/compiler+runtime/cmake/summary.cmake +++ b/compiler+runtime/cmake/summary.cmake @@ -11,6 +11,7 @@ jank_message("│ jank analyze : ${jank_analyze}") jank_message("│ jank sanitize : ${jank_sanitize}") jank_message("│ jank unity build : ${jank_unity_build}") jank_message("│ jank resource dir : ${jank_resource_dir}") +jank_message("│ jank debug gc : ${jank_debug_gc}") jank_message("│ clang version : ${LLVM_PACKAGE_VERSION}") jank_message("│ clang prefix : ${CLANG_INSTALL_PREFIX}") jank_message("│ clang resource dir : ${clang_resource_dir}") diff --git a/compiler+runtime/include/cpp/jank/analyze/cpp_util.hpp b/compiler+runtime/include/cpp/jank/analyze/cpp_util.hpp index 8cd03e655..9d8ec82ac 100644 --- a/compiler+runtime/include/cpp/jank/analyze/cpp_util.hpp +++ b/compiler+runtime/include/cpp/jank/analyze/cpp_util.hpp @@ -28,6 +28,7 @@ namespace jank::analyze::cpp_util native_vector> find_adl_scopes(native_vector> const &starters); jtl::immutable_string get_qualified_name(jtl::ptr scope); + jtl::immutable_string get_qualified_type_name(jtl::ptr type); void register_rtti(jtl::ptr type); jtl::ptr expression_type(expression_ref expr); diff --git a/compiler+runtime/include/cpp/jank/analyze/expr/if.hpp b/compiler+runtime/include/cpp/jank/analyze/expr/if.hpp index a97780514..a8636d3c8 100644 --- a/compiler+runtime/include/cpp/jank/analyze/expr/if.hpp +++ b/compiler+runtime/include/cpp/jank/analyze/expr/if.hpp @@ -25,6 +25,7 @@ namespace jank::analyze::expr /* TODO: Rename to have _expr suffixes. */ expression_ref condition; + /* The then/else exprs are expected to have the same type. We handle this during analysis. */ expression_ref then; jtl::option else_; }; diff --git a/compiler+runtime/include/cpp/jank/analyze/expr/try.hpp b/compiler+runtime/include/cpp/jank/analyze/expr/try.hpp index 088537884..e4cc210ef 100644 --- a/compiler+runtime/include/cpp/jank/analyze/expr/try.hpp +++ b/compiler+runtime/include/cpp/jank/analyze/expr/try.hpp @@ -1,7 +1,9 @@ #pragma once + #include +#include #include namespace jank::runtime::obj @@ -36,7 +38,7 @@ namespace jank::analyze::expr void walk(std::function)> const &f) override; do_ref body; - jtl::option catch_body{}; + native_vector catch_bodies{}; jtl::option finally_body{}; }; } diff --git a/compiler+runtime/include/cpp/jank/analyze/expr/var_ref.hpp b/compiler+runtime/include/cpp/jank/analyze/expr/var_ref.hpp index 272b71803..6ddae3921 100644 --- a/compiler+runtime/include/cpp/jank/analyze/expr/var_ref.hpp +++ b/compiler+runtime/include/cpp/jank/analyze/expr/var_ref.hpp @@ -26,6 +26,12 @@ namespace jank::analyze::expr runtime::object_ref to_runtime_data() const override; + /* Holds the fully qualified name for the originally resolved var. + * It will be useful to know that the var ref happened through a + * referred var, for static analysis and error reporting. + * + * For all the other purposes, `var` member should be used that points + * to the actual value of the var.. */ runtime::obj::symbol_ref qualified_name{}; runtime::var_ref var{}; }; diff --git a/compiler+runtime/include/cpp/jank/analyze/local_frame.hpp b/compiler+runtime/include/cpp/jank/analyze/local_frame.hpp index 9b389951d..4c02ddcdb 100644 --- a/compiler+runtime/include/cpp/jank/analyze/local_frame.hpp +++ b/compiler+runtime/include/cpp/jank/analyze/local_frame.hpp @@ -8,6 +8,7 @@ namespace jank::runtime { struct context; + using var_ref = oref; namespace obj { @@ -29,25 +30,6 @@ namespace jank::analyze using function_context_ref = jtl::ref; } - struct lifted_var - { - jtl::immutable_string native_name{}; - runtime::obj::symbol_ref var_name{}; - - runtime::object_ref to_runtime_data() const; - }; - - /* TODO: Track constant usages to figure out if boxing is needed at all, - * rather than just doing both. */ - struct lifted_constant - { - jtl::immutable_string native_name{}; - jtl::option unboxed_native_name{}; - runtime::object_ref data{}; - - runtime::object_ref to_runtime_data() const; - }; - struct local_binding { runtime::obj::symbol_ref name{}; @@ -135,14 +117,6 @@ namespace jank::analyze static bool within_same_fn(jtl::ptr, jtl::ptr); - runtime::obj::symbol_ref lift_var(runtime::obj::symbol_ref const &); - jtl::option> - find_lifted_var(runtime::obj::symbol_ref const &) const; - - void lift_constant(runtime::object_ref); - jtl::option> - find_lifted_constant(runtime::object_ref) const; - static local_frame const &find_closest_fn_frame(local_frame const &frame); static local_frame &find_closest_fn_frame(local_frame &frame); @@ -152,12 +126,6 @@ namespace jank::analyze jtl::option> parent; native_unordered_map locals; native_unordered_map captures; - native_unordered_map lifted_vars; - native_unordered_map, - runtime::very_equal_to> - lifted_constants; /* This is only set if the frame type is fn. */ jtl::ptr fn_ctx; }; diff --git a/compiler+runtime/include/cpp/jank/c_api.h b/compiler+runtime/include/cpp/jank/c_api.h index a66d5b9b5..ee4d6ceee 100644 --- a/compiler+runtime/include/cpp/jank/c_api.h +++ b/compiler+runtime/include/cpp/jank/c_api.h @@ -49,6 +49,8 @@ extern "C" jank_object_ref jank_read_string(jank_object_ref s); jank_object_ref jank_read_string_c(char const * const s); + jank_object_ref jank_ns_intern(jank_object_ref sym); + jank_object_ref jank_ns_intern_c(char const * const sym); void jank_ns_set_symbol_counter(char const * const ns, jank_u64 const count); jank_object_ref jank_var_intern(jank_object_ref ns, jank_object_ref name); diff --git a/compiler+runtime/include/cpp/jank/codegen/llvm_processor.hpp b/compiler+runtime/include/cpp/jank/codegen/llvm_processor.hpp index c5688b954..da55b0828 100644 --- a/compiler+runtime/include/cpp/jank/codegen/llvm_processor.hpp +++ b/compiler+runtime/include/cpp/jank/codegen/llvm_processor.hpp @@ -108,9 +108,10 @@ namespace jank::codegen llvm_processor(analyze::expr::function_ref const expr, jtl::immutable_string const &module, compilation_target target); + llvm_processor(analyze::expr::function_ref expr, std::unique_ptr ctx); /* For this ctor, we're inheriting the context from another function, which means * we're building a nested function. */ - llvm_processor(analyze::expr::function_ref expr, std::unique_ptr ctx); + llvm_processor(analyze::expr::function_ref expr, jtl::ref ctx); llvm_processor(llvm_processor const &) = delete; llvm_processor(llvm_processor &&) noexcept = default; diff --git a/compiler+runtime/include/cpp/jank/codegen/processor.hpp b/compiler+runtime/include/cpp/jank/codegen/processor.hpp index 048215b59..559f46ed4 100644 --- a/compiler+runtime/include/cpp/jank/codegen/processor.hpp +++ b/compiler+runtime/include/cpp/jank/codegen/processor.hpp @@ -92,104 +92,72 @@ namespace jank::codegen processor(processor const &) = delete; processor(processor &&) noexcept = delete; + jtl::option gen(analyze::expression_ref const, analyze::expr::function_arity const &); + jtl::option gen(analyze::expr::def_ref const, analyze::expr::function_arity const &); jtl::option - gen(analyze::expression_ref const, analyze::expr::function_arity const &, bool box_needed); + gen(analyze::expr::var_deref_ref const, analyze::expr::function_arity const &); jtl::option - gen(analyze::expr::def_ref const, analyze::expr::function_arity const &, bool box_needed); + gen(analyze::expr::var_ref_ref const, analyze::expr::function_arity const &); + jtl::option gen(analyze::expr::call_ref const, analyze::expr::function_arity const &); jtl::option - gen(analyze::expr::var_deref_ref const, analyze::expr::function_arity const &, bool box_needed); + gen(analyze::expr::primitive_literal_ref const, analyze::expr::function_arity const &); + jtl::option gen(analyze::expr::list_ref const, analyze::expr::function_arity const &); + jtl::option gen(analyze::expr::vector_ref const, analyze::expr::function_arity const &); + jtl::option gen(analyze::expr::map_ref const, analyze::expr::function_arity const &); + jtl::option gen(analyze::expr::set_ref const, analyze::expr::function_arity const &); jtl::option - gen(analyze::expr::var_ref_ref const, analyze::expr::function_arity const &, bool box_needed); + gen(analyze::expr::local_reference_ref const, analyze::expr::function_arity const &); jtl::option - gen(analyze::expr::call_ref const, analyze::expr::function_arity const &, bool box_needed); - jtl::option gen(analyze::expr::primitive_literal_ref const, - analyze::expr::function_arity const &, - bool box_needed); + gen(analyze::expr::function_ref const, analyze::expr::function_arity const &); + jtl::option gen(analyze::expr::recur_ref const, analyze::expr::function_arity const &); jtl::option - gen(analyze::expr::vector_ref const, analyze::expr::function_arity const &, bool box_needed); + gen(analyze::expr::recursion_reference_ref const, analyze::expr::function_arity const &); jtl::option - gen(analyze::expr::map_ref const, analyze::expr::function_arity const &, bool box_needed); + gen(analyze::expr::named_recursion_ref const, analyze::expr::function_arity const &); + jtl::option gen(analyze::expr::let_ref const, analyze::expr::function_arity const &); + jtl::option gen(analyze::expr::letfn_ref const, analyze::expr::function_arity const &); + jtl::option gen(analyze::expr::do_ref const, analyze::expr::function_arity const &); + jtl::option gen(analyze::expr::if_ref const, analyze::expr::function_arity const &); + jtl::option gen(analyze::expr::throw_ref const, analyze::expr::function_arity const &); + jtl::option gen(analyze::expr::try_ref const, analyze::expr::function_arity const &); + jtl::option gen(analyze::expr::case_ref const, analyze::expr::function_arity const &); jtl::option - gen(analyze::expr::set_ref const, analyze::expr::function_arity const &, bool box_needed); - jtl::option gen(analyze::expr::local_reference_ref const, - analyze::expr::function_arity const &, - bool box_needed); + gen(analyze::expr::cpp_raw_ref const, analyze::expr::function_arity const &); jtl::option - gen(analyze::expr::function_ref const, analyze::expr::function_arity const &, bool box_needed); + gen(analyze::expr::cpp_type_ref const, analyze::expr::function_arity const &); jtl::option - gen(analyze::expr::recur_ref const, analyze::expr::function_arity const &, bool box_needed); - jtl::option gen(analyze::expr::recursion_reference_ref const, - analyze::expr::function_arity const &, - bool box_needed); - jtl::option gen(analyze::expr::named_recursion_ref const, - analyze::expr::function_arity const &, - bool box_needed); + gen(analyze::expr::cpp_value_ref const, analyze::expr::function_arity const &); jtl::option - gen(analyze::expr::let_ref const, analyze::expr::function_arity const &, bool box_needed); + gen(analyze::expr::cpp_cast_ref const, analyze::expr::function_arity const &); jtl::option - gen(analyze::expr::letfn_ref const, analyze::expr::function_arity const &, bool box_needed); + gen(analyze::expr::cpp_call_ref const, analyze::expr::function_arity const &); jtl::option - gen(analyze::expr::do_ref const, analyze::expr::function_arity const &, bool box_needed); + gen(analyze::expr::cpp_constructor_call_ref const, analyze::expr::function_arity const &); jtl::option - gen(analyze::expr::if_ref const, analyze::expr::function_arity const &, bool box_needed); + gen(analyze::expr::cpp_member_call_ref const, analyze::expr::function_arity const &); jtl::option - gen(analyze::expr::throw_ref const, analyze::expr::function_arity const &, bool box_needed); + gen(analyze::expr::cpp_member_access_ref const, analyze::expr::function_arity const &); jtl::option - gen(analyze::expr::try_ref const, analyze::expr::function_arity const &, bool box_needed); + gen(analyze::expr::cpp_builtin_operator_call_ref const, analyze::expr::function_arity const &); jtl::option - gen(analyze::expr::case_ref const, analyze::expr::function_arity const &, bool box_needed); + gen(analyze::expr::cpp_box_ref const, analyze::expr::function_arity const &); jtl::option - gen(analyze::expr::cpp_raw_ref const, analyze::expr::function_arity const &, bool box_needed); + gen(analyze::expr::cpp_unbox_ref const, analyze::expr::function_arity const &); jtl::option - gen(analyze::expr::cpp_type_ref const, analyze::expr::function_arity const &, bool box_needed); + gen(analyze::expr::cpp_new_ref const, analyze::expr::function_arity const &); jtl::option - gen(analyze::expr::cpp_value_ref const, analyze::expr::function_arity const &, bool box_needed); - jtl::option - gen(analyze::expr::cpp_cast_ref const, analyze::expr::function_arity const &, bool box_needed); - jtl::option - gen(analyze::expr::cpp_call_ref const, analyze::expr::function_arity const &, bool box_needed); - jtl::option gen(analyze::expr::cpp_constructor_call_ref const, - analyze::expr::function_arity const &, - bool box_needed); - jtl::option gen(analyze::expr::cpp_member_call_ref const, - analyze::expr::function_arity const &, - bool box_needed); - jtl::option gen(analyze::expr::cpp_member_access_ref const, - analyze::expr::function_arity const &, - bool box_needed); - jtl::option gen(analyze::expr::cpp_builtin_operator_call_ref const, - analyze::expr::function_arity const &, - bool box_needed); - jtl::option - gen(analyze::expr::cpp_box_ref const, analyze::expr::function_arity const &, bool box_needed); - jtl::option - gen(analyze::expr::cpp_unbox_ref const, analyze::expr::function_arity const &, bool box_needed); + gen(analyze::expr::cpp_delete_ref const, analyze::expr::function_arity const &); jtl::immutable_string declaration_str(); void build_header(); void build_body(); void build_footer(); - jtl::immutable_string expression_str(bool box_needed); - - jtl::immutable_string module_init_str(jtl::immutable_string const &module); - - void format_elided_var(jtl::immutable_string const &start, - jtl::immutable_string const &end, - jtl::immutable_string const &ret_tmp, - native_vector const &arg_exprs, - analyze::expr::function_arity const &fn_arity, - bool arg_box_needed, - bool ret_box_needed); - void format_direct_call(jtl::immutable_string const &source_tmp, - jtl::immutable_string const &ret_tmp, - native_vector const &arg_exprs, - analyze::expr::function_arity const &fn_arity, - bool arg_box_needed); + jtl::immutable_string expression_str(); + void format_dynamic_call(jtl::immutable_string const &source_tmp, jtl::immutable_string const &ret_tmp, native_vector const &arg_exprs, - analyze::expr::function_arity const &fn_arity, - bool arg_box_needed); + analyze::expr::function_arity const &fn_arity); analyze::expr::function_ref root_fn; jtl::immutable_string module; @@ -202,6 +170,19 @@ namespace jank::codegen jtl::string_builder footer_buffer; jtl::string_builder expression_buffer; jtl::immutable_string expression_fn_name; + + struct lifted_var + { + jtl::immutable_string native_name; + bool owned{}; + }; + + native_unordered_map lifted_vars; + native_unordered_map, + runtime::very_equal_to> + lifted_constants; bool generated_declaration{}; bool generated_expression{}; }; diff --git a/compiler+runtime/include/cpp/jank/detail/to_runtime_data.hpp b/compiler+runtime/include/cpp/jank/detail/to_runtime_data.hpp index d2d555814..94fa6f535 100644 --- a/compiler+runtime/include/cpp/jank/detail/to_runtime_data.hpp +++ b/compiler+runtime/include/cpp/jank/detail/to_runtime_data.hpp @@ -1,13 +1,15 @@ #pragma once + #include #include -#include -#include #include #include +#include +#include +#include namespace jank::detail { @@ -100,4 +102,16 @@ namespace jank::detail { return m; } + + template + object_ref to_runtime_data(native_vector const &m) + { + /* NOLINTNEXTLINE(misc-const-correctness): Can't be const. */ + runtime::detail::native_persistent_vector ret; + for(auto const &e : m) + { + (void)ret.push_back(to_runtime_data(e)); + } + return make_box(ret); + } } diff --git a/compiler+runtime/include/cpp/jank/error.hpp b/compiler+runtime/include/cpp/jank/error.hpp index 746dd0f40..7526cea62 100644 --- a/compiler+runtime/include/cpp/jank/error.hpp +++ b/compiler+runtime/include/cpp/jank/error.hpp @@ -103,6 +103,7 @@ namespace jank::error analyze_invalid_cpp_member_access, analyze_invalid_cpp_capture, analyze_mismatched_if_types, + analyze_known_issue, internal_analyze_failure, internal_codegen_failure, @@ -301,6 +302,8 @@ namespace jank::error return "analyze/invalid-cpp-capture"; case kind::analyze_mismatched_if_types: return "analyze/mismatched-if-types"; + case kind::analyze_known_issue: + return "analyze/known-issue"; case kind::internal_analyze_failure: return "internal/analysis-failure"; @@ -378,6 +381,8 @@ namespace jank::error * is because cpptrace doesn't use our GC allocator. */ struct base : gc_cleanup { + static constexpr bool is_error{ true }; + base() = delete; base(base const &) = delete; base(base &&) noexcept = default; @@ -457,4 +462,12 @@ namespace jank { return jtl::make_ref(jtl::forward(args)...); } + + namespace error + { + error_ref internal_failure(jtl::immutable_string const &message); + /* This can be used by jtl helpers which can't reach into jank but which fail. */ + [[noreturn]] + void throw_internal_failure(jtl::immutable_string const &message); + } } diff --git a/compiler+runtime/include/cpp/jank/error/analyze.hpp b/compiler+runtime/include/cpp/jank/error/analyze.hpp index 280d4312e..0c5dfda7f 100644 --- a/compiler+runtime/include/cpp/jank/error/analyze.hpp +++ b/compiler+runtime/include/cpp/jank/error/analyze.hpp @@ -150,6 +150,9 @@ namespace jank::error error_ref analyze_invalid_cpp_member_access(jtl::immutable_string const &message, read::source const &source, runtime::object_ref expansion); + error_ref analyze_known_issue(jtl::immutable_string const &message, + read::source const &source, + runtime::object_ref expansion); error_ref internal_analyze_failure(jtl::immutable_string const &message, runtime::object_ref expansion); error_ref internal_analyze_failure(jtl::immutable_string const &message, diff --git a/compiler+runtime/include/cpp/jank/error/report.hpp b/compiler+runtime/include/cpp/jank/error/report.hpp index 04b38c62a..46418aa46 100644 --- a/compiler+runtime/include/cpp/jank/error/report.hpp +++ b/compiler+runtime/include/cpp/jank/error/report.hpp @@ -5,4 +5,5 @@ namespace jank::error { void report(error_ref e); + void warn(jtl::immutable_string const &); } diff --git a/compiler+runtime/include/cpp/jank/error/runtime.hpp b/compiler+runtime/include/cpp/jank/error/runtime.hpp index 1deab795f..706a2e5e0 100644 --- a/compiler+runtime/include/cpp/jank/error/runtime.hpp +++ b/compiler+runtime/include/cpp/jank/error/runtime.hpp @@ -9,6 +9,7 @@ namespace jank::error error_ref runtime_unable_to_open_file(jtl::immutable_string const &message); error_ref runtime_invalid_cpp_eval(); error_ref runtime_unable_to_load_module(jtl::immutable_string const &message); + error_ref runtime_unable_to_load_module(error_ref cause); error_ref internal_runtime_failure(jtl::immutable_string const &message); error_ref runtime_invalid_unbox(jtl::immutable_string const &message, read::source const &unbox_source); diff --git a/compiler+runtime/include/cpp/jank/jit/processor.hpp b/compiler+runtime/include/cpp/jank/jit/processor.hpp index ca870111a..b133f45f2 100644 --- a/compiler+runtime/include/cpp/jank/jit/processor.hpp +++ b/compiler+runtime/include/cpp/jank/jit/processor.hpp @@ -17,6 +17,11 @@ namespace llvm } } +namespace clang +{ + class Value; +} + namespace Cpp { class Interpreter; @@ -30,6 +35,7 @@ namespace jank::jit ~processor(); void eval_string(jtl::immutable_string const &s) const; + void eval_string(jtl::immutable_string const &s, clang::Value *) const; void load_object(jtl::immutable_string_view const &path) const; void load_dynamic_library(jtl::immutable_string const &path) const; void load_ir_module(llvm::orc::ThreadSafeModule &&m) const; diff --git a/compiler+runtime/include/cpp/jank/prelude.hpp b/compiler+runtime/include/cpp/jank/prelude.hpp index 64789c2df..2618f3e30 100644 --- a/compiler+runtime/include/cpp/jank/prelude.hpp +++ b/compiler+runtime/include/cpp/jank/prelude.hpp @@ -8,5 +8,7 @@ #include #include #include +#include #include #include +#include diff --git a/compiler+runtime/include/cpp/jank/read/parse.hpp b/compiler+runtime/include/cpp/jank/read/parse.hpp index 8de1c5e14..e42d364d4 100644 --- a/compiler+runtime/include/cpp/jank/read/parse.hpp +++ b/compiler+runtime/include/cpp/jank/read/parse.hpp @@ -117,7 +117,7 @@ namespace jank::read::parse * token, we should check this list to see if there's already a form we should pull out. * This is needed because parse iteration works one form at a time and splicing potentially * turns one form into many. */ - std::list pending_forms; + native_list pending_forms; lex::token latest_token; jtl::option shorthand; /* Whether or not the next form is considered quoted. */ diff --git a/compiler+runtime/include/cpp/jank/runtime/context.hpp b/compiler+runtime/include/cpp/jank/runtime/context.hpp index 0f23616f1..7fc357419 100644 --- a/compiler+runtime/include/cpp/jank/runtime/context.hpp +++ b/compiler+runtime/include/cpp/jank/runtime/context.hpp @@ -43,7 +43,6 @@ namespace jank::runtime context(); context(context const &) = delete; context(context &&) noexcept = delete; - ~context(); ns_ref intern_ns(jtl::immutable_string const &); ns_ref intern_ns(obj::symbol_ref const &); @@ -60,10 +59,12 @@ namespace jank::runtime obj::symbol_ref qualify_symbol(obj::symbol_ref const &) const; jtl::option find_local(obj::symbol_ref const &); - jtl::result intern_var(obj::symbol_ref const &); + jtl::result intern_var(obj::symbol_ref const &qualified_sym); + jtl::result intern_var(jtl::immutable_string const &); jtl::result intern_var(jtl::immutable_string const &ns, jtl::immutable_string const &name); jtl::result intern_owned_var(obj::symbol_ref const &); + jtl::result intern_owned_var(jtl::immutable_string const &); jtl::result intern_owned_var(jtl::immutable_string const &ns, jtl::immutable_string const &name); var_ref find_var(obj::symbol_ref const &); @@ -80,11 +81,11 @@ namespace jank::runtime object_ref macroexpand(object_ref o); object_ref eval_file(jtl::immutable_string const &path); - object_ref eval_string(jtl::immutable_string_view const &code); - jtl::result eval_cpp_string(jtl::immutable_string_view const &code) const; - object_ref read_string(jtl::immutable_string_view const &code); + object_ref eval_string(jtl::immutable_string const &code); + jtl::result eval_cpp_string(jtl::immutable_string const &code) const; + object_ref read_string(jtl::immutable_string const &code); native_vector - analyze_string(jtl::immutable_string_view const &code, bool const eval = true); + analyze_string(jtl::immutable_string const &code, bool const eval = true); /* Finds the specified module on the module path and loads it. If * the module is already loaded, nothing is done. @@ -98,10 +99,10 @@ namespace jank::runtime * Module meow.cat refers to foo.bar$meow.cat */ jtl::result - load_module(jtl::immutable_string_view const &module, module::origin ori); + load_module(jtl::immutable_string const &module, module::origin ori); /* Does all the same work as load_module, but also writes compiled files to the file system. */ - jtl::result compile_module(jtl::immutable_string_view const &module); + jtl::result compile_module(jtl::immutable_string const &module); object_ref eval(object_ref const o); @@ -111,11 +112,12 @@ namespace jank::runtime /* Generates a unique name for use with anything from codgen structs, * lifted vars, to shadowed locals. Prefixes with current namespace. */ jtl::immutable_string unique_namespaced_string() const; - jtl::immutable_string unique_namespaced_string(jtl::immutable_string_view const &prefix) const; + jtl::immutable_string unique_namespaced_string(jtl::immutable_string const &prefix) const; + jtl::immutable_string unique_string() const; jtl::immutable_string unique_munged_string() const; - jtl::immutable_string unique_munged_string(jtl::immutable_string_view const &prefix) const; + jtl::immutable_string unique_string(jtl::immutable_string const &prefix) const; obj::symbol unique_symbol() const; - obj::symbol unique_symbol(jtl::immutable_string_view const &prefix) const; + obj::symbol unique_symbol(jtl::immutable_string const &prefix) const; folly::Synchronized> namespaces; folly::Synchronized> keywords; @@ -161,8 +163,8 @@ namespace jank::runtime /* Hold onto the CLI Options for use at runtime */ util::cli::options opts; - /* TODO: Remove this map. Just use the list. */ - static thread_local native_unordered_map> + /* XXX: We can't use thread_local here, due to bdwgc not supporting it. */ + static native_unordered_map> thread_binding_frames; /* This must go last, since it'll try to access other bits in the runtime context during diff --git a/compiler+runtime/include/cpp/jank/runtime/convert/builtin.hpp b/compiler+runtime/include/cpp/jank/runtime/convert/builtin.hpp index eb8745bc8..148f0f7c3 100644 --- a/compiler+runtime/include/cpp/jank/runtime/convert/builtin.hpp +++ b/compiler+runtime/include/cpp/jank/runtime/convert/builtin.hpp @@ -35,10 +35,20 @@ namespace jank::runtime return const_cast(t); } + static constexpr object *into_object(object_ref const t) + { + return t.erase(); + } + static constexpr object *from_object(T t) { return const_cast(t); } + + static constexpr object *from_object(object_ref const t) + { + return t.erase(); + } }; /* Any typed object can convert to/from itself easily. */ diff --git a/compiler+runtime/include/cpp/jank/runtime/core/make_box.hpp b/compiler+runtime/include/cpp/jank/runtime/core/make_box.hpp index bdb09ab89..72930ecdb 100644 --- a/compiler+runtime/include/cpp/jank/runtime/core/make_box.hpp +++ b/compiler+runtime/include/cpp/jank/runtime/core/make_box.hpp @@ -9,9 +9,19 @@ #include #include #include +#include namespace jank::runtime { + template + requires(!behavior::object_like && !std::is_same_v, char>) + [[gnu::flatten, gnu::hot, gnu::visibility("default")]] + inline auto make_box(T * const d) + { + return make_box( + const_cast(reinterpret_cast(d))); + } + /* TODO: Constexpr more of these. */ [[gnu::flatten, gnu::hot, gnu::visibility("default")]] inline auto make_box(std::nullptr_t const &) diff --git a/compiler+runtime/include/cpp/jank/runtime/detail/type.hpp b/compiler+runtime/include/cpp/jank/runtime/detail/type.hpp index 8eab57d00..a115e6602 100644 --- a/compiler+runtime/include/cpp/jank/runtime/detail/type.hpp +++ b/compiler+runtime/include/cpp/jank/runtime/detail/type.hpp @@ -8,9 +8,6 @@ #include #include -#include -#include - #include #include @@ -36,15 +33,6 @@ namespace immer jank::memory_policy>; } -namespace bpptree::detail -{ - extern template struct BppTreeSet; - extern template struct BppTreeMap; -} - namespace jank::runtime::detail { using native_persistent_vector = immer::vector; @@ -54,11 +42,10 @@ namespace jank::runtime::detail set, std::equal_to, memory_policy>; using native_transient_hash_set = native_persistent_hash_set::transient_type; - /* TODO: These BppTree types will leak until we get them GC allocated. */ + /* TODO: Bring in proper immutable sorted maps/sets. */ using native_persistent_sorted_set - = bpptree::BppTreeSet::Persistent; - using native_transient_sorted_set - = bpptree::BppTreeSet::Transient; + = std::set>; + using native_transient_sorted_set = native_persistent_sorted_set; using native_persistent_hash_map = immer::map::Persistent; - using native_transient_sorted_map - = bpptree::BppTreeMap::Transient; + = std::map>>; + using native_transient_sorted_map = native_persistent_sorted_map; /* If an object requires this in its constructor, use your runtime context to intern * it instead. */ diff --git a/compiler+runtime/include/cpp/jank/runtime/obj/persistent_hash_map.hpp b/compiler+runtime/include/cpp/jank/runtime/obj/persistent_hash_map.hpp index 770ac4542..b511a8ecd 100644 --- a/compiler+runtime/include/cpp/jank/runtime/obj/persistent_hash_map.hpp +++ b/compiler+runtime/include/cpp/jank/runtime/obj/persistent_hash_map.hpp @@ -48,11 +48,7 @@ namespace jank::runtime::obj this->meta = meta; } - static persistent_hash_map_ref empty() - { - static auto const ret(make_box()); - return ret; - } + static persistent_hash_map_ref empty(); using base_persistent_map::base_persistent_map; diff --git a/compiler+runtime/include/cpp/jank/runtime/obj/transient_sorted_map.hpp b/compiler+runtime/include/cpp/jank/runtime/obj/transient_sorted_map.hpp index 97e3fdcc1..5b77e1145 100644 --- a/compiler+runtime/include/cpp/jank/runtime/obj/transient_sorted_map.hpp +++ b/compiler+runtime/include/cpp/jank/runtime/obj/transient_sorted_map.hpp @@ -18,8 +18,7 @@ namespace jank::runtime::obj transient_sorted_map() = default; transient_sorted_map(transient_sorted_map &&) noexcept = default; transient_sorted_map(transient_sorted_map const &) = default; - transient_sorted_map(runtime::detail::native_persistent_sorted_map const &d); - transient_sorted_map(runtime::detail::native_persistent_sorted_map &&d); + transient_sorted_map(value_type const &d); transient_sorted_map(value_type &&d); static transient_sorted_map_ref empty(); diff --git a/compiler+runtime/include/cpp/jank/runtime/obj/transient_sorted_set.hpp b/compiler+runtime/include/cpp/jank/runtime/obj/transient_sorted_set.hpp index e2d679eaf..1f05fab0b 100644 --- a/compiler+runtime/include/cpp/jank/runtime/obj/transient_sorted_set.hpp +++ b/compiler+runtime/include/cpp/jank/runtime/obj/transient_sorted_set.hpp @@ -18,8 +18,7 @@ namespace jank::runtime::obj transient_sorted_set() = default; transient_sorted_set(transient_sorted_set &&) noexcept = default; transient_sorted_set(transient_sorted_set const &) = default; - transient_sorted_set(runtime::detail::native_persistent_sorted_set const &d); - transient_sorted_set(runtime::detail::native_persistent_sorted_set &&d); + transient_sorted_set(value_type const &d); transient_sorted_set(value_type &&d); static transient_sorted_set_ref empty(); diff --git a/compiler+runtime/include/cpp/jank/runtime/oref.hpp b/compiler+runtime/include/cpp/jank/runtime/oref.hpp index 5c1f24e9e..9b09341c5 100644 --- a/compiler+runtime/include/cpp/jank/runtime/oref.hpp +++ b/compiler+runtime/include/cpp/jank/runtime/oref.hpp @@ -100,6 +100,14 @@ namespace jank::runtime constexpr oref &operator=(oref const &rhs) noexcept = default; constexpr oref &operator=(oref &&rhs) noexcept = default; + template + requires behavior::object_like + constexpr oref &operator=(oref const &rhs) noexcept + { + data = &rhs->base; + return *this; + } + constexpr bool operator==(oref const &rhs) const noexcept { return data == rhs.data; @@ -499,5 +507,4 @@ namespace jank::runtime { return static_cast(ptr.data); } - } diff --git a/compiler+runtime/include/cpp/jank/runtime/var.hpp b/compiler+runtime/include/cpp/jank/runtime/var.hpp index 4a8c5b442..b111a57ac 100644 --- a/compiler+runtime/include/cpp/jank/runtime/var.hpp +++ b/compiler+runtime/include/cpp/jank/runtime/var.hpp @@ -58,6 +58,7 @@ namespace jank::runtime var_ref set_dynamic(bool dyn); + obj::symbol_ref to_qualified_symbol() const; var_thread_binding_ref get_thread_binding() const; /* behavior::derefable */ diff --git a/compiler+runtime/include/cpp/jank/type.hpp b/compiler+runtime/include/cpp/jank/type.hpp index dbd3eef3a..0fb35c802 100644 --- a/compiler+runtime/include/cpp/jank/type.hpp +++ b/compiler+runtime/include/cpp/jank/type.hpp @@ -43,7 +43,7 @@ namespace jank template using native_list = std::list>; template - using native_map = std::map>>; + using native_map = std::map, native_allocator>>; template using native_set = std::set, native_allocator>; diff --git a/compiler+runtime/include/cpp/jank/util/cli.hpp b/compiler+runtime/include/cpp/jank/util/cli.hpp index 8e792b4eb..56b44ba88 100644 --- a/compiler+runtime/include/cpp/jank/util/cli.hpp +++ b/compiler+runtime/include/cpp/jank/util/cli.hpp @@ -21,6 +21,19 @@ namespace jank::util::cli cpp }; + constexpr char const *codegen_type_str(codegen_type const type) + { + switch(type) + { + case codegen_type::llvm_ir: + return "llvm-ir"; + case codegen_type::cpp: + return "cpp"; + default: + return "unknown"; + } + } + struct options { /* Runtime. */ @@ -29,7 +42,7 @@ namespace jank::util::cli bool profiler_enabled{}; bool perf_profiling_enabled{}; bool gc_incremental{}; - codegen_type codegen{ codegen_type::llvm_ir }; + codegen_type codegen{ codegen_type::cpp }; /* Native dependencies. */ native_vector include_dirs; diff --git a/compiler+runtime/include/cpp/jtl/immutable_string.hpp b/compiler+runtime/include/cpp/jtl/immutable_string.hpp index afc94dc41..13034501f 100644 --- a/compiler+runtime/include/cpp/jtl/immutable_string.hpp +++ b/compiler+runtime/include/cpp/jtl/immutable_string.hpp @@ -54,12 +54,10 @@ namespace jtl struct immutable_string { using value_type = char; - using allocator_type = jank::native_allocator; - using allocator_traits = std::allocator_traits; - using size_type = allocator_traits::size_type; using traits_type = std::char_traits; using pointer_type = value_type *; using const_pointer_type = value_type const *; + using size_type = usize; using iterator = pointer_type; using const_iterator = const_pointer_type; using reverse_iterator = std::reverse_iterator; @@ -705,7 +703,7 @@ namespace jtl * 3. As a large_storage instance, containing a pointer, size, and capacity */ /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) */ - struct storage : allocator_type + struct storage { /* TODO: What if we store a max of 22 chars and dedicate a byte for flags with no masking? */ union { @@ -724,7 +722,7 @@ namespace jtl /* NOTE: No performance difference between if/switch here. */ if(get_category() == category::large_owned) { - allocator_traits::deallocate(store, store.large.data, store.large.size + 1); + GC_free(store.large.data); } } @@ -838,7 +836,8 @@ namespace jtl { jank_debug_assert(max_small_size < size); /* TODO: Apply gnu::malloc to this fn. */ - store.large.data = std::assume_aligned(store.allocate(size + 1)); + store.large.data = std::assume_aligned( + static_cast(GC_malloc_atomic(size + 1))); traits_type::copy(store.large.data, data, size); store.large.data[size] = 0; store.large.size = size; @@ -849,7 +848,8 @@ namespace jtl constexpr void init_large_fill(value_type const fill, u8 const size) noexcept { jank_debug_assert(max_small_size < size); - store.large.data = std::assume_aligned(store.allocate(size + 1)); + store.large.data = std::assume_aligned( + static_cast(GC_malloc_atomic(size + 1))); traits_type::assign(store.large.data, size, fill); store.large.data[size] = 0; store.large.size = size; @@ -864,7 +864,8 @@ namespace jtl { auto const size(lhs_size + rhs_size); jank_debug_assert(max_small_size < size); - store.large.data = std::assume_aligned(store.allocate(size + 1)); + store.large.data = std::assume_aligned( + static_cast(GC_malloc_atomic(size + 1))); traits_type::copy(store.large.data, lhs, lhs_size); traits_type::copy(store.large.data + lhs_size, rhs, rhs_size); store.large.data[size] = 0; @@ -878,7 +879,8 @@ namespace jtl { auto const size(std::distance(begin, end)); jank_debug_assert(max_small_size < size); - store.large.data = std::assume_aligned(store.allocate(size + 1)); + store.large.data = std::assume_aligned( + static_cast(GC_malloc_atomic(size + 1))); std::copy(begin, end, store.large.data); store.large.data[size] = 0; store.large.size = size; diff --git a/compiler+runtime/include/cpp/jtl/result.hpp b/compiler+runtime/include/cpp/jtl/result.hpp index 6162aa035..946ff95d5 100644 --- a/compiler+runtime/include/cpp/jtl/result.hpp +++ b/compiler+runtime/include/cpp/jtl/result.hpp @@ -6,6 +6,12 @@ #include #include +namespace jank::error +{ + [[noreturn]] + void throw_internal_failure(jtl::immutable_string const &message); +} + namespace jtl { namespace detail @@ -30,6 +36,37 @@ namespace jtl struct result { }; + + template + [[noreturn]] + constexpr void panic(Result const &r) + { + using E = typename Result::error_type; + + /* A result can hold any type of error type, but when we expect a value + * and it's not there, we only want to throw a jank::error_ref. This + * not only makes catching easier, it also fits into our error reporting. + * + * So we need to do some work here to see if we have an error_ref, something + * we can use to build an error_ref (like a string), or just something else. */ + + /* This is a roundabout way of looking for error_ref. */ + if constexpr(requires(E t) { E::value_type::is_error; }) + { + throw r.expect_err(); + } + else if constexpr(jtl::is_same) + { + jank::error::throw_internal_failure(r.expect_err()); + } + else + { + immutable_string s{ "Unexpected result<" }; + s = s + type_name().data(); + s = s + ">"; + jank::error::throw_internal_failure(s); + } + } } constexpr detail::result ok() noexcept @@ -54,6 +91,9 @@ namespace jtl { static_assert(!std::same_as, "Result and error type must be different."); + using value_type = R; + using error_type = E; + constexpr result(detail::result &&r) noexcept : data{ R{ std::move(r.data) } } { @@ -112,9 +152,7 @@ namespace jtl return; } - /* TODO: Update all of these throws to throw a consistent type, regardless of the - * error type. This simplifies our catching logic. */ - throw expect_err(); + detail::panic(*this); } constexpr R const &expect_ok() const @@ -183,9 +221,8 @@ namespace jtl constexpr R unwrap_move() { if(!is_ok()) - /* TODO: Panic function. */ { - throw expect_err(); + detail::panic(*this); } return std::move(std::get(data)); } @@ -245,6 +282,8 @@ namespace jtl template struct [[nodiscard]] result { + using error_type = E; + constexpr result(detail::result &&) noexcept : data{ void_t{} } { @@ -288,7 +327,7 @@ namespace jtl return; } - throw expect_err(); + detail::panic(*this); } constexpr void expect_ok() const diff --git a/compiler+runtime/src/cpp/jank/analyze/cpp_util.cpp b/compiler+runtime/src/cpp/jank/analyze/cpp_util.cpp index aac12ebaa..6d76e2031 100644 --- a/compiler+runtime/src/cpp/jank/analyze/cpp_util.cpp +++ b/compiler+runtime/src/cpp/jank/analyze/cpp_util.cpp @@ -71,6 +71,17 @@ namespace jank::analyze::cpp_util jtl::ptr resolve_type(jtl::immutable_string const &sym, u8 const ptr_count) { + /* Clang canonicalizes "char" to "signed char" on some platforms, which breaks exception + * handling since they are distinct types. We use resolve_literal_type to get the + * exact type for "char". */ + if(sym == "char") + { + if(auto const res{ resolve_literal_type("char").expect_ok() }) + { + return apply_pointers(res, ptr_count); + } + } + auto const type{ Cpp::GetType(sym) }; if(type) { @@ -292,6 +303,51 @@ namespace jank::analyze::cpp_util return res; } + jtl::immutable_string get_qualified_type_name(jtl::ptr const type) + { + if(type == untyped_object_ptr_type()) + { + return "jank::runtime::object_ref"; + } + /* TODO: Handle typed object refs, too. */ + + /* TODO: We probably want a recursive approach to this, for types and scopes. */ + auto const qual_type{ clang::QualType::getFromOpaquePtr(type) }; + if(auto const *alias{ + llvm::dyn_cast_or_null(qual_type.getTypePtrOrNull()) }; + alias) + { + if(auto const *alias_decl{ alias->getDecl() }; alias_decl) + { + /* Type aliases already include pointer/reference semantics in their definition. + * Don't add extra `*` for pointer type aliases like `using void_ptr = void*;` + * as that would make it `void_ptr*` (void**) instead of `void_ptr` (void*). + * + * Use printQualifiedName with the default policy to get the fully qualified name. */ + std::string qualified_name; + llvm::raw_string_ostream os(qualified_name); + alias_decl->printQualifiedName(os); + os.flush(); + return qualified_name; + } + } + + if(auto const scope{ Cpp::GetScopeFromType(type) }; scope) + { + auto name{ get_qualified_name(scope) }; + if(Cpp::IsPointerType(type)) + { + name = name + "*"; + } + else if(Cpp::IsReferenceType(type)) + { + name = name + "&"; + } + return name; + } + return Cpp::GetTypeAsString(type); + } + /* This is a quick and dirty helper to get the RTTI for a given QualType. We need * this for exception catching. */ void register_rtti(jtl::ptr const type) @@ -299,7 +355,7 @@ namespace jank::analyze::cpp_util auto &diag{ runtime::__rt_ctx->jit_prc.interpreter->getCompilerInstance()->getDiagnostics() }; clang::DiagnosticErrorTrap const trap{ diag }; auto const alias{ runtime::__rt_ctx->unique_namespaced_string() }; - auto const code{ util::format("&typeid({})", Cpp::GetTypeAsString(type)) }; + auto const code{ util::format("&typeid({})", get_qualified_type_name(type)) }; clang::Value value; auto exec_res{ runtime::__rt_ctx->jit_prc.interpreter->ParseAndExecute(code.c_str(), &value) }; if(exec_res || trap.hasErrorOccurred()) diff --git a/compiler+runtime/src/cpp/jank/analyze/expr/try.cpp b/compiler+runtime/src/cpp/jank/analyze/expr/try.cpp index 04a333742..5a7b67f45 100644 --- a/compiler+runtime/src/cpp/jank/analyze/expr/try.cpp +++ b/compiler+runtime/src/cpp/jank/analyze/expr/try.cpp @@ -34,11 +34,14 @@ namespace jank::analyze::expr { position = pos; body->propagate_position(pos); - if(catch_body) + if(!catch_bodies.empty()) { - catch_body.unwrap().propagate_position(pos); + for(auto const &catch_body : catch_bodies) + { + catch_body.propagate_position(pos); + } } - /* The result of the 'finally' body is discarded, so we always keep it in statement position. */ + /* The result of the 'finally' body is discarded, so we always keep it in the statement position. */ } runtime::object_ref try_::to_runtime_data() const @@ -50,7 +53,7 @@ namespace jank::analyze::expr persistent_array_map::create_unique(make_box("body"), body->to_runtime_data(), make_box("catch"), - jank::detail::to_runtime_data(catch_body), + jank::detail::to_runtime_data(catch_bodies), make_box("finally"), jank::detail::to_runtime_data(finally_body))); } @@ -58,9 +61,12 @@ namespace jank::analyze::expr void try_::walk(std::function)> const &f) { f(body); - if(catch_body.is_some()) + if(!catch_bodies.empty()) { - f(catch_body.unwrap().body); + for(auto const &catch_body : catch_bodies) + { + f(catch_body.body); + } } if(finally_body.is_some()) { diff --git a/compiler+runtime/src/cpp/jank/analyze/local_frame.cpp b/compiler+runtime/src/cpp/jank/analyze/local_frame.cpp index d5dc397db..44383363a 100644 --- a/compiler+runtime/src/cpp/jank/analyze/local_frame.cpp +++ b/compiler+runtime/src/cpp/jank/analyze/local_frame.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -13,16 +14,6 @@ namespace jank::analyze { using namespace jank::runtime; - object_ref lifted_var::to_runtime_data() const - { - return obj::persistent_array_map::create_unique(make_box("var_name"), var_name); - } - - object_ref lifted_constant::to_runtime_data() const - { - return obj::persistent_array_map::create_unique(make_box("data"), data); - } - object_ref local_binding::to_runtime_data() const { return obj::persistent_array_map::create_unique( @@ -182,91 +173,15 @@ namespace jank::analyze return &find_closest_fn_frame(*l) == &find_closest_fn_frame(*r); } - obj::symbol_ref local_frame::lift_var(obj::symbol_ref const &sym) - { - auto &closest_fn(find_closest_fn_frame(*this)); - auto const &found(closest_fn.lifted_vars.find(sym)); - if(found != closest_fn.lifted_vars.end()) - { - return found->first; - } - - obj::symbol_ref qualified_sym{}; - if(sym->ns.empty()) - { - qualified_sym - = make_box(expect_object(__rt_ctx->current_ns_var->deref())->name->name, - sym->name); - } - else - { - qualified_sym = make_box(*sym); - } - - /* We use unique native names, just so var names don't clash with the underlying C++ API. */ - lifted_var lv{ __rt_ctx->unique_namespaced_string(munge(qualified_sym->name)), qualified_sym }; - closest_fn.lifted_vars.emplace(qualified_sym, std::move(lv)); - return qualified_sym; - } - - /* TODO: These are not used in IR gen. Remove entirely? */ - jtl::option> - local_frame::find_lifted_var(obj::symbol_ref const &sym) const - { - auto const &closest_fn(find_closest_fn_frame(*this)); - auto const &found(closest_fn.lifted_vars.find(sym)); - if(found != closest_fn.lifted_vars.end()) - { - return some(std::ref(found->second)); - } - return none; - } - - void local_frame::lift_constant(object_ref const constant) - { - auto &closest_fn(find_closest_fn_frame(*this)); - auto const &found(closest_fn.lifted_constants.find(constant)); - if(found != closest_fn.lifted_constants.end()) - { - return; - } - - auto const name(__rt_ctx->unique_symbol("const")); - auto const unboxed_name{ visit_number_like( - [&](auto const) -> jtl::option { return name.name + "__unboxed"; }, - []() -> jtl::option { return none; }, - constant) }; - - lifted_constant l{ name.name, unboxed_name, constant }; - closest_fn.lifted_constants.emplace(constant, std::move(l)); - } - - jtl::option> - local_frame::find_lifted_constant(object_ref const o) const - { - auto const &closest_fn(find_closest_fn_frame(*this)); - auto const &found(closest_fn.lifted_constants.find(o)); - if(found != closest_fn.lifted_constants.end()) - { - return some(std::ref(found->second)); - } - return none; - } - object_ref local_frame::to_runtime_data() const { - return obj::persistent_array_map::create_unique( - make_box("type"), - make_box(frame_type_str(type)), - make_box("parent"), - jank::detail::to_runtime_data(parent), - make_box("locals"), - jank::detail::to_runtime_data(locals), - make_box("captures"), - jank::detail::to_runtime_data(captures), - make_box("lifted_vars"), - jank::detail::to_runtime_data(lifted_vars), - make_box("lifted_constants"), - jank::detail::to_runtime_data(lifted_constants)); + return obj::persistent_array_map::create_unique(make_box("type"), + make_box(frame_type_str(type)), + make_box("parent"), + jank::detail::to_runtime_data(parent), + make_box("locals"), + jank::detail::to_runtime_data(locals), + make_box("captures"), + jank::detail::to_runtime_data(captures)); } } diff --git a/compiler+runtime/src/cpp/jank/analyze/processor.cpp b/compiler+runtime/src/cpp/jank/analyze/processor.cpp index 0d5262d3d..09578698c 100644 --- a/compiler+runtime/src/cpp/jank/analyze/processor.cpp +++ b/compiler+runtime/src/cpp/jank/analyze/processor.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -942,7 +943,9 @@ namespace jank::analyze latest_expansion(macro_expansions)); } if(is_ctor - && Cpp::IsAggregateConstructible(val->type, arg_types, __rt_ctx->unique_munged_string())) + && Cpp::IsAggregateConstructible(val->type, + arg_types, + runtime::munge(__rt_ctx->unique_namespaced_string()))) { //util::println("using aggregate initializaation"); return jtl::make_ref(position, @@ -1282,13 +1285,13 @@ namespace jank::analyze ->add_usage(read::parse::reparse_nth(l, 1)); } - auto qualified_sym(current_frame->lift_var(sym)); + auto qualified_sym(runtime::__rt_ctx->qualify_symbol(sym)); qualified_sym->meta = sym->meta; /* We always def in the current ns, so we want an owned var. */ - auto const var(__rt_ctx->intern_owned_var(qualified_sym)); - if(var.is_err()) + auto const var_res(__rt_ctx->intern_owned_var(qualified_sym)); + if(var_res.is_err()) { - return error::internal_analyze_failure(var.expect_err(), + return error::internal_analyze_failure(var_res.expect_err(), meta_source(sym), latest_expansion(macro_expansions)); } @@ -1316,7 +1319,7 @@ namespace jank::analyze } value_expr = some(value_result.expect_ok()); - vars.insert_or_assign(var.expect_ok(), value_expr.unwrap()); + vars.insert_or_assign(var_res.expect_ok(), value_expr.unwrap()); } if(has_docstring) @@ -1335,13 +1338,6 @@ namespace jank::analyze qualified_sym = qualified_sym->with_meta(meta_with_doc); } - /* Lift this so it can be used during codegen. */ - /* TODO: I don't think lifting meta is actually needed anymore. Verify. */ - if(qualified_sym->meta.is_some()) - { - current_frame->lift_constant(qualified_sym->meta.unwrap()); - } - return jtl::make_ref(position, current_frame, true, qualified_sym, value_expr); } @@ -1595,12 +1591,6 @@ namespace jank::analyze latest_expansion(macro_expansions)); } - /* Macros aren't lifted, since they're not used during runtime. */ - auto const macro_kw(__rt_ctx->intern_keyword("", "macro", true).expect_ok()); - if(var->meta.is_none() || get(var->meta.unwrap(), macro_kw).is_nil()) - { - current_frame->lift_var(qualified_sym); - } return jtl::make_ref(position, current_frame, true, qualified_sym, var); } @@ -1632,7 +1622,7 @@ namespace jank::analyze native_vector param_symbols; param_symbols.reserve(params->data.size()); - std::set unique_param_symbols; + native_set unique_param_symbols; bool is_variadic{}; for(auto it(params->data.begin()); it != params->data.end(); ++it) @@ -2259,7 +2249,7 @@ namespace jank::analyze /* All bindings in a letfn appear simultaneously and may be mutually recursive. * This makes creating a letfn locals frame a bit more involved than let, where locals - * are introduced left-to-right. For example, each binding in (letfn [(a [] b) (b [] a)]) + * are introduced left-to-right. For example, each binding in (letfn [(a [] b) (b [] a)]) * requires the other to be in scope in order to be analyzed. * * We tackle this in two steps. First, we create empty local bindings for all names. @@ -2308,7 +2298,7 @@ namespace jank::analyze /* Populate the local frame we prepared for sym in the previous loop with its binding. */ auto it(ret->pairs.emplace_back(sym, fexpr)); - auto local(ret->frame->locals.find(sym)->second); + auto &local(ret->frame->locals.find(sym)->second); local.value_expr = some(it.second); local.needs_box = it.second->needs_box; } @@ -2544,7 +2534,7 @@ namespace jank::analyze auto const arg_sym(runtime::expect_object(arg)); - auto const qualified_sym(current_frame->lift_var(arg_sym)); + auto const qualified_sym{ __rt_ctx->qualify_symbol(arg_sym) }; auto const found_var(__rt_ctx->find_var(qualified_sym)); if(found_var.is_nil()) { @@ -2554,7 +2544,11 @@ namespace jank::analyze latest_expansion(macro_expansions)); } - return jtl::make_ref(position, current_frame, true, qualified_sym, found_var); + return jtl::make_ref(position, + current_frame, + true, + found_var->to_qualified_symbol(), + found_var); } processor::expression_result @@ -2566,8 +2560,7 @@ namespace jank::analyze { auto const pop_macro_expansions{ push_macro_expansions(*this, o) }; - auto const qualified_sym( - current_frame->lift_var(make_box(o->n->name->name, o->name->name))); + auto const qualified_sym(__rt_ctx->qualify_symbol(o->to_qualified_symbol())); return jtl::make_ref(position, current_frame, true, qualified_sym, o); } @@ -2616,7 +2609,6 @@ namespace jank::analyze auto try_frame(jtl::make_ref(local_frame::frame_type::try_, current_frame)); /* We introduce a new frame so that we can register the sym as a local. * It holds the exception value which was caught. */ - auto catch_frame(jtl::make_ref(local_frame::frame_type::catch_, current_frame)); auto finally_frame(jtl::make_ref(local_frame::frame_type::finally, current_frame)); auto ret{ jtl::make_ref(position, try_frame, true, jtl::make_ref()) }; @@ -2701,69 +2693,124 @@ namespace jank::analyze object_source(item), latest_expansion(macro_expansions)); } - if(has_catch) - { - /* TODO: Note where the other catch is. */ - return error::analyze_invalid_try("Only one 'catch' form may be supplied.", - object_source(item), - latest_expansion(macro_expansions)); - } has_catch = true; - /* Verify we have (catch ...) */ + /* Verify we have (catch cpp/type ...) */ auto const catch_list(runtime::list(item)); - auto const catch_body_size(catch_list->count()); - if(catch_body_size == 1) + if(auto const catch_body_size(catch_list->count()); catch_body_size < 2) { return error::analyze_invalid_try( - "A symbol is required after 'catch', which is used as the binding to " - "hold the exception value.", + "Each 'catch' form requires the type of exception to catch and a symbol for the " + "name of the exception value.", object_source(item), latest_expansion(macro_expansions)); } + auto catch_it(catch_list->data.rest()); + auto const catch_type_form(catch_it.first().unwrap()); + catch_it = catch_it.rest(); + auto const catch_sym_form(catch_it.first().unwrap()); + auto const catch_type(analyze(catch_type_form, current_frame, position, fn_ctx, true)); + if(catch_type.is_err()) + { + return error::analyze_invalid_try(catch_type.expect_err()->message, + object_source(item), + error::note{ + "An exception type is required before this form.", + object_source(catch_sym_form), + }, + latest_expansion(macro_expansions)) + ->add_usage(read::parse::reparse_nth(item, 1)); + } - auto const sym_obj(catch_list->data.rest().first().unwrap()); - if(sym_obj->type != runtime::object_type::symbol) + if(catch_type.expect_ok()->kind != expression_kind::cpp_type) + { + return error::analyze_invalid_try("Exception is not a type.", + object_source(item), + error::note{ + "An exception type is required before this form.", + object_source(catch_sym_form), + }, + latest_expansion(macro_expansions)) + ->add_usage(read::parse::reparse_nth(item, 1)); + } + + if(catch_sym_form->type != runtime::object_type::symbol) { return error::analyze_invalid_try( - "A symbol required after 'catch', which is used as the binding to " + "A symbol is required after 'catch', which is used as the binding to " "hold the exception value.", object_source(item), error::note{ "A symbol is required before this form.", - object_source(sym_obj), + object_source(catch_sym_form), }, latest_expansion(macro_expansions)) - ->add_usage(read::parse::reparse_nth(item, 1)); + ->add_usage(read::parse::reparse_nth(item, 2)); } - - auto const sym(runtime::expect_object(sym_obj)); - if(!sym->get_namespace().empty()) + auto const catch_sym(runtime::expect_object(catch_sym_form)); + if(!catch_sym->get_namespace().empty()) { - return error::analyze_invalid_try("The symbol after 'catch' must be unqualified.", - object_source(sym_obj), - latest_expansion(macro_expansions)); + return error::analyze_invalid_try( + "The binding symbol in 'catch' must be unqualified.", + object_source(item), + latest_expansion(macro_expansions)); } + auto const catch_type_ref(static_ref_cast(catch_type.expect_ok())); + /* If we're catching a C++ class/struct by value, we want to promote it to a reference + * to avoid object slicing and to enable polymorphism. + * However, we must NOT promote types in the jank::runtime namespace (like object_ref), + * as these are smart pointers expected to be passed by value in the runtime. */ + if(!Cpp::IsPointerType(catch_type_ref->type) + && !Cpp::IsReferenceType(catch_type_ref->type) + && !Cpp::IsBuiltin(catch_type_ref->type)) + { + /* Check if this type is in the jank::runtime namespace */ + auto const type_scope{ Cpp::GetScopeFromType(catch_type_ref->type) }; + auto const type_name{ cpp_util::get_qualified_name(type_scope) }; - catch_frame->locals.emplace(sym, local_binding{ sym, sym->name, none, catch_frame }); + if(bool const is_jank_runtime_type{ type_name.starts_with("jank::runtime::") }; + !is_jank_runtime_type) + { + catch_type_ref->type = Cpp::GetLValueReferenceType(catch_type_ref->type); + } + } + + /* Check for duplicate catch types. */ + /*TODO Add full error handling for duplicated catch types: + * Add a new error kind and notes pointing to the duplicated types*/ + for(auto const &existing_catch : ret->catch_bodies) + { + if(existing_catch.type.data == catch_type_ref->type.data) + { + return error::analyze_invalid_try("Each catch form must specify a unique type.", + object_source(item), + latest_expansion(macro_expansions)); + } + } + bool const is_object{ cpp_util::is_any_object(catch_type_ref->type) }; + auto catch_frame( + jtl::make_ref(local_frame::frame_type::catch_, current_frame)); + catch_frame->locals.emplace(catch_sym, + local_binding{ catch_sym, + catch_sym->name, + none, + catch_frame, + is_object, + false, + false, + catch_type_ref->type }); /* Now we just turn the body into a do block and have the do analyzer handle the rest. */ - auto const do_list( - catch_list->data.rest().rest().conj(make_box("do"))); + auto const do_list(catch_it.rest().conj(make_box("do"))); auto const do_res(analyze(make_box(do_list), catch_frame, position, fn_ctx, true)); if(do_res.is_err()) { return do_res.expect_err(); } - - /* TODO: Read this from the catch form. */ - static auto const object_ref_type{ cpp_util::resolve_literal_type( - "jank::runtime::oref") - .expect_ok() }; - - ret->catch_body = expr::catch_{ sym, - object_ref_type, - static_ref_cast(do_res.expect_ok()) }; + do_res.expect_ok()->frame = catch_frame; + ret->catch_bodies.emplace_back(catch_sym, + catch_type_ref->type, + static_ref_cast(do_res.expect_ok())); } break; case try_expression_type::finally_: @@ -2797,10 +2844,6 @@ namespace jank::analyze ret->body->frame = try_frame; ret->body->propagate_position(position); - if(ret->catch_body.is_some()) - { - ret->catch_body.unwrap().body->frame = catch_frame; - } if(ret->finally_body.is_some()) { ret->finally_body.unwrap()->frame = finally_frame; @@ -2817,8 +2860,6 @@ namespace jank::analyze bool const needs_box) { auto const pop_macro_expansions{ push_macro_expansions(*this, o) }; - - current_frame->lift_constant(o); return jtl::make_ref(position, current_frame, needs_box, o); } @@ -2861,12 +2902,9 @@ namespace jank::analyze /* Eval the literal to resolve exprs such as quotes. */ auto const pre_eval_expr( jtl::make_ref(position, current_frame, true, std::move(exprs), o->meta)); - auto const o(evaluate::eval(pre_eval_expr)); + auto const oref(evaluate::eval(pre_eval_expr)); - /* TODO: Order lifted constants. Use sub constants during codegen. */ - current_frame->lift_constant(o); - - return jtl::make_ref(position, current_frame, true, o); + return jtl::make_ref(position, current_frame, true, oref); } return jtl::make_ref(position, current_frame, true, std::move(exprs), o->meta); @@ -2884,8 +2922,6 @@ namespace jank::analyze /* TODO: Detect literal and act accordingly. */ return visit_map_like( [&](auto const typed_o) -> processor::expression_result { - using T = typename decltype(typed_o)::value_type; - native_vector> exprs; exprs.reserve(typed_o->data.size()); @@ -2893,18 +2929,8 @@ namespace jank::analyze { /* The two maps (hash and sorted) have slightly different iterators, so we need to * pull out the entries differently. */ - object_ref first{}, second{}; - if constexpr(std::same_as) - { - auto const &entry(kv.get()); - first = entry.first; - second = entry.second; - } - else - { - first = kv.first; - second = kv.second; - } + object_ref const first{ kv.first }; + object_ref const second{ kv.second }; auto k_expr(analyze(first, current_frame, expression_position::value, fn_ctx, true)); if(k_expr.is_err()) @@ -2989,9 +3015,6 @@ namespace jank::analyze typed_o->meta)); auto const constant(evaluate::eval(pre_eval_expr)); - /* TODO: Order lifted constants. Use sub constants during codegen. */ - current_frame->lift_constant(constant); - return jtl::make_ref(position, current_frame, true, constant); } @@ -3360,12 +3383,8 @@ namespace jank::analyze if(Cpp::IsVariable(scope)) { vk = expr::cpp_value::value_kind::variable; - /* TODO: A Clang bug prevents us from supporting references to static members. - * https://github.com/llvm/llvm-project/issues/146956 - */ if(!Cpp::IsStaticDatamember(scope) && !Cpp::IsPointerType(type)) { - /* TODO: Error if it's static and non-primitive. */ type = Cpp::GetLValueReferenceType(type); } if(Cpp::IsArrayType(Cpp::GetNonReferenceType(type))) @@ -3597,7 +3616,7 @@ namespace jank::analyze for(usize i{}; i < arg_count; ++i, it = it.rest()) { auto arg_expr{ - analyze(it.first().unwrap(), current_frame, expression_position::value, fn_ctx, needs_box) + analyze(it.first().unwrap(), current_frame, expression_position::value, fn_ctx, true) }; if(arg_expr.is_err()) { diff --git a/compiler+runtime/src/cpp/jank/c_api.cpp b/compiler+runtime/src/cpp/jank/c_api.cpp index fd63c11ec..0be7e6aad 100644 --- a/compiler+runtime/src/cpp/jank/c_api.cpp +++ b/compiler+runtime/src/cpp/jank/c_api.cpp @@ -68,6 +68,17 @@ extern "C" return __rt_ctx->read_string(s).erase(); } + jank_object_ref jank_ns_intern(jank_object_ref const sym) + { + auto const sym_obj(try_object(reinterpret_cast(sym))); + return __rt_ctx->intern_ns(sym_obj).erase(); + } + + jank_object_ref jank_ns_intern_c(char const * const sym) + { + return __rt_ctx->intern_ns(sym).erase(); + } + void jank_ns_set_symbol_counter(char const * const ns, jank_u64 const count) { auto const ns_obj(__rt_ctx->intern_ns(ns)); @@ -940,8 +951,8 @@ extern "C" { if(o_obj->type == object_type::integer) { - /* We don't hash the integer if it's an int32 value. This is to be consistent with how keys are hashed in jank's - * case macro. */ + /* We don't hash the integer if it's within an i32 value. + * This is to be consistent with how keys are hashed in jank's case macro. */ integer = (integer >= std::numeric_limits::min() && integer <= std::numeric_limits::max()) ? integer @@ -1024,7 +1035,6 @@ extern "C" /* The GC needs to enabled even before arg parsing, since our native types, * like strings, use the GC for allocations. It can still be configured later. */ GC_set_all_interior_pointers(1); - GC_enable(); GC_init(); llvm::llvm_shutdown_obj const Y{}; diff --git a/compiler+runtime/src/cpp/jank/codegen/llvm_processor.cpp b/compiler+runtime/src/cpp/jank/codegen/llvm_processor.cpp index d606df146..c09d63a66 100644 --- a/compiler+runtime/src/cpp/jank/codegen/llvm_processor.cpp +++ b/compiler+runtime/src/cpp/jank/codegen/llvm_processor.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include #include #include @@ -73,6 +75,16 @@ namespace jank::codegen std::unique_ptr pic; std::unique_ptr si; llvm::ModulePassManager mpm; + + struct exception_handler_info + { + llvm::BasicBlock *dispatch_block; + llvm::PHINode *ex_phi; + llvm::PHINode *sel_phi; + analyze::expr::try_ref expr; + }; + + std::vector exception_handlers; }; struct llvm_processor::impl @@ -126,6 +138,38 @@ namespace jank::codegen llvm::Value *gen(analyze::expr::cpp_new_ref, analyze::expr::function_arity const &); llvm::Value *gen(analyze::expr::cpp_delete_ref, analyze::expr::function_arity const &); + void route_unhandled_exception(llvm::Value *ex_ptr, + llvm::Value *selector, + llvm::BasicBlock *current_block) const; + + void register_catch_clause_rtti(expr::catch_ const &catch_clause, + llvm::LandingPadInst *landing_pad, + native_set ®istered_rtti); + + void generate_catch_block(size_t catch_index, + expr::try_ref const expr, + llvm::BasicBlock *catch_block, + llvm::Value *current_ex_ptr, + llvm::AllocaInst *result_slot, + llvm::BasicBlock *finally_block, + llvm::AllocaInst *unwind_flag_slot, + llvm::BasicBlock *continue_block, + expr::function_arity const &arity); + + void generate_catch_dispatch(expr::try_ref const expr, + llvm::PHINode *exception_phi, + llvm::PHINode *selector_phi, + llvm::BasicBlock *dispatch_block, + llvm::BasicBlock *continue_block, + llvm::AllocaInst *result_slot, + llvm::BasicBlock *finally_block, + llvm::AllocaInst *unwind_flag_slot, + llvm::AllocaInst *exception_slot, + llvm::BasicBlock *catch_cleanup_block, + expr::function_arity const &arity); + void register_parent_catch_clauses(llvm::LandingPadInst *landing_pad, + native_set ®istered_rtti); + llvm::Value *gen_var(obj::symbol_ref qualified_name) const; llvm::Value *gen_var_root(obj::symbol_ref qualified_name, var_root_kind kind) const; llvm::Value *gen_c_string(jtl::immutable_string const &s) const; @@ -512,7 +556,7 @@ namespace jank::codegen reusable_context::reusable_context(jtl::immutable_string const &module_name, std::unique_ptr llvm_ctx) : module_name{ module_name } - , ctor_name{ __rt_ctx->unique_munged_string("jank_global_init") } + , ctor_name{ __rt_ctx->unique_munged_string() } //, llvm_ctx{ std::make_unique() } //, llvm_ctx{ reinterpret_cast *>( // reinterpret_cast( @@ -524,8 +568,7 @@ namespace jank::codegen , mam{ std::make_unique() } , pic{ std::make_unique() } { - auto m{ std::make_unique(__rt_ctx->unique_munged_string(module_name).c_str(), - *llvm_ctx) }; + auto m{ std::make_unique(__rt_ctx->unique_munged_string().c_str(), *llvm_ctx) }; module = llvm::orc::ThreadSafeModule{ std::move(m), std::move(llvm_ctx) }; auto const raw_ctx{ extract_context(module) }; @@ -1638,6 +1681,7 @@ namespace jank::codegen llvm_processor::impl::gen(expr::throw_ref const expr, expr::function_arity const &arity) { auto const value(gen(expr->value, arity)); + auto const loaded_val{ load_if_needed(ctx, value) }; auto const fn_type( llvm::FunctionType::get(ctx->builder->getVoidTy(), { ctx->builder->getPtrTy() }, false)); auto fn(llvm_module->getOrInsertFunction("jank_throw", fn_type)); @@ -1651,12 +1695,12 @@ namespace jank::codegen ctx->builder->CreateInvoke(fn, unreachable_dest, lpad_and_catch_body_stack.back().lpad_bb, - { value }); + { loaded_val }); ctx->builder->SetInsertPoint(unreachable_dest); } else { - ctx->builder->CreateCall(fn, { value }); + ctx->builder->CreateCall(fn, { loaded_val }); } /* Since this code path never completes, it doesn't matter what we return. @@ -1669,17 +1713,312 @@ namespace jank::codegen return ret; } + void llvm_processor::impl::route_unhandled_exception(llvm::Value *ex_ptr, + llvm::Value *selector, + llvm::BasicBlock *current_block) const + { + /* Get parent handler (if exists) */ + reusable_context::exception_handler_info const *parent_handler{}; + if(ctx->exception_handlers.size() > 1) + { + parent_handler = &ctx->exception_handlers[ctx->exception_handlers.size() - 2]; + } + + if(parent_handler) + { + /* Branch to parent handler */ + ctx->builder->CreateBr(parent_handler->dispatch_block); + parent_handler->ex_phi->addIncoming(ex_ptr, current_block); + parent_handler->sel_phi->addIncoming(selector, current_block); + } + else + { + /* No parent handler - resume unwinding */ + auto const resume_fn{ llvm_module->getOrInsertFunction("_Unwind_Resume", + ctx->builder->getVoidTy(), + ctx->builder->getPtrTy()) }; + ctx->builder->CreateCall(resume_fn, { ex_ptr }); + ctx->builder->CreateUnreachable(); + } + } + + void llvm_processor::impl::register_catch_clause_rtti(expr::catch_ const &catch_clause, + llvm::LandingPadInst *landing_pad, + native_set ®istered_rtti) + { + auto const catch_type{ catch_clause.type }; + auto const exception_rtti{ Cpp::MangleRTTI(catch_type) }; + if constexpr(jtl::current_platform == jtl::platform::macos_like) + { + static native_set rtti_syms; + if(!rtti_syms.contains(exception_rtti)) + { + cpp_util::register_rtti(catch_type); + rtti_syms.emplace(exception_rtti); + } + auto const callable{ + Cpp::MakeRTTICallable(catch_type, exception_rtti, __rt_ctx->unique_munged_string()) + }; + global_rtti.emplace(exception_rtti, callable); + } + auto const exception_rtti_global{ llvm_module->getOrInsertGlobal(exception_rtti, + ctx->builder->getPtrTy()) }; + if(!registered_rtti.contains(exception_rtti_global)) + { + landing_pad->addClause(exception_rtti_global); + registered_rtti.emplace(exception_rtti_global); + } + } + + void llvm_processor::impl::generate_catch_block(size_t catch_index, + expr::try_ref const expr, + llvm::BasicBlock *catch_block, + llvm::Value *current_ex_ptr, + llvm::AllocaInst *result_slot, + llvm::BasicBlock *finally_block, + llvm::AllocaInst *unwind_flag_slot, + llvm::BasicBlock *continue_block, + expr::function_arity const &arity) + { + auto const ptr_ty{ ctx->builder->getPtrTy() }; + auto const &catch_clause{ expr->catch_bodies[catch_index] }; + auto const &[catch_sym, catch_type, catch_body]{ catch_clause }; + + ctx->builder->SetInsertPoint(catch_block); + + auto old_locals(locals); + auto const begin_catch_fn{ + llvm_module->getOrInsertFunction("__cxa_begin_catch", ptr_ty, ptr_ty) + }; + auto const caught_ptr{ ctx->builder->CreateCall(begin_catch_fn, { current_ex_ptr }) }; + + /* The analyzer promotes object types to lvalue references to avoid slicing. + * For reference types, __cxa_begin_catch returns a pointer to the exception object, + * which is exactly what we need - we don't load anything, just use the pointer. + * For value types (primitives, pointers), we need to load the actual value. */ + llvm::Value *raw_ex_val{}; + llvm::Type *ex_val_type{}; + + if(Cpp::IsReferenceType(catch_type)) + { + // For references, use the pointer directly - no load needed + raw_ex_val = caught_ptr; + ex_val_type = ptr_ty; + } + else + { + // For values, load from the pointer + auto const load_type_result{ llvm_type(*ctx, llvm_ctx, catch_type) }; + ex_val_type = load_type_result.type.data; + raw_ex_val = ctx->builder->CreateLoad(ex_val_type, caught_ptr, "ex.val"); + } + + auto const current_fn = ctx->builder->GetInsertBlock()->getParent(); + llvm::AllocaInst *ex_val_slot{}; + { + llvm::IRBuilder<>::InsertPointGuard const guard(*ctx->builder); + llvm::IRBuilder<> entry_builder_local(¤t_fn->getEntryBlock(), + current_fn->getEntryBlock().getFirstInsertionPt()); + ex_val_slot + = entry_builder_local.CreateAlloca(ex_val_type, + nullptr, + util::format("{}.slot", catch_sym->name).data()); + } + ctx->builder->CreateStore(raw_ex_val, ex_val_slot); + + locals[catch_sym] = ex_val_slot; + + auto const original_catch_pos{ catch_body->position }; + catch_body->propagate_position(expression_position::value); + util::scope_exit const restore_catch_pos{ [&]() { + catch_body->propagate_position(original_catch_pos); + } }; + auto catch_val{ gen(catch_body, arity) }; + + auto const end_catch_fn{ llvm_module->getOrInsertFunction("__cxa_end_catch", + ctx->builder->getVoidTy()) }; + ctx->builder->CreateCall(end_catch_fn, {}); + auto body_type{ cpp_util::expression_type(catch_body) }; + if(!body_type) + { + body_type = catch_type; + } + auto const loaded_val{ load_if_needed(ctx, catch_val, body_type) }; + auto object_ref_type{ Cpp::GetType("jank::runtime::object_ref") }; + if(!object_ref_type) + { + object_ref_type = Cpp::GetType("jank::runtime::oref"); + } + if(!object_ref_type) + { + object_ref_type = Cpp::GetType("long long"); + } + + auto const converted_val{ convert_object(*ctx, + llvm_ctx, + llvm_module, + conversion_policy::into_object, + body_type, + object_ref_type, + body_type, + loaded_val) }; + locals = std::move(old_locals); + + if(!ctx->builder->GetInsertBlock()->getTerminator()) + { + if(!catch_val) + { + catch_val = gen_global(jank_nil); + ctx->builder->CreateStore(catch_val, result_slot); + } + else + { + ctx->builder->CreateStore(converted_val, result_slot); + } + if(finally_block) + { + ctx->builder->CreateStore(ctx->builder->getFalse(), unwind_flag_slot); + ctx->builder->CreateBr(finally_block); + } + else + { + ctx->builder->CreateBr(continue_block); + } + } + } + + void llvm_processor::impl::generate_catch_dispatch(expr::try_ref const expr, + llvm::PHINode *exception_phi, + llvm::PHINode *selector_phi, + llvm::BasicBlock *dispatch_block, + llvm::BasicBlock *continue_block, + llvm::AllocaInst *result_slot, + llvm::BasicBlock *finally_block, + llvm::AllocaInst *unwind_flag_slot, + llvm::AllocaInst *exception_slot, + llvm::BasicBlock *catch_cleanup_block, + expr::function_arity const &arity) + { + auto const current_fn = ctx->builder->GetInsertBlock()->getParent(); + auto const has_finally = (finally_block != nullptr); + + ctx->builder->SetInsertPoint(dispatch_block); + + /* Use PHI values for dispatch */ + auto const current_ex_ptr{ exception_phi }; + auto const current_sel{ selector_phi }; + + auto const typeid_fn_type{ + llvm::FunctionType::get(ctx->builder->getInt32Ty(), { ctx->builder->getPtrTy() }, false) + }; + auto const typeid_fn{ llvm_module->getOrInsertFunction("llvm.eh.typeid.for.p0", + typeid_fn_type) }; + + std::vector catch_blocks; + for(size_t i = 0; i < expr->catch_bodies.size(); ++i) + { + auto catch_block + = llvm::BasicBlock::Create(*llvm_ctx, util::format("catch.{}", i).data(), current_fn); + catch_blocks.push_back(catch_block); + } + + auto const fallback_block = llvm::BasicBlock::Create(*llvm_ctx, "catch.fallback", current_fn); + + /* Generate type matching and conditional branches */ + for(size_t i = 0; i < expr->catch_bodies.size(); ++i) + { + auto const &catch_clause{ expr->catch_bodies[i] }; + auto const catch_type{ catch_clause.type }; + auto const exception_rtti{ Cpp::MangleRTTI(catch_type) }; + auto const exception_rtti_global{ llvm_module->getOrInsertGlobal(exception_rtti, + ctx->builder->getPtrTy()) }; + + auto const type_id = ctx->builder->CreateCall(typeid_fn, + { exception_rtti_global }, + util::format("typeid.{}", i).data()); + + auto const matches + = ctx->builder->CreateICmpEQ(current_sel, type_id, util::format("matches.{}", i).data()); + + llvm::BasicBlock *no_match_target{}; + if(i + 1 < expr->catch_bodies.size()) + { + no_match_target = llvm::BasicBlock::Create(*llvm_ctx, + util::format("catch.check.{}", i + 1).data(), + current_fn); + } + else + { + no_match_target = fallback_block; + } + + ctx->builder->CreateCondBr(matches, catch_blocks[i], no_match_target); + + if(i + 1 < expr->catch_bodies.size()) + { + ctx->builder->SetInsertPoint(no_match_target); + } + } + + /* Push the cleanup pad for catch blocks. + * Any exception thrown from within a catch block (including rethrows) will land here. */ + lpad_and_catch_body_stack.emplace_back(catch_cleanup_block, nullptr); + util::scope_exit const pop_catch_lpad{ [this]() { lpad_and_catch_body_stack.pop_back(); } }; + + /* Generate catch block bodies */ + for(size_t i = 0; i < expr->catch_bodies.size(); ++i) + { + generate_catch_block(i, + expr, + catch_blocks[i], + current_ex_ptr, + result_slot, + finally_block, + unwind_flag_slot, + continue_block, + arity); + } + + /* Generate fallback block for unmatched exceptions */ + ctx->builder->SetInsertPoint(fallback_block); + if(has_finally) + { + ctx->builder->CreateStore(current_ex_ptr, exception_slot); + ctx->builder->CreateStore(ctx->builder->getTrue(), unwind_flag_slot); + ctx->builder->CreateBr(finally_block); + } + else + { + /* No finally in current try. Check for parent handler. */ + route_unhandled_exception(current_ex_ptr, current_sel, fallback_block); + } + } + + void + llvm_processor::impl::register_parent_catch_clauses(llvm::LandingPadInst *landing_pad, + native_set ®istered_rtti) + { + for(size_t i = 0; i < ctx->exception_handlers.size() - 1; ++i) + { + for(auto const &parent_expr = ctx->exception_handlers[i].expr; + auto const &catch_clause : parent_expr->catch_bodies) + { + register_catch_clause_rtti(catch_clause, landing_pad, registered_rtti); + } + } + } + llvm::Value * llvm_processor::impl::gen(expr::try_ref const expr, expr::function_arity const &arity) { - if(expr->catch_body.is_none() && expr->finally_body.is_none()) + if(expr->catch_bodies.empty() && expr->finally_body.is_none()) { return gen(expr->body, arity); } auto const current_fn(ctx->builder->GetInsertBlock()->getParent()); - auto &entry_bb{ current_fn->getEntryBlock() }; - llvm::IRBuilder<> entry_builder(&entry_bb, entry_bb.getFirstInsertionPt()); + auto &entry_block{ current_fn->getEntryBlock() }; + llvm::IRBuilder<> entry_builder(&entry_block, entry_block.getFirstInsertionPt()); auto const ptr_ty{ ctx->builder->getPtrTy() }; if(!current_fn->hasPersonalityFn()) @@ -1698,19 +2037,7 @@ namespace jank::codegen auto const is_return(expr->position == expression_position::tail); auto const has_finally{ expr->finally_body.is_some() }; - auto const has_catch{ expr->catch_body.is_some() }; - - /* unwind_flag_slot: An alloca for a boolean (i1). This flag is set to true if the 'finally' - * block is being entered as part of an exception unwinding process (e.g., from a landing pad). - * It's false if 'finally' is entered after normal completion of the try or catch block. - * This controls whether to resume unwinding or continue normally after the "finally" block. */ - llvm::AllocaInst *unwind_flag_slot{}; - - /* exception_slot: An alloca for a pointer. When unwinding_flag_slot is true, this slot holds - * the exception object (typically an i8* or a struct pointer) that was caught by the landing - * pad. This pointer is needed if the exception needs to be resumed or rethrown after the - * 'finally' block. */ - llvm::AllocaInst *exception_slot{}; + auto const has_catch{ !expr->catch_bodies.empty() }; /* result_slot: An alloca for a pointer (object_ref). This slot holds the llvm::Value* * that represents the result of the (try ...) expression. @@ -1719,354 +2046,285 @@ namespace jank::codegen * - If an exception is caught and handled by a 'catch' block, the result of the * 'catch' block is stored here. * - * This value is then loaded in the continuation block ('cont_bb') after any - * 'finally' block has executed. Because control flow always passes through the 'finally' - * block on normal exits (if a finally exists), we can't directly use a PHI node in - * 'cont_bb' with predecessors from the end of 'try' and 'catch'. This slot acts as a - * temporary variable to hold the result before entering 'finally'. */ + * This value is then loaded in the continuation block ('continue_block') after any + * 'finally' block has executed. + * */ llvm::AllocaInst *result_slot{ entry_builder.CreateAlloca(ptr_ty, nullptr, "try.result.slot") }; - llvm::BasicBlock *finally_bb{}; - llvm::BasicBlock *unwind_action_bb{}; ctx->builder->CreateStore(gen_global(jank_nil), result_slot); + llvm::AllocaInst *dispatch_ex_slot{ + entry_builder.CreateAlloca(ptr_ty, nullptr, "dispatch.ex") + }; + llvm::AllocaInst *selector_slot{ + entry_builder.CreateAlloca(ctx->builder->getInt32Ty(), nullptr, "selector.slot") + }; + + llvm::BasicBlock *finally_block{}; + llvm::BasicBlock *unwind_action_block{}; + + /* unwind_flag_slot: An alloca for a boolean (i1). This flag is set to true if the 'finally' + * block is being entered as part of an exception unwinding process (e.g., from a landing pad). + * It's false if 'finally' is entered after normal completion of the try or catch block. + * This controls whether to resume unwinding or continue normally after the "finally" block. */ + llvm::AllocaInst *unwind_flag_slot{}; + + /* preserved_ex_slot: An alloca for a pointer. When unwinding_flag_slot is true, this slot holds + * the exception object (typically an i8* or a struct pointer) that was caught by the landing + * pad. This pointer is needed if the exception needs to be resumed or rethrown after the + * 'finally' block. */ + llvm::AllocaInst *preserved_ex_slot{}; + if(has_finally) { unwind_flag_slot = entry_builder.CreateAlloca(ctx->builder->getInt1Ty(), nullptr, "unwind.flag.slot"); - exception_slot = entry_builder.CreateAlloca(ptr_ty, nullptr, "exception.slot"); - finally_bb = llvm::BasicBlock::Create(*llvm_ctx, "finally"); - unwind_action_bb = llvm::BasicBlock::Create(*llvm_ctx, "unwind.action"); + preserved_ex_slot = entry_builder.CreateAlloca(ptr_ty, nullptr, "preserved.ex"); + finally_block = llvm::BasicBlock::Create(*llvm_ctx, "finally"); + unwind_action_block = llvm::BasicBlock::Create(*llvm_ctx, "unwind.action"); + } + auto const continue_block{ llvm::BasicBlock::Create(*llvm_ctx, "try.cont") }; + auto const landing_pad_block{ llvm::BasicBlock::Create(*llvm_ctx, "lpad", current_fn) }; + /* dispatch_block: The entry point for exception dispatching. It takes the exception pointer + * and selector as PHI nodes, allowing entry from the landing pad OR from inner try blocks. */ + auto const dispatch_block{ llvm::BasicBlock::Create(*llvm_ctx, "dispatch", current_fn) }; + + llvm::BasicBlock *catch_cleanup_block{}; + /* NOLINTNEXTLINE(misc-const-correctness): Can't be const. */ + llvm::BasicBlock *catch_body_block{}; + if(has_catch) + { + catch_body_block = llvm::BasicBlock::Create(*llvm_ctx, "catch.body"); + /* catch_cleanup_block: A cleanup landing pad for the catch blocks themselves. + * If a catch block throws (rethrow or new throw), we must call __cxa_end_catch + * to release the currently caught exception before propagating the new one. + * We also need to route to 'finally' if it exists. */ + catch_cleanup_block = llvm::BasicBlock::Create(*llvm_ctx, "catch.cleanup", current_fn); } - auto const cont_bb{ llvm::BasicBlock::Create(*llvm_ctx, "try.cont") }; - auto const lpad_bb{ llvm::BasicBlock::Create(*llvm_ctx, "lpad", current_fn) }; - llvm::BasicBlock *catch_body_bb{}; - if(has_catch) + /* Create PHI nodes in dispatch_block early so we can push them to the stack. */ + llvm::PHINode *exception_phi{}; + llvm::PHINode *selector_phi{}; { - catch_body_bb = llvm::BasicBlock::Create(*llvm_ctx, "catch.body"); + llvm::IRBuilder<>::InsertPointGuard const guard(*ctx->builder); + ctx->builder->SetInsertPoint(dispatch_block); + exception_phi = ctx->builder->CreatePHI(ptr_ty, 1, "ex.phi"); + selector_phi = ctx->builder->CreatePHI(ctx->builder->getInt32Ty(), 1, "sel.phi"); } - lpad_and_catch_body_stack.emplace_back(lpad_bb, catch_body_bb); - util::scope_exit const pop_landing_pad{ [this]() { lpad_and_catch_body_stack.pop_back(); } }; + /* Push current handler info for nested tries to use. */ + ctx->exception_handlers.push_back({ dispatch_block, exception_phi, selector_phi, expr }); + util::scope_exit const pop_handler{ [this]() { ctx->exception_handlers.pop_back(); } }; /* --- Try block --- */ - auto const original_try_pos{ expr->body->position }; - - /* We put the try body into the value position so that no return is generated, which allows - * us to continue onto the finally block, if we have one. */ - expr->body->propagate_position(expression_position::value); - auto const try_val{ gen(expr->body, arity) }; - expr->body->propagate_position(original_try_pos); - - /* Handles the normal completion of the 'try' block. - * If code generation for the 'try' body produces a value (try_val is not null) - * and the current basic block doesn't already have a terminator (e.g., from a return - * or throw within the try body itself), this block adds the necessary instructions. - * - * 1. Store Result: The result of the 'try' block (try_val) is stored into the - * 'result_slot' to be potentially used after the 'finally' block. - * 2. Branch to finally or continuation: - * - If a 'finally' block exists ('has_finally' is true), it prepares for - * entering the finally block normally. This involves setting the 'unwind_flag_slot' - * to false (signifying not unwinding from an exception) and creating an - * unconditional branch to 'finally_bb'. - * - If there's no 'finally' block, it branches directly to the continuation - * block 'cont_bb', as the try-catch-finally construct is complete. */ - if(try_val && !ctx->builder->GetInsertBlock()->getTerminator()) - { - ctx->builder->CreateStore(try_val, result_slot); - if(has_finally) - { - ctx->builder->CreateStore(ctx->builder->getFalse(), unwind_flag_slot); - ctx->builder->CreateBr(finally_bb); - } - else + { + lpad_and_catch_body_stack.emplace_back(landing_pad_block, catch_body_block); + util::scope_exit const pop_landing_pad{ [this]() { lpad_and_catch_body_stack.pop_back(); } }; + + auto const original_try_pos{ expr->body->position }; + + /* We put the try body into the value position so that no return is generated, which allows + * us to continue onto the finally block, if we have one. */ + expr->body->propagate_position(expression_position::value); + auto const try_val{ gen(expr->body, arity) }; + expr->body->propagate_position(original_try_pos); + + /* Handles the normal completion of the 'try' block. + * If code generation for the 'try' body produces a value (try_val is not null) + */ + if(!ctx->builder->GetInsertBlock()->getTerminator()) { - ctx->builder->CreateBr(cont_bb); + if(try_val) + { + ctx->builder->CreateStore(try_val, result_slot); + } + else + { + ctx->builder->CreateStore(gen_global(jank_nil), result_slot); + } + + if(has_finally) + { + ctx->builder->CreateStore(ctx->builder->getFalse(), unwind_flag_slot); + ctx->builder->CreateBr(finally_block); + } + else + { + ctx->builder->CreateBr(continue_block); + } } } - - /* --- Landing Pad & Catch/Resume Logic --- - * We are now about to generate code for the landing pad (lpad_bb), which catches - * exceptions thrown from the preceding 'try' block. - * - * IMPORTANT: Exceptions thrown from *within* the 'catch' or 'finally' clauses - * associated with THIS try-catch-finally statement should NOT be caught by this same - * landing pad (lpad_bb). Instead, they should be handled by any outer exception - * handlers or propagate up. - * - * To achieve this, we pop the current (lpad_bb, catch_body_bb) pair from the - * 'lpad_and_catch_body_stack'. This stack is used by 'CreateInvoke' to determine - * the unwind destination. By popping, any 'invoke' calls within the catch/finally - * code will use the *next* landing pad on the stack (if any), belonging to an - * enclosing 'try' statement. */ - lpad_and_catch_body_stack.pop_back(); - ctx->builder->SetInsertPoint(lpad_bb); + /* --- Landing Pad & Catch/Resume Logic --- */ + /* The scope exit above automatically popped the landing pad stack entry. */ + ctx->builder->SetInsertPoint(landing_pad_block); auto const i32_ty{ ctx->builder->getInt32Ty() }; auto const lpad_ty{ llvm::StructType::get(*llvm_ctx, { ptr_ty, i32_ty }) }; auto const landing_pad{ ctx->builder->CreateLandingPad(lpad_ty, 1) }; if(has_finally) { - /* Mark the landing pad as a "cleanup" landing pad. - * A cleanup landing pad indicates that there is cleanup code (the 'finally' block) - * that MUST be executed regardless of whether the current exception is caught by - * any of the clauses in this landing pad instruction or not. - * - * Effect: When an exception is caught by this landing_pad: - * 1. The personality function is called. - * 2. If the exception type matches any of the 'addClause' types, control might - * go to the catch block. - * 3. CRUCIALLY, because setCleanup(true) is set, even if the exception type does - * NOT match any clause, or after a catch block finishes, the control flow - * is structured to eventually execute the cleanup code associated with this - * unwind path (which we've designed to be the 'finally_bb'). - * - * In essence, 'setCleanup(true)' ensures that the unwinding process will not - * bypass the 'finally' block's execution. After the 'finally' block, the exception - * handling might continue (e.g., by resuming the unwind if the exception wasn't - * fully handled). */ landing_pad->setCleanup(true); } auto exception_ptr{ ctx->builder->CreateExtractValue(landing_pad, 0, "ex.ptr") }; + ctx->builder->CreateStore(exception_ptr, dispatch_ex_slot); + auto const selector{ ctx->builder->CreateExtractValue(landing_pad, 1, "ex.sel") }; + ctx->builder->CreateStore(selector, selector_slot); + native_set registered_rtti{}; if(has_catch) { - /* To make the landing pad catch specific types of exceptions, we need to add clauses. - * Each clause represents a type of exception this landing pad can handle. - * - * We need a reference to the type information for the exception type we want to catch. - * The Itanium C++ ABI exception handling mechanism uses type info globals. - * The exact type of the global doesn't matter as much as its address, which is used - * by the personality function to identify the exception type. - * - * When an exception is thrown, the personality function compares the thrown - * exception's type info with the clauses added to the landing pads in the call stack. - * If a match is found, control is transferred to this landing pad. */ - auto const catch_type{ expr->catch_body.unwrap().type }; - auto const exception_rtti{ Cpp::MangleRTTI(catch_type) }; - - /* macOS requires explicit registration of RTTI symbols. */ - if constexpr(jtl::current_platform == jtl::platform::macos_like) + for(auto const &catch_clause : expr->catch_bodies) { - static native_set rtti_syms; - if(!rtti_syms.contains(exception_rtti)) - { - /* We need to register this RTTI right now, for the JIT. */ - cpp_util::register_rtti(catch_type); - rtti_syms.emplace(exception_rtti); - } - - /* We also need to surface this RTTI upward, to the module level, so it - * can end up in the generated object file. */ - auto const callable{ - Cpp::MakeRTTICallable(catch_type, exception_rtti, __rt_ctx->unique_munged_string()) - }; - global_rtti.emplace(exception_rtti, callable); + register_catch_clause_rtti(catch_clause, landing_pad, registered_rtti); } + } - auto const exception_rtti_global{ llvm_module->getOrInsertGlobal(exception_rtti, - ctx->builder->getPtrTy()) }; - - landing_pad->addClause(exception_rtti_global); - - /* Setup for handling exceptions that might be thrown FROM WITHIN the catch block itself. - * We need to ensure that if an exception occurs inside the 'catch' body, - * any 'finally' block is still executed. This is achieved by having a dedicated - * landing pad ('cleanup_lpad_bb') for the 'catch' body's scope. */ - llvm::BasicBlock *cleanup_lpad_bb{}; - if(has_finally) - { - /* To make any potentially throwing function calls (which will be generated as - * llvm::InvokeInst) within the *catch body* unwind to our 'cleanup_lpad_bb', - * we must push 'cleanup_lpad_bb' onto the 'lpad_and_catch_body_stack'. - * The code generation for 'invoke' uses the top of this stack as the - * unwind destination. */ - cleanup_lpad_bb = llvm::BasicBlock::Create(*llvm_ctx, "cleanup.lpad", current_fn); - lpad_and_catch_body_stack.emplace_back(cleanup_lpad_bb, nullptr); - } - util::scope_exit const pop_cleanup_lpad{ [this, has_finally]() { - if(has_finally) - { - lpad_and_catch_body_stack.pop_back(); - } - } }; + /* === NESTED TRY HANDLING === + * + * When try blocks are nested within each other, each inner try's landing pad must be aware + * of ALL catch clauses from both the current try AND all parent try blocks. This is required + * for correct exception routing during the stack unwinding process. + * + * EXAMPLE SCENARIO: + * (try ; Outer try + * (try ; Inner try + * (throw (std.runtime_error "error")) + * (catch cpp/std.logic_error e ; Inner catch (won't match) + * (println "inner"))) + * (catch cpp/std.runtime_error e ; Outer catch (SHOULD match) + * (println "outer"))) + * + * When the exception is thrown from the inner try: + * 1. Control transfers to the inner try's landing pad + * 2. The landing pad's personality function checks registered catch types + * 3. Inner catch (std.logic_error) doesn't match std.runtime_error + * 4. The personality function finds outer catch (std.runtime_error) IS registered + * 5. Returns the selector for std.runtime_error + * 6. Inner dispatch block sees no local match, routes to parent handler + * 7. Outer try catches the exception + * + * WHY REGISTER PARENT CLAUSES: + * - The personality function (__gxx_personality_v0) needs to know if ANY handler + * in the call stack can handle this exception type + * - If no matching handler is found in the RTTI clauses, the personality function + * will continue unwinding to the next function frame + * - By registering parent clauses, we tell the personality function "don't leave + * this function yet - we have a handler further up" + * + * HANDLER STACK MECHANICS: + * - ctx->exception_handlers is a stack of try blocks in the current function + * - exception_handlers[0] = outermost try + * - exception_handlers[size-1] = current (innermost) try + * - We iterate exception_handlers[0..size-2] to register ALL parent catch clauses + * + * This registration happens even if the current try has NO catch clauses (only finally), + * because the landing pad still needs to know about parent handlers for proper routing. + */ + register_parent_catch_clauses(landing_pad, registered_rtti); + + /* Branch to dispatch block and populate PHIs */ + ctx->builder->CreateBr(dispatch_block); + exception_phi->addIncoming(exception_ptr, landing_pad_block); + selector_phi->addIncoming(selector, landing_pad_block); + + /* --- Dispatch Block --- */ + if(!expr->catch_bodies.empty()) + { + generate_catch_dispatch(expr, + exception_phi, + selector_phi, + dispatch_block, + continue_block, + result_slot, + has_finally ? finally_block : nullptr, + has_finally ? unwind_flag_slot : nullptr, + preserved_ex_slot, + catch_cleanup_block, + arity); + } + + /* --- Catch Cleanup Block --- */ + /* This block is the landing pad for exceptions thrown FROM within a catch block. */ + if(has_catch) + { + ctx->builder->SetInsertPoint(catch_cleanup_block); + auto const catch_lpad{ ctx->builder->CreateLandingPad(lpad_ty, 1) }; + catch_lpad->setCleanup(true); - ctx->builder->CreateBr(catch_body_bb); - current_fn->insert(current_fn->end(), catch_body_bb); - ctx->builder->SetInsertPoint(catch_body_bb); + /* catch_cleanup_block only contains the parent's catch clauses. */ + registered_rtti.clear(); + register_parent_catch_clauses(catch_lpad, registered_rtti); - auto const &[catch_sym, _, catch_body]{ expr->catch_body.unwrap() }; - auto old_locals(locals); - auto const begin_catch_fn{ - llvm_module->getOrInsertFunction("__cxa_begin_catch", ptr_ty, ptr_ty) + /* We must call __cxa_end_catch to release the exception we were catching. */ + auto const end_catch_fn_cleanup{ + llvm_module->getOrInsertFunction("__cxa_end_catch", ctx->builder->getVoidTy()) }; - auto const caught_ptr{ ctx->builder->CreateCall(begin_catch_fn, { exception_ptr }) }; - locals[catch_sym] = ctx->builder->CreateLoad(ptr_ty, caught_ptr, "ex.val"); - - auto const original_catch_pos{ catch_body->position }; - catch_body->propagate_position(expression_position::value); - util::scope_exit const restore_catch_pos{ [&]() { - catch_body->propagate_position(original_catch_pos); - } }; - auto catch_val{ gen(catch_body, arity) }; - - auto const end_catch_fn{ llvm_module->getOrInsertFunction("__cxa_end_catch", - ctx->builder->getVoidTy()) }; - ctx->builder->CreateCall(end_catch_fn, {}); - locals = std::move(old_locals); + ctx->builder->CreateCall(end_catch_fn_cleanup, {}); - if(!ctx->builder->GetInsertBlock()->getTerminator()) - { - if(!catch_val) - { - catch_val = gen_global(jank_nil); - } - ctx->builder->CreateStore(catch_val, result_slot); - if(has_finally) - { - ctx->builder->CreateStore(ctx->builder->getFalse(), unwind_flag_slot); - ctx->builder->CreateBr(finally_bb); - } - else - { - ctx->builder->CreateBr(cont_bb); - } - } + auto const catch_ex_ptr{ ctx->builder->CreateExtractValue(catch_lpad, 0, "catch.ex.ptr") }; + auto const catch_sel{ ctx->builder->CreateExtractValue(catch_lpad, 1, "catch.ex.sel") }; + /* Determine how to route the exception: + * 1. If we have a finally block, go there first (it will resumeunwind after) + * 2. If we have a parent handler, propagate to it + * 3. Otherwise, resume unwinding */ if(has_finally) { - /* This block populates 'cleanup_lpad_bb', which acts as the landing pad - * for any exception thrown *within* the execution of the 'catch' block body. - * Its primary purpose is to ensure the 'finally' block is executed - * even if the catch handler itself throws. */ - ctx->builder->SetInsertPoint(cleanup_lpad_bb); - - /* Create the landing pad instruction for the catch block's cleanup. - * It takes no clauses because it's not trying to "catch" and handle - * the exception in the sense of stopping propagation, but rather to - * perform the necessary cleanups. */ - auto const cleanup_lpad{ ctx->builder->CreateLandingPad(lpad_ty, 0) }; - cleanup_lpad->setCleanup(true); - - /* Extract the pointer to the new exception object that was caught. And store the pointer - * to the exception object that was caught *inside the catch block*. - * This exception object will be needed in 'unwind_action_bb' after the 'finally' - * block runs to resume the stack unwinding process with this new exception. */ - auto cleanup_ex_ptr{ ctx->builder->CreateExtractValue(cleanup_lpad, 0, "cleanup.ex.ptr") }; - ctx->builder->CreateStore(cleanup_ex_ptr, exception_slot); - - /* Set the unwind flag to TRUE. We are inside a landing pad (cleanup_lpad_bb), - * which is only ever entered as a result of an exception being thrown. - * Therefore, we are definitely in an exception unwinding state. This flag - * signals to the code in 'finally_bb' that it should branch to - * 'unwind_action_bb' after completing the 'finally' logic, rather than - * continuing to 'cont_bb' as would happen in a normal execution flow. */ + ctx->builder->CreateStore(catch_ex_ptr, preserved_ex_slot); ctx->builder->CreateStore(ctx->builder->getTrue(), unwind_flag_slot); - ctx->builder->CreateBr(finally_bb); + ctx->builder->CreateStore(catch_sel, selector_slot); + ctx->builder->CreateBr(finally_block); + } + else + { + /* No finally in current try. Check for parent handler. */ + route_unhandled_exception(catch_ex_ptr, catch_sel, catch_cleanup_block); } } else { /* No catch, must have 'finally'. */ - ctx->builder->CreateStore(exception_ptr, exception_slot); + ctx->builder->SetInsertPoint(dispatch_block); + auto const current_ex_ptr = exception_phi; + ctx->builder->CreateStore(current_ex_ptr, preserved_ex_slot); ctx->builder->CreateStore(ctx->builder->getTrue(), unwind_flag_slot); - ctx->builder->CreateBr(finally_bb); + ctx->builder->CreateBr(finally_block); } /* --- Finally block --- */ if(has_finally) { - current_fn->insert(current_fn->end(), finally_bb); - ctx->builder->SetInsertPoint(finally_bb); + current_fn->insert(current_fn->end(), finally_block); + ctx->builder->SetInsertPoint(finally_block); gen(expr->finally_body.unwrap(), arity); if(!ctx->builder->GetInsertBlock()->getTerminator()) { auto unwind_flag = ctx->builder->CreateLoad(ctx->builder->getInt1Ty(), unwind_flag_slot); - ctx->builder->CreateCondBr(unwind_flag, unwind_action_bb, cont_bb); + ctx->builder->CreateCondBr(unwind_flag, unwind_action_block, continue_block); } /* --- Unwind Action block --- - * This block is entered from 'finally_bb' ONLY when the 'finally' block + * This block is entered from 'finally_block' ONLY when the 'finally' block * was executed as part of an exception unwinding process (i.e., unwind_flag_slot was true). * The purpose of this block is to continue the exception propagation after - * the cleanup code in 'finally_bb' has run. The exception object to be - * propagated was saved in 'exception_slot'. */ - current_fn->insert(current_fn->end(), unwind_action_bb); - ctx->builder->SetInsertPoint(unwind_action_bb); - auto current_ex = ctx->builder->CreateLoad(ptr_ty, exception_slot); + * the cleanup code in 'finally_block' has run. The exception object to be + * propagated was saved in 'preserved_ex_slot'. */ + current_fn->insert(current_fn->end(), unwind_action_block); + ctx->builder->SetInsertPoint(unwind_action_block); + auto current_ex = ctx->builder->CreateLoad(ptr_ty, preserved_ex_slot); + /* Load selector for propagation */ + auto current_sel = ctx->builder->CreateLoad(ctx->builder->getInt32Ty(), selector_slot); /* Determine how to propagate the exception: - * - If 'lpad_and_catch_body_stack' is not empty, it means there's an enclosing - * 'try' block within the *same* function. We should "rethrow" the exception - * in a way that it can be caught by the landing pad of that outer 'try' block. - * - If the stack is empty, there are no more exception handlers within this - * function to transfer control to, so we must resume the standard stack unwinding - * process, allowing handlers in caller functions to catch the exception. */ - if(!lpad_and_catch_body_stack.empty()) - { - /* Propagate to an outer landing pad in the same function. - * To ensure the outer catch receives the correct user exception object, - * we first need to extract it using the C++ ABI helper functions. */ - auto exception_ptr_reloaded{ current_ex }; - auto const begin_catch_fn{ - llvm_module->getOrInsertFunction("__cxa_begin_catch", ptr_ty, ptr_ty) - }; - auto const caught_ptr{ ctx->builder->CreateCall(begin_catch_fn, - { exception_ptr_reloaded }) }; - auto const ex_val_ptr{ ctx->builder->CreateLoad(ptr_ty, caught_ptr, "ex.val") }; - auto const end_catch_fn{ llvm_module->getOrInsertFunction("__cxa_end_catch", - ctx->builder->getVoidTy()) }; - ctx->builder->CreateCall(end_catch_fn, {}); - - /* Now, rethrow the *user exception object* (ex_val_ptr) using jank_throw. - * This call is wrapped in CreateInvoke, with the outer try's landing pad - * as the unwind destination. */ - auto const unreachable_dest{ - llvm::BasicBlock::Create(*llvm_ctx, "unreachable.throw", current_fn) - }; - auto const fn_type{ - llvm::FunctionType::get(ctx->builder->getVoidTy(), { ctx->builder->getPtrTy() }, false) - }; - auto function_callee{ llvm_module->getOrInsertFunction("jank_throw", fn_type) }; - llvm::cast(function_callee.getCallee())->setDoesNotReturn(); - - ctx->builder->CreateInvoke(function_callee, - unreachable_dest, - lpad_and_catch_body_stack.back().lpad_bb, - { ex_val_ptr }); - ctx->builder->SetInsertPoint(unreachable_dest); - ctx->builder->CreateUnreachable(); - } - else - { - /* No outer 'try' handlers within this function's scope. We need to resume - * the standard stack unwinding process. This allows the exception to propagate - * up the call stack to potentially be caught by handlers in caller functions. - * The 'llvm.resume' instruction is used for this purpose. - * - * We need to reconstruct the two-element struct { i8*, i32 } that 'llvm.resume' - * expects. This struct is the same type as what a 'landing pad' instruction returns. - * The first element is the exception pointer, and the second is a selector value. */ - auto lpad_val{ - ctx->builder->CreateInsertValue(llvm::UndefValue::get(lpad_ty), current_ex, 0) - }; - lpad_val = ctx->builder->CreateInsertValue(lpad_val, ctx->builder->getInt32(0), 1); - ctx->builder->CreateResume(lpad_val); - } + * - If we have a parent handler (nested try in same function), branch to it. + * - Else, resume unwinding. */ + route_unhandled_exception(current_ex, current_sel, unwind_action_block); } - /* We pushed the landing pad for our `try` block. It has now been popped, before - * generating catch/finally. We must push it back on so that the scope_exit - * guard at the top can correctly pop it later, restoring the stack for the rest - * of this function's codegen. */ - lpad_and_catch_body_stack.emplace_back(lpad_bb, catch_body_bb); - /* --- Continuation block --- */ - current_fn->insert(current_fn->end(), cont_bb); - ctx->builder->SetInsertPoint(cont_bb); + current_fn->insert(current_fn->end(), continue_block); + ctx->builder->SetInsertPoint(continue_block); auto final_val = ctx->builder->CreateLoad(ptr_ty, result_slot); if(is_return) @@ -2220,6 +2478,45 @@ namespace jank::codegen return alloc; } + /* For static variables that are non-copyable, we can't use MakeAotCallable (which copies). + * Instead, create a wrapper function that returns a pointer to the variable. + * Check if the type is copyable by seeing if it can be constructed from itself. */ + bool const is_copyable = Cpp::IsConstructible(expr->type, expr->type); + if(expr->val_kind == expr::cpp_value::value_kind::variable && Cpp::IsVariable(expr->scope) + && !is_copyable) + { + // Get the fully qualified variable name + auto const var_name{ Cpp::GetQualifiedCompleteName(expr->scope) }; + + // Create a wrapper function that returns the address of the variable + // Important: Don't use inline/always_inline or the function won't be emitted! + auto const wrapper_name{ __rt_ctx->unique_munged_string() }; + auto const wrapper_code{ + util::format("extern \"C\" auto {}() {{ return &{}; }}", wrapper_name, var_name) + }; + + // Parse and link the wrapper + auto parse_res{ __rt_ctx->jit_prc.interpreter->Parse(wrapper_code.c_str()) }; + if(!parse_res) + { + throw std::runtime_error{ util::format("Unable to create wrapper for variable: {}", + var_name) }; + } + link_module(*ctx, parse_res->TheModule.get()); + + // Call the wrapper to get the address + auto const fn_type(llvm::FunctionType::get(ctx->builder->getPtrTy(), false)); + auto const fn(llvm_module->getFunction(wrapper_name.c_str())); + auto const addr{ ctx->builder->CreateCall(fn_type, fn) }; + + if(expr->position == expression_position::tail) + { + return ctx->builder->CreateRet(addr); + } + + return addr; + } + auto const callable{ Cpp::IsFunctionPointerType(expr->type) /* We pass the type and the scope in here so that unresolved template * scopes can be turned into the correct specialization which matches @@ -2334,7 +2631,8 @@ namespace jank::codegen && !(Cpp::IsPointerType(Cpp::GetNonReferenceType(arg_type)) || Cpp::IsArrayType(Cpp::GetNonReferenceType(arg_type))) }; auto const is_arg_ptr{ Cpp::IsPointerType(arg_type) || Cpp::IsArrayType(arg_type) - || cpp_util::is_any_object(arg_type) }; + || cpp_util::is_any_object(arg_type) + || cpp_util::is_nullptr(arg_type) }; auto const is_arg_indirect{ is_arg_ref || is_arg_ptr }; if(i == 0 && requires_this_obj) @@ -2369,7 +2667,8 @@ namespace jank::codegen } } auto const is_param_ptr{ Cpp::IsPointerType(param_type) || Cpp::IsArrayType(param_type) - || cpp_util::is_any_object(param_type) }; + || cpp_util::is_any_object(param_type) + || cpp_util::is_nullptr(param_type) }; auto const is_param_indirect{ Cpp::IsReferenceType(param_type) || is_param_ptr }; //util::println( // "gen_aot_call arg {}, arg type {} {} (indirect {}), param type {} {} (indirect {}), " @@ -2434,7 +2733,22 @@ namespace jank::codegen args_array, sret }; - ctx->builder->CreateCall(target_fn, ctor_args); + if(!lpad_and_catch_body_stack.empty()) + { + llvm::BasicBlock *normal_dest + = llvm::BasicBlock::Create(*llvm_ctx, + "invoke.cxx.normal", + ctx->builder->GetInsertBlock()->getParent()); + ctx->builder->CreateInvoke(target_fn, + normal_dest, + lpad_and_catch_body_stack.back().lpad_bb, + ctor_args); + ctx->builder->SetInsertPoint(normal_dest); + } + else + { + ctx->builder->CreateCall(target_fn, ctor_args); + } if(position == expression_position::tail) { diff --git a/compiler+runtime/src/cpp/jank/codegen/processor.cpp b/compiler+runtime/src/cpp/jank/codegen/processor.cpp index 6fba00c93..b75435944 100644 --- a/compiler+runtime/src/cpp/jank/codegen/processor.cpp +++ b/compiler+runtime/src/cpp/jank/codegen/processor.cpp @@ -6,12 +6,15 @@ #include #include #include +#include +#include #include #include #include #include #include #include +#include #include /* The strategy for codegen to C++ is quite simple. Codegen always happens on a @@ -20,8 +23,8 @@ * jank fn has a nested fn, it becomes a nested struct, since this whole * generation works recursively. * - * Analysis lifts constants and vars, so those just become members which are - * initialized in the ctor. + * During codegen, we lift constants and vars, so those just become members which + * are initialized in the ctor. * * The most interesting part is the translation of expressions into statements, * so that something like `(println (if foo bar spam))` can become sane C++. @@ -34,13 +37,13 @@ * roughly this C++: * * ```c++ - * object_ref thing_result(thing->call()); - * object_ref if_result; + * object_ref thing_tmp(thing->call()); + * object_ref if_tmp; * if(foo) - * { if_result = bar; } + * { if_tmp = bar; } * else - * { if_result = spam; } - * println->call(thing_result, if_result); + * { if_tmp = spam; } + * println->call(thing_tmp, if_tmp); * ``` * * This is optimized by knowing what position every expression in, so trivial expressions used @@ -65,6 +68,7 @@ namespace jank::codegen constexpr jtl::immutable_string_view const recur_suffix{ "__recur" }; /* TODO: Consider making this a on the typed object: the C++ name. */ + [[maybe_unused]] static jtl::immutable_string gen_constant_type(runtime::object_ref const o, bool const boxed) { #pragma clang diagnostic push @@ -183,8 +187,41 @@ namespace jank::codegen { util::format_to(buffer, "jank::runtime::make_box(static_cast({}))", - typed_o->data); + "f64>("); + + if(std::isinf(typed_o->data)) + { + util::format_to(buffer, "INFINITY"); + } + else if(std::isnan(typed_o->data)) + { + util::format_to(buffer, "NAN"); + } + else + { + util::format_to(buffer, "{}", typed_o->data); + } + + util::format_to(buffer, "))"); + } + else if constexpr(std::same_as) + { + util::format_to(buffer, + "jank::runtime::make_box(\"{}\")", + typed_o->to_string()); + } + else if constexpr(std::same_as) + { + util::format_to(buffer, + "jank::runtime::make_box(\"{}\")", + typed_o->to_string()); + } + else if constexpr(std::same_as) + { + util::format_to(buffer, + "jank::runtime::obj::ratio::create({}, {})", + typed_o->data.numerator, + typed_o->data.denominator); } else if constexpr(std::same_as) { @@ -205,8 +242,8 @@ namespace jank::codegen else if constexpr(std::same_as) { util::format_to(buffer, - R"(jank::runtime::make_box({}))", - typed_o->to_code_string()); + R"(jank::runtime::make_box("{}"))", + util::escape(typed_o->to_string())); } else if constexpr(std::same_as) { @@ -216,111 +253,202 @@ namespace jank::codegen typed_o->sym->ns, typed_o->sym->name); } - else if constexpr(std::same_as) + else if constexpr(std::same_as) { util::format_to(buffer, - "jank::runtime::make_box({})", - typed_o->to_code_string()); + R"(jank::runtime::make_box({}))", + /* We remove the # prefix here. */ + typed_o->to_code_string().substr(1)); } - else if constexpr(std::same_as) + else if constexpr(std::same_as) { util::format_to(buffer, - "jank::runtime::make_box("); - if(typed_o->meta.is_some()) + R"(jank::runtime::make_box("{}"))", + typed_o->to_string()); + } + else if constexpr(std::same_as) + { + if(typed_o->data.empty()) + { + util::format_to(buffer, "jank::runtime::obj::persistent_string::empty()"); + } + else { - /* TODO: If meta is empty, use empty() fn. We'll need a gen helper for this. */ util::format_to(buffer, - "jank::runtime::__rt_ctx->read_string(\"{}\"), ", - util::escape(runtime::to_code_string(typed_o->meta.unwrap()))); + "jank::runtime::make_box({})", + typed_o->to_code_string()); } - util::format_to(buffer, "std::in_place "); - for(auto const &form : typed_o->data) + } + else if constexpr(std::same_as) + { + if(typed_o->data.empty()) { - util::format_to(buffer, ", "); - gen_constant(form, buffer, true); + util::format_to(buffer, "jank::runtime::obj::persistent_vector::empty()"); + if(typed_o->meta.is_some()) + { + /* TODO: If meta is empty, use empty() fn. We'll need a gen helper for this. */ + util::format_to(buffer, + "->with_meta(jank::runtime::__rt_ctx->read_string(\"{}\"))", + util::escape(runtime::to_code_string(typed_o->meta.unwrap()))); + } + } + else + { + util::format_to(buffer, + "jank::runtime::make_box("); + if(typed_o->meta.is_some()) + { + /* TODO: If meta is empty, use empty() fn. We'll need a gen helper for this. */ + util::format_to(buffer, + "jank::runtime::__rt_ctx->read_string(\"{}\"), ", + util::escape(runtime::to_code_string(typed_o->meta.unwrap()))); + } + util::format_to(buffer, "std::in_place "); + for(auto const &form : typed_o->data) + { + util::format_to(buffer, ", "); + gen_constant(form, buffer, true); + } + util::format_to(buffer, ")"); } - util::format_to(buffer, ")"); } else if constexpr(std::same_as) { - util::format_to(buffer, - "jank::runtime::make_box("); - if(typed_o->meta.is_some()) + if(typed_o->data.empty()) { - util::format_to(buffer, - "jank::runtime::__rt_ctx->read_string(\"{}\"), ", - util::escape(runtime::to_code_string(typed_o->meta.unwrap()))); + util::format_to(buffer, "jank::runtime::obj::persistent_list::empty()"); + if(typed_o->meta.is_some()) + { + /* TODO: If meta is empty, use empty() fn. We'll need a gen helper for this. */ + util::format_to(buffer, + "->with_meta(jank::runtime::__rt_ctx->read_string(\"{}\"))", + util::escape(runtime::to_code_string(typed_o->meta.unwrap()))); + } } - util::format_to(buffer, "std::in_place "); - for(auto const &form : typed_o->data) + else { - util::format_to(buffer, ", "); - gen_constant(form, buffer, true); + util::format_to(buffer, + "jank::runtime::make_box("); + if(typed_o->meta.is_some()) + { + util::format_to(buffer, + "jank::runtime::__rt_ctx->read_string(\"{}\"), ", + util::escape(runtime::to_code_string(typed_o->meta.unwrap()))); + } + util::format_to(buffer, "std::in_place "); + for(auto const &form : typed_o->data) + { + util::format_to(buffer, ", "); + gen_constant(form, buffer, true); + } + util::format_to(buffer, ")"); } - util::format_to(buffer, ")"); } else if constexpr(std::same_as) { - util::format_to(buffer, - "jank::runtime::make_box("); - if(typed_o->meta.is_some()) + if(typed_o->data.empty()) { - util::format_to(buffer, - "jank::runtime::__rt_ctx->read_string(\"{}\"), ", - util::escape(runtime::to_code_string(typed_o->meta.unwrap()))); + util::format_to(buffer, "jank::runtime::obj::persistent_hash_set::empty()"); + if(typed_o->meta.is_some()) + { + /* TODO: If meta is empty, use empty() fn. We'll need a gen helper for this. */ + util::format_to(buffer, + "->with_meta(jank::runtime::__rt_ctx->read_string(\"{}\"))", + util::escape(runtime::to_code_string(typed_o->meta.unwrap()))); + } } - util::format_to(buffer, "std::in_place "); - for(auto const &form : typed_o->data) + else { - util::format_to(buffer, ", "); - gen_constant(form, buffer, true); + util::format_to(buffer, + "jank::runtime::make_box("); + if(typed_o->meta.is_some()) + { + util::format_to(buffer, + "jank::runtime::__rt_ctx->read_string(\"{}\"), ", + util::escape(runtime::to_code_string(typed_o->meta.unwrap()))); + } + util::format_to(buffer, "std::in_place "); + for(auto const &form : typed_o->data) + { + util::format_to(buffer, ", "); + gen_constant(form, buffer, true); + } + util::format_to(buffer, ")"); } - util::format_to(buffer, ")"); } else if constexpr(std::same_as) { - bool need_comma{}; - if(typed_o->meta.is_some()) + if(typed_o->data.empty()) { - util::format_to(buffer, - "jank::runtime::obj::persistent_array_map::create_unique_with_meta("); - util::format_to(buffer, - "jank::runtime::__rt_ctx->read_string(\"{}\")", - util::escape(runtime::to_code_string(typed_o->meta.unwrap()))); - need_comma = true; + util::format_to(buffer, "jank::runtime::obj::persistent_array_map::empty()"); + if(typed_o->meta.is_some()) + { + /* TODO: If meta is empty, use empty() fn. We'll need a gen helper for this. */ + util::format_to(buffer, + "->with_meta(jank::runtime::__rt_ctx->read_string(\"{}\"))", + util::escape(runtime::to_code_string(typed_o->meta.unwrap()))); + } } else { - util::format_to(buffer, "jank::runtime::obj::persistent_array_map::create_unique("); - } - for(auto const &form : typed_o->data) - { - if(need_comma) + bool need_comma{}; + if(typed_o->meta.is_some()) + { + util::format_to( + buffer, + "jank::runtime::obj::persistent_array_map::create_unique_with_meta("); + util::format_to(buffer, + "jank::runtime::__rt_ctx->read_string(\"{}\")", + util::escape(runtime::to_code_string(typed_o->meta.unwrap()))); + need_comma = true; + } + else + { + util::format_to(buffer, "jank::runtime::obj::persistent_array_map::create_unique("); + } + for(auto const &form : typed_o->data) { + if(need_comma) + { + util::format_to(buffer, ", "); + } + need_comma = true; + gen_constant(form.first, buffer, true); util::format_to(buffer, ", "); + gen_constant(form.second, buffer, true); } - need_comma = true; - gen_constant(form.first, buffer, true); - util::format_to(buffer, ", "); - gen_constant(form.second, buffer, true); + util::format_to(buffer, ")"); } - util::format_to(buffer, ")"); } else if constexpr(std::same_as) { - auto const has_meta{ typed_o->meta.is_some() }; - if(has_meta) + if(typed_o->data.empty()) { - util::format_to(buffer, "jank::runtime::reset_meta("); + util::format_to(buffer, "jank::runtime::obj::persistent_hash_map::empty()"); + if(typed_o->meta.is_some()) + { + /* TODO: If meta is empty, use empty() fn. We'll need a gen helper for this. */ + util::format_to(buffer, + "->with_meta(jank::runtime::__rt_ctx->read_string(\"{}\"))", + util::escape(runtime::to_code_string(typed_o->meta.unwrap()))); + } } - util::format_to(buffer, - "jank::runtime::__rt_ctx->read_string(\"{}\")", - util::escape(typed_o->to_code_string())); - if(has_meta) + else { + auto const has_meta{ typed_o->meta.is_some() }; + if(has_meta) + { + util::format_to(buffer, "jank::runtime::with_meta("); + } util::format_to(buffer, - ", jank::runtime::__rt_ctx->read_string(\"{}\"))", - util::escape(runtime::to_code_string(typed_o->meta.unwrap()))); + "jank::runtime::__rt_ctx->read_string(\"{}\")", + util::escape(typed_o->to_code_string())); + if(has_meta) + { + util::format_to(buffer, + ", jank::runtime::__rt_ctx->read_string(\"{}\"))", + util::escape(runtime::to_code_string(typed_o->meta.unwrap()))); + } } } /* Cons, etc. */ @@ -329,7 +457,7 @@ namespace jank::codegen util::format_to( buffer, "jank::runtime::make_box(std::in_place"); - for(auto it : runtime::make_sequence_range(typed_o)) + for(auto const it : runtime::make_sequence_range(typed_o)) { util::format_to(buffer, ", "); gen_constant(it, buffer, true); @@ -347,7 +475,7 @@ namespace jank::codegen static jtl::immutable_string boxed_local_name(jtl::immutable_string const &local_name) { - return local_name + "__boxed"; + return local_name; // + "__boxed"; } } @@ -399,7 +527,7 @@ namespace jank::codegen } } - jtl::immutable_string handle::str(bool const needs_box) const + jtl::immutable_string handle::str([[maybe_unused]] bool const needs_box) const { if(needs_box) { @@ -426,27 +554,71 @@ namespace jank::codegen assert(root_fn->frame.data); } - jtl::option processor::gen(analyze::expression_ref const ex, - analyze::expr::function_arity const &fn_arity, - bool const box_needed) + jtl::option + processor::gen(analyze::expression_ref const ex, analyze::expr::function_arity const &fn_arity) { jtl::option ret; - visit_expr([&, this](auto const typed_ex) { ret = gen(typed_ex, fn_arity, box_needed); }, ex); + visit_expr([&, this](auto const typed_ex) { ret = gen(typed_ex, fn_arity); }, ex); return ret; } - jtl::option processor::gen(analyze::expr::def_ref const expr, - analyze::expr::function_arity const &fn_arity, - bool const) + static jtl::immutable_string + lift_var(native_unordered_map &lifted_vars, + jtl::immutable_string const &qualified_name, + bool const owned) + { + auto const existing{ lifted_vars.find(qualified_name) }; + if(existing != lifted_vars.end()) + { + return existing->second.native_name; + } + + static jtl::immutable_string const dot{ "\\." }; + auto const us{ __rt_ctx->unique_string(qualified_name) }; + auto const native_name{ runtime::munge_and_replace(us, dot, "_") }; + //util::println("lifting var '{}' with '{}' as '{}'", qualified_name, us, native_name); + lifted_vars.emplace(qualified_name, processor::lifted_var{ native_name, owned }); + return native_name; + } + + static jtl::immutable_string + lift_constant(native_unordered_map, + runtime::very_equal_to> &lifted_constants, + object_ref const &o) + { + auto const existing{ lifted_constants.find(o) }; + if(existing != lifted_constants.end()) + { + return existing->second; + } + + auto const &native_name{ runtime::munge(__rt_ctx->unique_namespaced_string("const")) }; + //util::println("lifting constant {} as {}", runtime::to_code_string(o), native_name); + lifted_constants.emplace(o, native_name); + return native_name; + } + + jtl::option + processor::gen(analyze::expr::def_ref const expr, analyze::expr::function_arity const &fn_arity) { - auto const &var(expr->frame->find_lifted_var(expr->name).unwrap().get()); - auto const &munged_name(runtime::munge(var.native_name)); - auto ret_tmp(runtime::munge(__rt_ctx->unique_namespaced_string(munged_name))); + /* def uses a var, but we don't lift it. Even if it's lifted by another usage, + * it'll be re-interned here as an owned var. This needs to happen at the point + * of the def, rather than prior (i.e. due to lifting), since there could be + * some other var-related effects such as refer which need to happen before + * def. */ + auto var_tmp(runtime::munge(__rt_ctx->unique_namespaced_string("var"))); + util::format_to( + body_buffer, + R"(auto const {}(jank::runtime::__rt_ctx->intern_owned_var("{}").expect_ok());)", + var_tmp, + expr->name->to_string()); - jtl::option> meta; + jtl::option meta; if(expr->name->meta.is_some()) { - meta = expr->frame->find_lifted_constant(expr->name->meta.unwrap()).unwrap(); + meta = lift_constant(lifted_constants, expr->name->meta.unwrap()); } /* Forward declarations just intern the var and evaluate to it. */ @@ -455,20 +627,16 @@ namespace jank::codegen if(meta.is_some()) { auto const dynamic{ truthy( - get(meta.unwrap().get().data, __rt_ctx->intern_keyword("dynamic").expect_ok())) }; - return util::format("{}->with_meta({})->set_dynamic({})", - runtime::munge(var.native_name), - runtime::munge(meta.unwrap().get().native_name), - dynamic); + get(expr->name->meta.unwrap(), __rt_ctx->intern_keyword("dynamic").expect_ok())) }; + return util::format("{}->with_meta({})->set_dynamic({})", var_tmp, meta.unwrap(), dynamic); } else { - return util::format("{}->with_meta(jank::runtime::jank_nil)", - runtime::munge(var.native_name)); + return util::format("{}->with_meta(jank::runtime::jank_nil)", var_tmp); } } - auto const val(gen(expr->value.unwrap(), fn_arity, true).unwrap()); + auto const val(gen(expr->value.unwrap(), fn_arity).unwrap()); switch(expr->position) { case analyze::expression_position::value: @@ -476,17 +644,17 @@ namespace jank::codegen if(meta.is_some()) { auto const dynamic{ truthy( - get(meta.unwrap().get().data, __rt_ctx->intern_keyword("dynamic").expect_ok())) }; + get(expr->name->meta.unwrap(), __rt_ctx->intern_keyword("dynamic").expect_ok())) }; return util::format("{}->bind_root({})->with_meta({})->set_dynamic({})", - runtime::munge(var.native_name), + var_tmp, val.str(true), - runtime::munge(meta.unwrap().get().native_name), + meta.unwrap(), dynamic); } else { return util::format("{}->bind_root({})->with_meta(jank::runtime::jank_nil)", - runtime::munge(var.native_name), + var_tmp, val.str(true)); } } @@ -500,19 +668,19 @@ namespace jank::codegen if(meta.is_some()) { auto const dynamic{ truthy( - get(meta.unwrap().get().data, __rt_ctx->intern_keyword("dynamic").expect_ok())) }; + get(expr->name->meta.unwrap(), __rt_ctx->intern_keyword("dynamic").expect_ok())) }; util::format_to(body_buffer, "{}->bind_root({})->with_meta({})->set_dynamic({});", - runtime::munge(var.native_name), + var_tmp, val.str(true), - runtime::munge(meta.unwrap().get().native_name), + meta.unwrap(), dynamic); } else { util::format_to(body_buffer, "{}->bind_root({})->with_meta(jank::runtime::jank_nil);", - runtime::munge(var.native_name), + var_tmp, val.str(true)); } return none; @@ -520,114 +688,48 @@ namespace jank::codegen } } - jtl::option processor::gen(analyze::expr::var_deref_ref const expr, - analyze::expr::function_arity const &, - bool const) + jtl::option + processor::gen(analyze::expr::var_deref_ref const expr, analyze::expr::function_arity const &) { - auto const &var(expr->frame->find_lifted_var(expr->qualified_name).unwrap().get()); + auto const &var(lift_var(lifted_vars, expr->var->to_qualified_symbol()->to_string(), false)); switch(expr->position) { case analyze::expression_position::statement: case analyze::expression_position::value: { - return util::format("{}->deref()", runtime::munge(var.native_name)); + return util::format("{}->deref()", var); } case analyze::expression_position::tail: { - util::format_to(body_buffer, "return {}->deref();", runtime::munge(var.native_name)); + util::format_to(body_buffer, "return {}->deref();", var); return none; } } } - jtl::option processor::gen(analyze::expr::var_ref_ref const expr, - analyze::expr::function_arity const &, - bool const) + jtl::option + processor::gen(analyze::expr::var_ref_ref const expr, analyze::expr::function_arity const &) { - auto const &var(expr->frame->find_lifted_var(expr->qualified_name).unwrap().get()); + auto const &var(lift_var(lifted_vars, expr->qualified_name->to_string(), false)); switch(expr->position) { case analyze::expression_position::statement: case analyze::expression_position::value: { - return runtime::munge(var.native_name); + return var; } case analyze::expression_position::tail: { - util::format_to(body_buffer, "return {};", runtime::munge(var.native_name)); + util::format_to(body_buffer, "return {};", var); return none; } } } - void processor::format_elided_var(jtl::immutable_string const &start, - jtl::immutable_string const &end, - jtl::immutable_string const &ret_tmp, - native_vector const &arg_exprs, - analyze::expr::function_arity const &fn_arity, - bool const arg_box_needed, - bool const ret_box_needed) - { - /* TODO: Assert arg count when we know it. */ - native_vector arg_tmps; - arg_tmps.reserve(arg_exprs.size()); - for(auto const &arg_expr : arg_exprs) - { - arg_tmps.emplace_back(gen(arg_expr, fn_arity, arg_box_needed).unwrap()); - } - - jtl::immutable_string ret_box; - if(ret_box_needed) - { - ret_box = "jank::runtime::make_box("; - } - util::format_to(body_buffer, "auto const {}({}{}", ret_tmp, ret_box, start); - bool need_comma{}; - for(size_t i{}; i < runtime::max_params && i < arg_tmps.size(); ++i) - { - if(need_comma) - { - util::format_to(body_buffer, ", "); - } - util::format_to(body_buffer, "{}", arg_tmps[i].str(arg_box_needed)); - need_comma = true; - } - util::format_to(body_buffer, "{}{});", end, (ret_box_needed ? ")" : "")); - } - - void processor::format_direct_call(jtl::immutable_string const &source_tmp, - jtl::immutable_string const &ret_tmp, - native_vector const &arg_exprs, - analyze::expr::function_arity const &fn_arity, - bool const arg_box_needed) - { - native_vector arg_tmps; - arg_tmps.reserve(arg_exprs.size()); - for(auto const &arg_expr : arg_exprs) - { - arg_tmps.emplace_back(gen(arg_expr, fn_arity, arg_box_needed).unwrap()); - } - - util::format_to(body_buffer, "auto const {}({}.call(", ret_tmp, source_tmp); - - bool need_comma{}; - for(size_t i{}; i < runtime::max_params && i < arg_tmps.size(); ++i) - { - if(need_comma) - { - util::format_to(body_buffer, ", "); - } - util::format_to(body_buffer, "{}", arg_tmps[i].str(true)); - need_comma = true; - } - util::format_to(body_buffer, "));"); - } - void processor::format_dynamic_call(jtl::immutable_string const &source_tmp, jtl::immutable_string const &ret_tmp, native_vector const &arg_exprs, - analyze::expr::function_arity const &fn_arity, - bool const arg_box_needed) + analyze::expr::function_arity const &fn_arity) { //util::println("format_dynamic_call source {}", source_tmp); native_vector arg_tmps; @@ -636,400 +738,33 @@ namespace jank::codegen { //util::println("\tformat_dynamic_call arg {}", // runtime::to_code_string(arg_expr->to_runtime_data())); - arg_tmps.emplace_back(gen(arg_expr, fn_arity, arg_box_needed).unwrap()); + arg_tmps.emplace_back(gen(arg_expr, fn_arity).unwrap()); } util::format_to(body_buffer, "auto const {}(jank::runtime::dynamic_call({}", ret_tmp, source_tmp); - for(size_t i{}; i < runtime::max_params && i < arg_tmps.size(); ++i) + for(size_t i{}; i < arg_tmps.size(); ++i) { util::format_to(body_buffer, ", {}", arg_tmps[i].str(true)); } - if(runtime::max_params < arg_tmps.size()) - { - util::format_to( - body_buffer, - ", jank::runtime::make_box(std::in_place"); - for(size_t i{ runtime::max_params }; i < arg_tmps.size(); ++i) - { - util::format_to(body_buffer, ", {}", arg_tmps[i].str(true)); - } - util::format_to(body_buffer, ")"); - } util::format_to(body_buffer, "));"); } - jtl::option processor::gen(analyze::expr::call_ref const expr, - analyze::expr::function_arity const &fn_arity, - bool const box_needed) + jtl::option + processor::gen(analyze::expr::call_ref const expr, analyze::expr::function_arity const &fn_arity) { - /* TODO: Doesn't take into account boxing. */ handle ret_tmp{ runtime::munge(__rt_ctx->unique_namespaced_string("call")) }; - /* Clojure's codegen actually skips vars for certain calls to clojure.core - * fns; this is not the same as direct linking, which uses `invokeStatic` - * instead. Rather, this makes calls to `get` become `RT.get`, calls to `+` become - * `Numbers.add`, and so on. We do the same thing here. */ - bool elided{}; - /* TODO: Use the actual var meta to do this, not a hard-coded set of if checks. */ - if(auto const * const ref = dynamic_cast(expr->source_expr.data)) - { - auto const &name{ ref->var->name->name }; - if(ref->var->n->name->name != "clojure.core") - { - } - else if(name == "get") - { - format_elided_var("jank::runtime::get(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - true, - false); - elided = true; - } - else if(expr->arg_exprs.empty()) - { - if(name == "rand") - { - format_elided_var("jank::runtime::rand(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - false, - box_needed); - elided = true; - ret_tmp = { ret_tmp.unboxed_name, box_needed }; - } - } - else if(expr->arg_exprs.size() == 1) - { - //if(name == "print") - //{ - // format_elided_var("jank::runtime::print(", - // ")", - // ret_tmp.str(false), - // expr->arg_exprs, - // fn_arity, - // true, - // false); - // elided = true; - //} - if(name == "abs") - { - format_elided_var("jank::runtime::abs(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - false, - box_needed); - elided = true; - ret_tmp = { ret_tmp.unboxed_name, box_needed }; - } - else if(name == "sqrt") - { - format_elided_var("jank::runtime::sqrt(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - false, - box_needed); - elided = true; - ret_tmp = { ret_tmp.unboxed_name, box_needed }; - } - else if(name == "int") - { - format_elided_var("jank::runtime::to_int(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - false, - box_needed); - elided = true; - ret_tmp = { ret_tmp.unboxed_name, box_needed }; - } - else if(name == "seq") - { - format_elided_var("jank::runtime::seq(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - true, - false); - elided = true; - } - else if(name == "fresh-seq") - { - format_elided_var("jank::runtime::fresh_seq(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - true, - false); - elided = true; - } - else if(name == "first") - { - format_elided_var("jank::runtime::first(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - true, - false); - elided = true; - } - else if(name == "next") - { - format_elided_var("jank::runtime::next(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - true, - false); - elided = true; - } - else if(name == "next-in-place") - { - format_elided_var("jank::runtime::next_in_place(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - true, - false); - elided = true; - } - else if(name == "nil?") - { - format_elided_var("jank::runtime::is_nil(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - true, - box_needed); - elided = true; - } - else if(name == "some?") - { - format_elided_var("jank::runtime::is_some(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - true, - box_needed); - elided = true; - } - } - else if(expr->arg_exprs.size() == 2) - { - if(name == "+") - { - format_elided_var("jank::runtime::add(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - false, - box_needed); - elided = true; - ret_tmp = { ret_tmp.unboxed_name, box_needed }; - } - else if(name == "-") - { - format_elided_var("jank::runtime::sub(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - false, - box_needed); - elided = true; - ret_tmp = { ret_tmp.unboxed_name, box_needed }; - } - else if(name == "*") - { - format_elided_var("jank::runtime::mul(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - false, - box_needed); - elided = true; - ret_tmp = { ret_tmp.unboxed_name, box_needed }; - } - else if(name == "/") - { - format_elided_var("jank::runtime::div(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - false, - box_needed); - elided = true; - ret_tmp = { ret_tmp.unboxed_name, box_needed }; - } - else if(name == "<") - { - format_elided_var("jank::runtime::lt(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - false, - box_needed); - elided = true; - ret_tmp = { ret_tmp.unboxed_name, box_needed }; - } - else if(name == "<=") - { - format_elided_var("jank::runtime::lte(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - false, - box_needed); - elided = true; - ret_tmp = { ret_tmp.unboxed_name, box_needed }; - } - else if(name == ">") - { - format_elided_var("jank::runtime::lt(", - ")", - ret_tmp.str(false), - { expr->arg_exprs.rbegin(), expr->arg_exprs.rend() }, - fn_arity, - false, - box_needed); - elided = true; - ret_tmp = { ret_tmp.unboxed_name, box_needed }; - } - else if(name == ">=") - { - format_elided_var("jank::runtime::lte(", - ")", - ret_tmp.str(false), - { expr->arg_exprs.rbegin(), expr->arg_exprs.rend() }, - fn_arity, - false, - box_needed); - elided = true; - ret_tmp = { ret_tmp.unboxed_name, box_needed }; - } - else if(name == "min") - { - format_elided_var("jank::runtime::min(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - false, - box_needed); - elided = true; - ret_tmp = { ret_tmp.unboxed_name, box_needed }; - } - else if(name == "max") - { - format_elided_var("jank::runtime::max(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - false, - box_needed); - elided = true; - ret_tmp = { ret_tmp.unboxed_name, box_needed }; - } - else if(name == "pow") - { - format_elided_var("jank::runtime::pow(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - false, - box_needed); - elided = true; - ret_tmp = { ret_tmp.unboxed_name, box_needed }; - } - else if(name == "conj") - { - format_elided_var("jank::runtime::conj(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - true, - false); - elided = true; - } - } - else if(expr->arg_exprs.size() == 3) - { - if(name == "assoc") - { - format_elided_var("jank::runtime::assoc(", - ")", - ret_tmp.str(false), - expr->arg_exprs, - fn_arity, - true, - false); - elided = true; - } - } - } - else if(auto const * const fn = dynamic_cast(expr->source_expr.data)) - { - bool variadic{}; - for(auto const &arity : fn->arities) - { - if(arity.fn_ctx->is_variadic) - { - variadic = true; - } - } - if(!variadic) - { - auto const &source_tmp(gen(expr->source_expr, fn_arity, false)); - format_direct_call(source_tmp.unwrap().str(false), - ret_tmp.str(true), - expr->arg_exprs, - fn_arity, - true); - elided = true; - } - } - - if(!elided) - { - auto const &source_tmp(gen(expr->source_expr, fn_arity, false)); - format_dynamic_call(source_tmp.unwrap().str(true), - ret_tmp.str(true), - expr->arg_exprs, - fn_arity, - true); - } + auto const &source_tmp(gen(expr->source_expr, fn_arity)); + format_dynamic_call(source_tmp.unwrap().str(true), + ret_tmp.str(true), + expr->arg_exprs, + fn_arity); if(expr->position == analyze::expression_position::tail) { - /* TODO: Box here, not in the calls above. Using false when we mean true is not good. */ - /* No need for extra boxing on this, since the boxing was done on the call above. */ util::format_to(body_buffer, "return {};", ret_tmp.str(false)); return none; } @@ -1038,16 +773,21 @@ namespace jank::codegen } jtl::option processor::gen(analyze::expr::primitive_literal_ref const expr, - analyze::expr::function_arity const &, - bool const) + analyze::expr::function_arity const &) { - auto const &constant(expr->frame->find_lifted_constant(expr->data).unwrap().get()); - - handle ret{ runtime::munge(constant.native_name) }; - if(constant.unboxed_native_name.is_some()) + handle ret; + if(expr->data->type == runtime::object_type::nil) + { + ret = handle{ "jank::runtime::jank_nil" }; + } + else if(expr->data->type == runtime::object_type::boolean) + { + ret = handle{ runtime::truthy(expr->data) ? "jank::runtime::jank_true" + : "jank::runtime::jank_false" }; + } + else { - ret = { runtime::munge(constant.native_name), - runtime::munge(constant.unboxed_native_name.unwrap()) }; + ret = lift_constant(lifted_constants, expr->data); } switch(expr->position) @@ -1065,20 +805,19 @@ namespace jank::codegen } } - jtl::option processor::gen(analyze::expr::vector_ref const expr, - analyze::expr::function_arity const &fn_arity, - bool const) + jtl::option + processor::gen(analyze::expr::list_ref const expr, analyze::expr::function_arity const &fn_arity) { native_vector data_tmps; data_tmps.reserve(expr->data_exprs.size()); for(auto const &data_expr : expr->data_exprs) { - data_tmps.emplace_back(gen(data_expr, fn_arity, true).unwrap()); + data_tmps.emplace_back(gen(data_expr, fn_arity).unwrap()); } - auto ret_tmp(runtime::munge(__rt_ctx->unique_namespaced_string("vec"))); + auto ret_tmp(runtime::munge(__rt_ctx->unique_namespaced_string("list"))); util::format_to(body_buffer, - "auto const {}(jank::runtime::make_box(", + "auto const {}(jank::runtime::make_box(", ret_tmp); if(expr->meta.is_some()) { @@ -1102,26 +841,61 @@ namespace jank::codegen return ret_tmp; } - jtl::option processor::gen(analyze::expr::map_ref const expr, - analyze::expr::function_arity const &fn_arity, - bool const) + jtl::option processor::gen(analyze::expr::vector_ref const expr, + analyze::expr::function_arity const &fn_arity) { - native_vector> data_tmps; + native_vector data_tmps; data_tmps.reserve(expr->data_exprs.size()); for(auto const &data_expr : expr->data_exprs) { - data_tmps.emplace_back(gen(data_expr.first, fn_arity, true).unwrap(), - gen(data_expr.second, fn_arity, true).unwrap()); + data_tmps.emplace_back(gen(data_expr, fn_arity).unwrap()); } - auto ret_tmp(runtime::munge(__rt_ctx->unique_namespaced_string("map"))); - - /* Jump right to a hash map, if we have enough values. */ - if(expr->data_exprs.size() <= runtime::obj::persistent_array_map::max_size) - { - util::format_to(body_buffer, "auto const {}(", ret_tmp); - bool need_comma{}; - if(expr->meta.is_some()) + auto ret_tmp(runtime::munge(__rt_ctx->unique_namespaced_string("vec"))); + util::format_to(body_buffer, + "auto const {}(jank::runtime::make_box(", + ret_tmp); + if(expr->meta.is_some()) + { + detail::gen_constant(expr->meta.unwrap(), body_buffer, true); + util::format_to(body_buffer, ", "); + } + util::format_to(body_buffer, "std::in_place "); + for(auto const &tmp : data_tmps) + { + util::format_to(body_buffer, ", "); + util::format_to(body_buffer, "{}", tmp.str(true)); + } + util::format_to(body_buffer, "));"); + + if(expr->position == analyze::expression_position::tail) + { + util::format_to(body_buffer, "return {};", ret_tmp); + return none; + } + + return ret_tmp; + } + + jtl::option + processor::gen(analyze::expr::map_ref const expr, analyze::expr::function_arity const &fn_arity) + { + native_vector> data_tmps; + data_tmps.reserve(expr->data_exprs.size()); + for(auto const &data_expr : expr->data_exprs) + { + data_tmps.emplace_back(gen(data_expr.first, fn_arity).unwrap(), + gen(data_expr.second, fn_arity).unwrap()); + } + + auto ret_tmp(runtime::munge(__rt_ctx->unique_namespaced_string("map"))); + + /* Jump right to a hash map, if we have enough values. */ + if(expr->data_exprs.size() <= runtime::obj::persistent_array_map::max_size) + { + util::format_to(body_buffer, "auto const {}(", ret_tmp); + bool need_comma{}; + if(expr->meta.is_some()) { util::format_to(body_buffer, "jank::runtime::obj::persistent_array_map::create_unique_with_meta("); @@ -1182,15 +956,14 @@ namespace jank::codegen return ret_tmp; } - jtl::option processor::gen(analyze::expr::set_ref const expr, - analyze::expr::function_arity const &fn_arity, - bool const) + jtl::option + processor::gen(analyze::expr::set_ref const expr, analyze::expr::function_arity const &fn_arity) { native_vector data_tmps; data_tmps.reserve(expr->data_exprs.size()); for(auto const &data_expr : expr->data_exprs) { - data_tmps.emplace_back(gen(data_expr, fn_arity, true).unwrap()); + data_tmps.emplace_back(gen(data_expr, fn_arity).unwrap()); } auto ret_tmp(runtime::munge(__rt_ctx->unique_namespaced_string("set"))); @@ -1221,18 +994,37 @@ namespace jank::codegen } jtl::option processor::gen(analyze::expr::local_reference_ref const expr, - analyze::expr::function_arity const &, - bool const) + analyze::expr::function_arity const &) { auto const munged_name(runtime::munge(expr->binding->native_name)); + // For captures, we need to check the originating binding's type, not the capture's type + // This matches the logic in build_header() + jtl::ptr actual_type{ expr->binding->type }; + auto const originating(root_fn->frame->find_local_or_capture(expr->binding->name)); + if(originating.is_some()) + { + actual_type = originating.unwrap().binding->type; + } + + // Check if this binding has a C++ primitive type (not object_ref) + bool const is_unboxed_type{ actual_type && !cpp_util::is_any_object(actual_type) }; + handle ret; - if(expr->binding->needs_box) + if(expr->binding->needs_box && !is_unboxed_type) { + // Normal boxed variable (object_ref) ret = munged_name; } + else if(is_unboxed_type) + { + // Unboxed C++ primitive - no boxed variable exists, + // handle::str will generate make_box() on demand + ret = handle{ "", munged_name }; + } else { + // Unboxed jank variable with both boxed and unboxed versions ret = handle{ detail::boxed_local_name(munged_name), munged_name }; } @@ -1251,22 +1043,16 @@ namespace jank::codegen } } - jtl::option processor::gen(analyze::expr::function_ref const expr, - analyze::expr::function_arity const &, - bool const box_needed) + jtl::option + processor::gen(analyze::expr::function_ref const expr, analyze::expr::function_arity const &) { auto const compiling(truthy(__rt_ctx->compile_files_var->deref())); /* Since each codegen proc handles one callable struct, we create a new one for this fn. */ processor prc{ expr, module, - //runtime::module::nest_module(module, runtime::munge(expr->unique_name)), compiling ? compilation_target::function : compilation_target::eval }; - /* If we're compiling, we'll create a separate file for this. */ - //if(target != compilation_target::module) - { - util::format_to(deps_buffer, "{}", prc.declaration_str()); - } + util::format_to(deps_buffer, "{}", prc.declaration_str()); switch(expr->position) { @@ -1274,41 +1060,65 @@ namespace jank::codegen case analyze::expression_position::value: /* TODO: Return a handle. */ { - return prc.expression_str(box_needed); + return prc.expression_str(); } case analyze::expression_position::tail: { - util::format_to(body_buffer, "return {};", prc.expression_str(box_needed)); + util::format_to(body_buffer, "return {};", prc.expression_str()); return none; } } } - jtl::option processor::gen(analyze::expr::recur_ref const expr, - analyze::expr::function_arity const &fn_arity, - bool const) + jtl::option + processor::gen(analyze::expr::recur_ref const expr, analyze::expr::function_arity const &fn_arity) { native_vector arg_tmps; arg_tmps.reserve(expr->arg_exprs.size()); for(auto const &arg_expr : expr->arg_exprs) { - arg_tmps.emplace_back(gen(arg_expr, fn_arity, true).unwrap()); + arg_tmps.emplace_back(gen(arg_expr, fn_arity).unwrap()); } auto arg_tmp_it(arg_tmps.begin()); - for(auto const ¶m : fn_arity.params) + if(expr->loop_target.is_some()) { - util::format_to(body_buffer, "{} = {};", runtime::munge(param->name), arg_tmp_it->str(true)); - ++arg_tmp_it; + auto const let{ expr->loop_target.unwrap() }; + for(usize i{}; i < expr->arg_exprs.size(); ++i) + { + auto const &pair{ let->pairs[i] }; + auto const local(expr->frame->find_local_or_capture(pair.first)); + auto const &local_name(runtime::munge(local.unwrap().binding->native_name)); + auto const &val_name(arg_tmp_it->str(true)); + + if(local_name != val_name) + { + util::format_to(body_buffer, "{} = {};", local_name, val_name); + } + ++arg_tmp_it; + } + + util::format_to(body_buffer, "continue;"); } - util::format_to(body_buffer, "continue;"); + else + { + for(auto const ¶m : fn_arity.params) + { + util::format_to(body_buffer, + "{} = {};", + runtime::munge(param->name), + arg_tmp_it->str(true)); + ++arg_tmp_it; + } + util::format_to(body_buffer, "continue;"); + } + return none; } /* NOLINTNEXTLINE(readability-make-member-function-const): Can't be const, due to overload resolution. */ jtl::option processor::gen(analyze::expr::recursion_reference_ref const expr, - analyze::expr::function_arity const &, - bool const) + analyze::expr::function_arity const &) { if(expr->position == analyze::expression_position::tail) { @@ -1319,18 +1129,16 @@ namespace jank::codegen } jtl::option processor::gen(analyze::expr::named_recursion_ref const expr, - analyze::expr::function_arity const &fn_arity, - bool const) + analyze::expr::function_arity const &fn_arity) { handle ret_tmp{ runtime::munge(__rt_ctx->unique_namespaced_string("named_recursion")) }; auto const &source_tmp( - gen(jtl::ref{ &expr->recursion_ref }, fn_arity, false)); + gen(jtl::ref{ &expr->recursion_ref }, fn_arity)); format_dynamic_call(source_tmp.unwrap().str(true), ret_tmp.str(true), expr->arg_exprs, - fn_arity, - true); + fn_arity); if(expr->position == analyze::expression_position::tail) { @@ -1341,98 +1149,96 @@ namespace jank::codegen return ret_tmp; } - jtl::option processor::gen(analyze::expr::let_ref const expr, - analyze::expr::function_arity const &fn_arity, - bool const) + jtl::option + processor::gen(analyze::expr::let_ref const expr, analyze::expr::function_arity const &fn_arity) { - handle const ret_tmp{ runtime::munge(__rt_ctx->unique_namespaced_string("let")), - expr->needs_box }; + auto const &ret_tmp{ runtime::munge(__rt_ctx->unique_namespaced_string("let")) }; bool used_option{}; - if(expr->needs_box) - { - /* TODO: The type may not be default constructible so this may fail. We likely - * want an array the same size as the desired type. When we have the last expression, - * we can then do a placement new with the move ctor. - * - * Also add a test for this. */ - auto const last_expr_type{ cpp_util::expression_type( - expr->body->values[expr->body->values.size() - 1]) }; + auto const last_expr_type{ cpp_util::expression_type( + expr->body->values[expr->body->values.size() - 1]) }; - jtl::immutable_string type_name; - /* In analysis, we treat untyped objects as object*, since that's easier for IR. - * However, for C++, we want to normalize that to object_ref to take full advantage - * of richer types. */ - if(cpp_util::is_untyped_object(last_expr_type)) - { - type_name = "object_ref"; - util::format_to(body_buffer, "{} {}{ }; {", type_name, ret_tmp.str(expr->needs_box)); - } - else - { - used_option = true; - type_name = Cpp::GetTypeAsString(Cpp::GetNonReferenceType(last_expr_type)); - /* TODO: Test for this with something non-default constructible. */ - util::format_to(body_buffer, - "jtl::option<{}> {}{ }; {", - type_name, - ret_tmp.str(expr->needs_box)); - } + auto const &type_name{ cpp_util::get_qualified_type_name( + Cpp::GetNonReferenceType(last_expr_type)) }; + if(cpp_util::is_any_object(last_expr_type)) + { + util::format_to(body_buffer, "{} {}{ }; {", type_name, ret_tmp); } else { - util::format_to(body_buffer, - "auto const {}([&](){}{", - ret_tmp.str(expr->needs_box), - (expr->needs_box ? "-> object_ref" : "")); + used_option = true; + util::format_to(body_buffer, "jtl::option<{}> {}{ }; {", type_name, ret_tmp); } for(auto const &pair : expr->pairs) { auto const local(expr->frame->find_local_or_capture(pair.first)); - if(local.is_none()) - { - throw std::runtime_error{ util::format("ICE: unable to find local: {}", - pair.first->to_string()) }; - } - - auto const &val_tmp(gen(pair.second, fn_arity, pair.second->needs_box)); + auto const local_type{ cpp_util::expression_type(pair.second) }; + auto const &val_tmp(gen(pair.second, fn_arity)); auto const &munged_name(runtime::munge(local.unwrap().binding->native_name)); + /* Every binding is wrapped in its own scope, to allow shadowing. * * Also, bindings are references to their value expression, rather than a copy. * This is important for C++ interop, since the we don't want to, and we may not - * be able to, just copy stack-allocated C++ objects around willy nillly. */ - util::format_to(body_buffer, "{ auto &&{}({}); ", munged_name, val_tmp.unwrap().str(false)); - - auto const binding(local.unwrap().binding); - if(!binding->needs_box && binding->has_boxed_usage) + * be able to, just copy stack-allocated C++ objects around willy nilly. */ + if(expr->is_loop) { - util::format_to(body_buffer, - "auto const {}({});", - detail::boxed_local_name(munged_name), - val_tmp.unwrap().str(true)); + if(cpp_util::is_any_object(local_type)) + { + util::format_to(body_buffer, + "{ jank::runtime::object_ref {}({}); ", + munged_name, + val_tmp.unwrap().str(true)); + } + else + { + util::format_to(body_buffer, "{ auto {}({}); ", munged_name, val_tmp.unwrap().str(true)); + } + } + else + { + /* Local array refs should be turned into pointers so we can work with them more easily. */ + if(Cpp::IsArrayType(Cpp::GetNonReferenceType(local_type))) + { + util::format_to(body_buffer, + "{ {} {}({}); ", + cpp_util::get_qualified_type_name(Cpp::GetPointerType( + Cpp::GetArrayElementType(Cpp::GetNonReferenceType(local_type)))), + munged_name, + val_tmp.unwrap().str(false)); + } + else + { + util::format_to(body_buffer, + "{ auto &&{}({}); ", + munged_name, + val_tmp.unwrap().str(false)); + } } } + if(expr->is_loop) + { + util::format_to(body_buffer, "while(true){"); + } + for(auto it(expr->body->values.begin()); it != expr->body->values.end();) { - auto const &val_tmp(gen(*it, fn_arity, true)); + auto const &val_tmp(gen(*it, fn_arity)); /* We ignore all values but the last. */ if(++it == expr->body->values.end() && val_tmp.is_some()) { - if(expr->needs_box) - { - /* The last expression tmp needs to be movable. */ - util::format_to(body_buffer, - "{} = std::move({});", - ret_tmp.str(true), - val_tmp.unwrap().str(expr->needs_box)); - } - else + /* The last expression tmp needs to be movable. */ + util::format_to(body_buffer, + "{} = std::move({});", + ret_tmp, + val_tmp.unwrap().str(cpp_util::is_any_object(last_expr_type))); + + if(expr->is_loop) { - util::format_to(body_buffer, "return {};", val_tmp.unwrap().str(expr->needs_box)); + util::format_to(body_buffer, " break;"); } } } @@ -1442,41 +1248,136 @@ namespace jank::codegen util::format_to(body_buffer, "}"); } - if(expr->needs_box) + if(expr->is_loop) { util::format_to(body_buffer, "}"); } - else - { - util::format_to(body_buffer, "}());"); - } + + util::format_to(body_buffer, "}"); if(expr->position == analyze::expression_position::tail) { - util::format_to(body_buffer, - "return {}{};", - ret_tmp.str(expr->needs_box), - (used_option ? ".unwrap()" : "")); + util::format_to(body_buffer, "return {}{};", ret_tmp, (used_option ? ".unwrap()" : "")); return none; } - return util::format("{}{}", ret_tmp.str(expr->needs_box), (used_option ? ".unwrap()" : "")); + return handle{ util::format("{}{}", ret_tmp, (used_option ? ".unwrap()" : "")), + cpp_util::is_any_object(last_expr_type) }; } jtl::option - processor::gen(analyze::expr::letfn_ref const, analyze::expr::function_arity const &, bool const) + processor::gen(analyze::expr::letfn_ref const expr, analyze::expr::function_arity const &fn_arity) { - return none; + auto const &ret_tmp{ runtime::munge(__rt_ctx->unique_namespaced_string("letfn")) }; + bool used_option{}; + + auto const last_expr_type{ cpp_util::expression_type( + expr->body->values[expr->body->values.size() - 1]) }; + + auto const &type_name{ cpp_util::get_qualified_type_name( + Cpp::GetNonReferenceType(last_expr_type)) }; + if(cpp_util::is_any_object(last_expr_type)) + { + util::format_to(body_buffer, "{} {}{ }; {", type_name, ret_tmp); + } + else + { + used_option = true; + util::format_to(body_buffer, "jtl::option<{}> {}{ }; {", type_name, ret_tmp); + } + + /* We don't handle shadowed bindings very well, so we can run into problems where our + * codegen doesn't work. For letfn, we detect shadowed bindings and get around potential + * assignment issues by just using an object_ref. This can be removed once we + * properly give shadowed bindings individual local_binding entries or we have some other + * mechanism for tracking them. */ + bool has_shadowed_bindings{}; + native_set seen_names; + for(auto const &pair : expr->pairs) + { + auto const local(expr->frame->find_local_or_capture(pair.first)); + auto const &name{ local.unwrap().binding->native_name }; + if(seen_names.contains(name)) + { + has_shadowed_bindings = true; + break; + } + seen_names.emplace(name); + } + + for(auto const &pair : expr->pairs) + { + auto const local(expr->frame->find_local_or_capture(pair.first)); + auto const val_expr(llvm::cast(pair.second.data)); + auto const &munged_name(runtime::munge(local.unwrap().binding->native_name)); + auto const type_name{ ( + has_shadowed_bindings + ? "jank::runtime::object_ref" + : util::format("jank::runtime::oref<{}>", runtime::munge(val_expr->unique_name))) }; + util::format_to(body_buffer, "{ {} {};", type_name, munged_name); + } + + for(auto const &pair : expr->pairs) + { + auto const local(expr->frame->find_local_or_capture(pair.first)); + auto const &val_tmp(gen(pair.second, fn_arity)); + auto const &munged_name(runtime::munge(local.unwrap().binding->native_name)); + + util::format_to(body_buffer, "{} = {}; ", munged_name, val_tmp.unwrap().str(false)); + } + + for(auto const &pair : expr->pairs) + { + auto const local(expr->frame->find_local_or_capture(pair.first)); + + auto const &munged_name(runtime::munge(local.unwrap().binding->native_name)); + auto const val_expr(llvm::cast(pair.second.data)); + for(auto const &capture_pair : val_expr->captures()) + { + auto const &capture_name(runtime::munge(capture_pair.second->native_name)); + util::format_to(body_buffer, "{}->{} = {}; ", munged_name, capture_name, capture_name); + } + } + + for(auto it(expr->body->values.begin()); it != expr->body->values.end();) + { + auto const &val_tmp(gen(*it, fn_arity)); + + /* We ignore all values but the last. */ + if(++it == expr->body->values.end() && val_tmp.is_some()) + { + /* The last expression tmp needs to be movable. */ + util::format_to(body_buffer, + "{} = std::move({});", + ret_tmp, + val_tmp.unwrap().str(cpp_util::is_any_object(last_expr_type))); + } + } + for(auto const &_ : expr->pairs) + { + static_cast(_); + util::format_to(body_buffer, "}"); + } + + util::format_to(body_buffer, "}"); + + if(expr->position == analyze::expression_position::tail) + { + util::format_to(body_buffer, "return {}{};", ret_tmp, (used_option ? ".unwrap()" : "")); + return none; + } + + return handle{ util::format("{}{}", ret_tmp, (used_option ? ".unwrap()" : "")), + cpp_util::is_any_object(last_expr_type) }; } - jtl::option processor::gen(analyze::expr::do_ref const expr, - analyze::expr::function_arity const &arity, - bool const) + jtl::option + processor::gen(analyze::expr::do_ref const expr, analyze::expr::function_arity const &arity) { jtl::option last; for(auto const &form : expr->values) { - last = gen(form, arity, true); + last = gen(form, arity); } switch(expr->position) @@ -1501,21 +1402,24 @@ namespace jank::codegen } } - jtl::option processor::gen(analyze::expr::if_ref const expr, - analyze::expr::function_arity const &fn_arity, - bool const) + jtl::option + processor::gen(analyze::expr::if_ref const expr, analyze::expr::function_arity const &fn_arity) { - /* TODO: Handle unboxed results! */ auto ret_tmp(runtime::munge(__rt_ctx->unique_namespaced_string("if"))); - util::format_to(body_buffer, "object_ref {}{ };", ret_tmp); - auto const &condition_tmp(gen(expr->condition, fn_arity, false)); + auto const expr_type{ cpp_util::expression_type(expr->then) }; + util::format_to(body_buffer, + "{} {}{ };", + cpp_util::get_qualified_type_name(expr_type), + ret_tmp); + auto const &condition_tmp(gen(expr->condition, fn_arity)); util::format_to(body_buffer, "if(jank::runtime::truthy({})) {", condition_tmp.unwrap().str(false)); - auto const &then_tmp(gen(expr->then, fn_arity, true)); + auto const &then_tmp(gen(expr->then, fn_arity)); + auto const is_object{ cpp_util::is_any_object(expr_type) }; if(then_tmp.is_some()) { - util::format_to(body_buffer, "{} = {}; }", ret_tmp, then_tmp.unwrap().str(expr->needs_box)); + util::format_to(body_buffer, "{} = {}; }", ret_tmp, then_tmp.unwrap().str(is_object)); } else { @@ -1525,10 +1429,10 @@ namespace jank::codegen if(expr->else_.is_some()) { util::format_to(body_buffer, "else {"); - auto const &else_tmp(gen(expr->else_.unwrap(), fn_arity, true)); + auto const &else_tmp(gen(expr->else_.unwrap(), fn_arity)); if(else_tmp.is_some()) { - util::format_to(body_buffer, "{} = {}; }", ret_tmp, else_tmp.unwrap().str(expr->needs_box)); + util::format_to(body_buffer, "{} = {}; }", ret_tmp, else_tmp.unwrap().str(is_object)); } else { @@ -1542,103 +1446,154 @@ namespace jank::codegen util::format_to(body_buffer, "else { return {}; }", ret_tmp); } - return ret_tmp; + return handle{ ret_tmp, is_object }; } - jtl::option processor::gen(analyze::expr::throw_ref const expr, - analyze::expr::function_arity const &fn_arity, - bool const) + jtl::option + processor::gen(analyze::expr::throw_ref const expr, analyze::expr::function_arity const &fn_arity) { - auto const &value_tmp(gen(expr->value, fn_arity, true)); + auto const &value_tmp(gen(expr->value, fn_arity)); /* We static_cast to object_ref here, since we'll be trying to catch an object_ref in any * try/catch forms. This loses us our type info, but C++ doesn't do implicit conversions * when catching and we're not using inheritance. */ util::format_to(body_buffer, "throw static_cast({});", value_tmp.unwrap().str(true)); - return none; + + if(expr->position == analyze::expression_position::tail) + { + util::format_to(body_buffer, "return jank::runtime::jank_nil;"); + } + + return "jank::runtime::jank_nil"; } - jtl::option processor::gen(analyze::expr::try_ref const expr, - analyze::expr::function_arity const &fn_arity, - bool const box_needed) + jtl::option + processor::gen(analyze::expr::try_ref const expr, analyze::expr::function_arity const &fn_arity) { - auto const has_catch{ expr->catch_body.is_some() }; + auto const has_catch{ !expr->catch_bodies.empty() }; auto ret_tmp(runtime::munge(__rt_ctx->unique_namespaced_string("try"))); - util::format_to(body_buffer, "object_ref {}{ };", ret_tmp); + util::format_to(body_buffer, "jank::runtime::object_ref {}{ };", ret_tmp); util::format_to(body_buffer, "{"); if(expr->finally_body.is_some()) { - util::format_to(body_buffer, "jank::util::scope_exit const finally{ [&](){ "); - gen(expr->finally_body.unwrap(), fn_arity, box_needed); + util::format_to(body_buffer, "auto const finally{ [&](){ "); + gen(expr->finally_body.unwrap(), fn_arity); util::format_to(body_buffer, "} };"); + /* We use a local struct for RAII to ensure the finally block is called even + * if the try block returns. We can't use jank::util::scope_exit because it + * swallows exceptions. */ + util::format_to(body_buffer, R"( + struct guard_t + { + decltype(finally) f; + bool active{ true }; + ~guard_t() noexcept(false) + { + if(active) { f(); } + } + } guard{ finally }; + )"); + util::format_to(body_buffer, "try {"); } if(has_catch) { util::format_to(body_buffer, "try {"); - auto const &body_tmp(gen(expr->body, fn_arity, box_needed)); + auto const &body_tmp(gen(expr->body, fn_arity)); if(body_tmp.is_some()) { - util::format_to(body_buffer, "{} = {};", ret_tmp, body_tmp.unwrap().str(box_needed)); - } - if(expr->position == analyze::expression_position::tail) - { - util::format_to(body_buffer, "return {};", ret_tmp); + util::format_to(body_buffer, "{} = {};", ret_tmp, body_tmp.unwrap().str(true)); } util::format_to(body_buffer, "}"); - - /* There's a gotcha here, tied to how we throw exceptions. We're catching an object_ref, which - * means we need to be throwing an object_ref. Since we're not using inheritance, we can't - * rely on a catch-all and C++ doesn't do implicit conversions into catch types. So, if we - * throw a persistent_string_ref, for example, it will not be caught as an object_ref. - * - * We mitigate this by ensuring during the codegen for throw that we type-erase to - * an object_ref. - */ - util::format_to(body_buffer, - "catch(jank::runtime::object_ref const {}) {", - runtime::munge(expr->catch_body.unwrap().sym->name)); - auto const &catch_tmp(gen(expr->catch_body.unwrap().body, fn_arity, box_needed)); - if(catch_tmp.is_some()) - { - util::format_to(body_buffer, "{} = {};", ret_tmp, catch_tmp.unwrap().str(box_needed)); - } - if(expr->position == analyze::expression_position::tail) + for(auto const &[sym, type, body] : expr->catch_bodies) { - util::format_to(body_buffer, "return {};", ret_tmp); + util::format_to( + body_buffer, + "catch({} & {}) {", + cpp_util::get_qualified_type_name(Cpp::GetTypeWithoutCv(Cpp::GetNonReferenceType(type))), + runtime::munge(sym->name)); + auto const &catch_tmp(gen(body, fn_arity)); + if(catch_tmp.is_some()) + { + util::format_to(body_buffer, "{} = {};", ret_tmp, catch_tmp.unwrap().str(true)); + } + util::format_to(body_buffer, "}"); } - util::format_to(body_buffer, "}"); } else { - auto const &body_tmp(gen(expr->body, fn_arity, box_needed)); + auto const &body_tmp(gen(expr->body, fn_arity)); if(body_tmp.is_some()) { - util::format_to(body_buffer, "{} = {};", ret_tmp, body_tmp.unwrap().str(box_needed)); - } - if(expr->position == analyze::expression_position::tail) - { - util::format_to(body_buffer, "return {};", ret_tmp); + util::format_to(body_buffer, "{} = {};", ret_tmp, body_tmp.unwrap().str(true)); } } + if(expr->finally_body.is_some()) + { + util::format_to(body_buffer, + "} catch(...) { guard.active = false; finally(); throw; } guard.active = " + "false; finally();"); + } + util::format_to(body_buffer, "}"); return ret_tmp; } jtl::option - processor::gen(analyze::expr::case_ref const, analyze::expr::function_arity const &, bool) + processor::gen(analyze::expr::case_ref const expr, analyze::expr::function_arity const &fn_arity) { - return none; + auto const is_tail{ expr->position == analyze::expression_position::tail }; + auto const &ret_tmp{ runtime::munge(__rt_ctx->unique_namespaced_string("case")) }; + + util::format_to(body_buffer, "jank::runtime::object_ref {}{ };", ret_tmp); + + auto const &value_tmp{ gen(expr->value_expr, fn_arity) }; + + util::format_to(body_buffer, + "switch(jank_shift_mask_case_integer({}.erase(), {}, {})) {", + value_tmp.unwrap().str(true), + expr->shift, + expr->mask); + + jank_debug_assert(expr->keys.size() == expr->exprs.size()); + for(usize i{}; i < expr->keys.size(); ++i) + { + util::format_to(body_buffer, "case {}: {", expr->keys[i]); + + auto const &case_tmp{ gen(expr->exprs[i], fn_arity) }; + if(!is_tail) + { + util::format_to(body_buffer, "{} = {};", ret_tmp, case_tmp.unwrap().str(true)); + } + util::format_to(body_buffer, "break; }"); + } + + util::format_to(body_buffer, "default: {"); + + auto const &default_tmp{ gen(expr->default_expr, fn_arity) }; + if(!is_tail) + { + util::format_to(body_buffer, "{} = {};", ret_tmp, default_tmp.unwrap().str(true)); + } + + util::format_to(body_buffer, "} }"); + + if(is_tail) + { + util::format_to(body_buffer, "return {};", ret_tmp); + return none; + } + + return handle{ ret_tmp, true }; } - jtl::option - processor::gen(expr::cpp_raw_ref const expr, expr::function_arity const &, bool) + jtl::option processor::gen(expr::cpp_raw_ref const expr, expr::function_arity const &) { - util::format_to(deps_buffer, "{}", expr->code); + util::format_to(deps_buffer, "\n{}\n", expr->code); if(expr->position == analyze::expression_position::tail) { @@ -1649,14 +1604,13 @@ namespace jank::codegen } jtl::option - processor::gen(analyze::expr::cpp_type_ref const, analyze::expr::function_arity const &, bool) + processor::gen(analyze::expr::cpp_type_ref const, analyze::expr::function_arity const &) { throw std::runtime_error{ "cpp_type has no codegen" }; } - jtl::option processor::gen(analyze::expr::cpp_value_ref const expr, - analyze::expr::function_arity const &, - bool) + jtl::option + processor::gen(analyze::expr::cpp_value_ref const expr, analyze::expr::function_arity const &) { if(expr->val_kind == expr::cpp_value::value_kind::null) { @@ -1681,6 +1635,17 @@ namespace jank::codegen auto tmp{ Cpp::GetQualifiedCompleteName(expr->scope) }; + /* Arrays need to decay to pointers. If the scope is a variable and the actual + * C++ type is an array, we force decay by adding +0. */ + if(expr->val_kind == expr::cpp_value::value_kind::variable && Cpp::IsVariable(expr->scope)) + { + auto const actual_cpp_type{ Cpp::GetTypeFromScope(expr->scope) }; + if(Cpp::IsArrayType(actual_cpp_type)) + { + tmp = util::format("({} + 0)", tmp); + } + } + if(expr->position == expression_position::tail) { util::format_to(body_buffer, "return {};", tmp); @@ -1690,20 +1655,30 @@ namespace jank::codegen return tmp; } - jtl::option processor::gen(analyze::expr::cpp_cast_ref const expr, - analyze::expr::function_arity const &arity, - bool const box_needed) + jtl::option + processor::gen(analyze::expr::cpp_cast_ref const expr, analyze::expr::function_arity const &arity) { auto ret_tmp(runtime::munge(__rt_ctx->unique_namespaced_string("cpp_cast"))); - auto const value_tmp{ gen(expr->value_expr, arity, box_needed) }; + auto const value_tmp{ gen(expr->value_expr, arity) }; + + if(Cpp::IsVoid(expr->conversion_type)) + { + if(expr->position == expression_position::tail) + { + util::format_to(body_buffer, "return jank::runtime::jank_nil;"); + return none; + } + return "jank::runtime::jank_nil"; + } util::format_to( body_buffer, "auto const {}{ jank::runtime::convert<{}>::{}({}) };", ret_tmp, - Cpp::GetTypeAsString(expr->conversion_type), + cpp_util::get_qualified_type_name( + Cpp::GetTypeWithoutCv(Cpp::GetNonReferenceType(expr->conversion_type))), (expr->policy == conversion_policy::into_object ? "into_object" : "from_object"), - value_tmp.unwrap().str(true)); + value_tmp.unwrap().str(expr->policy == conversion_policy::from_object)); if(expr->position == expression_position::tail) { @@ -1711,12 +1686,11 @@ namespace jank::codegen return none; } - return ret_tmp; + return handle{ ret_tmp, cpp_util::is_any_object(expr->conversion_type) }; } - jtl::option processor::gen(analyze::expr::cpp_call_ref const expr, - analyze::expr::function_arity const &arity, - bool const) + jtl::option + processor::gen(analyze::expr::cpp_call_ref const expr, analyze::expr::function_arity const &arity) { if(expr->source_expr->kind == expression_kind::cpp_value) { @@ -1727,26 +1701,118 @@ namespace jank::codegen arg_tmps.reserve(expr->arg_exprs.size()); for(auto const &arg_expr : expr->arg_exprs) { - arg_tmps.emplace_back(gen(arg_expr, arity, false).unwrap()); + arg_tmps.emplace_back(gen(arg_expr, arity).unwrap()); } - util::format_to(body_buffer, - "auto const {}{ {}(", - ret_tmp, - Cpp::GetQualifiedCompleteName(source->scope)); + auto const is_void{ Cpp::IsVoid(Cpp::GetFunctionReturnType(source->scope)) }; + + if(is_void) + { + util::format_to(body_buffer, "jank::runtime::object_ref const {};", ret_tmp); + } + else + { + util::format_to(body_buffer, "auto &&{}{ ", ret_tmp); + } + + util::format_to(body_buffer, "{}(", Cpp::GetQualifiedCompleteName(source->scope)); + + bool need_comma{}; + for(usize arg_idx{}; arg_idx < expr->arg_exprs.size(); ++arg_idx) + { + auto const arg_expr{ expr->arg_exprs[arg_idx] }; + auto const arg_type{ cpp_util::expression_type(arg_expr) }; + auto const param_type{ Cpp::GetFunctionArgType(source->scope, arg_idx) }; + auto const &arg_tmp{ arg_tmps[arg_idx] }; + + if(need_comma) + { + util::format_to(body_buffer, ", "); + } + util::format_to(body_buffer, + "{}", + arg_tmp.str(param_type && cpp_util::is_any_object(param_type))); + if(param_type && Cpp::IsPointerType(param_type) && cpp_util::is_any_object(arg_type)) + { + util::format_to(body_buffer, ".erase()"); + } + need_comma = true; + } + + util::format_to(body_buffer, ")"); + + if(!is_void) + { + util::format_to(body_buffer, "};"); + } + else + { + util::format_to(body_buffer, ";"); + } + + if(expr->position == expression_position::tail) + { + util::format_to(body_buffer, "return {};", ret_tmp); + return none; + } + + auto const ret_type{ Cpp::GetFunctionReturnType(source->scope) }; + return handle{ ret_tmp, is_void || cpp_util::is_any_object(ret_type) }; + } + else + { + auto ret_tmp(runtime::munge(__rt_ctx->unique_namespaced_string("cpp_call"))); + + auto const source_tmp{ gen(expr->source_expr, arity).unwrap() }; + + native_vector arg_tmps; + arg_tmps.reserve(expr->arg_exprs.size()); + for(auto const &arg_expr : expr->arg_exprs) + { + arg_tmps.emplace_back(gen(arg_expr, arity).unwrap()); + } + + auto const is_void{ Cpp::IsVoid(expr->type) }; + + if(is_void) + { + util::format_to(body_buffer, "jank::runtime::object_ref const {};", ret_tmp); + } + else + { + util::format_to(body_buffer, "auto &&{}{ ", ret_tmp); + } + + util::format_to(body_buffer, "{}(", source_tmp.str(false)); bool need_comma{}; - for(auto const &arg_tmp : arg_tmps) + auto const source_type{ cpp_util::expression_type(expr->source_expr) }; + for(usize arg_idx{}; arg_idx < expr->arg_exprs.size(); ++arg_idx) { + auto const &arg_tmp{ arg_tmps[arg_idx] }; + auto const param_type{ source_type ? Cpp::GetFunctionArgType(source_type, arg_idx) + : nullptr }; + if(need_comma) { util::format_to(body_buffer, ", "); } - util::format_to(body_buffer, "{}", arg_tmp.str(false)); + util::format_to(body_buffer, + "{}", + arg_tmp.str(param_type && cpp_util::is_any_object(param_type))); need_comma = true; } - util::format_to(body_buffer, ") };"); + util::format_to(body_buffer, ")"); + + if(!is_void) + { + util::format_to(body_buffer, "};"); + } + else + { + util::format_to(body_buffer, ";"); + } if(expr->position == expression_position::tail) { @@ -1754,18 +1820,12 @@ namespace jank::codegen return none; } - return ret_tmp; - } - else - { - jank_debug_assert(false); - return none; + return handle{ ret_tmp, is_void || cpp_util::is_any_object(expr->type) }; } } jtl::option processor::gen(analyze::expr::cpp_constructor_call_ref const expr, - analyze::expr::function_arity const &arity, - bool const) + analyze::expr::function_arity const &arity) { auto ret_tmp(runtime::munge(__rt_ctx->unique_namespaced_string("cpp_ctor"))); @@ -1773,29 +1833,73 @@ namespace jank::codegen arg_tmps.reserve(expr->arg_exprs.size()); for(auto const &arg_expr : expr->arg_exprs) { - arg_tmps.emplace_back(gen(arg_expr, arity, false).unwrap()); + arg_tmps.emplace_back(gen(arg_expr, arity).unwrap()); } if(expr->arg_exprs.empty()) { - util::format_to(body_buffer, "{} {}{ };", Cpp::GetTypeAsString(expr->type), ret_tmp); + util::format_to(body_buffer, + "{} {}{ };", + cpp_util::get_qualified_type_name(expr->type), + ret_tmp); return ret_tmp; } - util::format_to(body_buffer, "{} {}( ", Cpp::GetTypeAsString(expr->type), ret_tmp); + util::format_to(body_buffer, "{} {}{ ", cpp_util::get_qualified_type_name(expr->type), ret_tmp); - bool need_comma{}; - for(auto const &arg_tmp : arg_tmps) + if(!expr->arg_exprs.empty()) { - if(need_comma) + auto const arg_type{ cpp_util::expression_type(expr->arg_exprs[0]) }; + bool needs_conversion{}; + jtl::immutable_string conversion_type; + if(cpp_util::is_any_object(expr->type) && !cpp_util::is_any_object(arg_type)) { - util::format_to(body_buffer, ", "); + needs_conversion = true; + conversion_type = "into_object"; + } + else if(!cpp_util::is_any_object(expr->type) && cpp_util::is_any_object(arg_type)) + { + needs_conversion = true; + conversion_type = "from_object"; + } + + if(needs_conversion) + { + util::format_to(body_buffer, + "jank::runtime::convert<{}>::{}({}.get())", + cpp_util::get_qualified_type_name(expr->type), + conversion_type, + arg_tmps[0].str(false)); + } + else + { + auto const needs_static_cast{ expr->type != arg_type && expr->arg_exprs.size() == 1 }; + if(needs_static_cast) + { + util::format_to(body_buffer, + "static_cast<{}>(", + cpp_util::get_qualified_type_name(expr->type)); + } + + bool need_comma{}; + for(auto const &arg_tmp : arg_tmps) + { + if(need_comma) + { + util::format_to(body_buffer, ", "); + } + util::format_to(body_buffer, "{}", arg_tmp.str(false)); + need_comma = true; + } + + if(needs_static_cast) + { + util::format_to(body_buffer, ")"); + } } - util::format_to(body_buffer, "{}", arg_tmp.str(false)); - need_comma = true; } - util::format_to(body_buffer, " );"); + util::format_to(body_buffer, " };"); if(expr->position == expression_position::tail) { @@ -1806,8 +1910,7 @@ namespace jank::codegen } jtl::option processor::gen(analyze::expr::cpp_member_call_ref const expr, - analyze::expr::function_arity const &arity, - bool) + analyze::expr::function_arity const &arity) { auto const fn_name{ Cpp::GetName(expr->fn) }; auto ret_tmp(runtime::munge(__rt_ctx->unique_namespaced_string(fn_name))); @@ -1816,16 +1919,31 @@ namespace jank::codegen arg_tmps.reserve(expr->arg_exprs.size()); for(auto const &arg_expr : expr->arg_exprs) { - arg_tmps.emplace_back(gen(arg_expr, arity, false).unwrap()); + arg_tmps.emplace_back(gen(arg_expr, arity).unwrap()); } - util::format_to( - body_buffer, - "auto &&{}{ {}{}{}(", - ret_tmp, - arg_tmps[0].str(false), - (Cpp::IsPointerType(cpp_util::expression_type(expr->arg_exprs[0])) ? "->" : "."), - fn_name); + auto const is_void{ Cpp::IsVoid(Cpp::GetFunctionReturnType(expr->fn)) }; + + if(is_void) + { + util::format_to(body_buffer, "jank::runtime::object_ref {}{ };", ret_tmp); + util::format_to( + body_buffer, + "{}{}{}(", + arg_tmps[0].str(false), + (Cpp::IsPointerType(cpp_util::expression_type(expr->arg_exprs[0])) ? "->" : "."), + fn_name); + } + else + { + util::format_to( + body_buffer, + "auto &&{}{ {}{}{}(", + ret_tmp, + arg_tmps[0].str(false), + (Cpp::IsPointerType(cpp_util::expression_type(expr->arg_exprs[0])) ? "->" : "."), + fn_name); + } bool need_comma{}; for(auto it{ arg_tmps.begin() + 1 }; it != arg_tmps.end(); ++it) @@ -1838,7 +1956,14 @@ namespace jank::codegen need_comma = true; } - util::format_to(body_buffer, ") };"); + if(is_void) + { + util::format_to(body_buffer, ");"); + } + else + { + util::format_to(body_buffer, ") };"); + } if(expr->position == expression_position::tail) { @@ -1846,15 +1971,16 @@ namespace jank::codegen return none; } - return ret_tmp; + + auto const ret_type{ Cpp::GetFunctionReturnType(expr->fn) }; + return handle{ ret_tmp, is_void || cpp_util::is_any_object(ret_type) }; } jtl::option processor::gen(analyze::expr::cpp_member_access_ref const expr, - analyze::expr::function_arity const &arity, - bool) + analyze::expr::function_arity const &arity) { auto ret_tmp(runtime::munge(__rt_ctx->unique_namespaced_string(expr->name))); - auto obj_tmp(gen(expr->obj_expr, arity, false)); + auto obj_tmp(gen(expr->obj_expr, arity)); util::format_to(body_buffer, "auto &&{}{ {}{}{} };", @@ -1869,12 +1995,12 @@ namespace jank::codegen return none; } - return ret_tmp; + + return handle{ ret_tmp, cpp_util::is_any_object(expr->type) }; } jtl::option processor::gen(analyze::expr::cpp_builtin_operator_call_ref const expr, - analyze::expr::function_arity const &arity, - bool) + analyze::expr::function_arity const &arity) { auto ret_tmp(runtime::munge(__rt_ctx->unique_namespaced_string("cpp_operator"))); @@ -1882,24 +2008,30 @@ namespace jank::codegen arg_tmps.reserve(expr->arg_exprs.size()); for(auto const &arg_expr : expr->arg_exprs) { - arg_tmps.emplace_back(gen(arg_expr, arity, false).unwrap()); + arg_tmps.emplace_back(gen(arg_expr, arity).unwrap()); } + auto const op_name{ cpp_util::operator_name(static_cast(expr->op)).unwrap() }; + if(expr->arg_exprs.size() == 1) + { + util::format_to(body_buffer, "auto &&{}( {}{} );", ret_tmp, op_name, arg_tmps[0].str(false)); + } + else if(op_name == "aget") { util::format_to(body_buffer, - "auto {}( {}{} );", + "auto &&{}( {}[{}] );", ret_tmp, - cpp_util::operator_name(static_cast(expr->op)).unwrap(), - arg_tmps[0].str(false)); + arg_tmps[0].str(false), + arg_tmps[1].str(false)); } else { util::format_to(body_buffer, - "auto {}( {} {} {} );", + "auto &&{}( {} {} {} );", ret_tmp, arg_tmps[0].str(false), - cpp_util::operator_name(static_cast(expr->op)).unwrap(), + op_name, arg_tmps[1].str(false)); } @@ -1912,17 +2044,27 @@ namespace jank::codegen return ret_tmp; } - jtl::option processor::gen(analyze::expr::cpp_box_ref const expr, - analyze::expr::function_arity const &arity, - bool) + jtl::option + processor::gen(analyze::expr::cpp_box_ref const expr, analyze::expr::function_arity const &arity) { auto ret_tmp{ runtime::munge(__rt_ctx->unique_namespaced_string("cpp_box")) }; - auto value_tmp{ gen(expr->value_expr, arity, false) }; + auto value_tmp{ gen(expr->value_expr, arity) }; + auto const value_expr_type{ cpp_util::expression_type(expr->value_expr) }; + auto const type_str{ Cpp::GetTypeAsString( + Cpp::GetCanonicalType(Cpp::GetNonReferenceType(value_expr_type))) }; + + util::format_to( + body_buffer, + "auto {}{ jank::runtime::make_box({}, \"{}\") };\n", + ret_tmp, + value_tmp.unwrap().str(false), + type_str); + auto const meta{ runtime::source_to_meta(expr->source) }; util::format_to(body_buffer, - "auto {}{ jank::runtime::make_box({}) };", + "jank::runtime::reset_meta({}, jank::runtime::__rt_ctx->read_string(\"{}\"));", ret_tmp, - value_tmp.unwrap().str(false)); + util::escape(runtime::to_code_string(meta))); if(expr->position == expression_position::tail) { @@ -1934,18 +2076,62 @@ namespace jank::codegen } jtl::option processor::gen(analyze::expr::cpp_unbox_ref const expr, - analyze::expr::function_arity const &arity, - bool) + analyze::expr::function_arity const &arity) { auto ret_tmp{ runtime::munge(__rt_ctx->unique_namespaced_string("cpp_unbox")) }; - auto value_tmp{ gen(expr->value_expr, arity, false) }; + auto value_tmp{ gen(expr->value_expr, arity) }; + auto const type_name{ cpp_util::get_qualified_type_name(expr->type) }; + auto const meta{ runtime::source_to_meta(expr->source) }; + + util::format_to(body_buffer, + "auto {}{ " + "static_cast<{}>(jank_unbox_with_source(\"{}\", {}.data, " + "jank::runtime::__rt_ctx->read_string(\"{}\").data)" + ") };", + ret_tmp, + type_name, + type_name, + value_tmp.unwrap().str(false), + util::escape(runtime::to_code_string(meta))); + + if(expr->position == expression_position::tail) + { + util::format_to(body_buffer, "return {};", ret_tmp); + return none; + } + + return ret_tmp; + } + + jtl::option + processor::gen(analyze::expr::cpp_new_ref const expr, analyze::expr::function_arity const &arity) + { + auto ret_tmp{ runtime::munge(__rt_ctx->unique_namespaced_string("cpp_new")) }; + auto finalizer_tmp{ runtime::munge(__rt_ctx->unique_namespaced_string("finalizer")) }; + auto value_tmp{ gen(expr->value_expr, arity) }; + + auto const type_name{ cpp_util::get_qualified_type_name(expr->type) }; + auto const needs_finalizer{ !Cpp::IsTriviallyDestructible(expr->type) }; + + if(needs_finalizer) + { + util::format_to(body_buffer, + "using T = {};\n" + "static auto const {}{ " + "[](void * const obj, void *){" + "reinterpret_cast(obj)->~T();" + "} };", + type_name, + finalizer_tmp); + } util::format_to(body_buffer, "auto {}{ " - "static_cast<{}>(jank::runtime::try_object({})-" - ">data.data) };", + "new (GC{}) {}{ {} }" + " };", ret_tmp, - Cpp::GetTypeAsString(expr->type), + (needs_finalizer ? ", " + finalizer_tmp : ""), + type_name, value_tmp.unwrap().str(false)); if(expr->position == expression_position::tail) @@ -1957,12 +2143,43 @@ namespace jank::codegen return ret_tmp; } + jtl::option processor::gen(analyze::expr::cpp_delete_ref const expr, + analyze::expr::function_arity const &arity) + { + auto value_tmp{ gen(expr->value_expr, arity).unwrap() }; + auto const value_type{ Cpp::GetPointeeType(cpp_util::expression_type(expr->value_expr)) }; + auto const type_name{ cpp_util::get_qualified_type_name(value_type) }; + auto const needs_finalizer{ !Cpp::IsTriviallyDestructible(value_type) }; + + /* Calling GC_free won't trigger the finalizer. Not sure why, but it's explicitly + * documented in bdwgc. So, we'll invoke it manually if needed, prior to GC_free. */ + if(needs_finalizer) + { + util::format_to(body_buffer, + "using T = {};\n" + "{}->~T();", + type_name, + value_tmp.str(false)); + } + + util::format_to(body_buffer, "GC_free({});", value_tmp.str(false)); + + if(expr->position == expression_position::tail) + { + util::format_to(body_buffer, "return jank::runtime::jank_nil;"); + return none; + } + + return "jank::runtime::jank_nil"; + } + jtl::immutable_string processor::declaration_str() { if(!generated_declaration) { - build_header(); + profile::timer const timer{ util::format("cpp gen {}", root_fn->name) }; build_body(); + build_header(); build_footer(); generated_declaration = true; } @@ -1983,161 +2200,232 @@ namespace jank::codegen void processor::build_header() { - /* 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)) - //if(target == compilation_target::module) - { - util::format_to(header_buffer, - "namespace {} {", - runtime::module::module_to_native_ns(module)); - } + util::format_to(header_buffer, "namespace {} ", runtime::module::module_to_native_ns(module)); + util::format_to(header_buffer, "{}", "{\n"); util::format_to(header_buffer, R"( struct {} : jank::runtime::obj::jit_function - { )", runtime::munge(struct_name.name)); + util::format_to(header_buffer, "{}", "{\n"); + { - /* TODO: Constants and vars are not shared across arities. We'd need stable names. */ - native_set used_vars, used_constants, used_captures; + native_set used_captures; for(auto const &arity : root_fn->arities) { - for(auto const &v : arity.frame->lifted_vars) - { - if(used_vars.contains(v.second.native_name.to_hash())) - { - continue; - } - used_vars.emplace(v.second.native_name.to_hash()); - - util::format_to(header_buffer, - "jank::runtime::var_ref const {};", - runtime::munge(v.second.native_name)); - } - - for(auto const &v : arity.frame->lifted_constants) + /* TODO: More useful types here. */ + for(auto const &v : arity.frame->captures) { - if(used_constants.contains(v.second.native_name.to_hash())) + auto const hash{ v.first->to_hash() }; + if(used_captures.contains(hash)) { continue; } - used_constants.emplace(v.second.native_name.to_hash()); - - util::format_to(header_buffer, - "{} const {};", - detail::gen_constant_type(v.second.data, true), - runtime::munge(v.second.native_name)); + used_captures.emplace(hash); - if(v.second.unboxed_native_name.is_some()) + /* We're generating the inputs to the function ctor, which means we don't + * want the binding of the capture within the function; we want the one outside + * of it, which we're capturing. We need to reach further for that. + * + * We check for named recursion first, since that takes higher precedence + * than locals or captures. */ + auto const recursion(root_fn->frame->find_named_recursion(v.first)); + if(recursion.is_some()) { util::format_to(header_buffer, - "static constexpr {} const {}{ ", - detail::gen_constant_type(v.second.data, false), - runtime::munge(v.second.unboxed_native_name.unwrap())); - detail::gen_constant(v.second.data, header_buffer, false); - util::format_to(header_buffer, "};"); + "jank::runtime::obj::jit_function_ref {};\n", + munge(recursion.unwrap().fn_frame->fn_ctx->name)); } - } - - /* TODO: More useful types here. */ - for(auto const &v : arity.frame->captures) - { - if(used_captures.contains(v.first->to_hash())) + else { - continue; + auto const originating_local(root_fn->frame->find_local_or_capture(v.first)); + auto const local_type{ originating_local.unwrap().binding->type }; + util::format_to(header_buffer, + "{} {};\n", + cpp_util::get_qualified_type_name(local_type), + handle(originating_local.unwrap().binding).str(true)); } - used_captures.emplace(v.first->to_hash()); - - util::format_to(header_buffer, - "jank::runtime::object_ref const {};", - runtime::munge(v.second.native_name)); } } } + /* Lifted vars. */ + for(auto const &pair : lifted_vars) + { + util::format_to(header_buffer, "jank::runtime::var_ref const {};\n", pair.second.native_name); + } + + /* Lifted constants. */ + for(auto const &pair : lifted_constants) { - native_set used_captures; - util::format_to(header_buffer, "{}(", runtime::munge(struct_name.name)); + util::format_to(header_buffer, "jank::runtime::object_ref const {};\n", pair.second); + } + + util::format_to(header_buffer, "\n{}(", runtime::munge(struct_name.name)); + { + native_set used_captures; bool need_comma{}; for(auto const &arity : root_fn->arities) { for(auto const &v : arity.frame->captures) { - if(used_captures.contains(v.first->to_hash())) + auto const hash{ v.first->to_hash() }; + if(used_captures.contains(hash)) { continue; } - used_captures.emplace(v.first->to_hash()); + used_captures.emplace(hash); - /* TODO: More useful types here. */ - util::format_to(header_buffer, - "{} jank::runtime::object_ref {}", - (need_comma ? "," : ""), - runtime::munge(v.second.native_name)); + if(need_comma) + { + util::format_to(header_buffer, ", "); + } need_comma = true; + + auto const name{ runtime::munge(v.second.native_name) }; + /* We take these by value/copy because the struct will own them. */ + + /* Check if this is a named recursion first, since it needs a specific type. */ + auto const recursion(root_fn->frame->find_named_recursion(v.first)); + if(recursion.is_some()) + { + util::format_to(header_buffer, "jank::runtime::object_ref {}", name); + } + else + { + auto const originating_local(root_fn->frame->find_local_or_capture(v.first)); + auto const local_type{ originating_local.unwrap().binding->type }; + util::format_to(header_buffer, + "{} {}", + cpp_util::get_qualified_type_name(local_type), + name); + } } } } + util::format_to(header_buffer, ")"); - { - native_set used_vars, used_constants, used_captures; - util::format_to(header_buffer, ") : jank::runtime::obj::jit_function{ "); - /* TODO: All of the meta in clojure.core alone costs 2s to JIT compile at run-time. - * How can this be faster? */ - detail::gen_constant(root_fn->meta, header_buffer, true); - util::format_to(header_buffer, "}"); + util::format_to(header_buffer, "\n: jank::runtime::obj::jit_function"); + util::format_to(header_buffer, "{}", "{ jank::runtime::obj::persistent_hash_map::empty() }"); + { + native_set used_captures; for(auto const &arity : root_fn->arities) { - for(auto const &v : arity.frame->lifted_vars) + for(auto const &v : arity.frame->captures) { - if(used_vars.contains(v.second.native_name.to_hash())) + auto const hash{ v.first->to_hash() }; + if(used_captures.contains(hash)) { continue; } - used_vars.emplace(v.second.native_name.to_hash()); - - util::format_to(header_buffer, - R"(, {}{ jank::runtime::__rt_ctx->intern_var("{}", "{}").expect_ok() })", - runtime::munge(v.second.native_name), - v.second.var_name->ns, - v.second.var_name->name); - } + used_captures.emplace(hash); - for(auto const &v : arity.frame->lifted_constants) - { - if(used_constants.contains(v.second.native_name.to_hash())) + auto const recursion(root_fn->frame->find_named_recursion(v.first)); + if(recursion.is_some()) { - continue; + auto const recur_name(munge(recursion.unwrap().fn_frame->fn_ctx->name)); + util::format_to(header_buffer, ", {}", recur_name); + util::format_to( + header_buffer, + "{ jank::runtime::expect_object({}) }", + recur_name); + } + else + { + auto const originating_local(root_fn->frame->find_local_or_capture(v.first)); + handle const h{ originating_local.unwrap().binding }; + util::format_to(header_buffer, ", {}", h.str(true)); + util::format_to(header_buffer, "{}", "{ "); + util::format_to(header_buffer, "{}", h.str(true)); + util::format_to(header_buffer, "{}", " }"); } - used_constants.emplace(v.second.native_name.to_hash()); - - util::format_to(header_buffer, ", {}{", runtime::munge(v.second.native_name)); - detail::gen_constant(v.second.data, header_buffer, true); - util::format_to(header_buffer, "}"); } + } + } + + /* Lifted vars. */ + for(auto const &v : lifted_vars) + { + if(v.second.owned) + { + util::format_to(header_buffer, + R"(, {}{ jank::runtime::__rt_ctx->intern_owned_var("{}").expect_ok() })", + v.second.native_name, + v.first); + } + else + { + util::format_to(header_buffer, + R"(, {}{ jank::runtime::__rt_ctx->intern_var("{}").expect_ok() })", + v.second.native_name, + v.first); + } + } + /* Lifted constants. */ + for(auto const &pair : lifted_constants) + { + util::format_to(header_buffer, ", {}", pair.second); + util::format_to(header_buffer, "{}", "{ "); + detail::gen_constant(pair.first, header_buffer, true); + util::format_to(header_buffer, "{}", " }"); + } + + util::format_to(header_buffer, "\n{}\n", "{\n"); + /* If we have any recursive refs, we need to patch them up here. */ + { + native_set used_captures; + for(auto const &arity : root_fn->arities) + { for(auto const &v : arity.frame->captures) { - if(used_captures.contains(v.first->to_hash())) + auto const hash{ v.first->to_hash() }; + if(used_captures.contains(hash)) { continue; } - used_captures.emplace(v.first->to_hash()); + used_captures.emplace(hash); - auto const name{ runtime::munge(v.second.native_name) }; - util::format_to(header_buffer, ", {}{ {} }", name, name); + auto const recursion(root_fn->frame->find_named_recursion(v.first)); + if(recursion.is_some()) + { + auto const compute_arity_flags = [](auto const &fn) { + analyze::expr::function_arity const *variadic_arity{}; + analyze::expr::function_arity const *highest_fixed_arity{}; + for(auto const &arity : fn->arities) + { + if(arity.fn_ctx->is_variadic) + { + variadic_arity = &arity; + } + else if(!highest_fixed_arity + || highest_fixed_arity->fn_ctx->param_count < arity.fn_ctx->param_count) + { + highest_fixed_arity = &arity; + } + } + bool const variadic_ambiguous{ highest_fixed_arity && variadic_arity + && highest_fixed_arity->fn_ctx->param_count + == variadic_arity->fn_ctx->param_count - 1 }; + return jank::runtime::behavior::callable::build_arity_flags( + highest_fixed_arity ? highest_fixed_arity->fn_ctx->param_count : 0, + variadic_arity != nullptr, + variadic_ambiguous); + }; + + util::format_to(header_buffer, + "this->{}->arity_flags = {};\n", + munge(recursion.unwrap().fn_frame->fn_ctx->name), + compute_arity_flags(recursion.unwrap().fn_frame->fn_ctx->fn)); + } } } } - - util::format_to(header_buffer, "{ }"); + util::format_to(header_buffer, "{}", "}\n"); } void processor::build_body() @@ -2176,23 +2464,20 @@ namespace jank::codegen param_shadows_fn |= param->name == root_fn->name; } - util::format_to(body_buffer, - R"( - ) final { - using namespace jank; - using namespace jank::runtime; - )"); + util::format_to(body_buffer, ") final {\n"); //util::format_to(body_buffer, "jank::profile::timer __timer{ \"{}\" };", root_fn->name); if(!param_shadows_fn) { - util::format_to(body_buffer, "object_ref const {}{ this };", runtime::munge(root_fn->name)); + util::format_to(body_buffer, + "jank::runtime::object_ref const {}{ this };\n", + runtime::munge(root_fn->name)); } if(arity.fn_ctx->is_tail_recursive) { - util::format_to(body_buffer, "{"); + util::format_to(body_buffer, "{}", "{"); for(auto const ¶m : arity.params) { @@ -2209,7 +2494,7 @@ namespace jank::codegen for(auto const &form : arity.body->values) { - gen(form, arity, true); + gen(form, arity); } if(arity.body->values.empty()) @@ -2219,10 +2504,10 @@ namespace jank::codegen if(arity.fn_ctx->is_tail_recursive) { - util::format_to(body_buffer, "} }"); + util::format_to(body_buffer, "{}", "} }"); } - util::format_to(body_buffer, "}"); + util::format_to(body_buffer, "}\n"); } if(variadic_arity) @@ -2244,50 +2529,46 @@ namespace jank::codegen void processor::build_footer() { /* Struct. */ - util::format_to(footer_buffer, "};"); - - /* Namespace. */ - //if(!runtime::module::is_nested_module(module)) - //if(target == compilation_target::module) - { - util::format_to(footer_buffer, "}"); - } + util::format_to(footer_buffer, "};\n"); + util::format_to(footer_buffer, "}\n"); if(target == compilation_target::module) { util::format_to(footer_buffer, - "extern \"C\" void* {}(){", + "extern \"C\" void* {}()", runtime::module::module_to_load_function(module)); + util::format_to(footer_buffer, "{}", "{"); + + /* First thing we do when loading this module is to intern our ns. Everything else will + * build on that. */ + util::format_to(footer_buffer, "jank_ns_intern_c(\"{}\");", module); + util::format_to(footer_buffer, "jank_ns_intern_c(\"{}\");\n", module); + + /* This dance is performed to keep symbol names unique across all the modules. */ + auto const current_ns{ __rt_ctx->current_ns() }; util::format_to(footer_buffer, - "return {}::{}{ }.call().erase();", + "jank_ns_set_symbol_counter(\"{}\", {});\n", + current_ns->name->get_name(), + current_ns->symbol_counter.load()); + + util::format_to(footer_buffer, + "return {}::{}{ }.call().erase();\n", runtime::module::module_to_native_ns(module), runtime::munge(struct_name.name)); - util::format_to(footer_buffer, "}"); + + util::format_to(footer_buffer, "{}", "}\n"); } } - jtl::immutable_string processor::expression_str(bool const box_needed) + jtl::immutable_string processor::expression_str() { auto const module_ns(runtime::module::module_to_native_ns(module)); if(!generated_expression) { - jtl::immutable_string close = ")"; - if(box_needed) - { - util::format_to( - expression_buffer, - "jank::runtime::make_box<{}>(", - runtime::module::nest_native_ns(module_ns, runtime::munge(struct_name.name))); - } - else - { - util::format_to( - expression_buffer, - "{}{ ", - runtime::module::nest_native_ns(module_ns, runtime::munge(struct_name.name))); - close = "}"; - } + util::format_to(expression_buffer, + "jank::runtime::make_box<{}>(", + runtime::module::nest_native_ns(module_ns, runtime::munge(struct_name.name))); native_set used_captures; bool need_comma{}; @@ -2319,70 +2600,16 @@ namespace jank::codegen { auto const originating_local(root_fn->frame->find_local_or_capture(v.first)); handle const h{ originating_local.unwrap().binding }; - util::format_to(expression_buffer, - "{} {}", - (need_comma ? "," : ""), - h.str(true), - originating_local.unwrap().binding->name->to_code_string(), - originating_local.unwrap().binding->native_name); + util::format_to(expression_buffer, "{} {}", (need_comma ? "," : ""), h.str(true)); } need_comma = true; } } - util::format_to(expression_buffer, "{}", close); + util::format_to(expression_buffer, ")"); generated_expression = true; } return { expression_buffer.data(), expression_buffer.size() }; } - - /* TODO: Not sure if we want any of this. The module dependency loading feels wrong, - * since it should be tied to calls to require instead. */ - jtl::immutable_string processor::module_init_str(jtl::immutable_string const &module) - { - jtl::string_builder module_buffer; - - util::format_to(module_buffer, "namespace {} {", runtime::module::module_to_native_ns(module)); - - util::format_to(module_buffer, - R"( - struct __ns__init - { - )"); - - util::format_to(module_buffer, "static void __init(){"); - //util::format_to(module_buffer, "jank::profile::timer __timer{ \"ns __init\" };"); - util::format_to(module_buffer, - "constexpr auto const deps(jank::util::make_array("); - bool needs_comma{}; - for(auto const &dep : __rt_ctx->module_dependencies[module]) - { - if(needs_comma) - { - util::format_to(module_buffer, ", "); - } - util::format_to(module_buffer, "\"/{}\"", dep); - needs_comma = true; - } - util::format_to(module_buffer, "));"); - - util::format_to(module_buffer, "for(auto const &dep : deps){"); - util::format_to(module_buffer, "jank::runtime::__rt_ctx->load_module(dep).expect_ok();"); - util::format_to(module_buffer, "}"); - - /* __init fn */ - util::format_to(module_buffer, "}"); - - /* Struct */ - util::format_to(module_buffer, "};"); - - /* Namespace */ - util::format_to(module_buffer, "}"); - - native_transient_string ret; - ret.reserve(module_buffer.size()); - ret += jtl::immutable_string_view{ module_buffer.data(), module_buffer.size() }; - return ret; - } } diff --git a/compiler+runtime/src/cpp/jank/error.cpp b/compiler+runtime/src/cpp/jank/error.cpp index 732e4d384..cae8deb79 100644 --- a/compiler+runtime/src/cpp/jank/error.cpp +++ b/compiler+runtime/src/cpp/jank/error.cpp @@ -184,6 +184,8 @@ namespace jank::error return "Invalid C++ delete."; case kind::analyze_invalid_cpp_member_access: return "Invalid C++ member access."; + case kind::analyze_known_issue: + return "Known issue."; case kind::internal_analyze_failure: return "Internal analysis failure."; @@ -469,4 +471,16 @@ namespace jank::error { return os << "error(" << kind_str(e.kind) << " - " << e.source << ", \"" << e.message << "\")"; } + + error_ref internal_failure(jtl::immutable_string const &message) + { + auto const e{ make_error(kind::internal_failure, message, read::source::unknown) }; + e->trace = std::make_unique(cpptrace::generate_trace()); + return e; + } + + void throw_internal_failure(jtl::immutable_string const &message) + { + throw internal_failure(message); + } } diff --git a/compiler+runtime/src/cpp/jank/error/analyze.cpp b/compiler+runtime/src/cpp/jank/error/analyze.cpp index 53c0ae82c..f6e181327 100644 --- a/compiler+runtime/src/cpp/jank/error/analyze.cpp +++ b/compiler+runtime/src/cpp/jank/error/analyze.cpp @@ -153,7 +153,7 @@ namespace jank::error note &&extra, runtime::object_ref const expansion) { - return make_error(kind::analyze_invalid_try, message, source, std::move(extra), expansion); + return make_error(kind::analyze_invalid_try, message, source, jtl::move(extra), expansion); } error_ref analyze_unresolved_var(jtl::immutable_string const &message, @@ -229,28 +229,28 @@ namespace jank::error error_ref analyze_invalid_cpp_operator_call(jtl::immutable_string const &message, read::source const &source, - runtime::object_ref expansion) + runtime::object_ref const expansion) { return make_error(kind::analyze_invalid_cpp_operator_call, message, source, expansion); } error_ref analyze_invalid_cpp_constructor_call(jtl::immutable_string const &message, read::source const &source, - runtime::object_ref expansion) + runtime::object_ref const expansion) { return make_error(kind::analyze_invalid_cpp_constructor_call, message, source, expansion); } error_ref analyze_invalid_cpp_member_call(jtl::immutable_string const &message, read::source const &source, - runtime::object_ref expansion) + runtime::object_ref const expansion) { return make_error(kind::analyze_invalid_cpp_member_call, message, source, expansion); } error_ref analyze_invalid_cpp_capture(jtl::immutable_string const &message, read::source const &source, - runtime::object_ref expansion) + runtime::object_ref const expansion) { return make_error(kind::analyze_invalid_cpp_capture, message, @@ -261,109 +261,116 @@ namespace jank::error error_ref analyze_mismatched_if_types(jtl::immutable_string const &message, read::source const &source, - runtime::object_ref expansion) + runtime::object_ref const expansion) { return make_error(kind::analyze_mismatched_if_types, message, source, expansion); } error_ref analyze_invalid_cpp_function_call(jtl::immutable_string const &message, read::source const &source, - runtime::object_ref expansion) + runtime::object_ref const expansion) { return make_error(kind::analyze_invalid_cpp_function_call, message, source, expansion); } error_ref analyze_invalid_cpp_call(jtl::immutable_string const &message, read::source const &source, - runtime::object_ref expansion) + runtime::object_ref const expansion) { return make_error(kind::analyze_invalid_cpp_call, message, source, expansion); } error_ref analyze_invalid_cpp_conversion(jtl::immutable_string const &message, read::source const &source, - runtime::object_ref expansion) + runtime::object_ref const expansion) { return make_error(kind::analyze_invalid_cpp_conversion, message, source, expansion); } error_ref analyze_invalid_cpp_symbol(jtl::immutable_string const &message, read::source const &source, - runtime::object_ref expansion) + runtime::object_ref const expansion) { return make_error(kind::analyze_invalid_cpp_symbol, message, source, expansion); } error_ref analyze_unresolved_cpp_symbol(jtl::immutable_string const &message, read::source const &source, - runtime::object_ref expansion) + runtime::object_ref const expansion) { return make_error(kind::analyze_unresolved_cpp_symbol, message, source, expansion); } error_ref analyze_invalid_cpp_raw(jtl::immutable_string const &message, read::source const &source, - runtime::object_ref expansion) + runtime::object_ref const expansion) { return make_error(kind::analyze_invalid_cpp_raw, message, source, expansion); } error_ref analyze_invalid_cpp_type(jtl::immutable_string const &message, read::source const &source, - runtime::object_ref expansion) + runtime::object_ref const expansion) { return make_error(kind::analyze_invalid_cpp_type, message, source, expansion); } error_ref analyze_invalid_cpp_value(jtl::immutable_string const &message, read::source const &source, - runtime::object_ref expansion) + runtime::object_ref const expansion) { return make_error(kind::analyze_invalid_cpp_value, message, source, expansion); } error_ref analyze_invalid_cpp_cast(jtl::immutable_string const &message, read::source const &source, - runtime::object_ref expansion) + runtime::object_ref const expansion) { return make_error(kind::analyze_invalid_cpp_cast, message, source, expansion); } error_ref analyze_invalid_cpp_box(jtl::immutable_string const &message, read::source const &source, - runtime::object_ref expansion) + runtime::object_ref const expansion) { return make_error(kind::analyze_invalid_cpp_box, message, source, expansion); } error_ref analyze_invalid_cpp_unbox(jtl::immutable_string const &message, read::source const &source, - runtime::object_ref expansion) + runtime::object_ref const expansion) { return make_error(kind::analyze_invalid_cpp_unbox, message, source, expansion); } error_ref analyze_invalid_cpp_new(jtl::immutable_string const &message, read::source const &source, - runtime::object_ref expansion) + runtime::object_ref const expansion) { return make_error(kind::analyze_invalid_cpp_new, message, source, expansion); } error_ref analyze_invalid_cpp_delete(jtl::immutable_string const &message, read::source const &source, - runtime::object_ref expansion) + runtime::object_ref const expansion) { return make_error(kind::analyze_invalid_cpp_delete, message, source, expansion); } error_ref analyze_invalid_cpp_member_access(jtl::immutable_string const &message, read::source const &source, - runtime::object_ref expansion) + runtime::object_ref const expansion) { return make_error(kind::analyze_invalid_cpp_member_access, message, source, expansion); } + error_ref analyze_known_issue(jtl::immutable_string const &message, + read::source const &source, + runtime::object_ref const expansion) + { + return make_error(kind::analyze_known_issue, message, source, expansion); + } + error_ref internal_analyze_failure(jtl::immutable_string const &message, runtime::object_ref const expansion) { diff --git a/compiler+runtime/src/cpp/jank/error/report.cpp b/compiler+runtime/src/cpp/jank/error/report.cpp index a75d74538..5bbbc0069 100644 --- a/compiler+runtime/src/cpp/jank/error/report.cpp +++ b/compiler+runtime/src/cpp/jank/error/report.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include #include #include @@ -592,4 +594,13 @@ namespace jank::error report(e->cause.as_ref()); } } + + void warn(jtl::immutable_string const &msg) + { + util::println(stderr, + "{}warning:{} {}", + jtl::terminal_style::yellow, + jtl::terminal_style::reset, + msg); + } } diff --git a/compiler+runtime/src/cpp/jank/error/runtime.cpp b/compiler+runtime/src/cpp/jank/error/runtime.cpp index f84a5327f..bac4d49ab 100644 --- a/compiler+runtime/src/cpp/jank/error/runtime.cpp +++ b/compiler+runtime/src/cpp/jank/error/runtime.cpp @@ -29,6 +29,13 @@ namespace jank::error return make_error(kind::runtime_unable_to_load_module, message, read::source::unknown); } + error_ref runtime_unable_to_load_module(error_ref const cause) + { + auto const e{ make_error(kind::runtime_unable_to_load_module, read::source::unknown) }; + e->cause = cause; + return e; + } + error_ref internal_runtime_failure(jtl::immutable_string const &message) { return make_error(kind::internal_runtime_failure, message, read::source::unknown); diff --git a/compiler+runtime/src/cpp/jank/evaluate.cpp b/compiler+runtime/src/cpp/jank/evaluate.cpp index cfe9c7b9c..3074c8571 100644 --- a/compiler+runtime/src/cpp/jank/evaluate.cpp +++ b/compiler+runtime/src/cpp/jank/evaluate.cpp @@ -20,7 +20,6 @@ #include #include #include -#include namespace jank::evaluate { @@ -74,9 +73,12 @@ namespace jank::evaluate else if constexpr(std::same_as) { walk(expr.body, f); - if(expr.catch_body.is_some()) + if(!expr.catch_bodies.empty()) { - walk(expr.catch_body.unwrap().body, f); + for(auto const &catch_body : expr.catch_bodies) + { + walk(catch_body.body, f); + } } if(expr.finally_body.is_some()) { @@ -130,12 +132,7 @@ namespace jank::evaluate } /* 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. - * - * There's an oddity here, since that expr wouldn't've been analyzed within a fn frame, so - * its lifted vars/constants, for example, aren't in a fn frame. Instead, they're put in the - * root frame. So, when wrapping this expr, we give the fn the root frame, but change its - * type to a fn frame. */ + * For those, we wrap them in a fn expression and then JIT compile and call them. */ template static expr::function_ref wrap_expression(jtl::ref const orig_expr, jtl::immutable_string const &name, @@ -148,8 +145,6 @@ namespace jank::evaluate ret->unique_name = __rt_ctx->unique_namespaced_string(ret->name); ret->meta = obj::persistent_hash_map::empty(); - auto const &closest_fn_frame(local_frame::find_closest_fn_frame(*expr->frame)); - auto const frame{ jtl::make_ref(local_frame::frame_type::fn, expr->frame->parent) }; auto const fn_ctx{ jtl::make_ref() }; @@ -159,16 +154,12 @@ namespace jank::evaluate fn_ctx }; expr->frame->parent = arity.frame; ret->frame = arity.frame->parent.unwrap_or(arity.frame); - ret->frame->lift_constant(ret->meta); fn_ctx->name = ret->name; fn_ctx->unique_name = ret->unique_name; fn_ctx->fn = ret; arity.frame->fn_ctx = fn_ctx; arity.fn_ctx = fn_ctx; - arity.frame->lifted_vars = closest_fn_frame.lifted_vars; - arity.frame->lifted_constants = closest_fn_frame.lifted_constants; - arity.fn_ctx->param_count = arity.params.size(); for(auto const sym : arity.params) { @@ -267,7 +258,8 @@ namespace jank::evaluate object_ref eval(expression_ref const ex) { - profile::timer const timer{ "eval ast node" }; + profile::timer const timer{ util::format("eval ast node {}", + analyze::expression_kind_str(ex->kind)) }; object_ref ret{}; visit_expr([&ret](auto const typed_ex) { ret = eval(typed_ex); }, ex); return ret; @@ -580,6 +572,7 @@ namespace jank::evaluate object_ref eval(expr::function_ref const expr) { + profile::timer const timer{ util::format("eval jit function {}", expr->name) }; auto const &module( module::nest_module(expect_object(__rt_ctx->current_ns_var->deref())->to_string(), munge(expr->unique_name))); @@ -605,19 +598,18 @@ namespace jank::evaluate else { codegen::processor cg_prc{ expr, module, codegen::compilation_target::eval }; - util::println("{}\n", util::format_cpp_source(cg_prc.declaration_str()).expect_ok()); - __rt_ctx->jit_prc.eval_string(cg_prc.declaration_str()); - auto const expr_str{ cg_prc.expression_str(true) + ".erase()" }; - clang::Value v; - auto res( - __rt_ctx->jit_prc.interpreter->ParseAndExecute({ expr_str.data(), expr_str.size() }, &v)); - if(res) + + /* TODO: Rename to something generic which makes sense for IR and C++ gen? */ + jtl::immutable_string_view const print_settings{ getenv("JANK_PRINT_IR") ?: "" }; + if(print_settings == "1") { - /* TODO: Helper to turn an llvm::Error into a string. */ - jtl::immutable_string const msg{ "Unable to compile/eval C++ source." }; - llvm::logAllUnhandledErrors(jtl::move(res), llvm::errs(), "error: "); - throw error::internal_codegen_failure(msg); + util::println("{}\n", util::format_cpp_source(cg_prc.declaration_str()).expect_ok()); } + + __rt_ctx->jit_prc.eval_string(cg_prc.declaration_str()); + auto const expr_str{ cg_prc.expression_str() + ".erase()" }; + clang::Value v; + __rt_ctx->jit_prc.eval_string({ expr_str.data(), expr_str.size() }, &v); return try_object(v.convertTo()); } } @@ -685,28 +677,7 @@ namespace jank::evaluate object_ref eval(expr::try_ref const expr) { - util::scope_exit const finally{ [=]() { - if(expr->finally_body) - { - eval(expr->finally_body.unwrap()); - } - } }; - - if(!expr->catch_body) - { - return eval(expr->body); - } - try - { - return eval(expr->body); - } - catch(object_ref const e) - { - return dynamic_call(eval(wrap_expression(expr->catch_body.unwrap().body, - "catch", - { expr->catch_body.unwrap().sym })), - e); - } + return dynamic_call(eval(wrap_expression(expr, "try", {}))); } object_ref eval(expr::case_ref const expr) @@ -716,7 +687,8 @@ namespace jank::evaluate object_ref eval(expr::cpp_raw_ref const expr) { - return dynamic_call(eval(wrap_expression(expr, "cpp_raw", {}))); + __rt_ctx->jit_prc.eval_string(expr->code); + return runtime::jank_nil; } object_ref eval(expr::cpp_type_ref const) diff --git a/compiler+runtime/src/cpp/jank/jit/processor.cpp b/compiler+runtime/src/cpp/jank/jit/processor.cpp index fa5a992a0..9b261cc81 100644 --- a/compiler+runtime/src/cpp/jank/jit/processor.cpp +++ b/compiler+runtime/src/cpp/jank/jit/processor.cpp @@ -21,9 +21,11 @@ #include #include #include +#include #include #include #include +#include namespace jank::jit { @@ -165,6 +167,9 @@ namespace jank::jit args.emplace_back("-include-pch"); args.emplace_back(strdup(pch_path_str.c_str())); + args.emplace_back("-w"); + args.emplace_back("-Wno-c++11-narrowing"); + util::add_system_flags(args); /********* Every flag after this line is user-provided. *********/ @@ -239,12 +244,22 @@ namespace jank::jit } void processor::eval_string(jtl::immutable_string const &s) const + { + eval_string(s, nullptr); + } + + void processor::eval_string(jtl::immutable_string const &s, clang::Value * const ret) const { profile::timer const timer{ "jit eval_string" }; - //util::println("// eval_string:\n{}\n", s); - auto err(interpreter->ParseAndExecute({ s.data(), s.size() })); - /* TODO: Throw on errors. */ - llvm::logAllUnhandledErrors(std::move(err), llvm::errs(), "error: "); + auto const &formatted{ s }; + //auto const &formatted{ util::format_cpp_source(s).expect_ok() }; + //util::println("// eval_string:\n{}\n", formatted); + auto err(interpreter->ParseAndExecute({ formatted.data(), formatted.size() }, ret)); + if(err) + { + llvm::logAllUnhandledErrors(jtl::move(err), llvm::errs(), "error: "); + throw error::internal_codegen_failure("Unable to compile C++ source."); + } register_jit_stack_frames(); } diff --git a/compiler+runtime/src/cpp/jank/read/lex.cpp b/compiler+runtime/src/cpp/jank/read/lex.cpp index 17962c9cd..4391c9354 100644 --- a/compiler+runtime/src/cpp/jank/read/lex.cpp +++ b/compiler+runtime/src/cpp/jank/read/lex.cpp @@ -1081,7 +1081,7 @@ namespace jank::read::lex return error::lex_invalid_number( util::format( "Characters '{}' are invalid for a base {} number.", - jtl::immutable_string_view{ invalid_digits.begin(), invalid_digits.end() }, + jtl::immutable_string_view{ invalid_digits.data(), invalid_digits.size() }, radix), { token_start, pos }); } diff --git a/compiler+runtime/src/cpp/jank/read/parse.cpp b/compiler+runtime/src/cpp/jank/read/parse.cpp index a26697384..d3ca61ede 100644 --- a/compiler+runtime/src/cpp/jank/read/parse.cpp +++ b/compiler+runtime/src/cpp/jank/read/parse.cpp @@ -409,7 +409,7 @@ namespace jank::read::parse parsed_keys.insert({ key.ptr, key }); - if constexpr(std::same_as) + if constexpr(jtl::is_same) { map.insert_or_assign(key.ptr, value.unwrap().ptr); } @@ -435,7 +435,7 @@ namespace jank::read::parse return object_source_info{ make_box( source_to_meta(start_token.start, latest_token.end), - std::move(map)), + jtl::move(map)), start_token, latest_token }; } @@ -453,7 +453,7 @@ namespace jank::read::parse return object_source_info{ make_box( source_to_meta(start_token.start, latest_token.end), - std::move(map)), + jtl::move(map)), start_token, latest_token }; } @@ -538,7 +538,7 @@ namespace jank::read::parse auto meta_result(visit_object( [&](auto const typed_val) -> processor::object_result { using T = typename decltype(typed_val)::value_type; - if constexpr(std::same_as) + if constexpr(jtl::is_same) { return object_source_info{ obj::persistent_array_map::create_unique(typed_val, jank_true), start_token, @@ -683,7 +683,7 @@ namespace jank::read::parse expected_closer = prev_expected_closer; return object_source_info{ make_box( source_to_meta(start_token.start, latest_token.end), - std::move(ret).persistent()), + jtl::move(ret).persistent()), start_token, latest_token }; } @@ -1314,7 +1314,7 @@ namespace jank::read::parse [&](auto const typed_form) -> jtl::result { using T = typename decltype(typed_form)::value_type; - if constexpr(std::same_as) + if constexpr(jtl::is_same) { auto const seq(typed_form->seq()); if(seq.is_nil()) diff --git a/compiler+runtime/src/cpp/jank/runtime/context.cpp b/compiler+runtime/src/cpp/jank/runtime/context.cpp index 09db0d7f8..6a0708857 100644 --- a/compiler+runtime/src/cpp/jank/runtime/context.cpp +++ b/compiler+runtime/src/cpp/jank/runtime/context.cpp @@ -35,7 +35,7 @@ namespace jank::runtime { /* NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) */ - thread_local decltype(context::thread_binding_frames) context::thread_binding_frames{}; + decltype(context::thread_binding_frames) context::thread_binding_frames{}; /* NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) */ context *__rt_ctx{}; @@ -94,11 +94,6 @@ namespace jank::runtime .expect_ok(); } - context::~context() - { - thread_binding_frames.erase(this); - } - obj::symbol_ref context::qualify_symbol(obj::symbol_ref const &sym) const { obj::symbol_ref qualified_sym{ sym }; @@ -159,7 +154,7 @@ namespace jank::runtime return eval_string(file.expect_ok().view()); } - object_ref context::eval_string(jtl::immutable_string_view const &code) + object_ref context::eval_string(jtl::immutable_string const &code) { profile::timer const timer{ "rt eval_string" }; read::lex::processor l_prc{ code }; @@ -186,11 +181,12 @@ namespace jank::runtime * targeted at AOT and doesn't have access to what's loaded in the JIT runtime. */ if(truthy(compile_files_var->deref())) { + profile::timer const timer{ "rt compile-module" }; auto const &module(runtime::to_string(current_module_var->deref())); auto const name{ module::module_to_load_function(module) }; auto const form{ runtime::conj( - runtime::conj(runtime::conj(make_box(std::move(forms)), + runtime::conj(runtime::conj(make_box(jtl::move(forms)), obj::persistent_vector::empty()), make_box(name)), make_box("fn*")) }; @@ -208,6 +204,7 @@ namespace jank::runtime } else { + profile::timer const timer{ "rt compile-module parse + write" }; codegen::processor cg_prc{ fn, module, codegen::compilation_target::module }; //util::println("{}\n", util::format_cpp_source(cg_prc.declaration_str()).expect_ok()); auto const code{ cg_prc.declaration_str() }; @@ -228,8 +225,7 @@ namespace jank::runtime return ret; } - jtl::result - context::eval_cpp_string(jtl::immutable_string_view const &code) const + jtl::result context::eval_cpp_string(jtl::immutable_string const &code) const { profile::timer const timer{ "rt eval_cpp_string" }; @@ -259,7 +255,7 @@ namespace jank::runtime return ok(); } - object_ref context::read_string(jtl::immutable_string_view const &code) + object_ref context::read_string(jtl::immutable_string const &code) { profile::timer const timer{ "rt read_string" }; @@ -281,7 +277,7 @@ namespace jank::runtime } native_vector - context::analyze_string(jtl::immutable_string_view const &code, bool const eval) + context::analyze_string(jtl::immutable_string const &code, bool const eval) { profile::timer const timer{ "rt analyze_string" }; read::lex::processor l_prc{ code }; @@ -311,7 +307,7 @@ namespace jank::runtime } jtl::result - context::load_module(jtl::immutable_string_view const &module, module::origin const ori) + context::load_module(jtl::immutable_string const &module, module::origin const ori) { auto const ns(current_ns()); @@ -342,13 +338,17 @@ namespace jank::runtime { return error::runtime_unable_to_load_module(e.what()); } - catch(object_ref const &e) + catch(object_ref const e) { return error::runtime_unable_to_load_module(runtime::to_code_string(e)); } + catch(error_ref const e) + { + return error::runtime_unable_to_load_module(e); + } } - jtl::result context::compile_module(jtl::immutable_string_view const &module) + jtl::result context::compile_module(jtl::immutable_string const &module) { module_dependencies.clear(); @@ -421,26 +421,30 @@ namespace jank::runtime return unique_namespaced_string("G_"); } - jtl::immutable_string - context::unique_namespaced_string(jtl::immutable_string_view const &prefix) const + jtl::immutable_string context::unique_namespaced_string(jtl::immutable_string const &prefix) const { static jtl::immutable_string const dot{ "\\." }; auto const ns{ current_ns() }; return util::format("{}-{}-{}", runtime::munge_and_replace(ns->name->get_name(), dot, "_"), - prefix.data(), + prefix.c_str(), ++ns->symbol_counter); } + jtl::immutable_string context::unique_string() const + { + return unique_string("G_"); + } + jtl::immutable_string context::unique_munged_string() const { return munge(unique_namespaced_string()); } - jtl::immutable_string - context::unique_munged_string(jtl::immutable_string_view const &prefix) const + jtl::immutable_string context::unique_string(jtl::immutable_string const &prefix) const { - return munge(unique_namespaced_string(prefix)); + auto const ns{ current_ns() }; + return util::format("{}-{}", prefix.c_str(), ++ns->symbol_counter); } obj::symbol context::unique_symbol() const @@ -448,7 +452,7 @@ namespace jank::runtime return unique_symbol("G-"); } - obj::symbol context::unique_symbol(jtl::immutable_string_view const &prefix) const + obj::symbol context::unique_symbol(jtl::immutable_string const &prefix) const { return { "", unique_namespaced_string(prefix) }; } @@ -517,6 +521,12 @@ namespace jank::runtime return expect_object(current_ns_var->deref()); } + jtl::result + context::intern_var(jtl::immutable_string const &qualified_name) + { + return intern_var(make_box(qualified_name)); + } + jtl::result context::intern_var(jtl::immutable_string const &ns, jtl::immutable_string const &name) { @@ -550,6 +560,12 @@ namespace jank::runtime return intern_owned_var(make_box(ns, name)); } + jtl::result + context::intern_owned_var(jtl::immutable_string const &qualified_name) + { + return intern_owned_var(make_box(qualified_name)); + } + jtl::result context::intern_owned_var(obj::symbol_ref const &qualified_sym) { @@ -705,7 +721,7 @@ namespace jank::runtime jtl::string_result context::push_thread_bindings() { auto bindings(obj::persistent_hash_map::empty()); - auto &tbfs(thread_binding_frames[this]); + auto &tbfs(thread_binding_frames[std::this_thread::get_id()]); if(!tbfs.empty()) { bindings = tbfs.front().bindings; @@ -734,7 +750,7 @@ namespace jank::runtime context::push_thread_bindings(obj::persistent_hash_map_ref const bindings) { thread_binding_frame frame{ obj::persistent_hash_map::empty() }; - auto &tbfs(thread_binding_frames[this]); + auto &tbfs(thread_binding_frames[std::this_thread::get_id()]); if(!tbfs.empty()) { frame.bindings = tbfs.front().bindings; @@ -777,7 +793,7 @@ namespace jank::runtime jtl::string_result context::pop_thread_bindings() { - auto &tbfs(thread_binding_frames[this]); + auto &tbfs(thread_binding_frames[std::this_thread::get_id()]); if(tbfs.empty()) { return err("Mismatched thread binding pop"); @@ -790,7 +806,7 @@ namespace jank::runtime obj::persistent_hash_map_ref context::get_thread_bindings() const { - auto const &tbfs(thread_binding_frames[this]); + auto const &tbfs(thread_binding_frames[std::this_thread::get_id()]); if(tbfs.empty()) { return obj::persistent_hash_map::empty(); @@ -800,7 +816,7 @@ namespace jank::runtime jtl::option context::current_thread_binding_frame() { - auto &tbfs(thread_binding_frames[this]); + auto &tbfs(thread_binding_frames[std::this_thread::get_id()]); if(tbfs.empty()) { return none; diff --git a/compiler+runtime/src/cpp/jank/runtime/core/munge.cpp b/compiler+runtime/src/cpp/jank/runtime/core/munge.cpp index 1c508e91e..449eaffa4 100644 --- a/compiler+runtime/src/cpp/jank/runtime/core/munge.cpp +++ b/compiler+runtime/src/cpp/jank/runtime/core/munge.cpp @@ -28,6 +28,8 @@ namespace jank::runtime { '}', "_RBRACE_" }, { '[', "_LBRACK_" }, { ']', "_RBRACK_" }, + { '(', "_LPAREN_" }, + { ')', "_RPAREN_" }, { '/', "_SLASH_" }, { '\\', "_BSLASH_" }, { '?', "_QMARK_" } @@ -40,6 +42,8 @@ namespace jank::runtime { "_RBRACE_", '}' }, { "_LBRACK_", '[' }, { "_RBRACK_", ']' }, + { "_LPAREN_", '(' }, + { "_RPAREN_", ')' }, { "_BSLASH_", '\\' }, { "_SQUOTE_", '\'' }, { "_DQUOTE_", '"' }, diff --git a/compiler+runtime/src/cpp/jank/runtime/detail/type.cpp b/compiler+runtime/src/cpp/jank/runtime/detail/type.cpp index cd8a24c82..b8c4abafd 100644 --- a/compiler+runtime/src/cpp/jank/runtime/detail/type.cpp +++ b/compiler+runtime/src/cpp/jank/runtime/detail/type.cpp @@ -22,11 +22,3 @@ namespace immer std::equal_to, jank::memory_policy>; } - -namespace bpptree::detail -{ - template struct BppTreeSet; - template struct BppTreeMap; -} diff --git a/compiler+runtime/src/cpp/jank/runtime/module/loader.cpp b/compiler+runtime/src/cpp/jank/runtime/module/loader.cpp index 2687ff978..d4f613a02 100644 --- a/compiler+runtime/src/cpp/jank/runtime/module/loader.cpp +++ b/compiler+runtime/src/cpp/jank/runtime/module/loader.cpp @@ -992,8 +992,19 @@ namespace jank::runtime::module __rt_ctx->jit_prc.load_object(entry.path); } - auto const load{ __rt_ctx->jit_prc.find_symbol(load_function_name).expect_ok() }; - reinterpret_cast(load)(); + /* For C++ codegen, we use extern "C" for the load fn to ensure consistent linkage + * across the static/dynamic boundary. While it's technically UB to throw exceptions + * across C boundaries, we know that both sides are C++ and the unwinder should handle it. */ + auto const load_fn_res{ __rt_ctx->jit_prc.find_symbol(load_function_name) }; + if(load_fn_res.is_ok()) + { + reinterpret_cast(load_fn_res.expect_ok())(); + } + else + { + __rt_ctx->jit_prc.eval_string( + util::format("extern \"C\" void* {}(); {}();", load_function_name, load_function_name)); + } return ok(); } diff --git a/compiler+runtime/src/cpp/jank/runtime/ns.cpp b/compiler+runtime/src/cpp/jank/runtime/ns.cpp index fd971c22a..2306fe325 100644 --- a/compiler+runtime/src/cpp/jank/runtime/ns.cpp +++ b/compiler+runtime/src/cpp/jank/runtime/ns.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace jank::runtime { @@ -85,14 +86,12 @@ namespace jank::runtime if(redefined) { auto const v{ expect_object(*found_var) }; - /* TODO: Util for warning. */ - util::println( - stderr, - "WARNING: '{}' already referred to {} in namespace '{}' but has been replaced by {}", - unqualified_sym->to_string(), - v->to_code_string(), - name->to_string(), - new_var->to_code_string()); + error::warn( + util::format("'{}' already referred to {} in namespace '{}' but has been replaced by {}", + unqualified_sym->to_string(), + v->to_code_string(), + name->to_string(), + new_var->to_code_string())); } *locked_vars = make_box((*locked_vars)->data.set(unqualified_sym, new_var)); diff --git a/compiler+runtime/src/cpp/jank/runtime/obj/native_vector_sequence.cpp b/compiler+runtime/src/cpp/jank/runtime/obj/native_vector_sequence.cpp index 7603e8e3e..cea169687 100644 --- a/compiler+runtime/src/cpp/jank/runtime/obj/native_vector_sequence.cpp +++ b/compiler+runtime/src/cpp/jank/runtime/obj/native_vector_sequence.cpp @@ -12,13 +12,13 @@ namespace jank::runtime::obj } native_vector_sequence::native_vector_sequence(native_vector &&data) - : data{ std::move(data) } + : data{ jtl::move(data) } { jank_debug_assert(!this->data.empty()); } native_vector_sequence::native_vector_sequence(native_vector &&data, usize index) - : data{ std::move(data) } + : data{ jtl::move(data) } , index{ index } { jank_debug_assert(!this->data.empty()); @@ -26,7 +26,7 @@ namespace jank::runtime::obj native_vector_sequence::native_vector_sequence(jtl::option const &meta, native_vector &&data) - : data{ std::move(data) } + : data{ jtl::move(data) } , meta{ meta } { } diff --git a/compiler+runtime/src/cpp/jank/runtime/obj/persistent_hash_map.cpp b/compiler+runtime/src/cpp/jank/runtime/obj/persistent_hash_map.cpp index 406a1deec..793e95923 100644 --- a/compiler+runtime/src/cpp/jank/runtime/obj/persistent_hash_map.cpp +++ b/compiler+runtime/src/cpp/jank/runtime/obj/persistent_hash_map.cpp @@ -40,6 +40,12 @@ namespace jank::runtime::obj { } + persistent_hash_map_ref persistent_hash_map::empty() + { + static auto const ret(make_box()); + return ret; + } + persistent_hash_map_ref persistent_hash_map::create_from_seq(object_ref const seq) { return make_box(visit_seqable( diff --git a/compiler+runtime/src/cpp/jank/runtime/obj/persistent_sorted_map.cpp b/compiler+runtime/src/cpp/jank/runtime/obj/persistent_sorted_map.cpp index 23fbbea2d..212a26131 100644 --- a/compiler+runtime/src/cpp/jank/runtime/obj/persistent_sorted_map.cpp +++ b/compiler+runtime/src/cpp/jank/runtime/obj/persistent_sorted_map.cpp @@ -56,9 +56,9 @@ namespace jank::runtime::obj typed_seq->to_string()) }; } auto const val(*it); - transient.insert_or_assign(key, val); + transient[key] = val; } - return transient.persistent(); + return transient; } else { @@ -100,19 +100,21 @@ namespace jank::runtime::obj bool persistent_sorted_map::contains(object_ref const key) const { - return data.find(key) != data.end(); + return data.contains(key); } persistent_sorted_map_ref persistent_sorted_map::assoc(object_ref const key, object_ref const val) const { - auto copy(data.insert_or_assign(key, val)); + auto copy(data); + copy[key] = val; return make_box(meta, std::move(copy)); } persistent_sorted_map_ref persistent_sorted_map::dissoc(object_ref const key) const { - auto copy(data.erase_key(key)); + auto copy(data); + copy.erase(key); return make_box(meta, std::move(copy)); } diff --git a/compiler+runtime/src/cpp/jank/runtime/obj/persistent_sorted_set.cpp b/compiler+runtime/src/cpp/jank/runtime/obj/persistent_sorted_set.cpp index 2516fc267..283d1bef3 100644 --- a/compiler+runtime/src/cpp/jank/runtime/obj/persistent_sorted_set.cpp +++ b/compiler+runtime/src/cpp/jank/runtime/obj/persistent_sorted_set.cpp @@ -41,9 +41,9 @@ namespace jank::runtime::obj runtime::detail::native_transient_sorted_set transient; for(auto const e : make_sequence_range(typed_seq)) { - transient.insert_v(e); + transient.insert(e); } - return transient.persistent(); + return transient; }, seq)); } @@ -130,8 +130,9 @@ namespace jank::runtime::obj persistent_sorted_set_ref persistent_sorted_set::conj(object_ref const head) const { - auto set(data.insert_v(head)); - auto ret(make_box(meta, std::move(set))); + auto copy(data); + copy.insert(head); + auto ret(make_box(meta, std::move(copy))); return ret; } @@ -140,7 +141,7 @@ namespace jank::runtime::obj auto const found(data.find(o)); if(found != data.end()) { - return found.get(); + return *found; } return jank_nil; } @@ -152,13 +153,14 @@ namespace jank::runtime::obj bool persistent_sorted_set::contains(object_ref const o) const { - return data.find(o) != data.end(); + return data.contains(o); } persistent_sorted_set_ref persistent_sorted_set::disj(object_ref const o) const { - auto set(data.erase_key(o)); - auto ret(make_box(meta, std::move(set))); + auto copy(data); + copy.erase(o); + auto ret(make_box(meta, std::move(copy))); return ret; } } diff --git a/compiler+runtime/src/cpp/jank/runtime/obj/transient_sorted_map.cpp b/compiler+runtime/src/cpp/jank/runtime/obj/transient_sorted_map.cpp index 7fbb68553..530c8e4cb 100644 --- a/compiler+runtime/src/cpp/jank/runtime/obj/transient_sorted_map.cpp +++ b/compiler+runtime/src/cpp/jank/runtime/obj/transient_sorted_map.cpp @@ -9,17 +9,12 @@ namespace jank::runtime::obj { - transient_sorted_map::transient_sorted_map(runtime::detail::native_persistent_sorted_map &&d) - : data{ std::move(d).transient() } - { - } - transient_sorted_map::transient_sorted_map(runtime::detail::native_persistent_sorted_map const &d) - : data{ d.transient() } + : data{ d } { } - transient_sorted_map::transient_sorted_map(runtime::detail::native_transient_sorted_map &&d) + transient_sorted_map::transient_sorted_map(runtime::detail::native_persistent_sorted_map &&d) : data{ std::move(d) } { } @@ -100,21 +95,21 @@ namespace jank::runtime::obj bool transient_sorted_map::contains(object_ref const key) const { assert_active(); - return data.find(key) != data.end(); + return data.contains(key); } transient_sorted_map_ref transient_sorted_map::assoc_in_place(object_ref const key, object_ref const val) { assert_active(); - data.insert_or_assign(key, val); + data[key] = val; return this; } transient_sorted_map_ref transient_sorted_map::dissoc_in_place(object_ref const key) { assert_active(); - data.erase_key(key); + data.erase(key); return this; } @@ -151,7 +146,7 @@ namespace jank::runtime::obj { assert_active(); active = false; - return make_box(std::move(data).persistent()); + return make_box(std::move(data)); } object_ref transient_sorted_map::call(object_ref const o) const diff --git a/compiler+runtime/src/cpp/jank/runtime/obj/transient_sorted_set.cpp b/compiler+runtime/src/cpp/jank/runtime/obj/transient_sorted_set.cpp index 0e92a615a..cba4ce9af 100644 --- a/compiler+runtime/src/cpp/jank/runtime/obj/transient_sorted_set.cpp +++ b/compiler+runtime/src/cpp/jank/runtime/obj/transient_sorted_set.cpp @@ -6,17 +6,12 @@ namespace jank::runtime::obj { - transient_sorted_set::transient_sorted_set(runtime::detail::native_persistent_sorted_set &&d) - : data{ std::move(d).transient() } - { - } - transient_sorted_set::transient_sorted_set(runtime::detail::native_persistent_sorted_set const &d) - : data{ d.transient() } + : data{ d } { } - transient_sorted_set::transient_sorted_set(runtime::detail::native_transient_sorted_set &&d) + transient_sorted_set::transient_sorted_set(runtime::detail::native_persistent_sorted_set &&d) : data{ std::move(d) } { } @@ -64,7 +59,7 @@ namespace jank::runtime::obj transient_sorted_set_ref transient_sorted_set::conj_in_place(object_ref const elem) { assert_active(); - data.insert_v(elem); + data.insert(elem); return this; } @@ -72,7 +67,7 @@ namespace jank::runtime::obj { assert_active(); active = false; - return make_box(data.persistent()); + return make_box(data); } object_ref transient_sorted_set::call(object_ref const elem) @@ -81,7 +76,7 @@ namespace jank::runtime::obj auto const found(data.find(elem)); if(found != data.end()) { - return found.get(); + return *found; } return jank_nil; } @@ -92,7 +87,7 @@ namespace jank::runtime::obj auto const found(data.find(elem)); if(found != data.end()) { - return found.get(); + return *found; } return fallback; } @@ -109,11 +104,10 @@ namespace jank::runtime::obj object_ref transient_sorted_set::get_entry(object_ref const elem) { - auto const found = call(elem); - auto const nil(jank_nil); - if(found == nil) + auto const found{ call(elem) }; + if(found == jank_nil) { - return nil; + return found; } return make_box(std::in_place, found, found); @@ -122,13 +116,13 @@ namespace jank::runtime::obj bool transient_sorted_set::contains(object_ref const elem) const { assert_active(); - return data.find(elem) != data.end(); + return data.contains(elem); } transient_sorted_set_ref transient_sorted_set::disjoin_in_place(object_ref const elem) { assert_active(); - data.erase_key(elem); + data.erase(elem); return this; } diff --git a/compiler+runtime/src/cpp/jank/runtime/obj/uuid.cpp b/compiler+runtime/src/cpp/jank/runtime/obj/uuid.cpp index 38717e840..ef9634ebb 100644 --- a/compiler+runtime/src/cpp/jank/runtime/obj/uuid.cpp +++ b/compiler+runtime/src/cpp/jank/runtime/obj/uuid.cpp @@ -17,7 +17,7 @@ namespace jank::runtime::obj static jtl::ref from_string(jtl::immutable_string const &s) { - auto const result = uuids::uuid::from_string(s.c_str()); + auto const result{ uuids::uuid::from_string(s.c_str()) }; if(result) { return jtl::make_ref(result.value()); diff --git a/compiler+runtime/src/cpp/jank/runtime/var.cpp b/compiler+runtime/src/cpp/jank/runtime/var.cpp index 44f739671..788748899 100644 --- a/compiler+runtime/src/cpp/jank/runtime/var.cpp +++ b/compiler+runtime/src/cpp/jank/runtime/var.cpp @@ -143,6 +143,11 @@ namespace jank::runtime return this; } + obj::symbol_ref var::to_qualified_symbol() const + { + return make_box(n->name->name, name->name); + } + var_thread_binding_ref var::get_thread_binding() const { if(!thread_bound.load()) @@ -150,7 +155,7 @@ namespace jank::runtime return {}; } - auto &tbfs(__rt_ctx->thread_binding_frames[__rt_ctx]); + auto &tbfs(runtime::context::thread_binding_frames[std::this_thread::get_id()]); if(tbfs.empty()) { return {}; diff --git a/compiler+runtime/src/cpp/jank/ui/highlight.cpp b/compiler+runtime/src/cpp/jank/ui/highlight.cpp index 7c70d3812..b22afcf11 100644 --- a/compiler+runtime/src/cpp/jank/ui/highlight.cpp +++ b/compiler+runtime/src/cpp/jank/ui/highlight.cpp @@ -10,7 +10,7 @@ namespace jank::ui using namespace ftxui; /* TODO: Also support core fns? */ - static std::set const specials{ + static native_set const specials{ "def", "fn*", "fn", "let*", "let", "loop*", "loop", "do", "if", "quote", "var", "try", "catch", "finally", "throw", "letfn*", }; diff --git a/compiler+runtime/src/cpp/jank/util/clang_format.cpp b/compiler+runtime/src/cpp/jank/util/clang_format.cpp index 820a2aaf9..07176c5cc 100644 --- a/compiler+runtime/src/cpp/jank/util/clang_format.cpp +++ b/compiler+runtime/src/cpp/jank/util/clang_format.cpp @@ -68,6 +68,7 @@ namespace jank::util { return err(llvm::toString(formatted_code.takeError())); } - return ok(jtl::immutable_string{ *formatted_code }); + jtl::immutable_string const ret{ *formatted_code }; + return ok(ret); } } diff --git a/compiler+runtime/src/cpp/jank/util/cli.cpp b/compiler+runtime/src/cpp/jank/util/cli.cpp index e48405128..ab4e6dfc0 100644 --- a/compiler+runtime/src/cpp/jank/util/cli.cpp +++ b/compiler+runtime/src/cpp/jank/util/cli.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace jank::util::cli { @@ -47,12 +48,12 @@ namespace jank::util::cli ->check(CLI::Range(0, 3)); std::map const codegen_types{ - { "llvm_ir", codegen_type::llvm_ir }, + { "llvm-ir", codegen_type::llvm_ir }, { "cpp", codegen_type::cpp } }; cli.add_option("--codegen", opts.codegen, "The type of code generation to use.") - ->transform(CLI::CheckedTransformer(codegen_types).description("{llvm_ir,cpp}")) - ->default_str(make_default("llvm_ir")); + ->transform(CLI::CheckedTransformer(codegen_types).description("{llvm-ir,cpp}")) + ->default_str(make_default(codegen_type_str(opts.codegen))); /* Native dependencies. */ cli.add_option("-I,--include-dir", @@ -175,6 +176,12 @@ namespace jank::util::cli opts.command = command::check_health; } + if(opts.codegen == codegen_type::llvm_ir) + { + error::warn( + "LLVM IR code generation is currently unstable and incomplete. Use at your own risk."); + } + return ok(); } diff --git a/compiler+runtime/src/cpp/jank/util/try.cpp b/compiler+runtime/src/cpp/jank/util/try.cpp index 0a4430917..c50e77d98 100644 --- a/compiler+runtime/src/cpp/jank/util/try.cpp +++ b/compiler+runtime/src/cpp/jank/util/try.cpp @@ -47,7 +47,7 @@ namespace jank::util * at the start/end of each stack trace. */ static bool filter_frame(cpptrace::stacktrace_frame const &frame) { - static std::set const symbols_to_ignore{ + static native_set const symbols_to_ignore{ /* (Top) Linux exception pipework. */ "get_adjusted_ptr", "__gxx_personality_v0", diff --git a/compiler+runtime/src/cpp/jtl/string_builder.cpp b/compiler+runtime/src/cpp/jtl/string_builder.cpp index ef7de4a32..3c683c534 100644 --- a/compiler+runtime/src/cpp/jtl/string_builder.cpp +++ b/compiler+runtime/src/cpp/jtl/string_builder.cpp @@ -12,16 +12,12 @@ namespace jtl using allocator_type = jank::native_allocator; using allocator_traits = std::allocator_traits; - /* NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) */ - static allocator_type allocator; - static void realloc(string_builder &sb, usize const required) { auto const new_capacity{ std::bit_ceil(required) }; - /* TODO: Pointer-free GC alloc. */ - auto const new_data{ allocator_traits::allocate(allocator, new_capacity) }; + auto const new_data{ reinterpret_cast(GC_malloc_atomic(new_capacity)) }; string_builder::traits_type::copy(new_data, sb.buffer, sb.pos); - allocator_traits::deallocate(allocator, sb.buffer, sb.pos); + GC_free(sb.buffer); sb.buffer = new_data; sb.capacity = new_capacity; } @@ -103,7 +99,7 @@ namespace jtl string_builder::~string_builder() { - allocator_traits::deallocate(allocator, buffer, pos); + GC_free(buffer); } string_builder &string_builder::operator()(bool const d) & diff --git a/compiler+runtime/src/jank/clojure/core.jank b/compiler+runtime/src/jank/clojure/core.jank index 9c2cd137a..224762ed8 100644 --- a/compiler+runtime/src/jank/clojure/core.jank +++ b/compiler+runtime/src/jank/clojure/core.jank @@ -4228,7 +4228,7 @@ (if load (try (load lib need-ns? require) - (catch e + (catch cpp/jank.runtime.object_ref e (when undefined-on-entry? (remove-ns lib)) (throw e))) diff --git a/compiler+runtime/src/jank/clojure/test.jank b/compiler+runtime/src/jank/clojure/test.jank index 7ff5b83f9..195818a44 100644 --- a/compiler+runtime/src/jank/clojure/test.jank +++ b/compiler+runtime/src/jank/clojure/test.jank @@ -415,7 +415,7 @@ "Like var-get but returns nil if the var is unbound." [v] (try (var-get v) - (catch _))) + (catch cpp/jank.runtime.object_ref _))) (defn function? "Returns true if argument is a function or a symbol that resolves to @@ -502,7 +502,7 @@ `(try (do ~@body) (do-report {:type :fail :message ~msg :expected '~form :actual nil}) - (catch e# + (catch cpp/jank.runtime.object_ref e# (do-report {:type :pass :message ~msg :expected '~form :actual e#}) e#)))) @@ -516,7 +516,7 @@ body (nthnext form 2)] `(try (do ~@body) (do-report {:type :fail :message ~msg :expected '~form :actual nil}) - (catch e# + (catch cpp/jank.runtime.object_ref e# (let [m# (ex-message e#)] (if (and (string? m#) (re-find ~re m#)) (do-report {:type :pass :message ~msg @@ -531,7 +531,7 @@ You don't call this." [msg form] `(try (do ~(assert-expr msg form)) - (catch t# + (catch cpp/jank.runtime.object_ref t# (do-report {:type :error :message ~msg :expected '~form :actual t#})))) @@ -715,7 +715,7 @@ (do-report {:type :begin-test-var :var v}) (inc-report-counter :test) (try (t) - (catch e + (catch cpp/jank.runtime.object_ref e (do-report {:type :error :message "Uncaught exception, not in assertion." :expected nil :actual e}))) (do-report {:type :end-test-var :var v})))) diff --git a/compiler+runtime/test/bash/error-reporting/bin/jank/test/error_reporting.clj b/compiler+runtime/test/bash/error-reporting/bin/jank/test/error_reporting.clj index f88971779..453727800 100755 --- a/compiler+runtime/test/bash/error-reporting/bin/jank/test/error_reporting.clj +++ b/compiler+runtime/test/bash/error-reporting/bin/jank/test/error_reporting.clj @@ -27,7 +27,8 @@ "Removes ANSI color codes from the given string." [s] (-> (clojure.string/replace s #"\x1B\[[0-9;]*[mK]" "") - (clojure.string/replace #"\r\n" "\n"))) + (clojure.string/replace #"\r\n" "\n") + (clojure.string/replace (str (char 0)) ""))) (defn find-tests! [] (let [inputs (b.f/glob src-dir "**/input.jank")] @@ -62,7 +63,7 @@ (doseq [test tests] (print "testing dir" (b.f/file-name (:dir test)) "=> ") (let [expected (try - (slurp (:output-file test)) + (strip-ansi-codes (string/trim (slurp (:output-file test)))) (catch Exception _ ""))] (if (= (:output test) expected) diff --git a/compiler+runtime/test/bash/error-reporting/src/jar/output.txt b/compiler+runtime/test/bash/error-reporting/src/jar/output.txt index 83105bda7..a7e06fd52 100644 Binary files a/compiler+runtime/test/bash/error-reporting/src/jar/output.txt and b/compiler+runtime/test/bash/error-reporting/src/jar/output.txt differ diff --git a/compiler+runtime/test/cpp/jtl/string_builder.cpp b/compiler+runtime/test/cpp/jtl/string_builder.cpp index 61f0d4b94..bcfcdde96 100644 --- a/compiler+runtime/test/cpp/jtl/string_builder.cpp +++ b/compiler+runtime/test/cpp/jtl/string_builder.cpp @@ -89,6 +89,24 @@ namespace jtl CHECK_EQ("3.140000", sb.view()); } + TEST_CASE("infinity") + { + string_builder sb; + sb(INFINITY); + CHECK_EQ(3, sb.pos); + CHECK_EQ(initial_capacity, sb.capacity); + CHECK_EQ("inf", sb.view()); + } + + TEST_CASE("nan") + { + string_builder sb; + sb(NAN); + CHECK_EQ(3, sb.pos); + CHECK_EQ(initial_capacity, sb.capacity); + CHECK_EQ("nan", sb.view()); + } + TEST_CASE("char32_t") { string_builder sb; diff --git a/compiler+runtime/test/jank/cpp/auto-box/pass-closure.jank b/compiler+runtime/test/jank/cpp/auto-box/pass-closure.jank index b0640ac19..a07fd8182 100644 --- a/compiler+runtime/test/jank/cpp/auto-box/pass-closure.jank +++ b/compiler+runtime/test/jank/cpp/auto-box/pass-closure.jank @@ -1,9 +1,9 @@ (let [i (cpp/int. 5) - ifn (fn* [] + ifn (fn* capture-i [] (assert (= 5 i))) _ (ifn) f (cpp/float. 5.0) - ffn (fn* [] + ffn (fn* capture-f [] (if (= 5.0 f) :success))] (ffn)) diff --git a/compiler+runtime/test/jank/cpp/constructor/complex/fail-template-instantiation.jank b/compiler+runtime/test/jank/cpp/constructor/complex/fail-template-instantiation.jank index 59b73e64a..9f60209bc 100644 --- a/compiler+runtime/test/jank/cpp/constructor/complex/fail-template-instantiation.jank +++ b/compiler+runtime/test/jank/cpp/constructor/complex/fail-template-instantiation.jank @@ -10,6 +10,6 @@ std::string a{}; }; }") -(let* [arg (cpp/int 5) +(let* [arg (cpp/int* cpp/nullptr) _ (cpp/jank.cpp.constructor.complex.fail_template_instantiation.foo arg)] :success) diff --git a/compiler+runtime/test/jank/cpp/global-value/pass-static-non-copyable.jank b/compiler+runtime/test/jank/cpp/global-value/pass-static-non-copyable.jank new file mode 100644 index 000000000..bf87c2095 --- /dev/null +++ b/compiler+runtime/test/jank/cpp/global-value/pass-static-non-copyable.jank @@ -0,0 +1,25 @@ +(cpp/raw "namespace jank::cpp::global_value::pass_static_non_copyable + { + struct foo + { + foo() = delete; + foo(foo const&) = delete; + foo(foo &&) = delete; + foo(int i) + : data{ i } + { } + + int data{}; + }; + + struct bar + { + static foo const boop; + }; + foo const bar::boop{ 5 }; + }") + +(let* [foo cpp/jank.cpp.global_value.pass_static_non_copyable.bar.boop] + (if (= 5 (cpp/.-data foo)) + :success + :failure)) diff --git a/compiler+runtime/test/jank/cpp/global-value/pass-static.jank b/compiler+runtime/test/jank/cpp/global-value/pass-static.jank index ccbe5224a..54a3ab721 100644 --- a/compiler+runtime/test/jank/cpp/global-value/pass-static.jank +++ b/compiler+runtime/test/jank/cpp/global-value/pass-static.jank @@ -1,10 +1,10 @@ (cpp/raw "namespace jank::cpp::global_value::pass_static - { - struct foo - { - static int const boop{ 5 }; - }; - }") + { + struct foo + { + static int const boop{ 5 }; + }; + }") (if (= 5 cpp/jank.cpp.global_value.pass_static.foo.boop) :success :failure) diff --git a/compiler+runtime/test/jank/cpp/member/pass-static.jank b/compiler+runtime/test/jank/cpp/member/pass-static.jank index 56afb7ecd..582cb3460 100644 --- a/compiler+runtime/test/jank/cpp/member/pass-static.jank +++ b/compiler+runtime/test/jank/cpp/member/pass-static.jank @@ -2,8 +2,9 @@ { struct bar { - static const int a{ 5 }; + static int const a; }; + int const bar::a{ 5 }; }") (let* [b (cpp/jank.cpp.member.pass_static.bar.) a (cpp/.-a b)] diff --git a/compiler+runtime/test/jank/cpp/member/skip-static-const.jank b/compiler+runtime/test/jank/cpp/member/skip-static-const.jank new file mode 100644 index 000000000..757315a21 --- /dev/null +++ b/compiler+runtime/test/jank/cpp/member/skip-static-const.jank @@ -0,0 +1,11 @@ +(cpp/raw "namespace jank::cpp::member::pass_static_const + { + struct bar + { + static int const a{ 5 }; + }; + }") +(let* [b (cpp/jank.cpp.member.pass_static_const.bar.) + a (cpp/.-a b)] + (if (= 5 a) + :success)) diff --git a/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-char.jank b/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-char.jank new file mode 100644 index 000000000..f259c29b7 --- /dev/null +++ b/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-char.jank @@ -0,0 +1,13 @@ +;; Test catching C++ char exception +(cpp/raw "void throw_char_z() { throw 'z'; }") + +(assert + (= \z + (try + (cpp/throw_char_z) + (catch cpp/int i + (println "Wrong: caught int") + \0) + (catch cpp/char c c)))) + +:success diff --git a/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-double.jank b/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-double.jank new file mode 100644 index 000000000..0edea5775 --- /dev/null +++ b/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-double.jank @@ -0,0 +1,13 @@ +;; Test catching C++ double exception +(cpp/raw "void throw_double_pi() { throw 3.14159; }") + +(assert + (= 3.14159 + (try + (cpp/throw_double_pi) + (catch cpp/int i + (println "Wrong: caught int") + -1) + (catch cpp/double d d)))) + +:success diff --git a/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-float.jank b/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-float.jank new file mode 100644 index 000000000..6aeedac52 --- /dev/null +++ b/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-float.jank @@ -0,0 +1,13 @@ +;; Test catching C++ float exception +(cpp/raw "void throw_float_half() { throw 0.5f; }") + +(assert + (= 0.5 + (try + (cpp/throw_float_half) + (catch cpp/double d + (println "Wrong: caught double") + -1.0) + (catch cpp/float f f)))) + +:success diff --git a/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-int.jank b/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-int.jank new file mode 100644 index 000000000..0761fdc44 --- /dev/null +++ b/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-int.jank @@ -0,0 +1,13 @@ +;; Test catching C++ int exception +(cpp/raw "void throw_int_42() { throw 42; }") + +(assert + (= 42 + (try + (cpp/throw_int_42) + (catch cpp/double d + (println "Wrong: caught double") + -1) + (catch cpp/int i i)))) + +:success diff --git a/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-pointer.jank b/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-pointer.jank new file mode 100644 index 000000000..877987ef3 --- /dev/null +++ b/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-pointer.jank @@ -0,0 +1,16 @@ +;; Test catching C++ void* exception +(cpp/raw "using void_ptr = void*;") +(cpp/raw "void throw_void_ptr() { throw (void_ptr)0x1234; }") + +;; We can't easily assert the value of a void* in Jank yet without casting, +;; but we can verify we caught the right type. +(let [caught (atom false)] + (try + (cpp/throw_void_ptr) + (catch cpp/int i + (println "Wrong: caught int")) + (catch cpp/void_ptr p + (reset! caught true))) + (assert @caught "Should have caught void*")) + +:success diff --git a/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-std-invalid-argument.jank b/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-std-invalid-argument.jank new file mode 100644 index 000000000..e156c8c7c --- /dev/null +++ b/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-std-invalid-argument.jank @@ -0,0 +1,13 @@ +;; Test catching std::invalid_argument +(cpp/raw "void throw_invalid_argument() { throw std::invalid_argument(\"invalid argument\"); }") + +(let [caught (atom false)] + (try + (cpp/throw_invalid_argument) + (catch cpp/std.runtime_error e + (println "Wrong: caught runtime_error")) + (catch cpp/std.invalid_argument e + (reset! caught true))) + (assert @caught "Should have caught std::invalid_argument")) + +:success diff --git a/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-std-logic-error.jank b/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-std-logic-error.jank new file mode 100644 index 000000000..3af9f7634 --- /dev/null +++ b/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-std-logic-error.jank @@ -0,0 +1,13 @@ +;; Test catching std::logic_error +(cpp/raw "void throw_logic_error() { throw std::logic_error(\"logic error\"); }") + +(let [caught (atom false)] + (try + (cpp/throw_logic_error) + (catch cpp/std.runtime_error e + (println "Wrong: caught runtime_error")) + (catch cpp/std.logic_error e + (reset! caught true))) + (assert @caught "Should have caught std::logic_error")) + +:success diff --git a/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-std-out-of-range.jank b/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-std-out-of-range.jank new file mode 100644 index 000000000..bd8a72a50 --- /dev/null +++ b/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-std-out-of-range.jank @@ -0,0 +1,13 @@ +;; Test catching std::out_of_range +(cpp/raw "void throw_out_of_range() { throw std::out_of_range(\"out of range\"); }") + +(let [caught (atom false)] + (try + (cpp/throw_out_of_range) + (catch cpp/std.runtime_error e + (println "Wrong: caught runtime_error")) + (catch cpp/std.out_of_range e + (reset! caught true))) + (assert @caught "Should have caught std::out_of_range")) + +:success diff --git a/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-unsigned-int.jank b/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-unsigned-int.jank new file mode 100644 index 000000000..5ba1589f2 --- /dev/null +++ b/compiler+runtime/test/jank/cpp/try/pass-catch-cpp-unsigned-int.jank @@ -0,0 +1,14 @@ +;; Test catching C++ unsigned int exception +(cpp/raw "using my_uint = unsigned int;") +(cpp/raw "void throw_uint_42() { throw (my_uint)42; }") + +(assert + (= 42 + (try + (cpp/throw_uint_42) + (catch cpp/int i + (println "Wrong: caught int") + -1) + (catch cpp/my_uint u u)))) + +:success diff --git a/compiler+runtime/test/jank/cpp/try/pass-local-reference-to-cpp-value.jank b/compiler+runtime/test/jank/cpp/try/pass-local-reference-to-cpp-value.jank deleted file mode 100644 index f6fa7e92f..000000000 --- a/compiler+runtime/test/jank/cpp/try/pass-local-reference-to-cpp-value.jank +++ /dev/null @@ -1,4 +0,0 @@ -(let [bar cpp/true] - (try - (if bar - :success))) diff --git a/compiler+runtime/test/jank/cpp/try/pass-multiple-catch-cpp-classes.jank b/compiler+runtime/test/jank/cpp/try/pass-multiple-catch-cpp-classes.jank new file mode 100644 index 000000000..cd180327b --- /dev/null +++ b/compiler+runtime/test/jank/cpp/try/pass-multiple-catch-cpp-classes.jank @@ -0,0 +1,53 @@ +;; Test multiple catch with C++ class types +(cpp/raw "void throw_runtime_error() { throw std::runtime_error(\"test error\"); }") +(cpp/raw "void throw_logic_error() { throw std::logic_error(\"logic error\"); }") + +;; Test catching runtime_error +(try + (cpp/throw_runtime_error) + (catch cpp/int i + (assert false "Should not catch int")) + (catch cpp/std.runtime_error e + :caught-runtime-error) + (catch cpp/std.logic_error e + (assert false "Should not catch logic_error"))) + +;; Test catching logic_error +(try + (cpp/throw_logic_error) + (catch cpp/std.runtime_error e + (assert false "Should not catch runtime_error")) + (catch cpp/std.logic_error e + :caught-logic-error)) + +;; Test multiple C++ types in one try +(cpp/raw "void throw_based_on_value(int v) { + if (v == 1) throw 1; + if (v == 2) throw 2.0; + throw std::runtime_error(\"error\"); +}") + +(assert (= 1 + (try + (cpp/throw_based_on_value 1) + (catch cpp/double d 0.0) + (catch cpp/int i i) + (catch cpp/std.runtime_error e -1)))) + +(assert (= 2.0 + (try + (cpp/throw_based_on_value 2) + (catch cpp/int i 0) + (catch cpp/double d d) + (catch cpp/std.runtime_error e -1)))) + +(try + (cpp/throw_based_on_value 3) + (catch cpp/int i + (assert false "Should not catch int")) + (catch cpp/double d + (assert false "Should not catch double")) + (catch cpp/std.runtime_error e + :caught-runtime-error)) + +:success diff --git a/compiler+runtime/test/jank/cpp/try/pass-multiple-catch-cpp-with-finally.jank b/compiler+runtime/test/jank/cpp/try/pass-multiple-catch-cpp-with-finally.jank new file mode 100644 index 000000000..581eced94 --- /dev/null +++ b/compiler+runtime/test/jank/cpp/try/pass-multiple-catch-cpp-with-finally.jank @@ -0,0 +1,37 @@ +;; Test multiple catch with C++ exceptions and finally blocks +(cpp/raw "void throw_cpp_double() { throw 5.5; }") + +(let [cleanup (atom false)] + (assert (= 5.5 + (try + (cpp/throw_cpp_double) + (catch cpp/int i + (println "Wrong type")) + (catch cpp/double d + d) + (catch cpp/std.runtime_error e + -1.0) + (finally + (reset! cleanup true))))) + (assert (true? @cleanup) "Finally should execute")) + +;; Test finally executes even when exception is not caught +(cpp/raw "void throw_uncaught_type() { throw 'x'; }") + +(let [cleanup (atom false) + caught (atom false)] + (try + (try + (cpp/throw_uncaught_type) + (catch cpp/int i + (reset! caught true)) + (catch cpp/double d + (reset! caught true)) + (finally + (reset! cleanup true))) + (catch cpp/char c + :outer-caught)) + (assert (true? @cleanup) "Finally should execute before unwinding") + (assert (false? @caught) "No inner catch should match")) + +:success diff --git a/compiler+runtime/test/jank/cpp/try/pass-rethrow-cpp-exception.jank b/compiler+runtime/test/jank/cpp/try/pass-rethrow-cpp-exception.jank new file mode 100644 index 000000000..6c1ff3a7e --- /dev/null +++ b/compiler+runtime/test/jank/cpp/try/pass-rethrow-cpp-exception.jank @@ -0,0 +1,20 @@ +; throw_runtime_error4 is named with a "4" suffix because there is already throw_runtime_error, +; throw_runtime_error2, and throw_runtime_error3 defined in other files and different files are +; not run independently. +(cpp/raw "void throw_runtime_error4() { throw std::runtime_error(\"inner error\"); }") +(cpp/raw "void throw_current() { throw; }") + +(let [a (atom [])] + (try + (try + (cpp/throw_runtime_error4) + (catch cpp/std.runtime_error e + (swap! a conj :caught-inner) + (cpp/throw_current))) + ;; Rethrow using helper function + (catch cpp/std.runtime_error e + (swap! a conj :caught-outer))) + + (assert (= [:caught-inner :caught-outer] @a) (pr-str @a))) + +:success diff --git a/compiler+runtime/test/jank/cpp/try/pass-uncaught-cpp-exception-finally.jank b/compiler+runtime/test/jank/cpp/try/pass-uncaught-cpp-exception-finally.jank new file mode 100644 index 000000000..20d575a5a --- /dev/null +++ b/compiler+runtime/test/jank/cpp/try/pass-uncaught-cpp-exception-finally.jank @@ -0,0 +1,19 @@ +; throw_runtime_error2 is named with a "2" suffix because there is already another throw_runtime_error +; defined in different test jank file and different files are not run independently. +(cpp/raw "void throw_runtime_error2() { throw std::runtime_error(\"runtime error\"); }") + +(let [a (atom [])] + (try + (try + (cpp/throw_runtime_error2) + ;; No matching catch for runtime_error (inner catch is for logic_error) + (catch cpp/std.logic_error e + (swap! a conj :wrong-catch)) + (finally + (swap! a conj :finally-ran))) + (catch cpp/std.runtime_error e + (swap! a conj :caught-outer))) + + (assert (= [:finally-ran :caught-outer] @a) (pr-str @a))) + +:success diff --git a/compiler+runtime/test/jank/form/case/pass-real.jank b/compiler+runtime/test/jank/form/case/pass-real.jank index efb4ac9b3..4ebeabfb6 100644 --- a/compiler+runtime/test/jank/form/case/pass-real.jank +++ b/compiler+runtime/test/jank/form/case/pass-real.jank @@ -1,9 +1,8 @@ -(assert - (= - (case 3.14 3.14 :pi - 2.71 :e - 1.61 :phi - :default) - :pi)) +(assert (= (case 3.14 + 3.14 :pi + 2.71 :e + 1.61 :phi + :default) + :pi)) :success diff --git a/compiler+runtime/test/jank/form/fn/arity/variadic/pass-packing-exceeds-max-params.jank b/compiler+runtime/test/jank/form/fn/arity/variadic/pass-packing-exceeds-max-params.jank index 2986e57b1..eac3700fa 100644 --- a/compiler+runtime/test/jank/form/fn/arity/variadic/pass-packing-exceeds-max-params.jank +++ b/compiler+runtime/test/jank/form/fn/arity/variadic/pass-packing-exceeds-max-params.jank @@ -1,5 +1,5 @@ (def variadic - (fn* + (fn* variadic ([& args] args))) (assert (= (variadic 1 2 3 4 5 6 7 8 9) [1 2 3 4 5 6 7 8 9])) (assert (= (variadic 1 2 3 4 5 6 7 8 9 10) [1 2 3 4 5 6 7 8 9 10])) @@ -8,7 +8,7 @@ ; This will be code-generated, not evaluated. (def foo - (fn* [] + (fn* foo [] (assert (= (variadic 1 2 3 4 5 6 7 8 9 10 11) [1 2 3 4 5 6 7 8 9 10 11])) (assert (= (variadic 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15) [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15])))) (foo) diff --git a/compiler+runtime/test/jank/form/fn/closure/pass-recur-into-closure-skip-one.jank b/compiler+runtime/test/jank/form/fn/closure/pass-recur-into-closure-skip-one.jank index 8a577ddf9..3856cd189 100644 --- a/compiler+runtime/test/jank/form/fn/closure/pass-recur-into-closure-skip-one.jank +++ b/compiler+runtime/test/jank/form/fn/closure/pass-recur-into-closure-skip-one.jank @@ -1,8 +1,9 @@ -(let* [x :success] - ((fn* y [done?] - (if done? - x - ((fn* z [] - ((fn* a [] - (y true))))))) - false)) +((fn* [] + (let* [x :success] + ((fn* y [done?] + (if done? + x + ((fn* z [] + ((fn* a [] + (y true))))))) + false)))) diff --git a/compiler+runtime/test/jank/form/fn/closure/pass-recur-into-closure.jank b/compiler+runtime/test/jank/form/fn/closure/pass-recur-into-closure.jank index f98caacb2..36fec460a 100644 --- a/compiler+runtime/test/jank/form/fn/closure/pass-recur-into-closure.jank +++ b/compiler+runtime/test/jank/form/fn/closure/pass-recur-into-closure.jank @@ -1,6 +1,7 @@ -(let* [x :success] - (fn* y [] - x - (fn* [] - (y))) - x) +((fn* [] + (let* [x :success] + (fn* y [] + x + (fn* [] + (y))) + x))) diff --git a/compiler+runtime/test/jank/form/fn/closure/pass-recur-into-multi-arity-closure.jank b/compiler+runtime/test/jank/form/fn/closure/pass-recur-into-multi-arity-closure.jank index 8f0063374..02dc4dde5 100644 --- a/compiler+runtime/test/jank/form/fn/closure/pass-recur-into-multi-arity-closure.jank +++ b/compiler+runtime/test/jank/form/fn/closure/pass-recur-into-multi-arity-closure.jank @@ -1,9 +1,10 @@ -(let* [x :success +((fn* [] + (let* [x :success a 1] - ((fn* y - ([] - a - ((fn* z [] - (y 1)))) - ([_] - x)))) + ((fn* y + ([] + a + ((fn* z [] + (y 1)))) + ([_] + x)))))) diff --git a/compiler+runtime/test/jank/form/fn/named-recur/pass-recur-across-fn.jank b/compiler+runtime/test/jank/form/fn/named-recur/pass-recur-across-fn.jank index 38c330085..fcc833827 100644 --- a/compiler+runtime/test/jank/form/fn/named-recur/pass-recur-across-fn.jank +++ b/compiler+runtime/test/jank/form/fn/named-recur/pass-recur-across-fn.jank @@ -1,7 +1,8 @@ -(let* [result :success +((fn* [] + (let* [result :success foo (fn* foo [loop?] - ((fn* bar [] - (if loop? - (foo false) - result))))] - (foo true)) + ((fn* bar [] + (if loop? + (foo false) + result))))] + (foo true)))) diff --git a/compiler+runtime/test/jank/form/fn/named-recur/pass-recur-from-closure.jank b/compiler+runtime/test/jank/form/fn/named-recur/pass-recur-from-closure.jank index 74d9b37f0..3c8b70570 100644 --- a/compiler+runtime/test/jank/form/fn/named-recur/pass-recur-from-closure.jank +++ b/compiler+runtime/test/jank/form/fn/named-recur/pass-recur-from-closure.jank @@ -1,14 +1,13 @@ -(def outer-count 0) -(let [f (fn* outer [fn?] - (def outer-count (inc outer-count)) - (when fn? - (fn* inner [] - (outer false))))] - (assert (= nil (f false))) - (assert (= 1 outer-count)) - (def outer-count 0) - - (assert (= nil ((f true)))) - (assert (= 2 outer-count))) - -:success +((fn* [] + (let* [outer-count (atom 0) + f (fn* outer [fn?] + (swap! outer-count inc) + (when fn? + (fn* inner [] + (outer false))))] + (assert (= nil (f false))) + (assert (= 1 @outer-count)) +(reset! outer-count 0) +(assert (= nil ((f true)))) + (assert (= 2 @outer-count)) + :success))) diff --git a/compiler+runtime/test/jank/form/fn/named-recur/pass-recur-variadic-fn-across-non-variadic-fn.jank b/compiler+runtime/test/jank/form/fn/named-recur/pass-recur-variadic-fn-across-non-variadic-fn.jank index c8675bba8..d634b70ea 100644 --- a/compiler+runtime/test/jank/form/fn/named-recur/pass-recur-variadic-fn-across-non-variadic-fn.jank +++ b/compiler+runtime/test/jank/form/fn/named-recur/pass-recur-variadic-fn-across-non-variadic-fn.jank @@ -1,7 +1,8 @@ -(let* [result :success +((fn* [] + (let* [result :success foo (fn* foo [& loop?] - ((fn* bar [] - (if (first loop?) - (foo false) - result))))] - (foo true)) + ((fn* bar [] + (if (first loop?) + (foo false) + result))))] + (foo true)))) diff --git a/compiler+runtime/test/jank/form/try/fail-catch-after-finally.jank b/compiler+runtime/test/jank/form/try/fail-catch-after-finally.jank index 14532af20..5a533ee87 100644 --- a/compiler+runtime/test/jank/form/try/fail-catch-after-finally.jank +++ b/compiler+runtime/test/jank/form/try/fail-catch-after-finally.jank @@ -1,5 +1,3 @@ (try - (finally - ) - (catch _ - )) + (finally) + (catch cpp/jank.runtime.object_ref _)) diff --git a/compiler+runtime/test/jank/form/try/fail-duplicate-catch-type.jank b/compiler+runtime/test/jank/form/try/fail-duplicate-catch-type.jank new file mode 100644 index 000000000..00e127bba --- /dev/null +++ b/compiler+runtime/test/jank/form/try/fail-duplicate-catch-type.jank @@ -0,0 +1,7 @@ +;; Test that duplicate catch types are rejected at compile time +(try + (throw :test) + (catch cpp/jank.runtime.object_ref e1 + :first) + (catch cpp/jank.runtime.object_ref e2 + :second)) diff --git a/compiler+runtime/test/jank/form/try/fail-multiple-catch.jank b/compiler+runtime/test/jank/form/try/fail-multiple-catch.jank deleted file mode 100644 index 8681f12de..000000000 --- a/compiler+runtime/test/jank/form/try/fail-multiple-catch.jank +++ /dev/null @@ -1,5 +0,0 @@ -(try - (catch a - ) - (catch b - )) diff --git a/compiler+runtime/test/jank/form/try/fail-multiple-finally.jank b/compiler+runtime/test/jank/form/try/fail-multiple-finally.jank index 2c7bcad42..cbbe950ab 100644 --- a/compiler+runtime/test/jank/form/try/fail-multiple-finally.jank +++ b/compiler+runtime/test/jank/form/try/fail-multiple-finally.jank @@ -1,5 +1,3 @@ (try - (finally - ) - (finally - )) + (finally) + (finally)) diff --git a/compiler+runtime/test/jank/form/try/fail-other-form-after-finally.jank b/compiler+runtime/test/jank/form/try/fail-other-form-after-finally.jank index a2521023f..51b805a02 100644 --- a/compiler+runtime/test/jank/form/try/fail-other-form-after-finally.jank +++ b/compiler+runtime/test/jank/form/try/fail-other-form-after-finally.jank @@ -1,7 +1,5 @@ (try (+ 1 2) - (catch e - ) - (finally - ) + (catch cpp/jank.runtime.object_ref e) + (finally) :success) diff --git a/compiler+runtime/test/jank/form/try/fail-recur-in-catch.jank b/compiler+runtime/test/jank/form/try/fail-recur-in-catch.jank index 03c11b560..4ca22de57 100644 --- a/compiler+runtime/test/jank/form/try/fail-recur-in-catch.jank +++ b/compiler+runtime/test/jank/form/try/fail-recur-in-catch.jank @@ -1,5 +1,5 @@ (fn* [] (try :success - (catch _ + (catch cpp/jank.runtime.object_ref _ (recur)))) diff --git a/compiler+runtime/test/jank/form/try/fail-recur-in-finally.jank b/compiler+runtime/test/jank/form/try/fail-recur-in-finally.jank index 7138a4470..005010c48 100644 --- a/compiler+runtime/test/jank/form/try/fail-recur-in-finally.jank +++ b/compiler+runtime/test/jank/form/try/fail-recur-in-finally.jank @@ -1,7 +1,6 @@ (fn* [] (try :success - (catch _ - ) + (catch cpp/jank.runtime.object_ref _) (finally (recur)))) diff --git a/compiler+runtime/test/jank/form/try/fail-recur-in-try.jank b/compiler+runtime/test/jank/form/try/fail-recur-in-try.jank index 4d85496b0..d7e622b35 100644 --- a/compiler+runtime/test/jank/form/try/fail-recur-in-try.jank +++ b/compiler+runtime/test/jank/form/try/fail-recur-in-try.jank @@ -1,5 +1,4 @@ (fn* [] (try (recur) - (catch _ - ))) + (catch cpp/jank.runtime.object_ref _))) diff --git a/compiler+runtime/test/jank/form/try/pass-capture-within-let.jank b/compiler+runtime/test/jank/form/try/pass-capture-within-let.jank index d382fec06..97653c040 100644 --- a/compiler+runtime/test/jank/form/try/pass-capture-within-let.jank +++ b/compiler+runtime/test/jank/form/try/pass-capture-within-let.jank @@ -2,5 +2,5 @@ (try (let* [y x] y) - (catch _ - ))) + (catch cpp/jank.runtime.object_ref e + e))) diff --git a/compiler+runtime/test/jank/form/try/pass-catch-cpp-behavior.jank b/compiler+runtime/test/jank/form/try/pass-catch-cpp-behavior.jank new file mode 100644 index 000000000..18ab2be9f --- /dev/null +++ b/compiler+runtime/test/jank/form/try/pass-catch-cpp-behavior.jank @@ -0,0 +1,44 @@ +;; Tests for complex C++ exception behavior (rethrow, uncaught) + +(cpp/raw "namespace pass_catch_cpp_behavior + { + void throw_runtime_error() { throw std::runtime_error(\"error\"); } + void throw_current() { throw; } + }") + +;; 1. Rethrowing C++ exception +(let [a (atom []) + msg (atom nil)] + (try + (try + (cpp/pass_catch_cpp_behavior.throw_runtime_error) + (catch cpp/std.runtime_error e + (swap! a conj :caught-inner) + (cpp/pass_catch_cpp_behavior.throw_current))) + ;; Rethrow using helper function + (catch cpp/std.runtime_error e + (swap! a conj :caught-outer) + (reset! msg (cpp/.what e)))) + + (assert (= [:caught-inner :caught-outer] @a) (pr-str @a)) + (assert (= "error" @msg) "Should preserve exception message on rethrow")) + +;; 2. Uncaught C++ exception with finally +(let [a (atom []) + msg (atom nil)] + (try + (try + (cpp/pass_catch_cpp_behavior.throw_runtime_error) + ;; No matching catch for runtime_error (inner catch is for logic_error) + (catch cpp/std.logic_error e + (swap! a conj :wrong-catch)) + (finally + (swap! a conj :finally-ran))) + (catch cpp/std.runtime_error e + (swap! a conj :caught-outer) + (reset! msg (cpp/.what e)))) + + (assert (= [:finally-ran :caught-outer] @a) (pr-str @a)) + (assert (= "error" @msg) "Should catch exception with correct message")) + +:success diff --git a/compiler+runtime/test/jank/form/try/pass-catch-cpp-multiple-clauses.jank b/compiler+runtime/test/jank/form/try/pass-catch-cpp-multiple-clauses.jank new file mode 100644 index 000000000..a91c21778 --- /dev/null +++ b/compiler+runtime/test/jank/form/try/pass-catch-cpp-multiple-clauses.jank @@ -0,0 +1,46 @@ +;; Test multiple catch with C++ exceptions and finally blocks +(cpp/raw "namespace pass_multiple_catch_cpp_with_finally + { + void throw_uncaught_type() { throw 'x'; } + }") + +(cpp/raw "namespace pass_multiple_catch_cpp_with_finally + { + void throw_cpp_double() { throw 5.5; } + }") + +(let [cleanup (atom false)] + (assert + (= 5.5 + (try + (cpp/pass_multiple_catch_cpp_with_finally.throw_cpp_double) + (catch cpp/int i -1) + (catch cpp/double d d) + (catch cpp/std.runtime_error e -1.0) + (finally + (reset! cleanup true))))) + (assert (true? @cleanup) "Finally should execute")) + +;; Test finally executes even when exception is not caught +(let [cleanup (atom false) + caught (atom false)] + (try + (try + (cpp/pass_multiple_catch_cpp_with_finally.throw_uncaught_type) + (catch cpp/int i + (reset! caught true)) + (catch cpp/double d + (reset! caught true)) + (finally + (reset! cleanup true))) + (catch cpp/char c + c)) + (assert (true? @cleanup) "Finally should execute before unwinding") + (assert (false? @caught) "No inner catch should match") + (assert + (= \x + (try (cpp/pass_multiple_catch_cpp_with_finally.throw_uncaught_type) + (catch cpp/char c c))) + "Should catch char 'x'")) + +:success diff --git a/compiler+runtime/test/jank/form/try/pass-catch-cpp-no-slicing.jank b/compiler+runtime/test/jank/form/try/pass-catch-cpp-no-slicing.jank new file mode 100644 index 000000000..0eb050139 --- /dev/null +++ b/compiler+runtime/test/jank/form/try/pass-catch-cpp-no-slicing.jank @@ -0,0 +1,36 @@ +;; Test to verify that object slicing does not occur when catching by base type +;; See ERR61-CPP: Catch exceptions by lvalue reference + +(cpp/raw " +#include + +namespace pass_catch_no_slicing +{ + struct Base + { + virtual std::string name() const { return \"Base\"; } + virtual ~Base() = default; + }; + + struct Derived : Base + { + std::string name() const override { return \"Derived\"; } + }; + + void throw_derived() + { + throw Derived(); + } +}") + +(let [result (atom nil)] + (try + (cpp/pass_catch_no_slicing.throw_derived) + (catch cpp/pass_catch_no_slicing.Base e + ;; If slicing occurred, e would be a Base object and name() would return "Base" + ;; Since we catch by reference (implicitly), it should remain Derived + (reset! result (cpp/.name e)))) + + (assert (= "Derived" @result) "Object slicing occurred! Expected 'Derived' but got 'Base'")) + +:success diff --git a/compiler+runtime/test/jank/form/try/pass-catch-cpp-primitives.jank b/compiler+runtime/test/jank/form/try/pass-catch-cpp-primitives.jank new file mode 100644 index 000000000..e3c7e1ebe --- /dev/null +++ b/compiler+runtime/test/jank/form/try/pass-catch-cpp-primitives.jank @@ -0,0 +1,60 @@ +;; Test catching various C++ primitive types + +(cpp/raw "namespace pass_catch_cpp_primitives { using unsigned_int = unsigned int; }") +(cpp/raw "namespace pass_catch_cpp_primitives { using void_ptr = void*; }") +(cpp/raw "namespace pass_catch_cpp_primitives { using void_ptr_ptr = void**; }") +(cpp/raw "namespace pass_catch_cpp_primitives { unsigned long to_ulong(void* p) { return reinterpret_cast(p); } }") +(cpp/raw "namespace pass_catch_cpp_primitives { void throw_char() { throw 'c'; } }") +(cpp/raw "namespace pass_catch_cpp_primitives { void throw_double() { throw 1.23; } }") +(cpp/raw "namespace pass_catch_cpp_primitives { void throw_float() { throw 0.5f; } }") +(cpp/raw "namespace pass_catch_cpp_primitives { void throw_int() { throw 123; } }") +(cpp/raw "namespace pass_catch_cpp_primitives { void throw_unsigned_int() { throw 123u; } }") +(cpp/raw "namespace pass_catch_cpp_primitives { void throw_pointer() { throw (void*)0x1234; } }") + +;; 1. char +(assert + (= \c + (try + (cpp/pass_catch_cpp_primitives.throw_char) + (catch cpp/char c c)))) + +; +;;; 2. double +(assert + (= 1.23 + (try + (cpp/pass_catch_cpp_primitives.throw_double) + (catch cpp/double d d)))) + +; +;;; 3. float +(assert + (= 0.5 + (try + (cpp/pass_catch_cpp_primitives.throw_float) + (catch cpp/float f f)))) + +;; 4. int +(assert + (= 123 + (try + (cpp/pass_catch_cpp_primitives.throw_int) + (catch cpp/int i i)))) + +;; 5. unsigned int +(assert + (= 123 + (try + (cpp/pass_catch_cpp_primitives.throw_unsigned_int) + (catch cpp/pass_catch_cpp_primitives.unsigned_int u u)))) + +; +;;; 6. pointer +;(assert +; (= 0x1234 +; (try +; (cpp/pass_catch_cpp_primitives.throw_pointer) +; (catch cpp/pass_catch_cpp_primitives.void_ptr p +; (cpp/pass_catch_cpp_primitives.to_ulong p))))) + +:success diff --git a/compiler+runtime/test/jank/form/try/pass-catch-cpp-std-bad-cast.jank b/compiler+runtime/test/jank/form/try/pass-catch-cpp-std-bad-cast.jank new file mode 100644 index 000000000..749f3e73a --- /dev/null +++ b/compiler+runtime/test/jank/form/try/pass-catch-cpp-std-bad-cast.jank @@ -0,0 +1,21 @@ +;; Test catching std::bad_cast +(cpp/raw " +#include +namespace catch_cpp_bad_cast +{ + void throw_bad_cast() + { + throw std::bad_cast(); + } +}") + +(let [caught (atom nil)] + (try + (cpp/catch_cpp_bad_cast.throw_bad_cast) + (catch cpp/std.runtime_error e) + (catch cpp/std.bad_cast e + (reset! caught (cpp/.what e)))) + (assert (not (nil? @caught)) "Should have caught std::bad_cast") + (assert (= "std::bad_cast" @caught) "Error message should be 'std::bad_cast'")) + +:success diff --git a/compiler+runtime/test/jank/form/try/pass-catch-cpp-std-exception.jank b/compiler+runtime/test/jank/form/try/pass-catch-cpp-std-exception.jank new file mode 100644 index 000000000..f0c7874ba --- /dev/null +++ b/compiler+runtime/test/jank/form/try/pass-catch-cpp-std-exception.jank @@ -0,0 +1,12 @@ +;; Test catching std::exception base class (with implicit reference promotion) +(cpp/raw "namespace pass_catch_cpp_std_exception + { + void throw_runtime_error_as_exception() { throw std::runtime_error(\"derived error\"); } + }") + +(let [] + (try + (cpp/pass_catch_cpp_std_exception.throw_runtime_error_as_exception) + (catch cpp/std.exception e + (assert (= "derived error" (cpp/.what e))) + :success))) \ No newline at end of file diff --git a/compiler+runtime/test/jank/form/try/pass-catch-cpp-std-runtime-error.jank b/compiler+runtime/test/jank/form/try/pass-catch-cpp-std-runtime-error.jank new file mode 100644 index 000000000..d988ddbb5 --- /dev/null +++ b/compiler+runtime/test/jank/form/try/pass-catch-cpp-std-runtime-error.jank @@ -0,0 +1,15 @@ +;; Test catching std::runtime_error +(cpp/raw "namespace pass_catch_cpp_std_runtime_error + { + void throw_runtime_error() { throw std::runtime_error(\"runtime error\"); } + }") + +(let [caught (atom nil)] + (try + (cpp/pass_catch_cpp_std_runtime_error.throw_runtime_error) + (catch cpp/std.logic_error e) + (catch cpp/std.runtime_error e + (reset! caught (cpp/.what e)))) + (assert (= "runtime error" @caught) "Should have caught std::runtime_error with correct message")) + +:success diff --git a/compiler+runtime/test/jank/form/try/pass-catch-mixed-types.jank b/compiler+runtime/test/jank/form/try/pass-catch-mixed-types.jank new file mode 100644 index 000000000..15de2580c --- /dev/null +++ b/compiler+runtime/test/jank/form/try/pass-catch-mixed-types.jank @@ -0,0 +1,39 @@ +;; Test mixing jank and C++ exception types in multiple catch +(cpp/raw "namespace pass_multiple_catch_mixed_types + { + void throw_cpp_int() { throw 123; } + void throw_runtime_error() { throw std::runtime_error(\"mixed\"); } + }") + +;; Test jank exception followed by C++ catches +(assert + (= :jank-caught + (try + (throw :jank-exception) + (catch cpp/int i + :cpp-int-caught) + (catch cpp/jank.runtime.object_ref e + :jank-caught)))) + +;; Test C++ exception followed by jank catch +(assert + (= 123 + (try + (cpp/pass_multiple_catch_mixed_types.throw_cpp_int) + (catch cpp/int i + i) + (catch cpp/jank.runtime.object_ref e + :jank-caught)))) + +;; Test that C++ std exception is caught correctly (not by jank catch) +(let [msg (atom nil)] + (try + (cpp/pass_multiple_catch_mixed_types.throw_runtime_error) + (catch cpp/std.runtime_error ex + (reset! msg (cpp/.what ex))) + (catch cpp/jank.runtime.object_ref e + (assert false "Should not reach jank catch for C++ exception"))) + (assert (not (nil? @msg)) "Should have caught C++ exception") + (assert (= "mixed" @msg) "Error message should be 'mixed'")) + +:success diff --git a/compiler+runtime/test/jank/form/try/pass-catch-ordering.jank b/compiler+runtime/test/jank/form/try/pass-catch-ordering.jank new file mode 100644 index 000000000..78c001203 --- /dev/null +++ b/compiler+runtime/test/jank/form/try/pass-catch-ordering.jank @@ -0,0 +1,46 @@ +;; Test catch clause ordering + +(cpp/raw "namespace pass_catch_ordering + { + struct Base {}; + struct Derived : Base {}; + }") + +(cpp/raw "namespace pass_catch_ordering { void throw_derived() { throw Derived(); }}") +(cpp/raw "namespace pass_catch_ordering { void throw_base() { throw Base(); }}") +(cpp/raw "namespace pass_catch_ordering { void throw_int() { throw 123; }}") + +;; 1. Specific before general (correct order) +(assert + (= :derived + (try + (cpp/pass_catch_ordering.throw_derived) + (catch cpp/pass_catch_ordering.Derived _ :derived) + (catch cpp/pass_catch_ordering.Base _ :base)))) + +;; 2. General before specific (first match wins) +(assert + (= :base + (try + (cpp/pass_catch_ordering.throw_derived) + (catch cpp/pass_catch_ordering.Base _ :base) + (catch cpp/pass_catch_ordering.Derived _ :derived)))) + +;; 3. Fallthrough to general +(assert + (= :base + (try + (cpp/pass_catch_ordering.throw_base) + (catch cpp/pass_catch_ordering.Derived _ :derived) + (catch cpp/pass_catch_ordering.Base _ :base)))) + +;; 4. Unrelated types +(assert + (= :int + (try + (cpp/pass_catch_ordering.throw_int) + (catch cpp/pass_catch_ordering.Derived _ :derived) + (catch cpp/pass_catch_ordering.Base _ :base) + (catch cpp/int _ :int)))) + +:success diff --git a/compiler+runtime/test/jank/form/try/pass-catch-returns-exception.jank b/compiler+runtime/test/jank/form/try/pass-catch-returns-exception.jank index 952d86862..063099653 100644 --- a/compiler+runtime/test/jank/form/try/pass-catch-returns-exception.jank +++ b/compiler+runtime/test/jank/form/try/pass-catch-returns-exception.jank @@ -1,4 +1,4 @@ (try (throw :success) - (catch e + (catch cpp/jank.runtime.object_ref e e)) diff --git a/compiler+runtime/test/jank/form/try/pass-closure-capture.jank b/compiler+runtime/test/jank/form/try/pass-closure-capture.jank new file mode 100644 index 000000000..09d4f5f60 --- /dev/null +++ b/compiler+runtime/test/jank/form/try/pass-closure-capture.jank @@ -0,0 +1,31 @@ +;; Test closure capture in try-catch blocks +;; Verifies that try-catch correctly captures variables from parent scopes + +;; Test: Closure over both function parameter and local variable +((fn* [a] + (let [b 1] + (assert + (= true + (try + (throw (= a b)) + (catch cpp/jank.runtime.object_ref r + (assert (= 1 b) "Should capture local variable b") + r))) + "Should capture both parameter a and local b correctly"))) + 1) + +;; Test: Closure over function parameter in catch block +(assert + (= :success + ((fn* [fun] + (try + (fun true) + (catch cpp/jank.runtime.object_ref _ + (fun false)))) + (fn* [throw?] + (if throw? + (throw :success) + :success)))) + "Should call function parameter correctly in catch block") + +:success diff --git a/compiler+runtime/test/jank/form/try/pass-closure-from-parent-parameter-and-local.jank b/compiler+runtime/test/jank/form/try/pass-closure-from-parent-parameter-and-local.jank index 860a12772..662f9635c 100644 --- a/compiler+runtime/test/jank/form/try/pass-closure-from-parent-parameter-and-local.jank +++ b/compiler+runtime/test/jank/form/try/pass-closure-from-parent-parameter-and-local.jank @@ -2,7 +2,7 @@ (let [b 1] (try (throw (= a b)) - (catch r + (catch cpp/jank.runtime.object_ref r (when (and r (= 1 b)) :success))))) 1) diff --git a/compiler+runtime/test/jank/form/try/pass-closure-from-parent-parameter.jank b/compiler+runtime/test/jank/form/try/pass-closure-from-parent-parameter.jank index 159cb951e..e882af70e 100644 --- a/compiler+runtime/test/jank/form/try/pass-closure-from-parent-parameter.jank +++ b/compiler+runtime/test/jank/form/try/pass-closure-from-parent-parameter.jank @@ -1,7 +1,7 @@ ((fn* [fun] (try (fun true) - (catch _ + (catch cpp/jank.runtime.object_ref _ (fun false)) (finally ))) diff --git a/compiler+runtime/test/jank/form/try/pass-closure.jank b/compiler+runtime/test/jank/form/try/pass-closure.jank index 26b766ade..0deb37966 100644 --- a/compiler+runtime/test/jank/form/try/pass-closure.jank +++ b/compiler+runtime/test/jank/form/try/pass-closure.jank @@ -1,7 +1,7 @@ (let* [a 1] (try (throw a) - (catch e + (catch cpp/jank.runtime.object_ref e (when (= a e) :success)) (finally diff --git a/compiler+runtime/test/jank/form/try/pass-expression-no-throw.jank b/compiler+runtime/test/jank/form/try/pass-expression-no-throw.jank index 57fda7e9e..2ef4901d7 100644 --- a/compiler+runtime/test/jank/form/try/pass-expression-no-throw.jank +++ b/compiler+runtime/test/jank/form/try/pass-expression-no-throw.jank @@ -1,6 +1,6 @@ (assert (= (try :success - (catch _)) + (catch cpp/jank.runtime.object_ref _)) :success)) :success diff --git a/compiler+runtime/test/jank/form/try/pass-expression-with-throw.jank b/compiler+runtime/test/jank/form/try/pass-expression-with-throw.jank index 8748f5816..21369b824 100644 --- a/compiler+runtime/test/jank/form/try/pass-expression-with-throw.jank +++ b/compiler+runtime/test/jank/form/try/pass-expression-with-throw.jank @@ -1,6 +1,6 @@ (assert (= (try (throw :success) - (catch e + (catch cpp/jank.runtime.object_ref e e)))) :success diff --git a/compiler+runtime/test/jank/form/try/pass-finally-behavior.jank b/compiler+runtime/test/jank/form/try/pass-finally-behavior.jank new file mode 100644 index 000000000..51728d498 --- /dev/null +++ b/compiler+runtime/test/jank/form/try/pass-finally-behavior.jank @@ -0,0 +1,44 @@ +;; Tests for finally block behavior + +;; 1. Finally executes but does not affect return value (no exception) +(let [a (atom [])] + (assert + (= [:try] + (try + (swap! a conj :try) + (finally + (swap! a conj :finally))))) + (assert (= [:try :finally] @a) (pr-str @a))) + +;; 2. Finally executes but does not affect return value (with exception) +(let [a (atom [])] + (try + (try + (swap! a conj :inner-try) + (throw "skip finally") + (swap! a conj :inner-try-after-throw) + (finally + (swap! a conj :inner-finally))) + (catch cpp/jank.runtime.object_ref e + (swap! a conj [:outer-catch e]))) + (assert (= [:inner-try :inner-finally [:outer-catch "skip finally"]] @a) + (pr-str @a))) + +;; 3. Finally value is discarded (no exception) +(assert + (= + (try 1 + (catch cpp/jank.runtime.object_ref _ 2) + (finally 3)) + 1)) + +;; 4. Finally value is discarded (with exception) +(assert + (= + (try + (throw :anything) + (catch cpp/jank.runtime.object_ref _ 2) + (finally 3)) + 2)) + +:success diff --git a/compiler+runtime/test/jank/form/try/pass-finally-no-catch.jank b/compiler+runtime/test/jank/form/try/pass-finally-no-catch.jank index b3a76969f..333b1d277 100644 --- a/compiler+runtime/test/jank/form/try/pass-finally-no-catch.jank +++ b/compiler+runtime/test/jank/form/try/pass-finally-no-catch.jank @@ -13,7 +13,7 @@ (swap! a conj :inner-try-after-throw) (finally (swap! a conj :inner-finally))) - (catch e + (catch cpp/jank.runtime.object_ref e (swap! a conj [:outer-catch e]))) (assert (= [:inner-try :inner-finally [:outer-catch "skip finally"]] @a) (pr-str @a))) diff --git a/compiler+runtime/test/jank/form/try/pass-multiple-catch-nested.jank b/compiler+runtime/test/jank/form/try/pass-multiple-catch-nested.jank new file mode 100644 index 000000000..efbb1a324 --- /dev/null +++ b/compiler+runtime/test/jank/form/try/pass-multiple-catch-nested.jank @@ -0,0 +1,23 @@ +;; Test nested try with multiple catches +(assert + (= :inner-caught + (try + (try + (throw :inner-exception) + (catch cpp/jank.runtime.object_ref e1 + :inner-caught)) + (catch cpp/jank.runtime.object_ref outer-e + :outer-caught)))) + +;; Test that inner exception can be re-thrown and caught by outer +(assert + (= :outer-caught + (try + (try + (throw :rethrow) + (catch cpp/jank.runtime.object_ref e + (throw e))) + (catch cpp/jank.runtime.object_ref outer-e + :outer-caught)))) + +:success diff --git a/compiler+runtime/test/jank/form/try/pass-multiple-catch-with-finally.jank b/compiler+runtime/test/jank/form/try/pass-multiple-catch-with-finally.jank new file mode 100644 index 000000000..f13b558f4 --- /dev/null +++ b/compiler+runtime/test/jank/form/try/pass-multiple-catch-with-finally.jank @@ -0,0 +1,24 @@ +;; Test multiple catch with finally block - first matching catch executes +(let [execution-order (atom [])] + (try + (swap! execution-order conj :try) + (throw :exception) + (catch cpp/jank.runtime.object_ref e1 + (swap! execution-order conj :catch1)) + (finally + (swap! execution-order conj :finally))) + (assert (= [:try :catch1 :finally] @execution-order))) + +;; Test that finally executes after catch +(let [cleanup (atom false)] + (assert + (= :caught + (try + (throw "error") + (catch cpp/jank.runtime.object_ref e + :caught) + (finally + (reset! cleanup true))))) + (assert (true? @cleanup))) + +:success diff --git a/compiler+runtime/test/jank/form/try/pass-multiple-catch.jank b/compiler+runtime/test/jank/form/try/pass-multiple-catch.jank new file mode 100644 index 000000000..12224c93f --- /dev/null +++ b/compiler+runtime/test/jank/form/try/pass-multiple-catch.jank @@ -0,0 +1,26 @@ +;; Test basic multiple catch with same type - first one always matches +;; This demonstrates that type-based dispatch uses the first matching catch +(assert + (= :first-caught + (try + (throw :any-value) + (catch cpp/jank.runtime.object_ref e1 + :first-caught)))) + +;; Verify the caught value is accessible +(assert + (= :my-exception + (try + (throw :my-exception) + (catch cpp/jank.runtime.object_ref e + e)))) + +;; Test with string exception +(assert + (= "error-message" + (try + (throw "error-message") + (catch cpp/jank.runtime.object_ref e + e)))) + +:success diff --git a/compiler+runtime/test/jank/form/try/pass-nested-fn-finally.jank b/compiler+runtime/test/jank/form/try/pass-nested-fn-finally.jank new file mode 100644 index 000000000..85fe571f8 --- /dev/null +++ b/compiler+runtime/test/jank/form/try/pass-nested-fn-finally.jank @@ -0,0 +1,15 @@ +(let* [steps (atom []) + b (fn* b [] + (try + (throw :bthrow) + (catch cpp/jank.runtime.object_ref e (swap! steps conj :b-catch) (throw e)) + (finally (swap! steps conj :b-finally)))) + a (fn* a [] + (try + (b) + (catch cpp/jank.runtime.object_ref e (swap! steps conj :a-catch)) + (finally (swap! steps conj :a-finally))))] + (a) + (if (= [:b-catch :b-finally :a-catch :a-finally] @steps) + :success + (do (println "Failed:" @steps) :failure))) diff --git a/compiler+runtime/test/jank/form/try/pass-nested-no-catch.jank b/compiler+runtime/test/jank/form/try/pass-nested-no-catch.jank index ffaac5997..623741368 100644 --- a/compiler+runtime/test/jank/form/try/pass-nested-no-catch.jank +++ b/compiler+runtime/test/jank/form/try/pass-nested-no-catch.jank @@ -6,7 +6,7 @@ a (fn* a [] (try (b) - (catch e (swap! steps conj :a-catch))))] + (catch cpp/jank.runtime.object_ref e (swap! steps conj :a-catch))))] (a) (assert (= [:b-finally :a-catch] @steps) (pr-str @steps))) @@ -15,7 +15,7 @@ b (fn* b [] (try (throw :bthrow) - (catch e (swap! steps conj :b-catch)))) + (catch cpp/jank.runtime.object_ref e (swap! steps conj :b-catch)))) a (fn* a [] (try (b) @@ -32,7 +32,7 @@ a (fn* a [] (try (b) - (catch e (swap! steps conj :a-catch)) + (catch cpp/jank.runtime.object_ref e (swap! steps conj :a-catch)) (finally (swap! steps conj :a-finally))))] (a) (assert (= [:b-finally :a-catch :a-finally] @steps) @@ -42,11 +42,11 @@ b (fn* b [] (try (throw :bthrow) - (catch e (swap! steps conj :b-catch) (throw e)))) + (catch cpp/jank.runtime.object_ref e (swap! steps conj :b-catch) (throw e)))) a (fn* a [] (try (b) - (catch e (swap! steps conj :a-catch))))] + (catch cpp/jank.runtime.object_ref e (swap! steps conj :a-catch))))] (a) (assert (= [:b-catch :a-catch] @steps) (pr-str @steps))) @@ -55,12 +55,12 @@ b (fn* b [] (try (throw :bthrow) - (catch e (swap! steps conj :b-catch) (throw e)) + (catch cpp/jank.runtime.object_ref e (swap! steps conj :b-catch) (throw e)) (finally (swap! steps conj :b-finally)))) a (fn* a [] (try (b) - (catch e (swap! steps conj :a-catch)) + (catch cpp/jank.runtime.object_ref e (swap! steps conj :a-catch)) (finally (swap! steps conj :a-finally))))] (a) (assert (= [:b-catch :b-finally :a-catch :a-finally] @steps) diff --git a/compiler+runtime/test/jank/form/try/pass-nested-try-catch.jank b/compiler+runtime/test/jank/form/try/pass-nested-try-catch.jank new file mode 100644 index 000000000..1c724a28b --- /dev/null +++ b/compiler+runtime/test/jank/form/try/pass-nested-try-catch.jank @@ -0,0 +1,25 @@ +;; Test nested try/catch scenarios + +;; Test 1: Inner catch handles exception completely +(assert + (= :inner-caught + (try + (try + (throw :inner-exception) + (catch cpp/jank.runtime.object_ref e1 + :inner-caught)) + (catch cpp/jank.runtime.object_ref outer-e + :outer-caught)))) + +;; Test 2: Inner catch rethrows to outer catch +(assert + (= :success + (try + (try + (throw :success) + (catch cpp/jank.runtime.object_ref e1 + (throw e1))) + (catch cpp/jank.runtime.object_ref e2 + (if (= e2 :success) :success))))) + +:success diff --git a/compiler+runtime/test/jank/form/try/pass-nested-try-with-finally.jank b/compiler+runtime/test/jank/form/try/pass-nested-try-with-finally.jank new file mode 100644 index 000000000..4b8ee2b10 --- /dev/null +++ b/compiler+runtime/test/jank/form/try/pass-nested-try-with-finally.jank @@ -0,0 +1,14 @@ +;; Test nested try blocks with finally +;; Inner try has finally, outer try has catch +;; Verify that inner finally executes and outer catch receives the exception +(let* [finally-executed (atom false) + caught-exception (try + (try + (throw "inner-throw") + (finally (reset! finally-executed true))) + (catch cpp/jank.runtime.object_ref e e))] + (assert @finally-executed "inner finally should have executed") + (assert (= "inner-throw" caught-exception) + (str "expected 'inner-throw' but got: " caught-exception))) + +:success diff --git a/compiler+runtime/test/jank/form/try/pass-nested.jank b/compiler+runtime/test/jank/form/try/pass-nested.jank index 6b09b61c1..3396681d9 100644 --- a/compiler+runtime/test/jank/form/try/pass-nested.jank +++ b/compiler+runtime/test/jank/form/try/pass-nested.jank @@ -1,7 +1,7 @@ (try (try (throw :success) - (catch e1 + (catch cpp/jank.runtime.object_ref e1 (throw e1))) - (catch e2 - e2)) + (catch cpp/jank.runtime.object_ref e2 + (if (= e2 :success) :success))) diff --git a/compiler+runtime/test/jank/form/try/pass-no-catch-no-finally.jank b/compiler+runtime/test/jank/form/try/pass-no-catch-no-finally.jank deleted file mode 100644 index fedea2248..000000000 --- a/compiler+runtime/test/jank/form/try/pass-no-catch-no-finally.jank +++ /dev/null @@ -1,3 +0,0 @@ -(let* [v (try (+ 1 2))] - (if (= v 3) - :success)) diff --git a/compiler+runtime/test/jank/form/try/pass-no-throw-with-catch-and-finally.jank b/compiler+runtime/test/jank/form/try/pass-no-throw-with-catch-and-finally.jank index 2d7c4114e..691bce1d0 100644 --- a/compiler+runtime/test/jank/form/try/pass-no-throw-with-catch-and-finally.jank +++ b/compiler+runtime/test/jank/form/try/pass-no-throw-with-catch-and-finally.jank @@ -1,6 +1,6 @@ (try :success - (catch _ + (catch cpp/jank.runtime.object_ref _ ) (finally )) diff --git a/compiler+runtime/test/jank/form/try/pass-no-throw-with-catch.jank b/compiler+runtime/test/jank/form/try/pass-no-throw-with-catch.jank index 2744628ae..791dc0d8c 100644 --- a/compiler+runtime/test/jank/form/try/pass-no-throw-with-catch.jank +++ b/compiler+runtime/test/jank/form/try/pass-no-throw-with-catch.jank @@ -1,3 +1,3 @@ (try :success - (catch _)) + (catch cpp/jank.runtime.object_ref _)) diff --git a/compiler+runtime/test/jank/form/try/pass-no-value-from-finally.jank b/compiler+runtime/test/jank/form/try/pass-no-value-from-finally.jank index 3429cb4f6..11b02545d 100644 --- a/compiler+runtime/test/jank/form/try/pass-no-value-from-finally.jank +++ b/compiler+runtime/test/jank/form/try/pass-no-value-from-finally.jank @@ -1,6 +1,6 @@ (assert (= (try 1 - (catch _ + (catch cpp/jank.runtime.object_ref _ 2) (finally 3)) @@ -8,7 +8,7 @@ (assert (= (try (throw :anything) - (catch _ + (catch cpp/jank.runtime.object_ref _ 2) (finally 3)) diff --git a/compiler+runtime/test/jank/form/try/pass-throw-from-finally.jank b/compiler+runtime/test/jank/form/try/pass-throw-from-finally.jank new file mode 100644 index 000000000..47dbef71c --- /dev/null +++ b/compiler+runtime/test/jank/form/try/pass-throw-from-finally.jank @@ -0,0 +1,23 @@ +;; Test throwing from finally block + +;; 1. Throw from finally suppresses pending exception +(assert + (= :finally-exception + (try + (try + (throw :initial-exception) + (finally + (throw :finally-exception))) + (catch cpp/jank.runtime.object_ref e e)))) + +;; 2. Throw from finally with no pending exception +(assert + (= :finally-exception + (try + (try + :success + (finally + (throw :finally-exception))) + (catch cpp/jank.runtime.object_ref e e)))) + +:success diff --git a/compiler+runtime/test/jank/form/try/pass-try-basic.jank b/compiler+runtime/test/jank/form/try/pass-try-basic.jank new file mode 100644 index 000000000..e8d2c1696 --- /dev/null +++ b/compiler+runtime/test/jank/form/try/pass-try-basic.jank @@ -0,0 +1,29 @@ +;; Basic try/catch form logic tests + +;; 1. Try expression with no throw returns body value +(assert + (= + (try :success + (catch cpp/jank.runtime.object_ref _)) + :success)) + +;; 2. Try expression with throw returns caught value +(assert + (= + (try + (throw :success) + (catch cpp/jank.runtime.object_ref e e)) + :success)) + +;; 3. Try with no catch/finally +(let* [v (try (+ 1 2))] + (assert (= v 3))) + +;; 4. Local reference to C++ value in try block +(let [bar cpp/true] + (assert + (= :success + (try + (if bar :success))))) + +:success diff --git a/compiler+runtime/test/jank/form/try/pass-try-catch-finally-execution.jank b/compiler+runtime/test/jank/form/try/pass-try-catch-finally-execution.jank new file mode 100644 index 000000000..cbd1494ed --- /dev/null +++ b/compiler+runtime/test/jank/form/try/pass-try-catch-finally-execution.jank @@ -0,0 +1,36 @@ +;; Comprehensive tests for try-catch-finally execution order + +;; Test 1: Basic try-catch-finally order +(let [execution-order (atom [])] + (try + (swap! execution-order conj :try) + (throw :exception) + (catch cpp/jank.runtime.object_ref e1 + (swap! execution-order conj :catch)) + (finally + (swap! execution-order conj :finally))) + (assert (= [:try :catch :finally] @execution-order) "Basic execution order failed")) + +;; Test 2: Finally executes after catch returns value +(let [cleanup (atom false)] + (assert + (= :caught + (try + (throw "error") + (catch cpp/jank.runtime.object_ref e :caught) + (finally + (reset! cleanup true))))) + (assert (true? @cleanup) "Finally block did not execute")) + +;; Test 3: Nested try with finally - inner finally executes before outer catch +(let* [finally-executed (atom false) + caught-exception (try + (try + (throw "inner-throw") + (finally (reset! finally-executed true))) + (catch cpp/jank.runtime.object_ref e e))] + (assert @finally-executed "Inner finally should have executed before outer catch") + (assert (= "inner-throw" caught-exception) + (str "Expected 'inner-throw' but got: " caught-exception))) + +:success diff --git a/compiler+runtime/test/jank/form/try/pass-try-empty-bodies.jank b/compiler+runtime/test/jank/form/try/pass-try-empty-bodies.jank new file mode 100644 index 000000000..90f24f02f --- /dev/null +++ b/compiler+runtime/test/jank/form/try/pass-try-empty-bodies.jank @@ -0,0 +1,20 @@ +;; Test empty bodies in try/catch/finally + +;; 1. Empty try body -> nil +(assert (nil? (try))) + +;; 2. Empty try body with catch -> nil +(assert (nil? (try (catch cpp/jank.runtime.object_ref _)))) + +;; 3. Empty try body with finally -> nil +;; (let [side-effect (atom false)] +;; (assert (nil? (try (finally (reset! side-effect true))))) +;; (assert @side-effect)) + +;; 4. Empty catch body -> nil +;; (assert (nil? (try (throw :ex) (catch cpp/jank.runtime.object_ref _)))) + +;; 5. Empty finally body +;; (assert (= :val (try :val (finally)))) + +:success diff --git a/compiler+runtime/test/jank/form/try/pass-unboxed-position.jank b/compiler+runtime/test/jank/form/try/pass-unboxed-position.jank index df43ecd16..185224d80 100644 --- a/compiler+runtime/test/jank/form/try/pass-unboxed-position.jank +++ b/compiler+runtime/test/jank/form/try/pass-unboxed-position.jank @@ -1,10 +1,15 @@ -; A box will be required. -(def a (fn* [] - (let* [a 5 - b (try - 7 - (catch _ - ))] - []))) +;; Regression test: Verify that unboxed values from try expressions +;; are correctly boxed when assigned to bindings and used in boxed contexts +(def a + (fn* [] + (let* [a 5 + b (try 7 + (catch cpp/jank.runtime.object_ref _))] + (assert (= 7 b) "Try should return unboxed value 7") + (assert (= [7] [b]) "Boxed value should work correctly in vector") + []))) + +;; Execute the test function +(a) :success diff --git a/compiler+runtime/third-party/bdwgc b/compiler+runtime/third-party/bdwgc index 88a61fc6f..19a7f495c 160000 --- a/compiler+runtime/third-party/bdwgc +++ b/compiler+runtime/third-party/bdwgc @@ -1 +1 @@ -Subproject commit 88a61fc6f2688d8e1ea94f3f810c23db89a3f30b +Subproject commit 19a7f495cd7c48cdf6f56c593c6dd57187afa079 diff --git a/compiler+runtime/third-party/bpptree b/compiler+runtime/third-party/bpptree deleted file mode 160000 index addf92b02..000000000 --- a/compiler+runtime/third-party/bpptree +++ /dev/null @@ -1 +0,0 @@ -Subproject commit addf92b029bef74ae07f4ebb7e82312a6950ae8c