From ffe71e5f4d7b976af6f6e2f1060b95fbf84af365 Mon Sep 17 00:00:00 2001 From: Junyu Chen Date: Thu, 14 May 2026 01:13:52 +0800 Subject: [PATCH 1/4] Add initial GenWasm runtime structure --- genwasym_runtime/Makefile | 31 + genwasym_runtime/build/libgenwasym.a | Bin 0 -> 272712 bytes genwasym_runtime/build/libgenwasym.so | Bin 0 -> 305360 bytes genwasym_runtime/include/gensym.hpp | 69 + genwasym_runtime/include/gensym_client.h | 160 ++ genwasym_runtime/include/genwasym.h | 4 + genwasym_runtime/include/wasm.hpp | 11 + .../include/wasm/concolic_driver.hpp | 277 +++ .../include/wasm/concrete_num.hpp | 989 +++++++++ genwasym_runtime/include/wasm/concrete_rt.hpp | 497 +++++ genwasym_runtime/include/wasm/config.hpp | 82 + genwasym_runtime/include/wasm/controls.hpp | 105 + .../include/wasm/heap_mem_bookkeeper.hpp | 24 + .../include/wasm/output_report.hpp | 50 + genwasym_runtime/include/wasm/profile.hpp | 416 ++++ genwasym_runtime/include/wasm/smt_solver.hpp | 459 ++++ genwasym_runtime/include/wasm/sym_rt.hpp | 1926 +++++++++++++++++ .../include/wasm/symbolic_decl.hpp | 327 +++ .../include/wasm/symbolic_impl.hpp | 182 ++ genwasym_runtime/include/wasm/symval_decl.hpp | 95 + .../include/wasm/symval_factory.hpp | 654 ++++++ genwasym_runtime/include/wasm/symval_impl.hpp | 208 ++ genwasym_runtime/include/wasm/union_find.hpp | 60 + genwasym_runtime/include/wasm/utils.hpp | 92 + genwasym_runtime/include/wasm/z3_env.hpp | 36 + .../include/wasm_state_continue.hpp | 94 + genwasym_runtime/lib/genwasym.cpp | 5 + genwasym_runtime/lib/wasm_state_continue.cpp | 116 + 28 files changed, 6969 insertions(+) create mode 100644 genwasym_runtime/Makefile create mode 100644 genwasym_runtime/build/libgenwasym.a create mode 100755 genwasym_runtime/build/libgenwasym.so create mode 100644 genwasym_runtime/include/gensym.hpp create mode 100644 genwasym_runtime/include/gensym_client.h create mode 100644 genwasym_runtime/include/genwasym.h create mode 100644 genwasym_runtime/include/wasm.hpp create mode 100644 genwasym_runtime/include/wasm/concolic_driver.hpp create mode 100644 genwasym_runtime/include/wasm/concrete_num.hpp create mode 100644 genwasym_runtime/include/wasm/concrete_rt.hpp create mode 100644 genwasym_runtime/include/wasm/config.hpp create mode 100644 genwasym_runtime/include/wasm/controls.hpp create mode 100644 genwasym_runtime/include/wasm/heap_mem_bookkeeper.hpp create mode 100644 genwasym_runtime/include/wasm/output_report.hpp create mode 100644 genwasym_runtime/include/wasm/profile.hpp create mode 100644 genwasym_runtime/include/wasm/smt_solver.hpp create mode 100644 genwasym_runtime/include/wasm/sym_rt.hpp create mode 100644 genwasym_runtime/include/wasm/symbolic_decl.hpp create mode 100644 genwasym_runtime/include/wasm/symbolic_impl.hpp create mode 100644 genwasym_runtime/include/wasm/symval_decl.hpp create mode 100644 genwasym_runtime/include/wasm/symval_factory.hpp create mode 100644 genwasym_runtime/include/wasm/symval_impl.hpp create mode 100644 genwasym_runtime/include/wasm/union_find.hpp create mode 100644 genwasym_runtime/include/wasm/utils.hpp create mode 100644 genwasym_runtime/include/wasm/z3_env.hpp create mode 100644 genwasym_runtime/include/wasm_state_continue.hpp create mode 100644 genwasym_runtime/lib/genwasym.cpp create mode 100644 genwasym_runtime/lib/wasm_state_continue.cpp diff --git a/genwasym_runtime/Makefile b/genwasym_runtime/Makefile new file mode 100644 index 000000000..56f8ee9b9 --- /dev/null +++ b/genwasym_runtime/Makefile @@ -0,0 +1,31 @@ +CXX = g++ +CXXFLAGS = -std=c++17 -Wall -Iinclude -I../third-party/immer -fPIC +BUILD_DIR = build + +SRC = lib/genwasym.cpp lib/wasm_state_continue.cpp +OBJ = build/genwasym.o build/wasm_state_continue.o + +STATIC_LIB = build/libgenwasym.a +SHARED_LIB = build/libgenwasym.so + +.PHONY: all clean + +all: build $(STATIC_LIB) $(SHARED_LIB) + +build: + mkdir -p $(BUILD_DIR) + +build/genwasym.o: lib/genwasym.cpp | build + $(CXX) $(CXXFLAGS) -c lib/genwasym.cpp -o build/genwasym.o + +build/wasm_state_continue.o: lib/wasm_state_continue.cpp | build + $(CXX) $(CXXFLAGS) -c lib/wasm_state_continue.cpp -o build/wasm_state_continue.o + +$(STATIC_LIB): $(OBJ) + ar rcs $(STATIC_LIB) $(OBJ) + +$(SHARED_LIB): $(OBJ) + $(CXX) -shared -Wl,-install_name,@rpath/libgenwasym.so -o $(SHARED_LIB) $(OBJ) + +clean: + rm -rf $(BUILD_DIR) \ No newline at end of file diff --git a/genwasym_runtime/build/libgenwasym.a b/genwasym_runtime/build/libgenwasym.a new file mode 100644 index 0000000000000000000000000000000000000000..1d90fc2762f66722816d03ef1e1dff69041e80a0 GIT binary patch literal 272712 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%0DnT6i0=&uYFd5sUktjWW7aEiR9@2xx# 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
    yOy)0iVJf*?|ElBo1pi$eSJ2dmuES;Ipi>b zby%LM6-(>-EEY|ta!pG!Ui7IeIJ7Rr-5UCfh7Rvc@K~TyP@gXw7lB?$(Dfy`zC$Wj zJ0^jCWiO;6XBUI2`Dj4RUb6NCFLxX}d72aRe33Ajk_ zaM+n!Q!rM8iEe%e+?zjIzl-QDgSYRex4R=W4LjK^saPllXwARdwH!#rcDqx{e4SV6 z1+i#%XGztzXLlBaE`7JE-K9qhyWCElTG(Y~O*T&bTIl8W=-NUryJzp_v>puF_3^#Y z`z`fyaq1nHdYO1lkd{{2tZP?QQ9mW<^OaCPDUDNxffuv@^b*wn1IKR58XkQEiz|xu z=IwO9J3w0=J<33VOOqc=kE&1LPTRKi-A-8<25b*t_r!J@1AX4R@o`o*z_QCV-Fm5e z>9p~dOTJ4+a3_t7VQ=Lw%iyq!?Xe0DyXbBjB*QMZ=i=Z0GuYi8&jP%;xqBnI)8u6d z)VKTmWeEuHI)w?p>`t?o@Jk)!{ruhQ-n{nWmO#C`O=p&X=$&`%1@j8=9AN(F~Nzsd6UG<{a2O)5WaeOMO}h#F0@4 z-Ccv$=7i`g;~N=m=h*2IwKg`Pj#9TBChbrl{-4L~g))(EO}$Dhvhz75-|yDp=2TfRi<3~-g33>l=#`6=o! zl`5PmWzue7!Y@vi0G8>E=QDc&rxPkIckciIY*0&XoFUh z*gbD^kQKWrlYohs*+E~1QW8Ly{5>-tBQ*-?*;!pT4)rla`X)v#PyHHnPa?thv=*OI zQ}OK8l$zSuQ06(4X%D4j_BGX0HlJ#EW$M=IUMiD>T;qNMzzxrp4- zcZtLM)mvEHqZjGEuWu|hURMT2iaD~`1efn99cfV0EYrn9v;NSobSc}l$yQ1|oNbTw zqzZ-XE|@8mONda$$XX_j?W|k#l+&i!Eiz8~5A%lEXeKa~&*gbqY_n+_ z=Zn1~eX5(F@3srDib}nggZWd_xYM=jMUDpjO{2k?E~5d@OkAyWI+;AeeAdAr=Ryq}kx~bb zUXZ8D4Fkyfks%ACDjB?L8Yg#UVA<6Ic6l%V0;T6YhP0JlqF#Dy{&J<~J|^rdK%VS% ze82tG{5(6teJ!w<%9S#9E^X6&=WHguP3;z_y^X~1<{tXjaWL8QWf+`&ajlV_*E_r< zXc_j_SPtXxpjBUg^waP$IKTc_PYVz|e2}IirRxPLEOhv2D7rn^kMWASZLn7l+bvx$ zbYP`+<>%7dQ=QmeDmtxMd|$&ux5Z=g!Yr&)!}DF~IrJSJH&BA58O>u7ooJmzpHuGz z2N%!_cGD)8H8iq;)(nGbo{smjiF>Q8J~TM*+_ZGlO-1eV7vG~C&ulHnHT81T%E`P& z6O`ld9x6xw4?lq^O20Qr&nfo4R>>a|7CtsP%owRl(>vCCy)>Ked+!^U8fWLbG%h9F z`_sjhwcmRI_s=YQrqS}IpvwTixPsg_GSND750$0g8|8B_H0`mn3_!d5%NA#oQVJfL z>R)bMN7y~`jT@UUYJw~ukg>v0?~z(!wD&-I$adKPX!>g4GCkOnnWsfBXWBsqHY>(5 z#JUR8rAa<`K)U_QY!;+!(bxI(7N@WGO`mJ{z=n!qea{Gg&L?Xg`Ye)z<*=xO_U@?1 zcb%xyhaKMcaN3gMr|o-E6#att22=$P;a#K(uJ3|2Noa;s!fbh}FkqasW(`|avI}La zoEpp8?N53J=^gVp~s>wnQ76s*r7n8UOF_GhNU zvshodzplP|E*u=qy6gO3^oVlS+n%%j_Ghua_GjI(-g=Vtzs&ky^an-XQ}@!-mXK-I zUFomZ`Z1$*SNemZ>ef+p)&Gjs|1#@;(I4cwt3AHH_GevP^*HN)#p-{VUDSi9lYyJE z`LR@%Y%LY7OfFMiMP4Y4SUqt4uUP#rv;G(TL7vUJ=lWl<`d{Y!U!3G7I*eGbi=})n fmCcm5Su;@;MYK((ZgP+o+g1N7R{zV48vOhJI9i9! literal 0 HcmV?d00001 diff --git a/genwasym_runtime/build/libgenwasym.so b/genwasym_runtime/build/libgenwasym.so new file mode 100755 index 0000000000000000000000000000000000000000..07e0be4e6649b90d3f267d8640b07e32d6b36d58 GIT binary patch literal 305360 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¨d}gFL&5cJokskPR+RIJ5^EOF4+Ui z^H)a7ze|d0d6!==-SV0873W`{y!-L4gP%P4_1gYfhZn6XUKJ92{@AS%_1+oNJFWSu zy`6?HX%g?7u+?;7Vk_ZD%9rZDS~ncK{pkqPp0WQq-r~RO%l~T9s&~SV%{v8r(l+tJ zN1D#JF8O@((Fv{AKljq~#+%lgems^s{I;_bW14+=f62I=KOJ1W=l$Qe-uG(W)TG5z z_U&yDvA6RR6W@M6s#&-3N8fMv&pR(aw6N2doX+=b<=N)_0RuW*YWEy z!I7nlpMH4uq=a?)M|P);Ff1<3RrT|qyY%Z3KQ&x_xyKLZpF8nc>4ML-M|M3mLp^e{ zA?nVb`qh7Q`43O@jsN_uM^~OMoYuPc(U^LJe!e~TgQ$&{cXxijpv&o(b|i)FJo*0K zo-I1a#!dA7+QsV|mk=gfsS32>#Ly55`6*+m^>wOY>YSqMHYxgf}GI_zF z`8&3i{Na;0Vg0ka-!g=reB#k_4K92%_O4mO2KsyxclVir6H1>5t(Uq*7&WrntchQY mITDvJG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef PURE_STATE +#include +#endif +#ifdef IMPURE_STATE +#include +#endif + +#include +#include +#include + +#ifdef PURE_STATE +#include +#endif + +#ifdef IMPURE_STATE +#include +#endif + +#include + +#endif diff --git a/genwasym_runtime/include/gensym_client.h b/genwasym_runtime/include/gensym_client.h new file mode 100644 index 000000000..bf8592598 --- /dev/null +++ b/genwasym_runtime/include/gensym_client.h @@ -0,0 +1,160 @@ +#ifndef GS_CLIENT_HEADERS +#define GS_CLIENT_HEADERS + +#include +#include +#include +#include +#include + +/* Construct `byte_size` 8-bitvectors starting at address `addr`. + */ +void make_symbolic(void* addr, size_t byte_size, ...); + +/* Construct a single bitvector of size `byte_size`*8 at address `addr`. + */ +void make_symbolic_whole(void* addr, size_t byte_size, ...); + +#define gs_make_symbolic(addr, byte_size, name) make_symbolic(addr, byte_size, name) +#define gs_make_symbolic_whole(addr, byte_size, name) make_symbolic_whole(addr, byte_size, name) + +void gs_assert(bool, ...); +void gs_assert_eager(bool, ...); + +void gs_assume(bool); +void sym_print(int, ...); +void print_string(const char *message); +void sym_exit(int); + +/* gs_range - Construct a symbolic value in the signed interval + * [begin,end). + * + * \arg name - A name used for identifying the object in messages, output + * files, etc. If NULL, object is called "unnamed". + */ +static inline int gs_range(int begin, int end, const char *name) { + int x; + gs_make_symbolic(&x, sizeof(x), name); + gs_assume(x >= begin); + gs_assume(x < end); + return x; +} + +__attribute__((noreturn)) +void stop(int status); + +/* gs_report_error - Report a user defined error and terminate the current + * gensym process. + * + * \arg file - The filename to report in the error message. + * \arg line - The line number to report in the error message. + * \arg message - A string to include in the error message. + * \arg suffix - The suffix to use for error files. + */ +__attribute__((noreturn)) +static inline void gs_report_error(const char *file, + int line, + const char *message, + const char *suffix) { + print_string(file); + sym_print(line); + print_string(message); + print_string(suffix); + print_string("\n"); + stop(-1); +} + +static inline void gs_warning(const char *message) { + print_string(message); + print_string("\n"); +} + +void gs_warning_once(const char *message); + +void gs_prefer_cex(void *object, bool condition); +void gs_posix_prefer_cex(void *object, bool condition); + +/* return whether n is a symbolic value */ +unsigned gs_is_symbolic(uintptr_t n); + +/* return a feasible concrete value of expr */ +long gs_get_valuel(long expr); + +/* Support for running test-comp examples */ + +static inline void __VERIFIER_error(void) { gs_assert_eager(0); } +static inline void __VERIFIER_assert(int cond) { gs_assert_eager(cond); } +static inline void reach_error() { + fprintf(stderr, "error reached\n"); + gs_assert_eager(0); +} +static inline void __VERIFIER_assume(int x) { /* TODO */ } + +static inline int __VERIFIER_nondet_int(void) { + int x; + make_symbolic_whole(&x, sizeof(int)); + return x; +} + +static inline unsigned int __VERIFIER_nondet_uint(void) { + unsigned int x; + make_symbolic_whole(&x, sizeof(x)); + return x; +} + +typedef unsigned int u32; +static inline u32 __VERIFIER_nondet_u32(void) { + return __VERIFIER_nondet_uint(); +} + +static inline unsigned __VERIFIER_nondet_unsigned(void) { + unsigned x; + make_symbolic_whole(&x, sizeof(x)); + return x; +} + +static inline short __VERIFIER_nondet_short(void) { + short x; + make_symbolic_whole(&x, sizeof(x)); + return x; +} + +static inline unsigned short __VERIFIER_nondet_ushort(void) { + unsigned short x; + make_symbolic_whole(&x, sizeof(x)); + return x; +} + +static inline char __VERIFIER_nondet_char(void) { + char x; + make_symbolic_whole(&x, sizeof(x)); + return x; +} + +static inline unsigned char __VERIFIER_nondet_uchar(void) { + unsigned char x; + make_symbolic_whole(&x, sizeof(x)); + return x; +} + +static inline long __VERIFIER_nondet_long(void) { + long x; + make_symbolic_whole(&x, sizeof(x)); + return x; +} + +static inline unsigned long __VERIFIER_nondet_ulong(void) { + unsigned long x; + make_symbolic_whole(&x, sizeof(x)); + return x; +} + +/* Note: to be supported... +char* __VERIFIER_nondet_pchar(void) { } +float __VERIFIER_nondet_float(void) { } +double __VERIFIER_nondet_double(void) { } +void* __VERIFIER_nondet_pointer(void) { } +_Bool __VERIFIER_nondet_bool(void) { } +*/ + +#endif diff --git a/genwasym_runtime/include/genwasym.h b/genwasym_runtime/include/genwasym.h new file mode 100644 index 000000000..b7fa06a1a --- /dev/null +++ b/genwasym_runtime/include/genwasym.h @@ -0,0 +1,4 @@ +#pragma once + +int genwasym_dummy(); + diff --git a/genwasym_runtime/include/wasm.hpp b/genwasym_runtime/include/wasm.hpp new file mode 100644 index 000000000..897b2b211 --- /dev/null +++ b/genwasym_runtime/include/wasm.hpp @@ -0,0 +1,11 @@ +#ifndef WASM_HEADERS +#define WASM_HEADERS + +#include "wasm/concolic_driver.hpp" +#include "wasm/concrete_rt.hpp" +#include "wasm/controls.hpp" +#include "wasm/profile.hpp" +#include "wasm/sym_rt.hpp" +#include "wasm/utils.hpp" + +#endif \ No newline at end of file diff --git a/genwasym_runtime/include/wasm/concolic_driver.hpp b/genwasym_runtime/include/wasm/concolic_driver.hpp new file mode 100644 index 000000000..fb8ffb4e9 --- /dev/null +++ b/genwasym_runtime/include/wasm/concolic_driver.hpp @@ -0,0 +1,277 @@ +#ifndef CONCOLIC_DRIVER_HPP +#define CONCOLIC_DRIVER_HPP + +#include "concrete_rt.hpp" +#include "config.hpp" +#include "output_report.hpp" +#include "profile.hpp" +#include "smt_solver.hpp" +#include "sym_rt.hpp" +#include "utils.hpp" +#include "z3++.h" +#include +#include +#include +#include +#include +#include +#include +#include + +class ConcolicDriver { + friend class ManagedConcolicCleanup; + +public: + ConcolicDriver(std::function entrypoint, + std::optional tree_file, int branchCount) + : entrypoint(entrypoint), tree_file(tree_file) { + ExploreTree.true_branch_cov_map.assign(branchCount, false); + ExploreTree.false_branch_cov_map.assign(branchCount, false); + } + void run(); + +private: + void main_exploration_loop(); + std::optional get_new_input(); + std::vector> collect_all_unexplored_path_conds(); + std::function entrypoint; + std::optional tree_file; + std::vector work_list; + std::set visited; +}; + +class ManagedConcolicCleanup { + const ConcolicDriver &driver; + +public: + ManagedConcolicCleanup(const ConcolicDriver &driver) : driver(driver) {} + ~ManagedConcolicCleanup() { + // put any cleanup code that needs to be done after each execution here + + // Dump the explore tree if needed + if (driver.tree_file.has_value()) + ExploreTree.dump_graphviz(driver.tree_file.value()); + + // Profile.print_summary(); + } +}; + +static std::monostate reset_stacks(); + +// A PathFrontier represents the frontier of an unexplored path. From this +// frontier, we can explore the path by executing the program from the beginning +// with the model stored in QueryResult. +struct PathFrontier { + QueryResult query_result; + NodeBox *node; +}; + +class PathPicker { +public: + PathPicker(std::vector &unexplored_paths, + std::set &visited) + : unexplored_paths(unexplored_paths), visited(visited) {} + + virtual std::optional pick_path() = 0; + +protected: + std::vector &unexplored_paths; + std::set &visited; +}; + +class DefaultPathPicker : public PathPicker { +public: + DefaultPathPicker(std::vector &unexplored_paths, + std::set &visited) + : PathPicker(unexplored_paths, visited) {} + + std::optional pick_path() override { + NodeBox *node = unexplored_paths.back(); + unexplored_paths.pop_back(); + + if (visited.find(node) != visited.end()) { + return std::nullopt; + } else { + visited.insert(node); + } + + if (!node->isUnexplored()) { + // if it's not unexplored anymore, skip it + return std::nullopt; + } + + std::optional result; + { + ManagedTimer timer(TimeProfileKind::SOLVER_TOTAL); + auto cond = node->collect_path_conds(); + result = solver.solve_path_conds(cond, true); + } + if (!result.has_value()) { + GENSYM_INFO("Found an unreachable path, marking it as unreachable..."); + node->fillUnreachableNode(); + return std::nullopt; + } + return PathFrontier{result.value(), node}; + } +}; + +class RandomPathPicker : public PathPicker { +public: + RandomPathPicker(std::vector &unexplored_paths, + std::set &visited) + : PathPicker(unexplored_paths, visited) {} + std::optional pick_path() override { + ManagedTimer timer(TimeProfileKind::SOLVER_TOTAL); + + if (unexplored_paths.empty()) { + return std::nullopt; + } + std::vector> all_path_conds; + std::vector candidate_nodes; + + for (auto node : unexplored_paths) { + ManagedTimer timer(TimeProfileKind::COLLECT_PATH_CONDITIONS); + if (visited.find(node) != visited.end()) { + continue; + } + if (!node->isUnexplored()) { + // I suppose thse should not happen + // assert(false); + continue; + } + all_path_conds.push_back(node->collect_path_conds()); + candidate_nodes.push_back(node); + } + + auto result = solver.find_reachable_path_with_witness(all_path_conds, + candidate_nodes); + if (!result.has_value()) { + for (auto node : candidate_nodes) { + GENSYM_INFO("Found an unreachable path, marking it as unreachable..."); + node->fillUnreachableNode(); + } + unexplored_paths.clear(); + return std::nullopt; + } + return PathFrontier{.query_result = *result, .node = result->witness}; + } +}; + +inline void ConcolicDriver::main_exploration_loop() { + + // Register a collector to ExploreTree to add new nodes to work_list + ExploreTree.register_new_node_collector([&](NodeBox *new_node) { + if (std::find(work_list.begin(), work_list.end(), new_node) == + work_list.end()) + work_list.push_back(new_node); + }); + + assert(ExploreTree.get_root()->isUnexplored() && + "Before main loop, root should be unexplored!"); + work_list.push_back(ExploreTree.get_root()); + + PathPicker &&picker = DefaultPathPicker(work_list, visited); + + while (!work_list.empty()) { + if (INTERACTIVE_MODE) { + std::cout << "Press Enter to continue to the next path..." << std::endl; + std::cin.get(); + } + ManagedConcolicCleanup cleanup{*this}; + ManagedTimer timer(TimeProfileKind::MAIN_LOOP); + // Pick a frontier of an unexplored path from the work list + auto frontier = picker.pick_path(); + if (!frontier.has_value()) { + continue; + } + + auto &node = frontier.value().node; + + const NumMap &new_env = *frontier.value().query_result.map_box; + z3::model &model = frontier.value().query_result.model; + + // update global symbolic environment from SMT solved model + SymEnv.update(new_env); + try { + GENSYM_INFO("Now execute the program with symbolic environment: "); + GENSYM_INFO(SymEnv.to_string()); + auto snapshot = dynamic_cast(node->node.get()); + if (REUSE_SNAPSHOT && snapshot && snapshot->worth_to_reuse()) { + assert(REUSE_SNAPSHOT); + Profile.incr_fromsnapshot_count(); + auto snap = snapshot->get_snapshot(); + snap.resume_execution_by_model(node, model); + } else { + Profile.incr_restart_count(); + auto timer = ManagedTimer(TimeProfileKind::INSTR); + ExploreTree.reset_cursor(); + reset_stacks(); + CostManager.reset_timer(); + entrypoint(); + } + + GENSYM_INFO("Execution finished successfully"); + } catch (std::runtime_error &e) { + std::cout << "Caught runtime error: " << e.what() << std::endl; + ExploreTree.fillFailedNode(); + + if (std::string(e.what()) == "Symbolic assertion failed") { + GENSYM_INFO("Symbolic assertion failed, continuing to next path..."); + continue; + } + + GENSYM_INFO("Caught runtime error during execution"); + switch (EXPLORE_MODE) { + case ExploreMode::EarlyExit: + return; + case ExploreMode::ExitByCoverage: + if (ExploreTree.all_branch_covered()) { + GENSYM_INFO("All branches covered, exiting..."); + return; + } else { + GENSYM_INFO( + "Found a bug, but not all branches covered, continuing..."); + } + std::cout << e.what() << std::endl; + } + } +#if defined(RUN_ONCE) + return; +#endif + } +} + +inline std::vector> +ConcolicDriver::collect_all_unexplored_path_conds() { + std::vector> result; + for (auto node : work_list) { + if (node->isUnexplored()) { + result.push_back(node->collect_path_conds()); + } + } + return result; +} + +inline void ConcolicDriver::run() { + main_exploration_loop(); + auto overall = ExploreTree.read_current_overall_result(); + overall.print(); + Profile.print_summary(); + dump_all_summary_json(Profile, overall); +} + +static void start_concolic_execution_with( + std::function entrypoint, int branchCount) { + + const char *env_tree_file = std::getenv("TREE_FILE"); + + auto tree_file = + env_tree_file ? std::make_optional(env_tree_file) : std::nullopt; + + ConcolicDriver driver = ConcolicDriver( + [=]() { entrypoint(std::monostate{}); }, tree_file, branchCount); + driver.run(); + std::quick_exit(0); +} + +#endif // CONCOLIC_DRIVER_HPP \ No newline at end of file diff --git a/genwasym_runtime/include/wasm/concrete_num.hpp b/genwasym_runtime/include/wasm/concrete_num.hpp new file mode 100644 index 000000000..6a2eafc50 --- /dev/null +++ b/genwasym_runtime/include/wasm/concrete_num.hpp @@ -0,0 +1,989 @@ +#ifndef WASM_CONCRETE_NUM_HPP +#define WASM_CONCRETE_NUM_HPP +#include "wasm/profile.hpp" +#include "wasm/utils.hpp" +#include +#include + +struct Num { + Num(int64_t value) : value(value) {} + Num() : value(0) {} + int64_t value; + + int32_t toInt() const { return static_cast(value); } + uint32_t toUInt() const { return static_cast(value); } + int64_t toInt64() const { return static_cast(value); } + uint64_t toUInt64() const { return static_cast(value); } + float toF32() const { return *reinterpret_cast(&value); } + double toF64() const { return *reinterpret_cast(&value); } + + // debug printer: enabled only when -DDEBUG + static inline void debug_print(const char *op, const Num &a, const Num &b, + const Num &res) { +#ifdef DEBUG_OP + std::cout << "[Debug] " << op << ": lhs=" << static_cast(a.value) + << " rhs=" << static_cast(b.value) + << " -> res=" << static_cast(res.value) << std::endl; +#endif + } + + // Helper to create a Wasm Boolean result (1 or 0 as Num) + Num WasmBool(bool condition) const { + Num res(condition ? 1 : 0); + debug_print("WasmBool", *this, *this, res); + return res; + } + // TODO: support different bit width operations, for now we just assume all + // oprands are i32 + // i32.eq (Equals): *this == other + inline Num i32_eq(const Num &other) const { + Num res = WasmBool(this->toUInt() == other.toUInt()); + debug_print("i32.eq", *this, other, res); + return res; + } + + // i32.ne (Not Equals): *this != other + inline Num i32_ne(const Num &other) const { + Num res = WasmBool(this->toUInt() != other.toUInt()); + debug_print("i32.ne", *this, other, res); + return res; + } + + // i32.lt_s (Signed Less Than): *this < other + inline Num i32_lt_s(const Num &other) const { + Num res = WasmBool(this->toInt() < other.toInt()); + debug_print("i32.lt_s", *this, other, res); + return res; + } + + // i32.lt_u (Unsigned Less Than): *this < other (unsigned) + inline Num i32_lt_u(const Num &other) const { + Num res = WasmBool(this->toUInt() < other.toUInt()); + debug_print("i32.lt_u", *this, other, res); + return res; + } + + // i32.le_s (Signed Less Than or Equal): *this <= other + inline Num i32_le_s(const Num &other) const { + Num res = WasmBool(this->toInt() <= other.toInt()); + debug_print("i32.le_s", *this, other, res); + return res; + } + // i32.le_u (Unsigned Less Than or Equal): *this <= other (unsigned) + inline Num i32_le_u(const Num &other) const { + Num res = WasmBool(this->toUInt() <= other.toUInt()); + debug_print("i32.le_u", *this, other, res); + return res; + } + + // i32.gt_s (Signed Greater Than): *this > other + inline Num i32_gt_s(const Num &other) const { + Num res = WasmBool(this->toInt() > other.toInt()); + debug_print("i32.gt_s", *this, other, res); + return res; + } + + // i32.gt_u (Unsigned Greater Than): *this > other (unsigned) + inline Num i32_gt_u(const Num &other) const { + Num res = WasmBool(this->toUInt() > other.toUInt()); + debug_print("i32.gt_u", *this, other, res); + return res; + } + + // i32.ge_s (Signed Greater Than or Equal): *this >= other + inline Num i32_ge_s(const Num &other) const { + Num res = WasmBool(this->toInt() >= other.toInt()); + debug_print("i32.ge_s", *this, other, res); + return res; + } + + // i32.ge_u (Unsigned Greater Than or Equal): *this >= other (unsigned) + inline Num i32_ge_u(const Num &other) const { + Num res = WasmBool(this->toUInt() >= other.toUInt()); + debug_print("i32.ge_u", *this, other, res); + return res; + } + + // i32.add (Wrapping addition) + inline Num i32_add(const Num &other) const { + uint32_t result_u = this->toUInt() + other.toUInt(); + Num res(static_cast(result_u)); + debug_print("i32.add", *this, other, res); + return res; + } + + // i32.sub (Wrapping subtraction) + inline Num i32_sub(const Num &other) const { + uint32_t result_u = this->toUInt() - other.toUInt(); + Num res(static_cast(result_u)); + debug_print("i32.sub", *this, other, res); + return res; + } + + // i32.mul (Wrapping multiplication) + inline Num i32_mul(const Num &other) const { + uint32_t result_u = this->toUInt() * other.toUInt(); + Num res(static_cast(result_u)); + debug_print("i32.mul", *this, other, res); + return res; + } + + // i32.div_s (Signed division with traps) + inline Num i32_div_s(const Num &other) const { + int32_t divisor = other.toInt(); + int32_t dividend = this->toInt(); + + if (divisor == 0) { + throw std::runtime_error("i32.div_s: Division by zero"); + } + + Num res(dividend / divisor); + debug_print("i32.div_s", *this, other, res); + return res; + } + + // i32.div_u (Unsigned division with traps) + inline Num i32_div_u(const Num &other) const { + uint32_t divisor = other.toUInt(); + uint32_t dividend = this->toUInt(); + if (divisor == 0) { + throw std::runtime_error("i32.div_u: Division by zero"); + } + Num res(static_cast(dividend / divisor)); + debug_print("i32.div_u", *this, other, res); + return res; + } + + // i32.rem_s (Signed remainder with traps on division by zero) + inline Num i32_rem_s(const Num &other) const { + int32_t divisor = other.toInt(); + int32_t dividend = this->toInt(); + if (divisor == 0) { + throw std::runtime_error("i32.rem_s: Division by zero"); + } + // WebAssembly defines INT_MIN % -1 == 0 + if (dividend == INT32_MIN && divisor == -1) { + Num res(0); + debug_print("i32.rem_s", *this, other, res); + return res; + } + Num res(dividend % divisor); + debug_print("i32.rem_s", *this, other, res); + return res; + } + + // i32.rem_u (Unsigned remainder with traps on division by zero) + inline Num i32_rem_u(const Num &other) const { + uint32_t divisor = other.toUInt(); + uint32_t dividend = this->toUInt(); + if (divisor == 0) { + throw std::runtime_error("i32.rem_u: Division by zero"); + } + Num res(static_cast(dividend % divisor)); + debug_print("i32.rem_u", *this, other, res); + return res; + } + + // i32.shl (Shift Left): *this << other (shift count masked by 31) + inline Num i32_shl(const Num &other) const { + uint32_t shift_amount = other.toUInt() & 0x1F; + uint32_t result_u = toUInt() << shift_amount; + Num res(static_cast(result_u)); + debug_print("i32.shl", *this, other, res); + return res; + } + + // i32.shr_s (Signed Shift Right): *this >> other (Arithmetic shift) + inline Num i32_shr_s(const Num &other) const { + // Wasm masks the shift amount by 31 (0x1F) + uint32_t shift_amount = other.toUInt() & 0x1F; + int32_t result_s = toInt() >> shift_amount; + Num res(result_s); + debug_print("i32.shr_s", *this, other, res); + return res; + } + + // i32.shr_u (Unsigned Shift Right): *this >>> other (Logical shift) + inline Num i32_shr_u(const Num &other) const { + // Wasm masks the shift amount by 31 (0x1F) + uint32_t shift_amount = other.toUInt() & 0x1F; + uint32_t result_u = toUInt() >> shift_amount; + Num res(static_cast(result_u)); + debug_print("i32.shr_u", *this, other, res); + return res; + } + + // i32.and (Bitwise AND) + inline Num i32_and(const Num &other) const { + uint32_t result_u = this->toUInt() & other.toUInt(); + Num res(static_cast(result_u)); + debug_print("i32.and", *this, other, res); + return res; + } + + // i32.xor (Bitwise XOR) + inline Num i32_xor(const Num &other) const { + uint32_t result_u = this->toUInt() ^ other.toUInt(); + Num res(static_cast(result_u)); + debug_print("i32.xor", *this, other, res); + return res; + } + + inline Num i32_or(const Num &other) const { + uint32_t result_u = this->toUInt() | other.toUInt(); + Num res(static_cast(result_u)); + debug_print("i32.or", *this, other, res); + return res; + } + + // i64.extend_i32_s: sign-extend low 32 bits to i64 + inline Num i32_extend_to_i64_s() const { + int64_t result_s = static_cast(this->toInt()); + Num res(result_s); + debug_print("i32.extend_to_i64_s", *this, *this, res); + return res; + } + + // i64.extend_i32_u: zero-extend low 32 bits to i64 + inline Num i32_extend_to_i64_u() const { + uint64_t result_u = static_cast(this->toUInt()); + Num res(static_cast(result_u)); + debug_print("i32.extend_to_i64_u", *this, *this, res); + return res; + } + + // i64.eq (Equals): *this == other + inline Num i64_eq(const Num &other) const { + Num res = WasmBool(this->toUInt64() == other.toUInt64()); + debug_print("i64.eq", *this, other, res); + return res; + } + + // i64.ne (Not Equals): *this != other + inline Num i64_ne(const Num &other) const { + Num res = WasmBool(this->toUInt64() != other.toUInt64()); + debug_print("i64.ne", *this, other, res); + return res; + } + + // i64.lt_s (Signed Less Than): *this < other + inline Num i64_lt_s(const Num &other) const { + Num res = WasmBool(this->toInt64() < other.toInt64()); + debug_print("i64.lt_s", *this, other, res); + return res; + } + + // i64.lt_u (Unsigned Less Than): *this < other (unsigned) + inline Num i64_lt_u(const Num &other) const { + Num res = WasmBool(this->toUInt64() < other.toUInt64()); + debug_print("i64.lt_u", *this, other, res); + return res; + } + + // i64.le_s (Signed Less Than or Equal): *this <= other + inline Num i64_le_s(const Num &other) const { + Num res = WasmBool(this->toInt64() <= other.toInt64()); + debug_print("i64.le_s", *this, other, res); + return res; + } + + // i64.le_u (Unsigned Less Than or Equal): *this <= other (unsigned) + inline Num i64_le_u(const Num &other) const { + Num res = WasmBool(this->toUInt64() <= other.toUInt64()); + debug_print("i64.le_u", *this, other, res); + return res; + } + + // i64.gt_s (Signed Greater Than): *this > other + inline Num i64_gt_s(const Num &other) const { + Num res = WasmBool(this->toInt64() > other.toInt64()); + debug_print("i64.gt_s", *this, other, res); + return res; + } + + // i64.gt_u (Unsigned Greater Than): *this > other (unsigned) + inline Num i64_gt_u(const Num &other) const { + Num res = WasmBool(this->toUInt64() > other.toUInt64()); + debug_print("i64.gt_u", *this, other, res); + return res; + } + + // i64.ge_s (Signed Greater Than or Equal): *this >= other + inline Num i64_ge_s(const Num &other) const { + Num res = WasmBool(this->toInt64() >= other.toInt64()); + debug_print("i64.ge_s", *this, other, res); + return res; + } + + // i64.ge_u (Unsigned Greater Than or Equal): *this >= other (unsigned) + inline Num i64_ge_u(const Num &other) const { + Num res = WasmBool(this->toUInt64() >= other.toUInt64()); + debug_print("i64.ge_u", *this, other, res); + return res; + } + + // i64.add (Wrapping addition) + inline Num i64_add(const Num &other) const { + uint64_t result_u = this->toUInt64() + other.toUInt64(); + Num res(static_cast(result_u)); + debug_print("i64.add", *this, other, res); + return res; + } + + // i64.sub (Wrapping subtraction) + inline Num i64_sub(const Num &other) const { + uint64_t result_u = this->toUInt64() - other.toUInt64(); + Num res(static_cast(result_u)); + debug_print("i64.sub", *this, other, res); + return res; + } + + // i64.mul (Wrapping multiplication) + inline Num i64_mul(const Num &other) const { + uint64_t result_u = this->toUInt64() * other.toUInt64(); + Num res(static_cast(result_u)); + debug_print("i64.mul", *this, other, res); + return res; + } + + // i64.div_s (Signed division with traps) + inline Num i64_div_s(const Num &other) const { + int64_t divisor = other.toInt64(); + int64_t dividend = this->toInt64(); + + if (divisor == 0) { + throw std::runtime_error("i64.div_s: Division by zero"); + } + if (dividend == INT64_MIN && divisor == -1) { + throw std::runtime_error("i64.div_s: Integer overflow"); + } + + Num res(dividend / divisor); + debug_print("i64.div_s", *this, other, res); + return res; + } + + // i64.div_u (Unsigned division with traps) + inline Num i64_div_u(const Num &other) const { + uint64_t divisor = other.toUInt64(); + uint64_t dividend = this->toUInt64(); + if (divisor == 0) { + throw std::runtime_error("i64.div_u: Division by zero"); + } + Num res(static_cast(dividend / divisor)); + debug_print("i64.div_u", *this, other, res); + return res; + } + + // i64.rem_s (Signed remainder with traps on division by zero) + inline Num i64_rem_s(const Num &other) const { + int64_t divisor = other.toInt64(); + int64_t dividend = this->toInt64(); + if (divisor == 0) { + throw std::runtime_error("i64.rem_s: Division by zero"); + } + // WebAssembly defines INT64_MIN % -1 == 0 + if (dividend == INT64_MIN && divisor == -1) { + Num res(0); + debug_print("i64.rem_s", *this, other, res); + return res; + } + Num res(dividend % divisor); + debug_print("i64.rem_s", *this, other, res); + return res; + } + + // i64.rem_u (Unsigned remainder with traps on division by zero) + inline Num i64_rem_u(const Num &other) const { + uint64_t divisor = other.toUInt64(); + uint64_t dividend = this->toUInt64(); + if (divisor == 0) { + throw std::runtime_error("i64.rem_u: Division by zero"); + } + Num res(static_cast(dividend % divisor)); + debug_print("i64.rem_u", *this, other, res); + return res; + } + + // i64.shl (Shift Left): *this << other (shift count masked by 63) + inline Num i64_shl(const Num &other) const { + uint64_t shift_amount = other.toUInt64() & 0x3F; + uint64_t result_u = toUInt64() << shift_amount; + Num res(static_cast(result_u)); + debug_print("i64.shl", *this, other, res); + return res; + } + + // i64.shr_s (Signed Shift Right): *this >> other (Arithmetic shift) + inline Num i64_shr_s(const Num &other) const { + uint64_t shift_amount = other.toUInt64() & 0x3F; + int64_t result_s = toInt64() >> shift_amount; + Num res(result_s); + debug_print("i64.shr_s", *this, other, res); + return res; + } + + // i64.shr_u (Unsigned Shift Right): *this >>> other (Logical shift) + inline Num i64_shr_u(const Num &other) const { + uint64_t shift_amount = other.toUInt64() & 0x3F; + uint64_t result_u = toUInt64() >> shift_amount; + Num res(static_cast(result_u)); + debug_print("i64.shr_u", *this, other, res); + return res; + } + + // i64.and (Bitwise AND) + inline Num i64_and(const Num &other) const { + uint64_t result_u = this->toUInt64() & other.toUInt64(); + Num res(static_cast(result_u)); + debug_print("i64.and", *this, other, res); + return res; + } + + // i64.xor (Bitwise XOR) + inline Num i64_xor(const Num &other) const { + uint64_t result_u = this->toUInt64() ^ other.toUInt64(); + Num res(static_cast(result_u)); + debug_print("i64.xor", *this, other, res); + return res; + } + + // i64.or (Bitwise OR) + inline Num i64_or(const Num &other) const { + uint64_t result_u = this->toUInt64() | other.toUInt64(); + Num res(static_cast(result_u)); + debug_print("i64.or", *this, other, res); + return res; + } + + // f32 helpers: interpret low 32 bits of value as IEEE-754 float + static inline float f32_from_bits(uint32_t bits) { + union { + uint32_t i; + float f; + } u; + u.i = bits; + return u.f; + } + static inline uint32_t f32_to_bits(float f) { + union { + uint32_t i; + float f; + } u; + u.f = f; + return u.i; + } + static inline bool f32_is_nan(uint32_t bits) { + // Exponent all ones and mantissa non-zero -> NaN for IEEE-754 single + return (bits & 0x7F800000u) == 0x7F800000u && (bits & 0x007FFFFFu) != 0; + } + + // f32.add + inline Num f32_add(const Num &other) const { + uint32_t a_bits = toUInt(); + uint32_t b_bits = other.toUInt(); + float a = f32_from_bits(a_bits); + float b = f32_from_bits(b_bits); + float r = a + b; + uint32_t r_bits = f32_to_bits(r); + Num res(static_cast(r_bits)); + debug_print("f32.add", *this, other, res); + return res; + } + + // f32.sub + inline Num f32_sub(const Num &other) const { + uint32_t a_bits = toUInt(); + uint32_t b_bits = other.toUInt(); + float a = f32_from_bits(a_bits); + float b = f32_from_bits(b_bits); + float r = a - b; + uint32_t r_bits = f32_to_bits(r); + Num res(static_cast(r_bits)); + debug_print("f32.sub", *this, other, res); + return res; + } + + // f32.mul + inline Num f32_mul(const Num &other) const { + uint32_t a_bits = toUInt(); + uint32_t b_bits = other.toUInt(); + float a = f32_from_bits(a_bits); + float b = f32_from_bits(b_bits); + float r = a * b; + uint32_t r_bits = f32_to_bits(r); + Num res(static_cast(r_bits)); + debug_print("f32.mul", *this, other, res); + return res; + } + + // f32.div + inline Num f32_div(const Num &other) const { + uint32_t a_bits = toUInt(); + uint32_t b_bits = other.toUInt(); + float a = f32_from_bits(a_bits); + float b = f32_from_bits(b_bits); + float r = a / b; + uint32_t r_bits = f32_to_bits(r); + Num res(static_cast(r_bits)); + debug_print("f32.div", *this, other, res); + return res; + } + + // f32.eq : false if either is NaN + inline Num f32_eq(const Num &other) const { + uint32_t a_bits = toUInt(); + uint32_t b_bits = other.toUInt(); + if (f32_is_nan(a_bits) || f32_is_nan(b_bits)) { + Num res = WasmBool(false); + debug_print("f32.eq", *this, other, res); + return res; + } + float a = f32_from_bits(a_bits); + float b = f32_from_bits(b_bits); + Num res = WasmBool(a == b); + debug_print("f32.eq", *this, other, res); + return res; + } + + // f32.ne : true if values are unordered or not equal (i.e., NaN makes it + // true) + inline Num f32_ne(const Num &other) const { + uint32_t a_bits = toUInt(); + uint32_t b_bits = other.toUInt(); + // per wasm: if either is NaN, f32.ne is true + if (f32_is_nan(a_bits) || f32_is_nan(b_bits)) { + Num res = WasmBool(true); + debug_print("f32.ne", *this, other, res); + return res; + } + float a = f32_from_bits(a_bits); + float b = f32_from_bits(b_bits); + Num res = WasmBool(a != b); + debug_print("f32.ne", *this, other, res); + return res; + } + + // ordered comparisons: return false if any operand is NaN + inline Num f32_lt(const Num &other) const { + uint32_t a_bits = toUInt(), b_bits = other.toUInt(); + if (f32_is_nan(a_bits) || f32_is_nan(b_bits)) + return WasmBool(false); + float a = f32_from_bits(a_bits), b = f32_from_bits(b_bits); + Num res = WasmBool(a < b); + debug_print("f32.lt", *this, other, res); + return res; + } + inline Num f32_le(const Num &other) const { + uint32_t a_bits = toUInt(), b_bits = other.toUInt(); + if (f32_is_nan(a_bits) || f32_is_nan(b_bits)) + return WasmBool(false); + float a = f32_from_bits(a_bits), b = f32_from_bits(b_bits); + Num res = WasmBool(a <= b); + debug_print("f32.le", *this, other, res); + return res; + } + inline Num f32_gt(const Num &other) const { + uint32_t a_bits = toUInt(), b_bits = other.toUInt(); + if (f32_is_nan(a_bits) || f32_is_nan(b_bits)) + return WasmBool(false); + float a = f32_from_bits(a_bits), b = f32_from_bits(b_bits); + Num res = WasmBool(a > b); + debug_print("f32.gt", *this, other, res); + return res; + } + inline Num f32_ge(const Num &other) const { + uint32_t a_bits = toUInt(), b_bits = other.toUInt(); + if (f32_is_nan(a_bits) || f32_is_nan(b_bits)) + return WasmBool(false); + float a = f32_from_bits(a_bits), b = f32_from_bits(b_bits); + Num res = WasmBool(a >= b); + debug_print("f32.ge", *this, other, res); + return res; + } + + // f32.abs: clear sign bit + inline Num f32_abs() const { + uint32_t a_bits = toUInt(); + uint32_t r_bits = a_bits & 0x7FFFFFFFu; + Num res(static_cast(r_bits)); + debug_print("f32.abs", *this, *this, res); + return res; + } + + // f32.neg: flip sign bit + inline Num f32_neg() const { + uint32_t a_bits = toUInt(); + uint32_t r_bits = a_bits ^ 0x80000000u; + Num res(static_cast(r_bits)); + debug_print("f32.neg", *this, *this, res); + return res; + } + + inline Num convert_i32_to_f32_s() const { + uint32_t r_bits = f32_to_bits(static_cast(toInt())); + return Num(static_cast(r_bits)); + } + + inline Num convert_i32_to_f32_u() const { + uint32_t r_bits = f32_to_bits(static_cast(toUInt())); + return Num(static_cast(r_bits)); + } + + inline Num convert_i64_to_f32_s() const { + uint32_t r_bits = f32_to_bits(static_cast(toInt64())); + return Num(static_cast(r_bits)); + } + + inline Num convert_i64_to_f32_u() const { + uint32_t r_bits = f32_to_bits(static_cast(toUInt64())); + return Num(static_cast(r_bits)); + } + + // f32.min / f32.max: follow wasm-ish semantics: if either is NaN, return NaN + // (propagate) + inline Num f32_min(const Num &other) const { + uint32_t a_bits = toUInt(), b_bits = other.toUInt(); + if (f32_is_nan(a_bits)) + return Num(static_cast(a_bits)); + if (f32_is_nan(b_bits)) + return Num(static_cast(b_bits)); + float a = f32_from_bits(a_bits), b = f32_from_bits(b_bits); + // If values compare equal choose one to preserve signed zero: pick the one + // whose sign bit is set for min when both zeros (so -0 wins for min). + if (a == b) { + if ((a_bits & 0x80000000u) || (b_bits & 0x80000000u)) + return Num( + static_cast((a_bits & 0x80000000u) ? a_bits : b_bits)); + return Num(static_cast(a_bits)); + } + float r = (a < b) ? a : b; + uint32_t r_bits = f32_to_bits(r); + Num res(static_cast(r_bits)); + debug_print("f32.min", *this, other, res); + return res; + } + + inline Num f32_max(const Num &other) const { + uint32_t a_bits = toUInt(), b_bits = other.toUInt(); + if (f32_is_nan(a_bits)) + return Num(static_cast(a_bits)); + if (f32_is_nan(b_bits)) + return Num(static_cast(b_bits)); + float a = f32_from_bits(a_bits), b = f32_from_bits(b_bits); + if (a == b) { + if ((a_bits & 0x80000000u) || (b_bits & 0x80000000u)) + return Num( + static_cast((a_bits & 0x80000000u) ? b_bits : a_bits)); + return Num(static_cast(a_bits)); + } + float r = (a > b) ? a : b; + uint32_t r_bits = f32_to_bits(r); + Num res(static_cast(r_bits)); + debug_print("f32.max", *this, other, res); + return res; + } + + // f32.copysign: result has magnitude of lhs, sign of rhs + inline Num f32_copysign(const Num &other) const { + uint32_t a_bits = toUInt(), b_bits = other.toUInt(); + uint32_t r_bits = (a_bits & 0x7FFFFFFFu) | (b_bits & 0x80000000u); + Num res(static_cast(r_bits)); + debug_print("f32.copysign", *this, other, res); + return res; + } + + // f64 helpers: interpret all 64 bits of value as IEEE-754 double + static inline double f64_from_bits(uint64_t bits) { + union { + uint64_t i; + double d; + } u; + u.i = bits; + return u.d; + } + static inline uint64_t f64_to_bits(double d) { + union { + uint64_t i; + double d; + } u; + u.d = d; + return u.i; + } + static inline bool f64_is_nan(uint64_t bits) { + // Exponent all ones and mantissa non-zero -> NaN for IEEE-754 double + return (bits & 0x7FF0000000000000ull) == 0x7FF0000000000000ull && + (bits & 0x000FFFFFFFFFFFFFull) != 0; + } + + // f64.add + inline Num f64_add(const Num &other) const { + uint64_t a_bits = toUInt64(); + uint64_t b_bits = other.toUInt64(); + double a = f64_from_bits(a_bits); + double b = f64_from_bits(b_bits); + double r = a + b; + uint64_t r_bits = f64_to_bits(r); + Num res(static_cast(r_bits)); + debug_print("f64.add", *this, other, res); + return res; + } + + // f64.sub + inline Num f64_sub(const Num &other) const { + uint64_t a_bits = toUInt64(); + uint64_t b_bits = other.toUInt64(); + double a = f64_from_bits(a_bits); + double b = f64_from_bits(b_bits); + double r = a - b; + uint64_t r_bits = f64_to_bits(r); + Num res(static_cast(r_bits)); + debug_print("f64.sub", *this, other, res); + return res; + } + + // f64.mul + inline Num f64_mul(const Num &other) const { + uint64_t a_bits = toUInt64(); + uint64_t b_bits = other.toUInt64(); + double a = f64_from_bits(a_bits); + double b = f64_from_bits(b_bits); + double r = a * b; + uint64_t r_bits = f64_to_bits(r); + Num res(static_cast(r_bits)); + debug_print("f64.mul", *this, other, res); + return res; + } + + // f64.div + inline Num f64_div(const Num &other) const { + uint64_t a_bits = toUInt64(); + uint64_t b_bits = other.toUInt64(); + double a = f64_from_bits(a_bits); + double b = f64_from_bits(b_bits); + double r = a / b; + uint64_t r_bits = f64_to_bits(r); + Num res(static_cast(r_bits)); + debug_print("f64.div", *this, other, res); + return res; + } + + // f64.eq : false if either is NaN + inline Num f64_eq(const Num &other) const { + uint64_t a_bits = toUInt64(); + uint64_t b_bits = other.toUInt64(); + if (f64_is_nan(a_bits) || f64_is_nan(b_bits)) { + Num res = WasmBool(false); + debug_print("f64.eq", *this, other, res); + return res; + } + double a = f64_from_bits(a_bits); + double b = f64_from_bits(b_bits); + Num res = WasmBool(a == b); + debug_print("f64.eq", *this, other, res); + return res; + } + + // f64.ne : true if values are unordered or not equal (i.e., NaN makes it + // true) + inline Num f64_ne(const Num &other) const { + uint64_t a_bits = toUInt64(); + uint64_t b_bits = other.toUInt64(); + // per wasm: if either is NaN, f64.ne is true + if (f64_is_nan(a_bits) || f64_is_nan(b_bits)) { + Num res = WasmBool(true); + debug_print("f64.ne", *this, other, res); + return res; + } + double a = f64_from_bits(a_bits); + double b = f64_from_bits(b_bits); + Num res = WasmBool(a != b); + debug_print("f64.ne", *this, other, res); + return res; + } + + // ordered comparisons: return false if any operand is NaN + inline Num f64_lt(const Num &other) const { + uint64_t a_bits = toUInt64(), b_bits = other.toUInt64(); + if (f64_is_nan(a_bits) || f64_is_nan(b_bits)) + return WasmBool(false); + double a = f64_from_bits(a_bits), b = f64_from_bits(b_bits); + Num res = WasmBool(a < b); + debug_print("f64.lt", *this, other, res); + return res; + } + inline Num f64_le(const Num &other) const { + uint64_t a_bits = toUInt64(), b_bits = other.toUInt64(); + if (f64_is_nan(a_bits) || f64_is_nan(b_bits)) + return WasmBool(false); + double a = f64_from_bits(a_bits), b = f64_from_bits(b_bits); + Num res = WasmBool(a <= b); + debug_print("f64.le", *this, other, res); + return res; + } + inline Num f64_gt(const Num &other) const { + uint64_t a_bits = toUInt64(), b_bits = other.toUInt64(); + if (f64_is_nan(a_bits) || f64_is_nan(b_bits)) + return WasmBool(false); + double a = f64_from_bits(a_bits), b = f64_from_bits(b_bits); + Num res = WasmBool(a > b); + debug_print("f64.gt", *this, other, res); + return res; + } + inline Num f64_ge(const Num &other) const { + uint64_t a_bits = toUInt64(), b_bits = other.toUInt64(); + if (f64_is_nan(a_bits) || f64_is_nan(b_bits)) + return WasmBool(false); + double a = f64_from_bits(a_bits), b = f64_from_bits(b_bits); + Num res = WasmBool(a >= b); + debug_print("f64.ge", *this, other, res); + return res; + } + + // f64.abs: clear sign bit + inline Num f64_abs() const { + uint64_t a_bits = toUInt64(); + uint64_t r_bits = a_bits & 0x7FFFFFFFFFFFFFFFull; + Num res(static_cast(r_bits)); + debug_print("f64.abs", *this, *this, res); + return res; + } + + // f64.neg: flip sign bit + inline Num f64_neg() const { + uint64_t a_bits = toUInt64(); + uint64_t r_bits = a_bits ^ 0x8000000000000000ull; + Num res(static_cast(r_bits)); + debug_print("f64.neg", *this, *this, res); + return res; + } + + inline Num convert_i32_to_f64_s() const { + uint64_t r_bits = f64_to_bits(static_cast(toInt())); + return Num(static_cast(r_bits)); + } + + inline Num convert_i32_to_f64_u() const { + uint64_t r_bits = f64_to_bits(static_cast(toUInt())); + return Num(static_cast(r_bits)); + } + + inline Num convert_i64_to_f64_s() const { + uint64_t r_bits = f64_to_bits(static_cast(toInt64())); + return Num(static_cast(r_bits)); + } + + inline Num convert_i64_to_f64_u() const { + uint64_t r_bits = f64_to_bits(static_cast(toUInt64())); + return Num(static_cast(r_bits)); + } + + inline Num trunc_f64_to_i32_u() const { + uint64_t bits = toUInt64(); + double value = f64_from_bits(bits); + + if (std::isnan(value)) { + throw std::runtime_error("i32.trunc_f64_u: NaN"); + } + if (std::isinf(value)) { + throw std::runtime_error("i32.trunc_f64_u: Infinity"); + } + if (value < 0.0 || value >= 4294967296.0) { + throw std::runtime_error("i32.trunc_f64_u: Out of range"); + } + + double truncated = std::trunc(value); + uint32_t result = static_cast(truncated); + Num res(static_cast(result)); + debug_print("i32.trunc_f64_u", *this, *this, res); + return res; + } + + // f64.min / f64.max: follow wasm-ish semantics: if either is NaN, return + // NaN (propagate) + inline Num f64_min(const Num &other) const { + uint64_t a_bits = toUInt64(), b_bits = other.toUInt64(); + if (f64_is_nan(a_bits)) + return Num(static_cast(a_bits)); + if (f64_is_nan(b_bits)) + return Num(static_cast(b_bits)); + double a = f64_from_bits(a_bits), b = f64_from_bits(b_bits); + // If values compare equal choose one to preserve signed zero: pick the one + // whose sign bit is set for min when both zeros (so -0 wins for min). + if (a == b) { + if ((a_bits & 0x8000000000000000ull) || (b_bits & 0x8000000000000000ull)) + return Num(static_cast( + (a_bits & 0x8000000000000000ull) ? a_bits : b_bits)); + return Num(static_cast(a_bits)); + } + double r = (a < b) ? a : b; + uint64_t r_bits = f64_to_bits(r); + Num res(static_cast(r_bits)); + debug_print("f64.min", *this, other, res); + return res; + } + + inline Num f64_max(const Num &other) const { + uint64_t a_bits = toUInt64(), b_bits = other.toUInt64(); + if (f64_is_nan(a_bits)) + return Num(static_cast(a_bits)); + if (f64_is_nan(b_bits)) + return Num(static_cast(b_bits)); + double a = f64_from_bits(a_bits), b = f64_from_bits(b_bits); + if (a == b) { + if ((a_bits & 0x8000000000000000ull) || (b_bits & 0x8000000000000000ull)) + return Num(static_cast( + (a_bits & 0x8000000000000000ull) ? b_bits : a_bits)); + return Num(static_cast(a_bits)); + } + double r = (a > b) ? a : b; + uint64_t r_bits = f64_to_bits(r); + Num res(static_cast(r_bits)); + debug_print("f64.max", *this, other, res); + return res; + } + + // f64.copysign: result has magnitude of lhs, sign of rhs + inline Num f64_copysign(const Num &other) const { + uint64_t a_bits = toUInt64(), b_bits = other.toUInt64(); + uint64_t r_bits = + (a_bits & 0x7FFFFFFFFFFFFFFFull) | (b_bits & 0x8000000000000000ull); + Num res(static_cast(r_bits)); + debug_print("f64.copysign", *this, other, res); + return res; + } + + // logic and + inline bool logical_and(const Num &other) const { + return (this->toUInt() != 0) && (other.toUInt() != 0); + } + + // logic or + inline bool logical_or(const Num &other) const { + return (this->toUInt() != 0) || (other.toUInt() != 0); + } +}; + +static Num I32V(int v) { return v; } + +static Num I64V(int64_t v) { return v; } + +static Num F32V(float f) { + union { + uint32_t i; + float f; + } u; + u.f = f; + return static_cast(u.i); +} + +static Num F64V(double d) { + union { + uint64_t i; + double d; + } u; + u.d = d; + return static_cast(u.i); +} + +#endif // WASM_CONCRETE_NUM_HPP diff --git a/genwasym_runtime/include/wasm/concrete_rt.hpp b/genwasym_runtime/include/wasm/concrete_rt.hpp new file mode 100644 index 000000000..3e54baac8 --- /dev/null +++ b/genwasym_runtime/include/wasm/concrete_rt.hpp @@ -0,0 +1,497 @@ +#ifndef WASM_CONCRETE_RT_HPP +#define WASM_CONCRETE_RT_HPP + +#include "concrete_num.hpp" +#include "controls.hpp" +#include "immer/vector_transient.hpp" +#include "wasm/profile.hpp" +#include "wasm/utils.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const int STACK_SIZE = 1024 * 64; + +class Stack_t { +public: + Stack_t() : count(0), stack_ptr(new Num[STACK_SIZE]) { + size_t page_size = (size_t)sysconf(_SC_PAGESIZE); + // pre touch the memory to avoid page faults during execution + for (int i = 0; i < STACK_SIZE; i += page_size) { + stack_ptr[i] = Num(0); + } + } + + std::monostate push(Num &&num) { +#ifdef DEBUG + printf("[Debug] pushing a value %ld to stack, size of concrete stack is: " + "%d\n", + num.value, count); +#endif + Profile.step(StepProfileKind::PUSH); + stack_ptr[count] = num; + count++; + return std::monostate{}; + } + + std::monostate push(Num &num) { + Profile.step(StepProfileKind::PUSH); + stack_ptr[count] = num; + count++; + return std::monostate{}; + } + + Num pop() { + Profile.step(StepProfileKind::POP); + assert(count > 0 && "Stack underflow"); +#ifdef DEBUG + printf("[Debug] popping a value %ld from stack, size of concrete stack is: " + "%d\n", + stack_ptr[count - 1].value, count); +#endif + Num num = stack_ptr[count - 1]; + count--; + return num; + } + + Num peek() { + Profile.step(StepProfileKind::PEEK); +#ifdef DEBUG + if (count == 0) { + throw std::runtime_error("Stack underflow"); + } +#endif + return stack_ptr[count - 1]; + } + + int32_t size() { return count; } + + void shift(int32_t offset, int32_t size) { + Profile.step(StepProfileKind::SHIFT); +#ifdef DEBUG + if (offset < 0) { + throw std::out_of_range("Invalid offset: " + std::to_string(offset)); + } + if (size < 0) { + throw std::out_of_range("Invalid size: " + std::to_string(size)); + } + std::cout << "Shifting stack by offset " << offset << " and size " << size + << std::endl; + std::cout << "Current stack size: " << count << std::endl; +#endif + // shift last `size` of numbers forward of `offset` + for (int32_t i = count - size; i < count; ++i) { + assert(i - offset >= 0); + stack_ptr[i - offset] = stack_ptr[i]; + } + count -= offset; + } + + void print() { + std::cout << "Stack contents: " << std::endl; + for (int32_t i = 0; i < count; ++i) { + std::cout << stack_ptr[count - i - 1].value << std::endl; + } + std::cout << "End of Stack contents" << std::endl; + } + + void initialize() { + // todo: remove this method + reset(); + } + + void reset() { count = 0; } + + void resize(int32_t new_size) { + assert(new_size >= 0); + count = new_size; + } + + void set_from_front(int32_t index, const Num &num) { + assert(index >= 0 && index < count); + stack_ptr[index] = num; + } + +private: + int32_t count; + Num *stack_ptr; +}; +static Stack_t Stack; +class SymFrames_t; + +const int FRAME_SIZE = 1024 * 8; +class Frames_t { +public: + Frames_t() : count(0), stack_ptr(new Num[FRAME_SIZE]), frame_ptrs() { + size_t page_size = (size_t)sysconf(_SC_PAGESIZE); + // pre touch the memory to avoid page faults during execution + for (int i = 0; i < FRAME_SIZE; i += page_size) { + stack_ptr[i] = Num(0); + } + } + + std::monostate popFrameCaller(std::int32_t size) { + assert(size >= 0); + assert(size <= count); + assert(!frame_ptrs.empty()); + auto frame_base = current_frame_base(); + assert(frame_base + size == count); + count -= size; +#ifdef USE_IMM + frame_ptrs.take(frame_ptrs.size() - 1); +#else + frame_ptrs.pop_back(); +#endif + return std::monostate{}; + } + + std::monostate popFrameCallee(std::int32_t size) { + assert(size >= 0); + assert(size <= count); + count -= size; + return std::monostate{}; + } + + Num get(std::int32_t index) { + assert(!frame_ptrs.empty() && "No active frame"); + auto frame_base = current_frame_base(); + assert(index >= 0 && frame_base + index < count && "Index out of bounds"); + Profile.step(StepProfileKind::GET); + auto ret = stack_ptr[frame_base + index]; + return ret; + } + + void set(std::int32_t index, Num num) { + assert(!frame_ptrs.empty() && "No active frame"); + auto frame_base = current_frame_base(); + assert(index >= 0 && frame_base + index < count && "Index out of bounds"); + Profile.step(StepProfileKind::SET); + stack_ptr[frame_base + index] = num; + } + + void pushFrameCaller(std::int32_t size) { + assert(size >= 0); + frame_ptrs.push_back(count); + count += size; + // Zero-initialize the new stack frames. + for (std::int32_t i = 0; i < size; ++i) { + stack_ptr[count - size + i] = Num(0); + } + } + + void pushFrameCallee(std::int32_t size) { + assert(size >= 0); + assert(!frame_ptrs.empty() && "No active frame"); + auto old_count = count; + count += size; + for (std::int32_t i = 0; i < size; ++i) { + stack_ptr[old_count + i] = Num(0); + } + } + + void reset() { + count = 0; +#ifdef USE_IMM + frame_ptrs = immer::vector_transient(); +#else + frame_ptrs.clear(); +#endif + } + + size_t size() const { return count; } + + void set_from_front(int32_t index, const Num &num) { + assert(index >= 0 && index < count && "Index out of bounds"); + stack_ptr[index] = num; + } + + void resize(int32_t new_size) { + assert(new_size >= 0); + count = new_size; + } + +private: + friend class SymFrames_t; + + size_t current_frame_base() const { +#ifdef USE_IMM + return *(frame_ptrs.end() - 1); +#else + return frame_ptrs.back(); +#endif + } + + int32_t count; + Num *stack_ptr; +#ifdef USE_IMM + immer::vector_transient frame_ptrs; +#else + std::vector frame_ptrs; +#endif +}; + +static Frames_t Frames; +static Frames_t Globals; + +static void initRand() { + // for now, just do nothing +} + +static std::monostate unreachable() { + std::cout << "Unreachable code reached!" << std::endl; + throw std::runtime_error("Unreachable code reached"); +} + +static const int PRE_ALLOC_PAGES = 20; +static int32_t pagesize = 65536; +static int32_t page_count = 0; + +struct Memory_t { + + Memory_t(int32_t init_page_count) + : memory(PRE_ALLOC_PAGES * pagesize), init_page_count(init_page_count), + page_count(init_page_count), allocated_pages(PRE_ALLOC_PAGES) { + reset(); + } + + int32_t loadInt(int32_t base, int32_t offset) { + int32_t addr = base + offset; + if (!(addr + 3 < memory.size()) || addr < 0) { + throw std::runtime_error("Invalid memory access " + std::to_string(addr)); + } + int32_t result = 0; + // Little-endian: lowest byte at lowest address + for (int i = 0; i < 4; ++i) { + result |= static_cast(memory[addr + i]) << (8 * i); + } +#ifdef DEBUG + std::cout << "[Debug] loading int " << result << " from memory at address " + << addr << std::endl; + +#endif + // just load a 4-byte integer from memory of the vector + return result; + } + + uint8_t loadByte(int32_t base, int32_t offset) { + int32_t addr = base + offset; + if (!(addr < memory.size()) || addr < 0) { + throw std::runtime_error("Invalid memory access " + std::to_string(addr)); + } + return memory[addr]; + } + + int32_t loadInt8U(int32_t base, int32_t offset) { + return static_cast(loadByte(base, offset)); + } + + int32_t loadInt8S(int32_t base, int32_t offset) { + return static_cast(loadByte(base, offset)); + } + + int32_t loadInt16U(int32_t base, int32_t offset) { + uint32_t b0 = static_cast(loadByte(base, offset)); + uint32_t b1 = static_cast(loadByte(base, offset + 1)); + return static_cast(b0 | (b1 << 8)); + } + + int32_t loadInt16S(int32_t base, int32_t offset) { + uint32_t b0 = static_cast(loadByte(base, offset)); + uint32_t b1 = static_cast(loadByte(base, offset + 1)); + uint16_t raw = static_cast(b0 | (b1 << 8)); + return static_cast(raw); + } + + int64_t loadLong8U(int32_t base, int32_t offset) { + return static_cast(loadByte(base, offset)); + } + + int64_t loadLong8S(int32_t base, int32_t offset) { + return static_cast(loadByte(base, offset)); + } + + int64_t loadLong16U(int32_t base, int32_t offset) { + uint64_t b0 = static_cast(loadByte(base, offset)); + uint64_t b1 = static_cast(loadByte(base, offset + 1)); + return static_cast(b0 | (b1 << 8)); + } + + int64_t loadLong16S(int32_t base, int32_t offset) { + uint64_t b0 = static_cast(loadByte(base, offset)); + uint64_t b1 = static_cast(loadByte(base, offset + 1)); + uint16_t raw = static_cast(b0 | (b1 << 8)); + return static_cast(raw); + } + + int64_t loadLong32U(int32_t base, int32_t offset) { + uint64_t b0 = static_cast(loadByte(base, offset)); + uint64_t b1 = static_cast(loadByte(base, offset + 1)); + uint64_t b2 = static_cast(loadByte(base, offset + 2)); + uint64_t b3 = static_cast(loadByte(base, offset + 3)); + return static_cast(b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)); + } + + int64_t loadLong32S(int32_t base, int32_t offset) { + uint64_t b0 = static_cast(loadByte(base, offset)); + uint64_t b1 = static_cast(loadByte(base, offset + 1)); + uint64_t b2 = static_cast(loadByte(base, offset + 2)); + uint64_t b3 = static_cast(loadByte(base, offset + 3)); + uint32_t raw = static_cast(b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)); + return static_cast(raw); + } + + int64_t loadLong(int32_t base, int32_t offset) { + int32_t addr = base + offset; + if (!(addr + 7 < memory.size()) || addr < 0) { + throw std::runtime_error("Invalid memory access " + std::to_string(addr)); + } + int64_t result = 0; + for (int i = 0; i < 8; ++i) { + result |= static_cast(memory[addr + i]) << (8 * i); + } + return result; + } + + std::monostate storeInt(int32_t base, int32_t offset, int32_t value) { + int32_t addr = base + offset; + // Ensure we don't write out of bounds + if (!(addr + 3 < memory.size())) { + throw std::runtime_error("Invalid memory access " + std::to_string(addr)); + } + for (int i = 0; i < 4; ++i) { + memory[addr + i] = static_cast((value >> (8 * i)) & 0xFF); + } +#ifdef DEBUG + std::cout << "[Debug] storing int " << value << " to memory at address " + << addr << std::endl; +#endif + return std::monostate{}; + } + + std::monostate storeLong(int32_t base, int32_t offset, int64_t value) { + int32_t addr = base + offset; + if (!(addr + 7 < memory.size()) || addr < 0) { + throw std::runtime_error("Invalid memory access " + std::to_string(addr)); + } + for (int i = 0; i < 8; ++i) { + memory[addr + i] = static_cast((static_cast(value) >> (8 * i)) & 0xFF); + } + return std::monostate{}; + } + + std::monostate storeInt8(int32_t base, int32_t offset, int32_t value) { + return store_byte(base + offset, static_cast(value & 0xFF)); + } + + std::monostate storeInt16(int32_t base, int32_t offset, int32_t value) { + store_byte(base + offset, static_cast(value & 0xFF)); + store_byte(base + offset + 1, static_cast((value >> 8) & 0xFF)); + return std::monostate{}; + } + + std::monostate storeLong8(int32_t base, int32_t offset, int64_t value) { + return store_byte(base + offset, static_cast(value & 0xFF)); + } + + std::monostate storeLong16(int32_t base, int32_t offset, int64_t value) { + store_byte(base + offset, static_cast(value & 0xFF)); + store_byte(base + offset + 1, static_cast((value >> 8) & 0xFF)); + return std::monostate{}; + } + + std::monostate storeLong32(int32_t base, int32_t offset, int64_t value) { + store_byte(base + offset, static_cast(value & 0xFF)); + store_byte(base + offset + 1, static_cast((value >> 8) & 0xFF)); + store_byte(base + offset + 2, static_cast((value >> 16) & 0xFF)); + store_byte(base + offset + 3, static_cast((value >> 24) & 0xFF)); + return std::monostate{}; + } + + std::monostate store_byte(int32_t addr, uint8_t value) { +#ifdef DEBUG + std::cout << "[Debug] storing byte " << std::to_string(value) + << " to memory at address " << addr << std::endl; +#endif + assert(addr < memory.size()); + memory[addr] = value; + return std::monostate{}; + } + + // grow memory by delta bytes when bytes > 0. return -1 if failed, return old + // size when success + int32_t grow(int32_t delta) { + Profile.step(StepProfileKind::MEM_GROW); + if (delta <= 0) { + return page_count * pagesize; + } + + if (page_count + delta < allocated_pages) { + page_count += delta; + return page_count * pagesize; + } + + try { + assert(false && "Use pre-allocated memory, should not reach here"); + memory.resize(memory.size() + delta * pagesize); + auto old_page_count = page_count; + page_count += delta; + return memory.size(); + } catch (const std::bad_alloc &e) { + return -1; + } + } + + void reset() { + page_count = init_page_count; + allocated_pages = PRE_ALLOC_PAGES; + for (int i = 0; i < memory.size() && i < page_count * pagesize; ++i) { + memory[i] = 0; + } + } + +private: + std::vector memory; + int init_page_count; + int page_count; + int allocated_pages; +}; + +static Memory_t Memory(4); // 4 page memory + +struct FuncTable_t { + FuncTable_t() : table(20) {} + std::vector table; + + Func_t read(int32_t index) { + if (index < 0 || index >= table.size()) { + throw std::runtime_error("Function table read out of bounds: " + + std::to_string(index)); + } + if (!table[index]) { + assert(false); + throw std::runtime_error("Function table entry at index " + + std::to_string(index) + " is empty or invalid"); + } + return table[index]; + } + + std::monostate set(Num offset, int32_t index, Func_t func) { + if (index < 0 || index >= table.size()) { + throw std::runtime_error("Function table set out of bounds: " + + std::to_string(index)); + } + table[offset.toInt() + index] = func; + return std::monostate{}; + } +}; + +static FuncTable_t FuncTable; + +#endif // WASM_CONCRETE_RT_HPP diff --git a/genwasym_runtime/include/wasm/config.hpp b/genwasym_runtime/include/wasm/config.hpp new file mode 100644 index 000000000..6b64cbafa --- /dev/null +++ b/genwasym_runtime/include/wasm/config.hpp @@ -0,0 +1,82 @@ +#ifndef CONFIG_HPP +#define CONFIG_HPP + +// This file contains configuration settings for the concolic execution + +// If ENABLE_PROFILE_STEP defined, the compiled program will collect and print +// profiling how much steps of each data structure's operations are executed +#ifdef ENABLE_PROFILE_STEP +const bool PROFILE_STEP = true; +#else +const bool PROFILE_STEP = false; +#endif + +// If ENABLE_PROFILE_TIME defined, the compiled program will collect and print +// the profile of time spent in main loop and constraint solving +#ifdef ENABLE_PROFILE_TIME +const bool PROFILE_TIME = true; +#else +const bool PROFILE_TIME = false; +#endif + +#ifdef ENABLE_PROFILE_Z3_API_CALL +const bool PROFILE_Z3_API_CALL = true; +#else +const bool PROFILE_Z3_API_CALL = false; +#endif + +#ifdef ENABLE_PROFILE_CACHE +const bool PROFILE_CACHE = true; +#else +const bool PROFILE_CACHE = false; +#endif + +#ifdef ENABLE_PROFILE_PATH_CONDS +const bool PROFILE_PATH_CONDS = true; +#else +const bool PROFILE_PATH_CONDS = false; +#endif + +// This variable define when concolic execution will stop +enum class ExploreMode { + EarlyExit, // Stop at the first error encountered + + ExitByCoverage // Exit when all syntactic branches are covered +}; + +#ifdef EARLY_EXIT +static const ExploreMode EXPLORE_MODE = ExploreMode::EarlyExit; +#elif defined(BY_COVERAGE) +static const ExploreMode EXPLORE_MODE = ExploreMode::ExitByCoverage; +#else +static const ExploreMode EXPLORE_MODE = ExploreMode::EarlyExit; +#endif + +// This variable decides whether we enable the snapshot reuse optimization +#ifdef NO_REUSE +static const bool REUSE_SNAPSHOT = false; +#else +static const bool REUSE_SNAPSHOT = true; +#endif + +// If we use immutable data structures for symbolic states to reduce the cost of +// copying. +#ifdef USE_IMM +static const bool IMMUTABLE_SYMS = true; +#else +static const bool IMMUTABLE_SYMS = false; +#endif + +#ifdef INTERACTIVE +static const bool INTERACTIVE_MODE = true; +#else +static const bool INTERACTIVE_MODE = false; +#endif + +#ifdef USE_COST_MODEL +static const bool ENABLE_COST_MODEL = true; +#else +static const bool ENABLE_COST_MODEL = false; +#endif + +#endif // CONFIG_HPP \ No newline at end of file diff --git a/genwasym_runtime/include/wasm/controls.hpp b/genwasym_runtime/include/wasm/controls.hpp new file mode 100644 index 000000000..f3bbdf91f --- /dev/null +++ b/genwasym_runtime/include/wasm/controls.hpp @@ -0,0 +1,105 @@ + +#ifndef WASM_CONTROLS_HPP +#define WASM_CONTROLS_HPP + +#include +#include + +#include +#include +#include + +class MContRepr; +struct MCont_t { + std::shared_ptr ptr; + MCont_t() : ptr(nullptr) {} + MCont_t(const MCont_t &p) : ptr(p.ptr) {} + MCont_t(std::shared_ptr p) : ptr(p) {} + MCont_t(std::function haltK) + : ptr(std::make_shared(haltK)) { + assert(haltK); + } + bool is_null() const { return ptr == nullptr; } + + std::monostate enter(); +}; +using Cont_t = std::function; + +static MCont_t CURRENT_MCONT; + +inline std::monostate updateCurrentMCont(MCont_t newMCont) { + CURRENT_MCONT = newMCont; + return std::monostate{}; +} + +class MContRepr { + friend std::monostate enterCC(std::monostate); + +public: + MContRepr(Cont_t cont, MCont_t mcont) : cont(cont), mcont(mcont) {} + + MContRepr(std::function haltK) + : cont(haltK), mcont() {} + + // MContRepr() : cont(nullptr), mcont() {} + + std::monostate enter() { + // std::cout << "Entering MCont\n"; + // std::cout << "Cont cont: " << (cont ? "valid" : "null") << "\n"; + // std::cout << "MCont mcont: " << (mcont ? "valid" : "null") << "\n"; + + // This is necessary, because `this` may be deleted + // after next line. This copy is cheap because we always store a function + // pointer (non captured free variable lambda) in cont. + std::monostate (*func_ptr)(std::monostate) = nullptr; + { + auto cont = this->cont; + func_ptr = *cont.target(); + } + + CURRENT_MCONT = mcont; + + return func_ptr(std::monostate{}); + } + +private: + Cont_t cont; + MCont_t mcont; +}; + +inline MCont_t prependCont(Cont_t k, MCont_t mcont) { + return std::make_shared(k, mcont); +} + +inline std::monostate MCont_t::enter() { return ptr->enter(); } + +// Enter the current global MCont (CURRENT_MCONT) +inline std::monostate enterCC(std::monostate) { + // std::cout << "Entering MCont\n"; + // std::cout << "Cont cont: " << (cont ? "valid" : "null") << "\n"; + // std::cout << "MCont mcont: " << (mcont ? "valid" : "null") << "\n"; + + // This is necessary, because `this` may be deleted + // after next line. This copy is cheap because we always store a function + // pointer (non captured free variable lambda) in cont. + std::monostate (*func_ptr)(std::monostate) = nullptr; + { + auto cont = CURRENT_MCONT.ptr->cont; + func_ptr = *cont.target(); + } + + CURRENT_MCONT = CURRENT_MCONT.ptr->mcont; + + __attribute__((musttail)) return func_ptr(std::monostate{}); +} + +struct Control { + Cont_t cont; + MCont_t mcont; + + Control(Cont_t cont, MCont_t mcont) : cont(cont), mcont(mcont) {} +}; + +using Func_t = std::function; + +#endif // WASM_CONTROLS_HPP \ No newline at end of file diff --git a/genwasym_runtime/include/wasm/heap_mem_bookkeeper.hpp b/genwasym_runtime/include/wasm/heap_mem_bookkeeper.hpp new file mode 100644 index 000000000..c4cc7313f --- /dev/null +++ b/genwasym_runtime/include/wasm/heap_mem_bookkeeper.hpp @@ -0,0 +1,24 @@ +#ifndef HEAP_MEM_BOOKKEEPER_HPP +#define HEAP_MEM_BOOKKEEPER_HPP + +#include +#include + +// Todo: remove this later, this is just a workaround to make sure that the +// SymVals' memory will not be freed during the main execution. +// We can leave the SymVal's memory unmanaged if reference counting is not +// performant +template struct MemBookKeeper { + std::set> allocated; + + template + std::shared_ptr allocate(Args &&...args) { + auto ptr = std::make_shared(std::forward(args)...); + // allocated.insert(ptr); + return ptr; + } + + void clear() { allocated.clear(); } +}; + +#endif // HEAP_MEM_BOOKKEEPER_HPP \ No newline at end of file diff --git a/genwasym_runtime/include/wasm/output_report.hpp b/genwasym_runtime/include/wasm/output_report.hpp new file mode 100644 index 000000000..26bf54107 --- /dev/null +++ b/genwasym_runtime/include/wasm/output_report.hpp @@ -0,0 +1,50 @@ +#ifndef WASM_OUTPUT_REPORT_HPP +#define WASM_OUTPUT_REPORT_HPP + +#include "profile.hpp" +#include "sym_rt.hpp" +#include "config.hpp" +#include + +inline void dump_all_summary_json(const Profile_t &profile, + const OverallResult &overall) { + // use environment variable OUTPUT_FILE to config particular output profiling file + const char *output_file = std::getenv("OUTPUT_FILE"); + if (output_file == nullptr) { + return; + } + + std::filesystem::path report_path(output_file); + + auto parent = report_path.parent_path(); + if (!parent.empty()) { + std::error_code ec; + std::filesystem::create_directories(parent, ec); + if (ec) { + throw std::runtime_error("Failed to create output directory: " + + ec.message()); + } + } + + std::ofstream ofs(report_path); + if (!ofs.is_open()) { + throw std::runtime_error("Failed to open " + report_path.string() + + " for writing"); + } + + // Simple JSON dump (pretty-printed) + ofs << "{\n"; + ofs << " \"unexplored_count\": " << overall.unexplored_count << ",\n"; + ofs << " \"finished_count\": " << overall.finished_count << ",\n"; + ofs << " \"failed_count\": " << overall.failed_count << ",\n"; + ofs << " \"not_to_explore_count\": " << overall.not_to_explore_count + << ",\n"; + ofs << " \"unreachable_count\": " << overall.unreachable_count; + if (PROFILE_STEP || PROFILE_TIME) { + ofs << ",\n"; + profile.write_as_json(ofs); + } + ofs << "}\n"; + ofs.close(); +} +#endif // WASM_OUTPUT_REPORT_HPP \ No newline at end of file diff --git a/genwasym_runtime/include/wasm/profile.hpp b/genwasym_runtime/include/wasm/profile.hpp new file mode 100644 index 000000000..922b1d45f --- /dev/null +++ b/genwasym_runtime/include/wasm/profile.hpp @@ -0,0 +1,416 @@ +#ifndef PROFILE_HPP +#define PROFILE_HPP + +#include "config.hpp" +#include "utils.hpp" +#include "z3++.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum class StepProfileKind { + PUSH, + POP, + PEEK, + SHIFT, + SET, + GET, + BINARY, + TREE_FILL, + CURSOR_MOVE, + MEM_GROW, + SNAPSHOT_CREATE, + SYM_EVAL, + OperationCount // keep this as the last element, this is used to get the + // number of kinds of operations +}; + +enum class ExecutionKind { + RESTART, + FROMSNAPSHOT, + ExecutionKindCount // keep this as the last element, this is used to get the + // number of kinds of operations +}; + +enum class TimeProfileKind { + INSTR, + CALL_Z3_SOLVER, + MAKE_CONJUNCTION, + SOLVER_TOTAL, + RESUME_SNAPSHOT, + COUNT_SYM_SIZE, + SPLIT_CONDITIONS, + COLLECT_PATH_CONDITIONS, + MAIN_LOOP, + TimeOperationCount // keep this as the last element, this is used to get the + // number of kinds of operations +}; + +class Profile_t { +public: + Profile_t() : step_count(0), cache_hit_count(0), cache_miss_count(0) { + // refresh the output profile directory + if (PROFILE_Z3_API_CALL) { + std::filesystem::path out_path(base_profile_output_path); + std::error_code ec; + std::filesystem::remove_all(out_path, ec); + if (ec) { + throw std::runtime_error("Failed to clear output directory: " + + ec.message()); + } + std::filesystem::create_directories(out_path, ec); + if (ec) { + throw std::runtime_error("Failed to create output directory: " + + ec.message()); + } + std::string record_file = + base_profile_output_path + "/z3_solver_time_record.csv"; + std::ofstream ofs(record_file); + ofs << "Expression file,time spent (s),is_sat\n"; + ofs.close(); + std::filesystem::create_directories(z3_expr_output_path, ec); + if (ec) { + throw std::runtime_error("Failed to create z3 expr output directory: " + + ec.message()); + } + } + } + + void cache_hit() { + if (PROFILE_CACHE) + cache_hit_count++; + } + + void cache_miss() { + if (PROFILE_CACHE) + cache_miss_count++; + } + + std::monostate step() { + if (PROFILE_STEP) + step_count++; + return std::monostate(); + } + std::monostate step(StepProfileKind op) { + if (PROFILE_STEP) + op_count[static_cast(op)]++; + return std::monostate(); + } + std::monostate incr_restart_count() { + exec_kind_count[static_cast(ExecutionKind::RESTART)]++; + return std::monostate(); + } + std::monostate incr_fromsnapshot_count() { + exec_kind_count[static_cast(ExecutionKind::FROMSNAPSHOT)]++; + return std::monostate(); + } + std::monostate incr_call_solver_count() { + call_solver_count++; + return std::monostate(); + } + + void print_summary() { + if (PROFILE_STEP) { + std::cout << "Profile Summary:" << std::endl; + std::cout << "Total PUSH operations: " + << op_count[static_cast(StepProfileKind::PUSH)] + << std::endl; + std::cout << "Total POP operations: " + << op_count[static_cast(StepProfileKind::POP)] + << std::endl; + std::cout << "Total PEEK operations: " + << op_count[static_cast(StepProfileKind::PEEK)] + << std::endl; + std::cout << "Total SHIFT operations: " + << op_count[static_cast(StepProfileKind::SHIFT)] + << std::endl; + std::cout << "Total SET operations: " + << op_count[static_cast(StepProfileKind::SET)] + << std::endl; + std::cout << "Total GET operations: " + << op_count[static_cast(StepProfileKind::GET)] + << std::endl; + std::cout << "Total BINARY operations: " + << op_count[static_cast(StepProfileKind::BINARY)] + << std::endl; + std::cout + << "Total TREE_FILL operations: " + << op_count[static_cast(StepProfileKind::TREE_FILL)] + << std::endl; + std::cout + << "Total CURSOR_MOVE operations: " + << op_count[static_cast(StepProfileKind::CURSOR_MOVE)] + << std::endl; + std::cout << "Total other instructions executed: " << step_count + << std::endl; + std::cout << "Total MEM_GROW operations: " + << op_count[static_cast(StepProfileKind::MEM_GROW)] + << std::endl; + std::cout << "Total SNAPSHOT_CREATE operations: " + << op_count[static_cast( + StepProfileKind::SNAPSHOT_CREATE)] + << std::endl; + std::cout << "Total SYM_EVAL operations: " + << op_count[static_cast(StepProfileKind::SYM_EVAL)] + << std::endl; + } + if (PROFILE_TIME) { + std::cout << "Time Profile Summary:" << std::endl; + std::cout << "Total time in instruction execution (s): " + << std::setprecision(15) + << time_count[static_cast(TimeProfileKind::INSTR)] + << std::endl; + std::cout + << "Total time in solver (s): " << std::setprecision(15) + << time_count[static_cast(TimeProfileKind::SOLVER_TOTAL)] + << std::endl; + std::cout << "Total time in z3 api call (s): " << std::setprecision(15) + << time_count[static_cast( + TimeProfileKind::CALL_Z3_SOLVER)] + << std::endl; + std::cout << "Total time in resuming from snapshot (s): " + << std::setprecision(15) + << time_count[static_cast( + TimeProfileKind::RESUME_SNAPSHOT)] + << std::endl; + std::cout << "Total time in counting symbolic size (s): " + << std::setprecision(15) + << time_count[static_cast( + TimeProfileKind::COUNT_SYM_SIZE)] + << std::endl; + std::cout << "Total time in splitting path conditions (s): " + << std::setprecision(15) + << time_count[static_cast( + TimeProfileKind::SPLIT_CONDITIONS)] + << std::endl; + std::cout << "Total time in collecting path conditions (s): " + << std::setprecision(15) + << time_count[static_cast( + TimeProfileKind::COLLECT_PATH_CONDITIONS)] + << std::endl; + std::cout + << "Total time in main loop (s): " << std::setprecision(15) + << time_count[static_cast(TimeProfileKind::MAIN_LOOP)] + << std::endl; + } + if (PROFILE_CACHE) { + std::cout << "Solver Cache Summary:" << std::endl; + std::cout << "Total cache hits: " << cache_hit_count << std::endl; + std::cout << "Total cache misses: " << cache_miss_count << std::endl; + std::cout << "Time of making conjunctions (s): " << std::setprecision(15) + << time_count[static_cast( + TimeProfileKind::MAKE_CONJUNCTION)] + << std::endl; + std::cout << "Cache hit rate: " + << static_cast(cache_hit_count) / + static_cast(cache_hit_count + cache_miss_count) + << std::endl; + } + if (PROFILE_PATH_CONDS) { + std::cout << "Path Conditions Profile Summary:" << std::endl; + std::cout << "Total time in collecting path conditions (s): " + << std::setprecision(15) + << time_count[static_cast( + TimeProfileKind::COLLECT_PATH_CONDITIONS)] + << std::endl; + } + std::cout << "Number of calls to solver: " << call_solver_count + << std::endl; + std::cout << "Execution Kind Summary:" << std::endl; + std::cout + << "Total RESTART executions: " + << exec_kind_count[static_cast(ExecutionKind::RESTART)] + << std::endl; + std::cout << "Total FROMSNAPSHOT executions: " + << exec_kind_count[static_cast( + ExecutionKind::FROMSNAPSHOT)] + << std::endl; + } + + void write_as_json(std::ostream &os) const { + os << " \"profile_summary\": {\n"; + if (PROFILE_STEP) { + os << " \"total_push_operations\": " + << op_count[static_cast(StepProfileKind::PUSH)] << ",\n"; + os << " \"total_pop_operations\": " + << op_count[static_cast(StepProfileKind::POP)] << ",\n"; + os << " \"total_peek_operations\": " + << op_count[static_cast(StepProfileKind::PEEK)] << ",\n"; + os << " \"total_shift_operations\": " + << op_count[static_cast(StepProfileKind::SHIFT)] << ",\n"; + os << " \"total_set_operations\": " + << op_count[static_cast(StepProfileKind::SET)] << ",\n"; + os << " \"total_get_operations\": " + << op_count[static_cast(StepProfileKind::GET)] << ",\n"; + os << " \"total_binary_operations\": " + << op_count[static_cast(StepProfileKind::BINARY)] + << ",\n"; + os << " \"total_tree_fill_operations\": " + << op_count[static_cast(StepProfileKind::TREE_FILL)] + << ",\n"; + os << " \"total_cursor_move_operations\": " + << op_count[static_cast(StepProfileKind::CURSOR_MOVE)] + << ",\n"; + os << " \"total_other_instructions_executed\": " << step_count + << ",\n"; + os << " \"total_mem_grow_operations\": " + << op_count[static_cast(StepProfileKind::MEM_GROW)] + << ",\n"; + os << " \"total_snapshot_create_operations\": " + << op_count[static_cast(StepProfileKind::SNAPSHOT_CREATE)] + << ",\n"; + os << " \"total_sym_eval_operations\": " + << op_count[static_cast(StepProfileKind::SYM_EVAL)] + << "\n"; + } + if (PROFILE_TIME) { + os << " \"total_time_instruction_execution_s\": " + << std::setprecision(15) + << time_count[static_cast(TimeProfileKind::INSTR)] + << ",\n"; + os << " \"total_time_solver_s\": " << std::setprecision(15) + << time_count[static_cast( + TimeProfileKind::CALL_Z3_SOLVER)] + << ",\n"; + os << " \"total_time_resuming_from_snapshot_s\": " + << std::setprecision(15) + << time_count[static_cast( + TimeProfileKind::RESUME_SNAPSHOT)] + << ",\n"; + os << " \"total_time_counting_symbolic_size_s\": " + << std::setprecision(15) + << time_count[static_cast( + TimeProfileKind::COUNT_SYM_SIZE)] + << "\n"; + os << " \"total_time_splitting_path_conditions_s\": " + << std::setprecision(15) + << time_count[static_cast( + TimeProfileKind::SPLIT_CONDITIONS)] + << ",\n"; + } + if (PROFILE_CACHE) { + os << " \"total_cache_hits\": " << cache_hit_count << ",\n"; + os << " \"total_cache_misses\": " << cache_miss_count << ",\n"; + os << " \"cache_hit_rate\": " + << static_cast(cache_hit_count) / + static_cast(cache_hit_count + cache_miss_count) + << "\n"; + } + os << " }\n"; + } + + void record_z3_solver_time(z3::solver expr, double time, bool is_sat) { + // Write z3 expression in a file, and write the time spent in solving it and + // the file path in another file + if (PROFILE_Z3_API_CALL) { + static int count = 0; + std::string expr_file = + z3_expr_output_path + "/z3_expr_" + std::to_string(count) + ".smt2"; + std::error_code ec; + std::ofstream ofs(expr_file); + ofs << expr; + ofs.close(); + std::string record_file = + base_profile_output_path + "/z3_solver_time_record.csv"; + std::ofstream rofs(record_file, std::ios::app); + rofs << expr_file << "," << std::setprecision(15) << time << "," + << (is_sat ? "sat" : "unsat") << "\n"; + rofs.close(); + count++; + } + } + + std::string base_profile_output_path = "genwasym_profile_output"; + std::string z3_expr_output_path = "genwasym_profile_output/z3_expressions"; + + // record the time spent in main instruction execution, in seconds + void add_instruction_time(TimeProfileKind kind, double time) { + time_count[static_cast(kind)] += time; + } + + void remove_instruction_time(TimeProfileKind kind, double time) { + time_count[static_cast(kind)] -= time; + } + + int step_count; + std::array(StepProfileKind::OperationCount)> + op_count; + std::array(TimeProfileKind::TimeOperationCount)> + time_count; + std::array(ExecutionKind::ExecutionKindCount)> + exec_kind_count; + + int cache_hit_count; + int cache_miss_count; + int call_solver_count; +}; + +static Profile_t Profile; + +class ManagedTimer { +public: + ManagedTimer() = delete; + ManagedTimer(TimeProfileKind kind) : kind(kind), time_ref(nullptr) { + start = std::chrono::high_resolution_clock::now(); + } + ManagedTimer(TimeProfileKind kind, double &time_ref) + : kind(kind), time_ref(&time_ref) { + start = std::chrono::high_resolution_clock::now(); + } + ~ManagedTimer() { + auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = end - start; + Profile.add_instruction_time(kind, elapsed.count()); + if (time_ref != nullptr) { + *time_ref += elapsed.count(); + } + } + +private: + TimeProfileKind kind; + std::chrono::high_resolution_clock::time_point start; + double *time_ref; +}; + +using Time = std::chrono::time_point; + +inline Time getCurrentTime() { return std::chrono::steady_clock::now(); } + +inline double duration_time(Time start, Time end) { + std::chrono::duration duration = end - start; + return duration.count(); +} + +struct CostManager_t { + Time start_time; + + CostManager_t() : start_time() {} + + std::monostate reset_timer() { + start_time = getCurrentTime(); + return std::monostate(); + } + + double dump_instr_cost() { + auto current = getCurrentTime(); + double duration = duration_time(start_time, current); + reset_timer(); + return normalize_cost(duration); + } + + double normalize_cost(double cost) { + // Just return duration time as it is + return 1 * cost; + } +}; + +static CostManager_t CostManager; + +#endif // PROFILE_HPP \ No newline at end of file diff --git a/genwasym_runtime/include/wasm/smt_solver.hpp b/genwasym_runtime/include/wasm/smt_solver.hpp new file mode 100644 index 000000000..562eeecbf --- /dev/null +++ b/genwasym_runtime/include/wasm/smt_solver.hpp @@ -0,0 +1,459 @@ +#ifndef SMT_SOLVER_HPP +#define SMT_SOLVER_HPP + +#include "concrete_rt.hpp" +#include "sym_rt.hpp" +#include "union_find.hpp" +#include "utils.hpp" +#include "wasm/profile.hpp" +#include "wasm/symbolic_decl.hpp" +#include "z3++.h" +#include "z3_env.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct QueryResult { + ImmNumMapBox map_box; + z3::model model; +}; + +struct QueryResultWithWitness : public QueryResult { + QueryResultWithWitness(ImmNumMapBox map_box, z3::model model, + NodeBox *witness) + : QueryResult{map_box, model}, witness(witness) {} + NodeBox *witness; +}; + +static QueryResult +compose_query_results(const std::vector &results) { + ManagedTimer timer(TimeProfileKind::SPLIT_CONDITIONS); + NumMap combined_map; + z3::model combined_model(global_z3_ctx()); + for (const auto &res : results) { + auto num_map = res.map_box; + for (const auto &[id, num] : *num_map) { + assert( + combined_map.find(id) == combined_map.end() && + "Conflicting symbolic environment ids when composing query results"); + combined_map[id] = num; + } + const z3::model &model = res.model; + for (unsigned i = 0; i < model.num_consts(); ++i) { + z3::func_decl decl = model.get_const_decl(i); + std::string name = decl.name().str(); + assert((starts_with(name, "s_int") || starts_with(name, "s_f32") || + starts_with(name, "s_f64")) && + "Unexpected declaration in query result model"); + assert(!combined_model.has_interp(decl) && + "Internal Error: Conflicting constant declarations when composing query results"); + z3::expr value = model.get_const_interp(decl); + combined_model.add_const_interp(decl, value); + } + } + ImmNumMapBox combined_map_box(combined_map); + return QueryResult{combined_map_box, combined_model}; +} + +// VectorGroupResult groups a vector. key is the vector index, and value is the +// group id, ungrouped items do not have group id +using VectorGroupMap = std::unordered_map; + +struct GroupResult { + std::unordered_map> conds_in_groups; + std::vector ungrouped_conds; +}; + +static std::optional group_of_symval(const SymVal &sym, UnionFind &uf) { + // TODO: This process is un optimized and slow, just want to see if the idea + // of independent resolving works + if (auto symbol = dynamic_cast(sym.symptr.get())) { + return symbol->get_id(); + } else if (auto concrete = dynamic_cast(sym.symptr.get())) { + return std::nullopt; + } else if (auto binary = dynamic_cast(sym.symptr.get())) { + auto left_group = group_of_symval(binary->lhs, uf); + auto right_group = group_of_symval(binary->rhs, uf); + if (left_group.has_value() && right_group.has_value()) { + uf.unite(*left_group, *right_group); + return uf.find(*left_group); + } else if (left_group.has_value()) { + return uf.find(*left_group); + } else if (right_group.has_value()) { + return uf.find(*right_group); + } else { + return std::nullopt; + } + } else if (auto unary = dynamic_cast(sym.symptr.get())) { + return group_of_symval(unary->value, uf); + } else if (auto extract = dynamic_cast(sym.symptr.get())) { + return group_of_symval(extract->value, uf); + } + return std::nullopt; +} + +static VectorGroupMap build_group_map(const std::vector &conditions) { + // TODO: This is a slow temporary solution which only used for validating the + // idea of independent constraint resolving, the intermediate result of + // independent solving is reusable + ManagedTimer timer(TimeProfileKind::SPLIT_CONDITIONS); + if (conditions.empty()) { + return VectorGroupMap{}; + } + // use union find to group the conditions + UnionFind uf; + for (const auto &cond : conditions) { + group_of_symval(cond, uf); + } + + VectorGroupMap result; + + for (size_t i = 0; i < conditions.size(); ++i) { + auto group = group_of_symval(conditions[i], uf); + if (group.has_value()) { + int group_id = uf.find(*group); + result[i] = group_id; + } + } + return result; +} + +static struct GroupResult +split_conditions(const std::vector &conditions, + const VectorGroupMap &group_map, + const std::unordered_set unused_indexes = {}) { + ManagedTimer timer(TimeProfileKind::SPLIT_CONDITIONS); + std::vector ungrouped_conds; + std::unordered_map> conds_in_groups; + for (size_t i = 0; i < conditions.size(); ++i) { + auto cond = conditions[i]; + if (group_map.find(i) != group_map.end()) { + int group_id = group_map.at(i); + conds_in_groups[group_id].push_back(cond); + } else { + if (unused_indexes.find(i) == unused_indexes.end()) { + ungrouped_conds.push_back(cond); + } + } + } + return GroupResult{conds_in_groups, ungrouped_conds}; +} + +class Solver { +public: + Solver() {} + + // Solve the path conditions. `only_latest_unseen` indicates whether the + // previous conditions and the negation of the latest condition have been + // reached before + std::optional solve_path_conds(std::vector &conditions, + bool only_latest_unseen) { + if (conditions.empty()) { + return QueryResult{ImmNumMapBox(NumMap{}), z3::model(global_z3_ctx())}; + } + + // split the conditions into independent groups + auto group_map = build_group_map(conditions); + + if (only_latest_unseen) { + auto latest_pc_index = 0; + if (group_map.find(latest_pc_index) == group_map.end()) { + // the latest path condition is pure concrete, it must be a + // unsatisfiable concrete condition, because its negation has been + // executed before + return std::nullopt; + } + } + + GroupResult groups = split_conditions(conditions, group_map); + + if (only_latest_unseen) { + // We can safely remove all previously seen pure concrete conditions. + // They are satisfiable and contribute nothing to model. + groups.ungrouped_conds.clear(); + } + + return solve_by_groups(groups, group_map, conditions.size()); + } + + std::optional solve(const std::vector &conditions) { + if (conditions.empty()) { + return QueryResult{ImmNumMapBox(NumMap{}), z3::model(global_z3_ctx())}; + } + + // split the conditions into independent groups + VectorGroupMap group_map = build_group_map(conditions); + GroupResult groups = split_conditions(conditions, group_map); + + return solve_by_groups(groups, group_map, conditions.size()); + } + + std::optional + solve_under_reachable_path(std::vector &&conditions, + SymVal extra_cond) { + conditions.push_back(extra_cond); + VectorGroupMap group_map = build_group_map(conditions); + std::unordered_set unused_indexes; + for (size_t i = 0; i < conditions.size() - 1; ++i) { + if (group_map.find(i) == group_map.end()) { + // this condition is pure concrete, and has been executed before, it + // must be satisfiable and can be ignored + unused_indexes.insert(i); + } + } + GroupResult groups = + split_conditions(conditions, group_map, unused_indexes); + return solve_by_groups(groups, group_map, conditions.size()); + } + + std::optional find_reachable_path_with_witness( + const std::vector> &all_conditions, + const std::vector &candidate_nodes) { + assert(all_conditions.size() == candidate_nodes.size() && + "Conditions size and candidate nodes size must be equal"); + std::vector disjuncts; + auto witness = SymVal::get_witness_symbol(); + SymVal disjunction = SVFactory::FALSE; + { + ManagedTimer timer(TimeProfileKind::COLLECT_PATH_CONDITIONS); + for (size_t i = 0; i < all_conditions.size(); ++i) { + const auto &conds = all_conditions[i]; + auto clause = make_conjunction(conds, true); + clause = clause.land( + witness.eq_bool(SVFactory::make_concrete_bv(Num(i), 32))); + + disjuncts.push_back(clause); + } + disjunction = make_disjunction(disjuncts); + } + + auto result = solve_group({disjunction}, false); + if (!result.has_value()) { + return std::nullopt; + } + z3::model &model = result->model; + // find which clause in disjunct is satisfied + z3::expr witness_expr = model.eval(witness->z3_expr(), true); + int witness_index = witness_expr.get_numeral_int64(); + + return QueryResultWithWitness{ + result->map_box, + result->model, + candidate_nodes[witness_index], + }; + } + +private: + std::optional solve_group(const std::vector &conditions, + bool is_bv) { + + z3::solver z3_solver(global_z3_ctx()); + SymVal conjunction = SVFactory::TRUE; + z3::check_result solver_result; + double z3_solver_time = 0.0; + { + auto timer = + ManagedTimer(TimeProfileKind::CALL_Z3_SOLVER, z3_solver_time); + Profile.incr_call_solver_count(); + // make an conjunction of all conditions + conjunction = make_conjunction(conditions, is_bv); + // call z3 to solve the condition + if (auto it = solver_cache.find(conjunction); it != solver_cache.end()) { + Profile.cache_hit(); + return it->second; + } + Profile.cache_miss(); + SymValSet added_conds; + for (size_t i = 0; i < conditions.size(); ++i) { + SymVal temp = is_bv ? conditions[i].bv2bool() : conditions[i]; + if (added_conds.find(temp) != added_conds.end()) { + continue; + } + z3_solver.add(temp->z3_expr()); + added_conds.insert(temp); + } + GENSYM_INFO("Solving conditions with Z3 solver..."); + solver_result = z3_solver.check(); + } + Profile.record_z3_solver_time(z3_solver, z3_solver_time, + solver_result == z3::sat); + switch (solver_result) { + case z3::unsat: + solver_cache[conjunction] = std::nullopt; + return std::nullopt; // No solution found + case z3::sat: { + z3::model model = z3_solver.get_model(); + NumMap result; + // Reference: + // https://github.com/Z3Prover/z3/blob/master/examples/c%2B%2B/example.cpp#L59 + GENSYM_INFO("Solved Z3 model"); + GENSYM_INFO(model); + for (unsigned i = 0; i < model.size(); ++i) { + z3::func_decl var = model[i]; + z3::expr value = model.get_const_interp(var); + std::string name = var.name().str(); + if (starts_with(name, "s_int")) { + int id = std::stoi(name.substr(std::string("s_int").length())); + z3::expr evaluated = model.eval(value, true); + uint64_t bits = evaluated.get_numeral_uint64(); + int64_t raw = 0; + std::memcpy(&raw, &bits, sizeof(raw)); + result[id] = Num(raw); + } else if (starts_with(name, "s_f32")) { + int id = std::stoi(name.substr(std::string("s_f32").length())); + z3::expr evaluated = model.eval(value.mk_to_ieee_bv(), true); + uint64_t bits = evaluated.get_numeral_uint64(); + result[id] = Num(static_cast(static_cast(bits))); + } else if (starts_with(name, "s_f64")) { + int id = std::stoi(name.substr(std::string("s_f64").length())); + z3::expr evaluated = model.eval(value.mk_to_ieee_bv(), true); + uint64_t bits = evaluated.get_numeral_uint64(); + int64_t raw = 0; + std::memcpy(&raw, &bits, sizeof(raw)); + result[id] = Num(raw); + } else { + GENSYM_INFO("Find a variable that is not created by GenSym: " + name); + } + } + ImmNumMapBox map_box(result); + QueryResult query_result{map_box, model}; + solver_cache[conjunction] = query_result; + return query_result; + } + case z3::unknown: + throw std::runtime_error("Z3 solver returned unknown status"); + } + return std::nullopt; // Should not reach here + } + + std::optional solve_by_groups(const GroupResult &groups, + const VectorGroupMap &group_map, + int condition_size) { + + if (!solve_group(groups.ungrouped_conds, true).has_value()) { + return std::nullopt; + } + + std::vector group_results; + std::unordered_set processed_groups; + for (size_t i = 0; i < condition_size; ++i) { + if (group_map.find(i) == group_map.end()) { + // ungrouped condition, skip it + continue; + } + int group_id = group_map.at(i); + if (processed_groups.find(group_id) != processed_groups.end()) { + // already processed + continue; + } + processed_groups.insert(group_id); + auto &group_conds = groups.conds_in_groups.at(group_id); + auto group_result = solve_group(group_conds, true); + if (!group_result.has_value()) { + // this group is unsatisfiable, so the whole condition is + // unsatisfiable + return std::nullopt; + } + group_results.push_back(group_result.value()); + } + + // combine the results from all groups + return compose_query_results(group_results); + } + + // make a big conjunction from a list of bitvector symbolic values + SymVal make_conjunction(const std::vector &conditions, bool is_bv) { + ManagedTimer timer(TimeProfileKind::MAKE_CONJUNCTION); + SymVal result = SVFactory::make_concrete_bool(true); // true + SymValSet added_conds; + for (size_t i = 0; i < conditions.size(); ++i) { + SymVal temp = is_bv ? conditions[i].bv2bool() : conditions[i]; + if (added_conds.find(temp) != added_conds.end()) { + continue; + } + added_conds.insert(temp); + result = result.land(temp); + } + return result; + } + + // make a big disjunction from a list of bool symbolic values + SymVal make_disjunction(const std::vector &conditions) { + SymVal fls = SVFactory::make_concrete_bool(false); // false + SymVal result = fls; + SymValSet added_conds; + for (size_t i = 0; i < conditions.size(); ++i) { + if (added_conds.find(conditions[i]) != added_conds.end()) { + continue; + } + added_conds.insert(conditions[i]); + result = result.lor(conditions[i]); + } + return result; + } + + z3::expr to_z3_conjunction(std::vector &conditions) { + z3::expr conjunction = global_z3_ctx().bool_val(true); + for (auto &cond : conditions) { + auto z3_cond = cond->z3_expr(); + conjunction = conjunction && z3_cond != global_z3_ctx().bv_val(0, 32); + } +#ifdef DEBUG + // std::cout << "Symbolic conditions size: " << conditions.size() << + // std::endl; std::cout << "Solving conditions: " << conjunction << + // std::endl; +#endif + return conjunction; + } + + SymValMap> solver_cache; +}; + +static Solver solver; + +inline EvalRes eval_sym_expr_by_model(const SymVal &sym, z3::model &model) { + auto expr = sym->z3_expr(); + // let z3 decide the value of symbols that are not in the model + // every value is bitvector + switch (sym->value_kind()) { + case KindBV: { + z3::expr value = model.eval(expr, true); + int width = expr.get_sort().bv_size(); + return EvalRes(Num(value.get_numeral_int64()), width, KindBV); + } + case KindBool: { + assert(false && "unreachable"); + } + case KindFP: { + z3::expr value = model.eval(expr.mk_to_ieee_bv(), true); + int width = get_z3_fp_sort_size(expr.get_sort()); + return EvalRes(Num(value.get_numeral_int64()), width, KindFP); + } + } +} + +inline std::monostate GENSYM_SYM_ASSERT(SymVal &sym_cond) { + ManagedTimer timer(TimeProfileKind::SOLVER_TOTAL); + auto start = std::chrono::steady_clock::now(); + std::vector conds = ExploreTree.collect_current_path_conds(); + auto result = solver.solve_under_reachable_path( + std::move(conds), sym_cond.bv_negate().bool2bv()); + auto end = std::chrono::steady_clock::now(); + auto time_need_to_be_removed = std::chrono::duration(end - start); + Profile.remove_instruction_time(TimeProfileKind::INSTR, + time_need_to_be_removed.count()); + if (result.has_value()) { + std::cout << "Symbolic assertion failed" << std::endl; + throw std::runtime_error("Symbolic assertion failed"); + } + return std::monostate{}; +} + +#endif // SMT_SOLVER_HPP diff --git a/genwasym_runtime/include/wasm/sym_rt.hpp b/genwasym_runtime/include/wasm/sym_rt.hpp new file mode 100644 index 000000000..760112b5a --- /dev/null +++ b/genwasym_runtime/include/wasm/sym_rt.hpp @@ -0,0 +1,1926 @@ +#ifndef WASM_SYMBOLIC_RT_HPP +#define WASM_SYMBOLIC_RT_HPP + +#include "concrete_rt.hpp" +#include "config.hpp" +#include "controls.hpp" +#include "heap_mem_bookkeeper.hpp" +#include "immer/map.hpp" +#include "immer/map_transient.hpp" +#include "immer/vector.hpp" +#include "immer/vector_transient.hpp" +#include "profile.hpp" +#include "symbolic_decl.hpp" +#include "symbolic_impl.hpp" +#include "symval_decl.hpp" +#include "symval_factory.hpp" +#include "symval_impl.hpp" +#include "utils.hpp" +#include "wasm/concrete_num.hpp" +#include "wasm/z3_env.hpp" +#include "z3++.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Snapshot_t; + +class SymStack_t { +public: + void push(SymVal val) { + // Push a symbolic value to the stack + stack.push_back(val); + } + + SymVal pop() { + // Pop a symbolic value from the stack +#ifdef DEBUG + printf("[Debug] poping from stack, size of symbolic stack is: %zu\n", + stack.size()); +#endif +#ifdef USE_IMM + auto ret = *(stack.end() - 1); + stack.take(stack.size() - 1); + return ret; +#else + auto ret = stack.back(); + stack.pop_back(); + return ret; +#endif + } + + SymVal peek() { return *(stack.end() - 1); } + + std::monostate shift(int32_t offset, int32_t size) { + auto n = stack.size(); + for (size_t i = n - size; i < n; ++i) { + assert(i - offset >= 0); +#ifdef USE_IMM + stack.set(i - offset, stack[i]); +#else + stack[i - offset] = stack[i]; +#endif + } +#ifdef USE_IMM + stack.take(n - offset); +#else + stack.erase(stack.begin() + (n - offset), stack.end()); +#endif + return std::monostate(); + } + + void reset() { +// Reset the symbolic stack +#ifdef USE_IMM + stack = immer::vector_transient(); +#else + stack.clear(); +#endif + symbolic_size = 0; + } + + size_t size() const { return stack.size(); } + + SymVal operator[](size_t index) const { return stack[index]; } + + int total_sym_size() const { + ManagedTimer timer(TimeProfileKind::COUNT_SYM_SIZE); + int total_size = 0; + for (const auto &val : stack) { + // std::cout << "Symbolic Expression: " << val->z3_expr() << "\n"; + // std::cout << "Val size: " << val.size() << "\n"; + total_size += val->size(); + } + return total_size; + } + +private: + int symbolic_size = 0; +#ifdef USE_IMM + immer::vector_transient stack; +#else + std::vector stack; +#endif +}; + +static SymStack_t SymStack; + +class SymFrames_t { + +public: + void restore_frame_ptr(Frames_t &frame) const; + + void pushFramePtr() { +#ifdef USE_IMM + frame_ptrs.push_back(stack.size()); +#else + frame_ptrs.push_back(stack.size()); +#endif + } + + void pushFrameSlot(int width) { +#ifdef USE_IMM + stack.push_back(SVFactory::make_concrete_bv(I64V(0), width)); +#else + stack.emplace_back(SVFactory::make_concrete_bv(I64V(0), width)); +#endif + } + + std::monostate popFrameCaller(int size) { + assert(size >= 0); + assert(static_cast(size) <= stack.size()); + assert(!frame_ptrs.empty()); + auto frame_base = current_frame_base(); + assert(frame_base + size == stack.size()); + +#ifdef USE_IMM + stack.take(stack.size() - size); +#else + stack.erase(stack.end() - size, stack.end()); +#endif + +#ifdef USE_IMM + frame_ptrs.take(frame_ptrs.size() - 1); +#else + frame_ptrs.pop_back(); +#endif + + return std::monostate{}; + } + + std::monostate popFrameCallee(int size) { + // Pop the frame of the given size + assert(size >= 0); + assert(static_cast(size) <= stack.size()); + +#ifdef USE_IMM + stack.take(stack.size() - size); +#else + stack.erase(stack.end() - size, stack.end()); +#endif + + return std::monostate{}; + } + + SymVal get(int index) { + // Get the symbolic value at the given frame index + assert(!frame_ptrs.empty()); + auto frame_base = current_frame_base(); + assert(index >= 0 && + static_cast(frame_base + index) < stack.size()); + auto res = stack[frame_base + index]; + return res; + } + + void set(int index, SymVal val) { + // Set the symbolic value at the given index + assert(val.symptr != nullptr); + assert(!frame_ptrs.empty()); + auto frame_base = current_frame_base(); + assert(index >= 0 && + static_cast(frame_base + index) < stack.size()); +#ifdef USE_IMM + stack.set(frame_base + index, val); +#else + stack[frame_base + index] = val; +#endif + } + + void reset() { + // Reset the symbolic frames + +#ifdef USE_IMM + stack = immer::vector_transient(); + frame_ptrs = immer::vector_transient(); +#else + stack.clear(); + frame_ptrs.clear(); +#endif + symbolic_size = 0; + } + + size_t size() const { return stack.size(); } + + SymVal operator[](size_t index) const { return stack[index]; } + + int total_sym_size() const { + ManagedTimer timer(TimeProfileKind::COUNT_SYM_SIZE); + int total_size = 0; + for (const auto &val : stack) { + total_size += val->size(); + } + return total_size; + } + +private: + size_t current_frame_base() const { +#ifdef USE_IMM + return *(frame_ptrs.end() - 1); +#else + return frame_ptrs.back(); +#endif + } + + int symbolic_size = 0; +#ifdef USE_IMM + immer::vector_transient frame_ptrs; + immer::vector_transient stack; +#else + std::vector frame_ptrs; + std::vector stack; +#endif +}; + +struct NodeBox; +struct SymEnv_t; + +class SymMemory_t { +public: +#ifdef USE_IMM + immer::map_transient memory; +#else + std::unordered_map memory; +#endif + int symbolic_size = 0; + + SymVal loadSymByte(int32_t addr) { +// if the address is not in the memory, it must be a zero-initialized memory +#ifdef USE_IMM + auto it = memory.find(addr); + if (it != nullptr) { + return *it; + } else { + auto s = SVFactory::ZeroByte; + return s; + } +#else + auto it = memory.find(addr); + SymVal s = (it != memory.end()) ? it->second : SVFactory::ZeroByte; + return s; +#endif + } + + SymVal loadSym(int32_t base, int32_t offset) { + // calculate the real address + +#ifdef USE_IMM + int32_t addr = base + offset; + auto it = memory.find(addr); + SymVal s0 = it ? *it : SVFactory::ZeroByte; + it = memory.find(addr + 1); + SymVal s1 = it ? *it : SVFactory::ZeroByte; + it = memory.find(addr + 2); + SymVal s2 = it ? *it : SVFactory::ZeroByte; + it = memory.find(addr + 3); + SymVal s3 = it ? *it : SVFactory::ZeroByte; + + return s3.concat(s2).concat(s1).concat(s0); +#else + int32_t addr = base + offset; + auto it = memory.find(addr); + SymVal s0 = (it != memory.end()) ? it->second : SVFactory::ZeroByte; + it = memory.find(addr + 1); + SymVal s1 = (it != memory.end()) ? it->second : SVFactory::ZeroByte; + it = memory.find(addr + 2); + SymVal s2 = (it != memory.end()) ? it->second : SVFactory::ZeroByte; + it = memory.find(addr + 3); + SymVal s3 = (it != memory.end()) ? it->second : SVFactory::ZeroByte; + + return s3.concat(s2).concat(s1).concat(s0); +#endif + } + + SymVal loadSymLong(int32_t base, int32_t offset) { +#ifdef USE_IMM + int32_t addr = base + offset; + auto it = memory.find(addr); + SymVal s0 = it ? *it : SVFactory::ZeroByte; + it = memory.find(addr + 1); + SymVal s1 = it ? *it : SVFactory::ZeroByte; + it = memory.find(addr + 2); + SymVal s2 = it ? *it : SVFactory::ZeroByte; + it = memory.find(addr + 3); + SymVal s3 = it ? *it : SVFactory::ZeroByte; + it = memory.find(addr + 4); + SymVal s4 = it ? *it : SVFactory::ZeroByte; + it = memory.find(addr + 5); + SymVal s5 = it ? *it : SVFactory::ZeroByte; + it = memory.find(addr + 6); + SymVal s6 = it ? *it : SVFactory::ZeroByte; + it = memory.find(addr + 7); + SymVal s7 = it ? *it : SVFactory::ZeroByte; +#else + int32_t addr = base + offset; + auto it = memory.find(addr); + SymVal s0 = (it != memory.end()) ? it->second : SVFactory::ZeroByte; + it = memory.find(addr + 1); + SymVal s1 = (it != memory.end()) ? it->second : SVFactory::ZeroByte; + it = memory.find(addr + 2); + SymVal s2 = (it != memory.end()) ? it->second : SVFactory::ZeroByte; + it = memory.find(addr + 3); + SymVal s3 = (it != memory.end()) ? it->second : SVFactory::ZeroByte; + it = memory.find(addr + 4); + SymVal s4 = (it != memory.end()) ? it->second : SVFactory::ZeroByte; + it = memory.find(addr + 5); + SymVal s5 = (it != memory.end()) ? it->second : SVFactory::ZeroByte; + it = memory.find(addr + 6); + SymVal s6 = (it != memory.end()) ? it->second : SVFactory::ZeroByte; + it = memory.find(addr + 7); + SymVal s7 = (it != memory.end()) ? it->second : SVFactory::ZeroByte; +#endif + + return s7.concat(s6) + .concat(s5) + .concat(s4) + .concat(s3) + .concat(s2) + .concat(s1) + .concat(s0); + } + + SymVal loadSymFloat(int32_t base, int32_t offset) { + // For simplicity, we treat float as concrete value for now + auto symbv = loadSym(base, offset); + assert(symbv.is_concrete() && "Currently only support concrete symbolic " + "value for float-point values"); + if (auto concrete = dynamic_cast(symbv.symptr.get())) { + auto value = concrete->value; + return SVFactory::make_concrete_fp(value, 32); + } else { + assert(false && "unreachable"); + } + } + + SymVal loadSymDouble(int32_t base, int32_t offset) { + // For simplicity, we treat double as concrete value for now + auto symbv = loadSymLong(base, offset); + assert(symbv.is_concrete() && "Currently only support concrete symbolic " + "value for float-point values"); + if (auto concrete = dynamic_cast(symbv.symptr.get())) { + auto value = concrete->value; + return SVFactory::make_concrete_fp(value, 64); + } else { + assert(false && "unreachable"); + } + } + + SymVal loadSymInt8U(int32_t base, int32_t offset) { + return SVFactory::make_smallbv(24, 0).concat(loadSymByte(base + offset)); + } + + SymVal loadSymInt8S(int32_t base, int32_t offset) { + auto value = loadSymInt8U(base, offset); + auto shift = SVFactory::make_concrete_bv(I32V(24), 32); + return value.shl(shift).shr_s(shift); + } + + SymVal loadSymInt16U(int32_t base, int32_t offset) { + auto low = loadSymByte(base + offset); + auto high = loadSymByte(base + offset + 1); + return SVFactory::make_smallbv(16, 0).concat(high).concat(low); + } + + SymVal loadSymInt16S(int32_t base, int32_t offset) { + auto value = loadSymInt16U(base, offset); + auto shift = SVFactory::make_concrete_bv(I32V(16), 32); + return value.shl(shift).shr_s(shift); + } + + SymVal loadSymLong8U(int32_t base, int32_t offset) { + return SVFactory::make_smallbv(56, 0).concat(loadSymByte(base + offset)); + } + + SymVal loadSymLong8S(int32_t base, int32_t offset) { + auto value = loadSymLong8U(base, offset); + auto shift = SVFactory::make_concrete_bv(I64V(56), 64); + return value.shl(shift).shr_s(shift); + } + + SymVal loadSymLong16U(int32_t base, int32_t offset) { + auto low = loadSymByte(base + offset); + auto high = loadSymByte(base + offset + 1); + return SVFactory::make_smallbv(48, 0).concat(high).concat(low); + } + + SymVal loadSymLong16S(int32_t base, int32_t offset) { + auto value = loadSymLong16U(base, offset); + auto shift = SVFactory::make_concrete_bv(I64V(48), 64); + return value.shl(shift).shr_s(shift); + } + + SymVal loadSymLong32U(int32_t base, int32_t offset) { + auto b0 = loadSymByte(base + offset); + auto b1 = loadSymByte(base + offset + 1); + auto b2 = loadSymByte(base + offset + 2); + auto b3 = loadSymByte(base + offset + 3); + return SVFactory::make_smallbv(32, 0).concat(b3).concat(b2).concat(b1).concat(b0); + } + + SymVal loadSymLong32S(int32_t base, int32_t offset) { + auto value = loadSymLong32U(base, offset); + auto shift = SVFactory::make_concrete_bv(I64V(32), 64); + return value.shl(shift).shr_s(shift); + } + + // when loading a symval, we need to concat 4 symbolic values + // This sounds terribly bad for SMT... + // Load a 4-byte symbolic value from memory + // Store a 4-byte symbolic value to memory + std::monostate storeSym(int32_t base, int32_t offset, SymVal value) { + int32_t addr = base + offset; + // Extract 4 bytes from that symbol + SymVal s0 = value.extract(1, 1); + SymVal s1 = value.extract(2, 2); + SymVal s2 = value.extract(3, 3); + SymVal s3 = value.extract(4, 4); + storeSymByte(addr, s0); + storeSymByte(addr + 1, s1); + storeSymByte(addr + 2, s2); + storeSymByte(addr + 3, s3); + return std::monostate{}; + } + + std::monostate storeSymLong(int32_t base, int32_t offset, SymVal value) { + int32_t addr = base + offset; + // TODO: Can we receive a float point symbolic value here? which may produce a bug + SymVal s0 = value.extract(1, 1); + SymVal s1 = value.extract(2, 2); + SymVal s2 = value.extract(3, 3); + SymVal s3 = value.extract(4, 4); + SymVal s4 = value.extract(5, 5); + SymVal s5 = value.extract(6, 6); + SymVal s6 = value.extract(7, 7); + SymVal s7 = value.extract(8, 8); + storeSymByte(addr, s0); + storeSymByte(addr + 1, s1); + storeSymByte(addr + 2, s2); + storeSymByte(addr + 3, s3); + storeSymByte(addr + 4, s4); + storeSymByte(addr + 5, s5); + storeSymByte(addr + 6, s6); + storeSymByte(addr + 7, s7); + return std::monostate{}; + } + + std::monostate storeSymInt8(int32_t base, int32_t offset, SymVal value) { + int32_t addr = base + offset; + storeSymByte(addr, value.extract(1, 1)); + return std::monostate{}; + } + + std::monostate storeSymInt16(int32_t base, int32_t offset, SymVal value) { + int32_t addr = base + offset; + storeSymByte(addr, value.extract(1, 1)); + storeSymByte(addr + 1, value.extract(2, 2)); + return std::monostate{}; + } + + std::monostate storeSymLong8(int32_t base, int32_t offset, SymVal value) { + int32_t addr = base + offset; + storeSymByte(addr, value.extract(1, 1)); + return std::monostate{}; + } + + std::monostate storeSymLong16(int32_t base, int32_t offset, SymVal value) { + int32_t addr = base + offset; + storeSymByte(addr, value.extract(1, 1)); + storeSymByte(addr + 1, value.extract(2, 2)); + return std::monostate{}; + } + + std::monostate storeSymLong32(int32_t base, int32_t offset, SymVal value) { + int32_t addr = base + offset; + storeSymByte(addr, value.extract(1, 1)); + storeSymByte(addr + 1, value.extract(2, 2)); + storeSymByte(addr + 2, value.extract(3, 3)); + storeSymByte(addr + 3, value.extract(4, 4)); + return std::monostate{}; + } + + std::monostate storeSymFloat(int32_t base, int32_t offset, SymVal value) { + assert(value.is_concrete() && "Currently only support concrete symbolic " + "value for float-point values"); + return storeSym(base, offset, value); + } + + std::monostate storeSymDouble(int32_t base, int32_t offset, SymVal value) { + assert(value.is_concrete() && "Currently only support concrete symbolic " + "value for float-point values"); + return storeSymLong(base, offset, value); + } + + std::monostate storeSymByte(int32_t addr, SymVal value) { + // assume the input value is 8-bit symbolic value + bool exists; +#ifdef USE_IMM + auto it = memory.find(addr); + exists = (it != nullptr); +#else + auto it = memory.find(addr); + exists = (it != memory.end()); +#endif + auto old_value = loadSymByte(addr); +#ifdef USE_IMM + memory.set(addr, value); +#else + auto inserted = memory.insert({addr, value}); + if (!inserted.second) { + inserted.first->second = value; + } +#endif + return std::monostate{}; + } + + std::monostate reset() { +#ifdef USE_IMM + memory = immer::map_transient(); +#else + memory.clear(); +#endif + return std::monostate{}; + } + + int total_sym_size() const { + ManagedTimer timer(TimeProfileKind::COUNT_SYM_SIZE); + int total_size = 0; + for (const auto &[_, val] : memory) { + total_size += val->size(); + } + return total_size; + } +}; + +inline void SymFrames_t::restore_frame_ptr(Frames_t &frame) const { + frame.frame_ptrs = frame_ptrs; +} + +static SymMemory_t SymMemory; + +static std::monostate memoryInitialize(int32_t offset, + const std::string &data) { + // initialize concrete memory + for (size_t i = 0; i < data.size(); ++i) { + Memory.storeInt(offset, i, static_cast(data[i])); + } + // initialize symbolic memory + for (size_t i = 0; i < data.size(); ++i) { + SymMemory.storeSymByte( + offset + i, SVFactory::make_smallbv(8, static_cast(data[i]))); + } + return {}; +} + +using NumMap = std::unordered_map; + +// TODO: remove this class later +class ImmNumMapBox { +public: + ImmNumMapBox(const NumMap &sym_env) + : map_ptr(std::make_shared( + sym_env) /* create a immutable copy of SymEnv */ + ) {} + + const NumMap *operator->() const { return map_ptr.get(); } + const NumMap &operator*() const { return *map_ptr; } + +private: + std::shared_ptr map_ptr; +}; + +class SymEnv_t { +public: + SymEnv_t() : map(), imm_map_box(map) {} + + Num read(const Symbol &symbol) const { +#if DEBUG + std::cout << "Read symbol: " << symbol.get_id() + << " from symbolic environment" << std::endl; + std::cout << "Current symbolic environment: " << to_string() << std::endl; +#endif + if (map.find(symbol.get_id()) == map.end()) { + return Num(I32V(0)); + } + return map.at(symbol.get_id()); + } + + Num read(SymVal sym) { + // Read the value of a symbolic value from the environment, it will update + // the environment if the key does not exist. + auto symbol = dynamic_cast(sym.symptr.get()); + assert(symbol); + return read(*symbol); + } + + void update(NumMap new_env) { + map = std::move(new_env); + imm_map_box = ImmNumMapBox(map); + } + + // Absorb another symbolic environment into this one, if some keys not exist + // in another environment and exist in this one, they will be kept unchanged. + void absorb(const NumMap &other) { + for (const auto &[id, num] : other) { + map[id] = num; + } + imm_map_box = ImmNumMapBox(map); + } + + std::string to_string() const { + std::string result; + result += "(\n"; + for (const auto &[id, num] : map) { + result += + " (" + std::to_string(id) + "->" + std::to_string(num.value) + ")\n"; + } + result += ")"; + return result; + } + + size_t size() const { return map.size(); } + + ImmNumMapBox get_num_map() const { return imm_map_box; } + +private: + NumMap map; // The symbolic environment, a vector of Num + ImmNumMapBox imm_map_box; +}; + +static SymEnv_t SymEnv; + +// A snapshot of the symbolic state and execution context (control) +class Snapshot_t { +public: + explicit Snapshot_t(Cont_t cont, MCont_t mcont, SymStack_t stack, + SymFrames_t frames, + SymFrames_t globals, SymMemory_t memory, ImmNumMapBox num_map /* Current num map that corresponds to the symbolic environment */); + + SymStack_t get_stack() const { return stack; } + SymFrames_t get_frames() const { return frames; } + SymFrames_t get_globals() const { return globals; } + SymMemory_t get_memory() const { return memory; } + + std::monostate resume_execution(NodeBox *node) const; + std::monostate resume_execution_by_model(NodeBox *node, + z3::model &model) const; + + double cost_of_snapshot() const; + +private: + SymStack_t stack; + SymFrames_t frames; + SymFrames_t globals; + SymMemory_t memory; + // The continuation at the snapshot point + Cont_t cont; + MCont_t mcont; + ImmNumMapBox num_map; + void restore_states_to_global() const; +}; + +static SymFrames_t SymFrames; +static SymFrames_t SymGlobals; + +static Control makeControl(Cont_t cont, MCont_t mcont) { + return Control(cont, mcont); +} + +static Snapshot_t makeSnapshot(Control control) { + // create a snapshot from the current symbolic states and the control + return Snapshot_t(control.cont, control.mcont, SymStack, SymFrames, + SymGlobals, SymMemory, SymEnv.get_num_map()); +} + +struct Node; + +struct NodeBox { + explicit NodeBox(NodeBox *parent); + std::unique_ptr node; + NodeBox *parent; + double instr_cost() const; + + bool fillIfElseNode(SymVal cond, int id); + bool fillCallIndirectNode(SymVal cond, int id); + std::monostate fillFinishedNode(); + std::monostate fillFailedNode(); + std::monostate fillUnreachableNode(); + std::monostate fillSnapshotNode(Snapshot_t snapshot); + std::monostate fillNotToExploreNode(); + bool isUnexplored() const; + bool isSnapshotNode() const; + std::vector collect_path_conds(); + immer::vector collect_path_conds_imm(); + + void reach_here(std::function); + + Node *operator->() { + assert(node != nullptr && "Accessing an empty NodeBox"); + return node.get(); + } +}; + +struct Node { + friend struct NodeBox; + virtual ~Node(){}; + void set_cost(double c) { instr_cost = c; } + double get_cost() const { return instr_cost; } + virtual std::string to_string() = 0; + void to_graphviz(std::ostream &os) { + os << "digraph G {\n"; + os << " rankdir=TB;\n"; + os << " node [shape=box, style=filled, fillcolor=lightblue];\n"; + current_id = 0; + generate_dot(os, -1, ""); + + os << "}\n"; + } + virtual void generate_dot(std::ostream &os, int parent_dot_id, + const std::string &edge_label) = 0; + +protected: + // Counter for unique node IDs across the entire graph, only for generating + // graphviz purpose + static int current_id; + void graphviz_node(std::ostream &os, const int node_id, + const std::string &label, const std::string &shape, + const std::string &fillcolor) { + os << " node" << node_id << " [label=\"" << label << "\", shape=" << shape + << ", style=filled, fillcolor=" << fillcolor << "];\n"; + } + + void graphviz_edge(std::ostream &os, int from_id, int target_id, + const std::string &edge_label) { + os << " node" << from_id << " -> node" << target_id; + if (!edge_label.empty()) { + os << " [label=\"" << edge_label << "\"]"; + } + os << ";\n"; + } + +private: + double instr_cost = 0.0; + std::optional> path_conds_cache; +}; + +inline double NodeBox::instr_cost() const { + if (node) { + return node->get_cost(); + } else { + return 0.0; + } +} + +// TODO: use this header file in multiple compilation units will cause problems +// during linking +int Node::current_id = 0; + +struct IfElseNode : Node { + SymVal cond; + std::unique_ptr true_branch; + std::unique_ptr false_branch; + int id; + + IfElseNode(SymVal cond, NodeBox *parent, int id) + : cond(cond), true_branch(std::make_unique(parent)), + false_branch(std::make_unique(parent)), id(id) {} + + std::string to_string() override { + std::string result = "IfElseNode {\n"; + result += " true_branch: "; + if (true_branch) { + result += true_branch->node->to_string(); + } else { + result += "nullptr"; + } + result += "\n"; + + result += " false_branch: "; + if (false_branch) { + result += false_branch->node->to_string(); + } else { + result += "nullptr"; + } + result += "\n"; + result += "}"; + return result; + } + + void generate_dot(std::ostream &os, int parent_dot_id, + const std::string &edge_label) override { + int current_node_dot_id = current_id; + current_id += 1; + + graphviz_node(os, current_node_dot_id, "If", "diamond", "lightyellow"); + + // Draw edge from parent if this is not the root node + if (parent_dot_id != -1) { + graphviz_edge(os, parent_dot_id, current_node_dot_id, edge_label); + } + assert(true_branch != nullptr); + assert(true_branch->node != nullptr); + true_branch->node->generate_dot(os, current_node_dot_id, "true"); + assert(false_branch != nullptr); + assert(false_branch->node != nullptr); + false_branch->node->generate_dot(os, current_node_dot_id, "false"); + } +}; + +struct CallIndirectNode : Node { + SymVal cond; + std::unordered_map> branches; + std::unique_ptr otherwise_branch; + int id; + CallIndirectNode(SymVal cond, NodeBox *parent, int id) + : cond(cond), id(id), + otherwise_branch(std::make_unique(parent)) {} + std::string to_string() override { + std::string result = "CallIndirectNode {\n"; + for (const auto &pair : branches) { + result += " branch " + std::to_string(pair.first) + ": "; + if (pair.second && pair.second->node) { + result += pair.second->node->to_string(); + } else { + result += "nullptr"; + } + result += "\n"; + } + result += "}"; + return result; + } + + void generate_dot(std::ostream &os, int parent_dot_id, + const std::string &edge_label) override { + int current_node_dot_id = current_id; + current_id += 1; + + graphviz_node(os, current_node_dot_id, "Branch", "diamond", "lightyellow"); + + // Draw edge from parent if this is not the root node + if (parent_dot_id != -1) { + graphviz_edge(os, parent_dot_id, current_node_dot_id, edge_label); + } + for (const auto &pair : branches) { + assert(pair.second != nullptr); + assert(pair.second->node != nullptr); + pair.second->node->generate_dot(os, current_node_dot_id, + "branch " + std::to_string(pair.first)); + } + } +}; + +struct UnExploredNode : Node { + UnExploredNode() {} + std::string to_string() override { return "UnexploredNode"; } + +protected: + void generate_dot(std::ostream &os, int parent_dot_id, + const std::string &edge_label) override { + int current_node_dot_id = current_id++; + graphviz_node(os, current_node_dot_id, "Unexplored", "octagon", + "lightgrey"); + + if (parent_dot_id != -1) { + graphviz_edge(os, parent_dot_id, current_node_dot_id, edge_label); + } + } +}; + +struct NotToExploreNode : Node { + NotToExploreNode() {} + std::string to_string() override { return "NotToExploreNode"; } + +protected: + void generate_dot(std::ostream &os, int parent_dot_id, + const std::string &edge_label) override { + int current_node_dot_id = current_id++; + graphviz_node(os, current_node_dot_id, "NotToExplore", "box", "grey"); + + if (parent_dot_id != -1) { + graphviz_edge(os, parent_dot_id, current_node_dot_id, edge_label); + } + } +}; + +struct SnapshotNode : Node { + SnapshotNode(Snapshot_t snapshot) : snapshot(snapshot) {} + std::string to_string() override { return "SnapshotNode"; } + const Snapshot_t &get_snapshot() const { return snapshot; } + Snapshot_t move_out_snapshot() { return std::move(snapshot); } + + bool worth_to_reuse() const { + if (!ENABLE_COST_MODEL) { + // If we are not using cost model, always create snapshot + return REUSE_SNAPSHOT; + } + // find out the best way to reach the current position via our cost model + auto snapshot_cost = snapshot.cost_of_snapshot(); + double re_execution_cost = get_cost(); + // std::cout << "Snapshot cost: " << snapshot_cost + // << ", re-execution cost: " << re_execution_cost << std::endl; + if (snapshot_cost <= re_execution_cost) { + GENSYM_INFO("Snapshot is worth to create"); + } else { + GENSYM_INFO("Snapshot is NOT worth to create"); + } + return snapshot_cost <= re_execution_cost; + } + +protected: + void generate_dot(std::ostream &os, int parent_dot_id, + const std::string &edge_label) override { + int current_node_dot_id = current_id++; + graphviz_node(os, current_node_dot_id, "Snapshot", "box", "lightblue"); + + if (parent_dot_id != -1) { + graphviz_edge(os, parent_dot_id, current_node_dot_id, edge_label); + } + } + +private: + Snapshot_t snapshot; +}; + +struct Finished : Node { + Finished() {} + std::string to_string() override { return "FinishedNode"; } + +protected: + void generate_dot(std::ostream &os, int parent_dot_id, + const std::string &edge_label) override { + int current_node_dot_id = current_id++; + graphviz_node(os, current_node_dot_id, "Finished", "box", "lightgreen"); + + if (parent_dot_id != -1) { + graphviz_edge(os, parent_dot_id, current_node_dot_id, edge_label); + } + } +}; + +struct Failed : Node { + Failed() {} + std::string to_string() override { return "FailedNode"; } + +protected: + void generate_dot(std::ostream &os, int parent_dot_id, + const std::string &edge_label) override { + int current_node_dot_id = current_id++; + graphviz_node(os, current_node_dot_id, "Failed", "box", "red"); + + if (parent_dot_id != -1) { + graphviz_edge(os, parent_dot_id, current_node_dot_id, edge_label); + } + } +}; + +struct Unreachable : Node { + Unreachable() {} + std::string to_string() override { return "UnreachableNode"; } + +protected: + void generate_dot(std::ostream &os, int parent_dot_id, + const std::string &edge_label) override { + int current_node_dot_id = current_id++; + graphviz_node(os, current_node_dot_id, "Unreachable", "box", "orange"); + + if (parent_dot_id != -1) { + graphviz_edge(os, parent_dot_id, current_node_dot_id, edge_label); + } + } +}; + +inline NodeBox::NodeBox(NodeBox *parent) + : node(std::make_unique()), + /* TODO: avoid allocation of unexplored node */ + parent(parent) {} + +inline bool NodeBox::fillIfElseNode(SymVal cond, int id) { + // fill the current NodeBox with an ifelse branch node when it's unexplored + double cost_from_parent = CostManager.dump_instr_cost(); + double cost_from_root = + cost_from_parent + (this->parent ? this->parent->instr_cost() : 0); + // std::cout << "Cost from parent: " << cost_from_parent + // << ", cost from root: " << cost_from_root << std::endl; + + if (auto ptr = dynamic_cast(node.get())) { + node = std::make_unique(cond, this, id); + node->set_cost(cost_from_root); + return true; + } else if (dynamic_cast(node.get())) { + node = std::make_unique(cond, this, id); + node->set_cost(cost_from_root); + return true; + } else if (dynamic_cast(node.get()) != nullptr) { + assert(false && + "Unexpected traversal: arrived at a node marked 'NotToExplore'."); + return false; + } + + node->set_cost(cost_from_root); + assert( + dynamic_cast(node.get()) != nullptr && + "Current node is not an Unexplored nor an IfElseNode, cannot fill it!"); + return false; +} + +inline bool NodeBox::fillCallIndirectNode(SymVal cond, int id) { + // fill the current NodeBox with a call_indirect branch node when it's + // unexplored + if (auto ptr = dynamic_cast(node.get())) { + node = std::make_unique(cond, this, id); + return true; + } else if (dynamic_cast(node.get())) { + node = std::make_unique(cond, this, id); + return true; + } else if (dynamic_cast(node.get()) != nullptr) { + assert(false && + "Unexpected traversal: arrived at a node marked 'NotToExplore'."); + return false; + } + + assert( + dynamic_cast(node.get()) != nullptr && + "Current node is not an Unexplored nor a CallIndirectNode, cannot fill " + "it!"); + return false; +} + +inline std::monostate NodeBox::fillSnapshotNode(Snapshot_t snapshot) { + if (this->isUnexplored()) { + node = std::make_unique(snapshot); + } + node->set_cost(parent->instr_cost()); + return std::monostate(); +} + +inline std::monostate NodeBox::fillNotToExploreNode() { + if (this->isUnexplored()) { + node = std::make_unique(); + } else { + assert(dynamic_cast(node.get()) != nullptr); + } + return std::monostate(); +} + +inline std::monostate NodeBox::fillFinishedNode() { + if (this->isUnexplored()) { + node = std::make_unique(); + } else { + assert(dynamic_cast(node.get()) != nullptr); + } + return std::monostate(); +} + +inline std::monostate NodeBox::fillFailedNode() { + if (this->isUnexplored()) { + node = std::make_unique(); + } else { + assert(dynamic_cast(node.get()) != nullptr); + } + return std::monostate(); +} + +inline std::monostate NodeBox::fillUnreachableNode() { + if (this->isUnexplored()) { + node = std::make_unique(); + } else { + assert(dynamic_cast(node.get()) != nullptr); + } + return std::monostate(); +} + +inline bool NodeBox::isSnapshotNode() const { + assert(node != nullptr); + return dynamic_cast(node.get()) != nullptr; +} + +inline bool NodeBox::isUnexplored() const { + assert(node != nullptr); + if (dynamic_cast(node.get()) != nullptr) { + return true; + } + if (this->isSnapshotNode()) { + return true; + } + return false; +} + +inline std::vector NodeBox::collect_path_conds() { + ManagedTimer timer(TimeProfileKind::COLLECT_PATH_CONDITIONS); + auto box = this; + auto result = std::vector(); + while (box->parent) { + auto parent = box->parent; + if (auto if_else_node = dynamic_cast(parent->node.get())) { + if (if_else_node->true_branch.get() == box) { + // If the current box is the true branch, add the condition + result.push_back(if_else_node->cond); + } else if (if_else_node->false_branch.get() == box) { + // If the current box is the false branch, add the negated condition + result.push_back(if_else_node->cond.bv_negate().bool2bv()); + } else { + throw std::runtime_error("Unexpected node structure in explore tree"); + } + } else if (auto call_indirect_node = + dynamic_cast(parent->node.get())) { + // Find which branch we are in + bool found = false; + for (const auto &pair : call_indirect_node->branches) { + if (pair.second.get() == box) { + // We are in this branch + // Add the condition that leads to this branch + result.push_back( + call_indirect_node->cond.eq(Concrete(I32V(pair.first), 32))); + found = true; + break; + } + } + if (!found) { + // We must be in the otherwise branch + if (call_indirect_node->otherwise_branch.get() != box) { + throw std::runtime_error("Unexpected node structure in explore tree"); + } + // Add the negated conditions for all other branches + SymVal negated_conditions = Concrete(I32V(1), 32); // true + for (const auto &pair : call_indirect_node->branches) { + negated_conditions = negated_conditions.bitwise_and( + call_indirect_node->cond.neq(Concrete(I32V(pair.first), 32))); + } + result.push_back(negated_conditions); + } + } else { + // should never reach here + } + // Move to parent + box = box->parent; + } + return result; +} + +// same as collect_path_conds but return immer::vector, and cache the result +inline immer::vector NodeBox::collect_path_conds_imm() { + ManagedTimer timer(TimeProfileKind::COLLECT_PATH_CONDITIONS); + + auto box = this; + if (box->node->path_conds_cache.has_value()) { + return box->node->path_conds_cache.value(); + } + + if (!box->parent) { + // root node, and no path conditions + immer::vector empty; + box->node->path_conds_cache = empty; + return empty; + } + + auto parent_conds = box->parent->collect_path_conds_imm(); + immer::vector result = parent_conds; + if (auto if_else_node = dynamic_cast(box->parent->node.get())) { + if (if_else_node->true_branch.get() == box) { + // If the current box is the true branch, add the condition + result = result.push_back(if_else_node->cond); + } else if (if_else_node->false_branch.get() == box) { + // If the current box is the false branch, add the negated condition + result = result.push_back(if_else_node->cond.bv_negate().bool2bv()); + } else { + throw std::runtime_error("Unexpected node structure in explore tree"); + } + } else if (auto call_indirect_node = + dynamic_cast(box->parent->node.get())) { + // Find which branch we are in + bool found = false; + for (const auto &pair : call_indirect_node->branches) { + if (pair.second.get() == box) { + // We are in this branch + // Add the condition that leads to this branch + result = result.push_back( + call_indirect_node->cond.eq(Concrete(I32V(pair.first), 32))); + found = true; + break; + } + } + if (!found) { + // We must be in the otherwise branch + if (call_indirect_node->otherwise_branch.get() != box) { + throw std::runtime_error("Unexpected node structure in explore tree"); + } + // Add the negated conditions for all other branches + SymVal negated_conditions = Concrete(I32V(1), 32); // true + for (const auto &pair : call_indirect_node->branches) { + negated_conditions = negated_conditions.bitwise_and( + call_indirect_node->cond.neq(Concrete(I32V(pair.first), 32))); + } + result = result.push_back(negated_conditions); + } + } else { + // should never reach here + } + box->node->path_conds_cache = result; + return result; +} + +inline Snapshot_t::Snapshot_t(Cont_t cont, MCont_t mcont, SymStack_t stack, + SymFrames_t frames, SymFrames_t globals, + SymMemory_t memory, ImmNumMapBox num_map) + : stack(std::move(stack)), frames(std::move(frames)), + globals(std::move(globals)), memory(std::move(memory)), cont(cont), + mcont(mcont), num_map(num_map) { + Profile.step(StepProfileKind::SNAPSHOT_CREATE); +#ifdef DEBUG + std::cout << "Creating snapshot of size " << stack.size() << std::endl; +#endif +} + +const double INSTR_COST_SCALING_FACTOR = 1E-03; + +inline double Snapshot_t::cost_of_snapshot() const { + auto stack_sym_size = stack.total_sym_size(); + assert(stack_sym_size >= 0); + auto frame_sym_size = frames.total_sym_size(); + assert(frame_sym_size >= 0); + auto memory_sym_size = memory.total_sym_size(); + assert(memory_sym_size >= 0); + auto global_sym_size = globals.total_sym_size(); + assert(global_sym_size >= 0); + // The speed ratio between symbolic expression instantiation and WebAssembly + // instruction execution, given by benchmark results + auto total_size = + stack_sym_size + frame_sym_size + memory_sym_size + global_sym_size; + return INSTR_COST_SCALING_FACTOR * total_size; +} + +struct OverallResult { + int unexplored_count = 0; + int finished_count = 0; + int failed_count = 0; + int not_to_explore_count = 0; + int unreachable_count = 0; + + void print() { + std::cout << "Explore Tree Overall Result:" << std::endl; + std::cout << " Unexplored paths: " << unexplored_count << std::endl; + std::cout << " Finished paths: " << finished_count << std::endl; + std::cout << " Failed paths: " << failed_count << std::endl; + std::cout << " Unreachable paths: " << unreachable_count << std::endl; + std::cout << " NotToExplore paths: " << not_to_explore_count << std::endl; + } +}; + +class ExploreTree_t { +public: + explicit ExploreTree_t() + : root(std::make_unique(nullptr)), cursor(root.get()) {} + + void reset_cursor() { + GENSYM_INFO("Resetting cursor to root"); + // Reset the cursor to the root of the tree + cursor = root.get(); + } + + void clear() { + GENSYM_INFO("Clearing the explore tree"); + root = std::make_unique(nullptr); + cursor = root.get(); + true_branch_cov_map.clear(); + false_branch_cov_map.clear(); + } + + void set_cursor(NodeBox *new_cursor) { + GENSYM_INFO("Setting cursor to a new node"); + cursor = new_cursor; + assert(dynamic_cast(cursor->node.get()) != nullptr); + } + + std::monostate fillFinishedNode() { return cursor->fillFinishedNode(); } + + std::monostate fillFailedNode() { return cursor->fillFailedNode(); } + + std::monostate fillIfElseNode(SymVal cond, int id) { + if (cursor->fillIfElseNode(cond, id)) { + auto if_else_node = dynamic_cast(cursor->node.get()); + register_new_node(if_else_node->true_branch.get()); + register_new_node(if_else_node->false_branch.get()); + } + return std::monostate(); + } + + std::monostate fillCallIndirectNode(SymVal cond, int id) { + if (cursor->fillCallIndirectNode(cond, id)) { + auto indirect_node = dynamic_cast(cursor->node.get()); + register_new_node(indirect_node->otherwise_branch.get()); + } + return std::monostate(); + } + + std::monostate fillNotToExploredNode() { + return cursor->fillNotToExploreNode(); + } + + std::vector collect_current_path_conds() { + return cursor->collect_path_conds(); + } + + std::monostate moveCursor(bool branch, Control control) { + Profile.step(StepProfileKind::CURSOR_MOVE); + assert(cursor != nullptr); + auto if_else_node = dynamic_cast(cursor->node.get()); + assert( + if_else_node != nullptr && + "Can't move cursor when the branch node is not initialized correctly!"); + + if (branch) { + true_branch_cov_map[if_else_node->id] = true; + if (if_else_node->cond.is_concrete()) { + if_else_node->false_branch->fillUnreachableNode(); + } else { + if (REUSE_SNAPSHOT && !if_else_node->false_branch->isSnapshotNode()) { + auto snapshot = makeSnapshot(control); + if_else_node->false_branch->fillSnapshotNode(snapshot); + } else { + // Do nothing, the initial value of the branch is an unexplored node + } + } + cursor = if_else_node->true_branch.get(); + } else { + false_branch_cov_map[if_else_node->id] = true; + if (if_else_node->cond.is_concrete()) { + if_else_node->true_branch->fillUnreachableNode(); + } else { + if (REUSE_SNAPSHOT && !if_else_node->true_branch->isSnapshotNode()) { + auto snapshot = makeSnapshot(control); + if_else_node->true_branch->fillSnapshotNode(snapshot); + } else { + // Do nothing, the initial value of the branch is an unexplored node + } + } + cursor = if_else_node->false_branch.get(); + } + CostManager.reset_timer(); + return std::monostate(); + } + + std::monostate moveCursorNoControl(bool branch) { + Profile.step(StepProfileKind::CURSOR_MOVE); + assert(cursor != nullptr); + auto if_else_node = dynamic_cast(cursor->node.get()); + assert( + if_else_node != nullptr && + "Can't move cursor when the branch node is not initialized correctly!"); + if (branch) { + true_branch_cov_map[if_else_node->id] = true; + if_else_node->false_branch->fillNotToExploreNode(); + cursor = if_else_node->true_branch.get(); + } else { + assert(false && + "moveCursorNoControl should not be used for false branch"); + } + CostManager.reset_timer(); + return std::monostate(); + } + + std::monostate moveCursorIndirect(int branch_index) { + // Dont use snapshot reuse for untaken branches of indirect call + Profile.step(StepProfileKind::CURSOR_MOVE); + assert(cursor != nullptr); + auto branch_node = dynamic_cast(cursor->node.get()); + assert(branch_node != nullptr && + "Can't move cursor when the branch node is not initialized "); + if (branch_node->branches.find(branch_index) == + branch_node->branches.end()) { + // Create a new branch + branch_node->branches[branch_index] = std::make_unique(cursor); + register_new_node(branch_node->branches[branch_index].get()); + } + cursor = branch_node->branches[branch_index].get(); + + return std::monostate(); + } + + std::monostate print() { + std::cout << root->node->to_string() << std::endl; + return std::monostate(); + } + + std::monostate to_graphviz(std::ostream &os) { + root->node->to_graphviz(os); + return std::monostate(); + } + + std::monostate dump_graphviz(std::string filepath) { + std::filesystem::path out_path(filepath); + auto parent = out_path.parent_path(); + if (!parent.empty()) { + std::error_code ec; + std::filesystem::create_directories(parent, ec); + if (ec) { + throw std::runtime_error("Failed to create output directory: " + + ec.message()); + } + } + std::ofstream ofs(filepath); + if (!ofs.is_open()) { + throw std::runtime_error("Failed to open " + filepath + " for writing"); + } + to_graphviz(ofs); + return std::monostate(); + } + + OverallResult read_current_overall_result() { + OverallResult result; + std::vector stack; + stack.push_back(root.get()); + + while (!stack.empty()) { + NodeBox *node = stack.back(); + stack.pop_back(); + + if (auto if_else_node = dynamic_cast(node->node.get())) { + stack.push_back(if_else_node->true_branch.get()); + stack.push_back(if_else_node->false_branch.get()); + } else if (dynamic_cast(node->node.get())) { + result.unexplored_count += 1; + } else if (dynamic_cast(node->node.get())) { + result.finished_count += 1; + } else if (dynamic_cast(node->node.get())) { + result.failed_count += 1; + } else if (dynamic_cast(node->node.get())) { + result.unreachable_count += 1; + } else if (dynamic_cast(node->node.get())) { + // Snapshot node is considered unexplored + result.unexplored_count += 1; + } else if (dynamic_cast(node->node.get())) { + result.not_to_explore_count += 1; + } else if (auto call_indirect_node = + dynamic_cast(node->node.get())) { + for (const auto &pair : call_indirect_node->branches) { + stack.push_back(pair.second.get()); + } + stack.push_back(call_indirect_node->otherwise_branch.get()); + } else { + throw std::runtime_error("Unknown node type in explore tree"); + } + } + return result; + } + + std::monostate print_overall_result() {} + + NodeBox *pick_unexplored() { + // Pick an unexplored node from the tree + // For now, we just iterate through the tree and return the first unexplored + return pick_unexplored_of(root.get()); + } + std::vector true_branch_cov_map; + std::vector false_branch_cov_map; + bool all_branch_covered() const { + for (bool covered : true_branch_cov_map) { + if (!covered) + return false; + } + for (bool covered : false_branch_cov_map) { + if (!covered) + return false; + } + return true; + } + + NodeBox *get_root() const { return root.get(); } + + void register_new_node_collector(std::function func) { + new_node_collectors.push_back(func); + } + +private: + NodeBox *pick_unexplored_of(NodeBox *node) { + if (node->isUnexplored()) { + return node; + } + auto if_else_node = dynamic_cast(node->node.get()); + if (if_else_node) { + NodeBox *result = pick_unexplored_of(if_else_node->true_branch.get()); + if (result) { + return result; + } + return pick_unexplored_of(if_else_node->false_branch.get()); + } + return nullptr; // No unexplored node found + } + void register_new_node(NodeBox *node) { + for (auto &func : new_node_collectors) { + func(node); + } + } + std::unique_ptr root; + NodeBox *cursor; + std::vector> new_node_collectors; +}; + +static ExploreTree_t ExploreTree; + +static std::monostate reset_stacks() { + Stack.reset(); + SymStack.reset(); + Frames.reset(); + SymFrames.reset(); + Memory.reset(); + SymMemory.reset(); + initRand(); + return std::monostate{}; +} + +[[deprecated]] inline void +NodeBox::reach_here(std::function entrypoint) { + // reach the node of exploration tree with given input (symbolic environment) + if (auto snapshot = dynamic_cast(node.get())) { + assert(REUSE_SNAPSHOT); + auto snap = snapshot->get_snapshot(); + snap.resume_execution(this); + return; + } else if (parent == nullptr) { + // if it's the root node, the only way to reach here is to reset everything + // and start a new execution + assert(this == ExploreTree.get_root() && + "Only the root node can have no parent"); + auto timer = ManagedTimer(TimeProfileKind::INSTR); + ExploreTree.reset_cursor(); + reset_stacks(); + entrypoint(); + return; + } + // Reach the parent node, then from the parent node, we can reach here + // TODO: short circuit the lookup + parent->reach_here(entrypoint); + return; +} + +struct EvalRes { + Num value; + ValueKind kind; + int width; // in bits + EvalRes(Num value, int width, ValueKind kind) + : value(value), width(width), kind(kind) {} +}; + +static EvalRes eval_binary_op(EvalRes lhs_res, EvalRes rhs_res, + BinOperation operation) { + auto lhs = lhs_res.value; + auto rhs = rhs_res.value; + auto lhs_width = lhs_res.width; + auto rhs_width = rhs_res.width; + switch (operation) { + case ADD: + if (lhs_width == 32 && rhs_width == 32) { + return EvalRes(lhs.i32_add(rhs), 32, KindBV); + } else if (lhs_width == 64 && rhs_width == 64) { + return EvalRes(lhs.i64_add(rhs), 64, KindBV); + } else { + assert(false && "TODO"); + } + case SUB: + if (lhs_width == 32 && rhs_width == 32) { + return EvalRes(lhs.i32_sub(rhs), 32, KindBV); + } else if (lhs_width == 64 && rhs_width == 64) { + return EvalRes(lhs.i64_sub(rhs), 64, KindBV); + } else { + assert(false && "TODO"); + } + case MUL: + if (lhs_width == 32 && rhs_width == 32) { + return EvalRes(lhs.i32_mul(rhs), 32, KindBV); + } else if (lhs_width == 64 && rhs_width == 64) { + return EvalRes(lhs.i64_mul(rhs), 64, KindBV); + } else { + assert(false && "TODO"); + } + case DIV: + if (lhs_width == 32 && rhs_width == 32) { + return EvalRes(lhs.i32_div_s(rhs), 32, KindBV); + } else if (lhs_width == 64 && rhs_width == 64) { + return EvalRes(lhs.i64_div_s(rhs), 64, KindBV); + } else { + assert(false && "TODO"); + } + case LT_BOOL: + if (lhs_width == 32 && rhs_width == 32) { + return EvalRes(lhs.i32_lt_s(rhs), 32, KindBool); + } else if (lhs_width == 64 && rhs_width == 64) { + return EvalRes(lhs.i64_lt_s(rhs), 32, KindBool); + } else { + assert(false && "TODO"); + } + case LEQ_BOOL: + if (lhs_width == 32 && rhs_width == 32) { + return EvalRes(lhs.i32_le_s(rhs), 32, KindBool); + } else if (lhs_width == 64 && rhs_width == 64) { + return EvalRes(lhs.i64_le_s(rhs), 32, KindBool); + } else { + assert(false && "TODO"); + } + case GT_BOOL: + if (lhs_width == 32 && rhs_width == 32) { + return EvalRes(lhs.i32_gt_s(rhs), 32, KindBool); + } else if (lhs_width == 64 && rhs_width == 64) { + return EvalRes(lhs.i64_gt_s(rhs), 32, KindBool); + } else { + assert(false && "TODO"); + } + case GEQ_BOOL: + if (lhs_width == 32 && rhs_width == 32) { + return EvalRes(lhs.i32_ge_s(rhs), 32, KindBool); + } else if (lhs_width == 64 && rhs_width == 64) { + return EvalRes(lhs.i64_ge_s(rhs), 32, KindBool); + } else { + assert(false && "TODO"); + } + case NEQ_BOOL: + if (lhs_width == 32 && rhs_width == 32) { + return EvalRes(lhs.i32_ne(rhs), 32, KindBool); + } else if (lhs_width == 64 && rhs_width == 64) { + return EvalRes(lhs.i64_ne(rhs), 32, KindBool); + } else { + assert(false && "TODO"); + } + case EQ_BOOL: + if (lhs_width == 32 && rhs_width == 32) { + return EvalRes(lhs.i32_eq(rhs), 32, KindBool); + } else if (lhs_width == 64 && rhs_width == 64) { + return EvalRes(lhs.i64_eq(rhs), 32, KindBool); + } else { + assert(false && "TODO"); + } + case B_AND: + if (lhs_width == 32 && rhs_width == 32) { + return EvalRes(lhs.i32_and(rhs), 32, KindBV); + } else if (lhs_width == 64 && rhs_width == 64) { + return EvalRes(lhs.i64_and(rhs), 64, KindBV); + } else { + assert(false && "TODO"); + } + case CONCAT: { + auto conc_value = (lhs.value << rhs_width) | (rhs.value); + auto new_width = lhs_width + rhs_width; + return EvalRes(Num(I64V(conc_value)), new_width, KindBV); + } + case B_XOR: + if (lhs_width == 32 && rhs_width == 32) { + return EvalRes(lhs.i32_xor(rhs), 32, KindBV); + } else if (lhs_width == 64 && rhs_width == 64) { + return EvalRes(lhs.i64_xor(rhs), 64, KindBV); + } else { + assert(false && "TODO"); + } + case B_OR: + if (lhs_width == 32 && rhs_width == 32) { + return EvalRes(lhs.i32_or(rhs), 32, KindBV); + } else if (lhs_width == 64 && rhs_width == 64) { + return EvalRes(lhs.i64_or(rhs), 64, KindBV); + } else { + assert(false && "TODO"); + } + case SHR_U: + if (lhs_width == 32 && rhs_width == 32) { + return EvalRes(lhs.i32_shr_u(rhs), 32, KindBV); + } else if (lhs_width == 64 && rhs_width == 64) { + return EvalRes(lhs.i64_shr_u(rhs), 64, KindBV); + } else { + assert(false && "TODO"); + } + case SHR_S: + if (lhs_width == 32 && rhs_width == 32) { + return EvalRes(lhs.i32_shr_s(rhs), 32, KindBV); + } else if (lhs_width == 64 && rhs_width == 64) { + return EvalRes(lhs.i64_shr_s(rhs), 64, KindBV); + } else { + assert(false && "TODO"); + } + case LTU_BOOL: + if (lhs_width == 32 && rhs_width == 32) { + return EvalRes(lhs.i32_lt_u(rhs), 32, KindBool); + } else if (lhs_width == 64 && rhs_width == 64) { + return EvalRes(lhs.i64_lt_u(rhs), 32, KindBool); + } else { + assert(false && "TODO"); + } + case GTU_BOOL: + if (lhs_width == 32 && rhs_width == 32) { + return EvalRes(lhs.i32_gt_u(rhs), 32, KindBool); + } else if (lhs_width == 64 && rhs_width == 64) { + return EvalRes(lhs.i64_gt_u(rhs), 32, KindBool); + } else { + assert(false && "TODO"); + } + case GEU_BOOL: + if (lhs_width == 32 && rhs_width == 32) { + return EvalRes(lhs.i32_ge_u(rhs), 32, KindBool); + } else if (lhs_width == 64 && rhs_width == 64) { + return EvalRes(lhs.i64_ge_u(rhs), 32, KindBool); + } else { + assert(false && "TODO"); + } + case AND: + return EvalRes(lhs.logical_and(rhs), 32, KindBool); + case OR: + return EvalRes(lhs.logical_or(rhs), 32, KindBool); + default: + assert(false && "Operation not supported in evaluation"); + } +} + +// TODO: reduce the re-computation of the same symbolic expression, it's better +// if it can be done by the smt solver +static EvalRes eval_sym_expr(const SymVal &sym, const SymEnv_t &sym_env) { + Profile.step(StepProfileKind::SYM_EVAL); + assert(sym.symptr != nullptr && "Symbolic expression is null"); + if (auto concrete = dynamic_cast(sym.symptr.get())) { + return EvalRes(concrete->value, concrete->width(), concrete->kind); + } else if (auto extract = dynamic_cast(sym.symptr.get())) { + auto res = eval_sym_expr(extract->value, sym_env); + int high = extract->high; + int low = extract->low; + assert(high >= low && "Invalid extract range"); + int size = high - low + 1; // size in bytes + int64_t mask = (1LL << (size * 8)) - 1; + int64_t extracted_value = (res.value.toInt() >> ((low - 1) * 8)) & mask; + return EvalRes(Num(I64V(extracted_value)), size * 8, KindBV); + } else if (auto operation = dynamic_cast(sym.symptr.get())) { + // If it's a operation, we need to evaluate it + auto lhs_res = eval_sym_expr(operation->lhs, sym_env); + auto rhs_res = eval_sym_expr(operation->rhs, sym_env); + auto lhs = lhs_res.value; + auto rhs = rhs_res.value; + auto lhs_width = lhs_res.width; + auto rhs_width = rhs_res.width; + return eval_binary_op(lhs_res, rhs_res, operation->op); + } else if (auto symbol = dynamic_cast(sym.symptr.get())) { + auto sym_id = symbol->get_id(); + GENSYM_INFO("Reading symbol: " + std::to_string(sym_id)); + return EvalRes(sym_env.read(*symbol), 32, KindBV); + } + throw std::runtime_error("Not supported symbolic expression"); +} + +inline EvalRes eval_sym_expr_by_model(const SymVal &sym, z3::model &model); + +static void resume_conc_stack(const SymStack_t &sym_stack, Stack_t &stack, + SymEnv_t &sym_env) { + stack.resize(sym_stack.size()); + for (size_t i = 0; i < sym_stack.size(); ++i) { + auto sym = sym_stack[i]; + auto res = eval_sym_expr(sym, sym_env); + auto conc = res.value; + stack.set_from_front(i, conc); + } +} + +static void resume_conc_stack_by_model(const SymStack_t &sym_stack, + Stack_t &stack, z3::model &model) { + GENSYM_INFO("Restoring concrete stack from symbolic stack"); + stack.resize(sym_stack.size()); + for (size_t i = 0; i < sym_stack.size(); ++i) { + auto sym = sym_stack[i]; + auto res = eval_sym_expr_by_model(sym, model); + auto conc = res.value; + stack.set_from_front(i, conc); + } +} + +static void resume_conc_frames(const SymFrames_t &sym_frame, Frames_t &frames, + SymEnv_t &sym_env) { + GENSYM_INFO("Restoring concrete frames from symbolic frames"); + frames.resize(sym_frame.size()); + for (size_t i = 0; i < sym_frame.size(); ++i) { + auto sym = sym_frame[i]; + assert(sym.symptr != nullptr); + auto res = eval_sym_expr(sym, sym_env); + auto conc = res.value; + frames.set_from_front(i, conc); + } + sym_frame.restore_frame_ptr(frames); +} + +static void resume_conc_frames_by_model(const SymFrames_t &sym_frame, + Frames_t &frames, z3::model &model) { + GENSYM_INFO("Restoring concrete frames from symbolic frames"); + frames.resize(sym_frame.size()); + for (size_t i = 0; i < sym_frame.size(); ++i) { + auto sym = sym_frame[i]; + assert(sym.symptr != nullptr); + auto res = eval_sym_expr_by_model(sym, model); + auto conc = res.value; + frames.set_from_front(i, conc); + } + sym_frame.restore_frame_ptr(frames); +} + +static void resume_conc_memory(const SymMemory_t &sym_memory, Memory_t &memory, + const SymEnv_t &sym_env) { + GENSYM_INFO("Restoring concrete memory from symbolic memory"); + memory.reset(); + for (const auto &pair : sym_memory.memory) { + int32_t addr = pair.first; + SymVal sym = pair.second; + assert(sym.symptr != nullptr); + auto res = eval_sym_expr(sym, sym_env); + auto conc = res.value; + assert(res.width == 8 && "Memory should only store bytes"); + memory.store_byte(addr, conc.value & 0xFF); + } +} + +static void resume_conc_memory_by_model(const SymMemory_t &sym_memory, + Memory_t &memory, z3::model &model) { + GENSYM_INFO("Restoring concrete memory from symbolic memory"); + memory.reset(); + for (const auto &pair : sym_memory.memory) { + int32_t addr = pair.first; + SymVal sym = pair.second; + assert(sym.symptr != nullptr); + auto res = eval_sym_expr_by_model(sym, model); + auto conc = res.value; + assert(res.width == 8 && "Memory should only store bytes"); + memory.store_byte(addr, conc.value & 0xFF); + } +} + +static void resume_conc_states(const SymStack_t &sym_stack, + const SymFrames_t &sym_frame, + const SymFrames_t &sym_globals, + const SymMemory_t &sym_memory, Stack_t &stack, + Frames_t &frames, Frames_t &globals, + Memory_t &memory, SymEnv_t &sym_env) { + resume_conc_stack(sym_stack, stack, sym_env); + resume_conc_frames(sym_frame, frames, sym_env); + resume_conc_frames(sym_globals, globals, sym_env); + resume_conc_memory(sym_memory, memory, sym_env); +} + +static void resume_conc_states_by_model(const SymStack_t &sym_stack, + const SymFrames_t &sym_frame, + const SymFrames_t &sym_globals, + const SymMemory_t &sym_memory, + Stack_t &stack, Frames_t &frames, + Frames_t &globals, Memory_t &memory, + z3::model &model) { + resume_conc_stack_by_model(sym_stack, stack, model); + resume_conc_frames_by_model(sym_frame, frames, model); + resume_conc_frames_by_model(sym_globals, globals, model); + resume_conc_memory_by_model(sym_memory, memory, model); +} + +inline void Snapshot_t::restore_states_to_global() const { + // Restore the symbolic state from the snapshot + GENSYM_INFO("Reusing symbolic state from snapshot"); + SymStack = stack; + SymFrames = frames; + SymMemory = memory; + SymGlobals = globals; +} + +inline std::monostate +Snapshot_t::resume_execution_by_model(NodeBox *node, z3::model &model) const { + // Reset explore tree's cursor and restore symbolic states + ExploreTree.set_cursor(node); + restore_states_to_global(); + + { + auto timer = ManagedTimer(TimeProfileKind::RESUME_SNAPSHOT); + // Restore the concrete states from the symbolic states + resume_conc_states_by_model(stack, frames, globals, memory, Stack, Frames, + Globals, Memory, model); + } + // Resume execution from the continuation + auto timer = ManagedTimer(TimeProfileKind::INSTR); + CostManager.reset_timer(); + CURRENT_MCONT = mcont; + return cont(std::monostate{}); +} + +[[deprecated]] inline std::monostate +Snapshot_t::resume_execution(NodeBox *node) const { + // Reset explore tree's cursor and restore symbolic states + ExploreTree.set_cursor(node); + restore_states_to_global(); + { + auto timer = ManagedTimer(TimeProfileKind::RESUME_SNAPSHOT); + // Restore the concrete states from the symbolic states + resume_conc_states(stack, frames, globals, memory, Stack, Frames, Globals, + Memory, SymEnv); + } + + // Resume execution from the continuation + auto timer = ManagedTimer(TimeProfileKind::INSTR); + CURRENT_MCONT = mcont; + return cont(std::monostate{}); +} + +#endif // WASM_SYMBOLIC_RT_HPP diff --git a/genwasym_runtime/include/wasm/symbolic_decl.hpp b/genwasym_runtime/include/wasm/symbolic_decl.hpp new file mode 100644 index 000000000..0e2b5bd25 --- /dev/null +++ b/genwasym_runtime/include/wasm/symbolic_decl.hpp @@ -0,0 +1,327 @@ +#ifndef WASM_SYMVAL_REPR_HPP +#define WASM_SYMVAL_REPR_HPP + +#include "symval_decl.hpp" +#include +#include + +enum BinOperation { + ADD, // Addition + SUB, // Subtraction + MUL, // Multiplication + DIV, // Division + DIV_U, // Unsigned division + AND, // Logical AND + OR, // Logical OR + EQ_BOOL, // Equal (return a boolean) TODO: remove bv version of comparison ops + NEQ_BOOL, // Not equal (return a boolean) + LT_BOOL, // Less than (return a boolean) + LTU_BOOL, // Unsigned less than (return a boolean) + LEQ_BOOL, // Less than or equal (return a boolean) + LEU_BOOL, // Unsigned less than or equal (return a boolean) + GT_BOOL, // Greater than (return a boolean) + GTU_BOOL, // Unsigned greater than (return a boolean) + GEQ_BOOL, // Greater than or equal (return a boolean) + GEU_BOOL, // Unsigned greater than or equal (return a boolean) + SHL, // Shift left + SHR_U, // Shift right unsigned + SHR_S, // Shift right signed + REM_U, // Unsigned remainder + B_AND, // Bitwise AND + B_XOR, // Bitwise XOR + B_OR, // Bitwise OR + CONCAT, // Byte-level concatenation +}; + +enum UnaryOperation { + NOT, // bool not + BOOL2BV, // bool to bitvector, + EXTEND, // bitvector extension, extend i32 to i64 +}; + +enum ValueKind { KindBV, KindBool, KindFP }; + +class Symbolic { +public: + Symbolic() {} + virtual ~Symbolic() = default; // Make Symbolic polymorphic + virtual int size() = 0; + virtual ValueKind value_kind() = 0; + virtual int width() = 0; + virtual z3::expr z3_expr(); + +private: + z3::expr build_z3_expr_aux(); + std::optional _z3_expr; +}; + +class Symbol : public Symbolic { +public: + // TODO: add type information to determine the size of bitvector + // for now we just assume that only i32 will be used + Symbol(int id, int width, ValueKind kind) + : id(id), _width(width), _kind(kind) {} + int get_id() const { return id; } + + int size() override { return 1; } + + ValueKind value_kind() override { return _kind; } + int width() override { return _width; } + +private: + int id; + int _width; + ValueKind _kind; +}; + +class Witness : public Symbolic { +public: + int size() override { return 1; } + + ValueKind value_kind() override { return KindBV; } + + int width() override { return 32; } +}; + +class SymConcrete : public Symbolic { +public: + Num value; + ValueKind kind; + SymConcrete(Num num, ValueKind kind, int width) + : value(num), kind(kind), _width(width) {} + + int size() override { return 1; } + + ValueKind value_kind() override { return kind; } + int width() override { return _width; } + +private: + int _width; +}; + +inline int count_dag_size(Symbolic &val); + +// Extract is different from other operations, it only has one symbolic operand, +// the other two operands are constants +// Extract from value, both high and low are inclusive byte indexes +struct SymExtract : public Symbolic { + SymVal value; + int high; + int low; + + SymExtract(SymVal value, int high, int low) + : value(value), high(high), low(low) {} + + int size() override { + if (_cached_dag_size.has_value()) { + return _cached_dag_size.value(); + } + _cached_dag_size = 1 + value->size(); + return _cached_dag_size.value(); + } + + ValueKind value_kind() override { return KindBV; } + + int width() override { return (high - low + 1) * 8; } + +private: + friend std::tuple + count_dag_size_aux(Symbolic &val, std::set &visited); + + std::optional _cached_dag_size; +}; + +struct SymBinary : public Symbolic { + BinOperation op; + SymVal lhs; + SymVal rhs; + + SymBinary(BinOperation op, SymVal lhs, SymVal rhs) + : op(op), lhs(lhs), rhs(rhs) { + auto lhs_kind = lhs->value_kind(); + auto rhs_kind = rhs->value_kind(); + auto lhs_width = lhs->width(); + auto rhs_width = rhs->width(); + + switch (op) { + case ADD: + case SUB: + case MUL: + case DIV: + case DIV_U: + case SHL: + case SHR_U: + case SHR_S: + case REM_U: + case B_AND: + case B_XOR: + case B_OR: + assert(lhs_kind == KindBV && rhs_kind == KindBV); + assert(lhs_width == rhs_width); + _kind = KindBV; + _width = lhs_width; + break; + case CONCAT: + assert(lhs_kind == KindBV && rhs_kind == KindBV); + _kind = KindBV; + _width = lhs_width + rhs_width; + break; + case EQ_BOOL: + case NEQ_BOOL: + case LT_BOOL: + case LTU_BOOL: + case LEQ_BOOL: + case LEU_BOOL: + case GT_BOOL: + case GTU_BOOL: + case GEQ_BOOL: + case GEU_BOOL: + assert(lhs_kind == rhs_kind); + if (lhs_kind == KindBV) { + assert(lhs_width == rhs_width); + } + _kind = KindBool; + _width = 1; + break; + case AND: + case OR: + assert(lhs_kind == KindBool && rhs_kind == KindBool); + assert(lhs_width == 1 && rhs_width == 1); + _kind = KindBool; + _width = 1; + break; + default: + assert(false && "Unhandled binary operation"); + } + } + + int size() override { + if (_cached_dag_size.has_value()) { + return _cached_dag_size.value(); + } + + auto size = count_dag_size(*this); + _cached_dag_size = size; + return size; + } + + int width() override { return _width; } + + ValueKind value_kind() override { return _kind; } + +private: + friend std::tuple + count_dag_size_aux(Symbolic &val, std::set &visited); + std::optional _cached_dag_size; + ValueKind _kind; + int _width; +}; + +struct SymUnary : public Symbolic { + UnaryOperation op; + SymVal value; + + SymUnary(UnaryOperation op, SymVal value) : op(op), value(value) { + switch (op) { + case BOOL2BV: + assert(value->value_kind() == KindBool); + _width = 32; // Only 32 bit bit vector can be converted to boolean and + // vice versa. + break; + case NOT: + _width = 1; + break; + default: + assert(false && "Unknown unary operation"); + } + } + + int width() override { return _width; } + + int size() override { + if (_cached_dag_size.has_value()) { + return _cached_dag_size.value(); + } + _cached_dag_size = 1 + value->size(); + return _cached_dag_size.value(); + } + + ValueKind value_kind() override { + switch (op) { + case NOT: { + return ValueKind::KindBool; + } + case BOOL2BV: { + return ValueKind::KindBV; + } + default: { + assert(false && "Unknown unary operation"); + } + } + } + +private: + friend std::tuple + count_dag_size_aux(Symbolic &val, std::set &visited); + + int _width; + std::optional _cached_dag_size; +}; + +inline std::tuple count_dag_size_aux(Symbolic &val, + std::set &visited) { + if (visited.find(&val) != visited.end()) { + return {0, true}; + } + visited.insert(&val); + + if (auto binary = dynamic_cast(&val)) { + int size = 1; + auto [lhs_size, lhs_sharing] = + count_dag_size_aux(*binary->lhs.symptr, visited); + auto [rhs_size, rhs_sharing] = + count_dag_size_aux(*binary->rhs.symptr, visited); + size += lhs_size + rhs_size; + if (!lhs_sharing && !rhs_sharing) { + // if there is no sharing in two operands, this temporary size is valid + // and reusable + binary->_cached_dag_size = size; + } + return {size, lhs_sharing || rhs_sharing}; + } else if (auto unary = dynamic_cast(&val)) { + int size = 1; + auto [value_size, value_sharing] = + count_dag_size_aux(*unary->value.symptr, visited); + size += value_size; + if (!value_sharing) { + unary->_cached_dag_size = size; + } + return {size, value_sharing}; + + } else if (auto extract = dynamic_cast(&val)) { + int size = 1; + auto [value_size, value_sharing] = + count_dag_size_aux(*extract->value.symptr, visited); + size += value_size; + if (!value_sharing) { + extract->_cached_dag_size = size; + } + return {size, value_sharing}; + } else if (auto symbol = dynamic_cast(&val)) { + return {1, false}; + } else if (auto concrete = dynamic_cast(&val)) { + return {1, false}; + } else if (auto witness = dynamic_cast(&val)) { + assert(false && "Witness should not appear during instruction execution"); + } else { + assert(false && "Unknown symbolic type in dag size counting"); + } +} + +inline int count_dag_size(Symbolic &val) { + std::set visited; + auto [size, _] = count_dag_size_aux(val, visited); + return size; +} + +#endif // WASM_SYMVAL_REPR_HPP diff --git a/genwasym_runtime/include/wasm/symbolic_impl.hpp b/genwasym_runtime/include/wasm/symbolic_impl.hpp new file mode 100644 index 000000000..8c6fb067f --- /dev/null +++ b/genwasym_runtime/include/wasm/symbolic_impl.hpp @@ -0,0 +1,182 @@ +#ifndef WASM_SYMBOLIC_IMPL_HPP +#define WASM_SYMBOLIC_IMPL_HPP + +#include "symbolic_decl.hpp" +#include "wasm/symval_decl.hpp" +#include "wasm/z3_env.hpp" + +inline z3::expr Symbolic::build_z3_expr_aux() { + if (auto sym = dynamic_cast(this)) { + switch (sym->value_kind()) { + + case KindBV: { + return global_z3_ctx().bv_const( + ("s_int" + std::to_string(sym->get_id())).c_str(), width()); + } + case KindBool: { + assert(false && "Symbolic boolean variables are not supported yet"); + } + case KindFP: + if (sym->width() == 32) { + return global_z3_ctx().fpa_const<32>( + ("s_f32" + std::to_string(sym->get_id())).c_str()); + } else if (sym->width() == 64) { + return global_z3_ctx().fpa_const<64>( + ("s_f64" + std::to_string(sym->get_id())).c_str()); + } else { + throw std::runtime_error("Unsupported floating-point width: " + + std::to_string(sym->width())); + } + } + } else if (auto witness = dynamic_cast(this)) { + return global_z3_ctx().bv_const("witness", 32); + } else if (auto concrete = dynamic_cast(this)) { + switch (concrete->kind) { + case KindBool: { + return global_z3_ctx().bool_val(concrete->value.toInt() != 0); + } + case KindBV: { + return global_z3_ctx().bv_val(concrete->value.value, width()); + } + case KindFP: { + if (width() == 32) { + return global_z3_ctx().fpa_val(concrete->value.toF32()); + } else if (width() == 64) { + return global_z3_ctx().fpa_val(concrete->value.toF64()); + } else { + throw std::runtime_error("Unsupported floating-point width: " + + std::to_string(width())); + } + } + } + } else if (auto binary = dynamic_cast(this)) { + auto bit_width = width(); + + z3::expr left = binary->lhs->z3_expr(); + z3::expr right = binary->rhs->z3_expr(); + switch (binary->op) { + case EQ_BOOL: { + return left == right; + } + case NEQ_BOOL: { + return left != right; + } + case AND: { + return left && right; + } + case OR: { + return left || right; + } + case LT_BOOL: { + return left < right; + } + case LTU_BOOL: { + return z3::ult(left, right); + } + case LEQ_BOOL: { + return left <= right; + } + case LEU_BOOL: { + return z3::ule(left, right); + } + case GT_BOOL: { + return left > right; + } + case GTU_BOOL: { + return z3::ugt(left, right); + } + case GEU_BOOL: { + return z3::uge(left, right); + } + case SHL: { + if (bit_width == 32) { + z3::expr shift_mask = global_z3_ctx().bv_val(0x1F, bit_width); + return z3::shl(left, right & shift_mask); + } else if (bit_width == 64) { + z3::expr shift_mask = global_z3_ctx().bv_val(0x3F, bit_width); + return z3::shl(left, right & shift_mask); + } else { + throw std::runtime_error("Unsupported bit width for SHL: " + + std::to_string(bit_width)); + } + } + case SHR_U: { + return z3::lshr(left, right); + } + case SHR_S: { + return z3::ashr(left, right); + } + case REM_U: { + return z3::urem(left, right); + } + case GEQ_BOOL: { + return left >= right; + } + case ADD: { + return left + right; + } + case SUB: { + return left - right; + } + case MUL: { + return left * right; + } + case DIV: { + return left / right; + } + case DIV_U: { + return z3::udiv(left, right); + } + case B_AND: { + return left & right; + } + case B_XOR: { + return left ^ right; + } + case B_OR: { + return left | right; + } + case CONCAT: { + return z3::concat(left, right); + } + default: + throw std::runtime_error("Operation not supported: " + + std::to_string(binary->op)); + } + } else if (auto unary = dynamic_cast(this)) { + auto bit_width = 32; + z3::expr zero_bv = global_z3_ctx().bv_val(0, bit_width); + z3::expr one_bv = global_z3_ctx().bv_val(1, bit_width); + switch (unary->op) { + case NOT: { + return !unary->value->z3_expr(); + } + case BOOL2BV: { + z3::expr bool_expr = unary->value->z3_expr(); + return z3::ite(bool_expr, one_bv, zero_bv); + } + default: + throw std::runtime_error("Unary operation not supported: " + + std::to_string(unary->op)); + } + } else if (auto extract = dynamic_cast(this)) { + assert(extract); + int high = extract->high * 8 - 1; + int low = extract->low * 8 - 8; + auto s = extract->value->z3_expr(); + auto res = s.extract(high, low); + return res; + } + throw std::runtime_error("Unsupported symbolic value type"); +} + +inline z3::expr Symbolic::z3_expr() { + if (_z3_expr.has_value()) { + return *_z3_expr; + } + auto e = build_z3_expr_aux(); + _z3_expr = e; + return e; +} + +#endif // WASM_SYMBOLIC_IMPL_HPP diff --git a/genwasym_runtime/include/wasm/symval_decl.hpp b/genwasym_runtime/include/wasm/symval_decl.hpp new file mode 100644 index 000000000..8f9ac1a30 --- /dev/null +++ b/genwasym_runtime/include/wasm/symval_decl.hpp @@ -0,0 +1,95 @@ +#ifndef WASM_SYMVAL_HPP +#define WASM_SYMVAL_HPP +#include "concrete_num.hpp" +#include +#include +#include + +class Symbolic; + +struct SymVal { + std::shared_ptr symptr; + + SymVal() = delete; + SymVal(std::shared_ptr symptr) : symptr(symptr) {} + + // Create a new i32 symbol value + SymVal makeI32Symbol() const; + // Create a new i64 symbol value + SymVal makeI64Symbol() const; + // Create a new f32 symbol value + SymVal makeF32Symbol() const; + // Create a new f64 symbol value + SymVal makeF64Symbol() const; + + // bitvector arithmetic operations + SymVal is_zero() const; + SymVal add(const SymVal &other) const; + SymVal minus(const SymVal &other) const; + SymVal mul(const SymVal &other) const; + SymVal div(const SymVal &other) const; + SymVal div_u(const SymVal &other) const; + SymVal eq_bool(const SymVal &other) const; + SymVal neq_bool(const SymVal &other) const; + SymVal land(const SymVal &other) const; + SymVal lor(const SymVal &other) const; + SymVal eq(const SymVal &other) const; + SymVal neq(const SymVal &other) const; + SymVal lt(const SymVal &other) const; + SymVal ltu(const SymVal &other) const; + SymVal le(const SymVal &other) const; + SymVal leu(const SymVal &other) const; + SymVal gt(const SymVal &other) const; + SymVal gtu(const SymVal &other) const; + SymVal ge(const SymVal &other) const; + SymVal geu(const SymVal &other) const; + SymVal shl(const SymVal &other) const; + SymVal shr_u(const SymVal &other) const; + SymVal shr_s(const SymVal &other) const; + SymVal bv_negate() const; + SymVal bool_not() const; + SymVal bitwise_and(const SymVal &other) const; + SymVal bitwise_xor(const SymVal &other) const; + SymVal bitwise_or(const SymVal &other) const; + SymVal concat(const SymVal &other) const; + SymVal extract(int high, int low) const; + SymVal bv2bool() const; + SymVal bool2bv() const; + SymVal rem_u(const SymVal &other) const; + SymVal extend_to_i64() const; // only for i32 symbolic values, extend to i64 by sign extension + // TODO: add bitwise operations, and use the underlying bitvector theory + + bool is_concrete() const; + + static SymVal get_witness_symbol(); + + Symbolic *operator->() const { return symptr.get(); } + bool operator==(const SymVal &other) const { return symptr == other.symptr; } +}; + +struct SymValHash { + size_t operator()(const SymVal &key) const { + return std::hash{}(key.symptr.get()); + } +}; + +using SymValSet = std::unordered_set; + +template +using SymValMap = std::unordered_map; + +template inline bool allConcrete(const Args &...args) { + static_assert((std::is_same_v && ...), + "all_concrete only accepts SymVal arguments"); + return (... && args.is_concrete()); +} + +inline SymVal Concrete(Num num, int width); + +[[noreturn]] inline SymVal debug_unreachable(const char* msg) { + std::cerr << "unreachable: " << msg << '\n'; + assert(false && "unreachable reached"); + std::abort(); +} + +#endif // WASM_SYMVAL_HPP diff --git a/genwasym_runtime/include/wasm/symval_factory.hpp b/genwasym_runtime/include/wasm/symval_factory.hpp new file mode 100644 index 000000000..14875764c --- /dev/null +++ b/genwasym_runtime/include/wasm/symval_factory.hpp @@ -0,0 +1,654 @@ +#ifndef WASM_SYMVAL_FACTORY_HPP +#define WASM_SYMVAL_FACTORY_HPP + +#include "heap_mem_bookkeeper.hpp" +#include "symbolic_decl.hpp" +#include "symval_decl.hpp" + +namespace SVFactory { + +SymVal make_concrete_bv(Num num, int width); +SymVal make_concrete_bool(bool b); +SymVal make_int_symbolic(int index, int width); +SymVal make_smallbv(int width, int64_t value); +SymVal make_binary(BinOperation op, const SymVal &lhs, const SymVal &rhs); +SymVal make_unary(UnaryOperation op, const SymVal &value); +SymVal make_extract(const SymVal &value, int high, int low); + +// Core allocator and common constants. +static MemBookKeeper SymBookKeeper; + +static SymVal I32ZERO = + SymVal(SymBookKeeper.allocate(I32V(0), KindBV, 32)); + +static SymVal I64ZERO = + SymVal(SymBookKeeper.allocate(I64V(0), KindBV, 64)); + +static SymVal TRUE = + SymVal(SymBookKeeper.allocate(I32V(1), KindBool, 32)); + +static SymVal FALSE = + SymVal(SymBookKeeper.allocate(I32V(0), KindBool, 32)); + +static SymVal ZeroByte = + SymVal(SymBookKeeper.allocate(I64V(0), KindBV, 8)); + +// Key and hash types. +struct SmallBVKey { + int width; + int64_t value; + SmallBVKey(int width, int64_t value) : width(width), value(value) {} + + bool operator==(const SmallBVKey &other) const { + return width == other.width && value == other.value; + } +}; + +struct SmallBVKeyHash { + size_t operator()(const SmallBVKey &key) const { + size_t h1 = std::hash{}(key.width); + size_t h2 = std::hash{}(key.value); + return h1 ^ (h2 << 1); + } +}; + +struct ExtractKey { + SymVal value; + int high; + int low; + ExtractKey(const SymVal &value, int high, int low) + : value(value), high(high), low(low) {} + + bool operator==(const ExtractKey &other) const { + return value.symptr == other.value.symptr && high == other.high && + low == other.low; + } +}; + +struct ExtractKeyHash { + size_t operator()(const ExtractKey &key) const { + size_t h1 = std::hash{}(key.value.symptr.get()); + size_t h2 = std::hash{}(key.high); + size_t h3 = std::hash{}(key.low); + return h1 ^ (h2 << 1) ^ (h3 << 2); + } +}; + +struct BinOpKey { + BinOperation op; + SymVal lhs; + SymVal rhs; + BinOpKey(BinOperation op, const SymVal &lhs, const SymVal &rhs) + : op(op), lhs(lhs), rhs(rhs) {} + + bool operator==(const BinOpKey &other) const { + return op == other.op && lhs.symptr == other.lhs.symptr && + rhs.symptr == other.rhs.symptr; + } +}; + +struct BinOpKeyHash { + size_t operator()(const BinOpKey &key) const { + size_t h1 = std::hash{}(static_cast(key.op)); + size_t h2 = std::hash{}(key.lhs.symptr.get()); + size_t h3 = std::hash{}(key.rhs.symptr.get()); + return h1 ^ (h2 << 1) ^ (h3 << 2); + } +}; + +struct UnaryOpKey { + UnaryOperation op; + SymVal value; + UnaryOpKey(UnaryOperation op, const SymVal &value) : op(op), value(value) {} + + bool operator==(const UnaryOpKey &other) const { + return op == other.op && value.symptr == other.value.symptr; + } +}; + +struct UnaryOpKeyHash { + size_t operator()(const UnaryOpKey &key) const { + size_t h1 = std::hash{}(static_cast(key.op)); + size_t h2 = std::hash{}(key.value.symptr.get()); + return h1 ^ (h2 << 1); + } +}; + +// Caches. +static std::unordered_map SymbolStore; +static std::unordered_map FPStore; +static std::unordered_map SmallBVStore; +static std::unordered_map + ExtractOperationStore; +static std::unordered_map BinaryOperationStore; +static std::unordered_map + UnaryOperationStore; + +// Factory implementations. +inline SymVal make_concrete_bv(Num num, int width) { + auto key = SmallBVKey(width, num.toInt64()); + auto it = SmallBVStore.find(key); + if (it != SmallBVStore.end()) { + return it->second; + } + + auto new_val = + SymVal(SymBookKeeper.allocate(num, KindBV, width)); + SmallBVStore.insert({key, new_val}); + return new_val; +} + +inline SymVal make_concrete_fp(Num num, int width) { + auto it = FPStore.find(num.toInt64()); + if (it != FPStore.end()) { + return it->second; + } + + auto new_val = + SymVal(SymBookKeeper.allocate(num, KindFP, width)); + FPStore.insert({num.toInt64(), new_val}); + return new_val; +} + +inline SymVal make_concrete_bool(bool b) { + if (b) { + return TRUE; + } else { + return FALSE; + } +} + +inline SymVal make_int_symbolic(int index, int width) { + auto it = SymbolStore.find(index); + if (it != SymbolStore.end()) { + return it->second; + } + SymVal new_symbol = + SymVal(SymBookKeeper.allocate(index, width, KindBV)); + SymbolStore.insert({index, new_symbol}); + return new_symbol; +} + +inline SymVal make_fp_symbolic(int index, int width) { + auto it = SymbolStore.find(index); + if (it != SymbolStore.end()) { + return it->second; + } + SymVal new_symbol = + SymVal(SymBookKeeper.allocate(index, width, KindFP)); + SymbolStore.insert({index, new_symbol}); + return new_symbol; +} + +inline SymVal make_smallbv(int width, int64_t value) { + if (width == 32) { + return make_concrete_bv(I32V(value), width); + } + if (width == 64) { + return make_concrete_bv(I64V(value), width); + } + auto key = SmallBVKey(width, value); + auto it = SmallBVStore.find(key); + if (it != SmallBVStore.end()) { + return it->second; + } + auto new_val = + SymVal(SymBookKeeper.allocate(I64V(value), KindBV, width)); + SmallBVStore.insert({key, new_val}); + return new_val; +} + +inline SymVal make_extract(const SymVal &value, int high, int low) { + assert(value.symptr != nullptr && "Symbolic expression is null in extract"); + assert(high >= low && "Invalid extract range"); + int new_width = (high - low + 1) * 8; + int shift_bits = (low - 1) * 8; + + if (auto concrete = std::dynamic_pointer_cast(value.symptr)) { + // extract from concrete bitvector value + int64_t val = concrete->value.value; + int64_t mask = (1LL << ((high - low + 1) * 8)) - 1; + int64_t new_value = (val >> shift_bits) & mask; + return SVFactory::make_smallbv(new_width, new_value); + } + + // If the value is already an extract, we can merge the two extracts into one + if (auto extract = std::dynamic_pointer_cast(value.symptr)) { + if (extract->low == low && extract->high == high) { + // extracting the same range, return directly + return value; + } + } + + // Otherwise, create a new extract symbolic value + ExtractKey key(value, high, low); + auto it = ExtractOperationStore.find(key); + if (it != ExtractOperationStore.end()) { + return it->second; + } + auto result = SymVal(SymBookKeeper.allocate(value, high, low)); + ExtractOperationStore.insert({key, result}); + return result; +} + +inline SymVal make_binary(BinOperation op, const SymVal &lhs, + const SymVal &rhs) { + assert(lhs.symptr != nullptr && rhs.symptr != nullptr); + + BinOpKey key(op, lhs, rhs); + auto it = BinaryOperationStore.find(key); + if (it != BinaryOperationStore.end()) { + return it->second; + } + + if (auto lhs_concrete = dynamic_cast(lhs.symptr.get())) { + if (auto rhs_concrete = dynamic_cast(rhs.symptr.get())) { + auto lhs_value = lhs_concrete->value; + auto rhs_value = rhs_concrete->value; + auto lhs_width = lhs_concrete->width(); + auto rhs_width = rhs_concrete->width(); + + auto make_eval_bv = [&](Num num, int width) { + auto result = SVFactory::make_concrete_bv(num, width); + BinaryOperationStore.insert({key, result}); + return result; + }; + auto make_eval_bool = [&](Num num) { + auto result = SVFactory::make_concrete_bool(num.value); + BinaryOperationStore.insert({key, result}); + return result; + }; + + switch (op) { + case ADD: + if (lhs_width == 32 && rhs_width == 32) + return make_eval_bv(lhs_value.i32_add(rhs_value), 32); + if (lhs_width == 64 && rhs_width == 64) + return make_eval_bv(lhs_value.i64_add(rhs_value), 64); + break; + case SUB: + if (lhs_width == 32 && rhs_width == 32) + return make_eval_bv(lhs_value.i32_sub(rhs_value), 32); + if (lhs_width == 64 && rhs_width == 64) + return make_eval_bv(lhs_value.i64_sub(rhs_value), 64); + break; + case MUL: + if (lhs_width == 32 && rhs_width == 32) + return make_eval_bv(lhs_value.i32_mul(rhs_value), 32); + if (lhs_width == 64 && rhs_width == 64) + return make_eval_bv(lhs_value.i64_mul(rhs_value), 64); + break; + case DIV: + if (lhs_width == 32 && rhs_width == 32) + return make_eval_bv(lhs_value.i32_div_s(rhs_value), 32); + if (lhs_width == 64 && rhs_width == 64) + return make_eval_bv(lhs_value.i64_div_s(rhs_value), 64); + break; + case LT_BOOL: + if (lhs_width == 32 && rhs_width == 32) + return make_eval_bool(lhs_value.i32_lt_s(rhs_value)); + if (lhs_width == 64 && rhs_width == 64) + return make_eval_bool(lhs_value.i64_lt_s(rhs_value)); + break; + case LEQ_BOOL: + if (lhs_width == 32 && rhs_width == 32) + return make_eval_bool(lhs_value.i32_le_s(rhs_value)); + if (lhs_width == 64 && rhs_width == 64) + return make_eval_bool(lhs_value.i64_le_s(rhs_value)); + break; + case GT_BOOL: + if (lhs_width == 32 && rhs_width == 32) + return make_eval_bool(lhs_value.i32_gt_s(rhs_value)); + if (lhs_width == 64 && rhs_width == 64) + return make_eval_bool(lhs_value.i64_gt_s(rhs_value)); + break; + case GEQ_BOOL: + if (lhs_width == 32 && rhs_width == 32) + return make_eval_bool(lhs_value.i32_ge_s(rhs_value)); + if (lhs_width == 64 && rhs_width == 64) + return make_eval_bool(lhs_value.i64_ge_s(rhs_value)); + break; + case NEQ_BOOL: + if (lhs_width == 32 && rhs_width == 32) + return make_eval_bool(lhs_value.i32_ne(rhs_value)); + if (lhs_width == 64 && rhs_width == 64) + return make_eval_bool(lhs_value.i64_ne(rhs_value)); + break; + case EQ_BOOL: + if (lhs_width == 32 && rhs_width == 32) + return make_eval_bool(lhs_value.i32_eq(rhs_value)); + if (lhs_width == 64 && rhs_width == 64) + return make_eval_bool(lhs_value.i64_eq(rhs_value)); + break; + case B_AND: + if (lhs_width == 32 && rhs_width == 32) + return make_eval_bv(lhs_value.i32_and(rhs_value), 32); + if (lhs_width == 64 && rhs_width == 64) + return make_eval_bv(lhs_value.i64_and(rhs_value), 64); + break; + case CONCAT: { + auto conc_value = (lhs_value.value << rhs_width) | rhs_value.value; + auto new_width = lhs_width + rhs_width; + return make_eval_bv(Num(I64V(conc_value)), new_width); + } + case B_XOR: + if (lhs_width == 32 && rhs_width == 32) + return make_eval_bv(lhs_value.i32_xor(rhs_value), 32); + if (lhs_width == 64 && rhs_width == 64) + return make_eval_bv(lhs_value.i64_xor(rhs_value), 64); + break; + case B_OR: + if (lhs_width == 32 && rhs_width == 32) + return make_eval_bv(lhs_value.i32_or(rhs_value), 32); + if (lhs_width == 64 && rhs_width == 64) + return make_eval_bv(lhs_value.i64_or(rhs_value), 64); + break; + case SHR_U: + if (lhs_width == 32 && rhs_width == 32) + return make_eval_bv(lhs_value.i32_shr_u(rhs_value), 32); + if (lhs_width == 64 && rhs_width == 64) + return make_eval_bv(lhs_value.i64_shr_u(rhs_value), 64); + break; + case SHR_S: + if (lhs_width == 32 && rhs_width == 32) + return make_eval_bv(lhs_value.i32_shr_s(rhs_value), 32); + if (lhs_width == 64 && rhs_width == 64) + return make_eval_bv(lhs_value.i64_shr_s(rhs_value), 64); + break; + case SHL: + if (lhs_width == 32 && rhs_width == 32) + return make_eval_bv(lhs_value.i32_shl(rhs_value), 32); + if (lhs_width == 64 && rhs_width == 64) + return make_eval_bv(lhs_value.i64_shl(rhs_value), 64); + break; + case LTU_BOOL: + if (lhs_width == 32 && rhs_width == 32) + return make_eval_bool(lhs_value.i32_lt_u(rhs_value)); + if (lhs_width == 64 && rhs_width == 64) + return make_eval_bool(lhs_value.i64_lt_u(rhs_value)); + break; + case LEU_BOOL: + if (lhs_width == 32 && rhs_width == 32) + return make_eval_bool(lhs_value.i32_le_u(rhs_value)); + if (lhs_width == 64 && rhs_width == 64) + return make_eval_bool(lhs_value.i64_le_u(rhs_value)); + break; + case GTU_BOOL: + if (lhs_width == 32 && rhs_width == 32) + return make_eval_bool(lhs_value.i32_gt_u(rhs_value)); + if (lhs_width == 64 && rhs_width == 64) + return make_eval_bool(lhs_value.i64_gt_u(rhs_value)); + break; + case GEU_BOOL: + if (lhs_width == 32 && rhs_width == 32) + return make_eval_bool(lhs_value.i32_ge_u(rhs_value)); + if (lhs_width == 64 && rhs_width == 64) + return make_eval_bool(lhs_value.i64_ge_u(rhs_value)); + break; + case AND: + return make_eval_bool(lhs_value.logical_and(rhs_value)); + case OR: + return make_eval_bool(lhs_value.logical_or(rhs_value)); + default: + break; + } + assert(false && "Operation not supported in evaluation"); + } + } + + if (op == EQ_BOOL) { + if (auto lhs_unary = dynamic_cast(lhs.symptr.get())) { + if (auto rhs_concrete = dynamic_cast(rhs.symptr.get())) { + if (lhs_unary->op == BOOL2BV) { + auto rhs_value = rhs_concrete->value; + if (rhs_value.value == 0) { + auto result = lhs_unary->value.bool_not(); + BinaryOperationStore.insert({key, result}); + return result; + } + } + } + } + + if (auto rhs_unary = dynamic_cast(rhs.symptr.get())) { + if (auto lhs_concrete = dynamic_cast(lhs.symptr.get())) { + if (rhs_unary->op == BOOL2BV) { + auto lhs_value = lhs_concrete->value; + if (lhs_value.value == 0) { + auto result = rhs_unary->value.bool_not(); + BinaryOperationStore.insert({key, result}); + return result; + } + } + } + } + } + + if (op == NEQ_BOOL) { + if (auto lhs_unary = dynamic_cast(lhs.symptr.get())) { + if (auto rhs_concrete = dynamic_cast(rhs.symptr.get())) { + if (rhs_concrete->kind == KindBV && rhs_concrete->value.value == 0) { + if (lhs_unary->op == BOOL2BV) { + auto result = lhs_unary->value; + BinaryOperationStore.insert({key, result}); + return result; + } + } + } + } + if (auto rhs_unary = dynamic_cast(rhs.symptr.get())) { + if (auto lhs_concrete = dynamic_cast(lhs.symptr.get())) { + if (lhs_concrete->kind == KindBV && lhs_concrete->value.value == 0) { + if (rhs_unary->op == BOOL2BV) { + auto result = rhs_unary->value; + BinaryOperationStore.insert({key, result}); + return result; + } + } + } + } + } + + if (op == EQ_BOOL && lhs == rhs) { + auto result = SVFactory::make_concrete_bool(true); + BinaryOperationStore.insert({key, result}); + return result; + } + + if (op == NEQ_BOOL && lhs == rhs) { + auto result = SVFactory::make_concrete_bool(false); + BinaryOperationStore.insert({key, result}); + return result; + } + + if ((op == GT_BOOL || op == LT_BOOL || NEQ_BOOL) && lhs == rhs) { + auto result = SVFactory::make_concrete_bool(false); + BinaryOperationStore.insert({key, result}); + return result; + } + + if (op == AND) { + if (auto rhs_concrete = dynamic_cast(rhs.symptr.get())) { + if (rhs_concrete->kind == KindBool && rhs_concrete->value.value == 0) { + auto result = SVFactory::make_concrete_bool(false); + BinaryOperationStore.insert({key, result}); + return result; + } + } + if (auto lhs_concrete = dynamic_cast(lhs.symptr.get())) { + if (lhs_concrete->kind == KindBool && lhs_concrete->value.value == 0) { + auto result = SVFactory::make_concrete_bool(false); + BinaryOperationStore.insert({key, result}); + return result; + } + } + if (auto rhs_concrete = dynamic_cast(rhs.symptr.get())) { + if (rhs_concrete->kind == KindBool && rhs_concrete->value.value != 0) { + BinaryOperationStore.insert({key, lhs}); + return lhs; + } + } + if (auto lhs_concrete = dynamic_cast(lhs.symptr.get())) { + if (lhs_concrete->kind == KindBool && lhs_concrete->value.value != 0) { + BinaryOperationStore.insert({key, rhs}); + return rhs; + } + } + } + + if (op == B_AND) { + if (auto lhs_unary = dynamic_cast(lhs.symptr.get())) { + if (auto rhs_unary = dynamic_cast(rhs.symptr.get())) { + if (lhs_unary->op == BOOL2BV && rhs_unary->op == BOOL2BV) { + auto result = lhs_unary->value.land(rhs_unary->value).bool2bv(); + BinaryOperationStore.insert({key, result}); + return result; + } + } + } + + if (auto rhs_concrete = dynamic_cast(rhs.symptr.get())) { + if (rhs_concrete->kind == KindBV && rhs_concrete->value.value == 1) { + if (auto lhs_unary = dynamic_cast(lhs.symptr.get())) { + if (lhs_unary->op == BOOL2BV) { + BinaryOperationStore.insert({key, lhs}); + return lhs; + } + } + } + } + + if (auto lhs_concrete = dynamic_cast(lhs.symptr.get())) { + if (lhs_concrete->kind == KindBV && lhs_concrete->value.value == 1) { + if (auto rhs_unary = dynamic_cast(rhs.symptr.get())) { + if (rhs_unary->op == BOOL2BV) { + BinaryOperationStore.insert({key, rhs}); + return rhs; + } + } + } + } + } + + auto result = + SymVal(SVFactory::SymBookKeeper.allocate(op, lhs, rhs)); + BinaryOperationStore.insert({key, result}); + return result; +} + +inline SymVal make_unary(UnaryOperation op, const SymVal &value) { + assert(value.symptr != nullptr); + + UnaryOpKey key(op, value); + auto it = UnaryOperationStore.find(key); + if (it != UnaryOperationStore.end()) { + return it->second; + } + + if (op == BOOL2BV) { + if (auto concrete = dynamic_cast(value.symptr.get())) { + auto value_conc = concrete->value; + if (concrete->kind == KindBool) { + if (value_conc.value != 0) { + auto result = SVFactory::make_concrete_bv(Num(I32V(1)), 32); + UnaryOperationStore.insert({key, result}); + return result; + } else { + auto result = SVFactory::make_concrete_bv(Num(I32V(0)), 32); + UnaryOperationStore.insert({key, result}); + return result; + } + } + } + } + + if (op == NOT) { + if (auto concrete = dynamic_cast(value.symptr.get())) { + if (concrete->kind == KindBool) { + auto result = SVFactory::make_concrete_bool(concrete->value.value == 0); + UnaryOperationStore.insert({key, result}); + return result; + } + } + + if (auto inner_unary = dynamic_cast(value.symptr.get())) { + if (inner_unary->op == NOT) { + auto result = inner_unary->value; + UnaryOperationStore.insert({key, result}); + return result; + } + } + + if (auto inner_binary = dynamic_cast(value.symptr.get())) { + BinOperation negated_op; + switch (inner_binary->op) { + case EQ_BOOL: + negated_op = NEQ_BOOL; + break; + case NEQ_BOOL: + negated_op = EQ_BOOL; + break; + case LT_BOOL: + negated_op = GEQ_BOOL; + break; + case GT_BOOL: + negated_op = LEQ_BOOL; + break; + case LEQ_BOOL: + negated_op = GT_BOOL; + break; + case GEQ_BOOL: + negated_op = LT_BOOL; + break; + default: + negated_op = inner_binary->op; + break; + } + if (negated_op != inner_binary->op) { + auto result = SVFactory::make_binary(negated_op, inner_binary->lhs, + inner_binary->rhs); + UnaryOperationStore.insert({key, result}); + return result; + } + } + } + + auto result = SymVal(SVFactory::SymBookKeeper.allocate(op, value)); + UnaryOperationStore.insert({key, result}); + return result; +} + +inline SymVal make_concat(const SymVal &lhs, const SymVal &rhs) { + if (auto lhs_concrete = std::dynamic_pointer_cast(lhs.symptr)) { + if (auto rhs_concrete = + std::dynamic_pointer_cast(rhs.symptr)) { + if (lhs_concrete->kind == KindBV && rhs_concrete->kind == KindBV) { + int new_width = lhs_concrete->width() + rhs_concrete->width(); + int64_t new_value = + (lhs_concrete->value.value << rhs_concrete->width()) | + rhs_concrete->value.value; + return SVFactory::make_smallbv(new_width, new_value); + } + } + } + if (auto extract1 = std::dynamic_pointer_cast(lhs.symptr)) { + if (auto extract2 = std::dynamic_pointer_cast(rhs.symptr)) { + if (extract1->low == extract2->high + 1 && + extract1->value == extract2->value) { + if (extract1->high == 4 && extract2->low == 1) { + // special case for full 4-byte extract concatenation + // TODO: support 64-bit later, this optimization is only valid when we + // only work on 32-bit values + return extract1->value; + } + // two extracts are adjacent, we can merge them + return extract1->value.extract(extract1->high, extract2->low); + } + } + } + return SVFactory::make_binary(CONCAT, lhs, rhs); +} + +} // namespace SVFactory + +#endif // WASM_SYMVAL_FACTORY_HPP diff --git a/genwasym_runtime/include/wasm/symval_impl.hpp b/genwasym_runtime/include/wasm/symval_impl.hpp new file mode 100644 index 000000000..b7b3684c9 --- /dev/null +++ b/genwasym_runtime/include/wasm/symval_impl.hpp @@ -0,0 +1,208 @@ +#ifndef WASM_SYMVAL_IMPL_HPP +#define WASM_SYMVAL_IMPL_HPP + +#include "symval_decl.hpp" +#include "symval_factory.hpp" +#include "wasm/concrete_num.hpp" + +inline SymVal SymVal::add(const SymVal &other) const { + return SVFactory::make_binary(ADD, *this, other); +} + +inline SymVal SymVal::minus(const SymVal &other) const { + return SVFactory::make_binary(SUB, *this, other); +} + +inline SymVal SymVal::mul(const SymVal &other) const { + return SVFactory::make_binary(MUL, *this, other); +} + +inline SymVal SymVal::div(const SymVal &other) const { + return SVFactory::make_binary(DIV, *this, other); +} + +inline SymVal SymVal::div_u(const SymVal &other) const { + return SVFactory::make_binary(DIV_U, *this, other); +} + +inline SymVal SymVal::land(const SymVal &other) const { + return SVFactory::make_binary(AND, *this, other); +} + +inline SymVal SymVal::lor(const SymVal &other) const { + return SVFactory::make_binary(OR, *this, other); +} + +inline SymVal SymVal::eq_bool(const SymVal &other) const { + return SVFactory::make_binary(EQ_BOOL, *this, other); +} + +inline SymVal SymVal::neq_bool(const SymVal &other) const { + return SVFactory::make_binary(NEQ_BOOL, *this, other); +} + +inline SymVal SymVal::eq(const SymVal &other) const { + return SVFactory::make_binary(EQ_BOOL, *this, other); +} + +inline SymVal SymVal::neq(const SymVal &other) const { + return SVFactory::make_binary(NEQ_BOOL, *this, other); +} + +inline SymVal SymVal::bv2bool() const { + auto rhs = SVFactory::make_concrete_bv(I32V(0), symptr->width()); + return SVFactory::make_binary(NEQ_BOOL, *this, rhs); +} + +inline SymVal SymVal::bool2bv() const { + return SVFactory::make_unary(BOOL2BV, *this); +} + +inline SymVal SymVal::extend_to_i64() const { + return SVFactory::make_unary(EXTEND, *this); +} + +inline SymVal SymVal::lt(const SymVal &other) const { + return SVFactory::make_binary(LT_BOOL, *this, other); +} + +inline SymVal SymVal::ltu(const SymVal &other) const { + return SVFactory::make_binary(LTU_BOOL, *this, other); +} + +inline SymVal SymVal::le(const SymVal &other) const { + return SVFactory::make_binary(LEQ_BOOL, *this, other); +} + +inline SymVal SymVal::leu(const SymVal &other) const { + return SVFactory::make_binary(LEU_BOOL, *this, other); +} + +inline SymVal SymVal::gt(const SymVal &other) const { + return SVFactory::make_binary(GT_BOOL, *this, other); +} + +inline SymVal SymVal::gtu(const SymVal &other) const { + return SVFactory::make_binary(GTU_BOOL, *this, other); +} + +inline SymVal SymVal::ge(const SymVal &other) const { + return SVFactory::make_binary(GEQ_BOOL, *this, other); +} + +inline SymVal SymVal::geu(const SymVal &other) const { + return SVFactory::make_binary(GEU_BOOL, *this, other); +} + +inline SymVal SymVal::shl(const SymVal &other) const { + return SVFactory::make_binary(SHL, *this, other); +} + +inline SymVal SymVal::shr_u(const SymVal &other) const { + return SVFactory::make_binary(SHR_U, *this, other); +} + +inline SymVal SymVal::shr_s(const SymVal &other) const { + return SVFactory::make_binary(SHR_S, *this, other); +} + +inline SymVal SymVal::rem_u(const SymVal &other) const { + return SVFactory::make_binary(REM_U, *this, other); +} + +inline SymVal SymVal::is_zero() const { + return SVFactory::make_binary( + EQ_BOOL, *this, SVFactory::make_concrete_bv(I64V(0), symptr->width())); +} + +inline SymVal SymVal::bv_negate() const { + assert(symptr->width() != 1); + return SVFactory::make_binary( + EQ_BOOL, *this, SVFactory::make_concrete_bv(I64V(0), symptr->width())); +} + +inline SymVal SymVal::bool_not() const { + return SVFactory::make_unary(NOT, *this); +} + +inline SymVal SymVal::concat(const SymVal &other) const { + return SVFactory::make_concat(*this, other); +} + +inline SymVal SymVal::extract(int high, int low) const { + return SVFactory::make_extract(*this, high, low); +} + +inline SymVal SymVal::bitwise_and(const SymVal &other) const { + return SVFactory::make_binary(B_AND, *this, other); +} + +inline SymVal SymVal::bitwise_xor(const SymVal &other) const { + return SVFactory::make_binary(B_XOR, *this, other); +} + +inline SymVal SymVal::bitwise_or(const SymVal &other) const { + return SVFactory::make_binary(B_OR, *this, other); +} + +inline SymVal SymVal::get_witness_symbol() { + static SymVal witness = SymVal(SVFactory::SymBookKeeper.allocate()); + return witness; +} + +inline SymVal SymVal::makeI32Symbol() const { + if (auto concrete = dynamic_cast(symptr.get())) { + auto id = concrete->value.toInt(); + return SVFactory::make_int_symbolic(id, 32); + } + throw std::runtime_error( + "Cannot make symbolic a non-concrete symbolic value"); +} + +inline SymVal SymVal::makeI64Symbol() const { + if (auto concrete = dynamic_cast(symptr.get())) { + auto id = concrete->value.toInt(); + return SVFactory::make_int_symbolic(id, 64); + } + throw std::runtime_error( + "Cannot make symbolic a non-concrete symbolic value"); +} + +inline SymVal SymVal::makeF32Symbol() const { + if (auto concrete = dynamic_cast(symptr.get())) { + auto id = concrete->value.toInt(); + return SVFactory::make_fp_symbolic(id, 32); + } + throw std::runtime_error( + "Cannot make symbolic a non-concrete symbolic value"); +} + +inline SymVal SymVal::makeF64Symbol() const { + auto concrete = dynamic_cast(symptr.get()); + if (concrete) { + auto id = concrete->value.toInt(); + return SVFactory::make_fp_symbolic(id, 64); + } + throw std::runtime_error( + "Cannot make symbolic a non-concrete symbolic value"); +} + +inline bool SymVal::is_concrete() const { + return dynamic_cast(symptr.get()) != nullptr; +} + +inline SymVal Concrete(Num num, int width) { + // std::cout << "Creating concrete value: " << num.toInt() << " with width " + // << width + // << std::endl; + assert(width == 32 || width == 64); + return SVFactory::make_concrete_bv(num, width); +} + +inline SymVal FPConcrete(Num num, int width) { + assert(width == 32 || width == 64); + return SVFactory::make_concrete_fp(num, width); +} + + +#endif // WASM_SYMVAL_IMPL_HPP diff --git a/genwasym_runtime/include/wasm/union_find.hpp b/genwasym_runtime/include/wasm/union_find.hpp new file mode 100644 index 000000000..3ba139155 --- /dev/null +++ b/genwasym_runtime/include/wasm/union_find.hpp @@ -0,0 +1,60 @@ +#ifndef WASM_UNION_FIND_HPP +#define WASM_UNION_FIND_HPP +#include "config.hpp" +#include "immer/map.hpp" +#include +#include +#include + +// TODO: merge this file with headers/gensym/unionfind.hpp with a general implementation in a new PR +class UnionFind { +private: + immer::map_transient parent; + immer::map_transient rank; + +public: + UnionFind() = default; + + int find(int x) const { + auto parent_opt = parent.find(x); + if (!parent_opt) { + return x; + } + if (*parent_opt == x) { + return x; + } + return find(*parent_opt); + } + + void unite(int x, int y) { + int root_x = find(x); + int root_y = find(y); + + if (root_x == root_y) { + return; + } + + auto rank_x_ptr = rank.find(root_x); + auto rank_y_ptr = rank.find(root_y); + int rank_x = rank_x_ptr ? *rank_x_ptr : 0; + int rank_y = rank_y_ptr ? *rank_y_ptr : 0; + + if (rank_x < rank_y) { + parent.set(root_x, root_y); + } else if (rank_x > rank_y) { + parent.set(root_y, root_x); + } else { + parent.set(root_y, root_x); + rank.set(root_x, rank_x + 1); + } + } + + bool connected(int x, int y) const { return find(x) == find(y); } + + void clear() { + parent = immer::map_transient(); + rank = immer::map_transient(); + } +}; + +#endif // WASM_UNION_FIND_HPP \ No newline at end of file diff --git a/genwasym_runtime/include/wasm/utils.hpp b/genwasym_runtime/include/wasm/utils.hpp new file mode 100644 index 000000000..62c5bb8a2 --- /dev/null +++ b/genwasym_runtime/include/wasm/utils.hpp @@ -0,0 +1,92 @@ +#ifndef UTILS_HPP +#define UTILS_HPP +#include +#include + +#ifndef GENSYM_ASSERT +#define GENSYM_ASSERT(condition) \ + do { \ + if (!(condition)) { \ + throw std::runtime_error(std::string("Assertion failed: ") + " (" + \ + __FILE__ + ":" + std::to_string(__LINE__) + \ + ")"); \ + } \ + } while (0) +#endif + +#ifndef NO_DBG +#define GENSYM_DBG(obj) \ + do { \ + std::cout << "LOG: " << obj << " (" << __FILE__ << ":" \ + << std::to_string(__LINE__) << ")" << std::endl; \ + } while (0) +#else +#define GENSYM_LOG(message) \ + do { \ + } while (0) +#endif + +#ifndef NO_INFO +#define GENSYM_INFO(obj) \ + do { \ + std::cout << obj << std::endl; \ + } while (0) +#else +#define GENSYM_INFO(message) \ + do { \ + } while (0) + +#endif + +#if __cplusplus < 202002L +#include + +inline bool starts_with(const std::string &str, const std::string &prefix) { + return str.size() >= prefix.size() && + std::equal(prefix.begin(), prefix.end(), str.begin()); +} +#else +#include +inline bool starts_with(const std::string &str, const std::string &prefix) { + return str.starts_with(prefix); +} +#endif + +inline std::monostate print_infos() { + std::cout << std::endl; + return std::monostate{}; +} + +template +std::monostate print_infos(const T &first, const Args &...args) { + std::cout << first << " "; + print_infos(args...); + return std::monostate{}; +} + +template +std::monostate info(const T &first, const Args &...args) { +#ifdef DEBUG + print_infos(first, args...); +#endif + return std::monostate{}; +} + +constexpr const char *DEBUG_OPTS_ENV_VAR = "GENSYM_DEBUG"; + +template +std::monostate infoWhen(const char *dbg_option, const Args &...args) { +#ifdef DEBUGWHEN + const char *env_value = std::getenv(DEBUG_OPTS_ENV_VAR); + if (env_value && std::string(env_value).find(std::string(dbg_option)) != + std::string::npos) { + print_infos(args...); + } +#endif + return std::monostate{}; +} + +inline std::monostate get_unit() { return std::monostate{}; } +inline std::monostate get_unit(std::monostate x) { return std::monostate{}; } + +#endif // UTILS_HPP \ No newline at end of file diff --git a/genwasym_runtime/include/wasm/z3_env.hpp b/genwasym_runtime/include/wasm/z3_env.hpp new file mode 100644 index 000000000..f9109406b --- /dev/null +++ b/genwasym_runtime/include/wasm/z3_env.hpp @@ -0,0 +1,36 @@ +#ifndef WASM_Z3_ENV_HPP +#define WASM_Z3_ENV_HPP +#include "z3++.h" + +struct Z3Env { + z3::context z3_ctx; + + Z3Env() : z3_ctx() {} +}; + +static Z3Env GLOBAL_Z3_ENV; + +inline z3::context &global_z3_ctx() { return GLOBAL_Z3_ENV.z3_ctx; } + +// A map from z3 expression id to their ast size +static std::unordered_map Z3ExprSizeMap; + +inline int get_z3_fp_sort_size(const z3::sort &s) { + assert(s.is_fpa()); + return s.fpa_ebits() + s.fpa_sbits(); +} + +static int get_z3_expr_size(const z3::expr &e) { + unsigned id = e.id(); + if (Z3ExprSizeMap.find(id) != Z3ExprSizeMap.end()) { + return Z3ExprSizeMap[id]; + } + unsigned count = 1; // count self + for (unsigned i = 0; i < e.num_args(); i++) { + count += get_z3_expr_size(e.arg(i)); + } + Z3ExprSizeMap[id] = count; + return count; +} + +#endif // WASM_Z3_ENV_HPP diff --git a/genwasym_runtime/include/wasm_state_continue.hpp b/genwasym_runtime/include/wasm_state_continue.hpp new file mode 100644 index 000000000..5fc341197 --- /dev/null +++ b/genwasym_runtime/include/wasm_state_continue.hpp @@ -0,0 +1,94 @@ +#ifndef WASM_STATE_CONTINUE_HPP +#define WASM_STATE_CONTINUE_HPP + +#include +#include +#include +#include + +#include +#include +#include + +typedef std::function cont_t; + +extern cont_t fun_ret_cont_stack[1000]; +extern int fun_ret_cont_stack_ptr; + +void push_fun_ret_cont_stack(cont_t cont); +std::monostate pop_fun_ret_cont_stack(); + +template +immer::flex_vector flex_vector_reverse(immer::flex_vector v) { + immer::flex_vector result = immer::flex_vector(); + for (auto it = v.rbegin(); it != v.rend(); it++) { + result = result.push_back(*it); + } + return result; +} + +enum ValueTy { + I32, +}; + +struct Value { + ValueTy ty; + union { + int i32; + }; +}; + +Value I32V(int x); + +struct Mem {}; +struct Global {}; + +class State { + public: + immer::flex_vector memory; + immer::flex_vector globals; + size_t stack_ptr = 0; + size_t frame_ptr = 0; + Value stack[1000]; + immer::vector_transient> return_stack; + + size_t tmp_frame_ptr = 0; + + State(immer::flex_vector memory, immer::flex_vector globals); + + Value stack_at(int i); + void push_stack(Value v); + Value pop_stack(); + Value peek_stack(); + void print_stack(); + Value get_local(int i); + void set_local(int i, Value v); + void return_from_fun(int num_locals, int ret_num); + void bump_frame_ptr(); + void set_frame_ptr(int fp); + int get_frame_ptr(); + void save_frame_ptr(); + void restore_frame_ptr(); + void remove_stack_range(int start, int end); + void reverse_top_n(int n); +}; + +extern State global_state; + +State& init_state(immer::flex_vector memory, + immer::flex_vector globals, + int num_locals); + +enum EvalTag { + CONTINUE, + RETURNING, + BREAKING, +}; + +struct EvalResult { + EvalTag tag; + int n; + State state; +}; + +#endif \ No newline at end of file diff --git a/genwasym_runtime/lib/genwasym.cpp b/genwasym_runtime/lib/genwasym.cpp new file mode 100644 index 000000000..95233140c --- /dev/null +++ b/genwasym_runtime/lib/genwasym.cpp @@ -0,0 +1,5 @@ +#include "genwasym.h" + +int genwasym_dummy() { + return 0; +} \ No newline at end of file diff --git a/genwasym_runtime/lib/wasm_state_continue.cpp b/genwasym_runtime/lib/wasm_state_continue.cpp new file mode 100644 index 000000000..296219015 --- /dev/null +++ b/genwasym_runtime/lib/wasm_state_continue.cpp @@ -0,0 +1,116 @@ +#include "wasm_state_continue.hpp" + +cont_t fun_ret_cont_stack[1000]; +int fun_ret_cont_stack_ptr = 0; + +void push_fun_ret_cont_stack(cont_t cont) { + fun_ret_cont_stack[fun_ret_cont_stack_ptr++] = cont; +} + +std::monostate pop_fun_ret_cont_stack() { + return fun_ret_cont_stack[--fun_ret_cont_stack_ptr](std::monostate()); +} + +Value I32V(int x) { + Value v; + v.ty = I32; + v.i32 = x; + return v; +} + +State::State(immer::flex_vector memory, immer::flex_vector globals) + : memory(memory), globals(globals) { + for (int i = 0; i < 1000; i++) { + stack[i] = I32V(0); + } + return_stack = immer::vector_transient>(); +} + +Value State::stack_at(int i) { + return stack[i]; +} + +void State::push_stack(Value v) { + stack[stack_ptr++] = v; +} + +Value State::pop_stack() { + return stack[--stack_ptr]; +} + +Value State::peek_stack() { + return stack[stack_ptr - 1]; +} + +void State::print_stack() { + printf("sp: %ld, fp: %ld, Stack: ", stack_ptr, frame_ptr); + for (int i = 0; i < stack_ptr; i++) { + printf("%d ", stack[i].i32); + } + printf("\n"); +} + +Value State::get_local(int i) { + return stack[frame_ptr + i]; +} + +void State::set_local(int i, Value v) { + stack[frame_ptr + i] = v; +} + +void State::return_from_fun(int num_locals, int ret_num) { + remove_stack_range(frame_ptr - num_locals, frame_ptr); + remove_stack_range(frame_ptr + ret_num, stack_ptr); + stack_ptr = frame_ptr - num_locals + ret_num; +} + +void State::bump_frame_ptr() { + frame_ptr = stack_ptr; +} + +void State::set_frame_ptr(int fp) { + frame_ptr = fp; +} + +int State::get_frame_ptr() { + return frame_ptr; +} + +void State::save_frame_ptr() { + tmp_frame_ptr = frame_ptr; +} + +void State::restore_frame_ptr() { + frame_ptr = tmp_frame_ptr; +} + +void State::remove_stack_range(int start, int end) { + for (int i = start; i < end; i++) { + int j = end + (i - start); + if (j < stack_ptr) { + stack[i] = stack[j]; + } else { + stack[i] = I32V(0); + } + } +} + +void State::reverse_top_n(int n) { + for (int i = stack_ptr - n; i < stack_ptr - n / 2; i++) { + int j = stack_ptr - (i - (stack_ptr - n)) - 1; + Value tmp = stack[i]; + stack[i] = stack[j]; + stack[j] = tmp; + } +} + +State global_state = State(immer::flex_vector(), immer::flex_vector()); + +State& init_state(immer::flex_vector memory, + immer::flex_vector globals, + int num_locals) { + global_state = State(memory, globals); + global_state.stack_ptr = num_locals; + global_state.frame_ptr = num_locals; + return global_state; +} \ No newline at end of file From 2c815b68571504b2d70136038b90c13950952ea5 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Wed, 13 May 2026 13:59:26 -0400 Subject: [PATCH 2/4] remove compiled libraries from git --- .gitignore | 1 + genwasym_runtime/build/libgenwasym.a | Bin 272712 -> 0 bytes genwasym_runtime/build/libgenwasym.so | Bin 305360 -> 0 bytes 3 files changed, 1 insertion(+) delete mode 100644 genwasym_runtime/build/libgenwasym.a delete mode 100755 genwasym_runtime/build/libgenwasym.so diff --git a/.gitignore b/.gitignore index 1ad84c2b5..276a18cf1 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ conc_gen/* ccbse_gen/* headers/gensym/external.hpp grammar/.antlr/ +genwasym_runtime/build diff --git a/genwasym_runtime/build/libgenwasym.a b/genwasym_runtime/build/libgenwasym.a deleted file mode 100644 index 1d90fc2762f66722816d03ef1e1dff69041e80a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 272712 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%0DnT6i0=&uYFd5sUktjWW7aEiR9@2xx# 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
      yOy)0iVJf*?|ElBo1pi$eSJ2dmuES;Ipi>b zby%LM6-(>-EEY|ta!pG!Ui7IeIJ7Rr-5UCfh7Rvc@K~TyP@gXw7lB?$(Dfy`zC$Wj zJ0^jCWiO;6XBUI2`Dj4RUb6NCFLxX}d72aRe33Ajk_ zaM+n!Q!rM8iEe%e+?zjIzl-QDgSYRex4R=W4LjK^saPllXwARdwH!#rcDqx{e4SV6 z1+i#%XGztzXLlBaE`7JE-K9qhyWCElTG(Y~O*T&bTIl8W=-NUryJzp_v>puF_3^#Y z`z`fyaq1nHdYO1lkd{{2tZP?QQ9mW<^OaCPDUDNxffuv@^b*wn1IKR58XkQEiz|xu z=IwO9J3w0=J<33VOOqc=kE&1LPTRKi-A-8<25b*t_r!J@1AX4R@o`o*z_QCV-Fm5e z>9p~dOTJ4+a3_t7VQ=Lw%iyq!?Xe0DyXbBjB*QMZ=i=Z0GuYi8&jP%;xqBnI)8u6d z)VKTmWeEuHI)w?p>`t?o@Jk)!{ruhQ-n{nWmO#C`O=p&X=$&`%1@j8=9AN(F~Nzsd6UG<{a2O)5WaeOMO}h#F0@4 z-Ccv$=7i`g;~N=m=h*2IwKg`Pj#9TBChbrl{-4L~g))(EO}$Dhvhz75-|yDp=2TfRi<3~-g33>l=#`6=o! zl`5PmWzue7!Y@vi0G8>E=QDc&rxPkIckciIY*0&XoFUh z*gbD^kQKWrlYohs*+E~1QW8Ly{5>-tBQ*-?*;!pT4)rla`X)v#PyHHnPa?thv=*OI zQ}OK8l$zSuQ06(4X%D4j_BGX0HlJ#EW$M=IUMiD>T;qNMzzxrp4- zcZtLM)mvEHqZjGEuWu|hURMT2iaD~`1efn99cfV0EYrn9v;NSobSc}l$yQ1|oNbTw zqzZ-XE|@8mONda$$XX_j?W|k#l+&i!Eiz8~5A%lEXeKa~&*gbqY_n+_ z=Zn1~eX5(F@3srDib}nggZWd_xYM=jMUDpjO{2k?E~5d@OkAyWI+;AeeAdAr=Ryq}kx~bb zUXZ8D4Fkyfks%ACDjB?L8Yg#UVA<6Ic6l%V0;T6YhP0JlqF#Dy{&J<~J|^rdK%VS% ze82tG{5(6teJ!w<%9S#9E^X6&=WHguP3;z_y^X~1<{tXjaWL8QWf+`&ajlV_*E_r< zXc_j_SPtXxpjBUg^waP$IKTc_PYVz|e2}IirRxPLEOhv2D7rn^kMWASZLn7l+bvx$ zbYP`+<>%7dQ=QmeDmtxMd|$&ux5Z=g!Yr&)!}DF~IrJSJH&BA58O>u7ooJmzpHuGz z2N%!_cGD)8H8iq;)(nGbo{smjiF>Q8J~TM*+_ZGlO-1eV7vG~C&ulHnHT81T%E`P& z6O`ld9x6xw4?lq^O20Qr&nfo4R>>a|7CtsP%owRl(>vCCy)>Ked+!^U8fWLbG%h9F z`_sjhwcmRI_s=YQrqS}IpvwTixPsg_GSND750$0g8|8B_H0`mn3_!d5%NA#oQVJfL z>R)bMN7y~`jT@UUYJw~ukg>v0?~z(!wD&-I$adKPX!>g4GCkOnnWsfBXWBsqHY>(5 z#JUR8rAa<`K)U_QY!;+!(bxI(7N@WGO`mJ{z=n!qea{Gg&L?Xg`Ye)z<*=xO_U@?1 zcb%xyhaKMcaN3gMr|o-E6#att22=$P;a#K(uJ3|2Noa;s!fbh}FkqasW(`|avI}La zoEpp8?N53J=^gVp~s>wnQ76s*r7n8UOF_GhNU zvshodzplP|E*u=qy6gO3^oVlS+n%%j_Ghua_GjI(-g=Vtzs&ky^an-XQ}@!-mXK-I zUFomZ`Z1$*SNemZ>ef+p)&Gjs|1#@;(I4cwt3AHH_GevP^*HN)#p-{VUDSi9lYyJE z`LR@%Y%LY7OfFMiMP4Y4SUqt4uUP#rv;G(TL7vUJ=lWl<`d{Y!U!3G7I*eGbi=})n fmCcm5Su;@;MYK((ZgP+o+g1N7R{zV48vOhJI9i9! diff --git a/genwasym_runtime/build/libgenwasym.so b/genwasym_runtime/build/libgenwasym.so deleted file mode 100755 index 07e0be4e6649b90d3f267d8640b07e32d6b36d58..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 305360 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¨d}gFL&5cJokskPR+RIJ5^EOF4+Ui z^H)a7ze|d0d6!==-SV0873W`{y!-L4gP%P4_1gYfhZn6XUKJ92{@AS%_1+oNJFWSu zy`6?HX%g?7u+?;7Vk_ZD%9rZDS~ncK{pkqPp0WQq-r~RO%l~T9s&~SV%{v8r(l+tJ zN1D#JF8O@((Fv{AKljq~#+%lgems^s{I;_bW14+=f62I=KOJ1W=l$Qe-uG(W)TG5z z_U&yDvA6RR6W@M6s#&-3N8fMv&pR(aw6N2doX+=b<=N)_0RuW*YWEy z!I7nlpMH4uq=a?)M|P);Ff1<3RrT|qyY%Z3KQ&x_xyKLZpF8nc>4ML-M|M3mLp^e{ zA?nVb`qh7Q`43O@jsN_uM^~OMoYuPc(U^LJe!e~TgQ$&{cXxijpv&o(b|i)FJo*0K zo-I1a#!dA7+QsV|mk=gfsS32>#Ly55`6*+m^>wOY>YSqMHYxgf}GI_zF z`8&3i{Na;0Vg0ka-!g=reB#k_4K92%_O4mO2KsyxclVir6H1>5t(Uq*7&WrntchQY mITDvJG Date: Wed, 13 May 2026 14:07:46 -0400 Subject: [PATCH 3/4] remove unused files --- genwasym_runtime/include/gensym.hpp | 69 -------- genwasym_runtime/include/gensym_client.h | 160 ------------------ .../include/wasm_state_continue.hpp | 94 ---------- 3 files changed, 323 deletions(-) delete mode 100644 genwasym_runtime/include/gensym.hpp delete mode 100644 genwasym_runtime/include/gensym_client.h delete mode 100644 genwasym_runtime/include/wasm_state_continue.hpp diff --git a/genwasym_runtime/include/gensym.hpp b/genwasym_runtime/include/gensym.hpp deleted file mode 100644 index 2067458ac..000000000 --- a/genwasym_runtime/include/gensym.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef GS_HEADERS -#define GS_HEADERS - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef PURE_STATE -#include -#endif -#ifdef IMPURE_STATE -#include -#endif - -#include -#include -#include - -#ifdef PURE_STATE -#include -#endif - -#ifdef IMPURE_STATE -#include -#endif - -#include - -#endif diff --git a/genwasym_runtime/include/gensym_client.h b/genwasym_runtime/include/gensym_client.h deleted file mode 100644 index bf8592598..000000000 --- a/genwasym_runtime/include/gensym_client.h +++ /dev/null @@ -1,160 +0,0 @@ -#ifndef GS_CLIENT_HEADERS -#define GS_CLIENT_HEADERS - -#include -#include -#include -#include -#include - -/* Construct `byte_size` 8-bitvectors starting at address `addr`. - */ -void make_symbolic(void* addr, size_t byte_size, ...); - -/* Construct a single bitvector of size `byte_size`*8 at address `addr`. - */ -void make_symbolic_whole(void* addr, size_t byte_size, ...); - -#define gs_make_symbolic(addr, byte_size, name) make_symbolic(addr, byte_size, name) -#define gs_make_symbolic_whole(addr, byte_size, name) make_symbolic_whole(addr, byte_size, name) - -void gs_assert(bool, ...); -void gs_assert_eager(bool, ...); - -void gs_assume(bool); -void sym_print(int, ...); -void print_string(const char *message); -void sym_exit(int); - -/* gs_range - Construct a symbolic value in the signed interval - * [begin,end). - * - * \arg name - A name used for identifying the object in messages, output - * files, etc. If NULL, object is called "unnamed". - */ -static inline int gs_range(int begin, int end, const char *name) { - int x; - gs_make_symbolic(&x, sizeof(x), name); - gs_assume(x >= begin); - gs_assume(x < end); - return x; -} - -__attribute__((noreturn)) -void stop(int status); - -/* gs_report_error - Report a user defined error and terminate the current - * gensym process. - * - * \arg file - The filename to report in the error message. - * \arg line - The line number to report in the error message. - * \arg message - A string to include in the error message. - * \arg suffix - The suffix to use for error files. - */ -__attribute__((noreturn)) -static inline void gs_report_error(const char *file, - int line, - const char *message, - const char *suffix) { - print_string(file); - sym_print(line); - print_string(message); - print_string(suffix); - print_string("\n"); - stop(-1); -} - -static inline void gs_warning(const char *message) { - print_string(message); - print_string("\n"); -} - -void gs_warning_once(const char *message); - -void gs_prefer_cex(void *object, bool condition); -void gs_posix_prefer_cex(void *object, bool condition); - -/* return whether n is a symbolic value */ -unsigned gs_is_symbolic(uintptr_t n); - -/* return a feasible concrete value of expr */ -long gs_get_valuel(long expr); - -/* Support for running test-comp examples */ - -static inline void __VERIFIER_error(void) { gs_assert_eager(0); } -static inline void __VERIFIER_assert(int cond) { gs_assert_eager(cond); } -static inline void reach_error() { - fprintf(stderr, "error reached\n"); - gs_assert_eager(0); -} -static inline void __VERIFIER_assume(int x) { /* TODO */ } - -static inline int __VERIFIER_nondet_int(void) { - int x; - make_symbolic_whole(&x, sizeof(int)); - return x; -} - -static inline unsigned int __VERIFIER_nondet_uint(void) { - unsigned int x; - make_symbolic_whole(&x, sizeof(x)); - return x; -} - -typedef unsigned int u32; -static inline u32 __VERIFIER_nondet_u32(void) { - return __VERIFIER_nondet_uint(); -} - -static inline unsigned __VERIFIER_nondet_unsigned(void) { - unsigned x; - make_symbolic_whole(&x, sizeof(x)); - return x; -} - -static inline short __VERIFIER_nondet_short(void) { - short x; - make_symbolic_whole(&x, sizeof(x)); - return x; -} - -static inline unsigned short __VERIFIER_nondet_ushort(void) { - unsigned short x; - make_symbolic_whole(&x, sizeof(x)); - return x; -} - -static inline char __VERIFIER_nondet_char(void) { - char x; - make_symbolic_whole(&x, sizeof(x)); - return x; -} - -static inline unsigned char __VERIFIER_nondet_uchar(void) { - unsigned char x; - make_symbolic_whole(&x, sizeof(x)); - return x; -} - -static inline long __VERIFIER_nondet_long(void) { - long x; - make_symbolic_whole(&x, sizeof(x)); - return x; -} - -static inline unsigned long __VERIFIER_nondet_ulong(void) { - unsigned long x; - make_symbolic_whole(&x, sizeof(x)); - return x; -} - -/* Note: to be supported... -char* __VERIFIER_nondet_pchar(void) { } -float __VERIFIER_nondet_float(void) { } -double __VERIFIER_nondet_double(void) { } -void* __VERIFIER_nondet_pointer(void) { } -_Bool __VERIFIER_nondet_bool(void) { } -*/ - -#endif diff --git a/genwasym_runtime/include/wasm_state_continue.hpp b/genwasym_runtime/include/wasm_state_continue.hpp deleted file mode 100644 index 5fc341197..000000000 --- a/genwasym_runtime/include/wasm_state_continue.hpp +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef WASM_STATE_CONTINUE_HPP -#define WASM_STATE_CONTINUE_HPP - -#include -#include -#include -#include - -#include -#include -#include - -typedef std::function cont_t; - -extern cont_t fun_ret_cont_stack[1000]; -extern int fun_ret_cont_stack_ptr; - -void push_fun_ret_cont_stack(cont_t cont); -std::monostate pop_fun_ret_cont_stack(); - -template -immer::flex_vector flex_vector_reverse(immer::flex_vector v) { - immer::flex_vector result = immer::flex_vector(); - for (auto it = v.rbegin(); it != v.rend(); it++) { - result = result.push_back(*it); - } - return result; -} - -enum ValueTy { - I32, -}; - -struct Value { - ValueTy ty; - union { - int i32; - }; -}; - -Value I32V(int x); - -struct Mem {}; -struct Global {}; - -class State { - public: - immer::flex_vector memory; - immer::flex_vector globals; - size_t stack_ptr = 0; - size_t frame_ptr = 0; - Value stack[1000]; - immer::vector_transient> return_stack; - - size_t tmp_frame_ptr = 0; - - State(immer::flex_vector memory, immer::flex_vector globals); - - Value stack_at(int i); - void push_stack(Value v); - Value pop_stack(); - Value peek_stack(); - void print_stack(); - Value get_local(int i); - void set_local(int i, Value v); - void return_from_fun(int num_locals, int ret_num); - void bump_frame_ptr(); - void set_frame_ptr(int fp); - int get_frame_ptr(); - void save_frame_ptr(); - void restore_frame_ptr(); - void remove_stack_range(int start, int end); - void reverse_top_n(int n); -}; - -extern State global_state; - -State& init_state(immer::flex_vector memory, - immer::flex_vector globals, - int num_locals); - -enum EvalTag { - CONTINUE, - RETURNING, - BREAKING, -}; - -struct EvalResult { - EvalTag tag; - int n; - State state; -}; - -#endif \ No newline at end of file From f054acd88cbeebafa464672526c17c944edc2816 Mon Sep 17 00:00:00 2001 From: Junyu Chen Date: Tue, 19 May 2026 01:18:24 +0800 Subject: [PATCH 4/4] Update C++ compilation command to use GenWasm runtime --- .../genwasym/CppCompilationTestBase.scala | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/test/scala/genwasym/CppCompilationTestBase.scala b/src/test/scala/genwasym/CppCompilationTestBase.scala index 8211bab2f..aaf8e8f53 100644 --- a/src/test/scala/genwasym/CppCompilationTestBase.scala +++ b/src/test/scala/genwasym/CppCompilationTestBase.scala @@ -56,6 +56,26 @@ abstract class CppCompilationTestBase extends FunSuite { .filter(_.isDirectory) .map(_.getCanonicalPath) + protected lazy val genwasymRuntimeIncludeDir: String = { + val fromEnv = sys.env.get("GENWASYM_RUNTIME_INCLUDE_DIR") + val fromRepo = firstExistingDir(Seq("./genwasym_runtime/include")) + fromEnv.orElse(fromRepo).getOrElse { + throw new RuntimeException( + "Cannot locate GenWasm runtime include directory. Set GENWASYM_RUNTIME_INCLUDE_DIR or check genwasym_runtime/include." + ) + } + } + + protected lazy val genwasymRuntimeLibDir: String = { + val fromEnv = sys.env.get("GENWASYM_RUNTIME_LIB_DIR") + val fromRepo = firstExistingDir(Seq("./genwasym_runtime/build")) + fromEnv.orElse(fromRepo).getOrElse { + throw new RuntimeException( + "Cannot locate GenWasm runtime library directory. Set GENWASYM_RUNTIME_LIB_DIR or build genwasym_runtime." + ) + } + } + protected def prependPath(existing: Option[String], prefix: String): String = existing.filter(_.nonEmpty).map(old => s"$prefix:$old").getOrElse(prefix) @@ -64,6 +84,11 @@ abstract class CppCompilationTestBase extends FunSuite { "DYLD_LIBRARY_PATH" -> prependPath(sys.env.get("DYLD_LIBRARY_PATH"), z3LibDir) ) + protected lazy val genwasymRuntimeEnv: Seq[(String, String)] = Seq( + "LD_LIBRARY_PATH" -> prependPath(sys.env.get("LD_LIBRARY_PATH"), genwasymRuntimeLibDir), + "DYLD_LIBRARY_PATH" -> prependPath(sys.env.get("DYLD_LIBRARY_PATH"), genwasymRuntimeLibDir) + ) + protected def compileGeneratedCpp(source: String, headerFolders: Seq[String], outputCpp: String, @@ -113,16 +138,16 @@ abstract class CppCompilationTestBase extends FunSuite { macroDefs: Seq[String] = Seq.empty): Unit = { compileGeneratedCpp( source = source, - headerFolders = headerFolders, + headerFolders = genwasymRuntimeIncludeDir +: headerFolders, outputCpp = outputCpp, outputExe = outputExe, compiler = compiler, optimizeLevel = optimizeLevel, extraIncludeDirs = immerIncludeDirs :+ z3IncludeDir, macroDefs = macroDefs, - libraryDirs = Seq(z3LibDir), - runtimeLibraryDirs = Seq(z3LibDir), - libraries = Seq("z3") + libraryDirs = Seq(genwasymRuntimeLibDir, z3LibDir), + runtimeLibraryDirs = Seq(genwasymRuntimeLibDir, z3LibDir), + libraries = Seq("genwasym", "z3") ) } @@ -130,7 +155,7 @@ abstract class CppCompilationTestBase extends FunSuite { Process(Seq(exePath), None, env: _*).!! protected def runExeWithZ3(exePath: String, extraEnv: Seq[(String, String)] = Seq.empty): String = - runExe(exePath, z3RuntimeEnv ++ extraEnv) + runExe(exePath, z3RuntimeEnv ++ genwasymRuntimeEnv ++ extraEnv) protected def parseStackValues(output: String): List[Float] = { val startMarker = "Stack contents: \n"