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