From c420a2a6ab4f17733396d7eddacb6c299f6cbb90 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Sat, 5 Jul 2025 21:54:20 +0800 Subject: [PATCH 01/53] remove unused expressions --- src/main/scala/wasm/StagedMiniWasm.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/scala/wasm/StagedMiniWasm.scala b/src/main/scala/wasm/StagedMiniWasm.scala index 2e22e7a7..bfa2082d 100644 --- a/src/main/scala/wasm/StagedMiniWasm.scala +++ b/src/main/scala/wasm/StagedMiniWasm.scala @@ -433,10 +433,10 @@ trait StagedWasmEvaluator extends SAIOps { def push(v: StagedNum)(implicit ctx: Context): Context = { v match { - case I32(v) => NumType(I32Type); "stack-push".reflectCtrlWith[Unit](v) - case I64(v) => NumType(I64Type); "stack-push".reflectCtrlWith[Unit](v) - case F32(v) => NumType(F32Type); "stack-push".reflectCtrlWith[Unit](v) - case F64(v) => NumType(F64Type); "stack-push".reflectCtrlWith[Unit](v) + case I32(v) => "stack-push".reflectCtrlWith[Unit](v) + case I64(v) => "stack-push".reflectCtrlWith[Unit](v) + case F32(v) => "stack-push".reflectCtrlWith[Unit](v) + case F64(v) => "stack-push".reflectCtrlWith[Unit](v) } ctx.push(v.tipe) } From fb2a2c4e203fba7f571b3142ed2f739e6a7ed5ad Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Mon, 7 Jul 2025 14:31:16 +0800 Subject: [PATCH 02/53] let's start from the staged miniwasm interpreter --- .../scala/wasm/StagedConcolicMiniWasm.scala | 1170 +++++++++++++++++ 1 file changed, 1170 insertions(+) create mode 100644 src/main/scala/wasm/StagedConcolicMiniWasm.scala diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala new file mode 100644 index 00000000..6b14bcf6 --- /dev/null +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -0,0 +1,1170 @@ +package gensym.wasm.stagedconcolicminiwasm + +import scala.collection.mutable.{ArrayBuffer, HashMap} + +import lms.core.stub.Adapter +import lms.core.virtualize +import lms.macros.SourceContext +import lms.core.stub.{Base, ScalaGenBase, CGenBase} +import lms.core.Backend._ +import lms.core.Backend.{Block => LMSBlock, Const => LMSConst} +import lms.core.Graph + +import gensym.wasm.ast._ +import gensym.wasm.ast.{Const => WasmConst, Block => WasmBlock} +import gensym.wasm.miniwasm.{ModuleInstance} +import gensym.wasm.ast.{Const => WasmConst, Block => WasmBlock} +import gensym.lmsx.{SAIDriver, StringOps, SAIOps, SAICodeGenBase, CppSAIDriver, CppSAICodeGenBase} + +@virtualize +trait StagedWasmEvaluator extends SAIOps { + def module: ModuleInstance + + trait ReturnSite + + trait StagedNum { + def tipe: ValueType = this match { + case I32(_) => NumType(I32Type) + case I64(_) => NumType(I64Type) + case F32(_) => NumType(F32Type) + case F64(_) => NumType(F64Type) + } + + def i: Rep[Num] + } + case class I32(i: Rep[Num]) extends StagedNum + case class I64(i: Rep[Num]) extends StagedNum + case class F32(i: Rep[Num]) extends StagedNum + case class F64(i: Rep[Num]) extends StagedNum + + implicit def toStagedNum(num: Num): StagedNum = { + num match { + case I32V(_) => I32(num) + case I64V(_) => I64(num) + case F32V(_) => F32(num) + case F64V(_) => F64(num) + } + } + + implicit class ValueTypeOps(ty: ValueType) { + def size: Int = ty match { + case NumType(I32Type) => 4 + case NumType(I64Type) => 8 + case NumType(F32Type) => 4 + case NumType(F64Type) => 8 + } + } + + case class Context( + stackTypes: List[ValueType], + frameTypes: List[ValueType] + ) { + def push(ty: ValueType): Context = { + Context(ty :: stackTypes, frameTypes) + } + + def pop(): (ValueType, Context) = { + val (ty :: rest) = stackTypes + (ty, Context(rest, frameTypes)) + } + + def shift(offset: Int, size: Int): Context = { + // Predef.println(s"[DEBUG] Shifting stack by $offset, size $size, $this") + Predef.assert(offset >= 0, s"Context shift offset must be non-negative, get $offset") + if (offset == 0) { + this + } else { + this.copy( + stackTypes = stackTypes.take(size) ++ stackTypes.drop(offset + size) + ) + } + } + } + + type MCont[A] = Unit => A + type Cont[A] = (MCont[A]) => A + type Trail[A] = List[Context => Rep[Cont[A]]] + + // a cache storing the compiled code for each function, to reduce re-compilation + val compileCache = new HashMap[Int, Rep[(MCont[Unit]) => Unit]] + + def makeDummy: Rep[Unit] = "dummy".reflectCtrlWith[Unit]() + + def funHere[A:Manifest,B:Manifest](f: Rep[A] => Rep[B], dummy: Rep[Unit]): Rep[A => B] = { + // to avoid LMS lifting a function, we create a dummy node and read it inside function + fun((x: Rep[A]) => { + "dummy-op".reflectCtrlWith[Unit](dummy) + f(x) + }) + } + + + def eval(insts: List[Instr], + kont: Context => Rep[Cont[Unit]], + mkont: Rep[MCont[Unit]], + trail: Trail[Unit]) + (implicit ctx: Context): Rep[Unit] = { + if (insts.isEmpty) return kont(ctx)(mkont) + + // Predef.println(s"[DEBUG] Evaluating instructions: ${insts.mkString(", ")}") + // Predef.println(s"[DEBUG] Current context: $ctx") + + val (inst, rest) = (insts.head, insts.tail) + inst match { + case Drop => + val (_, newCtx) = Stack.pop() + eval(rest, kont, mkont, trail)(newCtx) + case WasmConst(num) => + val newCtx = Stack.push(num) + eval(rest, kont, mkont, trail)(newCtx) + case LocalGet(i) => + val newCtx = Stack.push(Frames.get(i)) + eval(rest, kont, mkont, trail)(newCtx) + case LocalSet(i) => + val (num, newCtx) = Stack.pop() + Frames.set(i, num)(newCtx) + eval(rest, kont, mkont, trail)(newCtx) + case LocalTee(i) => + val (num, newCtx) = Stack.peek + Frames.set(i, num) + eval(rest, kont, mkont, trail)(newCtx) + case GlobalGet(i) => + val newCtx = Stack.push(Globals(i)) + eval(rest, kont, mkont, trail)(newCtx) + case GlobalSet(i) => + val (value, newCtx) = Stack.pop() + module.globals(i).ty match { + case GlobalType(tipe, true) => Globals(i) = value + case _ => throw new Exception("Cannot set immutable global") + } + eval(rest, kont, mkont, trail)(newCtx) + case Store(StoreOp(align, offset, ty, None)) => + val (value, newCtx1) = Stack.pop() + val (addr, newCtx2) = Stack.pop()(newCtx1) + Memory.storeInt(addr.toInt, offset, value.toInt) + eval(rest, kont, mkont, trail)(newCtx2) + case Nop => eval(rest, kont, mkont, trail) + case Load(LoadOp(align, offset, ty, None, None)) => + val (addr, newCtx1) = Stack.pop() + val value = Memory.loadInt(addr.toInt, offset) + val newCtx2 = Stack.push(Values.I32V(value))(newCtx1) + eval(rest, kont, mkont, trail)(newCtx2) + case MemorySize => ??? + case MemoryGrow => + val (delta, newCtx1) = Stack.pop() + val newCtx2 = Stack.push(Values.I32V(Memory.grow(delta.toInt)))(newCtx1) + eval(rest, kont, mkont, trail)(newCtx2) + case MemoryFill => ??? + case Unreachable => unreachable() + case Test(op) => + val (v, newCtx1) = Stack.pop() + val newCtx2 = Stack.push(evalTestOp(op, v))(newCtx1) + eval(rest, kont, mkont, trail)(newCtx2) + case Unary(op) => + val (v, newCtx1) = Stack.pop() + val newCtx2 = Stack.push(evalUnaryOp(op, v))(newCtx1) + eval(rest, kont, mkont, trail)(newCtx2) + case Binary(op) => + val (v2, newCtx1) = Stack.pop() + val (v1, newCtx2) = Stack.pop()(newCtx1) + val newCtx3 = Stack.push(evalBinOp(op, v1, v2))(newCtx2) + eval(rest, kont, mkont, trail)(newCtx3) + case Compare(op) => + val (v2, newCtx1) = Stack.pop() + val (v1, newCtx2) = Stack.pop()(newCtx1) + val newCtx3 = Stack.push(evalRelOp(op, v1, v2))(newCtx2) + eval(rest, kont, mkont, trail)(newCtx3) + case WasmBlock(ty, inner) => + // no need to modify the stack when entering a block + // the type system guarantees that we will never take more than the input size from the stack + val funcTy = ty.funcType + val exitSize = ctx.stackTypes.size - funcTy.inps.size + funcTy.out.size + val dummy = makeDummy + def restK(restCtx: Context): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { + info(s"Exiting the block, stackSize =", Stack.size) + val offset = restCtx.stackTypes.size - exitSize + val newRestCtx = Stack.shift(offset, funcTy.out.size)(restCtx) + eval(rest, kont, mk, trail)(newRestCtx) + }) + eval(inner, restK _, mkont, restK _ :: trail) + case Loop(ty, inner) => + val funcTy = ty.funcType + val exitSize = ctx.stackTypes.size - funcTy.inps.size + funcTy.out.size + val dummy = makeDummy + def restK(restCtx: Context): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { + info(s"Exiting the loop, stackSize =", Stack.size) + val offset = restCtx.stackTypes.size - exitSize + val newRestCtx = Stack.shift(offset, funcTy.out.size)(restCtx) + eval(rest, kont, mk, trail)(newRestCtx) + }) + val enterSize = ctx.stackTypes.size + def loop(restCtx: Context): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { + info(s"Entered the loop, stackSize =", Stack.size) + val offset = restCtx.stackTypes.size - enterSize + val newRestCtx = Stack.shift(offset, funcTy.inps.size)(restCtx) + eval(inner, restK _, mk, loop _ :: trail)(newRestCtx) + }) + loop(ctx)(mkont) + case If(ty, thn, els) => + val funcTy = ty.funcType + val (cond, newCtx) = Stack.pop() + val exitSize = newCtx.stackTypes.size - funcTy.inps.size + funcTy.out.size + // TODO: can we avoid code duplication here? + val dummy = makeDummy + def restK(restCtx: Context): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { + info(s"Exiting the if, stackSize =", Stack.size) + val offset = restCtx.stackTypes.size - exitSize + val newRestCtx = Stack.shift(offset, funcTy.out.size)(restCtx) + eval(rest, kont, mk, trail)(newRestCtx) + }) + if (cond.toInt != 0) { + eval(thn, restK _, mkont, restK _ :: trail)(newCtx) + } else { + eval(els, restK _, mkont, restK _ :: trail)(newCtx) + } + () + case Br(label) => + info(s"Jump to $label") + trail(label)(ctx)(mkont) + case BrIf(label) => + val (cond, newCtx) = Stack.pop() + info(s"The br_if(${label})'s condition is ", cond.toInt) + if (cond.toInt != 0) { + info(s"Jump to $label") + trail(label)(newCtx)(mkont) + } else { + info(s"Continue") + eval(rest, kont, mkont, trail)(newCtx) + } + () + case BrTable(labels, default) => + val (cond, newCtx) = Stack.pop() + def aux(choices: List[Int], idx: Int): Rep[Unit] = { + if (choices.isEmpty) trail(default)(newCtx)(mkont) + else { + if (cond.toInt == idx) trail(choices.head)(newCtx)(mkont) + else aux(choices.tail, idx + 1) + } + } + aux(labels, 0) + case Return => trail.last(ctx)(mkont) + case Call(f) => evalCall(rest, kont, mkont, trail, f, false) + case ReturnCall(f) => evalCall(rest, kont, mkont, trail, f, true) + case _ => + val todo = "todo-op".reflectCtrlWith[Unit]() + eval(rest, kont, mkont, trail) + } + } + + def forwardKont: Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => mk(())) + + + def evalCall(rest: List[Instr], + kont: Context => Rep[Cont[Unit]], + mkont: Rep[MCont[Unit]], + trail: Trail[Unit], + funcIndex: Int, + isTail: Boolean) + (implicit ctx: Context): Rep[Unit] = { + module.funcs(funcIndex) match { + case FuncDef(_, FuncBodyDef(ty, _, bodyLocals, body)) => + val locals = bodyLocals ++ ty.inps + val callee = + if (compileCache.contains(funcIndex)) { + compileCache(funcIndex) + } else { + val callee = topFun((mk: Rep[MCont[Unit]]) => { + info(s"Entered the function at $funcIndex, stackSize =", Stack.size) + // we can do some check here to ensure the function returns correct size of stack + eval(body, (_: Context) => forwardKont, mk, ((_: Context) => forwardKont)::Nil)(Context(Nil, locals)) + }) + compileCache(funcIndex) = callee + callee + } + // Predef.println(s"[DEBUG] locals size: ${locals.size}") + val (args, newCtx) = Stack.take(ty.inps.size) + if (isTail) { + // when tail call, return to the caller's return continuation + Frames.popFrame(ctx.frameTypes.size) + Frames.pushFrame(locals) + Frames.putAll(args) + callee(mkont) + } else { + // We make a new trail by `restK`, since function creates a new block to escape + // (more or less like `return`) + val restK: Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { + info(s"Exiting the function at $funcIndex, stackSize =", Stack.size) + Frames.popFrame(locals.size) + eval(rest, kont, mk, trail)(newCtx.copy(stackTypes = ty.out.reverse ++ ctx.stackTypes.drop(ty.inps.size))) + }) + val dummy = makeDummy + val newMKont: Rep[MCont[Unit]] = funHere((_u: Rep[Unit]) => { + restK(mkont) + }, dummy) + Frames.pushFrame(locals) + Frames.putAll(args) + callee(newMKont) + } + case Import("console", "log", _) + | Import("spectest", "print_i32", _) => + //println(s"[DEBUG] current stack: $stack") + val (v, newCtx) = Stack.pop() + println(v.toInt) + eval(rest, kont, mkont, trail)(newCtx) + case Import(_, _, _) => throw new Exception(s"Unknown import at $funcIndex") + case _ => throw new Exception(s"Definition at $funcIndex is not callable") + } + } + + def evalTestOp(op: TestOp, value: StagedNum): StagedNum = op match { + case Eqz(_) => Values.I32V(if (value.toInt == 0) 1 else 0) + } + + def evalUnaryOp(op: UnaryOp, value: StagedNum): StagedNum = op match { + case Clz(_) => value.clz() + case Ctz(_) => value.ctz() + case Popcnt(_) => value.popcnt() + case _ => ??? + } + + def evalBinOp(op: BinOp, v1: StagedNum, v2: StagedNum): StagedNum = op match { + case Add(_) => v1 + v2 + case Mul(_) => v1 * v2 + case Sub(_) => v1 - v2 + case Shl(_) => v1 << v2 + // case ShrS(_) => v1 >> v2 // TODO: signed shift right + case ShrU(_) => v1 >> v2 + case And(_) => v1 & v2 + case DivS(_) => v1 / v2 + case DivU(_) => v1 / v2 + case _ => + throw new Exception(s"Unknown binary operation $op") + } + + def evalRelOp(op: RelOp, v1: StagedNum, v2: StagedNum): StagedNum = op match { + case Eq(_) => v1 numEq v2 + case Ne(_) => v1 numNe v2 + case LtS(_) => v1 < v2 + case LtU(_) => v1 ltu v2 + case GtS(_) => v1 > v2 + case GtU(_) => v1 gtu v2 + case LeS(_) => v1 <= v2 + case LeU(_) => v1 leu v2 + case GeS(_) => v1 >= v2 + case GeU(_) => v1 geu v2 + case _ => ??? + } + + def evalTop(mkont: Rep[MCont[Unit]], main: Option[String]): Rep[Unit] = { + val funBody: FuncBodyDef = main match { + case Some(func_name) => + module.defs.flatMap({ + case Export(`func_name`, ExportFunc(fid)) => + Predef.println(s"Now compiling start with function $main") + module.funcs(fid) match { + case FuncDef(_, body@FuncBodyDef(_,_,_,_)) => Some(body) + case _ => throw new Exception("Entry function has no concrete body") + } + case _ => None + }).head + case None => + val startIds = module.defs.flatMap { + case Start(id) => Some(id) + case _ => None + } + val startId = startIds.headOption.getOrElse { throw new Exception("No start function") } + module.funcs(startId) match { + case FuncDef(_, body@FuncBodyDef(_,_,_,_)) => body + case _ => + throw new Exception("Entry function has no concrete body") + } + } + val (instrs, locals) = (funBody.body, funBody.locals) + Stack.initialize() + Frames.pushFrame(locals) + eval(instrs, (_: Context) => forwardKont, mkont, ((_: Context) => forwardKont)::Nil)(Context(Nil, locals)) + Frames.popFrame(locals.size) + } + + def evalTop(main: Option[String], printRes: Boolean = false): Rep[Unit] = { + val haltK: Rep[Unit] => Rep[Unit] = (_) => { + info("Exiting the program...") + if (printRes) { + Stack.print() + } + "no-op".reflectCtrlWith[Unit]() + } + val temp: Rep[MCont[Unit]] = topFun(haltK) + evalTop(temp, main) + } + + // stack operations + object Stack { + def shift(offset: Int, size: Int)(ctx: Context): Context = { + if (offset > 0) { + "stack-shift".reflectCtrlWith[Unit](offset, size) + } + ctx.shift(offset, size) + } + + def initialize(): Rep[Unit] = { + "stack-init".reflectCtrlWith[Unit]() + } + + def pop()(implicit ctx: Context): (StagedNum, Context) = { + val (ty, newContext) = ctx.pop() + val num = ty match { + case NumType(I32Type) => I32("stack-pop".reflectCtrlWith[Num]()) + case NumType(I64Type) => I64("stack-pop".reflectCtrlWith[Num]()) + case NumType(F32Type) => F32("stack-pop".reflectCtrlWith[Num]()) + case NumType(F32Type) => F64("stack-pop".reflectCtrlWith[Num]()) + } + (num, newContext) + } + + def peek(implicit ctx: Context): (StagedNum, Context) = { + val ty = ctx.stackTypes.head + val num = ty match { + case NumType(I32Type) => I32("stack-peek".reflectCtrlWith[Num]()) + case NumType(I64Type) => I64("stack-peek".reflectCtrlWith[Num]()) + case NumType(F32Type) => F32("stack-peek".reflectCtrlWith[Num]()) + case NumType(F32Type) => F64("stack-peek".reflectCtrlWith[Num]()) + } + (num, ctx) + } + + def push(v: StagedNum)(implicit ctx: Context): Context = { + v match { + case I32(v) => "stack-push".reflectCtrlWith[Unit](v) + case I64(v) => "stack-push".reflectCtrlWith[Unit](v) + case F32(v) => "stack-push".reflectCtrlWith[Unit](v) + case F64(v) => "stack-push".reflectCtrlWith[Unit](v) + } + ctx.push(v.tipe) + } + + def take(n: Int)(implicit ctx: Context): (List[StagedNum], Context) = n match { + case 0 => (Nil, ctx) + case n => + val (v, newCtx1) = pop() + val (rest, newCtx2) = take(n - 1) + (v::rest, newCtx2) + } + + def drop(n: Int)(implicit ctx: Context): Context = { + take(n)._2 + } + + def shift(offset: Rep[Int], size: Rep[Int]): Rep[Unit] = { + if (offset > 0) { + "stack-shift".reflectCtrlWith[Unit](offset, size) + } + } + + def print(): Rep[Unit] = { + "stack-print".reflectCtrlWith[Unit]() + } + + def size: Rep[Int] = { + "stack-size".reflectCtrlWith[Int]() + } + } + + object Frames { + def get(i: Int)(implicit ctx: Context): StagedNum = { + // val offset = ctx.frameTypes.take(i).map(_.size).sum + ctx.frameTypes(i) match { + case NumType(I32Type) => I32("frame-get".reflectCtrlWith[Num](i)) + case NumType(I64Type) => I64("frame-get".reflectCtrlWith[Num](i)) + case NumType(F32Type) => F32("frame-get".reflectCtrlWith[Num](i)) + case NumType(F64Type) => F64("frame-get".reflectCtrlWith[Num](i)) + } + } + + def set(i: Int, v: StagedNum)(implicit ctx: Context): Rep[Unit] = { + // val offset = ctx.frameTypes.take(i).map(_.size).sum + v match { + case I32(v) => "frame-set".reflectCtrlWith[Unit](i, v) + case I64(v) => "frame-set".reflectCtrlWith[Unit](i, v) + case F32(v) => "frame-set".reflectCtrlWith[Unit](i, v) + case F64(v) => "frame-set".reflectCtrlWith[Unit](i, v) + } + } + + def pushFrame(locals: List[ValueType]): Rep[Unit] = { + // Predef.println(s"[DEBUG] push frame: $locals") + val size = locals.size + "frame-push".reflectCtrlWith[Unit](size) + } + + def popFrame(size: Int): Rep[Unit] = { + "frame-pop".reflectCtrlWith[Unit](size) + } + + def putAll(args: List[StagedNum])(implicit ctx: Context): Rep[Unit] = { + for ((arg, i) <- args.view.reverse.zipWithIndex) { + Frames.set(i, arg) + } + } + } + + object Memory { + def storeInt(base: Rep[Int], offset: Int, value: Rep[Int]): Rep[Unit] = { + "memory-store-int".reflectCtrlWith[Unit](base, offset, value) + } + + def loadInt(base: Rep[Int], offset: Int): Rep[Int] = { + "memory-load-int".reflectCtrlWith[Int](base, offset) + } + + def grow(delta: Rep[Int]): Rep[Int] = { + "memory-grow".reflectCtrlWith[Int](delta) + } + } + + // call unreachable + def unreachable(): Rep[Unit] = { + "unreachable".reflectCtrlWith[Unit]() + } + + def info(xs: Rep[_]*): Rep[Unit] = { + "info".reflectCtrlWith[Unit](xs: _*) + } + + // runtime values + object Values { + def I32V(i: Rep[Int]): StagedNum = { + I32("I32V".reflectCtrlWith[Num](i)) + } + + def I64V(i: Rep[Long]): StagedNum = { + I64("I64V".reflectCtrlWith[Num](i)) + } + } + + // global read/write + object Globals { + def apply(i: Int): StagedNum = { + module.globals(i).ty match { + case GlobalType(NumType(I32Type), _) => I32("global-get".reflectCtrlWith[Num](i)) + case GlobalType(NumType(I64Type), _) => I64("global-get".reflectCtrlWith[Num](i)) + case GlobalType(NumType(F32Type), _) => F32("global-get".reflectCtrlWith[Num](i)) + case GlobalType(NumType(F64Type), _) => F64("global-get".reflectCtrlWith[Num](i)) + } + } + + def update(i: Int, v: StagedNum): Rep[Unit] = { + module.globals(i).ty match { + case GlobalType(NumType(I32Type), _) => "global-set".reflectCtrlWith[Unit](i) + case GlobalType(NumType(I64Type), _) => "global-set".reflectCtrlWith[Unit](i) + case GlobalType(NumType(F32Type), _) => "global-set".reflectCtrlWith[Unit](i) + case GlobalType(NumType(F64Type), _) => "global-set".reflectCtrlWith[Unit](i) + } + } + } + + // runtime Num type + implicit class StagedNumOps(num: StagedNum) { + + def toInt: Rep[Int] = "num-to-int".reflectCtrlWith[Int](num.i) + + def clz(): StagedNum = num match { + case I32(i) => I32("clz".reflectCtrlWith[Num](i)) + case I64(i) => I64("clz".reflectCtrlWith[Num](i)) + } + + def ctz(): StagedNum = num match { + case I32(i) => I32("ctz".reflectCtrlWith[Num](i)) + case I64(i) => I64("ctz".reflectCtrlWith[Num](i)) + } + + def popcnt(): StagedNum = num match { + case I32(i) => I32("popcnt".reflectCtrlWith[Num](i)) + case I64(i) => I64("popcnt".reflectCtrlWith[Num](i)) + } + + def +(rhs: StagedNum): StagedNum = { + (num, rhs) match { + case (I32(x), I32(y)) => I32("binary-add".reflectCtrlWith[Num](x, y)) + case (I64(x), I64(y)) => I64("binary-add".reflectCtrlWith[Num](x, y)) + case (F32(x), F32(y)) => F32("binary-add".reflectCtrlWith[Num](x, y)) + case (F64(x), F64(y)) => F64("binary-add".reflectCtrlWith[Num](x, y)) + } + } + + def -(rhs: StagedNum): StagedNum = { + (num, rhs) match { + case (I32(x), I32(y)) => I32("binary-sub".reflectCtrlWith[Num](x, y)) + case (I64(x), I64(y)) => I64("binary-sub".reflectCtrlWith[Num](x, y)) + case (F32(x), F32(y)) => F32("binary-sub".reflectCtrlWith[Num](x, y)) + case (F64(x), F64(y)) => F64("binary-sub".reflectCtrlWith[Num](x, y)) + } + } + + def *(rhs: StagedNum): StagedNum = { + (num, rhs) match { + case (I32(x), I32(y)) => I32("binary-mul".reflectCtrlWith[Num](x, y)) + case (I64(x), I64(y)) => I64("binary-mul".reflectCtrlWith[Num](x, y)) + case (F32(x), F32(y)) => F32("binary-mul".reflectCtrlWith[Num](x, y)) + case (F64(x), F64(y)) => F64("binary-mul".reflectCtrlWith[Num](x, y)) + } + } + + def /(rhs: StagedNum): StagedNum = { + (num, rhs) match { + case (I32(x), I32(y)) => I32("binary-div".reflectCtrlWith[Num](x, y)) + case (I64(x), I64(y)) => I64("binary-div".reflectCtrlWith[Num](x, y)) + case (F32(x), F32(y)) => F32("binary-div".reflectCtrlWith[Num](x, y)) + case (F64(x), F64(y)) => F64("binary-div".reflectCtrlWith[Num](x, y)) + } + } + + def <<(rhs: StagedNum): StagedNum = { + (num, rhs) match { + case (I32(x), I32(y)) => I32("binary-shl".reflectCtrlWith[Num](x, y)) + case (I64(x), I64(y)) => I64("binary-shl".reflectCtrlWith[Num](x, y)) + } + } + + def >>(rhs: StagedNum): StagedNum = { + (num, rhs) match { + case (I32(x), I32(y)) => I32("binary-shr".reflectCtrlWith[Num](x, y)) + case (I64(x), I64(y)) => I64("binary-shr".reflectCtrlWith[Num](x, y)) + } + } + + def &(rhs: StagedNum): StagedNum = { + (num, rhs) match { + case (I32(x), I32(y)) => I32("binary-and".reflectCtrlWith[Num](x, y)) + case (I64(x), I64(y)) => I64("binary-and".reflectCtrlWith[Num](x, y)) + } + } + + def numEq(rhs: StagedNum): StagedNum = { + (num, rhs) match { + case (I32(x), I32(y)) => I32("relation-eq".reflectCtrlWith[Num](x, y)) + case (I64(x), I64(y)) => I32("relation-eq".reflectCtrlWith[Num](x, y)) + } + } + + def numNe(rhs: StagedNum): StagedNum = { + (num, rhs) match { + case (I32(x), I32(y)) => I32("relation-ne".reflectCtrlWith[Num](x, y)) + case (I64(x), I64(y)) => I32("relation-ne".reflectCtrlWith[Num](x, y)) + } + } + + def <(rhs: StagedNum): StagedNum = { + (num, rhs) match { + case (I32(x), I32(y)) => I32("relation-lt".reflectCtrlWith[Num](x, y)) + case (I64(x), I64(y)) => I32("relation-lt".reflectCtrlWith[Num](x, y)) + } + } + + def ltu(rhs: StagedNum): StagedNum = { + (num, rhs) match { + case (I32(x), I32(y)) => I32("relation-ltu".reflectCtrlWith[Num](x, y)) + case (I64(x), I64(y)) => I32("relation-ltu".reflectCtrlWith[Num](x, y)) + } + } + + def >(rhs: StagedNum): StagedNum = { + (num, rhs) match { + case (I32(x), I32(y)) => I32("relation-gt".reflectCtrlWith[Num](x, y)) + case (I64(x), I64(y)) => I32("relation-gt".reflectCtrlWith[Num](x, y)) + } + } + + def gtu(rhs: StagedNum): StagedNum = { + (num, rhs) match { + case (I32(x), I32(y)) => I32("relation-gtu".reflectCtrlWith[Num](x, y)) + case (I64(x), I64(y)) => I32("relation-gtu".reflectCtrlWith[Num](x, y)) + } + } + + def <=(rhs: StagedNum): StagedNum = { + (num, rhs) match { + case (I32(x), I32(y)) => I32("relation-le".reflectCtrlWith[Num](x, y)) + case (I64(x), I64(y)) => I32("relation-le".reflectCtrlWith[Num](x, y)) + } + } + + def leu(rhs: StagedNum): StagedNum = { + (num, rhs) match { + case (I32(x), I32(y)) => I32("relation-leu".reflectCtrlWith[Num](x, y)) + case (I64(x), I64(y)) => I32("relation-leu".reflectCtrlWith[Num](x, y)) + } + } + + def >=(rhs: StagedNum): StagedNum = { + (num, rhs) match { + case (I32(x), I32(y)) => I32("relation-ge".reflectCtrlWith[Num](x, y)) + case (I64(x), I64(y)) => I32("relation-ge".reflectCtrlWith[Num](x, y)) + } + } + + def geu(rhs: StagedNum): StagedNum = { + (num, rhs) match { + case (I32(x), I32(y)) => I32("relation-geu".reflectCtrlWith[Num](x, y)) + case (I64(x), I64(y)) => I32("relation-geu".reflectCtrlWith[Num](x, y)) + } + } + } +} + +trait StagedWasmScalaGen extends ScalaGenBase with SAICodeGenBase { + override def mayInline(n: Node): Boolean = n match { + case Node(s, "stack-pop", _, _) => false + case _ => super.mayInline(n) + } + + override def traverse(n: Node): Unit = n match { + case Node(_, "stack-drop", List(n), _) => + emit("Stack.drop("); shallow(n); emit(")\n") + case Node(_, "stack-reset", List(n), _) => + emit("Stack.reset("); shallow(n); emit(")\n") + case Node(_, "stack-init", _, _) => + emit("Stack.initialize()\n") + case Node(_, "stack-print", _, _) => + emit("Stack.print()\n") + case Node(_, "frame-push", List(i), _) => + emit("Frames.pushFrame("); shallow(i); emit(")\n") + case Node(_, "frame-pop", List(i), _) => + emit("Frames.popFrame("); shallow(i); emit(")\n") + case Node(_, "frame-putAll", List(args), _) => + emit("Frames.putAll("); shallow(args); emit(")\n") + case Node(_, "frame-set", List(i, value), _) => + emit("Frames.set("); shallow(i); emit(", "); shallow(value); emit(")\n") + case Node(_, "global-set", List(i, value), _) => + emit("Global.globalSet("); shallow(i); emit(", "); shallow(value); emit(")\n") + case _ => super.traverse(n) + } + + // code generation for pure nodes + override def shallow(n: Node): Unit = n match { + case Node(_, "frame-get", List(i), _) => + emit("Frames.get("); shallow(i); emit(")") + case Node(_, "frame-pop", List(i), _) => + emit("Frames.popFrame("); shallow(i); emit(")") + case Node(_, "stack-push", List(value), _) => + emit("Stack.push("); shallow(value); emit(")") + case Node(_, "stack-pop", _, _) => + emit("Stack.pop()") + case Node(_, "stack-peek", _, _) => + emit("Stack.peek") + case Node(_, "stack-take", List(n), _) => + emit("Stack.take("); shallow(n); emit(")") + case Node(_, "stack-size", _, _) => + emit("Stack.size") + case Node(_, "global-get", List(i), _) => + emit("Global.globalGet("); shallow(i); emit(")") + case Node(_, "binary-add", List(lhs, rhs), _) => + shallow(lhs); emit(" + "); shallow(rhs) + case Node(_, "binary-sub", List(lhs, rhs), _) => + shallow(lhs); emit(" - "); shallow(rhs) + case Node(_, "binary-mul", List(lhs, rhs), _) => + shallow(lhs); emit(" * "); shallow(rhs) + case Node(_, "binary-div", List(lhs, rhs), _) => + shallow(lhs); emit(" / "); shallow(rhs) + case Node(_, "binary-shl", List(lhs, rhs), _) => + shallow(lhs); emit(" << "); shallow(rhs) + case Node(_, "binary-shr", List(lhs, rhs), _) => + shallow(lhs); emit(" >> "); shallow(rhs) + case Node(_, "binary-and", List(lhs, rhs), _) => + shallow(lhs); emit(" & "); shallow(rhs) + case Node(_, "relation-eq", List(lhs, rhs), _) => + shallow(lhs); emit(" == "); shallow(rhs) + case Node(_, "relation-ne", List(lhs, rhs), _) => + shallow(lhs); emit(" != "); shallow(rhs) + case Node(_, "relation-lt", List(lhs, rhs), _) => + shallow(lhs); emit(" < "); shallow(rhs) + case Node(_, "relation-ltu", List(lhs, rhs), _) => + shallow(lhs); emit(" < "); shallow(rhs) + case Node(_, "relation-gt", List(lhs, rhs), _) => + shallow(lhs); emit(" > "); shallow(rhs) + case Node(_, "relation-gtu", List(lhs, rhs), _) => + shallow(lhs); emit(" > "); shallow(rhs) + case Node(_, "relation-le", List(lhs, rhs), _) => + shallow(lhs); emit(" <= "); shallow(rhs) + case Node(_, "relation-leu", List(lhs, rhs), _) => + shallow(lhs); emit(" <= "); shallow(rhs) + case Node(_, "relation-ge", List(lhs, rhs), _) => + shallow(lhs); emit(" >= "); shallow(rhs) + case Node(_, "relation-geu", List(lhs, rhs), _) => + shallow(lhs); emit(" >= "); shallow(rhs) + case Node(_, "num-to-int", List(num), _) => + shallow(num); emit(".toInt") + case Node(_, "no-op", _, _) => + emit("()") + case _ => super.shallow(n) + } +} + +trait WasmToScalaCompilerDriver[A, B] + extends SAIDriver[A, B] with StagedWasmEvaluator { q => + override val codegen = new StagedWasmScalaGen { + val IR: q.type = q + import IR._ + override def remap(m: Manifest[_]): String = { + if (m.toString.endsWith("Stack")) "Stack" + else if(m.toString.endsWith("Frame")) "Frame" + else super.remap(m) + } + } + + override val prelude = + """ +object Prelude { + sealed abstract class Num { + def +(that: Num): Num = (this, that) match { + case (I32V(x), I32V(y)) => I32V(x + y) + case (I64V(x), I64V(y)) => I64V(x + y) + case _ => throw new RuntimeException("Invalid addition") + } + + def -(that: Num): Num = (this, that) match { + case (I32V(x), I32V(y)) => I32V(x - y) + case (I64V(x), I64V(y)) => I64V(x - y) + case _ => throw new RuntimeException("Invalid subtraction") + } + + def !=(that: Num): Num = (this, that) match { + case (I32V(x), I32V(y)) => I32V(if (x != y) 1 else 0) + case (I64V(x), I64V(y)) => I32V(if (x != y) 1 else 0) + case _ => throw new RuntimeException("Invalid inequality") + } + + def toInt: Int = this match { + case I32V(i) => i + case I64V(i) => i.toInt + } + } + case class I32V(i: Int) extends Num + case class I64V(i: Long) extends Num + +object Stack { + private val buffer = new scala.collection.mutable.ArrayBuffer[Num]() + def push(v: Num): Unit = buffer.append(v) + def pop(): Num = { + buffer.remove(buffer.size - 1) + } + def peek: Num = { + buffer.last + } + def size: Int = buffer.size + def drop(n: Int): Unit = { + buffer.remove(buffer.size - n, n) + } + def take(n: Int): List[Num] = { + val xs = buffer.takeRight(n).toList + drop(n) + xs + } + def reset(size: Int): Unit = { + info(s"Reset stack to size $size") + while (buffer.size > size) { + buffer.remove(buffer.size - 1) + } + } + def initialize(): Unit = buffer.clear() + def print(): Unit = { + println("Stack: " + buffer.mkString(", ")) + } +} + + class Frame(val size: Int) { + private val data = new Array[Num](size) + def apply(i: Int): Num = { + info(s"frame(${i}) is ${data(i)}") + data(i) + } + def update(i: Int, v: Num): Unit = { + info(s"set frame(${i}) to ${v}") + data(i) = v + } + def putAll(xs: List[Num]): Unit = { + for (i <- 0 until xs.size) { + data(i) = xs(i) + } + } + override def toString: String = { + "Frame(" + data.mkString(", ") + ")" + } + } + + object Frames { + private var frames = List[Frame]() + def pushFrame(size: Int): Unit = { + frames = new Frame(size) :: frames + } + def popFrame(): Unit = { + frames = frames.tail + } + def top: Frame = frames.head + def set(i: Int, v: Num): Unit = { + top(i) = v + } + def get(i: Int): Num = { + top(i) + } + } + + object Global { + // TODO: create global with specific size + private val globals = new Array[Num](10) + def globalGet(i: Int): Num = globals(i) + def globalSet(i: Int, v: Num): Unit = globals(i) = v + } + + def info(xs: Any*): Unit = { + if (System.getenv("DEBUG") != null) { + println("[INFO] " + xs.mkString(" ")) + } + } +} +import Prelude._ + + +object Main { + def main(args: Array[String]): Unit = { + val snippet = new Snippet() + snippet(()) + } +} +""" +} + + +object WasmToScalaCompiler { + def compile(moduleInst: ModuleInstance, main: Option[String], printRes: Boolean = false): String = { + println(s"Now compiling wasm module with entry function $main") + val code = new WasmToScalaCompilerDriver[Unit, Unit] { + def module: ModuleInstance = moduleInst + def snippet(x: Rep[Unit]): Rep[Unit] = { + evalTop(main, printRes) + } + } + code.code + } +} + +trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { + // clear include path and headers by first + includePaths.clear() + headers.clear() + + registerHeader("headers", "\"wasm.hpp\"") + registerHeader("") + registerHeader("") + registerHeader("") + registerHeader("") + + override def mayInline(n: Node): Boolean = n match { + case Node(_, "stack-pop", _, _) + | Node(_, "stack-peek", _, _) + => false + case _ => super.mayInline(n) + } + + override def remap(m: Manifest[_]): String = { + if (m.toString.endsWith("Num")) "Num" + else if (m.toString.endsWith("Frame")) "Frame" + else if (m.toString.endsWith("Stack")) "Stack" + else if (m.toString.endsWith("Global")) "Global" + else if (m.toString.endsWith("I32V")) "I32V" + else if (m.toString.endsWith("I64V")) "I64V" + else super.remap(m) + } + + // for now, the traverse/shallow is same as the scala backend's + override def traverse(n: Node): Unit = n match { + case Node(_, "stack-push", List(value), _) => + emit("Stack.push("); shallow(value); emit(");\n") + case Node(_, "stack-drop", List(n), _) => + emit("Stack.drop("); shallow(n); emit(");\n") + case Node(_, "stack-init", _, _) => + emit("Stack.initialize();\n") + case Node(_, "stack-print", _, _) => + emit("Stack.print();\n") + case Node(_, "frame-push", List(i), _) => + emit("Frames.pushFrame("); shallow(i); emit(");\n") + case Node(_, "frame-pop", List(i), _) => + emit("Frames.popFrame("); shallow(i); emit(");\n") + case Node(_, "frame-putAll", List(args), _) => + emit("Frames.putAll("); shallow(args); emit(");\n") + case Node(_, "frame-set", List(i, value), _) => + emit("Frames.set("); shallow(i); emit(", "); shallow(value); emit(");\n") + case Node(_, "global-set", List(i, value), _) => + emit("Global.globalSet("); shallow(i); emit(", "); shallow(value); emit(");\n") + // Note: The following code is copied from the traverse of CppBackend.scala, try to avoid duplicated code + case n @ Node(f, "λ", (b: LMSBlock)::LMSConst(0)::rest, _) => + // TODO: Is a leading block followed by 0 a hint for top function? + super.traverse(n) + case n @ Node(f, "λ", (b: LMSBlock)::rest, _) => + val retType = remap(typeBlockRes(b.res)) + val argTypes = b.in.map(a => remap(typeMap(a))).mkString(", ") + emitln(s"std::function<$retType(${argTypes})> ${quote(f)};") + emit(quote(f)); emit(" = ") + quoteTypedBlock(b, false, true, capture = "&") + emitln(";") + case _ => super.traverse(n) + } + + // code generation for pure nodes + override def shallow(n: Node): Unit = n match { + case Node(_, "frame-get", List(i), _) => + emit("Frames.get("); shallow(i); emit(")") + case Node(_, "stack-drop", List(n), _) => + emit("Stack.drop("); shallow(n); emit(")") + case Node(_, "stack-push", List(value), _) => + emit("Stack.push("); shallow(value); emit(")") + case Node(_, "stack-shift", List(offset, size), _) => + emit("Stack.shift("); shallow(offset); emit(", "); shallow(size); emit(")") + case Node(_, "stack-pop", _, _) => + emit("Stack.pop()") + case Node(_, "frame-pop", List(i), _) => + emit("Frames.popFrame("); shallow(i); emit(")") + case Node(_, "stack-peek", _, _) => + emit("Stack.peek()") + case Node(_, "stack-take", List(n), _) => + emit("Stack.take("); shallow(n); emit(")") + case Node(_, "slice-reverse", List(slice), _) => + shallow(slice); emit(".reverse") + case Node(_, "memory-store-int", List(base, offset, value), _) => + emit("Memory.storeInt("); shallow(base); emit(", "); shallow(offset); emit(", "); shallow(value); emit(")") + case Node(_, "memory-load-int", List(base, offset), _) => + emit("Memory.loadInt("); shallow(base); emit(", "); shallow(offset); emit(")") + case Node(_, "memory-grow", List(delta), _) => + emit("Memory.grow("); shallow(delta); emit(")") + case Node(_, "stack-size", _, _) => + emit("Stack.size()") + case Node(_, "global-get", List(i), _) => + emit("Global.globalGet("); shallow(i); emit(")") + case Node(_, "binary-add", List(lhs, rhs), _) => + shallow(lhs); emit(" + "); shallow(rhs) + case Node(_, "binary-sub", List(lhs, rhs), _) => + shallow(lhs); emit(" - "); shallow(rhs) + case Node(_, "binary-mul", List(lhs, rhs), _) => + shallow(lhs); emit(" * "); shallow(rhs) + case Node(_, "binary-div", List(lhs, rhs), _) => + shallow(lhs); emit(" / "); shallow(rhs) + case Node(_, "binary-shl", List(lhs, rhs), _) => + shallow(lhs); emit(" << "); shallow(rhs) + case Node(_, "binary-shr", List(lhs, rhs), _) => + shallow(lhs); emit(" >> "); shallow(rhs) + case Node(_, "binary-and", List(lhs, rhs), _) => + shallow(lhs); emit(" & "); shallow(rhs) + case Node(_, "relation-eq", List(lhs, rhs), _) => + shallow(lhs); emit(" == "); shallow(rhs) + case Node(_, "relation-ne", List(lhs, rhs), _) => + shallow(lhs); emit(" != "); shallow(rhs) + case Node(_, "relation-lt", List(lhs, rhs), _) => + shallow(lhs); emit(" < "); shallow(rhs) + case Node(_, "relation-ltu", List(lhs, rhs), _) => + shallow(lhs); emit(" < "); shallow(rhs) + case Node(_, "relation-gt", List(lhs, rhs), _) => + shallow(lhs); emit(" > "); shallow(rhs) + case Node(_, "relation-gtu", List(lhs, rhs), _) => + shallow(lhs); emit(" > "); shallow(rhs) + case Node(_, "relation-le", List(lhs, rhs), _) => + shallow(lhs); emit(" <= "); shallow(rhs) + case Node(_, "relation-leu", List(lhs, rhs), _) => + shallow(lhs); emit(" <= "); shallow(rhs) + case Node(_, "relation-ge", List(lhs, rhs), _) => + shallow(lhs); emit(" >= "); shallow(rhs) + case Node(_, "relation-geu", List(lhs, rhs), _) => + shallow(lhs); emit(" >= "); shallow(rhs) + case Node(_, "num-to-int", List(num), _) => + shallow(num); emit(".toInt()") + case Node(_, "dummy", _, _) => emit("std::monostate()") + case Node(_, "dummy-op", _, _) => emit("std::monostate()") + case Node(_, "no-op", _, _) => + emit("std::monostate()") + case _ => super.shallow(n) + } + + override def registerTopLevelFunction(id: String, streamId: String = "general")(f: => Unit) = + if (!registeredFunctions(id)) { + //if (ongoingFun(streamId)) ??? + //ongoingFun += streamId + registeredFunctions += id + withStream(functionsStreams.getOrElseUpdate(id, { + val functionsStream = new java.io.ByteArrayOutputStream() + val functionsWriter = new java.io.PrintStream(functionsStream) + (functionsWriter, functionsStream) + })._1)(f) + //ongoingFun -= streamId + } else { + // If a function is registered, don't re-register it. + // withStream(functionsStreams(id)._1)(f) + } + + override def emitAll(g: Graph, name: String)(m1: Manifest[_], m2: Manifest[_]): Unit = { + val ng = init(g) + emitHeaders(stream) + emitln(""" + |/***************************************** + |Emitting Generated Code + |*******************************************/ + """.stripMargin) + val src = run(name, ng) + emitFunctionDecls(stream) + emitDatastructures(stream) + emitFunctions(stream) + emit(src) + emitln(""" + |/***************************************** + |End of Generated Code + |*******************************************/ + |int main(int argc, char *argv[]) { + | Snippet(std::monostate{}); + | return 0; + |}""".stripMargin) + } +} + +trait WasmToCppCompilerDriver[A, B] extends CppSAIDriver[A, B] with StagedWasmEvaluator { q => + override val codegen = new StagedWasmCppGen { + val IR: q.type = q + import IR._ + } +} + +object WasmToCppCompiler { + case class GeneratedCpp(source: String, headerFolders: List[String]) + + def compile(moduleInst: ModuleInstance, main: Option[String], printRes: Boolean = false): GeneratedCpp = { + println(s"Now compiling wasm module with entry function $main") + val driver = new WasmToCppCompilerDriver[Unit, Unit] { + def module: ModuleInstance = moduleInst + def snippet(x: Rep[Unit]): Rep[Unit] = { + evalTop(main, printRes) + } + } + GeneratedCpp(driver.code, driver.codegen.includePaths.toList) + } + + def compileToExe(moduleInst: ModuleInstance, + main: Option[String], + outputCpp: String, + outputExe: String, + printRes: Boolean = false): Unit = { + val generated = compile(moduleInst, main, printRes) + val code = generated.source + + val writer = new java.io.PrintWriter(new java.io.File(outputCpp)) + try { + writer.write(code) + } finally { + writer.close() + } + + import sys.process._ + val command = s"g++ -std=c++17 $outputCpp -o $outputExe -O3 -g " + generated.headerFolders.map(f => s"-I$f").mkString(" ") + if (command.! != 0) { + throw new RuntimeException(s"Compilation failed for $outputCpp") + } + } + +} + From 27e3e32f9269303d21d65583f6b2d14a74b04dbe Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Mon, 7 Jul 2025 14:34:24 +0800 Subject: [PATCH 03/53] dup all concrete operations to symbolic --- .../scala/wasm/StagedConcolicMiniWasm.scala | 444 +++++------------- 1 file changed, 111 insertions(+), 333 deletions(-) diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index 6b14bcf6..22b593a9 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -13,8 +13,9 @@ import lms.core.Graph import gensym.wasm.ast._ import gensym.wasm.ast.{Const => WasmConst, Block => WasmBlock} import gensym.wasm.miniwasm.{ModuleInstance} -import gensym.wasm.ast.{Const => WasmConst, Block => WasmBlock} +import gensym.wasm.symbolic.{SymVal} import gensym.lmsx.{SAIDriver, StringOps, SAIOps, SAICodeGenBase, CppSAIDriver, CppSAICodeGenBase} +import gensym.wasm.symbolic.Concrete @virtualize trait StagedWasmEvaluator extends SAIOps { @@ -24,25 +25,27 @@ trait StagedWasmEvaluator extends SAIOps { trait StagedNum { def tipe: ValueType = this match { - case I32(_) => NumType(I32Type) - case I64(_) => NumType(I64Type) - case F32(_) => NumType(F32Type) - case F64(_) => NumType(F64Type) + case I32(_, _) => NumType(I32Type) + case I64(_, _) => NumType(I64Type) + case F32(_, _) => NumType(F32Type) + case F64(_, _) => NumType(F64Type) } def i: Rep[Num] + + def s: Rep[SymVal] } - case class I32(i: Rep[Num]) extends StagedNum - case class I64(i: Rep[Num]) extends StagedNum - case class F32(i: Rep[Num]) extends StagedNum - case class F64(i: Rep[Num]) extends StagedNum + case class I32(i: Rep[Num], s: Rep[SymVal]) extends StagedNum + case class I64(i: Rep[Num], s: Rep[SymVal]) extends StagedNum + case class F32(i: Rep[Num], s: Rep[SymVal]) extends StagedNum + case class F64(i: Rep[Num], s: Rep[SymVal]) extends StagedNum implicit def toStagedNum(num: Num): StagedNum = { num match { - case I32V(_) => I32(num) - case I64V(_) => I64(num) - case F32V(_) => F32(num) - case F64V(_) => F64(num) + case I32V(_) => I32(num, Concrete(num)) + case I64V(_) => I64(num, Concrete(num)) + case F32V(_) => F32(num, Concrete(num)) + case F64V(_) => F64(num, Concrete(num)) } } @@ -147,7 +150,7 @@ trait StagedWasmEvaluator extends SAIOps { case Load(LoadOp(align, offset, ty, None, None)) => val (addr, newCtx1) = Stack.pop() val value = Memory.loadInt(addr.toInt, offset) - val newCtx2 = Stack.push(Values.I32V(value))(newCtx1) + val newCtx2 = Stack.push(value)(newCtx1) eval(rest, kont, mkont, trail)(newCtx2) case MemorySize => ??? case MemoryGrow => @@ -414,10 +417,10 @@ trait StagedWasmEvaluator extends SAIOps { def pop()(implicit ctx: Context): (StagedNum, Context) = { val (ty, newContext) = ctx.pop() val num = ty match { - case NumType(I32Type) => I32("stack-pop".reflectCtrlWith[Num]()) - case NumType(I64Type) => I64("stack-pop".reflectCtrlWith[Num]()) - case NumType(F32Type) => F32("stack-pop".reflectCtrlWith[Num]()) - case NumType(F32Type) => F64("stack-pop".reflectCtrlWith[Num]()) + case NumType(I32Type) => I32("stack-pop".reflectCtrlWith[Num](), "sym-stack-pop".reflectCtrlWith[SymVal]()) + case NumType(I64Type) => I64("stack-pop".reflectCtrlWith[Num](), "sym-stack-pop".reflectCtrlWith[SymVal]()) + case NumType(F32Type) => F32("stack-pop".reflectCtrlWith[Num](), "sym-stack-pop".reflectCtrlWith[SymVal]()) + case NumType(F32Type) => F64("stack-pop".reflectCtrlWith[Num](), "sym-stack-pop".reflectCtrlWith[SymVal]()) } (num, newContext) } @@ -425,22 +428,22 @@ trait StagedWasmEvaluator extends SAIOps { def peek(implicit ctx: Context): (StagedNum, Context) = { val ty = ctx.stackTypes.head val num = ty match { - case NumType(I32Type) => I32("stack-peek".reflectCtrlWith[Num]()) - case NumType(I64Type) => I64("stack-peek".reflectCtrlWith[Num]()) - case NumType(F32Type) => F32("stack-peek".reflectCtrlWith[Num]()) - case NumType(F32Type) => F64("stack-peek".reflectCtrlWith[Num]()) + case NumType(I32Type) => I32("stack-peek".reflectCtrlWith[Num](), "sym-stack-peek".reflectCtrlWith[SymVal]()) + case NumType(I64Type) => I64("stack-peek".reflectCtrlWith[Num](), "sym-stack-peek".reflectCtrlWith[SymVal]()) + case NumType(F32Type) => F32("stack-peek".reflectCtrlWith[Num](), "sym-stack-peek".reflectCtrlWith[SymVal]()) + case NumType(F32Type) => F64("stack-peek".reflectCtrlWith[Num](), "sym-stack-peek".reflectCtrlWith[SymVal]()) } (num, ctx) } - def push(v: StagedNum)(implicit ctx: Context): Context = { - v match { - case I32(v) => "stack-push".reflectCtrlWith[Unit](v) - case I64(v) => "stack-push".reflectCtrlWith[Unit](v) - case F32(v) => "stack-push".reflectCtrlWith[Unit](v) - case F64(v) => "stack-push".reflectCtrlWith[Unit](v) + def push(num: StagedNum)(implicit ctx: Context): Context = { + num match { + case I32(v, s) => "stack-push".reflectCtrlWith[Unit](v); "sym-stack-push".reflectCtrlWith[Unit](s) + case I64(v, s) => "stack-push".reflectCtrlWith[Unit](v); "sym-stack-push".reflectCtrlWith[Unit](s) + case F32(v, s) => "stack-push".reflectCtrlWith[Unit](v); "sym-stack-push".reflectCtrlWith[Unit](s) + case F64(v, s) => "stack-push".reflectCtrlWith[Unit](v); "sym-stack-push".reflectCtrlWith[Unit](s) } - ctx.push(v.tipe) + ctx.push(num.tipe) } def take(n: Int)(implicit ctx: Context): (List[StagedNum], Context) = n match { @@ -458,6 +461,7 @@ trait StagedWasmEvaluator extends SAIOps { def shift(offset: Rep[Int], size: Rep[Int]): Rep[Unit] = { if (offset > 0) { "stack-shift".reflectCtrlWith[Unit](offset, size) + "sym-stack-shift".reflectCtrlWith[Unit](offset, size) } } @@ -474,20 +478,20 @@ trait StagedWasmEvaluator extends SAIOps { def get(i: Int)(implicit ctx: Context): StagedNum = { // val offset = ctx.frameTypes.take(i).map(_.size).sum ctx.frameTypes(i) match { - case NumType(I32Type) => I32("frame-get".reflectCtrlWith[Num](i)) - case NumType(I64Type) => I64("frame-get".reflectCtrlWith[Num](i)) - case NumType(F32Type) => F32("frame-get".reflectCtrlWith[Num](i)) - case NumType(F64Type) => F64("frame-get".reflectCtrlWith[Num](i)) + case NumType(I32Type) => I32("frame-get".reflectCtrlWith[Num](i), "sym-frame-get".reflectCtrlWith[SymVal](i)) + case NumType(I64Type) => I64("frame-get".reflectCtrlWith[Num](i), "sym-frame-get".reflectCtrlWith[SymVal](i)) + case NumType(F32Type) => F32("frame-get".reflectCtrlWith[Num](i), "sym-frame-get".reflectCtrlWith[SymVal](i)) + case NumType(F64Type) => F64("frame-get".reflectCtrlWith[Num](i), "sym-frame-get".reflectCtrlWith[SymVal](i)) } } def set(i: Int, v: StagedNum)(implicit ctx: Context): Rep[Unit] = { // val offset = ctx.frameTypes.take(i).map(_.size).sum v match { - case I32(v) => "frame-set".reflectCtrlWith[Unit](i, v) - case I64(v) => "frame-set".reflectCtrlWith[Unit](i, v) - case F32(v) => "frame-set".reflectCtrlWith[Unit](i, v) - case F64(v) => "frame-set".reflectCtrlWith[Unit](i, v) + case I32(v, s) => "frame-set".reflectCtrlWith[Unit](i, v); "sym-frame-set".reflectCtrlWith[Unit](i, s) + case I64(v, s) => "frame-set".reflectCtrlWith[Unit](i, v); "sym-frame-set".reflectCtrlWith[Unit](i, s) + case F32(v, s) => "frame-set".reflectCtrlWith[Unit](i, v); "sym-frame-set".reflectCtrlWith[Unit](i, s) + case F64(v, s) => "frame-set".reflectCtrlWith[Unit](i, v); "sym-frame-set".reflectCtrlWith[Unit](i, s) } } @@ -495,10 +499,12 @@ trait StagedWasmEvaluator extends SAIOps { // Predef.println(s"[DEBUG] push frame: $locals") val size = locals.size "frame-push".reflectCtrlWith[Unit](size) + "sym-frame-push".reflectCtrlWith[Unit](size) } def popFrame(size: Int): Rep[Unit] = { "frame-pop".reflectCtrlWith[Unit](size) + "sym-frame-pop".reflectCtrlWith[Unit](size) } def putAll(args: List[StagedNum])(implicit ctx: Context): Rep[Unit] = { @@ -513,8 +519,8 @@ trait StagedWasmEvaluator extends SAIOps { "memory-store-int".reflectCtrlWith[Unit](base, offset, value) } - def loadInt(base: Rep[Int], offset: Int): Rep[Int] = { - "memory-load-int".reflectCtrlWith[Int](base, offset) + def loadInt(base: Rep[Int], offset: Int): StagedNum = { + I32("I32V".reflectCtrlWith[Num]("memory-load-int".reflectCtrlWith[Int](base, offset)), "sym-load-int-todo".reflectCtrlWith[SymVal](base, offset)) } def grow(delta: Rep[Int]): Rep[Int] = { @@ -534,11 +540,11 @@ trait StagedWasmEvaluator extends SAIOps { // runtime values object Values { def I32V(i: Rep[Int]): StagedNum = { - I32("I32V".reflectCtrlWith[Num](i)) + I32("I32V".reflectCtrlWith[Num](i), "Concrete".reflectCtrlWith[SymVal]("I32V".reflectCtrlWith[Num](i))) } def I64V(i: Rep[Long]): StagedNum = { - I64("I64V".reflectCtrlWith[Num](i)) + I64("I64V".reflectCtrlWith[Num](i), "Concrete".reflectCtrlWith[SymVal]("I64V".reflectCtrlWith[Num](i))) } } @@ -546,19 +552,19 @@ trait StagedWasmEvaluator extends SAIOps { object Globals { def apply(i: Int): StagedNum = { module.globals(i).ty match { - case GlobalType(NumType(I32Type), _) => I32("global-get".reflectCtrlWith[Num](i)) - case GlobalType(NumType(I64Type), _) => I64("global-get".reflectCtrlWith[Num](i)) - case GlobalType(NumType(F32Type), _) => F32("global-get".reflectCtrlWith[Num](i)) - case GlobalType(NumType(F64Type), _) => F64("global-get".reflectCtrlWith[Num](i)) + case GlobalType(NumType(I32Type), _) => I32("global-get".reflectCtrlWith[Num](i), "sym-global-get".reflectCtrlWith[SymVal](i)) + case GlobalType(NumType(I64Type), _) => I64("global-get".reflectCtrlWith[Num](i), "sym-global-get".reflectCtrlWith[SymVal](i)) + case GlobalType(NumType(F32Type), _) => F32("global-get".reflectCtrlWith[Num](i), "sym-global-get".reflectCtrlWith[SymVal](i)) + case GlobalType(NumType(F64Type), _) => F64("global-get".reflectCtrlWith[Num](i), "sym-global-get".reflectCtrlWith[SymVal](i)) } } def update(i: Int, v: StagedNum): Rep[Unit] = { module.globals(i).ty match { - case GlobalType(NumType(I32Type), _) => "global-set".reflectCtrlWith[Unit](i) - case GlobalType(NumType(I64Type), _) => "global-set".reflectCtrlWith[Unit](i) - case GlobalType(NumType(F32Type), _) => "global-set".reflectCtrlWith[Unit](i) - case GlobalType(NumType(F64Type), _) => "global-set".reflectCtrlWith[Unit](i) + case GlobalType(NumType(I32Type), _) => "global-set".reflectCtrlWith[Unit](i, v.i);"sym-global-set".reflectCtrlWith[Unit](i, v.s) + case GlobalType(NumType(I64Type), _) => "global-set".reflectCtrlWith[Unit](i, v.i);"sym-global-set".reflectCtrlWith[Unit](i, v.s) + case GlobalType(NumType(F32Type), _) => "global-set".reflectCtrlWith[Unit](i, v.i);"sym-global-set".reflectCtrlWith[Unit](i, v.s) + case GlobalType(NumType(F64Type), _) => "global-set".reflectCtrlWith[Unit](i, v.i);"sym-global-set".reflectCtrlWith[Unit](i, v.s) } } } @@ -569,382 +575,153 @@ trait StagedWasmEvaluator extends SAIOps { def toInt: Rep[Int] = "num-to-int".reflectCtrlWith[Int](num.i) def clz(): StagedNum = num match { - case I32(i) => I32("clz".reflectCtrlWith[Num](i)) - case I64(i) => I64("clz".reflectCtrlWith[Num](i)) + case I32(x_c, x_s) => I32("clz".reflectCtrlWith[Num](x_c), "sym-clz".reflectCtrlWith[SymVal](x_s)) + case I64(x_c, x_s) => I64("clz".reflectCtrlWith[Num](x_c), "sym-clz".reflectCtrlWith[SymVal](x_s)) } def ctz(): StagedNum = num match { - case I32(i) => I32("ctz".reflectCtrlWith[Num](i)) - case I64(i) => I64("ctz".reflectCtrlWith[Num](i)) + case I32(x_c, x_s) => I32("ctz".reflectCtrlWith[Num](x_c), "sym-ctz".reflectCtrlWith[SymVal](x_s)) + case I64(x_c, x_s) => I64("ctz".reflectCtrlWith[Num](x_c), "sym-ctz".reflectCtrlWith[SymVal](x_s)) } def popcnt(): StagedNum = num match { - case I32(i) => I32("popcnt".reflectCtrlWith[Num](i)) - case I64(i) => I64("popcnt".reflectCtrlWith[Num](i)) + case I32(x_c, x_s) => I32("popcnt".reflectCtrlWith[Num](x_c), "sym-popcnt".reflectCtrlWith[SymVal](x_s)) + case I64(x_c, x_s) => I64("popcnt".reflectCtrlWith[Num](x_c), "sym-popcnt".reflectCtrlWith[SymVal](x_s)) } def +(rhs: StagedNum): StagedNum = { (num, rhs) match { - case (I32(x), I32(y)) => I32("binary-add".reflectCtrlWith[Num](x, y)) - case (I64(x), I64(y)) => I64("binary-add".reflectCtrlWith[Num](x, y)) - case (F32(x), F32(y)) => F32("binary-add".reflectCtrlWith[Num](x, y)) - case (F64(x), F64(y)) => F64("binary-add".reflectCtrlWith[Num](x, y)) + case (I32(x_c, x_s), I32(y_c, y_s)) => I32("binary-add".reflectCtrlWith[Num](x_c, y_c), "sym-binary-add".reflectCtrlWith[SymVal](x_s, y_s)) + case (I64(x_c, x_s), I64(y_c, y_s)) => I64("binary-add".reflectCtrlWith[Num](x_c, y_c), "sym-binary-add".reflectCtrlWith[SymVal](x_s, y_s)) + case (F32(x_c, x_s), F32(y_c, y_s)) => F32("binary-add".reflectCtrlWith[Num](x_c, y_c), "sym-binary-add".reflectCtrlWith[SymVal](x_s, y_s)) + case (F64(x_c, x_s), F64(y_c, y_s)) => F64("binary-add".reflectCtrlWith[Num](x_c, y_c), "sym-binary-add".reflectCtrlWith[SymVal](x_s, y_s)) } } + def -(rhs: StagedNum): StagedNum = { (num, rhs) match { - case (I32(x), I32(y)) => I32("binary-sub".reflectCtrlWith[Num](x, y)) - case (I64(x), I64(y)) => I64("binary-sub".reflectCtrlWith[Num](x, y)) - case (F32(x), F32(y)) => F32("binary-sub".reflectCtrlWith[Num](x, y)) - case (F64(x), F64(y)) => F64("binary-sub".reflectCtrlWith[Num](x, y)) + case (I32(x_c, x_s), I32(y_c, y_s)) => I32("binary-sub".reflectCtrlWith[Num](x_c, y_c), "sym-binary-sub".reflectCtrlWith[SymVal](x_s, y_s)) + case (I64(x_c, x_s), I64(y_c, y_s)) => I64("binary-sub".reflectCtrlWith[Num](x_c, y_c), "sym-binary-sub".reflectCtrlWith[SymVal](x_s, y_s)) + case (F32(x_c, x_s), F32(y_c, y_s)) => F32("binary-sub".reflectCtrlWith[Num](x_c, y_c), "sym-binary-sub".reflectCtrlWith[SymVal](x_s, y_s)) + case (F64(x_c, x_s), F64(y_c, y_s)) => F64("binary-sub".reflectCtrlWith[Num](x_c, y_c), "sym-binary-sub".reflectCtrlWith[SymVal](x_s, y_s)) } } def *(rhs: StagedNum): StagedNum = { (num, rhs) match { - case (I32(x), I32(y)) => I32("binary-mul".reflectCtrlWith[Num](x, y)) - case (I64(x), I64(y)) => I64("binary-mul".reflectCtrlWith[Num](x, y)) - case (F32(x), F32(y)) => F32("binary-mul".reflectCtrlWith[Num](x, y)) - case (F64(x), F64(y)) => F64("binary-mul".reflectCtrlWith[Num](x, y)) + case (I32(x_c, x_s), I32(y_c, y_s)) => I32("binary-mul".reflectCtrlWith[Num](x_c, y_c), "sym-binary-mul".reflectCtrlWith[SymVal](x_s, y_s)) + case (I64(x_c, x_s), I64(y_c, y_s)) => I64("binary-mul".reflectCtrlWith[Num](x_c, y_c), "sym-binary-mul".reflectCtrlWith[SymVal](x_s, y_s)) + case (F32(x_c, x_s), F32(y_c, y_s)) => F32("binary-mul".reflectCtrlWith[Num](x_c, y_c), "sym-binary-mul".reflectCtrlWith[SymVal](x_s, y_s)) + case (F64(x_c, x_s), F64(y_c, y_s)) => F64("binary-mul".reflectCtrlWith[Num](x_c, y_c), "sym-binary-mul".reflectCtrlWith[SymVal](x_s, y_s)) } } def /(rhs: StagedNum): StagedNum = { (num, rhs) match { - case (I32(x), I32(y)) => I32("binary-div".reflectCtrlWith[Num](x, y)) - case (I64(x), I64(y)) => I64("binary-div".reflectCtrlWith[Num](x, y)) - case (F32(x), F32(y)) => F32("binary-div".reflectCtrlWith[Num](x, y)) - case (F64(x), F64(y)) => F64("binary-div".reflectCtrlWith[Num](x, y)) + case (I32(x_c, x_s), I32(y_c, y_s)) => I32("binary-div".reflectCtrlWith[Num](x_c, y_c), "sym-binary-div".reflectCtrlWith[SymVal](x_s, y_s)) + case (I64(x_c, x_s), I64(y_c, y_s)) => I64("binary-div".reflectCtrlWith[Num](x_c, y_c), "sym-binary-div".reflectCtrlWith[SymVal](x_s, y_s)) + case (F32(x_c, x_s), F32(y_c, y_s)) => F32("binary-div".reflectCtrlWith[Num](x_c, y_c), "sym-binary-div".reflectCtrlWith[SymVal](x_s, y_s)) + case (F64(x_c, x_s), F64(y_c, y_s)) => F64("binary-div".reflectCtrlWith[Num](x_c, y_c), "sym-binary-div".reflectCtrlWith[SymVal](x_s, y_s)) } } def <<(rhs: StagedNum): StagedNum = { (num, rhs) match { - case (I32(x), I32(y)) => I32("binary-shl".reflectCtrlWith[Num](x, y)) - case (I64(x), I64(y)) => I64("binary-shl".reflectCtrlWith[Num](x, y)) + case (I32(x_c, x_s), I32(y_c, y_s)) => I32("binary-shl".reflectCtrlWith[Num](x_c, y_c), "sym-binary-shl".reflectCtrlWith[SymVal](x_s, y_s)) + case (I64(x_c, x_s), I64(y_c, y_s)) => I64("binary-shl".reflectCtrlWith[Num](x_c, y_c), "sym-binary-shl".reflectCtrlWith[SymVal](x_s, y_s)) + case (F32(x_c, x_s), F32(y_c, y_s)) => F32("binary-shl".reflectCtrlWith[Num](x_c, y_c), "sym-binary-shl".reflectCtrlWith[SymVal](x_s, y_s)) + case (F64(x_c, x_s), F64(y_c, y_s)) => F64("binary-shl".reflectCtrlWith[Num](x_c, y_c), "sym-binary-shl".reflectCtrlWith[SymVal](x_s, y_s)) } } def >>(rhs: StagedNum): StagedNum = { (num, rhs) match { - case (I32(x), I32(y)) => I32("binary-shr".reflectCtrlWith[Num](x, y)) - case (I64(x), I64(y)) => I64("binary-shr".reflectCtrlWith[Num](x, y)) + case (I32(x_c, x_s), I32(y_c, y_s)) => I32("binary-shr".reflectCtrlWith[Num](x_c, y_c), "sym-binary-shr".reflectCtrlWith[SymVal](x_s, y_s)) + case (I64(x_c, x_s), I64(y_c, y_s)) => I64("binary-shr".reflectCtrlWith[Num](x_c, y_c), "sym-binary-shr".reflectCtrlWith[SymVal](x_s, y_s)) + case (F32(x_c, x_s), F32(y_c, y_s)) => F32("binary-shr".reflectCtrlWith[Num](x_c, y_c), "sym-binary-shr".reflectCtrlWith[SymVal](x_s, y_s)) + case (F64(x_c, x_s), F64(y_c, y_s)) => F64("binary-shr".reflectCtrlWith[Num](x_c, y_c), "sym-binary-shr".reflectCtrlWith[SymVal](x_s, y_s)) } } def &(rhs: StagedNum): StagedNum = { (num, rhs) match { - case (I32(x), I32(y)) => I32("binary-and".reflectCtrlWith[Num](x, y)) - case (I64(x), I64(y)) => I64("binary-and".reflectCtrlWith[Num](x, y)) + case (I32(x_c, x_s), I32(y_c, y_s)) => I32("binary-and".reflectCtrlWith[Num](x_c, y_c), "sym-binary-and".reflectCtrlWith[SymVal](x_s, y_s)) + case (I64(x_c, x_s), I64(y_c, y_s)) => I64("binary-and".reflectCtrlWith[Num](x_c, y_c), "sym-binary-and".reflectCtrlWith[SymVal](x_s, y_s)) + case (F32(x_c, x_s), F32(y_c, y_s)) => F32("binary-and".reflectCtrlWith[Num](x_c, y_c), "sym-binary-and".reflectCtrlWith[SymVal](x_s, y_s)) + case (F64(x_c, x_s), F64(y_c, y_s)) => F64("binary-and".reflectCtrlWith[Num](x_c, y_c), "sym-binary-and".reflectCtrlWith[SymVal](x_s, y_s)) } } def numEq(rhs: StagedNum): StagedNum = { (num, rhs) match { - case (I32(x), I32(y)) => I32("relation-eq".reflectCtrlWith[Num](x, y)) - case (I64(x), I64(y)) => I32("relation-eq".reflectCtrlWith[Num](x, y)) + case (I32(x_c, x_s), I32(y_c, y_s)) => I32("relation-eq".reflectCtrlWith[Num](x_c, y_c), "sym-relation-eq".reflectCtrlWith[SymVal](x_s, y_s)) + case (I64(x_c, x_s), I64(y_c, y_s)) => I32("relation-eq".reflectCtrlWith[Num](x_c, y_c), "sym-relation-eq".reflectCtrlWith[SymVal](x_s, y_s)) } } def numNe(rhs: StagedNum): StagedNum = { (num, rhs) match { - case (I32(x), I32(y)) => I32("relation-ne".reflectCtrlWith[Num](x, y)) - case (I64(x), I64(y)) => I32("relation-ne".reflectCtrlWith[Num](x, y)) + case (I32(x_c, x_s), I32(y_c, y_s)) => I32("relation-ne".reflectCtrlWith[Num](x_c, y_c), "sym-relation-ne".reflectCtrlWith[SymVal](x_s, y_s)) + case (I64(x_c, x_s), I64(y_c, y_s)) => I32("relation-ne".reflectCtrlWith[Num](x_c, y_c), "sym-relation-ne".reflectCtrlWith[SymVal](x_s, y_s)) } } def <(rhs: StagedNum): StagedNum = { (num, rhs) match { - case (I32(x), I32(y)) => I32("relation-lt".reflectCtrlWith[Num](x, y)) - case (I64(x), I64(y)) => I32("relation-lt".reflectCtrlWith[Num](x, y)) + case (I32(x_c, x_s), I32(y_c, y_s)) => I32("relation-lt".reflectCtrlWith[Num](x_c, y_c), "sym-relation-lt".reflectCtrlWith[SymVal](x_s, y_s)) + case (I64(x_c, x_s), I64(y_c, y_s)) => I32("relation-lt".reflectCtrlWith[Num](x_c, y_c), "sym-relation-lt".reflectCtrlWith[SymVal](x_s, y_s)) } } def ltu(rhs: StagedNum): StagedNum = { (num, rhs) match { - case (I32(x), I32(y)) => I32("relation-ltu".reflectCtrlWith[Num](x, y)) - case (I64(x), I64(y)) => I32("relation-ltu".reflectCtrlWith[Num](x, y)) + case (I32(x_c, x_s), I32(y_c, y_s)) => I32("relation-ltu".reflectCtrlWith[Num](x_c, y_c), "sym-relation-ltu".reflectCtrlWith[SymVal](x_s, y_s)) + case (I64(x_c, x_s), I64(y_c, y_s)) => I32("relation-ltu".reflectCtrlWith[Num](x_c, y_c), "sym-relation-ltu".reflectCtrlWith[SymVal](x_s, y_s)) } } def >(rhs: StagedNum): StagedNum = { (num, rhs) match { - case (I32(x), I32(y)) => I32("relation-gt".reflectCtrlWith[Num](x, y)) - case (I64(x), I64(y)) => I32("relation-gt".reflectCtrlWith[Num](x, y)) + case (I32(x_c, x_s), I32(y_c, y_s)) => I32("relation-gt".reflectCtrlWith[Num](x_c, y_c), "sym-relation-gt".reflectCtrlWith[SymVal](x_s, y_s)) + case (I64(x_c, x_s), I64(y_c, y_s)) => I32("relation-gt".reflectCtrlWith[Num](x_c, y_c), "sym-relation-gt".reflectCtrlWith[SymVal](x_s, y_s)) } } def gtu(rhs: StagedNum): StagedNum = { (num, rhs) match { - case (I32(x), I32(y)) => I32("relation-gtu".reflectCtrlWith[Num](x, y)) - case (I64(x), I64(y)) => I32("relation-gtu".reflectCtrlWith[Num](x, y)) + case (I32(x_c, x_s), I32(y_c, y_s)) => I32("relation-gtu".reflectCtrlWith[Num](x_c, y_c), "sym-relation-gtu".reflectCtrlWith[SymVal](x_s, y_s)) + case (I64(x_c, x_s), I64(y_c, y_s)) => I32("relation-gtu".reflectCtrlWith[Num](x_c, y_c), "sym-relation-gtu".reflectCtrlWith[SymVal](x_s, y_s)) } } def <=(rhs: StagedNum): StagedNum = { (num, rhs) match { - case (I32(x), I32(y)) => I32("relation-le".reflectCtrlWith[Num](x, y)) - case (I64(x), I64(y)) => I32("relation-le".reflectCtrlWith[Num](x, y)) + case (I32(x_c, x_s), I32(y_c, y_s)) => I32("relation-le".reflectCtrlWith[Num](x_c, y_c), "sym-relation-le".reflectCtrlWith[SymVal](x_s, y_s)) + case (I64(x_c, x_s), I64(y_c, y_s)) => I32("relation-le".reflectCtrlWith[Num](x_c, y_c), "sym-relation-le".reflectCtrlWith[SymVal](x_s, y_s)) } } def leu(rhs: StagedNum): StagedNum = { (num, rhs) match { - case (I32(x), I32(y)) => I32("relation-leu".reflectCtrlWith[Num](x, y)) - case (I64(x), I64(y)) => I32("relation-leu".reflectCtrlWith[Num](x, y)) + case (I32(x_c, x_s), I32(y_c, y_s)) => I32("relation-leu".reflectCtrlWith[Num](x_c, y_c), "sym-relation-leu".reflectCtrlWith[SymVal](x_s, y_s)) + case (I64(x_c, x_s), I64(y_c, y_s)) => I32("relation-leu".reflectCtrlWith[Num](x_c, y_c), "sym-relation-leu".reflectCtrlWith[SymVal](x_s, y_s)) } } def >=(rhs: StagedNum): StagedNum = { (num, rhs) match { - case (I32(x), I32(y)) => I32("relation-ge".reflectCtrlWith[Num](x, y)) - case (I64(x), I64(y)) => I32("relation-ge".reflectCtrlWith[Num](x, y)) + case (I32(x_c, x_s), I32(y_c, y_s)) => I32("relation-ge".reflectCtrlWith[Num](x_c, y_c), "sym-relation-ge".reflectCtrlWith[SymVal](x_s, y_s)) + case (I64(x_c, x_s), I64(y_c, y_s)) => I32("relation-ge".reflectCtrlWith[Num](x_c, y_c), "sym-relation-ge".reflectCtrlWith[SymVal](x_s, y_s)) } } def geu(rhs: StagedNum): StagedNum = { (num, rhs) match { - case (I32(x), I32(y)) => I32("relation-geu".reflectCtrlWith[Num](x, y)) - case (I64(x), I64(y)) => I32("relation-geu".reflectCtrlWith[Num](x, y)) - } - } - } -} - -trait StagedWasmScalaGen extends ScalaGenBase with SAICodeGenBase { - override def mayInline(n: Node): Boolean = n match { - case Node(s, "stack-pop", _, _) => false - case _ => super.mayInline(n) - } - - override def traverse(n: Node): Unit = n match { - case Node(_, "stack-drop", List(n), _) => - emit("Stack.drop("); shallow(n); emit(")\n") - case Node(_, "stack-reset", List(n), _) => - emit("Stack.reset("); shallow(n); emit(")\n") - case Node(_, "stack-init", _, _) => - emit("Stack.initialize()\n") - case Node(_, "stack-print", _, _) => - emit("Stack.print()\n") - case Node(_, "frame-push", List(i), _) => - emit("Frames.pushFrame("); shallow(i); emit(")\n") - case Node(_, "frame-pop", List(i), _) => - emit("Frames.popFrame("); shallow(i); emit(")\n") - case Node(_, "frame-putAll", List(args), _) => - emit("Frames.putAll("); shallow(args); emit(")\n") - case Node(_, "frame-set", List(i, value), _) => - emit("Frames.set("); shallow(i); emit(", "); shallow(value); emit(")\n") - case Node(_, "global-set", List(i, value), _) => - emit("Global.globalSet("); shallow(i); emit(", "); shallow(value); emit(")\n") - case _ => super.traverse(n) - } - - // code generation for pure nodes - override def shallow(n: Node): Unit = n match { - case Node(_, "frame-get", List(i), _) => - emit("Frames.get("); shallow(i); emit(")") - case Node(_, "frame-pop", List(i), _) => - emit("Frames.popFrame("); shallow(i); emit(")") - case Node(_, "stack-push", List(value), _) => - emit("Stack.push("); shallow(value); emit(")") - case Node(_, "stack-pop", _, _) => - emit("Stack.pop()") - case Node(_, "stack-peek", _, _) => - emit("Stack.peek") - case Node(_, "stack-take", List(n), _) => - emit("Stack.take("); shallow(n); emit(")") - case Node(_, "stack-size", _, _) => - emit("Stack.size") - case Node(_, "global-get", List(i), _) => - emit("Global.globalGet("); shallow(i); emit(")") - case Node(_, "binary-add", List(lhs, rhs), _) => - shallow(lhs); emit(" + "); shallow(rhs) - case Node(_, "binary-sub", List(lhs, rhs), _) => - shallow(lhs); emit(" - "); shallow(rhs) - case Node(_, "binary-mul", List(lhs, rhs), _) => - shallow(lhs); emit(" * "); shallow(rhs) - case Node(_, "binary-div", List(lhs, rhs), _) => - shallow(lhs); emit(" / "); shallow(rhs) - case Node(_, "binary-shl", List(lhs, rhs), _) => - shallow(lhs); emit(" << "); shallow(rhs) - case Node(_, "binary-shr", List(lhs, rhs), _) => - shallow(lhs); emit(" >> "); shallow(rhs) - case Node(_, "binary-and", List(lhs, rhs), _) => - shallow(lhs); emit(" & "); shallow(rhs) - case Node(_, "relation-eq", List(lhs, rhs), _) => - shallow(lhs); emit(" == "); shallow(rhs) - case Node(_, "relation-ne", List(lhs, rhs), _) => - shallow(lhs); emit(" != "); shallow(rhs) - case Node(_, "relation-lt", List(lhs, rhs), _) => - shallow(lhs); emit(" < "); shallow(rhs) - case Node(_, "relation-ltu", List(lhs, rhs), _) => - shallow(lhs); emit(" < "); shallow(rhs) - case Node(_, "relation-gt", List(lhs, rhs), _) => - shallow(lhs); emit(" > "); shallow(rhs) - case Node(_, "relation-gtu", List(lhs, rhs), _) => - shallow(lhs); emit(" > "); shallow(rhs) - case Node(_, "relation-le", List(lhs, rhs), _) => - shallow(lhs); emit(" <= "); shallow(rhs) - case Node(_, "relation-leu", List(lhs, rhs), _) => - shallow(lhs); emit(" <= "); shallow(rhs) - case Node(_, "relation-ge", List(lhs, rhs), _) => - shallow(lhs); emit(" >= "); shallow(rhs) - case Node(_, "relation-geu", List(lhs, rhs), _) => - shallow(lhs); emit(" >= "); shallow(rhs) - case Node(_, "num-to-int", List(num), _) => - shallow(num); emit(".toInt") - case Node(_, "no-op", _, _) => - emit("()") - case _ => super.shallow(n) - } -} - -trait WasmToScalaCompilerDriver[A, B] - extends SAIDriver[A, B] with StagedWasmEvaluator { q => - override val codegen = new StagedWasmScalaGen { - val IR: q.type = q - import IR._ - override def remap(m: Manifest[_]): String = { - if (m.toString.endsWith("Stack")) "Stack" - else if(m.toString.endsWith("Frame")) "Frame" - else super.remap(m) - } - } - - override val prelude = - """ -object Prelude { - sealed abstract class Num { - def +(that: Num): Num = (this, that) match { - case (I32V(x), I32V(y)) => I32V(x + y) - case (I64V(x), I64V(y)) => I64V(x + y) - case _ => throw new RuntimeException("Invalid addition") - } - - def -(that: Num): Num = (this, that) match { - case (I32V(x), I32V(y)) => I32V(x - y) - case (I64V(x), I64V(y)) => I64V(x - y) - case _ => throw new RuntimeException("Invalid subtraction") - } - - def !=(that: Num): Num = (this, that) match { - case (I32V(x), I32V(y)) => I32V(if (x != y) 1 else 0) - case (I64V(x), I64V(y)) => I32V(if (x != y) 1 else 0) - case _ => throw new RuntimeException("Invalid inequality") - } - - def toInt: Int = this match { - case I32V(i) => i - case I64V(i) => i.toInt - } - } - case class I32V(i: Int) extends Num - case class I64V(i: Long) extends Num - -object Stack { - private val buffer = new scala.collection.mutable.ArrayBuffer[Num]() - def push(v: Num): Unit = buffer.append(v) - def pop(): Num = { - buffer.remove(buffer.size - 1) - } - def peek: Num = { - buffer.last - } - def size: Int = buffer.size - def drop(n: Int): Unit = { - buffer.remove(buffer.size - n, n) - } - def take(n: Int): List[Num] = { - val xs = buffer.takeRight(n).toList - drop(n) - xs - } - def reset(size: Int): Unit = { - info(s"Reset stack to size $size") - while (buffer.size > size) { - buffer.remove(buffer.size - 1) - } - } - def initialize(): Unit = buffer.clear() - def print(): Unit = { - println("Stack: " + buffer.mkString(", ")) - } -} - - class Frame(val size: Int) { - private val data = new Array[Num](size) - def apply(i: Int): Num = { - info(s"frame(${i}) is ${data(i)}") - data(i) - } - def update(i: Int, v: Num): Unit = { - info(s"set frame(${i}) to ${v}") - data(i) = v - } - def putAll(xs: List[Num]): Unit = { - for (i <- 0 until xs.size) { - data(i) = xs(i) + case (I32(x_c, x_s), I32(y_c, y_s)) => I32("relation-geu".reflectCtrlWith[Num](x_c, y_c), "sym-relation-geu".reflectCtrlWith[SymVal](x_s, y_s)) + case (I64(x_c, x_s), I64(y_c, y_s)) => I32("relation-geu".reflectCtrlWith[Num](x_c, y_c), "sym-relation-geu".reflectCtrlWith[SymVal](x_s, y_s)) } } - override def toString: String = { - "Frame(" + data.mkString(", ") + ")" - } - } - - object Frames { - private var frames = List[Frame]() - def pushFrame(size: Int): Unit = { - frames = new Frame(size) :: frames - } - def popFrame(): Unit = { - frames = frames.tail - } - def top: Frame = frames.head - def set(i: Int, v: Num): Unit = { - top(i) = v - } - def get(i: Int): Num = { - top(i) - } - } - - object Global { - // TODO: create global with specific size - private val globals = new Array[Num](10) - def globalGet(i: Int): Num = globals(i) - def globalSet(i: Int, v: Num): Unit = globals(i) = v - } - - def info(xs: Any*): Unit = { - if (System.getenv("DEBUG") != null) { - println("[INFO] " + xs.mkString(" ")) - } - } -} -import Prelude._ - - -object Main { - def main(args: Array[String]): Unit = { - val snippet = new Snippet() - snippet(()) - } -} -""" -} - - -object WasmToScalaCompiler { - def compile(moduleInst: ModuleInstance, main: Option[String], printRes: Boolean = false): String = { - println(s"Now compiling wasm module with entry function $main") - val code = new WasmToScalaCompilerDriver[Unit, Unit] { - def module: ModuleInstance = moduleInst - def snippet(x: Rep[Unit]): Rep[Unit] = { - evalTop(main, printRes) - } - } - code.code } } @@ -1168,3 +945,4 @@ object WasmToCppCompiler { } + From 2143050a320d3fa182aa361bc6b9c154aff9045d Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Mon, 7 Jul 2025 21:46:15 +0800 Subject: [PATCH 04/53] maintain a symbolic stack during the execution --- headers/wasm.hpp | 1 + headers/wasm/concrete_rt.hpp | 7 +- headers/wasm/symbolic_rt.hpp | 72 +++++++++++++++++++ .../scala/wasm/StagedConcolicMiniWasm.scala | 52 ++++++++++---- .../genwasym/TestStagedConcolicEval.scala | 33 +++++++++ 5 files changed, 152 insertions(+), 13 deletions(-) create mode 100644 headers/wasm/symbolic_rt.hpp create mode 100644 src/test/scala/genwasym/TestStagedConcolicEval.scala diff --git a/headers/wasm.hpp b/headers/wasm.hpp index 21da2ff7..c7e98b6e 100644 --- a/headers/wasm.hpp +++ b/headers/wasm.hpp @@ -2,5 +2,6 @@ #define WASM_HEADERS #include "wasm/concrete_rt.hpp" +#include "wasm/symbolic_rt.hpp" #endif \ No newline at end of file diff --git a/headers/wasm/concrete_rt.hpp b/headers/wasm/concrete_rt.hpp index 34d739f4..e994cbde 100644 --- a/headers/wasm/concrete_rt.hpp +++ b/headers/wasm/concrete_rt.hpp @@ -1,3 +1,6 @@ +#ifndef WASM_CONCRETE_RT_HPP +#define WASM_CONCRETE_RT_HPP + #include #include #include @@ -200,4 +203,6 @@ struct Memory_t { } }; -static Memory_t Memory(1); // 1 page memory \ No newline at end of file +static Memory_t Memory(1); // 1 page memory + +#endif // WASM_CONCRETE_RT_HPP \ No newline at end of file diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp new file mode 100644 index 00000000..d03509ac --- /dev/null +++ b/headers/wasm/symbolic_rt.hpp @@ -0,0 +1,72 @@ +#ifndef WASM_SYMBOLIC_RT_HPP +#define WASM_SYMBOLIC_RT_HPP + +#include "concrete_rt.hpp" +#include + +class SymVal { +public: + SymVal operator+(const SymVal &other) const { + // Define how to add two symbolic values + // Not implemented yet + return SymVal(); + } + + SymVal is_zero() const { + // Check if the symbolic value is zero + // Not implemented yet + return SymVal(); + } +}; + +class SymStack_t { +public: + void push(SymVal val) { + // Push a symbolic value to the stack + // Not implemented yet + } + + SymVal pop() { + // Pop a symbolic value from the stack + // Not implemented yet + return SymVal(); + } + + SymVal peek() { return SymVal(); } +}; + +static SymStack_t SymStack; + +class SymFrames_t { +public: + void pushFrame(int size) { + // Push a new frame with the given size + // Not implemented yet + } + std::monostate popFrame(int size) { + // Pop the frame of the given size + // Not implemented yet + return std::monostate(); + } + + SymVal get(int index) { + // Get the symbolic value at the given index + // Not implemented yet + return SymVal(); + } + + void set(int index, SymVal val) { + // Set the symbolic value at the given index + // Not implemented yet + } +}; + +static SymFrames_t SymFrames; + +static SymVal Concrete(Num num) { + // Convert a concrete number to a symbolic value + // Not implemented yet + return SymVal(); +} + +#endif // WASM_SYMBOLIC_RT_HPP \ No newline at end of file diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index 22b593a9..c41c7c42 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -40,7 +40,7 @@ trait StagedWasmEvaluator extends SAIOps { case class F32(i: Rep[Num], s: Rep[SymVal]) extends StagedNum case class F64(i: Rep[Num], s: Rep[SymVal]) extends StagedNum - implicit def toStagedNum(num: Num): StagedNum = { + def toStagedNum(num: Num): StagedNum = { num match { case I32V(_) => I32(num, Concrete(num)) case I64V(_) => I64(num, Concrete(num)) @@ -118,7 +118,7 @@ trait StagedWasmEvaluator extends SAIOps { val (_, newCtx) = Stack.pop() eval(rest, kont, mkont, trail)(newCtx) case WasmConst(num) => - val newCtx = Stack.push(num) + val newCtx = Stack.push(toStagedNum(num)) eval(rest, kont, mkont, trail)(newCtx) case LocalGet(i) => val newCtx = Stack.push(Frames.get(i)) @@ -155,7 +155,10 @@ trait StagedWasmEvaluator extends SAIOps { case MemorySize => ??? case MemoryGrow => val (delta, newCtx1) = Stack.pop() - val newCtx2 = Stack.push(Values.I32V(Memory.grow(delta.toInt)))(newCtx1) + val ret = Memory.grow(delta.toInt) + val retNum = Values.I32V(ret) + val retSym = "Concrete".reflectCtrlWith[SymVal](retNum) + val newCtx2 = Stack.push(I32(retNum, retSym))(newCtx1) eval(rest, kont, mkont, trail)(newCtx2) case MemoryFill => ??? case Unreachable => unreachable() @@ -220,6 +223,7 @@ trait StagedWasmEvaluator extends SAIOps { val newRestCtx = Stack.shift(offset, funcTy.out.size)(restCtx) eval(rest, kont, mk, trail)(newRestCtx) }) + // TODO: put the cond.s to path condition if (cond.toInt != 0) { eval(thn, restK _, mkont, restK _ :: trail)(newCtx) } else { @@ -232,6 +236,7 @@ trait StagedWasmEvaluator extends SAIOps { case BrIf(label) => val (cond, newCtx) = Stack.pop() info(s"The br_if(${label})'s condition is ", cond.toInt) + // TODO: put the cond.s to path condition if (cond.toInt != 0) { info(s"Jump to $label") trail(label)(newCtx)(mkont) @@ -320,7 +325,7 @@ trait StagedWasmEvaluator extends SAIOps { } def evalTestOp(op: TestOp, value: StagedNum): StagedNum = op match { - case Eqz(_) => Values.I32V(if (value.toInt == 0) 1 else 0) + case Eqz(_) => value.isZero } def evalUnaryOp(op: UnaryOp, value: StagedNum): StagedNum = op match { @@ -523,6 +528,7 @@ trait StagedWasmEvaluator extends SAIOps { I32("I32V".reflectCtrlWith[Num]("memory-load-int".reflectCtrlWith[Int](base, offset)), "sym-load-int-todo".reflectCtrlWith[SymVal](base, offset)) } + // Returns the previous memory size on success, or -1 if the memory cannot be grown. def grow(delta: Rep[Int]): Rep[Int] = { "memory-grow".reflectCtrlWith[Int](delta) } @@ -539,12 +545,12 @@ trait StagedWasmEvaluator extends SAIOps { // runtime values object Values { - def I32V(i: Rep[Int]): StagedNum = { - I32("I32V".reflectCtrlWith[Num](i), "Concrete".reflectCtrlWith[SymVal]("I32V".reflectCtrlWith[Num](i))) + def I32V(i: Rep[Int]): Rep[Num] = { + "I32V".reflectCtrlWith[Num](i) } - def I64V(i: Rep[Long]): StagedNum = { - I64("I64V".reflectCtrlWith[Num](i), "Concrete".reflectCtrlWith[SymVal]("I64V".reflectCtrlWith[Num](i))) + def I64V(i: Rep[Long]): Rep[Num] = { + "I64V".reflectCtrlWith[Num](i) } } @@ -574,6 +580,10 @@ trait StagedWasmEvaluator extends SAIOps { def toInt: Rep[Int] = "num-to-int".reflectCtrlWith[Int](num.i) + def isZero(): StagedNum = num match { + case I32(x_c, x_s) => I32(Values.I32V("is-zero".reflectCtrlWith[Int](num.toInt)), "sym-is-zero".reflectCtrlWith[SymVal](x_s)) + } + def clz(): StagedNum = num match { case I32(x_c, x_s) => I32("clz".reflectCtrlWith[Num](x_c), "sym-clz".reflectCtrlWith[SymVal](x_s)) case I64(x_c, x_s) => I64("clz".reflectCtrlWith[Num](x_c), "sym-clz".reflectCtrlWith[SymVal](x_s)) @@ -750,13 +760,16 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { else if (m.toString.endsWith("Global")) "Global" else if (m.toString.endsWith("I32V")) "I32V" else if (m.toString.endsWith("I64V")) "I64V" + else if (m.toString.endsWith("SymVal")) "SymVal" + else super.remap(m) } - // for now, the traverse/shallow is same as the scala backend's override def traverse(n: Node): Unit = n match { case Node(_, "stack-push", List(value), _) => emit("Stack.push("); shallow(value); emit(");\n") + case Node(_, "sym-stack-push", List(s_value), _) => + emit("SymStack.push("); shallow(s_value); emit(");\n") case Node(_, "stack-drop", List(n), _) => emit("Stack.drop("); shallow(n); emit(");\n") case Node(_, "stack-init", _, _) => @@ -765,12 +778,14 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { emit("Stack.print();\n") case Node(_, "frame-push", List(i), _) => emit("Frames.pushFrame("); shallow(i); emit(");\n") + case Node(_, "sym-frame-push", List(i), _) => + emit("SymFrames.pushFrame("); shallow(i); emit(");\n") case Node(_, "frame-pop", List(i), _) => emit("Frames.popFrame("); shallow(i); emit(");\n") - case Node(_, "frame-putAll", List(args), _) => - emit("Frames.putAll("); shallow(args); emit(");\n") case Node(_, "frame-set", List(i, value), _) => emit("Frames.set("); shallow(i); emit(", "); shallow(value); emit(");\n") + case Node(_, "sym-frame-set", List(i, s_value), _) => + emit("SymFrames.set("); shallow(i); emit(", "); shallow(s_value); emit(");\n") case Node(_, "global-set", List(i, value), _) => emit("Global.globalSet("); shallow(i); emit(", "); shallow(value); emit(");\n") // Note: The following code is copied from the traverse of CppBackend.scala, try to avoid duplicated code @@ -787,10 +802,11 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { case _ => super.traverse(n) } - // code generation for pure nodes override def shallow(n: Node): Unit = n match { case Node(_, "frame-get", List(i), _) => emit("Frames.get("); shallow(i); emit(")") + case Node(_, "sym-frame-get", List(i), _) => + emit("SymFrames.get("); shallow(i); emit(")") case Node(_, "stack-drop", List(n), _) => emit("Stack.drop("); shallow(n); emit(")") case Node(_, "stack-push", List(value), _) => @@ -799,10 +815,16 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { emit("Stack.shift("); shallow(offset); emit(", "); shallow(size); emit(")") case Node(_, "stack-pop", _, _) => emit("Stack.pop()") + case Node(_, "sym-stack-pop", _, _) => + emit("SymStack.pop()") case Node(_, "frame-pop", List(i), _) => emit("Frames.popFrame("); shallow(i); emit(")") + case Node(_, "sym-frame-pop", List(i), _) => + emit("SymFrames.popFrame("); shallow(i); emit(")") case Node(_, "stack-peek", _, _) => emit("Stack.peek()") + case Node(_, "sym-stack-peek", _, _) => + emit("SymStack.peek()") case Node(_, "stack-take", List(n), _) => emit("Stack.take("); shallow(n); emit(")") case Node(_, "slice-reverse", List(slice), _) => @@ -817,8 +839,14 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { emit("Stack.size()") case Node(_, "global-get", List(i), _) => emit("Global.globalGet("); shallow(i); emit(")") + case Node(_, "is-zero", List(num), _) => + emit("(0 == "); shallow(num); emit(")") + case Node(_, "sym-is-zero", List(s_num), _) => + shallow(s_num); emit(".is_zero()") case Node(_, "binary-add", List(lhs, rhs), _) => shallow(lhs); emit(" + "); shallow(rhs) + case Node(_, "sym-binary-add", List(lhs, rhs), _) => + shallow(lhs); emit(" + "); shallow(rhs) case Node(_, "binary-sub", List(lhs, rhs), _) => shallow(lhs); emit(" - "); shallow(rhs) case Node(_, "binary-mul", List(lhs, rhs), _) => diff --git a/src/test/scala/genwasym/TestStagedConcolicEval.scala b/src/test/scala/genwasym/TestStagedConcolicEval.scala new file mode 100644 index 00000000..eef6ab01 --- /dev/null +++ b/src/test/scala/genwasym/TestStagedConcolicEval.scala @@ -0,0 +1,33 @@ +package gensym.wasm + +import org.scalatest.FunSuite + +import lms.core.stub.Adapter + +import gensym.wasm.miniwasm.{ModuleInstance} +import gensym.wasm.parser._ +import gensym.wasm.stagedconcolicminiwasm._ + +class TestStagedConcolicEval extends FunSuite { + def testFileToCpp(filename: String, main: Option[String] = None, expect: Option[List[Float]]=None) = { + val moduleInst = ModuleInstance(Parser.parseFile(filename)) + val cppFile = s"$filename.cpp" + val exe = s"$cppFile.exe" + WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, true) + + import sys.process._ + val result = s"./$exe".!! + println(result) + + expect.map(vs => { + val stackValues = result + .split("Stack contents: \n")(1) + .split("\n") + .map(_.toFloat) + .toList + assert(vs == stackValues) + }) + } + + test("ack-cpp") { testFileToCpp("./benchmarks/wasm/ack.wat", Some("real_main"), expect=Some(List(7))) } +} From 8d81fbe2e61a1f7e792be3fb5463d7ead9954ea4 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Wed, 9 Jul 2025 19:24:24 +0800 Subject: [PATCH 05/53] record path conditions --- headers/wasm/symbolic_rt.hpp | 18 ++++++++++++++++ .../scala/wasm/StagedConcolicMiniWasm.scala | 21 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index d03509ac..05fc41d4 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -17,6 +17,12 @@ class SymVal { // Not implemented yet return SymVal(); } + + SymVal negate() const { + // negate the symbolic condition by creating a new symbolic value + // not implemented yet + return SymVal(); + } }; class SymStack_t { @@ -69,4 +75,16 @@ static SymVal Concrete(Num num) { return SymVal(); } +class ExploreTree_t { +public: + std::monostate fillIfElseNode(SymVal s, bool branch) { + // fill the current node with the branch condition s + // parameter branch is redundant, to hint which branch we've entered + // Not implemented yet + return std::monostate(); + } +}; + +static ExploreTree_t ExploreTree; + #endif // WASM_SYMBOLIC_RT_HPP \ No newline at end of file diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index c41c7c42..1eb717e3 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -225,8 +225,10 @@ trait StagedWasmEvaluator extends SAIOps { }) // TODO: put the cond.s to path condition if (cond.toInt != 0) { + ExploreTree.fillWithIfElse(cond.s, true) eval(thn, restK _, mkont, restK _ :: trail)(newCtx) } else { + ExploreTree.fillWithIfElse(cond.s.not, false) eval(els, restK _, mkont, restK _ :: trail)(newCtx) } () @@ -239,9 +241,11 @@ trait StagedWasmEvaluator extends SAIOps { // TODO: put the cond.s to path condition if (cond.toInt != 0) { info(s"Jump to $label") + ExploreTree.fillWithIfElse(cond.s, true) trail(label)(newCtx)(mkont) } else { info(s"Continue") + ExploreTree.fillWithIfElse(cond.s.not, false) eval(rest, kont, mkont, trail)(newCtx) } () @@ -575,6 +579,13 @@ trait StagedWasmEvaluator extends SAIOps { } } + // Exploration tree, + object ExploreTree { + def fillWithIfElse(s: Rep[SymVal], branch: Boolean): Rep[Unit] = { + "tree-fill-if-else".reflectCtrlWith[Unit](s, branch) + } + } + // runtime Num type implicit class StagedNumOps(num: StagedNum) { @@ -733,6 +744,12 @@ trait StagedWasmEvaluator extends SAIOps { } } } + + implicit class SymbolicOps(s: Rep[SymVal]) { + def not(): Rep[SymVal] = { + "sym-not".reflectCtrlWith(s) + } + } } trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { @@ -881,6 +898,10 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { shallow(lhs); emit(" >= "); shallow(rhs) case Node(_, "num-to-int", List(num), _) => shallow(num); emit(".toInt()") + case Node(_, "tree-fill-if-else", List(s, b), _) => + emit("ExploreTree.fillIfElseNode("); shallow(s); emit(", "); shallow(b); emit(")") + case Node(_, "sym-not", List(s), _) => + shallow(s); emit(".negate()") case Node(_, "dummy", _, _) => emit("std::monostate()") case Node(_, "dummy-op", _, _) => emit("std::monostate()") case Node(_, "no-op", _, _) => From 61215b6c87a2ba1ffa3f88838c17dc3d7e3cfb86 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Wed, 9 Jul 2025 23:06:44 +0800 Subject: [PATCH 06/53] The branch node only needs to remember the positive condition. use the sub-nodes of the branch to classify whether the execution is true or false --- headers/wasm/symbolic_rt.hpp | 6 ++++- .../scala/wasm/StagedConcolicMiniWasm.scala | 24 ++++++++++++------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 05fc41d4..a0199db4 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -77,12 +77,16 @@ static SymVal Concrete(Num num) { class ExploreTree_t { public: - std::monostate fillIfElseNode(SymVal s, bool branch) { + std::monostate fillIfElseNode(SymVal s) { // fill the current node with the branch condition s // parameter branch is redundant, to hint which branch we've entered // Not implemented yet return std::monostate(); } + + std::monostate moveCursor(bool branch) { + return std::monostate(); + } }; static ExploreTree_t ExploreTree; diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index 1eb717e3..d31af3cb 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -224,11 +224,12 @@ trait StagedWasmEvaluator extends SAIOps { eval(rest, kont, mk, trail)(newRestCtx) }) // TODO: put the cond.s to path condition + ExploreTree.fillWithIfElse(cond.s) if (cond.toInt != 0) { - ExploreTree.fillWithIfElse(cond.s, true) + ExploreTree.moveCursor(true) eval(thn, restK _, mkont, restK _ :: trail)(newCtx) } else { - ExploreTree.fillWithIfElse(cond.s.not, false) + ExploreTree.moveCursor(false) eval(els, restK _, mkont, restK _ :: trail)(newCtx) } () @@ -239,13 +240,14 @@ trait StagedWasmEvaluator extends SAIOps { val (cond, newCtx) = Stack.pop() info(s"The br_if(${label})'s condition is ", cond.toInt) // TODO: put the cond.s to path condition + ExploreTree.fillWithIfElse(cond.s) if (cond.toInt != 0) { info(s"Jump to $label") - ExploreTree.fillWithIfElse(cond.s, true) + ExploreTree.moveCursor(true) trail(label)(newCtx)(mkont) } else { info(s"Continue") - ExploreTree.fillWithIfElse(cond.s.not, false) + ExploreTree.moveCursor(false) eval(rest, kont, mkont, trail)(newCtx) } () @@ -581,8 +583,12 @@ trait StagedWasmEvaluator extends SAIOps { // Exploration tree, object ExploreTree { - def fillWithIfElse(s: Rep[SymVal], branch: Boolean): Rep[Unit] = { - "tree-fill-if-else".reflectCtrlWith[Unit](s, branch) + def fillWithIfElse(s: Rep[SymVal]): Rep[Unit] = { + "tree-fill-if-else".reflectCtrlWith[Unit](s) + } + + def moveCursor(branch: Boolean): Rep[Unit] = { + "tree-move-cursor".reflectCtrlWith[Unit](branch) } } @@ -898,8 +904,10 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { shallow(lhs); emit(" >= "); shallow(rhs) case Node(_, "num-to-int", List(num), _) => shallow(num); emit(".toInt()") - case Node(_, "tree-fill-if-else", List(s, b), _) => - emit("ExploreTree.fillIfElseNode("); shallow(s); emit(", "); shallow(b); emit(")") + case Node(_, "tree-fill-if-else", List(s), _) => + emit("ExploreTree.fillIfElseNode("); shallow(s); emit(")") + case Node(_, "tree-move-cursor", List(b), _) => + emit("ExploreTree.moveCursor("); shallow(b); emit(")") case Node(_, "sym-not", List(s), _) => shallow(s); emit(".negate()") case Node(_, "dummy", _, _) => emit("std::monostate()") From d18b5f7ca62dc6a96baca04eb38f638d06e41b0b Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Sun, 13 Jul 2025 14:57:36 +0800 Subject: [PATCH 07/53] symbolic runtime for explore tree --- headers/wasm/symbolic_rt.hpp | 86 ++++++++++++++++++- .../scala/wasm/StagedConcolicMiniWasm.scala | 8 ++ 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index a0199db4..4373af84 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -2,6 +2,9 @@ #define WASM_SYMBOLIC_RT_HPP #include "concrete_rt.hpp" +#include +#include +#include #include class SymVal { @@ -75,18 +78,93 @@ static SymVal Concrete(Num num) { return SymVal(); } +struct Node; + +struct NodeBox { + explicit NodeBox(); + std::unique_ptr node; + NodeBox *parent; +}; + +struct Node { + virtual ~Node(){}; + virtual std::string to_string() = 0; +}; + +struct IfElseNode : Node { + SymVal cond; + std::unique_ptr true_branch; + std::unique_ptr false_branch; + + IfElseNode(SymVal cond) + : cond(cond), true_branch(std::make_unique()), + false_branch(std::make_unique()) {} + + 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; + } +}; + +struct UnExploredNode : Node { + UnExploredNode() {} + std::string to_string() override { return "UnexploredNode"; } +}; + +static UnExploredNode unexplored; + +inline NodeBox::NodeBox() + : node(std::make_unique< + UnExploredNode>() /* TODO: avoid allocation of unexplored node */) {} + class ExploreTree_t { public: - std::monostate fillIfElseNode(SymVal s) { - // fill the current node with the branch condition s - // parameter branch is redundant, to hint which branch we've entered - // Not implemented yet + explicit ExploreTree_t() + : root(std::make_unique()), cursor(root.get()) {} + std::monostate fillIfElseNode(SymVal cond) { + // fill the current node with an ifelse branch node + cursor->node = std::make_unique(cond); return std::monostate(); } std::monostate moveCursor(bool branch) { + 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) { + cursor = if_else_node->true_branch.get(); + } else { + cursor = if_else_node->false_branch.get(); + } return std::monostate(); } + + std::monostate print() { + std::cout << root->node->to_string() << std::endl; + return std::monostate(); + } + +private: + std::unique_ptr root; + NodeBox *cursor; }; static ExploreTree_t ExploreTree; diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index d31af3cb..4366ac78 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -16,6 +16,7 @@ import gensym.wasm.miniwasm.{ModuleInstance} import gensym.wasm.symbolic.{SymVal} import gensym.lmsx.{SAIDriver, StringOps, SAIOps, SAICodeGenBase, CppSAIDriver, CppSAICodeGenBase} import gensym.wasm.symbolic.Concrete +import gensym.wasm.symbolic.ExploreTree @virtualize trait StagedWasmEvaluator extends SAIOps { @@ -405,6 +406,7 @@ trait StagedWasmEvaluator extends SAIOps { info("Exiting the program...") if (printRes) { Stack.print() + ExploreTree.print() } "no-op".reflectCtrlWith[Unit]() } @@ -590,6 +592,10 @@ trait StagedWasmEvaluator extends SAIOps { def moveCursor(branch: Boolean): Rep[Unit] = { "tree-move-cursor".reflectCtrlWith[Unit](branch) } + + def print(): Rep[Unit] = { + "tree-print".reflectCtrlWith[Unit]() + } } // runtime Num type @@ -908,6 +914,8 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { emit("ExploreTree.fillIfElseNode("); shallow(s); emit(")") case Node(_, "tree-move-cursor", List(b), _) => emit("ExploreTree.moveCursor("); shallow(b); emit(")") + case Node(_, "tree-print", List(), _) => + emit("ExploreTree.print()") case Node(_, "sym-not", List(s), _) => shallow(s); emit(".negate()") case Node(_, "dummy", _, _) => emit("std::monostate()") From 92ab8ba0d03ec8ef8f3dd9a3b43a32d0c1bfc158 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Mon, 14 Jul 2025 01:30:20 +0800 Subject: [PATCH 08/53] add a to graphviz method, enhancing debug experience --- headers/wasm/symbolic_rt.hpp | 73 ++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 4373af84..02c3021c 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -3,8 +3,10 @@ #include "concrete_rt.hpp" #include +#include #include #include +#include #include class SymVal { @@ -89,8 +91,29 @@ struct NodeBox { struct Node { virtual ~Node(){}; 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"; + } + int get_next_id(int &id_counter) { return id_counter++; } + virtual int 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; }; +// 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; @@ -120,11 +143,56 @@ struct IfElseNode : Node { result += "}"; return result; } + + int 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; + + os << " node" << current_node_dot_id << " [label=\"If\"," + << "shape=diamond, fillcolor=lightyellow];\n"; + + // Draw edge from parent if this is not the root node + if (parent_dot_id != -1) { + os << " node" << parent_dot_id << " -> node" << current_node_dot_id; + if (!edge_label.empty()) { + os << " [label=\"" << edge_label << "\"]"; + } + os << ";\n"; + } + 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"); + return current_node_dot_id; + } }; struct UnExploredNode : Node { UnExploredNode() {} std::string to_string() override { return "UnexploredNode"; } + +protected: + int generate_dot(std::ostream &os, int parent_dot_id, + const std::string &edge_label) override { + int current_node_dot_id = current_id++; + + os << " node" << current_node_dot_id + << " [label=\"Unexplored\", shape=octagon, style=filled, " + "fillcolor=lightgrey];\n"; + + if (parent_dot_id != -1) { + os << " node" << parent_dot_id << " -> node" << current_node_dot_id; + if (!edge_label.empty()) { + os << " [label=\"" << edge_label << "\"]"; + } + os << ";\n"; + } + + return current_node_dot_id; + } }; static UnExploredNode unexplored; @@ -162,6 +230,11 @@ class ExploreTree_t { return std::monostate(); } + std::monostate to_graphviz(std::ostream &os) { + root->node->to_graphviz(os); + return std::monostate(); + } + private: std::unique_ptr root; NodeBox *cursor; From e1d7fc8fe801a877c8ef7e09d904e8714f6bbd17 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Mon, 14 Jul 2025 23:49:20 +0800 Subject: [PATCH 09/53] put symbolic expression on the SymStack --- headers/wasm/symbolic_rt.hpp | 126 +++++++++++++++++++++++++---------- 1 file changed, 92 insertions(+), 34 deletions(-) diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 02c3021c..fac3ce6d 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -8,42 +8,104 @@ #include #include #include +#include -class SymVal { +class Symbolic {}; + +class SymConcrete : public Symbolic { public: - SymVal operator+(const SymVal &other) const { - // Define how to add two symbolic values - // Not implemented yet - return SymVal(); - } + Num value; + SymConcrete(Num num) : value(num) {} +}; - SymVal is_zero() const { - // Check if the symbolic value is zero - // Not implemented yet - return SymVal(); - } +struct SymBinary; - SymVal negate() const { - // negate the symbolic condition by creating a new symbolic value - // not implemented yet - return SymVal(); - } +struct SymVal { + std::shared_ptr symptr; + + SymVal() : symptr(nullptr) {} + SymVal(std::shared_ptr symptr) : symptr(symptr) {} + + 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 eq(const SymVal &other) const; + SymVal neq(const SymVal &other) const; + SymVal lt(const SymVal &other) const; + SymVal leq(const SymVal &other) const; + SymVal gt(const SymVal &other) const; + SymVal geq(const SymVal &other) const; +}; + +inline SymVal Concrete(Num num) { + return SymVal(std::make_shared(num)); +} + +enum Operation { ADD, SUB, MUL, DIV, EQ, NEQ, LT, LEQ, GT, GEQ }; + +struct SymBinary : Symbolic { + Operation op; + SymVal lhs; + SymVal rhs; + + SymBinary(Operation op, SymVal lhs, SymVal rhs) + : op(op), lhs(lhs), rhs(rhs) {} }; +inline SymVal SymVal::add(const SymVal &other) const { + return SymVal(std::make_shared(ADD, this, other)); +} + +inline SymVal SymVal::minus(const SymVal &other) const { + return SymVal(std::make_shared(SUB, this, other)); +} + +inline SymVal SymVal::mul(const SymVal &other) const { + return SymVal(std::make_shared(MUL, this, other)); +} + +inline SymVal SymVal::div(const SymVal &other) const { + return SymVal(std::make_shared(DIV, this, other)); +} + +inline SymVal SymVal::eq(const SymVal &other) const { + return SymVal(std::make_shared(EQ, this, other)); +} + +inline SymVal SymVal::neq(const SymVal &other) const { + return SymVal(std::make_shared(NEQ, this, other)); +} +inline SymVal SymVal::lt(const SymVal &other) const { + return SymVal(std::make_shared(LT, this, other)); +} +inline SymVal SymVal::leq(const SymVal &other) const { + return SymVal(std::make_shared(LEQ, this, other)); +} +inline SymVal SymVal::gt(const SymVal &other) const { + return SymVal(std::make_shared(GT, this, other)); +} +inline SymVal SymVal::geq(const SymVal &other) const { + return SymVal(std::make_shared(GEQ, this, other)); +} + class SymStack_t { public: void push(SymVal val) { // Push a symbolic value to the stack - // Not implemented yet + stack.push_back(val); } SymVal pop() { // Pop a symbolic value from the stack - // Not implemented yet - return SymVal(); + auto ret = stack.back(); + stack.pop_back(); + return ret; } - SymVal peek() { return SymVal(); } + SymVal peek() { return stack.back(); } + + std::vector stack; }; static SymStack_t SymStack; @@ -52,34 +114,30 @@ class SymFrames_t { public: void pushFrame(int size) { // Push a new frame with the given size - // Not implemented yet + stack.resize(size + stack.size()); } std::monostate popFrame(int size) { // Pop the frame of the given size - // Not implemented yet + stack.resize(stack.size() - size); return std::monostate(); } SymVal get(int index) { - // Get the symbolic value at the given index - // Not implemented yet - return SymVal(); + // Get the symbolic value at the given frame index + return stack[stack.size() - 1 - index]; } void set(int index, SymVal val) { // Set the symbolic value at the given index // Not implemented yet + stack[stack.size() - 1 - index] = val; } + + std::vector stack; }; static SymFrames_t SymFrames; -static SymVal Concrete(Num num) { - // Convert a concrete number to a symbolic value - // Not implemented yet - return SymVal(); -} - struct Node; struct NodeBox { @@ -115,11 +173,11 @@ struct Node { int Node::current_id = 0; struct IfElseNode : Node { - SymVal cond; + Symbolic cond; std::unique_ptr true_branch; std::unique_ptr false_branch; - IfElseNode(SymVal cond) + IfElseNode(Symbolic cond) : cond(cond), true_branch(std::make_unique()), false_branch(std::make_unique()) {} @@ -205,7 +263,7 @@ class ExploreTree_t { public: explicit ExploreTree_t() : root(std::make_unique()), cursor(root.get()) {} - std::monostate fillIfElseNode(SymVal cond) { + std::monostate fillIfElseNode(Symbolic cond) { // fill the current node with an ifelse branch node cursor->node = std::make_unique(cond); return std::monostate(); From 77a4e6f58547a71a5544779f06f41e92b5398375 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Wed, 16 Jul 2025 22:50:08 +0800 Subject: [PATCH 10/53] `type.symbolic` instruction --- headers/wasm/symbolic_rt.hpp | 70 +++++++++++++++---- .../scala/wasm/StagedConcolicMiniWasm.scala | 62 +++++++++++++++- .../genwasym/TestStagedConcolicEval.scala | 4 ++ 3 files changed, 120 insertions(+), 16 deletions(-) diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index fac3ce6d..e0d3feef 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -10,7 +10,19 @@ #include #include -class Symbolic {}; +class Symbolic { +public: + Symbolic() {} // TODO: remove this default constructor later + virtual ~Symbolic() = default; // Make Symbolic polymorphic +}; + +class Symbol : public Symbolic { +public: + Symbol(int id) : id(id) {} + +private: + int id; +}; class SymConcrete : public Symbolic { public: @@ -26,6 +38,11 @@ struct SymVal { SymVal() : symptr(nullptr) {} SymVal(std::shared_ptr symptr) : symptr(symptr) {} + // data structure operations + SymVal makeSymbolic() const; + + // arithmetic operations + SymVal is_zero() const; SymVal add(const SymVal &other) const; SymVal minus(const SymVal &other) const; SymVal mul(const SymVal &other) const; @@ -54,39 +71,53 @@ struct SymBinary : Symbolic { }; inline SymVal SymVal::add(const SymVal &other) const { - return SymVal(std::make_shared(ADD, this, other)); + return SymVal(std::make_shared(ADD, *this, other)); } inline SymVal SymVal::minus(const SymVal &other) const { - return SymVal(std::make_shared(SUB, this, other)); + return SymVal(std::make_shared(SUB, *this, other)); } inline SymVal SymVal::mul(const SymVal &other) const { - return SymVal(std::make_shared(MUL, this, other)); + return SymVal(std::make_shared(MUL, *this, other)); } inline SymVal SymVal::div(const SymVal &other) const { - return SymVal(std::make_shared(DIV, this, other)); + return SymVal(std::make_shared(DIV, *this, other)); } inline SymVal SymVal::eq(const SymVal &other) const { - return SymVal(std::make_shared(EQ, this, other)); + return SymVal(std::make_shared(EQ, *this, other)); } inline SymVal SymVal::neq(const SymVal &other) const { - return SymVal(std::make_shared(NEQ, this, other)); + return SymVal(std::make_shared(NEQ, *this, other)); } inline SymVal SymVal::lt(const SymVal &other) const { - return SymVal(std::make_shared(LT, this, other)); + return SymVal(std::make_shared(LT, *this, other)); } inline SymVal SymVal::leq(const SymVal &other) const { - return SymVal(std::make_shared(LEQ, this, other)); + return SymVal(std::make_shared(LEQ, *this, other)); } inline SymVal SymVal::gt(const SymVal &other) const { - return SymVal(std::make_shared(GT, this, other)); + return SymVal(std::make_shared(GT, *this, other)); } inline SymVal SymVal::geq(const SymVal &other) const { - return SymVal(std::make_shared(GEQ, this, other)); + return SymVal(std::make_shared(GEQ, *this, other)); +} +inline SymVal SymVal::is_zero() const { + return SymVal(std::make_shared(EQ, *this, Concrete(I32V(0)))); +} + +inline SymVal SymVal::makeSymbolic() const { + auto concrete = dynamic_cast(symptr.get()); + if (concrete) { + // If the symbolic value is a concrete value, use it to create a symbol + return SymVal(std::make_shared(concrete->value.toInt())); + } else { + throw std::runtime_error( + "Cannot make symbolic a non-concrete symbolic value"); + } } class SymStack_t { @@ -173,11 +204,11 @@ struct Node { int Node::current_id = 0; struct IfElseNode : Node { - Symbolic cond; + SymVal cond; std::unique_ptr true_branch; std::unique_ptr false_branch; - IfElseNode(Symbolic cond) + IfElseNode(SymVal cond) : cond(cond), true_branch(std::make_unique()), false_branch(std::make_unique()) {} @@ -263,7 +294,7 @@ class ExploreTree_t { public: explicit ExploreTree_t() : root(std::make_unique()), cursor(root.get()) {} - std::monostate fillIfElseNode(Symbolic cond) { + std::monostate fillIfElseNode(SymVal cond) { // fill the current node with an ifelse branch node cursor->node = std::make_unique(cond); return std::monostate(); @@ -300,4 +331,15 @@ class ExploreTree_t { static ExploreTree_t ExploreTree; +class SymEnv_t { +public: + Num read(SymVal sym) { + // Read a symbolic value from the symbolic environment + // For now, we just return a zero + return Num(0); + } +}; + +static SymEnv_t SymEnv; + #endif // WASM_SYMBOLIC_RT_HPP \ No newline at end of file diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index 4366ac78..01bb91c2 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -57,6 +57,15 @@ trait StagedWasmEvaluator extends SAIOps { case NumType(F32Type) => 4 case NumType(F64Type) => 8 } + + def toTagger: (Rep[Num], Rep[SymVal]) => StagedNum = { + ty match { + case NumType(I32Type) => I32 + case NumType(I64Type) => I64 + case NumType(F32Type) => F32 + case NumType(F64Type) => F64 + } + } } case class Context( @@ -121,6 +130,14 @@ trait StagedWasmEvaluator extends SAIOps { case WasmConst(num) => val newCtx = Stack.push(toStagedNum(num)) eval(rest, kont, mkont, trail)(newCtx) + case Symbolic(ty) => + val (id, newCtx1) = Stack.pop() + val symVal = id.makeSymbolic() + val concVal = SymEnv.read(symVal) + val tagger = ty.toTagger + val value = tagger(concVal, symVal) + val newCtx2 = Stack.push(value)(newCtx1) + eval(rest, kont, mkont, trail)(newCtx2) case LocalGet(i) => val newCtx = Stack.push(Frames.get(i)) eval(rest, kont, mkont, trail)(newCtx) @@ -326,6 +343,10 @@ trait StagedWasmEvaluator extends SAIOps { val (v, newCtx) = Stack.pop() println(v.toInt) eval(rest, kont, mkont, trail)(newCtx) + case Import("console", "assert", _) => + val (v, newCtx) = Stack.pop() + runtimeAssert(v.toInt != 0) + eval(rest, kont, mkont, trail)(newCtx) case Import(_, _, _) => throw new Exception(s"Unknown import at $funcIndex") case _ => throw new Exception(s"Definition at $funcIndex is not callable") } @@ -414,6 +435,10 @@ trait StagedWasmEvaluator extends SAIOps { evalTop(temp, main) } + def runtimeAssert(b: Rep[Boolean]): Rep[Unit] = { + "assert-true".reflectCtrlWith[Unit](b) + } + // stack operations object Stack { def shift(offset: Int, size: Int)(ctx: Context): Context = { @@ -598,6 +623,12 @@ trait StagedWasmEvaluator extends SAIOps { } } + object SymEnv { + def read(sym: Rep[SymVal]): Rep[Num] = { + "sym-env-read".reflectCtrlWith[Num](sym) + } + } + // runtime Num type implicit class StagedNumOps(num: StagedNum) { @@ -622,6 +653,10 @@ trait StagedWasmEvaluator extends SAIOps { case I64(x_c, x_s) => I64("popcnt".reflectCtrlWith[Num](x_c), "sym-popcnt".reflectCtrlWith[SymVal](x_s)) } + def makeSymbolic(): Rep[SymVal] = { + "make-symbolic".reflectCtrlWith[SymVal](num.s) + } + def +(rhs: StagedNum): StagedNum = { (num, rhs) match { case (I32(x_c, x_s), I32(y_c, y_s)) => I32("binary-add".reflectCtrlWith[Num](x_c, y_c), "sym-binary-add".reflectCtrlWith[SymVal](x_s, y_s)) @@ -778,6 +813,7 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { override def mayInline(n: Node): Boolean = n match { case Node(_, "stack-pop", _, _) | Node(_, "stack-peek", _, _) + | Node(_, "sym-stack-pop", _, _) => false case _ => super.mayInline(n) } @@ -874,8 +910,6 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { shallow(s_num); emit(".is_zero()") case Node(_, "binary-add", List(lhs, rhs), _) => shallow(lhs); emit(" + "); shallow(rhs) - case Node(_, "sym-binary-add", List(lhs, rhs), _) => - shallow(lhs); emit(" + "); shallow(rhs) case Node(_, "binary-sub", List(lhs, rhs), _) => shallow(lhs); emit(" - "); shallow(rhs) case Node(_, "binary-mul", List(lhs, rhs), _) => @@ -908,8 +942,32 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { shallow(lhs); emit(" >= "); shallow(rhs) case Node(_, "relation-geu", List(lhs, rhs), _) => shallow(lhs); emit(" >= "); shallow(rhs) + case Node(_, "sym-binary-add", List(lhs, rhs), _) => + shallow(lhs); emit(".add("); shallow(rhs); emit(")") + case Node(_, "sym-binary-mul", List(lhs, rhs), _) => + shallow(lhs); emit(".mul("); shallow(rhs); emit(")") + case Node(_, "sym-binary-div", List(lhs, rhs), _) => + shallow(lhs); emit(".div("); shallow(rhs); emit(")") + case Node(_, "sym-relation-le", List(lhs, rhs), _) => + shallow(lhs); emit(".leq("); shallow(rhs); emit(")") + case Node(_, "sym-relation-leu", List(lhs, rhs), _) => + shallow(lhs); emit(".leu("); shallow(rhs); emit(")") + case Node(_, "sym-relation-ge", List(lhs, rhs), _) => + shallow(lhs); emit(".ge("); shallow(rhs); emit(")") + case Node(_, "sym-relation-geu", List(lhs, rhs), _) => + shallow(lhs); emit(".geu("); shallow(rhs); emit(")") + case Node(_, "sym-relation-eq", List(lhs, rhs), _) => + shallow(lhs); emit(".eq("); shallow(rhs); emit(")") + case Node(_, "sym-relation-ne", List(lhs, rhs), _) => + shallow(lhs); emit(".neq("); shallow(rhs); emit(")") case Node(_, "num-to-int", List(num), _) => shallow(num); emit(".toInt()") + case Node(_, "make-symbolic", List(num), _) => + shallow(num); emit(".makeSymbolic()") + case Node(_, "sym-env-read", List(sym), _) => + emit("SymEnv.read("); shallow(sym); emit(")") + case Node(_, "assert-true", List(cond), _) => + emit("assert("); shallow(cond); emit(")") case Node(_, "tree-fill-if-else", List(s), _) => emit("ExploreTree.fillIfElseNode("); shallow(s); emit(")") case Node(_, "tree-move-cursor", List(b), _) => diff --git a/src/test/scala/genwasym/TestStagedConcolicEval.scala b/src/test/scala/genwasym/TestStagedConcolicEval.scala index eef6ab01..409bd07a 100644 --- a/src/test/scala/genwasym/TestStagedConcolicEval.scala +++ b/src/test/scala/genwasym/TestStagedConcolicEval.scala @@ -30,4 +30,8 @@ class TestStagedConcolicEval extends FunSuite { } test("ack-cpp") { testFileToCpp("./benchmarks/wasm/ack.wat", Some("real_main"), expect=Some(List(7))) } + + test("bug-finding") { + testFileToCpp("./benchmarks/wasm/branch-strip-buggy.wat", Some("real_main")) + } } From 314ff5fe8b848cb1f40ee774283489c0760eec35 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Wed, 16 Jul 2025 23:11:33 +0800 Subject: [PATCH 11/53] test staged concolic compilation in CI --- .github/workflows/scala.yml | 1 + benchmarks/wasm/branch-strip-buggy.wat | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index 4677da77..d2f8348b 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -79,3 +79,4 @@ jobs: sbt 'testOnly gensym.wasm.TestConcolic' sbt 'testOnly gensym.wasm.TestDriver' sbt 'testOnly gensym.wasm.TestStagedEval' + sbt 'testOnly gensym.wasm.TestStagedConcolicEval' diff --git a/benchmarks/wasm/branch-strip-buggy.wat b/benchmarks/wasm/branch-strip-buggy.wat index c957db7f..0685f0be 100644 --- a/benchmarks/wasm/branch-strip-buggy.wat +++ b/benchmarks/wasm/branch-strip-buggy.wat @@ -29,6 +29,7 @@ else i32.const 0 call 2 + i32.const 1 ;; to satisfy the type checker, this line will never be reached end end ) From 873936902fe34538c698fbd664df370b9133003c Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Thu, 17 Jul 2025 00:18:51 +0800 Subject: [PATCH 12/53] dump graphviz by default --- headers/wasm/symbolic_rt.hpp | 10 +++++++++ .../scala/wasm/StagedConcolicMiniWasm.scala | 22 ++++++++++++++----- .../genwasym/TestStagedConcolicEval.scala | 3 ++- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index e0d3feef..a97fd0dd 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -4,6 +4,7 @@ #include "concrete_rt.hpp" #include #include +#include #include #include #include @@ -324,6 +325,15 @@ class ExploreTree_t { return std::monostate(); } + std::monostate dump_graphviz(std::string filepath) { + std::ofstream ofs(filepath); + if (!ofs.is_open()) { + throw std::runtime_error("Failed to open explore_tree.dot for writing"); + } + to_graphviz(ofs); + return std::monostate(); + } + private: std::unique_ptr root; NodeBox *cursor; diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index 01bb91c2..3c342177 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -422,12 +422,15 @@ trait StagedWasmEvaluator extends SAIOps { Frames.popFrame(locals.size) } - def evalTop(main: Option[String], printRes: Boolean = false): Rep[Unit] = { + def evalTop(main: Option[String], printRes: Boolean, dumpTree: Option[String]): Rep[Unit] = { val haltK: Rep[Unit] => Rep[Unit] = (_) => { info("Exiting the program...") if (printRes) { Stack.print() - ExploreTree.print() + } + dumpTree match { + case Some(filePath) => ExploreTree.dumpGraphiviz(filePath) + case None => () } "no-op".reflectCtrlWith[Unit]() } @@ -621,6 +624,10 @@ trait StagedWasmEvaluator extends SAIOps { def print(): Rep[Unit] = { "tree-print".reflectCtrlWith[Unit]() } + + def dumpGraphiviz(filePath: String): Rep[Unit] = { + "tree-dump-graphviz".reflectCtrlWith[Unit](filePath) + } } object SymEnv { @@ -974,6 +981,8 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { emit("ExploreTree.moveCursor("); shallow(b); emit(")") case Node(_, "tree-print", List(), _) => emit("ExploreTree.print()") + case Node(_, "tree-dump-graphviz", List(f), _) => + emit("ExploreTree.dump_graphviz("); shallow(f); emit(")") case Node(_, "sym-not", List(s), _) => shallow(s); emit(".negate()") case Node(_, "dummy", _, _) => emit("std::monostate()") @@ -1033,12 +1042,12 @@ trait WasmToCppCompilerDriver[A, B] extends CppSAIDriver[A, B] with StagedWasmEv object WasmToCppCompiler { case class GeneratedCpp(source: String, headerFolders: List[String]) - def compile(moduleInst: ModuleInstance, main: Option[String], printRes: Boolean = false): GeneratedCpp = { + def compile(moduleInst: ModuleInstance, main: Option[String], printRes: Boolean, dumpTree: Option[String]): GeneratedCpp = { println(s"Now compiling wasm module with entry function $main") val driver = new WasmToCppCompilerDriver[Unit, Unit] { def module: ModuleInstance = moduleInst def snippet(x: Rep[Unit]): Rep[Unit] = { - evalTop(main, printRes) + evalTop(main, printRes, dumpTree) } } GeneratedCpp(driver.code, driver.codegen.includePaths.toList) @@ -1048,8 +1057,9 @@ object WasmToCppCompiler { main: Option[String], outputCpp: String, outputExe: String, - printRes: Boolean = false): Unit = { - val generated = compile(moduleInst, main, printRes) + printRes: Boolean, + dumpTree: Option[String]): Unit = { + val generated = compile(moduleInst, main, printRes, dumpTree) val code = generated.source val writer = new java.io.PrintWriter(new java.io.File(outputCpp)) diff --git a/src/test/scala/genwasym/TestStagedConcolicEval.scala b/src/test/scala/genwasym/TestStagedConcolicEval.scala index 409bd07a..77868e2c 100644 --- a/src/test/scala/genwasym/TestStagedConcolicEval.scala +++ b/src/test/scala/genwasym/TestStagedConcolicEval.scala @@ -13,7 +13,8 @@ class TestStagedConcolicEval extends FunSuite { val moduleInst = ModuleInstance(Parser.parseFile(filename)) val cppFile = s"$filename.cpp" val exe = s"$cppFile.exe" - WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, true) + val exploreTreeFile = s"$filename.tree.dot" + WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, true, Some(exploreTreeFile)) import sys.process._ val result = s"./$exe".!! From 9a9988c560f58961b2fb09892912623c26b9691e Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Thu, 17 Jul 2025 18:15:48 +0800 Subject: [PATCH 13/53] concolic driver --- headers/wasm.hpp | 3 +- headers/wasm/concolic_driver.hpp | 98 +++++++++ headers/wasm/concrete_rt.hpp | 9 +- headers/wasm/smt_solver.hpp | 28 +++ headers/wasm/symbolic_rt.hpp | 205 +++++++++++++++--- headers/wasm/utils.hpp | 15 ++ .../scala/wasm/StagedConcolicMiniWasm.scala | 20 +- 7 files changed, 338 insertions(+), 40 deletions(-) create mode 100644 headers/wasm/concolic_driver.hpp create mode 100644 headers/wasm/smt_solver.hpp create mode 100644 headers/wasm/utils.hpp diff --git a/headers/wasm.hpp b/headers/wasm.hpp index c7e98b6e..36fe3849 100644 --- a/headers/wasm.hpp +++ b/headers/wasm.hpp @@ -3,5 +3,6 @@ #include "wasm/concrete_rt.hpp" #include "wasm/symbolic_rt.hpp" - +#include "wasm/concolic_driver.hpp" +#include "wasm/utils.hpp" #endif \ No newline at end of file diff --git a/headers/wasm/concolic_driver.hpp b/headers/wasm/concolic_driver.hpp new file mode 100644 index 00000000..4307413b --- /dev/null +++ b/headers/wasm/concolic_driver.hpp @@ -0,0 +1,98 @@ +#ifndef CONCOLIC_DRIVER_HPP +#define CONCOLIC_DRIVER_HPP + +#include "smt_solver.hpp" +#include "symbolic_rt.hpp" +#include +#include +#include + +class ConcolicDriver { + friend class ManagedConcolicCleanup; + +public: + ConcolicDriver(std::function entrypoint, std::string tree_file) + : entrypoint(entrypoint), tree_file(tree_file) {} + ConcolicDriver(std::function entrypoint) + : entrypoint(entrypoint), tree_file(std::nullopt) {} + void run(); + +private: + Solver solver; + std::function entrypoint; + std::optional tree_file; +}; + +class ManagedConcolicCleanup { + const ConcolicDriver &driver; + +public: + ManagedConcolicCleanup(const ConcolicDriver &driver) : driver(driver) {} + ~ManagedConcolicCleanup() { + if (driver.tree_file.has_value()) + ExploreTree.dump_graphviz(driver.tree_file.value()); + } +}; + +inline void ConcolicDriver::run() { + ManagedConcolicCleanup cleanup{*this}; + while (true) { + auto cond = ExploreTree.get_unexplored_conditions(); + ExploreTree.reset_cursor(); + + if (!cond.has_value()) { + std::cout << "No unexplored conditions found, exiting..." << std::endl; + return; + } + auto new_env = solver.solve(cond.value()); + if (!new_env.has_value()) { + std::cout << "All unexplored paths are unreachable, exiting..." + << std::endl; + return; + } + SymEnv.update(std::move(new_env.value())); + try { + entrypoint(); + std::cout << "Execution finished successfully with symbolic environment:" + << std::endl; + std::cout << SymEnv.to_string() << std::endl; + } catch (...) { + ExploreTree.fillFailedNode(); + std::cout << "Caught runtime error with symbolic environment:" + << std::endl; + std::cout << SymEnv.to_string() << std::endl; + return; + } + } +} + +static std::monostate reset_stacks() { + Stack.reset(); + Frames.reset(); + SymStack.reset(); + SymFrames.reset(); + initRand(); + Memory = Memory_t(1); + return std::monostate{}; +} + +static void start_concolic_execution_with( + std::function entrypoint, + std::string tree_file) { + ConcolicDriver driver([=]() { entrypoint(std::monostate{}); }, tree_file); + driver.run(); +} + +static void start_concolic_execution_with( + std::function entrypoint) { + + const char *env_tree_file = std::getenv("TREE_FILE"); + + ConcolicDriver driver = + env_tree_file ? ConcolicDriver([=]() { entrypoint(std::monostate{}); }, + env_tree_file) + : ConcolicDriver([=]() { entrypoint(std::monostate{}); }); + driver.run(); +} + +#endif // CONCOLIC_DRIVER_HPP \ No newline at end of file diff --git a/headers/wasm/concrete_rt.hpp b/headers/wasm/concrete_rt.hpp index e994cbde..a0961453 100644 --- a/headers/wasm/concrete_rt.hpp +++ b/headers/wasm/concrete_rt.hpp @@ -52,8 +52,6 @@ static Num I32V(int v) { return v; } static Num I64V(int64_t v) { return v; } -using Slice = std::vector; - const int STACK_SIZE = 1024 * 64; class Stack_t { @@ -118,9 +116,12 @@ class Stack_t { } void initialize() { - // do nothing for now + // todo: remove this method + reset(); } + void reset() { count = 0; } + private: int32_t count; Num *stack_ptr; @@ -151,6 +152,8 @@ class Frames_t { count += size; } + void reset() { count = 0; } + private: int32_t count; Num *stack_ptr; diff --git a/headers/wasm/smt_solver.hpp b/headers/wasm/smt_solver.hpp new file mode 100644 index 00000000..a3bbf78d --- /dev/null +++ b/headers/wasm/smt_solver.hpp @@ -0,0 +1,28 @@ +#ifndef SMT_SOLVER_HPP +#define SMT_SOLVER_HPP + +#include "concrete_rt.hpp" +#include "symbolic_rt.hpp" +#include +#include + +class Solver { +public: + Solver() : count(0) { + envs[0] = {Num(0), Num(0)}; + envs[1] = {Num(1), Num(2)}; + } + std::optional> solve(const std::vector &conditions) { + // here is just a placeholder implementation to simulate solving result + if (count >= envs.size()) { + return std::nullopt; // No more environments to return + } + return envs[count++ % envs.size()]; + } + +private: + std::array, 5> envs; + int count; +}; + +#endif // SMT_SOLVER_HPP \ No newline at end of file diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index a97fd0dd..920bcad4 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -7,7 +7,9 @@ #include #include #include +#include #include +#include #include #include @@ -20,6 +22,7 @@ class Symbolic { class Symbol : public Symbolic { public: Symbol(int id) : id(id) {} + int get_id() const { return id; } private: int id; @@ -54,6 +57,7 @@ struct SymVal { SymVal leq(const SymVal &other) const; SymVal gt(const SymVal &other) const; SymVal geq(const SymVal &other) const; + SymVal negate() const; }; inline SymVal Concrete(Num num) { @@ -109,6 +113,9 @@ inline SymVal SymVal::geq(const SymVal &other) const { inline SymVal SymVal::is_zero() const { return SymVal(std::make_shared(EQ, *this, Concrete(I32V(0)))); } +inline SymVal SymVal::negate() const { + return SymVal(std::make_shared(EQ, *this, Concrete(I32V(0)))); +} inline SymVal SymVal::makeSymbolic() const { auto concrete = dynamic_cast(symptr.get()); @@ -137,6 +144,11 @@ class SymStack_t { SymVal peek() { return stack.back(); } + void reset() { + // Reset the symbolic stack + stack.clear(); + } + std::vector stack; }; @@ -165,6 +177,11 @@ class SymFrames_t { stack[stack.size() - 1 - index] = val; } + void reset() { + // Reset the symbolic frames + stack.clear(); + } + std::vector stack; }; @@ -190,14 +207,28 @@ struct Node { os << "}\n"; } - int get_next_id(int &id_counter) { return id_counter++; } - virtual int generate_dot(std::ostream &os, int parent_dot_id, - const std::string &edge_label) = 0; + 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"; + } }; // TODO: use this header file in multiple compilation units will cause problems @@ -234,21 +265,16 @@ struct IfElseNode : Node { return result; } - int generate_dot(std::ostream &os, int parent_dot_id, - const std::string &edge_label) override { + 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; - os << " node" << current_node_dot_id << " [label=\"If\"," - << "shape=diamond, fillcolor=lightyellow];\n"; + 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) { - os << " node" << parent_dot_id << " -> node" << current_node_dot_id; - if (!edge_label.empty()) { - os << " [label=\"" << edge_label << "\"]"; - } - os << ";\n"; + graphviz_edge(os, parent_dot_id, current_node_dot_id, edge_label); } assert(true_branch != nullptr); assert(true_branch->node != nullptr); @@ -256,7 +282,6 @@ struct IfElseNode : Node { assert(false_branch != nullptr); assert(false_branch->node != nullptr); false_branch->node->generate_dot(os, current_node_dot_id, "false"); - return current_node_dot_id; } }; @@ -265,39 +290,92 @@ struct UnExploredNode : Node { std::string to_string() override { return "UnexploredNode"; } protected: - int generate_dot(std::ostream &os, int parent_dot_id, - const std::string &edge_label) override { + 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 Finished : Node { + Finished() {} + std::string to_string() override { return "FinishedNode"; } - os << " node" << current_node_dot_id - << " [label=\"Unexplored\", shape=octagon, style=filled, " - "fillcolor=lightgrey];\n"; +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) { - os << " node" << parent_dot_id << " -> node" << current_node_dot_id; - if (!edge_label.empty()) { - os << " [label=\"" << edge_label << "\"]"; - } - os << ";\n"; + graphviz_edge(os, parent_dot_id, current_node_dot_id, edge_label); } + } +}; + +struct Failed : Node { + Failed() {} + std::string to_string() override { return "FailedNode"; } - return current_node_dot_id; +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); + } } }; static UnExploredNode unexplored; inline NodeBox::NodeBox() - : node(std::make_unique< - UnExploredNode>() /* TODO: avoid allocation of unexplored node */) {} + : node(std::make_unique()), + /* TODO: avoid allocation of unexplored node */ + parent(nullptr) {} class ExploreTree_t { public: explicit ExploreTree_t() : root(std::make_unique()), cursor(root.get()) {} + + void reset_cursor() { + // Reset the cursor to the root of the tree + cursor = root.get(); + } + + std::monostate fillFinishedNode() { + if (dynamic_cast(cursor->node.get())) { + cursor->node = std::make_unique(); + } else { + assert(dynamic_cast(cursor->node.get()) != nullptr); + } + return std::monostate{}; + } + + std::monostate fillFailedNode() { + if (dynamic_cast(cursor->node.get())) { + cursor->node = std::make_unique(); + } else { + assert(dynamic_cast(cursor->node.get()) != nullptr); + } + return std::monostate{}; + } + std::monostate fillIfElseNode(SymVal cond) { - // fill the current node with an ifelse branch node - cursor->node = std::make_unique(cond); + // fill the current NodeBox with an ifelse branch node it's unexplored + if (dynamic_cast(cursor->node.get())) { + cursor->node = std::make_unique(cond); + } + assert(dynamic_cast(cursor->node.get()) != nullptr && + "Current node is not an IfElseNode, cannot fill it!"); return std::monostate(); } @@ -328,13 +406,60 @@ class ExploreTree_t { std::monostate dump_graphviz(std::string filepath) { std::ofstream ofs(filepath); if (!ofs.is_open()) { - throw std::runtime_error("Failed to open explore_tree.dot for writing"); + throw std::runtime_error("Failed to open " + filepath + " for writing"); } to_graphviz(ofs); return std::monostate(); } + std::optional> get_unexplored_conditions() { + // Get all unexplored conditions in the tree + std::vector result; + auto box = pick_unexplored(); + if (!box) { + return std::nullopt; + } + while (box->parent) { + auto parent = box->parent; + auto if_else_node = dynamic_cast(parent->node.get()); + if (if_else_node) { + 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.negate()); + } else { + throw std::runtime_error("Unexpected node structure in explore tree"); + } + } + // Move to parent + box = box->parent; + } + return 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()); + } + private: + NodeBox *pick_unexplored_of(NodeBox *node) { + if (dynamic_cast(node->node.get()) != nullptr) { + 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 + } std::unique_ptr root; NodeBox *cursor; }; @@ -344,10 +469,28 @@ static ExploreTree_t ExploreTree; class SymEnv_t { public: Num read(SymVal sym) { - // Read a symbolic value from the symbolic environment - // For now, we just return a zero return Num(0); + auto symbol = dynamic_cast(sym.symptr.get()); + assert(symbol); + return map[symbol->get_id()]; } + + void update(std::vector new_env) { map = std::move(new_env); } + + std::string to_string() const { + std::string result; + result += "(\n"; + for (int i = 0; i < map.size(); ++i) { + const Num &num = map[i]; + result += + " (" + std::to_string(i) + "->" + std::to_string(num.value) + ")\n"; + } + result += ")"; + return result; + } + +private: + std::vector map; // The symbolic environment, a vector of Num }; static SymEnv_t SymEnv; diff --git a/headers/wasm/utils.hpp b/headers/wasm/utils.hpp new file mode 100644 index 00000000..8a86ac98 --- /dev/null +++ b/headers/wasm/utils.hpp @@ -0,0 +1,15 @@ +#ifndef UTILS_HPP +#define UTILS_HPP + +#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 + +#endif // UTILS_HPP \ No newline at end of file diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index 3c342177..71463bf5 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -416,7 +416,7 @@ trait StagedWasmEvaluator extends SAIOps { } } val (instrs, locals) = (funBody.body, funBody.locals) - Stack.initialize() + resetStacks() Frames.pushFrame(locals) eval(instrs, (_: Context) => forwardKont, mkont, ((_: Context) => forwardKont)::Nil)(Context(Nil, locals)) Frames.popFrame(locals.size) @@ -428,10 +428,7 @@ trait StagedWasmEvaluator extends SAIOps { if (printRes) { Stack.print() } - dumpTree match { - case Some(filePath) => ExploreTree.dumpGraphiviz(filePath) - case None => () - } + ExploreTree.fillWithFinished() "no-op".reflectCtrlWith[Unit]() } val temp: Rep[MCont[Unit]] = topFun(haltK) @@ -558,6 +555,7 @@ trait StagedWasmEvaluator extends SAIOps { object Memory { def storeInt(base: Rep[Int], offset: Int, value: Rep[Int]): Rep[Unit] = { "memory-store-int".reflectCtrlWith[Unit](base, offset, value) + // todo: store symbolic value to memory via extract/concat operation } def loadInt(base: Rep[Int], offset: Int): StagedNum = { @@ -570,6 +568,10 @@ trait StagedWasmEvaluator extends SAIOps { } } + def resetStacks(): Rep[Unit] = { + "reset-stacks".reflectCtrlWith[Unit]() + } + // call unreachable def unreachable(): Rep[Unit] = { "unreachable".reflectCtrlWith[Unit]() @@ -617,6 +619,10 @@ trait StagedWasmEvaluator extends SAIOps { "tree-fill-if-else".reflectCtrlWith[Unit](s) } + def fillWithFinished(): Rep[Unit] = { + "tree-fill-finished".reflectCtrlWith[Unit]() + } + def moveCursor(branch: Boolean): Rep[Unit] = { "tree-move-cursor".reflectCtrlWith[Unit](branch) } @@ -875,6 +881,8 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { } override def shallow(n: Node): Unit = n match { + case Node(_, "reset-stacks", _, _) => + emit("reset_stacks()") case Node(_, "frame-get", List(i), _) => emit("Frames.get("); shallow(i); emit(")") case Node(_, "sym-frame-get", List(i), _) => @@ -977,6 +985,8 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { emit("assert("); shallow(cond); emit(")") case Node(_, "tree-fill-if-else", List(s), _) => emit("ExploreTree.fillIfElseNode("); shallow(s); emit(")") + case Node(_, "tree-fill-finished", List(), _) => + emit("ExploreTree.fillFinishedNode()") case Node(_, "tree-move-cursor", List(b), _) => emit("ExploreTree.moveCursor("); shallow(b); emit(")") case Node(_, "tree-print", List(), _) => From 9ab162fe0305be5c955154b6be9292befe8e4d70 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Fri, 18 Jul 2025 17:41:15 +0800 Subject: [PATCH 14/53] fix: add an unreachable node & use GENSYM_ASSERT --- headers/wasm/concolic_driver.hpp | 16 ++- headers/wasm/symbolic_rt.hpp | 128 ++++++++++++------ .../scala/wasm/StagedConcolicMiniWasm.scala | 2 +- 3 files changed, 97 insertions(+), 49 deletions(-) diff --git a/headers/wasm/concolic_driver.hpp b/headers/wasm/concolic_driver.hpp index 4307413b..9c35f161 100644 --- a/headers/wasm/concolic_driver.hpp +++ b/headers/wasm/concolic_driver.hpp @@ -37,18 +37,22 @@ class ManagedConcolicCleanup { inline void ConcolicDriver::run() { ManagedConcolicCleanup cleanup{*this}; while (true) { - auto cond = ExploreTree.get_unexplored_conditions(); ExploreTree.reset_cursor(); - if (!cond.has_value()) { - std::cout << "No unexplored conditions found, exiting..." << std::endl; + auto unexplored = ExploreTree.pick_unexplored(); + if (!unexplored) { + std::cout << "No unexplored nodes found, exiting..." << std::endl; return; } - auto new_env = solver.solve(cond.value()); + auto cond = unexplored->collect_path_conds(); + auto new_env = solver.solve(cond); if (!new_env.has_value()) { - std::cout << "All unexplored paths are unreachable, exiting..." + // TODO: current implementation is buggy, there could be other reachable + // unexplored paths + std::cout << "Found an unreachable path, marking it as unreachable..." << std::endl; - return; + unexplored->fillUnreachableNode(); + continue; } SymEnv.update(std::move(new_env.value())); try { diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 920bcad4..95f292b5 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -193,6 +193,13 @@ struct NodeBox { explicit NodeBox(); std::unique_ptr node; NodeBox *parent; + + std::monostate fillIfElseNode(SymVal cond); + std::monostate fillFinishedNode(); + std::monostate fillFailedNode(); + std::monostate fillUnreachableNode(); + + std::vector collect_path_conds(); }; struct Node { @@ -334,13 +341,87 @@ struct Failed : Node { } }; -static UnExploredNode unexplored; +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() : node(std::make_unique()), /* TODO: avoid allocation of unexplored node */ parent(nullptr) {} +inline std::monostate NodeBox::fillIfElseNode(SymVal cond) { + // fill the current NodeBox with an ifelse branch node it's unexplored + if (dynamic_cast(node.get())) { + node = std::make_unique(cond); + } + assert(dynamic_cast(node.get()) != nullptr && + "Current node is not an IfElseNode, cannot fill it!"); + return std::monostate(); +} + +inline std::monostate NodeBox::fillFinishedNode() { + if (dynamic_cast(node.get())) { + node = std::make_unique(); + } else { + assert(dynamic_cast(node.get()) != nullptr); + } + return std::monostate(); +} + +inline std::monostate NodeBox::fillFailedNode() { + if (dynamic_cast(node.get())) { + node = std::make_unique(); + } else { + assert(dynamic_cast(node.get()) != nullptr); + } + return std::monostate(); +} + +inline std::monostate NodeBox::fillUnreachableNode() { + if (dynamic_cast(node.get())) { + node = std::make_unique(); + } else { + assert(dynamic_cast(node.get()) != nullptr); + } + return std::monostate(); +} + +inline std::vector NodeBox::collect_path_conds() { + auto box = this; + auto result = std::vector(); + while (box->parent) { + auto parent = box->parent; + auto if_else_node = dynamic_cast(parent->node.get()); + if (if_else_node) { + 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.negate()); + } else { + throw std::runtime_error("Unexpected node structure in explore tree"); + } + } + // Move to parent + box = box->parent; + } + return result; +} + class ExploreTree_t { public: explicit ExploreTree_t() @@ -351,32 +432,12 @@ class ExploreTree_t { cursor = root.get(); } - std::monostate fillFinishedNode() { - if (dynamic_cast(cursor->node.get())) { - cursor->node = std::make_unique(); - } else { - assert(dynamic_cast(cursor->node.get()) != nullptr); - } - return std::monostate{}; - } + std::monostate fillFinishedNode() { return cursor->fillFinishedNode(); } - std::monostate fillFailedNode() { - if (dynamic_cast(cursor->node.get())) { - cursor->node = std::make_unique(); - } else { - assert(dynamic_cast(cursor->node.get()) != nullptr); - } - return std::monostate{}; - } + std::monostate fillFailedNode() { return cursor->fillFailedNode(); } std::monostate fillIfElseNode(SymVal cond) { - // fill the current NodeBox with an ifelse branch node it's unexplored - if (dynamic_cast(cursor->node.get())) { - cursor->node = std::make_unique(cond); - } - assert(dynamic_cast(cursor->node.get()) != nullptr && - "Current node is not an IfElseNode, cannot fill it!"); - return std::monostate(); + return cursor->fillIfElseNode(cond); } std::monostate moveCursor(bool branch) { @@ -419,24 +480,7 @@ class ExploreTree_t { if (!box) { return std::nullopt; } - while (box->parent) { - auto parent = box->parent; - auto if_else_node = dynamic_cast(parent->node.get()); - if (if_else_node) { - 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.negate()); - } else { - throw std::runtime_error("Unexpected node structure in explore tree"); - } - } - // Move to parent - box = box->parent; - } - return result; + return box->collect_path_conds(); } NodeBox *pick_unexplored() { diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index 71463bf5..eb8d3aab 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -982,7 +982,7 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { case Node(_, "sym-env-read", List(sym), _) => emit("SymEnv.read("); shallow(sym); emit(")") case Node(_, "assert-true", List(cond), _) => - emit("assert("); shallow(cond); emit(")") + emit("GENSYM_ASSERT("); shallow(cond); emit(")") case Node(_, "tree-fill-if-else", List(s), _) => emit("ExploreTree.fillIfElseNode("); shallow(s); emit(")") case Node(_, "tree-fill-finished", List(), _) => From b75a627a59c88ac7738b7bf4c8a7944e74b5f6b1 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Sat, 19 Jul 2025 13:20:40 +0800 Subject: [PATCH 15/53] call z3 to solve constraints --- headers/wasm/concolic_driver.hpp | 13 ++-- headers/wasm/smt_solver.hpp | 120 ++++++++++++++++++++++++++++--- headers/wasm/symbolic_rt.hpp | 39 ++++++---- 3 files changed, 145 insertions(+), 27 deletions(-) diff --git a/headers/wasm/concolic_driver.hpp b/headers/wasm/concolic_driver.hpp index 9c35f161..ea28d082 100644 --- a/headers/wasm/concolic_driver.hpp +++ b/headers/wasm/concolic_driver.hpp @@ -1,11 +1,13 @@ #ifndef CONCOLIC_DRIVER_HPP #define CONCOLIC_DRIVER_HPP +#include "concrete_rt.hpp" #include "smt_solver.hpp" #include "symbolic_rt.hpp" #include #include #include +#include class ConcolicDriver { friend class ManagedConcolicCleanup; @@ -45,16 +47,19 @@ inline void ConcolicDriver::run() { return; } auto cond = unexplored->collect_path_conds(); - auto new_env = solver.solve(cond); - if (!new_env.has_value()) { + std::vector new_env; + std::set valid_ids; + auto result = solver.solve(cond); + if (!result.has_value()) { // TODO: current implementation is buggy, there could be other reachable // unexplored paths std::cout << "Found an unreachable path, marking it as unreachable..." << std::endl; unexplored->fillUnreachableNode(); - continue; + continue; } - SymEnv.update(std::move(new_env.value())); + std::tie(new_env, valid_ids) = std::move(result.value()); + SymEnv.update(std::move(new_env), std::move(valid_ids)); try { entrypoint(); std::cout << "Execution finished successfully with symbolic environment:" diff --git a/headers/wasm/smt_solver.hpp b/headers/wasm/smt_solver.hpp index a3bbf78d..8e3f82e4 100644 --- a/headers/wasm/smt_solver.hpp +++ b/headers/wasm/smt_solver.hpp @@ -3,26 +3,124 @@ #include "concrete_rt.hpp" #include "symbolic_rt.hpp" +#include "z3++.h" #include +#include +#include +#include #include class Solver { public: - Solver() : count(0) { - envs[0] = {Num(0), Num(0)}; - envs[1] = {Num(1), Num(2)}; - } - std::optional> solve(const std::vector &conditions) { - // here is just a placeholder implementation to simulate solving result - if (count >= envs.size()) { - return std::nullopt; // No more environments to return + Solver() {} + std::optional, std::set>> + solve(const std::vector &conditions) { + // make an conjunction of all conditions + z3::expr conjunction = z3_ctx.bool_val(true); + for (const auto &cond : conditions) { + auto z3_cond = build_z3_expr(cond); + conjunction = conjunction && z3_cond != 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 + // call z3 to solve the condition + z3::solver z3_solver(z3_ctx); + z3_solver.add(conjunction); + switch (z3_solver.check()) { + case z3::unsat: + return std::nullopt; // No solution found + case z3::sat: { + z3::model model = z3_solver.get_model(); + std::vector result(max_id + 1, Num(0)); + // Reference: + // https://github.com/Z3Prover/z3/blob/master/examples/c%2B%2B/example.cpp#L59 + + std::cout << "Solved Z3 model" << model << std::endl; + std::set seen_ids; + 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 (name.starts_with("s_")) { + int id = std::stoi(name.substr(2)); + seen_ids.insert(id); + result[id] = Num(value.get_numeral_int()); + } else { + std::cout << "Find a variable that is not created by GenSym: " << name + << std::endl; + } + } + return std::make_tuple(result, seen_ids); } - return envs[count++ % envs.size()]; + case z3::unknown: + throw std::runtime_error("Z3 solver returned unknown status"); + } + return std::nullopt; // Should not reach here } private: - std::array, 5> envs; - int count; + z3::context z3_ctx; + z3::expr build_z3_expr(const SymVal &sym_val); }; +inline z3::expr Solver::build_z3_expr(const SymVal &sym_val) { + if (auto sym = std::dynamic_pointer_cast(sym_val.symptr)) { + return z3_ctx.bv_const(("s_" + std::to_string(sym->get_id())).c_str(), 32); + } else if (auto concrete = + std::dynamic_pointer_cast(sym_val.symptr)) { + return z3_ctx.bv_val(concrete->value.value, 32); + } else if (auto binary = + std::dynamic_pointer_cast(sym_val.symptr)) { + auto bit_width = 32; + z3::expr zero_bv = + z3_ctx.bv_val(0, bit_width); // Represents 0 as a 32-bit bitvector + z3::expr one_bv = + z3_ctx.bv_val(1, bit_width); // Represents 1 as a 32-bit bitvector + + z3::expr left = build_z3_expr(binary->lhs); + z3::expr right = build_z3_expr(binary->rhs); + // TODO: make sure the semantics of these operations are aligned with wasm + switch (binary->op) { + case EQ: { + auto temp_bool = left == right; + return z3::ite(temp_bool, one_bv, zero_bv); + } + case NEQ: { + auto temp_bool = left != right; + return z3::ite(temp_bool, one_bv, zero_bv); + } + case LT: { + auto temp_bool = left < right; + return z3::ite(temp_bool, one_bv, zero_bv); + } + case LEQ: { + auto temp_bool = left <= right; + return z3::ite(temp_bool, one_bv, zero_bv); + } + case GT: { + auto temp_bool = left > right; + return z3::ite(temp_bool, one_bv, zero_bv); + } + case GEQ: { + auto temp_bool = left >= right; + return z3::ite(temp_bool, one_bv, zero_bv); + } + case ADD: { + return left + right; + } + case SUB: { + return left - right; + } + case MUL: { + return left * right; + } + case DIV: { + return left / right; + } + } + } + throw std::runtime_error("Unsupported symbolic value type"); +} #endif // SMT_SOLVER_HPP \ No newline at end of file diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 95f292b5..3e7ea2cc 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -19,9 +20,13 @@ class Symbolic { virtual ~Symbolic() = default; // Make Symbolic polymorphic }; +static int max_id = 0; + class Symbol : public Symbolic { public: - Symbol(int id) : id(id) {} + // TODO: add type information to determine the size of bitvector + // for now we just assume that only i32 will be used + Symbol(int id) : id(id) { max_id = std::max(max_id, id); } int get_id() const { return id; } private: @@ -190,7 +195,7 @@ static SymFrames_t SymFrames; struct Node; struct NodeBox { - explicit NodeBox(); + explicit NodeBox(NodeBox *parent); std::unique_ptr node; NodeBox *parent; @@ -247,9 +252,9 @@ struct IfElseNode : Node { std::unique_ptr true_branch; std::unique_ptr false_branch; - IfElseNode(SymVal cond) - : cond(cond), true_branch(std::make_unique()), - false_branch(std::make_unique()) {} + IfElseNode(SymVal cond, NodeBox *parent) + : cond(cond), true_branch(std::make_unique(parent)), + false_branch(std::make_unique(parent)) {} std::string to_string() override { std::string result = "IfElseNode {\n"; @@ -357,15 +362,15 @@ struct Unreachable : Node { } }; -inline NodeBox::NodeBox() +inline NodeBox::NodeBox(NodeBox *parent) : node(std::make_unique()), /* TODO: avoid allocation of unexplored node */ - parent(nullptr) {} + parent(parent) {} inline std::monostate NodeBox::fillIfElseNode(SymVal cond) { // fill the current NodeBox with an ifelse branch node it's unexplored if (dynamic_cast(node.get())) { - node = std::make_unique(cond); + node = std::make_unique(cond, this); } assert(dynamic_cast(node.get()) != nullptr && "Current node is not an IfElseNode, cannot fill it!"); @@ -425,7 +430,7 @@ inline std::vector NodeBox::collect_path_conds() { class ExploreTree_t { public: explicit ExploreTree_t() - : root(std::make_unique()), cursor(root.get()) {} + : root(std::make_unique(nullptr)), cursor(root.get()) {} void reset_cursor() { // Reset the cursor to the root of the tree @@ -513,13 +518,22 @@ static ExploreTree_t ExploreTree; class SymEnv_t { public: Num read(SymVal sym) { - return Num(0); auto symbol = dynamic_cast(sym.symptr.get()); assert(symbol); + if (symbol->get_id() >= map.size()) { + map.resize(symbol->get_id() + 1); + } +#if DEBUG + std::cout << "Read symbol: " << symbol->get_id() + << " from symbolic environment" << std::endl; + std::cout << "Current symbolic environment: " << to_string() << std::endl; +#endif return map[symbol->get_id()]; } - void update(std::vector new_env) { map = std::move(new_env); } + void update(std::vector new_env, std::set valid_ids) { + map = std::move(new_env); + } std::string to_string() const { std::string result; @@ -534,7 +548,8 @@ class SymEnv_t { } private: - std::vector map; // The symbolic environment, a vector of Num + std::vector map; // The symbolic environment, a vector of Num + std::set valid_ids; // The set of valid IDs in the symbolic environment }; static SymEnv_t SymEnv; From 26c9917fcb39e84f33318955fb3a9b3a3fd9fb32 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Sat, 19 Jul 2025 17:48:20 +0800 Subject: [PATCH 16/53] remove unused & resize before update environment --- headers/wasm/concolic_driver.hpp | 6 ++---- headers/wasm/smt_solver.hpp | 14 +++++++------- headers/wasm/symbolic_rt.hpp | 3 +-- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/headers/wasm/concolic_driver.hpp b/headers/wasm/concolic_driver.hpp index ea28d082..8e8ca815 100644 --- a/headers/wasm/concolic_driver.hpp +++ b/headers/wasm/concolic_driver.hpp @@ -47,8 +47,6 @@ inline void ConcolicDriver::run() { return; } auto cond = unexplored->collect_path_conds(); - std::vector new_env; - std::set valid_ids; auto result = solver.solve(cond); if (!result.has_value()) { // TODO: current implementation is buggy, there could be other reachable @@ -58,8 +56,8 @@ inline void ConcolicDriver::run() { unexplored->fillUnreachableNode(); continue; } - std::tie(new_env, valid_ids) = std::move(result.value()); - SymEnv.update(std::move(new_env), std::move(valid_ids)); + auto new_env = result.value(); + SymEnv.update(std::move(new_env)); try { entrypoint(); std::cout << "Execution finished successfully with symbolic environment:" diff --git a/headers/wasm/smt_solver.hpp b/headers/wasm/smt_solver.hpp index 8e3f82e4..de5b80cb 100644 --- a/headers/wasm/smt_solver.hpp +++ b/headers/wasm/smt_solver.hpp @@ -13,8 +13,7 @@ class Solver { public: Solver() {} - std::optional, std::set>> - solve(const std::vector &conditions) { + std::optional> solve(const std::vector &conditions) { // make an conjunction of all conditions z3::expr conjunction = z3_ctx.bool_val(true); for (const auto &cond : conditions) { @@ -33,26 +32,27 @@ class Solver { return std::nullopt; // No solution found case z3::sat: { z3::model model = z3_solver.get_model(); - std::vector result(max_id + 1, Num(0)); + std::vector result; // Reference: // https://github.com/Z3Prover/z3/blob/master/examples/c%2B%2B/example.cpp#L59 - std::cout << "Solved Z3 model" << model << std::endl; - std::set seen_ids; + std::cout << "Solved Z3 model" << std::endl << model << std::endl; 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 (name.starts_with("s_")) { int id = std::stoi(name.substr(2)); - seen_ids.insert(id); + if (id >= result.size()) { + result.resize(id + 1); + } result[id] = Num(value.get_numeral_int()); } else { std::cout << "Find a variable that is not created by GenSym: " << name << std::endl; } } - return std::make_tuple(result, seen_ids); + return result; } case z3::unknown: throw std::runtime_error("Z3 solver returned unknown status"); diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 3e7ea2cc..18629c80 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -531,7 +531,7 @@ class SymEnv_t { return map[symbol->get_id()]; } - void update(std::vector new_env, std::set valid_ids) { + void update(std::vector new_env) { map = std::move(new_env); } @@ -549,7 +549,6 @@ class SymEnv_t { private: std::vector map; // The symbolic environment, a vector of Num - std::set valid_ids; // The set of valid IDs in the symbolic environment }; static SymEnv_t SymEnv; From 319cfd6576f0399bcfdd3668e0a19b9feeadcad2 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Wed, 23 Jul 2025 14:11:42 +0800 Subject: [PATCH 17/53] use c++20 --- src/main/scala/wasm/StagedConcolicMiniWasm.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index eb8d3aab..922d2113 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -1080,7 +1080,7 @@ object WasmToCppCompiler { } import sys.process._ - val command = s"g++ -std=c++17 $outputCpp -o $outputExe -O3 -g " + generated.headerFolders.map(f => s"-I$f").mkString(" ") + val command = s"g++ -std=c++20 $outputCpp -o $outputExe -O3 -g " + generated.headerFolders.map(f => s"-I$f").mkString(" ") if (command.! != 0) { throw new RuntimeException(s"Compilation failed for $outputCpp") } From 8f45912f6275c9dc5a5ee8bab34b02bcae3b5609 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Wed, 23 Jul 2025 19:28:50 +0800 Subject: [PATCH 18/53] branch in brtable --- benchmarks/wasm/staged/brtable_concolic.wat | 22 +++++++++++++++++++ headers/wasm/smt_solver.hpp | 2 +- .../scala/wasm/StagedConcolicMiniWasm.scala | 17 +++++++++++--- .../genwasym/TestStagedConcolicEval.scala | 4 ++++ 4 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 benchmarks/wasm/staged/brtable_concolic.wat diff --git a/benchmarks/wasm/staged/brtable_concolic.wat b/benchmarks/wasm/staged/brtable_concolic.wat new file mode 100644 index 00000000..04429e90 --- /dev/null +++ b/benchmarks/wasm/staged/brtable_concolic.wat @@ -0,0 +1,22 @@ +(module $brtable + (global (;0;) (mut i32) (i32.const 1048576)) + (type (;0;) (func (param i32))) + (func (;0;) (type 1) (result i32) + i32.const 2 + (block + (block + (block + i32.const 0 + i32.symbolic + br_table 0 1 2 0 ;; br_table will consume an element from the stack + ) + i32.const 1 + call 1 + br 1 + ) + i32.const 0 + call 1 + ) + ) + (import "console" "assert" (func (type 0))) + (start 0)) diff --git a/headers/wasm/smt_solver.hpp b/headers/wasm/smt_solver.hpp index de5b80cb..f2450905 100644 --- a/headers/wasm/smt_solver.hpp +++ b/headers/wasm/smt_solver.hpp @@ -46,7 +46,7 @@ class Solver { if (id >= result.size()) { result.resize(id + 1); } - result[id] = Num(value.get_numeral_int()); + result[id] = Num(value.get_numeral_int64()); } else { std::cout << "Find a variable that is not created by GenSym: " << name << std::endl; diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index 922d2113..6c8e45aa 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -17,6 +17,7 @@ import gensym.wasm.symbolic.{SymVal} import gensym.lmsx.{SAIDriver, StringOps, SAIOps, SAICodeGenBase, CppSAIDriver, CppSAICodeGenBase} import gensym.wasm.symbolic.Concrete import gensym.wasm.symbolic.ExploreTree +import gensym.structure.freer.Explore @virtualize trait StagedWasmEvaluator extends SAIOps { @@ -270,12 +271,20 @@ trait StagedWasmEvaluator extends SAIOps { } () case BrTable(labels, default) => - val (cond, newCtx) = Stack.pop() + val (label, newCtx) = Stack.pop() def aux(choices: List[Int], idx: Int): Rep[Unit] = { if (choices.isEmpty) trail(default)(newCtx)(mkont) else { - if (cond.toInt == idx) trail(choices.head)(newCtx)(mkont) - else aux(choices.tail, idx + 1) + val cond = (label - toStagedNum(I32V(idx))).isZero() + ExploreTree.fillWithIfElse(cond.s) + if (cond.toInt != 0) { + ExploreTree.moveCursor(true) + trail(choices.head)(newCtx)(mkont) + } + else { + ExploreTree.moveCursor(false) + aux(choices.tail, idx + 1) + } } } aux(labels, 0) @@ -959,6 +968,8 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { shallow(lhs); emit(" >= "); shallow(rhs) case Node(_, "sym-binary-add", List(lhs, rhs), _) => shallow(lhs); emit(".add("); shallow(rhs); emit(")") + case Node(_, "sym-binary-sub", List(lhs, rhs), _) => + shallow(lhs); emit(".minus("); shallow(rhs); emit(")") case Node(_, "sym-binary-mul", List(lhs, rhs), _) => shallow(lhs); emit(".mul("); shallow(rhs); emit(")") case Node(_, "sym-binary-div", List(lhs, rhs), _) => diff --git a/src/test/scala/genwasym/TestStagedConcolicEval.scala b/src/test/scala/genwasym/TestStagedConcolicEval.scala index 77868e2c..fa7f704b 100644 --- a/src/test/scala/genwasym/TestStagedConcolicEval.scala +++ b/src/test/scala/genwasym/TestStagedConcolicEval.scala @@ -35,4 +35,8 @@ class TestStagedConcolicEval extends FunSuite { test("bug-finding") { testFileToCpp("./benchmarks/wasm/branch-strip-buggy.wat", Some("real_main")) } + + test("brtable-bug-finding") { + testFileToCpp("./benchmarks/wasm/staged/brtable_concolic.wat") + } } From 2e2259d0bbd18aa122d201d62bc03d245c6ec191 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Wed, 23 Jul 2025 19:32:49 +0800 Subject: [PATCH 19/53] use driver's entrypoint by default --- src/main/scala/wasm/StagedConcolicMiniWasm.scala | 4 ++-- src/test/scala/genwasym/TestStagedConcolicEval.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index 6c8e45aa..833bbc9b 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -1047,7 +1047,7 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { |End of Generated Code |*******************************************/ |int main(int argc, char *argv[]) { - | Snippet(std::monostate{}); + | start_concolic_execution_with(Snippet); | return 0; |}""".stripMargin) } @@ -1091,7 +1091,7 @@ object WasmToCppCompiler { } import sys.process._ - val command = s"g++ -std=c++20 $outputCpp -o $outputExe -O3 -g " + generated.headerFolders.map(f => s"-I$f").mkString(" ") + val command = s"g++ -std=c++20 $outputCpp -o $outputExe -O3 -g -l z3 " + generated.headerFolders.map(f => s"-I$f").mkString(" ") if (command.! != 0) { throw new RuntimeException(s"Compilation failed for $outputCpp") } diff --git a/src/test/scala/genwasym/TestStagedConcolicEval.scala b/src/test/scala/genwasym/TestStagedConcolicEval.scala index fa7f704b..a65d0eda 100644 --- a/src/test/scala/genwasym/TestStagedConcolicEval.scala +++ b/src/test/scala/genwasym/TestStagedConcolicEval.scala @@ -30,7 +30,7 @@ class TestStagedConcolicEval extends FunSuite { }) } - test("ack-cpp") { testFileToCpp("./benchmarks/wasm/ack.wat", Some("real_main"), expect=Some(List(7))) } + test("ack-cpp") { testFileToCpp("./benchmarks/wasm/ack.wat", Some("real_main")) } test("bug-finding") { testFileToCpp("./benchmarks/wasm/branch-strip-buggy.wat", Some("real_main")) From 2b42b277cfa42e6d06ba49f70daba327c4c4abcf Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Wed, 23 Jul 2025 20:09:12 +0800 Subject: [PATCH 20/53] rename package name of staged miniwasm --- src/main/scala/wasm/StagedMiniWasm.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/scala/wasm/StagedMiniWasm.scala b/src/main/scala/wasm/StagedMiniWasm.scala index bfa2082d..ea9dc9c6 100644 --- a/src/main/scala/wasm/StagedMiniWasm.scala +++ b/src/main/scala/wasm/StagedMiniWasm.scala @@ -1,4 +1,4 @@ -package gensym.wasm.miniwasm +package gensym.wasm.stagedminiwasm import scala.collection.mutable.{ArrayBuffer, HashMap} @@ -12,6 +12,7 @@ import lms.core.Graph import gensym.wasm.ast._ import gensym.wasm.ast.{Const => WasmConst, Block => WasmBlock} +import gensym.wasm.miniwasm.ModuleInstance import gensym.lmsx.{SAIDriver, StringOps, SAIOps, SAICodeGenBase, CppSAIDriver, CppSAICodeGenBase} @virtualize From 619a8f022d015d67ccbc4919bc49801175dc9dda Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Wed, 23 Jul 2025 20:15:29 +0800 Subject: [PATCH 21/53] tweak --- src/test/scala/genwasym/TestStagedEval.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/scala/genwasym/TestStagedEval.scala b/src/test/scala/genwasym/TestStagedEval.scala index d4d1e960..3769428f 100644 --- a/src/test/scala/genwasym/TestStagedEval.scala +++ b/src/test/scala/genwasym/TestStagedEval.scala @@ -6,6 +6,7 @@ import lms.core.stub.Adapter import gensym.wasm.parser._ import gensym.wasm.miniwasm._ +import gensym.wasm.stagedminiwasm._ class TestStagedEval extends FunSuite { def testFileToScala(filename: String, main: Option[String] = None, printRes: Boolean = false) = { From af6751aca613e013efc78e751b0dd1a4e0b420e6 Mon Sep 17 00:00:00 2001 From: butterunderflow <112108686+butterunderflow@users.noreply.github.com> Date: Wed, 27 Aug 2025 19:09:08 -0400 Subject: [PATCH 22/53] Reuse symbolic states (#90) 1. split concrete and symbolic interpreter 2. copy tests from concrete execution 3. some bug fixes --- benchmarks/wasm/staged/return_poly.wat | 19 + headers/wasm/concolic_driver.hpp | 23 +- headers/wasm/concrete_rt.hpp | 6 +- headers/wasm/controls.hpp | 5 + headers/wasm/smt_solver.hpp | 8 +- headers/wasm/symbolic_rt.hpp | 146 +- headers/wasm/utils.hpp | 24 + .../scala/wasm/StagedConcolicMiniWasm.scala | 1234 +++++++++++++---- .../genwasym/TestStagedConcolicEval.scala | 70 +- 9 files changed, 1198 insertions(+), 337 deletions(-) create mode 100644 benchmarks/wasm/staged/return_poly.wat create mode 100644 headers/wasm/controls.hpp diff --git a/benchmarks/wasm/staged/return_poly.wat b/benchmarks/wasm/staged/return_poly.wat new file mode 100644 index 00000000..1bab5ef0 --- /dev/null +++ b/benchmarks/wasm/staged/return_poly.wat @@ -0,0 +1,19 @@ +(module + (type (;0;) (func)) + (type (;1;) (func (result i32))) + ;; TODO: It seems that our parser or preprocessor has some problems; the result type of the last line doesn't take effect + (func (result i32) + block + i32.const 21 + i32.const 35 + i32.const 42 + return + end + i32.const 100 + ) + (func (type 0) + call 0 + ;; unreachable + ) + (export "$real_main" (func 1)) +) diff --git a/headers/wasm/concolic_driver.hpp b/headers/wasm/concolic_driver.hpp index 8e8ca815..427a0de8 100644 --- a/headers/wasm/concolic_driver.hpp +++ b/headers/wasm/concolic_driver.hpp @@ -4,6 +4,7 @@ #include "concrete_rt.hpp" #include "smt_solver.hpp" #include "symbolic_rt.hpp" +#include "utils.hpp" #include #include #include @@ -43,33 +44,33 @@ inline void ConcolicDriver::run() { auto unexplored = ExploreTree.pick_unexplored(); if (!unexplored) { - std::cout << "No unexplored nodes found, exiting..." << std::endl; + GENSYM_INFO("No unexplored nodes found, exiting..."); return; } auto cond = unexplored->collect_path_conds(); auto result = solver.solve(cond); if (!result.has_value()) { - // TODO: current implementation is buggy, there could be other reachable - // unexplored paths - std::cout << "Found an unreachable path, marking it as unreachable..." - << std::endl; + GENSYM_INFO("Found an unreachable path, marking it as unreachable..."); unexplored->fillUnreachableNode(); continue; } auto new_env = result.value(); SymEnv.update(std::move(new_env)); try { + GENSYM_INFO("Now execute the program with symbolic environment: "); + GENSYM_INFO(SymEnv.to_string()); entrypoint(); - std::cout << "Execution finished successfully with symbolic environment:" - << std::endl; - std::cout << SymEnv.to_string() << std::endl; + GENSYM_INFO("Execution finished successfully with symbolic environment:"); + GENSYM_INFO(SymEnv.to_string()); } catch (...) { ExploreTree.fillFailedNode(); - std::cout << "Caught runtime error with symbolic environment:" - << std::endl; - std::cout << SymEnv.to_string() << std::endl; + GENSYM_INFO("Caught runtime error with symbolic environment:"); + GENSYM_INFO(SymEnv.to_string()); return; } +#if defined(RUN_ONCE) + return; +#endif } } diff --git a/headers/wasm/concrete_rt.hpp b/headers/wasm/concrete_rt.hpp index a0961453..a9abccf2 100644 --- a/headers/wasm/concrete_rt.hpp +++ b/headers/wasm/concrete_rt.hpp @@ -72,9 +72,7 @@ class Stack_t { Num pop() { #ifdef DEBUG - if (count == 0) { - throw std::runtime_error("Stack underflow"); - } + assert(count > 0 && "Stack underflow"); #endif Num num = stack_ptr[count - 1]; count--; @@ -117,7 +115,7 @@ class Stack_t { void initialize() { // todo: remove this method - reset(); + reset(); } void reset() { count = 0; } diff --git a/headers/wasm/controls.hpp b/headers/wasm/controls.hpp new file mode 100644 index 00000000..16fa5136 --- /dev/null +++ b/headers/wasm/controls.hpp @@ -0,0 +1,5 @@ +#include +#include + +using MCont_t = std::function; +using Cont_t = std::function; \ No newline at end of file diff --git a/headers/wasm/smt_solver.hpp b/headers/wasm/smt_solver.hpp index f2450905..bc8cc9f9 100644 --- a/headers/wasm/smt_solver.hpp +++ b/headers/wasm/smt_solver.hpp @@ -3,6 +3,7 @@ #include "concrete_rt.hpp" #include "symbolic_rt.hpp" +#include "utils.hpp" #include "z3++.h" #include #include @@ -35,8 +36,8 @@ class Solver { std::vector result; // Reference: // https://github.com/Z3Prover/z3/blob/master/examples/c%2B%2B/example.cpp#L59 - - std::cout << "Solved Z3 model" << std::endl << model << std::endl; + 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); @@ -48,8 +49,7 @@ class Solver { } result[id] = Num(value.get_numeral_int64()); } else { - std::cout << "Find a variable that is not created by GenSym: " << name - << std::endl; + GENSYM_INFO("Find a variable that is not created by GenSym: " + name); } } return result; diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 18629c80..94351f07 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -2,7 +2,9 @@ #define WASM_SYMBOLIC_RT_HPP #include "concrete_rt.hpp" +#include "controls.hpp" #include +#include #include #include #include @@ -22,6 +24,12 @@ class Symbolic { static int max_id = 0; +#ifdef NO_REUSE +static bool REUSE_MODE = false; +#else +static bool REUSE_MODE = true; +#endif + class Symbol : public Symbolic { public: // TODO: add type information to determine the size of bitvector @@ -65,6 +73,10 @@ struct SymVal { SymVal negate() const; }; +static SymVal make_symbolic(int index) { + return SymVal(std::make_shared(index)); +} + inline SymVal Concrete(Num num) { return SymVal(std::make_shared(num)); } @@ -133,6 +145,8 @@ inline SymVal SymVal::makeSymbolic() const { } } +class Snapshot_t; + class SymStack_t { public: void push(SymVal val) { @@ -142,6 +156,11 @@ class SymStack_t { 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 auto ret = stack.back(); stack.pop_back(); return ret; @@ -149,11 +168,25 @@ class SymStack_t { SymVal peek() { return stack.back(); } + std::monostate shift(int32_t offset, int32_t size) { + auto n = stack.size(); + for (size_t i = n - size; i < n; ++i) { + stack[i - offset] = stack[i]; + } + stack.resize(n - offset); + return std::monostate(); + } + void reset() { // Reset the symbolic stack stack.clear(); } + void reuse(Snapshot_t snapshot); + + size_t size() const { return stack.size(); } + +private: std::vector stack; }; @@ -187,9 +220,46 @@ class SymFrames_t { stack.clear(); } + void reuse(Snapshot_t snapshot); + std::vector stack; }; +// A snapshot of the symbolic state and execution context (control) +class Snapshot_t { +public: + explicit Snapshot_t(); + + SymStack_t get_stack() const { return stack; } + SymFrames_t get_frames() const { return frames; } + +private: + SymStack_t stack; + SymFrames_t frames; +}; + +inline void SymStack_t::reuse(Snapshot_t snapshot) { +// Reusing the symbolic stack from the snapshot +#ifdef DEBUG + std::cout << "Reusing symbolic state from snapshot" << std::endl; + std::cout << "Old stack size = " << stack.size() << std::endl; + std::cout << "New stack size = " << snapshot.get_stack().stack.size() + << std::endl; +#endif + stack = snapshot.get_stack().stack; +} + +inline void SymFrames_t::reuse(Snapshot_t snapshot) { +// Reusing the symbolic frames from the snapshot +#ifdef DEBUG + std::cout << "Reusing symbolic state from snapshot" << std::endl; + std::cout << "Old frame size = " << stack.size() << std::endl; + std::cout << "New frame size = " << snapshot.get_frames().stack.size() + << std::endl; +#endif + stack = snapshot.get_frames().stack; +} + static SymFrames_t SymFrames; struct Node; @@ -199,7 +269,7 @@ struct NodeBox { std::unique_ptr node; NodeBox *parent; - std::monostate fillIfElseNode(SymVal cond); + std::monostate fillIfElseNode(SymVal cond, const Snapshot_t &snapshot); std::monostate fillFinishedNode(); std::monostate fillFailedNode(); std::monostate fillUnreachableNode(); @@ -251,8 +321,9 @@ struct IfElseNode : Node { SymVal cond; std::unique_ptr true_branch; std::unique_ptr false_branch; + Snapshot_t snapshot; - IfElseNode(SymVal cond, NodeBox *parent) + IfElseNode(SymVal cond, NodeBox *parent, Snapshot_t snapshot) : cond(cond), true_branch(std::make_unique(parent)), false_branch(std::make_unique(parent)) {} @@ -367,13 +438,15 @@ inline NodeBox::NodeBox(NodeBox *parent) /* TODO: avoid allocation of unexplored node */ parent(parent) {} -inline std::monostate NodeBox::fillIfElseNode(SymVal cond) { - // fill the current NodeBox with an ifelse branch node it's unexplored +inline std::monostate NodeBox::fillIfElseNode(SymVal cond, + const Snapshot_t &snapshot) { + // fill the current NodeBox with an ifelse branch node when it's unexplored if (dynamic_cast(node.get())) { - node = std::make_unique(cond, this); + node = std::make_unique(cond, this, snapshot); } - assert(dynamic_cast(node.get()) != nullptr && - "Current node is not an IfElseNode, cannot fill it!"); + assert( + dynamic_cast(node.get()) != nullptr && + "Current node is not an Unexplored nor an IfElseNode, cannot fill it!"); return std::monostate(); } @@ -427,6 +500,32 @@ inline std::vector NodeBox::collect_path_conds() { return result; } +class Reuse_t { +public: + Reuse_t() : reuse_flag(false) {} + bool is_reusing() { + // we are in reuse mode and the flag is set + return REUSE_MODE && reuse_flag; + } + + void turn_on_reusing() { reuse_flag = true; } + + void turn_off_reusing() { reuse_flag = false; } + +private: + bool reuse_flag; +}; + +static Reuse_t Reuse; + +inline Snapshot_t::Snapshot_t() : stack(SymStack), frames(SymFrames) { +#ifdef DEBUG + std::cout << "Creating snapshot of size " << stack.size() << std::endl; +#endif + assert(!Reuse.is_reusing() && + "Creating snapshot while reusing the symbolic stack"); +} + class ExploreTree_t { public: explicit ExploreTree_t() @@ -435,14 +534,19 @@ class ExploreTree_t { void reset_cursor() { // Reset the cursor to the root of the tree cursor = root.get(); + Reuse.turn_off_reusing(); + // if root cursor is a branch node, then we can reuse the snapshot inside it + if (auto ite = dynamic_cast(cursor->node.get())) { + Reuse.turn_on_reusing(); + } } std::monostate fillFinishedNode() { return cursor->fillFinishedNode(); } std::monostate fillFailedNode() { return cursor->fillFailedNode(); } - std::monostate fillIfElseNode(SymVal cond) { - return cursor->fillIfElseNode(cond); + std::monostate fillIfElseNode(SymVal cond, const Snapshot_t &snapshot) { + return cursor->fillIfElseNode(cond, snapshot); } std::monostate moveCursor(bool branch) { @@ -456,6 +560,24 @@ class ExploreTree_t { } else { cursor = if_else_node->false_branch.get(); } + + if (dynamic_cast(cursor->node.get())) { + // If we meet an unexplored node, resume the snapshot before and keep + // going + +#ifdef DEBUG + std::cout << "Resuming snapshot for unexplored node" << std::endl; +#endif + if (Reuse.is_reusing()) { + Reuse.turn_off_reusing(); + SymStack.reuse(if_else_node->snapshot); + } + } else if (dynamic_cast(cursor->node.get())) { + // if we are moving to a branch node, we must have reused the symbolic + // states + assert((!REUSE_MODE || Reuse.is_reusing()) && + "Moving to a branch node without reusing symbolic states"); + } return std::monostate(); } @@ -531,9 +653,7 @@ class SymEnv_t { return map[symbol->get_id()]; } - void update(std::vector new_env) { - map = std::move(new_env); - } + void update(std::vector new_env) { map = std::move(new_env); } std::string to_string() const { std::string result; @@ -548,7 +668,7 @@ class SymEnv_t { } private: - std::vector map; // The symbolic environment, a vector of Num + std::vector map; // The symbolic environment, a vector of Num }; static SymEnv_t SymEnv; diff --git a/headers/wasm/utils.hpp b/headers/wasm/utils.hpp index 8a86ac98..ba57a1df 100644 --- a/headers/wasm/utils.hpp +++ b/headers/wasm/utils.hpp @@ -12,4 +12,28 @@ } 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 + #endif // UTILS_HPP \ No newline at end of file diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index 833bbc9b..769a0b85 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -26,28 +26,57 @@ trait StagedWasmEvaluator extends SAIOps { trait ReturnSite trait StagedNum { + def tipe: ValueType + } + + trait StagedConcreteNum { def tipe: ValueType = this match { - case I32(_, _) => NumType(I32Type) - case I64(_, _) => NumType(I64Type) - case F32(_, _) => NumType(F32Type) - case F64(_, _) => NumType(F64Type) + case I32C(_) => NumType(I32Type) + case I64C(_) => NumType(I64Type) + case F32C(_) => NumType(F32Type) + case F64C(_) => NumType(F64Type) } def i: Rep[Num] + } + + case class I32C(i: Rep[Num]) extends StagedConcreteNum + case class I64C(i: Rep[Num]) extends StagedConcreteNum + case class F32C(i: Rep[Num]) extends StagedConcreteNum + case class F64C(i: Rep[Num]) extends StagedConcreteNum + + + trait StagedSymbolicNum { + def tipe: ValueType = this match { + case I32S(_) => NumType(I32Type) + case I64S(_) => NumType(I64Type) + case F32S(_) => NumType(F32Type) + case F64S(_) => NumType(F64Type) + } def s: Rep[SymVal] } - case class I32(i: Rep[Num], s: Rep[SymVal]) extends StagedNum - case class I64(i: Rep[Num], s: Rep[SymVal]) extends StagedNum - case class F32(i: Rep[Num], s: Rep[SymVal]) extends StagedNum - case class F64(i: Rep[Num], s: Rep[SymVal]) extends StagedNum - def toStagedNum(num: Num): StagedNum = { + case class I32S(s: Rep[SymVal]) extends StagedSymbolicNum + case class I64S(s: Rep[SymVal]) extends StagedSymbolicNum + case class F32S(s: Rep[SymVal]) extends StagedSymbolicNum + case class F64S(s: Rep[SymVal]) extends StagedSymbolicNum + + def toStagedNum(num: Num): StagedConcreteNum = { num match { - case I32V(_) => I32(num, Concrete(num)) - case I64V(_) => I64(num, Concrete(num)) - case F32V(_) => F32(num, Concrete(num)) - case F64V(_) => F64(num, Concrete(num)) + case I32V(_) => I32C(num) + case I64V(_) => I64C(num) + case F32V(_) => F32C(num) + case F64V(_) => F64C(num) + } + } + + def toStagedSymbolicNum(num: Num): StagedSymbolicNum = { + num match { + case I32V(_) => I32S(Concrete(num)) + case I64V(_) => I64S(Concrete(num)) + case F32V(_) => F32S(Concrete(num)) + case F64V(_) => F64S(Concrete(num)) } } @@ -59,12 +88,21 @@ trait StagedWasmEvaluator extends SAIOps { case NumType(F64Type) => 8 } - def toTagger: (Rep[Num], Rep[SymVal]) => StagedNum = { + def concreteTag: (Rep[Num]) => StagedConcreteNum = { + ty match { + case NumType(I32Type) => I32C + case NumType(I64Type) => I64C + case NumType(F32Type) => F32C + case NumType(F64Type) => F64C + } + } + + def symbolicTag: (Rep[SymVal]) => StagedSymbolicNum = { ty match { - case NumType(I32Type) => I32 - case NumType(I64Type) => I64 - case NumType(F32Type) => F32 - case NumType(F64Type) => F64 + case NumType(I32Type) => I32S + case NumType(I64Type) => I64S + case NumType(F32Type) => F32S + case NumType(F64Type) => F64S } } } @@ -74,12 +112,22 @@ trait StagedWasmEvaluator extends SAIOps { frameTypes: List[ValueType] ) { def push(ty: ValueType): Context = { - Context(ty :: stackTypes, frameTypes) + this.copy(stackTypes = ty :: stackTypes) + } + + def peek: ValueType = { + stackTypes.head } def pop(): (ValueType, Context) = { val (ty :: rest) = stackTypes - (ty, Context(rest, frameTypes)) + (ty, this.copy(stackTypes = rest)) + } + + def take(n: Int): Context = { + Predef.assert(n <= stackTypes.size, s"Context.take size $n is larger than stack size ${stackTypes.size}") + val (taken, rest) = stackTypes.splitAt(n) + this.copy(stackTypes = rest) } def shift(offset: Int, size: Int): Context = { @@ -93,11 +141,50 @@ trait StagedWasmEvaluator extends SAIOps { ) } } + + } + + case class ContextTransition(startCtx: Context, history: List[Instr], endCtx: Context) { + def log(instr: Instr): ContextTransition = { + this.copy(history = instr :: history) + } + + def clearHistory: (Context, List[Instr], CleanCT) = { + (startCtx, history, CleanCT(endCtx)) + } + + def push(ty: ValueType): ContextTransition = { + this.copy(endCtx = endCtx.push(ty)) + } + + def peek: ValueType = { + endCtx.peek + } + + def pop(): (ValueType, ContextTransition) = { + val (ty, newCtx) = endCtx.pop() + (ty, this.copy(endCtx = newCtx)) + } + + def take(n: Int): ContextTransition = { + this.copy(endCtx = endCtx.take(n)) + } + + def shift(offset: Int, size: Int): ContextTransition = { + this.copy(endCtx = endCtx.shift(offset, size)) + } + } + + case class CleanCT(ctx: Context) + + // we can treat every CleanCT as a ContextTransition + implicit def toContextCT(ct: CleanCT): ContextTransition = { + ContextTransition(ct.ctx, Nil, ct.ctx) } type MCont[A] = Unit => A type Cont[A] = (MCont[A]) => A - type Trail[A] = List[Context => Rep[Cont[A]]] + type Trail[A] = List[CleanCT => Rep[Cont[A]]] // a cache storing the compiled code for each function, to reduce re-compilation val compileCache = new HashMap[Int, Rep[(MCont[Unit]) => Unit]] @@ -113,173 +200,268 @@ trait StagedWasmEvaluator extends SAIOps { } + // TODO: maybe we don't need concern snapshot at compile time at all + trait Snapshot + + // Create a snapshot of the symbolic execution, we should ensure that current symstack is in use + // We don't need to store the control information, since the control is totally decided by concrete states + def makeSnapshot(): Rep[Snapshot] = { + "snapshot-make".reflectCtrlWith[Snapshot]() + } + + def isSymStateInUse: Rep[Boolean] = !ReuseManager.isReusing + def eval(insts: List[Instr], - kont: Context => Rep[Cont[Unit]], + kont: CleanCT => Rep[Cont[Unit]], mkont: Rep[MCont[Unit]], trail: Trail[Unit]) - (implicit ctx: Context): Rep[Unit] = { - if (insts.isEmpty) return kont(ctx)(mkont) + (oldCT: ContextTransition): Rep[Unit] = { + if (insts.isEmpty) { + val (oldCtx, history, ct) = oldCT.clearHistory + if (isSymStateInUse) { + evalSym(history)(oldCtx) + } + return kont(ct)(mkont) + } // Predef.println(s"[DEBUG] Evaluating instructions: ${insts.mkString(", ")}") // Predef.println(s"[DEBUG] Current context: $ctx") - val (inst, rest) = (insts.head, insts.tail) + val ct = oldCT.log(inst) inst match { case Drop => - val (_, newCtx) = Stack.pop() - eval(rest, kont, mkont, trail)(newCtx) + val (ty, ct1) = ct.pop() + Stack.popC(ty) + eval(rest, kont, mkont, trail)(ct1) case WasmConst(num) => - val newCtx = Stack.push(toStagedNum(num)) - eval(rest, kont, mkont, trail)(newCtx) + Stack.pushC(toStagedNum(num)) + val ct1 = ct.push(num.tipe(module)) + eval(rest, kont, mkont, trail)(ct1) case Symbolic(ty) => - val (id, newCtx1) = Stack.pop() - val symVal = id.makeSymbolic() - val concVal = SymEnv.read(symVal) - val tagger = ty.toTagger - val value = tagger(concVal, symVal) - val newCtx2 = Stack.push(value)(newCtx1) - eval(rest, kont, mkont, trail)(newCtx2) + val id = Stack.popC(ty) + val symVal = id.makeSymbolic(ty) + val num = SymEnv.read(symVal.s) + Stack.pushC(ty.concreteTag(num)) + val ct1 = ct.pop()._2.push(ty) + eval(rest, kont, mkont, trail)(ct1) case LocalGet(i) => - val newCtx = Stack.push(Frames.get(i)) - eval(rest, kont, mkont, trail)(newCtx) + Stack.pushC(Frames.getC(i)(ct.endCtx)) + val ct1 = ct.push(ct.endCtx.frameTypes(i)) + eval(rest, kont, mkont, trail)(ct1) case LocalSet(i) => - val (num, newCtx) = Stack.pop() - Frames.set(i, num)(newCtx) - eval(rest, kont, mkont, trail)(newCtx) + val (ty, ct1) = ct.pop() + val num = Stack.popC(ty) + Frames.setC(i, num) + eval(rest, kont, mkont, trail)(ct1) case LocalTee(i) => - val (num, newCtx) = Stack.peek - Frames.set(i, num) - eval(rest, kont, mkont, trail)(newCtx) + val ty = ct.peek + val num = Stack.peekC(ty) + Frames.setC(i, num) + eval(rest, kont, mkont, trail)(ct) case GlobalGet(i) => - val newCtx = Stack.push(Globals(i)) - eval(rest, kont, mkont, trail)(newCtx) + Stack.pushC(Globals.getC(i)) + val ct1 = ct.push(module.globals(i).ty.ty) + eval(rest, kont, mkont, trail)(ct1) case GlobalSet(i) => - val (value, newCtx) = Stack.pop() + val (ty, ct1) = ct.pop() + val num = Stack.popC(ty) module.globals(i).ty match { - case GlobalType(tipe, true) => Globals(i) = value + case GlobalType(tipe, true) => { + Globals.setC(i, num) + } case _ => throw new Exception("Cannot set immutable global") } - eval(rest, kont, mkont, trail)(newCtx) + eval(rest, kont, mkont, trail)(ct1) case Store(StoreOp(align, offset, ty, None)) => - val (value, newCtx1) = Stack.pop() - val (addr, newCtx2) = Stack.pop()(newCtx1) + val (ty1, ct1) = ct.pop() + val value = Stack.popC(ty1) + val (ty2, ct2) = ct1.pop() + val addr = Stack.popC(ty2) Memory.storeInt(addr.toInt, offset, value.toInt) - eval(rest, kont, mkont, trail)(newCtx2) - case Nop => eval(rest, kont, mkont, trail) + eval(rest, kont, mkont, trail)(ct2) + case Nop => eval(rest, kont, mkont, trail)(ct) case Load(LoadOp(align, offset, ty, None, None)) => - val (addr, newCtx1) = Stack.pop() - val value = Memory.loadInt(addr.toInt, offset) - val newCtx2 = Stack.push(value)(newCtx1) - eval(rest, kont, mkont, trail)(newCtx2) + val (ty1, ct1) = ct.pop() + val addr = Stack.popC(ty1) + val num = Memory.loadIntC(addr.toInt, offset) + Stack.pushC(num) + val ct2 = ct1.push(ty) + eval(rest, kont, mkont, trail)(ct2) case MemorySize => ??? case MemoryGrow => - val (delta, newCtx1) = Stack.pop() + val (ty, ct1) = ct.pop() + val delta = Stack.popC(ty) val ret = Memory.grow(delta.toInt) val retNum = Values.I32V(ret) - val retSym = "Concrete".reflectCtrlWith[SymVal](retNum) - val newCtx2 = Stack.push(I32(retNum, retSym))(newCtx1) - eval(rest, kont, mkont, trail)(newCtx2) + // For now, we assume that the result of memory.grow only depends on the execution path, + // we can relax this by turning it return to a symbol value and mimic the memory.grow's result as input. + Stack.pushC(I32C(retNum)) + val ct2 = ct1.push(NumType(I32Type)) + eval(rest, kont, mkont, trail)(ct2) case MemoryFill => ??? case Unreachable => unreachable() case Test(op) => - val (v, newCtx1) = Stack.pop() - val newCtx2 = Stack.push(evalTestOp(op, v))(newCtx1) - eval(rest, kont, mkont, trail)(newCtx2) + val (ty, ct1) = ct.pop() + val v = Stack.popC(ty) + Stack.pushC(evalTestOpC(op, v)) + val ct2 = ct1.push(v.tipe) + eval(rest, kont, mkont, trail)(ct2) case Unary(op) => - val (v, newCtx1) = Stack.pop() - val newCtx2 = Stack.push(evalUnaryOp(op, v))(newCtx1) - eval(rest, kont, mkont, trail)(newCtx2) + val (ty, ct1) = ct.pop() + val v = Stack.popC(ty) + val res = evalUnaryOpC(op, v) + Stack.pushC(res) + val ct2 = ct1.push(res.tipe) + eval(rest, kont, mkont, trail)(ct2) case Binary(op) => - val (v2, newCtx1) = Stack.pop() - val (v1, newCtx2) = Stack.pop()(newCtx1) - val newCtx3 = Stack.push(evalBinOp(op, v1, v2))(newCtx2) - eval(rest, kont, mkont, trail)(newCtx3) + val (ty2, ct1) = ct.pop() + val v2 = Stack.popC(ty2) + val (ty1, ct2) = ct1.pop() + val v1 = Stack.popC(ty1) + val res = evalBinOpC(op, v1, v2) + Stack.pushC(res) + val ct3 = ct2.push(res.tipe) + eval(rest, kont, mkont, trail)(ct3) case Compare(op) => - val (v2, newCtx1) = Stack.pop() - val (v1, newCtx2) = Stack.pop()(newCtx1) - val newCtx3 = Stack.push(evalRelOp(op, v1, v2))(newCtx2) - eval(rest, kont, mkont, trail)(newCtx3) + val (ty2, ct1) = ct.pop() + val v2 = Stack.popC(ty2) + val (ty1, ct2) = ct1.pop() + val v1 = Stack.popC(ty1) + val res = evalRelOpC(op, v1, v2) + Stack.pushC(res) + val ct3 = ct2.push(res.tipe) + eval(rest, kont, mkont, trail)(ct3) case WasmBlock(ty, inner) => // no need to modify the stack when entering a block // the type system guarantees that we will never take more than the input size from the stack val funcTy = ty.funcType - val exitSize = ctx.stackTypes.size - funcTy.inps.size + funcTy.out.size + val exitSize = ct.endCtx.stackTypes.size - funcTy.inps.size + funcTy.out.size val dummy = makeDummy - def restK(restCtx: Context): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { + def restK(ct: CleanCT): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { info(s"Exiting the block, stackSize =", Stack.size) - val offset = restCtx.stackTypes.size - exitSize - val newRestCtx = Stack.shift(offset, funcTy.out.size)(restCtx) - eval(rest, kont, mk, trail)(newRestCtx) + val offset = ct.endCtx.stackTypes.size - exitSize + Stack.shiftC(offset, funcTy.out.size) + if (isSymStateInUse) { + Stack.shiftS(offset, funcTy.out.size) + } + val ct1 = ct.shift(offset, funcTy.out.size) + eval(rest, kont, mk, trail)(ct1) }) - eval(inner, restK _, mkont, restK _ :: trail) + // TODO: extract this into a function + val (oldCtx, history, ct1) = ct.clearHistory + if (isSymStateInUse) { + evalSym(history)(oldCtx) + } + eval(inner, restK _, mkont, restK _ :: trail)(ct1) case Loop(ty, inner) => val funcTy = ty.funcType - val exitSize = ctx.stackTypes.size - funcTy.inps.size + funcTy.out.size + val exitSize = ct.endCtx.stackTypes.size - funcTy.inps.size + funcTy.out.size val dummy = makeDummy - def restK(restCtx: Context): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { + def restK(ct: CleanCT): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { info(s"Exiting the loop, stackSize =", Stack.size) - val offset = restCtx.stackTypes.size - exitSize - val newRestCtx = Stack.shift(offset, funcTy.out.size)(restCtx) - eval(rest, kont, mk, trail)(newRestCtx) + val offset = ct.endCtx.stackTypes.size - exitSize + Stack.shiftC(offset, funcTy.out.size) + if (isSymStateInUse) { + Stack.shiftS(offset, funcTy.out.size) + } + val ct1 = ct.shift(offset, funcTy.out.size) + eval(rest, kont, mk, trail)(ct1) }) - val enterSize = ctx.stackTypes.size - def loop(restCtx: Context): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { + val enterSize = ct.endCtx.stackTypes.size + def loop(ct: CleanCT): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { info(s"Entered the loop, stackSize =", Stack.size) - val offset = restCtx.stackTypes.size - enterSize - val newRestCtx = Stack.shift(offset, funcTy.inps.size)(restCtx) - eval(inner, restK _, mk, loop _ :: trail)(newRestCtx) + val offset = ct.endCtx.stackTypes.size - enterSize + Stack.shiftC(offset, funcTy.inps.size) + if (isSymStateInUse) { + Stack.shiftS(offset, funcTy.inps.size) + } + val ct1 = ct.shift(offset, funcTy.inps.size) + eval(inner, restK _, mk, loop _ :: trail)(ct1) }) - loop(ctx)(mkont) + val (oldCtx, history, ct1) = ct.clearHistory + if (isSymStateInUse) { + evalSym(history)(oldCtx) + } + loop(ct1)(mkont) case If(ty, thn, els) => val funcTy = ty.funcType - val (cond, newCtx) = Stack.pop() - val exitSize = newCtx.stackTypes.size - funcTy.inps.size + funcTy.out.size - // TODO: can we avoid code duplication here? - val dummy = makeDummy - def restK(restCtx: Context): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { + val (condTy, ct1) = ct.pop() + val cond = Stack.popC(condTy) + val exitSize = ct1.endCtx.stackTypes.size - funcTy.inps.size + funcTy.out.size + def restK(ct: CleanCT): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { info(s"Exiting the if, stackSize =", Stack.size) - val offset = restCtx.stackTypes.size - exitSize - val newRestCtx = Stack.shift(offset, funcTy.out.size)(restCtx) - eval(rest, kont, mk, trail)(newRestCtx) + val offset = ct.endCtx.stackTypes.size - exitSize + Stack.shiftC(offset, funcTy.out.size) + if (isSymStateInUse) { + Stack.shiftS(offset, funcTy.out.size) + } + val ct1 = ct.shift(offset, funcTy.out.size) + eval(rest, kont, mk, trail)(ct1) }) - // TODO: put the cond.s to path condition - ExploreTree.fillWithIfElse(cond.s) + val (oldCtx, history, ct2) = ct1.clearHistory + if (isSymStateInUse) { + // when we are not reusing + evalSym(history)(oldCtx) + val snapshot = makeSnapshot() + val symCond = Stack.popS(condTy) + ExploreTree.fillWithIfElse(symCond.s, snapshot) + } if (cond.toInt != 0) { ExploreTree.moveCursor(true) - eval(thn, restK _, mkont, restK _ :: trail)(newCtx) + eval(thn, restK _, mkont, restK _ :: trail)(ct2) } else { ExploreTree.moveCursor(false) - eval(els, restK _, mkont, restK _ :: trail)(newCtx) + eval(els, restK _, mkont, restK _ :: trail)(ct2) } () case Br(label) => info(s"Jump to $label") - trail(label)(ctx)(mkont) + val (oldCtx, history, ct1) = ct.clearHistory + if (isSymStateInUse) { + evalSym(history)(oldCtx) + } + trail(label)(ct1)(mkont) case BrIf(label) => - val (cond, newCtx) = Stack.pop() + val (ty, ct1) = ct.pop() + val cond = Stack.popC(ty) + val (oldCtx, history, ct2) = ct1.clearHistory info(s"The br_if(${label})'s condition is ", cond.toInt) - // TODO: put the cond.s to path condition - ExploreTree.fillWithIfElse(cond.s) + if (isSymStateInUse) { + evalSym(history)(oldCtx) + val symCond = Stack.popS(ty) + val snapshot = makeSnapshot() + ExploreTree.fillWithIfElse(symCond.s, snapshot) + } if (cond.toInt != 0) { info(s"Jump to $label") ExploreTree.moveCursor(true) - trail(label)(newCtx)(mkont) + trail(label)(ct2)(mkont) } else { info(s"Continue") ExploreTree.moveCursor(false) - eval(rest, kont, mkont, trail)(newCtx) + eval(rest, kont, mkont, trail)(ct2) } () case BrTable(labels, default) => - val (label, newCtx) = Stack.pop() + val (ty, ct1) = ct.pop() + val label = Stack.popC(ty) + val (oldCtx, history, ct2) = ct1.clearHistory + if (isSymStateInUse) { + evalSym(history)(oldCtx) + } def aux(choices: List[Int], idx: Int): Rep[Unit] = { - if (choices.isEmpty) trail(default)(newCtx)(mkont) + if (choices.isEmpty) trail(default)(ct2)(mkont) else { val cond = (label - toStagedNum(I32V(idx))).isZero() - ExploreTree.fillWithIfElse(cond.s) + if (isSymStateInUse) { + val labelSym = Stack.peekS(ty) + val condSym = (labelSym - toStagedSymbolicNum(I32V(idx))).isZero() + val snapshot = makeSnapshot() + ExploreTree.fillWithIfElse(condSym.s, snapshot) + } if (cond.toInt != 0) { ExploreTree.moveCursor(true) - trail(choices.head)(newCtx)(mkont) + trail(choices.head)(ct2)(mkont) } else { ExploreTree.moveCursor(false) @@ -288,12 +470,142 @@ trait StagedWasmEvaluator extends SAIOps { } } aux(labels, 0) - case Return => trail.last(ctx)(mkont) - case Call(f) => evalCall(rest, kont, mkont, trail, f, false) - case ReturnCall(f) => evalCall(rest, kont, mkont, trail, f, true) + if (isSymStateInUse) { + Stack.popS(ty) + } + () + case Return => + // return instruction is also stack-polymorphic + val (oldCtx, history, ct2) = ct.clearHistory + if (isSymStateInUse) { + evalSym(history)(oldCtx) + } + trail.last(ct2)(mkont) + case Call(f) => evalCall(rest, kont, mkont, trail, f, false)(ct) + case ReturnCall(f) => evalCall(rest, kont, mkont, trail, f, true)(ct) + case _ => + val todo = "todo-op".reflectCtrlWith[Unit]() + eval(rest, kont, mkont, trail)(ct) + } + } + + def replayAndClearHistory(ct: ContextTransition): ContextTransition = { + val (oldCtx, history, ct1) = ct.clearHistory + if (isSymStateInUse) { + evalSym(history)(oldCtx) + } + ct1 + } + + // call the symbolic interpreter to evaluate the history that just executed by + // concrete interpreter + def evalSym(history: List[Instr]) + (ctx: Context): Rep[Unit] = { + // val func = topFun((_: Rep[Unit]) => evalS(history.reverse)) + // func(()) + evalS(history.reverse)(ctx) + } + + def evalS(insts: List[Instr]) + (ctx: Context): Rep[Unit] = { + if (insts.isEmpty) return () + + // Predef.println(s"[DEBUG] Evaluating instructions: ${insts.mkString(", ")}") + // Predef.println(s"[DEBUG] Current context: $ctx") + val (inst, rest) = (insts.head, insts.tail) + inst match { + case Drop => + val (ty, newCtx) = ctx.pop() + Stack.popS(ty) + evalS(rest)(newCtx) + case WasmConst(num) => + Stack.pushS(toStagedSymbolicNum(num)) + val newCtx = ctx.push(num.tipe(module)) + evalS(rest)(newCtx) + case Symbolic(ty) => + val id = Stack.popS(ty) + val symVal = id.makeSymbolic(ty) + Stack.pushS(symVal) + val newCtx = ctx.pop()._2.push(ty) + evalS(rest)(newCtx) + case LocalGet(i) => + Stack.pushS(Frames.getS(i)(ctx)) + val newCtx = ctx.push(ctx.frameTypes(i)) + evalS(rest)(newCtx) + case LocalSet(i) => + val (ty, newCtx) = ctx.pop() + val sym = Stack.popS(ty) + Frames.setS(i, sym) + evalS(rest)(newCtx) + case LocalTee(i) => + val ty = ctx.pop()._1 + val sym = Stack.peekS(ty) + Frames.setS(i, sym) + evalS(rest)(ctx) + case GlobalGet(i) => + Stack.pushS(Globals.getS(i)) + val newCtx = ctx.push(module.globals(i).ty.ty) + evalS(rest)(newCtx) + case GlobalSet(i) => + val (ty, newCtx) = ctx.pop() + val sym = Stack.popS(ty) + module.globals(i).ty match { + case GlobalType(tipe, true) => { + Globals.setS(i, sym) + } + case _ => throw new Exception("Cannot set immutable global") + } + evalS(rest)(newCtx) + case Nop => evalS(rest)(ctx) + case Store(StoreOp(align, offset, ty, None)) => ??? + case Load(LoadOp(align, offset, ty, None, None)) => ??? + case MemorySize => ??? + case MemoryGrow => ??? + case MemoryFill => ??? + case Unreachable => unreachable() + case Test(op) => + val (ty, newCtx1) = ctx.pop() + val s = Stack.popS(ty) + Stack.pushS(evalTestOpS(op, s)) + val newCtx2 = newCtx1.push(s.tipe) + evalS(rest)(newCtx2) + case Unary(op) => + val (ty, newCtx1) = ctx.pop() + val s = Stack.popS(ty) + val res = evalUnaryOpS(op, s) + Stack.pushS(res) + val newCtx2 = newCtx1.push(res.tipe) + evalS(rest)(newCtx2) + case Binary(op) => + val (ty2, newCtx1) = ctx.pop() + val s2 = Stack.popS(ty2) + val (ty1, newCtx2) = newCtx1.pop() + val s1 = Stack.popS(ty1) + val res = evalBinOpS(op, s1, s2) + Stack.pushS(res) + val newCtx3 = newCtx2.push(res.tipe) + evalS(rest)(newCtx3) + case Compare(op) => + val (ty2, newCtx1) = ctx.pop() + val s2 = Stack.popS(ty2) + val (ty1, newCtx2) = newCtx1.pop() + val s1 = Stack.popS(ty1) + val res = evalRelOpS(op, s1, s2) + Stack.pushS(res) + val newCtx3 = newCtx2.push(res.tipe) + evalS(rest)(newCtx3) + case WasmBlock(ty, inner) => () + case Loop(ty, inner) => () + case If(ty, thn, els) => () + case Br(label) => () + case BrIf(label) => () + case BrTable(labels, default) => () + case Return => () + case Call(f) => () + case ReturnCall(f) => () case _ => val todo = "todo-op".reflectCtrlWith[Unit]() - eval(rest, kont, mkont, trail) + evalS(rest)(ctx) } } @@ -301,12 +613,16 @@ trait StagedWasmEvaluator extends SAIOps { def evalCall(rest: List[Instr], - kont: Context => Rep[Cont[Unit]], + kont: CleanCT => Rep[Cont[Unit]], mkont: Rep[MCont[Unit]], trail: Trail[Unit], funcIndex: Int, isTail: Boolean) - (implicit ctx: Context): Rep[Unit] = { + (implicit ct: ContextTransition): Rep[Unit] = { + val (oldCtx, history, ct1) = ct.clearHistory + if (isSymStateInUse) { + evalSym(history)(oldCtx) + } module.funcs(funcIndex) match { case FuncDef(_, FuncBodyDef(ty, _, bodyLocals, body)) => val locals = bodyLocals ++ ty.inps @@ -316,63 +632,102 @@ trait StagedWasmEvaluator extends SAIOps { } else { val callee = topFun((mk: Rep[MCont[Unit]]) => { info(s"Entered the function at $funcIndex, stackSize =", Stack.size) - // we can do some check here to ensure the function returns correct size of stack - eval(body, (_: Context) => forwardKont, mk, ((_: Context) => forwardKont)::Nil)(Context(Nil, locals)) + // the return instruction is also stack polymorphic + def retK(ct: CleanCT): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { + info(s"Exiting the function at $funcIndex, stackSize =", Stack.size) + val offset = ct.ctx.stackTypes.size - ty.out.size + Stack.shiftC(offset, ty.out.size) + Stack.shiftS(offset, ty.out.size) + mk(()) + }) + eval(body, retK _, mk, retK _::Nil)(CleanCT(Context(Nil, locals))) }) compileCache(funcIndex) = callee callee } // Predef.println(s"[DEBUG] locals size: ${locals.size}") - val (args, newCtx) = Stack.take(ty.inps.size) + val ct2 = ct1.take(ty.inps.size) + val exitSize = ty.out.size + ct2.endCtx.stackTypes.size if (isTail) { // when tail call, return to the caller's return continuation - Frames.popFrame(ctx.frameTypes.size) - Frames.pushFrame(locals) - Frames.putAll(args) + val argsC = Stack.takeC(ty.inps) + Frames.popFrameC(ct2.endCtx.frameTypes.size) + Frames.pushFrameC(locals) + Frames.putAllC(argsC) + if (isSymStateInUse) { + val argsS = Stack.takeS(ty.inps) + Frames.popFrameS(ct2.endCtx.frameTypes.size) + Frames.pushFrameS(locals) + Frames.putAllS(argsS) + } callee(mkont) } else { // We make a new trail by `restK`, since function creates a new block to escape // (more or less like `return`) val restK: Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { info(s"Exiting the function at $funcIndex, stackSize =", Stack.size) - Frames.popFrame(locals.size) - eval(rest, kont, mk, trail)(newCtx.copy(stackTypes = ty.out.reverse ++ ctx.stackTypes.drop(ty.inps.size))) + Frames.popFrameC(locals.size) + Frames.popFrameS(locals.size) + val newCtx = ct2.endCtx.copy(stackTypes = ty.out.reverse ++ ct2.endCtx.stackTypes) + eval(rest, kont, mk, trail)(CleanCT(newCtx)) }) val dummy = makeDummy val newMKont: Rep[MCont[Unit]] = funHere((_u: Rep[Unit]) => { restK(mkont) }, dummy) - Frames.pushFrame(locals) - Frames.putAll(args) + val argsC = Stack.takeC(ty.inps) + Frames.pushFrameC(locals) + Frames.putAllC(argsC) + if (isSymStateInUse) { + val argsS = Stack.takeS(ty.inps) + Frames.pushFrameS(locals) + Frames.putAllS(argsS) + } callee(newMKont) } case Import("console", "log", _) | Import("spectest", "print_i32", _) => //println(s"[DEBUG] current stack: $stack") - val (v, newCtx) = Stack.pop() + val (ty, ct2) = ct1.pop() + val v = Stack.popC(ty) + Stack.popS(ty) println(v.toInt) - eval(rest, kont, mkont, trail)(newCtx) + eval(rest, kont, mkont, trail)(ct2) case Import("console", "assert", _) => - val (v, newCtx) = Stack.pop() + val (ty, ct2) = ct1.pop() + val v = Stack.popC(ty) + // TODO: We should also add s into exploration tree + val s = Stack.popS(ty) runtimeAssert(v.toInt != 0) - eval(rest, kont, mkont, trail)(newCtx) + eval(rest, kont, mkont, trail)(ct2) case Import(_, _, _) => throw new Exception(s"Unknown import at $funcIndex") case _ => throw new Exception(s"Definition at $funcIndex is not callable") } } - def evalTestOp(op: TestOp, value: StagedNum): StagedNum = op match { + def evalTestOpC(op: TestOp, value: StagedConcreteNum): StagedConcreteNum = op match { case Eqz(_) => value.isZero } - def evalUnaryOp(op: UnaryOp, value: StagedNum): StagedNum = op match { + def evalTestOpS(op: TestOp, value: StagedSymbolicNum): StagedSymbolicNum = op match { + case Eqz(_) => value.isZero + } + + def evalUnaryOpC(op: UnaryOp, value: StagedConcreteNum): StagedConcreteNum = op match { case Clz(_) => value.clz() case Ctz(_) => value.ctz() case Popcnt(_) => value.popcnt() case _ => ??? } - def evalBinOp(op: BinOp, v1: StagedNum, v2: StagedNum): StagedNum = op match { + def evalUnaryOpS(op: UnaryOp, value: StagedSymbolicNum): StagedSymbolicNum = op match { + case Clz(_) => value.clz() + case Ctz(_) => value.ctz() + case Popcnt(_) => value.popcnt() + case _ => ??? + } + + def evalBinOpC(op: BinOp, v1: StagedConcreteNum, v2: StagedConcreteNum): StagedConcreteNum = op match { case Add(_) => v1 + v2 case Mul(_) => v1 * v2 case Sub(_) => v1 - v2 @@ -386,7 +741,35 @@ trait StagedWasmEvaluator extends SAIOps { throw new Exception(s"Unknown binary operation $op") } - def evalRelOp(op: RelOp, v1: StagedNum, v2: StagedNum): StagedNum = op match { + def evalBinOpS(op: BinOp, v1: StagedSymbolicNum, v2: StagedSymbolicNum): StagedSymbolicNum = op match { + case Add(_) => v1 + v2 + case Mul(_) => v1 * v2 + case Sub(_) => v1 - v2 + case Shl(_) => v1 << v2 + // case ShrS(_) => v1 >> v2 // TODO: signed shift right + case ShrU(_) => v1 >> v2 + case And(_) => v1 & v2 + case DivS(_) => v1 / v2 + case DivU(_) => v1 / v2 + case _ => + throw new Exception(s"Unknown binary operation $op") + } + + def evalRelOpC(op: RelOp, v1: StagedConcreteNum, v2: StagedConcreteNum): StagedConcreteNum = op match { + case Eq(_) => v1 numEq v2 + case Ne(_) => v1 numNe v2 + case LtS(_) => v1 < v2 + case LtU(_) => v1 ltu v2 + case GtS(_) => v1 > v2 + case GtU(_) => v1 gtu v2 + case LeS(_) => v1 <= v2 + case LeU(_) => v1 leu v2 + case GeS(_) => v1 >= v2 + case GeU(_) => v1 geu v2 + case _ => ??? + } + + def evalRelOpS(op: RelOp, v1: StagedSymbolicNum, v2: StagedSymbolicNum): StagedSymbolicNum = op match { case Eq(_) => v1 numEq v2 case Ne(_) => v1 numNe v2 case LtS(_) => v1 < v2 @@ -426,12 +809,14 @@ trait StagedWasmEvaluator extends SAIOps { } val (instrs, locals) = (funBody.body, funBody.locals) resetStacks() - Frames.pushFrame(locals) - eval(instrs, (_: Context) => forwardKont, mkont, ((_: Context) => forwardKont)::Nil)(Context(Nil, locals)) - Frames.popFrame(locals.size) + Frames.pushFrameC(locals) + Frames.pushFrameS(locals) + eval(instrs, _ => forwardKont, mkont, ((_: CleanCT) => forwardKont)::Nil)(CleanCT(Context(Nil, locals))) + Frames.popFrameC(locals.size) + Frames.popFrameS(locals.size) } - def evalTop(main: Option[String], printRes: Boolean, dumpTree: Option[String]): Rep[Unit] = { + def evalTop(main: Option[String], printRes: Boolean): Rep[Unit] = { val haltK: Rep[Unit] => Rep[Unit] = (_) => { info("Exiting the program...") if (printRes) { @@ -450,66 +835,78 @@ trait StagedWasmEvaluator extends SAIOps { // stack operations object Stack { - def shift(offset: Int, size: Int)(ctx: Context): Context = { + def shiftC(offset: Int, size: Int) = { if (offset > 0) { "stack-shift".reflectCtrlWith[Unit](offset, size) } - ctx.shift(offset, size) + } + + def shiftS(offset: Int, size: Int) = { + if (offset > 0) { + "sym-stack-shift".reflectCtrlWith[Unit](offset, size) + } } def initialize(): Rep[Unit] = { "stack-init".reflectCtrlWith[Unit]() } - def pop()(implicit ctx: Context): (StagedNum, Context) = { - val (ty, newContext) = ctx.pop() - val num = ty match { - case NumType(I32Type) => I32("stack-pop".reflectCtrlWith[Num](), "sym-stack-pop".reflectCtrlWith[SymVal]()) - case NumType(I64Type) => I64("stack-pop".reflectCtrlWith[Num](), "sym-stack-pop".reflectCtrlWith[SymVal]()) - case NumType(F32Type) => F32("stack-pop".reflectCtrlWith[Num](), "sym-stack-pop".reflectCtrlWith[SymVal]()) - case NumType(F32Type) => F64("stack-pop".reflectCtrlWith[Num](), "sym-stack-pop".reflectCtrlWith[SymVal]()) - } - (num, newContext) + def popC(ty: ValueType): StagedConcreteNum = ty match { + case NumType(I32Type) => I32C("stack-pop".reflectCtrlWith[Num]()) + case NumType(I64Type) => I64C("stack-pop".reflectCtrlWith[Num]()) + case NumType(F32Type) => F32C("stack-pop".reflectCtrlWith[Num]()) + case NumType(F32Type) => F64C("stack-pop".reflectCtrlWith[Num]()) } - def peek(implicit ctx: Context): (StagedNum, Context) = { - val ty = ctx.stackTypes.head - val num = ty match { - case NumType(I32Type) => I32("stack-peek".reflectCtrlWith[Num](), "sym-stack-peek".reflectCtrlWith[SymVal]()) - case NumType(I64Type) => I64("stack-peek".reflectCtrlWith[Num](), "sym-stack-peek".reflectCtrlWith[SymVal]()) - case NumType(F32Type) => F32("stack-peek".reflectCtrlWith[Num](), "sym-stack-peek".reflectCtrlWith[SymVal]()) - case NumType(F32Type) => F64("stack-peek".reflectCtrlWith[Num](), "sym-stack-peek".reflectCtrlWith[SymVal]()) - } - (num, ctx) + def popS(ty: ValueType): StagedSymbolicNum = ty match { + case NumType(I32Type) => I32S("sym-stack-pop".reflectCtrlWith[SymVal]()) + case NumType(I64Type) => I64S("sym-stack-pop".reflectCtrlWith[SymVal]()) + case NumType(F32Type) => F32S("sym-stack-pop".reflectCtrlWith[SymVal]()) + case NumType(F64Type) => F64S("sym-stack-pop".reflectCtrlWith[SymVal]()) } - def push(num: StagedNum)(implicit ctx: Context): Context = { - num match { - case I32(v, s) => "stack-push".reflectCtrlWith[Unit](v); "sym-stack-push".reflectCtrlWith[Unit](s) - case I64(v, s) => "stack-push".reflectCtrlWith[Unit](v); "sym-stack-push".reflectCtrlWith[Unit](s) - case F32(v, s) => "stack-push".reflectCtrlWith[Unit](v); "sym-stack-push".reflectCtrlWith[Unit](s) - case F64(v, s) => "stack-push".reflectCtrlWith[Unit](v); "sym-stack-push".reflectCtrlWith[Unit](s) - } - ctx.push(num.tipe) + def peekC(ty: ValueType): StagedConcreteNum = ty match { + case NumType(I32Type) => I32C("stack-peek".reflectCtrlWith[Num]()) + case NumType(I64Type) => I64C("stack-peek".reflectCtrlWith[Num]()) + case NumType(F32Type) => F32C("stack-peek".reflectCtrlWith[Num]()) + case NumType(F32Type) => F64C("stack-peek".reflectCtrlWith[Num]()) } - def take(n: Int)(implicit ctx: Context): (List[StagedNum], Context) = n match { - case 0 => (Nil, ctx) - case n => - val (v, newCtx1) = pop() - val (rest, newCtx2) = take(n - 1) - (v::rest, newCtx2) + def peekS(ty: ValueType): StagedSymbolicNum = ty match { + case NumType(I32Type) => I32S("sym-stack-peek".reflectCtrlWith[SymVal]()) + case NumType(I64Type) => I64S("sym-stack-peek".reflectCtrlWith[SymVal]()) + case NumType(F32Type) => F32S("sym-stack-peek".reflectCtrlWith[SymVal]()) + case NumType(F64Type) => F64S("sym-stack-peek".reflectCtrlWith[SymVal]()) } - def drop(n: Int)(implicit ctx: Context): Context = { - take(n)._2 + def pushC(num: StagedConcreteNum) = num match { + case I32C(v) => "stack-push".reflectCtrlWith[Unit](v) + case I64C(v) => "stack-push".reflectCtrlWith[Unit](v) + case F32C(v) => "stack-push".reflectCtrlWith[Unit](v) + case F64C(v) => "stack-push".reflectCtrlWith[Unit](v) } - def shift(offset: Rep[Int], size: Rep[Int]): Rep[Unit] = { - if (offset > 0) { - "stack-shift".reflectCtrlWith[Unit](offset, size) - "sym-stack-shift".reflectCtrlWith[Unit](offset, size) - } + def pushS(num: StagedSymbolicNum) = num match { + case I32S(s) => "sym-stack-push".reflectCtrlWith[Unit](s) + case I64S(s) => "sym-stack-push".reflectCtrlWith[Unit](s) + case F32S(s) => "sym-stack-push".reflectCtrlWith[Unit](s) + case F64S(s) => "sym-stack-push".reflectCtrlWith[Unit](s) + } + + def takeC(types: List[ValueType]): List[StagedConcreteNum] = types match { + case Nil => Nil + case t :: ts => + val v = popC(t) + val rest = takeC(ts) + v :: rest + } + + def takeS(types: List[ValueType]): List[StagedSymbolicNum] = types match { + case Nil => Nil + case t :: ts => + val v = popS(t) + val rest = takeS(ts) + v :: rest } def print(): Rep[Unit] = { @@ -522,41 +919,72 @@ trait StagedWasmEvaluator extends SAIOps { } object Frames { - def get(i: Int)(implicit ctx: Context): StagedNum = { + def getC(i: Int)(implicit ctx: Context): StagedConcreteNum = { // val offset = ctx.frameTypes.take(i).map(_.size).sum ctx.frameTypes(i) match { - case NumType(I32Type) => I32("frame-get".reflectCtrlWith[Num](i), "sym-frame-get".reflectCtrlWith[SymVal](i)) - case NumType(I64Type) => I64("frame-get".reflectCtrlWith[Num](i), "sym-frame-get".reflectCtrlWith[SymVal](i)) - case NumType(F32Type) => F32("frame-get".reflectCtrlWith[Num](i), "sym-frame-get".reflectCtrlWith[SymVal](i)) - case NumType(F64Type) => F64("frame-get".reflectCtrlWith[Num](i), "sym-frame-get".reflectCtrlWith[SymVal](i)) + case NumType(I32Type) => I32C("frame-get".reflectCtrlWith[Num](i)) + case NumType(I64Type) => I64C("frame-get".reflectCtrlWith[Num](i)) + case NumType(F32Type) => F32C("frame-get".reflectCtrlWith[Num](i)) + case NumType(F64Type) => F64C("frame-get".reflectCtrlWith[Num](i)) } } - def set(i: Int, v: StagedNum)(implicit ctx: Context): Rep[Unit] = { - // val offset = ctx.frameTypes.take(i).map(_.size).sum + def getS(i: Int)(implicit ctx: Context): StagedSymbolicNum = { + ctx.frameTypes(i) match { + case NumType(I32Type) => I32S("sym-frame-get".reflectCtrlWith[SymVal](i)) + case NumType(I64Type) => I64S("sym-frame-get".reflectCtrlWith[SymVal](i)) + case NumType(F32Type) => F32S("sym-frame-get".reflectCtrlWith[SymVal](i)) + case NumType(F64Type) => F64S("sym-frame-get".reflectCtrlWith[SymVal](i)) + } + } + + def setC(i: Int, v: StagedConcreteNum): Rep[Unit] = { v match { - case I32(v, s) => "frame-set".reflectCtrlWith[Unit](i, v); "sym-frame-set".reflectCtrlWith[Unit](i, s) - case I64(v, s) => "frame-set".reflectCtrlWith[Unit](i, v); "sym-frame-set".reflectCtrlWith[Unit](i, s) - case F32(v, s) => "frame-set".reflectCtrlWith[Unit](i, v); "sym-frame-set".reflectCtrlWith[Unit](i, s) - case F64(v, s) => "frame-set".reflectCtrlWith[Unit](i, v); "sym-frame-set".reflectCtrlWith[Unit](i, s) + case I32C(v) => "frame-set".reflectCtrlWith[Unit](i, v) + case I64C(v) => "frame-set".reflectCtrlWith[Unit](i, v) + case F32C(v) => "frame-set".reflectCtrlWith[Unit](i, v) + case F64C(v) => "frame-set".reflectCtrlWith[Unit](i, v) + } + } + + def setS(i: Int, s: StagedSymbolicNum): Rep[Unit] = { + s match { + case I32S(s) => "sym-frame-set".reflectCtrlWith[Unit](i, s) + case I64S(s) => "sym-frame-set".reflectCtrlWith[Unit](i, s) + case F32S(s) => "sym-frame-set".reflectCtrlWith[Unit](i, s) + case F64S(s) => "sym-frame-set".reflectCtrlWith[Unit](i, s) } } - def pushFrame(locals: List[ValueType]): Rep[Unit] = { + def pushFrameC(locals: List[ValueType]): Rep[Unit] = { // Predef.println(s"[DEBUG] push frame: $locals") val size = locals.size "frame-push".reflectCtrlWith[Unit](size) + } + + def pushFrameS(locals: List[ValueType]): Rep[Unit] = { + // Predef.println(s"[DEBUG] push frame: $locals") + val size = locals.size "sym-frame-push".reflectCtrlWith[Unit](size) } - def popFrame(size: Int): Rep[Unit] = { + def popFrameC(size: Int): Rep[Unit] = { "frame-pop".reflectCtrlWith[Unit](size) + } + + def popFrameS(size: Int): Rep[Unit] = { "sym-frame-pop".reflectCtrlWith[Unit](size) } - def putAll(args: List[StagedNum])(implicit ctx: Context): Rep[Unit] = { + def putAllC(args: List[StagedConcreteNum]): Rep[Unit] = { for ((arg, i) <- args.view.reverse.zipWithIndex) { - Frames.set(i, arg) + Frames.setC(i, arg) + } + } + + def putAllS(args: List[StagedSymbolicNum]): Rep[Unit] = { + for ((arg, i) <- args.view.reverse.zipWithIndex) { + Frames.setS(i, arg) } } } @@ -567,8 +995,12 @@ trait StagedWasmEvaluator extends SAIOps { // todo: store symbolic value to memory via extract/concat operation } - def loadInt(base: Rep[Int], offset: Int): StagedNum = { - I32("I32V".reflectCtrlWith[Num]("memory-load-int".reflectCtrlWith[Int](base, offset)), "sym-load-int-todo".reflectCtrlWith[SymVal](base, offset)) + def loadIntC(base: Rep[Int], offset: Int): StagedConcreteNum = { + I32C("I32V".reflectCtrlWith[Num]("memory-load-int".reflectCtrlWith[Int](base, offset))) + } + + def loadIntS(base: Rep[Int], offset: Int): StagedSymbolicNum = { + I32S("sym-load-int-todo".reflectCtrlWith[SymVal](base, offset)) } // Returns the previous memory size on success, or -1 if the memory cannot be grown. @@ -603,29 +1035,47 @@ trait StagedWasmEvaluator extends SAIOps { // global read/write object Globals { - def apply(i: Int): StagedNum = { + def getC(i: Int): StagedConcreteNum = { module.globals(i).ty match { - case GlobalType(NumType(I32Type), _) => I32("global-get".reflectCtrlWith[Num](i), "sym-global-get".reflectCtrlWith[SymVal](i)) - case GlobalType(NumType(I64Type), _) => I64("global-get".reflectCtrlWith[Num](i), "sym-global-get".reflectCtrlWith[SymVal](i)) - case GlobalType(NumType(F32Type), _) => F32("global-get".reflectCtrlWith[Num](i), "sym-global-get".reflectCtrlWith[SymVal](i)) - case GlobalType(NumType(F64Type), _) => F64("global-get".reflectCtrlWith[Num](i), "sym-global-get".reflectCtrlWith[SymVal](i)) + case GlobalType(NumType(I32Type), _) => I32C("global-get".reflectCtrlWith[Num](i)) + case GlobalType(NumType(I64Type), _) => I64C("global-get".reflectCtrlWith[Num](i)) + case GlobalType(NumType(F32Type), _) => F32C("global-get".reflectCtrlWith[Num](i)) + case GlobalType(NumType(F64Type), _) => F64C("global-get".reflectCtrlWith[Num](i)) } } - def update(i: Int, v: StagedNum): Rep[Unit] = { + def getS(i: Int): StagedSymbolicNum = { module.globals(i).ty match { - case GlobalType(NumType(I32Type), _) => "global-set".reflectCtrlWith[Unit](i, v.i);"sym-global-set".reflectCtrlWith[Unit](i, v.s) - case GlobalType(NumType(I64Type), _) => "global-set".reflectCtrlWith[Unit](i, v.i);"sym-global-set".reflectCtrlWith[Unit](i, v.s) - case GlobalType(NumType(F32Type), _) => "global-set".reflectCtrlWith[Unit](i, v.i);"sym-global-set".reflectCtrlWith[Unit](i, v.s) - case GlobalType(NumType(F64Type), _) => "global-set".reflectCtrlWith[Unit](i, v.i);"sym-global-set".reflectCtrlWith[Unit](i, v.s) + case GlobalType(NumType(I32Type), _) => I32S("sym-global-get".reflectCtrlWith[SymVal](i)) + case GlobalType(NumType(I64Type), _) => I64S("sym-global-get".reflectCtrlWith[SymVal](i)) + case GlobalType(NumType(F32Type), _) => F32S("sym-global-get".reflectCtrlWith[SymVal](i)) + case GlobalType(NumType(F64Type), _) => F64S("sym-global-get".reflectCtrlWith[SymVal](i)) + } + } + + def setC(i: Int, v: StagedConcreteNum): Rep[Unit] = { + module.globals(i).ty match { + case GlobalType(NumType(I32Type), _) => "global-set".reflectCtrlWith[Unit](i, v.i) + case GlobalType(NumType(I64Type), _) => "global-set".reflectCtrlWith[Unit](i, v.i) + case GlobalType(NumType(F32Type), _) => "global-set".reflectCtrlWith[Unit](i, v.i) + case GlobalType(NumType(F64Type), _) => "global-set".reflectCtrlWith[Unit](i, v.i) + } + } + + def setS(i: Int, s: StagedSymbolicNum): Rep[Unit] = { + module.globals(i).ty match { + case GlobalType(NumType(I32Type), _) => "sym-global-set".reflectCtrlWith[Unit](i, s.s) + case GlobalType(NumType(I64Type), _) => "sym-global-set".reflectCtrlWith[Unit](i, s.s) + case GlobalType(NumType(F32Type), _) => "sym-global-set".reflectCtrlWith[Unit](i, s.s) + case GlobalType(NumType(F64Type), _) => "sym-global-set".reflectCtrlWith[Unit](i, s.s) } } } // Exploration tree, object ExploreTree { - def fillWithIfElse(s: Rep[SymVal]): Rep[Unit] = { - "tree-fill-if-else".reflectCtrlWith[Unit](s) + def fillWithIfElse(sym: Rep[SymVal], snapshot: Rep[Snapshot]): Rep[Unit] = { + "tree-fill-if-else".reflectCtrlWith[Unit](sym, snapshot) } def fillWithFinished(): Rep[Unit] = { @@ -633,6 +1083,7 @@ trait StagedWasmEvaluator extends SAIOps { } def moveCursor(branch: Boolean): Rep[Unit] = { + // when moving cursor from to an unexplored node, we need to change the reuse state "tree-move-cursor".reflectCtrlWith[Unit](branch) } @@ -651,165 +1102,337 @@ trait StagedWasmEvaluator extends SAIOps { } } + object ReuseManager { + def isReusing: Rep[Boolean] = { + "reuse-is-reusing".reflectCtrlWith[Boolean]() + } + + def turnOnReuse(): Rep[Unit] = { + "reuse-turn-on".reflectCtrlWith[Unit]() + } + + def turnOffReuse(): Rep[Unit] = { + "reuse-turn-off".reflectCtrlWith[Unit]() + } + } + // runtime Num type - implicit class StagedNumOps(num: StagedNum) { + implicit class StagedConcreteNumOps(num: StagedConcreteNum) { + + def makeSymbolic(ty: ValueType): StagedSymbolicNum = num match { + case I32C(x) => I32S("make-symbolic-concrete".reflectCtrlWith[SymVal](num.toInt)) + } def toInt: Rep[Int] = "num-to-int".reflectCtrlWith[Int](num.i) - def isZero(): StagedNum = num match { - case I32(x_c, x_s) => I32(Values.I32V("is-zero".reflectCtrlWith[Int](num.toInt)), "sym-is-zero".reflectCtrlWith[SymVal](x_s)) + def isZero(): StagedConcreteNum = num match { + case I32C(x_c) => I32C(Values.I32V("is-zero".reflectCtrlWith[Int](num.toInt))) + } + + def clz(): StagedConcreteNum = num match { + case I32C(x) => I32C("clz".reflectCtrlWith[Num](x)) + case I64C(x) => I64C("clz".reflectCtrlWith[Num](x)) + } + + def ctz(): StagedConcreteNum = num match { + case I32C(x) => I32C("ctz".reflectCtrlWith[Num](x)) + case I64C(x) => I64C("ctz".reflectCtrlWith[Num](x)) + } + + def popcnt(): StagedConcreteNum = num match { + case I32C(x) => I32C("popcnt".reflectCtrlWith[Num](x)) + case I64C(x) => I64C("popcnt".reflectCtrlWith[Num](x)) + } + + def +(rhs: StagedConcreteNum): StagedConcreteNum = { + (num, rhs) match { + case (I32C(x), I32C(y)) => I32C("binary-add".reflectCtrlWith[Num](x, y)) + case (I64C(x), I64C(y)) => I64C("binary-add".reflectCtrlWith[Num](x, y)) + case (F32C(x), F32C(y)) => F32C("binary-add".reflectCtrlWith[Num](x, y)) + case (F64C(x), F64C(y)) => F64C("binary-add".reflectCtrlWith[Num](x, y)) + } + } + + def -(rhs: StagedConcreteNum): StagedConcreteNum = { + (num, rhs) match { + case (I32C(x), I32C(y)) => I32C("binary-sub".reflectCtrlWith[Num](x, y)) + case (I64C(x), I64C(y)) => I64C("binary-sub".reflectCtrlWith[Num](x, y)) + case (F32C(x), F32C(y)) => F32C("binary-sub".reflectCtrlWith[Num](x, y)) + case (F64C(x), F64C(y)) => F64C("binary-sub".reflectCtrlWith[Num](x, y)) + } + } + + def *(rhs: StagedConcreteNum): StagedConcreteNum = { + (num, rhs) match { + case (I32C(x), I32C(y)) => I32C("binary-mul".reflectCtrlWith[Num](x, y)) + case (I64C(x), I64C(y)) => I64C("binary-mul".reflectCtrlWith[Num](x, y)) + case (F32C(x), F32C(y)) => F32C("binary-mul".reflectCtrlWith[Num](x, y)) + case (F64C(x), F64C(y)) => F64C("binary-mul".reflectCtrlWith[Num](x, y)) + } + } + + def /(rhs: StagedConcreteNum): StagedConcreteNum = { + (num, rhs) match { + case (I32C(x), I32C(y)) => I32C("binary-div".reflectCtrlWith[Num](x, y)) + case (I64C(x), I64C(y)) => I64C("binary-div".reflectCtrlWith[Num](x, y)) + case (F32C(x), F32C(y)) => F32C("binary-div".reflectCtrlWith[Num](x, y)) + case (F64C(x), F64C(y)) => F64C("binary-div".reflectCtrlWith[Num](x, y)) + } + } + + def <<(rhs: StagedConcreteNum): StagedConcreteNum = { + (num, rhs) match { + case (I32C(x), I32C(y)) => I32C("binary-shl".reflectCtrlWith[Num](x, y)) + case (I64C(x), I64C(y)) => I64C("binary-shl".reflectCtrlWith[Num](x, y)) + case (F32C(x), F32C(y)) => F32C("binary-shl".reflectCtrlWith[Num](x, y)) + case (F64C(x), F64C(y)) => F64C("binary-shl".reflectCtrlWith[Num](x, y)) + } + } + + def >>(rhs: StagedConcreteNum): StagedConcreteNum = { + (num, rhs) match { + case (I32C(x), I32C(y)) => I32C("binary-shr".reflectCtrlWith[Num](x, y)) + case (I64C(x), I64C(y)) => I64C("binary-shr".reflectCtrlWith[Num](x, y)) + case (F32C(x), F32C(y)) => F32C("binary-shr".reflectCtrlWith[Num](x, y)) + case (F64C(x), F64C(y)) => F64C("binary-shr".reflectCtrlWith[Num](x, y)) + } + } + + def &(rhs: StagedConcreteNum): StagedConcreteNum = { + (num, rhs) match { + case (I32C(x), I32C(y)) => I32C("binary-and".reflectCtrlWith[Num](x, y)) + case (I64C(x), I64C(y)) => I64C("binary-and".reflectCtrlWith[Num](x, y)) + case (F32C(x), F32C(y)) => F32C("binary-and".reflectCtrlWith[Num](x, y)) + case (F64C(x), F64C(y)) => F64C("binary-and".reflectCtrlWith[Num](x, y)) + } + } + + def numEq(rhs: StagedConcreteNum): StagedConcreteNum = { + (num, rhs) match { + case (I32C(x), I32C(y)) => I32C("relation-eq".reflectCtrlWith[Num](x, y)) + case (I64C(x), I64C(y)) => I32C("relation-eq".reflectCtrlWith[Num](x, y)) + } + } + + def numNe(rhs: StagedConcreteNum): StagedConcreteNum = { + (num, rhs) match { + case (I32C(x), I32C(y)) => I32C("relation-ne".reflectCtrlWith[Num](x, y)) + case (I64C(x), I64C(y)) => I32C("relation-ne".reflectCtrlWith[Num](x, y)) + } + } + + def <(rhs: StagedConcreteNum): StagedConcreteNum = { + (num, rhs) match { + case (I32C(x), I32C(y)) => I32C("relation-lt".reflectCtrlWith[Num](x, y)) + case (I64C(x), I64C(y)) => I32C("relation-lt".reflectCtrlWith[Num](x, y)) + } } - def clz(): StagedNum = num match { - case I32(x_c, x_s) => I32("clz".reflectCtrlWith[Num](x_c), "sym-clz".reflectCtrlWith[SymVal](x_s)) - case I64(x_c, x_s) => I64("clz".reflectCtrlWith[Num](x_c), "sym-clz".reflectCtrlWith[SymVal](x_s)) + def ltu(rhs: StagedConcreteNum): StagedConcreteNum = { + (num, rhs) match { + case (I32C(x), I32C(y)) => I32C("relation-ltu".reflectCtrlWith[Num](x, y)) + case (I64C(x), I64C(y)) => I32C("relation-ltu".reflectCtrlWith[Num](x, y)) + } } - def ctz(): StagedNum = num match { - case I32(x_c, x_s) => I32("ctz".reflectCtrlWith[Num](x_c), "sym-ctz".reflectCtrlWith[SymVal](x_s)) - case I64(x_c, x_s) => I64("ctz".reflectCtrlWith[Num](x_c), "sym-ctz".reflectCtrlWith[SymVal](x_s)) + def >(rhs: StagedConcreteNum): StagedConcreteNum = { + (num, rhs) match { + case (I32C(x), I32C(y)) => I32C("relation-gt".reflectCtrlWith[Num](x, y)) + case (I64C(x), I64C(y)) => I32C("relation-gt".reflectCtrlWith[Num](x, y)) + } } - def popcnt(): StagedNum = num match { - case I32(x_c, x_s) => I32("popcnt".reflectCtrlWith[Num](x_c), "sym-popcnt".reflectCtrlWith[SymVal](x_s)) - case I64(x_c, x_s) => I64("popcnt".reflectCtrlWith[Num](x_c), "sym-popcnt".reflectCtrlWith[SymVal](x_s)) + def gtu(rhs: StagedConcreteNum): StagedConcreteNum = { + (num, rhs) match { + case (I32C(x), I32C(y)) => I32C("relation-gtu".reflectCtrlWith[Num](x, y)) + case (I64C(x), I64C(y)) => I32C("relation-gtu".reflectCtrlWith[Num](x, y)) + } } - def makeSymbolic(): Rep[SymVal] = { - "make-symbolic".reflectCtrlWith[SymVal](num.s) + def <=(rhs: StagedConcreteNum): StagedConcreteNum = { + (num, rhs) match { + case (I32C(x), I32C(y)) => I32C("relation-le".reflectCtrlWith[Num](x, y)) + case (I64C(x), I64C(y)) => I32C("relation-le".reflectCtrlWith[Num](x, y)) + } + } + + def leu(rhs: StagedConcreteNum): StagedConcreteNum = { + (num, rhs) match { + case (I32C(x), I32C(y)) => I32C("relation-leu".reflectCtrlWith[Num](x, y)) + case (I64C(x), I64C(y)) => I32C("relation-leu".reflectCtrlWith[Num](x, y)) + } } - def +(rhs: StagedNum): StagedNum = { + def >=(rhs: StagedConcreteNum): StagedConcreteNum = { (num, rhs) match { - case (I32(x_c, x_s), I32(y_c, y_s)) => I32("binary-add".reflectCtrlWith[Num](x_c, y_c), "sym-binary-add".reflectCtrlWith[SymVal](x_s, y_s)) - case (I64(x_c, x_s), I64(y_c, y_s)) => I64("binary-add".reflectCtrlWith[Num](x_c, y_c), "sym-binary-add".reflectCtrlWith[SymVal](x_s, y_s)) - case (F32(x_c, x_s), F32(y_c, y_s)) => F32("binary-add".reflectCtrlWith[Num](x_c, y_c), "sym-binary-add".reflectCtrlWith[SymVal](x_s, y_s)) - case (F64(x_c, x_s), F64(y_c, y_s)) => F64("binary-add".reflectCtrlWith[Num](x_c, y_c), "sym-binary-add".reflectCtrlWith[SymVal](x_s, y_s)) + case (I32C(x), I32C(y)) => I32C("relation-ge".reflectCtrlWith[Num](x, y)) + case (I64C(x), I64C(y)) => I32C("relation-ge".reflectCtrlWith[Num](x, y)) } } + def geu(rhs: StagedConcreteNum): StagedConcreteNum = { + (num, rhs) match { + case (I32C(x), I32C(y)) => I32C("relation-geu".reflectCtrlWith[Num](x, y)) + case (I64C(x), I64C(y)) => I32C("relation-geu".reflectCtrlWith[Num](x, y)) + } + } + } + + implicit class StagedSymbolicNumOps(num: StagedSymbolicNum) { + def makeSymbolic(ty: ValueType): StagedSymbolicNum = num match { + case I32S(x) => I32S("make-symbolic".reflectCtrlWith[SymVal](x)) + case _ => throw new RuntimeException("Symbol index must be an i32") + } + + def isZero(): StagedSymbolicNum = num match { + case I32S(x) => I32S("sym-is-zero".reflectCtrlWith[SymVal](x)) + } + + def clz(): StagedSymbolicNum = num match { + case I32S(x) => I32S("sym-clz".reflectCtrlWith[SymVal](x)) + case I64S(x) => I64S("sym-clz".reflectCtrlWith[SymVal](x)) + } + + def ctz(): StagedSymbolicNum = num match { + case I32S(x) => I32S("sym-ctz".reflectCtrlWith[SymVal](x)) + case I64S(x) => I64S("sym-ctz".reflectCtrlWith[SymVal](x)) + } + + def popcnt(): StagedSymbolicNum = num match { + case I32S(x) => I32S("sym-popcnt".reflectCtrlWith[SymVal](x)) + case I64S(x) => I64S("sym-popcnt".reflectCtrlWith[SymVal](x)) + } + + def +(rhs: StagedSymbolicNum): StagedSymbolicNum = { + (num, rhs) match { + case (I32S(x), I32S(y)) => I32S("sym-binary-add".reflectCtrlWith[SymVal](x, y)) + case (I64S(x), I64S(y)) => I64S("sym-binary-add".reflectCtrlWith[SymVal](x, y)) + case (F32S(x), F32S(y)) => F32S("sym-binary-add".reflectCtrlWith[SymVal](x, y)) + case (F64S(x), F64S(y)) => F64S("sym-binary-add".reflectCtrlWith[SymVal](x, y)) + } + } - def -(rhs: StagedNum): StagedNum = { + def -(rhs: StagedSymbolicNum): StagedSymbolicNum = { (num, rhs) match { - case (I32(x_c, x_s), I32(y_c, y_s)) => I32("binary-sub".reflectCtrlWith[Num](x_c, y_c), "sym-binary-sub".reflectCtrlWith[SymVal](x_s, y_s)) - case (I64(x_c, x_s), I64(y_c, y_s)) => I64("binary-sub".reflectCtrlWith[Num](x_c, y_c), "sym-binary-sub".reflectCtrlWith[SymVal](x_s, y_s)) - case (F32(x_c, x_s), F32(y_c, y_s)) => F32("binary-sub".reflectCtrlWith[Num](x_c, y_c), "sym-binary-sub".reflectCtrlWith[SymVal](x_s, y_s)) - case (F64(x_c, x_s), F64(y_c, y_s)) => F64("binary-sub".reflectCtrlWith[Num](x_c, y_c), "sym-binary-sub".reflectCtrlWith[SymVal](x_s, y_s)) + case (I32S(x), I32S(y)) => I32S("sym-binary-sub".reflectCtrlWith[SymVal](x, y)) + case (I64S(x), I64S(y)) => I64S("sym-binary-sub".reflectCtrlWith[SymVal](x, y)) + case (F32S(x), F32S(y)) => F32S("sym-binary-sub".reflectCtrlWith[SymVal](x, y)) + case (F64S(x), F64S(y)) => F64S("sym-binary-sub".reflectCtrlWith[SymVal](x, y)) } } - def *(rhs: StagedNum): StagedNum = { + def *(rhs: StagedSymbolicNum): StagedSymbolicNum = { (num, rhs) match { - case (I32(x_c, x_s), I32(y_c, y_s)) => I32("binary-mul".reflectCtrlWith[Num](x_c, y_c), "sym-binary-mul".reflectCtrlWith[SymVal](x_s, y_s)) - case (I64(x_c, x_s), I64(y_c, y_s)) => I64("binary-mul".reflectCtrlWith[Num](x_c, y_c), "sym-binary-mul".reflectCtrlWith[SymVal](x_s, y_s)) - case (F32(x_c, x_s), F32(y_c, y_s)) => F32("binary-mul".reflectCtrlWith[Num](x_c, y_c), "sym-binary-mul".reflectCtrlWith[SymVal](x_s, y_s)) - case (F64(x_c, x_s), F64(y_c, y_s)) => F64("binary-mul".reflectCtrlWith[Num](x_c, y_c), "sym-binary-mul".reflectCtrlWith[SymVal](x_s, y_s)) + case (I32S(x), I32S(y)) => I32S("sym-binary-mul".reflectCtrlWith[SymVal](x, y)) + case (I64S(x), I64S(y)) => I64S("sym-binary-mul".reflectCtrlWith[SymVal](x, y)) + case (F32S(x), F32S(y)) => F32S("sym-binary-mul".reflectCtrlWith[SymVal](x, y)) + case (F64S(x), F64S(y)) => F64S("sym-binary-mul".reflectCtrlWith[SymVal](x, y)) } } - def /(rhs: StagedNum): StagedNum = { + def /(rhs: StagedSymbolicNum): StagedSymbolicNum = { (num, rhs) match { - case (I32(x_c, x_s), I32(y_c, y_s)) => I32("binary-div".reflectCtrlWith[Num](x_c, y_c), "sym-binary-div".reflectCtrlWith[SymVal](x_s, y_s)) - case (I64(x_c, x_s), I64(y_c, y_s)) => I64("binary-div".reflectCtrlWith[Num](x_c, y_c), "sym-binary-div".reflectCtrlWith[SymVal](x_s, y_s)) - case (F32(x_c, x_s), F32(y_c, y_s)) => F32("binary-div".reflectCtrlWith[Num](x_c, y_c), "sym-binary-div".reflectCtrlWith[SymVal](x_s, y_s)) - case (F64(x_c, x_s), F64(y_c, y_s)) => F64("binary-div".reflectCtrlWith[Num](x_c, y_c), "sym-binary-div".reflectCtrlWith[SymVal](x_s, y_s)) + case (I32S(x), I32S(y)) => I32S("sym-binary-div".reflectCtrlWith[SymVal](x, y)) + case (I64S(x), I64S(y)) => I64S("sym-binary-div".reflectCtrlWith[SymVal](x, y)) + case (F32S(x), F32S(y)) => F32S("sym-binary-div".reflectCtrlWith[SymVal](x, y)) + case (F64S(x), F64S(y)) => F64S("sym-binary-div".reflectCtrlWith[SymVal](x, y)) } } - def <<(rhs: StagedNum): StagedNum = { + def <<(rhs: StagedSymbolicNum): StagedSymbolicNum = { (num, rhs) match { - case (I32(x_c, x_s), I32(y_c, y_s)) => I32("binary-shl".reflectCtrlWith[Num](x_c, y_c), "sym-binary-shl".reflectCtrlWith[SymVal](x_s, y_s)) - case (I64(x_c, x_s), I64(y_c, y_s)) => I64("binary-shl".reflectCtrlWith[Num](x_c, y_c), "sym-binary-shl".reflectCtrlWith[SymVal](x_s, y_s)) - case (F32(x_c, x_s), F32(y_c, y_s)) => F32("binary-shl".reflectCtrlWith[Num](x_c, y_c), "sym-binary-shl".reflectCtrlWith[SymVal](x_s, y_s)) - case (F64(x_c, x_s), F64(y_c, y_s)) => F64("binary-shl".reflectCtrlWith[Num](x_c, y_c), "sym-binary-shl".reflectCtrlWith[SymVal](x_s, y_s)) + case (I32S(x), I32S(y)) => I32S("sym-binary-shl".reflectCtrlWith[SymVal](x, y)) + case (I64S(x), I64S(y)) => I64S("sym-binary-shl".reflectCtrlWith[SymVal](x, y)) + case (F32S(x), F32S(y)) => F32S("sym-binary-shl".reflectCtrlWith[SymVal](x, y)) + case (F64S(x), F64S(y)) => F64S("sym-binary-shl".reflectCtrlWith[SymVal](x, y)) } } - def >>(rhs: StagedNum): StagedNum = { + def >>(rhs: StagedSymbolicNum): StagedSymbolicNum = { (num, rhs) match { - case (I32(x_c, x_s), I32(y_c, y_s)) => I32("binary-shr".reflectCtrlWith[Num](x_c, y_c), "sym-binary-shr".reflectCtrlWith[SymVal](x_s, y_s)) - case (I64(x_c, x_s), I64(y_c, y_s)) => I64("binary-shr".reflectCtrlWith[Num](x_c, y_c), "sym-binary-shr".reflectCtrlWith[SymVal](x_s, y_s)) - case (F32(x_c, x_s), F32(y_c, y_s)) => F32("binary-shr".reflectCtrlWith[Num](x_c, y_c), "sym-binary-shr".reflectCtrlWith[SymVal](x_s, y_s)) - case (F64(x_c, x_s), F64(y_c, y_s)) => F64("binary-shr".reflectCtrlWith[Num](x_c, y_c), "sym-binary-shr".reflectCtrlWith[SymVal](x_s, y_s)) + case (I32S(x), I32S(y)) => I32S("sym-binary-shr".reflectCtrlWith[SymVal](x, y)) + case (I64S(x), I64S(y)) => I64S("sym-binary-shr".reflectCtrlWith[SymVal](x, y)) + case (F32S(x), F32S(y)) => F32S("sym-binary-shr".reflectCtrlWith[SymVal](x, y)) + case (F64S(x), F64S(y)) => F64S("sym-binary-shr".reflectCtrlWith[SymVal](x, y)) } } - def &(rhs: StagedNum): StagedNum = { + def &(rhs: StagedSymbolicNum): StagedSymbolicNum = { (num, rhs) match { - case (I32(x_c, x_s), I32(y_c, y_s)) => I32("binary-and".reflectCtrlWith[Num](x_c, y_c), "sym-binary-and".reflectCtrlWith[SymVal](x_s, y_s)) - case (I64(x_c, x_s), I64(y_c, y_s)) => I64("binary-and".reflectCtrlWith[Num](x_c, y_c), "sym-binary-and".reflectCtrlWith[SymVal](x_s, y_s)) - case (F32(x_c, x_s), F32(y_c, y_s)) => F32("binary-and".reflectCtrlWith[Num](x_c, y_c), "sym-binary-and".reflectCtrlWith[SymVal](x_s, y_s)) - case (F64(x_c, x_s), F64(y_c, y_s)) => F64("binary-and".reflectCtrlWith[Num](x_c, y_c), "sym-binary-and".reflectCtrlWith[SymVal](x_s, y_s)) + case (I32S(x), I32S(y)) => I32S("sym-binary-and".reflectCtrlWith[SymVal](x, y)) + case (I64S(x), I64S(y)) => I64S("sym-binary-and".reflectCtrlWith[SymVal](x, y)) + case (F32S(x), F32S(y)) => F32S("sym-binary-and".reflectCtrlWith[SymVal](x, y)) + case (F64S(x), F64S(y)) => F64S("sym-binary-and".reflectCtrlWith[SymVal](x, y)) } } - def numEq(rhs: StagedNum): StagedNum = { + def numEq(rhs: StagedSymbolicNum): StagedSymbolicNum = { (num, rhs) match { - case (I32(x_c, x_s), I32(y_c, y_s)) => I32("relation-eq".reflectCtrlWith[Num](x_c, y_c), "sym-relation-eq".reflectCtrlWith[SymVal](x_s, y_s)) - case (I64(x_c, x_s), I64(y_c, y_s)) => I32("relation-eq".reflectCtrlWith[Num](x_c, y_c), "sym-relation-eq".reflectCtrlWith[SymVal](x_s, y_s)) + case (I32S(x), I32S(y)) => I32S("sym-relation-eq".reflectCtrlWith[SymVal](x, y)) + case (I64S(x), I64S(y)) => I32S("sym-relation-eq".reflectCtrlWith[SymVal](x, y)) } } - def numNe(rhs: StagedNum): StagedNum = { + def numNe(rhs: StagedSymbolicNum): StagedSymbolicNum = { (num, rhs) match { - case (I32(x_c, x_s), I32(y_c, y_s)) => I32("relation-ne".reflectCtrlWith[Num](x_c, y_c), "sym-relation-ne".reflectCtrlWith[SymVal](x_s, y_s)) - case (I64(x_c, x_s), I64(y_c, y_s)) => I32("relation-ne".reflectCtrlWith[Num](x_c, y_c), "sym-relation-ne".reflectCtrlWith[SymVal](x_s, y_s)) + case (I32S(x), I32S(y)) => I32S("sym-relation-ne".reflectCtrlWith[SymVal](x, y)) + case (I64S(x), I64S(y)) => I32S("sym-relation-ne".reflectCtrlWith[SymVal](x, y)) } } - def <(rhs: StagedNum): StagedNum = { + def <(rhs: StagedSymbolicNum): StagedSymbolicNum = { (num, rhs) match { - case (I32(x_c, x_s), I32(y_c, y_s)) => I32("relation-lt".reflectCtrlWith[Num](x_c, y_c), "sym-relation-lt".reflectCtrlWith[SymVal](x_s, y_s)) - case (I64(x_c, x_s), I64(y_c, y_s)) => I32("relation-lt".reflectCtrlWith[Num](x_c, y_c), "sym-relation-lt".reflectCtrlWith[SymVal](x_s, y_s)) + case (I32S(x), I32S(y)) => I32S("sym-relation-lt".reflectCtrlWith[SymVal](x, y)) + case (I64S(x), I64S(y)) => I32S("sym-relation-lt".reflectCtrlWith[SymVal](x, y)) } } - def ltu(rhs: StagedNum): StagedNum = { + def ltu(rhs: StagedSymbolicNum): StagedSymbolicNum = { (num, rhs) match { - case (I32(x_c, x_s), I32(y_c, y_s)) => I32("relation-ltu".reflectCtrlWith[Num](x_c, y_c), "sym-relation-ltu".reflectCtrlWith[SymVal](x_s, y_s)) - case (I64(x_c, x_s), I64(y_c, y_s)) => I32("relation-ltu".reflectCtrlWith[Num](x_c, y_c), "sym-relation-ltu".reflectCtrlWith[SymVal](x_s, y_s)) + case (I32S(x), I32S(y)) => I32S("relation-ltu".reflectCtrlWith[SymVal](x, y)) + case (I64S(x), I64S(y)) => I32S("relation-ltu".reflectCtrlWith[SymVal](x, y)) } } - def >(rhs: StagedNum): StagedNum = { + def >(rhs: StagedSymbolicNum): StagedSymbolicNum = { (num, rhs) match { - case (I32(x_c, x_s), I32(y_c, y_s)) => I32("relation-gt".reflectCtrlWith[Num](x_c, y_c), "sym-relation-gt".reflectCtrlWith[SymVal](x_s, y_s)) - case (I64(x_c, x_s), I64(y_c, y_s)) => I32("relation-gt".reflectCtrlWith[Num](x_c, y_c), "sym-relation-gt".reflectCtrlWith[SymVal](x_s, y_s)) + case (I32S(x), I32S(y)) => I32S("sym-relation-gt".reflectCtrlWith[SymVal](x, y)) + case (I64S(x), I64S(y)) => I32S("sym-relation-gt".reflectCtrlWith[SymVal](x, y)) } } - def gtu(rhs: StagedNum): StagedNum = { + def gtu(rhs: StagedSymbolicNum): StagedSymbolicNum = { (num, rhs) match { - case (I32(x_c, x_s), I32(y_c, y_s)) => I32("relation-gtu".reflectCtrlWith[Num](x_c, y_c), "sym-relation-gtu".reflectCtrlWith[SymVal](x_s, y_s)) - case (I64(x_c, x_s), I64(y_c, y_s)) => I32("relation-gtu".reflectCtrlWith[Num](x_c, y_c), "sym-relation-gtu".reflectCtrlWith[SymVal](x_s, y_s)) + case (I32S(x), I32S(y)) => I32S("sym-relation-gtu".reflectCtrlWith[SymVal](x, y)) + case (I64S(x), I64S(y)) => I32S("sym-relation-gtu".reflectCtrlWith[SymVal](x, y)) } } - def <=(rhs: StagedNum): StagedNum = { + def <=(rhs: StagedSymbolicNum): StagedSymbolicNum = { (num, rhs) match { - case (I32(x_c, x_s), I32(y_c, y_s)) => I32("relation-le".reflectCtrlWith[Num](x_c, y_c), "sym-relation-le".reflectCtrlWith[SymVal](x_s, y_s)) - case (I64(x_c, x_s), I64(y_c, y_s)) => I32("relation-le".reflectCtrlWith[Num](x_c, y_c), "sym-relation-le".reflectCtrlWith[SymVal](x_s, y_s)) + case (I32S(x), I32S(y)) => I32S("sym-relation-le".reflectCtrlWith[SymVal](x, y)) + case (I64S(x), I64S(y)) => I32S("sym-relation-le".reflectCtrlWith[SymVal](x, y)) } } - def leu(rhs: StagedNum): StagedNum = { + def leu(rhs: StagedSymbolicNum): StagedSymbolicNum = { (num, rhs) match { - case (I32(x_c, x_s), I32(y_c, y_s)) => I32("relation-leu".reflectCtrlWith[Num](x_c, y_c), "sym-relation-leu".reflectCtrlWith[SymVal](x_s, y_s)) - case (I64(x_c, x_s), I64(y_c, y_s)) => I32("relation-leu".reflectCtrlWith[Num](x_c, y_c), "sym-relation-leu".reflectCtrlWith[SymVal](x_s, y_s)) + case (I32S(x), I32S(y)) => I32S("sym-relation-leu".reflectCtrlWith[SymVal](x, y)) + case (I64S(x), I64S(y)) => I32S("sym-relation-leu".reflectCtrlWith[SymVal](x, y)) } } - def >=(rhs: StagedNum): StagedNum = { + def >=(rhs: StagedSymbolicNum): StagedSymbolicNum = { (num, rhs) match { - case (I32(x_c, x_s), I32(y_c, y_s)) => I32("relation-ge".reflectCtrlWith[Num](x_c, y_c), "sym-relation-ge".reflectCtrlWith[SymVal](x_s, y_s)) - case (I64(x_c, x_s), I64(y_c, y_s)) => I32("relation-ge".reflectCtrlWith[Num](x_c, y_c), "sym-relation-ge".reflectCtrlWith[SymVal](x_s, y_s)) + case (I32S(x), I32S(y)) => I32S("sym-relation-ge".reflectCtrlWith[SymVal](x, y)) + case (I64S(x), I64S(y)) => I32S("sym-relation-ge".reflectCtrlWith[SymVal](x, y)) } } - def geu(rhs: StagedNum): StagedNum = { + def geu(rhs: StagedSymbolicNum): StagedSymbolicNum = { (num, rhs) match { - case (I32(x_c, x_s), I32(y_c, y_s)) => I32("relation-geu".reflectCtrlWith[Num](x_c, y_c), "sym-relation-geu".reflectCtrlWith[SymVal](x_s, y_s)) - case (I64(x_c, x_s), I64(y_c, y_s)) => I32("relation-geu".reflectCtrlWith[Num](x_c, y_c), "sym-relation-geu".reflectCtrlWith[SymVal](x_s, y_s)) + case (I32S(x), I32S(y)) => I32S("sym-relation-geu".reflectCtrlWith[SymVal](x, y)) + case (I64S(x), I64S(y)) => I32S("sym-relation-geu".reflectCtrlWith[SymVal](x, y)) } } } @@ -848,7 +1471,7 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { else if (m.toString.endsWith("I32V")) "I32V" else if (m.toString.endsWith("I64V")) "I64V" else if (m.toString.endsWith("SymVal")) "SymVal" - + else if (m.toString.endsWith("Snapshot")) "Snapshot_t" else super.remap(m) } @@ -902,10 +1525,14 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { emit("Stack.push("); shallow(value); emit(")") case Node(_, "stack-shift", List(offset, size), _) => emit("Stack.shift("); shallow(offset); emit(", "); shallow(size); emit(")") + case Node(_, "sym-stack-shift", List(offset, size), _) => + emit("SymStack.shift("); shallow(offset); emit(", "); shallow(size); emit(")") case Node(_, "stack-pop", _, _) => emit("Stack.pop()") case Node(_, "sym-stack-pop", _, _) => emit("SymStack.pop()") + case Node(_, "snapshot-make", _, _) => + emit("Snapshot_t()") case Node(_, "frame-pop", List(i), _) => emit("Frames.popFrame("); shallow(i); emit(")") case Node(_, "sym-frame-pop", List(i), _) => @@ -935,7 +1562,8 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { case Node(_, "binary-add", List(lhs, rhs), _) => shallow(lhs); emit(" + "); shallow(rhs) case Node(_, "binary-sub", List(lhs, rhs), _) => - shallow(lhs); emit(" - "); shallow(rhs) + // todo: avoid using c++ operator, use explicit method call so operator's precedence issues won't exist + emit("("); shallow(lhs); emit(" - "); shallow(rhs); emit(")") case Node(_, "binary-mul", List(lhs, rhs), _) => shallow(lhs); emit(" * "); shallow(rhs) case Node(_, "binary-div", List(lhs, rhs), _) => @@ -990,12 +1618,14 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { shallow(num); emit(".toInt()") case Node(_, "make-symbolic", List(num), _) => shallow(num); emit(".makeSymbolic()") + case Node(_, "make-symbolic-concrete", List(num), _) => + emit("make_symbolic("); shallow(num); emit(")") case Node(_, "sym-env-read", List(sym), _) => emit("SymEnv.read("); shallow(sym); emit(")") case Node(_, "assert-true", List(cond), _) => emit("GENSYM_ASSERT("); shallow(cond); emit(")") - case Node(_, "tree-fill-if-else", List(s), _) => - emit("ExploreTree.fillIfElseNode("); shallow(s); emit(")") + case Node(_, "tree-fill-if-else", List(sym, snapshot), _) => + emit("ExploreTree.fillIfElseNode("); shallow(sym); emit(", "); shallow(snapshot); emit(")") case Node(_, "tree-fill-finished", List(), _) => emit("ExploreTree.fillFinishedNode()") case Node(_, "tree-move-cursor", List(b), _) => @@ -1006,6 +1636,8 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { emit("ExploreTree.dump_graphviz("); shallow(f); emit(")") case Node(_, "sym-not", List(s), _) => shallow(s); emit(".negate()") + case Node(_, "reuse-is-reusing", List(), _) => + emit("Reuse.is_reusing()") case Node(_, "dummy", _, _) => emit("std::monostate()") case Node(_, "dummy-op", _, _) => emit("std::monostate()") case Node(_, "no-op", _, _) => @@ -1063,12 +1695,12 @@ trait WasmToCppCompilerDriver[A, B] extends CppSAIDriver[A, B] with StagedWasmEv object WasmToCppCompiler { case class GeneratedCpp(source: String, headerFolders: List[String]) - def compile(moduleInst: ModuleInstance, main: Option[String], printRes: Boolean, dumpTree: Option[String]): GeneratedCpp = { + def compile(moduleInst: ModuleInstance, main: Option[String], printRes: Boolean): GeneratedCpp = { println(s"Now compiling wasm module with entry function $main") val driver = new WasmToCppCompilerDriver[Unit, Unit] { def module: ModuleInstance = moduleInst def snippet(x: Rep[Unit]): Rep[Unit] = { - evalTop(main, printRes, dumpTree) + evalTop(main, printRes) } } GeneratedCpp(driver.code, driver.codegen.includePaths.toList) @@ -1079,8 +1711,8 @@ object WasmToCppCompiler { outputCpp: String, outputExe: String, printRes: Boolean, - dumpTree: Option[String]): Unit = { - val generated = compile(moduleInst, main, printRes, dumpTree) + macros: String*): Unit = { + val generated = compile(moduleInst, main, printRes) val code = generated.source val writer = new java.io.PrintWriter(new java.io.File(outputCpp)) @@ -1091,7 +1723,9 @@ object WasmToCppCompiler { } import sys.process._ - val command = s"g++ -std=c++20 $outputCpp -o $outputExe -O3 -g -l z3 " + generated.headerFolders.map(f => s"-I$f").mkString(" ") + val includeFlags = generated.headerFolders.map(f => s"-I$f").mkString(" ") + val macroFlags = macros.map(m => s"-D$m").mkString(" ") + val command = s"g++ -std=c++20 $outputCpp -o $outputExe -O3 -g -l z3 " + includeFlags + " " + macroFlags if (command.! != 0) { throw new RuntimeException(s"Compilation failed for $outputCpp") } diff --git a/src/test/scala/genwasym/TestStagedConcolicEval.scala b/src/test/scala/genwasym/TestStagedConcolicEval.scala index a65d0eda..48c24634 100644 --- a/src/test/scala/genwasym/TestStagedConcolicEval.scala +++ b/src/test/scala/genwasym/TestStagedConcolicEval.scala @@ -9,12 +9,24 @@ import gensym.wasm.parser._ import gensym.wasm.stagedconcolicminiwasm._ class TestStagedConcolicEval extends FunSuite { - def testFileToCpp(filename: String, main: Option[String] = None, expect: Option[List[Float]]=None) = { + def testFileConcolicCpp(filename: String, main: Option[String] = None) = { val moduleInst = ModuleInstance(Parser.parseFile(filename)) val cppFile = s"$filename.cpp" val exe = s"$cppFile.exe" val exploreTreeFile = s"$filename.tree.dot" - WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, true, Some(exploreTreeFile)) + WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, true) + + import sys.process._ + val result = Process(s"./$exe", None, "TREE_FILE" -> exploreTreeFile).!! + println(result) + } + + // only test concrete execution and its result + def testFileConcreteCpp(filename: String, main: Option[String] = None, expect: Option[List[Float]] = None) = { + val moduleInst = ModuleInstance(Parser.parseFile(filename)) + val cppFile = s"$filename.cpp" + val exe = s"$cppFile.exe" + WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, true, "NO_INFO", "RUN_ONCE") import sys.process._ val result = s"./$exe".!! @@ -30,13 +42,61 @@ class TestStagedConcolicEval extends FunSuite { }) } - test("ack-cpp") { testFileToCpp("./benchmarks/wasm/ack.wat", Some("real_main")) } + test("ack-cpp") { testFileConcolicCpp("./benchmarks/wasm/ack.wat", Some("real_main")) } test("bug-finding") { - testFileToCpp("./benchmarks/wasm/branch-strip-buggy.wat", Some("real_main")) + testFileConcolicCpp("./benchmarks/wasm/branch-strip-buggy.wat", Some("real_main")) } test("brtable-bug-finding") { - testFileToCpp("./benchmarks/wasm/staged/brtable_concolic.wat") + testFileConcolicCpp("./benchmarks/wasm/staged/brtable_concolic.wat") + } + + test("return-poly - concrete") { + testFileConcreteCpp("./benchmarks/wasm/staged/return_poly.wat", Some("$real_main"), expect=Some(List(42))) + } + test("ack-cpp - concrete") { testFileConcreteCpp("./benchmarks/wasm/ack.wat", Some("real_main"), expect=Some(List(7))) } + test("power - concrete") { testFileConcreteCpp("./benchmarks/wasm/pow.wat", Some("real_main"), expect=Some(List(1024))) } + test("start - concrete") { testFileConcreteCpp("./benchmarks/wasm/start.wat") } + test("fact - concrete") { testFileConcreteCpp("./benchmarks/wasm/fact.wat", None, expect=Some(List(120))) } + // TODO: Waiting more symbolic operators' implementations + // test("loop - concrete") { testFileConcreteCpp("./benchmarks/wasm/loop.wat", None, expect=Some(List(10))) } + test("even-odd - concrete") { testFileConcreteCpp("./benchmarks/wasm/even_odd.wat", None, expect=Some(List(1))) } + // TODO: Waiting symbolic memory's implementations + // test("load - concrete") { testFileConcreteCpp("./benchmarks/wasm/load.wat", None, expect=Some(List(1))) } + // test("btree - concrete") { testFileConcreteCpp("./benchmarks/wasm/btree/2o1u-unlabeled.wat") } + test("fib - concrete") { testFileConcreteCpp("./benchmarks/wasm/fib.wat", None, expect=Some(List(144))) } + test("tribonacci - concrete") { testFileConcreteCpp("./benchmarks/wasm/tribonacci.wat", None, expect=Some(List(504))) } + + // test("return - concrete") { + // Since all of the thrown exceptions had been captured in concolic driver, this test is not valid anymore + // intercept[java.lang.RuntimeException] { + // testFileConcreteCpp("./benchmarks/wasm/return.wat", Some("$real_main")) + // } + // } + + test("return_call - concrete") { + testFileConcreteCpp("./benchmarks/wasm/sum.wat", Some("sum10"), expect=Some(List(55))) + } + + test("block input - concrete") { + testFileConcreteCpp("./benchmarks/wasm/block.wat", Some("real_main"), expect=Some(List(9))) + } + test("loop block input - concrete") { + testFileConcreteCpp("./benchmarks/wasm/block.wat", Some("test_loop_input"), expect=Some(List(55))) + } + test("if block input - concrete") { + testFileConcreteCpp("./benchmarks/wasm/block.wat", Some("test_if_input"), expect=Some(List(25))) } + test("block input - poly br - concrete") { + testFileConcreteCpp("./benchmarks/wasm/block.wat", Some("test_poly_br"), expect=Some(List(0))) + } + test("loop block - poly br - concrete") { + testFileConcreteCpp("./benchmarks/wasm/loop_poly.wat", None, expect=Some(List(2, 1))) + } + + test("brtable-cpp - concrete") { + testFileConcreteCpp("./benchmarks/wasm/staged/brtable.wat") + } + } From 731ff9e14a6060b5c972bba2418ebaa3836baa8f Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Wed, 27 Aug 2025 19:28:49 -0400 Subject: [PATCH 23/53] c++17 compatible --- headers/wasm/smt_solver.hpp | 2 +- headers/wasm/utils.hpp | 14 ++++++++++++++ src/main/scala/wasm/StagedConcolicMiniWasm.scala | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/headers/wasm/smt_solver.hpp b/headers/wasm/smt_solver.hpp index bc8cc9f9..504422f7 100644 --- a/headers/wasm/smt_solver.hpp +++ b/headers/wasm/smt_solver.hpp @@ -42,7 +42,7 @@ class Solver { z3::func_decl var = model[i]; z3::expr value = model.get_const_interp(var); std::string name = var.name().str(); - if (name.starts_with("s_")) { + if (starts_with(name, "s_")) { int id = std::stoi(name.substr(2)); if (id >= result.size()) { result.resize(id + 1); diff --git a/headers/wasm/utils.hpp b/headers/wasm/utils.hpp index ba57a1df..f814858d 100644 --- a/headers/wasm/utils.hpp +++ b/headers/wasm/utils.hpp @@ -36,4 +36,18 @@ #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 + #endif // UTILS_HPP \ No newline at end of file diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index 769a0b85..cfb2803a 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -1725,7 +1725,7 @@ object WasmToCppCompiler { import sys.process._ val includeFlags = generated.headerFolders.map(f => s"-I$f").mkString(" ") val macroFlags = macros.map(m => s"-D$m").mkString(" ") - val command = s"g++ -std=c++20 $outputCpp -o $outputExe -O3 -g -l z3 " + includeFlags + " " + macroFlags + val command = s"g++ -std=c++17 $outputCpp -o $outputExe -O3 -g -l z3 " + includeFlags + " " + macroFlags if (command.! != 0) { throw new RuntimeException(s"Compilation failed for $outputCpp") } From ffa5670dbc470ae84124f14bb7845ec7a7d3b1fa Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Thu, 28 Aug 2025 20:49:33 -0400 Subject: [PATCH 24/53] fix --- headers/wasm/symbolic_rt.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 94351f07..566b5db6 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -325,7 +325,7 @@ struct IfElseNode : Node { IfElseNode(SymVal cond, NodeBox *parent, Snapshot_t snapshot) : cond(cond), true_branch(std::make_unique(parent)), - false_branch(std::make_unique(parent)) {} + false_branch(std::make_unique(parent)), snapshot(snapshot) {} std::string to_string() override { std::string result = "IfElseNode {\n"; From b57929ad4fcda386a8e43a672d9dec44572867ff Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Fri, 29 Aug 2025 16:14:10 -0400 Subject: [PATCH 25/53] revert: don't split concrete/symbolic interpreter & don't support snapshot for now --- headers/wasm/symbolic_rt.hpp | 33 +- .../scala/wasm/StagedConcolicMiniWasm.scala | 511 +++++------------- 2 files changed, 156 insertions(+), 388 deletions(-) diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 566b5db6..6efa7cfe 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -269,7 +269,7 @@ struct NodeBox { std::unique_ptr node; NodeBox *parent; - std::monostate fillIfElseNode(SymVal cond, const Snapshot_t &snapshot); + std::monostate fillIfElseNode(SymVal cond); std::monostate fillFinishedNode(); std::monostate fillFailedNode(); std::monostate fillUnreachableNode(); @@ -321,11 +321,10 @@ struct IfElseNode : Node { SymVal cond; std::unique_ptr true_branch; std::unique_ptr false_branch; - Snapshot_t snapshot; - IfElseNode(SymVal cond, NodeBox *parent, Snapshot_t snapshot) + IfElseNode(SymVal cond, NodeBox *parent) : cond(cond), true_branch(std::make_unique(parent)), - false_branch(std::make_unique(parent)), snapshot(snapshot) {} + false_branch(std::make_unique(parent)) {} std::string to_string() override { std::string result = "IfElseNode {\n"; @@ -438,11 +437,10 @@ inline NodeBox::NodeBox(NodeBox *parent) /* TODO: avoid allocation of unexplored node */ parent(parent) {} -inline std::monostate NodeBox::fillIfElseNode(SymVal cond, - const Snapshot_t &snapshot) { +inline std::monostate NodeBox::fillIfElseNode(SymVal cond) { // fill the current NodeBox with an ifelse branch node when it's unexplored if (dynamic_cast(node.get())) { - node = std::make_unique(cond, this, snapshot); + node = std::make_unique(cond, this); } assert( dynamic_cast(node.get()) != nullptr && @@ -545,8 +543,8 @@ class ExploreTree_t { std::monostate fillFailedNode() { return cursor->fillFailedNode(); } - std::monostate fillIfElseNode(SymVal cond, const Snapshot_t &snapshot) { - return cursor->fillIfElseNode(cond, snapshot); + std::monostate fillIfElseNode(SymVal cond) { + return cursor->fillIfElseNode(cond); } std::monostate moveCursor(bool branch) { @@ -561,23 +559,6 @@ class ExploreTree_t { cursor = if_else_node->false_branch.get(); } - if (dynamic_cast(cursor->node.get())) { - // If we meet an unexplored node, resume the snapshot before and keep - // going - -#ifdef DEBUG - std::cout << "Resuming snapshot for unexplored node" << std::endl; -#endif - if (Reuse.is_reusing()) { - Reuse.turn_off_reusing(); - SymStack.reuse(if_else_node->snapshot); - } - } else if (dynamic_cast(cursor->node.get())) { - // if we are moving to a branch node, we must have reused the symbolic - // states - assert((!REUSE_MODE || Reuse.is_reusing()) && - "Moving to a branch node without reusing symbolic states"); - } return std::monostate(); } diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index cfb2803a..7372ede9 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -141,50 +141,11 @@ trait StagedWasmEvaluator extends SAIOps { ) } } - - } - - case class ContextTransition(startCtx: Context, history: List[Instr], endCtx: Context) { - def log(instr: Instr): ContextTransition = { - this.copy(history = instr :: history) - } - - def clearHistory: (Context, List[Instr], CleanCT) = { - (startCtx, history, CleanCT(endCtx)) - } - - def push(ty: ValueType): ContextTransition = { - this.copy(endCtx = endCtx.push(ty)) - } - - def peek: ValueType = { - endCtx.peek - } - - def pop(): (ValueType, ContextTransition) = { - val (ty, newCtx) = endCtx.pop() - (ty, this.copy(endCtx = newCtx)) - } - - def take(n: Int): ContextTransition = { - this.copy(endCtx = endCtx.take(n)) - } - - def shift(offset: Int, size: Int): ContextTransition = { - this.copy(endCtx = endCtx.shift(offset, size)) - } - } - - case class CleanCT(ctx: Context) - - // we can treat every CleanCT as a ContextTransition - implicit def toContextCT(ct: CleanCT): ContextTransition = { - ContextTransition(ct.ctx, Nil, ct.ctx) } type MCont[A] = Unit => A type Cont[A] = (MCont[A]) => A - type Trail[A] = List[CleanCT => Rep[Cont[A]]] + type Trail[A] = List[Context => Rep[Cont[A]]] // a cache storing the compiled code for each function, to reduce re-compilation val compileCache = new HashMap[Int, Rep[(MCont[Unit]) => Unit]] @@ -209,259 +170,243 @@ trait StagedWasmEvaluator extends SAIOps { "snapshot-make".reflectCtrlWith[Snapshot]() } - def isSymStateInUse: Rep[Boolean] = !ReuseManager.isReusing - def eval(insts: List[Instr], - kont: CleanCT => Rep[Cont[Unit]], + kont: Context => Rep[Cont[Unit]], mkont: Rep[MCont[Unit]], trail: Trail[Unit]) - (oldCT: ContextTransition): Rep[Unit] = { - if (insts.isEmpty) { - val (oldCtx, history, ct) = oldCT.clearHistory - if (isSymStateInUse) { - evalSym(history)(oldCtx) - } - return kont(ct)(mkont) - } + (implicit ctx: Context): Rep[Unit] = { + if (insts.isEmpty) return kont(ctx)(mkont) // Predef.println(s"[DEBUG] Evaluating instructions: ${insts.mkString(", ")}") // Predef.println(s"[DEBUG] Current context: $ctx") + val (inst, rest) = (insts.head, insts.tail) - val ct = oldCT.log(inst) inst match { case Drop => - val (ty, ct1) = ct.pop() + val (ty, newCtx) = ctx.pop() Stack.popC(ty) - eval(rest, kont, mkont, trail)(ct1) + Stack.popS(ty) + eval(rest, kont, mkont, trail)(newCtx) case WasmConst(num) => Stack.pushC(toStagedNum(num)) - val ct1 = ct.push(num.tipe(module)) - eval(rest, kont, mkont, trail)(ct1) + Stack.pushS(toStagedSymbolicNum(num)) + val newCtx = ctx.push(num.tipe(module)) + eval(rest, kont, mkont, trail)(newCtx) case Symbolic(ty) => - val id = Stack.popC(ty) + Stack.popC(ty) + val id = Stack.popS(ty) val symVal = id.makeSymbolic(ty) val num = SymEnv.read(symVal.s) Stack.pushC(ty.concreteTag(num)) - val ct1 = ct.pop()._2.push(ty) - eval(rest, kont, mkont, trail)(ct1) + Stack.pushS(symVal) + val newCtx = ctx.pop()._2.push(ty) + eval(rest, kont, mkont, trail)(newCtx) case LocalGet(i) => - Stack.pushC(Frames.getC(i)(ct.endCtx)) - val ct1 = ct.push(ct.endCtx.frameTypes(i)) - eval(rest, kont, mkont, trail)(ct1) + Stack.pushC(Frames.getC(i)) + Stack.pushS(Frames.getS(i)) + val newCtx = ctx.push(ctx.frameTypes(i)) + eval(rest, kont, mkont, trail)(newCtx) case LocalSet(i) => - val (ty, ct1) = ct.pop() + val (ty, newCtx) = ctx.pop() val num = Stack.popC(ty) + val sym = Stack.popS(ty) Frames.setC(i, num) - eval(rest, kont, mkont, trail)(ct1) + Frames.setS(i, sym) + eval(rest, kont, mkont, trail)(newCtx) case LocalTee(i) => - val ty = ct.peek + val ty = ctx.pop()._1 val num = Stack.peekC(ty) + val sym = Stack.peekS(ty) Frames.setC(i, num) - eval(rest, kont, mkont, trail)(ct) + Frames.setS(i, sym) + eval(rest, kont, mkont, trail)(ctx) case GlobalGet(i) => Stack.pushC(Globals.getC(i)) - val ct1 = ct.push(module.globals(i).ty.ty) - eval(rest, kont, mkont, trail)(ct1) + Stack.pushS(Globals.getS(i)) + val newCtx = ctx.push(module.globals(i).ty.ty) + eval(rest, kont, mkont, trail)(newCtx) case GlobalSet(i) => - val (ty, ct1) = ct.pop() + val (ty, newCtx) = ctx.pop() val num = Stack.popC(ty) + val sym = Stack.popS(ty) module.globals(i).ty match { case GlobalType(tipe, true) => { Globals.setC(i, num) + Globals.setS(i, sym) } case _ => throw new Exception("Cannot set immutable global") } - eval(rest, kont, mkont, trail)(ct1) + eval(rest, kont, mkont, trail)(newCtx) case Store(StoreOp(align, offset, ty, None)) => - val (ty1, ct1) = ct.pop() + val (ty1, newCtx1) = ctx.pop() val value = Stack.popC(ty1) - val (ty2, ct2) = ct1.pop() + val symValue = Stack.popS(ty1) + val (ty2, newCtx2) = newCtx1.pop() val addr = Stack.popC(ty2) + val symAddr = Stack.popS(ty2) Memory.storeInt(addr.toInt, offset, value.toInt) - eval(rest, kont, mkont, trail)(ct2) - case Nop => eval(rest, kont, mkont, trail)(ct) + eval(rest, kont, mkont, trail)(newCtx2) + case Nop => eval(rest, kont, mkont, trail) case Load(LoadOp(align, offset, ty, None, None)) => - val (ty1, ct1) = ct.pop() + val (ty1, newCtx1) = ctx.pop() val addr = Stack.popC(ty1) + Stack.popS(ty1) val num = Memory.loadIntC(addr.toInt, offset) + val sym = Memory.loadIntS(addr.toInt, offset) Stack.pushC(num) - val ct2 = ct1.push(ty) - eval(rest, kont, mkont, trail)(ct2) + Stack.pushS(sym) + val newCtx2 = newCtx1.push(ty) + eval(rest, kont, mkont, trail)(newCtx2) case MemorySize => ??? case MemoryGrow => - val (ty, ct1) = ct.pop() + val (ty, newCtx) = ctx.pop() val delta = Stack.popC(ty) + Stack.popS(ty) val ret = Memory.grow(delta.toInt) val retNum = Values.I32V(ret) // For now, we assume that the result of memory.grow only depends on the execution path, // we can relax this by turning it return to a symbol value and mimic the memory.grow's result as input. + val retSym = "Concrete".reflectCtrlWith[SymVal](retNum) Stack.pushC(I32C(retNum)) - val ct2 = ct1.push(NumType(I32Type)) - eval(rest, kont, mkont, trail)(ct2) + Stack.pushS(I32S(retSym)) + val newCtx2 = ctx.push(NumType(I32Type)) + eval(rest, kont, mkont, trail)(newCtx2) case MemoryFill => ??? case Unreachable => unreachable() case Test(op) => - val (ty, ct1) = ct.pop() + val (ty, newCtx1) = ctx.pop() val v = Stack.popC(ty) + val s = Stack.popS(ty) Stack.pushC(evalTestOpC(op, v)) - val ct2 = ct1.push(v.tipe) - eval(rest, kont, mkont, trail)(ct2) + Stack.pushS(evalTestOpS(op, s)) + val newCtx2 = newCtx1.push(v.tipe) + eval(rest, kont, mkont, trail)(newCtx2) case Unary(op) => - val (ty, ct1) = ct.pop() + val (ty, newCtx1) = ctx.pop() val v = Stack.popC(ty) + val s = Stack.popS(ty) val res = evalUnaryOpC(op, v) Stack.pushC(res) - val ct2 = ct1.push(res.tipe) - eval(rest, kont, mkont, trail)(ct2) + Stack.pushS(evalUnaryOpS(op, s)) + val newCtx2 = newCtx1.push(res.tipe) + eval(rest, kont, mkont, trail)(newCtx2) case Binary(op) => - val (ty2, ct1) = ct.pop() + val (ty2, newCtx1) = ctx.pop() val v2 = Stack.popC(ty2) - val (ty1, ct2) = ct1.pop() + val s2 = Stack.popS(ty2) + val (ty1, newCtx2) = newCtx1.pop() val v1 = Stack.popC(ty1) + val s1 = Stack.popS(ty1) val res = evalBinOpC(op, v1, v2) Stack.pushC(res) - val ct3 = ct2.push(res.tipe) - eval(rest, kont, mkont, trail)(ct3) + Stack.pushS(evalBinOpS(op, s1, s2)) + val newCtx3 = newCtx2.push(res.tipe) + eval(rest, kont, mkont, trail)(newCtx3) case Compare(op) => - val (ty2, ct1) = ct.pop() + val (ty2, newCtx1) = ctx.pop() val v2 = Stack.popC(ty2) - val (ty1, ct2) = ct1.pop() + val s2 = Stack.popS(ty2) + val (ty1, newCtx2) = newCtx1.pop() val v1 = Stack.popC(ty1) + val s1 = Stack.popS(ty1) val res = evalRelOpC(op, v1, v2) Stack.pushC(res) - val ct3 = ct2.push(res.tipe) - eval(rest, kont, mkont, trail)(ct3) + Stack.pushS(evalRelOpS(op, s1, s2)) + val newCtx3 = newCtx2.push(res.tipe) + eval(rest, kont, mkont, trail)(newCtx3) case WasmBlock(ty, inner) => // no need to modify the stack when entering a block // the type system guarantees that we will never take more than the input size from the stack val funcTy = ty.funcType - val exitSize = ct.endCtx.stackTypes.size - funcTy.inps.size + funcTy.out.size + val exitSize = ctx.stackTypes.size - funcTy.inps.size + funcTy.out.size val dummy = makeDummy - def restK(ct: CleanCT): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { + def restK(restCtx: Context): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { info(s"Exiting the block, stackSize =", Stack.size) - val offset = ct.endCtx.stackTypes.size - exitSize + val offset = restCtx.stackTypes.size - exitSize Stack.shiftC(offset, funcTy.out.size) - if (isSymStateInUse) { - Stack.shiftS(offset, funcTy.out.size) - } - val ct1 = ct.shift(offset, funcTy.out.size) - eval(rest, kont, mk, trail)(ct1) + Stack.shiftS(offset, funcTy.out.size) + val newRestCtx = restCtx.shift(offset, funcTy.out.size) + eval(rest, kont, mk, trail)(newRestCtx) }) - // TODO: extract this into a function - val (oldCtx, history, ct1) = ct.clearHistory - if (isSymStateInUse) { - evalSym(history)(oldCtx) - } - eval(inner, restK _, mkont, restK _ :: trail)(ct1) + eval(inner, restK _, mkont, restK _ :: trail) case Loop(ty, inner) => val funcTy = ty.funcType - val exitSize = ct.endCtx.stackTypes.size - funcTy.inps.size + funcTy.out.size + val exitSize = ctx.stackTypes.size - funcTy.inps.size + funcTy.out.size val dummy = makeDummy - def restK(ct: CleanCT): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { + def restK(restCtx: Context): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { info(s"Exiting the loop, stackSize =", Stack.size) - val offset = ct.endCtx.stackTypes.size - exitSize + val offset = restCtx.stackTypes.size - exitSize Stack.shiftC(offset, funcTy.out.size) - if (isSymStateInUse) { - Stack.shiftS(offset, funcTy.out.size) - } - val ct1 = ct.shift(offset, funcTy.out.size) - eval(rest, kont, mk, trail)(ct1) + Stack.shiftS(offset, funcTy.out.size) + val newRestCtx = restCtx.shift(offset, funcTy.out.size) + eval(rest, kont, mk, trail)(newRestCtx) }) - val enterSize = ct.endCtx.stackTypes.size - def loop(ct: CleanCT): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { + val enterSize = ctx.stackTypes.size + def loop(restCtx: Context): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { info(s"Entered the loop, stackSize =", Stack.size) - val offset = ct.endCtx.stackTypes.size - enterSize + val offset = restCtx.stackTypes.size - enterSize Stack.shiftC(offset, funcTy.inps.size) - if (isSymStateInUse) { - Stack.shiftS(offset, funcTy.inps.size) - } - val ct1 = ct.shift(offset, funcTy.inps.size) - eval(inner, restK _, mk, loop _ :: trail)(ct1) + Stack.shiftS(offset, funcTy.inps.size) + val newRestCtx = restCtx.shift(offset, funcTy.inps.size) + eval(inner, restK _, mk, loop _ :: trail)(newRestCtx) }) - val (oldCtx, history, ct1) = ct.clearHistory - if (isSymStateInUse) { - evalSym(history)(oldCtx) - } - loop(ct1)(mkont) + loop(ctx)(mkont) case If(ty, thn, els) => val funcTy = ty.funcType - val (condTy, ct1) = ct.pop() + val (condTy, newCtx) = ctx.pop() val cond = Stack.popC(condTy) - val exitSize = ct1.endCtx.stackTypes.size - funcTy.inps.size + funcTy.out.size - def restK(ct: CleanCT): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { + val symCond = Stack.popS(condTy) + val exitSize = newCtx.stackTypes.size - funcTy.inps.size + funcTy.out.size + def restK(restCtx: Context): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { info(s"Exiting the if, stackSize =", Stack.size) - val offset = ct.endCtx.stackTypes.size - exitSize + val offset = restCtx.stackTypes.size - exitSize Stack.shiftC(offset, funcTy.out.size) - if (isSymStateInUse) { - Stack.shiftS(offset, funcTy.out.size) - } - val ct1 = ct.shift(offset, funcTy.out.size) - eval(rest, kont, mk, trail)(ct1) + Stack.shiftS(offset, funcTy.out.size) + val newRestCtx = restCtx.shift(offset, funcTy.out.size) + eval(rest, kont, mk, trail)(newRestCtx) }) - val (oldCtx, history, ct2) = ct1.clearHistory - if (isSymStateInUse) { - // when we are not reusing - evalSym(history)(oldCtx) - val snapshot = makeSnapshot() - val symCond = Stack.popS(condTy) - ExploreTree.fillWithIfElse(symCond.s, snapshot) - } + // TODO: put the cond.s to path condition + ExploreTree.fillWithIfElse(symCond.s) if (cond.toInt != 0) { ExploreTree.moveCursor(true) - eval(thn, restK _, mkont, restK _ :: trail)(ct2) + eval(thn, restK _, mkont, restK _ :: trail)(newCtx) } else { ExploreTree.moveCursor(false) - eval(els, restK _, mkont, restK _ :: trail)(ct2) + eval(els, restK _, mkont, restK _ :: trail)(newCtx) } () case Br(label) => info(s"Jump to $label") - val (oldCtx, history, ct1) = ct.clearHistory - if (isSymStateInUse) { - evalSym(history)(oldCtx) - } - trail(label)(ct1)(mkont) + trail(label)(ctx)(mkont) case BrIf(label) => - val (ty, ct1) = ct.pop() + val (ty, newCtx) = ctx.pop() val cond = Stack.popC(ty) - val (oldCtx, history, ct2) = ct1.clearHistory + val symCond = Stack.popS(ty) info(s"The br_if(${label})'s condition is ", cond.toInt) - if (isSymStateInUse) { - evalSym(history)(oldCtx) - val symCond = Stack.popS(ty) - val snapshot = makeSnapshot() - ExploreTree.fillWithIfElse(symCond.s, snapshot) - } + ExploreTree.fillWithIfElse(symCond.s) if (cond.toInt != 0) { info(s"Jump to $label") ExploreTree.moveCursor(true) - trail(label)(ct2)(mkont) + trail(label)(newCtx)(mkont) } else { info(s"Continue") ExploreTree.moveCursor(false) - eval(rest, kont, mkont, trail)(ct2) + eval(rest, kont, mkont, trail)(newCtx) } () case BrTable(labels, default) => - val (ty, ct1) = ct.pop() + val (ty, newCtx) = ctx.pop() val label = Stack.popC(ty) - val (oldCtx, history, ct2) = ct1.clearHistory - if (isSymStateInUse) { - evalSym(history)(oldCtx) - } + val labelSym = Stack.popS(ty) def aux(choices: List[Int], idx: Int): Rep[Unit] = { - if (choices.isEmpty) trail(default)(ct2)(mkont) + if (choices.isEmpty) trail(default)(newCtx)(mkont) else { val cond = (label - toStagedNum(I32V(idx))).isZero() - if (isSymStateInUse) { - val labelSym = Stack.peekS(ty) - val condSym = (labelSym - toStagedSymbolicNum(I32V(idx))).isZero() - val snapshot = makeSnapshot() - ExploreTree.fillWithIfElse(condSym.s, snapshot) - } + val condSym = (labelSym - toStagedSymbolicNum(I32V(idx))).isZero() + ExploreTree.fillWithIfElse(condSym.s) if (cond.toInt != 0) { ExploreTree.moveCursor(true) - trail(choices.head)(ct2)(mkont) + trail(choices.head)(newCtx)(mkont) } else { ExploreTree.moveCursor(false) @@ -470,142 +415,12 @@ trait StagedWasmEvaluator extends SAIOps { } } aux(labels, 0) - if (isSymStateInUse) { - Stack.popS(ty) - } - () - case Return => - // return instruction is also stack-polymorphic - val (oldCtx, history, ct2) = ct.clearHistory - if (isSymStateInUse) { - evalSym(history)(oldCtx) - } - trail.last(ct2)(mkont) - case Call(f) => evalCall(rest, kont, mkont, trail, f, false)(ct) - case ReturnCall(f) => evalCall(rest, kont, mkont, trail, f, true)(ct) + case Return => trail.last(ctx)(mkont) + case Call(f) => evalCall(rest, kont, mkont, trail, f, false) + case ReturnCall(f) => evalCall(rest, kont, mkont, trail, f, true) case _ => val todo = "todo-op".reflectCtrlWith[Unit]() - eval(rest, kont, mkont, trail)(ct) - } - } - - def replayAndClearHistory(ct: ContextTransition): ContextTransition = { - val (oldCtx, history, ct1) = ct.clearHistory - if (isSymStateInUse) { - evalSym(history)(oldCtx) - } - ct1 - } - - // call the symbolic interpreter to evaluate the history that just executed by - // concrete interpreter - def evalSym(history: List[Instr]) - (ctx: Context): Rep[Unit] = { - // val func = topFun((_: Rep[Unit]) => evalS(history.reverse)) - // func(()) - evalS(history.reverse)(ctx) - } - - def evalS(insts: List[Instr]) - (ctx: Context): Rep[Unit] = { - if (insts.isEmpty) return () - - // Predef.println(s"[DEBUG] Evaluating instructions: ${insts.mkString(", ")}") - // Predef.println(s"[DEBUG] Current context: $ctx") - val (inst, rest) = (insts.head, insts.tail) - inst match { - case Drop => - val (ty, newCtx) = ctx.pop() - Stack.popS(ty) - evalS(rest)(newCtx) - case WasmConst(num) => - Stack.pushS(toStagedSymbolicNum(num)) - val newCtx = ctx.push(num.tipe(module)) - evalS(rest)(newCtx) - case Symbolic(ty) => - val id = Stack.popS(ty) - val symVal = id.makeSymbolic(ty) - Stack.pushS(symVal) - val newCtx = ctx.pop()._2.push(ty) - evalS(rest)(newCtx) - case LocalGet(i) => - Stack.pushS(Frames.getS(i)(ctx)) - val newCtx = ctx.push(ctx.frameTypes(i)) - evalS(rest)(newCtx) - case LocalSet(i) => - val (ty, newCtx) = ctx.pop() - val sym = Stack.popS(ty) - Frames.setS(i, sym) - evalS(rest)(newCtx) - case LocalTee(i) => - val ty = ctx.pop()._1 - val sym = Stack.peekS(ty) - Frames.setS(i, sym) - evalS(rest)(ctx) - case GlobalGet(i) => - Stack.pushS(Globals.getS(i)) - val newCtx = ctx.push(module.globals(i).ty.ty) - evalS(rest)(newCtx) - case GlobalSet(i) => - val (ty, newCtx) = ctx.pop() - val sym = Stack.popS(ty) - module.globals(i).ty match { - case GlobalType(tipe, true) => { - Globals.setS(i, sym) - } - case _ => throw new Exception("Cannot set immutable global") - } - evalS(rest)(newCtx) - case Nop => evalS(rest)(ctx) - case Store(StoreOp(align, offset, ty, None)) => ??? - case Load(LoadOp(align, offset, ty, None, None)) => ??? - case MemorySize => ??? - case MemoryGrow => ??? - case MemoryFill => ??? - case Unreachable => unreachable() - case Test(op) => - val (ty, newCtx1) = ctx.pop() - val s = Stack.popS(ty) - Stack.pushS(evalTestOpS(op, s)) - val newCtx2 = newCtx1.push(s.tipe) - evalS(rest)(newCtx2) - case Unary(op) => - val (ty, newCtx1) = ctx.pop() - val s = Stack.popS(ty) - val res = evalUnaryOpS(op, s) - Stack.pushS(res) - val newCtx2 = newCtx1.push(res.tipe) - evalS(rest)(newCtx2) - case Binary(op) => - val (ty2, newCtx1) = ctx.pop() - val s2 = Stack.popS(ty2) - val (ty1, newCtx2) = newCtx1.pop() - val s1 = Stack.popS(ty1) - val res = evalBinOpS(op, s1, s2) - Stack.pushS(res) - val newCtx3 = newCtx2.push(res.tipe) - evalS(rest)(newCtx3) - case Compare(op) => - val (ty2, newCtx1) = ctx.pop() - val s2 = Stack.popS(ty2) - val (ty1, newCtx2) = newCtx1.pop() - val s1 = Stack.popS(ty1) - val res = evalRelOpS(op, s1, s2) - Stack.pushS(res) - val newCtx3 = newCtx2.push(res.tipe) - evalS(rest)(newCtx3) - case WasmBlock(ty, inner) => () - case Loop(ty, inner) => () - case If(ty, thn, els) => () - case Br(label) => () - case BrIf(label) => () - case BrTable(labels, default) => () - case Return => () - case Call(f) => () - case ReturnCall(f) => () - case _ => - val todo = "todo-op".reflectCtrlWith[Unit]() - evalS(rest)(ctx) + eval(rest, kont, mkont, trail) } } @@ -613,16 +428,12 @@ trait StagedWasmEvaluator extends SAIOps { def evalCall(rest: List[Instr], - kont: CleanCT => Rep[Cont[Unit]], + kont: Context => Rep[Cont[Unit]], mkont: Rep[MCont[Unit]], trail: Trail[Unit], funcIndex: Int, isTail: Boolean) - (implicit ct: ContextTransition): Rep[Unit] = { - val (oldCtx, history, ct1) = ct.clearHistory - if (isSymStateInUse) { - evalSym(history)(oldCtx) - } + (implicit ctx: Context): Rep[Unit] = { module.funcs(funcIndex) match { case FuncDef(_, FuncBodyDef(ty, _, bodyLocals, body)) => val locals = bodyLocals ++ ty.inps @@ -633,33 +444,30 @@ trait StagedWasmEvaluator extends SAIOps { val callee = topFun((mk: Rep[MCont[Unit]]) => { info(s"Entered the function at $funcIndex, stackSize =", Stack.size) // the return instruction is also stack polymorphic - def retK(ct: CleanCT): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { + def retK(ctx: Context): Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { info(s"Exiting the function at $funcIndex, stackSize =", Stack.size) - val offset = ct.ctx.stackTypes.size - ty.out.size + val offset = ctx.stackTypes.size - ty.out.size Stack.shiftC(offset, ty.out.size) Stack.shiftS(offset, ty.out.size) mk(()) }) - eval(body, retK _, mk, retK _::Nil)(CleanCT(Context(Nil, locals))) + eval(body, retK _, mk, retK _::Nil)(Context(Nil, locals)) }) compileCache(funcIndex) = callee callee } // Predef.println(s"[DEBUG] locals size: ${locals.size}") - val ct2 = ct1.take(ty.inps.size) - val exitSize = ty.out.size + ct2.endCtx.stackTypes.size + val newCtx = ctx.take(ty.inps.size) + val argsC = Stack.takeC(ty.inps) + val argsS = Stack.takeS(ty.inps) if (isTail) { // when tail call, return to the caller's return continuation - val argsC = Stack.takeC(ty.inps) - Frames.popFrameC(ct2.endCtx.frameTypes.size) + Frames.popFrameC(ctx.frameTypes.size) + Frames.popFrameS(ctx.frameTypes.size) Frames.pushFrameC(locals) + Frames.pushFrameS(locals) Frames.putAllC(argsC) - if (isSymStateInUse) { - val argsS = Stack.takeS(ty.inps) - Frames.popFrameS(ct2.endCtx.frameTypes.size) - Frames.pushFrameS(locals) - Frames.putAllS(argsS) - } + Frames.putAllS(argsS) callee(mkont) } else { // We make a new trail by `restK`, since function creates a new block to escape @@ -668,38 +476,33 @@ trait StagedWasmEvaluator extends SAIOps { info(s"Exiting the function at $funcIndex, stackSize =", Stack.size) Frames.popFrameC(locals.size) Frames.popFrameS(locals.size) - val newCtx = ct2.endCtx.copy(stackTypes = ty.out.reverse ++ ct2.endCtx.stackTypes) - eval(rest, kont, mk, trail)(CleanCT(newCtx)) + eval(rest, kont, mk, trail)(newCtx.copy(stackTypes = ty.out.reverse ++ ctx.stackTypes.drop(ty.inps.size))) }) val dummy = makeDummy val newMKont: Rep[MCont[Unit]] = funHere((_u: Rep[Unit]) => { restK(mkont) }, dummy) - val argsC = Stack.takeC(ty.inps) Frames.pushFrameC(locals) + Frames.pushFrameS(locals) Frames.putAllC(argsC) - if (isSymStateInUse) { - val argsS = Stack.takeS(ty.inps) - Frames.pushFrameS(locals) - Frames.putAllS(argsS) - } + Frames.putAllS(argsS) callee(newMKont) } case Import("console", "log", _) | Import("spectest", "print_i32", _) => //println(s"[DEBUG] current stack: $stack") - val (ty, ct2) = ct1.pop() + val (ty, newCtx) = ctx.pop() val v = Stack.popC(ty) Stack.popS(ty) println(v.toInt) - eval(rest, kont, mkont, trail)(ct2) + eval(rest, kont, mkont, trail)(newCtx) case Import("console", "assert", _) => - val (ty, ct2) = ct1.pop() + val (ty, newCtx) = ctx.pop() val v = Stack.popC(ty) // TODO: We should also add s into exploration tree val s = Stack.popS(ty) runtimeAssert(v.toInt != 0) - eval(rest, kont, mkont, trail)(ct2) + eval(rest, kont, mkont, trail)(newCtx) case Import(_, _, _) => throw new Exception(s"Unknown import at $funcIndex") case _ => throw new Exception(s"Definition at $funcIndex is not callable") } @@ -811,7 +614,7 @@ trait StagedWasmEvaluator extends SAIOps { resetStacks() Frames.pushFrameC(locals) Frames.pushFrameS(locals) - eval(instrs, _ => forwardKont, mkont, ((_: CleanCT) => forwardKont)::Nil)(CleanCT(Context(Nil, locals))) + eval(instrs, (_: Context) => forwardKont, mkont, ((_: Context) => forwardKont)::Nil)(Context(Nil, locals)) Frames.popFrameC(locals.size) Frames.popFrameS(locals.size) } @@ -1074,8 +877,8 @@ trait StagedWasmEvaluator extends SAIOps { // Exploration tree, object ExploreTree { - def fillWithIfElse(sym: Rep[SymVal], snapshot: Rep[Snapshot]): Rep[Unit] = { - "tree-fill-if-else".reflectCtrlWith[Unit](sym, snapshot) + def fillWithIfElse(s: Rep[SymVal]): Rep[Unit] = { + "tree-fill-if-else".reflectCtrlWith[Unit](s) } def fillWithFinished(): Rep[Unit] = { @@ -1102,20 +905,6 @@ trait StagedWasmEvaluator extends SAIOps { } } - object ReuseManager { - def isReusing: Rep[Boolean] = { - "reuse-is-reusing".reflectCtrlWith[Boolean]() - } - - def turnOnReuse(): Rep[Unit] = { - "reuse-turn-on".reflectCtrlWith[Unit]() - } - - def turnOffReuse(): Rep[Unit] = { - "reuse-turn-off".reflectCtrlWith[Unit]() - } - } - // runtime Num type implicit class StagedConcreteNumOps(num: StagedConcreteNum) { @@ -1624,8 +1413,8 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { emit("SymEnv.read("); shallow(sym); emit(")") case Node(_, "assert-true", List(cond), _) => emit("GENSYM_ASSERT("); shallow(cond); emit(")") - case Node(_, "tree-fill-if-else", List(sym, snapshot), _) => - emit("ExploreTree.fillIfElseNode("); shallow(sym); emit(", "); shallow(snapshot); emit(")") + case Node(_, "tree-fill-if-else", List(sym), _) => + emit("ExploreTree.fillIfElseNode("); shallow(sym); emit(")") case Node(_, "tree-fill-finished", List(), _) => emit("ExploreTree.fillFinishedNode()") case Node(_, "tree-move-cursor", List(b), _) => @@ -1636,8 +1425,6 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { emit("ExploreTree.dump_graphviz("); shallow(f); emit(")") case Node(_, "sym-not", List(s), _) => shallow(s); emit(".negate()") - case Node(_, "reuse-is-reusing", List(), _) => - emit("Reuse.is_reusing()") case Node(_, "dummy", _, _) => emit("std::monostate()") case Node(_, "dummy-op", _, _) => emit("std::monostate()") case Node(_, "no-op", _, _) => From 1bdb7da1a3d78e6e2e93be8f880f4f2ba0326d0d Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Fri, 29 Aug 2025 21:16:43 -0400 Subject: [PATCH 26/53] introduce a SnapshotNode, which currently behaves same as UnexploredNode --- headers/wasm/concolic_driver.hpp | 2 +- headers/wasm/symbolic_rt.hpp | 43 ++++++++++++++++--- .../scala/wasm/StagedConcolicMiniWasm.scala | 3 ++ 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/headers/wasm/concolic_driver.hpp b/headers/wasm/concolic_driver.hpp index 427a0de8..ab14525e 100644 --- a/headers/wasm/concolic_driver.hpp +++ b/headers/wasm/concolic_driver.hpp @@ -38,8 +38,8 @@ class ManagedConcolicCleanup { }; inline void ConcolicDriver::run() { - ManagedConcolicCleanup cleanup{*this}; while (true) { + ManagedConcolicCleanup cleanup{*this}; ExploreTree.reset_cursor(); auto unexplored = ExploreTree.pick_unexplored(); diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 6efa7cfe..7931a3c3 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -273,7 +273,8 @@ struct NodeBox { std::monostate fillFinishedNode(); std::monostate fillFailedNode(); std::monostate fillUnreachableNode(); - + std::monostate fillSnapshotNode(); + bool isUnexplored() const; std::vector collect_path_conds(); }; @@ -384,6 +385,22 @@ struct UnExploredNode : Node { } }; +struct SnapshotNode : Node { + SnapshotNode() {} + std::string to_string() override { return "SnapshotNode"; } + +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); + } + } +}; + struct Finished : Node { Finished() {} std::string to_string() override { return "FinishedNode"; } @@ -439,7 +456,7 @@ inline NodeBox::NodeBox(NodeBox *parent) inline std::monostate NodeBox::fillIfElseNode(SymVal cond) { // fill the current NodeBox with an ifelse branch node when it's unexplored - if (dynamic_cast(node.get())) { + if (this->isUnexplored()) { node = std::make_unique(cond, this); } assert( @@ -448,8 +465,15 @@ inline std::monostate NodeBox::fillIfElseNode(SymVal cond) { return std::monostate(); } +inline std::monostate NodeBox::fillSnapshotNode() { + if (this->isUnexplored()) { + node = std::make_unique(); + } + return std::monostate(); +} + inline std::monostate NodeBox::fillFinishedNode() { - if (dynamic_cast(node.get())) { + if (this->isUnexplored()) { node = std::make_unique(); } else { assert(dynamic_cast(node.get()) != nullptr); @@ -458,7 +482,7 @@ inline std::monostate NodeBox::fillFinishedNode() { } inline std::monostate NodeBox::fillFailedNode() { - if (dynamic_cast(node.get())) { + if (this->isUnexplored()) { node = std::make_unique(); } else { assert(dynamic_cast(node.get()) != nullptr); @@ -467,7 +491,7 @@ inline std::monostate NodeBox::fillFailedNode() { } inline std::monostate NodeBox::fillUnreachableNode() { - if (dynamic_cast(node.get())) { + if (this->isUnexplored()) { node = std::make_unique(); } else { assert(dynamic_cast(node.get()) != nullptr); @@ -475,6 +499,11 @@ inline std::monostate NodeBox::fillUnreachableNode() { return std::monostate(); } +inline bool NodeBox::isUnexplored() const { + return dynamic_cast(node.get()) != nullptr || + dynamic_cast(node.get()) != nullptr; +} + inline std::vector NodeBox::collect_path_conds() { auto box = this; auto result = std::vector(); @@ -554,8 +583,10 @@ class ExploreTree_t { if_else_node != nullptr && "Can't move cursor when the branch node is not initialized correctly!"); if (branch) { + if_else_node->false_branch->fillSnapshotNode(); cursor = if_else_node->true_branch.get(); } else { + if_else_node->true_branch->fillSnapshotNode(); cursor = if_else_node->false_branch.get(); } @@ -599,7 +630,7 @@ class ExploreTree_t { private: NodeBox *pick_unexplored_of(NodeBox *node) { - if (dynamic_cast(node->node.get()) != nullptr) { + if (node->isUnexplored()) { return node; } auto if_else_node = dynamic_cast(node->node.get()); diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index 7372ede9..d488e7d1 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -404,6 +404,9 @@ trait StagedWasmEvaluator extends SAIOps { val cond = (label - toStagedNum(I32V(idx))).isZero() val condSym = (labelSym - toStagedSymbolicNum(I32V(idx))).isZero() ExploreTree.fillWithIfElse(condSym.s) + // When moving the cursor to a branch, we mark another branch as + // snapshotNode (this is done by moveCursor's runtime implementation) + // TODO: store snapshot into this snapshot node if (cond.toInt != 0) { ExploreTree.moveCursor(true) trail(choices.head)(newCtx)(mkont) From 64dce3263ad69956cd9636bab4f6787428e775ed Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Sat, 30 Aug 2025 14:11:56 -0400 Subject: [PATCH 27/53] fill snapshot into SnapshotNode --- headers/wasm/symbolic_rt.hpp | 27 +++--- .../scala/wasm/StagedConcolicMiniWasm.scala | 88 +++++++++++++------ 2 files changed, 76 insertions(+), 39 deletions(-) diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 7931a3c3..e96adad9 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -228,7 +228,7 @@ class SymFrames_t { // A snapshot of the symbolic state and execution context (control) class Snapshot_t { public: - explicit Snapshot_t(); + explicit Snapshot_t(Cont_t cont, MCont_t mcont); SymStack_t get_stack() const { return stack; } SymFrames_t get_frames() const { return frames; } @@ -236,6 +236,9 @@ class Snapshot_t { private: SymStack_t stack; SymFrames_t frames; + // The continuation at the snapshot point + Cont_t cont; + MCont_t mcont; }; inline void SymStack_t::reuse(Snapshot_t snapshot) { @@ -273,7 +276,7 @@ struct NodeBox { std::monostate fillFinishedNode(); std::monostate fillFailedNode(); std::monostate fillUnreachableNode(); - std::monostate fillSnapshotNode(); + std::monostate fillSnapshotNode(Snapshot_t snapshot); bool isUnexplored() const; std::vector collect_path_conds(); }; @@ -386,7 +389,7 @@ struct UnExploredNode : Node { }; struct SnapshotNode : Node { - SnapshotNode() {} + SnapshotNode(Snapshot_t snapshot) : snapshot(snapshot) {} std::string to_string() override { return "SnapshotNode"; } protected: @@ -399,6 +402,9 @@ struct SnapshotNode : Node { graphviz_edge(os, parent_dot_id, current_node_dot_id, edge_label); } } + +private: + Snapshot_t snapshot; }; struct Finished : Node { @@ -465,9 +471,9 @@ inline std::monostate NodeBox::fillIfElseNode(SymVal cond) { return std::monostate(); } -inline std::monostate NodeBox::fillSnapshotNode() { +inline std::monostate NodeBox::fillSnapshotNode(Snapshot_t snapshot) { if (this->isUnexplored()) { - node = std::make_unique(); + node = std::make_unique(snapshot); } return std::monostate(); } @@ -545,12 +551,11 @@ class Reuse_t { static Reuse_t Reuse; -inline Snapshot_t::Snapshot_t() : stack(SymStack), frames(SymFrames) { +inline Snapshot_t::Snapshot_t(Cont_t cont, MCont_t mcont) + : stack(SymStack), frames(SymFrames), cont(cont), mcont(mcont) { #ifdef DEBUG std::cout << "Creating snapshot of size " << stack.size() << std::endl; #endif - assert(!Reuse.is_reusing() && - "Creating snapshot while reusing the symbolic stack"); } class ExploreTree_t { @@ -576,17 +581,17 @@ class ExploreTree_t { return cursor->fillIfElseNode(cond); } - std::monostate moveCursor(bool branch) { + std::monostate moveCursor(bool branch, Snapshot_t snapshot) { 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) { - if_else_node->false_branch->fillSnapshotNode(); + if_else_node->false_branch->fillSnapshotNode(snapshot); cursor = if_else_node->true_branch.get(); } else { - if_else_node->true_branch->fillSnapshotNode(); + if_else_node->true_branch->fillSnapshotNode(snapshot); cursor = if_else_node->false_branch.get(); } diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index d488e7d1..9b34bfba 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -165,9 +165,9 @@ trait StagedWasmEvaluator extends SAIOps { trait Snapshot // Create a snapshot of the symbolic execution, we should ensure that current symstack is in use - // We don't need to store the control information, since the control is totally decided by concrete states - def makeSnapshot(): Rep[Snapshot] = { - "snapshot-make".reflectCtrlWith[Snapshot]() + // We need to store the control information, so we can resume the execution later + def makeSnapshot(kont: Rep[Cont[Unit]], mkont: Rep[MCont[Unit]]): Rep[Snapshot] = { + "snapshot-make".reflectCtrlWith[Snapshot](kont, mkont) } def eval(insts: List[Instr], @@ -365,14 +365,23 @@ trait StagedWasmEvaluator extends SAIOps { val newRestCtx = restCtx.shift(offset, funcTy.out.size) eval(rest, kont, mk, trail)(newRestCtx) }) - // TODO: put the cond.s to path condition ExploreTree.fillWithIfElse(symCond.s) + def thnK: Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { + info("Entering the true branch of the if") + eval(thn, restK _, mk, restK _ :: trail)(newCtx) + }) + def elsK: Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { + info("Entering the false branch of the if") + eval(els, restK _, mk, restK _ :: trail)(newCtx) + }) if (cond.toInt != 0) { - ExploreTree.moveCursor(true) - eval(thn, restK _, mkont, restK _ :: trail)(newCtx) + val snapshot = makeSnapshot(elsK, mkont) + ExploreTree.moveCursor(true, snapshot) + thnK(mkont) } else { - ExploreTree.moveCursor(false) - eval(els, restK _, mkont, restK _ :: trail)(newCtx) + val snapshot = makeSnapshot(thnK, mkont) + ExploreTree.moveCursor(false, snapshot) + elsK(mkont) } () case Br(label) => @@ -384,40 +393,63 @@ trait StagedWasmEvaluator extends SAIOps { val symCond = Stack.popS(ty) info(s"The br_if(${label})'s condition is ", cond.toInt) ExploreTree.fillWithIfElse(symCond.s) + def thnK: Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { + trail(label)(newCtx)(mk) + }) + def elsK: Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { + eval(rest, kont, mk, trail)(newCtx) + }) if (cond.toInt != 0) { info(s"Jump to $label") - ExploreTree.moveCursor(true) - trail(label)(newCtx)(mkont) + val snapshot = makeSnapshot(elsK, mkont) + ExploreTree.moveCursor(true, snapshot) + thnK(mkont) } else { info(s"Continue") - ExploreTree.moveCursor(false) - eval(rest, kont, mkont, trail)(newCtx) + val snapshot = makeSnapshot(thnK, mkont) + ExploreTree.moveCursor(false, snapshot) + elsK(mkont) } () case BrTable(labels, default) => val (ty, newCtx) = ctx.pop() - val label = Stack.popC(ty) - val labelSym = Stack.popS(ty) - def aux(choices: List[Int], idx: Int): Rep[Unit] = { - if (choices.isEmpty) trail(default)(newCtx)(mkont) - else { + def aux(choices: List[Int], idx: Int, mkont: Rep[MCont[Unit]]): Rep[Unit] = { + if (choices.isEmpty) { + Stack.popC(ty) + Stack.popS(ty) + trail(default)(newCtx)(mkont) + } else { + val label = Stack.peekC(ty) + val labelSym = Stack.peekS(ty) val cond = (label - toStagedNum(I32V(idx))).isZero() val condSym = (labelSym - toStagedSymbolicNum(I32V(idx))).isZero() ExploreTree.fillWithIfElse(condSym.s) // When moving the cursor to a branch, we mark another branch as // snapshotNode (this is done by moveCursor's runtime implementation) // TODO: store snapshot into this snapshot node + def thnK: Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { + info("Entering the true branch of the br_table") + Stack.popC(ty) + Stack.popS(ty) + trail(choices.head)(newCtx)(mk) + }) + def elsK: Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { + info("Entering the false branch of the br_table") + aux(choices.tail, idx + 1, mk) + }) if (cond.toInt != 0) { - ExploreTree.moveCursor(true) - trail(choices.head)(newCtx)(mkont) + val snapshot = makeSnapshot(elsK, mkont) + ExploreTree.moveCursor(true, snapshot) + thnK(mkont) } else { - ExploreTree.moveCursor(false) - aux(choices.tail, idx + 1) + val snapshot = makeSnapshot(thnK, mkont) + ExploreTree.moveCursor(false, snapshot) + elsK(mkont) } } } - aux(labels, 0) + aux(labels, 0, mkont) case Return => trail.last(ctx)(mkont) case Call(f) => evalCall(rest, kont, mkont, trail, f, false) case ReturnCall(f) => evalCall(rest, kont, mkont, trail, f, true) @@ -888,9 +920,9 @@ trait StagedWasmEvaluator extends SAIOps { "tree-fill-finished".reflectCtrlWith[Unit]() } - def moveCursor(branch: Boolean): Rep[Unit] = { + def moveCursor(branch: Boolean, snapshot: Rep[Snapshot]): Rep[Unit] = { // when moving cursor from to an unexplored node, we need to change the reuse state - "tree-move-cursor".reflectCtrlWith[Unit](branch) + "tree-move-cursor".reflectCtrlWith[Unit](branch, snapshot) } def print(): Rep[Unit] = { @@ -1323,8 +1355,8 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { emit("Stack.pop()") case Node(_, "sym-stack-pop", _, _) => emit("SymStack.pop()") - case Node(_, "snapshot-make", _, _) => - emit("Snapshot_t()") + case Node(_, "snapshot-make", List(k, mk), _) => + emit("Snapshot_t("); shallow(k); emit(", "); shallow(mk); emit(")") case Node(_, "frame-pop", List(i), _) => emit("Frames.popFrame("); shallow(i); emit(")") case Node(_, "sym-frame-pop", List(i), _) => @@ -1420,8 +1452,8 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { emit("ExploreTree.fillIfElseNode("); shallow(sym); emit(")") case Node(_, "tree-fill-finished", List(), _) => emit("ExploreTree.fillFinishedNode()") - case Node(_, "tree-move-cursor", List(b), _) => - emit("ExploreTree.moveCursor("); shallow(b); emit(")") + case Node(_, "tree-move-cursor", List(b, snapshot), _) => + emit("ExploreTree.moveCursor("); shallow(b); emit(", "); shallow(snapshot); emit(")") case Node(_, "tree-print", List(), _) => emit("ExploreTree.print()") case Node(_, "tree-dump-graphviz", List(f), _) => From 463871cf3c5ce2f30cb64fa3856f92f263c99a56 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Sun, 31 Aug 2025 16:36:53 -0400 Subject: [PATCH 28/53] snapshot reuse via continuation more tests should be added --- headers/wasm/concolic_driver.hpp | 12 +- headers/wasm/concrete_rt.hpp | 24 +++ headers/wasm/controls.hpp | 9 +- headers/wasm/symbolic_rt.hpp | 157 ++++++++++++++---- .../genwasym/TestStagedConcolicEval.scala | 6 +- 5 files changed, 172 insertions(+), 36 deletions(-) diff --git a/headers/wasm/concolic_driver.hpp b/headers/wasm/concolic_driver.hpp index ab14525e..1c2fcc2e 100644 --- a/headers/wasm/concolic_driver.hpp +++ b/headers/wasm/concolic_driver.hpp @@ -38,9 +38,9 @@ class ManagedConcolicCleanup { }; inline void ConcolicDriver::run() { + ExploreTree.reset_cursor(); while (true) { ManagedConcolicCleanup cleanup{*this}; - ExploreTree.reset_cursor(); auto unexplored = ExploreTree.pick_unexplored(); if (!unexplored) { @@ -55,11 +55,19 @@ inline void ConcolicDriver::run() { continue; } auto new_env = result.value(); + + // update global symbolic environment from SMT solved model SymEnv.update(std::move(new_env)); try { GENSYM_INFO("Now execute the program with symbolic environment: "); GENSYM_INFO(SymEnv.to_string()); - entrypoint(); + if (auto snapshot_node = + dynamic_cast(unexplored->node.get())) { + snapshot_node->get_snapshot().resume_execution(SymEnv, unexplored); + } else { + entrypoint(); + } + GENSYM_INFO("Execution finished successfully with symbolic environment:"); GENSYM_INFO(SymEnv.to_string()); } catch (...) { diff --git a/headers/wasm/concrete_rt.hpp b/headers/wasm/concrete_rt.hpp index a9abccf2..16642067 100644 --- a/headers/wasm/concrete_rt.hpp +++ b/headers/wasm/concrete_rt.hpp @@ -1,6 +1,7 @@ #ifndef WASM_CONCRETE_RT_HPP #define WASM_CONCRETE_RT_HPP +#include "wasm/utils.hpp" #include #include #include @@ -120,6 +121,16 @@ class Stack_t { 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; @@ -152,6 +163,19 @@ class Frames_t { void reset() { count = 0; } + size_t size() const { return count; } + + void set_from_front(int32_t index, const Num &num) { + GENSYM_DBG(index); + 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: int32_t count; Num *stack_ptr; diff --git a/headers/wasm/controls.hpp b/headers/wasm/controls.hpp index 16fa5136..513f0692 100644 --- a/headers/wasm/controls.hpp +++ b/headers/wasm/controls.hpp @@ -1,5 +1,12 @@ + +#ifndef WASM_CONTROLS_HPP +#define WASM_CONTROLS_HPP + #include + #include using MCont_t = std::function; -using Cont_t = std::function; \ No newline at end of file +using Cont_t = std::function; + +#endif // WASM_CONTROLS_HPP \ No newline at end of file diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index e96adad9..d18ea609 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -3,6 +3,7 @@ #include "concrete_rt.hpp" #include "controls.hpp" +#include "utils.hpp" #include #include #include @@ -71,6 +72,8 @@ struct SymVal { SymVal gt(const SymVal &other) const; SymVal geq(const SymVal &other) const; SymVal negate() const; + + bool is_concrete() const; }; static SymVal make_symbolic(int index) { @@ -145,6 +148,10 @@ inline SymVal SymVal::makeSymbolic() const { } } +inline bool SymVal::is_concrete() const { + return dynamic_cast(symptr.get()) != nullptr; +} + class Snapshot_t; class SymStack_t { @@ -186,6 +193,8 @@ class SymStack_t { size_t size() const { return stack.size(); } + SymVal operator[](size_t index) const { return stack[index]; } + private: std::vector stack; }; @@ -222,9 +231,17 @@ class SymFrames_t { void reuse(Snapshot_t snapshot); + size_t size() const { return stack.size(); } + + SymVal operator[](size_t index) const { return stack[index]; } + +private: std::vector stack; }; +struct NodeBox; +struct SymEnv_t; + // A snapshot of the symbolic state and execution context (control) class Snapshot_t { public: @@ -233,6 +250,8 @@ class Snapshot_t { SymStack_t get_stack() const { return stack; } SymFrames_t get_frames() const { return frames; } + std::monostate resume_execution(SymEnv_t &sym_env, NodeBox *node) const; + private: SymStack_t stack; SymFrames_t frames; @@ -391,6 +410,7 @@ struct UnExploredNode : Node { struct SnapshotNode : Node { SnapshotNode(Snapshot_t snapshot) : snapshot(snapshot) {} std::string to_string() override { return "SnapshotNode"; } + const Snapshot_t &get_snapshot() const { return snapshot; } protected: void generate_dot(std::ostream &os, int parent_dot_id, @@ -479,6 +499,7 @@ inline std::monostate NodeBox::fillSnapshotNode(Snapshot_t snapshot) { } inline std::monostate NodeBox::fillFinishedNode() { + GENSYM_DBG("Filling with a Finished Node"); if (this->isUnexplored()) { node = std::make_unique(); } else { @@ -491,6 +512,11 @@ inline std::monostate NodeBox::fillFailedNode() { if (this->isUnexplored()) { node = std::make_unique(); } else { + if (auto if_else_node = dynamic_cast(node.get())) { + GENSYM_DBG(typeid(*if_else_node).name()); + } else if (auto finished_node = dynamic_cast(node.get())) { + GENSYM_DBG(typeid(*finished_node).name()); + } assert(dynamic_cast(node.get()) != nullptr); } return std::monostate(); @@ -533,24 +559,6 @@ inline std::vector NodeBox::collect_path_conds() { return result; } -class Reuse_t { -public: - Reuse_t() : reuse_flag(false) {} - bool is_reusing() { - // we are in reuse mode and the flag is set - return REUSE_MODE && reuse_flag; - } - - void turn_on_reusing() { reuse_flag = true; } - - void turn_off_reusing() { reuse_flag = false; } - -private: - bool reuse_flag; -}; - -static Reuse_t Reuse; - inline Snapshot_t::Snapshot_t(Cont_t cont, MCont_t mcont) : stack(SymStack), frames(SymFrames), cont(cont), mcont(mcont) { #ifdef DEBUG @@ -564,13 +572,15 @@ class 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(); - Reuse.turn_off_reusing(); - // if root cursor is a branch node, then we can reuse the snapshot inside it - if (auto ite = dynamic_cast(cursor->node.get())) { - Reuse.turn_on_reusing(); - } + } + + 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(); } @@ -656,18 +666,23 @@ static ExploreTree_t ExploreTree; class SymEnv_t { public: - Num read(SymVal sym) { - auto symbol = dynamic_cast(sym.symptr.get()); - assert(symbol); - if (symbol->get_id() >= map.size()) { - map.resize(symbol->get_id() + 1); + Num read(const Symbol &symbol) { + if (symbol.get_id() >= map.size()) { + map.resize(symbol.get_id() + 1); } #if DEBUG - std::cout << "Read symbol: " << symbol->get_id() + std::cout << "Read symbol: " << symbol.get_id() << " from symbolic environment" << std::endl; std::cout << "Current symbolic environment: " << to_string() << std::endl; #endif - return map[symbol->get_id()]; + + return map[symbol.get_id()]; + } + + Num read(SymVal sym) { + auto symbol = dynamic_cast(sym.symptr.get()); + assert(symbol); + return read(*symbol); } void update(std::vector new_env) { map = std::move(new_env); } @@ -684,10 +699,92 @@ class SymEnv_t { return result; } + size_t size() const { return map.size(); } + private: std::vector map; // The symbolic environment, a vector of Num }; static SymEnv_t SymEnv; +// TODO: reduce the re-computation of the same symbolic expression, it's better +// if it can be done by the smt solver +static Num eval_sym_expr(const SymVal &sym, SymEnv_t &sym_env) { + if (auto concrete = dynamic_cast(sym.symptr.get())) { + return concrete->value; + } else if (auto operation = dynamic_cast(sym.symptr.get())) { + // If it's a operation, we need to evaluate it + auto lhs = eval_sym_expr(operation->lhs, sym_env); + auto rhs = eval_sym_expr(operation->rhs, sym_env); + switch (operation->op) { + case ADD: + return lhs + rhs; + case SUB: + return lhs - rhs; + case MUL: + return lhs * rhs; + case DIV: + return lhs / rhs; + case LT: + return lhs < rhs; + case LEQ: + return lhs <= rhs; + case GT: + return lhs > rhs; + case GEQ: + return lhs >= rhs; + default: + throw std::runtime_error("Operation not supported: " + + std::to_string(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 sym_env.read(sym); + } + throw std::runtime_error("Not supported symbolic expression"); +} + +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 conc = eval_sym_expr(sym, sym_env); + stack.set_from_front(i, conc); + } +} + +static void resume_conc_frames(const SymFrames_t &sym_frame, Frames_t &frames, + SymEnv_t &sym_env) { + frames.resize(sym_frame.size()); + for (size_t i = 0; i < sym_frame.size(); ++i) { + auto sym = sym_frame[i]; + auto conc = eval_sym_expr(sym, sym_env); + frames.set_from_front(i, conc); + } +} + +static void resume_conc_states(const SymStack_t &sym_stack, + const SymFrames_t &sym_frame, Stack_t &stack, + Frames_t &frames, SymEnv_t &sym_env) { + resume_conc_stack(sym_stack, stack, sym_env); + resume_conc_frames(sym_frame, frames, sym_env); +} + +inline std::monostate Snapshot_t::resume_execution(SymEnv_t &sym_env, + NodeBox *node) const { + // Reset explore tree's cursor + ExploreTree.set_cursor(node); + + // Restore the symbolic state from the snapshot + GENSYM_INFO("Reusing symbolic state from snapshot"); + SymStack = stack; + SymFrames = frames; + // Restore the concrete states from the symbolic states + resume_conc_states(stack, frames, Stack, Frames, sym_env); + // Resume execution from the continuation + return cont(mcont); +} + #endif // WASM_SYMBOLIC_RT_HPP \ No newline at end of file diff --git a/src/test/scala/genwasym/TestStagedConcolicEval.scala b/src/test/scala/genwasym/TestStagedConcolicEval.scala index 48c24634..5a77f58d 100644 --- a/src/test/scala/genwasym/TestStagedConcolicEval.scala +++ b/src/test/scala/genwasym/TestStagedConcolicEval.scala @@ -42,13 +42,13 @@ class TestStagedConcolicEval extends FunSuite { }) } - test("ack-cpp") { testFileConcolicCpp("./benchmarks/wasm/ack.wat", Some("real_main")) } + test("ack-cpp-concolic") { testFileConcolicCpp("./benchmarks/wasm/ack.wat", Some("real_main")) } - test("bug-finding") { + test("bug-finding-concolic") { testFileConcolicCpp("./benchmarks/wasm/branch-strip-buggy.wat", Some("real_main")) } - test("brtable-bug-finding") { + test("brtable-bug-finding-concolic") { testFileConcolicCpp("./benchmarks/wasm/staged/brtable_concolic.wat") } From 261c650e78956204ae4b190e7841b27e97a0084f Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Mon, 1 Sep 2025 13:28:40 -0400 Subject: [PATCH 29/53] remove debug printings --- headers/wasm/concrete_rt.hpp | 1 - headers/wasm/symbolic_rt.hpp | 6 ------ 2 files changed, 7 deletions(-) diff --git a/headers/wasm/concrete_rt.hpp b/headers/wasm/concrete_rt.hpp index 16642067..76c2537a 100644 --- a/headers/wasm/concrete_rt.hpp +++ b/headers/wasm/concrete_rt.hpp @@ -166,7 +166,6 @@ class Frames_t { size_t size() const { return count; } void set_from_front(int32_t index, const Num &num) { - GENSYM_DBG(index); assert(index >= 0 && index < count && "Index out of bounds"); stack_ptr[index] = num; } diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index d18ea609..730e778b 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -499,7 +499,6 @@ inline std::monostate NodeBox::fillSnapshotNode(Snapshot_t snapshot) { } inline std::monostate NodeBox::fillFinishedNode() { - GENSYM_DBG("Filling with a Finished Node"); if (this->isUnexplored()) { node = std::make_unique(); } else { @@ -512,11 +511,6 @@ inline std::monostate NodeBox::fillFailedNode() { if (this->isUnexplored()) { node = std::make_unique(); } else { - if (auto if_else_node = dynamic_cast(node.get())) { - GENSYM_DBG(typeid(*if_else_node).name()); - } else if (auto finished_node = dynamic_cast(node.get())) { - GENSYM_DBG(typeid(*finished_node).name()); - } assert(dynamic_cast(node.get()) != nullptr); } return std::monostate(); From 1c6a0452bf41479c861a9d4f2fada72edad5574a Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Wed, 3 Sep 2025 20:37:19 -0400 Subject: [PATCH 30/53] give every branch node an ID --- headers/wasm/concolic_driver.hpp | 2 +- headers/wasm/symbolic_rt.hpp | 15 +++---- .../scala/wasm/StagedConcolicMiniWasm.scala | 42 +++++++++++++++---- 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/headers/wasm/concolic_driver.hpp b/headers/wasm/concolic_driver.hpp index 1c2fcc2e..e0b9e726 100644 --- a/headers/wasm/concolic_driver.hpp +++ b/headers/wasm/concolic_driver.hpp @@ -100,7 +100,7 @@ static void start_concolic_execution_with( } static void start_concolic_execution_with( - std::function entrypoint) { + std::function entrypoint, int branchCount) { const char *env_tree_file = std::getenv("TREE_FILE"); diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 730e778b..dfc0f7b1 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -291,7 +291,7 @@ struct NodeBox { std::unique_ptr node; NodeBox *parent; - std::monostate fillIfElseNode(SymVal cond); + std::monostate fillIfElseNode(SymVal cond, int id); std::monostate fillFinishedNode(); std::monostate fillFailedNode(); std::monostate fillUnreachableNode(); @@ -344,10 +344,11 @@ struct IfElseNode : Node { SymVal cond; std::unique_ptr true_branch; std::unique_ptr false_branch; + int id; - IfElseNode(SymVal cond, NodeBox *parent) + IfElseNode(SymVal cond, NodeBox *parent, int id) : cond(cond), true_branch(std::make_unique(parent)), - false_branch(std::make_unique(parent)) {} + false_branch(std::make_unique(parent)), id(id) {} std::string to_string() override { std::string result = "IfElseNode {\n"; @@ -480,10 +481,10 @@ inline NodeBox::NodeBox(NodeBox *parent) /* TODO: avoid allocation of unexplored node */ parent(parent) {} -inline std::monostate NodeBox::fillIfElseNode(SymVal cond) { +inline std::monostate NodeBox::fillIfElseNode(SymVal cond, int id) { // fill the current NodeBox with an ifelse branch node when it's unexplored if (this->isUnexplored()) { - node = std::make_unique(cond, this); + node = std::make_unique(cond, this, id); } assert( dynamic_cast(node.get()) != nullptr && @@ -581,8 +582,8 @@ class ExploreTree_t { std::monostate fillFailedNode() { return cursor->fillFailedNode(); } - std::monostate fillIfElseNode(SymVal cond) { - return cursor->fillIfElseNode(cond); + std::monostate fillIfElseNode(SymVal cond, int id) { + return cursor->fillIfElseNode(cond, id); } std::monostate moveCursor(bool branch, Snapshot_t snapshot) { diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index 9b34bfba..5630e18d 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -19,6 +19,26 @@ import gensym.wasm.symbolic.Concrete import gensym.wasm.symbolic.ExploreTree import gensym.structure.freer.Explore +object Counter { + var currentId: Int = 0 + private val dict = new HashMap[WIR, Int]() + + def reset(): Unit = { + currentId = 0 + dict.clear() + } + + def getId(wir: WIR): Int = { + if (dict.contains(wir)) { + dict(wir) + } else { + val id = currentId + currentId += 1 + dict(wir) = id + id + } + } +} @virtualize trait StagedWasmEvaluator extends SAIOps { def module: ModuleInstance @@ -365,7 +385,8 @@ trait StagedWasmEvaluator extends SAIOps { val newRestCtx = restCtx.shift(offset, funcTy.out.size) eval(rest, kont, mk, trail)(newRestCtx) }) - ExploreTree.fillWithIfElse(symCond.s) + val id = Counter.getId(inst) + ExploreTree.fillWithIfElse(symCond.s, id) def thnK: Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { info("Entering the true branch of the if") eval(thn, restK _, mk, restK _ :: trail)(newCtx) @@ -392,7 +413,8 @@ trait StagedWasmEvaluator extends SAIOps { val cond = Stack.popC(ty) val symCond = Stack.popS(ty) info(s"The br_if(${label})'s condition is ", cond.toInt) - ExploreTree.fillWithIfElse(symCond.s) + val id = Counter.getId(inst) + ExploreTree.fillWithIfElse(symCond.s, id) def thnK: Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { trail(label)(newCtx)(mk) }) @@ -423,7 +445,8 @@ trait StagedWasmEvaluator extends SAIOps { val labelSym = Stack.peekS(ty) val cond = (label - toStagedNum(I32V(idx))).isZero() val condSym = (labelSym - toStagedSymbolicNum(I32V(idx))).isZero() - ExploreTree.fillWithIfElse(condSym.s) + val id = Counter.getId(inst) + ExploreTree.fillWithIfElse(condSym.s, id) // When moving the cursor to a branch, we mark another branch as // snapshotNode (this is done by moveCursor's runtime implementation) // TODO: store snapshot into this snapshot node @@ -622,6 +645,7 @@ trait StagedWasmEvaluator extends SAIOps { } def evalTop(mkont: Rep[MCont[Unit]], main: Option[String]): Rep[Unit] = { + Counter.reset() val funBody: FuncBodyDef = main match { case Some(func_name) => module.defs.flatMap({ @@ -912,8 +936,8 @@ trait StagedWasmEvaluator extends SAIOps { // Exploration tree, object ExploreTree { - def fillWithIfElse(s: Rep[SymVal]): Rep[Unit] = { - "tree-fill-if-else".reflectCtrlWith[Unit](s) + def fillWithIfElse(s: Rep[SymVal], id: Int): Rep[Unit] = { + "tree-fill-if-else".reflectCtrlWith[Unit](s, id) } def fillWithFinished(): Rep[Unit] = { @@ -1448,8 +1472,8 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { emit("SymEnv.read("); shallow(sym); emit(")") case Node(_, "assert-true", List(cond), _) => emit("GENSYM_ASSERT("); shallow(cond); emit(")") - case Node(_, "tree-fill-if-else", List(sym), _) => - emit("ExploreTree.fillIfElseNode("); shallow(sym); emit(")") + case Node(_, "tree-fill-if-else", List(sym, id), _) => + emit("ExploreTree.fillIfElseNode("); shallow(sym); emit(", "); emit(id.toString); emit(")") case Node(_, "tree-fill-finished", List(), _) => emit("ExploreTree.fillFinishedNode()") case Node(_, "tree-move-cursor", List(b, snapshot), _) => @@ -1496,12 +1520,12 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { emitDatastructures(stream) emitFunctions(stream) emit(src) - emitln(""" + emitln(s""" |/***************************************** |End of Generated Code |*******************************************/ |int main(int argc, char *argv[]) { - | start_concolic_execution_with(Snippet); + | start_concolic_execution_with(Snippet, ${Counter.currentId}); | return 0; |}""".stripMargin) } From 8971eb54fd9740925cc23243b38ff7233a92613e Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Wed, 3 Sep 2025 21:22:51 -0400 Subject: [PATCH 31/53] a bitmap to record the branch coverage --- headers/wasm/concolic_driver.hpp | 26 +++++++++++--------------- headers/wasm/symbolic_rt.hpp | 22 +++++++++++++++------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/headers/wasm/concolic_driver.hpp b/headers/wasm/concolic_driver.hpp index e0b9e726..83252f7d 100644 --- a/headers/wasm/concolic_driver.hpp +++ b/headers/wasm/concolic_driver.hpp @@ -6,6 +6,7 @@ #include "symbolic_rt.hpp" #include "utils.hpp" #include +#include #include #include #include @@ -14,10 +15,11 @@ class ConcolicDriver { friend class ManagedConcolicCleanup; public: - ConcolicDriver(std::function entrypoint, std::string tree_file) - : entrypoint(entrypoint), tree_file(tree_file) {} - ConcolicDriver(std::function entrypoint) - : entrypoint(entrypoint), tree_file(std::nullopt) {} + ConcolicDriver(std::function entrypoint, + std::optional tree_file, int branchCount) + : entrypoint(entrypoint), tree_file(tree_file) { + ExploreTree.branch_cov_map.assign(branchCount, false); + } void run(); private: @@ -92,22 +94,16 @@ static std::monostate reset_stacks() { return std::monostate{}; } -static void start_concolic_execution_with( - std::function entrypoint, - std::string tree_file) { - ConcolicDriver driver([=]() { entrypoint(std::monostate{}); }, tree_file); - driver.run(); -} - static void start_concolic_execution_with( std::function entrypoint, int branchCount) { const char *env_tree_file = std::getenv("TREE_FILE"); - ConcolicDriver driver = - env_tree_file ? ConcolicDriver([=]() { entrypoint(std::monostate{}); }, - env_tree_file) - : ConcolicDriver([=]() { entrypoint(std::monostate{}); }); + 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(); } diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index dfc0f7b1..1d80b037 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -291,7 +291,7 @@ struct NodeBox { std::unique_ptr node; NodeBox *parent; - std::monostate fillIfElseNode(SymVal cond, int id); + std::monostate fillIfElseNode(SymVal cond); std::monostate fillFinishedNode(); std::monostate fillFailedNode(); std::monostate fillUnreachableNode(); @@ -344,11 +344,10 @@ struct IfElseNode : Node { SymVal cond; std::unique_ptr true_branch; std::unique_ptr false_branch; - int id; - IfElseNode(SymVal cond, NodeBox *parent, int id) + IfElseNode(SymVal cond, NodeBox *parent) : cond(cond), true_branch(std::make_unique(parent)), - false_branch(std::make_unique(parent)), id(id) {} + false_branch(std::make_unique(parent)) {} std::string to_string() override { std::string result = "IfElseNode {\n"; @@ -481,10 +480,10 @@ inline NodeBox::NodeBox(NodeBox *parent) /* TODO: avoid allocation of unexplored node */ parent(parent) {} -inline std::monostate NodeBox::fillIfElseNode(SymVal cond, int id) { +inline std::monostate NodeBox::fillIfElseNode(SymVal cond) { // fill the current NodeBox with an ifelse branch node when it's unexplored if (this->isUnexplored()) { - node = std::make_unique(cond, this, id); + node = std::make_unique(cond, this); } assert( dynamic_cast(node.get()) != nullptr && @@ -583,7 +582,8 @@ class ExploreTree_t { std::monostate fillFailedNode() { return cursor->fillFailedNode(); } std::monostate fillIfElseNode(SymVal cond, int id) { - return cursor->fillIfElseNode(cond, id); + branch_cov_map[id] = true; + return cursor->fillIfElseNode(cond); } std::monostate moveCursor(bool branch, Snapshot_t snapshot) { @@ -637,6 +637,14 @@ class ExploreTree_t { // For now, we just iterate through the tree and return the first unexplored return pick_unexplored_of(root.get()); } + std::vector branch_cov_map; + bool all_branch_covered() const { + for (bool covered : branch_cov_map) { + if (!covered) + return false; + } + return true; + } private: NodeBox *pick_unexplored_of(NodeBox *node) { From 1b92fc0e8db85ac4b567fe121e23dd970aa178d4 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Thu, 4 Sep 2025 13:00:17 -0400 Subject: [PATCH 32/53] a new exploring strategy: exit when all branches are covered --- headers/wasm/concolic_driver.hpp | 23 +++++++++++++++-- headers/wasm/symbolic_rt.hpp | 25 ++++++++++++------- .../scala/wasm/StagedConcolicMiniWasm.scala | 20 ++++++++++----- .../genwasym/TestStagedConcolicEval.scala | 14 +++++++++-- 4 files changed, 63 insertions(+), 19 deletions(-) diff --git a/headers/wasm/concolic_driver.hpp b/headers/wasm/concolic_driver.hpp index 83252f7d..0ad21246 100644 --- a/headers/wasm/concolic_driver.hpp +++ b/headers/wasm/concolic_driver.hpp @@ -11,6 +11,16 @@ #include #include +enum class ExploreMode { EarlyExit, ExitByCoverage }; + +#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 + class ConcolicDriver { friend class ManagedConcolicCleanup; @@ -18,7 +28,8 @@ class ConcolicDriver { ConcolicDriver(std::function entrypoint, std::optional tree_file, int branchCount) : entrypoint(entrypoint), tree_file(tree_file) { - ExploreTree.branch_cov_map.assign(branchCount, false); + ExploreTree.true_branch_cov_map.assign(branchCount, false); + ExploreTree.false_branch_cov_map.assign(branchCount, false); } void run(); @@ -76,7 +87,15 @@ inline void ConcolicDriver::run() { ExploreTree.fillFailedNode(); GENSYM_INFO("Caught runtime error with symbolic environment:"); GENSYM_INFO(SymEnv.to_string()); - return; + switch (EXPLORE_MODE) { + case ExploreMode::EarlyExit: + return; + case ExploreMode::ExitByCoverage: + if (ExploreTree.all_branch_covered()) { + GENSYM_INFO("All branches covered, exiting..."); + return; + } + } } #if defined(RUN_ONCE) return; diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 1d80b037..974a71c7 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -291,7 +291,7 @@ struct NodeBox { std::unique_ptr node; NodeBox *parent; - std::monostate fillIfElseNode(SymVal cond); + std::monostate fillIfElseNode(SymVal cond, int id); std::monostate fillFinishedNode(); std::monostate fillFailedNode(); std::monostate fillUnreachableNode(); @@ -344,10 +344,11 @@ struct IfElseNode : Node { SymVal cond; std::unique_ptr true_branch; std::unique_ptr false_branch; + int id; - IfElseNode(SymVal cond, NodeBox *parent) + IfElseNode(SymVal cond, NodeBox *parent, int id) : cond(cond), true_branch(std::make_unique(parent)), - false_branch(std::make_unique(parent)) {} + false_branch(std::make_unique(parent)), id(id) {} std::string to_string() override { std::string result = "IfElseNode {\n"; @@ -480,10 +481,10 @@ inline NodeBox::NodeBox(NodeBox *parent) /* TODO: avoid allocation of unexplored node */ parent(parent) {} -inline std::monostate NodeBox::fillIfElseNode(SymVal cond) { +inline std::monostate NodeBox::fillIfElseNode(SymVal cond, int id) { // fill the current NodeBox with an ifelse branch node when it's unexplored if (this->isUnexplored()) { - node = std::make_unique(cond, this); + node = std::make_unique(cond, this, id); } assert( dynamic_cast(node.get()) != nullptr && @@ -582,8 +583,7 @@ class ExploreTree_t { std::monostate fillFailedNode() { return cursor->fillFailedNode(); } std::monostate fillIfElseNode(SymVal cond, int id) { - branch_cov_map[id] = true; - return cursor->fillIfElseNode(cond); + return cursor->fillIfElseNode(cond, id); } std::monostate moveCursor(bool branch, Snapshot_t snapshot) { @@ -593,9 +593,11 @@ class ExploreTree_t { 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->fillSnapshotNode(snapshot); cursor = if_else_node->true_branch.get(); } else { + false_branch_cov_map[if_else_node->id] = true; if_else_node->true_branch->fillSnapshotNode(snapshot); cursor = if_else_node->false_branch.get(); } @@ -637,9 +639,14 @@ class ExploreTree_t { // For now, we just iterate through the tree and return the first unexplored return pick_unexplored_of(root.get()); } - std::vector branch_cov_map; + std::vector true_branch_cov_map; + std::vector false_branch_cov_map; bool all_branch_covered() const { - for (bool covered : branch_cov_map) { + for (bool covered : true_branch_cov_map) { + if (!covered) + return false; + } + for (bool covered : false_branch_cov_map) { if (!covered) return false; } diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index 5630e18d..118b5949 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -21,22 +21,30 @@ import gensym.structure.freer.Explore object Counter { var currentId: Int = 0 - private val dict = new HashMap[WIR, Int]() + + // WIR is the branch's corresponding ast, while the Int stands for the nth + // branch of the AST(a WIR may contain multiple branches, e.g., br_table) + private val dict = new HashMap[(WIR, Int), Int]() def reset(): Unit = { currentId = 0 dict.clear() } - def getId(wir: WIR): Int = { - if (dict.contains(wir)) { - dict(wir) + def getId(wir: WIR, nth: Int = 0): Int = { + if (dict.contains((wir, nth))) { + dict((wir, nth)) } else { val id = currentId currentId += 1 - dict(wir) = id + dict((wir, nth)) = id id } + + } + + def getId(wir: WIR): Int = { + getId(wir, 0) } } @virtualize @@ -445,7 +453,7 @@ trait StagedWasmEvaluator extends SAIOps { val labelSym = Stack.peekS(ty) val cond = (label - toStagedNum(I32V(idx))).isZero() val condSym = (labelSym - toStagedSymbolicNum(I32V(idx))).isZero() - val id = Counter.getId(inst) + val id = Counter.getId(inst, idx) ExploreTree.fillWithIfElse(condSym.s, id) // When moving the cursor to a branch, we mark another branch as // snapshotNode (this is done by moveCursor's runtime implementation) diff --git a/src/test/scala/genwasym/TestStagedConcolicEval.scala b/src/test/scala/genwasym/TestStagedConcolicEval.scala index 5a77f58d..0e0019ee 100644 --- a/src/test/scala/genwasym/TestStagedConcolicEval.scala +++ b/src/test/scala/genwasym/TestStagedConcolicEval.scala @@ -9,12 +9,14 @@ import gensym.wasm.parser._ import gensym.wasm.stagedconcolicminiwasm._ class TestStagedConcolicEval extends FunSuite { - def testFileConcolicCpp(filename: String, main: Option[String] = None) = { + def testFileConcolicCpp(filename: String, + main: Option[String] = None, + exitByCoverage: Boolean = false) = { val moduleInst = ModuleInstance(Parser.parseFile(filename)) val cppFile = s"$filename.cpp" val exe = s"$cppFile.exe" val exploreTreeFile = s"$filename.tree.dot" - WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, true) + WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, true, if (exitByCoverage) "BY_COVERAGE" else "EARLY_EXIT") import sys.process._ val result = Process(s"./$exe", None, "TREE_FILE" -> exploreTreeFile).!! @@ -52,6 +54,14 @@ class TestStagedConcolicEval extends FunSuite { testFileConcolicCpp("./benchmarks/wasm/staged/brtable_concolic.wat") } + test("bug-finding-cov-concolic") { + testFileConcolicCpp("./benchmarks/wasm/branch-strip-buggy.wat", Some("real_main"), exitByCoverage=true) + } + + test("brtable-bug-finding-cov-concolic") { + testFileConcolicCpp("./benchmarks/wasm/staged/brtable_concolic.wat", exitByCoverage=true) + } + test("return-poly - concrete") { testFileConcreteCpp("./benchmarks/wasm/staged/return_poly.wat", Some("$real_main"), expect=Some(List(42))) } From 0f7ca5a5ea02d870147f6e3b23dfc2c576e5419d Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Sun, 7 Sep 2025 16:25:15 -0400 Subject: [PATCH 33/53] support numeric globals --- benchmarks/wasm/staged/simple_global.wat | 22 +++++++++++++ headers/wasm/concolic_driver.hpp | 2 ++ headers/wasm/concrete_rt.hpp | 1 + headers/wasm/symbolic_rt.hpp | 1 + .../scala/wasm/StagedConcolicMiniWasm.scala | 32 +++++++++++++++++-- .../genwasym/TestStagedConcolicEval.scala | 4 +++ 6 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 benchmarks/wasm/staged/simple_global.wat diff --git a/benchmarks/wasm/staged/simple_global.wat b/benchmarks/wasm/staged/simple_global.wat new file mode 100644 index 00000000..4877f42c --- /dev/null +++ b/benchmarks/wasm/staged/simple_global.wat @@ -0,0 +1,22 @@ +(module $simple_global + (type (;0;) (func (param i32 i32) (result i32))) + (type (;1;) (func (result i32))) + (type (;2;) (func (param i32))) + (func $real_main (type 1) (result i32) + (local i32) + i32.const 0 + i32.symbolic + local.tee 0 + local.get 0 + global.set 0 + if + else + i32.const 0 + call 1 + end) + (import "console" "assert" (func (type 2))) + (memory (;0;) 16) + (global $__stack_pointer (mut i32) (i32.const 1048576)) + (global (;1;) i32 (i32.const 1048576)) + (global (;2;) i32 (i32.const 1048576)) + (export "real_main" (func 0))) diff --git a/headers/wasm/concolic_driver.hpp b/headers/wasm/concolic_driver.hpp index 0ad21246..81b5d5ad 100644 --- a/headers/wasm/concolic_driver.hpp +++ b/headers/wasm/concolic_driver.hpp @@ -94,6 +94,8 @@ inline void ConcolicDriver::run() { if (ExploreTree.all_branch_covered()) { GENSYM_INFO("All branches covered, exiting..."); return; + } else { + GENSYM_INFO("Found a bug, but not all branches covered, continuing..."); } } } diff --git a/headers/wasm/concrete_rt.hpp b/headers/wasm/concrete_rt.hpp index 76c2537a..179ef245 100644 --- a/headers/wasm/concrete_rt.hpp +++ b/headers/wasm/concrete_rt.hpp @@ -181,6 +181,7 @@ class Frames_t { }; static Frames_t Frames; +static Frames_t Globals; static void initRand() { // for now, just do nothing diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 974a71c7..137d071d 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -283,6 +283,7 @@ inline void SymFrames_t::reuse(Snapshot_t snapshot) { } static SymFrames_t SymFrames; +static SymFrames_t SymGlobals; struct Node; diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index 118b5949..a4958e27 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -679,6 +679,7 @@ trait StagedWasmEvaluator extends SAIOps { } val (instrs, locals) = (funBody.body, funBody.locals) resetStacks() + initGlobals(module.globals) Frames.pushFrameC(locals) Frames.pushFrameS(locals) eval(instrs, (_: Context) => forwardKont, mkont, ((_: Context) => forwardKont)::Nil)(Context(Nil, locals)) @@ -883,6 +884,18 @@ trait StagedWasmEvaluator extends SAIOps { "reset-stacks".reflectCtrlWith[Unit]() } + def initGlobals(globals: List[RTGlobal]): Rep[Unit] = { + Globals.reserveSpace(globals.size) + for ((g, i) <- globals.view.zipWithIndex) { + val initValue = g.value match { + case n: Num => n + case _ => throw new RuntimeException("Non-numeric global value is not supported yet") + } + Globals.setC(i, toStagedNum(initValue)) + Globals.setS(i, toStagedSymbolicNum(initValue)) + } + } + // call unreachable def unreachable(): Rep[Unit] = { "unreachable".reflectCtrlWith[Unit]() @@ -905,6 +918,11 @@ trait StagedWasmEvaluator extends SAIOps { // global read/write object Globals { + def reserveSpace(size: Int): Rep[Unit] = { + "global-reserve".reflectCtrlWith[Unit](size) + "sym-global-reserve".reflectCtrlWith[Unit](size) + } + def getC(i: Int): StagedConcreteNum = { module.globals(i).ty match { case GlobalType(NumType(I32Type), _) => I32C("global-get".reflectCtrlWith[Num](i)) @@ -1352,8 +1370,6 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { emit("Frames.set("); shallow(i); emit(", "); shallow(value); emit(");\n") case Node(_, "sym-frame-set", List(i, s_value), _) => emit("SymFrames.set("); shallow(i); emit(", "); shallow(s_value); emit(");\n") - case Node(_, "global-set", List(i, value), _) => - emit("Global.globalSet("); shallow(i); emit(", "); shallow(value); emit(");\n") // Note: The following code is copied from the traverse of CppBackend.scala, try to avoid duplicated code case n @ Node(f, "λ", (b: LMSBlock)::LMSConst(0)::rest, _) => // TODO: Is a leading block followed by 0 a hint for top function? @@ -1410,7 +1426,17 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { case Node(_, "stack-size", _, _) => emit("Stack.size()") case Node(_, "global-get", List(i), _) => - emit("Global.globalGet("); shallow(i); emit(")") + emit("Globals.get("); shallow(i); emit(")") + case Node(_, "sym-global-get", List(i), _) => + emit("SymGlobal.get("); shallow(i); emit(")") + case Node(_, "global-set", List(i, value), _) => + emit("Globals.set("); shallow(i); emit(", "); shallow(value); emit(")") + case Node(_, "sym-global-set", List(i, s_value), _) => + emit("SymGlobals.set("); shallow(i); emit(", "); shallow(s_value); emit(")") + case Node(_, "global-reserve", List(i), _) => + emit("Globals.pushFrame("); shallow(i); emit(")") + case Node(_, "sym-global-reserve", List(i), _) => + emit("SymGlobals.pushFrame("); shallow(i); emit(")") case Node(_, "is-zero", List(num), _) => emit("(0 == "); shallow(num); emit(")") case Node(_, "sym-is-zero", List(s_num), _) => diff --git a/src/test/scala/genwasym/TestStagedConcolicEval.scala b/src/test/scala/genwasym/TestStagedConcolicEval.scala index 0e0019ee..303a3fe5 100644 --- a/src/test/scala/genwasym/TestStagedConcolicEval.scala +++ b/src/test/scala/genwasym/TestStagedConcolicEval.scala @@ -62,6 +62,10 @@ class TestStagedConcolicEval extends FunSuite { testFileConcolicCpp("./benchmarks/wasm/staged/brtable_concolic.wat", exitByCoverage=true) } + test("simple-global-bug-finding-cov-concolic") { + testFileConcolicCpp("./benchmarks/wasm/staged/simple_global.wat", Some("real_main"), exitByCoverage=true) + } + test("return-poly - concrete") { testFileConcreteCpp("./benchmarks/wasm/staged/return_poly.wat", Some("$real_main"), expect=Some(List(42))) } From 0fded4c6f34c1b16b10a3779ad42aa4675cfcc41 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Mon, 8 Sep 2025 20:00:47 -0400 Subject: [PATCH 34/53] Explicitly classify the next stage computation and its type Then we can compose/decompose them more easily. --- .../scala/wasm/StagedConcolicMiniWasm.scala | 596 +++++++++--------- 1 file changed, 288 insertions(+), 308 deletions(-) diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index a4958e27..aee63cda 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -51,60 +51,26 @@ object Counter { trait StagedWasmEvaluator extends SAIOps { def module: ModuleInstance - trait ReturnSite + case class StagedConcreteNum(tipe: ValueType, i: Rep[Num]) - trait StagedNum { - def tipe: ValueType - } - - trait StagedConcreteNum { - def tipe: ValueType = this match { - case I32C(_) => NumType(I32Type) - case I64C(_) => NumType(I64Type) - case F32C(_) => NumType(F32Type) - case F64C(_) => NumType(F64Type) - } - - def i: Rep[Num] - } - - case class I32C(i: Rep[Num]) extends StagedConcreteNum - case class I64C(i: Rep[Num]) extends StagedConcreteNum - case class F32C(i: Rep[Num]) extends StagedConcreteNum - case class F64C(i: Rep[Num]) extends StagedConcreteNum - - trait StagedSymbolicNum { - def tipe: ValueType = this match { - case I32S(_) => NumType(I32Type) - case I64S(_) => NumType(I64Type) - case F32S(_) => NumType(F32Type) - case F64S(_) => NumType(F64Type) - } - - def s: Rep[SymVal] - } - - case class I32S(s: Rep[SymVal]) extends StagedSymbolicNum - case class I64S(s: Rep[SymVal]) extends StagedSymbolicNum - case class F32S(s: Rep[SymVal]) extends StagedSymbolicNum - case class F64S(s: Rep[SymVal]) extends StagedSymbolicNum + case class StagedSymbolicNum(tipe: ValueType, s: Rep[SymVal]) def toStagedNum(num: Num): StagedConcreteNum = { num match { - case I32V(_) => I32C(num) - case I64V(_) => I64C(num) - case F32V(_) => F32C(num) - case F64V(_) => F64C(num) + case I32V(_) => StagedConcreteNum(NumType(I32Type), num) + case I64V(_) => StagedConcreteNum(NumType(I64Type), num) + case F32V(_) => StagedConcreteNum(NumType(F32Type), num) + case F64V(_) => StagedConcreteNum(NumType(F64Type), num) } } def toStagedSymbolicNum(num: Num): StagedSymbolicNum = { num match { - case I32V(_) => I32S(Concrete(num)) - case I64V(_) => I64S(Concrete(num)) - case F32V(_) => F32S(Concrete(num)) - case F64V(_) => F64S(Concrete(num)) + case I32V(_) => StagedSymbolicNum(NumType(I32Type), Concrete(num)) + case I64V(_) => StagedSymbolicNum(NumType(I64Type), Concrete(num)) + case F32V(_) => StagedSymbolicNum(NumType(F32Type), Concrete(num)) + case F64V(_) => StagedSymbolicNum(NumType(F64Type), Concrete(num)) } } @@ -115,24 +81,6 @@ trait StagedWasmEvaluator extends SAIOps { case NumType(F32Type) => 4 case NumType(F64Type) => 8 } - - def concreteTag: (Rep[Num]) => StagedConcreteNum = { - ty match { - case NumType(I32Type) => I32C - case NumType(I64Type) => I64C - case NumType(F32Type) => F32C - case NumType(F64Type) => F64C - } - } - - def symbolicTag: (Rep[SymVal]) => StagedSymbolicNum = { - ty match { - case NumType(I32Type) => I32S - case NumType(I64Type) => I64S - case NumType(F32Type) => F32S - case NumType(F64Type) => F64S - } - } } case class Context( @@ -225,7 +173,7 @@ trait StagedWasmEvaluator extends SAIOps { val id = Stack.popS(ty) val symVal = id.makeSymbolic(ty) val num = SymEnv.read(symVal.s) - Stack.pushC(ty.concreteTag(num)) + Stack.pushC(StagedConcreteNum(ty, num)) Stack.pushS(symVal) val newCtx = ctx.pop()._2.push(ty) eval(rest, kont, mkont, trail)(newCtx) @@ -295,8 +243,8 @@ trait StagedWasmEvaluator extends SAIOps { // For now, we assume that the result of memory.grow only depends on the execution path, // we can relax this by turning it return to a symbol value and mimic the memory.grow's result as input. val retSym = "Concrete".reflectCtrlWith[SymVal](retNum) - Stack.pushC(I32C(retNum)) - Stack.pushS(I32S(retSym)) + Stack.pushC(StagedConcreteNum(NumType(I32Type), retNum)) + Stack.pushS(StagedSymbolicNum(NumType(I32Type), retSym)) val newCtx2 = ctx.push(NumType(I32Type)) eval(rest, kont, mkont, trail)(newCtx2) case MemoryFill => ??? @@ -722,47 +670,25 @@ trait StagedWasmEvaluator extends SAIOps { "stack-init".reflectCtrlWith[Unit]() } - def popC(ty: ValueType): StagedConcreteNum = ty match { - case NumType(I32Type) => I32C("stack-pop".reflectCtrlWith[Num]()) - case NumType(I64Type) => I64C("stack-pop".reflectCtrlWith[Num]()) - case NumType(F32Type) => F32C("stack-pop".reflectCtrlWith[Num]()) - case NumType(F32Type) => F64C("stack-pop".reflectCtrlWith[Num]()) + def popC(ty: ValueType): StagedConcreteNum = { + StagedConcreteNum(ty, "stack-pop".reflectCtrlWith[Num]()) } - def popS(ty: ValueType): StagedSymbolicNum = ty match { - case NumType(I32Type) => I32S("sym-stack-pop".reflectCtrlWith[SymVal]()) - case NumType(I64Type) => I64S("sym-stack-pop".reflectCtrlWith[SymVal]()) - case NumType(F32Type) => F32S("sym-stack-pop".reflectCtrlWith[SymVal]()) - case NumType(F64Type) => F64S("sym-stack-pop".reflectCtrlWith[SymVal]()) + def popS(ty: ValueType): StagedSymbolicNum = { + StagedSymbolicNum(ty, "sym-stack-pop".reflectCtrlWith[SymVal]()) } - def peekC(ty: ValueType): StagedConcreteNum = ty match { - case NumType(I32Type) => I32C("stack-peek".reflectCtrlWith[Num]()) - case NumType(I64Type) => I64C("stack-peek".reflectCtrlWith[Num]()) - case NumType(F32Type) => F32C("stack-peek".reflectCtrlWith[Num]()) - case NumType(F32Type) => F64C("stack-peek".reflectCtrlWith[Num]()) + def peekC(ty: ValueType): StagedConcreteNum = { + StagedConcreteNum(ty, "stack-peek".reflectCtrlWith[Num]()) } - def peekS(ty: ValueType): StagedSymbolicNum = ty match { - case NumType(I32Type) => I32S("sym-stack-peek".reflectCtrlWith[SymVal]()) - case NumType(I64Type) => I64S("sym-stack-peek".reflectCtrlWith[SymVal]()) - case NumType(F32Type) => F32S("sym-stack-peek".reflectCtrlWith[SymVal]()) - case NumType(F64Type) => F64S("sym-stack-peek".reflectCtrlWith[SymVal]()) + def peekS(ty: ValueType): StagedSymbolicNum = { + StagedSymbolicNum(ty, "sym-stack-peek".reflectCtrlWith[SymVal]()) } - def pushC(num: StagedConcreteNum) = num match { - case I32C(v) => "stack-push".reflectCtrlWith[Unit](v) - case I64C(v) => "stack-push".reflectCtrlWith[Unit](v) - case F32C(v) => "stack-push".reflectCtrlWith[Unit](v) - case F64C(v) => "stack-push".reflectCtrlWith[Unit](v) - } + def pushC(num: StagedConcreteNum) = "stack-push".reflectCtrlWith[Unit](num.i) - def pushS(num: StagedSymbolicNum) = num match { - case I32S(s) => "sym-stack-push".reflectCtrlWith[Unit](s) - case I64S(s) => "sym-stack-push".reflectCtrlWith[Unit](s) - case F32S(s) => "sym-stack-push".reflectCtrlWith[Unit](s) - case F64S(s) => "sym-stack-push".reflectCtrlWith[Unit](s) - } + def pushS(num: StagedSymbolicNum) = "sym-stack-push".reflectCtrlWith[Unit](num.s) def takeC(types: List[ValueType]): List[StagedConcreteNum] = types match { case Nil => Nil @@ -792,39 +718,19 @@ trait StagedWasmEvaluator extends SAIOps { object Frames { def getC(i: Int)(implicit ctx: Context): StagedConcreteNum = { // val offset = ctx.frameTypes.take(i).map(_.size).sum - ctx.frameTypes(i) match { - case NumType(I32Type) => I32C("frame-get".reflectCtrlWith[Num](i)) - case NumType(I64Type) => I64C("frame-get".reflectCtrlWith[Num](i)) - case NumType(F32Type) => F32C("frame-get".reflectCtrlWith[Num](i)) - case NumType(F64Type) => F64C("frame-get".reflectCtrlWith[Num](i)) - } + StagedConcreteNum(ctx.frameTypes(i), "frame-get".reflectCtrlWith[Num](i)) } def getS(i: Int)(implicit ctx: Context): StagedSymbolicNum = { - ctx.frameTypes(i) match { - case NumType(I32Type) => I32S("sym-frame-get".reflectCtrlWith[SymVal](i)) - case NumType(I64Type) => I64S("sym-frame-get".reflectCtrlWith[SymVal](i)) - case NumType(F32Type) => F32S("sym-frame-get".reflectCtrlWith[SymVal](i)) - case NumType(F64Type) => F64S("sym-frame-get".reflectCtrlWith[SymVal](i)) - } + StagedSymbolicNum(ctx.frameTypes(i), "sym-frame-get".reflectCtrlWith[SymVal](i)) } def setC(i: Int, v: StagedConcreteNum): Rep[Unit] = { - v match { - case I32C(v) => "frame-set".reflectCtrlWith[Unit](i, v) - case I64C(v) => "frame-set".reflectCtrlWith[Unit](i, v) - case F32C(v) => "frame-set".reflectCtrlWith[Unit](i, v) - case F64C(v) => "frame-set".reflectCtrlWith[Unit](i, v) - } + "frame-set".reflectCtrlWith[Unit](i, v.i) } def setS(i: Int, s: StagedSymbolicNum): Rep[Unit] = { - s match { - case I32S(s) => "sym-frame-set".reflectCtrlWith[Unit](i, s) - case I64S(s) => "sym-frame-set".reflectCtrlWith[Unit](i, s) - case F32S(s) => "sym-frame-set".reflectCtrlWith[Unit](i, s) - case F64S(s) => "sym-frame-set".reflectCtrlWith[Unit](i, s) - } + "sym-frame-set".reflectCtrlWith[Unit](i, s.s) } def pushFrameC(locals: List[ValueType]): Rep[Unit] = { @@ -867,11 +773,11 @@ trait StagedWasmEvaluator extends SAIOps { } def loadIntC(base: Rep[Int], offset: Int): StagedConcreteNum = { - I32C("I32V".reflectCtrlWith[Num]("memory-load-int".reflectCtrlWith[Int](base, offset))) + StagedConcreteNum(NumType(I32Type), "I32V".reflectCtrlWith[Num]("memory-load-int".reflectCtrlWith[Int](base, offset))) } def loadIntS(base: Rep[Int], offset: Int): StagedSymbolicNum = { - I32S("sym-load-int-todo".reflectCtrlWith[SymVal](base, offset)) + StagedSymbolicNum(NumType(I32Type), "sym-load-int-todo".reflectCtrlWith[SymVal](base, offset)) } // Returns the previous memory size on success, or -1 if the memory cannot be grown. @@ -924,43 +830,23 @@ trait StagedWasmEvaluator extends SAIOps { } def getC(i: Int): StagedConcreteNum = { - module.globals(i).ty match { - case GlobalType(NumType(I32Type), _) => I32C("global-get".reflectCtrlWith[Num](i)) - case GlobalType(NumType(I64Type), _) => I64C("global-get".reflectCtrlWith[Num](i)) - case GlobalType(NumType(F32Type), _) => F32C("global-get".reflectCtrlWith[Num](i)) - case GlobalType(NumType(F64Type), _) => F64C("global-get".reflectCtrlWith[Num](i)) - } + StagedConcreteNum(module.globals(i).ty.ty, "global-get".reflectCtrlWith[Num](i)) } def getS(i: Int): StagedSymbolicNum = { - module.globals(i).ty match { - case GlobalType(NumType(I32Type), _) => I32S("sym-global-get".reflectCtrlWith[SymVal](i)) - case GlobalType(NumType(I64Type), _) => I64S("sym-global-get".reflectCtrlWith[SymVal](i)) - case GlobalType(NumType(F32Type), _) => F32S("sym-global-get".reflectCtrlWith[SymVal](i)) - case GlobalType(NumType(F64Type), _) => F64S("sym-global-get".reflectCtrlWith[SymVal](i)) - } + StagedSymbolicNum(module.globals(i).ty.ty, "sym-global-get".reflectCtrlWith[SymVal](i)) } def setC(i: Int, v: StagedConcreteNum): Rep[Unit] = { - module.globals(i).ty match { - case GlobalType(NumType(I32Type), _) => "global-set".reflectCtrlWith[Unit](i, v.i) - case GlobalType(NumType(I64Type), _) => "global-set".reflectCtrlWith[Unit](i, v.i) - case GlobalType(NumType(F32Type), _) => "global-set".reflectCtrlWith[Unit](i, v.i) - case GlobalType(NumType(F64Type), _) => "global-set".reflectCtrlWith[Unit](i, v.i) - } + "global-set".reflectCtrlWith[Unit](i, v.i) } def setS(i: Int, s: StagedSymbolicNum): Rep[Unit] = { - module.globals(i).ty match { - case GlobalType(NumType(I32Type), _) => "sym-global-set".reflectCtrlWith[Unit](i, s.s) - case GlobalType(NumType(I64Type), _) => "sym-global-set".reflectCtrlWith[Unit](i, s.s) - case GlobalType(NumType(F32Type), _) => "sym-global-set".reflectCtrlWith[Unit](i, s.s) - case GlobalType(NumType(F64Type), _) => "sym-global-set".reflectCtrlWith[Unit](i, s.s) - } + "sym-global-set".reflectCtrlWith[Unit](i, s.s) } } - // Exploration tree, + // Exploration tree, object ExploreTree { def fillWithIfElse(s: Rep[SymVal], id: Int): Rep[Unit] = { "tree-fill-if-else".reflectCtrlWith[Unit](s, id) @@ -993,320 +879,414 @@ trait StagedWasmEvaluator extends SAIOps { // runtime Num type implicit class StagedConcreteNumOps(num: StagedConcreteNum) { - def makeSymbolic(ty: ValueType): StagedSymbolicNum = num match { - case I32C(x) => I32S("make-symbolic-concrete".reflectCtrlWith[SymVal](num.toInt)) + def makeSymbolic(ty: ValueType): StagedSymbolicNum = num.tipe match { + case NumType(I32Type) => + StagedSymbolicNum(NumType(I32Type), "make-symbolic-concrete".reflectCtrlWith[SymVal](num.toInt)) } def toInt: Rep[Int] = "num-to-int".reflectCtrlWith[Int](num.i) - def isZero(): StagedConcreteNum = num match { - case I32C(x_c) => I32C(Values.I32V("is-zero".reflectCtrlWith[Int](num.toInt))) + def isZero(): StagedConcreteNum = num.tipe match { + case NumType(I32Type) => + StagedConcreteNum(NumType(I32Type), Values.I32V("is-zero".reflectCtrlWith[Int](num.toInt))) } - def clz(): StagedConcreteNum = num match { - case I32C(x) => I32C("clz".reflectCtrlWith[Num](x)) - case I64C(x) => I64C("clz".reflectCtrlWith[Num](x)) + def clz(): StagedConcreteNum = num.tipe match { + case NumType(I32Type) => StagedConcreteNum(NumType(I32Type), "clz".reflectCtrlWith[Num](num.i)) + case NumType(I64Type) => StagedConcreteNum(NumType(I64Type), "clz".reflectCtrlWith[Num](num.i)) } - def ctz(): StagedConcreteNum = num match { - case I32C(x) => I32C("ctz".reflectCtrlWith[Num](x)) - case I64C(x) => I64C("ctz".reflectCtrlWith[Num](x)) + def ctz(): StagedConcreteNum = num.tipe match { + case NumType(I32Type) => StagedConcreteNum(NumType(I32Type), "ctz".reflectCtrlWith[Num](num.i)) + case NumType(I64Type) => StagedConcreteNum(NumType(I64Type), "ctz".reflectCtrlWith[Num](num.i)) } - def popcnt(): StagedConcreteNum = num match { - case I32C(x) => I32C("popcnt".reflectCtrlWith[Num](x)) - case I64C(x) => I64C("popcnt".reflectCtrlWith[Num](x)) + def popcnt(): StagedConcreteNum = num.tipe match { + case NumType(I32Type) => StagedConcreteNum(NumType(I32Type), "popcnt".reflectCtrlWith[Num](num.i)) + case NumType(I64Type) => StagedConcreteNum(NumType(I64Type), "popcnt".reflectCtrlWith[Num](num.i)) } - def +(rhs: StagedConcreteNum): StagedConcreteNum = { - (num, rhs) match { - case (I32C(x), I32C(y)) => I32C("binary-add".reflectCtrlWith[Num](x, y)) - case (I64C(x), I64C(y)) => I64C("binary-add".reflectCtrlWith[Num](x, y)) - case (F32C(x), F32C(y)) => F32C("binary-add".reflectCtrlWith[Num](x, y)) - case (F64C(x), F64C(y)) => F64C("binary-add".reflectCtrlWith[Num](x, y)) - } + def +(rhs: StagedConcreteNum): StagedConcreteNum = (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedConcreteNum(NumType(I32Type), "binary-add".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(I64Type), NumType(I64Type)) => + StagedConcreteNum(NumType(I64Type), "binary-add".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(F32Type), NumType(F32Type)) => + StagedConcreteNum(NumType(F32Type), "binary-add".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(F64Type), NumType(F64Type)) => + StagedConcreteNum(NumType(F64Type), "binary-add".reflectCtrlWith[Num](num.i, rhs.i)) } - def -(rhs: StagedConcreteNum): StagedConcreteNum = { - (num, rhs) match { - case (I32C(x), I32C(y)) => I32C("binary-sub".reflectCtrlWith[Num](x, y)) - case (I64C(x), I64C(y)) => I64C("binary-sub".reflectCtrlWith[Num](x, y)) - case (F32C(x), F32C(y)) => F32C("binary-sub".reflectCtrlWith[Num](x, y)) - case (F64C(x), F64C(y)) => F64C("binary-sub".reflectCtrlWith[Num](x, y)) - } + def -(rhs: StagedConcreteNum): StagedConcreteNum = (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedConcreteNum(NumType(I32Type), "binary-sub".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(I64Type), NumType(I64Type)) => + StagedConcreteNum(NumType(I64Type), "binary-sub".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(F32Type), NumType(F32Type)) => + StagedConcreteNum(NumType(F32Type), "binary-sub".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(F64Type), NumType(F64Type)) => + StagedConcreteNum(NumType(F64Type), "binary-sub".reflectCtrlWith[Num](num.i, rhs.i)) } def *(rhs: StagedConcreteNum): StagedConcreteNum = { - (num, rhs) match { - case (I32C(x), I32C(y)) => I32C("binary-mul".reflectCtrlWith[Num](x, y)) - case (I64C(x), I64C(y)) => I64C("binary-mul".reflectCtrlWith[Num](x, y)) - case (F32C(x), F32C(y)) => F32C("binary-mul".reflectCtrlWith[Num](x, y)) - case (F64C(x), F64C(y)) => F64C("binary-mul".reflectCtrlWith[Num](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedConcreteNum(NumType(I32Type), "binary-mul".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(I64Type), NumType(I64Type)) => + StagedConcreteNum(NumType(I64Type), "binary-mul".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(F32Type), NumType(F32Type)) => + StagedConcreteNum(NumType(F32Type), "binary-mul".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(F64Type), NumType(F64Type)) => + StagedConcreteNum(NumType(F64Type), "binary-mul".reflectCtrlWith[Num](num.i, rhs.i)) } } def /(rhs: StagedConcreteNum): StagedConcreteNum = { - (num, rhs) match { - case (I32C(x), I32C(y)) => I32C("binary-div".reflectCtrlWith[Num](x, y)) - case (I64C(x), I64C(y)) => I64C("binary-div".reflectCtrlWith[Num](x, y)) - case (F32C(x), F32C(y)) => F32C("binary-div".reflectCtrlWith[Num](x, y)) - case (F64C(x), F64C(y)) => F64C("binary-div".reflectCtrlWith[Num](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedConcreteNum(NumType(I32Type), "binary-div".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(I64Type), NumType(I64Type)) => + StagedConcreteNum(NumType(I64Type), "binary-div".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(F32Type), NumType(F32Type)) => + StagedConcreteNum(NumType(F32Type), "binary-div".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(F64Type), NumType(F64Type)) => + StagedConcreteNum(NumType(F64Type), "binary-div".reflectCtrlWith[Num](num.i, rhs.i)) } } def <<(rhs: StagedConcreteNum): StagedConcreteNum = { - (num, rhs) match { - case (I32C(x), I32C(y)) => I32C("binary-shl".reflectCtrlWith[Num](x, y)) - case (I64C(x), I64C(y)) => I64C("binary-shl".reflectCtrlWith[Num](x, y)) - case (F32C(x), F32C(y)) => F32C("binary-shl".reflectCtrlWith[Num](x, y)) - case (F64C(x), F64C(y)) => F64C("binary-shl".reflectCtrlWith[Num](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedConcreteNum(NumType(I32Type), "binary-shl".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(I64Type), NumType(I64Type)) => + StagedConcreteNum(NumType(I64Type), "binary-shl".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(F32Type), NumType(F32Type)) => + StagedConcreteNum(NumType(F32Type), "binary-shl".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(F64Type), NumType(F64Type)) => + StagedConcreteNum(NumType(F64Type), "binary-shl".reflectCtrlWith[Num](num.i, rhs.i)) } } def >>(rhs: StagedConcreteNum): StagedConcreteNum = { - (num, rhs) match { - case (I32C(x), I32C(y)) => I32C("binary-shr".reflectCtrlWith[Num](x, y)) - case (I64C(x), I64C(y)) => I64C("binary-shr".reflectCtrlWith[Num](x, y)) - case (F32C(x), F32C(y)) => F32C("binary-shr".reflectCtrlWith[Num](x, y)) - case (F64C(x), F64C(y)) => F64C("binary-shr".reflectCtrlWith[Num](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedConcreteNum(NumType(I32Type), "binary-shr".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(I64Type), NumType(I64Type)) => + StagedConcreteNum(NumType(I64Type), "binary-shr".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(F32Type), NumType(F32Type)) => + StagedConcreteNum(NumType(F32Type), "binary-shr".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(F64Type), NumType(F64Type)) => + StagedConcreteNum(NumType(F64Type), "binary-shr".reflectCtrlWith[Num](num.i, rhs.i)) } } def &(rhs: StagedConcreteNum): StagedConcreteNum = { - (num, rhs) match { - case (I32C(x), I32C(y)) => I32C("binary-and".reflectCtrlWith[Num](x, y)) - case (I64C(x), I64C(y)) => I64C("binary-and".reflectCtrlWith[Num](x, y)) - case (F32C(x), F32C(y)) => F32C("binary-and".reflectCtrlWith[Num](x, y)) - case (F64C(x), F64C(y)) => F64C("binary-and".reflectCtrlWith[Num](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedConcreteNum(NumType(I32Type), "binary-and".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(I64Type), NumType(I64Type)) => + StagedConcreteNum(NumType(I64Type), "binary-and".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(F32Type), NumType(F32Type)) => + StagedConcreteNum(NumType(F32Type), "binary-and".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(F64Type), NumType(F64Type)) => + StagedConcreteNum(NumType(F64Type), "binary-and".reflectCtrlWith[Num](num.i, rhs.i)) } } def numEq(rhs: StagedConcreteNum): StagedConcreteNum = { - (num, rhs) match { - case (I32C(x), I32C(y)) => I32C("relation-eq".reflectCtrlWith[Num](x, y)) - case (I64C(x), I64C(y)) => I32C("relation-eq".reflectCtrlWith[Num](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedConcreteNum(NumType(I32Type), "relation-eq".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(I64Type), NumType(I64Type)) => + StagedConcreteNum(NumType(I32Type), "relation-eq".reflectCtrlWith[Num](num.i, rhs.i)) } } def numNe(rhs: StagedConcreteNum): StagedConcreteNum = { - (num, rhs) match { - case (I32C(x), I32C(y)) => I32C("relation-ne".reflectCtrlWith[Num](x, y)) - case (I64C(x), I64C(y)) => I32C("relation-ne".reflectCtrlWith[Num](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedConcreteNum(NumType(I32Type), "relation-ne".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(I64Type), NumType(I64Type)) => + StagedConcreteNum(NumType(I32Type), "relation-ne".reflectCtrlWith[Num](num.i, rhs.i)) } } def <(rhs: StagedConcreteNum): StagedConcreteNum = { - (num, rhs) match { - case (I32C(x), I32C(y)) => I32C("relation-lt".reflectCtrlWith[Num](x, y)) - case (I64C(x), I64C(y)) => I32C("relation-lt".reflectCtrlWith[Num](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedConcreteNum(NumType(I32Type), "relation-lt".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(I64Type), NumType(I64Type)) => + StagedConcreteNum(NumType(I32Type), "relation-lt".reflectCtrlWith[Num](num.i, rhs.i)) } } def ltu(rhs: StagedConcreteNum): StagedConcreteNum = { - (num, rhs) match { - case (I32C(x), I32C(y)) => I32C("relation-ltu".reflectCtrlWith[Num](x, y)) - case (I64C(x), I64C(y)) => I32C("relation-ltu".reflectCtrlWith[Num](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedConcreteNum(NumType(I32Type), "relation-ltu".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(I64Type), NumType(I64Type)) => + StagedConcreteNum(NumType(I32Type), "relation-ltu".reflectCtrlWith[Num](num.i, rhs.i)) } } def >(rhs: StagedConcreteNum): StagedConcreteNum = { - (num, rhs) match { - case (I32C(x), I32C(y)) => I32C("relation-gt".reflectCtrlWith[Num](x, y)) - case (I64C(x), I64C(y)) => I32C("relation-gt".reflectCtrlWith[Num](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedConcreteNum(NumType(I32Type), "relation-gt".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(I64Type), NumType(I64Type)) => + StagedConcreteNum(NumType(I32Type), "relation-gt".reflectCtrlWith[Num](num.i, rhs.i)) } } def gtu(rhs: StagedConcreteNum): StagedConcreteNum = { - (num, rhs) match { - case (I32C(x), I32C(y)) => I32C("relation-gtu".reflectCtrlWith[Num](x, y)) - case (I64C(x), I64C(y)) => I32C("relation-gtu".reflectCtrlWith[Num](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedConcreteNum(NumType(I32Type), "relation-gtu".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(I64Type), NumType(I64Type)) => + StagedConcreteNum(NumType(I32Type), "relation-gtu".reflectCtrlWith[Num](num.i, rhs.i)) } } def <=(rhs: StagedConcreteNum): StagedConcreteNum = { - (num, rhs) match { - case (I32C(x), I32C(y)) => I32C("relation-le".reflectCtrlWith[Num](x, y)) - case (I64C(x), I64C(y)) => I32C("relation-le".reflectCtrlWith[Num](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedConcreteNum(NumType(I32Type), "relation-le".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(I64Type), NumType(I64Type)) => + StagedConcreteNum(NumType(I32Type), "relation-le".reflectCtrlWith[Num](num.i, rhs.i)) } } def leu(rhs: StagedConcreteNum): StagedConcreteNum = { - (num, rhs) match { - case (I32C(x), I32C(y)) => I32C("relation-leu".reflectCtrlWith[Num](x, y)) - case (I64C(x), I64C(y)) => I32C("relation-leu".reflectCtrlWith[Num](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedConcreteNum(NumType(I32Type), "relation-leu".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(I64Type), NumType(I64Type)) => + StagedConcreteNum(NumType(I32Type), "relation-leu".reflectCtrlWith[Num](num.i, rhs.i)) } } def >=(rhs: StagedConcreteNum): StagedConcreteNum = { - (num, rhs) match { - case (I32C(x), I32C(y)) => I32C("relation-ge".reflectCtrlWith[Num](x, y)) - case (I64C(x), I64C(y)) => I32C("relation-ge".reflectCtrlWith[Num](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedConcreteNum(NumType(I32Type), "relation-ge".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(I64Type), NumType(I64Type)) => + StagedConcreteNum(NumType(I32Type), "relation-ge".reflectCtrlWith[Num](num.i, rhs.i)) } } def geu(rhs: StagedConcreteNum): StagedConcreteNum = { - (num, rhs) match { - case (I32C(x), I32C(y)) => I32C("relation-geu".reflectCtrlWith[Num](x, y)) - case (I64C(x), I64C(y)) => I32C("relation-geu".reflectCtrlWith[Num](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedConcreteNum(NumType(I32Type), "relation-geu".reflectCtrlWith[Num](num.i, rhs.i)) + case (NumType(I64Type), NumType(I64Type)) => + StagedConcreteNum(NumType(I32Type), "relation-geu".reflectCtrlWith[Num](num.i, rhs.i)) } } } implicit class StagedSymbolicNumOps(num: StagedSymbolicNum) { - def makeSymbolic(ty: ValueType): StagedSymbolicNum = num match { - case I32S(x) => I32S("make-symbolic".reflectCtrlWith[SymVal](x)) + def makeSymbolic(ty: ValueType): StagedSymbolicNum = num.tipe match { + case NumType(I32Type) => StagedSymbolicNum(NumType(I32Type), "make-symbolic".reflectCtrlWith[SymVal](num.s)) case _ => throw new RuntimeException("Symbol index must be an i32") } - def isZero(): StagedSymbolicNum = num match { - case I32S(x) => I32S("sym-is-zero".reflectCtrlWith[SymVal](x)) + def isZero(): StagedSymbolicNum = num.tipe match { + case NumType(I32Type) => StagedSymbolicNum(NumType(I32Type), "sym-is-zero".reflectCtrlWith[SymVal](num.s)) } - def clz(): StagedSymbolicNum = num match { - case I32S(x) => I32S("sym-clz".reflectCtrlWith[SymVal](x)) - case I64S(x) => I64S("sym-clz".reflectCtrlWith[SymVal](x)) + def clz(): StagedSymbolicNum = num.tipe match { + case NumType(I32Type) => StagedSymbolicNum(NumType(I32Type), "sym-clz".reflectCtrlWith[SymVal](num.s)) + case NumType(I64Type) => StagedSymbolicNum(NumType(I64Type), "sym-clz".reflectCtrlWith[SymVal](num.s)) } - def ctz(): StagedSymbolicNum = num match { - case I32S(x) => I32S("sym-ctz".reflectCtrlWith[SymVal](x)) - case I64S(x) => I64S("sym-ctz".reflectCtrlWith[SymVal](x)) + def ctz(): StagedSymbolicNum = num.tipe match { + case NumType(I32Type) => StagedSymbolicNum(NumType(I32Type), "sym-ctz".reflectCtrlWith[SymVal](num.s)) + case NumType(I64Type) => StagedSymbolicNum(NumType(I64Type), "sym-ctz".reflectCtrlWith[SymVal](num.s)) } - def popcnt(): StagedSymbolicNum = num match { - case I32S(x) => I32S("sym-popcnt".reflectCtrlWith[SymVal](x)) - case I64S(x) => I64S("sym-popcnt".reflectCtrlWith[SymVal](x)) + def popcnt(): StagedSymbolicNum = num.tipe match { + case NumType(I32Type) => StagedSymbolicNum(NumType(I32Type), "sym-popcnt".reflectCtrlWith[SymVal](num.s)) + case NumType(I64Type) => StagedSymbolicNum(NumType(I64Type), "sym-popcnt".reflectCtrlWith[SymVal](num.s)) } def +(rhs: StagedSymbolicNum): StagedSymbolicNum = { - (num, rhs) match { - case (I32S(x), I32S(y)) => I32S("sym-binary-add".reflectCtrlWith[SymVal](x, y)) - case (I64S(x), I64S(y)) => I64S("sym-binary-add".reflectCtrlWith[SymVal](x, y)) - case (F32S(x), F32S(y)) => F32S("sym-binary-add".reflectCtrlWith[SymVal](x, y)) - case (F64S(x), F64S(y)) => F64S("sym-binary-add".reflectCtrlWith[SymVal](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-binary-add".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(I64Type), NumType(I64Type)) => + StagedSymbolicNum(NumType(I64Type), "sym-binary-add".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(F32Type), NumType(F32Type)) => + StagedSymbolicNum(NumType(F32Type), "sym-binary-add".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(F64Type), NumType(F64Type)) => + StagedSymbolicNum(NumType(F64Type), "sym-binary-add".reflectCtrlWith[SymVal](num.s, rhs.s)) } } def -(rhs: StagedSymbolicNum): StagedSymbolicNum = { - (num, rhs) match { - case (I32S(x), I32S(y)) => I32S("sym-binary-sub".reflectCtrlWith[SymVal](x, y)) - case (I64S(x), I64S(y)) => I64S("sym-binary-sub".reflectCtrlWith[SymVal](x, y)) - case (F32S(x), F32S(y)) => F32S("sym-binary-sub".reflectCtrlWith[SymVal](x, y)) - case (F64S(x), F64S(y)) => F64S("sym-binary-sub".reflectCtrlWith[SymVal](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-binary-sub".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(I64Type), NumType(I64Type)) => + StagedSymbolicNum(NumType(I64Type), "sym-binary-sub".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(F32Type), NumType(F32Type)) => + StagedSymbolicNum(NumType(F32Type), "sym-binary-sub".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(F64Type), NumType(F64Type)) => + StagedSymbolicNum(NumType(F64Type), "sym-binary-sub".reflectCtrlWith[SymVal](num.s, rhs.s)) } } def *(rhs: StagedSymbolicNum): StagedSymbolicNum = { - (num, rhs) match { - case (I32S(x), I32S(y)) => I32S("sym-binary-mul".reflectCtrlWith[SymVal](x, y)) - case (I64S(x), I64S(y)) => I64S("sym-binary-mul".reflectCtrlWith[SymVal](x, y)) - case (F32S(x), F32S(y)) => F32S("sym-binary-mul".reflectCtrlWith[SymVal](x, y)) - case (F64S(x), F64S(y)) => F64S("sym-binary-mul".reflectCtrlWith[SymVal](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-binary-mul".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(I64Type), NumType(I64Type)) => + StagedSymbolicNum(NumType(I64Type), "sym-binary-mul".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(F32Type), NumType(F32Type)) => + StagedSymbolicNum(NumType(F32Type), "sym-binary-mul".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(F64Type), NumType(F64Type)) => + StagedSymbolicNum(NumType(F64Type), "sym-binary-mul".reflectCtrlWith[SymVal](num.s, rhs.s)) } } def /(rhs: StagedSymbolicNum): StagedSymbolicNum = { - (num, rhs) match { - case (I32S(x), I32S(y)) => I32S("sym-binary-div".reflectCtrlWith[SymVal](x, y)) - case (I64S(x), I64S(y)) => I64S("sym-binary-div".reflectCtrlWith[SymVal](x, y)) - case (F32S(x), F32S(y)) => F32S("sym-binary-div".reflectCtrlWith[SymVal](x, y)) - case (F64S(x), F64S(y)) => F64S("sym-binary-div".reflectCtrlWith[SymVal](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-binary-div".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(I64Type), NumType(I64Type)) => + StagedSymbolicNum(NumType(I64Type), "sym-binary-div".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(F32Type), NumType(F32Type)) => + StagedSymbolicNum(NumType(F32Type), "sym-binary-div".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(F64Type), NumType(F64Type)) => + StagedSymbolicNum(NumType(F64Type), "sym-binary-div".reflectCtrlWith[SymVal](num.s, rhs.s)) } } def <<(rhs: StagedSymbolicNum): StagedSymbolicNum = { - (num, rhs) match { - case (I32S(x), I32S(y)) => I32S("sym-binary-shl".reflectCtrlWith[SymVal](x, y)) - case (I64S(x), I64S(y)) => I64S("sym-binary-shl".reflectCtrlWith[SymVal](x, y)) - case (F32S(x), F32S(y)) => F32S("sym-binary-shl".reflectCtrlWith[SymVal](x, y)) - case (F64S(x), F64S(y)) => F64S("sym-binary-shl".reflectCtrlWith[SymVal](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-binary-shl".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(I64Type), NumType(I64Type)) => + StagedSymbolicNum(NumType(I64Type), "sym-binary-shl".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(F32Type), NumType(F32Type)) => + StagedSymbolicNum(NumType(F32Type), "sym-binary-shl".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(F64Type), NumType(F64Type)) => + StagedSymbolicNum(NumType(F64Type), "sym-binary-shl".reflectCtrlWith[SymVal](num.s, rhs.s)) } } def >>(rhs: StagedSymbolicNum): StagedSymbolicNum = { - (num, rhs) match { - case (I32S(x), I32S(y)) => I32S("sym-binary-shr".reflectCtrlWith[SymVal](x, y)) - case (I64S(x), I64S(y)) => I64S("sym-binary-shr".reflectCtrlWith[SymVal](x, y)) - case (F32S(x), F32S(y)) => F32S("sym-binary-shr".reflectCtrlWith[SymVal](x, y)) - case (F64S(x), F64S(y)) => F64S("sym-binary-shr".reflectCtrlWith[SymVal](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-binary-shr".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(I64Type), NumType(I64Type)) => + StagedSymbolicNum(NumType(I64Type), "sym-binary-shr".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(F32Type), NumType(F32Type)) => + StagedSymbolicNum(NumType(F32Type), "sym-binary-shr".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(F64Type), NumType(F64Type)) => + StagedSymbolicNum(NumType(F64Type), "sym-binary-shr".reflectCtrlWith[SymVal](num.s, rhs.s)) } } def &(rhs: StagedSymbolicNum): StagedSymbolicNum = { - (num, rhs) match { - case (I32S(x), I32S(y)) => I32S("sym-binary-and".reflectCtrlWith[SymVal](x, y)) - case (I64S(x), I64S(y)) => I64S("sym-binary-and".reflectCtrlWith[SymVal](x, y)) - case (F32S(x), F32S(y)) => F32S("sym-binary-and".reflectCtrlWith[SymVal](x, y)) - case (F64S(x), F64S(y)) => F64S("sym-binary-and".reflectCtrlWith[SymVal](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-binary-and".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(I64Type), NumType(I64Type)) => + StagedSymbolicNum(NumType(I64Type), "sym-binary-and".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(F32Type), NumType(F32Type)) => + StagedSymbolicNum(NumType(F32Type), "sym-binary-and".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(F64Type), NumType(F64Type)) => + StagedSymbolicNum(NumType(F64Type), "sym-binary-and".reflectCtrlWith[SymVal](num.s, rhs.s)) } } def numEq(rhs: StagedSymbolicNum): StagedSymbolicNum = { - (num, rhs) match { - case (I32S(x), I32S(y)) => I32S("sym-relation-eq".reflectCtrlWith[SymVal](x, y)) - case (I64S(x), I64S(y)) => I32S("sym-relation-eq".reflectCtrlWith[SymVal](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-relation-eq".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(I64Type), NumType(I64Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-relation-eq".reflectCtrlWith[SymVal](num.s, rhs.s)) } } def numNe(rhs: StagedSymbolicNum): StagedSymbolicNum = { - (num, rhs) match { - case (I32S(x), I32S(y)) => I32S("sym-relation-ne".reflectCtrlWith[SymVal](x, y)) - case (I64S(x), I64S(y)) => I32S("sym-relation-ne".reflectCtrlWith[SymVal](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-relation-ne".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(I64Type), NumType(I64Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-relation-ne".reflectCtrlWith[SymVal](num.s, rhs.s)) } } def <(rhs: StagedSymbolicNum): StagedSymbolicNum = { - (num, rhs) match { - case (I32S(x), I32S(y)) => I32S("sym-relation-lt".reflectCtrlWith[SymVal](x, y)) - case (I64S(x), I64S(y)) => I32S("sym-relation-lt".reflectCtrlWith[SymVal](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-relation-lt".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(I64Type), NumType(I64Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-relation-lt".reflectCtrlWith[SymVal](num.s, rhs.s)) } } def ltu(rhs: StagedSymbolicNum): StagedSymbolicNum = { - (num, rhs) match { - case (I32S(x), I32S(y)) => I32S("relation-ltu".reflectCtrlWith[SymVal](x, y)) - case (I64S(x), I64S(y)) => I32S("relation-ltu".reflectCtrlWith[SymVal](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedSymbolicNum(NumType(I32Type), "relation-ltu".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(I64Type), NumType(I64Type)) => + StagedSymbolicNum(NumType(I32Type), "relation-ltu".reflectCtrlWith[SymVal](num.s, rhs.s)) } } def >(rhs: StagedSymbolicNum): StagedSymbolicNum = { - (num, rhs) match { - case (I32S(x), I32S(y)) => I32S("sym-relation-gt".reflectCtrlWith[SymVal](x, y)) - case (I64S(x), I64S(y)) => I32S("sym-relation-gt".reflectCtrlWith[SymVal](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-relation-gt".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(I64Type), NumType(I64Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-relation-gt".reflectCtrlWith[SymVal](num.s, rhs.s)) } } def gtu(rhs: StagedSymbolicNum): StagedSymbolicNum = { - (num, rhs) match { - case (I32S(x), I32S(y)) => I32S("sym-relation-gtu".reflectCtrlWith[SymVal](x, y)) - case (I64S(x), I64S(y)) => I32S("sym-relation-gtu".reflectCtrlWith[SymVal](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-relation-gtu".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(I64Type), NumType(I64Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-relation-gtu".reflectCtrlWith[SymVal](num.s, rhs.s)) } } def <=(rhs: StagedSymbolicNum): StagedSymbolicNum = { - (num, rhs) match { - case (I32S(x), I32S(y)) => I32S("sym-relation-le".reflectCtrlWith[SymVal](x, y)) - case (I64S(x), I64S(y)) => I32S("sym-relation-le".reflectCtrlWith[SymVal](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-relation-le".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(I64Type), NumType(I64Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-relation-le".reflectCtrlWith[SymVal](num.s, rhs.s)) } } def leu(rhs: StagedSymbolicNum): StagedSymbolicNum = { - (num, rhs) match { - case (I32S(x), I32S(y)) => I32S("sym-relation-leu".reflectCtrlWith[SymVal](x, y)) - case (I64S(x), I64S(y)) => I32S("sym-relation-leu".reflectCtrlWith[SymVal](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-relation-leu".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(I64Type), NumType(I64Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-relation-leu".reflectCtrlWith[SymVal](num.s, rhs.s)) } } def >=(rhs: StagedSymbolicNum): StagedSymbolicNum = { - (num, rhs) match { - case (I32S(x), I32S(y)) => I32S("sym-relation-ge".reflectCtrlWith[SymVal](x, y)) - case (I64S(x), I64S(y)) => I32S("sym-relation-ge".reflectCtrlWith[SymVal](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-relation-ge".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(I64Type), NumType(I64Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-relation-ge".reflectCtrlWith[SymVal](num.s, rhs.s)) } } def geu(rhs: StagedSymbolicNum): StagedSymbolicNum = { - (num, rhs) match { - case (I32S(x), I32S(y)) => I32S("sym-relation-geu".reflectCtrlWith[SymVal](x, y)) - case (I64S(x), I64S(y)) => I32S("sym-relation-geu".reflectCtrlWith[SymVal](x, y)) + (num.tipe, rhs.tipe) match { + case (NumType(I32Type), NumType(I32Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-relation-geu".reflectCtrlWith[SymVal](num.s, rhs.s)) + case (NumType(I64Type), NumType(I64Type)) => + StagedSymbolicNum(NumType(I32Type), "sym-relation-geu".reflectCtrlWith[SymVal](num.s, rhs.s)) } } } From 5656536f4e659dec0d875422bf2eb7e966816752 Mon Sep 17 00:00:00 2001 From: ahuoguo Date: Wed, 10 Sep 2025 00:12:03 -0400 Subject: [PATCH 35/53] correct behavior for global --- benchmarks/wasm/global-sym.wat | 21 +++++++++++++++++++ .../scala/wasm/StagedConcolicMiniWasm.scala | 2 +- .../genwasym/TestStagedConcolicEval.scala | 2 ++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 benchmarks/wasm/global-sym.wat diff --git a/benchmarks/wasm/global-sym.wat b/benchmarks/wasm/global-sym.wat new file mode 100644 index 00000000..54f55894 --- /dev/null +++ b/benchmarks/wasm/global-sym.wat @@ -0,0 +1,21 @@ +(module + (type (;0;) (func (result i32))) + (type (;1;) (func)) + (type (;2;) (func (param i32) (result i32))) + + (func (;0;) (type 2) (param i32) (result i32) + local.get 0 + global.set 0 + global.get 0 + ) + (func (;1;) (type 1) + i32.const 0 + i32.symbolic + ;; TODO Somehow this value is always 0? + call 0 + ) + (start 1) + (memory (;0;) 2) + (export "main" (func 1)) + (global (;0;) (mut i32) (i32.const 42)) +) \ No newline at end of file diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index aee63cda..2c41e2eb 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -1408,7 +1408,7 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { case Node(_, "global-get", List(i), _) => emit("Globals.get("); shallow(i); emit(")") case Node(_, "sym-global-get", List(i), _) => - emit("SymGlobal.get("); shallow(i); emit(")") + emit("SymGlobals.get("); shallow(i); emit(")") case Node(_, "global-set", List(i, value), _) => emit("Globals.set("); shallow(i); emit(", "); shallow(value); emit(")") case Node(_, "sym-global-set", List(i, s_value), _) => diff --git a/src/test/scala/genwasym/TestStagedConcolicEval.scala b/src/test/scala/genwasym/TestStagedConcolicEval.scala index 303a3fe5..851ef55d 100644 --- a/src/test/scala/genwasym/TestStagedConcolicEval.scala +++ b/src/test/scala/genwasym/TestStagedConcolicEval.scala @@ -76,6 +76,8 @@ class TestStagedConcolicEval extends FunSuite { // TODO: Waiting more symbolic operators' implementations // test("loop - concrete") { testFileConcreteCpp("./benchmarks/wasm/loop.wat", None, expect=Some(List(10))) } test("even-odd - concrete") { testFileConcreteCpp("./benchmarks/wasm/even_odd.wat", None, expect=Some(List(1))) } + // Try global + test("global - concrete") { testFileConcreteCpp("./benchmarks/wasm/global-sym.wat", None) } // TODO: Waiting symbolic memory's implementations // test("load - concrete") { testFileConcreteCpp("./benchmarks/wasm/load.wat", None, expect=Some(List(1))) } // test("btree - concrete") { testFileConcreteCpp("./benchmarks/wasm/btree/2o1u-unlabeled.wat") } From 51544e8fd0366e9d22614c8d86f4dbc0f30773c9 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Mon, 22 Sep 2025 22:49:02 -0400 Subject: [PATCH 36/53] make log function returning std::monostate/Unit type --- headers/wasm/concrete_rt.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/headers/wasm/concrete_rt.hpp b/headers/wasm/concrete_rt.hpp index 179ef245..77f7588a 100644 --- a/headers/wasm/concrete_rt.hpp +++ b/headers/wasm/concrete_rt.hpp @@ -11,18 +11,20 @@ #include #include -void info() { +inline std::monostate info() { #ifdef DEBUG std::cout << std::endl; #endif + return std::monostate{}; } template -void info(const T &first, const Args &...args) { +std::monostate info(const T &first, const Args &...args) { #ifdef DEBUG std::cout << first << " "; info(args...); #endif + return std::monostate{}; } struct Num { From 4bdc93b1fde5d3a95c0c4e571fbac50df22bfefe Mon Sep 17 00:00:00 2001 From: Alex Bai Date: Wed, 24 Sep 2025 11:30:40 +0800 Subject: [PATCH 37/53] Symbolic memory (#91) * move Memory_t to symbolic * failing cases for load * correct behavior for load/store (concrete) * test offset * test concolic, currently failed * oops * symstack and stack should be consistent * added test for extract(currently failing * extract & concat * some fixes, make btree example runable * tweak --------- Co-authored-by: butterunderflow --- benchmarks/wasm/Makefile | 7 + benchmarks/wasm/load-offset.wat | 19 ++ benchmarks/wasm/load-overflow1.wat | 19 ++ benchmarks/wasm/load-overflow2.wat | 19 ++ benchmarks/wasm/load.wat | 2 +- benchmarks/wasm/mem-sym-extract.wat | 32 +++ benchmarks/wasm/mem-sym.wat | 32 +++ headers/wasm/concrete_rt.hpp | 40 +--- headers/wasm/smt_solver.hpp | 19 ++ headers/wasm/symbolic_rt.hpp | 216 ++++++++++++++++-- .../scala/wasm/StagedConcolicMiniWasm.scala | 32 ++- .../genwasym/TestStagedConcolicEval.scala | 18 +- 12 files changed, 390 insertions(+), 65 deletions(-) create mode 100644 benchmarks/wasm/Makefile create mode 100644 benchmarks/wasm/load-offset.wat create mode 100644 benchmarks/wasm/load-overflow1.wat create mode 100644 benchmarks/wasm/load-overflow2.wat create mode 100644 benchmarks/wasm/mem-sym-extract.wat create mode 100644 benchmarks/wasm/mem-sym.wat diff --git a/benchmarks/wasm/Makefile b/benchmarks/wasm/Makefile new file mode 100644 index 00000000..bad47a22 --- /dev/null +++ b/benchmarks/wasm/Makefile @@ -0,0 +1,7 @@ +.PHONY: clean + +clean: + find . -type f -name '*.cpp' -delete + find . -type f -name '*.cpp.exe' -delete + find . -type d -name '*.dSYM' -exec rm -rf {} + + find . -type f -name '*.dot' -delete diff --git a/benchmarks/wasm/load-offset.wat b/benchmarks/wasm/load-offset.wat new file mode 100644 index 00000000..1c42df1b --- /dev/null +++ b/benchmarks/wasm/load-offset.wat @@ -0,0 +1,19 @@ +(module + (type (;0;) (func (result i32))) + (type (;1;) (func)) + (func (;0;) (type 0) (result i32) + i32.const 0 + i32.const 256 + i32.store + i32.const 0 + i32.load offset=1 + ) + (func (;1;) (type 1) + call 0 + ;; should be 1 + ;; drop + ) + (start 1) + (memory (;0;) 2) + (export "main" (func 1)) +) \ No newline at end of file diff --git a/benchmarks/wasm/load-overflow1.wat b/benchmarks/wasm/load-overflow1.wat new file mode 100644 index 00000000..9f005ea1 --- /dev/null +++ b/benchmarks/wasm/load-overflow1.wat @@ -0,0 +1,19 @@ +(module + (type (;0;) (func (result i32))) + (type (;1;) (func)) + (func (;0;) (type 0) (result i32) + i32.const 0 + i32.const 256 + i32.store + i32.const 1 + i32.load + ) + (func (;1;) (type 1) + call 0 + ;; should be 1 + ;; drop + ) + (start 1) + (memory (;0;) 2) + (export "main" (func 1)) +) \ No newline at end of file diff --git a/benchmarks/wasm/load-overflow2.wat b/benchmarks/wasm/load-overflow2.wat new file mode 100644 index 00000000..86c1a574 --- /dev/null +++ b/benchmarks/wasm/load-overflow2.wat @@ -0,0 +1,19 @@ +(module + (type (;0;) (func (result i32))) + (type (;1;) (func)) + (func (;0;) (type 0) (result i32) + i32.const 0 + i32.const 65536 + i32.store + i32.const 2 + i32.load + ) + (func (;1;) (type 1) + call 0 + ;; should be 1 + ;; drop + ) + (start 1) + (memory (;0;) 2) + (export "main" (func 1)) +) \ No newline at end of file diff --git a/benchmarks/wasm/load.wat b/benchmarks/wasm/load.wat index 916328aa..6c43d79b 100644 --- a/benchmarks/wasm/load.wat +++ b/benchmarks/wasm/load.wat @@ -10,7 +10,7 @@ ) (func (;1;) (type 1) call 0 - ;; should be 65536 + ;; should be 1 ;; drop ) (start 1) diff --git a/benchmarks/wasm/mem-sym-extract.wat b/benchmarks/wasm/mem-sym-extract.wat new file mode 100644 index 00000000..ca7e70eb --- /dev/null +++ b/benchmarks/wasm/mem-sym-extract.wat @@ -0,0 +1,32 @@ +(module + (type (;0;) (func (result i32))) + (type (;1;) (func)) + (type (;2;) (func (param i32) (result i32))) + (type (;3;) (func (param i32))) + (import "console" "assert" (func (type 3))) + (func (;1;) (type 2) (param i32) (result i32) + i32.const 0 + local.get 0 + i32.store + i32.const 0 + i32.load + i32.const 1 + i32.eq + if (result i32) ;; if x == 256 + i32.const 1 ;; return 1 + else + i32.const 0 + call 0 ;; assert false + i32.const 1 ;; to satisfy the type checker, this line will never be reached + end + ) + (func (;2;) (type 1) + i32.const 0 + i32.symbolic ;; call it x + call 1 + ) + (start 2) + (memory (;0;) 2) + (export "main" (func 1)) + (global (;0;) (mut i32) (i32.const 42)) +) \ No newline at end of file diff --git a/benchmarks/wasm/mem-sym.wat b/benchmarks/wasm/mem-sym.wat new file mode 100644 index 00000000..c7094008 --- /dev/null +++ b/benchmarks/wasm/mem-sym.wat @@ -0,0 +1,32 @@ +(module + (type (;0;) (func (result i32))) + (type (;1;) (func)) + (type (;2;) (func (param i32) (result i32))) + (type (;3;) (func (param i32))) + (import "console" "assert" (func (type 3))) + (func (;1;) (type 2) (param i32) (result i32) + i32.const 0 + local.get 0 + i32.store + i32.const 0 + i32.load + i32.const 25 + i32.eq + if (result i32) ;; if x == 25 + i32.const 1 ;; return 1 + else + i32.const 0 + call 0 ;; assert false + i32.const 1 ;; to satisfy the type checker, this line will never be reached + end + ) + (func (;2;) (type 1) + i32.const 0 + i32.symbolic ;; call it x + call 1 + ) + (start 2) + (memory (;0;) 2) + (export "main" (func 1)) + (global (;0;) (mut i32) (i32.const 42)) +) \ No newline at end of file diff --git a/headers/wasm/concrete_rt.hpp b/headers/wasm/concrete_rt.hpp index 77f7588a..5d9243c4 100644 --- a/headers/wasm/concrete_rt.hpp +++ b/headers/wasm/concrete_rt.hpp @@ -76,6 +76,7 @@ class Stack_t { Num pop() { #ifdef DEBUG assert(count > 0 && "Stack underflow"); + printf("[Debug] poping from stack, size of concrete stack is: %d\n", count); #endif Num num = stack_ptr[count - 1]; count--; @@ -101,9 +102,13 @@ class Stack_t { 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; @@ -197,39 +202,4 @@ static std::monostate unreachable() { static int32_t pagesize = 65536; static int32_t page_count = 0; -struct Memory_t { - std::vector memory; - Memory_t(int32_t init_page_count) : memory(init_page_count * pagesize) {} - - int32_t loadInt(int32_t base, int32_t offset) { - return *reinterpret_cast(static_cast(memory.data()) + - base + offset); - } - - std::monostate storeInt(int32_t base, int32_t offset, int32_t value) { - *reinterpret_cast(static_cast(memory.data()) + base + - offset) = 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) { - if (delta <= 0) { - return memory.size(); - } - - try { - 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; - } - } -}; - -static Memory_t Memory(1); // 1 page memory - #endif // WASM_CONCRETE_RT_HPP \ No newline at end of file diff --git a/headers/wasm/smt_solver.hpp b/headers/wasm/smt_solver.hpp index 504422f7..64952bcc 100644 --- a/headers/wasm/smt_solver.hpp +++ b/headers/wasm/smt_solver.hpp @@ -71,6 +71,9 @@ inline z3::expr Solver::build_z3_expr(const SymVal &sym_val) { } else if (auto concrete = std::dynamic_pointer_cast(sym_val.symptr)) { return z3_ctx.bv_val(concrete->value.value, 32); + } else if (auto smallbv = + std::dynamic_pointer_cast(sym_val.symptr)) { + return z3_ctx.bv_val(smallbv->get_value(), smallbv->get_size()); } else if (auto binary = std::dynamic_pointer_cast(sym_val.symptr)) { auto bit_width = 32; @@ -119,7 +122,23 @@ inline z3::expr Solver::build_z3_expr(const SymVal &sym_val) { case DIV: { return left / right; } + case B_AND: { + 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 extract = dynamic_cast(sym_val.symptr.get())) { + assert(extract); + int high = extract->high * 8 - 1; + int low = extract->low * 8 - 8; + auto s = build_z3_expr(extract->value); + auto res = s.extract(high, low); + return res; } throw std::runtime_error("Unsupported symbolic value type"); } diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 137d071d..d7aa70ed 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -6,6 +6,7 @@ #include "utils.hpp" #include #include +#include #include #include #include @@ -48,8 +49,34 @@ class SymConcrete : public Symbolic { SymConcrete(Num num) : value(num) {} }; +class SmallBV : public Symbolic { +public: + SmallBV(int size, int64_t value) : size(size), value(value) {} + int get_size() const { return size; } + int64_t get_value() const { return value; } + +private: + int size; + int64_t value; +}; + struct SymBinary; +enum Operation { + ADD, // Addition + SUB, // Subtraction + MUL, // Multiplication + DIV, // Division + EQ, // Equal + NEQ, // Not equal + LT, // Less than + LEQ, // Less than or equal + GT, // Greater than + GEQ, // Greater than or equal + B_AND, // Bitwise AND + CONCAT, // Byte-level concatenation +}; + struct SymVal { std::shared_ptr symptr; @@ -59,7 +86,7 @@ struct SymVal { // data structure operations SymVal makeSymbolic() const; - // arithmetic operations + // bitvector arithmetic operations SymVal is_zero() const; SymVal add(const SymVal &other) const; SymVal minus(const SymVal &other) const; @@ -68,12 +95,19 @@ struct SymVal { SymVal eq(const SymVal &other) const; SymVal neq(const SymVal &other) const; SymVal lt(const SymVal &other) const; - SymVal leq(const SymVal &other) const; + SymVal le(const SymVal &other) const; SymVal gt(const SymVal &other) const; - SymVal geq(const SymVal &other) const; + SymVal ge(const SymVal &other) const; SymVal negate() const; + SymVal bitwise_and(const SymVal &other) const; + SymVal concat(const SymVal &other) const; + SymVal extract(int high, int low) const; + // TODO: add bitwise operations, and use the underlying bitvector theory bool is_concrete() const; + +private: + static SymVal make_binary(Operation op, const SymVal &lhs, const SymVal &rhs); }; static SymVal make_symbolic(int index) { @@ -84,7 +118,17 @@ inline SymVal Concrete(Num num) { return SymVal(std::make_shared(num)); } -enum Operation { ADD, SUB, MUL, DIV, EQ, NEQ, LT, LEQ, GT, GEQ }; +// 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 : Symbolic { + SymVal value; + int high; + int low; + + SymExtract(SymVal value, int high, int low) + : value(value), high(high), low(low) {} +}; struct SymBinary : Symbolic { Operation op; @@ -96,47 +140,70 @@ struct SymBinary : Symbolic { }; inline SymVal SymVal::add(const SymVal &other) const { - return SymVal(std::make_shared(ADD, *this, other)); + return make_binary(ADD, *this, other); } inline SymVal SymVal::minus(const SymVal &other) const { - return SymVal(std::make_shared(SUB, *this, other)); + return make_binary(SUB, *this, other); } inline SymVal SymVal::mul(const SymVal &other) const { - return SymVal(std::make_shared(MUL, *this, other)); + return make_binary(MUL, *this, other); } inline SymVal SymVal::div(const SymVal &other) const { - return SymVal(std::make_shared(DIV, *this, other)); + return make_binary(DIV, *this, other); } inline SymVal SymVal::eq(const SymVal &other) const { - return SymVal(std::make_shared(EQ, *this, other)); + return make_binary(EQ, *this, other); } inline SymVal SymVal::neq(const SymVal &other) const { - return SymVal(std::make_shared(NEQ, *this, other)); + return make_binary(NEQ, *this, other); } + inline SymVal SymVal::lt(const SymVal &other) const { - return SymVal(std::make_shared(LT, *this, other)); + return make_binary(LT, *this, other); } -inline SymVal SymVal::leq(const SymVal &other) const { - return SymVal(std::make_shared(LEQ, *this, other)); + +inline SymVal SymVal::le(const SymVal &other) const { + return make_binary(LEQ, *this, other); } + inline SymVal SymVal::gt(const SymVal &other) const { - return SymVal(std::make_shared(GT, *this, other)); + return make_binary(GT, *this, other); } -inline SymVal SymVal::geq(const SymVal &other) const { - return SymVal(std::make_shared(GEQ, *this, other)); + +inline SymVal SymVal::ge(const SymVal &other) const { + return make_binary(GEQ, *this, other); } + inline SymVal SymVal::is_zero() const { - return SymVal(std::make_shared(EQ, *this, Concrete(I32V(0)))); + return make_binary(EQ, *this, Concrete(I32V(0))); } + inline SymVal SymVal::negate() const { - return SymVal(std::make_shared(EQ, *this, Concrete(I32V(0)))); + return make_binary(EQ, *this, Concrete(I32V(0))); } +inline SymVal SymVal::concat(const SymVal &other) const { + return make_binary(CONCAT, *this, other); +} + +inline SymVal SymVal::extract(int high, int low) const { + assert(high >= low && "Invalid extract range"); + return SymVal(std::make_shared(*this, high, low)); +} + +inline SymVal SymVal::bitwise_and(const SymVal &other) const { + return make_binary(B_AND, *this, other); +} +inline SymVal SymVal::make_binary(Operation op, const SymVal &lhs, + const SymVal &rhs) { + assert(lhs.symptr != nullptr && rhs.symptr != nullptr); + return SymVal(std::make_shared(op, lhs, rhs)); +} inline SymVal SymVal::makeSymbolic() const { auto concrete = dynamic_cast(symptr.get()); if (concrete) { @@ -178,6 +245,7 @@ class SymStack_t { 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); stack[i - offset] = stack[i]; } stack.resize(n - offset); @@ -723,6 +791,17 @@ static SymEnv_t SymEnv; static Num eval_sym_expr(const SymVal &sym, SymEnv_t &sym_env) { if (auto concrete = dynamic_cast(sym.symptr.get())) { return concrete->value; + } else if (auto extract = dynamic_cast(sym.symptr.get())) { + auto value = 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 = (value.toInt() >> (low * 8)) & mask; + return Num(I64V(extracted_value)); + } else if (auto smallbv = dynamic_cast(sym.symptr.get())) { + return Num(I64V(smallbv->get_value())); } else if (auto operation = dynamic_cast(sym.symptr.get())) { // If it's a operation, we need to evaluate it auto lhs = eval_sym_expr(operation->lhs, sym_env); @@ -744,8 +823,18 @@ static Num eval_sym_expr(const SymVal &sym, SymEnv_t &sym_env) { return lhs > rhs; case GEQ: return lhs >= rhs; + case NEQ: + return lhs != rhs; + case EQ: + return lhs == rhs; + case B_AND: + return Num(I64V(lhs.value & rhs.value)); + case CONCAT: + // we must know the size of lhs and rhs in bytes to support concat + throw std::runtime_error( + "Concatenation operation not supported in evaluation"); default: - throw std::runtime_error("Operation not supported: " + + throw std::runtime_error("Operation not supported in evaluation: " + std::to_string(operation->op)); } } else if (auto symbol = dynamic_cast(sym.symptr.get())) { @@ -798,4 +887,93 @@ inline std::monostate Snapshot_t::resume_execution(SymEnv_t &sym_env, return cont(mcont); } +struct Memory_t { + // TODO: We assign a SymVal to each byte in memory + std::vector> memory; + + Memory_t(int32_t init_page_count) : memory(init_page_count * pagesize) {} + + int32_t loadInt(int32_t base, int32_t offset) { + // just load a 4-byte integer from memory of the vector + int32_t addr = base + offset; + assert(addr + 3 < memory.size()); + int32_t result = 0; + // Little-endian: lowest byte at lowest address + for (int i = 0; i < 4; ++i) { + result |= static_cast(memory[addr + i].first) << (8 * i); + } + return result; + } + + // TODO: 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 + SymVal loadSym(int32_t base, int32_t offset) { + int32_t addr = base + offset; + assert(addr + 3 < memory.size()); + SymVal s0 = memory[addr].second; + if (s0.symptr == nullptr) { + s0 = SymVal(std::make_shared(8, 0)); + } + SymVal s1 = memory[addr + 1].second; + if (s1.symptr == nullptr) { + s1 = SymVal(std::make_shared(8, 0)); + } + SymVal s2 = memory[addr + 2].second; + if (s2.symptr == nullptr) { + s2 = SymVal(std::make_shared(8, 0)); + } + SymVal s3 = memory[addr + 3].second; + if (s3.symptr == nullptr) { + s3 = SymVal(std::make_shared(8, 0)); + } + return s0.concat(s1).concat(s2).concat(s3); + } + + // 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); + memory[addr].second = s0; + memory[addr + 1].second = s1; + memory[addr + 2].second = s2; + memory[addr + 3].second = s3; + return std::monostate{}; + } + + 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 + assert(addr + 3 < memory.size()); + for (int i = 0; i < 4; ++i) { + memory[addr + i].first = static_cast((value >> (8 * i)) & 0xFF); + // Optionally, update memory[addr + i].second (SymVal) if needed + } + 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) { + if (delta <= 0) { + return memory.size(); + } + + try { + 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; + } + } +}; + +static Memory_t Memory(1); // 1 page memory + #endif // WASM_SYMBOLIC_RT_HPP \ No newline at end of file diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index 2c41e2eb..5b5ae8de 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -220,7 +220,7 @@ trait StagedWasmEvaluator extends SAIOps { val (ty2, newCtx2) = newCtx1.pop() val addr = Stack.popC(ty2) val symAddr = Stack.popS(ty2) - Memory.storeInt(addr.toInt, offset, value.toInt) + Memory.storeInt(addr.toInt, offset, (value.toInt, symValue)) eval(rest, kont, mkont, trail)(newCtx2) case Nop => eval(rest, kont, mkont, trail) case Load(LoadOp(align, offset, ty, None, None)) => @@ -245,7 +245,7 @@ trait StagedWasmEvaluator extends SAIOps { val retSym = "Concrete".reflectCtrlWith[SymVal](retNum) Stack.pushC(StagedConcreteNum(NumType(I32Type), retNum)) Stack.pushS(StagedSymbolicNum(NumType(I32Type), retSym)) - val newCtx2 = ctx.push(NumType(I32Type)) + val newCtx2 = newCtx.push(NumType(I32Type)) eval(rest, kont, mkont, trail)(newCtx2) case MemoryFill => ??? case Unreachable => unreachable() @@ -767,9 +767,11 @@ trait StagedWasmEvaluator extends SAIOps { } object Memory { - def storeInt(base: Rep[Int], offset: Int, value: Rep[Int]): Rep[Unit] = { - "memory-store-int".reflectCtrlWith[Unit](base, offset, value) - // todo: store symbolic value to memory via extract/concat operation + // TODO: why this is only one function, rather than `storeInC` and `storeInS`? + // TODO: what should the type of SymVal be? + def storeInt(base: Rep[Int], offset: Int, value: (Rep[Int], StagedSymbolicNum)): Rep[Unit] = { + "memory-store-int".reflectCtrlWith[Unit](base, offset, value._1) + "sym-store-int".reflectCtrlWith[Unit](base, offset, value._2.s) } def loadIntC(base: Rep[Int], offset: Int): StagedConcreteNum = { @@ -777,7 +779,7 @@ trait StagedWasmEvaluator extends SAIOps { } def loadIntS(base: Rep[Int], offset: Int): StagedSymbolicNum = { - StagedSymbolicNum(NumType(I32Type), "sym-load-int-todo".reflectCtrlWith[SymVal](base, offset)) + StagedSymbolicNum(NumType(I32Type), "sym-load-int".reflectCtrlWith[SymVal](base, offset)) } // Returns the previous memory size on success, or -1 if the memory cannot be grown. @@ -1405,6 +1407,14 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { emit("Memory.grow("); shallow(delta); emit(")") case Node(_, "stack-size", _, _) => emit("Stack.size()") + // Symbolic Memory + case Node(_, "sym-store-int", List(base, offset, s_value), _) => + emit("Memory.storeSym("); shallow(base); emit(", "); shallow(offset); emit(", "); shallow(s_value); emit(")") + case Node(_, "sym-load-int", List(base, offset), _) => + emit("Memory.loadSym("); shallow(base); emit(", "); shallow(offset); emit(")") + case Node(_, "sym-memory-grow", List(delta), _) => + emit("SymMemory.grow("); shallow(delta); emit(")") + // Globals case Node(_, "global-get", List(i), _) => emit("Globals.get("); shallow(i); emit(")") case Node(_, "sym-global-get", List(i), _) => @@ -1464,11 +1474,15 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { shallow(lhs); emit(".mul("); shallow(rhs); emit(")") case Node(_, "sym-binary-div", List(lhs, rhs), _) => shallow(lhs); emit(".div("); shallow(rhs); emit(")") + case Node(_, "sym-binary-and", List(lhs, rhs), _) => + shallow(lhs); emit(".bitwise_and("); shallow(rhs); emit(")") case Node(_, "sym-relation-le", List(lhs, rhs), _) => - shallow(lhs); emit(".leq("); shallow(rhs); emit(")") + shallow(lhs); emit(".le("); shallow(rhs); emit(")") case Node(_, "sym-relation-leu", List(lhs, rhs), _) => shallow(lhs); emit(".leu("); shallow(rhs); emit(")") - case Node(_, "sym-relation-ge", List(lhs, rhs), _) => + case Node(_, "sym-relation-lt", List(lhs, rhs), _) => + shallow(lhs); emit(".lt("); shallow(rhs); emit(")") + case Node(_, "sym-relation-ge", List(lhs, rhs), _) => shallow(lhs); emit(".ge("); shallow(rhs); emit(")") case Node(_, "sym-relation-geu", List(lhs, rhs), _) => shallow(lhs); emit(".geu("); shallow(rhs); emit(")") @@ -1476,6 +1490,8 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { shallow(lhs); emit(".eq("); shallow(rhs); emit(")") case Node(_, "sym-relation-ne", List(lhs, rhs), _) => shallow(lhs); emit(".neq("); shallow(rhs); emit(")") + case Node(_, "sym-relation-gt", List(lhs, rhs), _) => + shallow(lhs); emit(".gt("); shallow(rhs); emit(")") case Node(_, "num-to-int", List(num), _) => shallow(num); emit(".toInt()") case Node(_, "make-symbolic", List(num), _) => diff --git a/src/test/scala/genwasym/TestStagedConcolicEval.scala b/src/test/scala/genwasym/TestStagedConcolicEval.scala index 851ef55d..03a5df09 100644 --- a/src/test/scala/genwasym/TestStagedConcolicEval.scala +++ b/src/test/scala/genwasym/TestStagedConcolicEval.scala @@ -66,6 +66,16 @@ class TestStagedConcolicEval extends FunSuite { testFileConcolicCpp("./benchmarks/wasm/staged/simple_global.wat", Some("real_main"), exitByCoverage=true) } + test("mem-sym-concolic") { + testFileConcolicCpp("./benchmarks/wasm/mem-sym.wat", None, exitByCoverage=true) + } + + test("mem-sym-extract-concolic") { + testFileConcolicCpp("./benchmarks/wasm/mem-sym-extract.wat", None, exitByCoverage=true) + } + test("btree-bug-finding-concolic") { testFileConcolicCpp("./benchmarks/wasm/btree/2o1u-unlabeled.wat") } + + test("return-poly - concrete") { testFileConcreteCpp("./benchmarks/wasm/staged/return_poly.wat", Some("$real_main"), expect=Some(List(42))) } @@ -76,10 +86,14 @@ class TestStagedConcolicEval extends FunSuite { // TODO: Waiting more symbolic operators' implementations // test("loop - concrete") { testFileConcreteCpp("./benchmarks/wasm/loop.wat", None, expect=Some(List(10))) } test("even-odd - concrete") { testFileConcreteCpp("./benchmarks/wasm/even_odd.wat", None, expect=Some(List(1))) } - // Try global test("global - concrete") { testFileConcreteCpp("./benchmarks/wasm/global-sym.wat", None) } // TODO: Waiting symbolic memory's implementations - // test("load - concrete") { testFileConcreteCpp("./benchmarks/wasm/load.wat", None, expect=Some(List(1))) } + test("load - concrete") { testFileConcreteCpp("./benchmarks/wasm/load.wat", None, expect=Some(List(1))) } + test("load overflow 1 - concrete") { testFileConcreteCpp("./benchmarks/wasm/load-overflow1.wat", None, expect=Some(List(1))) } + test("load overflow 2 - concrete") { testFileConcreteCpp("./benchmarks/wasm/load-overflow2.wat", None, expect=Some(List(1))) } + + test("load offset - concrete") { testFileConcreteCpp("./benchmarks/wasm/load-offset.wat", None, expect=Some(List(1))) } + // test("btree - concrete") { testFileConcreteCpp("./benchmarks/wasm/btree/2o1u-unlabeled.wat") } test("fib - concrete") { testFileConcreteCpp("./benchmarks/wasm/fib.wat", None, expect=Some(List(144))) } test("tribonacci - concrete") { testFileConcreteCpp("./benchmarks/wasm/tribonacci.wat", None, expect=Some(List(504))) } From 74732ad5f4f47883a73bc57f9574c3f6724d2caa Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Wed, 24 Sep 2025 14:35:15 -0400 Subject: [PATCH 38/53] fix: high bits should be concat first --- benchmarks/wasm/mem-sym.wat | 15 +++++++++++++-- headers/wasm/symbolic_rt.hpp | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/benchmarks/wasm/mem-sym.wat b/benchmarks/wasm/mem-sym.wat index c7094008..45b9d1c3 100644 --- a/benchmarks/wasm/mem-sym.wat +++ b/benchmarks/wasm/mem-sym.wat @@ -13,11 +13,22 @@ i32.const 25 i32.eq if (result i32) ;; if x == 25 - i32.const 1 ;; return 1 - else i32.const 0 call 0 ;; assert false i32.const 1 ;; to satisfy the type checker, this line will never be reached + else + i32.const 1 + i32.load + i32.const 1 + i32.eq + if (result i32) ;; if x >> 8 == 1 + i32.const 0 + call 0 ;; assert false + i32.const 1 ;; to satisfy the type checker, this line will never be reached + else + i32.const 1 + end + i32.const 1 end ) (func (;2;) (type 1) diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index d7aa70ed..4c69f73b 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -927,7 +927,7 @@ struct Memory_t { if (s3.symptr == nullptr) { s3 = SymVal(std::make_shared(8, 0)); } - return s0.concat(s1).concat(s2).concat(s3); + return s3.concat(s2).concat(s1).concat(s0); } // Store a 4-byte symbolic value to memory From e3f8488e4aa2841d7fedd71d6592e18d0b875626 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Sun, 28 Sep 2025 20:49:02 -0400 Subject: [PATCH 39/53] make btree example work with concolic execution --- benchmarks/wasm/btree/2o1u-unlabeled.wat | 5 +- headers/wasm/concolic_driver.hpp | 4 +- headers/wasm/symbolic_rt.hpp | 94 ++++++++++++------- .../genwasym/TestStagedConcolicEval.scala | 2 +- 4 files changed, 68 insertions(+), 37 deletions(-) diff --git a/benchmarks/wasm/btree/2o1u-unlabeled.wat b/benchmarks/wasm/btree/2o1u-unlabeled.wat index 096f1004..443bf5d4 100644 --- a/benchmarks/wasm/btree/2o1u-unlabeled.wat +++ b/benchmarks/wasm/btree/2o1u-unlabeled.wat @@ -2626,9 +2626,12 @@ i32.and drop) (func (;7;) (type 4) - i32.const 3 i32.const 2 + i32.symbolic i32.const 1 + i32.symbolic + i32.const 0 + i32.symbolic call 6) (memory (;0;) 2) (export "main" (func 7)) diff --git a/headers/wasm/concolic_driver.hpp b/headers/wasm/concolic_driver.hpp index 81b5d5ad..06b3459c 100644 --- a/headers/wasm/concolic_driver.hpp +++ b/headers/wasm/concolic_driver.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -83,7 +84,8 @@ inline void ConcolicDriver::run() { GENSYM_INFO("Execution finished successfully with symbolic environment:"); GENSYM_INFO(SymEnv.to_string()); - } catch (...) { + } catch (std::runtime_error &e) { + std::cout << "Caught runtime error: " << e.what() << std::endl; ExploreTree.fillFailedNode(); GENSYM_INFO("Caught runtime error with symbolic environment:"); GENSYM_INFO(SymEnv.to_string()); diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 4c69f73b..96b2f039 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -56,7 +56,7 @@ class SmallBV : public Symbolic { int64_t get_value() const { return value; } private: - int size; + int size; // in bits int64_t value; }; @@ -77,10 +77,13 @@ enum Operation { CONCAT, // Byte-level concatenation }; +static std::shared_ptr ZERO = + std::make_shared(I32V(0)); + struct SymVal { std::shared_ptr symptr; - SymVal() : symptr(nullptr) {} + SymVal() : symptr(ZERO) {} SymVal(std::shared_ptr symptr) : symptr(symptr) {} // data structure operations @@ -283,12 +286,13 @@ class SymFrames_t { SymVal get(int index) { // Get the symbolic value at the given frame index - return stack[stack.size() - 1 - index]; + auto res = stack[stack.size() - 1 - index]; + return res; } void set(int index, SymVal val) { // Set the symbolic value at the given index - // Not implemented yet + assert(val.symptr != nullptr); stack[stack.size() - 1 - index] = val; } @@ -786,61 +790,72 @@ class SymEnv_t { static SymEnv_t SymEnv; +struct EvalRes { + Num value; + int width; // in bits + EvalRes(Num value, int width) : value(value), width(width) {} +}; + // TODO: reduce the re-computation of the same symbolic expression, it's better // if it can be done by the smt solver -static Num eval_sym_expr(const SymVal &sym, SymEnv_t &sym_env) { +static EvalRes eval_sym_expr(const SymVal &sym, SymEnv_t &sym_env) { + assert(sym.symptr != nullptr && "Symbolic expression is null"); if (auto concrete = dynamic_cast(sym.symptr.get())) { - return concrete->value; + return EvalRes(concrete->value, 32); } else if (auto extract = dynamic_cast(sym.symptr.get())) { - auto value = eval_sym_expr(extract->value, sym_env); + 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 = (value.toInt() >> (low * 8)) & mask; - return Num(I64V(extracted_value)); + int64_t extracted_value = (res.value.toInt() >> (low * 8)) & mask; + return EvalRes(Num(I64V(extracted_value)), size * 8); } else if (auto smallbv = dynamic_cast(sym.symptr.get())) { - return Num(I64V(smallbv->get_value())); + return EvalRes(Num(I64V(smallbv->get_value())), smallbv->get_size()); } else if (auto operation = dynamic_cast(sym.symptr.get())) { // If it's a operation, we need to evaluate it - auto lhs = eval_sym_expr(operation->lhs, sym_env); - auto rhs = eval_sym_expr(operation->rhs, sym_env); + 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; switch (operation->op) { case ADD: - return lhs + rhs; + return EvalRes(lhs + rhs, 32); case SUB: - return lhs - rhs; + return EvalRes(lhs - rhs, 32); case MUL: - return lhs * rhs; + return EvalRes(lhs * rhs, 32); case DIV: - return lhs / rhs; + return EvalRes(lhs / rhs, 32); case LT: - return lhs < rhs; + return EvalRes(lhs < rhs, 32); case LEQ: - return lhs <= rhs; + return EvalRes(lhs <= rhs, 32); case GT: - return lhs > rhs; + return EvalRes(lhs > rhs, 32); case GEQ: - return lhs >= rhs; + return EvalRes(lhs >= rhs, 32); case NEQ: - return lhs != rhs; + return EvalRes(lhs != rhs, 32); case EQ: - return lhs == rhs; + return EvalRes(lhs == rhs, 32); case B_AND: - return Num(I64V(lhs.value & rhs.value)); - case CONCAT: - // we must know the size of lhs and rhs in bytes to support concat - throw std::runtime_error( - "Concatenation operation not supported in evaluation"); + return EvalRes(Num(I64V(lhs.value & rhs.value)), 32); + case CONCAT: { + auto lhs_width = lhs_res.width; + auto rhs_width = rhs_res.width; + auto conc_value = (lhs.value << rhs_width) | (rhs.value); + auto new_width = lhs_width + rhs_width; + return EvalRes(Num(I64V(conc_value)), new_width); + } default: - throw std::runtime_error("Operation not supported in evaluation: " + - std::to_string(operation->op)); + assert(false && "Operation not supported in evaluation"); } } 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 sym_env.read(sym); + return EvalRes(sym_env.read(sym), 32); } throw std::runtime_error("Not supported symbolic expression"); } @@ -850,7 +865,8 @@ static void resume_conc_stack(const SymStack_t &sym_stack, Stack_t &stack, stack.resize(sym_stack.size()); for (size_t i = 0; i < sym_stack.size(); ++i) { auto sym = sym_stack[i]; - auto conc = eval_sym_expr(sym, sym_env); + auto res = eval_sym_expr(sym, sym_env); + auto conc = res.value; stack.set_from_front(i, conc); } } @@ -860,7 +876,9 @@ static void resume_conc_frames(const SymFrames_t &sym_frame, Frames_t &frames, frames.resize(sym_frame.size()); for (size_t i = 0; i < sym_frame.size(); ++i) { auto sym = sym_frame[i]; - auto conc = eval_sym_expr(sym, sym_env); + assert(sym.symptr != nullptr); + auto res = eval_sym_expr(sym, sym_env); + auto conc = res.value; frames.set_from_front(i, conc); } } @@ -896,7 +914,9 @@ struct Memory_t { int32_t loadInt(int32_t base, int32_t offset) { // just load a 4-byte integer from memory of the vector int32_t addr = base + offset; - assert(addr + 3 < memory.size()); + if (!(addr + 3 < memory.size())) { + 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) { @@ -910,22 +930,28 @@ struct Memory_t { // Load a 4-byte symbolic value from memory SymVal loadSym(int32_t base, int32_t offset) { int32_t addr = base + offset; - assert(addr + 3 < memory.size()); + if (!(addr + 3 < memory.size())) { + throw std::runtime_error("Invalid memory access" + std::to_string(addr)); + } SymVal s0 = memory[addr].second; if (s0.symptr == nullptr) { s0 = SymVal(std::make_shared(8, 0)); + memory[addr].second = s0; } SymVal s1 = memory[addr + 1].second; if (s1.symptr == nullptr) { s1 = SymVal(std::make_shared(8, 0)); + memory[addr + 1].second = s1; } SymVal s2 = memory[addr + 2].second; if (s2.symptr == nullptr) { s2 = SymVal(std::make_shared(8, 0)); + memory[addr + 2].second = s2; } SymVal s3 = memory[addr + 3].second; if (s3.symptr == nullptr) { s3 = SymVal(std::make_shared(8, 0)); + memory[addr + 3].second = s3; } return s3.concat(s2).concat(s1).concat(s0); } diff --git a/src/test/scala/genwasym/TestStagedConcolicEval.scala b/src/test/scala/genwasym/TestStagedConcolicEval.scala index 03a5df09..ada087cc 100644 --- a/src/test/scala/genwasym/TestStagedConcolicEval.scala +++ b/src/test/scala/genwasym/TestStagedConcolicEval.scala @@ -73,7 +73,7 @@ class TestStagedConcolicEval extends FunSuite { test("mem-sym-extract-concolic") { testFileConcolicCpp("./benchmarks/wasm/mem-sym-extract.wat", None, exitByCoverage=true) } - test("btree-bug-finding-concolic") { testFileConcolicCpp("./benchmarks/wasm/btree/2o1u-unlabeled.wat") } + test("btree-bug-finding-concolic") { testFileConcolicCpp("./benchmarks/wasm/btree/2o1u-unlabeled.wat", exitByCoverage = true) } test("return-poly - concrete") { From 827f2b0bf2e4914bc64a6fd73b63b2e4969abf78 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Fri, 3 Oct 2025 13:20:55 -0400 Subject: [PATCH 40/53] preallocate pages for the memory --- headers/wasm/symbolic_rt.hpp | 40 +++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 96b2f039..97568928 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -4,6 +4,7 @@ #include "concrete_rt.hpp" #include "controls.hpp" #include "utils.hpp" +#include "wasm/profile.hpp" #include #include #include @@ -645,6 +646,14 @@ class ExploreTree_t { 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; @@ -660,6 +669,7 @@ class ExploreTree_t { } std::monostate moveCursor(bool branch, Snapshot_t snapshot) { + Profile.step(ProfileKind::CURSOR_MOVE); assert(cursor != nullptr); auto if_else_node = dynamic_cast(cursor->node.get()); assert( @@ -905,17 +915,30 @@ inline std::monostate Snapshot_t::resume_execution(SymEnv_t &sym_env, return cont(mcont); } + +static const int PRE_ALLOC_PAGES = 20; + struct Memory_t { // TODO: We assign a SymVal to each byte in memory std::vector> memory; - - Memory_t(int32_t init_page_count) : memory(init_page_count * pagesize) {} + int page_count; + int allocated_pages; + + Memory_t(int32_t init_page_count) + : memory(PRE_ALLOC_PAGES * pagesize), page_count(init_page_count), + allocated_pages(PRE_ALLOC_PAGES) { + // warm up the memory with zero bytes + for (auto &byte : memory) { + byte.first = 0; + byte.second = SymVal(); + } + } int32_t loadInt(int32_t base, int32_t offset) { // just load a 4-byte integer from memory of the vector int32_t addr = base + offset; if (!(addr + 3 < memory.size())) { - throw std::runtime_error("Invalid memory access" + std::to_string(addr)); + throw std::runtime_error("Invalid memory access " + std::to_string(addr)); } int32_t result = 0; // Little-endian: lowest byte at lowest address @@ -931,7 +954,7 @@ struct Memory_t { SymVal loadSym(int32_t base, int32_t offset) { int32_t addr = base + offset; if (!(addr + 3 < memory.size())) { - throw std::runtime_error("Invalid memory access" + std::to_string(addr)); + throw std::runtime_error("Invalid memory access " + std::to_string(addr)); } SymVal s0 = memory[addr].second; if (s0.symptr == nullptr) { @@ -985,11 +1008,18 @@ struct Memory_t { // 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(ProfileKind::MEM_GROW); if (delta <= 0) { - return memory.size(); + 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; From 75b6347802483974bf643633df56bd72d4e4fbea Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Sat, 4 Oct 2025 19:57:15 -0400 Subject: [PATCH 41/53] remove some unperformant code 1. dont intialize memory twice 2. dont allocate memory for unuse symbolic memory, because we need to copy them 3. profiling utilities --- headers/wasm.hpp | 5 +- headers/wasm/concolic_driver.hpp | 28 ++- headers/wasm/concrete_rt.hpp | 126 ++++++++-- headers/wasm/heap_mem_bookkeeper.hpp | 24 ++ headers/wasm/profile.hpp | 114 +++++++++ headers/wasm/symbolic_rt.hpp | 216 +++++++----------- headers/wasm/utils.hpp | 27 ++- .../scala/wasm/StagedConcolicMiniWasm.scala | 6 +- 8 files changed, 386 insertions(+), 160 deletions(-) create mode 100644 headers/wasm/heap_mem_bookkeeper.hpp create mode 100644 headers/wasm/profile.hpp diff --git a/headers/wasm.hpp b/headers/wasm.hpp index 36fe3849..aa93e379 100644 --- a/headers/wasm.hpp +++ b/headers/wasm.hpp @@ -1,8 +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/symbolic_rt.hpp" -#include "wasm/concolic_driver.hpp" #include "wasm/utils.hpp" + #endif \ No newline at end of file diff --git a/headers/wasm/concolic_driver.hpp b/headers/wasm/concolic_driver.hpp index 06b3459c..c9f0b856 100644 --- a/headers/wasm/concolic_driver.hpp +++ b/headers/wasm/concolic_driver.hpp @@ -5,6 +5,9 @@ #include "smt_solver.hpp" #include "symbolic_rt.hpp" #include "utils.hpp" +#include "wasm/profile.hpp" +#include +#include #include #include #include @@ -35,6 +38,7 @@ class ConcolicDriver { void run(); private: + void main_exploration_loop(); Solver solver; std::function entrypoint; std::optional tree_file; @@ -46,13 +50,20 @@ class ManagedConcolicCleanup { 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()); + + // Clear the symbol bookkeeper + SymBookKeeper.clear(); } }; -inline void ConcolicDriver::run() { - ExploreTree.reset_cursor(); +static std::monostate reset_stacks(); + +inline void ConcolicDriver::main_exploration_loop() { while (true) { ManagedConcolicCleanup cleanup{*this}; @@ -79,6 +90,8 @@ inline void ConcolicDriver::run() { dynamic_cast(unexplored->node.get())) { snapshot_node->get_snapshot().resume_execution(SymEnv, unexplored); } else { + auto timer = ManagedTimer(); + reset_stacks(); entrypoint(); } @@ -97,7 +110,8 @@ inline void ConcolicDriver::run() { GENSYM_INFO("All branches covered, exiting..."); return; } else { - GENSYM_INFO("Found a bug, but not all branches covered, continuing..."); + GENSYM_INFO( + "Found a bug, but not all branches covered, continuing..."); } } } @@ -107,13 +121,19 @@ inline void ConcolicDriver::run() { } } +inline void ConcolicDriver::run() { + ExploreTree.reset_cursor(); + main_exploration_loop(); + Profile.print_summary(); +} + static std::monostate reset_stacks() { Stack.reset(); Frames.reset(); SymStack.reset(); SymFrames.reset(); initRand(); - Memory = Memory_t(1); + Memory.reset(); return std::monostate{}; } diff --git a/headers/wasm/concrete_rt.hpp b/headers/wasm/concrete_rt.hpp index 5d9243c4..cca614c6 100644 --- a/headers/wasm/concrete_rt.hpp +++ b/headers/wasm/concrete_rt.hpp @@ -1,6 +1,7 @@ #ifndef WASM_CONCRETE_RT_HPP #define WASM_CONCRETE_RT_HPP +#include "wasm/profile.hpp" #include "wasm/utils.hpp" #include #include @@ -8,25 +9,10 @@ #include #include #include +#include #include #include -inline std::monostate info() { -#ifdef DEBUG - std::cout << std::endl; -#endif - return std::monostate{}; -} - -template -std::monostate info(const T &first, const Args &...args) { -#ifdef DEBUG - std::cout << first << " "; - info(args...); -#endif - return std::monostate{}; -} - struct Num { Num(int64_t value) : value(value) {} Num() : value(0) {} @@ -59,24 +45,34 @@ const int STACK_SIZE = 1024 * 64; class Stack_t { public: - Stack_t() : count(0), stack_ptr(new Num[STACK_SIZE]) {} + 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) { + Profile.step(ProfileKind::PUSH); stack_ptr[count] = num; count++; return std::monostate{}; } std::monostate push(Num &num) { + Profile.step(ProfileKind::PUSH); stack_ptr[count] = num; count++; return std::monostate{}; } Num pop() { + Profile.step(ProfileKind::POP); #ifdef DEBUG assert(count > 0 && "Stack underflow"); - printf("[Debug] poping from stack, size of concrete stack is: %d\n", count); + printf("[Debug] popping from stack, size of concrete stack is: %d\n", + count); #endif Num num = stack_ptr[count - 1]; count--; @@ -84,6 +80,7 @@ class Stack_t { } Num peek() { + Profile.step(ProfileKind::PEEK); #ifdef DEBUG if (count == 0) { throw std::runtime_error("Stack underflow"); @@ -95,6 +92,7 @@ class Stack_t { int32_t size() { return count; } void shift(int32_t offset, int32_t size) { + Profile.step(ProfileKind::SHIFT); #ifdef DEBUG if (offset < 0) { throw std::out_of_range("Invalid offset: " + std::to_string(offset)); @@ -148,7 +146,13 @@ const int FRAME_SIZE = 1024; class Frames_t { public: - Frames_t() : count(0), stack_ptr(new Num[FRAME_SIZE]) {} + Frames_t() : count(0), stack_ptr(new Num[FRAME_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 < FRAME_SIZE; i += page_size) { + stack_ptr[i] = Num(0); + } + } std::monostate popFrame(std::int32_t size) { assert(size >= 0); @@ -157,11 +161,15 @@ class Frames_t { } Num get(std::int32_t index) { + Profile.step(ProfileKind::GET); auto ret = stack_ptr[count - 1 - index]; return ret; } - void set(std::int32_t index, Num num) { stack_ptr[count - 1 - index] = num; } + void set(std::int32_t index, Num num) { + Profile.step(ProfileKind::SET); + stack_ptr[count - 1 - index] = num; + } void pushFrame(std::int32_t size) { assert(size >= 0); @@ -199,7 +207,85 @@ static std::monostate unreachable() { 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 { + // TODO: We assign a SymVal to each byte in memory + std::vector memory; + int init_page_count; + int page_count; + int allocated_pages; + + 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) {} + + int32_t loadInt(int32_t base, int32_t offset) { + // just load a 4-byte integer from memory of the vector + int32_t addr = base + offset; + if (!(addr + 3 < memory.size())) { + 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); + } + 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 + assert(addr + 3 < memory.size()); + for (int i = 0; i < 4; ++i) { + memory[addr + i] = static_cast((value >> (8 * i)) & 0xFF); + // Optionally, update memory[addr + i].second (SymVal) if needed + } + return std::monostate{}; + } + + std::monostate store_byte(int32_t addr, uint8_t value) { + 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(ProfileKind::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; + } + } +}; + +static Memory_t Memory(1); // 1 page memory + #endif // WASM_CONCRETE_RT_HPP \ No newline at end of file diff --git a/headers/wasm/heap_mem_bookkeeper.hpp b/headers/wasm/heap_mem_bookkeeper.hpp new file mode 100644 index 00000000..c4cc7313 --- /dev/null +++ b/headers/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/headers/wasm/profile.hpp b/headers/wasm/profile.hpp new file mode 100644 index 00000000..b112c932 --- /dev/null +++ b/headers/wasm/profile.hpp @@ -0,0 +1,114 @@ +#ifndef PROFILE_HPP +#define PROFILE_HPP + +#include "utils.hpp" +#include +#include +#include +#include + +enum class ProfileKind { + PUSH, + POP, + PEEK, + SHIFT, + SET, + GET, + BINARY, + TREE_FILL, + CURSOR_MOVE, + MEM_GROW, + SNAPSHOT_CREATE, + OperationCount // 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) {} + std::monostate step() { +#ifdef ENABLE_PROFILE + step_count++; +#endif + return std::monostate(); + } + std::monostate step(ProfileKind op) { +#ifdef ENABLE_PROFILE + op_count[static_cast(op)]++; +#endif + return std::monostate(); + } + void print_summary() { +#ifdef ENABLE_PROFILE + std::cout << "Profile Summary:" << std::endl; + std::cout << "Total PUSH operations: " + << op_count[static_cast(ProfileKind::PUSH)] + << std::endl; + std::cout << "Total POP operations: " + << op_count[static_cast(ProfileKind::POP)] + << std::endl; + std::cout << "Total PEEK operations: " + << op_count[static_cast(ProfileKind::PEEK)] + << std::endl; + std::cout << "Total SHIFT operations: " + << op_count[static_cast(ProfileKind::SHIFT)] + << std::endl; + std::cout << "Total SET operations: " + << op_count[static_cast(ProfileKind::SET)] + << std::endl; + std::cout << "Total GET operations: " + << op_count[static_cast(ProfileKind::GET)] + << std::endl; + std::cout << "Total BINARY operations: " + << op_count[static_cast(ProfileKind::BINARY)] + << std::endl; + std::cout << "Total TREE_FILL operations: " + << op_count[static_cast(ProfileKind::TREE_FILL)] + << std::endl; + std::cout << "Total CURSOR_MOVE operations: " + << op_count[static_cast(ProfileKind::CURSOR_MOVE)] + << std::endl; + std::cout << "Total other instructions executed: " << step_count + << std::endl; + std::cout << "Total MEM_GROW operations: " + << op_count[static_cast(ProfileKind::MEM_GROW)] + << std::endl; + std::cout + << "Total SNAPSHOT_CREATE operations: " + << op_count[static_cast(ProfileKind::SNAPSHOT_CREATE)] + << std::endl; + std::cout << "Total time for instruction execution (s): " + << std::setprecision(15) << execution_time << std::endl; +#endif + } + + // record the time spent in main instruction execution, in seconds + void add_instruction_time(double time) { +#ifdef ENABLE_PROFILE + execution_time += time; +#endif + } + +private: + int step_count; + std::array(ProfileKind::OperationCount)> + op_count; + double execution_time = 0.0; +}; + +static Profile_t Profile; + +class ManagedTimer { +public: + ManagedTimer() { 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(elapsed.count()); + } + +private: + std::chrono::high_resolution_clock::time_point start; +}; + +#endif // PROFILE_HPP \ No newline at end of file diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 97568928..338afb77 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -3,8 +3,9 @@ #include "concrete_rt.hpp" #include "controls.hpp" +#include "heap_mem_bookkeeper.hpp" +#include "profile.hpp" #include "utils.hpp" -#include "wasm/profile.hpp" #include #include #include @@ -16,6 +17,7 @@ #include #include #include +#include #include #include @@ -60,7 +62,6 @@ class SmallBV : public Symbolic { int size; // in bits int64_t value; }; - struct SymBinary; enum Operation { @@ -77,9 +78,12 @@ enum Operation { B_AND, // Bitwise AND CONCAT, // Byte-level concatenation }; +static MemBookKeeper SymBookKeeper; static std::shared_ptr ZERO = - std::make_shared(I32V(0)); + SymBookKeeper.allocate(I32V(0)); +static std::shared_ptr ZeroByte = + SymBookKeeper.allocate(8, 0); struct SymVal { std::shared_ptr symptr; @@ -115,11 +119,11 @@ struct SymVal { }; static SymVal make_symbolic(int index) { - return SymVal(std::make_shared(index)); + return SymVal(SymBookKeeper.allocate(index)); } inline SymVal Concrete(Num num) { - return SymVal(std::make_shared(num)); + return SymVal(SymBookKeeper.allocate(num)); } // Extract is different from other operations, it only has one symbolic operand, @@ -197,7 +201,7 @@ inline SymVal SymVal::concat(const SymVal &other) const { inline SymVal SymVal::extract(int high, int low) const { assert(high >= low && "Invalid extract range"); - return SymVal(std::make_shared(*this, high, low)); + return SymVal(SymBookKeeper.allocate(*this, high, low)); } inline SymVal SymVal::bitwise_and(const SymVal &other) const { @@ -206,13 +210,13 @@ inline SymVal SymVal::bitwise_and(const SymVal &other) const { inline SymVal SymVal::make_binary(Operation op, const SymVal &lhs, const SymVal &rhs) { assert(lhs.symptr != nullptr && rhs.symptr != nullptr); - return SymVal(std::make_shared(op, lhs, rhs)); + return SymVal(SymBookKeeper.allocate(op, lhs, rhs)); } inline SymVal SymVal::makeSymbolic() const { auto concrete = dynamic_cast(symptr.get()); if (concrete) { // If the symbolic value is a concrete value, use it to create a symbol - return SymVal(std::make_shared(concrete->value.toInt())); + return SymVal(SymBookKeeper.allocate(concrete->value.toInt())); } else { throw std::runtime_error( "Cannot make symbolic a non-concrete symbolic value"); @@ -315,6 +319,55 @@ class SymFrames_t { struct NodeBox; struct SymEnv_t; +class SymMemory_t { +public: + std::unordered_map memory; + + SymVal loadSymByte(int32_t addr) { + // if the address is not in the memory, it must be a zero-initialized memory + auto it = memory.find(addr); + SymVal s = (it != memory.end()) + ? it->second + : SymVal(SymBookKeeper.allocate(8, 0)); + return s; + } + + SymVal loadSym(int32_t base, int32_t offset) { + // calculate the real address + int32_t addr = base + offset; + auto it = memory.find(addr); + SymVal s0 = (it != memory.end()) ? it->second : SymVal(ZeroByte); + it = memory.find(addr + 1); + SymVal s1 = (it != memory.end()) ? it->second : SymVal(ZeroByte); + it = memory.find(addr + 2); + SymVal s2 = (it != memory.end()) ? it->second : SymVal(ZeroByte); + it = memory.find(addr + 3); + SymVal s3 = (it != memory.end()) ? it->second : SymVal(ZeroByte); + + return s3.concat(s2).concat(s1).concat(s0); + } + + // 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); + memory[addr] = s0; + memory[addr + 1] = s1; + memory[addr + 2] = s2; + memory[addr + 3] = s3; + return std::monostate{}; + } +}; + +static SymMemory_t SymMemory; + // A snapshot of the symbolic state and execution context (control) class Snapshot_t { public: @@ -322,12 +375,14 @@ class Snapshot_t { SymStack_t get_stack() const { return stack; } SymFrames_t get_frames() const { return frames; } + SymMemory_t get_memory() const { return memory; } std::monostate resume_execution(SymEnv_t &sym_env, NodeBox *node) const; private: SymStack_t stack; SymFrames_t frames; + SymMemory_t memory; // The continuation at the snapshot point Cont_t cont; MCont_t mcont; @@ -629,7 +684,9 @@ inline std::vector NodeBox::collect_path_conds() { } inline Snapshot_t::Snapshot_t(Cont_t cont, MCont_t mcont) - : stack(SymStack), frames(SymFrames), cont(cont), mcont(mcont) { + : stack(SymStack), frames(SymFrames), memory(SymMemory), cont(cont), + mcont(mcont) { + Profile.step(ProfileKind::SNAPSHOT_CREATE); #ifdef DEBUG std::cout << "Creating snapshot of size " << stack.size() << std::endl; #endif @@ -893,11 +950,28 @@ static void resume_conc_frames(const SymFrames_t &sym_frame, Frames_t &frames, } } +static void resume_conc_memory(const SymMemory_t &sym_memory, Memory_t &memory, + SymEnv_t &sym_env) { + 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_states(const SymStack_t &sym_stack, - const SymFrames_t &sym_frame, Stack_t &stack, - Frames_t &frames, SymEnv_t &sym_env) { + const SymFrames_t &sym_frame, + const SymMemory_t &sym_memory, Stack_t &stack, + Frames_t &frames, 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_memory(sym_memory, memory, sym_env); } inline std::monostate Snapshot_t::resume_execution(SymEnv_t &sym_env, @@ -909,127 +983,11 @@ inline std::monostate Snapshot_t::resume_execution(SymEnv_t &sym_env, GENSYM_INFO("Reusing symbolic state from snapshot"); SymStack = stack; SymFrames = frames; + SymMemory = memory; // Restore the concrete states from the symbolic states - resume_conc_states(stack, frames, Stack, Frames, sym_env); + resume_conc_states(stack, frames, memory, Stack, Frames, Memory, sym_env); // Resume execution from the continuation return cont(mcont); } - -static const int PRE_ALLOC_PAGES = 20; - -struct Memory_t { - // TODO: We assign a SymVal to each byte in memory - std::vector> memory; - int page_count; - int allocated_pages; - - Memory_t(int32_t init_page_count) - : memory(PRE_ALLOC_PAGES * pagesize), page_count(init_page_count), - allocated_pages(PRE_ALLOC_PAGES) { - // warm up the memory with zero bytes - for (auto &byte : memory) { - byte.first = 0; - byte.second = SymVal(); - } - } - - int32_t loadInt(int32_t base, int32_t offset) { - // just load a 4-byte integer from memory of the vector - int32_t addr = base + offset; - if (!(addr + 3 < memory.size())) { - 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].first) << (8 * i); - } - return result; - } - - // TODO: 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 - SymVal loadSym(int32_t base, int32_t offset) { - int32_t addr = base + offset; - if (!(addr + 3 < memory.size())) { - throw std::runtime_error("Invalid memory access " + std::to_string(addr)); - } - SymVal s0 = memory[addr].second; - if (s0.symptr == nullptr) { - s0 = SymVal(std::make_shared(8, 0)); - memory[addr].second = s0; - } - SymVal s1 = memory[addr + 1].second; - if (s1.symptr == nullptr) { - s1 = SymVal(std::make_shared(8, 0)); - memory[addr + 1].second = s1; - } - SymVal s2 = memory[addr + 2].second; - if (s2.symptr == nullptr) { - s2 = SymVal(std::make_shared(8, 0)); - memory[addr + 2].second = s2; - } - SymVal s3 = memory[addr + 3].second; - if (s3.symptr == nullptr) { - s3 = SymVal(std::make_shared(8, 0)); - memory[addr + 3].second = s3; - } - return s3.concat(s2).concat(s1).concat(s0); - } - - // 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); - memory[addr].second = s0; - memory[addr + 1].second = s1; - memory[addr + 2].second = s2; - memory[addr + 3].second = s3; - return std::monostate{}; - } - - 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 - assert(addr + 3 < memory.size()); - for (int i = 0; i < 4; ++i) { - memory[addr + i].first = static_cast((value >> (8 * i)) & 0xFF); - // Optionally, update memory[addr + i].second (SymVal) if needed - } - 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(ProfileKind::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; - } - } -}; - -static Memory_t Memory(1); // 1 page memory - #endif // WASM_SYMBOLIC_RT_HPP \ No newline at end of file diff --git a/headers/wasm/utils.hpp b/headers/wasm/utils.hpp index f814858d..8b7b0471 100644 --- a/headers/wasm/utils.hpp +++ b/headers/wasm/utils.hpp @@ -1,5 +1,7 @@ #ifndef UTILS_HPP #define UTILS_HPP +#include +#include #ifndef GENSYM_ASSERT #define GENSYM_ASSERT(condition) \ @@ -39,15 +41,34 @@ #if __cplusplus < 202002L #include -inline bool starts_with(const std::string& str, const std::string& prefix) { +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()); + std::equal(prefix.begin(), prefix.end(), str.begin()); } #else #include -inline bool starts_with(const std::string& str, const std::string& prefix) { +inline bool starts_with(const std::string &str, const std::string &prefix) { return str.starts_with(prefix); } #endif +inline std::monostate info() { +#ifdef DEBUG + std::cout << std::endl; +#endif + return std::monostate{}; +} + +template +std::monostate info(const T &first, const Args &...args) { +#ifdef DEBUG + std::cout << first << " "; + info(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/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index 5b5ae8de..86d86a8a 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -626,7 +626,7 @@ trait StagedWasmEvaluator extends SAIOps { } } val (instrs, locals) = (funBody.body, funBody.locals) - resetStacks() + // resetStacks() // Don't manually reset the global states (like stack), manage them in the driver initGlobals(module.globals) Frames.pushFrameC(locals) Frames.pushFrameS(locals) @@ -1409,9 +1409,9 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { emit("Stack.size()") // Symbolic Memory case Node(_, "sym-store-int", List(base, offset, s_value), _) => - emit("Memory.storeSym("); shallow(base); emit(", "); shallow(offset); emit(", "); shallow(s_value); emit(")") + emit("SymMemory.storeSym("); shallow(base); emit(", "); shallow(offset); emit(", "); shallow(s_value); emit(")") case Node(_, "sym-load-int", List(base, offset), _) => - emit("Memory.loadSym("); shallow(base); emit(", "); shallow(offset); emit(")") + emit("SymMemory.loadSym("); shallow(base); emit(", "); shallow(offset); emit(")") case Node(_, "sym-memory-grow", List(delta), _) => emit("SymMemory.grow("); shallow(delta); emit(")") // Globals From 8a40d30b4a5d3c6b92b7ed712deba4567d74baea Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Sat, 4 Oct 2025 21:51:43 -0400 Subject: [PATCH 42/53] work list algorithm for exploration --- headers/wasm/concolic_driver.hpp | 39 ++++++++++++++++++++++++-------- headers/wasm/symbolic_rt.hpp | 36 +++++++++++++++++------------ 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/headers/wasm/concolic_driver.hpp b/headers/wasm/concolic_driver.hpp index c9f0b856..e49a99e8 100644 --- a/headers/wasm/concolic_driver.hpp +++ b/headers/wasm/concolic_driver.hpp @@ -42,6 +42,7 @@ class ConcolicDriver { Solver solver; std::function entrypoint; std::optional tree_file; + std::vector work_list; }; class ManagedConcolicCleanup { @@ -64,19 +65,39 @@ class ManagedConcolicCleanup { static std::monostate reset_stacks(); inline void ConcolicDriver::main_exploration_loop() { - while (true) { + + // Register a collector to ExploreTree to add new nodes to work_list + ExploreTree.register_new_node_collector( + [&](NodeBox *new_node) { work_list.push_back(new_node); }); + + std::set visited; + + assert(ExploreTree.get_root()->isUnexplored() && + "Before main loop, root should be unexplored!"); + work_list.push_back(ExploreTree.get_root()); + + while (!work_list.empty()) { ManagedConcolicCleanup cleanup{*this}; + // Pick an unexplored node from the work list + auto node = work_list.back(); + work_list.pop_back(); - auto unexplored = ExploreTree.pick_unexplored(); - if (!unexplored) { - GENSYM_INFO("No unexplored nodes found, exiting..."); - return; + if (visited.find(node) != visited.end()) { + continue; + } else { + visited.insert(node); } - auto cond = unexplored->collect_path_conds(); + + if (!node->isUnexplored()) { + // if it's not unexplored anymore, skip it + continue; + } + + auto cond = node->collect_path_conds(); auto result = solver.solve(cond); if (!result.has_value()) { GENSYM_INFO("Found an unreachable path, marking it as unreachable..."); - unexplored->fillUnreachableNode(); + node->fillUnreachableNode(); continue; } auto new_env = result.value(); @@ -87,8 +108,8 @@ inline void ConcolicDriver::main_exploration_loop() { GENSYM_INFO("Now execute the program with symbolic environment: "); GENSYM_INFO(SymEnv.to_string()); if (auto snapshot_node = - dynamic_cast(unexplored->node.get())) { - snapshot_node->get_snapshot().resume_execution(SymEnv, unexplored); + dynamic_cast(node->node.get())) { + snapshot_node->get_snapshot().resume_execution(SymEnv, node); } else { auto timer = ManagedTimer(); reset_stacks(); diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 338afb77..1cc3fb2d 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -420,7 +420,7 @@ struct NodeBox { std::unique_ptr node; NodeBox *parent; - std::monostate fillIfElseNode(SymVal cond, int id); + bool fillIfElseNode(SymVal cond, int id); std::monostate fillFinishedNode(); std::monostate fillFailedNode(); std::monostate fillUnreachableNode(); @@ -610,15 +610,16 @@ inline NodeBox::NodeBox(NodeBox *parent) /* TODO: avoid allocation of unexplored node */ parent(parent) {} -inline std::monostate NodeBox::fillIfElseNode(SymVal cond, int id) { +inline bool NodeBox::fillIfElseNode(SymVal cond, int id) { // fill the current NodeBox with an ifelse branch node when it's unexplored if (this->isUnexplored()) { node = std::make_unique(cond, this, id); + return true; } assert( dynamic_cast(node.get()) != nullptr && "Current node is not an Unexplored nor an IfElseNode, cannot fill it!"); - return std::monostate(); + return false; } inline std::monostate NodeBox::fillSnapshotNode(Snapshot_t snapshot) { @@ -722,7 +723,12 @@ class ExploreTree_t { std::monostate fillFailedNode() { return cursor->fillFailedNode(); } std::monostate fillIfElseNode(SymVal cond, int id) { - return cursor->fillIfElseNode(cond, 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 moveCursor(bool branch, Snapshot_t snapshot) { @@ -764,16 +770,6 @@ class ExploreTree_t { return std::monostate(); } - std::optional> get_unexplored_conditions() { - // Get all unexplored conditions in the tree - std::vector result; - auto box = pick_unexplored(); - if (!box) { - return std::nullopt; - } - return box->collect_path_conds(); - } - NodeBox *pick_unexplored() { // Pick an unexplored node from the tree // For now, we just iterate through the tree and return the first unexplored @@ -793,6 +789,12 @@ class ExploreTree_t { 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()) { @@ -808,8 +810,14 @@ class ExploreTree_t { } 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; From 568928c53494ac69fa6e92836f086e8d7df4c9c8 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Sun, 5 Oct 2025 12:20:09 -0400 Subject: [PATCH 43/53] config header; fix extract evaluation; capture by value in lambda --- headers/wasm/concolic_driver.hpp | 26 ++---- headers/wasm/concrete_rt.hpp | 47 +++++++--- headers/wasm/config.hpp | 36 +++++++ headers/wasm/profile.hpp | 93 +++++++++---------- headers/wasm/symbolic_rt.hpp | 72 +++++++------- .../scala/wasm/StagedConcolicMiniWasm.scala | 15 +-- 6 files changed, 166 insertions(+), 123 deletions(-) create mode 100644 headers/wasm/config.hpp diff --git a/headers/wasm/concolic_driver.hpp b/headers/wasm/concolic_driver.hpp index e49a99e8..37ef4004 100644 --- a/headers/wasm/concolic_driver.hpp +++ b/headers/wasm/concolic_driver.hpp @@ -2,10 +2,11 @@ #define CONCOLIC_DRIVER_HPP #include "concrete_rt.hpp" +#include "config.hpp" +#include "profile.hpp" #include "smt_solver.hpp" #include "symbolic_rt.hpp" #include "utils.hpp" -#include "wasm/profile.hpp" #include #include #include @@ -15,16 +16,6 @@ #include #include -enum class ExploreMode { EarlyExit, ExitByCoverage }; - -#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 - class ConcolicDriver { friend class ManagedConcolicCleanup; @@ -107,12 +98,14 @@ inline void ConcolicDriver::main_exploration_loop() { try { GENSYM_INFO("Now execute the program with symbolic environment: "); GENSYM_INFO(SymEnv.to_string()); - if (auto snapshot_node = - dynamic_cast(node->node.get())) { + if (auto snapshot_node = dynamic_cast(node->node.get())) { + assert(REUSE_SNAPSHOT); + auto timer = ManagedTimer(); snapshot_node->get_snapshot().resume_execution(SymEnv, node); } else { auto timer = ManagedTimer(); reset_stacks(); + ExploreTree.reset_cursor(); entrypoint(); } @@ -134,6 +127,7 @@ inline void ConcolicDriver::main_exploration_loop() { GENSYM_INFO( "Found a bug, but not all branches covered, continuing..."); } + std::cout << e.what() << std::endl; } } #if defined(RUN_ONCE) @@ -143,18 +137,18 @@ inline void ConcolicDriver::main_exploration_loop() { } inline void ConcolicDriver::run() { - ExploreTree.reset_cursor(); main_exploration_loop(); Profile.print_summary(); } static std::monostate reset_stacks() { Stack.reset(); - Frames.reset(); SymStack.reset(); + Frames.reset(); SymFrames.reset(); - initRand(); Memory.reset(); + SymMemory.reset(); + initRand(); return std::monostate{}; } diff --git a/headers/wasm/concrete_rt.hpp b/headers/wasm/concrete_rt.hpp index cca614c6..c75c0215 100644 --- a/headers/wasm/concrete_rt.hpp +++ b/headers/wasm/concrete_rt.hpp @@ -17,24 +17,30 @@ struct Num { Num(int64_t value) : value(value) {} Num() : value(0) {} int64_t value; - int32_t toInt() { return static_cast(value); } + int32_t toInt() const { return static_cast(value); } - bool operator==(const Num &other) const { return value == other.value; } + // TODO: support different bit width operations, for now we just assume all + // oprands are i32 + bool operator==(const Num &other) const { return toInt() == other.toInt(); } bool operator!=(const Num &other) const { return !(*this == other); } - Num operator+(const Num &other) const { return Num(value + other.value); } - Num operator-(const Num &other) const { return Num(value - other.value); } - Num operator*(const Num &other) const { return Num(value * other.value); } + Num operator+(const Num &other) const { return Num(toInt() + other.toInt()); } + Num operator-(const Num &other) const { return Num(toInt() - other.toInt()); } + Num operator*(const Num &other) const { return Num(toInt() * other.toInt()); } Num operator/(const Num &other) const { - if (other.value == 0) { + if (other.toInt() == 0) { throw std::runtime_error("Division by zero"); } - return Num(value / other.value); + return Num(toInt() / other.toInt()); } - Num operator<(const Num &other) const { return Num(value < other.value); } - Num operator<=(const Num &other) const { return Num(value <= other.value); } - Num operator>(const Num &other) const { return Num(value > other.value); } - Num operator>=(const Num &other) const { return Num(value >= other.value); } - Num operator&(const Num &other) const { return Num(value & other.value); } + Num operator<(const Num &other) const { return Num(toInt() < other.toInt()); } + Num operator<=(const Num &other) const { + return Num(toInt() <= other.toInt()); + } + Num operator>(const Num &other) const { return Num(toInt() > other.toInt()); } + Num operator>=(const Num &other) const { + return Num(toInt() >= other.toInt()); + } + Num operator&(const Num &other) const { return Num(toInt() & other.toInt()); } }; static Num I32V(int v) { return v; } @@ -71,8 +77,9 @@ class Stack_t { Profile.step(ProfileKind::POP); #ifdef DEBUG assert(count > 0 && "Stack underflow"); - printf("[Debug] popping from stack, size of concrete stack is: %d\n", - count); + 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--; @@ -223,6 +230,10 @@ struct Memory_t { page_count(init_page_count), allocated_pages(PRE_ALLOC_PAGES) {} int32_t loadInt(int32_t base, int32_t offset) { +#ifdef DEBUG + std::cout << "[Debug] loading int from memory at address: " + << (base + offset) << std::endl; +#endif // just load a 4-byte integer from memory of the vector int32_t addr = base + offset; if (!(addr + 3 < memory.size())) { @@ -238,8 +249,14 @@ struct Memory_t { std::monostate storeInt(int32_t base, int32_t offset, int32_t value) { int32_t addr = base + offset; +#ifdef DEBUG + std::cout << "[Debug] storing int " << value << " to memory at address " + << addr << std::endl; +#endif // Ensure we don't write out of bounds - assert(addr + 3 < memory.size()); + 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); // Optionally, update memory[addr + i].second (SymVal) if needed diff --git a/headers/wasm/config.hpp b/headers/wasm/config.hpp new file mode 100644 index 00000000..48a54634 --- /dev/null +++ b/headers/wasm/config.hpp @@ -0,0 +1,36 @@ +#ifndef CONFIG_HPP +#define CONFIG_HPP + +// This file contains configuration settings for the concolic execution + +// If ENABLE_PROFILE defined, the compiled program will collect and print +// profiling information +#ifdef ENABLE_PROFILE +const bool PROFILE_ENABLED = true; +#else +const bool PROFILE_ENABLED = 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 + +#endif // CONFIG_HPP \ No newline at end of file diff --git a/headers/wasm/profile.hpp b/headers/wasm/profile.hpp index b112c932..ec17448a 100644 --- a/headers/wasm/profile.hpp +++ b/headers/wasm/profile.hpp @@ -2,6 +2,7 @@ #define PROFILE_HPP #include "utils.hpp" +#include "config.hpp" #include #include #include @@ -27,59 +28,57 @@ class Profile_t { public: Profile_t() : step_count(0) {} std::monostate step() { -#ifdef ENABLE_PROFILE - step_count++; -#endif + if (PROFILE_ENABLED) + step_count++; return std::monostate(); } std::monostate step(ProfileKind op) { -#ifdef ENABLE_PROFILE - op_count[static_cast(op)]++; -#endif + if (PROFILE_ENABLED) + op_count[static_cast(op)]++; return std::monostate(); } void print_summary() { -#ifdef ENABLE_PROFILE - std::cout << "Profile Summary:" << std::endl; - std::cout << "Total PUSH operations: " - << op_count[static_cast(ProfileKind::PUSH)] - << std::endl; - std::cout << "Total POP operations: " - << op_count[static_cast(ProfileKind::POP)] - << std::endl; - std::cout << "Total PEEK operations: " - << op_count[static_cast(ProfileKind::PEEK)] - << std::endl; - std::cout << "Total SHIFT operations: " - << op_count[static_cast(ProfileKind::SHIFT)] - << std::endl; - std::cout << "Total SET operations: " - << op_count[static_cast(ProfileKind::SET)] - << std::endl; - std::cout << "Total GET operations: " - << op_count[static_cast(ProfileKind::GET)] - << std::endl; - std::cout << "Total BINARY operations: " - << op_count[static_cast(ProfileKind::BINARY)] - << std::endl; - std::cout << "Total TREE_FILL operations: " - << op_count[static_cast(ProfileKind::TREE_FILL)] - << std::endl; - std::cout << "Total CURSOR_MOVE operations: " - << op_count[static_cast(ProfileKind::CURSOR_MOVE)] - << std::endl; - std::cout << "Total other instructions executed: " << step_count - << std::endl; - std::cout << "Total MEM_GROW operations: " - << op_count[static_cast(ProfileKind::MEM_GROW)] - << std::endl; - std::cout - << "Total SNAPSHOT_CREATE operations: " - << op_count[static_cast(ProfileKind::SNAPSHOT_CREATE)] - << std::endl; - std::cout << "Total time for instruction execution (s): " - << std::setprecision(15) << execution_time << std::endl; -#endif + if (PROFILE_ENABLED) { + std::cout << "Profile Summary:" << std::endl; + std::cout << "Total PUSH operations: " + << op_count[static_cast(ProfileKind::PUSH)] + << std::endl; + std::cout << "Total POP operations: " + << op_count[static_cast(ProfileKind::POP)] + << std::endl; + std::cout << "Total PEEK operations: " + << op_count[static_cast(ProfileKind::PEEK)] + << std::endl; + std::cout << "Total SHIFT operations: " + << op_count[static_cast(ProfileKind::SHIFT)] + << std::endl; + std::cout << "Total SET operations: " + << op_count[static_cast(ProfileKind::SET)] + << std::endl; + std::cout << "Total GET operations: " + << op_count[static_cast(ProfileKind::GET)] + << std::endl; + std::cout << "Total BINARY operations: " + << op_count[static_cast(ProfileKind::BINARY)] + << std::endl; + std::cout << "Total TREE_FILL operations: " + << op_count[static_cast(ProfileKind::TREE_FILL)] + << std::endl; + std::cout << "Total CURSOR_MOVE operations: " + << op_count[static_cast(ProfileKind::CURSOR_MOVE)] + << std::endl; + std::cout << "Total other instructions executed: " << step_count + << std::endl; + std::cout << "Total MEM_GROW operations: " + << op_count[static_cast(ProfileKind::MEM_GROW)] + << std::endl; + std::cout + << "Total SNAPSHOT_CREATE operations: " + << op_count[static_cast(ProfileKind::SNAPSHOT_CREATE)] + << std::endl; + std::cout << "Total time for instruction execution (s): " + << std::setprecision(15) << execution_time << std::endl; + } } // record the time spent in main instruction execution, in seconds diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 1cc3fb2d..cc7c2d7b 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -2,6 +2,7 @@ #define WASM_SYMBOLIC_RT_HPP #include "concrete_rt.hpp" +#include "config.hpp" #include "controls.hpp" #include "heap_mem_bookkeeper.hpp" #include "profile.hpp" @@ -29,12 +30,6 @@ class Symbolic { static int max_id = 0; -#ifdef NO_REUSE -static bool REUSE_MODE = false; -#else -static bool REUSE_MODE = true; -#endif - class Symbol : public Symbolic { public: // TODO: add type information to determine the size of bitvector @@ -265,8 +260,6 @@ class SymStack_t { stack.clear(); } - void reuse(Snapshot_t snapshot); - size_t size() const { return stack.size(); } SymVal operator[](size_t index) const { return stack[index]; } @@ -306,8 +299,6 @@ class SymFrames_t { stack.clear(); } - void reuse(Snapshot_t snapshot); - size_t size() const { return stack.size(); } SymVal operator[](size_t index) const { return stack[index]; } @@ -364,6 +355,11 @@ class SymMemory_t { memory[addr + 3] = s3; return std::monostate{}; } + + std::monostate reset() { + memory.clear(); + return std::monostate{}; + } }; static SymMemory_t SymMemory; @@ -371,7 +367,9 @@ static SymMemory_t SymMemory; // A snapshot of the symbolic state and execution context (control) class Snapshot_t { public: - explicit Snapshot_t(Cont_t cont, MCont_t mcont); + explicit Snapshot_t(Cont_t cont, MCont_t mcont, SymStack_t stack, + SymFrames_t frames, SymMemory_t memory); + explicit Snapshot_t() {} SymStack_t get_stack() const { return stack; } SymFrames_t get_frames() const { return frames; } @@ -388,31 +386,18 @@ class Snapshot_t { MCont_t mcont; }; -inline void SymStack_t::reuse(Snapshot_t snapshot) { -// Reusing the symbolic stack from the snapshot -#ifdef DEBUG - std::cout << "Reusing symbolic state from snapshot" << std::endl; - std::cout << "Old stack size = " << stack.size() << std::endl; - std::cout << "New stack size = " << snapshot.get_stack().stack.size() - << std::endl; -#endif - stack = snapshot.get_stack().stack; -} - -inline void SymFrames_t::reuse(Snapshot_t snapshot) { -// Reusing the symbolic frames from the snapshot -#ifdef DEBUG - std::cout << "Reusing symbolic state from snapshot" << std::endl; - std::cout << "Old frame size = " << stack.size() << std::endl; - std::cout << "New frame size = " << snapshot.get_frames().stack.size() - << std::endl; -#endif - stack = snapshot.get_frames().stack; -} - static SymFrames_t SymFrames; static SymFrames_t SymGlobals; +static Snapshot_t makeSnapshot(Cont_t cont, MCont_t mcont) { + if (REUSE_SNAPSHOT) { + return Snapshot_t(cont, mcont, SymStack, SymFrames, SymMemory); + } else { + // create a dummy snapshot, which will not be used + return Snapshot_t(); + } +} + struct Node; struct NodeBox { @@ -684,9 +669,10 @@ inline std::vector NodeBox::collect_path_conds() { return result; } -inline Snapshot_t::Snapshot_t(Cont_t cont, MCont_t mcont) - : stack(SymStack), frames(SymFrames), memory(SymMemory), cont(cont), - mcont(mcont) { +inline Snapshot_t::Snapshot_t(Cont_t cont, MCont_t mcont, SymStack_t stack, + SymFrames_t frames, SymMemory_t memory) + : stack(std::move(stack)), frames(std::move(frames)), + memory(std::move(memory)), cont(cont), mcont(mcont) { Profile.step(ProfileKind::SNAPSHOT_CREATE); #ifdef DEBUG std::cout << "Creating snapshot of size " << stack.size() << std::endl; @@ -740,11 +726,19 @@ class ExploreTree_t { "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->fillSnapshotNode(snapshot); + if (REUSE_SNAPSHOT) { + 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_else_node->true_branch->fillSnapshotNode(snapshot); + if (REUSE_SNAPSHOT) { + 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(); } @@ -884,7 +878,7 @@ static EvalRes eval_sym_expr(const SymVal &sym, SymEnv_t &sym_env) { 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 * 8)) & mask; + int64_t extracted_value = (res.value.toInt() >> ((low - 1) * 8)) & mask; return EvalRes(Num(I64V(extracted_value)), size * 8); } else if (auto smallbv = dynamic_cast(sym.symptr.get())) { return EvalRes(Num(I64V(smallbv->get_value())), smallbv->get_size()); diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index 86d86a8a..c07ecbbc 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -344,11 +344,11 @@ trait StagedWasmEvaluator extends SAIOps { val id = Counter.getId(inst) ExploreTree.fillWithIfElse(symCond.s, id) def thnK: Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { - info("Entering the true branch of the if") + info(s"Entering the true branch $id of the if") eval(thn, restK _, mk, restK _ :: trail)(newCtx) }) def elsK: Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { - info("Entering the false branch of the if") + info(s"Entering the false branch $id of the if") eval(els, restK _, mk, restK _ :: trail)(newCtx) }) if (cond.toInt != 0) { @@ -407,13 +407,13 @@ trait StagedWasmEvaluator extends SAIOps { // snapshotNode (this is done by moveCursor's runtime implementation) // TODO: store snapshot into this snapshot node def thnK: Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { - info("Entering the true branch of the br_table") + info(s"Entering the true branch $id of the br_table") Stack.popC(ty) Stack.popS(ty) trail(choices.head)(newCtx)(mk) }) def elsK: Rep[Cont[Unit]] = topFun((mk: Rep[MCont[Unit]]) => { - info("Entering the false branch of the br_table") + info(s"Entering the false branch $id of the br_table") aux(choices.tail, idx + 1, mk) }) if (cond.toInt != 0) { @@ -1361,7 +1361,10 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { val argTypes = b.in.map(a => remap(typeMap(a))).mkString(", ") emitln(s"std::function<$retType(${argTypes})> ${quote(f)};") emit(quote(f)); emit(" = ") - quoteTypedBlock(b, false, true, capture = "&") + // We need to capture by value here, because we want to save a function in + // snapshot, and use the function later, while the local variables have + // been released. + quoteTypedBlock(b, false, true, capture = "=") emitln(";") case _ => super.traverse(n) } @@ -1386,7 +1389,7 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { case Node(_, "sym-stack-pop", _, _) => emit("SymStack.pop()") case Node(_, "snapshot-make", List(k, mk), _) => - emit("Snapshot_t("); shallow(k); emit(", "); shallow(mk); emit(")") + emit("makeSnapshot("); shallow(k); emit(", "); shallow(mk); emit(")") case Node(_, "frame-pop", List(i), _) => emit("Frames.popFrame("); shallow(i); emit(")") case Node(_, "sym-frame-pop", List(i), _) => From ee9e57bedf0c7b43163c58bd13d2edd972bae6d0 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Sun, 5 Oct 2025 19:33:14 -0400 Subject: [PATCH 44/53] replace SymEnv_t's underlying representation --- headers/wasm/smt_solver.hpp | 7 ++----- headers/wasm/symbolic_rt.hpp | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/headers/wasm/smt_solver.hpp b/headers/wasm/smt_solver.hpp index 64952bcc..8625a65d 100644 --- a/headers/wasm/smt_solver.hpp +++ b/headers/wasm/smt_solver.hpp @@ -14,7 +14,7 @@ class Solver { public: Solver() {} - std::optional> solve(const std::vector &conditions) { + std::optional solve(const std::vector &conditions) { // make an conjunction of all conditions z3::expr conjunction = z3_ctx.bool_val(true); for (const auto &cond : conditions) { @@ -33,7 +33,7 @@ class Solver { return std::nullopt; // No solution found case z3::sat: { z3::model model = z3_solver.get_model(); - std::vector result; + NumMap result; // Reference: // https://github.com/Z3Prover/z3/blob/master/examples/c%2B%2B/example.cpp#L59 GENSYM_INFO("Solved Z3 model"); @@ -44,9 +44,6 @@ class Solver { std::string name = var.name().str(); if (starts_with(name, "s_")) { int id = std::stoi(name.substr(2)); - if (id >= result.size()) { - result.resize(id + 1); - } result[id] = Num(value.get_numeral_int64()); } else { GENSYM_INFO("Find a variable that is not created by GenSym: " + name); diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index cc7c2d7b..8ecd207d 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -816,36 +816,36 @@ class ExploreTree_t { static ExploreTree_t ExploreTree; +using NumMap = std::unordered_map; + class SymEnv_t { public: Num read(const Symbol &symbol) { - if (symbol.get_id() >= map.size()) { - map.resize(symbol.get_id() + 1); - } #if DEBUG std::cout << "Read symbol: " << symbol.get_id() << " from symbolic environment" << std::endl; std::cout << "Current symbolic environment: " << to_string() << std::endl; #endif - - return map[symbol.get_id()]; + map.try_emplace(symbol.get_id(), 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(std::vector new_env) { map = std::move(new_env); } + void update(NumMap new_env) { map = std::move(new_env); } std::string to_string() const { std::string result; result += "(\n"; - for (int i = 0; i < map.size(); ++i) { - const Num &num = map[i]; + for (const auto &[id, num] : map) { result += - " (" + std::to_string(i) + "->" + std::to_string(num.value) + ")\n"; + " (" + std::to_string(id) + "->" + std::to_string(num.value) + ")\n"; } result += ")"; return result; @@ -854,7 +854,7 @@ class SymEnv_t { size_t size() const { return map.size(); } private: - std::vector map; // The symbolic environment, a vector of Num + NumMap map; // The symbolic environment, a vector of Num }; static SymEnv_t SymEnv; From bd5036bcf41eee87dbef13e9f21bef9dd683e793 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Sun, 5 Oct 2025 19:53:13 -0400 Subject: [PATCH 45/53] compare the exploration trees (w/ vs. w/o snapshot reuse) --- .../genwasym/TestStagedConcolicEval.scala | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/test/scala/genwasym/TestStagedConcolicEval.scala b/src/test/scala/genwasym/TestStagedConcolicEval.scala index ada087cc..a0877796 100644 --- a/src/test/scala/genwasym/TestStagedConcolicEval.scala +++ b/src/test/scala/genwasym/TestStagedConcolicEval.scala @@ -12,15 +12,38 @@ class TestStagedConcolicEval extends FunSuite { def testFileConcolicCpp(filename: String, main: Option[String] = None, exitByCoverage: Boolean = false) = { + import sys.process._ + + val moduleInst = ModuleInstance(Parser.parseFile(filename)) val cppFile = s"$filename.cpp" - val exe = s"$cppFile.exe" - val exploreTreeFile = s"$filename.tree.dot" - WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, true, if (exitByCoverage) "BY_COVERAGE" else "EARLY_EXIT") - - import sys.process._ - val result = Process(s"./$exe", None, "TREE_FILE" -> exploreTreeFile).!! - println(result) + val exploreTreeFile = { + // Do concolic execution with snapshot reuse + val exe = s"$cppFile.exe" + val exploreTreeFile = s"$filename.tree.dot" + WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, true, if (exitByCoverage) "BY_COVERAGE" else "EARLY_EXIT") + println(s"Running compiled concolic execution with snapshot reuse: $exe") + val result = Process(s"./$exe", None, "TREE_FILE" -> exploreTreeFile).!! + println(result) + exploreTreeFile + } + val exploreTreeFileNoReuse = { + // Do concolic execution without snapshot reuse + val exe = s"$cppFile.noreuse.exe" + val exploreTreeFile = s"$filename.noreuse.tree.dot" + WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, false, if (exitByCoverage) "BY_COVERAGE" else "EARLY_EXIT") + println(s"Running compiled concolic execution without snapshot reuse: $exe") + val result = Process(s"./$exe", None, "TREE_FILE" -> exploreTreeFile).!! + println(result) + exploreTreeFile + } + // The explore tree generated by two executions should be same + import java.nio.file.Files + assert( + Files.readAllBytes(java.nio.file.Paths.get(exploreTreeFile)) + sameElements Files.readAllBytes(java.nio.file.Paths.get(exploreTreeFileNoReuse)), + s"Explore trees $exploreTreeFile and $exploreTreeFileNoReuse are different!" + ) } // only test concrete execution and its result From f63f682ec919535a9fa91b386ec3dda7a09acc4d Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Sun, 5 Oct 2025 20:01:48 -0400 Subject: [PATCH 46/53] accelerate test by using O0 optimization --- src/main/scala/wasm/StagedConcolicMiniWasm.scala | 3 ++- src/test/scala/genwasym/TestStagedConcolicEval.scala | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index c07ecbbc..739a0f39 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -1590,6 +1590,7 @@ object WasmToCppCompiler { outputCpp: String, outputExe: String, printRes: Boolean, + optimizeLevel: Int, macros: String*): Unit = { val generated = compile(moduleInst, main, printRes) val code = generated.source @@ -1604,7 +1605,7 @@ object WasmToCppCompiler { import sys.process._ val includeFlags = generated.headerFolders.map(f => s"-I$f").mkString(" ") val macroFlags = macros.map(m => s"-D$m").mkString(" ") - val command = s"g++ -std=c++17 $outputCpp -o $outputExe -O3 -g -l z3 " + includeFlags + " " + macroFlags + val command = s"g++ -std=c++17 $outputCpp -o $outputExe -O$optimizeLevel -g -l z3 " + includeFlags + " " + macroFlags if (command.! != 0) { throw new RuntimeException(s"Compilation failed for $outputCpp") } diff --git a/src/test/scala/genwasym/TestStagedConcolicEval.scala b/src/test/scala/genwasym/TestStagedConcolicEval.scala index a0877796..68009388 100644 --- a/src/test/scala/genwasym/TestStagedConcolicEval.scala +++ b/src/test/scala/genwasym/TestStagedConcolicEval.scala @@ -21,7 +21,7 @@ class TestStagedConcolicEval extends FunSuite { // Do concolic execution with snapshot reuse val exe = s"$cppFile.exe" val exploreTreeFile = s"$filename.tree.dot" - WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, true, if (exitByCoverage) "BY_COVERAGE" else "EARLY_EXIT") + WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, true, optimizeLevel=0, if (exitByCoverage) "BY_COVERAGE" else "EARLY_EXIT") println(s"Running compiled concolic execution with snapshot reuse: $exe") val result = Process(s"./$exe", None, "TREE_FILE" -> exploreTreeFile).!! println(result) @@ -31,7 +31,7 @@ class TestStagedConcolicEval extends FunSuite { // Do concolic execution without snapshot reuse val exe = s"$cppFile.noreuse.exe" val exploreTreeFile = s"$filename.noreuse.tree.dot" - WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, false, if (exitByCoverage) "BY_COVERAGE" else "EARLY_EXIT") + WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, false, optimizeLevel=0, if (exitByCoverage) "BY_COVERAGE" else "EARLY_EXIT") println(s"Running compiled concolic execution without snapshot reuse: $exe") val result = Process(s"./$exe", None, "TREE_FILE" -> exploreTreeFile).!! println(result) @@ -51,7 +51,7 @@ class TestStagedConcolicEval extends FunSuite { val moduleInst = ModuleInstance(Parser.parseFile(filename)) val cppFile = s"$filename.cpp" val exe = s"$cppFile.exe" - WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, true, "NO_INFO", "RUN_ONCE") + WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, true, optimizeLevel=0, "NO_INFO", "RUN_ONCE") import sys.process._ val result = s"./$exe".!! From e4ac385aa2b0ac2099b03790bf63cf3c525d6341 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Mon, 6 Oct 2025 21:54:54 -0400 Subject: [PATCH 47/53] add an option to use immutable data structure --- headers/wasm/config.hpp | 8 +++ headers/wasm/profile.hpp | 2 +- headers/wasm/symbolic_rt.hpp | 100 ++++++++++++++++++++++++++++++++--- 3 files changed, 103 insertions(+), 7 deletions(-) diff --git a/headers/wasm/config.hpp b/headers/wasm/config.hpp index 48a54634..9a34daad 100644 --- a/headers/wasm/config.hpp +++ b/headers/wasm/config.hpp @@ -33,4 +33,12 @@ static const bool REUSE_SNAPSHOT = false; 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 + #endif // CONFIG_HPP \ No newline at end of file diff --git a/headers/wasm/profile.hpp b/headers/wasm/profile.hpp index ec17448a..e453a386 100644 --- a/headers/wasm/profile.hpp +++ b/headers/wasm/profile.hpp @@ -1,8 +1,8 @@ #ifndef PROFILE_HPP #define PROFILE_HPP -#include "utils.hpp" #include "config.hpp" +#include "utils.hpp" #include #include #include diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 8ecd207d..98a2a33a 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -5,6 +5,9 @@ #include "config.hpp" #include "controls.hpp" #include "heap_mem_bookkeeper.hpp" +#include "immer/map.hpp" +#include "immer/map_transient.hpp" +#include "immer/vector_transient.hpp" #include "profile.hpp" #include "utils.hpp" #include @@ -238,26 +241,44 @@ class SymStack_t { 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.back(); } + 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.resize(n - offset); +#endif return std::monostate(); } void reset() { - // Reset the symbolic stack +// Reset the symbolic stack +#ifdef USE_IMM + stack = immer::vector_transient(); +#else stack.clear(); +#endif } size_t size() const { return stack.size(); } @@ -265,20 +286,36 @@ class SymStack_t { SymVal operator[](size_t index) const { return stack[index]; } private: +#ifdef USE_IMM + immer::vector_transient stack; +#else std::vector stack; +#endif }; static SymStack_t SymStack; class SymFrames_t { + public: void pushFrame(int size) { // Push a new frame with the given size +#ifdef USE_IMM + for (int i = 0; i < size; ++i) { + stack.push_back(SymVal()); + } +#else stack.resize(size + stack.size()); +#endif } std::monostate popFrame(int size) { // Pop the frame of the given size + +#ifdef USE_IMM + stack.take(stack.size() - size); +#else stack.resize(stack.size() - size); +#endif return std::monostate(); } @@ -291,12 +328,21 @@ class SymFrames_t { void set(int index, SymVal val) { // Set the symbolic value at the given index assert(val.symptr != nullptr); +#ifdef USE_IMM + stack.set(stack.size() - 1 - index, val); +#else stack[stack.size() - 1 - index] = val; +#endif } void reset() { // Reset the symbolic frames + +#ifdef USE_IMM + stack = immer::vector_transient(); +#else stack.clear(); +#endif } size_t size() const { return stack.size(); } @@ -304,7 +350,11 @@ class SymFrames_t { SymVal operator[](size_t index) const { return stack[index]; } private: +#ifdef USE_IMM + immer::vector_transient stack; +#else std::vector stack; +#endif }; struct NodeBox; @@ -312,19 +362,45 @@ struct SymEnv_t; class SymMemory_t { public: +#ifdef USE_IMM + immer::map_transient memory; +#else std::unordered_map memory; +#endif SymVal loadSymByte(int32_t addr) { - // if the address is not in the memory, it must be a zero-initialized memory +// if the address is not in the memory, it must be a zero-initialized memory +#ifdef USE_IMM auto it = memory.find(addr); - SymVal s = (it != memory.end()) - ? it->second - : SymVal(SymBookKeeper.allocate(8, 0)); + if (it != nullptr) { + return *it; + } else { + auto s = SymVal(ZeroByte); + return s; + } +#else + auto it = memory.find(addr); + SymVal s = (it != memory.end()) ? it->second : SymVal(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 : SymVal(ZeroByte); + it = memory.find(addr + 1); + SymVal s1 = it ? *it : SymVal(ZeroByte); + it = memory.find(addr + 2); + SymVal s2 = it ? *it : SymVal(ZeroByte); + it = memory.find(addr + 3); + SymVal s3 = it ? *it : SymVal(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 : SymVal(ZeroByte); @@ -336,6 +412,7 @@ class SymMemory_t { SymVal s3 = (it != memory.end()) ? it->second : SymVal(ZeroByte); return s3.concat(s2).concat(s1).concat(s0); +#endif } // when loading a symval, we need to concat 4 symbolic values @@ -349,15 +426,26 @@ class SymMemory_t { SymVal s1 = value.extract(2, 2); SymVal s2 = value.extract(3, 3); SymVal s3 = value.extract(4, 4); +#ifdef USE_IMM + memory.set(addr, s0); + memory.set(addr + 1, s1); + memory.set(addr + 2, s2); + memory.set(addr + 3, s3); +#else memory[addr] = s0; memory[addr + 1] = s1; memory[addr + 2] = s2; memory[addr + 3] = s3; +#endif return std::monostate{}; } std::monostate reset() { +#ifdef USE_IMM + memory = immer::map_transient(); +#else memory.clear(); +#endif return std::monostate{}; } }; From 6bae60f4c11ac90f17464f34fc3fb2802ec29cf1 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Mon, 6 Oct 2025 22:48:18 -0400 Subject: [PATCH 48/53] a simple test case to show immutable's improvements --- .../wasm/staged/long-trivial-execution.wat | 54 +++++++++++++++++++ .../genwasym/TestStagedConcolicEval.scala | 21 +++++++- 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 benchmarks/wasm/staged/long-trivial-execution.wat diff --git a/benchmarks/wasm/staged/long-trivial-execution.wat b/benchmarks/wasm/staged/long-trivial-execution.wat new file mode 100644 index 00000000..2a23f007 --- /dev/null +++ b/benchmarks/wasm/staged/long-trivial-execution.wat @@ -0,0 +1,54 @@ +(module + (type (;0;) (func)) + (type (;1;) (func (param i32))) + (import "console" "assert" (func (type 1))) + (func (;1;) (type 0) + (local i32 i32) + i32.const 1 + local.set 0 + i32.const 0 + local.set 1 + block + loop + local.get 0 + i32.const 10000 + i32.ge_s + br_if 1 + local.get 1 + i32.const 0 + i32.add + local.set 1 + local.get 0 + i32.const 1 + i32.add + local.set 0 + br 0 + end + end + i32.const 10000 + local.set 0 + i32.const 0 + local.set 1 + block + loop + local.get 0 + i32.eqz + br_if 1 ;; break if counter == 0 + local.get 1 + i32.const 0 + i32.sub ;; acc - 0 (no change) + local.set 1 + local.get 0 + i32.const 1 + i32.sub + local.set 0 ;; counter-- + br 0 ;; repeat loop + end + end + local.get 1 + if + i32.const 0 + call 0 + end + ) + (start 1)) diff --git a/src/test/scala/genwasym/TestStagedConcolicEval.scala b/src/test/scala/genwasym/TestStagedConcolicEval.scala index 68009388..6a505f14 100644 --- a/src/test/scala/genwasym/TestStagedConcolicEval.scala +++ b/src/test/scala/genwasym/TestStagedConcolicEval.scala @@ -37,6 +37,16 @@ class TestStagedConcolicEval extends FunSuite { println(result) exploreTreeFile } + val exploreTreeFileImm = { + // Do concolic execution with immutable data structure and snapshot reuse + val exe = s"$cppFile.imm.exe" + val exploreTreeFile = s"$filename.imm.tree.dot" + WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, true, optimizeLevel=0, if (exitByCoverage) "BY_COVERAGE" else "EARLY_EXIT", "USE_IMM") + println(s"Running compiled concolic execution with immutable data structure and snapshot reuse: $exe") + val result = Process(s"./$exe", None, "TREE_FILE" -> exploreTreeFile).!! + println(result) + exploreTreeFile + } // The explore tree generated by two executions should be same import java.nio.file.Files assert( @@ -44,6 +54,11 @@ class TestStagedConcolicEval extends FunSuite { sameElements Files.readAllBytes(java.nio.file.Paths.get(exploreTreeFileNoReuse)), s"Explore trees $exploreTreeFile and $exploreTreeFileNoReuse are different!" ) + assert( + Files.readAllBytes(java.nio.file.Paths.get(exploreTreeFile)) + sameElements Files.readAllBytes(java.nio.file.Paths.get(exploreTreeFileImm)), + s"Explore trees $exploreTreeFile and $exploreTreeFileImm are different!" + ) } // only test concrete execution and its result @@ -51,7 +66,7 @@ class TestStagedConcolicEval extends FunSuite { val moduleInst = ModuleInstance(Parser.parseFile(filename)) val cppFile = s"$filename.cpp" val exe = s"$cppFile.exe" - WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, true, optimizeLevel=0, "NO_INFO", "RUN_ONCE") + WasmToCppCompiler.compileToExe(moduleInst, main, cppFile, exe, true, optimizeLevel=0, "NO_INFO", "RUN_ONCE", "USE_IMM") import sys.process._ val result = s"./$exe".!! @@ -98,6 +113,10 @@ class TestStagedConcolicEval extends FunSuite { } test("btree-bug-finding-concolic") { testFileConcolicCpp("./benchmarks/wasm/btree/2o1u-unlabeled.wat", exitByCoverage = true) } + test("long-trivial-execution-concrete") { + // This is a example to show how much performance improvement we can get by immutable data structure + testFileConcreteCpp("./benchmarks/wasm/staged/long-trivial-execution.wat", None) + } test("return-poly - concrete") { testFileConcreteCpp("./benchmarks/wasm/staged/return_poly.wat", Some("$real_main"), expect=Some(List(42))) From 46b8a85da96132a027deb2f17cf0e4db2f40c429 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Sun, 12 Oct 2025 05:10:56 +0800 Subject: [PATCH 49/53] add a benchmark testcase --- benchmarks/wasm/compare_wasp/Makefile | 16 + benchmarks/wasm/compare_wasp/large-branch.wat | 591 ++++++++++++++++++ headers/wasm/concolic_driver.hpp | 3 +- headers/wasm/concrete_rt.hpp | 16 +- headers/wasm/config.hpp | 18 +- headers/wasm/profile.hpp | 90 ++- headers/wasm/smt_solver.hpp | 37 +- headers/wasm/symbolic_rt.hpp | 12 +- .../genwasym/TestStagedConcolicEval.scala | 3 + 9 files changed, 724 insertions(+), 62 deletions(-) create mode 100644 benchmarks/wasm/compare_wasp/Makefile create mode 100644 benchmarks/wasm/compare_wasp/large-branch.wat diff --git a/benchmarks/wasm/compare_wasp/Makefile b/benchmarks/wasm/compare_wasp/Makefile new file mode 100644 index 00000000..c9cc74ae --- /dev/null +++ b/benchmarks/wasm/compare_wasp/Makefile @@ -0,0 +1,16 @@ +.PHONY: all run clean + +all: large-branch.wat.cpp.imm.noreuse.exe large-branch.wat.cpp.imm.exe + +large-branch.wat.cpp.imm.noreuse.exe: large-branch.wat.cpp + g++ large-branch.wat.cpp -o large-branch.wat.cpp.imm.noreuse.exe -DUSE_IMM -I ../../../headers -lz3 -DENABLE_PROFILE_TIME -O3 -DNO_REUSE + +large-branch.wat.cpp.imm.exe: large-branch.wat.cpp + g++ large-branch.wat.cpp -o large-branch.wat.cpp.imm.exe -DUSE_IMM -I ../../../headers -lz3 -DENABLE_PROFILE_TIME -O3 + +run: all + ./large-branch.wat.cpp.imm.noreuse.exe + ./large-branch.wat.cpp.imm.exe + +clean: + rm -f *.exe \ No newline at end of file diff --git a/benchmarks/wasm/compare_wasp/large-branch.wat b/benchmarks/wasm/compare_wasp/large-branch.wat new file mode 100644 index 00000000..76e40191 --- /dev/null +++ b/benchmarks/wasm/compare_wasp/large-branch.wat @@ -0,0 +1,591 @@ +(module + (type (;0;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32))) + (type (;1;) (func (result i32))) + (import "spectest" "print_i32" (func (;0;) (param i32))) + + (func (;1;) (type 0) (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32) + local.get 0 + i32.const 0 + i32.gt_s + if ;; label = @1 + local.get 7 + local.get 13 + i32.add + i32.const 13 + i32.add + local.set 0 + end + local.get 1 + i32.const 1 + i32.gt_s + if ;; label = @1 + local.get 10 + local.get 7 + i32.add + i32.const 35 + i32.add + local.set 1 + end + local.get 2 + i32.const 2 + i32.gt_s + if ;; label = @1 + local.get 0 + local.get 4 + i32.add + i32.const 69 + i32.add + local.set 2 + end + local.get 3 + i32.const 3 + i32.gt_s + if ;; label = @1 + local.get 13 + local.get 14 + i32.add + i32.const 91 + i32.add + local.set 3 + end + local.get 4 + i32.const 4 + i32.gt_s + if ;; label = @1 + local.get 19 + local.get 10 + i32.add + i32.const 47 + i32.add + local.set 4 + end + local.get 5 + i32.const 5 + i32.gt_s + if ;; label = @1 + local.get 15 + local.get 6 + i32.add + i32.const 74 + i32.add + local.set 5 + end + local.get 6 + i32.const 6 + i32.gt_s + if ;; label = @1 + local.get 19 + local.get 8 + i32.add + i32.const 15 + i32.add + local.set 6 + end + local.get 7 + i32.const 7 + i32.gt_s + if ;; label = @1 + local.get 10 + local.get 9 + i32.add + i32.const 34 + i32.add + local.set 7 + end + local.get 8 + i32.const 8 + i32.gt_s + if ;; label = @1 + local.get 6 + local.get 12 + i32.add + i32.const 57 + i32.add + local.set 8 + end + local.get 9 + i32.const 9 + i32.gt_s + if ;; label = @1 + local.get 10 + local.get 11 + i32.add + i32.const 3 + i32.add + local.set 9 + end + local.get 10 + i32.const 10 + i32.gt_s + if ;; label = @1 + local.get 6 + local.get 6 + i32.add + i32.const 21 + i32.add + local.set 10 + end + local.get 11 + i32.const 11 + i32.gt_s + if ;; label = @1 + local.get 6 + local.get 5 + i32.add + i32.const 72 + i32.add + local.set 11 + end + local.get 12 + i32.const 12 + i32.gt_s + if ;; label = @1 + local.get 19 + local.get 6 + i32.add + i32.const 55 + i32.add + local.set 12 + end + local.get 13 + i32.const 13 + i32.gt_s + if ;; label = @1 + local.get 0 + local.get 3 + i32.add + i32.const 12 + i32.add + local.set 13 + end + local.get 14 + i32.const 14 + i32.gt_s + if ;; label = @1 + local.get 0 + local.get 12 + i32.add + i32.const 69 + i32.add + local.set 14 + end + local.get 15 + i32.const 15 + i32.gt_s + if ;; label = @1 + local.get 18 + local.get 5 + i32.add + i32.const 92 + i32.add + local.set 15 + end + local.get 16 + i32.const 16 + i32.gt_s + if ;; label = @1 + local.get 0 + local.get 12 + i32.add + i32.const 39 + i32.add + local.set 16 + end + local.get 17 + i32.const 17 + i32.gt_s + if ;; label = @1 + local.get 15 + local.get 8 + i32.add + i32.const 39 + i32.add + local.set 17 + end + local.get 18 + i32.const 18 + i32.gt_s + if ;; label = @1 + local.get 17 + local.get 3 + i32.add + i32.const 64 + i32.add + local.set 18 + end + local.get 19 + i32.const 19 + i32.gt_s + if ;; label = @1 + local.get 11 + local.get 5 + i32.add + i32.const 78 + i32.add + local.set 19 + end + local.get 6 + i32.const 6 + i32.gt_s + if ;; label = @1 + local.get 19 + local.get 8 + i32.add + i32.const 15 + i32.add + local.set 6 + end + local.get 7 + i32.const 7 + i32.gt_s + if ;; label = @1 + local.get 10 + local.get 9 + i32.add + i32.const 34 + i32.add + local.set 7 + end + local.get 8 + i32.const 8 + i32.gt_s + if ;; label = @1 + local.get 6 + local.get 12 + i32.add + i32.const 57 + i32.add + local.set 8 + end + local.get 9 + i32.const 9 + i32.gt_s + if ;; label = @1 + local.get 10 + local.get 11 + i32.add + i32.const 3 + i32.add + local.set 9 + end + local.get 10 + i32.const 10 + i32.gt_s + if ;; label = @1 + local.get 6 + local.get 6 + i32.add + i32.const 21 + i32.add + local.set 10 + end + local.get 11 + i32.const 11 + i32.gt_s + if ;; label = @1 + local.get 6 + local.get 5 + i32.add + i32.const 72 + i32.add + local.set 11 + end + local.get 12 + i32.const 12 + i32.gt_s + if ;; label = @1 + local.get 19 + local.get 6 + i32.add + i32.const 55 + i32.add + local.set 12 + end + local.get 13 + i32.const 13 + i32.gt_s + if ;; label = @1 + local.get 0 + local.get 3 + i32.add + i32.const 12 + i32.add + local.set 13 + end + local.get 14 + i32.const 14 + i32.gt_s + if ;; label = @1 + local.get 0 + local.get 12 + i32.add + i32.const 69 + i32.add + local.set 14 + end + local.get 15 + i32.const 15 + i32.gt_s + if ;; label = @1 + local.get 18 + local.get 5 + i32.add + i32.const 92 + i32.add + local.set 15 + end + local.get 16 + i32.const 16 + i32.gt_s + if ;; label = @1 + local.get 0 + local.get 12 + i32.add + i32.const 39 + i32.add + local.set 16 + end + local.get 1 + local.get 2 + i32.add + local.get 3 + i32.add + local.get 4 + i32.add + local.get 5 + i32.add + local.get 6 + i32.add + local.get 7 + i32.add + local.get 8 + i32.add + local.get 9 + i32.add + local.get 10 + i32.add + local.get 11 + i32.add + local.get 12 + i32.add + local.get 13 + i32.add + local.get 14 + i32.add + local.get 15 + i32.add + local.get 16 + i32.add + local.get 17 + i32.add + local.get 18 + i32.add + local.get 19 + i32.add) + (func (;2;) (type 1) (result i32) + i32.const 0 + i32.symbolic + i32.const 1 + i32.symbolic + i32.const 2 + i32.symbolic + i32.const 3 + i32.symbolic + i32.const 4 + i32.symbolic + i32.const 5 + i32.symbolic + i32.const 6 + i32.symbolic + i32.const 7 + i32.symbolic + i32.const 8 + i32.symbolic + i32.const 9 + i32.symbolic + i32.const 10 + i32.const 11 + i32.const 12 + i32.const 13 + i32.const 14 + i32.const 15 + i32.const 16 + i32.const 17 + i32.const 18 + i32.const 19 + call 3) + (func (;3;) (type 0) (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32) + local.get 0 + i32.const 0 + i32.gt_s + if ;; label = @1 + local.get 7 + local.get 13 + i32.add + i32.const 13 + i32.add + local.set 0 + end + local.get 1 + i32.const 1 + i32.gt_s + if ;; label = @1 + local.get 10 + local.get 7 + i32.add + i32.const 35 + i32.add + local.set 1 + end + local.get 2 + i32.const 2 + i32.gt_s + if ;; label = @1 + local.get 0 + local.get 4 + i32.add + i32.const 69 + i32.add + local.set 2 + end + local.get 3 + i32.const 3 + i32.gt_s + if ;; label = @1 + local.get 13 + local.get 14 + i32.add + i32.const 91 + i32.add + local.set 3 + end + local.get 4 + i32.const 4 + i32.gt_s + if ;; label = @1 + local.get 19 + local.get 10 + i32.add + i32.const 47 + i32.add + local.set 4 + end + local.get 5 + i32.const 5 + i32.gt_s + if ;; label = @1 + local.get 15 + local.get 6 + i32.add + i32.const 74 + i32.add + local.set 5 + end + local.get 6 + i32.const 6 + i32.gt_s + if ;; label = @1 + local.get 19 + local.get 8 + i32.add + i32.const 15 + i32.add + local.set 6 + end + local.get 6 + i32.const 6 + i32.gt_s + if ;; label = @1 + local.get 19 + local.get 8 + i32.add + i32.const 15 + i32.add + local.set 6 + end + local.get 7 + i32.const 7 + i32.gt_s + if ;; label = @1 + local.get 10 + local.get 9 + i32.add + i32.const 34 + i32.add + local.set 7 + end + local.get 8 + i32.const 8 + i32.gt_s + if ;; label = @1 + local.get 6 + local.get 12 + i32.add + i32.const 57 + i32.add + local.set 8 + end + local.get 9 + i32.const 9 + i32.gt_s + if ;; label = @1 + local.get 10 + local.get 11 + i32.add + i32.const 3 + i32.add + local.set 9 + end + local.get 10 + i32.const 10 + i32.gt_s + if ;; label = @1 + local.get 6 + local.get 6 + i32.add + i32.const 21 + i32.add + local.set 10 + end + local.get 1 + local.get 2 + i32.add + local.get 3 + i32.add + local.get 4 + i32.add + local.get 5 + i32.add + local.get 6 + i32.add + local.get 7 + i32.add + local.get 8 + i32.add + local.get 9 + i32.add + local.get 10 + i32.add + local.get 11 + i32.add + local.get 12 + i32.add + local.get 13 + i32.add + local.get 14 + i32.add + local.get 15 + i32.add + local.get 16 + i32.add + local.get 17 + i32.add + local.get 18 + i32.add + local.get 19 + i32.add + ) + (export "f" (func 1)) + (export "main" (func 2)) + (start 2)) + diff --git a/headers/wasm/concolic_driver.hpp b/headers/wasm/concolic_driver.hpp index 37ef4004..84e05c84 100644 --- a/headers/wasm/concolic_driver.hpp +++ b/headers/wasm/concolic_driver.hpp @@ -100,10 +100,9 @@ inline void ConcolicDriver::main_exploration_loop() { GENSYM_INFO(SymEnv.to_string()); if (auto snapshot_node = dynamic_cast(node->node.get())) { assert(REUSE_SNAPSHOT); - auto timer = ManagedTimer(); snapshot_node->get_snapshot().resume_execution(SymEnv, node); } else { - auto timer = ManagedTimer(); + auto timer = ManagedTimer(TimeProfileKind::INSTR); reset_stacks(); ExploreTree.reset_cursor(); entrypoint(); diff --git a/headers/wasm/concrete_rt.hpp b/headers/wasm/concrete_rt.hpp index c75c0215..ea321f45 100644 --- a/headers/wasm/concrete_rt.hpp +++ b/headers/wasm/concrete_rt.hpp @@ -60,21 +60,21 @@ class Stack_t { } std::monostate push(Num &&num) { - Profile.step(ProfileKind::PUSH); + Profile.step(StepProfileKind::PUSH); stack_ptr[count] = num; count++; return std::monostate{}; } std::monostate push(Num &num) { - Profile.step(ProfileKind::PUSH); + Profile.step(StepProfileKind::PUSH); stack_ptr[count] = num; count++; return std::monostate{}; } Num pop() { - Profile.step(ProfileKind::POP); + Profile.step(StepProfileKind::POP); #ifdef DEBUG assert(count > 0 && "Stack underflow"); printf("[Debug] popping a value %ld from stack, size of concrete stack is: " @@ -87,7 +87,7 @@ class Stack_t { } Num peek() { - Profile.step(ProfileKind::PEEK); + Profile.step(StepProfileKind::PEEK); #ifdef DEBUG if (count == 0) { throw std::runtime_error("Stack underflow"); @@ -99,7 +99,7 @@ class Stack_t { int32_t size() { return count; } void shift(int32_t offset, int32_t size) { - Profile.step(ProfileKind::SHIFT); + Profile.step(StepProfileKind::SHIFT); #ifdef DEBUG if (offset < 0) { throw std::out_of_range("Invalid offset: " + std::to_string(offset)); @@ -168,13 +168,13 @@ class Frames_t { } Num get(std::int32_t index) { - Profile.step(ProfileKind::GET); + Profile.step(StepProfileKind::GET); auto ret = stack_ptr[count - 1 - index]; return ret; } void set(std::int32_t index, Num num) { - Profile.step(ProfileKind::SET); + Profile.step(StepProfileKind::SET); stack_ptr[count - 1 - index] = num; } @@ -273,7 +273,7 @@ struct Memory_t { // 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(ProfileKind::MEM_GROW); + Profile.step(StepProfileKind::MEM_GROW); if (delta <= 0) { return page_count * pagesize; } diff --git a/headers/wasm/config.hpp b/headers/wasm/config.hpp index 9a34daad..76be60ac 100644 --- a/headers/wasm/config.hpp +++ b/headers/wasm/config.hpp @@ -3,12 +3,20 @@ // This file contains configuration settings for the concolic execution -// If ENABLE_PROFILE defined, the compiled program will collect and print -// profiling information -#ifdef ENABLE_PROFILE -const bool PROFILE_ENABLED = true; +// 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_ENABLED = false; +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 // This variable define when concolic execution will stop diff --git a/headers/wasm/profile.hpp b/headers/wasm/profile.hpp index e453a386..ee0bef38 100644 --- a/headers/wasm/profile.hpp +++ b/headers/wasm/profile.hpp @@ -8,7 +8,7 @@ #include #include -enum class ProfileKind { +enum class StepProfileKind { PUSH, POP, PEEK, @@ -24,74 +24,100 @@ enum class ProfileKind { // number of kinds of operations }; +enum class TimeProfileKind { + INSTR, + SOLVER, + RESUME_SNAPSHOT, + 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) {} std::monostate step() { - if (PROFILE_ENABLED) + if (PROFILE_STEP) step_count++; return std::monostate(); } - std::monostate step(ProfileKind op) { - if (PROFILE_ENABLED) + std::monostate step(StepProfileKind op) { + if (PROFILE_STEP) op_count[static_cast(op)]++; return std::monostate(); } void print_summary() { - if (PROFILE_ENABLED) { + if (PROFILE_STEP) { std::cout << "Profile Summary:" << std::endl; std::cout << "Total PUSH operations: " - << op_count[static_cast(ProfileKind::PUSH)] + << op_count[static_cast(StepProfileKind::PUSH)] << std::endl; std::cout << "Total POP operations: " - << op_count[static_cast(ProfileKind::POP)] + << op_count[static_cast(StepProfileKind::POP)] << std::endl; std::cout << "Total PEEK operations: " - << op_count[static_cast(ProfileKind::PEEK)] + << op_count[static_cast(StepProfileKind::PEEK)] << std::endl; std::cout << "Total SHIFT operations: " - << op_count[static_cast(ProfileKind::SHIFT)] + << op_count[static_cast(StepProfileKind::SHIFT)] << std::endl; std::cout << "Total SET operations: " - << op_count[static_cast(ProfileKind::SET)] + << op_count[static_cast(StepProfileKind::SET)] << std::endl; std::cout << "Total GET operations: " - << op_count[static_cast(ProfileKind::GET)] + << op_count[static_cast(StepProfileKind::GET)] << std::endl; std::cout << "Total BINARY operations: " - << op_count[static_cast(ProfileKind::BINARY)] - << std::endl; - std::cout << "Total TREE_FILL operations: " - << op_count[static_cast(ProfileKind::TREE_FILL)] - << std::endl; - std::cout << "Total CURSOR_MOVE operations: " - << op_count[static_cast(ProfileKind::CURSOR_MOVE)] + << 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(ProfileKind::MEM_GROW)] + << 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 SNAPSHOT_CREATE operations: " - << op_count[static_cast(ProfileKind::SNAPSHOT_CREATE)] - << std::endl; std::cout << "Total time for instruction execution (s): " << std::setprecision(15) << execution_time << 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)] + << std::endl; + std::cout << "Total time in resuming from snapshot (s): " + << std::setprecision(15) + << time_count[static_cast( + TimeProfileKind::RESUME_SNAPSHOT)] + << std::endl; + } } // record the time spent in main instruction execution, in seconds - void add_instruction_time(double time) { -#ifdef ENABLE_PROFILE - execution_time += time; -#endif + void add_instruction_time(TimeProfileKind kind, double time) { + time_count[static_cast(kind)] += time; } private: int step_count; - std::array(ProfileKind::OperationCount)> + std::array(StepProfileKind::OperationCount)> op_count; + std::array(TimeProfileKind::TimeOperationCount)> + time_count; double execution_time = 0.0; }; @@ -99,14 +125,18 @@ static Profile_t Profile; class ManagedTimer { public: - ManagedTimer() { start = std::chrono::high_resolution_clock::now(); } + ManagedTimer() = delete; + ManagedTimer(TimeProfileKind kind) : kind(kind) { + 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(elapsed.count()); + Profile.add_instruction_time(kind, elapsed.count()); } private: + TimeProfileKind kind; std::chrono::high_resolution_clock::time_point start; }; diff --git a/headers/wasm/smt_solver.hpp b/headers/wasm/smt_solver.hpp index 8625a65d..5df683f7 100644 --- a/headers/wasm/smt_solver.hpp +++ b/headers/wasm/smt_solver.hpp @@ -4,6 +4,7 @@ #include "concrete_rt.hpp" #include "symbolic_rt.hpp" #include "utils.hpp" +#include "wasm/profile.hpp" #include "z3++.h" #include #include @@ -15,20 +16,17 @@ class Solver { public: Solver() {} std::optional solve(const std::vector &conditions) { - // make an conjunction of all conditions - z3::expr conjunction = z3_ctx.bool_val(true); - for (const auto &cond : conditions) { - auto z3_cond = build_z3_expr(cond); - conjunction = conjunction && z3_cond != 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 - // call z3 to solve the condition z3::solver z3_solver(z3_ctx); - z3_solver.add(conjunction); - switch (z3_solver.check()) { + z3::check_result solver_result; + { + // make an conjunction of all conditions + auto conjunction = to_z3_conjunction(conditions); + // call z3 to solve the condition + auto timer = ManagedTimer(TimeProfileKind::SOLVER); + z3_solver.add(conjunction); // NOTE: half of the solver time is spent in solver.add + solver_result = z3_solver.check(); + } + switch (solver_result) { case z3::unsat: return std::nullopt; // No solution found case z3::sat: { @@ -58,6 +56,19 @@ class Solver { } private: + z3::expr to_z3_conjunction(const std::vector &conditions) { + z3::expr conjunction = z3_ctx.bool_val(true); + for (const auto &cond : conditions) { + auto z3_cond = build_z3_expr(cond); + conjunction = conjunction && z3_cond != 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; + } + z3::context z3_ctx; z3::expr build_z3_expr(const SymVal &sym_val); }; diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 98a2a33a..35e19bd8 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -761,7 +761,7 @@ inline Snapshot_t::Snapshot_t(Cont_t cont, MCont_t mcont, SymStack_t stack, SymFrames_t frames, SymMemory_t memory) : stack(std::move(stack)), frames(std::move(frames)), memory(std::move(memory)), cont(cont), mcont(mcont) { - Profile.step(ProfileKind::SNAPSHOT_CREATE); + Profile.step(StepProfileKind::SNAPSHOT_CREATE); #ifdef DEBUG std::cout << "Creating snapshot of size " << stack.size() << std::endl; #endif @@ -806,7 +806,7 @@ class ExploreTree_t { } std::monostate moveCursor(bool branch, Snapshot_t snapshot) { - Profile.step(ProfileKind::CURSOR_MOVE); + Profile.step(StepProfileKind::CURSOR_MOVE); assert(cursor != nullptr); auto if_else_node = dynamic_cast(cursor->node.get()); assert( @@ -1074,9 +1074,13 @@ inline std::monostate Snapshot_t::resume_execution(SymEnv_t &sym_env, SymStack = stack; SymFrames = frames; SymMemory = memory; - // Restore the concrete states from the symbolic states - resume_conc_states(stack, frames, memory, Stack, Frames, Memory, sym_env); + { + auto timer = ManagedTimer(TimeProfileKind::RESUME_SNAPSHOT); + // Restore the concrete states from the symbolic states + resume_conc_states(stack, frames, memory, Stack, Frames, Memory, sym_env); + } // Resume execution from the continuation + auto timer = ManagedTimer(TimeProfileKind::INSTR); return cont(mcont); } diff --git a/src/test/scala/genwasym/TestStagedConcolicEval.scala b/src/test/scala/genwasym/TestStagedConcolicEval.scala index 6a505f14..3bee9f75 100644 --- a/src/test/scala/genwasym/TestStagedConcolicEval.scala +++ b/src/test/scala/genwasym/TestStagedConcolicEval.scala @@ -171,4 +171,7 @@ class TestStagedConcolicEval extends FunSuite { testFileConcreteCpp("./benchmarks/wasm/staged/brtable.wat") } + test("large-branch-concrete") { + testFileConcreteCpp("./benchmarks/wasm/compare_wasp/large-branch.wat") + } } From db4925140d7df371bb25ec6fce1ff8be08d5f868 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Sun, 12 Oct 2025 06:12:50 +0800 Subject: [PATCH 50/53] a pool to store symbolic concrete --- headers/wasm/symbolic_rt.hpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 35e19bd8..798adf4d 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -120,8 +120,16 @@ static SymVal make_symbolic(int index) { return SymVal(SymBookKeeper.allocate(index)); } +static std::unordered_map concrete_pool; + inline SymVal Concrete(Num num) { - return SymVal(SymBookKeeper.allocate(num)); + if (concrete_pool.find(num.toInt()) != concrete_pool.end()) { + return concrete_pool[num.toInt()]; + } + + auto new_val = SymVal(SymBookKeeper.allocate(num)); + concrete_pool[num.toInt()] = new_val; + return new_val; } // Extract is different from other operations, it only has one symbolic operand, From b4d18ba03bc2a08dd0471fab89ce43779f92704f Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Sun, 12 Oct 2025 06:16:36 +0800 Subject: [PATCH 51/53] a example to show when snapshot is highly effective --- .../wasm/compare_wasp/small-snapshot.wat | 22301 ++++++++++++++++ .../genwasym/TestStagedConcolicEval.scala | 4 + 2 files changed, 22305 insertions(+) create mode 100644 benchmarks/wasm/compare_wasp/small-snapshot.wat diff --git a/benchmarks/wasm/compare_wasp/small-snapshot.wat b/benchmarks/wasm/compare_wasp/small-snapshot.wat new file mode 100644 index 00000000..991a3d25 --- /dev/null +++ b/benchmarks/wasm/compare_wasp/small-snapshot.wat @@ -0,0 +1,22301 @@ +(module + (type (;0;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32))) + (type (;1;) (func (result i32))) + (import "spectest" "print_i32" (func (;0;) (param i32))) + + (func (;1;) (type 0) (result i32) + i32.const 0 + i32.symbolic + call 2 + if (result i32) + i32.const 42 + else + i32.const 19 + end) + + (func (;2;) (param i32) (result i32) + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + i32.const 3 + drop + local.get 0) + (export "f" (func 2)) + (export "main" (func 1)) + (start 1)) + diff --git a/src/test/scala/genwasym/TestStagedConcolicEval.scala b/src/test/scala/genwasym/TestStagedConcolicEval.scala index 3bee9f75..86f55daa 100644 --- a/src/test/scala/genwasym/TestStagedConcolicEval.scala +++ b/src/test/scala/genwasym/TestStagedConcolicEval.scala @@ -174,4 +174,8 @@ class TestStagedConcolicEval extends FunSuite { test("large-branch-concrete") { testFileConcreteCpp("./benchmarks/wasm/compare_wasp/large-branch.wat") } + + test("small-snapshot-concrete") { + testFileConcreteCpp("./benchmarks/wasm/compare_wasp/small-snapshot.wat", Some("main")) + } } From 960aacbb51fbce95c16c9f4abfebbe4910a713b6 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Sat, 18 Oct 2025 19:47:47 +0800 Subject: [PATCH 52/53] some infra to support the cost model to decide if we should create snapshot --- headers/wasm/concolic_driver.hpp | 26 +-- headers/wasm/config.hpp | 6 + headers/wasm/controls.hpp | 7 + headers/wasm/profile.hpp | 27 +++ headers/wasm/symbolic_rt.hpp | 201 +++++++++++++++--- .../scala/wasm/StagedConcolicMiniWasm.scala | 61 ++++-- 6 files changed, 261 insertions(+), 67 deletions(-) diff --git a/headers/wasm/concolic_driver.hpp b/headers/wasm/concolic_driver.hpp index 84e05c84..80ff38d5 100644 --- a/headers/wasm/concolic_driver.hpp +++ b/headers/wasm/concolic_driver.hpp @@ -84,6 +84,11 @@ inline void ConcolicDriver::main_exploration_loop() { continue; } + if (INTERACTIVE_MODE) { + std::cout << "Press Enter to continue to the next path..." << std::endl; + std::cin.get(); + } + auto cond = node->collect_path_conds(); auto result = solver.solve(cond); if (!result.has_value()) { @@ -98,15 +103,7 @@ inline void ConcolicDriver::main_exploration_loop() { try { GENSYM_INFO("Now execute the program with symbolic environment: "); GENSYM_INFO(SymEnv.to_string()); - if (auto snapshot_node = dynamic_cast(node->node.get())) { - assert(REUSE_SNAPSHOT); - snapshot_node->get_snapshot().resume_execution(SymEnv, node); - } else { - auto timer = ManagedTimer(TimeProfileKind::INSTR); - reset_stacks(); - ExploreTree.reset_cursor(); - entrypoint(); - } + { node->reach_here(entrypoint); } GENSYM_INFO("Execution finished successfully with symbolic environment:"); GENSYM_INFO(SymEnv.to_string()); @@ -140,17 +137,6 @@ inline void ConcolicDriver::run() { Profile.print_summary(); } -static std::monostate reset_stacks() { - Stack.reset(); - SymStack.reset(); - Frames.reset(); - SymFrames.reset(); - Memory.reset(); - SymMemory.reset(); - initRand(); - return std::monostate{}; -} - static void start_concolic_execution_with( std::function entrypoint, int branchCount) { diff --git a/headers/wasm/config.hpp b/headers/wasm/config.hpp index 76be60ac..a9c8d5a1 100644 --- a/headers/wasm/config.hpp +++ b/headers/wasm/config.hpp @@ -49,4 +49,10 @@ static const bool IMMUTABLE_SYMS = true; static const bool IMMUTABLE_SYMS = false; #endif +#ifdef INTERACTIVE +static const bool INTERACTIVE_MODE = true; +#else +static const bool INTERACTIVE_MODE = false; +#endif + #endif // CONFIG_HPP \ No newline at end of file diff --git a/headers/wasm/controls.hpp b/headers/wasm/controls.hpp index 513f0692..97ba0130 100644 --- a/headers/wasm/controls.hpp +++ b/headers/wasm/controls.hpp @@ -9,4 +9,11 @@ using MCont_t = std::function; using Cont_t = std::function; +struct Control { + Cont_t cont; + MCont_t mcont; + + Control(Cont_t cont, MCont_t mcont) : cont(cont), mcont(mcont) {} +}; + #endif // WASM_CONTROLS_HPP \ No newline at end of file diff --git a/headers/wasm/profile.hpp b/headers/wasm/profile.hpp index ee0bef38..bc4ce911 100644 --- a/headers/wasm/profile.hpp +++ b/headers/wasm/profile.hpp @@ -28,6 +28,7 @@ enum class TimeProfileKind { INSTR, SOLVER, RESUME_SNAPSHOT, + COUNT_SYM_SIZE, TimeOperationCount // keep this as the last element, this is used to get the // number of kinds of operations }; @@ -103,6 +104,11 @@ class Profile_t { << 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; } } @@ -140,4 +146,25 @@ class ManagedTimer { std::chrono::high_resolution_clock::time_point start; }; +struct CostManager_t { + int instr_cost; + + CostManager_t() : instr_cost(0) {} + + std::monostate add_instr_cost(int n) { + instr_cost += n; + return {}; + } + + int dump_instr_cost() { + auto cost = instr_cost; + instr_cost = 0; + return normalize_cost(cost); + } + + int normalize_cost(int cost) { return 1 * cost; } +}; + +static CostManager_t CostManager; + #endif // PROFILE_HPP \ No newline at end of file diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 798adf4d..1423dcf1 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -27,8 +27,9 @@ class Symbolic { public: - Symbolic() {} // TODO: remove this default constructor later + Symbolic() {} virtual ~Symbolic() = default; // Make Symbolic polymorphic + virtual int size() = 0; }; static int max_id = 0; @@ -39,6 +40,7 @@ class Symbol : public Symbolic { // for now we just assume that only i32 will be used Symbol(int id) : id(id) { max_id = std::max(max_id, id); } int get_id() const { return id; } + int size() override { return 1; } private: int id; @@ -48,16 +50,18 @@ class SymConcrete : public Symbolic { public: Num value; SymConcrete(Num num) : value(num) {} + int size() override { return 1; } }; class SmallBV : public Symbolic { public: - SmallBV(int size, int64_t value) : size(size), value(value) {} - int get_size() const { return size; } + SmallBV(int width, int64_t value) : width(width), value(value) {} + int get_size() const { return width; } int64_t get_value() const { return value; } + int size() override { return 1; } private: - int size; // in bits + int width; // in bits int64_t value; }; struct SymBinary; @@ -111,6 +115,7 @@ struct SymVal { // TODO: add bitwise operations, and use the underlying bitvector theory bool is_concrete() const; + int size() const { return symptr->size(); } private: static SymVal make_binary(Operation op, const SymVal &lhs, const SymVal &rhs); @@ -142,6 +147,7 @@ struct SymExtract : Symbolic { SymExtract(SymVal value, int high, int low) : value(value), high(high), low(low) {} + int size() override { return 1 + value.size(); } }; struct SymBinary : Symbolic { @@ -151,6 +157,7 @@ struct SymBinary : Symbolic { SymBinary(Operation op, SymVal lhs, SymVal rhs) : op(op), lhs(lhs), rhs(rhs) {} + int size() override { return 1 + lhs.size() + rhs.size(); } }; inline SymVal SymVal::add(const SymVal &other) const { @@ -293,6 +300,14 @@ class SymStack_t { SymVal operator[](size_t index) const { return stack[index]; } + int cost_of_copy() const { + int cost = 0; + for (size_t i = 0; i < stack.size(); ++i) { + cost += stack[i].size(); + } + return cost; + } + private: #ifdef USE_IMM immer::vector_transient stack; @@ -357,6 +372,14 @@ class SymFrames_t { SymVal operator[](size_t index) const { return stack[index]; } + int cost_of_copy() const { + int cost = 0; + for (size_t i = 0; i < stack.size(); ++i) { + cost += stack[i].size(); + } + return cost; + } + private: #ifdef USE_IMM immer::vector_transient stack; @@ -456,6 +479,15 @@ class SymMemory_t { #endif return std::monostate{}; } + + int cost_of_copy() const { +#ifdef USE_IMM + // If we use immer, the copy cost should be negligible + return 0; +#else + return memory.size(); +#endif + } }; static SymMemory_t SymMemory; @@ -471,7 +503,9 @@ class Snapshot_t { SymFrames_t get_frames() const { return frames; } SymMemory_t get_memory() const { return memory; } - std::monostate resume_execution(SymEnv_t &sym_env, NodeBox *node) const; + std::monostate resume_execution(NodeBox *node) const; + + static int cost_of_snapshot(); private: SymStack_t stack; @@ -485,13 +519,14 @@ class Snapshot_t { static SymFrames_t SymFrames; static SymFrames_t SymGlobals; -static Snapshot_t makeSnapshot(Cont_t cont, MCont_t mcont) { - if (REUSE_SNAPSHOT) { - return Snapshot_t(cont, mcont, SymStack, SymFrames, SymMemory); - } else { - // create a dummy snapshot, which will not be used - return Snapshot_t(); - } +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, + SymMemory); } struct Node; @@ -500,6 +535,8 @@ struct NodeBox { explicit NodeBox(NodeBox *parent); std::unique_ptr node; NodeBox *parent; + int cost; + int instr_cost; bool fillIfElseNode(SymVal cond, int id); std::monostate fillFinishedNode(); @@ -508,6 +545,8 @@ struct NodeBox { std::monostate fillSnapshotNode(Snapshot_t snapshot); bool isUnexplored() const; std::vector collect_path_conds(); + int min_cost_of_reaching_here(); + void reach_here(std::function); }; struct Node { @@ -555,10 +594,17 @@ struct IfElseNode : Node { std::unique_ptr true_branch; std::unique_ptr false_branch; int id; + std::optional snapshot; IfElseNode(SymVal cond, NodeBox *parent, int id) : cond(cond), true_branch(std::make_unique(parent)), - false_branch(std::make_unique(parent)), id(id) {} + false_branch(std::make_unique(parent)), id(id), + snapshot(std::nullopt) {} + + IfElseNode(SymVal cond, NodeBox *parent, int id, Snapshot_t snapshot) + : cond(cond), true_branch(std::make_unique(parent)), + false_branch(std::make_unique(parent)), id(id), + snapshot(snapshot) {} std::string to_string() override { std::string result = "IfElseNode {\n"; @@ -622,6 +668,7 @@ 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); } protected: void generate_dot(std::ostream &os, int parent_dot_id, @@ -689,11 +736,15 @@ struct Unreachable : Node { inline NodeBox::NodeBox(NodeBox *parent) : node(std::make_unique()), /* TODO: avoid allocation of unexplored node */ - parent(parent) {} + parent(parent), cost(-1), instr_cost(0) {} inline bool NodeBox::fillIfElseNode(SymVal cond, int id) { // fill the current NodeBox with an ifelse branch node when it's unexplored - if (this->isUnexplored()) { + if (auto ptr = dynamic_cast(node.get())) { + node = + std::make_unique(cond, this, id, ptr->move_out_snapshot()); + return true; + } else if (dynamic_cast(node.get())) { node = std::make_unique(cond, this, id); return true; } @@ -738,8 +789,14 @@ inline std::monostate NodeBox::fillUnreachableNode() { } inline bool NodeBox::isUnexplored() const { - return dynamic_cast(node.get()) != nullptr || - dynamic_cast(node.get()) != nullptr; + assert(node != nullptr); + if (dynamic_cast(node.get()) != nullptr) { + return true; + } + if (dynamic_cast(node.get()) != nullptr) { + return true; + } + return false; } inline std::vector NodeBox::collect_path_conds() { @@ -765,6 +822,25 @@ inline std::vector NodeBox::collect_path_conds() { return result; } +inline int NodeBox::min_cost_of_reaching_here() { + if (cost != -1) { + return cost; + } + + if (auto snapshot = dynamic_cast(node.get())) { + cost = snapshot->get_snapshot().cost_of_snapshot(); + return cost; + } + + if (parent != nullptr) { + auto parent_cost = parent->min_cost_of_reaching_here(); + cost = parent_cost + instr_cost; + return cost; + } + cost = instr_cost; + return cost; +} + inline Snapshot_t::Snapshot_t(Cont_t cont, MCont_t mcont, SymStack_t stack, SymFrames_t frames, SymMemory_t memory) : stack(std::move(stack)), frames(std::move(frames)), @@ -775,6 +851,12 @@ inline Snapshot_t::Snapshot_t(Cont_t cont, MCont_t mcont, SymStack_t stack, #endif } +inline int Snapshot_t::cost_of_snapshot() { + auto cost_of_stack_copy = SymStack.cost_of_copy(); + auto cost_of_frame_copy = SymFrames.cost_of_copy(); + auto cost_of_memory_copy = SymMemory.cost_of_copy(); + return cost_of_stack_copy + cost_of_frame_copy + cost_of_memory_copy; +} class ExploreTree_t { public: explicit ExploreTree_t() @@ -813,17 +895,37 @@ class ExploreTree_t { return std::monostate(); } - std::monostate moveCursor(bool branch, Snapshot_t snapshot) { + bool worth_to_create_snapshot() { + // find out the best way to reach the current position via our cost model + auto snapshot_cost = Snapshot_t::cost_of_snapshot(); + auto parent_cost = + cursor->parent ? cursor->parent->min_cost_of_reaching_here() : 0; + auto exec_from_parent_cost = + cursor->min_cost_of_reaching_here() + cursor->instr_cost; + // TODO: return a random result to test the infrastructure, replace this to the + // real cost model later + if (std::rand() % 2 == 0) { + return true; + } else { + return false; + } + } + + 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!"); + int cost_from_parent = CostManager.dump_instr_cost(); if (branch) { true_branch_cov_map[if_else_node->id] = true; if (REUSE_SNAPSHOT) { - if_else_node->false_branch->fillSnapshotNode(snapshot); + if (worth_to_create_snapshot()) { + auto snapshot = makeSnapshot(control); + if_else_node->false_branch->fillSnapshotNode(snapshot); + } } else { // Do nothing, the initial value of the branch is an unexplored node } @@ -831,7 +933,10 @@ class ExploreTree_t { } else { false_branch_cov_map[if_else_node->id] = true; if (REUSE_SNAPSHOT) { - if_else_node->true_branch->fillSnapshotNode(snapshot); + if (worth_to_create_snapshot()) { + auto snapshot = makeSnapshot(control); + if_else_node->true_branch->fillSnapshotNode(snapshot); + } } else { // Do nothing, the initial value of the branch is an unexplored node } @@ -955,6 +1060,41 @@ class SymEnv_t { static SymEnv_t SymEnv; +static std::monostate reset_stacks() { + Stack.reset(); + SymStack.reset(); + Frames.reset(); + SymFrames.reset(); + Memory.reset(); + SymMemory.reset(); + initRand(); + return std::monostate{}; +} + +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); + } + 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; int width; // in bits @@ -1072,8 +1212,7 @@ static void resume_conc_states(const SymStack_t &sym_stack, resume_conc_memory(sym_memory, memory, sym_env); } -inline std::monostate Snapshot_t::resume_execution(SymEnv_t &sym_env, - NodeBox *node) const { +inline std::monostate Snapshot_t::resume_execution(NodeBox *node) const { // Reset explore tree's cursor ExploreTree.set_cursor(node); @@ -1085,11 +1224,25 @@ inline std::monostate Snapshot_t::resume_execution(SymEnv_t &sym_env, { auto timer = ManagedTimer(TimeProfileKind::RESUME_SNAPSHOT); // Restore the concrete states from the symbolic states - resume_conc_states(stack, frames, memory, Stack, Frames, Memory, sym_env); + resume_conc_states(stack, frames, memory, Stack, Frames, Memory, SymEnv); + } + int sym_size = 0; + { + auto timer = ManagedTimer(TimeProfileKind::COUNT_SYM_SIZE); + for (size_t i = 0; i < stack.size(); ++i) { + sym_size += stack[i].size(); + } + for (size_t i = 0; i < frames.size(); ++i) { + sym_size += frames[i].size(); + } } + std::cout << "[Info] Resumed symbolic execution from snapshot, total " + "symbolic expression and frame size: " + << sym_size << std::endl; + // Resume execution from the continuation auto timer = ManagedTimer(TimeProfileKind::INSTR); return cont(mcont); } -#endif // WASM_SYMBOLIC_RT_HPP \ No newline at end of file +#endif // WASM_SYMBOLIC_RT_HPP diff --git a/src/main/scala/wasm/StagedConcolicMiniWasm.scala b/src/main/scala/wasm/StagedConcolicMiniWasm.scala index 739a0f39..cafddbf0 100644 --- a/src/main/scala/wasm/StagedConcolicMiniWasm.scala +++ b/src/main/scala/wasm/StagedConcolicMiniWasm.scala @@ -136,14 +136,20 @@ trait StagedWasmEvaluator extends SAIOps { }) } + trait Control - // TODO: maybe we don't need concern snapshot at compile time at all - trait Snapshot - - // Create a snapshot of the symbolic execution, we should ensure that current symstack is in use + // Save the current control information into a structure Control // We need to store the control information, so we can resume the execution later - def makeSnapshot(kont: Rep[Cont[Unit]], mkont: Rep[MCont[Unit]]): Rep[Snapshot] = { - "snapshot-make".reflectCtrlWith[Snapshot](kont, mkont) + def makeControl(kont: Rep[Cont[Unit]], mkont: Rep[MCont[Unit]]): Rep[Control] = { + "control-make".reflectCtrlWith[Control](kont, mkont) + } + + var instrCost: Int = 0 + + def addInstrCost(): Rep[Unit] = { + "add-instr-cost".reflectCtrlWith[Unit](instrCost) + instrCost = 0 + () } def eval(insts: List[Instr], @@ -152,7 +158,7 @@ trait StagedWasmEvaluator extends SAIOps { trail: Trail[Unit]) (implicit ctx: Context): Rep[Unit] = { if (insts.isEmpty) return kont(ctx)(mkont) - + instrCost += 1 // Predef.println(s"[DEBUG] Evaluating instructions: ${insts.mkString(", ")}") // Predef.println(s"[DEBUG] Current context: $ctx") @@ -352,19 +358,21 @@ trait StagedWasmEvaluator extends SAIOps { eval(els, restK _, mk, restK _ :: trail)(newCtx) }) if (cond.toInt != 0) { - val snapshot = makeSnapshot(elsK, mkont) - ExploreTree.moveCursor(true, snapshot) + val control = makeControl(elsK, mkont) + ExploreTree.moveCursor(true, control) thnK(mkont) } else { - val snapshot = makeSnapshot(thnK, mkont) - ExploreTree.moveCursor(false, snapshot) + val control = makeControl(thnK, mkont) + ExploreTree.moveCursor(false, control) elsK(mkont) } () case Br(label) => info(s"Jump to $label") + addInstrCost() trail(label)(ctx)(mkont) case BrIf(label) => + addInstrCost() val (ty, newCtx) = ctx.pop() val cond = Stack.popC(ty) val symCond = Stack.popS(ty) @@ -379,17 +387,18 @@ trait StagedWasmEvaluator extends SAIOps { }) if (cond.toInt != 0) { info(s"Jump to $label") - val snapshot = makeSnapshot(elsK, mkont) - ExploreTree.moveCursor(true, snapshot) + val control = makeControl(elsK, mkont) + ExploreTree.moveCursor(true, control) thnK(mkont) } else { info(s"Continue") - val snapshot = makeSnapshot(thnK, mkont) - ExploreTree.moveCursor(false, snapshot) + val control = makeControl(thnK, mkont) + ExploreTree.moveCursor(false, control) elsK(mkont) } () case BrTable(labels, default) => + addInstrCost() val (ty, newCtx) = ctx.pop() def aux(choices: List[Int], idx: Int, mkont: Rep[MCont[Unit]]): Rep[Unit] = { if (choices.isEmpty) { @@ -417,13 +426,13 @@ trait StagedWasmEvaluator extends SAIOps { aux(choices.tail, idx + 1, mk) }) if (cond.toInt != 0) { - val snapshot = makeSnapshot(elsK, mkont) - ExploreTree.moveCursor(true, snapshot) + val control = makeControl(elsK, mkont) + ExploreTree.moveCursor(true, control) thnK(mkont) } else { - val snapshot = makeSnapshot(thnK, mkont) - ExploreTree.moveCursor(false, snapshot) + val control = makeControl(thnK, mkont) + ExploreTree.moveCursor(false, control) elsK(mkont) } } @@ -451,6 +460,8 @@ trait StagedWasmEvaluator extends SAIOps { module.funcs(funcIndex) match { case FuncDef(_, FuncBodyDef(ty, _, bodyLocals, body)) => val locals = bodyLocals ++ ty.inps + instrCost += locals.size * 2 - 1 + addInstrCost() val callee = if (compileCache.contains(funcIndex)) { compileCache(funcIndex) @@ -505,12 +516,14 @@ trait StagedWasmEvaluator extends SAIOps { case Import("console", "log", _) | Import("spectest", "print_i32", _) => //println(s"[DEBUG] current stack: $stack") + addInstrCost() val (ty, newCtx) = ctx.pop() val v = Stack.popC(ty) Stack.popS(ty) println(v.toInt) eval(rest, kont, mkont, trail)(newCtx) case Import("console", "assert", _) => + addInstrCost() val (ty, newCtx) = ctx.pop() val v = Stack.popC(ty) // TODO: We should also add s into exploration tree @@ -858,9 +871,9 @@ trait StagedWasmEvaluator extends SAIOps { "tree-fill-finished".reflectCtrlWith[Unit]() } - def moveCursor(branch: Boolean, snapshot: Rep[Snapshot]): Rep[Unit] = { + def moveCursor(branch: Boolean, control: Rep[Control]): Rep[Unit] = { // when moving cursor from to an unexplored node, we need to change the reuse state - "tree-move-cursor".reflectCtrlWith[Unit](branch, snapshot) + "tree-move-cursor".reflectCtrlWith[Unit](branch, control) } def print(): Rep[Unit] = { @@ -1388,8 +1401,8 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { emit("Stack.pop()") case Node(_, "sym-stack-pop", _, _) => emit("SymStack.pop()") - case Node(_, "snapshot-make", List(k, mk), _) => - emit("makeSnapshot("); shallow(k); emit(", "); shallow(mk); emit(")") + case Node(_, "control-make", List(k, mk), _) => + emit("makeControl("); shallow(k); emit(", "); shallow(mk); emit(")") case Node(_, "frame-pop", List(i), _) => emit("Frames.popFrame("); shallow(i); emit(")") case Node(_, "sym-frame-pop", List(i), _) => @@ -1511,6 +1524,8 @@ trait StagedWasmCppGen extends CGenBase with CppSAICodeGenBase { emit("ExploreTree.fillFinishedNode()") case Node(_, "tree-move-cursor", List(b, snapshot), _) => emit("ExploreTree.moveCursor("); shallow(b); emit(", "); shallow(snapshot); emit(")") + case Node(_, "add-instr-cost", List(n), _) => + emit("CostManager.add_instr_cost("); shallow(n); emit(")") case Node(_, "tree-print", List(), _) => emit("ExploreTree.print()") case Node(_, "tree-dump-graphviz", List(f), _) => From a1501030b56be67d3329b0516b4500d1f7dc4de3 Mon Sep 17 00:00:00 2001 From: butterunderflow Date: Sat, 18 Oct 2025 20:59:19 +0800 Subject: [PATCH 53/53] a cost model to determin if we want to create snapshot --- benchmarks/wasm/compare_wasp/Makefile | 10 ++++-- headers/wasm/config.hpp | 6 ++++ headers/wasm/profile.hpp | 4 +++ headers/wasm/symbolic_rt.hpp | 46 ++++++++++++++------------- 4 files changed, 41 insertions(+), 25 deletions(-) diff --git a/benchmarks/wasm/compare_wasp/Makefile b/benchmarks/wasm/compare_wasp/Makefile index c9cc74ae..1c3e58c6 100644 --- a/benchmarks/wasm/compare_wasp/Makefile +++ b/benchmarks/wasm/compare_wasp/Makefile @@ -1,12 +1,16 @@ .PHONY: all run clean -all: large-branch.wat.cpp.imm.noreuse.exe large-branch.wat.cpp.imm.exe +all: large-branch.wat.cpp.imm.noreuse.exe large-branch.wat.cpp.imm.exe large-branch.wat.cpp.imm.cost-model.exe large-branch.wat.cpp.imm.noreuse.exe: large-branch.wat.cpp - g++ large-branch.wat.cpp -o large-branch.wat.cpp.imm.noreuse.exe -DUSE_IMM -I ../../../headers -lz3 -DENABLE_PROFILE_TIME -O3 -DNO_REUSE + g++ large-branch.wat.cpp -o large-branch.wat.cpp.imm.noreuse.exe -DUSE_IMM -I ../../../headers -lz3 -DENABLE_PROFILE_TIME -O3 -DNO_REUSE -DENABLE_PROFILE_STEP large-branch.wat.cpp.imm.exe: large-branch.wat.cpp - g++ large-branch.wat.cpp -o large-branch.wat.cpp.imm.exe -DUSE_IMM -I ../../../headers -lz3 -DENABLE_PROFILE_TIME -O3 + g++ large-branch.wat.cpp -o large-branch.wat.cpp.imm.exe -DUSE_IMM -I ../../../headers -lz3 -DENABLE_PROFILE_TIME -O3 -DENABLE_PROFILE_STEP + +large-branch.wat.cpp.imm.cost-model.exe: large-branch.wat.cpp + g++ large-branch.wat.cpp -o large-branch.wat.cpp.imm.cost-model.exe -DUSE_IMM -I ../../../headers -lz3 -DENABLE_PROFILE_TIME -O3 -DUSE_COST_MODEL + run: all ./large-branch.wat.cpp.imm.noreuse.exe diff --git a/headers/wasm/config.hpp b/headers/wasm/config.hpp index a9c8d5a1..037c6263 100644 --- a/headers/wasm/config.hpp +++ b/headers/wasm/config.hpp @@ -55,4 +55,10 @@ static const bool INTERACTIVE_MODE = true; 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/headers/wasm/profile.hpp b/headers/wasm/profile.hpp index bc4ce911..9814b5eb 100644 --- a/headers/wasm/profile.hpp +++ b/headers/wasm/profile.hpp @@ -20,6 +20,7 @@ enum class StepProfileKind { 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 }; @@ -87,6 +88,9 @@ class Profile_t { << op_count[static_cast( StepProfileKind::SNAPSHOT_CREATE)] << std::endl; + std::cout << "Total SYM_EVAL operations: " + << op_count[static_cast(StepProfileKind::SYM_EVAL)] + << std::endl; std::cout << "Total time for instruction execution (s): " << std::setprecision(15) << execution_time << std::endl; } diff --git a/headers/wasm/symbolic_rt.hpp b/headers/wasm/symbolic_rt.hpp index 1423dcf1..6edef6ee 100644 --- a/headers/wasm/symbolic_rt.hpp +++ b/headers/wasm/symbolic_rt.hpp @@ -855,7 +855,8 @@ inline int Snapshot_t::cost_of_snapshot() { auto cost_of_stack_copy = SymStack.cost_of_copy(); auto cost_of_frame_copy = SymFrames.cost_of_copy(); auto cost_of_memory_copy = SymMemory.cost_of_copy(); - return cost_of_stack_copy + cost_of_frame_copy + cost_of_memory_copy; + return 5.336 * + (cost_of_stack_copy + cost_of_frame_copy + cost_of_memory_copy); } class ExploreTree_t { public: @@ -896,19 +897,23 @@ class ExploreTree_t { } bool worth_to_create_snapshot() { + if (!ENABLE_COST_MODEL) { + return REUSE_SNAPSHOT; + } // find out the best way to reach the current position via our cost model auto snapshot_cost = Snapshot_t::cost_of_snapshot(); - auto parent_cost = - cursor->parent ? cursor->parent->min_cost_of_reaching_here() : 0; - auto exec_from_parent_cost = - cursor->min_cost_of_reaching_here() + cursor->instr_cost; - // TODO: return a random result to test the infrastructure, replace this to the - // real cost model later - if (std::rand() % 2 == 0) { - return true; + int reach_parent_cost = 0; + if (cursor->parent) { + reach_parent_cost = cursor->parent->min_cost_of_reaching_here(); } else { - return false; + reach_parent_cost = 0; } + auto parent_cost = + cursor->parent ? cursor->parent->min_cost_of_reaching_here() : 0; + auto exec_from_parent_cost = reach_parent_cost + cursor->instr_cost; + GENSYM_INFO("The score of snapshot tendency: " + std::to_string(exec_from_parent_cost - + snapshot_cost)); + return snapshot_cost <= exec_from_parent_cost; } std::monostate moveCursor(bool branch, Control control) { @@ -921,22 +926,18 @@ class ExploreTree_t { int cost_from_parent = CostManager.dump_instr_cost(); if (branch) { true_branch_cov_map[if_else_node->id] = true; - if (REUSE_SNAPSHOT) { - if (worth_to_create_snapshot()) { - auto snapshot = makeSnapshot(control); - if_else_node->false_branch->fillSnapshotNode(snapshot); - } + if (worth_to_create_snapshot()) { + 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 (REUSE_SNAPSHOT) { - if (worth_to_create_snapshot()) { - auto snapshot = makeSnapshot(control); - if_else_node->true_branch->fillSnapshotNode(snapshot); - } + if (worth_to_create_snapshot()) { + auto snapshot = makeSnapshot(control); + if_else_node->true_branch->fillSnapshotNode(snapshot); } else { // Do nothing, the initial value of the branch is an unexplored node } @@ -1077,8 +1078,8 @@ inline void NodeBox::reach_here(std::function entrypoint) { assert(REUSE_SNAPSHOT); auto snap = snapshot->get_snapshot(); snap.resume_execution(this); - } - if (parent == nullptr) { + 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() && @@ -1104,6 +1105,7 @@ struct EvalRes { // 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, 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, 32);