// WebAssembly interpreter. Executes decoded modules directly from their bytecode using // precomputed block side tables. Every failure mode is a trap returned to the caller. #include "wasm.h" #include #include #include namespace unify::wasm { namespace { // Portable integer bit operations for GCC, Clang and MSVC. inline uint32_t leading_zeros(uint32_t x) { uint32_t n=32; for (;x;x>>=1) --n; return n; } inline uint32_t trailing_zeros(uint32_t x) { if (!x) return 32; uint32_t n=0; while (!(x&1)) {++n;x>>=1;} return n; } inline uint32_t population(uint32_t x) { uint32_t n=0; while (x) {x&=x-1;++n;} return n; } inline uint32_t f2b(float f) { uint32_t b; std::memcpy(&b, &f, 4); return b; } inline float b2f(uint64_t b) { uint32_t x = uint32_t(b); float f; std::memcpy(&f, &x, 4); return f; } inline uint64_t d2b(double d) { uint64_t b; std::memcpy(&b, &d, 8); return b; } inline double b2d(uint64_t b) { double d; std::memcpy(&d, &b, 8); return d; } struct Code { const uint8_t* base; const uint8_t* p; uint32_t pos() const { return uint32_t(p - base); } uint64_t uleb() { uint64_t r = 0; int s = 0; uint8_t b; do { b = *p++; r |= uint64_t(b & 0x7F) << s; s += 7; } while (b & 0x80); return r; } int64_t sleb() { int64_t r = 0; int s = 0; uint8_t b; do { b = *p++; r |= int64_t(b & 0x7F) << s; s += 7; } while (b & 0x80); if (s < 64 && (b & 0x40)) r |= -(int64_t(1) << s); return r; } }; template F wasm_min(F a, F b) { if (std::isnan(a) || std::isnan(b)) return std::numeric_limits::quiet_NaN(); if (a == b) return std::signbit(a) ? a : b; // min(-0, +0) = -0 return a < b ? a : b; } template F wasm_max(F a, F b) { if (std::isnan(a) || std::isnan(b)) return std::numeric_limits::quiet_NaN(); if (a == b) return std::signbit(a) ? b : a; return a > b ? a : b; } struct Label { uint32_t cont; uint32_t height; uint32_t arity; bool loop; }; } // namespace const FuncType& Instance::type_of(uint32_t idx) const { if (idx < module_->imported_funcs) { uint32_t k = 0; for (auto& im : module_->imports) if (im.kind == 0 && k++ == idx) return module_->types[im.type_index]; } return module_->types[module_->functions[idx - module_->imported_funcs].type_index]; } bool Instance::instantiate(std::shared_ptr module, std::shared_ptr shared_memory, const std::map& host_funcs, std::string& error) { module_ = std::move(module); host_.clear(); for (auto& im : module_->imports) { if (im.kind != 0) continue; auto it = host_funcs.find(im.module + "." + im.name); if (it == host_funcs.end()) { error = "unresolved import " + im.module + "." + im.name; return false; } host_.push_back(it->second); } if (module_->memory_imported) { if (!shared_memory) { error = "module imports a memory but none was provided"; return false; } if (shared_memory->pages() < module_->memory_min) { error = "provided memory is smaller than the import minimum"; return false; } memory_ = shared_memory; } else if (module_->has_memory) { memory_ = std::make_shared(); memory_->max_pages = module_->memory_has_max ? module_->memory_max : 65536; memory_->bytes.assign(size_t(module_->memory_min) * Memory::kPage, 0); } for (auto& d : module_->data) { if (!memory_ || uint64_t(d.offset) + d.bytes.size() > memory_->bytes.size()) { error = "data segment out of bounds"; return false; } std::memcpy(memory_->bytes.data() + d.offset, d.bytes.data(), d.bytes.size()); } globals_.clear(); for (auto& g : module_->globals) globals_.push_back(g.init); return true; } bool Instance::has_export(const std::string& name) const { for (auto& e : module_->exports) if (e.kind == 0 && e.name == name) return true; return false; } ExecResult Instance::call(const std::string& name, const std::vector& args) { for (auto& e : module_->exports) if (e.kind == 0 && e.name == name) return call_index(e.index, args); ExecResult r; r.ok = false; r.trap = "no exported function '" + name + "'"; return r; } ExecResult Instance::call_index(uint32_t idx, const std::vector& args) { ExecResult r; uint32_t total = module_->imported_funcs + uint32_t(module_->functions.size()); if (idx >= total) { r.ok = false; r.trap = "function index out of range"; return r; } const FuncType& t = type_of(idx); if (args.size() != t.params.size()) { r.ok = false; r.trap = "argument count mismatch"; return r; } for (size_t i = 0; i < args.size(); i++) if (args[i].type != t.params[i]) { r.ok = false; r.trap = "argument " + std::to_string(i) + " has the wrong type"; return r; } std::vector stack; stack.reserve(1024); for (auto& a : args) stack.push_back(a.bits); r.ok = run(idx, stack, r.trap, r.instructions, 0); if (r.ok) for (size_t i = 0; i < t.results.size(); i++) r.results.push_back({t.results[i], stack[stack.size() - t.results.size() + i]}); return r; } bool Instance::run(uint32_t idx, std::vector& stack, std::string& trap, uint64_t& executed, uint32_t depth) { if (depth > max_call_depth) { trap = "call stack exhausted"; return false; } const FuncType& ft = type_of(idx); if (idx < module_->imported_funcs) { std::vector args(ft.params.size()), results; for (size_t i = ft.params.size(); i-- > 0;) { args[i] = {ft.params[i], stack.back()}; stack.pop_back(); } if (!host_[idx](args, results, trap)) return false; if (results.size() != ft.results.size()) { trap = "host function returned wrong result count"; return false; } for (auto& v : results) stack.push_back(v.bits); return true; } const Function& fn = module_->functions[idx - module_->imported_funcs]; std::vector locals(ft.params.size() + fn.locals.size(), 0); for (size_t i = ft.params.size(); i-- > 0;) { locals[i] = stack.back(); stack.pop_back(); } const uint32_t base_height = uint32_t(stack.size()); std::vector