mutable
A Database System for Research and Fast Prototyping
Loading...
Searching...
No Matches
WasmDSL.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include "backend/WebAssembly.hpp"
5#include <concepts>
6#include <cstdlib>
7#include <deque>
8#include <experimental/type_traits>
9#include <functional>
10#include <iostream>
11#include <list>
12#include <memory>
14#include <mutable/util/fn.hpp>
16#include <mutable/util/tag.hpp>
18#include <numeric>
19#include <tuple>
20#include <type_traits>
21#include <utility>
22
23// Binaryen
24#include <wasm-binary.h>
25#include <wasm-builder.h>
26#include <wasm-interpreter.h>
27#include <wasm-validator.h>
28#include <wasm.h>
29
30
31namespace m {
32
33#if !defined(NDEBUG) && defined(M_ENABLE_SANITY_FIELDS)
34namespace dsl_options {
35
38static bool insist_no_ternary_logic = false;
39
40}
41
42#define M_insist_no_ternary_logic() M_insist(not m::dsl_options::insist_no_ternary_logic, "ternary logic must not occur")
43
44#else
45#define M_insist_no_ternary_logic()
46
47#endif
48
49// forward declarations
50struct Table;
51
52namespace wasm {
53
54/*======================================================================================================================
55 * Concepts needed for forward declarations
56 *====================================================================================================================*/
57
59template<typename T>
61
63template<typename T>
65
66
67/*======================================================================================================================
68 * Type forward declarations
69 *====================================================================================================================*/
70
72enum class VariableKind {
73 Local,
74 Param,
75 Global,
76};
77
78struct Allocator; // for use in Module
79struct Bit; // for use in PrimitiveExpr
80template<std::size_t = 1> struct LocalBit; // for use in Module
81struct LocalBitmap; // for use in Module
82struct LocalBitvector; // for use in Module
83template<typename> struct invoke_interpreter; // for unittests only
84template<typename> struct invoke_v8; // for unittests only
85template<typename, std::size_t = 1> struct PrimitiveExpr;
86template<typename, std::size_t = 1> struct Expr;
87template<typename, VariableKind, bool, std::size_t = 1> struct Variable;
88template<typename, std::size_t = 1> struct Parameter;
89
90namespace detail {
91
92template<typename, VariableKind, bool, std::size_t> class variable_storage;
93
94template<dsl_primitive, std::size_t, bool> struct the_reference;
95
96}
97
98template<typename T, std::size_t L = 1>
100template<typename T, std::size_t L = 1>
102
103
104/*======================================================================================================================
105 * Concepts and meta types
106 *====================================================================================================================*/
107
109template<typename T>
111
113template<decayable T>
114requires requires { typename primitive_expr<std::decay_t<T>>::type; }
116{ using type = typename primitive_expr<std::decay_t<T>>::type; };
117
119template<dsl_primitive T>
120struct primitive_expr<T>
122
124template<dsl_pointer_to_primitive T>
125struct primitive_expr<T>
127
129template<typename T, std::size_t L>
132
134template<typename T, VariableKind Kind, std::size_t L>
135struct primitive_expr<Variable<T, Kind, false, L>>
137
139template<typename T, std::size_t L>
142
144template<typename T, std::size_t L, bool IsConst>
145struct primitive_expr<detail::the_reference<T, L, IsConst>>
147
149template<typename T>
151
152
154template<typename T>
155concept primitive_convertible = not pointer<T> and requires { typename primitive_expr_t<T>; };
156
157
159template<typename T>
160struct expr;
161
163template<decayable T>
164requires requires { typename expr<std::decay_t<T>>::type; }
165struct expr<T>
166{ using type = typename expr<std::decay_t<T>>::type; };
167
169template<dsl_primitive T>
170struct expr<T>
171{ using type = Expr<T, 1>; };
172
174template<dsl_pointer_to_primitive T>
175struct expr<T>
176{ using type = Expr<T, 1>; };
177
179template<typename T, std::size_t L>
181{ using type = Expr<T, L>; };
182
184template<typename T, std::size_t L>
185struct expr<Expr<T, L>>
186{ using type = Expr<T, L>; };
187
189template<typename T, VariableKind Kind, bool CanBeNull, std::size_t L>
190struct expr<Variable<T, Kind, CanBeNull, L>>
191{ using type = Expr<T, L>; };
192
194template<typename T, std::size_t L>
195struct expr<Parameter<T, L>>
196{ using type = Expr<T, L>; };
197
199template<typename T, std::size_t L, bool IsConst>
200struct expr<detail::the_reference<T, L, IsConst>>
201{ using type = Expr<T, L>; };
202
204template<typename T>
205using expr_t = typename expr<T>::type;
206
207
209template<typename T>
210concept expr_convertible = not pointer<T> and requires { typename expr_t<T>; };
211
212
213namespace detail {
214
216template<typename, std::size_t>
218
220template<typename T>
221requires (std::is_void_v<T>)
223{
224 ::wasm::Type operator()() const { return ::wasm::Type(::wasm::Type::none); }
225};
226
228template<std::integral T>
229struct wasm_type_helper<T, 1>
230{
231 ::wasm::Type operator()() const {
232 /* NOTE: there are no unsigned types, only unsigned operations */
233 if constexpr (sizeof(T) <= 4)
234 return ::wasm::Type(::wasm::Type::i32);
235 if constexpr (sizeof(T) == 8)
236 return ::wasm::Type(::wasm::Type::i64);
237 M_unreachable("illegal type");
238 };
239};
240
242template<std::floating_point T>
243struct wasm_type_helper<T, 1>
244{
245 ::wasm::Type operator()()
246 {
247 if constexpr (sizeof(T) <= 4)
248 return ::wasm::Type(::wasm::Type::f32);
249 if constexpr (sizeof(T) == 8)
250 return ::wasm::Type(::wasm::Type::f64);
251 M_unreachable("illegal type");
252 };
253};
254
256template<dsl_pointer_to_primitive T, std::size_t L>
258{
259 ::wasm::Type operator()() { return ::wasm::Type(::wasm::Type::i32); };
260};
261
263template<dsl_primitive T, std::size_t L>
264requires (L > 1)
265struct wasm_type_helper<T, L>
266{
267 ::wasm::Type operator()() const { return ::wasm::Type(::wasm::Type::v128); }
268};
269
272template<typename T, std::size_t L>
274{
275 ::wasm::Type operator()() { return wasm_type_helper<T, L>{}(); };
276};
277
279template<typename ReturnType, typename... ParamTypes>
280struct wasm_type_helper<ReturnType(ParamTypes...), 1>
281{
282 ::wasm::Signature operator()()
283 {
284 return ::wasm::Signature(
285 /* params= */ { wasm_type_helper<ParamTypes, 1>{}()... },
286 /* result= */ wasm_type_helper<ReturnType, 1>{}());
287 };
288};
289
290}
291
292template<typename T, std::size_t L>
294
295
297template<std::size_t W>
298struct _int;
299
300template<>
301struct _int<1>
302{ using type = int8_t; };
303
304template<>
305struct _int<2>
306{ using type = int16_t; };
307
308template<>
309struct _int<4>
310{ using type = int32_t; };
311
312template<>
313struct _int<8>
314{ using type = int64_t; };
315
316template<std::size_t W>
317using int_t = typename _int<W>::type;
318
320template<std::size_t W>
321struct uint;
322
323template<>
324struct uint<1>
325{ using type = uint8_t; };
326
327template<>
328struct uint<2>
329{ using type = uint16_t; };
330
331template<>
332struct uint<4>
333{ using type = uint32_t; };
334
335template<>
336struct uint<8>
337{ using type = uint64_t; };
338
339template<std::size_t W>
340using uint_t = typename uint<W>::type;
341
342
343/*======================================================================================================================
344 * Wasm_insist(COND [, MSG])
345 *
346 * Similarly to `M_insist()`, checks a condition in debug build and prints location information and an optional
347 * message if it evaluates to `false`. However, the condition is checked at runtime inside the Wasm code.
348 *====================================================================================================================*/
349
350#ifndef NDEBUG
351
352#ifdef M_ENABLE_SANITY_FIELDS
353#define WASM_INSIST2_(COND, MSG) ({ \
354 auto old = std::exchange(m::dsl_options::insist_no_ternary_logic, false); \
355 m::wasm::Module::Get().emit_insist((COND), __FILE__, __LINE__, (MSG)); \
356 m::dsl_options::insist_no_ternary_logic = old; \
357})
358#else
359#define WASM_INSIST2_(COND, MSG) ({ \
360 m::wasm::Module::Get().emit_insist((COND), __FILE__, __LINE__, (MSG)); \
361})
362#endif
363
364#define WASM_INSIST1_(COND) WASM_INSIST2_((COND), nullptr)
365
366#else
367#define WASM_INSIST2_(COND, MSG) while (0) { ((void) (COND), (void) (MSG)); }
368#define WASM_INSIST1_(COND) while (0) { ((void) (COND)); }
369
370#endif
371
372#define WASM_GET_INSIST_(XXX, _1, _2, NAME, ...) NAME
373#define Wasm_insist(...) WASM_GET_INSIST_(XXX, ##__VA_ARGS__, WASM_INSIST2_, WASM_INSIST1_)(__VA_ARGS__)
374
375
376/*######################################################################################################################
377 * TYPE DEFINITIONS
378 *####################################################################################################################*/
379
380/*======================================================================================================================
381 * Boxing types
382 *====================================================================================================================*/
383
389{
391 ::wasm::Name brk;
393 ::wasm::Name continu;
395 ::wasm::Expression *condition = nullptr;
396
397 branch_target_t(::wasm::Name brk, ::wasm::Name continu, ::wasm::Expression *condition)
399 { }
400};
401
402
403/*======================================================================================================================
404 * Helper functions
405 *====================================================================================================================*/
406
409template<param_pack Ts, std::size_t... Ls>
411
414template<typename T, typename... Ts, std::size_t L, std::size_t... Ls>
415requires (sizeof...(Ts) == sizeof...(Ls))
417{
418 friend std::ostream & operator<<(std::ostream &out, print_types) {
419 return out << wasm_type<T, L>() << ", " << print_types<Ts..., Ls...>{};
420 }
421};
422
424template<typename T, std::size_t L>
426{
427 friend std::ostream & operator<<(std::ostream &out, print_types) {
428 return out << wasm_type<T, L>();
429 }
430};
431
433inline std::string unique(std::string prefix, unsigned &counter)
434{
435 static thread_local std::ostringstream oss;
436 oss.str("");
437 oss << prefix << '<' << counter++ << '>';
438 return oss.str();
439}
440
442template<typename T, std::size_t L, bool = false, typename U>
443requires (L == 1) and std::floating_point<T> and std::floating_point<U>
444inline ::wasm::Literal make_literal(U value)
445{
446 return ::wasm::Literal(T(value));
447}
448
450template<typename T, std::size_t L, bool = false, typename U>
451requires (L == 1) and signed_integral<T> and integral<U>
452inline ::wasm::Literal make_literal(U value)
453{
454 return sizeof(T) <= 4 ? ::wasm::Literal(int32_t(value))
455 : ::wasm::Literal(int64_t(value));
456}
457
459template<typename T, std::size_t L, bool = false, typename U>
460requires (L == 1) and unsigned_integral<T> and integral<U>
461inline ::wasm::Literal make_literal(U value)
462{
463 return sizeof(T) <= 4 ? ::wasm::Literal(uint32_t(value))
464 : ::wasm::Literal(uint64_t(value));
465}
466
468template<typename T, std::size_t L, bool VecRepr = false, typename U>
469requires (L == 1) and boolean<T> and boolean<U>
470inline ::wasm::Literal make_literal(U value)
471{
472 return M_CONSTEXPR_COND(VecRepr, ::wasm::Literal(0U - uint32_t(value)), ::wasm::Literal(uint32_t(value)));
473}
474
476template<typename T, std::size_t L, bool = false>
477requires (L == 1) and std::is_pointer_v<T>
478inline ::wasm::Literal make_literal(uint32_t value)
479{
480 return ::wasm::Literal(uint32_t(value));
481}
482
485template<typename T, std::size_t L, bool = false, typename U>
486requires (L > 1) and (L * sizeof(T) <= 16) and
487requires (U value) { make_literal<T, 1>(value); }
488inline ::wasm::Literal make_literal(U value)
489{
490 std::array<::wasm::Literal, 16 / sizeof(T)> vec; // must be fully utilized vector
491 auto it = std::fill_n(vec.begin(), L, make_literal<T, 1, true>(value)); // fill range [0, L) with value
492 std::fill(it, vec.end(), make_literal<T, 1>(T(0))); // fill range [L, end) with 0
493 return ::wasm::Literal(vec);
494}
495
498template<typename T, std::size_t L, bool = false, typename U>
499requires (L > 1) and (L * sizeof(T) > 16) and ((L * sizeof(T)) % 16 == 0) and
500requires (U value) { make_literal<T, 1>(value); }
501inline std::array<::wasm::Literal, (L * sizeof(T)) / 16> make_literal(U value)
502{
503 std::array<::wasm::Literal, 16 / sizeof(T)> vec;
504 vec.fill(make_literal<T, 1, true>(value)); // fill single fully utilized vector with value
505 ::wasm::Literal elem(vec);
506
507 std::array<::wasm::Literal, (L * sizeof(T)) / 16> literals;
508 literals.fill(elem); // fill each vector with single vectorial literal
509 return literals;
510}
511
514template<typename T, std::size_t L, bool = false, typename... Us>
515requires (L > 1) and (L * sizeof(T) <= 16) and (L == sizeof...(Us)) and
516requires (Us... values) { (make_literal<T, 1>(values), ...); }
517inline ::wasm::Literal make_literal(Us... values)
518{
519 std::array<::wasm::Literal, 16 / sizeof(T)> vec; // must be fully utilized vector
520 auto it = vec.begin();
521 ((*(it++) = make_literal<T, 1, true>(values)), ...); // fill range [0, L) with values
522 std::fill(it, vec.end(), make_literal<T, 1>(T(0))); // fill range [L, end) with 0
523 return ::wasm::Literal(vec);
524}
525
528template<typename T, std::size_t L, bool = false, typename... Us>
529requires (L > 1) and (L * sizeof(T) > 16) and ((L * sizeof(T)) % 16 == 0) and (L == sizeof...(Us)) and
530requires (Us... values) { (make_literal<T, 1>(values), ...); }
531inline std::array<::wasm::Literal, (L * sizeof(T)) / 16> make_literal(Us... values)
532{
533 ::wasm::Literal vectors[L] = { make_literal<T, 1, true>(values)... }; // fill multiple vectors with values
534
535 std::array<::wasm::Literal, (L * sizeof(T)) / 16> literals;
536 for (std::size_t idx = 0; idx < (L * sizeof(T)) / 16; ++idx) {
537 auto vec = std::to_array(*reinterpret_cast<::wasm::Literal(*)[16 / sizeof(T)]>(vectors + idx * (16 / sizeof(T))));
538 literals[idx] = ::wasm::Literal(vec);
539 }
540 return literals;
541}
542
543
544/*======================================================================================================================
545 * Exceptions
546 *====================================================================================================================*/
547
548#define M_EXCEPTION_LIST(X) \
549 X(invalid_escape_sequence) \
550 X(unreachable) \
551 X(failed_unittest_check)
552
554{
555#define DECLARE_ENUM(TYPE) TYPE,
556 enum exception_t : uint64_t {
558 };
559#undef DECLARE_ENUM
560
561#define DECLARE_NAMES(TYPE) #TYPE,
562 static constexpr const char * const names_[] = {
564 };
565#undef DECLARE_NAMES
566
567 private:
569
570 public:
571 explicit exception(exception_t type, std::string message) : backend_exception(std::move(message)), type_(type) { }
572};
573
574
575/*======================================================================================================================
576 * Callback functions
577 *====================================================================================================================*/
578
581::wasm::Literals insist_interpreter(::wasm::Literals &args);
582
585::wasm::Literals throw_interpreter(::wasm::Literals &args);
586
587const std::map<::wasm::Name, std::function<::wasm::Literals(::wasm::Literals&)>> callback_functions = {
588#define CALLBACK(NAME, FUNC) { NAME, FUNC },
591#undef CALLBACK
592};
593
594
595/*======================================================================================================================
596 * GarbageCollectedData
597 *====================================================================================================================*/
598
603{
604 friend struct Module;
605
606 private:
608
609 public:
611
613};
614
615
616/*======================================================================================================================
617 * ConstantFolding
618 *====================================================================================================================*/
619
622{
626 FALSE
627 };
628
635 static boolean_result_t EvalBoolean(const ::wasm::Expression *expr);
636};
637
638
639/*======================================================================================================================
640 * Module
641 *====================================================================================================================*/
642
643struct Module final
644{
645 /*----- Friends --------------------------------------------------------------------------------------------------*/
646 friend struct Block;
647 friend struct BlockUser;
648 template<typename> friend struct Function;
649 template<typename, std::size_t> friend struct PrimitiveExpr;
650 template<typename, VariableKind, bool, std::size_t> friend class detail::variable_storage;
651 template<std::size_t> friend struct LocalBit;
652 friend struct Allocator;
653 template<typename> friend struct invoke_v8;
654
655 private:
657 static inline std::atomic_uint NEXT_MODULE_ID_ = 0;
658
660 unsigned id_;
662 unsigned next_block_id_ = 0;
664 unsigned next_function_id_ = 0;
666 unsigned next_global_id_ = 0;
668 unsigned next_if_id_ = 0;
670 unsigned next_loop_id_ = 0;
672 ::wasm::Module module_;
674 ::wasm::Builder builder_;
676 ::wasm::Block *active_block_ = nullptr;
678 ::wasm::Function *active_function_ = nullptr;
680 ::wasm::Memory *memory_ = nullptr;
682 std::unique_ptr<std::pair<memory::AddressSpace, memory::Memory>> vm_;
684 std::unique_ptr<Allocator> allocator_;
686 std::vector<branch_target_t> branch_target_stack_;
688 std::vector<std::tuple<const char*, unsigned, const char*>> messages_;
690 std::unique_ptr<::wasm::ModuleRunner::ExternalInterface> interface_;
692 std::vector<std::vector<LocalBitmap*>> local_bitmaps_stack_;
694 std::vector<std::vector<LocalBitvector*>> local_bitvectors_stack_;
696 std::unordered_map<void*, std::unique_ptr<GarbageCollectedData>> garbage_collected_data_;
697
698 /*----- Thread-local instance ------------------------------------------------------------------------------------*/
699 private:
700 static thread_local std::unique_ptr<Module> the_module_;
701
702 Module();
703 Module(const Module&) = delete;
704
705 public:
706 static void Init() {
707 M_insist(not the_module_, "must not have a module yet");
708 the_module_ = std::unique_ptr<Module>(new Module());
709 }
710 static void Dispose() {
711 M_insist(bool(the_module_), "must have a module");
712 the_module_ = nullptr;
713 }
714 static Module & Get() {
715 M_insist(bool(the_module_), "must have a module");
716 return *the_module_;
717 }
718
719 /*----- Access methods -------------------------------------------------------------------------------------------*/
721 static unsigned ID() { return Get().id_; }
722
724 static std::string Unique_Block_Name(std::string prefix = "block") { return unique(prefix, Get().next_block_id_); }
726 static std::string Unique_Function_Name(std::string prefix = "function") {
727 return unique(prefix, Get().next_function_id_);
728 }
730 static std::string Unique_Global_Name(std::string prefix = "global") {
731 return unique(prefix, Get().next_global_id_);
732 }
734 static std::string Unique_If_Name(std::string prefix = "if") { return unique(prefix, Get().next_if_id_); }
736 static std::string Unique_Loop_Name(std::string prefix = "loop") { return unique(prefix, Get().next_loop_id_); }
737
739 static ::wasm::Builder & Builder() { return Get().builder_; }
740
742 static ::wasm::Block & Block() { return *M_notnull(Get().active_block_); }
743
745 static ::wasm::Function & Function() { return *M_notnull(Get().active_function_); }
746
748 static memory::AddressSpace & Memory();
749
752
754 static bool Validate(bool verbose = true, bool global = true);
755
757 static void Optimize(int optimization_level);
758
760 ::wasm::Block * set_active_block(::wasm::Block *block) { return std::exchange(active_block_, block); }
762 ::wasm::Function * set_active_function(::wasm::Function *fn) { return std::exchange(active_function_, fn); }
763
764 /*----- Control flow ---------------------------------------------------------------------------------------------*/
766 void emit_return();
768 template<typename T, std::size_t L>
769 void emit_return(PrimitiveExpr<T, L> expr);
771 template<typename T, std::size_t L>
772 void emit_return(Expr<T, L> expr);
773
774 void emit_break(std::size_t level = 1);
775 void emit_break(PrimitiveExpr<bool, 1> cond, std::size_t level = 1);
776
777 void emit_continue(std::size_t level = 1);
778 void emit_continue(PrimitiveExpr<bool, 1> cond, std::size_t level = 1);
779
780 template<typename T, std::size_t L>
782 template<typename T, std::size_t L>
783 Expr<T, L> emit_select(PrimitiveExpr<bool, 1> cond, Expr<T, L> tru, Expr<T, L> fals);
784 template<typename T, std::size_t L>
785 requires (L > 1) and requires (PrimitiveExpr<int8_t, L> e) { e.template to<int_t<sizeof(T)>, L>(); }
787 template<typename T, std::size_t L>
788 requires (L > 1) and requires (PrimitiveExpr<int8_t, L> e) { e.template to<int_t<sizeof(T)>, L>(); }
789 Expr<T, L> emit_select(PrimitiveExpr<bool, L> cond, Expr<T, L> tru, Expr<T, L> fals);
790
791 /*----- Shuffle --------------------------------------------------------------------------------------------------*/
796 template<typename T, std::size_t L, std::size_t M>
797 requires (L > 1) and (L * sizeof(T) <= 16) and (M > 0) and (M <= 16) and (M % sizeof(T) == 0)
798 PrimitiveExpr<T, M / sizeof(T)> emit_shuffle_bytes(PrimitiveExpr<T, L> first, PrimitiveExpr<T, L> second,
799 const std::array<uint8_t, M> &indices);
803 template<typename T, std::size_t L, std::size_t M>
804 requires (L > 1) and (L * sizeof(T) <= 16) and (M > 0) and (is_pow_2(M)) and (M * sizeof(T) <= 16)
805 PrimitiveExpr<T, M> emit_shuffle_lanes(PrimitiveExpr<T, L> first, PrimitiveExpr<T, L> second,
806 const std::array<uint8_t, M> &indices);
811 template<typename T, std::size_t L, std::size_t M>
812 requires (L > 1) and (L * sizeof(T) <= 16) and (M > 0) and (M <= 16) and (M % sizeof(T) == 0) and (sizeof(T) == 1)
813 Expr<T, M / sizeof(T)> emit_shuffle_bytes(Expr<T, L> first, Expr<T, L> second, const std::array<uint8_t, M> &indices);
817 template<typename T, std::size_t L, std::size_t M>
818 requires (L > 1) and (L * sizeof(T) <= 16) and (M > 0) and (is_pow_2(M)) and (M * sizeof(T) <= 16)
819 Expr<T, M> emit_shuffle_lanes(Expr<T, L> first, Expr<T, L> second, const std::array<uint8_t, M> &indices);
820
821 /*----- Globals. -------------------------------------------------------------------------------------------------*/
822 template<dsl_primitive T, std::size_t L = 1, dsl_primitive... Us>
823 requires (L * sizeof(T) <= 16) and requires (Us... us) { make_literal<T, L>(us...); }
824 void emit_global(::wasm::Name name, bool is_mutable, Us... inits) {
825 ::wasm::Builder::Mutability mut = is_mutable ? ::wasm::Builder::Mutability::Mutable
826 : ::wasm::Builder::Mutability::Immutable;
827 ::wasm::Const *_init = builder_.makeConst(make_literal<T, L>(inits...));
828 auto global = builder_.makeGlobal(name, wasm_type<T, L>(), _init, mut);
829 module_.addGlobal(std::move(global));
830 }
831 template<dsl_primitive T, std::size_t L = 1, dsl_primitive... Us>
832 requires (L * sizeof(T) <= 16) and requires (Us... us) { make_literal<T, L>(us...); }
833 void emit_global(const std::array<::wasm::Name, 1> &names, bool is_mutable, Us... inits) {
834 emit_global<T, L>(names[0], is_mutable, inits...);
835 }
836 template<dsl_primitive T, std::size_t L = 1, std::size_t N, dsl_primitive... Us>
837 requires requires (Us... us) { { make_literal<T, L>(us...) } -> std::same_as<std::array<::wasm::Literal, N>>; }
838 void emit_global(const std::array<::wasm::Name, N> &names, bool is_mutable, Us... inits) {
839 ::wasm::Builder::Mutability mut = is_mutable ? ::wasm::Builder::Mutability::Mutable
840 : ::wasm::Builder::Mutability::Immutable;
841 auto literals = make_literal<T, L>(inits...);
842 for (std::size_t idx = 0; idx < N; ++idx) {
843 ::wasm::Const *_init = builder_.makeConst(literals[idx]);
844 auto global = builder_.makeGlobal(names[idx], wasm_type<T, L>(), _init, mut);
845 module_.addGlobal(std::move(global));
846 }
847 }
848 template<dsl_pointer_to_primitive T, std::size_t L = 1>
849 void emit_global(::wasm::Name name, bool is_mutable, uint32_t init) {
850 ::wasm::Builder::Mutability mut = is_mutable ? ::wasm::Builder::Mutability::Mutable
851 : ::wasm::Builder::Mutability::Immutable;
852 ::wasm::Const *_init = builder_.makeConst(::wasm::Literal(init));
853 auto global = builder_.makeGlobal(name, wasm_type<T, L>(), _init, mut);
854 module_.addGlobal(std::move(global));
855 }
856
857 template<typename T, std::size_t L = 1>
858 requires (L * sizeof(T) <= 16)
859 PrimitiveExpr<T, L> get_global(const char *name);
860
861 /*----- Imports & Exports ----------------------------------------------------------------------------------------*/
862 template<typename T, std::size_t L = 1>
864 requires { M_CONSTEXPR_COND_UNCAPTURED(std::is_pointer_v<T>, (make_literal<T, L>(0)), (make_literal<T, L>(T()))); }
865 void emit_import(const char *extern_name, const char *intern_name = nullptr)
866 {
867 ::wasm::Const *value = M_CONSTEXPR_COND(std::is_pointer_v<T>, builder_.makeConst(make_literal<T, L>(0)),
868 builder_.makeConst(make_literal<T, L>(T())));
869 auto global = builder_.makeGlobal(intern_name ? intern_name : extern_name, wasm_type<T, L>(), M_notnull(value),
870 ::wasm::Builder::Mutability::Immutable);
871 global->module = "imports";
872 global->base = extern_name;
873 module_.addGlobal(std::move(global));
874 }
875
877 template<typename T>
878 requires std::is_function_v<T> and requires { wasm_type<T, 1>(); }
879 void emit_function_import(const char *name) {
880 auto func = module_.addFunction(builder_.makeFunction(name, wasm_type<T, 1>(), {}));
881 func->module = "imports";
882 func->base = name;
883 }
884
886 void emit_function_export(const char *name) {
887 module_.addExport(builder_.makeExport(name, name, ::wasm::ExternalKind::Function));
888 }
889
890 /*----- Function calls -------------------------------------------------------------------------------------------*/
891 template<typename ReturnType, typename... ParamTypes, std::size_t... ParamLs>
892 requires std::is_void_v<ReturnType>
893 void emit_call(const char *fn, PrimitiveExpr<ParamTypes, ParamLs>... args);
894
895 template<typename ReturnType, std::size_t ReturnL = 1, typename... ParamTypes, std::size_t... ParamLs>
898
899 /*----- Runtime checks and throwing exceptions -------------------------------------------------------------------*/
900 template<std::size_t L>
901 void emit_insist(PrimitiveExpr<bool, L> cond, const char *filename, unsigned line, const char *msg);
902 template<>
903 void emit_insist<1>(PrimitiveExpr<bool, 1> cond, const char *filename, unsigned line, const char *msg);
904
905 void emit_throw(exception::exception_t type, const char *filename, unsigned line, const char *msg);
906
907 const std::tuple<const char*, unsigned, const char*> & get_message(std::size_t idx) const {
908 return messages_.at(idx);
909 }
910
911 /*----- Garbage collected data -----------------------------------------------------------------------------------*/
915 template<class C, typename... Args>
916 C & add_garbage_collected_data(void *handle, Args... args) {
917 auto it = garbage_collected_data_.template try_emplace(
918 /* key= */ handle,
919 /* value= */ std::make_unique<C>(GarbageCollectedData(), std::forward<Args>(args)...)
920 ).first;
921 return as<C>(*it->second);
922 }
923
924 /*----- Interpretation & Debugging -------------------------------------------------------------------------------*/
925 ::wasm::ModuleRunner::ExternalInterface * get_mock_interface(::wasm::GlobalValueSet imports = {});
926
928 ::wasm::ModuleRunner instantiate(::wasm::GlobalValueSet imports = {}) {
929 return ::wasm::ModuleRunner(module_, get_mock_interface(std::move(imports)));
930 }
931
932 /*----- Module settings ------------------------------------------------------------------------------------------*/
933 void set_feature(::wasm::FeatureSet feature, bool value) { module_.features.set(feature, value); }
934
937 std::pair<uint8_t*, std::size_t> binary();
938
939 private:
940 void create_local_bitmap_stack();
941 void create_local_bitvector_stack();
942 void dispose_local_bitmap_stack();
943 void dispose_local_bitvector_stack();
944 public:
945 template<std::size_t L = 1>
946 requires (L > 0) and (L <= 16)
947 LocalBit<L> allocate_bit();
948
949 void push_branch_targets(::wasm::Name brk, ::wasm::Name continu) {
950 branch_target_stack_.emplace_back(brk, continu, nullptr);
951 }
952
953 void push_branch_targets(::wasm::Name brk, ::wasm::Name continu, PrimitiveExpr<bool, 1> condition);
954
956 auto top = branch_target_stack_.back();
957 branch_target_stack_.pop_back();
958 return top;
959 }
960
961 const branch_target_t & current_branch_targets() const { return branch_target_stack_.back(); }
962
963 /*----- Printing -------------------------------------------------------------------------------------------------*/
964 public:
965 friend std::ostream & operator<<(std::ostream &out, const Module &M) {
966 out << "Module\n";
967
968 out << " currently active block: ";
969 if (M.active_block_) {
970 if (M.active_block_->name.is())
971 out << '"' << M.active_block_->name << '"';
972 else
973 out << "<anonymous block>";
974 } else {
975 out << "none";
976 }
977 out << '\n';
978
979 // out << " currently active function: ";
980 // if (M.active_function_) {
981 // out << '"' << M.active_function_->name << '"';
982 // } else {
983 // out << "none";
984 // }
985 // out << '\n';
986
987 return out;
988 }
989
990 void dump(std::ostream &out) const { out << *this << std::endl; }
991 void dump() const { dump(std::cerr); }
992
993 void dump_all(std::ostream &out) { out << module_ << std::endl; }
994 void dump_all() { dump_all(std::cerr); }
995};
996
997
998/*======================================================================================================================
999 * Block
1000 *====================================================================================================================*/
1001
1004struct Block final
1005{
1006 template<typename T> friend struct Function; // to get ::wasm::Block of body
1007 friend struct BlockUser; // to access `Block` internals
1008 friend struct If; // to get ::wasm::Block for *then* and *else* part
1009 friend struct Loop; // to get ::wasm::Block for loop body
1010 friend struct DoWhile; // to get ::wasm::Block for loop body
1011
1012 private:
1014 ::wasm::Block *this_block_ = nullptr;
1016 ::wasm::Block *parent_block_ = nullptr;
1018 bool attach_to_parent_ = false;
1019
1020 public:
1021 friend void swap(Block &first, Block &second) {
1022 using std::swap;
1023 swap(first.this_block_, second.this_block_);
1024 swap(first.parent_block_, second.parent_block_);
1026 }
1027
1028 private:
1029 Block() = default;
1030
1032 Block(::wasm::Block *block, bool attach_to_parent)
1033 : this_block_(M_notnull(block))
1034 , attach_to_parent_(attach_to_parent)
1035 {
1036 if (attach_to_parent_) {
1037 parent_block_ = Module::Get().active_block_;
1038 M_insist(not attach_to_parent_ or parent_block_, "can only attach to parent if there is a parent block");
1039 }
1040 }
1041
1042 public:
1044 explicit Block(bool attach_to_parent) : Block(Module::Builder().makeBlock(), attach_to_parent) { }
1046 explicit Block(std::string name, bool attach_to_parent)
1047 : Block(Module::Builder().makeBlock(Module::Unique_Block_Name(name)), attach_to_parent)
1048 { }
1050 explicit Block(const char *name, bool attach_to_parent) : Block(std::string(name), attach_to_parent) { }
1051
1052 Block(Block &&other) : Block() { swap(*this, other); }
1053
1055 if (this_block_ and attach_to_parent_)
1056 attach_to(*M_notnull(parent_block_));
1057 }
1058
1059 Block & operator=(Block &&other) { swap(*this, other); return *this; }
1060
1061 private:
1062 ::wasm::Block & get() const { return *M_notnull(this_block_); }
1063 ::wasm::Block & previous() const { return *M_notnull(parent_block_); }
1064
1065 void attach_to(::wasm::Block &other) {
1066 other.list.push_back(this_block_);
1067 this_block_ = nullptr;
1068 }
1069
1070 public:
1071 bool has_name() const { return bool(get().name); }
1072 std::string name() const { M_insist(has_name()); return get().name.toString(); }
1073
1075 bool empty() const { return get().list.empty(); }
1076
1078 void attach_to(Block &other) {
1079 M_insist(not attach_to_parent_, "cannot explicitly attach if attach_to_parent is true");
1080 attach_to(*M_notnull(other.this_block_));
1081 }
1082
1085 M_insist(not attach_to_parent_, "cannot explicitly attach if attach_to_parent is true");
1086 attach_to(Module::Block());
1087 }
1088
1090 void go_to() const { Module::Block().list.push_back(Module::Builder().makeBreak(get().name)); }
1092 void go_to(PrimitiveExpr<bool, 1> cond) const;
1093
1094 friend std::ostream & operator<<(std::ostream &out, const Block &B) {
1095 out << "vvvvvvvvvv block";
1096 if (B.has_name())
1097 out << " \"" << B.name() << '"';
1098 out << " starts here vvvvvvvvvv\n";
1099
1100 for (auto expr : B.get().list)
1101 out << *expr << '\n';
1102
1103 out << "^^^^^^^^^^^ block";
1104 if (B.has_name())
1105 out << " \"" << B.name() << '"';
1106 out << " ends here ^^^^^^^^^^^\n";
1107
1108 return out;
1109 }
1110
1111 void dump(std::ostream &out) const { out << *this; out.flush(); }
1112 void dump() const { dump(std::cerr); }
1113};
1114
1118{
1119 private:
1121 ::wasm::Block *old_block_ = nullptr;
1122
1123 public:
1124 BlockUser(Block &block) : block_(block) {
1125 old_block_ = Module::Get().set_active_block(block_.this_block_); // set active block
1126 }
1127
1128 ~BlockUser() { Module::Get().set_active_block(old_block_); } // restore previously active block
1129};
1130
1131
1132/*======================================================================================================================
1133 * Function
1134 *====================================================================================================================*/
1135
1138template<typename>
1140
1141template<typename ReturnType, typename... ParamTypes, std::size_t ReturnL, std::size_t... ParamLs>
1142requires ((std::is_void_v<ReturnType> and (ReturnL == 1)) or
1144 (not dsl_primitive<ReturnType> or (ReturnL * sizeof(ReturnType) <= 16)) and // must fit in single register
1145 (requires { typename PrimitiveExpr<ParamTypes, ParamLs>; } and ...) and
1146 ((not dsl_primitive<ParamTypes> or (ParamLs * sizeof(ParamTypes) <= 16)) and ...) // must fit in single register
1147 struct Function<PrimitiveExpr<ReturnType, ReturnL>(PrimitiveExpr<ParamTypes, ParamLs>...)>
1148{
1149 template<typename> friend struct FunctionProxy;
1150
1152 using type = ReturnType(ParamTypes...);
1156 using return_type = ReturnType;
1158 static constexpr std::size_t PARAMETER_COUNT = sizeof...(ParamTypes);
1159
1160 /*------------------------------------------------------------------------------------------------------------------
1161 * Parameter helper types
1162 *----------------------------------------------------------------------------------------------------------------*/
1163 private:
1164 template<typename... Ts, std::size_t... Ls, std::size_t... Is>
1165 requires (sizeof...(Ts) == sizeof...(Ls)) and (sizeof...(Ts) == sizeof...(Is))
1166 std::tuple<Parameter<Ts, Ls>...> make_parameters_helper(std::index_sequence<Is...>) {
1167 return std::make_tuple<Parameter<Ts, Ls>...>(
1168 (Parameter<Ts, Ls>(Is), ...)
1169 );
1170 }
1171
1173 template<typename... Ts, std::size_t... Ls, typename Indices = std::make_index_sequence<sizeof...(Ts)>>
1174 requires (sizeof...(Ts) == sizeof...(Ls))
1175 std::tuple<Parameter<Ts, Ls>...> make_parameters() { return make_parameters_helper<Ts..., Ls...>(Indices{}); }
1176
1178 template<std::size_t I, typename... Ts>
1179 struct parameter_type;
1180 template<std::size_t I, typename T, typename... Ts>
1181 struct parameter_type<I, T, Ts...>
1182 {
1183 static_assert(I <= sizeof...(Ts), "parameter index out of range");
1184 using type = typename parameter_type<I - 1, Ts...>::type;
1185 };
1186 template<typename T, typename... Ts>
1187 struct parameter_type<0, T, Ts...>
1188 {
1189 using type = T;
1190 };
1192 template<std::size_t I>
1193 using parameter_type_t = typename parameter_type<I, ParamTypes...>::type;
1194
1196 template<std::size_t I, std::size_t... Ls>
1197 struct parameter_num_simd_lanes;
1198 template<std::size_t I, std::size_t L, std::size_t... Ls>
1199 struct parameter_num_simd_lanes<I, L, Ls...>
1200 {
1201 static_assert(I <= sizeof...(Ls), "parameter index out of range");
1202 static constexpr std::size_t value = parameter_num_simd_lanes<I - 1, Ls...>::value;
1203 };
1204 template<std::size_t L, std::size_t... Ls>
1205 struct parameter_num_simd_lanes<0, L, Ls...>
1206 {
1207 static constexpr std::size_t value = L;
1208 };
1210 template<std::size_t I>
1211 static constexpr std::size_t parameter_num_simd_lanes_v = parameter_num_simd_lanes<I, ParamLs...>::value;
1212
1213 private:
1214 ::wasm::Name name_;
1215 Block body_;
1217 ::wasm::Function *this_function_ = nullptr;
1219 ::wasm::Function *previous_function_ = nullptr;
1220
1221 public:
1222 friend void swap(Function &first, Function &second) {
1223 using std::swap;
1224 swap(first.name_, second.name_);
1225 swap(first.body_, second.body_);
1226 swap(first.this_function_, second.this_function_);
1227 swap(first.previous_function_, second.previous_function_);
1228 }
1229
1230 private:
1231 Function() = default;
1232
1233 public:
1235 Function(const std::string &name)
1236 : name_(name)
1237 , body_(name + ".body", /* attach_to_parent= */ false)
1238 {
1239 /*----- Set block return type for non-`void` functions. -----*/
1240 if constexpr (not std::is_void_v<ReturnType>)
1241 body_.get().type = wasm_type<ReturnType, ReturnL>();
1242
1243 /*----- Create Binaryen function. -----*/
1244 auto fn = Module::Builder().makeFunction(
1245 /* name= */ name,
1246 /* type= */ wasm_type<dsl_type, 1>(),
1247 /* vars= */ std::vector<::wasm::Type>{}
1248 );
1249 fn->body = &body_.get(); // set function body
1250 this_function_ = Module::Get().module_.addFunction(std::move(fn));
1251 M_insist(this_function_->getNumParams() == PARAMETER_COUNT);
1252 Module::Get().create_local_bitmap_stack();
1253 Module::Get().create_local_bitvector_stack();
1254
1255 /*----- Set this function active in the `Module`. -----*/
1256 previous_function_ = Module::Get().set_active_function(this_function_);
1257 }
1258
1259 Function(const Function&) = delete;
1260 Function(Function &&other) : Function() { swap(*this, other); }
1261
1262 ~Function() {
1263 if constexpr (not std::is_void_v<ReturnType>)
1264 body_.get().list.push_back(Module::Builder().makeUnreachable());
1265 Module::Get().dispose_local_bitmap_stack();
1266 Module::Get().dispose_local_bitvector_stack();
1267 /*----- Restore previously active function in the `Module`. -----*/
1268 Module::Get().set_active_function(previous_function_);
1269 }
1270
1271 Function & operator=(Function &&other) { swap(*this, other); return *this; }
1272
1273 public:
1275 Block & body() { return body_; }
1277 const Block & body() const { return body_; }
1278
1280 std::string name() const { return name_.toString(); }
1281
1283 std::tuple<Parameter<ParamTypes, ParamLs>...> parameters() { return make_parameters<ParamTypes..., ParamLs...>(); }
1284
1286 template<std::size_t I>
1287 Parameter<parameter_type_t<I>, parameter_num_simd_lanes_v<I>> parameter() {
1288 return Parameter<parameter_type_t<I>, parameter_num_simd_lanes_v<I>>(I);
1289 }
1290
1292 void emit_return() requires std::is_void_v<ReturnType> {
1293 Module::Block().list.push_back(Module::Builder().makeReturn());
1294 }
1295
1297 template<primitive_convertible T>
1298 requires (not std::is_void_v<ReturnType>) and
1299 requires (T &&t) { PrimitiveExpr<ReturnType, ReturnL>(primitive_expr_t<T>(std::forward<T>(t))); }
1300 void emit_return(T &&t) {
1302 Module::Get().emit_return(value);
1303 }
1304
1307 template<expr_convertible T>
1308 requires (not std::is_void_v<ReturnType>) and (not primitive_convertible<T>) and
1309 requires (T t) { Expr<ReturnType, ReturnL>(expr_t<T>(std::forward<T>(t))); }
1310 void emit_return(T &&t)
1311 {
1312 Expr<ReturnType, ReturnL> expr(expr_t<T>(std::forward<T>(t)));
1313 Module::Get().emit_return(expr);
1314 }
1315
1316 private:
1317 ::wasm::Function & get() const { return *M_notnull(this_function_); }
1318
1319 public:
1320 friend std::ostream & operator<<(std::ostream &out, const Function &Fn) {
1321 out << "function \"" << Fn.name() << "\" : ";
1322 if constexpr (PARAMETER_COUNT)
1323 out << print_types<param_pack_t<ParamTypes...>, ParamLs...>{};
1324 else
1325 out << typeid(void).name();
1326 out << " -> " << print_types<param_pack_t<ReturnType>, ReturnL>{} << '\n';
1327
1328 if (not Fn.get().vars.empty()) {
1329 out << " " << Fn.get().getNumVars() << " local variables:";
1330 for (::wasm::Index i = 0, end = Fn.get().getNumVars(); i != end; ++i)
1331 out << " [" << i << "] " << Fn.get().vars[i];
1332 out << '\n';
1333 }
1334
1335 out << Fn.body();
1336 return out;
1337 }
1338
1339 void dump(std::ostream &out) const { out << *this; out.flush(); }
1340 void dump() const { dump(std::cerr); }
1341};
1342
1343template<typename ReturnType, typename... ParamTypes>
1344requires (std::is_void_v<ReturnType> or dsl_primitive<ReturnType> or dsl_pointer_to_primitive<ReturnType>) and
1345 ((dsl_primitive<ParamTypes> or dsl_pointer_to_primitive<ParamTypes>) and ...)
1346struct Function<ReturnType(ParamTypes...)> : Function<PrimitiveExpr<ReturnType, 1>(PrimitiveExpr<ParamTypes, 1>...)>
1347{
1348 using Function<PrimitiveExpr<ReturnType, 1>(PrimitiveExpr<ParamTypes, 1>...)>::Function;
1349};
1350
1351template<typename... ParamTypes, std::size_t... ParamLs>
1352struct Function<void(PrimitiveExpr<ParamTypes, ParamLs>...)>
1353 : Function<PrimitiveExpr<void, 1>(PrimitiveExpr<ParamTypes, ParamLs>...)>
1354{
1356};
1357
1358
1359/*======================================================================================================================
1360 * FunctionProxy
1361 *====================================================================================================================*/
1362
1366template<typename>
1368
1369template<typename ReturnType, typename... ParamTypes, std::size_t ReturnL, std::size_t... ParamLs>
1370requires ((std::is_void_v<ReturnType> and (ReturnL == 1)) or
1371 requires { typename PrimitiveExpr<ReturnType, ReturnL>; }) and
1372 (not dsl_primitive<ReturnType> or (ReturnL * sizeof(ReturnType) <= 16)) and // must fit in single register
1373 (requires { typename PrimitiveExpr<ParamTypes, ParamLs>; } and ...) and
1374 ((not dsl_primitive<ParamTypes> or (ParamLs * sizeof(ParamTypes) <= 16)) and ...) // must fit in single register
1375struct FunctionProxy<PrimitiveExpr<ReturnType, ReturnL>(PrimitiveExpr<ParamTypes, ParamLs>...)>
1376{
1377 using type = ReturnType(ParamTypes...);
1378 using dsl_type = PrimitiveExpr<ReturnType, ReturnL>(PrimitiveExpr<ParamTypes, ParamLs>...);
1379
1380 private:
1381 std::string name_;
1382
1383 public:
1384 FunctionProxy() = delete;
1385 FunctionProxy(std::string name) : name_(Module::Unique_Function_Name(name)) { }
1386 FunctionProxy(const char *name) : FunctionProxy(std::string(name)) { }
1387
1388 FunctionProxy(FunctionProxy&&) = default;
1389
1390 FunctionProxy & operator=(FunctionProxy&&) = default;
1391
1392 const std::string & name() const { return name_; }
1393 const char * c_name() const { return name_.c_str(); }
1394
1395 Function<dsl_type> make_function() const { return Function<dsl_type>(name_); }
1396
1397 /*----- Overload operator() to emit function calls ---------------------------------------------------------------*/
1399 template<typename... Args>
1400 requires std::is_void_v<ReturnType> and
1401 requires (Args&&... args) { (PrimitiveExpr<ParamTypes, ParamLs>(std::forward<Args>(args)), ...); }
1402 void operator()(Args&&... args) const {
1403 operator()(PrimitiveExpr<ParamTypes, ParamLs>(std::forward<Args>(args))...);
1404 }
1405
1407 void operator()(PrimitiveExpr<ParamTypes, ParamLs>... args) const requires std::is_void_v<ReturnType> {
1408 Module::Block().list.push_back(
1409 Module::Builder().makeCall(name_, { args.expr()... }, wasm_type<ReturnType, ReturnL>())
1410 );
1411 }
1412
1414 template<typename... Args>
1415 requires (not std::is_void_v<ReturnType>) and
1416 requires (Args&&... args) { (PrimitiveExpr<ParamTypes, ParamLs>(std::forward<Args>(args)), ...); }
1417 PrimitiveExpr<ReturnType, ReturnL> operator()(Args&&... args) const {
1418 return operator()(PrimitiveExpr<ParamTypes, ParamLs>(std::forward<Args>(args))...);
1419 }
1420
1422 PrimitiveExpr<ReturnType, ReturnL>
1423 operator()(PrimitiveExpr<ParamTypes, ParamLs>... args) const requires (not std::is_void_v<ReturnType>) {
1424 return PrimitiveExpr<ReturnType, ReturnL>(
1425 Module::Builder().makeCall(name_, { args.expr()... }, wasm_type<ReturnType, ReturnL>())
1426 );
1427 }
1428};
1429
1430template<typename ReturnType, typename... ParamTypes>
1431requires (std::is_void_v<ReturnType> or dsl_primitive<ReturnType> or dsl_pointer_to_primitive<ReturnType>) and
1432 ((dsl_primitive<ParamTypes> or dsl_pointer_to_primitive<ParamTypes>) and ...)
1433struct FunctionProxy<ReturnType(ParamTypes...)>
1434 : FunctionProxy<PrimitiveExpr<ReturnType, 1>(PrimitiveExpr<ParamTypes, 1>...)>
1435{
1436 using FunctionProxy<PrimitiveExpr<ReturnType, 1>(PrimitiveExpr<ParamTypes, 1>...)>::FunctionProxy;
1437};
1438
1439template<typename... ParamTypes, std::size_t... ParamLs>
1440struct FunctionProxy<void(PrimitiveExpr<ParamTypes, ParamLs>...)>
1441 : FunctionProxy<PrimitiveExpr<void, 1>(PrimitiveExpr<ParamTypes, ParamLs>...)>
1442{
1444};
1445
1446
1447/*======================================================================================================================
1448 * PrimitiveExpr
1449 *====================================================================================================================*/
1450
1451template<typename T, typename U, std::size_t L>
1453requires (PrimitiveExpr<T, L> e) { PrimitiveExpr<common_type_t<T, U>, L>(e); } and
1455
1459template<dsl_primitive T, std::size_t L>
1460requires (L > 0) and (is_pow_2(L)) and (L * sizeof(T) <= 16) // since Wasm currently only supports 128 bit SIMD vectors
1462{
1464 using type = T;
1466 static constexpr std::size_t num_simd_lanes = L;
1467
1468 /*----- Friends --------------------------------------------------------------------------------------------------*/
1469 template<typename, std::size_t> friend struct PrimitiveExpr; // to convert U to T and U* to uint32_t
1470 template<typename, std::size_t>
1471 friend struct Expr; // to construct an empty `PrimitiveExpr<bool>` for the NULL information
1472 template<typename, VariableKind, bool, std::size_t>
1473 friend class detail::variable_storage; // to construct from `::wasm::Expression` and access private `expr()`
1474 friend struct Module; // to access internal `::wasm::Expression`, e.g. in `emit_return()`
1475 friend struct Block; // to access internal `::wasm::Expression`, e.g. in `go_to()`
1476 template<typename> friend struct FunctionProxy; // to access internal `::wasm::Expr` to construct function calls
1477 template<std::size_t> friend struct LocalBit; // to access private `move()`
1478 friend struct If; // to use PrimitiveExpr<bool> as condition
1479 friend struct While; // to use PrimitiveExpr<bool> as condition
1480 template<typename> friend struct invoke_interpreter; // to access private `expr()`
1481 template<typename, std::size_t> friend struct std::array; // to be able to default construct vectors array
1482
1483 private:
1485 ::wasm::Expression *expr_ = nullptr;
1487 std::list<std::shared_ptr<Bit>> referenced_bits_;
1488
1489 private:
1491 explicit PrimitiveExpr() = default;
1492
1494 explicit PrimitiveExpr(::wasm::Expression *expr, std::list<std::shared_ptr<Bit>> referenced_bits = {})
1495 : expr_(expr)
1496 , referenced_bits_(std::move(referenced_bits))
1497 { }
1500 explicit PrimitiveExpr(std::pair<::wasm::Expression*, std::list<std::shared_ptr<Bit>>> expr)
1501 : PrimitiveExpr(std::move(expr.first), std::move(expr.second))
1502 { }
1503
1505 explicit PrimitiveExpr(const std::array<uint8_t, 16> &bytes)
1506 requires (L > 1)
1507 : PrimitiveExpr(Module::Builder().makeConst(::wasm::Literal(bytes.data())))
1508 { }
1509
1511 explicit PrimitiveExpr(std::array<PrimitiveExpr, 1> vectors)
1512 requires (L > 1)
1513 : PrimitiveExpr(vectors[0])
1514 { }
1515
1516 public:
1518 template<dsl_primitive... Us>
1519 requires (sizeof...(Us) > 0) and requires (Us... us) { make_literal<T, L>(us...); }
1520 explicit PrimitiveExpr(Us... value)
1521 : PrimitiveExpr(Module::Builder().makeConst(make_literal<T, L>(value...)))
1522 { }
1523
1525 template<decayable... Us>
1526 requires (sizeof...(Us) > 0) and (dsl_primitive<std::decay_t<Us>> and ...) and
1527 requires (Us... us) { PrimitiveExpr(std::decay_t<Us>(us)...); }
1528 explicit PrimitiveExpr(Us... value)
1529 : PrimitiveExpr(std::decay_t<Us>(value)...)
1530 { }
1531
1536 : PrimitiveExpr(std::exchange(other.expr_, nullptr), std::move(other.referenced_bits_))
1537 { /* move, not copy */ }
1541 : PrimitiveExpr(std::exchange(other.expr_, nullptr), std::move(other.referenced_bits_))
1542 { }
1543
1544 private:
1547 using std::swap;
1548 swap(this->expr_, other.expr_);
1549 swap(this->referenced_bits_, other.referenced_bits_);
1550 return *this;
1551 }
1552
1553 public:
1554 ~PrimitiveExpr() { M_insist(not expr_, "expression must be used or explicitly discarded"); }
1555
1556 private:
1558 ::wasm::Expression * expr() {
1559 M_insist(expr_, "cannot access an already moved or discarded expression of a `PrimitiveExpr`");
1560 return std::exchange(expr_, nullptr);
1561 }
1563 std::list<std::shared_ptr<Bit>> referenced_bits() { return std::move(referenced_bits_); }
1566 template<dsl_primitive U = T, std::size_t M = L>
1567 std::pair<::wasm::Expression*, std::list<std::shared_ptr<Bit>>> move() {
1568 return { expr(), referenced_bits() };
1569 }
1570
1571 public:
1574 explicit operator bool() const { return expr_ != nullptr; }
1575
1578 M_insist(expr_, "cannot clone an already moved or discarded `PrimitiveExpr`");
1579 return PrimitiveExpr(
1580 /* expr= */ ::wasm::ExpressionManipulator::copy(expr_, Module::Get().module_),
1581 /* referenced_bits= */ referenced_bits_ // copy
1582 );
1583 }
1584
1588 void discard() {
1589 M_insist(expr_, "cannot discard an already moved or discarded `PrimitiveExpr`");
1590 if (expr_->is<::wasm::Call>())
1591 Module::Block().list.push_back(Module::Builder().makeDrop(expr_)); // keep the function call
1592#ifndef NDEBUG
1593 expr_ = nullptr;
1594#endif
1595 referenced_bits_.clear();
1596 }
1597
1598
1599 /*------------------------------------------------------------------------------------------------------------------
1600 * Operation helper
1601 *----------------------------------------------------------------------------------------------------------------*/
1602
1603 private:
1606 template<dsl_primitive ResultType, std::size_t ResultL>
1609 /* expr= */ Module::Builder().makeUnary(op, expr()),
1610 /* referenced_bits= */ referenced_bits() // moved
1611 );
1612 }
1613
1616 template<dsl_primitive ResultType, std::size_t ResultL, dsl_primitive OperandType, std::size_t OperandL>
1618 auto referenced_bits = this->referenced_bits(); // moved
1619 referenced_bits.splice(referenced_bits.end(), other.referenced_bits());
1621 /* expr= */ Module::Builder().makeBinary(op,
1622 this->template to<OperandType, OperandL>().expr(),
1623 other.expr()),
1624 /* referenced_bits= */ std::move(referenced_bits)
1625 );
1626 }
1627
1628
1629 /*------------------------------------------------------------------------------------------------------------------
1630 * Conversion operations
1631 *----------------------------------------------------------------------------------------------------------------*/
1632
1633 private:
1634 template<dsl_primitive U, std::size_t M>
1636 using From = T;
1637 using To = U;
1638 constexpr std::size_t FromL = L;
1639 constexpr std::size_t ToL = M;
1640
1641 if constexpr (std::same_as<From, To> and FromL == ToL)
1642 return *this;
1643 if constexpr (integral<From> and integral<To> and std::is_signed_v<From> == std::is_signed_v<To> and
1644 sizeof(From) == sizeof(To) and FromL == ToL)
1645 return PrimitiveExpr<To, ToL>(move());
1646
1647 if constexpr (boolean<From>) { // from boolean
1648 if constexpr (integral<To>) { // to integer
1649 if constexpr (FromL == 1 and ToL == 1) { // scalar
1650 if constexpr (sizeof(To) <= 4) // bool -> i32
1651 return PrimitiveExpr<To, ToL>(move());
1652 if constexpr (sizeof(To) == 8) // bool -> i64
1653 return unary<To, ToL>(::wasm::ExtendUInt32);
1654 }
1655 if constexpr (FromL > 1 and FromL == ToL) { // vectorial
1656 if constexpr (sizeof(To) == 1) // bool -> i8/u8
1657 return -PrimitiveExpr<To, ToL>(move()); // negate to convert 0xff to 1
1658 if constexpr (std::is_signed_v<To>) {
1659 if constexpr (sizeof(To) == 2) // bool -> i16
1660 return to<int8_t, ToL>().template to<To, ToL>();
1661 if constexpr (sizeof(To) == 4) // bool -> i32
1662 return to<int8_t, ToL>().template to<To, ToL>();
1663 if constexpr (sizeof(To) == 8) // bool -> i64
1664 return to<int8_t, ToL>().template to<To, ToL>();
1665 } else {
1666 if constexpr (sizeof(To) == 2) // bool -> u16
1667 return to<uint8_t, ToL>().template to<To, ToL>();
1668 if constexpr (sizeof(To) == 4) // bool -> u32
1669 return to<uint8_t, ToL>().template to<To, ToL>();
1670 if constexpr (sizeof(To) == 8) // bool -> u64
1671 return to<uint8_t, ToL>().template to<To, ToL>();
1672 }
1673 }
1674 }
1675 if constexpr (std::floating_point<To>) { // to floating point
1676 if constexpr (FromL == 1 and ToL == 1) { // scalar
1677 if constexpr (sizeof(To) == 4) // bool -> f32
1678 return unary<To, ToL>(::wasm::ConvertUInt32ToFloat32);
1679 if constexpr (sizeof(To) == 8) // bool -> f64
1680 return unary<To, ToL>(::wasm::ConvertUInt32ToFloat64);
1681 }
1682 if constexpr (FromL > 1 and FromL == ToL) { // vectorial
1683 if constexpr (sizeof(To) == 4) // bool -> f32
1684 return to<uint32_t, ToL>().template convert<To, ToL>();
1685 if constexpr (sizeof(To) == 8) // bool -> f64
1686 return to<uint32_t, ToL>().template convert<To, ToL>();
1687 }
1688 }
1689 }
1690
1691 if constexpr (boolean<To>) // to boolean
1692 return *this != PrimitiveExpr(static_cast<From>(0));
1693
1694 if constexpr (integral<From>) { // from integer
1695 if constexpr (integral<To>) { // to integer
1696 if constexpr (FromL == 1 and ToL == 1) { // scalar
1697 if constexpr (std::is_signed_v<From>) { // signed
1698 if constexpr (sizeof(From) <= 4 and sizeof(To) == 8) // i32 -> i64
1699 return unary<To, ToL>(::wasm::ExtendSInt32);
1700 if constexpr (sizeof(From) == 8 and sizeof(To) == 4) // i64 -> i32
1701 return unary<To, ToL>(::wasm::WrapInt64);
1702 } else { // unsigned
1703 if constexpr (sizeof(From) <= 4 and sizeof(To) == 8) // u32 -> u64
1704 return unary<To, ToL>(::wasm::ExtendUInt32);
1705 if constexpr (sizeof(From) == 8 and sizeof(To) == 4) // u64 -> u32
1706 return unary<To, ToL>(::wasm::WrapInt64);
1707 }
1708 if constexpr (sizeof(To) <= 4 and sizeof(From) < sizeof(To)) // From less precise than To
1709 return PrimitiveExpr<To, ToL>(move()); // extend integer
1710 if constexpr (sizeof(From) <= 4 and sizeof(To) < sizeof(From)) { // To less precise than From
1711 constexpr From MASK = (uint64_t(1) << (8 * sizeof(To))) - uint64_t(1);
1712 return PrimitiveExpr<To, ToL>((*this bitand PrimitiveExpr(MASK)).move()); // truncate integer
1713 }
1714 if constexpr (sizeof(From) == 8 and sizeof(To) < 4) { // To less precise than From
1715 if constexpr (std::is_signed_v<To>) {
1716 auto wrapped = unary<int32_t, ToL>(::wasm::WrapInt64); // wrap integer
1717 constexpr int32_t MASK = (int64_t(1) << (8 * sizeof(To))) - int64_t(1);
1719 (wrapped bitand PrimitiveExpr<int32_t, ToL>(MASK)).move() // truncate integer
1720 );
1721 } else {
1722 auto wrapped = unary<uint32_t, ToL>(::wasm::WrapInt64); // wrap integer
1723 constexpr uint32_t MASK = (uint64_t(1) << (8 * sizeof(To))) - uint64_t(1);
1725 (wrapped bitand PrimitiveExpr<uint32_t, ToL>(MASK)).move() // truncate integer
1726 );
1727 }
1728 }
1729 }
1730 if constexpr (FromL > 1 and FromL == ToL) { // vectorial
1731 if constexpr (std::is_signed_v<From>) { // signed
1732 if constexpr (sizeof(From) == 1 and sizeof(To) == 2 and FromL <= 8) // i8 -> i16
1733 return unary<To, ToL>(::wasm::ExtendLowSVecI8x16ToVecI16x8);
1734 if constexpr (sizeof(From) == 1 and sizeof(To) == 2 and FromL == 16) { // i8 -> i16
1735 std::array<PrimitiveExpr<To, ToL / 2>, 2> vectors;
1736 vectors[0] =
1737 clone().template unary<To, ToL / 2>(::wasm::ExtendLowSVecI8x16ToVecI16x8);
1738 vectors[1] = unary<To, ToL / 2>(::wasm::ExtendHighSVecI8x16ToVecI16x8);
1739 return PrimitiveExpr<To, ToL>(std::move(vectors));
1740 }
1741 if constexpr (sizeof(From) == 1 and sizeof(To) == 4) // i8 -> i32
1742 return to<int16_t, ToL>().template to<To, ToL>();
1743 if constexpr (sizeof(From) == 1 and sizeof(To) == 8) // i8 -> i64
1744 return to<int32_t, ToL>().template to<To, ToL>();
1745 if constexpr (sizeof(From) == 2 and sizeof(To) == 4 and FromL <= 4) // i16 -> i32
1746 return unary<To, ToL>(::wasm::ExtendLowSVecI16x8ToVecI32x4);
1747 if constexpr (sizeof(From) == 2 and sizeof(To) == 4 and FromL == 8) { // i16 -> i32
1748 std::array<PrimitiveExpr<To, ToL / 2>, 2> vectors;
1749 vectors[0] =
1750 clone().template unary<To, ToL / 2>(::wasm::ExtendLowSVecI16x8ToVecI32x4);
1751 vectors[1] = unary<To, ToL / 2>(::wasm::ExtendHighSVecI16x8ToVecI32x4);
1752 return PrimitiveExpr<To, ToL>(std::move(vectors));
1753 }
1754 if constexpr (sizeof(From) == 2 and sizeof(To) == 8) // i16 -> i64
1755 return to<int32_t, ToL>().template to<To, ToL>();
1756 if constexpr (sizeof(From) == 4 and sizeof(To) == 8 and FromL <= 2) // i32 -> i64
1757 return unary<To, ToL>(::wasm::ExtendLowSVecI32x4ToVecI64x2);
1758 if constexpr (sizeof(From) == 4 and sizeof(To) == 8 and FromL == 4) { // i32 -> i64
1759 std::array<PrimitiveExpr<To, ToL / 2>, 2> vectors;
1760 vectors[0] =
1761 clone().template unary<To, ToL / 2>(::wasm::ExtendLowSVecI32x4ToVecI64x2);
1762 vectors[1] = unary<To, ToL / 2>(::wasm::ExtendHighSVecI32x4ToVecI64x2);
1763 return PrimitiveExpr<To, ToL>(std::move(vectors));
1764 }
1765 } else { // unsigned
1766 if constexpr (sizeof(From) == 1 and sizeof(To) == 2 and FromL <= 8) // u8 -> u16
1767 return unary<To, ToL>(::wasm::ExtendLowUVecI8x16ToVecI16x8);
1768 if constexpr (sizeof(From) == 1 and sizeof(To) == 2 and FromL == 16) { // u8 -> u16
1769 std::array<PrimitiveExpr<To, ToL / 2>, 2> vectors;
1770 vectors[0] =
1771 clone().template unary<To, ToL / 2>(::wasm::ExtendLowUVecI8x16ToVecI16x8);
1772 vectors[1] = unary<To, ToL / 2>(::wasm::ExtendHighUVecI8x16ToVecI16x8);
1773 return PrimitiveExpr<To, ToL>(std::move(vectors));
1774 }
1775 if constexpr (sizeof(From) == 1 and sizeof(To) == 4) // u8 -> u32
1776 return to<uint16_t, ToL>().template to<To, ToL>();
1777 if constexpr (sizeof(From) == 1 and sizeof(To) == 8) // u8 -> u64
1778 return to<uint32_t, ToL>().template to<To, ToL>();
1779 if constexpr (sizeof(From) == 2 and sizeof(To) == 4 and FromL <= 4) // u16 -> u32
1780 return unary<To, ToL>(::wasm::ExtendLowUVecI16x8ToVecI32x4);
1781 if constexpr (sizeof(From) == 2 and sizeof(To) == 4 and FromL == 8) { // u16 -> u32
1782 std::array<PrimitiveExpr<To, ToL / 2>, 2> vectors;
1783 vectors[0] =
1784 clone().template unary<To, ToL / 2>(::wasm::ExtendLowUVecI16x8ToVecI32x4);
1785 vectors[1] = unary<To, ToL / 2>(::wasm::ExtendHighUVecI16x8ToVecI32x4);
1786 return PrimitiveExpr<To, ToL>(std::move(vectors));
1787 }
1788 if constexpr (sizeof(From) == 2 and sizeof(To) == 8) // u16 -> u64
1789 return to<uint32_t, ToL>().template to<To, ToL>();
1790 if constexpr (sizeof(From) == 4 and sizeof(To) == 8 and FromL <= 2) // u32 -> u64
1791 return unary<To, ToL>(::wasm::ExtendLowUVecI32x4ToVecI64x2);
1792 if constexpr (sizeof(From) == 4 and sizeof(To) == 8 and FromL == 4) { // u32 -> u64
1793 std::array<PrimitiveExpr<To, ToL / 2>, 2> vectors;
1794 vectors[0] =
1795 clone().template unary<To, ToL / 2>(::wasm::ExtendLowUVecI32x4ToVecI64x2);
1796 vectors[1] = unary<To, ToL / 2>(::wasm::ExtendHighUVecI32x4ToVecI64x2);
1797 return PrimitiveExpr<To, ToL>(std::move(vectors));
1798 }
1799 }
1800 }
1801 }
1802 if constexpr (std::floating_point<To>) { // to floating point
1803 if constexpr (FromL == 1 and ToL == 1) { // scalar
1804 if constexpr (std::is_signed_v<From>) { // signed
1805 if constexpr (sizeof(From) <= 4 and sizeof(To) == 4) // i32 -> f32
1806 return unary<To, ToL>(::wasm::ConvertSInt32ToFloat32);
1807 if constexpr (sizeof(From) <= 4 and sizeof(To) == 8) // i32 -> f64
1808 return unary<To, ToL>(::wasm::ConvertSInt32ToFloat64);
1809 if constexpr (sizeof(From) == 8 and sizeof(To) == 4) // i64 -> f32
1810 return unary<To, ToL>(::wasm::ConvertSInt64ToFloat32);
1811 if constexpr (sizeof(From) == 8 and sizeof(To) == 8) // i64 -> f64
1812 return unary<To, ToL>(::wasm::ConvertSInt64ToFloat64);
1813 } else { // unsigned
1814 if constexpr (sizeof(From) <= 4 and sizeof(To) == 4) // u32 -> f32
1815 return unary<To, ToL>(::wasm::ConvertUInt32ToFloat32);
1816 if constexpr (sizeof(From) <= 4 and sizeof(To) == 8) // u32 -> f64
1817 return unary<To, ToL>(::wasm::ConvertUInt32ToFloat64);
1818 if constexpr (sizeof(From) == 8 and sizeof(To) == 4) // u64 -> f32
1819 return unary<To, ToL>(::wasm::ConvertUInt64ToFloat32);
1820 if constexpr (sizeof(From) == 8 and sizeof(To) == 8) // u64 -> f64
1821 return unary<To, ToL>(::wasm::ConvertUInt64ToFloat64);
1822 }
1823 }
1824 if constexpr (FromL > 1 and FromL == ToL) { // vectorial
1825 if constexpr (std::is_signed_v<From>) { // signed
1826 if constexpr (sizeof(From) == 1 and sizeof(To) == 4) // i8 -> f32
1827 return to<int32_t, ToL>().template to<To, ToL>();
1828 if constexpr (sizeof(From) == 1 and sizeof(To) == 8) // i8 -> f64
1829 return to<int32_t, ToL>().template to<To, ToL>();
1830 if constexpr (sizeof(From) == 2 and sizeof(To) == 4) // i16 -> f32
1831 return to<int32_t, ToL>().template to<To, ToL>();
1832 if constexpr (sizeof(From) == 2 and sizeof(To) == 8) // i16 -> f64
1833 return to<int32_t, ToL>().template to<To, ToL>();
1834 if constexpr (sizeof(From) == 4 and sizeof(To) == 4) // i32 -> f32
1835 return unary<To, ToL>(::wasm::ConvertSVecI32x4ToVecF32x4);
1836 if constexpr (sizeof(From) == 4 and sizeof(To) == 8 and FromL <= 2) // i32 -> f64
1837 return unary<To, ToL>(::wasm::ConvertLowSVecI32x4ToVecF64x2);
1838 if constexpr (sizeof(From) == 4 and sizeof(To) == 8 and FromL == 4) { // i32 -> f64
1839 std::array<PrimitiveExpr<To, ToL / 2>, 2> vectors;
1840 vectors[0] =
1841 clone().template unary<To, ToL / 2>(::wasm::ConvertLowSVecI32x4ToVecF64x2);
1842 auto high_to_low = swizzle_lanes(std::to_array<uint8_t>({ 2, 3 }));
1843 vectors[1] =
1844 high_to_low.template unary<To, ToL / 2>(::wasm::ConvertLowSVecI32x4ToVecF64x2);
1845 return PrimitiveExpr<To, ToL>(std::move(vectors));
1846 }
1847 } else { // unsigned
1848 if constexpr (sizeof(From) == 1 and sizeof(To) == 4) // u8 -> f32
1849 return to<uint32_t, ToL>().template convert<To, ToL>();
1850 if constexpr (sizeof(From) == 1 and sizeof(To) == 8) // u8 -> f64
1851 return to<uint32_t, ToL>().template convert<To, ToL>();
1852 if constexpr (sizeof(From) == 2 and sizeof(To) == 4) // u16 -> f32
1853 return to<uint32_t, ToL>().template convert<To, ToL>();
1854 if constexpr (sizeof(From) == 2 and sizeof(To) == 8) // u16 -> f64
1855 return to<uint32_t, ToL>().template convert<To, ToL>();
1856 if constexpr (sizeof(From) == 4 and sizeof(To) == 4) // u32 -> f32
1857 return unary<To, ToL>(::wasm::ConvertUVecI32x4ToVecF32x4);
1858 if constexpr (sizeof(From) == 4 and sizeof(To) == 8 and FromL <= 2) // u32 -> f64
1859 return unary<To, ToL>(::wasm::ConvertLowUVecI32x4ToVecF64x2);
1860 if constexpr (sizeof(From) == 4 and sizeof(To) == 8 and FromL == 4) { // u32 -> f64
1861 std::array<PrimitiveExpr<To, ToL / 2>, 2> vectors;
1862 vectors[0] =
1863 clone().template unary<To, ToL / 2>(::wasm::ConvertLowUVecI32x4ToVecF64x2);
1864 auto high_to_low = swizzle_lanes(std::to_array<uint8_t>({ 2, 3 }));
1865 vectors[1] =
1866 high_to_low.template unary<To, ToL / 2>(::wasm::ConvertLowUVecI32x4ToVecF64x2);
1867 return PrimitiveExpr<To, ToL>(std::move(vectors));
1868 }
1869 }
1870 }
1871 }
1872 }
1873
1874 if constexpr (std::floating_point<From>) { // from floating point
1875 if constexpr (integral<To>) { // to integer
1876 if constexpr (FromL == 1 and ToL == 1) { // scalar
1877 if constexpr (std::is_signed_v<To>) { // signed
1878 if constexpr (sizeof(From) == 4 and sizeof(To) <= 4) // f32 -> i32
1879 return unary<int32_t, ToL>(::wasm::TruncSFloat32ToInt32).template to<To, ToL>();
1880 if constexpr (sizeof(From) == 4 and sizeof(To) == 8) // f32 -> i64
1881 return unary<To, ToL>(::wasm::TruncSFloat32ToInt64);
1882 if constexpr (sizeof(From) == 8 and sizeof(To) <= 4) // f64 -> i32
1883 return unary<int32_t, ToL>(::wasm::TruncSFloat64ToInt32).template to<To, ToL>();
1884 if constexpr (sizeof(From) == 8 and sizeof(To) == 8) // f64 -> i64
1885 return unary<To, ToL>(::wasm::TruncSFloat64ToInt64);
1886 } else { // unsigned
1887 if constexpr (sizeof(From) == 4 and sizeof(To) <= 4) // f32 -> u32
1888 return unary<uint32_t, ToL>(::wasm::TruncUFloat32ToInt32).template to<To, ToL>();
1889 if constexpr (sizeof(From) == 4 and sizeof(To) == 8) // f32 -> u64
1890 return unary<To, ToL>(::wasm::TruncUFloat32ToInt64);
1891 if constexpr (sizeof(From) == 8 and sizeof(To) <= 4) // f64 -> u32
1892 return unary<uint32_t, ToL>(::wasm::TruncUFloat64ToInt32).template to<To, ToL>();
1893 if constexpr (sizeof(From) == 8 and sizeof(To) == 8) // f64 -> u64
1894 return unary<To, ToL>(::wasm::TruncUFloat64ToInt64);
1895 }
1896 }
1897 if constexpr (FromL > 1 and FromL == ToL) { // vectorial
1898 if constexpr (std::is_signed_v<To>) { // signed
1899 if constexpr (sizeof(From) == 4 and sizeof(To) == 4) // f32 -> i32
1900 return unary<To, ToL>(::wasm::TruncSatSVecF32x4ToVecI32x4);
1901 if constexpr (sizeof(From) == 8 and sizeof(To) == 4) // f64 -> i32
1902 return unary<To, ToL>(::wasm::TruncSatZeroSVecF64x2ToVecI32x4);
1903 if constexpr (sizeof(From) == 4 and sizeof(To) == 8) // f32 -> i64
1904 return to<int32_t, ToL>().template to<To, ToL>();
1905 if constexpr (sizeof(From) == 8 and sizeof(To) == 8) // f64 -> i64
1906 return to<int32_t, ToL>().template to<To, ToL>();
1907 } else { // unsigned
1908 if constexpr (sizeof(From) == 4 and sizeof(To) == 4) // f32 -> u32
1909 return unary<To, ToL>(::wasm::TruncSatUVecF32x4ToVecI32x4);
1910 if constexpr (sizeof(From) == 8 and sizeof(To) == 4) // f64 -> u32
1911 return unary<To, ToL>(::wasm::TruncSatZeroUVecF64x2ToVecI32x4);
1912 if constexpr (sizeof(From) == 4 and sizeof(To) == 8) // f32 -> u64
1913 return convert<uint32_t, ToL>().template to<To, ToL>();
1914 if constexpr (sizeof(From) == 8 and sizeof(To) == 8) // f64 -> u64
1915 return convert<uint32_t, ToL>().template to<To, ToL>();
1916 }
1917 }
1918 }
1919 if constexpr (std::floating_point<To>) { // to floating point
1920 if constexpr (FromL == 1 and ToL == 1) { // scalar
1921 if constexpr (sizeof(From) == 4 and sizeof(To) == 8) // f32 -> f64
1922 return unary<To, ToL>(::wasm::PromoteFloat32);
1923 if constexpr (sizeof(From) == 8 and sizeof(To) == 4) // f64 -> f32
1924 return unary<To, ToL>(::wasm::DemoteFloat64);
1925 }
1926 if constexpr (FromL > 1 and FromL == ToL) { // vectorial
1927 if constexpr (sizeof(From) == 4 and sizeof(To) == 8 and FromL <= 2) // f32 -> f64
1928 return unary<To, ToL>(::wasm::PromoteLowVecF32x4ToVecF64x2);
1929 if constexpr (sizeof(From) == 4 and sizeof(To) == 8 and FromL == 4) { // f32 -> f64
1930 std::array<PrimitiveExpr<To, ToL / 2>, 2> vectors;
1931 vectors[0] = clone().template unary<To, ToL / 2>(::wasm::PromoteLowVecF32x4ToVecF64x2);
1932 auto high_to_low = swizzle_lanes(std::to_array<uint8_t>({ 2, 3 }));
1933 vectors[1] =
1934 high_to_low.template unary<To, ToL / 2>(::wasm::PromoteLowVecF32x4ToVecF64x2);
1935 return PrimitiveExpr<To, ToL>(std::move(vectors));
1936 }
1937 if constexpr (sizeof(From) == 8 and sizeof(To) == 4) // f64 -> f32
1938 return unary<To, ToL>(::wasm::DemoteZeroVecF64x2ToVecF32x4);
1939 }
1940 }
1941 }
1942
1943 M_unreachable("illegal conversion");
1944 }
1945
1946 public:
1955 template<dsl_primitive To, std::size_t ToL = L>
1956 requires (L == ToL) and // L and ToL are equal
1957 same_signedness<T, To> and // T and To have same signedness
1958 (integral<T> == integral<To>) and // neither nor both T and To are integers (excluding bool)
1959 (sizeof(T) <= sizeof(To)) // T can be *trivially* converted to To
1960 operator PrimitiveExpr<To, ToL>() { return convert<To, ToL>(); }
1961
1973 template<dsl_primitive To, std::size_t ToL = L>
1974 requires (L == ToL) and // L and ToL are equal
1975 (same_signedness<T, To> or // T and To have same signedness
1976 boolean<T> or std::same_as<T, char> or // or T is bool or char
1977 boolean<To> or std::same_as<To, char>) and // or To is bool or char
1978 ((L != 1) or // for scalar values
1979 std::is_convertible_v<T, To>) and // T can be converted to To
1980 ((L == 1) or // for vectorial values
1981 (std::is_convertible_v<T, To> and // T can be converted to To
1982 not (integral<T> and integral<To> and sizeof(T) > sizeof(To)) and // except integer conversion to less precise type
1983 not (integral<T> and sizeof(T) == 8 and std::floating_point<To>) and // except 64-bit integer to floating point
1984 not (std::floating_point<T> and integral<To> and sizeof(To) <= 2))) // except floating point to 8-bit or 16-bit integer
1985 PrimitiveExpr<To, ToL> to() { return convert<To, ToL>(); }
1986
1993 template<dsl_pointer_to_primitive To, std::size_t ToL = L>
1994 PrimitiveExpr<To, ToL> to() requires std::same_as<T, uint32_t> and (L == 1) {
1995 return PrimitiveExpr<To, ToL>(*this);
1996 }
1997
2002 auto make_signed() requires unsigned_integral<T> { return PrimitiveExpr<std::make_signed_t<T>, L>(move()); }
2003
2008 auto make_unsigned() requires signed_integral<T> { return PrimitiveExpr<std::make_unsigned_t<T>, L>(move()); }
2009
2016 template<dsl_primitive To, std::size_t ToL = L>
2017 requires (L == ToL) and (L == 1) and // value is scalar
2018 ((integral<T> and std::floating_point<To>) or // either T is integral and To is floating point
2019 (std::floating_point<T> and integral<To>)) and // or vice versa
2020 (sizeof(T) == sizeof(To)) // T and To have same size
2021 PrimitiveExpr<To, ToL> reinterpret() {
2022 using From = T;
2023 constexpr std::size_t FromL = L;
2024
2025 if constexpr (integral<From>) { // from integer
2026 if constexpr (std::floating_point<To>) { // to floating point
2027 if constexpr (FromL == 1) { // scalar
2028 if constexpr (sizeof(From) == 4 and sizeof(To) == 4) // i32 -> f32
2029 return unary<To, ToL>(::wasm::ReinterpretInt32);
2030 if constexpr (sizeof(From) == 8 and sizeof(To) == 8) // i64 -> f64
2031 return unary<To, ToL>(::wasm::ReinterpretInt64);
2032 }
2033 }
2034 }
2035
2036 if constexpr (std::floating_point<From>) { // from floating point
2037 if constexpr (integral<To>) { // to integer
2038 if constexpr (FromL == 1) { // scalar
2039 if constexpr (sizeof(From) == 4 and sizeof(To) == 4) // f32 -> i32
2040 return unary<To, ToL>(::wasm::ReinterpretFloat32);
2041 if constexpr (sizeof(From) == 8 and sizeof(To) == 8) // f64 -> i64
2042 return unary<To, ToL>(::wasm::ReinterpretFloat64);
2043 }
2044 }
2045 }
2046
2047 M_unreachable("illegal reinterpretation");
2048 }
2049
2051 template<std::size_t ToL>
2052 requires (L == 1) and (ToL > 1) and (ToL * sizeof(T) <= 16)
2053 PrimitiveExpr<T, ToL> broadcast() {
2054 if constexpr (boolean<T>)
2055 return unary<T, ToL>(::wasm::UnaryOp::SplatVecI8x16);
2056
2057 if constexpr (integral<T>) {
2058 if constexpr (sizeof(T) == 1)
2059 return unary<T, ToL>(::wasm::UnaryOp::SplatVecI8x16);
2060 if constexpr (sizeof(T) == 2)
2061 return unary<T, ToL>(::wasm::UnaryOp::SplatVecI16x8);
2062 if constexpr (sizeof(T) == 4)
2063 return unary<T, ToL>(::wasm::UnaryOp::SplatVecI32x4);
2064 if constexpr (sizeof(T) == 8)
2065 return unary<T, ToL>(::wasm::UnaryOp::SplatVecI64x2);
2066 }
2067
2068 if constexpr (std::floating_point<T>) {
2069 if constexpr (sizeof(T) == 4)
2070 return unary<T, ToL>(::wasm::UnaryOp::SplatVecF32x4);
2071 if constexpr (sizeof(T) == 8)
2072 return unary<T, ToL>(::wasm::UnaryOp::SplatVecF64x2);
2073 }
2074
2075 M_unreachable("illegal broadcast");
2076 }
2078 template<std::size_t ToL>
2079 requires (L == 1) and (ToL > 1) and (ToL * sizeof(T) > 16)
2080 PrimitiveExpr<T, ToL> broadcast() {
2081 using ResT = PrimitiveExpr<T, ToL>;
2082 std::array<typename ResT::vector_type, ResT::num_vectors> vectors;
2083 for (std::size_t idx = 0; idx < ResT::num_vectors; ++idx)
2084 vectors[idx] = clone().template broadcast<ResT::vector_type::num_simd_lanes>();
2085 return ResT(std::move(vectors));
2086 }
2087
2088
2089 /*------------------------------------------------------------------------------------------------------------------
2090 * Unary operations
2091 *----------------------------------------------------------------------------------------------------------------*/
2092
2093#define UNOP_(NAME, TYPE) (::wasm::UnaryOp::NAME##TYPE)
2094#define UNIOP_(NAME) [] { \
2095 if constexpr (sizeof(T) == 8) \
2096 return UNOP_(NAME,Int64); \
2097 else if constexpr (sizeof(T) <= 4) \
2098 return UNOP_(NAME,Int32); \
2099 else \
2100 M_unreachable("unsupported operation"); \
2101} ()
2102#define UNFOP_(NAME) [] { \
2103 if constexpr (sizeof(T) == 8) \
2104 return UNOP_(NAME,Float64); \
2105 else if constexpr (sizeof(T) == 4) \
2106 return UNOP_(NAME,Float32); \
2107 else \
2108 M_unreachable("unsupported operation"); \
2109} ()
2110#define UNVOP_(NAME, TYPE) (::wasm::UnaryOp::NAME##Vec##TYPE)
2111#define UNIVOP_(NAME) [] { \
2112 if constexpr (sizeof(T) == 8) \
2113 return UNVOP_(NAME,I64x2); \
2114 else if constexpr (sizeof(T) == 4) \
2115 return UNVOP_(NAME,I32x4); \
2116 else if constexpr (sizeof(T) == 2) \
2117 return UNVOP_(NAME,I16x8); \
2118 else if constexpr (sizeof(T) == 1) \
2119 return UNVOP_(NAME,I8x16); \
2120 else \
2121 M_unreachable("unsupported operation"); \
2122} ()
2123#define UNFVOP_(NAME) [] { \
2124 if constexpr (sizeof(T) == 8) \
2125 return UNVOP_(NAME,F64x2); \
2126 else if constexpr (sizeof(T) == 4) \
2127 return UNVOP_(NAME,F32x4); \
2128 else \
2129 M_unreachable("unsupported operation"); \
2130} ()
2131#define UNARY_VOP(NAME) [] { \
2132 if constexpr (std::integral<T>) \
2133 return UNIVOP_(NAME); \
2134 else if constexpr (std::floating_point<T>) \
2135 return UNFVOP_(NAME); \
2136 else \
2137 M_unreachable("unsupported operation"); \
2138} ()
2139
2140 /*----- Arithmetical operations ----------------------------------------------------------------------------------*/
2141
2142 PrimitiveExpr operator+() requires arithmetic<T> { return *this; }
2143
2144 PrimitiveExpr operator-() requires integral<T> and (L == 1) { return PrimitiveExpr(T(0)) - *this; }
2145 PrimitiveExpr operator-() requires std::floating_point<T> and (L == 1) { return unary<T, L>(UNFOP_(Neg)); }
2146 PrimitiveExpr operator-() requires arithmetic<T> and (L > 1) { return unary<T, L>(UNARY_VOP(Neg)); }
2147
2148 PrimitiveExpr abs() requires std::floating_point<T> and (L == 1) { return unary<T, L>(UNFOP_(Abs)); }
2149 PrimitiveExpr abs() requires (L > 1) { return unary<T, L>(UNARY_VOP(Abs)); }
2150 PrimitiveExpr ceil() requires std::floating_point<T> and (L == 1) { return unary<T, L>(UNFOP_(Ceil)); }
2151 PrimitiveExpr ceil() requires std::floating_point<T> and (L > 1) { return unary<T, L>(UNFVOP_(Ceil)); }
2152 PrimitiveExpr floor() requires std::floating_point<T> and (L == 1) { return unary<T, L>(UNFOP_(Floor)); }
2153 PrimitiveExpr floor() requires std::floating_point<T> and (L > 1) { return unary<T, L>(UNFVOP_(Floor)); }
2154 PrimitiveExpr trunc() requires std::floating_point<T> and (L > 1) { return unary<T, L>(UNFVOP_(Trunc)); }
2155 PrimitiveExpr nearest() requires std::floating_point<T> and (L > 1) { return unary<T, L>(UNFVOP_(Nearest)); }
2156
2157 PrimitiveExpr sqrt() requires std::floating_point<T> and (L == 1) { return unary<T, L>(UNFOP_(Sqrt)); }
2158 PrimitiveExpr sqrt() requires std::floating_point<T> and (L > 1) { return unary<T, L>(UNFVOP_(Sqrt)); }
2159
2160 PrimitiveExpr<int16_t, L / 2> add_pairwise() requires signed_integral<T> and (sizeof(T) == 1) and (L > 1) {
2161 static_assert(L % 2 == 0, "must mask this expression first");
2162 auto vec = unary<int16_t, L / 2>(UNVOP_(ExtAddPairwiseS, I8x16ToI16x8));
2163 return M_CONSTEXPR_COND(decltype(vec)::num_simd_lanes == 1,
2164 vec.template extract_unsafe<0>(), // extract a single sum from vector to scalar
2165 vec);
2166 }
2167 PrimitiveExpr<uint16_t, L / 2> add_pairwise() requires unsigned_integral<T> and (sizeof(T) == 1) and (L > 1) {
2168 static_assert(L % 2 == 0, "must mask this expression first");
2169 auto vec = unary<uint16_t, L / 2>(UNVOP_(ExtAddPairwiseU, I8x16ToI16x8));
2170 return M_CONSTEXPR_COND(decltype(vec)::num_simd_lanes == 1,
2171 vec.template extract_unsafe<0>(), // extract a single sum from vector to scalar
2172 vec);
2173 }
2174 PrimitiveExpr<int32_t, L / 2> add_pairwise() requires signed_integral<T> and (sizeof(T) == 2) and (L > 1) {
2175 static_assert(L % 2 == 0, "must mask this expression first");
2176 auto vec = unary<int32_t, L / 2>(UNVOP_(ExtAddPairwiseS, I16x8ToI32x4));
2177 return M_CONSTEXPR_COND(decltype(vec)::num_simd_lanes == 1,
2178 vec.template extract_unsafe<0>(), // extract a single sum from vector to scalar
2179 vec);
2180 }
2181 PrimitiveExpr<uint32_t, L / 2> add_pairwise() requires unsigned_integral<T> and (sizeof(T) == 2) and (L > 1) {
2182 static_assert(L % 2 == 0, "must mask this expression first");
2183 auto vec = unary<uint32_t, L / 2>(UNVOP_(ExtAddPairwiseU, I16x8ToI32x4));
2184 return M_CONSTEXPR_COND(decltype(vec)::num_simd_lanes == 1,
2185 vec.template extract_unsafe<0>(), // extract a single sum from vector to scalar
2186 vec);
2187 }
2188
2189 /*----- Bitwise operations ---------------------------------------------------------------------------------------*/
2190
2191 PrimitiveExpr operator~() requires integral<T> and (L == 1) { return PrimitiveExpr(T(-1)) xor *this; }
2192 PrimitiveExpr operator~() requires integral<T> and (L > 1) { return unary<T, L>(UNVOP_(Not, 128)); }
2193
2194 PrimitiveExpr clz() requires unsigned_integral<T> and (sizeof(T) >= 4) and (L == 1) {
2195 return unary<T, L>(UNIOP_(Clz));
2196 }
2197 PrimitiveExpr clz() requires unsigned_integral<T> and (sizeof(T) == 2) and (L == 1) {
2198 return unary<T, L>(UNIOP_(Clz)) - PrimitiveExpr(16U); // the value is represented as I32
2199 }
2200 PrimitiveExpr clz() requires unsigned_integral<T> and (sizeof(T) == 1) and (L == 1) {
2201 return unary<T, L>(UNIOP_(Clz)) - PrimitiveExpr(24U); // the value is represented as I32
2202 }
2203 PrimitiveExpr ctz() requires unsigned_integral<T> and (L == 1) { return unary<T, L>(UNIOP_(Ctz)); }
2204 PrimitiveExpr popcnt() requires unsigned_integral<T> and (L == 1) { return unary<T, L>(UNIOP_(Popcnt)); }
2205 PrimitiveExpr popcnt() requires unsigned_integral<T> and (sizeof(T) == 1) and (L > 1) {
2206 return unary<T, L>(UNVOP_(Popcnt, I8x16));
2207 }
2208 PrimitiveExpr popcnt() requires unsigned_integral<T> and (sizeof(T) == 2) and (L > 1) {
2209 auto popcnt_on_I8x16 = this->unary<uint8_t, L * 2>(UNVOP_(Popcnt, I8x16));
2210 return popcnt_on_I8x16.add_pairwise();
2211 }
2212 PrimitiveExpr popcnt() requires unsigned_integral<T> and (sizeof(T) == 4) and (L > 1) {
2213 auto popcnt_on_I8x16 = this->unary<uint8_t, L * 4>(UNVOP_(Popcnt, I8x16));
2214 return popcnt_on_I8x16.add_pairwise().add_pairwise();
2215 }
2216
2219 auto bitmask = unary<uint32_t, 1>(UNVOP_(Bitmask, I8x16));
2220 return M_CONSTEXPR_COND(L * sizeof(T) == 16, bitmask, bitmask bitand uint32_t((1U << L) - 1U)); // to remove unused lanes
2221 }
2223 PrimitiveExpr<uint32_t, 1> bitmask() requires integral<T> and (L > 1) {
2224 auto bitmask = unary<uint32_t, 1>(UNIVOP_(Bitmask));
2225 return M_CONSTEXPR_COND(L * sizeof(T) == 16, bitmask, bitmask bitand uint32_t((1U << L) - 1U)); // to remove unused lanes
2226 }
2227
2228 /*----- Comparison operations ------------------------------------------------------------------------------------*/
2229
2230 PrimitiveExpr<bool, L> eqz() requires integral<T> and (L == 1) { return unary<bool, L>(UNIOP_(EqZ)); }
2231
2232 /*----- Logical operations ---------------------------------------------------------------------------------------*/
2233
2234 PrimitiveExpr operator not() requires boolean<T> and (L == 1) { return unary<T, L>(UNIOP_(EqZ)); }
2235 PrimitiveExpr operator not() requires boolean<T> and (L > 1) { return unary<T, L>(UNVOP_(Not, 128)); }
2236
2239 auto masked =
2240 M_CONSTEXPR_COND(L * sizeof(T) == 16, *this, PrimitiveExpr(true) and *this); // to set the unused lanes to false
2241 return masked.template unary<bool, 1>(UNVOP_(AnyTrue, 128));
2242 }
2244 PrimitiveExpr<bool, 1> any_true() requires integral<T> and (L > 1) {
2245 auto masked =
2246 M_CONSTEXPR_COND(L * sizeof(T) == 16, *this, PrimitiveExpr(T(-1)) bitand *this); // to set the unused lanes to 0
2247 return masked.template unary<bool, 1>(UNVOP_(AnyTrue, 128));
2248 }
2251 std::array<uint8_t, 16> bytes;
2252 auto it = std::fill_n(bytes.begin(), L, 0);
2253 std::fill(it, bytes.end(), 0xff); // all bits to 1 represent true
2254 auto masked =
2255 M_CONSTEXPR_COND(L * sizeof(T) == 16, *this, PrimitiveExpr(bytes) or *this); // to set the unused lanes to true
2256 return masked.template unary<bool, 1>(UNVOP_(AllTrue, I8x16));
2257 }
2259 PrimitiveExpr<bool, 1> all_true() requires integral<T> and (L > 1) {
2260 std::array<uint8_t, 16> bytes;
2261 auto it = std::fill_n(bytes.begin(), L, 0);
2262 std::fill(it, bytes.end(), 1);
2263 auto masked =
2264 M_CONSTEXPR_COND(L * sizeof(T) == 16, *this, PrimitiveExpr(bytes) bitor *this); // to set the unused lanes to 1
2265 return masked.template unary<bool, 1>(UNIVOP_(AllTrue));
2266 }
2267
2268 /*----- Hashing operations ---------------------------------------------------------------------------------------*/
2269
2270 PrimitiveExpr<uint64_t, L> hash() requires unsigned_integral<T> and (L == 1) { return *this; }
2271 PrimitiveExpr<uint64_t, L> hash() requires signed_integral<T> and (L == 1) { return make_unsigned(); }
2272 PrimitiveExpr<uint64_t, L> hash() requires std::floating_point<T> and (sizeof(T) == 4) and (L == 1) {
2273 return reinterpret<int32_t>().make_unsigned();
2274 }
2275 PrimitiveExpr<uint64_t, L> hash() requires std::floating_point<T> and (sizeof(T) == 8) and (L == 1) {
2276 return reinterpret<int64_t>().make_unsigned();
2277 }
2278 PrimitiveExpr<uint64_t, L> hash() requires std::same_as<T, bool> and (L == 1) { return to<uint64_t>(); }
2279
2280#undef UNARY_VOP
2281#undef UNFVOP_
2282#undef UNIVOP_
2283#undef UNVOP_
2284#undef UNFOP_
2285#undef UNIOP_
2286#undef UNOP_
2287
2288
2289 /*------------------------------------------------------------------------------------------------------------------
2290 * Binary operations
2291 *----------------------------------------------------------------------------------------------------------------*/
2292
2293#define BINOP_(NAME, SIGN, TYPE) (::wasm::BinaryOp::NAME##SIGN##TYPE)
2294#define BINIOP_(NAME, SIGN) [] { \
2295 if constexpr (sizeof(To) == 8) \
2296 return BINOP_(NAME,SIGN,Int64); \
2297 else if constexpr (sizeof(To) <= 4) \
2298 return BINOP_(NAME,SIGN,Int32); \
2299 else \
2300 M_unreachable("unsupported operation"); \
2301} ()
2302#define BINFOP_(NAME) [] { \
2303 if constexpr (sizeof(To) == 8) \
2304 return BINOP_(NAME,,Float64); \
2305 else if constexpr (sizeof(To) == 4) \
2306 return BINOP_(NAME,,Float32); \
2307 else \
2308 M_unreachable("unsupported operation"); \
2309} ()
2310#define BINARY_OP(NAME, SIGN) [] { \
2311 if constexpr (std::integral<To>) \
2312 return BINIOP_(NAME, SIGN); \
2313 else if constexpr (std::floating_point<To>) \
2314 return BINFOP_(NAME); \
2315 else \
2316 M_unreachable("unsupported operation"); \
2317} ()
2318#define BINVOP_(NAME, SIGN, TYPE) (::wasm::BinaryOp::NAME##SIGN##Vec##TYPE)
2319#define BINIVOP_(NAME, SIGN) [] { \
2320 if constexpr (sizeof(To) == 8) \
2321 return BINVOP_(NAME,SIGN,I64x2); \
2322 else if constexpr (sizeof(To) == 4) \
2323 return BINVOP_(NAME,SIGN,I32x4); \
2324 else if constexpr (sizeof(To) == 2) \
2325 return BINVOP_(NAME,SIGN,I16x8); \
2326 else if constexpr (sizeof(To) == 1) \
2327 return BINVOP_(NAME,SIGN,I8x16); \
2328 else \
2329 M_unreachable("unsupported operation"); \
2330} ()
2331#define BINFVOP_(NAME) [] { \
2332 if constexpr (sizeof(To) == 8) \
2333 return BINVOP_(NAME,,F64x2); \
2334 else if constexpr (sizeof(To) == 4) \
2335 return BINVOP_(NAME,,F32x4); \
2336 else \
2337 M_unreachable("unsupported operation"); \
2338} ()
2339#define BINARY_VOP(NAME, SIGN) [] { \
2340 if constexpr (std::integral<To>) \
2341 return BINIVOP_(NAME, SIGN); \
2342 else if constexpr (std::floating_point<To>) \
2343 return BINFVOP_(NAME); \
2344 else \
2345 M_unreachable("unsupported operation"); \
2346} ()
2347
2348 /*----- Arithmetical operations ----------------------------------------------------------------------------------*/
2349
2351 template<arithmetic U>
2354 using To = common_type_t<T, U>;
2355 if constexpr (L * sizeof(To) <= 16)
2356 return binary<To, L, To, L>(M_CONSTEXPR_COND(L == 1, BINARY_OP(Add,), BINARY_VOP(Add,)), other);
2357 else
2358 return this->template to<To, L>().operator+(other.template to<To, L>());
2359 }
2360
2362 template<arithmetic U>
2363 requires same_signedness<T, U> and arithmetically_combinable<T, U, L>
2365 using To = common_type_t<T, U>;
2366 if constexpr (L * sizeof(To) <= 16)
2367 return binary<To, L, To, L>(M_CONSTEXPR_COND(L == 1, BINARY_OP(Sub,), BINARY_VOP(Sub,)), other);
2368 else
2369 return this->template to<To, L>().operator-(other.template to<To, L>());
2370 }
2371
2373 template<arithmetic U>
2374 requires arithmetically_combinable<T, U, L>
2376 using To = common_type_t<T, U>;
2377 return binary<To, L, To, L>(BINARY_OP(Mul,), other);
2378 }
2380 template<arithmetic U>
2382 auto operator*(PrimitiveExpr<U, L> other) -> PrimitiveExpr<common_type_t<T, U>, L> requires (L > 1) {
2383 using To = common_type_t<T, U>;
2384 auto op = [](){
2385 if constexpr (std::integral<To>) {
2386 if constexpr (sizeof(To) == 8)
2387 return BINVOP_(Mul,, I64x2);
2388 else if constexpr (sizeof(To) == 4)
2389 return BINVOP_(Mul,, I32x4);
2390 else if constexpr (sizeof(To) == 2)
2391 return BINVOP_(Mul,, I16x8);
2392 } else if (std::floating_point<To>) {
2393 return BINFVOP_(Mul);
2394 }
2395 M_unreachable("unsupported operation");
2396 }();
2397 if constexpr (L * sizeof(To) <= 16)
2398 return binary<To, L, To, L>(op, other);
2399 else
2400 return this->template to<To, L>().operator*(other.template to<To, L>());
2401 }
2403 template<arithmetic U>
2405 auto operator*(PrimitiveExpr<U, L> other) -> PrimitiveExpr<common_type_t<T, U>, L> requires (L > 8) {
2407 static_assert(L == 16);
2408 using To = std::conditional_t<std::is_signed_v<T>, int16_t, uint16_t>;
2409 auto op_low =
2410 M_CONSTEXPR_COND(std::is_signed_v<T>, BINVOP_(ExtMulLow, S, I16x8), BINVOP_(ExtMulLow, U, I16x8));
2411 auto op_high =
2412 M_CONSTEXPR_COND(std::is_signed_v<T>, BINVOP_(ExtMulHigh, S, I16x8), BINVOP_(ExtMulHigh, U, I16x8));
2413 auto this_cpy = this->clone();
2414 auto other_cpy = other.clone();
2415 auto referenced_bits_low = this_cpy.referenced_bits(); // moved
2416 auto referenced_bits_high = this->referenced_bits(); // moved
2417 referenced_bits_low.splice(referenced_bits_low.end(), other_cpy.referenced_bits());
2418 referenced_bits_high.splice(referenced_bits_high.end(), other.referenced_bits());
2420 /* expr= */ Module::Builder().makeBinary(op_low, this_cpy.expr(), other_cpy.expr()),
2421 /* referenced_bits= */ std::move(referenced_bits_low)
2422 );
2424 /* expr= */ Module::Builder().makeBinary(op_high, this->expr(), other.expr()),
2425 /* referenced_bits= */ std::move(referenced_bits_high)
2426 );
2427 /* Shuffle to select the lower byte of each lane since integer narrowing would be performed saturating, i.e.
2428 * instead of overflows the values are mapped to the extremes of their domain. */
2429 auto indices = std::to_array<uint8_t>({ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 });
2430 return PrimitiveExpr<common_type_t<T, U>, L>(ShuffleBytes(low, high, indices).move());
2431 }
2432
2434 template<arithmetic U>
2435 requires same_signedness<T, U> and arithmetically_combinable<T, U, L>
2437 using To = common_type_t<T, U>;
2438 return binary<To, L, To, L>(M_CONSTEXPR_COND(std::is_signed_v<To>, BINARY_OP(Div, S), BINARY_OP(Div, U)),
2439 other);
2440 }
2442 template<std::floating_point U>
2443 requires arithmetically_combinable<T, U, L>
2445 requires std::floating_point<T> and (L > 1) {
2446 using To = common_type_t<T, U>;
2447 if constexpr (L * sizeof(To) <= 16)
2448 return binary<To, L, To, L>(BINFVOP_(Div), other);
2449 else
2450 return this->template to<To, L>().operator/(other.template to<To, L>());
2451 }
2452
2454 template<integral U>
2455 requires same_signedness<T, U> and arithmetically_combinable<T, U, L>
2457 requires integral<T> and (L == 1) {
2458 using To = common_type_t<T, U>;
2459 return binary<To, L, To, L>(M_CONSTEXPR_COND(std::is_signed_v<To>, BINIOP_(Rem, S), BINIOP_(Rem, U)), other);
2460 }
2461
2463 template<std::floating_point U>
2464 requires arithmetically_combinable<T, U, L>
2466 requires std::floating_point<T> and (L == 1) {
2467 using To = common_type_t<T, U>;
2468 return binary<To, L, To, L>(BINFOP_(CopySign), other);
2469 }
2470
2472 template<std::floating_point U>
2473 requires arithmetically_combinable<T, U, L>
2474 auto min(PrimitiveExpr<U, L> other) -> PrimitiveExpr<common_type_t<T, U>, L> requires std::floating_point<T> {
2475 using To = common_type_t<T, U>;
2476 if constexpr (L * sizeof(To) <= 16)
2477 return binary<To, L, To, L>(M_CONSTEXPR_COND(L == 1, BINFOP_(Min), BINFVOP_(Min)), other); // XXX: or PMin?
2478 else
2479 return this->template to<To, L>().min(other.template to<To, L>());
2480 }
2482 template<integral U>
2483 requires arithmetically_combinable<T, U, L> and (sizeof(common_type_t<T, U>) != 8)
2484 auto min(PrimitiveExpr<U, L> other) -> PrimitiveExpr<common_type_t<T, U>, L> requires integral<T> and (L > 1) {
2485 using To = common_type_t<T, U>;
2486 auto op = [](){
2487 if constexpr (sizeof(To) == 4)
2488 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Min, S, I32x4), BINVOP_(Min, U, I32x4));
2489 else if constexpr (sizeof(To) == 2)
2490 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Min, S, I16x8), BINVOP_(Min, U, I16x8));
2491 else if constexpr (sizeof(To) == 1)
2492 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Min, S, I8x16), BINVOP_(Min, U, I8x16));
2493 M_unreachable("unsupported operation");
2494 }();
2495 if constexpr (L * sizeof(To) <= 16)
2496 return binary<To, L, To, L>(op, other);
2497 else
2498 return this->template to<To, L>().min(other.template to<To, L>());
2499 }
2500
2502 template<std::floating_point U>
2503 requires arithmetically_combinable<T, U, L>
2504 auto max(PrimitiveExpr<U, L> other) -> PrimitiveExpr<common_type_t<T, U>, L> requires std::floating_point<T> {
2505 using To = common_type_t<T, U>;
2506 if constexpr (L * sizeof(To) <= 16)
2507 return binary<To, L, To, L>(M_CONSTEXPR_COND(L == 1, BINFOP_(Max), BINFVOP_(Max)), other); // XXX: or PMax?
2508 else
2509 return this->template to<To, L>().max(other.template to<To, L>());
2510 }
2512 template<integral U>
2513 requires arithmetically_combinable<T, U, L> and (sizeof(common_type_t<T, U>) != 8)
2514 auto max(PrimitiveExpr<U, L> other) -> PrimitiveExpr<common_type_t<T, U>, L> requires integral<T> and (L > 1) {
2515 using To = common_type_t<T, U>;
2516 auto op = [](){
2517 if constexpr (sizeof(To) == 4)
2518 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Max, S, I32x4), BINVOP_(Max, U, I32x4));
2519 else if constexpr (sizeof(To) == 2)
2520 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Max, S, I16x8), BINVOP_(Max, U, I16x8));
2521 else if constexpr (sizeof(To) == 1)
2522 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Max, S, I8x16), BINVOP_(Max, U, I8x16));
2523 M_unreachable("unsupported operation");
2524 }();
2525 if constexpr (L * sizeof(To) <= 16)
2526 return binary<To, L, To, L>(op, other);
2527 else
2528 return this->template to<To, L>().max(other.template to<To, L>());
2529 }
2530
2532 template<unsigned_integral U>
2534 auto avg(PrimitiveExpr<U, L> other) -> PrimitiveExpr<common_type_t<T, U>, L>
2535 requires unsigned_integral<T> and (L > 1) {
2536 using To = common_type_t<T, U>;
2537 auto op = [](){
2538 if constexpr (sizeof(To) == 2)
2539 return BINVOP_(Avgr, U, I16x8);
2540 else if constexpr (sizeof(To) == 1)
2541 return BINVOP_(Avgr, U, I8x16);
2542 M_unreachable("unsupported operation");
2543 }();
2544 if constexpr (L * sizeof(To) <= 16)
2545 return binary<To, L, To, L>(op, other);
2546 else
2547 return this->template to<To, L>().avg(other.template to<To, L>());
2548 }
2549
2550 /*----- Bitwise operations ---------------------------------------------------------------------------------------*/
2551
2553 template<std::integral U>
2554 requires arithmetically_combinable<T, U, L>
2555 auto operator bitand(PrimitiveExpr<U, L> other) -> PrimitiveExpr<common_type_t<T, U>, L> requires std::integral<T> {
2556 using To = common_type_t<T, U>;
2557 if constexpr (L * sizeof(To) <= 16)
2558 return binary<To, L, To, L>(M_CONSTEXPR_COND(L == 1, BINIOP_(And,), BINVOP_(And,, 128)), other);
2559 else
2560 return this->template to<To, L>().operator bitand(other.template to<To, L>());
2561 }
2562
2564 template<std::integral U>
2565 requires arithmetically_combinable<T, U, L>
2566 auto operator bitor(PrimitiveExpr<U, L> other) -> PrimitiveExpr<common_type_t<T, U>, L> requires std::integral<T> {
2567 using To = common_type_t<T, U>;
2568 if constexpr (L * sizeof(To) <= 16)
2569 return binary<To, L, To, L>(M_CONSTEXPR_COND(L == 1, BINIOP_(Or,), BINVOP_(Or,, 128)), other);
2570 else
2571 return this->template to<To, L>().operator bitor(other.template to<To, L>());
2572 }
2573
2575 template<std::integral U>
2576 requires arithmetically_combinable<T, U, L>
2577 auto operator xor(PrimitiveExpr<U, L> other) -> PrimitiveExpr<common_type_t<T, U>, L> requires std::integral<T> {
2578 using To = common_type_t<T, U>;
2579 if constexpr (L * sizeof(To) <= 16)
2580 return binary<To, L, To, L>(M_CONSTEXPR_COND(L == 1, BINIOP_(Xor,), BINVOP_(Xor,, 128)), other);
2581 else
2582 return this->template to<To, L>().operator xor(other.template to<To, L>());
2583 }
2584
2586 template<integral U>
2587 requires arithmetically_combinable<T, U, L>
2588 auto operator<<(PrimitiveExpr<U, L> other) -> PrimitiveExpr<common_type_t<T, U>, L>
2589 requires integral<T> and (L == 1) {
2590 using To = common_type_t<T, U>;
2591 if constexpr (sizeof(To) >= 4)
2592 return binary<To, L, To, L>(BINIOP_(Shl,), other);
2593 else if constexpr (sizeof(To) == 2)
2594 return binary<To, L, To, L>(BINOP_(Shl,, Int32), other) bitand PrimitiveExpr<To, 1>(0xffff);
2595 else if constexpr (sizeof(To) == 1)
2596 return binary<To, L, To, L>(BINOP_(Shl,, Int32), other) bitand PrimitiveExpr<To, 1>(0xff);
2597 else
2598 M_unreachable("unsupported operation");
2599 }
2601 template<integral U>
2602 requires requires (PrimitiveExpr<U, 1> e) {
2603 PrimitiveExpr<std::conditional_t<std::is_signed_v<T>, int32_t, uint32_t>, 1>(e);
2604 }
2606 requires integral<T> and (L > 1) {
2607 using Op = std::conditional_t<std::is_signed_v<T>, int32_t, uint32_t>;
2608 auto op = [](){
2609 if constexpr (sizeof(T) == 8)
2610 return ::wasm::SIMDShiftOp::ShlVecI64x2;
2611 else if constexpr (sizeof(T) == 4)
2612 return ::wasm::SIMDShiftOp::ShlVecI32x4;
2613 else if constexpr (sizeof(T) == 2)
2614 return ::wasm::SIMDShiftOp::ShlVecI16x8;
2615 else if constexpr (sizeof(T) == 1)
2616 return ::wasm::SIMDShiftOp::ShlVecI8x16;
2617 else
2618 M_unreachable("unsupported operation");
2619 }();
2620 auto referenced_bits = this->referenced_bits(); // moved
2621 referenced_bits.splice(referenced_bits.end(), other.referenced_bits());
2622 return PrimitiveExpr(
2623 /* expr= */ Module::Builder().makeSIMDShift(op, this->expr(), PrimitiveExpr<Op, 1>(other).expr()),
2624 /* referenced_bits= */ std::move(referenced_bits)
2625 );
2626 }
2627
2629 template<integral U>
2630 requires arithmetically_combinable<T, U, L>
2632 requires integral<T> and (L == 1) {
2633 using To = common_type_t<T, U>;
2634 return binary<To, L, To, L>(M_CONSTEXPR_COND(std::is_signed_v<T>, BINIOP_(Shr, S), BINIOP_(Shr, U)), other);
2635 }
2637 template<integral U>
2638 requires requires (PrimitiveExpr<U, 1> e) {
2639 PrimitiveExpr<std::conditional_t<std::is_signed_v<T>, int32_t, uint32_t>, 1>(e);
2640 }
2641 PrimitiveExpr operator>>(PrimitiveExpr<U, 1> other)
2642 requires integral<T> and (L > 1) {
2643 using Op = std::conditional_t<std::is_signed_v<T>, int32_t, uint32_t>;
2644 auto op = [](){
2645 if constexpr (sizeof(T) == 8)
2646 return M_CONSTEXPR_COND(std::is_signed_v<T>, ::wasm::SIMDShiftOp::ShrSVecI64x2,
2647 ::wasm::SIMDShiftOp::ShrUVecI64x2);
2648 else if constexpr (sizeof(T) == 4)
2649 return M_CONSTEXPR_COND(std::is_signed_v<T>, ::wasm::SIMDShiftOp::ShrSVecI32x4,
2650 ::wasm::SIMDShiftOp::ShrUVecI32x4);
2651 else if constexpr (sizeof(T) == 2)
2652 return M_CONSTEXPR_COND(std::is_signed_v<T>, ::wasm::SIMDShiftOp::ShrSVecI16x8,
2653 ::wasm::SIMDShiftOp::ShrUVecI16x8);
2654 else if constexpr (sizeof(T) == 1)
2655 return M_CONSTEXPR_COND(std::is_signed_v<T>, ::wasm::SIMDShiftOp::ShrSVecI8x16,
2656 ::wasm::SIMDShiftOp::ShrUVecI8x16);
2657 else
2658 M_unreachable("unsupported operation");
2659 }();
2660 auto referenced_bits = this->referenced_bits(); // moved
2661 referenced_bits.splice(referenced_bits.end(), other.referenced_bits());
2662 return PrimitiveExpr(
2663 /* expr= */ Module::Builder().makeSIMDShift(op, this->expr(), PrimitiveExpr<Op, 1>(other).expr()),
2664 /* referenced_bits= */ std::move(referenced_bits)
2665 );
2666 }
2667
2669 template<integral U>
2671 auto rotl(PrimitiveExpr<U, L> other) -> PrimitiveExpr<common_type_t<T, U>, L> requires integral<T> and (L == 1) {
2672 using To = common_type_t<T, U>;
2673 return binary<To, L, To, L>(BINIOP_(RotL,), other);
2674 }
2675
2677 template<integral U>
2679 auto rotr(PrimitiveExpr<U, L> other) -> PrimitiveExpr<common_type_t<T, U>, L> requires integral<T> and (L == 1) {
2680 using To = common_type_t<T, U>;
2681 return binary<To, L, To, L>(BINIOP_(RotR,), other);
2682 }
2683
2684 /*----- Comparison operations ------------------------------------------------------------------------------------*/
2685
2687 template<dsl_primitive U>
2688 requires same_signedness<T, U> and arithmetically_combinable<T, U, L>
2690 using To = common_type_t<T, U>;
2691 constexpr std::size_t ToL = L == 1 ? L : L * sizeof(T);
2692 if constexpr (L * sizeof(To) <= 16) {
2693 auto cmp = binary<bool, ToL, To, L>(M_CONSTEXPR_COND(L == 1, BINARY_OP(Eq,), BINARY_VOP(Eq,)), other);
2694 std::array<uint8_t, L> indices;
2695 for (std::size_t idx = 0; idx < L; ++idx)
2696 indices[idx] = idx * sizeof(To);
2697 return M_CONSTEXPR_COND(L == 1 or sizeof(To) == 1, cmp, cmp.swizzle_bytes(indices));
2698 } else {
2699 return this->template to<To, L>().operator==(other.template to<To, L>());
2700 }
2701 }
2702
2704 template<dsl_primitive U>
2705 requires same_signedness<T, U> and arithmetically_combinable<T, U, L>
2707 using To = common_type_t<T, U>;
2708 constexpr std::size_t ToL = L == 1 ? L : L * sizeof(T);
2709 if constexpr (L * sizeof(To) <= 16) {
2710 auto cmp = binary<bool, ToL, To, L>(M_CONSTEXPR_COND(L == 1, BINARY_OP(Ne,), BINARY_VOP(Ne,)), other);
2711 std::array<uint8_t, L> indices;
2712 for (std::size_t idx = 0; idx < L; ++idx)
2713 indices[idx] = idx * sizeof(To);
2714 return M_CONSTEXPR_COND(L == 1 or sizeof(To) == 1, cmp, cmp.swizzle_bytes(indices));
2715 } else {
2716 return this->template to<To, L>().operator!=(other.template to<To, L>());
2717 }
2718 }
2719
2721 template<arithmetic U>
2724 using To = common_type_t<T, U>;
2725 constexpr std::size_t ToL = L == 1 ? L : L * sizeof(T);
2726 auto op = [](){
2727 if constexpr (L == 1) {
2728 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINARY_OP(Lt, S), BINARY_OP(Lt, U));
2729 } else {
2730 if constexpr (std::integral<To>) {
2731 if constexpr (sizeof(To) == 8)
2732 return BINVOP_(Lt, S, I64x2); // unsigned comparison missing, use signed but flip MSB of operands
2733 else if constexpr (sizeof(To) == 4)
2734 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Lt, S, I32x4), BINVOP_(Lt, U, I32x4));
2735 else if constexpr (sizeof(To) == 2)
2736 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Lt, S, I16x8), BINVOP_(Lt, U, I16x8));
2737 else if constexpr (sizeof(To) == 1)
2738 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Lt, S, I8x16), BINVOP_(Lt, U, I8x16));
2739 } else if (std::floating_point<To>) {
2740 return BINFVOP_(Lt);
2741 }
2742 }
2743 M_unreachable("unsupported operation");
2744 }();
2745 if constexpr (L * sizeof(To) <= 16) {
2746 /* Wasm does not support comparison of U64 SIMD vectors. Thus, flip the MSB in each lane (which
2747 * basically shifts the unsigned domain into the signed one) and use the signed comparison afterwards. */
2748 constexpr bool is_u64_vec = unsigned_integral<To> and sizeof(To) == 8 and L > 1;
2749 auto _this =
2750 M_CONSTEXPR_COND(is_u64_vec, (*this xor PrimitiveExpr<T, L>(T(1) << (CHAR_BIT * sizeof(T) - 1))), *this);
2751 auto _other =
2752 M_CONSTEXPR_COND(is_u64_vec, (other xor PrimitiveExpr<U, L>(U(1) << (CHAR_BIT * sizeof(U) - 1))), other);
2753 auto cmp = _this.template binary<bool, ToL, To, L>(op, _other);
2754 std::array<uint8_t, L> indices;
2755 for (std::size_t idx = 0; idx < L; ++idx)
2756 indices[idx] = idx * sizeof(To);
2757 return M_CONSTEXPR_COND(L == 1 or sizeof(To) == 1, cmp, cmp.swizzle_bytes(indices));
2758 } else {
2759 return this->template to<To, L>().operator<(other.template to<To, L>());
2760 }
2761 }
2762
2764 template<arithmetic U>
2767 using To = common_type_t<T, U>;
2768 constexpr std::size_t ToL = L == 1 ? L : L * sizeof(T);
2769 auto op = [](){
2770 if constexpr (L == 1) {
2771 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINARY_OP(Le, S), BINARY_OP(Le, U));
2772 } else {
2773 if constexpr (std::integral<To>) {
2774 if constexpr (sizeof(To) == 8)
2775 return BINVOP_(Le, S, I64x2); // unsigned comparison missing, use signed but flip MSB of operands
2776 else if constexpr (sizeof(To) == 4)
2777 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Le, S, I32x4), BINVOP_(Le, U, I32x4));
2778 else if constexpr (sizeof(To) == 2)
2779 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Le, S, I16x8), BINVOP_(Le, U, I16x8));
2780 else if constexpr (sizeof(To) == 1)
2781 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Le, S, I8x16), BINVOP_(Le, U, I8x16));
2782 } else if (std::floating_point<To>) {
2783 return BINFVOP_(Le);
2784 }
2785 }
2786 M_unreachable("unsupported operation");
2787 }();
2788 if constexpr (L * sizeof(To) <= 16) {
2789 /* Wasm does not support comparison of U64 SIMD vectors. Thus, flip the MSB in each lane (which
2790 * basically shifts the unsigned domain into the signed one) and use the signed comparison afterwards. */
2791 constexpr bool is_u64_vec = unsigned_integral<To> and sizeof(To) == 8 and L > 1;
2792 auto _this =
2793 M_CONSTEXPR_COND(is_u64_vec, (*this xor PrimitiveExpr<T, L>(T(1) << (CHAR_BIT * sizeof(T) - 1))), *this);
2794 auto _other =
2795 M_CONSTEXPR_COND(is_u64_vec, (other xor PrimitiveExpr<U, L>(U(1) << (CHAR_BIT * sizeof(U) - 1))), other);
2796 auto cmp = _this.template binary<bool, ToL, To, L>(op, _other);
2797 std::array<uint8_t, L> indices;
2798 for (std::size_t idx = 0; idx < L; ++idx)
2799 indices[idx] = idx * sizeof(To);
2800 return M_CONSTEXPR_COND(L == 1 or sizeof(To) == 1, cmp, cmp.swizzle_bytes(indices));
2801 } else {
2802 return this->template to<To, L>().operator<=(other.template to<To, L>());
2803 }
2804 }
2805
2807 template<arithmetic U>
2808 requires same_signedness<T, U> and arithmetically_combinable<T, U, L>
2810 using To = common_type_t<T, U>;
2811 constexpr std::size_t ToL = L == 1 ? L : L * sizeof(T);
2812 auto op = [](){
2813 if constexpr (L == 1) {
2814 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINARY_OP(Gt, S), BINARY_OP(Gt, U));
2815 } else {
2816 if constexpr (std::integral<To>) {
2817 if constexpr (sizeof(To) == 8)
2818 return BINVOP_(Gt, S, I64x2); // unsigned comparison missing, use signed but flip MSB of operands
2819 else if constexpr (sizeof(To) == 4)
2820 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Gt, S, I32x4), BINVOP_(Gt, U, I32x4));
2821 else if constexpr (sizeof(To) == 2)
2822 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Gt, S, I16x8), BINVOP_(Gt, U, I16x8));
2823 else if constexpr (sizeof(To) == 1)
2824 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Gt, S, I8x16), BINVOP_(Gt, U, I8x16));
2825 } else if (std::floating_point<To>) {
2826 return BINFVOP_(Gt);
2827 }
2828 }
2829 M_unreachable("unsupported operation");
2830 }();
2831 if constexpr (L * sizeof(To) <= 16) {
2832 /* Wasm does not support comparison of U64 SIMD vectors. Thus, flip the MSB in each lane (which
2833 * basically shifts the unsigned domain into the signed one) and use the signed comparison afterwards. */
2834 constexpr bool is_u64_vec = unsigned_integral<To> and sizeof(To) == 8 and L > 1;
2835 auto _this =
2836 M_CONSTEXPR_COND(is_u64_vec, (*this xor PrimitiveExpr<T, L>(T(1) << (CHAR_BIT * sizeof(T) - 1))), *this);
2837 auto _other =
2838 M_CONSTEXPR_COND(is_u64_vec, (other xor PrimitiveExpr<U, L>(U(1) << (CHAR_BIT * sizeof(U) - 1))), other);
2839 auto cmp = _this.template binary<bool, ToL, To, L>(op, _other);
2840 std::array<uint8_t, L> indices;
2841 for (std::size_t idx = 0; idx < L; ++idx)
2842 indices[idx] = idx * sizeof(To);
2843 return M_CONSTEXPR_COND(L == 1 or sizeof(To) == 1, cmp, cmp.swizzle_bytes(indices));
2844 } else {
2845 return this->template to<To, L>().operator>(other.template to<To, L>());
2846 }
2847 }
2848
2850 template<arithmetic U>
2851 requires same_signedness<T, U> and arithmetically_combinable<T, U, L>
2853 using To = common_type_t<T, U>;
2854 constexpr std::size_t ToL = L == 1 ? L : L * sizeof(T);
2855 auto op = [](){
2856 if constexpr (L == 1) {
2857 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINARY_OP(Ge, S), BINARY_OP(Ge, U));
2858 } else {
2859 if constexpr (std::integral<To>) {
2860 if constexpr (sizeof(To) == 8)
2861 return BINVOP_(Ge, S, I64x2); // unsigned comparison missing, use signed but flip MSB of operands
2862 else if constexpr (sizeof(To) == 4)
2863 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Ge, S, I32x4), BINVOP_(Ge, U, I32x4));
2864 else if constexpr (sizeof(To) == 2)
2865 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Ge, S, I16x8), BINVOP_(Ge, U, I16x8));
2866 else if constexpr (sizeof(To) == 1)
2867 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Ge, S, I8x16), BINVOP_(Ge, U, I8x16));
2868 } else if (std::floating_point<To>) {
2869 return BINFVOP_(Ge);
2870 }
2871 }
2872 M_unreachable("unsupported operation");
2873 }();
2874 if constexpr (L * sizeof(To) <= 16) {
2875 /* Wasm does not support comparison of U64 SIMD vectors. Thus, flip the MSB in each lane (which
2876 * basically shifts the unsigned domain into the signed one) and use the signed comparison afterwards. */
2877 constexpr bool is_u64_vec = unsigned_integral<To> and sizeof(To) == 8 and L > 1;
2878 auto _this =
2879 M_CONSTEXPR_COND(is_u64_vec, (*this xor PrimitiveExpr<T, L>(T(1) << (CHAR_BIT * sizeof(T) - 1))), *this);
2880 auto _other =
2881 M_CONSTEXPR_COND(is_u64_vec, (other xor PrimitiveExpr<U, L>(U(1) << (CHAR_BIT * sizeof(U) - 1))), other);
2882 auto cmp = _this.template binary<bool, ToL, To, L>(op, _other);
2883 std::array<uint8_t, L> indices;
2884 for (std::size_t idx = 0; idx < L; ++idx)
2885 indices[idx] = idx * sizeof(To);
2886 return M_CONSTEXPR_COND(L == 1 or sizeof(To) == 1, cmp, cmp.swizzle_bytes(indices));
2887 } else {
2888 return this->template to<To, L>().operator>=(other.template to<To, L>());
2889 }
2890 }
2891
2892 /*----- Logical operations ---------------------------------------------------------------------------------------*/
2893
2895 template<boolean U>
2897 return binary<T, L, T, L>(M_CONSTEXPR_COND(L == 1, BINOP_(And,,Int32), BINVOP_(And,, 128)), other);
2898 }
2899
2901 template<boolean U>
2903 return binary<T, L, T, L>(BINVOP_(AndNot,, 128), other);
2904 }
2905
2907 template<boolean U>
2909 return binary<T, L, T, L>(M_CONSTEXPR_COND(L == 1, BINOP_(Or,,Int32), BINVOP_(Or,, 128)), other);
2910 }
2911
2912#undef BINARY_VOP
2913#undef BINFVOP_
2914#undef BINIVOP_
2915#undef BINVOP_
2916#undef BINARY_OP
2917#undef BINFOP_
2918#undef BINIOP_
2919#undef BINOP_
2920
2921
2922 /*------------------------------------------------------------------------------------------------------------------
2923 * Modifications
2924 *----------------------------------------------------------------------------------------------------------------*/
2925
2926 private:
2930 template<std::size_t M>
2931 requires (M * sizeof(T) < 16)
2933 auto op = [](){
2934 if constexpr (std::integral<T>) {
2935 if constexpr (sizeof(T) == 8)
2936 return ::wasm::SIMDExtractOp::ExtractLaneVecI64x2;
2937 else if constexpr (sizeof(T) == 4)
2938 return ::wasm::SIMDExtractOp::ExtractLaneVecI32x4;
2939 else if constexpr (sizeof(T) == 2)
2940 return M_CONSTEXPR_COND(std::is_signed_v<T>, ::wasm::SIMDExtractOp::ExtractLaneSVecI16x8,
2941 ::wasm::SIMDExtractOp::ExtractLaneUVecI16x8);
2942 else if constexpr (sizeof(T) == 1)
2943 return M_CONSTEXPR_COND(std::is_signed_v<T>, ::wasm::SIMDExtractOp::ExtractLaneSVecI8x16,
2944 ::wasm::SIMDExtractOp::ExtractLaneUVecI8x16);
2945 } else if (std::floating_point<T>) {
2946 if constexpr (sizeof(T) == 8)
2947 return ::wasm::SIMDExtractOp::ExtractLaneVecF64x2;
2948 else if constexpr (sizeof(T) == 4)
2949 return ::wasm::SIMDExtractOp::ExtractLaneVecF32x4;
2950 }
2951 M_unreachable("unsupported operation");
2952 }();
2953 auto extracted = PrimitiveExpr<T, 1>(
2954 /* expr= */ Module::Builder().makeSIMDExtract(op, expr(), M),
2955 /* referenced_bits= */ referenced_bits() // moved
2956 );
2957 return M_CONSTEXPR_COND(boolean<T>, extracted != false, extracted);
2958 }
2959 public:
2961 template<std::size_t M>
2962 requires (M < L)
2963 PrimitiveExpr<T, 1> extract() requires (L > 1) { return extract_unsafe<M>(); }
2964
2966 template<std::size_t M, primitive_convertible U>
2967 requires (M < L) and
2968 requires (primitive_expr_t<U> u) { PrimitiveExpr<T, 1>(u); }
2969 PrimitiveExpr replace(U &&_value) requires (L > 1) {
2970 auto op = [](){
2971 if constexpr (std::integral<T>) {
2972 if constexpr (sizeof(T) == 8)
2973 return ::wasm::SIMDReplaceOp::ReplaceLaneVecI64x2;
2974 else if constexpr (sizeof(T) == 4)
2975 return ::wasm::SIMDReplaceOp::ReplaceLaneVecI32x4;
2976 else if constexpr (sizeof(T) == 2)
2977 return ::wasm::SIMDReplaceOp::ReplaceLaneVecI16x8;
2978 else if constexpr (sizeof(T) == 1)
2979 return ::wasm::SIMDReplaceOp::ReplaceLaneVecI8x16;
2980 } else if (std::floating_point<T>) {
2981 if constexpr (sizeof(T) == 8)
2982 return ::wasm::SIMDReplaceOp::ReplaceLaneVecF64x2;
2983 else if constexpr (sizeof(T) == 4)
2984 return ::wasm::SIMDReplaceOp::ReplaceLaneVecF32x4;
2985 }
2986 M_unreachable("unsupported operation");
2987 }();
2988 PrimitiveExpr<T, 1> value(primitive_expr_t<U>(std::forward<U>(_value)));
2989 auto replacement =
2990 M_CONSTEXPR_COND(boolean<T>, (PrimitiveExpr<T, 1>((-value.template to<uint8_t, 1>()).move())), value);
2991 auto referenced_bits = this->referenced_bits(); // moved
2992 referenced_bits.splice(referenced_bits.end(), replacement.referenced_bits());
2993 return PrimitiveExpr(
2994 /* expr= */ Module::Builder().makeSIMDReplace(op, this->expr(), M, replacement.expr()),
2995 /* referenced_bits= */ std::move(referenced_bits)
2996 );
2997 }
2998
3002 auto referenced_bits = this->referenced_bits(); // moved
3003 referenced_bits.splice(referenced_bits.end(), indices.referenced_bits());
3004 return PrimitiveExpr(
3005 /* expr= */ Module::Builder().makeBinary(::wasm::BinaryOp::SwizzleVecI8x16,
3006 this->expr(),
3007 indices.expr()),
3008 /* referenced_bits= */ std::move(referenced_bits)
3009 );
3010 }
3013 template<std::size_t M>
3014 requires (M > 0) and (M <= 16) and (M % sizeof(T) == 0)
3015 PrimitiveExpr<T, M / sizeof(T)> swizzle_bytes(const std::array<uint8_t, M> &_indices) requires (L > 1) {
3016 std::array<uint8_t, 16> indices;
3017 for (std::size_t idx = 0; idx < M; ++idx)
3018 indices[idx] = _indices[idx] < L * sizeof(T) ? _indices[idx] : 16;
3019 std::fill(indices.begin() + M, indices.end(), 16);
3020 PrimitiveExpr<uint8_t, 16> indices_expr(
3021 /* expr= */ Module::Builder().makeConst(::wasm::Literal(indices.data()))
3022 );
3023 auto vec = PrimitiveExpr<T, M / sizeof(T)>(swizzle_bytes(indices_expr).move());
3024 return M_CONSTEXPR_COND(decltype(vec)::num_simd_lanes == 1,
3025 vec.template extract_unsafe<0>(), // extract a single value from vector to scalar
3026 vec);
3027 }
3028
3031 template<std::size_t M>
3032 requires (M > 0) and (is_pow_2(M)) and (M * sizeof(T) <= 16)
3033 PrimitiveExpr<T, M> swizzle_lanes(const std::array<uint8_t, M> &_indices) requires (L > 1) {
3034 std::array<uint8_t, 16> indices;
3035 for (std::size_t idx = 0; idx < M; ++idx) {
3036 for (std::size_t byte = 0; byte < sizeof(T); ++byte)
3037 indices[idx * sizeof(T) + byte] = _indices[idx] < L ? _indices[idx] * sizeof(T) + byte : 16;
3038 }
3039 std::fill(indices.begin() + M * sizeof(T), indices.end(), 16);
3040 PrimitiveExpr<uint8_t, 16> indices_expr(
3041 /* expr= */ Module::Builder().makeConst(::wasm::Literal(indices.data()))
3042 );
3043 auto vec = PrimitiveExpr<T, M>(swizzle_bytes(indices_expr).move());
3044 return M_CONSTEXPR_COND(decltype(vec)::num_simd_lanes == 1,
3045 vec.template extract_unsafe<0>(), // extract a single value from vector to scalar
3046 vec);
3047 }
3048
3049
3050 /*------------------------------------------------------------------------------------------------------------------
3051 * Printing
3052 *----------------------------------------------------------------------------------------------------------------*/
3053
3054 friend std::ostream & operator<<(std::ostream &out, const PrimitiveExpr &P) {
3055 out << "PrimitiveExpr<" << typeid(type).name() << "," << num_simd_lanes << ">: ";
3056 if (P.expr_) out << *P.expr_;
3057 else out << "None";
3058 return out;
3059 }
3060
3061 void dump(std::ostream &out) const { out << *this << std::endl; }
3062 void dump() const { dump(std::cerr); }
3063};
3064
3068template<dsl_primitive T, std::size_t L>
3069requires (L > 1) and (is_pow_2(L)) and (L * sizeof(T) > 16) and ((L * sizeof(T)) % 16 == 0)
3071{
3073 using type = T;
3075 static constexpr std::size_t num_simd_lanes = L;
3077 using vector_type = PrimitiveExpr<T, 16 / sizeof(T)>;
3079 static constexpr std::size_t num_vectors = (L * sizeof(T)) / 16;
3080 static_assert(num_vectors >= 2);
3081
3082 /*----- Friends --------------------------------------------------------------------------------------------------*/
3083 template<typename, std::size_t> friend struct PrimitiveExpr; // to convert U to T and U* to uint32_t
3084 template<typename, std::size_t>
3085 friend struct Expr; // to construct an empty `PrimitiveExpr<bool>` for the NULL information
3086 template<typename, VariableKind, bool, std::size_t>
3087 friend class detail::variable_storage; // to construct from `::wasm::Expression` and access private `expr()`
3088 friend struct Module; // to access internal `::wasm::Expression`, e.g. in `emit_return()`
3089 friend struct Block; // to access internal `::wasm::Expression`, e.g. in `go_to()`
3090 template<typename> friend struct FunctionProxy; // to access internal `::wasm::Expr` to construct function calls
3091 template<typename> friend struct invoke_interpreter; // to access private `expr()`
3092
3093 private:
3095 std::array<vector_type, num_vectors> vectors_;
3096
3097 private:
3099 explicit PrimitiveExpr() {
3100 /* Do not use `vectors_.fill()` since it internally delegates to `std::fill_n()` which tries to copy-assign the
3101 * given value to each slot, however, the assignment operator of `vector_type` is private and thus not
3102 * accessible. Therefore, implement this logic by ourselves as we are befriended with `vector_type`. */
3103 for (auto it = vectors_.begin(); it != vectors_.end(); ++it)
3104 *it = vector_type();
3105 }
3106
3108 explicit PrimitiveExpr(std::array<vector_type, num_vectors> vectors) : vectors_(std::move(vectors)) { }
3110 explicit PrimitiveExpr(std::initializer_list<vector_type> vectors) : vectors_(std::move(vectors)) { }
3111
3112 public:
3114 template<dsl_primitive... Us>
3115 requires (sizeof...(Us) > 0) and
3116 requires (Us... us) { { make_literal<T, L>(us...) } -> std::same_as<std::array<::wasm::Literal, num_vectors>>; }
3117 explicit PrimitiveExpr(Us... value)
3118 : PrimitiveExpr([&](){
3119 std::array<vector_type, num_vectors> vectors;
3120 auto it = vectors.begin();
3121 for (auto literal : make_literal<T, L>(value...))
3122 *(it++) = vector_type(Module::Builder().makeConst(literal));
3123 M_insist(it == vectors.end());
3124 return std::move(vectors);
3125 }())
3126 { }
3127
3129 template<decayable... Us>
3130 requires (sizeof...(Us) > 0) and (dsl_primitive<std::decay_t<Us>> and ...) and
3131 requires (Us... us) { PrimitiveExpr(std::decay_t<Us>(us)...); }
3132 explicit PrimitiveExpr(Us... value)
3133 : PrimitiveExpr(std::decay_t<Us>(value)...)
3134 { }
3135
3136 PrimitiveExpr(const PrimitiveExpr&) = delete;
3138 PrimitiveExpr(PrimitiveExpr &other) : PrimitiveExpr(std::move(other.vectors_)) { /* move, not copy */ }
3140 PrimitiveExpr(PrimitiveExpr &&other) : PrimitiveExpr(std::move(other.vectors_)) { }
3141
3143
3144 ~PrimitiveExpr() = default;
3145
3146 private:
3148 std::array<vector_type, num_vectors> vectors() { return std::move(vectors_); }
3150 template<dsl_primitive U, std::size_t M>
3151 requires ((M * sizeof(U)) / 16 == num_vectors)
3152 auto move() {
3153 using ToVecT = PrimitiveExpr<U, 16 / sizeof(U)>;
3154 std::array<ToVecT, num_vectors> vectors;
3155 for (std::size_t idx = 0; idx < num_vectors; ++idx)
3156 vectors[idx] = ToVecT(vectors_[idx].move());
3157 return std::move(vectors);
3158 }
3159
3160 public:
3163 explicit operator bool() const {
3164 return std::all_of(vectors_.cbegin(), vectors_.cend(), [](const auto &expr){ return bool(expr); });
3165 }
3166
3168 PrimitiveExpr clone() const {
3169 M_insist(bool(*this), "cannot clone an already moved or discarded `PrimitiveExpr`");
3170 std::array<vector_type, num_vectors> vectors_cpy;
3171 for (std::size_t idx = 0; idx < num_vectors; ++idx)
3172 vectors_cpy[idx] = vectors_[idx].clone();
3173 return PrimitiveExpr(
3174 /* vectors= */ std::move(vectors_cpy)
3175 );
3176 }
3177
3181 void discard() {
3182 M_insist(bool(*this), "cannot discard an already moved or discarded `PrimitiveExpr`");
3183 std::for_each(vectors_.begin(), vectors_.end(), [](auto &expr){ expr.discard(); });
3184 }
3185
3186
3187 /*------------------------------------------------------------------------------------------------------------------
3188 * Conversion operations
3189 *----------------------------------------------------------------------------------------------------------------*/
3190
3191 private:
3192 template<dsl_primitive U, std::size_t M>
3193 requires ((M * sizeof(U)) % 16 == 0)
3195 using From = T;
3196 using To = U;
3197 constexpr std::size_t FromL = L;
3198 constexpr std::size_t ToL = M;
3199 using FromVecT = PrimitiveExpr<T, 16 / sizeof(T)>;
3200 using ToVecT = PrimitiveExpr<U, 16 / sizeof(U)>;
3201 constexpr std::size_t FromVecL = (L * sizeof(T)) / 16;
3202 constexpr std::size_t ToVecL = (M * sizeof(U)) / 16;
3203
3204 if constexpr (std::same_as<From, To> and FromL == ToL)
3205 return *this;
3206 if constexpr (integral<From> and integral<To> and std::is_signed_v<From> == std::is_signed_v<To> and
3207 sizeof(From) == sizeof(To) and FromL == ToL)
3208 return PrimitiveExpr<To, ToL>(move<To, ToL>());
3209
3210 if constexpr (boolean<From>) { // from boolean
3211 if constexpr (integral<To>) { // to integer
3212 if constexpr (FromL == ToL) { // vectorial
3213 if constexpr (sizeof(To) == 1) // bool -> i8/u8
3214 return -PrimitiveExpr<To, ToL>(move<To, ToL>()); // negate to convert 0xff to 1
3215 if constexpr (std::is_signed_v<To>) {
3216 if constexpr (sizeof(To) == 2) // bool -> i16
3217 return to<int8_t, ToL>().template to<To, ToL>();
3218 if constexpr (sizeof(To) == 4) // bool -> i32
3219 return to<int8_t, ToL>().template to<To, ToL>();
3220 if constexpr (sizeof(To) == 8) // bool -> i64
3221 return to<int8_t, ToL>().template to<To, ToL>();
3222 } else {
3223 if constexpr (sizeof(To) == 2) // bool -> u16
3224 return to<uint8_t, ToL>().template to<To, ToL>();
3225 if constexpr (sizeof(To) == 4) // bool -> u32
3226 return to<uint8_t, ToL>().template to<To, ToL>();
3227 if constexpr (sizeof(To) == 8) // bool -> u64
3228 return to<uint8_t, ToL>().template to<To, ToL>();
3229 }
3230 }
3231 }
3232 if constexpr (std::floating_point<To>) { // to floating point
3233 if constexpr (FromL == ToL) { // vectorial
3234 if constexpr (sizeof(To) == 4) // bool -> f32
3235 return to<uint32_t, ToL>().template convert<To, ToL>();
3236 if constexpr (sizeof(To) == 8) // bool -> f64
3237 return to<uint32_t, ToL>().template convert<To, ToL>();
3238 }
3239 }
3240 }
3241
3242 if constexpr (boolean<To>) { // to boolean
3243 if constexpr (integral<From>) { // from integer
3244 if constexpr (FromL == ToL) { // vectorial
3245 if constexpr (sizeof(From) == 1) // i8/u8 -> bool
3246 return *this != PrimitiveExpr(static_cast<From>(0));
3247 if constexpr (std::is_signed_v<From>) {
3248 if constexpr (sizeof(From) == 2) // i16 -> bool
3249 return to<int8_t, ToL>().template to<To, ToL>();
3250 if constexpr (sizeof(From) == 4 and FromVecL >= 4) // i32 -> bool
3251 return to<int8_t, ToL>().template to<To, ToL>();
3252 if constexpr (sizeof(From) == 8 and FromVecL >= 8) // i64 -> bool
3253 return to<int8_t, ToL>().template to<To, ToL>();
3254 } else {
3255 if constexpr (sizeof(From) == 2) // u16 -> bool
3256 return to<uint8_t, ToL>().template to<To, ToL>();
3257 if constexpr (sizeof(From) == 4 and FromVecL >= 4) // u32 -> bool
3258 return to<uint8_t, ToL>().template to<To, ToL>();
3259 if constexpr (sizeof(From) == 8 and FromVecL >= 8) // u64 -> bool
3260 return to<uint8_t, ToL>().template to<To, ToL>();
3261 }
3262 }
3263 }
3264 if constexpr (std::floating_point<From>) { // from floating point
3265 if constexpr (FromL == ToL) { // vectorial
3266 if constexpr (sizeof(From) == 4 and FromVecL >= 4) // f32 -> bool
3267 return *this != PrimitiveExpr(static_cast<From>(0));
3268 if constexpr (sizeof(From) == 8 and FromVecL >= 8) // f64 -> bool
3269 return *this != PrimitiveExpr(static_cast<From>(0));
3270 }
3271 }
3272 }
3273
3274 if constexpr (integral<From>) { // from integer
3275 if constexpr (integral<To>) { // to integer
3276 if constexpr (FromL == ToL) { // vectorial
3277 if constexpr (std::is_signed_v<From>) { // signed
3278 if constexpr (sizeof(From) == 1 and sizeof(To) == 2) { // i8 -> i16
3279 std::array<ToVecT, ToVecL> vectors;
3280 for (std::size_t idx = 0; idx < FromVecL; ++idx) {
3281 vectors[2 * idx] = vectors_[idx].clone().template unary<To, ToVecT::num_simd_lanes>(
3282 ::wasm::ExtendLowSVecI8x16ToVecI16x8
3283 );
3284 vectors[2 * idx + 1] = vectors_[idx].template unary<To, ToVecT::num_simd_lanes>(
3285 ::wasm::ExtendHighSVecI8x16ToVecI16x8
3286 );
3287 }
3288 return PrimitiveExpr<To, ToL>(std::move(vectors));
3289 }
3290 if constexpr (sizeof(From) == 2 and sizeof(To) == 1) { // i16 -> i8
3291 std::array<ToVecT, ToVecL> vectors;
3292 for (std::size_t idx = 0; idx < ToVecL; ++idx)
3293 vectors[idx] = vectors_[2 * idx].template binary<To, ToVecT::num_simd_lanes>(
3294 ::wasm::NarrowSVecI16x8ToVecI8x16, vectors_[2 * idx + 1]
3295 );
3296 return PrimitiveExpr<To, ToL>(std::move(vectors));
3297 }
3298 if constexpr (sizeof(From) == 1 and sizeof(To) == 4) // i8 -> i32
3299 return to<int16_t, ToL>().template to<To, ToL>();
3300 if constexpr (sizeof(From) == 4 and sizeof(To) == 1 and FromVecL >= 4) // i32 -> i8
3301 return to<int16_t, ToL>().template to<To, ToL>();
3302 if constexpr (sizeof(From) == 1 and sizeof(To) == 8) // i8 -> i64
3303 return to<int32_t, ToL>().template to<To, ToL>();
3304 if constexpr (sizeof(From) == 8 and sizeof(To) == 1 and FromVecL >= 8) // i64 -> i8
3305 return to<int16_t, ToL>().template to<To, ToL>();
3306 if constexpr (sizeof(From) == 2 and sizeof(To) == 4) { // i16 -> i32
3307 std::array<ToVecT, ToVecL> vectors;
3308 for (std::size_t idx = 0; idx < FromVecL; ++idx) {
3309 vectors[2 * idx] = vectors_[idx].clone().template unary<To, ToVecT::num_simd_lanes>(
3310 ::wasm::ExtendLowSVecI16x8ToVecI32x4
3311 );
3312 vectors[2 * idx + 1] = vectors_[idx].template unary<To, ToVecT::num_simd_lanes>(
3313 ::wasm::ExtendHighSVecI16x8ToVecI32x4
3314 );
3315 }
3316 return PrimitiveExpr<To, ToL>(std::move(vectors));
3317 }
3318 if constexpr (sizeof(From) == 4 and sizeof(To) == 2) { // i32 -> i16
3319 std::array<ToVecT, ToVecL> vectors;
3320 for (std::size_t idx = 0; idx < ToVecL; ++idx)
3321 vectors[idx] = vectors_[2 * idx].template binary<To, ToVecT::num_simd_lanes>(
3322 ::wasm::NarrowSVecI32x4ToVecI16x8, vectors_[2 * idx + 1]
3323 );
3324 return PrimitiveExpr<To, ToL>(std::move(vectors));
3325 }
3326 if constexpr (sizeof(From) == 2 and sizeof(To) == 8) // i16 -> i64
3327 return to<int32_t, ToL>().template to<To, ToL>();
3328 if constexpr (sizeof(From) == 8 and sizeof(To) == 2 and FromVecL >= 4) // i64 -> i16
3329 return to<int32_t, ToL>().template to<To, ToL>();
3330 if constexpr (sizeof(From) == 4 and sizeof(To) == 8) { // i32 -> i64
3331 std::array<ToVecT, ToVecL> vectors;
3332 for (std::size_t idx = 0; idx < FromVecL; ++idx) {
3333 vectors[2 * idx] = vectors_[idx].clone().template unary<To, ToVecT::num_simd_lanes>(
3334 ::wasm::ExtendLowSVecI32x4ToVecI64x2
3335 );
3336 vectors[2 * idx + 1] = vectors_[idx].template unary<To, ToVecT::num_simd_lanes>(
3337 ::wasm::ExtendHighSVecI32x4ToVecI64x2
3338 );
3339 }
3340 return PrimitiveExpr<To, ToL>(std::move(vectors));
3341 }
3342 if constexpr (sizeof(From) == 8 and sizeof(To) == 4) { // i64 -> i32
3343 std::array<ToVecT, ToVecL> vectors;
3344 for (std::size_t idx = 0; idx < ToVecL; ++idx) {
3345 std::array<uint8_t, 16> indices =
3346 { 0, 1, 2, 3, 8, 9 , 10, 11, 16, 17, 18, 19, 24, 25, 26, 27 };
3347 vectors[idx] =
3348 ToVecT(ShuffleBytes(vectors_[2 * idx], vectors_[2 * idx + 1], indices).move());
3349 }
3350 return PrimitiveExpr<To, ToL>(std::move(vectors));
3351 }
3352 } else { // unsigned
3353 if constexpr (sizeof(From) == 1 and sizeof(To) == 2) { // u8 -> u16
3354 std::array<ToVecT, ToVecL> vectors;
3355 for (std::size_t idx = 0; idx < FromVecL; ++idx) {
3356 vectors[2 * idx] = vectors_[idx].clone().template unary<To, ToVecT::num_simd_lanes>(
3357 ::wasm::ExtendLowUVecI8x16ToVecI16x8
3358 );
3359 vectors[2 * idx + 1] = vectors_[idx].template unary<To, ToVecT::num_simd_lanes>(
3360 ::wasm::ExtendHighUVecI8x16ToVecI16x8
3361 );
3362 }
3363 return PrimitiveExpr<To, ToL>(std::move(vectors));
3364 }
3365 if constexpr (sizeof(From) == 2 and sizeof(To) == 1) { // u16 -> u8
3366 std::array<ToVecT, ToVecL> vectors;
3367 for (std::size_t idx = 0; idx < ToVecL; ++idx)
3368 vectors[idx] = vectors_[2 * idx].template binary<To, ToVecT::num_simd_lanes>(
3369 ::wasm::NarrowSVecI16x8ToVecI8x16, vectors_[2 * idx + 1]
3370 );
3371 return PrimitiveExpr<To, ToL>(std::move(vectors));
3372 }
3373 if constexpr (sizeof(From) == 1 and sizeof(To) == 4) // u8 -> u32
3374 return to<uint16_t, ToL>().template to<To, ToL>();
3375 if constexpr (sizeof(From) == 4 and sizeof(To) == 1 and FromVecL >= 4) // u32 -> u8
3376 return to<uint16_t, ToL>().template to<To, ToL>();
3377 if constexpr (sizeof(From) == 1 and sizeof(To) == 8) // u8 -> u64
3378 return to<uint32_t, ToL>().template to<To, ToL>();
3379 if constexpr (sizeof(From) == 8 and sizeof(To) == 1 and FromVecL >= 8) // u64 -> u8
3380 return to<uint16_t, ToL>().template to<To, ToL>();
3381 if constexpr (sizeof(From) == 2 and sizeof(To) == 4) { // u16 -> u32
3382 std::array<ToVecT, ToVecL> vectors;
3383 for (std::size_t idx = 0; idx < FromVecL; ++idx) {
3384 vectors[2 * idx] = vectors_[idx].clone().template unary<To, ToVecT::num_simd_lanes>(
3385 ::wasm::ExtendLowUVecI16x8ToVecI32x4
3386 );
3387 vectors[2 * idx + 1] = vectors_[idx].template unary<To, ToVecT::num_simd_lanes>(
3388 ::wasm::ExtendHighUVecI16x8ToVecI32x4
3389 );
3390 }
3391 return PrimitiveExpr<To, ToL>(std::move(vectors));
3392 }
3393 if constexpr (sizeof(From) == 4 and sizeof(To) == 2) { // u32 -> u16
3394 std::array<ToVecT, ToVecL> vectors;
3395 for (std::size_t idx = 0; idx < ToVecL; ++idx)
3396 vectors[idx] = vectors_[2 * idx].template binary<To, ToVecT::num_simd_lanes>(
3397 ::wasm::NarrowSVecI32x4ToVecI16x8, vectors_[2 * idx + 1]
3398 );
3399 return PrimitiveExpr<To, ToL>(std::move(vectors));
3400 }
3401 if constexpr (sizeof(From) == 2 and sizeof(To) == 8) // u16 -> u64
3402 return to<uint32_t, ToL>().template to<To, ToL>();
3403 if constexpr (sizeof(From) == 8 and sizeof(To) == 2 and FromVecL >= 4) // u64 -> u16
3404 return to<uint32_t, ToL>().template to<To, ToL>();
3405 if constexpr (sizeof(From) == 4 and sizeof(To) == 8) { // u32 -> u64
3406 std::array<ToVecT, ToVecL> vectors;
3407 for (std::size_t idx = 0; idx < FromVecL; ++idx) {
3408 vectors[2 * idx] = vectors_[idx].clone().template unary<To, ToVecT::num_simd_lanes>(
3409 ::wasm::ExtendLowUVecI32x4ToVecI64x2
3410 );
3411 vectors[2 * idx + 1] = vectors_[idx].template unary<To, ToVecT::num_simd_lanes>(
3412 ::wasm::ExtendHighUVecI32x4ToVecI64x2
3413 );
3414 }
3415 return PrimitiveExpr<To, ToL>(std::move(vectors));
3416 }
3417 if constexpr (sizeof(From) == 8 and sizeof(To) == 4) { // u64 -> u32
3418 std::array<ToVecT, ToVecL> vectors;
3419 for (std::size_t idx = 0; idx < ToVecL; ++idx) {
3420 std::array<uint8_t, 16> indices =
3421 { 0, 1, 2, 3, 8, 9 , 10, 11, 16, 17, 18, 19, 24, 25, 26, 27 };
3422 vectors[idx] =
3423 ToVecT(ShuffleBytes(vectors_[2 * idx], vectors_[2 * idx + 1], indices).move());
3424 }
3425 return PrimitiveExpr<To, ToL>(std::move(vectors));
3426 }
3427 }
3428 }
3429 }
3430 if constexpr (std::floating_point<To>) { // to floating point
3431 if constexpr (FromL == ToL) { // vectorial
3432 if constexpr (std::is_signed_v<From>) { // signed
3433 if constexpr (sizeof(From) == 1 and sizeof(To) == 4) // i8 -> f32
3434 return to<int32_t, ToL>().template to<To, ToL>();
3435 if constexpr (sizeof(From) == 1 and sizeof(To) == 8) // i8 -> f64
3436 return to<int32_t, ToL>().template to<To, ToL>();
3437 if constexpr (sizeof(From) == 2 and sizeof(To) == 4) // i16 -> f32
3438 return to<int32_t, ToL>().template to<To, ToL>();
3439 if constexpr (sizeof(From) == 2 and sizeof(To) == 8) // i16 -> f64
3440 return to<int32_t, ToL>().template to<To, ToL>();
3441 if constexpr (sizeof(From) == 4 and sizeof(To) == 4) { // i32 -> f32
3442 std::array<ToVecT, ToVecL> vectors;
3443 for (std::size_t idx = 0; idx < ToVecL; ++idx)
3444 vectors[idx] = vectors_[idx].template unary<To, ToVecT::num_simd_lanes>(
3445 ::wasm::ConvertSVecI32x4ToVecF32x4
3446 );
3447 return PrimitiveExpr<To, ToL>(std::move(vectors));
3448 }
3449 if constexpr (sizeof(From) == 4 and sizeof(To) == 8) { // i32 -> f64
3450 std::array<ToVecT, ToVecL> vectors;
3451 for (std::size_t idx = 0; idx < FromVecL; ++idx) {
3452 vectors[2 * idx] = vectors_[idx].clone().template unary<To, ToVecT::num_simd_lanes>(
3453 ::wasm::ConvertLowSVecI32x4ToVecF64x2
3454 );
3455 auto high_to_low = vectors_[idx].swizzle_lanes(std::to_array<uint8_t>({ 2, 3 }));
3456 vectors[2 * idx + 1] = high_to_low.template unary<To, ToVecT::num_simd_lanes>(
3457 ::wasm::ConvertLowSVecI32x4ToVecF64x2
3458 );
3459 }
3460 return PrimitiveExpr<To, ToL>(std::move(vectors));
3461 }
3462 if constexpr (sizeof(From) == 8 and sizeof(To) == 4) // i64 -> f32
3463 return to<int32_t, ToL>().template to<To, ToL>();
3464 if constexpr (sizeof(From) == 8 and sizeof(To) == 8) // i64 -> f64
3465 return to<int32_t, ToL>().template to<To, ToL>();
3466 } else { // unsigned
3467 if constexpr (sizeof(From) == 1 and sizeof(To) == 4) // u8 -> f32
3468 return to<uint32_t, ToL>().template convert<To, ToL>();
3469 if constexpr (sizeof(From) == 1 and sizeof(To) == 8) // u8 -> f64
3470 return to<uint32_t, ToL>().template convert<To, ToL>();
3471 if constexpr (sizeof(From) == 2 and sizeof(To) == 4) // u16 -> f32
3472 return to<uint32_t, ToL>().template convert<To, ToL>();
3473 if constexpr (sizeof(From) == 2 and sizeof(To) == 8) // u16 -> f64
3474 return to<uint32_t, ToL>().template convert<To, ToL>();
3475 if constexpr (sizeof(From) == 4 and sizeof(To) == 4) { // u32 -> f32
3476 std::array<ToVecT, ToVecL> vectors;
3477 for (std::size_t idx = 0; idx < ToVecL; ++idx)
3478 vectors[idx] = vectors_[idx].template unary<To, ToVecT::num_simd_lanes>(
3479 ::wasm::ConvertUVecI32x4ToVecF32x4
3480 );
3481 return PrimitiveExpr<To, ToL>(std::move(vectors));
3482 }
3483 if constexpr (sizeof(From) == 4 and sizeof(To) == 8) { // u32 -> f64
3484 std::array<ToVecT, ToVecL> vectors;
3485 for (std::size_t idx = 0; idx < FromVecL; ++idx) {
3486 vectors[2 * idx] = vectors_[idx].clone().template unary<To, ToVecT::num_simd_lanes>(
3487 ::wasm::ConvertLowUVecI32x4ToVecF64x2
3488 );
3489 auto high_to_low = vectors_[idx].swizzle_lanes(std::to_array<uint8_t>({ 2, 3 }));
3490 vectors[2 * idx + 1] = high_to_low.template unary<To, ToVecT::num_simd_lanes>(
3491 ::wasm::ConvertLowUVecI32x4ToVecF64x2
3492 );
3493 }
3494 return PrimitiveExpr<To, ToL>(std::move(vectors));
3495 }
3496 if constexpr (sizeof(From) == 8 and sizeof(To) == 4) // u64 -> f32
3497 return to<uint32_t, ToL>().template convert<To, ToL>();
3498 if constexpr (sizeof(From) == 8 and sizeof(To) == 8) // u64 -> f64
3499 return to<uint32_t, ToL>().template convert<To, ToL>();
3500 }
3501 }
3502 }
3503 }
3504
3505 if constexpr (std::floating_point<From>) { // from floating point
3506 if constexpr (integral<To>) { // to integer
3507 if constexpr (FromL == ToL) { // vectorial
3508 if constexpr (std::is_signed_v<To>) { // signed
3509 if constexpr (sizeof(From) == 4 and sizeof(To) == 1 and FromVecL >= 4) // f32 -> i8
3510 return to<int32_t, ToL>().template to<To, ToL>();
3511 if constexpr (sizeof(From) == 8 and sizeof(To) == 1 and FromVecL >= 8) // f64 -> i8
3512 return to<int32_t, ToL>().template to<To, ToL>();
3513 if constexpr (sizeof(From) == 4 and sizeof(To) == 2) // f32 -> i16
3514 return to<int32_t, ToL>().template to<To, ToL>();
3515 if constexpr (sizeof(From) == 8 and sizeof(To) == 2 and FromVecL >= 4) // f64 -> i16
3516 return to<int32_t, ToL>().template to<To, ToL>();
3517 if constexpr (sizeof(From) == 4 and sizeof(To) == 4) { // f32 -> i32
3518 std::array<ToVecT, ToVecL> vectors;
3519 for (std::size_t idx = 0; idx < ToVecL; ++idx)
3520 vectors[idx] = vectors_[idx].template unary<To, ToVecT::num_simd_lanes>(
3521 ::wasm::TruncSatSVecF32x4ToVecI32x4
3522 );
3523 return PrimitiveExpr<To, ToL>(std::move(vectors));
3524 }
3525 if constexpr (sizeof(From) == 8 and sizeof(To) == 4) { // f64 -> i32
3526 std::array<ToVecT, ToVecL> vectors;
3527 for (std::size_t idx = 0; idx < ToVecL; ++idx) {
3528 auto low = vectors_[2 * idx].template unary<To, ToVecT::num_simd_lanes>(
3529 ::wasm::TruncSatZeroSVecF64x2ToVecI32x4
3530 );
3531 auto high = vectors_[2 * idx + 1].template unary<To, ToVecT::num_simd_lanes>(
3532 ::wasm::TruncSatZeroSVecF64x2ToVecI32x4
3533 );
3534 vectors[idx] = ShuffleLanes(low, high, std::to_array<uint8_t>({ 0, 1, 4, 5 }));
3535 }
3536 return PrimitiveExpr<To, ToL>(std::move(vectors));
3537 }
3538 if constexpr (sizeof(From) == 4 and sizeof(To) == 8) // f32 -> i64
3539 return to<int32_t, ToL>().template to<To, ToL>();
3540 if constexpr (sizeof(From) == 8 and sizeof(To) == 8) // f64 -> i64
3541 return to<int32_t, ToL>().template to<To, ToL>();
3542 } else { // unsigned
3543 if constexpr (sizeof(From) == 4 and sizeof(To) == 1 and FromVecL >= 4) // f32 -> u8
3544 return convert<uint32_t, ToL>().template to<To, ToL>();
3545 if constexpr (sizeof(From) == 8 and sizeof(To) == 1 and FromVecL >= 8) // f64 -> u8
3546 return convert<uint32_t, ToL>().template to<To, ToL>();
3547 if constexpr (sizeof(From) == 4 and sizeof(To) == 2) // f32 -> u16
3548 return convert<uint32_t, ToL>().template to<To, ToL>();
3549 if constexpr (sizeof(From) == 8 and sizeof(To) == 2 and FromVecL >= 4) // f64 -> u16
3550 return convert<uint32_t, ToL>().template to<To, ToL>();
3551 if constexpr (sizeof(From) == 4 and sizeof(To) == 4) { // f32 -> u32
3552 std::array<ToVecT, ToVecL> vectors;
3553 for (std::size_t idx = 0; idx < ToVecL; ++idx)
3554 vectors[idx] = vectors_[idx].template unary<To, ToVecT::num_simd_lanes>(
3555 ::wasm::TruncSatUVecF32x4ToVecI32x4
3556 );
3557 return PrimitiveExpr<To, ToL>(std::move(vectors));
3558 }
3559 if constexpr (sizeof(From) == 8 and sizeof(To) == 4) { // f64 -> u32
3560 std::array<ToVecT, ToVecL> vectors;
3561 for (std::size_t idx = 0; idx < ToVecL; ++idx) {
3562 auto low = vectors_[2 * idx].template unary<To, ToVecT::num_simd_lanes>(
3563 ::wasm::TruncSatZeroUVecF64x2ToVecI32x4
3564 );
3565 auto high = vectors_[2 * idx + 1].template unary<To, ToVecT::num_simd_lanes>(
3566 ::wasm::TruncSatZeroUVecF64x2ToVecI32x4
3567 );
3568 vectors[idx] = ShuffleLanes(low, high, std::to_array<uint8_t>({ 0, 1, 4, 5 }));
3569 }
3570 return PrimitiveExpr<To, ToL>(std::move(vectors));
3571 }
3572 if constexpr (sizeof(From) == 4 and sizeof(To) == 8) // f32 -> u64
3573 return convert<uint32_t, ToL>().template to<To, ToL>();
3574 if constexpr (sizeof(From) == 8 and sizeof(To) == 8) // f64 -> u64
3575 return convert<uint32_t, ToL>().template to<To, ToL>();
3576 }
3577 }
3578 }
3579 if constexpr (std::floating_point<To>) { // to floating point
3580 if constexpr (FromL == ToL) { // vectorial
3581 if constexpr (sizeof(From) == 4 and sizeof(To) == 8) { // f32 -> f64
3582 std::array<ToVecT, ToVecL> vectors;
3583 for (std::size_t idx = 0; idx < FromVecL; ++idx) {
3584 vectors[2 * idx] = vectors_[idx].clone().template unary<To, ToVecT::num_simd_lanes>(
3585 ::wasm::PromoteLowVecF32x4ToVecF64x2
3586 );
3587 auto high_to_low = vectors_[idx].swizzle_lanes(std::to_array<uint8_t>({ 2, 3 }));
3588 vectors[2 * idx + 1] = high_to_low.template unary<To, ToVecT::num_simd_lanes>(
3589 ::wasm::PromoteLowVecF32x4ToVecF64x2
3590 );
3591 }
3592 return PrimitiveExpr<To, ToL>(std::move(vectors));
3593 }
3594 if constexpr (sizeof(From) == 8 and sizeof(To) == 4) { // f64 -> f32
3595 std::array<ToVecT, ToVecL> vectors;
3596 for (std::size_t idx = 0; idx < ToVecL; ++idx) {
3597 auto low = vectors_[2 * idx].template unary<To, ToVecT::num_simd_lanes>(
3598 ::wasm::DemoteZeroVecF64x2ToVecF32x4
3599 );
3600 auto high = vectors_[2 * idx + 1].template unary<To, ToVecT::num_simd_lanes>(
3601 ::wasm::DemoteZeroVecF64x2ToVecF32x4
3602 );
3603 vectors[idx] = ShuffleLanes(low, high, std::to_array<uint8_t>({ 0, 1, 4, 5 }));
3604 }
3605 return PrimitiveExpr<To, ToL>(std::move(vectors));
3606 }
3607 }
3608 }
3609 }
3610
3611 M_unreachable("illegal conversion");
3612 }
3613
3614 public:
3623 template<dsl_primitive To, std::size_t ToL = L>
3624 requires (L == ToL) and // L and ToL are equal
3625 same_signedness<T, To> and // T and To have same signedness
3626 (integral<T> == integral<To>) and // neither nor both T and To are integers (excluding bool)
3627 (sizeof(T) <= sizeof(To)) // T can be *trivially* converted to To
3628 operator PrimitiveExpr<To, ToL>() { return convert<To, ToL>(); }
3629
3637 template<dsl_primitive To, std::size_t ToL = L>
3638 requires (L == ToL) and // L and ToL are equal
3639 (same_signedness<T, To> or // T and To have same signedness
3640 boolean<T> or std::same_as<T, char> or // or T is bool or char
3641 boolean<To> or std::same_as<To, char>) and // or To is bool or char
3642 std::is_convertible_v<T, To> and // T can be converted to To
3643 (num_vectors >= sizeof(T) / sizeof(To)) // at least one vector afterwards
3644 PrimitiveExpr<To, ToL> to() { return convert<To, ToL>(); }
3645
3650 auto make_signed() requires unsigned_integral<T> {
3651 return PrimitiveExpr<std::make_signed_t<T>, L>(move<std::make_signed_t<T>, L>());
3652 }
3653
3658 auto make_unsigned() requires signed_integral<T> {
3659 return PrimitiveExpr<std::make_unsigned_t<T>, L>(move<std::make_unsigned_t<T>, L>());
3660 }
3661
3662
3663 /*------------------------------------------------------------------------------------------------------------------
3664 * Unary operations
3665 *----------------------------------------------------------------------------------------------------------------*/
3666
3667#define UNARY(OP) \
3668 auto OP() requires requires (vector_type v) { v.OP(); } { \
3669 using ResVecT = decltype(std::declval<vector_type>().OP()); \
3670 static_assert(ResVecT::num_simd_lanes * sizeof(typename ResVecT::type) == 16, \
3671 "result vectors must be fully utilized"); \
3672 std::array<ResVecT, num_vectors> vectors; \
3673 for (std::size_t idx = 0; idx < num_vectors; ++idx) \
3674 vectors[idx] = vectors_[idx].OP(); \
3675 return PrimitiveExpr<typename ResVecT::type, ResVecT::num_simd_lanes * num_vectors>(std::move(vectors)); \
3676 }
3677
3678 UNARY(operator +)
3679 UNARY(operator -)
3680 UNARY(abs)
3681 UNARY(ceil)
3682 UNARY(floor)
3683 UNARY(trunc)
3684 UNARY(nearest)
3685 UNARY(sqrt)
3686 UNARY(add_pairwise)
3687 UNARY(operator ~)
3688 UNARY(popcnt)
3689 UNARY(operator not)
3690#undef UNARY
3691
3694 PrimitiveExpr<uint32_t, 1> bitmask() requires (L <= 32) and requires (vector_type v) { v.bitmask(); } {
3695 std::optional<PrimitiveExpr<uint32_t, 1>> res = vectors_[0].bitmask();
3696 for (std::size_t idx = 1; idx < num_vectors; ++idx)
3697 res.emplace((vectors_[idx].bitmask() << uint32_t(idx * vector_type::num_simd_lanes)) bitor *res);
3698 return *res;
3699 }
3702 PrimitiveExpr<uint64_t, 1> bitmask() requires (L > 32) and (L <= 64) and requires (vector_type v) { v.bitmask(); } {
3703 std::optional<PrimitiveExpr<uint64_t, 1>> res = vectors_[0].bitmask();
3704 for (std::size_t idx = 1; idx < num_vectors; ++idx)
3705 res.emplace((vectors_[idx].bitmask() << uint64_t(idx * vector_type::num_simd_lanes)) bitor *res);
3706 return *res;
3707 }
3708
3710 PrimitiveExpr<bool, 1> any_true() requires requires (vector_type v) { v.any_true(); } {
3711 std::optional<PrimitiveExpr<bool, 1>> res = vectors_[0].any_true();
3712 for (std::size_t idx = 1; idx < num_vectors; ++idx)
3713 res.emplace(vectors_[idx].any_true() or *res);
3714 return *res;
3715 }
3716
3718 PrimitiveExpr<bool, 1> all_true() requires requires (vector_type v) { v.all_true(); } {
3719 std::optional<PrimitiveExpr<bool, 1>> res = vectors_[0].all_true();
3720 for (std::size_t idx = 1; idx < num_vectors; ++idx)
3721 res.emplace(vectors_[idx].all_true() and *res);
3722 return *res;
3723 }
3724
3725
3726 /*------------------------------------------------------------------------------------------------------------------
3727 * Binary operations
3728 *----------------------------------------------------------------------------------------------------------------*/
3729
3730#define BINARY(OP) \
3731 template<dsl_primitive U> \
3732 requires arithmetically_combinable<T, U, L> and \
3733 requires (typename PrimitiveExpr<common_type_t<T, U>, L>::vector_type left, \
3734 typename PrimitiveExpr<common_type_t<T, U>, L>::vector_type right) \
3735 { left.OP(right); } \
3736 auto OP(PrimitiveExpr<U, L> other) { \
3737 using To = common_type_t<T, U>; \
3738 using OpT = decltype(to<To, L>()); \
3739 using ResVecT = \
3740 decltype(std::declval<typename OpT::vector_type>().OP(std::declval<typename OpT::vector_type>())); \
3741 static_assert(ResVecT::num_simd_lanes * sizeof(typename ResVecT::type) == 16, \
3742 "result vectors must be fully utilized"); \
3743 auto this_converted = this->template to<To, L>(); \
3744 auto other_converted = other.template to<To, L>(); \
3745 std::array<ResVecT, OpT::num_vectors> vectors; \
3746 for (std::size_t idx = 0; idx < OpT::num_vectors; ++idx) \
3747 vectors[idx] = this_converted.vectors_[idx].OP(other_converted.vectors_[idx]); \
3748 return PrimitiveExpr<typename ResVecT::type, ResVecT::num_simd_lanes * OpT::num_vectors>(std::move(vectors)); \
3749 }
3750
3751 BINARY(operator +)
3752 BINARY(operator -)
3753 BINARY(operator *)
3754 BINARY(operator /)
3755 BINARY(min)
3756 BINARY(max)
3757 BINARY(avg)
3758 BINARY(operator bitand)
3759 BINARY(operator bitor)
3760 BINARY(operator xor)
3761 BINARY(operator and)
3762 BINARY(and_not)
3763 BINARY(operator or)
3764#undef BINARY
3765
3766#define SHIFT(OP) \
3767 template<dsl_primitive U> \
3768 PrimitiveExpr OP(PrimitiveExpr<U, 1> other) requires requires (vector_type v) { v.OP(other); } { \
3769 std::array<vector_type, num_vectors> vectors; \
3770 for (std::size_t idx = 0; idx < num_vectors; ++idx) \
3771 vectors[idx] = vectors_[idx].OP(other.clone()); \
3772 other.discard(); \
3773 return PrimitiveExpr(std::move(vectors)); \
3774 }
3775
3776 SHIFT(operator <<)
3777 SHIFT(operator >>)
3778#undef SHIFT
3779
3780#define BINVOP_(NAME, SIGN, TYPE) (::wasm::BinaryOp::NAME##SIGN##Vec##TYPE)
3781#define BINIVOP_(NAME, SIGN) [] { \
3782 if constexpr (sizeof(To) == 8) \
3783 return BINVOP_(NAME,SIGN,I64x2); \
3784 else if constexpr (sizeof(To) == 4) \
3785 return BINVOP_(NAME,SIGN,I32x4); \
3786 else if constexpr (sizeof(To) == 2) \
3787 return BINVOP_(NAME,SIGN,I16x8); \
3788 else if constexpr (sizeof(To) == 1) \
3789 return BINVOP_(NAME,SIGN,I8x16); \
3790 else \
3791 M_unreachable("unsupported operation"); \
3792} ()
3793#define BINFVOP_(NAME) [] { \
3794 if constexpr (sizeof(To) == 8) \
3795 return BINVOP_(NAME,,F64x2); \
3796 else if constexpr (sizeof(To) == 4) \
3797 return BINVOP_(NAME,,F32x4); \
3798 else \
3799 M_unreachable("unsupported operation"); \
3800} ()
3801#define BINARY_VOP(NAME, SIGN) [] { \
3802 if constexpr (std::integral<To>) \
3803 return BINIVOP_(NAME, SIGN); \
3804 else if constexpr (std::floating_point<To>) \
3805 return BINFVOP_(NAME); \
3806 else \
3807 M_unreachable("unsupported operation"); \
3808} ()
3809
3810 private:
3812 PrimitiveExpr<bool, L> cmp_helper() requires unsigned_integral<T> {
3813 if constexpr (L > 16) { // enough values present s.t. integer narrowing can be used on multiple vectors
3814 auto narrowed = to<uint8_t, L>();
3815 return PrimitiveExpr<bool, L>(narrowed.template move<bool, L>());
3816 } else if constexpr (L == 16) { // enough values present s.t. integer narrowing can be used on single vector
3817 auto narrowed = to<uint8_t, L>();
3818 return PrimitiveExpr<bool, L>(narrowed.move());
3819 } else if constexpr (num_vectors == 2) { // swizzle bytes of two vectors together
3820 PrimitiveExpr<bool, L * sizeof(T)> cmp(move<bool, L * sizeof(T)>());
3821 std::array<uint8_t, L> indices;
3822 for (std::size_t idx = 0; idx < L; ++idx)
3823 indices[idx] = idx * sizeof(T);
3824 return cmp.swizzle_bytes(indices);
3825 } else { // shuffle bytes of four vectors together
3826 auto vectors = move<bool, L * sizeof(T)>();
3827 static_assert(vectors.size() == 4);
3828 std::array<uint8_t, L / 2> indices;
3829 for (std::size_t idx = 0; idx < L / 2; ++idx)
3830 indices[idx] = idx * sizeof(T);
3831 auto low = ShuffleBytes(vectors[0], vectors[1], indices);
3832 auto high = ShuffleBytes(vectors[2], vectors[3], indices);
3833 std::array<uint8_t, L> lanes;
3834 std::iota(lanes.begin(), lanes.end(), 0); // fill with [0, L), i.e. all L/2 lanes of low and high concatenated
3835 return ShuffleLanes(low, high, lanes);
3836 }
3837 }
3838
3839 public:
3841 template<dsl_primitive U>
3842 requires same_signedness<T, U> and arithmetically_combinable<T, U, L>
3843 PrimitiveExpr<bool, L> operator==(PrimitiveExpr<U, L> other) {
3844 using To = common_type_t<T, U>;
3845 using OpT = decltype(to<To, L>());
3846 static constexpr std::size_t lanes = OpT::vector_type::num_simd_lanes;
3847 auto this_converted = this->template to<To, L>();
3848 auto other_converted = other.template to<To, L>();
3849 std::array<PrimitiveExpr<uint_t<sizeof(To)>, lanes>, OpT::num_vectors> vectors;
3850 for (std::size_t idx = 0; idx < OpT::num_vectors; ++idx)
3851 vectors[idx] = this_converted.vectors_[idx].template binary<uint_t<sizeof(To)>, lanes>(
3852 BINARY_VOP(Eq,), other_converted.vectors_[idx]
3853 );
3854 return PrimitiveExpr<uint_t<sizeof(To)>, L>(std::move(vectors)).cmp_helper();
3855 }
3856
3858 template<dsl_primitive U>
3859 requires same_signedness<T, U> and arithmetically_combinable<T, U, L>
3860 PrimitiveExpr<bool, L> operator!=(PrimitiveExpr<U, L> other) {
3861 using To = common_type_t<T, U>;
3862 using OpT = decltype(to<To, L>());
3863 static constexpr std::size_t lanes = OpT::vector_type::num_simd_lanes;
3864 auto this_converted = this->template to<To, L>();
3865 auto other_converted = other.template to<To, L>();
3866 std::array<PrimitiveExpr<uint_t<sizeof(To)>, lanes>, OpT::num_vectors> vectors;
3867 for (std::size_t idx = 0; idx < OpT::num_vectors; ++idx)
3868 vectors[idx] = this_converted.vectors_[idx].template binary<uint_t<sizeof(To)>, lanes>(
3869 BINARY_VOP(Ne,), other_converted.vectors_[idx]
3870 );
3871 return PrimitiveExpr<uint_t<sizeof(To)>, L>(std::move(vectors)).cmp_helper();
3872 }
3873
3875 template<arithmetic U>
3876 requires same_signedness<T, U> and arithmetically_combinable<T, U, L>
3877 PrimitiveExpr<bool, L> operator<(PrimitiveExpr<U, L> other) requires arithmetic<T> {
3878 using To = common_type_t<T, U>;
3879 using OpT = decltype(to<To, L>());
3880 static constexpr std::size_t lanes = OpT::vector_type::num_simd_lanes;
3881 auto op = [](){
3882 if constexpr (std::integral<To>) {
3883 if constexpr (sizeof(To) == 8)
3884 return BINVOP_(Lt, S, I64x2); // unsigned comparison missing, use signed but flip MSB of operands
3885 else if constexpr (sizeof(To) == 4)
3886 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Lt, S, I32x4), BINVOP_(Lt, U, I32x4));
3887 else if constexpr (sizeof(To) == 2)
3888 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Lt, S, I16x8), BINVOP_(Lt, U, I16x8));
3889 else if constexpr (sizeof(To) == 1)
3890 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Lt, S, I8x16), BINVOP_(Lt, U, I8x16));
3891 } else if (std::floating_point<To>) {
3892 return BINFVOP_(Lt);
3893 }
3894 M_unreachable("unsupported operation");
3895 }();
3896 /* Wasm does not support comparison of U64 SIMD vectors. Thus, flip the MSB in each lane (which
3897 * basically shifts the unsigned domain into the signed one) and use the signed comparison afterwards. */
3898 constexpr bool is_u64_vec = unsigned_integral<To> and sizeof(To) == 8 and L > 1;
3899 auto _this =
3900 M_CONSTEXPR_COND(is_u64_vec, (*this xor PrimitiveExpr<T, L>(T(1) << (CHAR_BIT * sizeof(T) - 1))), *this);
3901 auto _other =
3902 M_CONSTEXPR_COND(is_u64_vec, (other xor PrimitiveExpr<U, L>(U(1) << (CHAR_BIT * sizeof(U) - 1))), other);
3903 auto this_converted = _this.template to<To, L>();
3904 auto other_converted = _other.template to<To, L>();
3905 std::array<PrimitiveExpr<uint_t<sizeof(To)>, lanes>, OpT::num_vectors> vectors;
3906 for (std::size_t idx = 0; idx < OpT::num_vectors; ++idx)
3907 vectors[idx] = this_converted.vectors_[idx].template binary<uint_t<sizeof(To)>, lanes>(
3908 op, other_converted.vectors_[idx]
3909 );
3910 return PrimitiveExpr<uint_t<sizeof(To)>, L>(std::move(vectors)).cmp_helper();
3911 }
3912
3914 template<arithmetic U>
3915 requires same_signedness<T, U> and arithmetically_combinable<T, U, L>
3916 PrimitiveExpr<bool, L> operator<=(PrimitiveExpr<U, L> other) requires arithmetic<T> {
3917 using To = common_type_t<T, U>;
3918 using OpT = decltype(to<To, L>());
3919 static constexpr std::size_t lanes = OpT::vector_type::num_simd_lanes;
3920 auto op = [](){
3921 if constexpr (std::integral<To>) {
3922 if constexpr (sizeof(To) == 8)
3923 return BINVOP_(Le, S, I64x2); // unsigned comparison missing, use signed but flip MSB of operands
3924 else if constexpr (sizeof(To) == 4)
3925 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Le, S, I32x4), BINVOP_(Le, U, I32x4));
3926 else if constexpr (sizeof(To) == 2)
3927 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Le, S, I16x8), BINVOP_(Le, U, I16x8));
3928 else if constexpr (sizeof(To) == 1)
3929 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Le, S, I8x16), BINVOP_(Le, U, I8x16));
3930 } else if (std::floating_point<To>) {
3931 return BINFVOP_(Le);
3932 }
3933 M_unreachable("unsupported operation");
3934 }();
3935 /* Wasm does not support comparison of U64 SIMD vectors. Thus, flip the MSB in each lane (which
3936 * basically shifts the unsigned domain into the signed one) and use the signed comparison afterwards. */
3937 constexpr bool is_u64_vec = unsigned_integral<To> and sizeof(To) == 8 and L > 1;
3938 auto _this =
3939 M_CONSTEXPR_COND(is_u64_vec, (*this xor PrimitiveExpr<T, L>(T(1) << (CHAR_BIT * sizeof(T) - 1))), *this);
3940 auto _other =
3941 M_CONSTEXPR_COND(is_u64_vec, (other xor PrimitiveExpr<U, L>(U(1) << (CHAR_BIT * sizeof(U) - 1))), other);
3942 auto this_converted = _this.template to<To, L>();
3943 auto other_converted = _other.template to<To, L>();
3944 std::array<PrimitiveExpr<uint_t<sizeof(To)>, lanes>, OpT::num_vectors> vectors;
3945 for (std::size_t idx = 0; idx < OpT::num_vectors; ++idx)
3946 vectors[idx] = this_converted.vectors_[idx].template binary<uint_t<sizeof(To)>, lanes>(
3947 op, other_converted.vectors_[idx]
3948 );
3949 return PrimitiveExpr<uint_t<sizeof(To)>, L>(std::move(vectors)).cmp_helper();
3950 }
3951
3953 template<arithmetic U>
3954 requires same_signedness<T, U> and arithmetically_combinable<T, U, L>
3955 PrimitiveExpr<bool, L> operator>(PrimitiveExpr<U, L> other) requires arithmetic<T> {
3956 using To = common_type_t<T, U>;
3957 using OpT = decltype(to<To, L>());
3958 static constexpr std::size_t lanes = OpT::vector_type::num_simd_lanes;
3959 auto op = [](){
3960 if constexpr (std::integral<To>) {
3961 if constexpr (sizeof(To) == 8)
3962 return BINVOP_(Gt, S, I64x2); // unsigned comparison missing, use signed but flip MSB of operands
3963 else if constexpr (sizeof(To) == 4)
3964 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Gt, S, I32x4), BINVOP_(Gt, U, I32x4));
3965 else if constexpr (sizeof(To) == 2)
3966 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Gt, S, I16x8), BINVOP_(Gt, U, I16x8));
3967 else if constexpr (sizeof(To) == 1)
3968 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Gt, S, I8x16), BINVOP_(Gt, U, I8x16));
3969 } else if (std::floating_point<To>) {
3970 return BINFVOP_(Gt);
3971 }
3972 M_unreachable("unsupported operation");
3973 }();
3974 /* Wasm does not support comparison of U64 SIMD vectors. Thus, flip the MSB in each lane (which
3975 * basically shifts the unsigned domain into the signed one) and use the signed comparison afterwards. */
3976 constexpr bool is_u64_vec = unsigned_integral<To> and sizeof(To) == 8 and L > 1;
3977 auto _this =
3978 M_CONSTEXPR_COND(is_u64_vec, (*this xor PrimitiveExpr<T, L>(T(1) << (CHAR_BIT * sizeof(T) - 1))), *this);
3979 auto _other =
3980 M_CONSTEXPR_COND(is_u64_vec, (other xor PrimitiveExpr<U, L>(U(1) << (CHAR_BIT * sizeof(U) - 1))), other);
3981 auto this_converted = _this.template to<To, L>();
3982 auto other_converted = _other.template to<To, L>();
3983 std::array<PrimitiveExpr<uint_t<sizeof(To)>, lanes>, OpT::num_vectors> vectors;
3984 for (std::size_t idx = 0; idx < OpT::num_vectors; ++idx)
3985 vectors[idx] = this_converted.vectors_[idx].template binary<uint_t<sizeof(To)>, lanes>(
3986 op, other_converted.vectors_[idx]
3987 );
3988 return PrimitiveExpr<uint_t<sizeof(To)>, L>(std::move(vectors)).cmp_helper();
3989 }
3990
3992 template<arithmetic U>
3993 requires same_signedness<T, U> and arithmetically_combinable<T, U, L>
3994 PrimitiveExpr<bool, L> operator>=(PrimitiveExpr<U, L> other) requires arithmetic<T> {
3995 using To = common_type_t<T, U>;
3996 using OpT = decltype(to<To, L>());
3997 static constexpr std::size_t lanes = OpT::vector_type::num_simd_lanes;
3998 auto op = [](){
3999 if constexpr (std::integral<To>) {
4000 if constexpr (sizeof(To) == 8)
4001 return BINVOP_(Ge, S, I64x2); // unsigned comparison missing, use signed but flip MSB of operands
4002 else if constexpr (sizeof(To) == 4)
4003 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Ge, S, I32x4), BINVOP_(Ge, U, I32x4));
4004 else if constexpr (sizeof(To) == 2)
4005 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Ge, S, I16x8), BINVOP_(Ge, U, I16x8));
4006 else if constexpr (sizeof(To) == 1)
4007 return M_CONSTEXPR_COND(std::is_signed_v<To>, BINVOP_(Ge, S, I8x16), BINVOP_(Ge, U, I8x16));
4008 } else if (std::floating_point<To>) {
4009 return BINFVOP_(Ge);
4010 }
4011 M_unreachable("unsupported operation");
4012 }();
4013 /* Wasm does not support comparison of U64 SIMD vectors. Thus, flip the MSB in each lane (which
4014 * basically shifts the unsigned domain into the signed one) and use the signed comparison afterwards. */
4015 constexpr bool is_u64_vec = unsigned_integral<To> and sizeof(To) == 8 and L > 1;
4016 auto _this =
4017 M_CONSTEXPR_COND(is_u64_vec, (*this xor PrimitiveExpr<T, L>(T(1) << (CHAR_BIT * sizeof(T) - 1))), *this);
4018 auto _other =
4019 M_CONSTEXPR_COND(is_u64_vec, (other xor PrimitiveExpr<U, L>(U(1) << (CHAR_BIT * sizeof(U) - 1))), other);
4020 auto this_converted = _this.template to<To, L>();
4021 auto other_converted = _other.template to<To, L>();
4022 std::array<PrimitiveExpr<uint_t<sizeof(To)>, lanes>, OpT::num_vectors> vectors;
4023 for (std::size_t idx = 0; idx < OpT::num_vectors; ++idx)
4024 vectors[idx] = this_converted.vectors_[idx].template binary<uint_t<sizeof(To)>, lanes>(
4025 op, other_converted.vectors_[idx]
4026 );
4027 return PrimitiveExpr<uint_t<sizeof(To)>, L>(std::move(vectors)).cmp_helper();
4028 }
4029
4030#undef BINARY_VOP
4031#undef BINFVOP_
4032#undef BINIVOP_
4033#undef BINVOP_
4034
4035
4036 /*------------------------------------------------------------------------------------------------------------------
4037 * Modifications
4038 *----------------------------------------------------------------------------------------------------------------*/
4039
4041 template<std::size_t M>
4042 requires (M < L)
4044 auto res = vectors_[M / vector_type::num_simd_lanes].clone().template extract<M % vector_type::num_simd_lanes>();
4045 discard(); // to discard all vectors not used for extraction
4046 return res;
4047 }
4048
4050 template<std::size_t M, primitive_convertible U>
4051 requires (M < L)
4052 PrimitiveExpr replace(U &&value) requires requires (vector_type v) { v.replace<0>(std::forward<U>(value)); } {
4053 static constexpr std::size_t lanes = vector_type::num_simd_lanes;
4054 vectors_[M / lanes] = vectors_[M / lanes].template replace<M % lanes>(std::forward<U>(value));
4055 return *this;
4056 }
4057
4060 template<std::size_t M>
4061 requires (M > 0) and (M <= 16) and (M % sizeof(T) == 0)
4062 PrimitiveExpr<T, M / sizeof(T)> swizzle_bytes(const std::array<uint8_t, M> &indices) requires (num_vectors == 2) {
4063 return Module::Get().emit_shuffle_bytes(vectors_[0], vectors_[1], indices);
4064 }
4065
4068 template<std::size_t M>
4069 requires (M > 0) and (is_pow_2(M)) and (M * sizeof(T) <= 16)
4070 PrimitiveExpr<T, M> swizzle_lanes(const std::array<uint8_t, M> &indices) requires (num_vectors == 2) {
4071 return Module::Get().emit_shuffle_lanes(vectors_[0], vectors_[1], indices);
4072 }
4073
4074
4075 /*------------------------------------------------------------------------------------------------------------------
4076 * Printing
4077 *----------------------------------------------------------------------------------------------------------------*/
4078
4079 friend std::ostream & operator<<(std::ostream &out, const PrimitiveExpr &P) {
4080 out << "PrimitiveExpr<" << typeid(T).name() << "," << L << ">: [";
4081 for (auto it = P.vectors_.cbegin(); it != P.vectors_.cend(); ++it) {
4082 if (it != P.vectors_.cbegin())
4083 out << ", ";
4084 out << *it;
4085 }
4086 out << "]";
4087 return out;
4088 }
4089
4090 void dump(std::ostream &out) const { out << *this << std::endl; }
4091 void dump() const { dump(std::cerr); }
4092};
4093
4094
4095/*======================================================================================================================
4096 * Define binary operators on `PrimitiveExpr`
4097 *====================================================================================================================*/
4098
4100#define BINARY_LIST(X) \
4101 X(operator +) \
4102 X(operator -) \
4103 X(operator *) \
4104 X(operator /) \
4105 X(operator %) \
4106 X(operator bitand) \
4107 X(operator bitor) \
4108 X(operator xor) \
4109 X(operator <<) \
4110 X(operator >>) \
4111 X(operator ==) \
4112 X(operator !=) \
4113 X(operator <) \
4114 X(operator <=) \
4115 X(operator >) \
4116 X(operator >=) \
4117 X(operator and) \
4118 X(operator or) \
4119 X(copy_sign) \
4120 X(min) \
4121 X(max) \
4122 X(avg) \
4123 X(rotl) \
4124 X(rotr) \
4125 X(and_not)
4126
4127/*----- Forward binary operators on operands convertible to PrimitiveExpr<T, L> --------------------------------------*/
4128#define MAKE_BINARY(OP) \
4129 template<primitive_convertible T, primitive_convertible U> \
4130 requires requires (primitive_expr_t<T> t, primitive_expr_t<U> u) { t.OP(u); } \
4131 auto OP(T &&t, U &&u) \
4132 { \
4133 return primitive_expr_t<T>(std::forward<T>(t)).OP(primitive_expr_t<U>(std::forward<U>(u))); \
4134 }
4136#undef MAKE_BINARY
4137
4140template<dsl_pointer_to_primitive T, std::size_t L>
4141requires (L > 0) and (is_pow_2(L)) and
4142 ((L == 1) or requires { PrimitiveExpr<std::remove_pointer_t<T>, L>(); })
4143struct PrimitiveExpr<T, L>
4144{
4145 using type = T;
4146 static constexpr std::size_t num_simd_lanes = L;
4147 using pointed_type = std::decay_t<std::remove_pointer_t<T>>;
4148 using offset_t = int32_t;
4149
4150 /*----- Friends --------------------------------------------------------------------------------------------------*/
4151 template<typename, std::size_t> friend struct PrimitiveExpr; // to convert U* to T* and to convert uint32_t to T*
4152 template<typename, VariableKind, bool, std::size_t>
4153 friend class detail::variable_storage; // to construct from `::wasm::Expression` and access private `expr()`
4154 friend struct Module; // to acces internal ::wasm::Expr
4155 template<typename> friend struct FunctionProxy; // to access internal `::wasm::Expr` to construct function calls
4156 template<dsl_primitive, std::size_t, bool> friend struct detail::the_reference; // to access load()/store()
4157 template<typename> friend struct invoke_interpreter; // to access private `expr()`
4158
4159 private:
4160 PrimitiveExpr<uint32_t, 1> addr_;
4161 offset_t offset_ = 0;
4162
4163 public:
4165 explicit PrimitiveExpr(PrimitiveExpr<uint32_t, 1> addr, offset_t offset = 0) : addr_(addr), offset_(offset) { }
4166
4167 private:
4170 explicit PrimitiveExpr(::wasm::Expression *addr, std::list<std::shared_ptr<Bit>> referenced_bits = {},
4171 offset_t offset = 0)
4172 : addr_(addr, std::move(referenced_bits))
4173 , offset_(offset)
4174 { }
4177 explicit PrimitiveExpr(std::pair<::wasm::Expression*, std::list<std::shared_ptr<Bit>>> addr, offset_t offset = 0)
4178 : PrimitiveExpr(std::move(addr.first), std::move(addr.second), offset)
4179 { }
4180
4181 public:
4182 PrimitiveExpr(T raw_ptr) requires (L == 1)
4183 : addr_(0U)
4184 , offset_([&raw_ptr](){
4185 auto &memory = Module::Memory();
4186 const auto offset = reinterpret_cast<uint8_t*>(raw_ptr) - static_cast<uint8_t*>(memory.addr());
4187 M_insist(offset >= 0 and offset < memory.size(), "invalid raw pointer");
4188 return offset;
4189 }())
4190 { }
4191 PrimitiveExpr(const PrimitiveExpr&) = delete;
4194 PrimitiveExpr(PrimitiveExpr &other) : addr_(other.addr_), offset_(other.offset_) { /* move, not copy */ }
4197 PrimitiveExpr(PrimitiveExpr &&other) : addr_(other.addr_), offset_(other.offset_) { }
4198
4199 PrimitiveExpr & operator=(PrimitiveExpr&&) = delete;
4200
4203 static PrimitiveExpr Nullptr() { return PrimitiveExpr(PrimitiveExpr<uint32_t, 1>(0U)); }
4204
4205 private:
4207 ::wasm::Expression * expr() { return to<uint32_t>().expr(); }
4209 std::list<std::shared_ptr<Bit>> referenced_bits() { return addr_.referenced_bits(); }
4211 std::pair<::wasm::Expression*, std::list<std::shared_ptr<Bit>>> move() { return addr_.move(); }
4212
4213 public:
4216 explicit operator bool() const { return bool(addr_); }
4217
4219 PrimitiveExpr clone() const { return PrimitiveExpr(addr_.clone(), offset_); }
4220
4224 void discard() { addr_.discard(); }
4225
4226
4227 /*------------------------------------------------------------------------------------------------------------------
4228 * Conversion operations
4229 *----------------------------------------------------------------------------------------------------------------*/
4230
4231 public:
4234 template<dsl_pointer_to_primitive To, std::size_t ToL = L>
4235 requires (not std::is_void_v<std::remove_pointer_t<To>>)
4236 PrimitiveExpr<To, ToL> to() requires std::is_void_v<pointed_type> and (L == 1) {
4237 Wasm_insist((clone().template to<uint32_t>() % uint32_t(alignof(std::remove_pointer_t<To>))).eqz(),
4238 "cannot convert to type whose alignment requirement is not fulfilled");
4239 return PrimitiveExpr<To, ToL>(addr_.move(), offset_);
4240 }
4241
4244 template<typename To, std::size_t ToL = 1>
4245 requires std::same_as<To, uint32_t> and (ToL == 1)
4246 PrimitiveExpr<uint32_t, 1> to() {
4247 return offset_ ? (offset_ > 0 ? addr_ + uint32_t(offset_) : addr_ - uint32_t(-offset_)) : addr_;
4248 }
4249
4251 template<typename To, std::size_t ToL = 1>
4252 requires (not std::same_as<To, T>) and std::same_as<To, void*> and (ToL == 1)
4253 PrimitiveExpr<void*, 1> to() { return PrimitiveExpr<void*, 1>(addr_.move(), offset_); }
4254
4257 template<typename To, std::size_t ToL = L>
4258 requires std::same_as<To, T> and (L == ToL)
4259 PrimitiveExpr to() { return *this; }
4260
4261
4262 /*------------------------------------------------------------------------------------------------------------------
4263 * Hashing operations
4264 *----------------------------------------------------------------------------------------------------------------*/
4265
4266 PrimitiveExpr<uint64_t, L> hash() { return to<uint32_t>().hash(); }
4267
4268
4269 /*------------------------------------------------------------------------------------------------------------------
4270 * Pointer operations
4271 *----------------------------------------------------------------------------------------------------------------*/
4272
4273 public:
4275 PrimitiveExpr<bool, 1> is_nullptr() { return to<uint32_t>() == 0U; }
4276
4280 PrimitiveExpr<bool, 1> is_null() {
4282 return to<uint32_t>() == 0U;
4283 }
4284
4288 PrimitiveExpr<bool, 1> not_null() {
4290 return to<uint32_t>() != 0U;
4291 }
4292
4294 std::pair<PrimitiveExpr, PrimitiveExpr<bool, 1>> split() { auto cpy = clone(); return { cpy, is_nullptr() }; }
4295
4297 auto operator*() requires dsl_primitive<pointed_type> {
4298 Wasm_insist(not clone().is_nullptr(), "cannot dereference `nullptr`");
4299 return Reference<pointed_type, L>(*this);
4300 }
4301
4303 auto operator*() const requires dsl_primitive<pointed_type> {
4304 Wasm_insist(not clone().is_nullptr(), "cannot dereference `nullptr`");
4305 return ConstReference<pointed_type, L>(*this);
4306 }
4307
4309 PrimitiveExpr<pointed_type, L> operator->() const requires dsl_primitive<pointed_type> {
4310 return operator*(); // implicitly convert from ConstReference<pointed_type, L>
4311 }
4312
4313
4314 /*------------------------------------------------------------------------------------------------------------------
4315 * Pointer arithmetic
4316 *----------------------------------------------------------------------------------------------------------------*/
4317
4319 PrimitiveExpr operator+(PrimitiveExpr<offset_t, 1> delta) {
4320 if constexpr (std::is_void_v<pointed_type>) {
4321 return PrimitiveExpr(addr_ + delta.make_unsigned(), offset_);
4322 } else {
4323 const uint32_t log_size = std::countr_zero(sizeof(pointed_type));
4324 return PrimitiveExpr(addr_ + (delta.make_unsigned() << log_size), offset_);
4325 }
4326 }
4327
4329 PrimitiveExpr operator+(offset_t delta) {
4330 if constexpr (std::is_void_v<pointed_type>) {
4331 offset_ += delta; // in bytes
4332 } else {
4333 const uint32_t log_size = std::countr_zero(sizeof(pointed_type));
4334 offset_ += delta << log_size; // in elements
4335 }
4336 return *this;
4337 }
4338
4340 PrimitiveExpr operator-(PrimitiveExpr<offset_t, 1> delta) {
4341 if constexpr (std::is_void_v<pointed_type>) {
4342 return PrimitiveExpr(addr_ - delta.make_unsigned(), offset_);
4343 } else {
4344 const uint32_t log_size = std::countr_zero(sizeof(pointed_type));
4345 return PrimitiveExpr(addr_ - (delta.make_unsigned() << log_size), offset_);
4346 }
4347 }
4348
4350 PrimitiveExpr operator-(offset_t delta) {
4351 if constexpr (std::is_void_v<pointed_type>) {
4352 offset_ -= delta; // in bytes
4353 } else {
4354 const uint32_t log_size = std::countr_zero(sizeof(pointed_type));
4355 offset_ -= delta << log_size; // in elements
4356 }
4357 return *this;
4358 }
4359
4361 PrimitiveExpr<offset_t, 1> operator-(PrimitiveExpr other) {
4362 if constexpr (std::is_void_v<pointed_type>) {
4363 PrimitiveExpr<offset_t, 1> delta_addr = (this->addr_ - other.addr_).make_signed();
4364 offset_t delta_offset = this->offset_ - other.offset_;
4365 return (delta_offset ? (delta_addr + delta_offset) : delta_addr);
4366 } else {
4367 const int32_t log_size = std::countr_zero(sizeof(pointed_type));
4368 PrimitiveExpr<offset_t, 1> delta_addr = (this->addr_ - other.addr_).make_signed() >> log_size;
4369 offset_t delta_offset = (this->offset_ - other.offset_) >> log_size;
4370 return (delta_offset ? (delta_addr + delta_offset) : delta_addr);
4371
4372 }
4373 }
4374
4375#define CMP_OP(SYMBOL) \
4376 \
4377 PrimitiveExpr<bool, 1> operator SYMBOL(PrimitiveExpr other) { \
4378 return this->to<uint32_t>() SYMBOL other.to<uint32_t>(); \
4379 }
4380 CMP_OP(==)
4381 CMP_OP(!=)
4382 CMP_OP(<)
4383 CMP_OP(<=)
4384 CMP_OP(>)
4385 CMP_OP(>=)
4386#undef CMP_OP
4387
4388
4389 /*------------------------------------------------------------------------------------------------------------------
4390 * Load/Store operations
4391 *----------------------------------------------------------------------------------------------------------------*/
4392
4393 private:
4394 PrimitiveExpr<pointed_type, L> load() requires dsl_primitive<pointed_type> {
4395 M_insist(bool(addr_), "address already moved or discarded");
4396 if constexpr (L * sizeof(pointed_type) <= 16) {
4397 auto value = Module::Builder().makeLoad(
4398 /* bytes= */ M_CONSTEXPR_COND(L == 1, sizeof(pointed_type), 16),
4399 /* signed= */ std::is_signed_v<pointed_type>,
4400 /* offset= */ offset_ >= 0 ? offset_ : 0,
4401 /* align= */ alignof(pointed_type),
4402 /* ptr= */ offset_ >= 0 ? addr_.expr() : (addr_ - uint32_t(-offset_)).expr(),
4403 /* type= */ wasm_type<pointed_type, L>(),
4404 /* memory= */ Module::Get().memory_->name
4405 );
4406 return PrimitiveExpr<pointed_type, L>(value, addr_.referenced_bits());
4407 } else {
4408 using ResT = PrimitiveExpr<pointed_type, L>;
4409 std::array<typename ResT::vector_type, ResT::num_vectors> vectors;
4410 for (std::size_t idx = 0; idx < ResT::num_vectors; ++idx) {
4411 auto addr_cpy = addr_.clone();
4412 auto offset = offset_ + offset_t(idx * 16);
4413 auto value = Module::Builder().makeLoad(
4414 /* bytes= */ 16,
4415 /* signed= */ std::is_signed_v<pointed_type>,
4416 /* offset= */ offset >= 0 ? offset : 0,
4417 /* align= */ alignof(pointed_type),
4418 /* ptr= */ offset >= 0 ? addr_cpy.expr() : (addr_cpy - uint32_t(-offset)).expr(),
4419 /* type= */ ::wasm::Type(::wasm::Type::v128),
4420 /* memory= */ Module::Get().memory_->name
4421 );
4422 vectors[idx] = typename ResT::vector_type(value, addr_cpy.referenced_bits());
4423 }
4424 addr_.discard(); // since it was always cloned
4425 return ResT(std::move(vectors));
4426 }
4427 }
4428
4429 void store(PrimitiveExpr<pointed_type, L> value) requires dsl_primitive<pointed_type> {
4430 M_insist(bool(addr_), "address already moved or discarded");
4431 M_insist(bool(value), "value already moved or discarded");
4432 if constexpr (L * sizeof(pointed_type) <= 16) {
4433 auto e = Module::Builder().makeStore(
4434 /* bytes= */ M_CONSTEXPR_COND(L == 1, sizeof(pointed_type), 16),
4435 /* offset= */ offset_ >= 0 ? offset_ : 0,
4436 /* align= */ alignof(pointed_type),
4437 /* ptr= */ offset_ >= 0 ? addr_.expr() : (addr_ - uint32_t(-offset_)).expr(),
4438 /* value= */ value.expr(),
4439 /* type= */ wasm_type<pointed_type, L>(),
4440 /* memory= */ Module::Get().memory_->name
4441 );
4442 Module::Block().list.push_back(e);
4443 } else {
4444 auto vectors = value.vectors();
4445 for (std::size_t idx = 0; idx < PrimitiveExpr<pointed_type, L>::num_vectors; ++idx) {
4446 auto addr_cpy = addr_.clone();
4447 auto offset = offset_ + offset_t(idx * 16);
4448 auto e = Module::Builder().makeStore(
4449 /* bytes= */ 16,
4450 /* offset= */ offset >= 0 ? offset : 0,
4451 /* align= */ alignof(pointed_type),
4452 /* ptr= */ offset >= 0 ? addr_cpy.expr() : (addr_cpy - uint32_t(-offset)).expr(),
4453 /* value= */ vectors[idx].expr(),
4454 /* type= */ ::wasm::Type(::wasm::Type::v128),
4455 /* memory= */ Module::Get().memory_->name
4456 );
4457 Module::Block().list.push_back(e);
4458 }
4459 addr_.discard(); // since it was always cloned
4460 }
4461 }
4462
4463
4464 /*------------------------------------------------------------------------------------------------------------------
4465 * Printing
4466 *----------------------------------------------------------------------------------------------------------------*/
4467
4468 public:
4469 friend std::ostream & operator<<(std::ostream &out, const PrimitiveExpr &P) {
4470 out << "PrimitiveExpr<" << typeid(T).name() << "*, " << L << ">: " << P.addr_ << " [" << P.offset_ << "]";
4471 return out;
4472 }
4473
4474 void dump(std::ostream &out) const { out << *this << std::endl; }
4475 void dump() const { dump(std::cerr); }
4476};
4477
4478namespace detail {
4479
4480template<typename T>
4482
4483template<>
4484struct ptr_helper<void>
4485{
4487};
4488
4489template<typename T, std::size_t L>
4491{
4493};
4494
4495}
4496
4498template<typename T>
4500
4501
4502/*======================================================================================================================
4503 * Expr
4504 *====================================================================================================================*/
4505
4512template<dsl_primitive T, std::size_t L>
4513requires requires { PrimitiveExpr<T, L>(); PrimitiveExpr<bool, L>(); }
4514struct Expr<T, L>
4515{
4516 using type = T;
4517 static constexpr std::size_t num_simd_lanes = L;
4519
4520 /*----- Friends --------------------------------------------------------------------------------------------------*/
4521 template<typename, std::size_t> friend struct Expr; // to convert Expr<U, L> to Expr<T, L>
4522 template<typename, VariableKind, bool, std::size_t> friend class detail::variable_storage; // to use split_unsafe()
4523
4524 private:
4530
4531 public:
4533 Expr(PrimitiveExpr<T, L> value) : value_(value) {
4534 M_insist(bool(value_), "value must be present");
4535 }
4536
4539 : value_(value)
4540 , is_null_(is_null)
4541 {
4542 M_insist(bool(value_), "value must be present");
4543 if (is_null)
4545 }
4546
4549 : Expr(value.first, value.second)
4550 { }
4551
4553 template<dsl_primitive... Us>
4554 requires (sizeof...(Us) > 0) and requires (Us... us) { PrimitiveExpr<T, L>(us...); }
4555 explicit Expr(Us... value)
4556 : Expr(PrimitiveExpr<T, L>(value...))
4557 { }
4558
4560 template<decayable... Us>
4561 requires (sizeof...(Us) > 0) and (dsl_primitive<std::decay_t<Us>> and ...) and
4562 requires (Us... us) { Expr(std::decay_t<Us>(us)...); }
4563 explicit Expr(Us... value)
4564 : Expr(std::decay_t<Us>(value)...)
4565 { }
4566
4567 Expr(const Expr&) = delete;
4569 Expr(Expr &other) : Expr(other.split_unsafe()) { /* move, not copy */ }
4571 Expr(Expr &&other) : Expr(other.split_unsafe()) { }
4572
4573 Expr & operator=(Expr&&) = delete;
4574
4576 M_insist(not bool(value_), "value must be used or explicitly discarded");
4577 M_insist(not bool(is_null_), "NULL flag must be used or explicitly discarded");
4578 }
4579
4580 private:
4584 std::pair<PrimitiveExpr<T, L>, PrimitiveExpr<bool, L>> split_unsafe() {
4585 M_insist(bool(value_), "`Expr` has already been moved");
4586 return { value_, is_null_ };
4587 }
4588
4589 public:
4592 explicit operator bool() const { return bool(value_); }
4593
4596 M_insist(bool(value_), "`Expr` has already been moved");
4597 if (can_be_null())
4598 Wasm_insist(not is_null_, "must not be NULL");
4599 return value_;
4600 }
4601
4604 std::pair<PrimitiveExpr<T, L>, PrimitiveExpr<bool, L>> split() {
4605 M_insist(bool(value_), "`Expr` has already been moved");
4606 auto [value, is_null] = split_unsafe();
4607 if (is_null)
4608 return { value, is_null };
4609 else
4610 return { value, PrimitiveExpr<bool, L>(false) };
4611 }
4612
4614 Expr clone() const {
4615 M_insist(bool(value_), "`Expr` has already been moved`");
4616 return Expr(
4617 /* value= */ value_.clone(),
4618 /* is_null= */ is_null_ ? is_null_.clone() : PrimitiveExpr<bool, L>()
4619 );
4620 }
4621
4623 void discard() {
4624 value_.discard();
4625 if (can_be_null())
4626 is_null_.discard();
4627 }
4628
4629
4630 /*------------------------------------------------------------------------------------------------------------------
4631 * methods related to NULL
4632 *----------------------------------------------------------------------------------------------------------------*/
4633
4634 public:
4636 bool can_be_null() const { return bool(is_null_); }
4637
4640 value_.discard();
4641 if (can_be_null()) {
4643 return is_null_;
4644 } else {
4645 return PrimitiveExpr<bool, L>(false);
4646 }
4647 }
4648
4651 value_.discard();
4652 if (can_be_null()) {
4654 return not is_null_;
4655 } else {
4656 return PrimitiveExpr<bool, L>(true);
4657 }
4658 }
4659
4663 if (can_be_null())
4664 return value_ and not is_null_;
4665 else
4666 return value_;
4667 }
4668
4672 if (can_be_null())
4673 return not value_ and not is_null_;
4674 else
4675 return not value_;
4676 }
4677
4678
4679 /*------------------------------------------------------------------------------------------------------------------
4680 * Factory method for NULL
4681 *----------------------------------------------------------------------------------------------------------------*/
4682
4683 public:
4686
4687
4688 /*------------------------------------------------------------------------------------------------------------------
4689 * Conversion operations
4690 *----------------------------------------------------------------------------------------------------------------*/
4691
4692 public:
4695 template<dsl_primitive To, std::size_t ToL = L>
4696 requires requires { static_cast<PrimitiveExpr<To, ToL>>(value_); }
4697 operator Expr<To, ToL>() { return Expr<To, ToL>(static_cast<PrimitiveExpr<To, ToL>>(value_), is_null_); }
4698
4701 template<dsl_primitive To, std::size_t ToL = L>
4702 requires requires { value_.template to<To, ToL>(); }
4703 Expr<To, ToL> to() { return Expr<To, ToL>(value_.template to<To, ToL>(), is_null_); }
4704
4707 template<dsl_primitive To, std::size_t ToL = L>
4708 requires requires { value_.template reinterpret<To, ToL>(); }
4709 Expr<To, ToL> reinterpret() { return Expr<To, ToL>(value_.template reinterpret<To, ToL>(), is_null_); }
4710
4713 template<std::size_t ToL>
4714 requires requires { value_.template broadcast<ToL>(); is_null_.template broadcast<ToL>(); }
4716 return Expr<T, ToL>(value_.template broadcast<ToL>(), is_null_.template broadcast<ToL>());
4717 }
4718
4719
4720 /*------------------------------------------------------------------------------------------------------------------
4721 * Unary operations
4722 *----------------------------------------------------------------------------------------------------------------*/
4723
4724 public:
4726#define UNARY_LIST(X) \
4727 X(make_signed) \
4728 X(make_unsigned) \
4729 X(operator +) \
4730 X(operator -) \
4731 X(abs) \
4732 X(ceil) \
4733 X(floor) \
4734 X(trunc) \
4735 X(nearest) \
4736 X(sqrt) \
4737 X(operator ~) \
4738 X(clz) \
4739 X(ctz) \
4740 X(popcnt) \
4741 X(eqz) \
4742 X(operator not)
4743
4744#define UNARY(OP) \
4745 auto OP() requires requires { value_.OP(); } { \
4746 using PrimExprT = decltype(value_.OP()); \
4747 using ExprT = expr_t<PrimExprT>; \
4748 return ExprT(value_.OP(), is_null_); \
4749 }
4751#undef UNARY
4752
4753 /*----- Arithmetical operations with special three-valued logic --------------------------------------------------*/
4754
4755 auto add_pairwise() requires requires { value_.add_pairwise(); } {
4756 using PrimExprT = decltype(value_.add_pairwise());
4757 using ExprT = expr_t<PrimExprT>;
4758 if (can_be_null())
4759 return ExprT(value_.add_pairwise(), is_null_.template to<uint8_t>().add_pairwise().template to<bool>());
4760 else
4761 return ExprT(value_.add_pairwise());
4762 }
4763
4764 /*----- Bitwise operations with special three-valued logic -------------------------------------------------------*/
4765
4766 Expr<uint32_t, 1> bitmask() requires requires { value_.bitmask(); } {
4767 if (can_be_null())
4768 return Expr<uint32_t, 1>(value_.bitmask(), is_null_.any_true());
4769 else
4770 return Expr<uint32_t, 1>(value_.bitmask());
4771 }
4772
4773 /*----- Logical operations with special three-valued logic -------------------------------------------------------*/
4774
4775 Expr<bool, 1> any_true() requires requires { value_.any_true(); } {
4776 if (can_be_null())
4777 return Expr<bool, 1>(value_.any_true(), is_null_.any_true());
4778 else
4779 return Expr<bool, 1>(value_.any_true());
4780 }
4781
4782 Expr<bool, 1> all_true() requires requires { value_.all_true(); } {
4783 if (can_be_null())
4784 return Expr<bool, 1>(value_.all_true(), is_null_.any_true());
4785 else
4786 return Expr<bool, 1>(value_.all_true());
4787 }
4788
4789 /*----- Hashing operations with special three-valued logic -------------------------------------------------------*/
4790
4791 PrimitiveExpr<uint64_t, L> hash() requires requires { value_.hash(); } {
4792 if (can_be_null())
4793 return Select(is_null_, PrimitiveExpr<uint64_t, 1>(1UL << 63), value_.hash());
4794 else
4795 return value_.hash();
4796 }
4797
4798
4799 /*------------------------------------------------------------------------------------------------------------------
4800 * Binary operations
4801 *----------------------------------------------------------------------------------------------------------------*/
4802
4803 public:
4804#define BINARY(OP) \
4805 template<dsl_primitive U> \
4806 auto OP(Expr<U, L> other) requires requires { this->value_.OP(other.value_); } { \
4807 const unsigned idx = (other.can_be_null() << 1U) | this->can_be_null(); \
4808 auto result = this->value_.OP(other.value_); \
4809 using ReturnType = typename decltype(result)::type; \
4810 constexpr std::size_t ReturnLength = decltype(result)::num_simd_lanes; \
4811 switch (idx) { \
4812 default: M_unreachable("invalid index"); \
4813 case 0b00: /* neither `this` nor `other` can be `NULL` */ \
4814 return Expr<ReturnType, ReturnLength>(result); \
4815 case 0b01: /* `this` can be `NULL` */ \
4816 return Expr<ReturnType, ReturnLength>(result, this->is_null_); \
4817 case 0b10: /* `other` can be `NULL` */ \
4818 return Expr<ReturnType, ReturnLength>(result, other.is_null_); \
4819 case 0b11: /* both `this` and `other` can be `NULL` */ \
4820 return Expr<ReturnType, ReturnLength>(result, this->is_null_ or other.is_null_); \
4821 } \
4822 }
4823
4824 BINARY(operator +)
4825 BINARY(operator -)
4826 BINARY(operator *)
4827 BINARY(operator /)
4828 BINARY(operator %)
4829 BINARY(operator bitand)
4830 BINARY(operator bitor)
4831 BINARY(operator xor)
4832 BINARY(operator <<)
4833 BINARY(operator >>)
4834 BINARY(operator ==)
4835 BINARY(operator !=)
4836 BINARY(operator <)
4837 BINARY(operator <=)
4838 BINARY(operator >)
4839 BINARY(operator >=)
4840 BINARY(copy_sign)
4841 BINARY(min)
4842 BINARY(max)
4843 BINARY(avg)
4844 BINARY(rotl)
4845 BINARY(rotr)
4846#undef BINARY
4847
4848 /*----- Bitwise operations with special three-valued logic -------------------------------------------------------*/
4849
4850 template<dsl_primitive U>
4851 Expr operator<<(Expr<U, 1> other) requires requires { this->value_ << other.value_; } and (L > 1) {
4852 const unsigned idx = (bool(other.is_null_) << 1U) | bool(this->is_null_);
4853 PrimitiveExpr result = this->value_ << other.value_;
4854 switch (idx) {
4855 default: M_unreachable("invalid index");
4856
4857 case 0b00: { /* neither `this` nor `other` can be `NULL` */
4858 return Expr(result);
4859 }
4860 case 0b01: { /* `this` can be `NULL` */
4861 return Expr(result, this->is_null_);
4862 }
4863 case 0b10: { /* `other` can be `NULL` */
4864 PrimitiveExpr<bool, L> is_null = other.is_null_.template broadcast<L>();
4865 return Expr(result, is_null);
4866 }
4867 case 0b11: { /* both `this` and `other` can be `NULL` */
4868 PrimitiveExpr<bool, L> is_null = this->is_null_ or other.is_null_.template broadcast<L>();
4869 return Expr(result, is_null);
4870 }
4871 }
4872 }
4873
4874 template<dsl_primitive U>
4875 Expr operator>>(Expr<U, 1> other) requires requires { this->value_ >> other.value_; } and (L > 1) {
4876 const unsigned idx = (bool(other.is_null_) << 1U) | bool(this->is_null_);
4877 PrimitiveExpr result = this->value_ >> other.value_;
4878 switch (idx) {
4879 default: M_unreachable("invalid index");
4880
4881 case 0b00: { /* neither `this` nor `other` can be `NULL` */
4882 return Expr(result);
4883 }
4884 case 0b01: { /* `this` can be `NULL` */
4885 return Expr(result, this->is_null_);
4886 }
4887 case 0b10: { /* `other` can be `NULL` */
4888 PrimitiveExpr<bool, L> is_null = other.is_null_.template broadcast<L>();
4889 return Expr(result, is_null);
4890 }
4891 case 0b11: { /* both `this` and `other` can be `NULL` */
4892 PrimitiveExpr<bool, L> is_null = this->is_null_ or other.is_null_.template broadcast<L>();
4893 return Expr(result, is_null);
4894 }
4895 }
4896 }
4897
4898 /*----- Logical operations with special three-valued logic -------------------------------------------------------*/
4899
4902 Expr<bool, L> operator and(Expr<bool, L> other) requires requires { this->value_ and other.value_; } {
4903 const unsigned idx = (bool(other.is_null_) << 1U) | bool(this->is_null_);
4904 switch (idx) {
4905 default: M_unreachable("invalid index");
4906
4907 case 0b00: { /* neither `this` nor `other` can be `NULL` */
4908 PrimitiveExpr<bool, L> result = this->value_ and other.value_;
4909 return Expr<bool, L>(result);
4910 }
4911 case 0b01: { /* `this` can be `NULL` */
4912 PrimitiveExpr<bool, L> result = this->value_ and other.value_.clone();
4913 PrimitiveExpr<bool, L> is_null =
4914 this->is_null_ and // `this` is NULL
4915 other.value_; // `other` does not dominate, i.e. is true
4916 return Expr<bool, L>(result, is_null);
4917 }
4918 case 0b10: { /* `other` can be `NULL` */
4919 PrimitiveExpr<bool, L> result = this->value_.clone() and other.value_;
4920 PrimitiveExpr<bool, L> is_null =
4921 other.is_null_ and // `other` is NULL
4922 this->value_; // `this` does not dominate, i.e. is true
4923 return Expr<bool, L>(result, is_null);
4924 }
4925 case 0b11: { /* both `this` and `other` can be `NULL` */
4926 auto this_is_null = this->is_null_.clone();
4927 auto other_is_null = other.is_null_.clone();
4928 PrimitiveExpr<bool, L> result = this->value_.clone() and other.value_.clone();
4929 PrimitiveExpr<bool, L> is_null =
4930 (this_is_null or other_is_null) and // at least one is NULL
4931 (this->value_ or this->is_null_) and // `this` does not dominate, i.e. is not real false
4932 (other.value_ or other.is_null_); // `other` does not dominate, i.e. is not real false
4933 return Expr<bool, L>(result, is_null);
4934 }
4935 }
4936 }
4937
4940 Expr<bool, L> and_not(Expr<bool, L> other) requires requires { this->value_.and_not(other.value_); } {
4941 const unsigned idx = (bool(other.is_null_) << 1U) | bool(this->is_null_);
4942 switch (idx) {
4943 default: M_unreachable("invalid index");
4944
4945 case 0b00: { /* neither `this` nor `other` can be `NULL` */
4946 PrimitiveExpr<bool, L> result = this->value_.and_not(other.value_);
4947 return Expr<bool, L>(result);
4948 }
4949 case 0b01: { /* `this` can be `NULL` */
4950 PrimitiveExpr<bool, L> result = this->value_.and_not(other.value_.clone());
4951 PrimitiveExpr<bool, L> is_null =
4952 this->is_null_.and_not( // `this` is NULL
4953 other.value_ // `other` does not dominate, i.e. is false
4954 );
4955 return Expr<bool, L>(result, is_null);
4956 }
4957 case 0b10: { /* `other` can be `NULL` */
4958 PrimitiveExpr<bool, L> result = this->value_.clone().and_not(other.value_);
4959 PrimitiveExpr<bool, L> is_null =
4960 other.is_null_ and // `other` is NULL
4961 this->value_; // `this` does not dominate, i.e. is true
4962 return Expr<bool, L>(result, is_null);
4963 }
4964 case 0b11: { /* both `this` and `other` can be `NULL` */
4965 auto this_is_null = this->is_null_.clone();
4966 auto other_is_null = other.is_null_.clone();
4967 PrimitiveExpr<bool, L> result = this->value_.clone().and_not(other.value_.clone());
4968 PrimitiveExpr<bool, L> is_null =
4969 (this_is_null or other_is_null) and // at least one is NULL
4970 (this->value_ or this->is_null_) and // `this` does not dominate, i.e. is not real false
4971 (not other.value_ or other.is_null_); // `other` does not dominate, i.e. is not real true
4972 return Expr<bool, L>(result, is_null);
4973 }
4974 }
4975 }
4976
4979 Expr<bool, L> operator or(Expr<bool, L> other) requires requires { this->value_ or other.value_; } {
4980 const unsigned idx = (bool(other.is_null_) << 1U) | bool(this->is_null_);
4981 switch (idx) {
4982 default: M_unreachable("invalid index");
4983
4984 case 0b00: { /* neither `this` nor `other` can be `NULL` */
4985 PrimitiveExpr<bool, L> result = this->value_ or other.value_;
4986 return Expr<bool, L>(result);
4987 }
4988 case 0b01: { /* `this` can be `NULL` */
4989 PrimitiveExpr<bool, L> result = this->value_ or other.value_.clone();
4990 PrimitiveExpr<bool, L> is_null =
4991 this->is_null_ and // `this` is NULL
4992 not other.value_; // `other` does not dominate, i.e. is false
4993 return Expr<bool, L>(result, is_null);
4994 }
4995 case 0b10: { /* `other` can be `NULL` */
4996 PrimitiveExpr<bool, L> result = this->value_.clone() or other.value_;
4997 PrimitiveExpr<bool, L> is_null =
4998 other.is_null_ and // `other` is NULL
4999 not this->value_; // `this` does not dominate, i.e. is false
5000 return Expr<bool, L>(result, is_null);
5001 }
5002 case 0b11: { /* both `this` and `other` can be `NULL` */
5003 auto this_is_null = this->is_null_.clone();
5004 auto other_is_null = other.is_null_.clone();
5005 PrimitiveExpr<bool, L> result = this->value_.clone() or other.value_.clone();
5006 PrimitiveExpr<bool, L> is_null =
5007 (this_is_null or other_is_null) and // at least one is NULL
5008 (not this->value_ or this->is_null_) and // `this` does not dominate, i.e. is not real true
5009 (not other.value_ or other.is_null_); // `other` does not dominate, i.e. is not real true
5010 return Expr<bool, L>(result, is_null);
5011 }
5012 }
5013 }
5014
5015
5016 /*------------------------------------------------------------------------------------------------------------------
5017 * Modifications
5018 *----------------------------------------------------------------------------------------------------------------*/
5019
5021 template<std::size_t M>
5022 Expr<T, 1> extract() requires requires { value_.template extract<M>(); } {
5023 if (can_be_null())
5024 return Expr<T, 1>(value_.template extract<M>(), is_null_.template extract<M>());
5025 else
5026 return Expr<T, 1>(value_.template extract<M>());
5027 }
5028
5030 template<std::size_t M, expr_convertible U>
5031 requires requires (expr_t<U> u) { Expr<T, 1>(u); }
5032 Expr replace(U &&_value) requires requires (Expr<T, 1> e) { this->value_.template replace<M>(e.value_); } {
5033 Expr<T, 1> value(expr_t<U>(std::forward<U>(_value)));
5034 if (can_be_null()) {
5035 auto [value_val, value_is_null] = value.split();
5036 return Expr(value_.template replace<M>(value_val), is_null_.template replace<M>(value_is_null));
5037 } else {
5038 M_insist(not value.can_be_null(), "cannot replace a non-nullable value with a nullable one");
5039 return Expr(value_.template replace<M>(value.value_));
5040 }
5041 }
5042
5046 requires (sizeof(T) == 1) and requires { value_.swizzle_bytes(indices); } {
5047 if (can_be_null()) {
5048 auto indices_cpy = indices.clone();
5049 return Expr(value_.swizzle_bytes(indices), is_null_.swizzle_bytes(indices_cpy));
5050 } else {
5051 return Expr(value_.swizzle_bytes(indices));
5052 }
5053 }
5056 template<std::size_t M>
5057 Expr<T, M / sizeof(T)> swizzle_bytes(const std::array<uint8_t, M> &indices)
5058 requires (sizeof(T) == 1) and requires { value_.swizzle_bytes(indices); } {
5059 if (can_be_null())
5060 return Expr<T, M / sizeof(T)>(value_.swizzle_bytes(indices), is_null_.swizzle_bytes(indices));
5061 else
5062 return Expr<T, M / sizeof(T)>(value_.swizzle_bytes(indices));
5063 }
5064
5067 template<std::size_t M>
5068 Expr<T, M> swizzle_lanes(const std::array<uint8_t, M> &indices)
5069 requires requires { value_.swizzle_lanes(indices); } {
5070 if (can_be_null())
5071 return Expr<T, M>(value_.swizzle_lanes(indices), is_null_.swizzle_lanes(indices));
5072 else
5073 return Expr<T, M>(value_.swizzle_lanes(indices));
5074 }
5075
5076
5077
5078 /*------------------------------------------------------------------------------------------------------------------
5079 * Printing
5080 *----------------------------------------------------------------------------------------------------------------*/
5081
5082 public:
5083 friend std::ostream & operator<<(std::ostream &out, const Expr &E) {
5084 out << "Expr<" << typeid(type).name() << "," << num_simd_lanes << ">: value_=" << E.value_
5085 << ", is_null_=" << E.is_null_;
5086 return out;
5087 }
5088
5089 void dump(std::ostream &out) const { out << *this << std::endl; }
5090 void dump() const { dump(std::cerr); }
5091};
5092
5094template<typename T, std::size_t L>
5096
5097/*----- Forward binary operators on operands convertible to Expr<T, L> -----------------------------------------------*/
5098#define MAKE_BINARY(OP) \
5099 template<expr_convertible T, expr_convertible U> \
5100 requires (not primitive_convertible<T> or not primitive_convertible<U>) and \
5101 requires (expr_t<T> t, expr_t<U> u) { t.OP(u); } \
5102 auto OP(T &&t, U &&u) \
5103 { \
5104 return expr_t<T>(std::forward<T>(t)).OP(expr_t<U>(std::forward<U>(u))); \
5105 }
5107#undef MAKE_BINARY
5108
5109/*----- Short aliases for all `PrimitiveExpr` and `Expr` types. ------------------------------------------------------*/
5110#define USING_N(TYPE, LENGTH, NAME) \
5111 using NAME = PrimitiveExpr<TYPE, LENGTH>; \
5112 using _ ## NAME = Expr<TYPE, LENGTH>;
5113#define USING(TYPE, NAME) \
5114 template<std::size_t L> using NAME = PrimitiveExpr<TYPE, L>; \
5115 template<std::size_t L> using _ ## NAME = Expr<TYPE, L>; \
5116 USING_N(TYPE, 1, NAME ## x1) \
5117 USING_N(TYPE, 2, NAME ## x2) \
5118 USING_N(TYPE, 4, NAME ## x4) \
5119 USING_N(TYPE, 8, NAME ## x8) \
5120 USING_N(TYPE, 16, NAME ## x16) \
5121 USING_N(TYPE, 32, NAME ## x32)
5122
5123 USING(bool, Bool)
5124 USING(int8_t, I8)
5125 USING(uint8_t, U8)
5126 USING(int16_t, I16)
5127 USING(uint16_t, U16)
5128 USING(int32_t, I32)
5129 USING(uint32_t, U32)
5130 USING(int64_t, I64)
5131 USING(uint64_t, U64)
5132 USING(float, Float)
5133 USING(double, Double)
5135 USING_N(char, 1, Charx1)
5136 USING_N(char, 16, Charx16)
5137 USING_N(char, 32, Charx32)
5138#undef USING
5139
5140
5141/*======================================================================================================================
5142 * Variable
5143 *====================================================================================================================*/
5144
5145namespace detail {
5146
5149template<dsl_primitive T, std::size_t L>
5150::wasm::Index allocate_local()
5151{
5152 ::wasm::Function &fn = Module::Function();
5153 const ::wasm::Index index = fn.getNumParams() + fn.vars.size();
5154 const ::wasm::Type type = wasm_type<T, L>();
5155 fn.vars.emplace_back(type); // allocate new local variable
5156 M_insist(fn.isVar(index));
5157 M_insist(fn.getLocalType(index) == type);
5158 return index;
5159}
5160
5164template<typename T, VariableKind Kind, bool CanBeNull, std::size_t L>
5165class variable_storage;
5166
5168template<dsl_primitive T, VariableKind Kind, std::size_t L>
5169requires arithmetic<T> or (boolean<T> and Kind == VariableKind::Param)
5170class variable_storage<T, Kind, /* CanBeNull= */ false, L>
5171{
5173 static constexpr std::size_t num_locals = ((L * sizeof(T)) + 15) / 16;
5174 static_assert(Kind != VariableKind::Param or num_locals == 1, "parameters must fit in a single Wasm local");
5175
5176 template<typename, VariableKind, bool, std::size_t>
5177 friend class variable_storage; // to enable use in other `variable_storage`
5178 friend struct Variable<T, Kind, false, L>; // to be usable by the respective Variable
5179
5180 std::array<::wasm::Index, num_locals> indices_;
5181 ::wasm::Type type_;
5182
5184 variable_storage()
5185 : indices_([](){
5186 std::array<::wasm::Index, num_locals> indices;
5187 for (std::size_t idx = 0; idx < num_locals; ++idx)
5188 indices[idx] = allocate_local<T, L>();
5189 return indices;
5190 }())
5191 , type_(wasm_type<T, L>())
5192 { }
5193
5194 variable_storage(const variable_storage&) = delete;
5195 variable_storage(variable_storage&&) = default;
5196 variable_storage & operator=(variable_storage&&) = default;
5197
5199 variable_storage(::wasm::Index idx, tag<int>)
5200 requires (num_locals == 1)
5201 : indices_(std::to_array({ idx })), type_(wasm_type<T, L>())
5202 {
5203#ifndef NDEBUG
5204 ::wasm::Function &fn = Module::Function();
5205 M_insist(fn.isParam(indices_[0]));
5206 M_insist(fn.getLocalType(indices_[0]) == type_);
5207#endif
5208 }
5209
5211 template<typename... Us>
5212 requires (sizeof...(Us) > 0) and requires (Us... us) { PrimitiveExpr<T, L>(us...); }
5213 explicit variable_storage(Us... value) : variable_storage() { operator=(PrimitiveExpr<T, L>(value...)); }
5214
5216 template<primitive_convertible U>
5217 requires requires (U &&u) { PrimitiveExpr<T, L>(primitive_expr_t<U>(std::forward<U>(u))); }
5218 explicit variable_storage(U &&value) : variable_storage() { operator=(std::forward<U>(value)); }
5219
5221 template<primitive_convertible U>
5222 requires requires (U &&u) { PrimitiveExpr<T, L>(primitive_expr_t<U>(std::forward<U>(u))); }
5223 void operator=(U &&_value) requires (num_locals == 1) {
5224 PrimitiveExpr<T, L> value(primitive_expr_t<U>(std::forward<U>(_value)));
5225 Module::Block().list.push_back(Module::Builder().makeLocalSet(indices_[0], value.expr()));
5226 }
5228 template<primitive_convertible U>
5229 requires requires (U &&u) { PrimitiveExpr<T, L>(primitive_expr_t<U>(std::forward<U>(u))); }
5230 void operator=(U &&_value) requires (num_locals > 1) {
5231 PrimitiveExpr<T, L> value(primitive_expr_t<U>(std::forward<U>(_value)));
5232 static_assert(num_locals == decltype(value)::num_vectors);
5233 auto vectors = value.vectors();
5234 for (std::size_t idx = 0; idx < num_locals; ++idx)
5235 Module::Block().list.push_back(Module::Builder().makeLocalSet(indices_[idx], vectors[idx].expr()));
5236 }
5237
5239 operator PrimitiveExpr<T, L>() const requires (num_locals == 1) {
5240 return PrimitiveExpr<T, L>(Module::Builder().makeLocalGet(indices_[0], type_));
5241 }
5243 operator PrimitiveExpr<T, L>() const requires (num_locals > 1) {
5244 static_assert(num_locals == PrimitiveExpr<T, L>::num_vectors);
5245 std::array<typename PrimitiveExpr<T, L>::vector_type, num_locals> vectors;
5246 for (std::size_t idx = 0; idx < num_locals; ++idx)
5248 Module::Builder().makeLocalGet(indices_[idx], type_)
5249 );
5250 return PrimitiveExpr<T, L>(std::move(vectors));
5251 }
5252};
5253
5255template<std::size_t L>
5256class variable_storage<bool, VariableKind::Local, /* CanBeNull= */ false, L>
5257{
5259 static constexpr std::size_t bit_num_simd_lanes = std::min<std::size_t>(L, 16);
5261 static constexpr std::size_t num_locals = (L + 15) / 16;
5262
5263 template<typename, VariableKind, bool, std::size_t>
5264 friend class variable_storage; // to enable use in other `variable_storage`
5265 friend struct Variable<bool, VariableKind::Local, false, L>; // to be usable by the respective Variable
5266
5268 std::array<std::shared_ptr<LocalBit<bit_num_simd_lanes>>, num_locals> values_;
5269
5271 variable_storage(); // impl delayed because `LocalBit` defined later
5272
5273 variable_storage(const variable_storage&) = delete;
5274 variable_storage(variable_storage&&) = default;
5275 variable_storage & operator=(variable_storage&&) = default;
5276
5278 template<typename... Us>
5279 requires (sizeof...(Us) > 0) and requires (Us... us) { PrimitiveExpr<bool, L>(us...); }
5280 explicit variable_storage(Us... value) : variable_storage() { operator=(PrimitiveExpr<bool, L>(value...)); }
5281
5283 template<primitive_convertible U>
5284 requires requires (U &&u) { PrimitiveExpr<bool, L>(primitive_expr_t<U>(std::forward<U>(u))); }
5285 explicit variable_storage(U &&value) : variable_storage() { operator=(std::forward<U>(value)); }
5286
5288 template<primitive_convertible U>
5289 requires requires (U &&u) { PrimitiveExpr<bool, L>(primitive_expr_t<U>(std::forward<U>(u))); }
5290 void operator=(U &&value); // impl delayed because `LocalBit` defined later
5291
5292 /* Set this value to `true`. */
5293 void set_true();
5294
5295 /* Set this value to `false`. */
5296 void set_false();
5297
5299 operator PrimitiveExpr<bool, L>() const; // impl delayed because `LocalBit` defined later
5300};
5301
5303template<dsl_primitive T, std::size_t L>
5304class variable_storage<T, VariableKind::Local, /* CanBeNull= */ true, L>
5305{
5306 friend struct Variable<T, VariableKind::Local, true, L>; // to be usable by the respective Variable
5307
5308 variable_storage<T, VariableKind::Local, false, L> value_;
5309 variable_storage<bool, VariableKind::Local, false, L> is_null_;
5310
5312 variable_storage() { M_insist_no_ternary_logic(); }
5313
5315 template<typename... Us>
5316 requires (sizeof...(Us) > 0) and requires (Us... us) { Expr<T, L>(us...); }
5317 explicit variable_storage(Us... value) : variable_storage() { operator=(Expr<T, L>(value...)); }
5318
5320 template<expr_convertible U>
5321 requires requires (U &&u) { Expr<T, L>(expr_t<U>(std::forward<U>(u))); }
5322 explicit variable_storage(U &&value) : variable_storage() { operator=(std::forward<U>(value)); }
5323
5325 template<expr_convertible U>
5326 requires requires (U &&u) { Expr<T, L>(expr_t<U>(std::forward<U>(u))); }
5327 void operator=(U &&_value) {
5328 Expr<T, L> value(expr_t<U>(std::forward<U>(_value)));
5329 auto [val, is_null] = value.split_unsafe();
5330 this->value_ = val;
5331 this->is_null_ = bool(is_null) ? is_null : PrimitiveExpr<bool, L>(false);
5332 }
5333
5335 void set_true()
5336 requires boolean<T>
5337 {
5338 value_.set_true();
5339 is_null_.set_false();
5340 }
5341
5343 void set_false()
5344 requires boolean<T>
5345 {
5346 value_.set_false();
5347 is_null_.set_false();
5348 }
5349
5351 void set_null() { is_null_.set_true(); }
5352
5354 operator Expr<T, L>() const { return Expr<T, L>(PrimitiveExpr<T, L>(value_), PrimitiveExpr<bool, L>(is_null_)); }
5355};
5356
5358template<dsl_pointer_to_primitive T, VariableKind Kind, std::size_t L>
5359requires (Kind != VariableKind::Global)
5360class variable_storage<T, Kind, /* CanBeNull= */ false, L>
5361{
5362 friend struct Variable<T, Kind, false, L>; // to be usable by the respective Variable
5363
5365 variable_storage<uint32_t, Kind, false, 1> addr_;
5366
5368 variable_storage() = default;
5369
5371 explicit variable_storage(::wasm::Index idx, tag<int> tag) : addr_(idx, tag) { }
5372
5374 template<primitive_convertible U>
5375 requires requires (U &&u) { PrimitiveExpr<T, L>(primitive_expr_t<U>(std::forward<U>(u))); }
5376 explicit variable_storage(U &&value) : variable_storage() { operator=(std::forward<U>(value)); }
5377
5379 template<primitive_convertible U>
5380 requires requires (U &&u) { PrimitiveExpr<T, L>(primitive_expr_t<U>(std::forward<U>(u))); }
5381 void operator=(U &&value) {
5382 addr_ = PrimitiveExpr<T, L>(primitive_expr_t<U>(std::forward<U>(value))).template to<uint32_t, 1>();
5383 }
5384
5386 operator PrimitiveExpr<T, L>() const { return PrimitiveExpr<uint32_t, 1>(addr_).template to<T, L>(); }
5387};
5388
5391template<typename T, std::size_t L>
5393class variable_storage<T, VariableKind::Global, /* CanBeNull= */ false, L>
5394{
5396 static constexpr std::size_t num_globals = dsl_primitive<T> ? ((L * sizeof(T)) + 15) / 16 : 1;
5397
5398 friend struct Variable<T, VariableKind::Global, false, L>; // to be usable by the respective Variable
5399
5400 std::array<::wasm::Name, num_globals> names_;
5401 ::wasm::Type type_;
5402
5404 variable_storage()
5405 requires dsl_primitive<T>
5406 : variable_storage(T())
5407 { }
5408
5409 variable_storage(const variable_storage&) = delete;
5410 variable_storage(variable_storage&&) = default;
5411 variable_storage & operator=(variable_storage&&) = default;
5412
5414 template<typename... Us>
5415 requires (sizeof...(Us) > 0) and requires (Us... us) { Module::Get().emit_global<T, L>(names_, true, us...); }
5416 explicit variable_storage(Us... init)
5417 requires dsl_primitive<T>
5418 : names_([](){
5419 std::array<::wasm::Name, num_globals> names;
5420 for (std::size_t idx = 0; idx < num_globals; ++idx)
5421 names[idx] = Module::Unique_Global_Name();
5422 return names;
5423 }())
5424 , type_(wasm_type<T, L>())
5425 {
5426 Module::Get().emit_global<T, L>(names_, true, init...);
5427 }
5429 explicit variable_storage(uint32_t init = 0)
5431 : names_(std::to_array<::wasm::Name>({ Module::Unique_Global_Name() }))
5432 , type_(wasm_type<T, L>())
5433 {
5434 Module::Get().emit_global<T, L>(names_[0], true, init);
5435 }
5436
5438 template<dsl_primitive... Us>
5439 requires (sizeof...(Us) > 0) and requires (Us... us) { make_literal<T, L>(us...); }
5440 void init(Us... init) requires dsl_primitive<T> and (num_globals == 1) {
5441 Module::Get().module_.getGlobal(names_[0])->init = Module::Builder().makeConst(make_literal<T, L>(init...));
5442 }
5444 template<dsl_primitive... Us>
5445 requires (sizeof...(Us) > 0) and
5446 requires (Us... us) { { make_literal<T, L>(us...) } -> std::same_as<std::array<::wasm::Literal, num_globals>>; }
5447 void init(Us... init) requires dsl_primitive<T> and (num_globals > 1) {
5448 auto literals = make_literal<T, L>(init...);
5449 for (std::size_t idx = 0; idx < num_globals; ++idx)
5450 Module::Get().module_.getGlobal(names_[idx])->init = Module::Builder().makeConst(literals[idx]);
5451 }
5453 void init(uint32_t init) requires dsl_pointer_to_primitive<T> {
5454 Module::Get().module_.getGlobal(names_[0])->init = Module::Builder().makeConst(::wasm::Literal(init));
5455 }
5457 template<primitive_convertible U>
5458 requires requires (U &&u) { PrimitiveExpr<T, L>(primitive_expr_t<U>(std::forward<U>(u))); }
5459 void init(U &&_init) requires (num_globals == 1) {
5460 PrimitiveExpr<T, L> init(primitive_expr_t<U>(std::forward<U>(_init)));
5461 Module::Get().module_.getGlobal(names_[0])->init = init.expr();
5462 }
5463
5465 template<primitive_convertible U>
5466 requires requires (U &&u) { PrimitiveExpr<T, L>(primitive_expr_t<U>(std::forward<U>(u))); }
5467 void operator=(U &&_value) requires (num_globals == 1) {
5468 PrimitiveExpr<T, L> value(primitive_expr_t<U>(std::forward<U>(_value)));
5469 Module::Block().list.push_back(Module::Builder().makeGlobalSet(names_[0], value.expr()));
5470 }
5472 template<primitive_convertible U>
5473 requires requires (U &&u) { PrimitiveExpr<T, L>(primitive_expr_t<U>(std::forward<U>(u))); }
5474 void operator=(U &&_value) requires (num_globals > 1) {
5475 PrimitiveExpr<T, L> value(primitive_expr_t<U>(std::forward<U>(_value)));
5476 static_assert(num_globals == decltype(value)::num_vectors);
5477 auto vectors = value.vectors();
5478 for (std::size_t idx = 0; idx < num_globals; ++idx)
5479 Module::Block().list.push_back(Module::Builder().makeGlobalSet(names_[idx], vectors[idx].expr()));
5480 }
5481
5483 operator PrimitiveExpr<T, L>() const requires (num_globals == 1) {
5484 return PrimitiveExpr<T, L>(Module::Builder().makeGlobalGet(names_[0], type_));
5485 }
5487 operator PrimitiveExpr<T, L>() const requires (num_globals > 1) {
5488 static_assert(num_globals == PrimitiveExpr<T, L>::num_vectors);
5489 std::array<typename PrimitiveExpr<T, L>::vector_type, num_globals> vectors;
5490 for (std::size_t idx = 0; idx < num_globals; ++idx)
5492 Module::Builder().makeGlobalGet(names_[idx], type_)
5493 );
5494 return PrimitiveExpr<T, L>(std::move(vectors));
5495 }
5496};
5497
5498}
5499
5500template<typename T, VariableKind Kind, bool CanBeNull, std::size_t L>
5501requires (not (dsl_pointer_to_primitive<T> and CanBeNull)) and // pointers cannot be NULL
5502 (not (Kind == VariableKind::Global and CanBeNull)) and // globals cannot be NULL
5503requires { typename std::conditional_t<CanBeNull, Expr<T, L>, PrimitiveExpr<T, L>>; }
5504struct Variable<T, Kind, CanBeNull, L>
5505{
5506 using type = T;
5507 static constexpr std::size_t num_simd_lanes = L;
5508 template<typename X>
5511
5512 private:
5517#ifdef M_ENABLE_SANITY_FIELDS
5519 mutable bool used_ = false;
5520#define REGISTER_USE(VAR) (VAR).used_ = true
5521#else
5522#define REGISTER_USE(VAR)
5523#endif
5524
5525 public:
5526 friend void swap(Variable &first, Variable &second) {
5527 using std::swap;
5528 swap(first.storage_, second.storage_);
5529#ifdef M_ENABLE_SANITY_FIELDS
5530 swap(first.used_, second.used_);
5531#endif
5532 }
5533
5535 Variable() = default;
5536
5537 Variable(const Variable&) = delete;
5539 : storage_(std::forward<storage_type>(other.storage_))
5540#ifdef M_ENABLE_SANITY_FIELDS
5541 , used_(other.used_)
5542#endif
5543 {
5544 REGISTER_USE(other);
5545 }
5546
5547 Variable & operator=(const Variable &other) { operator=(other.val()); return *this; }
5548 Variable & operator=(Variable &&other) { operator=(other.val()); return *this; }
5549
5551#ifdef M_ENABLE_SANITY_FIELDS
5552 M_insist(used_, "variable must be used at least once");
5553#endif
5554 }
5555
5557 template<typename... Us>
5558 requires requires (Us&&... us) { storage_type(std::forward<Us>(us)...); }
5559 explicit Variable(Us&&... value) : storage_(std::forward<Us>(value)...) { }
5560
5561 protected:
5564 Variable(::wasm::Index idx, tag<int> tag)
5565 requires (Kind == VariableKind::Param)
5566 : storage_(idx, tag)
5567 { }
5568
5569 public:
5572 constexpr bool has_null_bit() const { return CanBeNull; }
5574 bool can_be_null() const {
5575 if constexpr (CanBeNull)
5576 return dependent_expr_type(*this).can_be_null();
5577 else
5578 return false;
5579 }
5580
5584 dependent_expr_type val() const { REGISTER_USE(*this); return dependent_expr_type(storage_); }
5585
5588 operator dependent_expr_type() const { REGISTER_USE(*this); return dependent_expr_type(storage_); }
5589
5590 template<typename U>
5591 requires requires (dependent_expr_type v) { dependent_expr_t<U>(v); }
5593
5594 template<typename To, std::size_t ToL = L>
5595 requires requires (dependent_expr_type v) { v.template to<To, ToL>(); }
5596 dependent_expr_t<PrimitiveExpr<To, ToL>> to() const { return dependent_expr_type(*this).template to<To, ToL>(); }
5597
5598 template<typename To, std::size_t ToL = L>
5599 requires requires (dependent_expr_type v) { v.template reinterpret<To, ToL>(); }
5601 return dependent_expr_type(*this).template reinterpret<To, ToL>();
5602 }
5603
5604 template<std::size_t ToL>
5605 requires requires (dependent_expr_type v) { v.template broadcast<ToL>(); }
5607 return dependent_expr_type(*this).template broadcast<ToL>();
5608 }
5609
5610 template<typename... Us>
5611 requires requires (Us&&... us) { storage_.init(std::forward<Us>(us)...); }
5612 void init(Us&&... init) { storage_.init(std::forward<Us>(init)...); }
5613
5614 template<typename U>
5615 requires requires (U &&u) { storage_ = std::forward<U>(u); }
5616 Variable & operator=(U &&value) { storage_ = std::forward<U>(value); return *this; }
5617
5619 requires requires (storage_type s) { { s.set_true() } -> std::same_as<void>; }
5620 {
5621 storage_.set_true();
5622 }
5623
5625 requires requires (storage_type s) { { s.set_false() } -> std::same_as<void>; }
5626 {
5627 storage_.set_false();
5628 }
5629
5631 requires requires (storage_type s) { { s.set_null() } -> std::same_as<void>; }
5632 {
5633 storage_.set_null();
5634 }
5635
5636
5637 /*------------------------------------------------------------------------------------------------------------------
5638 * Forward operators on Variable<T, L>
5639 *----------------------------------------------------------------------------------------------------------------*/
5640
5641 /*----- Unary operators ------------------------------------------------------------------------------------------*/
5642#define UNARY(OP) \
5643 auto OP() const requires requires (dependent_expr_type e) { e.OP(); } { return dependent_expr_type(*this).OP(); }
5644
5646 UNARY(add_pairwise) // from PrimitiveExpr and Expr
5647 UNARY(bitmask) // from PrimitiveExpr and Expr
5648 UNARY(any_true) // from PrimitiveExpr and Expr
5649 UNARY(all_true) // from PrimitiveExpr and Expr
5650 UNARY(hash) // from PrimitiveExpr and Expr
5651 UNARY(operator *) // from PrimitiveExpr for pointers
5652 UNARY(operator ->) // from PrimitiveExpr for pointers
5653 UNARY(is_nullptr) // from PrimitiveExpr for pointers
5654 UNARY(is_null) // from Expr
5655 UNARY(not_null) // from Expr
5656 UNARY(is_true_and_not_null) // from Expr
5657 UNARY(is_false_and_not_null) // from Expr
5658#undef UNARY
5659
5660 /*----- Assignment operators -------------------------------------------------------------------------------------*/
5661#define ASSIGNOP_LIST(X) \
5662 X(+) \
5663 X(-) \
5664 X(*) \
5665 X(/) \
5666 X(%) \
5667 X(&) \
5668 X(|) \
5669 X(^) \
5670 X(<<) \
5671 X(>>)
5672
5673#define ASSIGNOP(SYMBOL) \
5674 template<typename U> \
5675 requires requires { typename dependent_expr_t<U>; } and \
5676 requires (U &&u) { dependent_expr_t<U>(std::forward<U>(u)); } and \
5677 requires (dependent_expr_type var_value, dependent_expr_t<U> other_value) \
5678 { var_value SYMBOL other_value; } and \
5679 requires (Variable var, \
5680 decltype(std::declval<dependent_expr_type>() SYMBOL std::declval<dependent_expr_t<U>>()) value) \
5681 { var = value; } \
5682 Variable & operator SYMBOL##= (U &&value) { \
5683 dependent_expr_t<U> _value(std::forward<U>(value)); \
5684 this->operator=(dependent_expr_type(*this) SYMBOL _value); \
5685 return *this; \
5686 }
5688#undef ASSIGNOP
5689
5690 /*----- Modifications --------------------------------------------------------------------------------------------*/
5692 template<std::size_t M>
5693 auto extract() const requires requires (dependent_expr_type e) { e.template extract<M>(); } {
5694 return dependent_expr_type(*this).template extract<M>();
5695 }
5696
5698 template<std::size_t M, typename U>
5699 Variable & replace(U &&value)
5700 requires requires (dependent_expr_type e) { e.template replace<M>(std::forward<U>(value)); } {
5701 this->operator=(dependent_expr_type(*this).template replace<M>(std::forward<U>(value)));
5702 return *this;
5703 }
5704
5708 requires requires (dependent_expr_type e) { e.swizzle_bytes(indices); } {
5709 return dependent_expr_type(*this).swizzle_bytes(indices);
5710 }
5711
5714 template<std::size_t M>
5715 auto swizzle_lanes(const std::array<uint8_t, M> &indices) const
5716 requires requires (dependent_expr_type e) { e.swizzle_lanes(indices); } {
5717 return dependent_expr_type(*this).swizzle_lanes(indices);
5718 }
5719
5720#undef REGISTER_USE
5721};
5722
5723/*----- Overload forwarded binary operators for pointer advancing on PrimitiveExpr<T*, L> ----------------------------*/
5724template<dsl_pointer_to_primitive T, VariableKind Kind, bool CanBeNull, std::size_t L>
5725requires requires (const Variable<T, Kind, CanBeNull, L> &var, typename PrimitiveExpr<T, L>::offset_t delta)
5726 { var.val().operator+(delta); }
5728{
5729 return var.val().operator+(delta);
5730}
5731
5732template<dsl_pointer_to_primitive T, VariableKind Kind, bool CanBeNull, std::size_t L>
5733requires requires (const Variable<T, Kind, CanBeNull, L> &var, typename PrimitiveExpr<T, L>::offset_t delta)
5734 { var.val().operator-(delta); }
5736{
5737 return var.val().operator-(delta);
5738}
5739
5740namespace detail {
5741
5743template<typename T>
5745
5746template<typename T, std::size_t L>
5748{ using type = Variable<T, VariableKind::Local, /* CanBeNull= */ false, L>; };
5749
5750template<typename T, std::size_t L>
5752{ using type = Variable<T, VariableKind::Local, /* CanBeNull= */ true, L>; };
5753
5755template<typename T>
5757
5758template<typename T, std::size_t L>
5760{ using type = Variable<T, VariableKind::Local, /* CanBeNull= */ true, L>; };
5761
5762template<typename T, std::size_t L>
5764{ using type = Variable<T, VariableKind::Local, /* CanBeNull= */ true, L>; };
5765
5767template<typename T>
5769
5770template<typename T, std::size_t L>
5772{ using type = Variable<T, VariableKind::Global, /* CanBeNull= */ false, L>; };
5773
5774}
5775
5777template<typename T>
5778requires requires { typename detail::var_helper<T>::type; }
5780
5782template<typename T>
5783requires requires { typename detail::_var_helper<T>::type; }
5785
5787template<typename T>
5788requires requires { typename detail::global_helper<T>::type; }
5790
5791
5792/*======================================================================================================================
5793 * Parameter
5794 *====================================================================================================================*/
5795
5799template<typename T, std::size_t L>
5800requires (L * sizeof(T) <= 16) // parameter must fit in a single Wasm local
5801struct Parameter<T, L> : Variable<T, VariableKind::Param, /* CanBeNull= */ false, L>
5802{
5803 template<typename>
5804 friend struct Function; // to enable `Function` to create `Parameter` instances through private c'tor
5805
5806 using base_type = Variable<T, VariableKind::Param, /* CanBeNull= */ false, L>;
5807 using base_type::operator=;
5808 using dependent_expr_type = typename base_type::dependent_expr_type;
5809 using base_type::operator dependent_expr_type;
5810
5811 private:
5814 Parameter(unsigned index)
5815 : base_type(::wasm::Index(index), tag<int>{})
5816 {
5817 ::wasm::Function &fn = Module::Function();
5818 M_insist(index < fn.getNumLocals(), "index out of bounds");
5819 M_insist(fn.isParam(index), "not a parameter");
5820 M_insist(fn.getLocalType(index) == (wasm_type<T, L>()), "type mismatch");
5821 }
5822};
5823
5824
5825/*======================================================================================================================
5826 * Pointer and References
5827 *====================================================================================================================*/
5828
5829namespace detail {
5830
5831template<dsl_primitive T, std::size_t L, bool IsConst>
5833{
5834 friend struct PrimitiveExpr<T*, L>; // to construct a reference to the pointed-to memory
5835 friend struct Variable<T*, VariableKind::Local, false, L>; // to construct a reference to the pointed-to memory
5836 friend struct Variable<T*, VariableKind::Global, false, L>; // to construct a reference to the pointed-to memory
5837 friend struct Variable<T*, VariableKind::Param, false, L>; // to construct a reference to the pointed-to memory
5838
5839 static constexpr bool is_const = IsConst;
5840
5841 private:
5843
5844 private:
5846 : ptr_(ptr)
5847 {
5848 M_insist(bool(ptr_), "must not be moved or discarded");
5849 }
5850
5851 public:
5852 template<typename U>
5853 requires (not is_const) and requires (U &&u) { PrimitiveExpr<T, L>(primitive_expr_t<U>(std::forward<U>(u))); }
5854 void operator=(U &&_value) {
5855 PrimitiveExpr<T, L> value(primitive_expr_t<U>(std::forward<U>(_value)));
5856 ptr_.store(value);
5857 }
5858
5860 operator PrimitiveExpr<T, L>() { return ptr_.load(); }
5861
5862#define ASSIGNOP(SYMBOL) \
5863 template<typename U> \
5864 requires requires (the_reference ref, U &&u) { ref SYMBOL std::forward<U>(u); } and \
5865 requires (the_reference ref, decltype(ref SYMBOL std::declval<U>()) value) \
5866 { ref = value; } \
5867 void operator SYMBOL##= (U &&value) { \
5868 this->operator=(the_reference(ptr_.clone()) SYMBOL std::forward<U>(value)); \
5869 }
5871#undef ASSIGNOP
5872};
5873
5874}
5875
5876
5877/*======================================================================================================================
5878 * LocalBitmap, LocalBitvector, Bit, and LocalBit
5879 *====================================================================================================================*/
5880
5882{
5883 friend struct Module;
5884
5886 uint64_t bitmask = uint64_t(-1UL);
5887
5888 private:
5889 LocalBitmap() = default;
5890 LocalBitmap(const LocalBitmap&) = delete;
5891};
5892
5894{
5895 friend struct Module;
5896
5899 std::array<uint16_t, 8> bitmask_per_offset;
5900
5901 private:
5903 {
5904 bitmask_per_offset.fill(uint16_t(-1U));
5905 }
5907};
5908
5909struct Bit
5910{
5911 virtual ~Bit() { }
5912};
5913
5914namespace detail {
5915
5917template<std::size_t L>
5918requires (L <= 16)
5920{
5921 friend struct LocalBit<L>; // to be usable by the respective LocalBit
5922
5923 friend void swap(local_bit_storage &first, local_bit_storage &second) {
5924 using std::swap;
5925 swap(first.bitvector_, second.bitvector_);
5926 swap(first.bit_offset_, second.bit_offset_);
5927 swap(first.starting_lane_, second.starting_lane_);
5928 }
5929
5930 LocalBitvector *bitvector_ = nullptr;
5931 uint8_t bit_offset_;
5933
5936 local_bit_storage(LocalBitvector &bitvector, uint8_t bit_offset, uint8_t starting_lane)
5937 : bitvector_(&bitvector)
5938 , bit_offset_(bit_offset)
5939 , starting_lane_(starting_lane)
5940 {
5941 M_insist(bit_offset_ < 8, "offset out of bounds");
5942 M_insist(starting_lane_ + L <= 16, "starting lane out of bounds");
5943 }
5944};
5945
5947template<>
5949{
5950 friend struct LocalBit<1>; // to be usable by the respective LocalBit
5951
5952 friend void swap(local_bit_storage &first, local_bit_storage &second) {
5953 using std::swap;
5954 swap(first.bitmap_, second.bitmap_);
5955 swap(first.bit_offset_, second.bit_offset_);
5956 }
5957
5958 LocalBitmap *bitmap_ = nullptr;
5959 uint8_t bit_offset_;
5960
5963 local_bit_storage(LocalBitmap &bitmap, uint8_t bit_offset) : bitmap_(&bitmap), bit_offset_(bit_offset)
5964 {
5965 M_insist(bit_offset_ < CHAR_BIT * sizeof(uint64_t), "offset out of bounds");
5966 }
5967};
5968
5969}
5970
5977template<std::size_t L>
5978requires (L > 0) and (L <= 16)
5979struct LocalBit<L> : Bit
5980{
5981 friend void swap(LocalBit &first, LocalBit &second) {
5982 using std::swap;
5983 swap(first.storage_, second.storage_);
5984 }
5985
5986 friend struct Module; // to construct LocalBit
5987
5988 private:
5989 using storage_type = detail::local_bit_storage<L>;
5990 storage_type storage_;
5991
5992 LocalBit() = default;
5993
5994 template<typename... Us>
5995 requires requires (Us&&... us) { storage_type(std::forward<Us>(us)...); }
5996 LocalBit(Us&&... us) : storage_(std::forward<Us>(us)...) { }
5997
5998 public:
5999 LocalBit(const LocalBit&) = delete;
6000 LocalBit(LocalBit &&other) : LocalBit() { swap(*this, other); }
6001
6003 ~LocalBit() {
6004 if constexpr (L == 1) {
6005 if (storage_.bitmap_) {
6006 M_insist((storage_.bitmap_->bitmask bitand mask()) == 0, "bit must still be allocated");
6007
6008 if (storage_.bitmap_->bitmask == 0) // empty bitmap
6009 Module::Get().local_bitmaps_stack_.back().emplace_back(storage_.bitmap_); // make discoverable again
6010
6011 storage_.bitmap_->bitmask |= mask(); // deallocate bit
6012 }
6013 } else {
6014 if (storage_.bitvector_) {
6015 constexpr uint16_t MASK = (1U << L) - 1U;
6016 M_insist((storage_.bitvector_->bitmask_per_offset[offset()] bitand (MASK << starting_lane())) == 0,
6017 "bits must still be allocated");
6018
6019 const auto &bitmasks = storage_.bitvector_->bitmask_per_offset;
6020 auto pred = [](auto bitmask){ return bitmask == 0; };
6021 if (std::all_of(bitmasks.cbegin(), bitmasks.cend(), pred)) // empty bitvector
6022 Module::Get().local_bitvectors_stack_.back().emplace_back(storage_.bitvector_); // make discoverable again
6023
6024 storage_.bitvector_->bitmask_per_offset[offset()] |= MASK << starting_lane(); // deallocate bits
6025 }
6026 }
6027 }
6028
6029 LocalBit & operator=(LocalBit &&other) { swap(*this, other); return *this; }
6030
6031 private:
6033 std::conditional_t<L == 1, uint64_t, uint8_t> offset() const { return storage_.bit_offset_; }
6035 uint64_t mask() const requires (L == 1) { return 1UL << offset(); }
6037 U8x16 mask() const requires (L > 1) { return U8x16(1U << offset()); }
6039 U8x16 mask_inverted() const requires (L > 1) { return U8x16(~(1U << offset())); }
6041 uint8_t starting_lane() const requires (L > 1) { return storage_.starting_lane_; }
6042
6043 public:
6045 PrimitiveExpr<bool, L> is_set() const {
6046 if constexpr (L == 1) {
6047 return (storage_.bitmap_->u64 bitand mask()).template to<bool>();
6048 } else {
6049 if constexpr (L == 16) { // all lanes used
6050 M_insist(starting_lane() == 0);
6051 return (storage_.bitvector_->u8x16 bitand mask()).template to<bool>();
6052 } else if (starting_lane()) { // swizzle lanes to correct starting point
6053 std::array<uint8_t, L> indices;
6054 std::iota(indices.begin(), indices.end(), starting_lane()); // fill with [starting_lane(), starting_lane() + L)
6055 return (storage_.bitvector_->u8x16 bitand mask()).swizzle_bytes(indices).template to<bool>();
6056 } else { // no swizzling needed, but explicitly convert s.t. only first L lanes are used
6057 return PrimitiveExpr<bool, L>((storage_.bitvector_->u8x16 bitand mask()).template to<bool>().move());
6058 }
6059 }
6060 }
6061
6063 void set() {
6064 if constexpr (L == 1)
6065 storage_.bitmap_->u64 |= mask();
6066 else
6067 storage_.bitvector_->u8x16 |= mask();
6068 }
6070 void clear() {
6071 if constexpr (L == 1)
6072 storage_.bitmap_->u64 &= ~mask();
6073 else
6074 storage_.bitvector_->u8x16 &= mask_inverted();
6075 }
6076
6078 void set(PrimitiveExpr<bool, L> value) {
6079 if constexpr (L == 1) {
6080 storage_.bitmap_->u64 =
6081 (storage_.bitmap_->u64 bitand ~mask()) bitor (value.template to<uint64_t>() << offset());
6082 } else {
6083 if constexpr (L == 16) { // all lanes used
6084 M_insist(starting_lane() == 0);
6085 storage_.bitvector_->u8x16 = (storage_.bitvector_->u8x16 bitand mask_inverted()) bitor
6086 (value.template to<uint8_t>() << offset());
6087 } else if (starting_lane()) { // swizzle lanes to correct starting point, other lanes are 0
6088 std::array<uint8_t, 16> indices;
6089 auto it = std::fill_n(indices.begin(), starting_lane(), L); // fill range [0, starting_lane()) with L
6090 std::iota(it, it + L, 0); // fill range [starting_lane(), starting_lane() + L) with [0, L)
6091 std::fill(it + L, indices.end(), L); // fill range [starting_lane() + L, 16) with L
6092 storage_.bitvector_->u8x16 = (storage_.bitvector_->u8x16 bitand mask_inverted()) bitor
6093 (value.swizzle_bytes(indices).template to<uint8_t>() << offset());
6094 } else { // no swizzling needed, but explicitly mask s.t. last (unused) 16-L lanes are 0
6095 Boolx16 value_masked((PrimitiveExpr<bool, L>(true) and value).move());
6096 storage_.bitvector_->u8x16 = (storage_.bitvector_->u8x16 bitand mask_inverted()) bitor
6097 (value_masked.template to<uint8_t>() << offset());
6098 }
6099 }
6100 }
6101
6104 LocalBit & operator=(const LocalBit &other) {
6105 if constexpr (L == 1) {
6106 auto other_bit = other.storage_.bitmap_->u64 bitand other.mask();
6107 Var<U64x1> this_bit;
6108
6109 if (this->offset() > other.offset()) {
6110 const auto shift_width = this->offset() - other.offset();
6111 this_bit = other_bit << shift_width;
6112 } else if (other.offset() > this->offset()) {
6113 const auto shift_width = other.offset() - this->offset();
6114 this_bit = other_bit >> shift_width;
6115 } else {
6116 this_bit = other_bit;
6117 }
6118
6119 this->storage_.bitmap_->u64 =
6120 (this->storage_.bitmap_->u64 bitand ~this->mask()) bitor this_bit; // clear, then set bit
6121
6122 return *this;
6123 } else {
6124 auto other_bits = other.storage_.bitvector_->u8x16 bitand other.mask();
6125 Var<U8x16> this_bits;
6126
6127 if (this->starting_lane() != other.starting_lane()) {
6128 std::array<uint8_t, 16> indices;
6129 auto it = std::fill_n(indices.begin(), starting_lane(), L); // fill range [0, starting_lane()) with L
6130 std::iota(it, it + L, 0); // fill range [starting_lane(), starting_lane() + L) with [0, L)
6131 std::fill(it + L, indices.end(), L); // fill range [starting_lane() + L, 16) with L
6132 this_bits = other_bits.swizzle_bytes(indices);
6133 } else {
6134 this_bits = other_bits;
6135 }
6136
6137 if (this->offset() > other.offset()) {
6138 const auto shift_width = this->offset() - other.offset();
6139 this_bits = other_bits << shift_width;
6140 } else if (other.offset() > this->offset()) {
6141 const auto shift_width = other.offset() - this->offset();
6142 this_bits = other_bits >> shift_width;
6143 } else {
6144 this_bits = other_bits;
6145 }
6146
6147 this->storage_.bitvector_->u8x16 =
6148 (this->storage_.bitvector_->u8x16 bitand this->mask_inverted()) bitor this_bits; // clear, then set bit
6149
6150 return *this;
6151 }
6152 }
6153
6155 operator PrimitiveExpr<bool, L>() const { return is_set(); }
6156};
6157
6158
6159/*======================================================================================================================
6160 * Control flow
6161 *====================================================================================================================*/
6162
6163/*----- Return unsafe, i.e. without static type checking -------------------------------------------------------------*/
6164
6165inline void RETURN_UNSAFE() { Module::Get().emit_return(); }
6166
6167template<primitive_convertible T>
6168inline void RETURN_UNSAFE(T &&t) { Module::Get().emit_return(primitive_expr_t<T>(std::forward<T>(t))); }
6169
6170template<expr_convertible T>
6171requires (not primitive_convertible<T>)
6172inline void RETURN_UNSAFE(T &&t) { Module::Get().emit_return(expr_t<T>(std::forward<T>(t))); }
6173
6174/*----- BREAK --------------------------------------------------------------------------------------------------------*/
6175
6176inline void BREAK(std::size_t level = 1) { Module::Get().emit_break(level); }
6177template<primitive_convertible C>
6178requires requires (C &&c) { PrimitiveExpr<bool, 1>(std::forward<C>(c)); }
6179inline void BREAK(C &&_cond, std::size_t level = 1)
6180{
6181 PrimitiveExpr<bool, 1> cond(std::forward<C>(_cond));
6182 Module::Get().emit_break(cond, level);
6183}
6184
6185/*----- CONTINUE -----------------------------------------------------------------------------------------------------*/
6186
6187inline void CONTINUE(std::size_t level = 1) { Module::Get().emit_continue(level); }
6188template<primitive_convertible C>
6189requires requires (C &&c) { PrimitiveExpr<bool, 1>(std::forward<C>(c)); }
6190inline void CONTINUE(C &&_cond, std::size_t level = 1)
6191{
6192 PrimitiveExpr<bool, 1> cond(std::forward<C>(_cond));
6193 Module::Get().emit_continue(cond, level);
6194}
6195
6196/*----- GOTO ---------------------------------------------------------------------------------------------------------*/
6197
6199inline void GOTO(const Block &block) { block.go_to(); }
6200template<primitive_convertible C>
6201requires requires (C &&c) { PrimitiveExpr<bool, 1>(std::forward<C>(c)); }
6203inline void GOTO(C &&_cond, const Block &block)
6204{
6205 PrimitiveExpr<bool, 1> cond(std::forward<C>(_cond));
6206 block.go_to(cond);
6207}
6208
6209/*----- Select -------------------------------------------------------------------------------------------------------*/
6210
6211template<primitive_convertible C, primitive_convertible T, primitive_convertible U>
6214requires (C &&c) { PrimitiveExpr<bool, 1>(std::forward<C>(c)); }
6215inline auto Select(C &&_cond, T &&_tru, U &&_fals)
6216{
6217 primitive_expr_t<T> tru(std::forward<T>(_tru));
6218 primitive_expr_t<U> fals(std::forward<U>(_fals));
6219
6220 using To = common_type_t<typename decltype(tru)::type, typename decltype(fals)::type>;
6221 constexpr std::size_t L = decltype(tru)::num_simd_lanes;
6222
6223 PrimitiveExpr<bool, 1> cond(std::forward<C>(_cond));
6224 return Module::Get().emit_select<To, L>(cond, tru.template to<To, L>(), fals.template to<To, L>());
6225}
6226
6227template<primitive_convertible C, expr_convertible T, expr_convertible U>
6228requires (not primitive_convertible<T> or not primitive_convertible<U>) and
6231requires (C &&c) { PrimitiveExpr<bool, 1>(std::forward<C>(c)); }
6232inline auto Select(C &&_cond, T &&_tru, U &&_fals)
6233{
6234 expr_t<T> tru(std::forward<T>(_tru));
6235 expr_t<U> fals(std::forward<U>(_fals));
6236
6237 using To = common_type_t<typename decltype(tru)::type, typename decltype(fals)::type>;
6238 constexpr std::size_t L = decltype(tru)::num_simd_lanes;
6239
6240 PrimitiveExpr<bool, 1> cond(std::forward<C>(_cond));
6241 return Module::Get().emit_select<To, L>(cond, tru.template to<To, L>(), fals.template to<To, L>());
6242}
6243
6244template<primitive_convertible C, primitive_convertible T, primitive_convertible U>
6248requires (C &&c) { PrimitiveExpr<bool, primitive_expr_t<T>::num_simd_lanes>(std::forward<C>(c)); }
6249inline auto Select(C &&_cond, T &&_tru, U &&_fals)
6250{
6251 primitive_expr_t<T> tru(std::forward<T>(_tru));
6252 primitive_expr_t<U> fals(std::forward<U>(_fals));
6253
6254 using To = common_type_t<typename decltype(tru)::type, typename decltype(fals)::type>;
6255 constexpr std::size_t L = decltype(tru)::num_simd_lanes;
6256
6257 PrimitiveExpr<bool, L> cond(std::forward<C>(_cond));
6258 return Module::Get().emit_select<To, L>(cond, tru.template to<To, L>(), fals.template to<To, L>());
6259}
6260
6261template<primitive_convertible C, expr_convertible T, expr_convertible U>
6262requires (not primitive_convertible<T> or not primitive_convertible<U>) and
6265requires (C &&c) { PrimitiveExpr<bool, expr_t<T>::num_simd_lanes>(std::forward<C>(c)); }
6266inline auto Select(C &&_cond, T &&_tru, U &&_fals)
6267{
6268 expr_t<T> tru(std::forward<T>(_tru));
6269 expr_t<U> fals(std::forward<U>(_fals));
6270
6271 using To = common_type_t<typename decltype(tru)::type, typename decltype(fals)::type>;
6272 constexpr std::size_t L = decltype(tru)::num_simd_lanes;
6273
6274 PrimitiveExpr<bool, L> cond(std::forward<C>(_cond));
6275 return Module::Get().emit_select<To, L>(cond, tru.template to<To, L>(), fals.template to<To, L>());
6276}
6277
6278
6279/*----- Shuffle ------------------------------------------------------------------------------------------------------*/
6280
6281template<primitive_convertible T, primitive_convertible U, std::size_t M>
6286 const std::array<uint8_t, M> &a)
6287 { Module::Get().emit_shuffle_bytes(e, e, a); }
6288inline auto ShuffleBytes(T &&_first, U &&_second, const std::array<uint8_t, M> &indices)
6289{
6290 primitive_expr_t<T> first(std::forward<T>(_first));
6291 primitive_expr_t<U> second(std::forward<U>(_second));
6292
6293 using To = common_type_t<typename decltype(first)::type, typename decltype(second)::type>;
6294 constexpr std::size_t L = decltype(first)::num_simd_lanes;
6295
6296 return Module::Get().emit_shuffle_bytes<To, L>(first.template to<To, L>(), second.template to<To, L>(), indices);
6297}
6298
6299template<primitive_convertible T, primitive_convertible U, std::size_t M>
6300requires have_common_type<typename primitive_expr_t<T>::type, typename primitive_expr_t<U>::type> and
6301 (primitive_expr_t<T>::num_simd_lanes == primitive_expr_t<U>::num_simd_lanes) and
6302requires (PrimitiveExpr<common_type_t<typename primitive_expr_t<T>::type, typename primitive_expr_t<U>::type>,
6303 primitive_expr_t<T>::num_simd_lanes> e,
6304 const std::array<uint8_t, M> &a)
6305 { Module::Get().emit_shuffle_lanes(e, e, a); }
6306inline auto ShuffleLanes(T &&_first, U &&_second, const std::array<uint8_t, M> &indices)
6307{
6308 primitive_expr_t<T> first(std::forward<T>(_first));
6309 primitive_expr_t<U> second(std::forward<U>(_second));
6310
6311 using To = common_type_t<typename decltype(first)::type, typename decltype(second)::type>;
6312 constexpr std::size_t L = decltype(first)::num_simd_lanes;
6313
6314 return Module::Get().emit_shuffle_lanes<To, L>(first.template to<To, L>(), second.template to<To, L>(), indices);
6315}
6316
6317template<expr_convertible T, expr_convertible U, std::size_t M>
6318requires (not primitive_convertible<T> or not primitive_convertible<U>) and
6322 const std::array<uint8_t, M> &a)
6323 { Module::Get().emit_shuffle_bytes(e, e, a); }
6324inline auto ShuffleBytes(T &&_first, U &&_second, const std::array<uint8_t, M> &indices)
6325{
6326 expr_t<T> first(std::forward<T>(_first));
6327 expr_t<U> second(std::forward<U>(_second));
6328
6329 using To = common_type_t<typename decltype(first)::type, typename decltype(second)::type>;
6330 constexpr std::size_t L = decltype(first)::num_simd_lanes;
6331
6332 return Module::Get().emit_shuffle_bytes<To, L>(first.template to<To, L>(), second.template to<To, L>(), indices);
6333}
6334
6335template<expr_convertible T, expr_convertible U, std::size_t M>
6336requires (not primitive_convertible<T> or not primitive_convertible<U>) and
6337 have_common_type<typename expr_t<T>::type, typename expr_t<U>::type> and
6338 (expr_t<T>::num_simd_lanes == expr_t<U>::num_simd_lanes) and
6339requires (Expr<common_type_t<typename expr_t<T>::type, typename expr_t<U>::type>, expr_t<T>::num_simd_lanes> e,
6340 const std::array<uint8_t, M> &a)
6341 { Module::Get().emit_shuffle_lanes(e, e, a); }
6342inline auto ShuffleLanes(T &&_first, U &&_second, const std::array<uint8_t, M> &indices)
6343{
6344 expr_t<T> first(std::forward<T>(_first));
6345 expr_t<U> second(std::forward<U>(_second));
6346
6347 using To = common_type_t<typename decltype(first)::type, typename decltype(second)::type>;
6348 constexpr std::size_t L = decltype(first)::num_simd_lanes;
6349
6350 return Module::Get().emit_shuffle_lanes<To, L>(first.template to<To, L>(), second.template to<To, L>(), indices);
6351}
6352
6353
6354/*----- If -----------------------------------------------------------------------------------------------------------*/
6355
6356struct If
6357{
6358 using continuation_t = std::function<void(void)>;
6359
6360 private:
6362 std::string name_;
6363
6364 public:
6366
6367 template<primitive_convertible C>
6368 requires requires (C &&c) { PrimitiveExpr<bool, 1>(std::forward<C>(c)); }
6369 explicit If(C &&cond)
6370 : cond_(std::forward<C>(cond))
6371 , name_(Module::Unique_If_Name())
6372 { }
6373
6374 If(const If&) = delete;
6375 If(If&&) = delete;
6376
6377 ~If();
6378};
6379
6380/*----- Loop ---------------------------------------------------------------------------------------------------------*/
6381
6384struct Loop
6385{
6386 friend void swap(Loop &first, Loop &second) {
6387 using std::swap;
6388 swap(first.body_, second.body_);
6389 swap(first.loop_, second.loop_);
6390 }
6391
6392 private:
6394 ::wasm::Loop *loop_ = nullptr;
6395
6396 private:
6398 Loop(std::string name, tag<int>)
6399 : body_(name + ".body", false)
6400 , loop_(M_notnull(Module::Builder().makeLoop(name, &body_.get())))
6401 {
6402 Module::Get().push_branch_targets(
6403 /* brk= */ body_.get().name,
6404 /* continu= */ loop_->name
6405 );
6406 }
6407
6408 public:
6409 explicit Loop(std::string name) : Loop(Module::Unique_Loop_Name(name), tag<int>{}) { }
6410 explicit Loop(const char *name) : Loop(std::string(name)) { }
6411
6412 Loop(const Loop&) = delete;
6413 Loop(Loop &&other) { swap(*this, other); }
6414
6416 if (loop_) {
6417 Module::Get().pop_branch_targets();
6418 Module::Block().list.push_back(loop_);
6419 }
6420 }
6421
6422 Loop & operator=(Loop &&other) { swap(*this, other); return *this; }
6423
6424 std::string name() const { return loop_->name.toString(); }
6425
6426 Block & body() { return body_; }
6427 const Block & body() const { return body_; }
6428};
6429
6431{
6432 template<primitive_convertible C>
6433 requires requires (C &&c) { PrimitiveExpr<bool, 1>(std::forward<C>(c)); }
6434 DoWhile(std::string name, C &&_cond)
6435 : Loop(name)
6436 {
6437 PrimitiveExpr<bool, 1> cond(std::forward<C>(_cond));
6438
6439 /*----- Update condition in branch targets. -----*/
6440 auto branch_targets = Module::Get().pop_branch_targets();
6441 Module::Get().push_branch_targets(branch_targets.brk, branch_targets.continu, cond);
6442 }
6443
6444 template<primitive_convertible C>
6445 requires requires (C &&c) { PrimitiveExpr<bool, 1>(std::forward<C>(c)); }
6446 DoWhile(const char *name, C &&cond) : DoWhile(std::string(name), cond) { }
6447
6448 DoWhile(const DoWhile&) = delete;
6449 DoWhile(DoWhile&&) = default;
6450
6451 ~DoWhile();
6452};
6453
6454struct While
6455{
6456 private:
6458 std::unique_ptr<DoWhile> do_while_;
6459
6460 public:
6461 While(std::string name, PrimitiveExpr<bool, 1> cond)
6462 : cond_(cond.clone())
6463 , do_while_(std::make_unique<DoWhile>(name + ".do-while", cond))
6464 { }
6465
6466 template<primitive_convertible C>
6467 requires requires (C &&c) { PrimitiveExpr<bool, 1>(std::forward<C>(c)); }
6468 While(std::string name, C &&cond) : While(name, PrimitiveExpr<bool, 1>(std::forward<C>(cond))) { }
6469
6470 template<primitive_convertible C>
6471 requires requires (C &&c) { PrimitiveExpr<bool, 1>(std::forward<C>(c)); }
6472 While(const char *name, C &&cond) : While(std::string(name), cond) { }
6473
6474 While(const While&) = delete;
6475 While(While&&) = default;
6476
6477 ~While();
6478
6479 Block & body() { return do_while_->body(); }
6480 const Block & body() const { return do_while_->body(); }
6481};
6482
6483
6484/*======================================================================================================================
6485 * Allocator
6486 *====================================================================================================================*/
6487
6489{
6490 public:
6491 virtual ~Allocator() { }
6492
6493 public:
6496 virtual void * raw_allocate(uint32_t bytes, uint32_t align = 1) = 0;
6499 virtual Ptr<void> pre_allocate(uint32_t bytes, uint32_t align = 1) = 0;
6502 virtual Var<Ptr<void>> allocate(U32x1 bytes, uint32_t align = 1) = 0;
6504 virtual void deallocate(Ptr<void> ptr, U32x1 bytes) = 0;
6505
6509 virtual uint32_t perform_pre_allocations() = 0;
6510
6512 virtual uint32_t pre_allocated_memory_consumption() const = 0;
6514 virtual U32x1 allocated_memory_consumption() const = 0;
6516 virtual U32x1 allocated_memory_peak() const = 0;
6517
6518 Var<Ptr<void>> allocate(uint32_t bytes, uint32_t align = 1) { return allocate(U32x1(bytes), align); }
6519 void deallocate(Ptr<void> ptr, uint32_t bytes) { return deallocate(ptr, U32x1(bytes)); }
6520
6521
6523 template<dsl_primitive T>
6524 T * raw_malloc() { return raw_malloc<T>(1U); }
6527 template<dsl_primitive T, std::size_t L = 1>
6528 Ptr<PrimitiveExpr<T, L>> pre_malloc() { return pre_malloc<T, L>(1U); }
6531 template<dsl_primitive T, std::size_t L = 1>
6532 Var<Ptr<PrimitiveExpr<T, L>>> malloc() { return malloc<T, L>(1U); }
6533
6536 template<dsl_primitive T>
6537 T * raw_malloc(uint32_t count) { return static_cast<T*>(raw_allocate(sizeof(T) * count, alignof(T))); }
6540 template<dsl_primitive T, std::size_t L = 1>
6542 if constexpr (L == 1)
6543 return pre_allocate(sizeof(T) * count, alignof(T)).template to<T*, L>();
6544 else if constexpr (L * sizeof(T) <= 16)
6545 return pre_allocate(16 * count, alignof(T)).template to<T*, L>();
6546 else
6547 return pre_allocate(16 * PrimitiveExpr<T, L>::num_vectors * count, alignof(T)).template to<T*, L>();
6548 }
6551 template<dsl_primitive T, std::size_t L = 1, typename U>
6552 requires requires (U &&u) { U32x1(std::forward<U>(u)); }
6554 if constexpr (L == 1) {
6556 allocate(uint32_t(sizeof(T)) * std::forward<U>(count), alignof(T)).template to<T*, L>()
6557 );
6558 return ptr;
6559 } else if constexpr (L * sizeof(T) <= 16) {
6561 allocate(16U * std::forward<U>(count), alignof(T)).template to<T*, L>()
6562 );
6563 return ptr;
6564 } else {
6566 allocate(
6567 uint32_t(16 * PrimitiveExpr<T, L>::num_vectors) * std::forward<U>(count), alignof(T)
6568 ).template to<T*, L>()
6569 );
6570 return ptr;
6571 }
6572 }
6573
6575 template<primitive_convertible T>
6576 requires requires (T &&t) { primitive_expr_t<T>(std::forward<T>(t)).template to<void*>(); }
6577 void free(T &&ptr) { free(std::forward<T>(ptr), 1U); }
6578
6580 template<primitive_convertible T, typename U>
6581 requires requires (U &&u) { U32x1(std::forward<U>(u)); } and
6582 requires (T &&t) { primitive_expr_t<T>(std::forward<T>(t)).template to<void*>(); }
6583 void free(T &&ptr, U &&count) {
6584 primitive_expr_t<T> _ptr(std::forward<T>(ptr));
6585 using pointed_type = typename decltype(_ptr)::pointed_type;
6586 constexpr std::size_t L = decltype(_ptr)::num_simd_lanes;
6587 if constexpr (L == 1)
6588 deallocate(_ptr.template to<void*>(), uint32_t(sizeof(pointed_type)) * std::forward<U>(count));
6589 else if constexpr (L * sizeof(T) <= 16)
6590 deallocate(_ptr.template to<void*>(), 16U * std::forward<U>(count));
6591 else
6592 deallocate(_ptr.template to<void*>(),
6593 uint32_t(16 * PrimitiveExpr<T, L>::num_vectors) * std::forward<U>(count));
6594 }
6595};
6596
6597
6598/*######################################################################################################################
6599 * DELAYED DEFINITIONS
6600 *####################################################################################################################*/
6601
6602/*======================================================================================================================
6603 * Module
6604 *====================================================================================================================*/
6605
6606inline void Module::create_local_bitmap_stack()
6607{
6608 local_bitmaps_stack_.emplace_back();
6609}
6610
6611inline void Module::create_local_bitvector_stack()
6612{
6613 local_bitvectors_stack_.emplace_back();
6614}
6615
6616inline void Module::dispose_local_bitmap_stack()
6617{
6618 auto &local_bitmaps = local_bitmaps_stack_.back();
6619 for (LocalBitmap *bitmap : local_bitmaps) {
6620 M_insist(~bitmap->bitmask == 0, "all bits must have been deallocated");
6621 delete bitmap;
6622 }
6623 local_bitmaps_stack_.pop_back();
6624}
6625
6626inline void Module::dispose_local_bitvector_stack()
6627{
6628 auto &local_bitvectors = local_bitvectors_stack_.back();
6629 for (LocalBitvector *bitvector : local_bitvectors) {
6630#ifndef NDEBUG
6631 for (auto bitmask : bitvector->bitmask_per_offset)
6632 M_insist(uint16_t(~bitmask) == 0, "all bits must have been deallocated");
6633#endif
6634 delete bitvector;
6635 }
6636 local_bitvectors_stack_.pop_back();
6637}
6638
6639template<std::size_t L>
6640requires (L > 0) and (L <= 16)
6641inline LocalBit<L> Module::allocate_bit()
6642{
6643 if constexpr (L == 1) {
6644 auto &local_bitmaps = local_bitmaps_stack_.back();
6645
6646 if (local_bitmaps.empty())
6647 local_bitmaps.emplace_back(new LocalBitmap()); // allocate new local bitmap in current function
6648
6649 LocalBitmap &bitmap = *local_bitmaps.back();
6650 M_insist(bitmap.bitmask, "bitmap must have at least one bit unoccupied");
6651
6652 uint8_t bit_offset = std::countr_zero(bitmap.bitmask);
6653 bitmap.bitmask ^= 1UL << bit_offset; // clear allocated bit
6654
6655 LocalBit<L> bit(bitmap, bit_offset);
6656
6657 if (bitmap.bitmask == 0) // all bits have been allocated
6658 local_bitmaps.pop_back(); // remove bitmap entry, ownership transitions to *all* referencing `LocalBit`s
6659
6660 return bit;
6661 } else {
6662 bool fresh_bitvector = false;
6663
6664 auto &local_bitvectors = local_bitvectors_stack_.back();
6665
6666 if (local_bitvectors.empty()) {
6667allocate_bitvector:
6668 local_bitvectors.emplace_back(new LocalBitvector()); // allocate new local bitvector in current function
6669 fresh_bitvector = true;
6670 }
6671
6672 LocalBitvector &bitvector = *local_bitvectors.back();
6673
6674 uint8_t bit_offset;
6675 uint8_t starting_lane = uint8_t(-1U);
6676 constexpr uint16_t MASK = (1U << L) - 1U;
6677 for (uint8_t lane = 0; lane <= 16 - L; ++lane) {
6678 bit_offset = 0;
6679 for (uint16_t bitmask : bitvector.bitmask_per_offset) {
6680 const uint16_t masked = bitmask bitand (MASK << lane);
6681 if (masked == (MASK << lane)) { // `bit_offset`-th bit is free in L consecutive lanes
6682 starting_lane = lane;
6683 goto found_bits;
6684 }
6685 ++bit_offset;
6686 }
6687 }
6688found_bits:
6689 if (starting_lane == uint8_t(-1U)) {
6690 M_insist(not fresh_bitvector, "fresh bitvector must have at least L consecutive bits unoccupied");
6691 goto allocate_bitvector; // no bits found, retry with fresh bitvector
6692 }
6693
6694 bitvector.bitmask_per_offset[bit_offset] ^= MASK << starting_lane; // clear allocated bits
6695
6696 LocalBit<L> bit(bitvector, bit_offset, starting_lane);
6697
6698 const auto &bitmasks = bitvector.bitmask_per_offset;
6699 auto pred = [](auto bitmask){ return bitmask == 0; };
6700 if (std::all_of(bitmasks.cbegin(), bitmasks.cend(), pred)) // all bits have been allocated
6701 local_bitvectors.pop_back(); // remove bitvector entry, ownership transitions to *all* referencing `LocalBit`s
6702
6703 return bit;
6704 }
6705}
6706
6707template<typename T, std::size_t L>
6708requires (L * sizeof(T) <= 16)
6709inline PrimitiveExpr<T, L> Module::get_global(const char *name)
6710{
6711 return PrimitiveExpr<T, L>(builder_.makeGlobalGet(name, wasm_type<T, L>()));
6712}
6713
6714template<typename ReturnType, typename... ParamTypes, std::size_t... ParamLs>
6715requires std::is_void_v<ReturnType>
6716inline void Module::emit_call(const char *fn, PrimitiveExpr<ParamTypes, ParamLs>... args)
6717{
6718 active_block_->list.push_back(
6719 builder_.makeCall(fn, { args.expr()... }, wasm_type<ReturnType, 1>())
6720 );
6721}
6722
6723template<typename ReturnType, std::size_t ReturnL, typename... ParamTypes, std::size_t... ParamLs>
6726{
6728 builder_.makeCall(fn, { args.expr()... }, wasm_type<ReturnType, ReturnL>())
6729 );
6730}
6731
6732inline void Module::emit_return()
6733{
6734 active_block_->list.push_back(builder_.makeReturn());
6735}
6736
6737template<typename T, std::size_t L>
6738inline void Module::emit_return(PrimitiveExpr<T, L> value)
6739{
6740 active_block_->list.push_back(builder_.makeReturn(value.expr()));
6741}
6742
6743template<typename T, std::size_t L>
6744inline void Module::emit_return(Expr<T, L> value)
6745{
6746 emit_return(value.insist_not_null());
6747}
6748
6750inline void Module::emit_break(std::size_t level)
6751{
6752 M_insist(level > 0);
6753 M_insist(branch_target_stack_.size() >= level);
6754 auto &branch_targets = branch_target_stack_[branch_target_stack_.size() - level];
6755 active_block_->list.push_back(builder_.makeBreak(branch_targets.brk));
6756}
6757
6759inline void Module::emit_break(PrimitiveExpr<bool, 1> cond, std::size_t level)
6760{
6761 M_insist(level > 0);
6762 M_insist(branch_target_stack_.size() >= level);
6763 auto &branch_targets = branch_target_stack_[branch_target_stack_.size() - level];
6764 active_block_->list.push_back(builder_.makeBreak(branch_targets.brk, nullptr, cond.expr()));
6765}
6766
6767template<typename T, std::size_t L>
6769{
6770 if constexpr (L * sizeof(T) <= 16) {
6771 auto referenced_bits = cond.referenced_bits(); // moved
6772 referenced_bits.splice(referenced_bits.end(), tru.referenced_bits());
6773 referenced_bits.splice(referenced_bits.end(), fals.referenced_bits());
6774 return PrimitiveExpr<T, L>(
6775 /* expr= */ builder_.makeSelect(cond.expr(), tru.expr(), fals.expr()),
6776 /* referenced_bits= */ std::move(referenced_bits)
6777 );
6778 } else {
6779 using ResT = PrimitiveExpr<T, L>;
6780 std::array<typename ResT::vector_type, ResT::num_vectors> vectors;
6781 auto vectors_tru = tru.vectors();
6782 auto vectors_fals = fals.vectors();
6783 for (std::size_t idx = 0; idx < ResT::num_vectors; ++idx) {
6784 auto cond_cpy = cond.clone();
6785 auto referenced_bits = cond_cpy.referenced_bits(); // moved
6786 referenced_bits.splice(referenced_bits.end(), vectors_tru[idx].referenced_bits());
6787 referenced_bits.splice(referenced_bits.end(), vectors_fals[idx].referenced_bits());
6788 vectors[idx] = typename ResT::vector_type(
6789 /* expr= */ builder_.makeSelect(cond_cpy.expr(), vectors_tru[idx].expr(),
6790 vectors_fals[idx].expr()),
6791 /* referenced_bits= */ std::move(referenced_bits)
6792 );
6793 }
6794 cond.discard(); // since it was always cloned
6795 return ResT(std::move(vectors));
6796 }
6797}
6798
6799template<typename T, std::size_t L>
6800Expr<T, L> Module::emit_select(PrimitiveExpr<bool, 1> cond, Expr<T, L> tru, Expr<T, L> fals)
6801{
6802 if (tru.can_be_null() or fals.can_be_null()) {
6803 auto [tru_val, tru_is_null] = tru.split();
6804 auto [fals_val, fals_is_null] = fals.split();
6805 auto cond_cpy = cond.clone();
6806 return Expr<T, L>(
6807 /* value= */ emit_select(cond, tru_val, fals_val),
6808 /* is_null= */ emit_select(cond_cpy, tru_is_null, fals_is_null)
6809 );
6810 } else {
6811 return Expr<T, L>(
6812 /* value= */ emit_select(cond, tru.insist_not_null(), fals.insist_not_null())
6813 );
6814 }
6815}
6816
6817template<typename T, std::size_t L>
6818requires (L > 1) and requires (PrimitiveExpr<int8_t, L> e) { e.template to<int_t<sizeof(T)>, L>(); }
6820{
6821 using To = int_t<sizeof(T)>;
6822
6823 PrimitiveExpr<int8_t, L> mask_i8(cond.template move<int8_t, L>()); // convert without transforming `true`, i.e. 0xff, to 1
6824 PrimitiveExpr<To, L> mask = mask_i8.template to<To, L>(); // convert (w/ sign extension!) to same bit width as values
6825
6826 if constexpr (L * sizeof(T) <= 16) {
6827 auto referenced_bits = mask.referenced_bits(); // moved
6828 referenced_bits.splice(referenced_bits.end(), tru.referenced_bits());
6829 referenced_bits.splice(referenced_bits.end(), fals.referenced_bits());
6830 return PrimitiveExpr<T, L>(
6831 /* expr= */ builder_.makeSIMDTernary(::wasm::SIMDTernaryOp::Bitselect, tru.expr(), fals.expr(),
6832 mask.expr()),
6833 /* referenced_bits= */ std::move(referenced_bits)
6834 );
6835 } else {
6836 using ResT = PrimitiveExpr<T, L>;
6837 std::array<typename ResT::vector_type, ResT::num_vectors> vectors;
6838 auto vectors_mask = mask.vectors();
6839 static_assert(ResT::num_vectors == vectors_mask.size());
6840 auto vectors_tru = tru.vectors();
6841 auto vectors_fals = fals.vectors();
6842 for (std::size_t idx = 0; idx < ResT::num_vectors; ++idx) {
6843 auto referenced_bits = vectors_mask[idx].referenced_bits(); // moved
6844 referenced_bits.splice(referenced_bits.end(), vectors_tru[idx].referenced_bits());
6845 referenced_bits.splice(referenced_bits.end(), vectors_fals[idx].referenced_bits());
6846 vectors[idx] = typename ResT::vector_type(
6847 /* expr= */ builder_.makeSIMDTernary(::wasm::SIMDTernaryOp::Bitselect,
6848 vectors_tru[idx].expr(), vectors_fals[idx].expr(),
6849 vectors_mask[idx].expr()),
6850 /* referenced_bits= */ std::move(referenced_bits)
6851 );
6852 }
6853 return ResT(std::move(vectors));
6854 }
6855}
6856
6857template<typename T, std::size_t L>
6858requires (L > 1) and requires (PrimitiveExpr<int8_t, L> e) { e.template to<int_t<sizeof(T)>, L>(); }
6859Expr<T, L> Module::emit_select(PrimitiveExpr<bool, L> cond, Expr<T, L> tru, Expr<T, L> fals)
6860{
6861 if (tru.can_be_null() or fals.can_be_null()) {
6862 auto [tru_val, tru_is_null] = tru.split();
6863 auto [fals_val, fals_is_null] = fals.split();
6864 auto cond_cpy = cond.clone();
6865 return Expr<T, L>(
6866 /* value= */ emit_select(cond, tru_val, fals_val),
6867 /* is_null= */ emit_select(cond_cpy, tru_is_null, fals_is_null)
6868 );
6869 } else {
6870 return Expr<T, L>(
6871 /* value= */ emit_select(cond, tru.insist_not_null(), fals.insist_not_null())
6872 );
6873 }
6874}
6875
6876template<typename T, std::size_t L, std::size_t M>
6877requires (L > 1) and (L * sizeof(T) <= 16) and (M > 0) and (M <= 16) and (M % sizeof(T) == 0)
6878inline PrimitiveExpr<T, M / sizeof(T)>
6879Module::emit_shuffle_bytes(PrimitiveExpr<T, L> first, PrimitiveExpr<T, L> second,
6880 const std::array<uint8_t, M> &_indices)
6881{
6882 std::array<uint8_t, 16> indices;
6883 for (std::size_t idx = 0; idx < M; ++idx) {
6884 if (_indices[idx] < L * sizeof(T))
6885 indices[idx] = _indices[idx]; // given byte of `first`
6886 else if (_indices[idx] < 2 * L * sizeof(T))
6887 indices[idx] = _indices[idx] + (16 - L * sizeof(T)); // shift to given byte of `second`
6888 else
6889 indices[idx] = 15; // last byte of `first`
6890 }
6891 std::fill(indices.begin() + M, indices.end(), 15); // last byte of `first`
6892
6893 auto referenced_bits = first.referenced_bits(); // moved
6894 referenced_bits.splice(referenced_bits.end(), second.referenced_bits());
6895 auto vec = PrimitiveExpr<T, M / sizeof(T)>(
6896 /* expr= */ builder_.makeSIMDShuffle(first.expr(), second.expr(), indices),
6897 /* referenced_bits= */ std::move(referenced_bits)
6898 );
6899 return M_CONSTEXPR_COND(decltype(vec)::num_simd_lanes == 1,
6900 vec.template extract_unsafe<0>(), // extract a single value from vector to scalar
6901 vec);
6902}
6903
6904template<typename T, std::size_t L, std::size_t M>
6905requires (L > 1) and (L * sizeof(T) <= 16) and (M > 0) and (is_pow_2(M)) and (M * sizeof(T) <= 16)
6906inline PrimitiveExpr<T, M>
6907Module::emit_shuffle_lanes(PrimitiveExpr<T, L> first, PrimitiveExpr<T, L> second,
6908 const std::array<uint8_t, M> &_indices)
6909{
6910 std::array<uint8_t, 16> indices;
6911 for (std::size_t idx = 0; idx < M; ++idx) {
6912 for (std::size_t byte = 0; byte < sizeof(T); ++byte) {
6913 if (_indices[idx] < L)
6914 indices[idx * sizeof(T) + byte] = _indices[idx] * sizeof(T) + byte; // given lane of `first`
6915 else if (_indices[idx] < 2 * L)
6916 indices[idx * sizeof(T) + byte] =
6917 _indices[idx] * sizeof(T) + byte + (16 - L * sizeof(T)); // shift to given lane of `second`
6918 else
6919 indices[idx * sizeof(T) + byte] = 15; // last byte of `first`
6920 }
6921 }
6922 std::fill(indices.begin() + M * sizeof(T), indices.end(), 15); // last byte of `first`
6923
6924 auto referenced_bits = first.referenced_bits(); // moved
6925 referenced_bits.splice(referenced_bits.end(), second.referenced_bits());
6926 auto vec = PrimitiveExpr<T, M>(
6927 /* expr= */ builder_.makeSIMDShuffle(first.expr(), second.expr(), indices),
6928 /* referenced_bits= */ std::move(referenced_bits)
6929 );
6930 return M_CONSTEXPR_COND(decltype(vec)::num_simd_lanes == 1,
6931 vec.template extract_unsafe<0>(), // extract a single value from vector to scalar
6932 vec);
6933}
6934
6935template<typename T, std::size_t L, std::size_t M>
6936requires (L > 1) and (L * sizeof(T) <= 16) and (M > 0) and (M <= 16) and (M % sizeof(T) == 0) and (sizeof(T) == 1)
6937inline Expr<T, M / sizeof(T)>
6938Module::emit_shuffle_bytes(Expr<T, L> first, Expr<T, L> second, const std::array<uint8_t, M> &indices)
6939{
6940 if (first.can_be_null() or second.can_be_null()) {
6941 auto [first_val, first_is_null] = first.split();
6942 auto [second_val, second_is_null] = second.split();
6943 return Expr<T, M / sizeof(T)>(
6944 /* value= */ emit_shuffle_bytes(first_val, second_val, indices),
6945 /* is_null= */ emit_shuffle_bytes(first_is_null, second_is_null, indices)
6946 );
6947 } else {
6948 return Expr<T, M / sizeof(T)>(
6949 /* value= */ emit_shuffle_bytes(first.insist_not_null(), second.insist_not_null(), indices)
6950 );
6951 }
6952}
6953
6954template<typename T, std::size_t L, std::size_t M>
6955requires (L > 1) and (L * sizeof(T) <= 16) and (M > 0) and (is_pow_2(M)) and (M * sizeof(T) <= 16)
6956inline Expr<T, M>
6957Module::emit_shuffle_lanes(Expr<T, L> first, Expr<T, L> second, const std::array<uint8_t, M> &indices)
6958{
6959 if (first.can_be_null() or second.can_be_null()) {
6960 auto [first_val, first_is_null] = first.split();
6961 auto [second_val, second_is_null] = second.split();
6962 return Expr<T, M>(
6963 /* value= */ emit_shuffle_lanes(first_val, second_val, indices),
6964 /* is_null= */ emit_shuffle_lanes(first_is_null, second_is_null, indices)
6965 );
6966 } else {
6967 return Expr<T, M>(
6968 /* value= */ emit_shuffle_lanes(first.insist_not_null(), second.insist_not_null(), indices)
6969 );
6970 }
6971}
6972
6973inline void Module::push_branch_targets(::wasm::Name brk, ::wasm::Name continu, PrimitiveExpr<bool, 1> condition)
6974{
6975 branch_target_stack_.emplace_back(brk, continu, condition.expr());
6976}
6977
6978
6979/*======================================================================================================================
6980 * Block
6981 *====================================================================================================================*/
6982
6983inline void Block::go_to(PrimitiveExpr<bool, 1> cond) const
6984{
6985 Module::Block().list.push_back(Module::Builder().makeBreak(get().name, nullptr, cond.expr()));
6986}
6987
6988
6989/*======================================================================================================================
6990 * Specialization of `variable_storage` for local, non-`NULL` boolean.
6991 *====================================================================================================================*/
6992
6993namespace detail {
6994
6995/*----- Specialization for local variables of boolean type that *cannot* be `NULL`. ----------------------------------*/
6996
6997template<std::size_t L>
6998inline variable_storage<bool, VariableKind::Local, false, L>::variable_storage()
6999 : values_([](){
7000 std::array<std::shared_ptr<LocalBit<bit_num_simd_lanes>>, num_locals> values;
7001 for (std::size_t idx = 0; idx < num_locals; ++idx)
7002 values[idx] = std::make_shared<LocalBit<bit_num_simd_lanes>>(
7003 Module::Get().allocate_bit<bit_num_simd_lanes>()
7004 );
7005 return values;
7006 }())
7007{ }
7008
7009template<std::size_t L>
7010void variable_storage<bool, VariableKind::Local, false, L>::set_true()
7011{
7012 for (auto &local_bit : values_)
7013 local_bit->set();
7014}
7015
7016template<std::size_t L>
7017void variable_storage<bool, VariableKind::Local, false, L>::set_false()
7018{
7019 for (auto &local_bit : values_)
7020 local_bit->clear();
7021}
7022
7023template<std::size_t L>
7024template<primitive_convertible U>
7025requires requires (U &&u) { PrimitiveExpr<bool, L>(primitive_expr_t<U>(std::forward<U>(u))); }
7026void variable_storage<bool, VariableKind::Local, false, L>::operator=(U &&_value)
7027{
7028 if constexpr (num_locals == 1) {
7029 PrimitiveExpr<bool, L> value(primitive_expr_t<U>(std::forward<U>(_value)));
7030 values_[0]->set(value);
7031 } else {
7032 PrimitiveExpr<bool, L> value(primitive_expr_t<U>(std::forward<U>(_value)));
7033 static_assert(num_locals == decltype(value)::num_vectors);
7034 auto vectors = value.vectors();
7035 for (std::size_t idx = 0; idx < num_locals; ++idx)
7036 values_[idx]->set(vectors[idx]);
7037 }
7038}
7039
7040template<std::size_t L>
7041inline variable_storage<bool, VariableKind::Local, false, L>::operator PrimitiveExpr<bool, L>() const
7042{
7043 if constexpr (num_locals == 1) {
7044 return PrimitiveExpr<bool, L>(/* expr= */ values_[0]->is_set().expr(), /* referenced_bits= */ { values_[0] });
7045 } else {
7046 static_assert(num_locals == PrimitiveExpr<bool, L>::num_vectors);
7047 std::array<typename PrimitiveExpr<bool, L>::vector_type, num_locals> vectors;
7048 for (std::size_t idx = 0; idx < num_locals; ++idx)
7049 vectors[idx] = typename PrimitiveExpr<bool, L>::vector_type(
7050 /* expr= */ values_[idx]->is_set().expr(),
7051 /* referenced_bits= */ { values_[idx] }
7052 );
7053 return PrimitiveExpr<bool, L>(std::move(vectors));
7054 }
7055}
7056
7057}
7058
7059#undef UNARY_LIST
7060#undef BINARY_LIST
7061#undef ASSIGNOP_LIST
7062
7063
7064/*======================================================================================================================
7065 * explicit instantiation declarations
7066 *====================================================================================================================*/
7067
7068extern template void Module::emit_insist(PrimitiveExpr<bool, 2>, const char*, unsigned, const char*);
7069extern template void Module::emit_insist(PrimitiveExpr<bool, 4>, const char*, unsigned, const char*);
7070extern template void Module::emit_insist(PrimitiveExpr<bool, 8>, const char*, unsigned, const char*);
7071extern template void Module::emit_insist(PrimitiveExpr<bool, 16>, const char*, unsigned, const char*);
7072extern template void Module::emit_insist(PrimitiveExpr<bool, 32>, const char*, unsigned, const char*);
7073
7074}
7075
7076}
#define UNARY(OP, TYPE)
#define BINARY(OP, TYPE)
and(sizeof(T)==4) U64x1 reinterpret_to_U64(m
Definition: WasmAlgo.cpp:266
#define REGISTER_USE(VAR)
Definition: WasmDSL.hpp:5522
#define ASSIGNOP_LIST(X)
#define M_EXCEPTION_LIST(X)
Definition: WasmDSL.hpp:548
#define BINVOP_(NAME, SIGN, TYPE)
#define BINARY_OP(NAME, SIGN)
#define BINIOP_(NAME, SIGN)
#define USING(TYPE, NAME)
#define UNIOP_(NAME)
Definition: WasmDSL.hpp:2094
#define BINFVOP_(NAME)
#define CALLBACK(NAME, FUNC)
#define BINARY_LIST(X)
List of supported binary operators on PrimitiveExpr, Expr, Variable, etc.
Definition: WasmDSL.hpp:4100
#define DECLARE_ENUM(TYPE)
Definition: WasmDSL.hpp:555
#define M_insist_no_ternary_logic()
Definition: WasmDSL.hpp:45
#define CMP_OP(SYMBOL)
#define UNFOP_(NAME)
Definition: WasmDSL.hpp:2102
#define DECLARE_NAMES(TYPE)
Definition: WasmDSL.hpp:561
#define UNIVOP_(NAME)
Definition: WasmDSL.hpp:2111
#define BINARY_VOP(NAME, SIGN)
#define UNVOP_(NAME, TYPE)
Definition: WasmDSL.hpp:2110
#define UNARY_VOP(NAME)
Definition: WasmDSL.hpp:2131
#define ASSIGNOP(SYMBOL)
Definition: WasmDSL.hpp:5862
#define BINFOP_(NAME)
#define SHIFT(OP)
#define MAKE_BINARY(OP)
Definition: WasmDSL.hpp:5098
#define BINARY(OP)
Definition: WasmDSL.hpp:4804
#define BINOP_(NAME, SIGN, TYPE)
#define UNFVOP_(NAME)
Definition: WasmDSL.hpp:2123
#define UNARY(OP)
Definition: WasmDSL.hpp:5642
#define USING_N(TYPE, LENGTH, NAME)
#define Wasm_insist(...)
Definition: WasmDSL.hpp:373
struct @5 args
friend void swap(local_bit_storage &first, local_bit_storage &second)
Definition: WasmDSL.hpp:5952
local_bit_storage(LocalBitmap &bitmap, uint8_t bit_offset)
Creates a single bit with storage allocated in bitmap.
Definition: WasmDSL.hpp:5963
uint8_t bit_offset_
the offset of the single bit
Definition: WasmDSL.hpp:5959
Helper class to select appropriate storage for a LocalBit.
Definition: WasmDSL.hpp:5920
uint8_t starting_lane_
the lane index at which the L bits start
Definition: WasmDSL.hpp:5932
uint8_t bit_offset_
the offset of each bit in every lane
Definition: WasmDSL.hpp:5931
friend void swap(local_bit_storage &first, local_bit_storage &second)
Definition: WasmDSL.hpp:5923
LocalBitvector * bitvector_
the bitvector in which the multiple bits are contained
Definition: WasmDSL.hpp:5930
local_bit_storage(LocalBitvector &bitvector, uint8_t bit_offset, uint8_t starting_lane)
Creates multiple bits with storage allocated in bitvector.
Definition: WasmDSL.hpp:5936
Check whether.
Definition: concepts.hpp:35
Check whether.
Definition: concepts.hpp:14
Check whether.
Definition: concepts.hpp:52
Check whether.
Definition: concepts.hpp:100
Check whether.
Definition: concepts.hpp:23
Check whether.
Definition: concepts.hpp:173
Check whether.
Definition: concepts.hpp:47
Check whether.
Definition: concepts.hpp:39
Check whether.
Definition: concepts.hpp:43
Check whether types.
Definition: concepts.hpp:56
Check whether.
Definition: concepts.hpp:27
Check whether.
Definition: concepts.hpp:31
Check whether.
Definition: WasmDSL.hpp:60
Detect whether a type.
Definition: WasmDSL.hpp:210
Detects whether a type.
Definition: WasmDSL.hpp:155
#define M_unreachable(MSG)
Definition: macro.hpp:146
#define M_CONSTEXPR_COND(COND, IF_TRUE, IF_FALSE)
Definition: macro.hpp:54
#define M_notnull(ARG)
Definition: macro.hpp:182
#define M_insist(...)
Definition: macro.hpp:129
#define M_CONSTEXPR_COND_UNCAPTURED(COND, IF_TRUE, IF_FALSE)
Definition: macro.hpp:57
const Expr
Definition: AST.hpp:437
and arithmetically_combinable< T, U, L > auto L auto copy_sign(PrimitiveExpr< U, L > other) -> PrimitiveExpr< common_type_t< T, U >, L > and(L
typename expr< T >::type expr_t
Convenience alias for expr.
Definition: WasmDSL.hpp:205
auto indices
Definition: WasmDSL.hpp:2429
PrimitiveExpr< To, ToL > std::size_t ToL
Definition: WasmDSL.hpp:2016
VariableKind
Declares the kind of a variable: local, parameter, or global.
Definition: WasmDSL.hpp:72
friend struct PrimitiveExpr
‍Constructs an empty PrimitiveExpr, for which operator bool() returns false.
Definition: WasmDSL.hpp:1469
To ToL to()
Definition: WasmDSL.hpp:1985
::wasm::Expression * expr()
Moves the underlying Binaryen ::wasm::Expression out of this.
Definition: WasmDSL.hpp:1558
auto ShuffleBytes(T &&_first, U &&_second, const std::array< uint8_t, M > &indices)
Definition: WasmDSL.hpp:6288
and(L<=16) struct LocalBit< L > void RETURN_UNSAFE()
A scalar bit or a vector of bits that is managed by the current function's stack.
Definition: WasmDSL.hpp:6165
friend struct FunctionProxy
Definition: WasmDSL.hpp:1476
bool can_be_null(const SQL_t &variant)
Definition: WasmUtil.hpp:452
Bool< L > not_null(SQL_t &variant)
Definition: WasmUtil.hpp:475
typename detail::_var_helper< T >::type _Var
Local variable that can always be NULL.
Definition: WasmDSL.hpp:5784
auto op_low
Definition: WasmDSL.hpp:2409
PrimitiveExpr & operator=(PrimitiveExpr other)
Assigns other to this.
Definition: WasmDSL.hpp:1546
PrimitiveExpr< bool, L > and_not(PrimitiveExpr< U, L > other) and(L > 1)
Computes the logical conjunction (and) of this and the logical negation (not) of other.
Definition: WasmDSL.hpp:2902
Bool< L > T mask
Definition: WasmUtil.hpp:1325
typename detail::var_helper< T >::type Var
Local variable.
Definition: WasmDSL.hpp:5779
PrimitiveExpr replace(U &&_value)
Definition: WasmDSL.hpp:2969
and arithmetically_combinable< T, U, L > auto operator/(PrimitiveExpr< U, L > other) -> PrimitiveExpr< common_type_t< T, U >, L >
Divides this by other.
Definition: WasmDSL.hpp:2436
auto make_unsigned()
Conversion of a PrimitiveExpr<T, L> to a PrimitiveExpr<std::make_unsigned_t<T>, L>.
Definition: WasmDSL.hpp:3658
PrimitiveExpr< T, 16/sizeof(T)> vector_type
‍the type of a single fully utilized vector
Definition: WasmDSL.hpp:3077
std::list< std::shared_ptr< Bit > > referenced_bits()
Moves the referenced bits out of this.
Definition: WasmDSL.hpp:1563
auto referenced_bits_low
Definition: WasmDSL.hpp:2415
PrimitiveExpr< bool, L > eqz() and(L
auto make_signed()
Conversion of a PrimitiveExpr<T, L> to a PrimitiveExpr<std::make_signed_t<T>, L>.
Definition: WasmDSL.hpp:3650
void GOTO(const Block &block)
Jumps to the end of block.
Definition: WasmDSL.hpp:6199
PrimitiveExpr< bool, L > any_true() and(L > 1)
Returns true iff any value is non-zero.
Definition: WasmDSL.hpp:2238
::wasm::Literals insist_interpreter(::wasm::Literals &args)
Reports a runtime error.
Definition: WasmDSL.cpp:516
and
Constructs a new PrimitiveExpr from a constant value.
Definition: WasmDSL.hpp:1519
std::size_t L
Definition: WasmDSL.hpp:528
PrimitiveExpr< U, M > convert()
Definition: WasmDSL.hpp:1635
PrimitiveExpr< To, 8 > low(Module::Builder().makeBinary(op_low, this_cpy.expr(), other_cpy.expr()), std::move(referenced_bits_low))
typename detail::global_helper< T >::type Global
Global variable.
Definition: WasmDSL.hpp:5789
Bool< L > value
Definition: WasmUtil.hpp:1317
Bool< L > is_null(SQL_t &variant)
Definition: WasmUtil.hpp:461
auto Select(C &&_cond, T &&_tru, U &&_fals)
Definition: WasmDSL.hpp:6215
PrimitiveExpr< ResultType, ResultL > unary(::wasm::UnaryOp op)
Helper function to implement unary operations.
Definition: WasmDSL.hpp:1607
auto operator*(PrimitiveExpr< U, L > other) -> PrimitiveExpr< common_type_t< T, U >, L >
Multiplies this and other.
Definition: WasmDSL.hpp:2375
and ReturnL(PrimitiveExpr< ParamTypes, ParamLs >...)>
Definition: WasmDSL.hpp:1147
std::array< vector_type, num_vectors > vectors()
Moves the underlying PrimitiveExpr<T, 16 / sizeof(T)>s out of this.
Definition: WasmDSL.hpp:3148
and arithmetically_combinable< T, U, L > PrimitiveExpr< bool, L > operator>=(PrimitiveExpr< U, L > other) arithmetic< T >
Checks whether this greater than or equals to other.
Definition: WasmDSL.hpp:2852
and arithmetically_combinable< T, U, L > auto operator%(PrimitiveExpr< U, L > other) -> PrimitiveExpr< common_type_t< T, U >, L > integral< T > and(L
Computes the remainder of dividing this by other.
auto op_high
Definition: WasmDSL.hpp:2411
PrimitiveExpr L add_pairwise() and(sizeof(T)
auto max(PrimitiveExpr< U, L > other) -> PrimitiveExpr< common_type_t< T, U >, L > std
Computes the maximum of this and other.
Definition: WasmDSL.hpp:2504
void discard()
Discards this.
Definition: WasmDSL.hpp:1588
and arithmetically_combinable< T, U, L > PrimitiveExpr< bool, L > operator!=(PrimitiveExpr< U, L > other)
Checks whether this unequal to other.
Definition: WasmDSL.hpp:2706
std::array< vector_type, num_vectors > vectors_
‍the fully utilized SIMD vectors represented as PrimitiveExprs
Definition: WasmDSL.hpp:3095
auto wasm_type()
Definition: WasmDSL.hpp:293
PrimitiveExpr< uint64_t, L > L L L L U
Definition: WasmDSL.hpp:2352
PrimitiveExpr swizzle_bytes(PrimitiveExpr< uint8_t, 16 > indices)
Selects lanes of this in byte granularity depending on the indices specified by indices.
Definition: WasmDSL.hpp:3001
::wasm::Expression * expr_
‍the referenced Binaryen expression (AST)
Definition: WasmDSL.hpp:1485
for(std::size_t idx=1;idx< num_vectors;++idx) res.emplace((vectors_[idx].bitmask()<< uint32_t(idx *vector_type return * res
Definition: WasmDSL.hpp:3696
auto op
Definition: WasmDSL.hpp:2384
PrimitiveExpr< To, 8 > high(Module::Builder().makeBinary(op_high, this->expr(), other.expr()), std::move(referenced_bits_high))
PrimitiveExpr< T, 1 > extract()
Extracts the.
Definition: WasmDSL.hpp:2963
PrimitiveExpr< T, 1 > extract_unsafe()
Extracts the.
Definition: WasmDSL.hpp:2932
typename primitive_expr< T >::type primitive_expr_t
Convenience alias for primitive_expr.
Definition: WasmDSL.hpp:150
PrimitiveExpr< bool, 1 > all_true() and(L > 1)
Returns true iff all values are true.
Definition: WasmDSL.hpp:2250
std::size_t bool
Definition: WasmDSL.hpp:528
PrimitiveExpr< ResultType, ResultL > binary(::wasm::BinaryOp op, PrimitiveExpr< OperandType, OperandL > other)
Helper function to implement binary operations.
Definition: WasmDSL.hpp:1617
std::list< std::shared_ptr< Bit > > referenced_bits_
‍a list of referenced Bits
Definition: WasmDSL.hpp:1487
and std::floating_point< T > and std::floating_point< U >inline ::wasm::Literal make_literal(U value)
Creates a ::wasm::Literal of type.
Definition: WasmDSL.hpp:444
std::string unique(std::string prefix, unsigned &counter)
Creates a unique name from a given prefix and a counter.
Definition: WasmDSL.hpp:433
::wasm::Literals throw_interpreter(::wasm::Literals &args)
Throws an exception.
Definition: WasmDSL.cpp:531
void CONTINUE(std::size_t level=1)
Definition: WasmDSL.hpp:6187
M swizzle_lanes(const std::array< uint8_t, M > &_indices)
Definition: WasmDSL.hpp:3033
static constexpr std::size_t num_vectors
‍the number of SIMD vectors needed for the represented expression
Definition: WasmDSL.hpp:3079
const std::map<::wasm::Name, std::function<::wasm::Literals(::wasm::Literals &)> > callback_functions
Definition: WasmDSL.hpp:587
typename _int< W >::type int_t
Definition: WasmDSL.hpp:317
and arithmetically_combinable< T, U, L > PrimitiveExpr< bool, L > operator<(PrimitiveExpr< U, L > other) arithmetic< T >
Checks whether this less than other.
Definition: WasmDSL.hpp:2723
std::conditional_t< std::is_signed_v< T >, int16_t, uint16_t > To
Definition: WasmDSL.hpp:2408
std::pair<::wasm::Expression *, std::list< std::shared_ptr< Bit > > > move()
Moves the underlying Binaryen ::wasm::Expression and the referenced bits out of this.
Definition: WasmDSL.hpp:1567
typename uint< W >::type uint_t
Definition: WasmDSL.hpp:340
auto operator>>(PrimitiveExpr< U, L > other) -> PrimitiveExpr< common_type_t< T, U >, L > integral< T > and(L
Shifts this right by other.
PrimitiveExpr< uint64_t, L > hash() and(L
PrimitiveExpr clone() const
Creates and returns a deep copy of this.
Definition: WasmDSL.hpp:1577
and arithmetically_combinable< T, U, L > PrimitiveExpr< bool, L > operator>(PrimitiveExpr< U, L > other) arithmetic< T >
Checks whether this greater than to other.
Definition: WasmDSL.hpp:2809
void dump(std::ostream &out) const
Definition: WasmDSL.hpp:3061
auto this_cpy
Definition: WasmDSL.hpp:2413
and arithmetically_combinable< T, U, L > PrimitiveExpr< bool, L > operator<=(PrimitiveExpr< U, L > other) arithmetic< T >
Checks whether this less than or equals to other.
Definition: WasmDSL.hpp:2766
and arithmetically_combinable< T, U, L > auto L auto L auto min(PrimitiveExpr< U, L > other) -> PrimitiveExpr< common_type_t< T, U >, L >
Definition: WasmDSL.hpp:2474
auto ShuffleLanes(T &&_first, U &&_second, const std::array< uint8_t, M > &indices)
Definition: WasmDSL.hpp:6306
void BREAK(std::size_t level=1)
Definition: WasmDSL.hpp:6176
auto referenced_bits_high
Definition: WasmDSL.hpp:2416
PrimitiveExpr L L L L bitmask() and(L > 1)
Concatenates the most significant bit of each value of this into a single mask.
Definition: WasmDSL.hpp:2218
auto other_cpy
Definition: WasmDSL.hpp:2414
static constexpr std::size_t num_simd_lanes
‍the number of SIMD lanes of the represented expression, i.e. 1 for scalar and at least 2 for vectori...
Definition: WasmDSL.hpp:1466
friend struct LocalBit
Definition: WasmDSL.hpp:1477
‍mutable namespace
Definition: Backend.hpp:10
M_EXPORT constexpr bool is_pow_2(T n)
Definition: fn.hpp:129
Schema operator+(const Schema &left, const Schema &right)
Definition: Schema.hpp:250
void swap(PlanTableBase< Actual > &first, PlanTableBase< Actual > &second)
Definition: PlanTable.hpp:394
T(x)
and
Definition: enum_ops.hpp:12
typename conditional_one< Cond, TrueType, FalseType >::template type< Arg > conditional_one_t
Definition: type_traits.hpp:18
constexpr T operator~(T t)
Definition: enum_ops.hpp:53
typename common_type< T, U >::type common_type_t
Convenience alias for common_type<T, U>::type.
Definition: concepts.hpp:96
constexpr T operator-(T left, T right)
Definition: enum_ops.hpp:41
M_LCOV_EXCL_START std::ostream & operator<<(std::ostream &out, const PlanTableBase< Actual > &PT)
Definition: PlanTable.hpp:401
and arithmetic< U > and same_signedness< T, U > U
Definition: concepts.hpp:90
bool M_EXPORT init(void)
Initializes the mu*t*able library.
Definition: mutable.cpp:23
STL namespace.
bool operator==(std::reference_wrapper< T > left, std::reference_wrapper< T > right)
Definition: hash.hpp:43
This class represents a reserved address space in virtual memory.
Definition: memory.hpp:45
Helper struct for parameter packs.
Definition: concepts.hpp:138
Definition: tag.hpp:8
void free(T &&ptr, U &&count)
Definition: WasmDSL.hpp:6583
virtual void * raw_allocate(uint32_t bytes, uint32_t align=1)=0
Pre-allocates memory for bytes consecutive bytes with alignment requirement align and returns a raw p...
void deallocate(Ptr< void > ptr, uint32_t bytes)
Definition: WasmDSL.hpp:6519
virtual uint32_t perform_pre_allocations()=0
Performs the actual pre-allocations.
void free(T &&ptr)
Frees exactly one value of type.
Definition: WasmDSL.hpp:6577
T * raw_malloc(uint32_t count)
Pre-allocates memory for an array of count consecutive values of type.
Definition: WasmDSL.hpp:6537
virtual U32x1 allocated_memory_consumption() const =0
Returns the allocated memory overall consumption.
T * raw_malloc()
Pre-allocates memory for exactly one value of type.
Definition: WasmDSL.hpp:6524
Var< Ptr< void > > allocate(uint32_t bytes, uint32_t align=1)
Definition: WasmDSL.hpp:6518
Ptr< PrimitiveExpr< T, L > > pre_malloc()
Pre-allocates memory for exactly one value of type.
Definition: WasmDSL.hpp:6528
virtual U32x1 allocated_memory_peak() const =0
Returns the allocated memory peak consumption.
virtual ~Allocator()
Definition: WasmDSL.hpp:6491
virtual Var< Ptr< void > > allocate(U32x1 bytes, uint32_t align=1)=0
Allocates memory for bytes consecutive bytes with alignment requirement align and returns a pointer t...
virtual uint32_t pre_allocated_memory_consumption() const =0
Returns the pre-allocated memory overall consumption.
Var< Ptr< PrimitiveExpr< T, L > > > malloc()
Allocates memory for exactly one value of type.
Definition: WasmDSL.hpp:6532
virtual void deallocate(Ptr< void > ptr, U32x1 bytes)=0
Deallocates the bytes consecutive bytes of allocated memory at address ptr.
virtual Ptr< void > pre_allocate(uint32_t bytes, uint32_t align=1)=0
Pre-allocates memory for bytes consecutive bytes with alignment requirement align and returns a point...
Ptr< PrimitiveExpr< T, L > > pre_malloc(uint32_t count)
Pre-allocates memory for an array of count consecutive values of type.
Definition: WasmDSL.hpp:6541
Var< Ptr< PrimitiveExpr< T, L > > > malloc(U &&count)
Allocates memory for an array of count consecutive values of type.
Definition: WasmDSL.hpp:6553
A helper class to use a Block, thereby setting the Block active for code generation.
Definition: WasmDSL.hpp:1118
Block & block_
the block to use (for code gen)
Definition: WasmDSL.hpp:1120
BlockUser(Block &block)
Definition: WasmDSL.hpp:1124
Represents a code block, i.e.
Definition: WasmDSL.hpp:1005
void attach_to(::wasm::Block &other)
Definition: WasmDSL.hpp:1065
void attach_to_current()
Attaches this Block to the wasm::Block currently active in the Module.
Definition: WasmDSL.hpp:1084
bool empty() const
Returns whether this Block is empty, i.e.
Definition: WasmDSL.hpp:1075
void dump() const
Definition: WasmDSL.hpp:1112
friend void swap(Block &first, Block &second)
Definition: WasmDSL.hpp:1021
Block & operator=(Block &&other)
Definition: WasmDSL.hpp:1059
Block(::wasm::Block *block, bool attach_to_parent)
Create a new Block for a given ::wasm::Block.
Definition: WasmDSL.hpp:1032
::wasm::Block & get() const
Definition: WasmDSL.hpp:1062
void go_to() const
Emits a jump to the end of this Block.
Definition: WasmDSL.hpp:1090
bool attach_to_parent_
‍whether this block attaches itself to its parent block
Definition: WasmDSL.hpp:1018
Block(bool attach_to_parent)
Create an anonymous Block.
Definition: WasmDSL.hpp:1044
Block()=default
void dump(std::ostream &out) const
Definition: WasmDSL.hpp:1111
::wasm::Block * this_block_
‍this block, can be nullptr if default-constructed or the block has already been attached
Definition: WasmDSL.hpp:1014
void attach_to(Block &other)
Attaches this Block to the given Block other.
Definition: WasmDSL.hpp:1078
Block(std::string name, bool attach_to_parent)
Create a named Block and set it active in the current Module.
Definition: WasmDSL.hpp:1046
::wasm::Block & previous() const
Definition: WasmDSL.hpp:1063
Block(Block &&other)
Definition: WasmDSL.hpp:1052
std::string name() const
Definition: WasmDSL.hpp:1072
bool has_name() const
Definition: WasmDSL.hpp:1071
::wasm::Block * parent_block_
‍the parent block, before this block was created
Definition: WasmDSL.hpp:1016
friend std::ostream & operator<<(std::ostream &out, const Block &B)
Definition: WasmDSL.hpp:1094
Block(const char *name, bool attach_to_parent)
Create a named Block and set it active in the current Module.
Definition: WasmDSL.hpp:1050
Helper struct to perform constant folding at compile time.
Definition: WasmDSL.hpp:622
DoWhile(const DoWhile &)=delete
DoWhile(DoWhile &&)=default
DoWhile(const char *name, C &&cond)
Definition: WasmDSL.hpp:6446
DoWhile(std::string name, C &&_cond)
Definition: WasmDSL.hpp:6434
An Expr<T, L> combines a PrimitiveExpr<T, L> value with a PrimitiveExpr<bool, L>, called NULL informa...
Definition: WasmDSL.hpp:4515
Expr< T, ToL > broadcast()
Broadcasts a PrimitiveExpr<T, 1> to a PrimitiveExpr<T, ToL>.
Definition: WasmDSL.hpp:4715
Expr(PrimitiveExpr< T, L > value, PrimitiveExpr< bool, L > is_null)
‍Constructs an Expr from a value and NULL information is_null.
Definition: WasmDSL.hpp:4538
bool can_be_null() const
Returns true if this may be NULL, false otherwise.
Definition: WasmDSL.hpp:4636
Expr< To, ToL > reinterpret()
Reinterpret an Expr<T, L> to an Expr<To, ToL>.
Definition: WasmDSL.hpp:4709
PrimitiveExpr< T, L > value_
‍the referenced value expression
Definition: WasmDSL.hpp:4526
Expr(Us... value)
Definition: WasmDSL.hpp:4555
Expr swizzle_bytes(PrimitiveExpr< uint8_t, 16 > indices) and
Selects lanes of this in byte granularity depending on the indices specified by indices.
Definition: WasmDSL.hpp:5045
Expr< uint32_t, 1 > bitmask()
Definition: WasmDSL.hpp:4766
PrimitiveExpr< uint64_t, L > hash()
Definition: WasmDSL.hpp:4791
void discard()
Discards this.
Definition: WasmDSL.hpp:4623
UNARY_LIST(UNARY) auto add_pairwise()
Definition: WasmDSL.hpp:4750
Expr(Expr &other)
‍Constructs a new Expr by moving the underlying value_ and is_null_ of other to this.
Definition: WasmDSL.hpp:4569
Expr< bool, L > and_not(Expr< bool, L > other)
Implements logical and not according to 3VL of Kleene and Priest's logic.
Definition: WasmDSL.hpp:4940
Expr & operator=(Expr &&)=delete
Expr< T, 1 > extract()
Extracts the.
Definition: WasmDSL.hpp:5022
Expr(PrimitiveExpr< T, L > value)
‍Implicitly constructs an Expr from a value.
Definition: WasmDSL.hpp:4533
Expr(Expr &&other)
‍Constructs a new Expr by moving the underlying value_ and is_null_ of other to this.
Definition: WasmDSL.hpp:4571
PrimitiveExpr< bool, L > is_false_and_not_null()
Returns true if the value is false and NOT NULL.
Definition: WasmDSL.hpp:4671
PrimitiveExpr< bool, L > is_null()
Returns true if this is NULL, false otherwise.
Definition: WasmDSL.hpp:4639
and(dsl_primitive< std::decay_t< Us > > and ...) and
Constructs an Expr from a decayable constant value.
Definition: WasmDSL.hpp:4561
PrimitiveExpr< bool, L > is_true_and_not_null()
Returns true if the value is true and NOT NULL.
Definition: WasmDSL.hpp:4662
PrimitiveExpr< bool, L > not_null()
Returns true if this is NOT NULL, false otherwise.
Definition: WasmDSL.hpp:4650
BINARY(operator+)BINARY(operator-)BINARY(operator*)BINARY(operator/)BINARY(operator%)BINARY(operator bitand) BINARY(operator bitor) BINARY(operator xor) BINARY(operator<<) BINARY(operator>>) BINARY(operator
Expr< bool, 1 > any_true()
Definition: WasmDSL.hpp:4775
std::pair< PrimitiveExpr< T, L >, PrimitiveExpr< bool, L > > split()
Splits this Expr into a PrimitiveExpr<T, L> with the value and a PrimitiveExpr<bool,...
Definition: WasmDSL.hpp:4604
Expr< bool, 1 > all_true()
Definition: WasmDSL.hpp:4782
Expr(std::pair< PrimitiveExpr< T, L >, PrimitiveExpr< bool, L > > value)
‍Constructs an Expr from a std::pair value of value and NULL info.
Definition: WasmDSL.hpp:4548
Expr clone() const
Returns a deep copy of this.
Definition: WasmDSL.hpp:4614
static Expr Null()
Returns an Expr that is NULL.
Definition: WasmDSL.hpp:4685
std::pair< PrimitiveExpr< T, L >, PrimitiveExpr< bool, L > > split_unsafe()
Splits this Expr into a PrimitiveExpr<T, L> with the value and a PrimitiveExpr<bool,...
Definition: WasmDSL.hpp:4584
Expr replace(U &&_value)
Replaces the.
Definition: WasmDSL.hpp:5032
PrimitiveExpr< T, L > insist_not_null()
Moves the current value_ out of this.
Definition: WasmDSL.hpp:4595
Expr(const Expr &)=delete
Expr< To, ToL > to()
Explicitly converts an Expr<T, L> to an Expr<To, ToL>.
Definition: WasmDSL.hpp:4703
A handle to create a Function and to create invocations of that function.
Definition: WasmDSL.hpp:1367
Represents a Wasm function.
Definition: WasmDSL.hpp:1139
Helper struct for garbage collection done by the Module.
Definition: WasmDSL.hpp:603
GarbageCollectedData(GarbageCollectedData &&)=default
std::function< void(void)> continuation_t
Definition: WasmDSL.hpp:6358
If(C &&cond)
Definition: WasmDSL.hpp:6369
continuation_t Else
Definition: WasmDSL.hpp:6365
If(If &&)=delete
If(const If &)=delete
std::string name_
Definition: WasmDSL.hpp:6362
PrimitiveExpr< bool, 1 > cond_
Definition: WasmDSL.hpp:6361
LocalBitmap(const LocalBitmap &)=delete
Var< U64x1 > u64
Definition: WasmDSL.hpp:5885
LocalBitvector(const LocalBitvector &)=delete
std::array< uint16_t, 8 > bitmask_per_offset
‍entry at index i is a bitmask for the 16 lanes using the constant bit offset i
Definition: WasmDSL.hpp:5899
Implements a loop which iterates exactly once unless explicitly continue-ed.
Definition: WasmDSL.hpp:6385
Block & body()
Definition: WasmDSL.hpp:6426
const Block & body() const
Definition: WasmDSL.hpp:6427
Loop(std::string name)
Definition: WasmDSL.hpp:6409
::wasm::Loop * loop_
the Binaryen loop
Definition: WasmDSL.hpp:6394
Loop(std::string name, tag< int >)
Convenience c'tor accessible via tag-dispatching.
Definition: WasmDSL.hpp:6398
Loop(const char *name)
Definition: WasmDSL.hpp:6410
Loop & operator=(Loop &&other)
Definition: WasmDSL.hpp:6422
Block body_
the loop body
Definition: WasmDSL.hpp:6393
Loop(const Loop &)=delete
Loop(Loop &&other)
Definition: WasmDSL.hpp:6413
std::string name() const
Definition: WasmDSL.hpp:6424
friend void swap(Loop &first, Loop &second)
Definition: WasmDSL.hpp:6386
::wasm::Builder & Builder()
Returns the expression builder of the current module.
Definition: WasmDSL.hpp:739
C & add_garbage_collected_data(void *handle, Args... args)
Adds and returns an instance of.
Definition: WasmDSL.hpp:916
std::unique_ptr<::wasm::ModuleRunner::ExternalInterface > interface_
‍this module's interface, if any
Definition: WasmDSL.hpp:690
::wasm::ModuleRunner instantiate(::wasm::GlobalValueSet imports={})
Create an instance of this module.
Definition: WasmDSL.hpp:928
std::vector< branch_target_t > branch_target_stack_
‍stack of Binaryen branch targets
Definition: WasmDSL.hpp:686
static std::string Unique_Global_Name(std::string prefix="global")
Returns a unique global name in the current module.
Definition: WasmDSL.hpp:730
::wasm::Block * active_block_
‍the currently active Binaryen block
Definition: WasmDSL.hpp:676
void dump_all(std::ostream &out)
Definition: WasmDSL.hpp:993
std::unique_ptr< std::pair< memory::AddressSpace, memory::Memory > > vm_
‍the virtual address space and its backed memory; only set if no WasmContext was created
Definition: WasmDSL.hpp:682
std::unordered_map< void *, std::unique_ptr< GarbageCollectedData > > garbage_collected_data_
‍mapping from handles to garbage collected data
Definition: WasmDSL.hpp:696
static void Dispose()
Definition: WasmDSL.hpp:710
void push_branch_targets(::wasm::Name brk, ::wasm::Name continu)
Definition: WasmDSL.hpp:949
void emit_import(const char *extern_name, const char *intern_name=nullptr)
Definition: WasmDSL.hpp:865
static thread_local std::unique_ptr< Module > the_module_
Definition: WasmDSL.hpp:700
std::vector< std::vector< LocalBitmap * > > local_bitmaps_stack_
‍the per-function stacks of local bitmaps; used for local scalar boolean variables and NULL bits
Definition: WasmDSL.hpp:692
static std::string Unique_Function_Name(std::string prefix="function")
Returns a unique function name in the current module.
Definition: WasmDSL.hpp:726
void emit_global(const std::array<::wasm::Name, N > &names, bool is_mutable, Us... inits)
Definition: WasmDSL.hpp:838
void emit_global(::wasm::Name name, bool is_mutable, Us... inits)
Definition: WasmDSL.hpp:824
friend std::ostream & operator<<(std::ostream &out, const Module &M)
Definition: WasmDSL.hpp:965
static Module & Get()
Definition: WasmDSL.hpp:714
unsigned id_
‍the unique ID for this Module
Definition: WasmDSL.hpp:660
void dump() const
Definition: WasmDSL.hpp:991
void emit_global(const std::array<::wasm::Name, 1 > &names, bool is_mutable, Us... inits)
Definition: WasmDSL.hpp:833
static void Init()
Definition: WasmDSL.hpp:706
void emit_function_import(const char *name)
Definition: WasmDSL.hpp:879
static std::string Unique_Block_Name(std::string prefix="block")
Returns a unique block name in the current module.
Definition: WasmDSL.hpp:724
static std::string Unique_Loop_Name(std::string prefix="loop")
Returns a unique loop name in the current module.
Definition: WasmDSL.hpp:736
const branch_target_t & current_branch_targets() const
Definition: WasmDSL.hpp:961
void dump(std::ostream &out) const
Definition: WasmDSL.hpp:990
static unsigned ID()
Returns the ID of the current module.
Definition: WasmDSL.hpp:721
void emit_function_export(const char *name)
Add function name as export.
Definition: WasmDSL.hpp:886
Module(const Module &)=delete
void set_feature(::wasm::FeatureSet feature, bool value)
Definition: WasmDSL.hpp:933
::wasm::Block * set_active_block(::wasm::Block *block)
Sets the new active ::wasm::Block and returns the previously active ::wasm::Block.
Definition: WasmDSL.hpp:760
::wasm::Function & Function()
Returns the currently active function.
Definition: WasmDSL.hpp:745
std::vector< std::vector< LocalBitvector * > > local_bitvectors_stack_
‍the per-function stacks of local bitvectors; used for local vectorial boolean variables and NULL bit...
Definition: WasmDSL.hpp:694
std::vector< std::tuple< const char *, unsigned, const char * > > messages_
‍filename, line, and an optional message for each emitted insist or exception throw
Definition: WasmDSL.hpp:688
void emit_global(::wasm::Name name, bool is_mutable, uint32_t init)
Definition: WasmDSL.hpp:849
::wasm::Function * set_active_function(::wasm::Function *fn)
Sets the new active ::wasm::Function and returns the previously active ::wasm::Function.
Definition: WasmDSL.hpp:762
std::unique_ptr< Allocator > allocator_
‍the allocator
Definition: WasmDSL.hpp:684
branch_target_t pop_branch_targets()
Definition: WasmDSL.hpp:955
static std::string Unique_If_Name(std::string prefix="if")
Returns a unique if name in the current module.
Definition: WasmDSL.hpp:734
::wasm::Module module_
‍the Binaryen Wasm module
Definition: WasmDSL.hpp:672
static Allocator & Allocator()
Returns the allocator.
and(L *sizeof(T)<=16) and(M > 0) and(M<
Selects lanes of first and second in byte granularity depending on the indices specified by indices.
::wasm::Block & Block()
Returns the currently active block.
Definition: WasmDSL.hpp:742
void dump_all()
Definition: WasmDSL.hpp:994
::wasm::Builder builder_
‍the Binaryen expression builder for the module_
Definition: WasmDSL.hpp:674
const std::tuple< const char *, unsigned, const char * > & get_message(std::size_t idx) const
Definition: WasmDSL.hpp:907
Parameter(unsigned index)
Create a Parameter<T, L> for the existing parameter local of given index.
Definition: WasmDSL.hpp:5814
typename base_type::dependent_expr_type dependent_expr_type
Definition: WasmDSL.hpp:5808
Variable & operator=(Variable &&other)
Definition: WasmDSL.hpp:5548
auto swizzle_bytes(PrimitiveExpr< uint8_t, 16 > indices) const
Selects lanes of this in byte granularity depending on the indices specified by indices.
Definition: WasmDSL.hpp:5707
auto swizzle_lanes(const std::array< uint8_t, M > &indices) const
Selects lanes of this in lane granularity depending on the indices specified by indices.
Definition: WasmDSL.hpp:5715
storage_type storage_
‍storage of this Variable
Definition: WasmDSL.hpp:5516
dependent_expr_type val() const
Obtain a Variable<T, L>s value as a PrimitiveExpr<T, L> or Expr<T, L>, depending on CanBeNull.
Definition: WasmDSL.hpp:5584
dependent_expr_t< PrimitiveExpr< T, ToL > > broadcast() const
Definition: WasmDSL.hpp:5606
Variable & operator=(const Variable &other)
Definition: WasmDSL.hpp:5547
Variable(Us &&... value)
Constructs a new Variable and initializes it with value.
Definition: WasmDSL.hpp:5559
conditional_one_t< CanBeNull, expr_t, primitive_expr_t, X > dependent_expr_t
Definition: WasmDSL.hpp:5509
Variable(::wasm::Index idx, tag< int > tag)
Constructs a Variable instance from an already allocated local with the given index idx.
Definition: WasmDSL.hpp:5564
UNARY_LIST(UNARY) UNARY(add_pairwise) UNARY(bitmask) UNARY(any_true) UNARY(all_true) UNARY(hash) UNARY(operator*)UNARY(operator->) UNARY(is_nullptr) UNARY(is_null) UNARY(not_null) UNARY(is_true_and_not_null) UNARY(is_false_and_not_null) #define ASSIGNOP_LIST(X) #define ASSIGNOP(SYMBOL) ASSIGNOP_LIST(ASSIGNOP) template< std::size_t M > auto extract() const
Definition: WasmDSL.hpp:5645
bool can_be_null() const
Check whether the value of this Variable can be NULL.
Definition: WasmDSL.hpp:5574
dependent_expr_t< PrimitiveExpr< To, ToL > > reinterpret() const
Definition: WasmDSL.hpp:5600
Variable & replace(U &&value)
Replaces the.
Definition: WasmDSL.hpp:5699
dependent_expr_t< PrimitiveExpr< T, L > > dependent_expr_type
Definition: WasmDSL.hpp:5510
friend void swap(Variable &first, Variable &second)
Definition: WasmDSL.hpp:5526
Variable()=default
Default-constructs a new Variable.
constexpr bool has_null_bit() const
Check whether this Variable can be assigned to NULL, i.e.
Definition: WasmDSL.hpp:5572
dependent_expr_t< PrimitiveExpr< To, ToL > > to() const
Definition: WasmDSL.hpp:5596
std::unique_ptr< DoWhile > do_while_
Definition: WasmDSL.hpp:6458
While(While &&)=default
Block & body()
Definition: WasmDSL.hpp:6479
While(std::string name, C &&cond)
Definition: WasmDSL.hpp:6468
While(const While &)=delete
While(std::string name, PrimitiveExpr< bool, 1 > cond)
Definition: WasmDSL.hpp:6461
PrimitiveExpr< bool, 1 > cond_
Definition: WasmDSL.hpp:6457
While(const char *name, C &&cond)
Definition: WasmDSL.hpp:6472
const Block & body() const
Definition: WasmDSL.hpp:6480
Helper type to deduce the signed integral type with a given byte width.
Definition: WasmDSL.hpp:298
Stores the "branch targets" introduced by control flow structures, i.e.
Definition: WasmDSL.hpp:389
::wasm::Expression * condition
‍the continue condition (may be nullptr if there is no condition)
Definition: WasmDSL.hpp:395
::wasm::Name brk
‍the break target
Definition: WasmDSL.hpp:391
branch_target_t(::wasm::Name brk, ::wasm::Name continu, ::wasm::Expression *condition)
Definition: WasmDSL.hpp:397
::wasm::Name continu
‍the continue target
Definition: WasmDSL.hpp:393
Deduces a suitable specialization of Variable that can be NULL for the given type.
Definition: WasmDSL.hpp:5756
Deduces a suitable specialization of Variable for global variables of the given type.
Definition: WasmDSL.hpp:5768
the_reference(PrimitiveExpr< T *, L > ptr)
Definition: WasmDSL.hpp:5845
PrimitiveExpr< T *, L > ptr_
Definition: WasmDSL.hpp:5842
void operator=(U &&_value)
Definition: WasmDSL.hpp:5854
Deduces a suitable specialization of Variable for the given type.
Definition: WasmDSL.hpp:5744
Converts a compile-time type into a runtime-type ::wasm::Type.
Definition: WasmDSL.hpp:217
exception_t type_
Definition: WasmDSL.hpp:568
exception(exception_t type, std::string message)
Definition: WasmDSL.hpp:571
typename expr< std::decay_t< T > >::type type
Definition: WasmDSL.hpp:166
Helper type to deduce the Expr<U> type given a.
Definition: WasmDSL.hpp:160
typename primitive_expr< std::decay_t< T > >::type type
Definition: WasmDSL.hpp:116
Helper type to deduce the PrimitiveExpr<U> type given a type.
Definition: WasmDSL.hpp:110
friend std::ostream & operator<<(std::ostream &out, print_types)
Definition: WasmDSL.hpp:427
A helper type to print Wasm types.
Definition: WasmDSL.hpp:410
Helper type to deduce the unsigned integral type with a given byte width.
Definition: WasmDSL.hpp:321