ESBMC
Efficient SMT-based Context-Bounded Model Checker
Loading...
Searching...
No Matches
irep2.h
Go to the documentation of this file.
1#ifndef IREP2_H_
2#define IREP2_H_
3
30
31#include <big-int/bigint.hh>
32#include <atomic>
33#include <cstddef>
34#include <cstdint>
35#include <functional>
36#include <memory>
37#include <stdexcept>
38#include <thread>
39#include <type_traits>
40#include <utility>
42#include <util/irep/irep_idt.h>
43#include <util/irep/irep.h>
44
45// The list of irep2 expression kinds lives in expr_kinds.inc; the
46// list of type kinds lives in type_kinds.inc. Every consumer (the
47// enum entries, the per-kind forward declarations, the is/to/try_to
48// predicate generators, the pretty name tables) #include the matching
49// .inc with a redefining IREP2_EXPR / IREP2_TYPE macro.
50
51namespace esbmct
52{
63template <typename Signature>
65
66template <typename R, typename... Args>
67class function_ref<R(Args...)>
68{
69public:
70 template <
71 typename F,
72 typename = std::enable_if_t<
73 !std::is_same_v<std::decay_t<F>, function_ref> &&
74 std::is_invocable_r_v<R, F &, Args...>>>
75 function_ref(F &&fn) noexcept
76 : invoke_(&function_ref::call<std::remove_reference_t<F>>),
77 ctx_(const_cast<void *>(static_cast<const void *>(std::addressof(fn))))
78 {
79 }
80
81 R operator()(Args... args) const
82 {
83 if constexpr (std::is_void_v<R>)
84 invoke_(ctx_, std::forward<Args>(args)...);
85 else
86 return invoke_(ctx_, std::forward<Args>(args)...);
87 }
88
89private:
90 template <typename F>
91 static R call(void *p, Args... args)
92 {
93 if constexpr (std::is_void_v<R>)
94 (*static_cast<F *>(p))(std::forward<Args>(args)...);
95 else
96 return (*static_cast<F *>(p))(std::forward<Args>(args)...);
97 }
98
99 R (*invoke_)(void *, Args...);
100 void *ctx_;
101};
102
110template <class T>
111inline void hash_combine(std::size_t &seed, const T &v)
112{
113 seed ^= std::hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
114}
115} // namespace esbmct
116
117class type2t;
118class expr2t;
119
142template <class T>
143class irep_container
144{
145private:
146 // Private tag gate for the raw-pointer adopt constructor. Only
147 // `make_irep` and other befriended factories can construct one,
148 // which means user code cannot do `expr2tc(new foo2t(...))` — the
149 // canonical idiom is the per-kind `foo2tc(...)` factory (itself a
150 // thin wrapper over make_irep). The tag carries no state.
151 struct make_tag
152 {
153 explicit make_tag() = default;
154 };
155
156 // Adopt a freshly-allocated node. The pointee's refcount must be 0
157 // (i.e. just-new'd); we bump it to 1.
158 irep_container(T *raw, make_tag) noexcept : ptr_(raw)
159 {
160 if (ptr_)
161 ptr_->refcount.fetch_add(1, std::memory_order_relaxed);
162 }
163
164 template <class U, class... Args>
165 friend irep_container<typename U::base_type> make_irep(Args &&...args);
166
167public:
168 // Default: empty container, nullptr inside. `noexcept`/`constexpr` to
169 // preserve the storage-class properties of the previous design.
170 constexpr irep_container() noexcept : ptr_(nullptr)
171 {
172 }
173
174 irep_container(const irep_container &ref) noexcept : ptr_(ref.ptr_)
175 {
176 if (ptr_)
177 ptr_->refcount.fetch_add(1, std::memory_order_relaxed);
178 }
179
180 irep_container(irep_container &&ref) noexcept : ptr_(ref.ptr_)
181 {
182 ref.ptr_ = nullptr;
183 }
184
186 {
187 release();
188 }
189
190 irep_container &operator=(const irep_container &ref) noexcept
191 {
192 if (this != &ref)
193 {
194 // Snapshot ref.ptr_ before calling release(). ref may alias a
195 // member of an object that release() destroys (e.g.
196 // `x = to_array_type(x).subtype` where x holds the only reference
197 // to the array): after release() runs, that storage is gone and
198 // any further access to ref is use-after-free. Reading the
199 // pointer up-front and then working off the local copy keeps us
200 // correct regardless of whether the compiler caches or reloads
201 // ref.ptr_ (gcc on Linux happens to cache it, AppleClang on macOS
202 // reloads and crashes).
203 T *new_ptr = ref.ptr_;
204 if (new_ptr)
205 new_ptr->refcount.fetch_add(1, std::memory_order_relaxed);
206 release();
207 ptr_ = new_ptr;
208 }
209 return *this;
210 }
211
212 irep_container &operator=(irep_container &&ref) noexcept
213 {
214 if (this != &ref)
215 {
216 // Same use-after-free concern as the copy-assign path: snapshot
217 // and null out ref before release(), so even if release() ends
218 // up destroying the storage backing ref we never re-read it.
219 T *new_ptr = ref.ptr_;
220 ref.ptr_ = nullptr;
221 release();
222 ptr_ = new_ptr;
223 }
224 return *this;
225 }
226
227 // Const accessors: hand out the pointee without detaching, since
228 // they're const operations. Prefer these whenever you only need to
229 // read the node — they are O(1) atomic-free pointer loads.
230 [[nodiscard]] const T &operator*() const noexcept
231 {
232 return *ptr_;
233 }
234
235 [[nodiscard]] const T *operator->() const noexcept
236 {
237 return ptr_;
238 }
239
240 [[nodiscard]] const T *get() const noexcept
241 {
242 return ptr_;
243 }
244
245 // Non-const accessors detach: if the pointee is shared (refcount > 1)
246 // we clone into a fresh refcount-1 object and rebind, then invalidate
247 // the CRC cache on the (now uniquely-owned) copy. **WARNING:** even
248 // when no actual mutation follows, calling these allocates+copies on
249 // every shared node touch — favour the const overloads above when a
250 // read is enough.
252 {
253 detach();
254 // Single-writer contract: relaxed is enough to invalidate the
255 // cache; the next crc() recomputation will publish via release.
256 ptr_->crc_val.store(0, std::memory_order_relaxed);
257#ifndef NDEBUG
258 // Stamp the current thread as the writer of this (now uniquely
259 // owned) node; if another thread is already inside a mutable
260 // access on the same node, mark_writer asserts.
261 ptr_->mark_writer();
262#endif
263 return ptr_;
264 }
265
267 {
268 return *get();
269 }
270
272 {
273 return get();
274 }
275
276 explicit operator bool() const noexcept
277 {
278 return ptr_ != nullptr;
279 }
280
281 void reset() noexcept
282 {
283 release();
284 ptr_ = nullptr;
285 }
286
287 void detach()
288 {
289 if (!ptr_)
290 return;
291 // Acquire so we observe whatever state the owner of the prior
292 // (about-to-be-replaced) reference flushed before storing here.
293 // Almost always 1 — only shared subtrees go down the clone path.
294 if (ptr_->refcount.load(std::memory_order_acquire) == 1)
295 return;
296 // Shared: clone into a fresh refcount-0 node and adopt it.
297 *this = ptr_->clone();
298 }
299
300 friend void swap(irep_container &a, irep_container &b) noexcept
301 {
302 T *tmp = a.ptr_;
303 a.ptr_ = b.ptr_;
304 b.ptr_ = tmp;
305 }
306
307 void swap(irep_container &b) noexcept
308 {
309 using std::swap;
310 swap(*this, b);
311 }
312
313 irep_container simplify() const
314 {
315 const T *foo = get();
316 return foo->simplify();
317 }
318
319 size_t crc() const
320 {
321 const T *foo = get();
322 // Acquire ordering on the load so a non-zero cached value also
323 // synchronises with the writer that produced it (crc()'s release
324 // store), and we observe whatever node state went into computing it.
325 if (
326 size_t cached = foo->crc_val.load(std::memory_order_acquire); cached != 0)
327 return cached;
328 return foo->crc();
329 }
330
331 /* Provide comparison operators here as inline friends so they don't pollute
332 * the outer namespace; this reduces clutter when there are error messages
333 * about these infix operators. It also means that no user-defined
334 * conversions are considered unless at least one operand has the type of
335 * this class or is derived from it. This is usually wanted since supplying
336 * those conversions means someone else has to care about comparing whatever
337 * values they potentially convert...
338 *
339 * This implementation assumes that the type T is totally ordered.
340 *
341 * TODO: when switching to >= C++20, replace these with only operator== and
342 * operator<=>
343 */
344
345 friend bool operator==(const irep_container &a, const irep_container &b)
346 {
347 if (same(a, b))
348 return true;
349
350 if (!a || !b)
351 return false;
352
353 return *a == *b; // different pointees could still compare equal
354 }
355
356 friend bool operator!=(const irep_container &a, const irep_container &b)
357 {
358 return !(a == b);
359 }
360
361 friend bool operator<(const irep_container &a, const irep_container &b)
362 {
363 if (!b)
364 return false; // If b is nil, nothing can be lower
365 if (!a)
366 return true; // nil is lower than non-nil
367
368 if (same(a, b))
369 return false;
370
371 return *a < *b;
372 }
373
374 friend bool operator<=(const irep_container &a, const irep_container &b)
375 {
376 return !(a > b);
377 }
378
379 friend bool operator>=(const irep_container &a, const irep_container &b)
380 {
381 return !(a < b);
382 }
383
384 friend bool operator>(const irep_container &a, const irep_container &b)
385 {
386 return b < a;
387 }
388
389private:
390 static bool same(const irep_container &a, const irep_container &b) noexcept
391 {
392 // Direct pointer equality is fine here because both pointers were
393 // obtained from `new` (or are nullptr), so they are valid objects
394 // of the same type and the comparison is well-defined.
395 return a.ptr_ == b.ptr_;
396 }
397
398 // Drop our reference. Release ordering pairs with the acquire in
399 // detach()/destruction on whichever container observes refcount==1
400 // and is about to mutate or delete the pointee; this makes any
401 // prior writes through *this happen-before that observer's access.
402 void release() noexcept
403 {
404 if (!ptr_)
405 return;
406 unsigned int prev = ptr_->refcount.fetch_sub(1, std::memory_order_release);
407 if (prev == 1)
408 {
409 // Acquire fence prevents the delete (and any side-effects of
410 // the destructor) from being reordered before the final
411 // fetch_sub, ensuring we see the prior owner's writes.
412 std::atomic_thread_fence(std::memory_order_acquire);
413 delete ptr_;
414 }
415#ifndef NDEBUG
416 else if (prev == 2)
417 {
418 // We were one of exactly two owners and just dropped out; the
419 // remaining sole owner is free to mutate. Clear the writer
420 // stamp so they get a clean slate (the next non-const get()
421 // will re-stamp).
422 ptr_->clear_writer();
423 }
424#endif
425 }
426
427 T *ptr_;
428};
429
432
450template <class T, class... Args>
451inline irep_container<typename T::base_type> make_irep(Args &&...args)
452{
453 using container_t = irep_container<typename T::base_type>;
454 return container_t(
455 new T(std::forward<Args>(args)...), typename container_t::make_tag{});
456}
457
458typedef std::pair<std::string, std::string> member_entryt;
459typedef std::list<member_entryt> list_of_memberst;
460
468{
469public:
470 irep2t() noexcept
471 : refcount(0)
472#ifndef NDEBUG
473 ,
475#endif
476 {
477 }
478 // Copy constructor must NOT propagate the refcount: a fresh copy is
479 // a brand new object that no container has yet adopted. The single
480 // copy site that matters is clone(), which always wraps the result
481 // in an irep_container immediately afterwards.
482 irep2t(const irep2t &) noexcept
483 : refcount(0)
484#ifndef NDEBUG
485 ,
487#endif
488 {
489 }
490 // Refcount is per-object identity; assignment is meaningless on the
491 // base. Subclasses' copy-assignment operators do not call into here.
492 irep2t &operator=(const irep2t &) = delete;
493 virtual ~irep2t() = default;
494
495 mutable std::atomic<unsigned int> refcount;
496
497#ifndef NDEBUG
498 // Debug-only writer-thread stamp. The threading contract says irep2
499 // nodes are single-writer; on the first mutable access through an
500 // irep_container, the container stamps this slot with a hash of the
501 // current thread id (0 means "no writer"). Subsequent mutable
502 // accesses compare — a mismatch means two threads are trying to
503 // mutate the same uniquely-owned node, which is the contract
504 // violation we want to catch. The stamp clears when the refcount
505 // drops back to 1 in release(), so a single-owner-handoff across
506 // threads (publish, then a new owner mutates) works correctly.
507 // Compiled out entirely under NDEBUG. uintptr_t is used (rather
508 // than std::thread::id directly) so the atomic is guaranteed
509 // lock-free on every platform we target.
510 mutable std::atomic<std::uintptr_t> writer_thread;
511
512 static std::uintptr_t current_thread_tag() noexcept
513 {
514 auto h = std::hash<std::thread::id>{}(std::this_thread::get_id());
515 // 0 is the "no writer" sentinel; if the hash collides with 0
516 // (vanishingly unlikely but possible), bump it.
517 return h == 0 ? 1 : static_cast<std::uintptr_t>(h);
518 }
519
520 void mark_writer() const noexcept
521 {
522 std::uintptr_t me = current_thread_tag();
523 std::uintptr_t prev = writer_thread.load(std::memory_order_acquire);
524 if (prev == me)
525 return; // already mine, common fast path
526 if (prev == 0)
527 {
528 // Race-free attempt to claim the slot. If we lose the CAS to a
529 // concurrent writer we fall through to the mismatch assert
530 // below, which is the contract violation.
531 if (
532 writer_thread.compare_exchange_strong(
533 prev, me, std::memory_order_release, std::memory_order_acquire))
534 return;
535 }
536 assert(
537 prev == me &&
538 "irep2 single-writer contract violated: a second thread tried to "
539 "mutate a node already being mutated by another thread");
540 }
541
542 void clear_writer() const noexcept
543 {
544 writer_thread.store(0, std::memory_order_release);
545 }
546#endif
547};
548
553class type2t : public irep2t
554{
555public:
560 {
561#define IREP2_TYPE(kind, pretty) kind##_id,
562#include <irep2/type_kinds.inc>
563#undef IREP2_TYPE
565 };
566
572 {
573 public:
574 const char *what() const noexcept
575 {
576 return "symbolic type encountered";
577 }
578 };
579
580 // Non-owning callable references. The historic typedefs were
581 // std::function<void(...)>, which allocated for any non-trivially-
582 // copyable lambda capture and indirected through the std::function
583 // type-erasure. function_ref is two pointers wide and inlines into
584 // the caller's stack frame.
587
588protected:
592 type2t(type_ids id);
593
595 type2t(const type2t &ref);
596
600public:
601 // Provide base / container types for some templates stuck on top:
604
605 virtual ~type2t() = default;
606
621 unsigned int get_width() const;
622
623 bool operator==(const type2t &ref) const;
624 bool operator!=(const type2t &ref) const;
625 bool operator<(const type2t &ref) const;
626
631 std::string pretty(unsigned int indent = 0) const;
632
638 DUMP_METHOD void dump() const;
639
647 size_t crc() const;
648
654 bool cmp(const type2t &ref) const;
655
661 int lt(const type2t &ref) const;
662
670 list_of_memberst tostring(unsigned int indent) const;
671
675 type2tc clone() const;
676
677 // Please see the equivalent methods in expr2t for documentation
678 template <typename T>
679 void foreach_subtype(T &&t) const
680 {
681 const_subtype_delegate wrapped(t);
683 }
684
685 template <typename T>
687 {
688 subtype_delegate wrapped(t);
689 foreach_subtype_impl(wrapped);
690 }
691
694
695 // CRC cache: 0 means "not yet computed". Atomic so concurrent readers
696 // see either the prior value or the fresh one — never a torn read.
697 // Writers respect the single-writer contract documented at the top of
698 // this header; the cache is therefore safe to set without a lock as
699 // long as readers and the (single) writer share happens-before via the
700 // atomic.
701 mutable std::atomic<size_t> crc_val;
702};
703
708std::string get_type_id(const type2t &type);
709
715static inline std::string get_type_id(const type2tc &type)
716{
717 return get_type_id(*type);
718}
719
724class expr2t : public irep2t
725{
726public:
730 {
731// The single source of truth for the kind list lives in
732// expr_kinds.inc; see that file's header for the IREP2_EXPR(kind,
733// pretty_name) contract.
734#define IREP2_EXPR(kind, pretty) kind##_id,
735#include <irep2/expr_kinds.inc>
736#undef IREP2_EXPR
738 };
739
740 // Non-owning callable references; see commentary on type2t's variants.
743
744protected:
749 expr2t(const type2tc &type, expr_ids id);
751 expr2t(const expr2t &ref);
752
755
756public:
757 // Provide base / container types for some templates stuck on top:
760
761 virtual ~expr2t() = default;
762
763 expr2tc clone() const;
764
769 expr2tc with_type(const type2tc &new_type) const;
770
771 bool operator==(const expr2t &ref) const;
772 bool operator<(const expr2t &ref) const;
773 bool operator!=(const expr2t &ref) const;
774
781 std::string pretty(unsigned int indent = 0) const;
782
787 DUMP_METHOD void dump() const;
788
794 size_t crc() const;
795
803 bool cmp(const expr2t &ref) const;
804
810 int lt(const expr2t &ref) const;
811
820 list_of_memberst tostring(unsigned int indent) const;
821
826 const expr2tc *get_sub_expr(size_t idx) const;
827
830 size_t get_num_sub_exprs() const;
831
837 expr2tc simplify() const;
838
847 expr2tc simplify(bool suppress_reassoc) const;
848
874 [[nodiscard]] virtual expr2tc do_simplify() const;
875
892 template <typename T>
893 void foreach_operand(T &&t) const
894 {
895 const_op_delegate wrapped(t);
897 }
898
899 template <typename T>
902 op_delegate wrapped(t);
903 foreach_operand_impl(wrapped);
904 }
905
908
911
912 // CRC cache; see commentary on type2t::crc_val.
913 mutable std::atomic<size_t> crc_val;
914};
915
916inline bool is_nil_expr(const expr2tc &exp)
917{
918 return exp.get() == nullptr;
919}
920
931template <class T>
932inline bool same_pointer(const irep_container<T> &a, const irep_container<T> &b)
933{
934 return a.get() == b.get();
935}
936
937inline bool is_nil_type(const type2tc &t)
938{
939 return t.get() == nullptr;
940}
941
942// For boost multi-index hashing,
943inline std::size_t hash_value(const expr2tc &expr)
944{
945 return expr.crc();
946}
947
952std::string get_expr_id(const expr2t &expr);
953
958static inline std::string get_expr_id(const expr2tc &expr)
959{
960 return get_expr_id(*expr);
961}
962
991namespace esbmct
992{
998const unsigned int num_type_fields = 6;
999
1000// ---------------------------------------------------------------------------
1001// Per-kind invariants for the flat irep2 layout.
1002//
1003// Each concrete kind exposes:
1004// * A `static constexpr auto fields` tuple of member pointers covering its
1005// user-visible storage. Pointers may refer to members of the kind itself
1006// OR of its base (e.g. `&expr2t::type` for kinds whose type slot is part
1007// of the trait set).
1008// * A `static std::string field_names[num_type_fields]` array of labels for
1009// the pretty-printer.
1010//
1011// `assert_kind_invariants<K>()` packages the two compile-time checks we want
1012// every concrete kind to satisfy:
1013//
1014// 1. `fields_cover_class<K>()` — the member pointers in K::fields that
1015// refer to K (i.e. not to a base) collectively cover the derived-class
1016// storage span, modulo trailing padding (< alignof(K)). If someone
1017// adds a raw `BigInt extra;` to a kind and forgets to extend `fields`,
1018// this fires.
1019//
1020// 2. The number of derived-class entries in K::fields is at most
1021// num_type_fields, so the field_names[] array indexing in
1022// generic_tostring is in-bounds.
1023//
1024// Instantiated for every kind from the switch dispatchers (each `case`
1025// references `K::fields`, which transitively forces the static_asserts in
1026// the helpers below). Adding a new kind without listing its fields gets a
1027// compile error from this site rather than a silent bug at cmp/crc/hash time.
1028// ---------------------------------------------------------------------------
1029
1030template <class P>
1032template <class C, class M>
1033struct member_traits<M C::*>
1034{
1035 using class_t = C;
1036 using member_t = M;
1037};
1038template <class C, class M>
1039struct member_traits<const M C::*>
1040{
1041 using class_t = C;
1042 using member_t = M;
1043};
1044
1045// Sizeof the i-th K::fields entry, but only if it refers to K itself
1046// (member pointers into the base class point at base storage, so they
1047// shouldn't be counted against the derived-class storage span).
1048template <class K, std::size_t I>
1049constexpr std::size_t nth_field_size_in_derived()
1050{
1051 using P = std::remove_cvref_t<decltype(std::get<I>(K::fields))>;
1052 using class_t = typename member_traits<P>::class_t;
1053 if constexpr (std::is_same_v<class_t, K>)
1054 return sizeof(typename member_traits<P>::member_t);
1055 else
1056 return 0;
1057}
1058
1059template <class K, std::size_t... Is>
1060constexpr std::size_t sum_derived_field_sizes(std::index_sequence<Is...>)
1061{
1062 return (nth_field_size_in_derived<K, Is>() + ... + std::size_t{0});
1063}
1064
1065template <class K, std::size_t... Is>
1066constexpr std::size_t count_derived_field_entries(std::index_sequence<Is...>)
1067{
1068 return (
1069 (std::is_same_v<
1070 typename member_traits<
1071 std::remove_cvref_t<decltype(std::get<Is>(K::fields))>>::class_t,
1072 K>
1073 ? std::size_t{1}
1074 : std::size_t{0}) +
1075 ... + std::size_t{0});
1076}
1077
1078// A kind may *intentionally* hold a member that is excluded from
1079// cmp/crc/hash/tostring — i.e. deliberately not listed in `fields`. Such a
1080// kind declares `static constexpr std::size_t excluded_field_bytes = ...;`
1081// giving the byte size of those excluded members, so `fields_cover_class`
1082// stops counting them as "missed". The excluded members are the non-reflected
1083// source `locationt`s carried by many code/if kinds (esbmc/esbmc#4715) — the
1084// V.4 structured-CF kinds carry a `location`, code_block2t also carries an
1085// `end_location`, and if2t (an expression) carries the ternary position — plus
1086// the per-loop `pragma_unroll_count` on the loop kinds. Source locations must
1087// travel with the statement for goto_convert, but must not enter value
1088// identity (matching how a goto instructiont stores its locationt separately
1089// from its IREP2 code). Kinds without such members get 0.
1090template <class K, class = void>
1092{
1093 static constexpr std::size_t value = 0;
1094};
1095template <class K>
1097 K,
1098 std::void_t<decltype(K::excluded_field_bytes)>>
1099{
1100 static constexpr std::size_t value = K::excluded_field_bytes;
1101};
1102
1103// `fields_cover_class<K>()` — the sum of derived-class field sizes in
1104// K::fields must equal `sizeof(K) - sizeof(base)` (minus any declared
1105// `excluded_field_bytes`), modulo at most `alignof(K) - 1` bytes of trailing
1106// padding. Catches missed-member-in-tuple at compile time for any kind where
1107// the missed member is at least one byte larger than the existing trailing
1108// padding (which covers every realistic case — irep2 nodes use pointer-sized
1109// fields almost exclusively).
1110template <class K>
1111constexpr bool fields_cover_class()
1112{
1113 using fields_t = std::remove_cv_t<decltype(K::fields)>;
1114 constexpr std::size_t derived_storage = sizeof(K) -
1115 sizeof(typename K::base_type) -
1117 constexpr std::size_t covered = sum_derived_field_sizes<K>(
1118 std::make_index_sequence<std::tuple_size_v<fields_t>>{});
1119 return derived_storage - covered < alignof(K);
1120}
1121
1122// `derived_field_count<K>()` — number of K::fields entries that live in K
1123// itself (not in K::base_type). Compared against num_type_fields to ensure
1124// the field_names[] indexing in generic_tostring stays in-bounds.
1125template <class K>
1126constexpr std::size_t derived_field_count()
1127{
1128 using fields_t = std::remove_cv_t<decltype(K::fields)>;
1130 std::make_index_sequence<std::tuple_size_v<fields_t>>{});
1131}
1132
1133template <class K>
1135{
1136 static_assert(
1138 "irep2 kind invariant: fields tuple does not cover the derived class "
1139 "storage. Either add the missing member pointer to the kind's "
1140 "`fields = std::make_tuple(...)`, or — if the member is intentionally "
1141 "excluded from cmp/crc/hash — list every non-excluded member explicitly "
1142 "and document the omission.");
1143 static_assert(
1145 "irep2 kind invariant: this kind has more derived-class field entries "
1146 "than esbmct::num_type_fields. Either bump num_type_fields and the "
1147 "corresponding field_names[] arrays, or replace the fixed-size array "
1148 "with one sized by std::tuple_size_v<decltype(fields)>.");
1149 return true;
1150}
1151
1152} // namespace esbmct
1153
1154inline std::ostream &operator<<(std::ostream &out, const expr2tc &a)
1155{
1156 out << a->pretty(0);
1157 return out;
1158}
1159
1161{
1162 size_t operator()(const expr2tc &ref) const
1163 {
1164 return ref.crc();
1165 }
1166};
1167
1169{
1170 size_t operator()(const type2tc &ref) const
1171 {
1172 return ref->crc();
1173 }
1174};
1175
1176// Exception thrown by irep2_checked_type_cast / irep2_checked_expr_cast when
1177// the runtime id does not match the requested derived type. A bad to_*2t /
1178// to_*_type was always a logic bug — surfacing it as an exception keeps the
1179// failure deterministic in every build mode (rather than the undefined
1180// behaviour the previous NDEBUG dynamic_cast→static_cast redefine produced)
1181// while staying recoverable for callers that want to handle it.
1182class irep2_cast_error : public std::logic_error
1183{
1184public:
1185 using std::logic_error::logic_error;
1186};
1187
1188// Diagnose a bad to_* downcast and throw irep2_cast_error. Out-of-line and
1189// [[noreturn]] so the happy path stays a single compare+branch and the
1190// compiler does not pessimise the inlined helpers.
1191[[noreturn]] void
1192irep2_bad_type_cast(unsigned actual, unsigned expected, const char *target);
1193[[noreturn]] void
1194irep2_bad_expr_cast(unsigned actual, unsigned expected, const char *target);
1195[[noreturn]] void irep2_bad_family_cast(unsigned actual, const char *accessor);
1196
1197// Checked downcast for type2t / expr2t hierarchies. The is_*_type / is_*2t
1198// predicates already do a single enum compare; these helpers do the same
1199// check before a static_cast, so a bad to_*2t throws irep2_cast_error in
1200// every build mode rather than invoking undefined behaviour under NDEBUG.
1201template <typename Derived>
1203 type2t &t,
1204 type2t::type_ids expected,
1205 const char *target_name)
1206{
1207 if (t.type_id != expected)
1208 irep2_bad_type_cast(t.type_id, expected, target_name);
1209 return static_cast<Derived &>(t);
1210}
1211
1212template <typename Derived>
1213inline const Derived &irep2_checked_type_cast(
1214 const type2t &t,
1215 type2t::type_ids expected,
1216 const char *target_name)
1217{
1218 if (t.type_id != expected)
1219 irep2_bad_type_cast(t.type_id, expected, target_name);
1220 return static_cast<const Derived &>(t);
1221}
1222
1223template <typename Derived>
1225 expr2t &e,
1226 expr2t::expr_ids expected,
1227 const char *target_name)
1228{
1229 if (e.expr_id != expected)
1230 irep2_bad_expr_cast(e.expr_id, expected, target_name);
1231 return static_cast<Derived &>(e);
1232}
1233
1234template <typename Derived>
1235inline const Derived &irep2_checked_expr_cast(
1236 const expr2t &e,
1237 expr2t::expr_ids expected,
1238 const char *target_name)
1239{
1240 if (e.expr_id != expected)
1241 irep2_bad_expr_cast(e.expr_id, expected, target_name);
1242 return static_cast<const Derived &>(e);
1243}
1244
1245#endif /* IREP2_H_ */
static float R(float z)
Definition acosf.c:26
R operator()(Args... args) const
Definition irep2.h:81
function_ref(F &&fn) noexcept
Definition irep2.h:75
Type-erased callable reference: non-owning, two pointers wide, no heap allocation.
Definition irep2.h:64
Base class for all expressions.
Definition irep2.h:725
void foreach_operand_impl_const(const_op_delegate &expr) const
Definition irep2_expr.cpp:684
expr2t base_type
Definition irep2.h:759
list_of_memberst tostring(unsigned int indent) const
Convert fields of subclasses to a string representation.
Definition irep2_expr.cpp:636
void Foreach_operand(T &&t)
Definition irep2.h:900
const expr2tc * get_sub_expr(size_t idx) const
Fetch a sub-operand.
Definition irep2_expr.cpp:652
bool cmp(const expr2t &ref) const
Structural comparison.
Definition irep2_expr.cpp:414
bool operator<(const expr2t &ref) const
Definition irep2_expr.cpp:71
std::string pretty(unsigned int indent=0) const
Produce textual representation of this expr.
Definition irep2_expr.cpp:81
expr2tc clone() const
Definition irep2_expr.cpp:455
DUMP_METHOD void dump() const
Write textual representation of this object to stdout.
Definition irep2_expr.cpp:92
expr2t(const type2tc &type, expr_ids id)
Primary constructor.
Definition irep2_expr.cpp:49
bool operator==(const expr2t &ref) const
Definition irep2_expr.cpp:61
std::atomic< size_t > crc_val
Definition irep2.h:913
virtual ~expr2t()=default
expr_ids
Enumeration identifying each sort of expr.
Definition irep2.h:730
@ end_expr_id
Definition irep2.h:901
const expr_ids expr_id
Instance of expr_ids recording tihs exprs type.
Definition irep2.h:907
void foreach_operand(T &&t) const
Indirect, abstract operand iteration.
Definition irep2.h:893
esbmct::function_ref< void(const expr2tc &)> const_op_delegate
Definition irep2.h:741
void foreach_operand_impl(op_delegate &expr)
Definition irep2_expr.cpp:700
virtual expr2tc do_simplify() const
expr-specific simplification methods.
Definition expr_simplifier.cpp:17
esbmct::function_ref< void(expr2tc &)> op_delegate
Definition irep2.h:742
bool operator!=(const expr2t &ref) const
Definition irep2_expr.cpp:66
int lt(const expr2t &ref) const
Trinary structural ordering.
Definition irep2_expr.cpp:438
const type2tc type
Type of this expr.
Definition irep2.h:910
size_t crc() const
Calculate a hash/digest of the current expr.
Definition irep2_crc.cpp:290
expr2tc container_type
Definition irep2.h:758
expr2tc with_type(const type2tc &new_type) const
Build a fresh expression of the same kind and field values as this one, but with a different type.
Definition irep2_expr.cpp:613
expr2tc simplify() const
Simplify an expression.
Definition expr_simplifier.cpp:40
size_t get_num_sub_exprs() const
Count the number of sub-exprs there are.
Definition irep2_expr.cpp:668
Definition irep2.h:1183
static std::uintptr_t current_thread_tag() noexcept
Definition irep2.h:512
std::atomic< std::uintptr_t > writer_thread
Definition irep2.h:510
irep2t(const irep2t &) noexcept
Definition irep2.h:482
irep2t() noexcept
Definition irep2.h:470
void clear_writer() const noexcept
Definition irep2.h:542
virtual ~irep2t()=default
irep2t & operator=(const irep2t &)=delete
std::atomic< unsigned int > refcount
Definition irep2.h:495
void mark_writer() const noexcept
Definition irep2.h:520
Reference counted container for irep2 nodes.
Definition irep2.h:144
size_t crc() const
Definition irep2.h:319
void reset() noexcept
Definition irep2.h:281
T * operator->()
Definition irep2.h:271
friend bool operator==(const irep_container &a, const irep_container &b)
Definition irep2.h:345
irep_container & operator=(const irep_container &ref) noexcept
Definition irep2.h:190
irep_container(const irep_container &ref) noexcept
Definition irep2.h:174
friend bool operator<(const irep_container &a, const irep_container &b)
Definition irep2.h:361
irep_container(irep_container &&ref) noexcept
Definition irep2.h:180
friend bool operator<=(const irep_container &a, const irep_container &b)
Definition irep2.h:374
friend bool operator>(const irep_container &a, const irep_container &b)
Definition irep2.h:384
~irep_container()
Definition irep2.h:185
irep_container simplify() const
Definition irep2.h:313
irep_container & operator=(irep_container &&ref) noexcept
Definition irep2.h:212
const T & operator*() const noexcept
Definition irep2.h:230
const T * get() const noexcept
Definition irep2.h:240
const T * operator->() const noexcept
Definition irep2.h:235
friend bool operator!=(const irep_container &a, const irep_container &b)
Definition irep2.h:356
T * get()
Definition irep2.h:251
T & operator*()
Definition irep2.h:266
void swap(irep_container &b) noexcept
Definition irep2.h:307
friend irep_container< typename U::base_type > make_irep(Args &&...args)
Allocate a fresh irep2 node and adopt it into an irep_container.
Definition irep2.h:451
friend bool operator>=(const irep_container &a, const irep_container &b)
Definition irep2.h:379
friend void swap(irep_container &a, irep_container &b) noexcept
Definition irep2.h:300
void detach()
Definition irep2.h:287
constexpr irep_container() noexcept
Definition irep2.h:170
Symbolic type exception class.
Definition irep2.h:572
const char * what() const noexcept
Definition irep2.h:574
Base class for all types.
Definition irep2.h:554
type2tc container_type
Definition irep2.h:602
void Foreach_subtype(T &&t)
Definition irep2.h:686
esbmct::function_ref< void(type2tc &)> subtype_delegate
Definition irep2.h:586
unsigned int get_width() const
Fetch bit width of this type.
Definition irep2_type.cpp:352
type2tc clone() const
Clone method.
Definition irep2_type.cpp:288
esbmct::function_ref< void(const type2tc &)> const_subtype_delegate
Definition irep2.h:585
bool operator!=(const type2t &ref) const
Definition irep2_type.cpp:90
void foreach_subtype_impl(subtype_delegate &t)
Definition irep2_type.cpp:336
void foreach_subtype_impl_const(const_subtype_delegate &t) const
Definition irep2_type.cpp:319
bool operator<(const type2t &ref) const
Definition irep2_type.cpp:95
type_ids
Enumeration identifying each sort of type.
Definition irep2.h:560
@ end_type_id
Definition irep2.h:599
type2t base_type
Definition irep2.h:603
std::string pretty(unsigned int indent=0) const
Produce a human-readable string representation of type.
Definition irep2_type.cpp:100
bool operator==(const type2t &ref) const
Definition irep2_type.cpp:85
size_t crc() const
Produce a checksum/hash of the current object.
Definition irep2_crc.cpp:295
type2t(type_ids id)
Primary constructor.
Definition irep2_type.cpp:72
int lt(const type2t &ref) const
Trinary structural ordering.
Definition irep2_type.cpp:270
DUMP_METHOD void dump() const
Dump object string representation to stdout.
Definition irep2_type.cpp:110
void foreach_subtype(T &&t) const
Definition irep2.h:679
std::atomic< size_t > crc_val
Definition irep2.h:701
virtual ~type2t()=default
list_of_memberst tostring(unsigned int indent) const
Extract a list of members from type as strings.
Definition irep2_type.cpp:303
bool cmp(const type2t &ref) const
Structural comparison.
Definition irep2_type.cpp:252
const type_ids type_id
Instance of type_ids recording this types type.
Definition irep2.h:693
#define DUMP_METHOD
Definition compiler_defs.h:97
#define T
Definition exp2f.c:24
#define C
Definition exp2f.c:25
double exp(double x)
Definition exp.c:84
@ raw
No normalisation. Baseline for the measurement.
Definition fingerprint.h:18
void irep2_bad_family_cast(unsigned actual, const char *accessor)
Definition irep2_type.cpp:61
std::ostream & operator<<(std::ostream &out, const expr2tc &a)
Definition irep2.h:1154
std::list< member_entryt > list_of_memberst
Definition irep2.h:459
irep_container< typename T::base_type > make_irep(Args &&...args)
Allocate a fresh irep2 node and adopt it into an irep_container.
Definition irep2.h:451
Derived & irep2_checked_expr_cast(expr2t &e, expr2t::expr_ids expected, const char *target_name)
Definition irep2.h:1224
irep_container< type2t > type2tc
Definition irep2.h:430
bool is_nil_expr(const expr2tc &exp)
Definition irep2.h:916
Derived & irep2_checked_type_cast(type2t &t, type2t::type_ids expected, const char *target_name)
Definition irep2.h:1202
std::size_t hash_value(const expr2tc &expr)
Definition irep2.h:943
std::pair< std::string, std::string > member_entryt
Definition irep2.h:458
irep_container< expr2t > expr2tc
Definition irep2.h:431
bool is_nil_type(const type2tc &t)
Definition irep2.h:937
bool same_pointer(const irep_container< T > &a, const irep_container< T > &b)
Node-identity test: true iff a and b refer to the very same hash-consed node.
Definition irep2.h:932
void irep2_bad_expr_cast(unsigned actual, unsigned expected, const char *target)
Definition irep2_expr.cpp:33
std::string get_expr_id(const expr2t &expr)
Fetch string identifier for an expression.
Definition irep2_expr.cpp:76
void irep2_bad_type_cast(unsigned actual, unsigned expected, const char *target)
Definition irep2_type.cpp:41
std::string get_type_id(const type2t &type)
Fetch identifying name for a type.
Definition irep2_type.cpp:36
Containers and field-walking infrastructure for irep2 nodes.
Definition esbmc_internal_cpp.h:3
constexpr std::size_t count_derived_field_entries(std::index_sequence< Is... >)
Definition irep2.h:1066
constexpr std::size_t derived_field_count()
Definition irep2.h:1126
constexpr bool assert_kind_invariants()
Definition irep2.h:1134
constexpr std::size_t sum_derived_field_sizes(std::index_sequence< Is... >)
Definition irep2.h:1060
constexpr bool fields_cover_class()
Definition irep2.h:1111
void hash_combine(std::size_t &seed, const T &v)
Mix a value into a running hash seed.
Definition irep2.h:111
constexpr std::size_t nth_field_size_in_derived()
Definition irep2.h:1049
const unsigned int num_type_fields
Maximum number of fields to support in expr2t subclasses.
Definition irep2.h:998
Definition exception.cpp:51
#define bool
Definition stdbool.h:9
Definition irep2.h:1092
static constexpr std::size_t value
Definition irep2.h:1093
M member_t
Definition irep2.h:1036
C class_t
Definition irep2.h:1035
C class_t
Definition irep2.h:1041
M member_t
Definition irep2.h:1042
Definition irep2.h:1031
Definition irep2.h:1161
size_t operator()(const expr2tc &ref) const
Definition irep2.h:1162
Definition irep2.h:1169
size_t operator()(const type2tc &ref) const
Definition irep2.h:1170