From 7cad956ff150a06bcb0e750db76bc1a849cd9b8d Mon Sep 17 00:00:00 2001 From: jeaye Date: Fri, 16 Feb 2024 18:33:43 -0800 Subject: [PATCH] Add initial transient support This includes transient_hash_map and the core `transient` and `persistent!` fns. --- CMakeLists.txt | 1 + include/cpp/jank/prelude.hpp | 1 + .../behavior/associatively_writable.hpp | 7 ++ .../cpp/jank/runtime/behavior/consable.hpp | 7 ++ .../jank/runtime/behavior/transientable.hpp | 18 +++ include/cpp/jank/runtime/erasure.hpp | 6 + .../obj/detail/base_persistent_map.hpp | 1 + .../jank/runtime/obj/persistent_hash_map.hpp | 6 + .../jank/runtime/obj/persistent_string.hpp | 2 +- .../jank/runtime/obj/transient_hash_map.hpp | 61 +++++++++ include/cpp/jank/runtime/object.hpp | 1 + .../jank/runtime/obj/persistent_hash_map.cpp | 9 +- .../jank/runtime/obj/transient_hash_map.cpp | 117 ++++++++++++++++++ src/jank/clojure/core.jank | 36 ++++++ 14 files changed, 269 insertions(+), 4 deletions(-) create mode 100644 include/cpp/jank/runtime/behavior/transientable.hpp create mode 100644 include/cpp/jank/runtime/obj/transient_hash_map.hpp create mode 100644 src/cpp/jank/runtime/obj/transient_hash_map.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3878cd059..7d2c8bfdb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -170,6 +170,7 @@ add_library( src/cpp/jank/runtime/obj/persistent_vector_sequence.cpp src/cpp/jank/runtime/obj/persistent_array_map.cpp src/cpp/jank/runtime/obj/persistent_hash_map.cpp + src/cpp/jank/runtime/obj/transient_hash_map.cpp src/cpp/jank/runtime/obj/persistent_set.cpp src/cpp/jank/runtime/obj/persistent_string.cpp src/cpp/jank/runtime/obj/cons.cpp diff --git a/include/cpp/jank/prelude.hpp b/include/cpp/jank/prelude.hpp index fe1d85068..40a6a22ea 100644 --- a/include/cpp/jank/prelude.hpp +++ b/include/cpp/jank/prelude.hpp @@ -27,3 +27,4 @@ #include #include #include +#include diff --git a/include/cpp/jank/runtime/behavior/associatively_writable.hpp b/include/cpp/jank/runtime/behavior/associatively_writable.hpp index a7db9e7c8..8bf45646a 100644 --- a/include/cpp/jank/runtime/behavior/associatively_writable.hpp +++ b/include/cpp/jank/runtime/behavior/associatively_writable.hpp @@ -10,4 +10,11 @@ namespace jank::runtime::behavior t->assoc(object_ptr{}, object_ptr{}) } -> std::convertible_to; }; + + template + concept associatively_writable_in_place = requires(T * const t) { + { + t->assoc_in_place(object_ptr{}, object_ptr{}) + } -> std::convertible_to; + }; } diff --git a/include/cpp/jank/runtime/behavior/consable.hpp b/include/cpp/jank/runtime/behavior/consable.hpp index 467d389a2..2135bd27c 100644 --- a/include/cpp/jank/runtime/behavior/consable.hpp +++ b/include/cpp/jank/runtime/behavior/consable.hpp @@ -11,4 +11,11 @@ namespace jank::runtime::behavior t->cons(object_ptr{}) }; // -> consable }; + + template + concept consable_in_place = requires(T * const t) { + { + t->cons_in_place(object_ptr{}) + }; // -> consable_in_place + }; } diff --git a/include/cpp/jank/runtime/behavior/transientable.hpp b/include/cpp/jank/runtime/behavior/transientable.hpp new file mode 100644 index 000000000..a6b62505e --- /dev/null +++ b/include/cpp/jank/runtime/behavior/transientable.hpp @@ -0,0 +1,18 @@ +#pragma once + +namespace jank::runtime::behavior +{ + template + concept persistentable = requires(T * const t) { + { + t->to_persistent() + } -> std::convertible_to; + }; + + template + concept transientable = requires(T * const t) { + { + t->to_transient() + } -> std::convertible_to; + }; +} diff --git a/include/cpp/jank/runtime/erasure.hpp b/include/cpp/jank/runtime/erasure.hpp index 19ca66f71..d0c5e4d0a 100644 --- a/include/cpp/jank/runtime/erasure.hpp +++ b/include/cpp/jank/runtime/erasure.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -173,6 +174,11 @@ namespace jank::runtime std::forward(args)...); } break; + case object_type::transient_hash_map: + { + return fn(expect_object(erased), std::forward(args)...); + } + break; case object_type::persistent_set: { return fn(expect_object(erased), std::forward(args)...); diff --git a/include/cpp/jank/runtime/obj/detail/base_persistent_map.hpp b/include/cpp/jank/runtime/obj/detail/base_persistent_map.hpp index 209abe16c..3e9bd2519 100644 --- a/include/cpp/jank/runtime/obj/detail/base_persistent_map.hpp +++ b/include/cpp/jank/runtime/obj/detail/base_persistent_map.hpp @@ -40,6 +40,7 @@ namespace jank::runtime::obj::detail return false; } + /* TODO: Is this enough? Value comparison may be needed. */ return to_hash() == hash::visit(p); } diff --git a/include/cpp/jank/runtime/obj/persistent_hash_map.hpp b/include/cpp/jank/runtime/obj/persistent_hash_map.hpp index c0a321e3b..3bf2ec8ca 100644 --- a/include/cpp/jank/runtime/obj/persistent_hash_map.hpp +++ b/include/cpp/jank/runtime/obj/persistent_hash_map.hpp @@ -11,6 +11,9 @@ namespace jank::runtime { using persistent_array_map = static_object; using persistent_array_map_ptr = native_box; + + using transient_hash_map = static_object; + using transient_hash_map_ptr = native_box; } template <> @@ -68,6 +71,9 @@ namespace jank::runtime object_ptr call(object_ptr) const; object_ptr call(object_ptr, object_ptr) const; + /* behavior::transientable */ + obj::transient_hash_map_ptr to_transient() const; + value_type data{}; }; diff --git a/include/cpp/jank/runtime/obj/persistent_string.hpp b/include/cpp/jank/runtime/obj/persistent_string.hpp index c6355095d..3ef765672 100644 --- a/include/cpp/jank/runtime/obj/persistent_string.hpp +++ b/include/cpp/jank/runtime/obj/persistent_string.hpp @@ -8,7 +8,7 @@ namespace jank::runtime template <> struct static_object : gc { - static constexpr bool pointer_free{ true }; + static constexpr bool pointer_free{ false }; static_object() = default; static_object(static_object &&) = default; diff --git a/include/cpp/jank/runtime/obj/transient_hash_map.hpp b/include/cpp/jank/runtime/obj/transient_hash_map.hpp new file mode 100644 index 000000000..492f0c2b0 --- /dev/null +++ b/include/cpp/jank/runtime/obj/transient_hash_map.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include + +namespace jank::runtime +{ + template <> + struct static_object : gc + { + static constexpr bool pointer_free{ false }; + using value_type = detail::native_transient_hash_map; + + static_object() = default; + static_object(static_object &&) = default; + static_object(static_object const &) = default; + static_object(detail::native_persistent_hash_map const &d); + static_object(detail::native_persistent_hash_map &&d); + static_object(value_type &&d); + + static native_box empty() + { + static auto const ret(make_box()); + return ret; + } + + /* behavior::objectable */ + native_bool equal(object const &) const; + native_persistent_string const &to_string() const; + void to_string(fmt::memory_buffer &buff) const; + native_hash to_hash() const; + + /* behavior::countable */ + size_t count() const; + + /* behavior::associatively_readable */ + object_ptr get(object_ptr const key) const; + object_ptr get(object_ptr const key, object_ptr const fallback) const; + object_ptr get_entry(object_ptr key) const; + native_bool contains(object_ptr key) const; + + /* behavior::associatively_writable_in_place */ + native_box assoc_in_place(object_ptr key, object_ptr val); + + /* behavior::consable_in_place */ + native_box cons_in_place(object_ptr head); + + /* behavior::callable */ + object_ptr call(object_ptr) const; + object_ptr call(object_ptr, object_ptr) const; + + object base{ object_type::transient_hash_map }; + value_type data; + mutable native_hash hash{}; + }; + + namespace obj + { + using transient_hash_map = static_object; + using transient_hash_map_ptr = native_box; + } +} diff --git a/include/cpp/jank/runtime/object.hpp b/include/cpp/jank/runtime/object.hpp index e6c1fbf2f..85dae4fc8 100644 --- a/include/cpp/jank/runtime/object.hpp +++ b/include/cpp/jank/runtime/object.hpp @@ -22,6 +22,7 @@ namespace jank::runtime persistent_array_map_sequence, persistent_hash_map, persistent_hash_map_sequence, + transient_hash_map, persistent_set, cons, range, diff --git a/src/cpp/jank/runtime/obj/persistent_hash_map.cpp b/src/cpp/jank/runtime/obj/persistent_hash_map.cpp index e245eeff0..ccb4e4505 100644 --- a/src/cpp/jank/runtime/obj/persistent_hash_map.cpp +++ b/src/cpp/jank/runtime/obj/persistent_hash_map.cpp @@ -1,10 +1,8 @@ -#include -#include - #include #include #include #include +#include namespace jank::runtime { @@ -147,4 +145,9 @@ namespace jank::runtime } return *found; } + + obj::transient_hash_map_ptr obj::persistent_hash_map::to_transient() const + { + return make_box(data); + } } diff --git a/src/cpp/jank/runtime/obj/transient_hash_map.cpp b/src/cpp/jank/runtime/obj/transient_hash_map.cpp new file mode 100644 index 000000000..b2ef4d23d --- /dev/null +++ b/src/cpp/jank/runtime/obj/transient_hash_map.cpp @@ -0,0 +1,117 @@ +#include +#include +#include + +namespace jank::runtime +{ + obj::transient_hash_map::static_object(runtime::detail::native_persistent_hash_map &&d) + : data{ d.transient() } + { + } + + obj::transient_hash_map::static_object(runtime::detail::native_persistent_hash_map const &d) + : data{ d.transient() } + { + } + + obj::transient_hash_map::static_object(runtime::detail::native_transient_hash_map &&d) + : data{ d } + { + } + + native_hash obj::transient_hash_map::to_hash() const + { + if(hash) + { + return hash; + } + + return hash = hash::unordered(data.begin(), data.end()); + } + + size_t obj::transient_hash_map::count() const + { + return data.size(); + } + + object_ptr obj::transient_hash_map::get(object_ptr const key) const + { + auto const res(data.find(key)); + if(res) + { + return *res; + } + return obj::nil::nil_const(); + } + + object_ptr obj::transient_hash_map::get(object_ptr const key, object_ptr const fallback) const + { + auto const res(data.find(key)); + if(res) + { + return *res; + } + return fallback; + } + + object_ptr obj::transient_hash_map::get_entry(object_ptr const key) const + { + auto const res(data.find(key)); + if(res) + { + return make_box(key, *res); + } + return obj::nil::nil_const(); + } + + native_bool obj::transient_hash_map::contains(object_ptr const key) const + { + return data.find(key); + } + + obj::transient_hash_map_ptr + obj::transient_hash_map::assoc_in_place(object_ptr const key, object_ptr const val) + { + data.set(key, val); + return this; + } + + obj::transient_hash_map_ptr obj::transient_hash_map::cons_in_place(object_ptr const head) + { + if(head->type != object_type::persistent_vector) + { + throw std::runtime_error{ fmt::format("invalid map entry: {}", + runtime::detail::to_string(head)) }; + } + + auto const vec(expect_object(head)); + if(vec->count() != 2) + { + throw std::runtime_error{ fmt::format("invalid map entry: {}", + runtime::detail::to_string(head)) }; + } + + data.set(vec->data[0], vec->data[1]); + return this; + } + + object_ptr obj::transient_hash_map::call(object_ptr const o) const + { + auto const found(data.find(o)); + if(!found) + { + return obj::nil::nil_const(); + } + return *found; + } + + object_ptr obj::transient_hash_map::call(object_ptr const o, object_ptr const fallback) const + { + auto const found(data.find(o)); + if(!found) + { + return fallback; + } + return *found; + } +} diff --git a/src/jank/clojure/core.jank b/src/jank/clojure/core.jank index 0fa10574d..7c3a53c33 100644 --- a/src/jank/clojure/core.jank +++ b/src/jank/clojure/core.jank @@ -396,6 +396,41 @@ (defn some? [o] (native/raw "__value = (o == obj::nil::nil_const()) ? #{ false }# : #{ true }#")) +; Transients. +; Returns a new, transient version of the collection, in constant time. +(defn transient [o] + (native/raw "__value = visit_object + ( + [=](auto const typed_o) -> object_ptr + { + using T = typename decltype(typed_o)::value_type; + + if constexpr(behavior::transientable) + { return typed_o->to_transient(); } + else + { throw #{ (ex-info :not-transientable {:o o}) }#; } + }, + #{ o }# + );")) + +; Returns a new, persistent version of the transient collection, in +; constant time. The transient collection cannot be used after this +; call, any such use will throw an exception. +(defn persistent! [o] + (native/raw "__value = visit_object + ( + [=](auto const typed_o) -> object_ptr + { + using T = typename decltype(typed_o)::value_type; + + if constexpr(behavior::persistentable) + { return typed_o->to_persistent(); } + else + { throw #{ (ex-info :not-persistentable {:o o}) }#; } + }, + #{ o }# + );")) + ; Functions. (defn- spread [arglist] @@ -672,6 +707,7 @@ ;; Maps. (defn hash-map ([] + ; NOTE: Actually returns an array map. Clojure does the same. {}) ([& kvs] (native/raw "__value = obj::persistent_hash_map::create_from_seq(#{ kvs }#);")))