diff --git a/compiler+runtime/CMakeLists.txt b/compiler+runtime/CMakeLists.txt index 7d9c079d..a52125ad 100644 --- a/compiler+runtime/CMakeLists.txt +++ b/compiler+runtime/CMakeLists.txt @@ -432,6 +432,7 @@ if(jank_tests) test/cpp/jank/read/lex.cpp test/cpp/jank/read/parse.cpp test/cpp/jank/analyze/box.cpp + test/cpp/jank/analyze/processor.cpp test/cpp/jank/runtime/detail/native_persistent_list.cpp test/cpp/jank/runtime/obj/ratio.cpp test/cpp/jank/jit/processor.cpp diff --git a/compiler+runtime/include/cpp/jank/analyze/expr/case.hpp b/compiler+runtime/include/cpp/jank/analyze/expr/case.hpp new file mode 100644 index 00000000..5fe871c1 --- /dev/null +++ b/compiler+runtime/include/cpp/jank/analyze/expr/case.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include +#include + +namespace jank::analyze::expr +{ + using namespace jank::runtime; + + template + struct case_ : expression_base + { + enum switch_type + { + integers, + hashes, + hash_equiv, + hash_identity, + }; + + native_box value_expr{}; + native_integer shift{}; + native_integer mask{}; + native_box default_expr{}; + std::vector keys{}; + // // native_vector keys{}; + // obj::persistent_vector_ptr exprs{}; + std::vector> exprs{}; + native_bool is_compact{}; + switch_type switch_type{}; + obj::persistent_hash_set_ptr collided_keys{}; + + void propagate_position(expression_position const pos) + { + std::cout << "set case expr position to " << expression_position_str(pos) << std::endl; + default_expr->propagate_position(pos); + for(auto &expr : exprs) + { + expr->propagate_position(pos); + } + position = pos; + } + + object_ptr to_runtime_data() const + { + return merge(static_cast(this)->to_runtime_data(), + obj::persistent_array_map::create_unique(make_box("__type"), + make_box("expr::case"), + make_box("value_expr"), + value_expr->to_runtime_data(), + make_box("shift"), + make_box(shift), + make_box("mask"), + make_box(mask), + make_box("default_expr"), + default_expr->to_runtime_data(), + // make_box("keys"), + // keys, + // make_box("body_exprs"), + // exprs, + make_box("is_compact"), + make_box(is_compact), + make_box("switch_type"), + make_box(switch_type), + make_box("collided_keys"), + collided_keys)); + } + }; +} diff --git a/compiler+runtime/include/cpp/jank/analyze/expr/if.hpp b/compiler+runtime/include/cpp/jank/analyze/expr/if.hpp index dced5797..2b97989e 100644 --- a/compiler+runtime/include/cpp/jank/analyze/expr/if.hpp +++ b/compiler+runtime/include/cpp/jank/analyze/expr/if.hpp @@ -19,6 +19,7 @@ namespace jank::analyze::expr void propagate_position(expression_position const pos) { + std::cout << "set if expr position to" << expression_position_str(pos) << std::endl; position = pos; if(then) { diff --git a/compiler+runtime/include/cpp/jank/analyze/expression.hpp b/compiler+runtime/include/cpp/jank/analyze/expression.hpp index 0dbeaa4b..9675fb00 100644 --- a/compiler+runtime/include/cpp/jank/analyze/expression.hpp +++ b/compiler+runtime/include/cpp/jank/analyze/expression.hpp @@ -21,6 +21,7 @@ #include #include #include +#include namespace jank::analyze { @@ -46,7 +47,8 @@ namespace jank::analyze expr::do_, expr::if_, expr::throw_, - expr::try_>; + expr::try_, + expr::case_>; static constexpr native_bool pointer_free{ false }; diff --git a/compiler+runtime/include/cpp/jank/analyze/processor.hpp b/compiler+runtime/include/cpp/jank/analyze/processor.hpp index fa043209..226dbfa4 100644 --- a/compiler+runtime/include/cpp/jank/analyze/processor.hpp +++ b/compiler+runtime/include/cpp/jank/analyze/processor.hpp @@ -143,9 +143,33 @@ namespace jank::analyze option const &, native_bool needs_box); + expression_result analyze_case(runtime::obj::persistent_list_ptr const &, + local_frame_ptr &, + expression_position, + option const &, + native_bool needs_box); + /* Returns whether or not the form is a special symbol. */ native_bool is_special(runtime::object_ptr form); + struct keys_and_exprs + { + std::vector keys{}; + std::vector exprs{}; + }; + + result + get_keys_and_exprs_from_array_map(native_box imap, + local_frame_ptr &f, + expression_position, + option const &fc, + native_bool needs_box); + result + get_keys_and_exprs_from_sorted_map(native_box imap, + local_frame_ptr &f, + expression_position, + option const &fc, + native_bool needs_box); using special_function_type = std::function const &); llvm::Value *gen(analyze::expr::try_ const &, analyze::expr::function_arity const &); + llvm::Value *gen(analyze::expr::case_ const &, + analyze::expr::function_arity const &); llvm::Value *gen_var(obj::symbol_ptr qualified_name) const; llvm::Value *gen_c_string(native_persistent_string const &s) const; diff --git a/compiler+runtime/include/cpp/jank/evaluate.hpp b/compiler+runtime/include/cpp/jank/evaluate.hpp index f4fcbb5c..c447445d 100644 --- a/compiler+runtime/include/cpp/jank/evaluate.hpp +++ b/compiler+runtime/include/cpp/jank/evaluate.hpp @@ -36,4 +36,5 @@ namespace jank::evaluate runtime::object_ptr eval(analyze::expr::if_ const &); runtime::object_ptr eval(analyze::expr::throw_ const &); runtime::object_ptr eval(analyze::expr::try_ const &); + runtime::object_ptr eval(analyze::expr::case_ const &); } diff --git a/compiler+runtime/src/cpp/jank/analyze/processor.cpp b/compiler+runtime/src/cpp/jank/analyze/processor.cpp index ef965759..608a4b4e 100644 --- a/compiler+runtime/src/cpp/jank/analyze/processor.cpp +++ b/compiler+runtime/src/cpp/jank/analyze/processor.cpp @@ -47,6 +47,7 @@ namespace jank::analyze { make_box("var"), make_fn(&processor::analyze_var_call) }, { make_box("throw"), make_fn(&processor::analyze_throw) }, { make_box("try"), make_fn(&processor::analyze_try) }, + { make_box("case*"), make_fn(&processor::analyze_case) }, }; } @@ -59,8 +60,8 @@ namespace jank::analyze } /* We wrap all of the expressions we get in an anonymous fn so that we can call it easily. - * This also simplifies codegen, since we only ever codegen a single fn, even if that fn - * represents a ns, a single REPL expression, or an actual source fn. */ + * This also simplifies codegen, since we only ever codegen a single fn, even if that fn + * represents a ns, a single REPL expression, or an actual source fn. */ runtime::detail::native_transient_vector fn; fn.push_back(make_box("fn*")); fn.push_back(make_box()); @@ -162,6 +163,239 @@ namespace jank::analyze }); } + result + processor::get_keys_and_exprs_from_array_map(native_box imap, + local_frame_ptr &f, + expression_position const position, + option const &fc, + native_bool needs_box) + { + std::vector keys; + std::vector exprs; + for(auto const &pair : imap->data) + { + if(pair.first.data->type != object_type::integer) + { + return err(error{ "expected integer for key" }); + } + keys.push_back(runtime::expect_object(pair.first.data)->data); + + auto const body_obj{ pair.second }; + if(body_obj.data->type != object_type::persistent_vector) + { + return err(error{ "expected vector for body" }); + } + auto const body = runtime::expect_object(body_obj); + if(body->data.size() != 2) + { + return err(error{ "expected vector of size 2 for body" }); + } + auto body_expr = analyze(body->data[1], f, position, fc, needs_box); + if(body_expr.is_err()) + { + return body_expr.expect_err_move(); + } + exprs.push_back(body_expr.expect_ok()); + } + return result{ + keys_and_exprs{ keys, exprs } + }; + } + + result + processor::get_keys_and_exprs_from_sorted_map(native_box imap, + local_frame_ptr &f, + expression_position const position, + option const &fc, + native_bool needs_box) + { + std::vector keys; + std::vector exprs; + for(auto it = imap->data.begin(); it != imap->data.end(); ++it) + { + if(it->first.data->type != object_type::integer) + { + return err(error{ "expected integer for key" }); + } + keys.push_back(runtime::expect_object(it->first.data)->data); + + auto const body_obj{ it->second }; + if(body_obj.data->type != object_type::persistent_vector) + { + return err(error{ "expected vector for body" }); + } + auto const body = runtime::expect_object(body_obj); + if(body->data.size() != 2) + { + return err(error{ "expected vector of size 2 for body" }); + } + auto body_expr = analyze(body->data[1], f, position, fc, needs_box); + if(body_expr.is_err()) + { + return body_expr.expect_err_move(); + } + exprs.push_back(body_expr.expect_ok()); + } + return result{ + keys_and_exprs{ keys, exprs } + }; + } + + processor::expression_result processor::analyze_case(obj::persistent_list_ptr const &o, + local_frame_ptr &f, + expression_position const position, + option const &fc, + const native_bool needs_box) + { + + if(auto const length(o->count()); length != 8 && length != 9) + { + return err(error{ "invalid case: incorrect number of elements in form" }); + } + + auto const first(o->data.first().unwrap()); + if(first.data->type != object_type::symbol) + { + return err(error{ "invalid case: first element must be 'case*'" }); + } + if(runtime::expect_object(first).data->name != "case*") + { + return err(error{ "invalid case: first element must be 'case*'" }); + } + + auto it{ o->data.rest() }; + auto const value_expr_obj{ it.first().unwrap() }; + auto const value_expr{ analyze(value_expr_obj, f, expression_position::value, fc, needs_box) }; + + it = it.rest(); + auto const shift_obj{ it.first().unwrap() }; + if(shift_obj.data->type != object_type::integer) + { + return err(error{ "expected integer for shift" }); + } + auto const shift{ runtime::expect_object(shift_obj) }; + + it = it.rest(); + auto const mask_obj{ it.first().unwrap() }; + if(mask_obj.data->type != object_type::integer) + { + return err(error{ "expected integer for mask" }); + } + auto const mask{ runtime::expect_object(mask_obj) }; + + it = it.rest(); + auto const default_expr_obj{ it.first().unwrap() }; + auto const default_expr{ analyze(default_expr_obj, f, position, fc, needs_box) }; + + it = it.rest(); + std::vector switch_keys; + std::vector switch_exprs; + auto const imap_obj{ it.first().unwrap() }; + if(imap_obj.data->type == object_type::persistent_array_map) + { + auto [keys, exprs] = get_keys_and_exprs_from_array_map( + runtime::expect_object(imap_obj), + f, + position, + fc, + needs_box) + .expect_ok(); + switch_keys = keys; + switch_exprs = exprs; + } + else if(imap_obj.data->type == object_type::persistent_sorted_map) + { + auto [keys, exprs] = get_keys_and_exprs_from_sorted_map( + runtime::expect_object(imap_obj), + f, + position, + fc, + needs_box) + .expect_ok(); + switch_keys = keys; + switch_exprs = exprs; + } + else + { + return err(error{ "expected array map for keys" }); + } + + it = it.rest(); + auto const is_compact_obj{ it.first().unwrap() }; + if(is_compact_obj.data->type != object_type::keyword) + { + return err(error{ "expected keyword for compact" }); + } + auto const compact{ runtime::expect_object(is_compact_obj) }; + auto const is_compact{ compact->sym->get_name() == "compact" }; + + it = it.rest(); + auto const switch_type_obj = it.first().unwrap(); + if(switch_type_obj.data->type != object_type::keyword) + { + return err(error{ "expected keyword for switch_type" }); + } + auto const switch_type = runtime::expect_object(switch_type_obj); + + it = it.rest(); + obj::persistent_hash_set_ptr collided_keys; + // check if the iterator is at the end + if(it.empty()) + { + collided_keys = runtime::obj::persistent_hash_set::empty(); + } + else + { + auto const collided_keys_obj{ it.first().unwrap() }; + if(collided_keys_obj.data->type != object_type::persistent_hash_set + && collided_keys_obj.data->type != object_type::nil) + { + return err(error{ "expected hash set for collided_keys" }); + } + if(collided_keys_obj.data->type == object_type::nil) + { + collided_keys = runtime::obj::persistent_hash_set::empty(); + } + else + { + collided_keys + = runtime::expect_object(collided_keys_obj); + } + } + + auto const switch_type_name = switch_type->sym->get_name(); + auto switch_type_enum = expr::case_::switch_type::integers; + + if(switch_type_name == "hashes") + { + switch_type_enum = expr::case_::switch_type::hashes; + } + else if(switch_type_name == "hash-equiv") + { + switch_type_enum = expr::case_::switch_type::hash_equiv; + } + else if(switch_type_name == "hash-identity") + { + switch_type_enum = expr::case_::switch_type::hash_identity; + } + + std::cout << "analyze_case: expr position is " << expression_position_str(position) << std::endl; + auto case_expr{make_box(expr::case_{ + expression_base{ {}, position, f, needs_box }, + value_expr.expect_ok(), + shift->data, + mask->data, + default_expr.expect_ok(), + switch_keys, + switch_exprs, + is_compact, + switch_type_enum, + collided_keys + })}; + case_expr->propagate_position(position); + return case_expr; + } + processor::expression_result processor::analyze_symbol(runtime::obj::symbol_ptr const &sym, local_frame_ptr ¤t_frame, expression_position const position, @@ -189,12 +423,12 @@ namespace jank::analyze unwrapped_local.binding.has_boxed_usage = true; /* The first time we reference a captured local from within a function, we get here. - * We determine that we had to cross one or more function scopes to find the relevant - * local, so it's a new capture. We register the capture above, but we need to search - * again to get the binding within our current function, since the one we have now - * is the originating binding. - * - * All future lookups for this capatured local, in this function, will skip this branch. */ + * We determine that we had to cross one or more function scopes to find the relevant + * local, so it's a new capture. We register the capture above, but we need to search + * again to get the binding within our current function, since the one we have now + * is the originating binding. + * + * All future lookups for this capatured local, in this function, will skip this branch. */ found_local = current_frame->find_local_or_capture(sym); } @@ -215,7 +449,7 @@ namespace jank::analyze } /* If it's not a local and it matches a fn's name, we're dealing with a - * reference to a fn. We don't want to go to var lookup now. */ + * reference to a fn. We don't want to go to var lookup now. */ auto const found_named_recursion(current_frame->find_named_recursion(sym)); if(found_named_recursion.is_some()) { @@ -311,7 +545,7 @@ namespace jank::analyze if(param->equal(*sym)) { /* C++ doesn't allow multiple params with the same name, so we generate a unique - * name for shared params. */ + * name for shared params. */ param = make_box(runtime::context::unique_string("shadowed")); break; } @@ -323,7 +557,7 @@ namespace jank::analyze } /* We do this after building the symbols vector, since the & symbol isn't a param - * and would cause an off-by-one error. */ + * and would cause an off-by-one error. */ if(param_symbols.size() > runtime::max_params) { return err( @@ -354,8 +588,8 @@ namespace jank::analyze } /* If it turns out this function uses recur, we need to ensure that its tail expression - * is boxed. This is because unboxed values may use IIFE for initialization, which will - * not work with the generated while/continue we use for recursion. */ + * is boxed. This is because unboxed values may use IIFE for initialization, which will + * not work with the generated while/continue we use for recursion. */ if(fn_ctx->is_tail_recursive) { body_do = step::force_boxed(std::move(body_do)); @@ -465,7 +699,7 @@ namespace jank::analyze } /* The variadic arity, if present, must have at least as many fixed params as the - * highest non-variadic arity. Clojure requires this. */ + * highest non-variadic arity. Clojure requires this. */ if(found_variadic > 0) { for(auto const &arity : arities) @@ -615,6 +849,8 @@ namespace jank::analyze expression_position const position, option const &fn_ctx, native_bool const needs_box) + // (let* [vec-2426 [1 3] a (clojure.core/nth vec-2426 0 nil) b (clojure.core/nth vec-2426 1 nil)]) + { if(o->count() < 2) { @@ -745,49 +981,49 @@ namespace jank::analyze } /* We take the lazy way out here. Clojure JVM handles loop* with two cases: - * - * 1. Statements, which expand the loop inline and use labels, gotos, and mutation - * 2. Expressions, which wrap the loop in a fn which does the same - * - * We do something similar to the second, but we transform the loop into just function - * recursion and call the function on the spot. It works for both cases, though it's - * marginally less efficient. - * - * However, there's an additional snag. If we just transform the loop into a fn to - * call immediately, we get something like this: - * - * ``` - * (loop* [a 1 - * b 2] - * (println a b)) - * ``` - * - * Becoming this: - * - * ``` - * ((fn* [a b] - * (println a b)) 1 2) - * ``` - * - * This works great, but loop* can actually be used as a let*. That means we can do something - * like this: - * - * ``` - * (loop* [a 1 - * b (* 2 a)] - * (println a b)) - * ``` - * - * But we can't translate that like the one above, since we'd be referring to `a` before it - * was bound. So we get around this by actually just lifting all of this into a let*: - * - * ``` - * (let* [a 1 - * b (* 2 a)] - * ((fn* [a b] - * (println a b)) a b)) - * ``` - */ + * + * 1. Statements, which expand the loop inline and use labels, gotos, and mutation + * 2. Expressions, which wrap the loop in a fn which does the same + * + * We do something similar to the second, but we transform the loop into just function + * recursion and call the function on the spot. It works for both cases, though it's + * marginally less efficient. + * + * However, there's an additional snag. If we just transform the loop into a fn to + * call immediately, we get something like this: + * + * ``` + * (loop* [a 1 + * b 2] + * (println a b)) + * ``` + * + * Becoming this: + * + * ``` + * ((fn* [a b] + * (println a b)) 1 2) + * ``` + * + * This works great, but loop* can actually be used as a let*. That means we can do something + * like this: + * + * ``` + * (loop* [a 1 + * b (* 2 a)] + * (println a b)) + * ``` + * + * But we can't translate that like the one above, since we'd be referring to `a` before it + * was bound. So we get around this by actually just lifting all of this into a let*: + * + * ``` + * (let* [a 1 + * b (* 2 a)] + * ((fn* [a b] + * (println a b)) a b)) + * ``` + */ runtime::detail::native_persistent_list const args{ binding_syms.rbegin(), binding_syms.rend() }; auto const params(make_box(binding_syms.persistent())); @@ -810,7 +1046,7 @@ namespace jank::analyze native_bool needs_box) { /* We can't (yet) guarantee that each branch of an if returns the same unboxed type, - * so we're unable to unbox them. */ + * so we're unable to unbox them. */ needs_box = true; auto const form_count(o->count()); @@ -851,12 +1087,14 @@ namespace jank::analyze else_expr_opt = else_expr.expect_ok(); } - return make_box(expr::if_{ + auto const if_expr{ make_box(expr::if_{ expression_base{ {}, position, current_frame, needs_box }, condition_expr.expect_ok(), then_expr.expect_ok(), else_expr_opt - }); + })}; + std::cout << "analyze_if: if expr position is " << expression_position_str(if_expr->get_base()->position) << std::endl; + return if_expr; } processor::expression_result @@ -963,7 +1201,7 @@ namespace jank::analyze auto try_frame( make_box(local_frame::frame_type::try_, current_frame->rt_ctx, 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. */ + * It holds the exception value which was caught. */ auto catch_frame( make_box(local_frame::frame_type::catch_, current_frame->rt_ctx, current_frame)); auto finally_frame(make_box(local_frame::frame_type::finally, @@ -1215,7 +1453,7 @@ namespace jank::analyze for(auto const &kv : typed_o->data) { /* The two maps (hash and sorted) have slightly different iterators, so we need to - * pull out the entries differently. */ + * pull out the entries differently. */ object_ptr first{}, second{}; if constexpr(std::same_as) { @@ -1354,7 +1592,7 @@ namespace jank::analyze auto const var_deref(boost::get>(&source->data)); /* If this expression doesn't need to be boxed, based on where it's called, we can dig - * into the call details itself to see if the function supports unboxed returns. Most don't. */ + * into the call details itself to see if the function supports unboxed returns. Most don't. */ if(var_deref && var_deref->var->meta.is_some()) { auto const arity_meta( @@ -1376,9 +1614,9 @@ namespace jank::analyze { auto const fn_res(vars.find(var_deref->var)); /* If we don't have a valid var_deref, we know the var exists, but we - * don't have an AST node for it. This means the var came in through - * a pre-compiled module. In that case, we can only rely on meta to - * tell us what we need. */ + * don't have an AST node for it. This means the var came in through + * a pre-compiled module. In that case, we can only rely on meta to + * tell us what we need. */ if(fn_res != vars.end()) { auto const fn(boost::get>(&fn_res->second->data)); @@ -1423,11 +1661,11 @@ namespace jank::analyze } /* If we have more args than a fn allows, we need to pack all of the extras - * into a single list and tack that on at the end. So, if max_params is 10, and - * we pass 15 args, we'll pass 10 normally and then we'll have a special 11th - * arg which is a list containing the 5 remaining params. We rely on dynamic_call - * to do the hard work of packing that in the shape the function actually wants, - * based on its highest fixed arity flag. */ + * into a single list and tack that on at the end. So, if max_params is 10, and + * we pass 15 args, we'll pass 10 normally and then we'll have a special 11th + * arg which is a list containing the 5 remaining params. We rely on dynamic_call + * to do the hard work of packing that in the shape the function actually wants, + * based on its highest fixed arity flag. */ if(runtime::max_params < arg_count) { native_vector packed_arg_exprs; @@ -1524,7 +1762,7 @@ namespace jank::analyze return analyze_symbol(typed_o, current_frame, position, fn_ctx, needs_box); } /* This is used when building code from macros; they may end up being other forms of - * sequences and not just lists. */ + * sequences and not just lists. */ if constexpr(runtime::behavior::sequential) { return analyze_call(runtime::obj::persistent_list::create(typed_o->seq()), diff --git a/compiler+runtime/src/cpp/jank/c_api.cpp b/compiler+runtime/src/cpp/jank/c_api.cpp index 210fc1cb..52d09b87 100644 --- a/compiler+runtime/src/cpp/jank/c_api.cpp +++ b/compiler+runtime/src/cpp/jank/c_api.cpp @@ -809,6 +809,22 @@ extern "C" return to_hash(o_obj); } + native_integer to_integer(object const *o) + { + if (o->type == object_type::integer) + { + return dyn_cast(o)->data; + } + + return to_hash(o); + } + + jank_native_integer jank_to_integer(jank_object_ptr const o) + { + auto const o_obj(reinterpret_cast(o)); + return to_integer(o_obj); + } + void jank_set_meta(jank_object_ptr const o, jank_object_ptr const meta) { auto const o_obj(reinterpret_cast(o)); @@ -827,7 +843,10 @@ extern "C" void jank_throw(jank_object_ptr const o) { - throw runtime::object_ptr{ reinterpret_cast(o) }; + auto const o_obj(reinterpret_cast(o)); + std::cout << "throw object type: " << object_type_str(o_obj->type) << std::endl; + std::cout << "throw object to_string: " << to_string(o_obj) << std::endl; + throw object_ptr{ reinterpret_cast(o) }; } jank_object_ptr jank_try(jank_object_ptr const try_fn, diff --git a/compiler+runtime/src/cpp/jank/codegen/llvm_processor.cpp b/compiler+runtime/src/cpp/jank/codegen/llvm_processor.cpp index df9e1950..79887f17 100644 --- a/compiler+runtime/src/cpp/jank/codegen/llvm_processor.cpp +++ b/compiler+runtime/src/cpp/jank/codegen/llvm_processor.cpp @@ -194,7 +194,7 @@ namespace jank::codegen } /* Run our optimization passes on the function, mutating it. */ - ctx->fpm->run(*fn, *ctx->fam); + // ctx->fpm->run(*fn, *ctx->fam); if(target != compilation_target::function) { @@ -807,6 +807,10 @@ namespace jank::codegen /* If we're in return position, our then/else branches will generate return instructions * for us. Since LLVM basic blocks can only have one terminating instruction, we need * to take care to not generate our own, too. */ + std::cout << "llvm_processor::gen: if position is " << expression_position_str(expr.position) + << std::endl; + std::cout << "llvm_processor::gen: if.then position is " << expression_position_str(expr.then->get_base()->position) + << std::endl; auto const is_return(expr.position == expression_position::tail); auto const condition(gen(expr.condition, arity)); auto const truthy_fn_type( @@ -879,6 +883,7 @@ namespace jank::codegen expr::function_arity const &arity) { /* TODO: Generate direct call to __cxa_throw. */ + std::cout << "gen throw: position is " << expression_position_str(expr.position) << std::endl; auto const value(gen(expr.value, arity)); auto const fn_type( llvm::FunctionType::get(ctx->builder->getPtrTy(), { ctx->builder->getPtrTy() }, false)); @@ -931,6 +936,127 @@ namespace jank::codegen return call; } + llvm::Value *llvm_processor::gen(expr::case_ const &expr, + expr::function_arity const &arity) + { + std::cout << "llvm_processor::gen: case position is " << expression_position_str(expr.position) + << std::endl; + auto const is_return(expr.position == expression_position::tail); + auto const current_fn(ctx->builder->GetInsertBlock()->getParent()); + auto const value(gen(expr.value_expr, arity)); + + // Call jank_to_integer to transform value into an integer for the switch statement + auto const integer_fn_type( + llvm::FunctionType::get(ctx->builder->getInt64Ty(), { ctx->builder->getPtrTy() }, false)); + auto const fn(ctx->module->getOrInsertFunction("jank_to_integer", integer_fn_type)); + llvm::SmallVector const args{ value }; + auto const call(ctx->builder->CreateCall(fn, args)); + auto const switch_val(ctx->builder->CreateIntCast(call, ctx->builder->getInt64Ty(), true)); + + // Create default and merge blocks + auto const default_block{ llvm::BasicBlock::Create(*ctx->llvm_ctx, "default", current_fn) }; + auto const merge_block{ + is_return ? nullptr : llvm::BasicBlock::Create(*ctx->llvm_ctx, "case_merge", current_fn) + }; + + // Create the switch instruction + auto const switch_ = ctx->builder->CreateSwitch(switch_val, default_block, expr.keys.size()); + + // Generate the default block + ctx->builder->SetInsertPoint(default_block); + auto const default_val{ gen(expr.default_expr, arity) }; + + if(!is_return) + { + std::cout << "llvm_processor::gen: case default block is not in return position, br to merge_block" + << std::endl; + ctx->builder->CreateBr(merge_block); + } + + // Track the current block for PHI node creation + const auto default_block_exit = ctx->builder->GetInsertBlock(); + + // Generate each case block + llvm::SmallVector case_blocks; + llvm::SmallVector case_values; + + for(size_t block_counter = 0; block_counter < expr.keys.size(); ++block_counter) + { + auto const block_name = fmt::format("case_{}", block_counter); + auto const block = llvm::BasicBlock::Create(*ctx->llvm_ctx, block_name, current_fn); + switch_->addCase( + llvm::ConstantInt::getSigned(ctx->builder->getInt64Ty(), expr.keys[block_counter]), + block); + + ctx->builder->SetInsertPoint(block); + auto const case_val = gen(expr.exprs[block_counter], arity); + case_values.push_back(case_val); + + if(!is_return) + { + ctx->builder->CreateBr(merge_block); + } + + case_blocks.push_back(ctx->builder->GetInsertBlock()); + } + + // If not in a tail position, generate the merge block and PHI node + if(!is_return) + { + current_fn->insert(current_fn->end(), merge_block); + ctx->builder->SetInsertPoint(merge_block); + auto const phi = ctx->builder->CreatePHI(ctx->builder->getPtrTy(), + static_cast(case_blocks.size() + 1), + "case_tmp"); + phi->addIncoming(default_val, default_block_exit); + + for(size_t i = 0; i < case_blocks.size(); ++i) + { + phi->addIncoming(case_values[i], case_blocks[i]); + } + return phi; + } + // return switch_; + return nullptr; + } + + // llvm::Value *llvm_processor::gen(expr::case_ const &expr, + // expr::function_arity const &arity) + // { + // auto const current_fn(ctx->builder->GetInsertBlock()->getParent()); + // auto const value(gen(expr.value_expr, arity)); + // + // auto const integer_fn_type( + // llvm::FunctionType::get(ctx->builder->getInt64Ty(), { ctx->builder->getPtrTy() }, false)); + // auto const fn(ctx->module->getOrInsertFunction("jank_to_integer", integer_fn_type)); + // llvm::SmallVector const args{ value }; + // auto const call(ctx->builder->CreateCall(fn, args)); + // auto const switch_val(ctx->builder->CreateIntCast(call, ctx->builder->getInt64Ty(), true)); + // + // auto const default_block = llvm::BasicBlock::Create(*ctx->llvm_ctx, "default", current_fn); + // auto const switch_ = ctx->builder->CreateSwitch(switch_val, default_block, expr.keys.size()); + // + // { + // llvm::IRBuilder<>::InsertPointGuard const guard{ *ctx->builder }; + // ctx->builder->SetInsertPoint(default_block); + // ctx->builder->Insert(gen(expr.default_expr, arity)); + // // ctx->builder->CreateRet(gen(expr.default_expr, arity)); + // } + // + // for(size_t block_counter = 0; block_counter < expr.keys.size(); ++block_counter) + // { + // auto const block_name = fmt::format("case_{}", block_counter); + // auto const block = llvm::BasicBlock::Create(*ctx->llvm_ctx, block_name, current_fn); + // switch_->addCase( + // llvm::ConstantInt::getSigned(ctx->builder->getInt64Ty(), expr.keys[block_counter]), + // block); + // ctx->builder->SetInsertPoint(block); + // // ctx->builder->CreateRet(gen(expr.exprs[block_counter], arity)); + // ctx->builder->Insert(gen(expr.exprs[block_counter], arity)); + // } + // return switch_; + // } + llvm::Value *llvm_processor::gen_var(obj::symbol_ptr const qualified_name) const { auto const found(ctx->var_globals.find(qualified_name)); @@ -1401,7 +1527,7 @@ namespace jank::codegen == variadic_arity->fn_ctx->param_count - 1); /* If there's a variadic arity, the highest fixed args is however many precede the "rest" - * args. Otherwise, the highest fixed args is just the highest fixed arity. */ + * args. Otherwise, the highest fixed args is just the highest fixed arity. */ auto const highest_fixed_args(variadic_arity ? variadic_arity->fn_ctx->param_count - 1 : highest_fixed_arity->fn_ctx->param_count); @@ -1508,8 +1634,8 @@ namespace jank::codegen ctx->global_ctor_block->insertInto(init); /* XXX: Modules are written to object files, which can't use global ctors until - * we're on the ORC runtime. Instead, we just generate our load function to call - * our global ctor first. */ + * we're on the ORC runtime. Instead, we just generate our load function to call + * our global ctor first. */ if(target != compilation_target::module) { llvm::appendToGlobalCtors(*ctx->module, init, 65535); diff --git a/compiler+runtime/src/cpp/jank/evaluate.cpp b/compiler+runtime/src/cpp/jank/evaluate.cpp index dfa17b77..027a3d32 100644 --- a/compiler+runtime/src/cpp/jank/evaluate.cpp +++ b/compiler+runtime/src/cpp/jank/evaluate.cpp @@ -599,20 +599,22 @@ namespace jank::evaluate object_ptr eval(expr::if_ const &expr) { - auto const condition(eval(expr.condition)); - if(truthy(condition)) - { - return eval(expr.then); - } - else if(expr.else_.is_some()) - { - return eval(expr.else_.unwrap()); - } - return obj::nil::nil_const(); + // auto const condition(eval(expr.condition)); + // if(truthy(condition)) + // { + // return eval(expr.then); + // } + // else if(expr.else_.is_some()) + // { + // return eval(expr.else_.unwrap()); + // } + // return obj::nil::nil_const(); + return dynamic_call(eval(wrap_expression(expr, "if_eval", {}))); } object_ptr eval(expr::throw_ const &expr) { + std::cout << "throw in eval" << std::endl; throw eval(expr.value); } @@ -637,4 +639,11 @@ namespace jank::evaluate e); } } + + object_ptr eval(expr::case_ const &expr) { + // wrap case into a function + // do ir gen on the function + // then call the function + return dynamic_call(eval(wrap_expression(expr, "case", {}))); + } } diff --git a/compiler+runtime/src/cpp/jank/util/string_builder.cpp b/compiler+runtime/src/cpp/jank/util/string_builder.cpp index 18bf633a..cffdb84a 100644 --- a/compiler+runtime/src/cpp/jank/util/string_builder.cpp +++ b/compiler+runtime/src/cpp/jank/util/string_builder.cpp @@ -14,7 +14,7 @@ namespace jank::util auto const new_capacity{ std::bit_ceil(required) }; auto const new_data{ new(PointerFreeGC) string_builder::value_type[new_capacity] }; string_builder::traits_type::copy(new_data, sb.buffer, sb.pos); - delete sb.buffer; + // delete sb.buffer; sb.buffer = new_data; sb.capacity = new_capacity; } @@ -47,7 +47,7 @@ namespace jank::util string_builder::~string_builder() { - delete buffer; + // delete buffer; } string_builder &string_builder::operator()(native_bool const d) & diff --git a/compiler+runtime/test/cpp/jank/analyze/processor.cpp b/compiler+runtime/test/cpp/jank/analyze/processor.cpp new file mode 100644 index 00000000..5c2193d4 --- /dev/null +++ b/compiler+runtime/test/cpp/jank/analyze/processor.cpp @@ -0,0 +1,95 @@ +// #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +// #include "doctest/doctest.h" +// #include "jank/analyze/processor.hpp" +// #include "jank/runtime/context.hpp" +// #include "jank/runtime/obj/symbol.hpp" +// +// #include +// +// // using namespace jank::runtime; +// namespace jank::analyze { +// TEST_SUITE("processor") { +// TEST_CASE("analyze_def with valid input") { +// runtime::context rt_ctx; +// processor proc(rt_ctx); +// +// // Create a valid def form: (def x 42) +// auto sym = make_box("x"); +// auto val = 42ll; +// // auto const wrapped( +// // make_box(std::in_place, make_box("fn*"), args, call)); +// +// auto const def_form ( make_box(std::in_place, +// make_box("def"), // First element: "def" symbol +// {sym}, // Second element: variable symbol +// val // Third element: value +// )); +// +// auto frame = make_box(local_frame::frame_type::root, rt_ctx, none); +// auto result = proc.analyze(def_form, frame, expression_position::value, none, true); +// +// CHECK(result.is_ok()); +// auto expr = result.expect_ok(); +// CHECK(expr != nullptr); +// } +// +// TEST_CASE("analyze_def with missing elements") { +// runtime::context rt_ctx; +// processor proc(rt_ctx); +// +// // Create an invalid def form: (def x) +// auto sym = make_box("x"); +// auto def_form = make_box(std::in_place, +// make_box("def"), +// sym +// ); +// +// auto frame = make_box(local_frame::frame_type::root, rt_ctx, none); +// auto result = proc.analyze(def_form, frame, expression_position::value, none, true); +// +// CHECK(result.is_err()); +// } +// +// TEST_CASE("analyze_if with valid input") { +// runtime::context rt_ctx; +// processor proc(rt_ctx); +// +// // Create a valid if form: (if true 1 0) +// auto condition = make_box(true); +// auto then_expr = 1ll; +// auto else_expr = 0ll; +// auto if_form = make_box(std::in_place, +// make_box("if"), +// condition, +// then_expr, +// else_expr +// ); +// +// auto frame = make_box(local_frame::frame_type::root, rt_ctx, none); +// auto result = proc.analyze(if_form, frame, expression_position::value, none, true); +// +// CHECK(result.is_ok()); +// auto expr = result.expect_ok(); +// CHECK(expr != nullptr); +// } +// +// TEST_CASE("analyze_if with missing else branch") { +// runtime::context rt_ctx; +// processor proc(rt_ctx); +// +// // Create an invalid if form: (if true 1) +// auto condition = make_box(true); +// auto then_expr = 1ll; +// auto if_form = make_box(std::in_place, +// make_box("if"), +// condition, +// then_expr +// ); +// +// auto frame = make_box(local_frame::frame_type::root, rt_ctx, none); +// auto result = proc.analyze(if_form, frame, expression_position::value, none, true); +// +// CHECK(result.is_ok()); // Should allow missing else as it defaults to nil. +// } +// } +// } diff --git a/compiler+runtime/test/jank/form/case/pass-mixed-clauses-case.jank b/compiler+runtime/test/jank/form/case/pass-mixed-clauses-case.jank new file mode 100644 index 00000000..ed591c56 --- /dev/null +++ b/compiler+runtime/test/jank/form/case/pass-mixed-clauses-case.jank @@ -0,0 +1,19 @@ +(case 1 1 :one + 2 :two + "3" :three + :default) + +(case "3" 1 :one + 2 :two + "3" :three + :default) + +(case 1 1 :one + 2 :two + "3" :three) + +(case "3" 1 :one + 2 :two + "3" :three) + +:success \ No newline at end of file diff --git a/compiler+runtime/test/jank/form/case/pass-simple-case-no-default.jank b/compiler+runtime/test/jank/form/case/pass-simple-case-no-default.jank new file mode 100644 index 00000000..01cb6bbb --- /dev/null +++ b/compiler+runtime/test/jank/form/case/pass-simple-case-no-default.jank @@ -0,0 +1,9 @@ +(case 1 + 1 :one + 2 :two) + +(case "3" + 1 :one + 2 :two) + +:success \ No newline at end of file diff --git a/compiler+runtime/test/jank/form/case/pass-simple-case.jank b/compiler+runtime/test/jank/form/case/pass-simple-case.jank new file mode 100644 index 00000000..c426ad30 --- /dev/null +++ b/compiler+runtime/test/jank/form/case/pass-simple-case.jank @@ -0,0 +1,6 @@ +(case 1 + 1 :one + 2 :two + :default) + +:success \ No newline at end of file