runtime 1.0.0
Checked CEL programs, owned values, and reusable execution stacks
Loading...
Searching...
No Matches
dyn_segment.hpp
1// SPDX-License-Identifier: BSL-1.0
2#ifndef CEL_RUNTIME_DYN_SEGMENT_HPP
3#define CEL_RUNTIME_DYN_SEGMENT_HPP
4#include <cel/runtime/detail/raw_segment.hpp>
5#include <concepts>
6#include <new>
7#include <optional>
8#include <vector>
9
10namespace cel {
13
19 std::size_t bytes = 0;
20 std::size_t depth = 0;
21};
22
24inline bool operator==(const stack_origin& left, const stack_origin& right) noexcept {
25 return left.bytes == right.bytes && left.depth == right.depth;
26}
27class compiled_segment;
28namespace detail {
29class prepared_raw_segment;
30}
31template <class R, class... Args>
33
39 std::size_t slots;
40};
41
49 std::size_t alignment = 1;
50 std::size_t frame_extent = 0;
51 std::size_t live_slots = 0;
52
61 stack_reservation for_prefix(std::size_t cursor, std::size_t depth) const;
62};
63
64namespace detail {
65struct frozen_program;
67template <class T>
68inline constexpr bool native_leaf = true;
70template <class T, class A>
71inline constexpr bool native_leaf<std::vector<T, A>> = false;
73template <class... T>
74inline constexpr bool native_leaf<std::tuple<T...>> = false;
76template <class T>
77struct callback_signature {};
79template <class R, class... A>
80struct callback_signature<R (*)(A...)> {
81 using arguments = std::tuple<std::remove_cvref_t<A>...>;
82};
84template <class R, class... A>
85struct callback_signature<R (*)(A...) noexcept> : callback_signature<R (*)(A...)> {};
87template <class C, class R, class... A>
88struct callback_signature<R (C::*)(A...) const> : callback_signature<R (*)(A...)> {};
90template <class C, class R, class... A>
91struct callback_signature<R (C::*)(A...) const noexcept> : callback_signature<R (*)(A...)> {};
93template <class C, class R, class... A>
94struct callback_signature<R (C::*)(A...) const&> : callback_signature<R (*)(A...)> {};
96template <class C, class R, class... A>
97struct callback_signature<R (C::*)(A...) const & noexcept> : callback_signature<R (*)(A...)> {};
101template <class F, class R, class... A>
102consteval auto generic_member() {
103 if constexpr (requires { static_cast<R (F::*)(A...) const>(&F::template operator()); })
104 return static_cast<R (F::*)(A...) const>(&F::template operator());
105 else if constexpr (requires { static_cast<R (F::*)(A...) const&>(&F::template operator()); })
106 return static_cast<R (F::*)(A...) const&>(&F::template operator());
107 else if constexpr (requires {
108 static_cast<R (F::*)(const A&...) const>(&F::template operator());
109 })
110 return static_cast<R (F::*)(const A&...) const>(&F::template operator());
111 else if constexpr (requires {
112 static_cast<R (F::*)(const A&...) const&>(&F::template operator());
113 })
114 return static_cast<R (F::*)(const A&...) const&>(&F::template operator());
115 else if constexpr (requires { static_cast<R (F::*)(A&&...) const>(&F::template operator()); })
116 return static_cast<R (F::*)(A&&...) const>(&F::template operator());
117 else if constexpr (requires { static_cast<R (F::*)(A&&...) const&>(&F::template operator()); })
118 return static_cast<R (F::*)(A&&...) const&>(&F::template operator());
119}
121template <class F, class R, class... A>
122consteval bool exact_callback() {
123 using callback_type = std::decay_t<F>;
124 if constexpr (!std::is_invocable_v<const callback_type&, A...>)
125 return false;
126 else {
127 constexpr bool result_matches =
128 std::same_as<R, std::remove_cvref_t<std::invoke_result_t<const callback_type&, A...>>>;
129 if constexpr (requires { typename callback_signature<callback_type>::arguments; })
130 return result_matches &&
131 std::same_as<typename callback_signature<callback_type>::arguments,
132 std::tuple<A...>>;
133 else if constexpr (requires {
134 typename callback_signature<
135 decltype(&callback_type::operator())>::arguments;
136 })
137 return result_matches &&
138 std::same_as<
139 typename callback_signature<decltype(&callback_type::operator())>::arguments,
140 std::tuple<A...>>;
141 else {
142 using member =
143 decltype(generic_member<callback_type,
144 std::invoke_result_t<const callback_type&, A...>, A...>());
145 if constexpr (std::is_void_v<member>)
146 return false;
147 else
148 return result_matches && std::is_invocable_v<member, const callback_type&, A...>;
149 }
150 }
151}
153template <class Signature, class F>
154struct signature_binding {
155 static constexpr bool available = false;
156};
158template <class R, class... A, class F>
159struct signature_binding<R(A...), F> {
161 static consteval auto select() {
162 if constexpr (requires { static_cast<R (F::*)(A...) const>(&F::operator()); })
163 return static_cast<R (F::*)(A...) const>(&F::operator());
164 else if constexpr (requires { static_cast<R (F::*)(A...) const&>(&F::operator()); })
165 return static_cast<R (F::*)(A...) const&>(&F::operator());
166 }
167 static constexpr bool available = !std::is_void_v<decltype(select())>;
169};
171template <class Signature, class F>
172class signature_callback;
174template <class R, class... A, class F>
175class signature_callback<R(A...), F> {
176 F _callback;
177
178public:
181 template <class G>
182 requires(!std::same_as<std::remove_cvref_t<G>, signature_callback> &&
183 std::constructible_from<F, G&&>) explicit signature_callback(G&& callback) :
184 _callback(std::forward<G>(callback)) {}
187 R operator()(A... arguments) const {
188 return std::invoke(signature_binding<R(A...), F>::select(), _callback,
189 std::forward<A>(arguments)...);
190 }
191};
193struct argument_read {
194 std::size_t index;
195 value_type type;
196};
198struct construction_effect {
199 std::size_t cursor;
200 stack_requirements requirements;
201};
202} // namespace detail
203
211template <class Signature, class F>
212requires(detail::signature_binding<Signature, std::decay_t<F>>::available) auto with_signature(
213 F&& callback) {
214 return detail::signature_callback<Signature, std::decay_t<F>>(std::forward<F>(callback));
215}
216
228 detail::raw_segment _body;
229 std::vector<value_type> _stack;
230 std::vector<detail::argument_read> _reads;
231 std::vector<std::size_t> _end_cursors;
232 stack_requirements _requirements;
233 stack_origin _origin;
234 std::vector<std::optional<stack_requirements>> _known_calls;
238 void pack(value_type type, std::span<const std::size_t> permutation, stack_origin ambient);
241 void project(std::size_t index, type_kind category);
247 detail::construction_effect check_operation(std::span<const value_type> inputs,
248 std::span<const value_type> outputs);
253 void apply_operation(std::size_t consumed,
254 std::span<const value_type> outputs,
255 const detail::construction_effect& effect) noexcept;
256
257public:
260 dyn_segment() = default;
266 explicit dyn_segment(stack_origin origin) : _origin(origin) {}
268 dyn_segment(const dyn_segment&) = delete;
273 dyn_segment(dyn_segment&&) noexcept = default;
277 dyn_segment& operator=(dyn_segment&&) noexcept = default;
280 stack_origin origin() const noexcept { return _origin; }
287 std::span<const value_type> operand_types(std::size_t arity) const noexcept {
288 if (arity > _stack.size()) return {};
289 return std::span<const value_type>(_stack).last(arity);
290 }
291
299 stack_origin origin_below(std::size_t count) const;
310 void make_tuple(std::size_t count, stack_origin ambient);
317 void make_array(std::size_t count, value_type element, stack_origin ambient);
322 void make_record(value_type record, stack_origin ambient);
331 std::span<const std::size_t> permutation,
332 stack_origin ambient);
337 void project_tuple(std::size_t index);
342 void project_record(std::string_view name);
348 // [knowledge] Check representability before the size_t cast, then exact bounds before access.
364 void if_else(dyn_segment then_body, dyn_segment else_body);
381 void push_closure(compiled_segment body, std::span<const value_type> parameters);
391 void invoke_closure(std::size_t arity);
399 void push_argument(std::size_t index, value_type type);
407 void just(value literal);
426 template <class R, class... Args, class F>
427 requires(detail::exact_callback<F, R, Args...>()) void op(F&& callback) {
428 static_assert((std::same_as<Args, std::remove_cvref_t<Args>> && ...));
429 const std::array<value_type, sizeof...(Args)> inputs{
430 detail::concrete_adapter<Args>::type()...};
431 const auto outputs = [] {
432 if constexpr (std::is_void_v<R>)
433 return std::array<value_type, 0>{};
434 else
435 return std::array{detail::concrete_adapter<R>::type()};
436 }();
437 const auto effect = check_operation(inputs, outputs);
438 _body.template push_op<R, Args...>(
439 [callback = std::forward<F>(callback)](detail::frame&, Args&&... operands) -> R {
440 return std::invoke(callback, std::move(operands)...);
441 });
442 apply_operation(sizeof...(Args), outputs, effect);
443 }
444
448 template <class R, class F>
449 requires(detail::exact_callback<F, R>()) void op0(F&& callback) {
450 op<R>(std::forward<F>(callback));
451 }
452
456 template <class R, class A, class F>
457 requires(detail::exact_callback<F, R, A>()) void op1(F&& callback) {
458 op<R, A>(std::forward<F>(callback));
459 }
460
464 template <class R, class A, class B, class F>
465 requires(detail::exact_callback<F, R, A, B>()) void op2(F&& callback) {
466 op<R, A, B>(std::forward<F>(callback));
467 }
468
473 template <class R, class A, class B, class C, class F>
474 requires(detail::exact_callback<F, R, A, B, C>()) void op3(F&& callback) {
475 op<R, A, B, C>(std::forward<F>(callback));
476 }
477
483};
484
496class compiled_segment {
497 detail::retirement_ptr<const detail::frozen_program> _program;
498 friend class dyn_segment;
499 friend struct detail::segment_access;
501 explicit compiled_segment(
502 detail::retirement_ptr<const detail::frozen_program> program) noexcept;
503
511 detail::prepared_raw_segment prepare_raw(std::span<const value_type> arguments,
512 const value_type& result) const;
513
514public:
516 compiled_segment(const compiled_segment&) = delete;
518 compiled_segment& operator=(const compiled_segment&) = delete;
520 compiled_segment(compiled_segment&&) noexcept = default;
524 compiled_segment& operator=(compiled_segment&&) noexcept = default;
529 const value_type& result_type() const noexcept;
534 const stack_requirements& requirements() const noexcept;
545 value execute(raw_stack& stack, std::span<const value> arguments) const;
553 template <class R, class... Args>
554 prepared_segment<R, Args...> prepare() const;
555};
556
557namespace detail {
565class prepared_raw_segment {
566 detail::retirement_ptr<const detail::frozen_program> _program;
567 std::vector<value_type> _arguments;
568 value_type _result;
569 std::vector<bool> _used;
570 value_type _unit;
571 friend class cel::compiled_segment;
572 friend struct segment_access;
573 template <class R, class... Args>
574 friend class cel::prepared_segment;
577 prepared_raw_segment(detail::retirement_ptr<const detail::frozen_program> program,
578 std::span<const value_type> arguments,
579 value_type result);
580
581public:
583 prepared_raw_segment(const prepared_raw_segment&) = delete;
585 prepared_raw_segment& operator=(const prepared_raw_segment&) = delete;
587 prepared_raw_segment(prepared_raw_segment&&) noexcept = default;
590 prepared_raw_segment& operator=(prepared_raw_segment&&) noexcept = default;
594 std::span<const value_type> argument_types() const noexcept { return _arguments; }
598 const value_type& result_type() const noexcept { return _result; }
602 const stack_requirements& requirements() const noexcept;
617 void call_into(raw_stack& stack,
618 std::span<const borrowed_value> arguments,
619 std::byte* result_destination) const;
627 void destroy_result(std::byte* result) const noexcept;
628};
629
631const std::byte* canonical_data(const value& source) noexcept;
633template <class T>
634struct typed_input {
636 using storage = std::conditional_t<native_leaf<T>, std::reference_wrapper<const T>, value>;
637 storage source;
638
641 static storage bind(const T& input, bool used) {
642 if constexpr (native_leaf<T>) {
643 return std::cref(input);
644 } else {
645 return used ? value::from(input) : value{};
646 }
647 }
650 explicit typed_input(const T& input, bool used) : source(bind(input, used)) {}
652 borrowed_value borrow(const value_type& type) const noexcept {
653 if constexpr (native_leaf<T>) {
654 return {type, reinterpret_cast<const std::byte*>(&source.get())};
655 } else {
656 return {type, canonical_data(source)};
657 }
658 }
659};
662class result_storage {
663 const prepared_raw_segment& _binding;
664 std::byte* _data;
665 bool _initialized = false;
666
667public:
670 explicit result_storage(const prepared_raw_segment& binding);
673 ~result_storage();
675 result_storage(const result_storage&) = delete;
677 result_storage& operator=(const result_storage&) = delete;
679 std::byte* data() const noexcept { return _data; }
681 void initialized() noexcept { _initialized = true; }
682};
683} // namespace detail
684
694template <class R, class... Args>
695class prepared_segment {
696 detail::prepared_raw_segment _binding;
697 friend class compiled_segment;
699 explicit prepared_segment(detail::prepared_raw_segment binding) :
700 _binding(std::move(binding)) {}
703 template <std::size_t... I>
704 auto bind(const std::tuple<const Args&...>& inputs, std::index_sequence<I...>) const {
705 return std::tuple<detail::typed_input<Args>...>{
706 detail::typed_input<Args>(std::get<I>(inputs), _binding._used[I])...};
707 }
710 template <std::size_t... I>
711 auto borrow(const std::tuple<detail::typed_input<Args>...>& inputs,
712 std::index_sequence<I...>) const {
713 return std::array<detail::borrowed_value, sizeof...(Args)>{
714 (_binding._used[I] ? std::get<I>(inputs).borrow(_binding.argument_types()[I]) :
715 detail::borrowed_value{_binding._unit, nullptr})...};
716 }
717
718public:
720 prepared_segment(const prepared_segment&) = delete;
722 prepared_segment& operator=(const prepared_segment&) = delete;
724 prepared_segment(prepared_segment&&) noexcept = default;
727 prepared_segment& operator=(prepared_segment&&) noexcept = default;
732 const stack_requirements& requirements() const noexcept { return _binding.requirements(); }
740 R call(raw_stack& stack, const Args&... arguments) const {
741 const auto inputs = bind(std::tie(arguments...), std::index_sequence_for<Args...>{});
742 const auto views = borrow(inputs, std::index_sequence_for<Args...>{});
743 if constexpr (detail::native_leaf<R>) {
744 alignas(R) std::array<std::byte, sizeof(R)> bytes{};
745 _binding.call_into(stack, views, bytes.data());
746 return detail::concrete_adapter<R>::read(_binding.result_type(), bytes.data());
747 } else {
748 detail::result_storage result(_binding);
749 _binding.call_into(stack, views, result.data());
750 result.initialized();
751 return detail::concrete_adapter<R>::read(_binding.result_type(), result.data());
752 }
753 }
754};
755
757template <class R, class... Args>
759 static_assert(!std::is_void_v<R>, "use tuple<> for the source-unit result");
760 const std::array<value_type, sizeof...(Args)> types{detail::concrete_adapter<Args>::type()...};
761 return prepared_segment<R, Args...>(prepare_raw(types, detail::concrete_adapter<R>::type()));
762}
763
765} // namespace cel
766#endif
Retains immutable frozen code and its captures in a move-only handle. Prepared bindings and callables...
Definition dyn_segment.hpp:496
const value_type & result_type() const noexcept
Returns the complete frozen result descriptor. The reference borrows retained code; copying the descr...
compiled_segment(compiled_segment &&) noexcept=default
Transfers the frozen handle.
value execute(raw_stack &stack, std::span< const value > arguments) const
Validates all actual read witnesses before effects and returns an independent owner....
const stack_requirements & requirements() const noexcept
Borrows frozen intermediate alignment, peak canonical extent, and live-slot depth....
compiled_segment & operator=(const compiled_segment &)=delete
Disallows copied assignment.
compiled_segment(const compiled_segment &)=delete
Disallows misleading independent capture copies.
dyn_segment(dyn_segment &&) noexcept=default
Transfers construction ownership. Use the source only for destruction or assignment after moving.
void op1(F &&callback)
Consumes one exact operand and pushes R unless R is void. Follows op's callback, validation,...
Definition dyn_segment.hpp:457
void index_array()
Consumes an array followed by an f64 index and transfers only the selected element....
void make_record(value_type record, stack_origin ambient)
Packs record operands already ordered by the canonical schema. Follows the source-order overload's ow...
compiled_segment finish() &&
Freezes exactly one result, mapping an empty final stack to source unit. Retains the ambient origin; ...
void make_tuple(std::size_t count, stack_origin ambient)
Packs count source-ordered operands into an exact owning tuple at ambient. Count zero emits source un...
void just(value literal)
Retains a literal owner and emits an independent clone on every execution. Callable components share ...
dyn_segment(const dyn_segment &)=delete
Disallows copying mutable construction ownership.
void invoke_closure(std::size_t arity)
Consumes an installed callee followed by source-ordered complete declared arguments....
void project_tuple(std::size_t index)
Transfers one tuple field while retiring the consumed tuple and siblings, without clones....
void make_array(std::size_t count, value_type element, stack_origin ambient)
Packs exact element operands into one owning packed array, including typed empty arrays....
dyn_segment(stack_origin origin)
Creates an empty dependent fragment at the supplied ambient origin. A nonzero byte or depth origin pr...
Definition dyn_segment.hpp:266
void logical_and(dyn_segment rhs)
Executes bool rhs only if the bool left operand is true. Follows if_else's origins,...
dyn_segment & operator=(const dyn_segment &)=delete
Disallows copied assignment.
void make_record(value_type record, std::span< const std::size_t > permutation, stack_origin ambient)
Packs source-position to schema-position fields without reordering expression execution....
dyn_segment()=default
Creates an empty checked stack. Example: dyn_segment builder;.
void push_closure(compiled_segment body, std::span< const value_type > parameters)
Emits an installed callable retaining explicit parameters and independent frozen code....
void push_argument(std::size_t index, value_type type)
Emits a read that clones a borrowed positional input when the program executes. Repeated declarations...
stack_origin before_top() const
Returns the origin immediately below the top local operand. Throws invalid_argument if no local opera...
void make_range(range_kind kind)
Packs source-ordered f64 endpoints into the complete specified range; full consumes none....
stack_origin origin_below(std::size_t count) const
Returns the absolute origin below count local operands without mutation or allocation....
stack_origin origin() const noexcept
Returns the retained ambient origin. Example: builder.origin() == stack_origin{}.
Definition dyn_segment.hpp:280
void if_else(dyn_segment then_body, dyn_segment else_body)
Consumes bool and executes only one exact one-result dependent fragment. Bodies must originate immedi...
void op3(F &&callback)
Consumes three source-order operands and pushes R unless R is void. Follows op's callback,...
Definition dyn_segment.hpp:474
void project_record(std::string_view name)
Transfers the named record field and retires the consumed owner and siblings. Throws invalid_argument...
void op2(F &&callback)
Consumes two operands older-first and pushes R unless R is void. Follows op's callback,...
Definition dyn_segment.hpp:465
stack_origin snapshot() const
Returns the current absolute logical cursor and live-slot depth. Throws overflow_error if ambient plu...
void op(F &&callback)
Emits an arbitrary-arity const callback with exact declared operands and result. R is a trusted concr...
Definition dyn_segment.hpp:427
std::span< const value_type > operand_types(std::size_t arity) const noexcept
Borrows the requested local logical operand suffix in older-first order. Returns an empty view for ze...
Definition dyn_segment.hpp:287
void logical_or(dyn_segment rhs)
Executes bool rhs only if the bool left operand is false. Follows if_else's origins,...
void op0(F &&callback)
Emits a zero-operand callback; void produces no value, otherwise pushes one result....
Definition dyn_segment.hpp:449
Borrows typed inputs and returns independent owning results through bound adapters....
Definition dyn_segment.hpp:695
R call(raw_stack &stack, const Args &... arguments) const
Executes with borrowed const inputs and returns a disjoint result. Precondition: the handle is live,...
Definition dyn_segment.hpp:740
const stack_requirements & requirements() const noexcept
Borrows the frozen program's canonical frame reservation requirements. Precondition: this handle has ...
Definition dyn_segment.hpp:732
prepared_segment(prepared_segment &&) noexcept=default
Transfers typed binding ownership.
prepared_segment(const prepared_segment &)=delete
Disallows independent capture copies.
prepared_segment & operator=(const prepared_segment &)=delete
Disallows copied assignment.
Owns reusable canonical operand bytes and complete live-slot metadata. The owner outlives every borro...
Definition raw_stack.hpp:33
Shares an immutable, complete semantic type and canonical byte layout. Leaf identity is concrete C++ ...
Definition value_type.hpp:55
Owns a complete runtime value; copies own independent mutable payloads. Installed callables are expli...
Definition value.hpp:58
static value from(const T &source)
Copies a concrete leaf or recursively nested owning vector/tuple into a runtime value....
Definition value.hpp:138
prepared_segment< R, Args... > prepare() const
Binds exact recursive concrete adapters once, admitting valid client leaves at roots....
Definition dyn_segment.hpp:758
auto with_signature(F &&callback)
Selects an exact const callback member from an ambiguous or uninspectable overload set....
Definition dyn_segment.hpp:212
bool operator==(const stack_origin &left, const stack_origin &right) noexcept
Compares both ambient coordinates exactly. Example: stack_origin{} == stack_origin{0,...
Definition dyn_segment.hpp:24
range_kind
Identifies every source range form independently of its endpoint layout. Example: range_kind::range_i...
Definition value_type.hpp:20
type_kind
Identifies a descriptor's structural category. Example: an empty source unit has type_kind::tuple.
Definition value_type.hpp:31
Provides byte extents for stack reservation and type descriptors.
Definition callable.hpp:6
Describes a byte extent and its requested alignment, both in bytes. A checked storage operation accep...
Definition layout.hpp:16
Identifies the byte cursor and live-slot depth below a fragment's local operands. Both coordinates mu...
Definition dyn_segment.hpp:18
std::size_t depth
Gives the ambient live-slot count.
Definition dyn_segment.hpp:20
std::size_t bytes
Gives the ambient canonical cursor.
Definition dyn_segment.hpp:19
Describes statically known canonical frame storage for a frozen ordinary program. Extent and slots ar...
Definition dyn_segment.hpp:48
std::size_t live_slots
Gives peak simultaneously live frame operands.
Definition dyn_segment.hpp:51
std::size_t frame_extent
Gives peak canonical extent for a zero-cursor frame.
Definition dyn_segment.hpp:50
std::size_t alignment
Gives maximum intermediate canonical alignment.
Definition dyn_segment.hpp:49
stack_reservation for_prefix(std::size_t cursor, std::size_t depth) const
Returns checked conservative total capacities for an existing caller prefix. Rounds cursor up to alig...
Describes total caller storage and slot capacity for one prefixed execution. Both fields include the ...
Definition dyn_segment.hpp:37
layout bytes
Gives total byte capacity and required alignment.
Definition dyn_segment.hpp:38
std::size_t slots
Gives total live-slot capacity, including the caller prefix.
Definition dyn_segment.hpp:39