diff --git a/GasCosts.tex b/GasCosts.tex new file mode 100644 index 0000000..0a1dc3d --- /dev/null +++ b/GasCosts.tex @@ -0,0 +1,272 @@ +\documentclass{article} +\usepackage[utf8]{inputenc} +\usepackage{caption} + +\title{AlchemyVM Gas Calculation} + +\begin{document} + +\maketitle + +\section{Gas Cost Calculation +Logic} + +According to CPU World \cite{CPU World} there were 55 CPU models released by AMD and Intel in 2018, with varying clock speeds. Clock speeds across all 55 models average about 3.3 GHz, with a mode of 3.2 GHz. + +Assuming that we're forward-thinking developers who want our products to be relevant and accurate for as long as possible, it makes sense to use 2018 as the benchmark year for clock speeds (as it's the most recent complete dataset); although the majority of computers in existence aren't built in 2018, the majority will shift over time (assuming an average computer life expectancy of 2-3 years), this means each year, on average, roughly 33\% of computers will get upgraded, which leads to the assumption that roughly 33\% of computers in use as of 2018 we're obtained in 2018 -- likely featuring a CPU released some time in 2018. These numbers are likely much more skewed but we can assume a roll over to keep things from getting too complicated. + +We can say that we want 1 second of execution time to cost 1 billion gas. If we use 3.2GHz as our baseline CPU clock speed, we can find the gas cost per cycle by dividing the gas cost per second by the clock speed (\texttt{1,000,000,000 / 3,200,000,000}), which gives a cost per cycle of 0.3125. From here, the gas cost for any given operation can be calculated as \texttt{instruction\_cpu\_cycles\ *\ 0.3125}; an operation that takes 20 CPU cycles to complete would cost 6.25 gas. +\\\\ + +Due to the fact that CPU architectures vary, counting clock cycles on the CPU that is running the program is unreliable; gas needs to be deterministic (the same program executed on different CPUs will cost the same amount of gas). It is instead necessary to choose a specific architecture from which these cycle counts can be based; AlchemyVM makes use of the instruction set latency (clock cycles) defined in Appendix C of Intel's Architecture Optimization Manual \cite{Intel Manual}. Each VM instruction is decomposed to the approximate necessary CPU instructions that are necessary to execute the operation defined by the VM instruction, the latencies of the CPU instructions are then summed -- this becomes the clock count for the specified VM instruction. This clock count is then plugged into the above equation to determine the gas cost for the given operation. + +\begin{table} +\centering +\captionsetup{labelformat=empty} +\caption{Gas Price per OpCode} +\begin{tabular}{|l|l|l|l|} +\hline +\textbf{~ OpCode~~} & \textbf{~ Gas Price~~} & \textbf{~ Corresponding CPU Instructions~ ~} & ~ \textbf{Notes~~} \\ +\hline +~ i32.add & ~ 0.3125 & ~ ADD & \\ +\hline +~ i32.sub & ~ 0.3125 & ~ SUB & \\ +\hline +~ i32.mul & ~ 1.5625 & ~ IMUL r32 & \\ +\hline +~ i32.div\_s & ~ 6.25 & ~ IDIV r32 & ~ [1]~~ \\ +\hline +~ i32.div\_u & ~ 6.25 & ~ DIV r32 & ~ [1]~~ \\ +\hline +~ i32.le\_s & ~ 0.3125 & ~ CMP & \\ +\hline +~ i32.ge\_s & ~ 0.3125 & ~ CMP & \\ +\hline +~ i32.lt\_u & ~ 0.3125 & ~ CMP & \\ +\hline +~ i32.gt\_u & ~ 0.3125 & ~ CMP & \\ +\hline +~ i32.le\_u & ~ 0.3125 & ~ CMP & \\ +\hline +~ i32.ge\_u & ~ 0.3125 & ~ CMP & \\ +\hline +~ i32.lt\_s & ~ 0.3125 & ~ CMP & \\ +\hline +~ i32.ge\_s & ~ 0.3125 & ~ CMP & \\ +\hline +~ i32.gt\_s & ~ 0.3125 & ~ CMP & \\ +\hline +~ i32.eq & ~ 0.3125 & ~ CMP & \\ +\hline +~ i32.ne & ~ 0.3125 & ~ CMP & \\ +\hline +~ i32.eqz & ~ 0.3125 & ~ CMP & \\ +\hline +~ i32.rotl & ~ 0.3125 & ~ ROL reg imm & \\ +\hline +~ i32.rotr & ~ 0.3125 & ~ ROR reg imm & \\ +\hline +~ i32.and & ~ 0.3125 & ~ AND & \\ +\hline +~ i32.or & ~ 0.3125 & ~ OR & \\ +\hline +~ i32.xor & ~ 0.3125 & ~ XOR & \\ +\hline +~ i32.shl & ~ 0.3125 & ~ SHL reg imm & \\ +\hline +~ i32.shr\_u & ~ 0.3125 & ~ SHR reg imm & \\ +\hline +~ i32.shr\_s & ~ 0.3125 & ~ SHR reg imm & \\ +\hline +~ i32.popcnt~~ & ~ Varies & ~ CMP, INC & ~ [4] \\ +\hline +~ i32.clz & ~ Varies & ~ CMP, INC & ~ [5] \\ +\hline +~ i32.ctz & ~ Varies & ~ CMP, INC & ~ [5] \\ +\hline +~ i32.rem\_s & ~ 6.25 & ~ IDIV r32 & ~ [1] \\ +\hline +~ i32.rem\_u & ~ 6.25 & ~ DIV r32 & ~ [1] \\ +\hline +~ i32.const & ~ 1.25 & ~ MOV & \\ +\hline + & & & \\ +\hline + & & & \\ +\hline +~ i64.add & ~ 0.3125 & ~ ADD & \\ +\hline +~ i64.sub & ~ 0.3125 & ~ SUB & \\ +\hline +~ i64.mul & ~ 0.9375 & ~ IMUL r64, r64 & \\ +\hline +~ i64.div\_s & ~ 26.5625 & ~ IDIV r64 & ~ [2]~~ \\ +\hline +~ i64.div\_u & ~ 25.0 & ~ DIV r64 & ~ [3]~~ \\ +\hline +~ i64.le\_s & ~ 0.3125 & ~ CMP & \\ +\hline +~ i64.ge\_s & ~ 0.3125 & ~ CMP & \\ +\hline +~ i64.lt\_u & ~ 0.3215 & ~ CMP & \\ +\hline +~ i64.gt\_u & ~ 0.3125 & ~ CMP & \\ +\hline +~ i64.le\_u & ~ 0.3125 & ~ CMP & \\ +\hline +~ i64.ge\_u & ~ 0.3125 & ~ CMP & \\ +\hline +~ i64.lt\_s & ~ 0.3125 & ~ CMP & \\ +\hline +~ i64.ge\_s & ~ 0.3125 & ~ CMP & \\ +\hline +~ i64.gt\_s & ~ 0.3125 & ~ CMP & \\ +\hline +~ i64.eq & ~ 0.3125 & ~ CMP & \\ +\hline +~ i64.ne & ~ 0.3125 & ~ CMP & \\ +\hline +~ i64.eqz & ~ 0.3125 & ~ CMP & \\ +\hline +~ i64.rotl & ~ 0.3125 & ~ ROL reg imm & \\ +\hline +~ i64.rotr & ~ 0.3125 & ~ ROR reg imm & \\ +\hline +~ i64.and & ~ 0.3125 & ~ AND & \\ +\hline +~ i64.or & ~ 0.3125 & ~ OR & \\ +\hline +~ i64.xor & ~ 0.3125 & ~ XOR & \\ +\hline +~ i64.shl & ~ 0.3125 & ~ SHL reg imm & \\ +\hline +~ i64.shr\_u & ~ 0.3125 & ~ SHR reg imm & \\ +\hline +~ i64.shr\_s & ~ 0.3125 & ~ SHR reg imm & \\ +\hline +~ i64.popcnt & ~ Varies & ~ CMP, INC & ~ [4] \\ +\hline +~ i64.clz & ~ Varies & ~ CMP, INC & ~ [5] \\ +\hline +~ i64.ctz & ~ Varies & ~ CMP, INC & ~ [5] \\ +\hline +~ i64.rem\_s & ~ 26.5625 & ~ IDIV r64 & ~ [2] \\ +\hline +~ i64.rem\_u & ~ 25.0 & ~ DIV r64 & ~ [3] \\ +\hline +~ i64.const & ~ 1.25 & ~ MOV & \\ +\hline + & & & \\ +\hline + & & & \\ +\hline +\multicolumn{1}{l}{} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.add} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ ADDPS} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.sub} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ SUBPS} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.mul} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ MULPS} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.div} & \multicolumn{1}{l}{~ 3.4375} & \multicolumn{1}{l}{~ DIVPS} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.le} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ CMPPS} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.ge} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ CMPPS} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.lt} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ CMPPS} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.gt} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ CMPPS} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.eq} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ CMPPS} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.ne} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ CMPPS} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.const} & \multicolumn{1}{l}{~ 0.3125} & \multicolumn{1}{l}{~ MOVAPS} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.min} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ MINPS} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.max} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ MAXPS} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.copysign} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.nearest} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.trunc} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.floor} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.neg} & \multicolumn{1}{l}{~ 1.5625} & \multicolumn{1}{l}{~ MOVAPS, MULPS} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.abs} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.sqrt} & \multicolumn{1}{l}{~ 4.0625} & \multicolumn{1}{l}{~ SQRTPS} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.convert\_u\_i32~~} & \multicolumn{1}{l}{~ 1.875} & \multicolumn{1}{l}{~ MOVAPS, ANDPS, MULPS} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.convert\_s\_i32} & \multicolumn{1}{l}{~ 1.5625} & \multicolumn{1}{l}{~ MOVAPS, MULPS} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.convert\_u\_i64} & \multicolumn{1}{l}{~ 1.875} & \multicolumn{1}{l}{~ MOVAPS, ANDPS, MULPS} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.convert\_s\_i64} & \multicolumn{1}{l}{~ 1.5625} & \multicolumn{1}{l}{~ MOVAPS, MULPS} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f32.demote\_f64} & \multicolumn{1}{l}{~ 3.125} & \multicolumn{1}{l}{~ MOVAPS, MULPS, MOVAPS, MULPS~~} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.add} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ ADDPD} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.sub} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ SUBPD} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.mul} & \multicolumn{1}{l}{~ 0.9375} & \multicolumn{1}{l}{~ MULPD} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.div} & \multicolumn{1}{l}{~ 4.375} & \multicolumn{1}{l}{~ DIVPD} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.le} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ CMPPD} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.ge} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ CMPPD} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.lt} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ CMPPD} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.gt} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ CMPPD} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.eq} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ CMPPD} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.ne} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ CMPPD} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.const} & \multicolumn{1}{l}{~ 0.3125} & \multicolumn{1}{l}{~ MOVAPD} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.min} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ MINPD} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.max} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ MAXPD} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.copysign} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.nearest} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.trunc} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.floor} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.neg} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ MOVAPD, MULPD} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.abs} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.sqrt} & \multicolumn{1}{l}{~ 5.625} & \multicolumn{1}{l}{~ SQRTPD} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.convert\_u\_i32} & \multicolumn{1}{l}{~ 1.5625} & \multicolumn{1}{l}{~ MOVAPD, ANDPD, MULPD} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.convert\_s\_i32} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ MOVAPD, MULPD} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.convert\_u\_i64} & \multicolumn{1}{l}{~ 1.5625} & \multicolumn{1}{l}{~ MOVAPD, ANDPD, MULPD} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.convert\_s\_i64} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ MOVAPD, MULPD} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{~ f64.promote\_f32} & \multicolumn{1}{l}{~ 1.25} & \multicolumn{1}{l}{~ MOVAPD, MULPD} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} \\ +\multicolumn{1}{l}{} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} & \multicolumn{1}{l}{} \\ +\hline +~ select & ~ 1.5625 & ~ CMP, MOV & \\ +\hline +~ br\_if & ~ 2.8125 & ~ CMP, MOV, MOV & \\ +\hline +~ br & ~ 2.5 & ~ MOV, MOV & \\ +\hline +~ nop & ~ 0.0 & & \\ +\hline +~ unreachable~~ & ~ 0.0 & & \\ +\hline +~ return & ~ 0.0 & & \\ +\hline +~ if & ~ 0.3125 & ~ CMP & \\ +\hline +~ else & ~ 0.0 & & \\ +\hline +~ end (block/loop)~~ & ~ 2.5 & ~ MOV, MOV & \\ +\hline +~ end (function) & ~ 0.0 & & \\ +\hline +~ drop & ~ 1.25 & ~ MOV & \\ +\hline +~ loop & ~ 2.5 & ~ MOV, MOV & \\ +\hline +~ block & ~ 2.5 & ~ MOV, MOV & \\ +\hline + & & & \\ +\hline + & & & \\ +\hline + & & & \\ +\hline +\end{tabular} +\end{table} + +Footnotes +\\\\ +1. The latency of “DIV/IDIV r32 varies with the significant bits of input values. Lower bound of 20 cycles is used. +\\\\ +2. The latency of “DIV/IDIV r64 varies with the significant bits of input values. Lower bound of 85 cycles is used. +\\\\ +3. The latency of “DIV/IDIV r64 varies with the significant bits of input values. Lower bound of 80 cycles is used. +\\\\ +4. Popcnt opcode varies in gas cost based on the size of the number and the number of bits that are set to 1. The formula for calculating gas for Popcnt is (total\_bits\_in\_number + total\_1\_bits) * 0.3125. +\\\\ +5. Clz and Ctz opcodes vary in gas cost based on the number that they return. The formula for calculating gas for these opcodes is total\_number\_of\_zeros * 0.625. + +\begin{thebibliography}{} + \bibitem{CPU World}Release dates of desktop microprocessors (2018). Retrieved January 25, 2019, from http://www.cpu-world.com/Releases/Desktop\_CPU\_releases\_%282018%29.html + \bibitem{Intel Manual}Intel® 64 and IA-32 Architectures Optimization Reference Manual. Retrieved January 25, 2019, from https://software.intel.com/sites/default/files/managed/9e/bc/64-ia-32-architectures-optimization-manual.pdf + +\end{thebibliography} + +\end{document} diff --git a/lib/wasp_vm.ex b/lib/alchemy_vm.ex similarity index 90% rename from lib/wasp_vm.ex rename to lib/alchemy_vm.ex index 83d87f5..6fb346a 100644 --- a/lib/wasp_vm.ex +++ b/lib/alchemy_vm.ex @@ -216,16 +216,35 @@ defmodule AlchemyVM do @spec execute_func(AlchemyVM, integer, list, :infinity | integer, String.t(), list) :: tuple defp execute_func(vm, addr, args, gas_limit, fname, opts) do - stack = Enum.reduce(args, [], & [&1 | &2]) + args = Enum.reverse(args) # Conditional for Trace if opts[:trace], do: create_log_timestamp(fname) - {vm, gas, stack} = Executor.create_frame_and_execute(vm, addr, gas_limit, opts, 0, stack) + # We'll have to update this when we allow multiple return values post-MVP + {return_type, {vm, gas, stack}} = Executor.create_frame_and_execute(vm, addr, gas_limit, opts, 0, [], args) case vm do tuple when is_tuple(tuple) -> tuple - _ -> {{:ok, gas, List.first(stack)}, vm} + _ -> + return_val = + case return_type do + {:i32} -> + [<> | _] = stack + value + {:i64} -> + [<> | _] = stack + value + {:f32} -> + [<> | _] = stack + value + {:f64} -> + [<> | _] = stack + value + {} -> nil + end + + {{:ok, gas, return_val}, vm} end end diff --git a/lib/execution/dsl.ex b/lib/execution/dsl.ex new file mode 100644 index 0000000..3ebaeec --- /dev/null +++ b/lib/execution/dsl.ex @@ -0,0 +1,70 @@ +defmodule AlchemyVM.DSL do + @moduledoc false + + defmacro __using__(_opts) do + quote do + import AlchemyVM.DSL + end + end + + @doc """ + This allows us to internally write OpCode definition instructions in a more + concise format. + + We can write the following definition: + + defop i32_add(a, b) do + stack = [a + b | stack] + {ctx, gas + Gas.cost(:i32_add), stack} + end + + and it will generate the following code: + + defp instruction(ctx, gas, [a, b | stack], _opts, :i32_add) do + stack = [a + b | stack] + {ctx, gas + Gas.cost(:i32_add), stack} + end + + In the above example, the i32_add(a, b) will implicitly pull values off the + stack and assign them to their respective variables. + + When we have opcodes that are tuples rather than just atoms (opcodes that + have immediates, like i32_const), we can specify their immediates like so: + + defop i32_const(immediates: [i32]) do + ... + end + + This gets translated to + + defp instruction(ctx, gas, stack, _opts, {:i32_const, i32}) do + ... + end + """ + defmacro defop(head, do: block) do + {opname, args_ast} = Macro.decompose_call(head) + + {opname, args_ast} = + case List.last(args_ast) do + [immediates: immediates] -> + op = {:{}, [], [opname | immediates]} + [_ | args] = Enum.reverse(args_ast) + args = Enum.reverse(args) + + {op, args} + + _ -> {opname, args_ast} + end + + num_args = length(args_ast) + + quote generated: true do + defp instruction({var!(frame), var!(vm), var!(ip)} = var!(ctx), var!(gas), s, var!(opts), unquote(opname)) do + {unquote(args_ast), var!(stack)} = Enum.split(s, unquote(num_args)) + + unquote(block) + end + end + end + +end diff --git a/lib/execution/executor.ex b/lib/execution/executor.ex index ba7b3e4..f3fdf03 100644 --- a/lib/execution/executor.ex +++ b/lib/execution/executor.ex @@ -1,9 +1,10 @@ defmodule AlchemyVM.Executor do alias AlchemyVM.Frame alias AlchemyVM.Memory + alias AlchemyVM.Gas alias AlchemyVM.HostFunction.API use Bitwise - require Logger + use AlchemyVM.DSL require IEx alias Decimal, as: D @@ -11,10 +12,26 @@ defmodule AlchemyVM.Executor do # Reference for tests being used: https://github.com/WebAssembly/wabt/tree/master/test - def create_frame_and_execute(vm, addr, gas_limit, opts, gas \\ 0, stack \\ []) do + defp typecast_param({:i32, param}), do: <> + defp typecast_param({:i64, param}), do: <> + defp typecast_param({:f32, param}), do: <> + defp typecast_param({:f64, param}), do: <> + + def create_frame_and_execute(vm, addr, gas_limit, opts, gas \\ 0, stack \\ [], parameters \\ []) do case elem(vm.store.funcs, addr) do - {{inputs, _outputs}, module_ref, instr, locals} -> - {args, stack} = Enum.split(stack, tuple_size(inputs)) + {{inputs, outputs}, module_ref, instr, locals} -> + {args, stack} = + if length(parameters) > 0 do + args = + inputs + |> Tuple.to_list() + |> Enum.zip(parameters) + |> Enum.map(&typecast_param/1) + + {args, stack} + else + Enum.split(stack, tuple_size(inputs)) + end %{^module_ref => module} = vm.modules @@ -27,12 +44,20 @@ defmodule AlchemyVM.Executor do total_instr = map_size(instr) - execute(frame, vm, gas, stack, total_instr, gas_limit, opts) - {:hostfunc, {inputs, _outputs}, mname, fname, module_ref} -> - # TODO: How should we handle gas for host functions? Does gas price get passed in? - # Do we default to a gas value? - - {args, stack} = Enum.split(stack, tuple_size(inputs)) + {outputs, execute(frame, vm, gas, stack, total_instr, gas_limit, opts)} + {:hostfunc, {inputs, outputs}, mname, fname, module_ref} -> + {args, stack} = + if length(parameters) > 0 do + args = + inputs + |> Tuple.to_list() + |> Enum.zip(parameters) + |> Enum.map(&typecast_param/1) + + {args, stack} + else + Enum.split(stack, tuple_size(inputs)) + end %{^module_ref => module} = vm.modules @@ -53,12 +78,13 @@ defmodule AlchemyVM.Executor do # Kill the API agent now that it's served it's purpose API.stop(ctx) - # TODO: Gas needs to be updated based on the comment above instead of - # just getting passed through - if !is_number(return_val) do - {vm, gas, stack} + # TODO: How should we handle gas for host functions? Does gas price + # get passed in? Do we default to a gas value? Gas needs to be updated + # instead of just getting passed through + if !is_binary(return_val) do + {outputs, {vm, gas, stack}} else - {vm, gas, [return_val | stack]} + {outputs, {vm, gas, [return_val | stack]}} end end end @@ -73,7 +99,7 @@ defmodule AlchemyVM.Executor do def execute(frame, vm, gas, stack, total_instr, gas_limit, opts, next_instr) do %{^next_instr => instr} = frame.instructions - {{frame, vm, next_instr}, gas, stack} = instruction(instr, frame, vm, gas, stack, next_instr, opts) + {{frame, vm, next_instr}, gas, stack} = instruction({frame, vm, next_instr}, gas, stack, opts, instr) if opts[:trace] do write_to_file(instr, gas) @@ -82,662 +108,1156 @@ defmodule AlchemyVM.Executor do execute(frame, vm, gas, stack, total_instr, gas_limit, opts, next_instr + 1) end - def instruction(opcode, f, v, g, s, n, opts) when is_atom(opcode), do: exec_inst({f, v, n}, g, s, opts, opcode) - def instruction(opcode, f, v, g, s, n, opts) when is_tuple(opcode), do: exec_inst({f, v, n}, g, s, opts, opcode) - - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_add), do: {ctx, gas + 3, [(a + b) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_sub), do: {ctx, gas + 3, [a - b | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_mul), do: {ctx, gas + 5, [a * b | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_add), do: {ctx, gas + 3, [a + b | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_sub), do: {ctx, gas + 3, [a - b | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_mul), do: {ctx, gas + 5, [a * b | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_le_s) when a <= b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_ge_s) when a >= b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_lt_u) when a < b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_lt_u) when a < b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_gt_u) when a > b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_gt_u) when a > b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_le_u) when a <= b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_le_u) when a <= b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_ge_u) when a >= b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_ge_u) when a >= b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_eq) when a === b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_eq) when a === b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_ne) when a !== b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f32_eq) when a === b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f64_eq) when a === b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_ne) when a !== b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f32_lt) when a < b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f64_lt) when a < b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f32_le) when a <= b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f64_le) when a <= b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f32_ge) when a <= b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f64_ge) when a <= b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f32_gt) when a > b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f64_gt) when a > b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f32_ne) when a !== b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f64_ne) when a !== b, do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f32_add), do: {ctx, gas + 3, [float_point_op(a + b) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f32_sub), do: {ctx, gas + 3, [float_point_op(b - a) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f32_mul), do: {ctx, gas + 5, [float_point_op(a * b) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f64_add), do: {ctx, gas + 3, [float_point_op(a + b) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f64_sub), do: {ctx, gas + 3, [float_point_op(b - a) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f64_mul), do: {ctx, gas + 5, [float_point_op(a * b) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f32_min), do: {ctx, gas + 5, [float_point_op(min(a, b)) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f32_max), do: {ctx, gas + 5, [float_point_op(max(a, b)) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f64_min), do: {ctx, gas + 5, [float_point_op(min(a, b)) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f64_max), do: {ctx, gas + 5, [float_point_op(max(a, b)) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f32_copysign), do: {ctx, gas + 5, [float_point_op(copysign(b, a)) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f64_copysign), do: {ctx, gas + 5, [float_point_op(copysign(b, a)) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :f32_div), do: {ctx, gas + 5, [float_point_op(a / b) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_rotl), do: {ctx, gas + 5, [rotl(b, a) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_rotr), do: {ctx, gas + 5, [rotr(b, a) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_and), do: {ctx, gas + 3, [band(a, b) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_or), do: {ctx, gas + 3, [bor(a, b) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_xor), do: {ctx, gas + 3, [bxor(a, b) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_and), do: {ctx, gas + 3, [band(a, b) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_or), do: {ctx, gas + 3, [bor(a, b) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_xor), do: {ctx, gas + 3, [bxor(a, b) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_shl), do: {ctx, gas + 5, [bsl(a, b) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_shl), do: {ctx, gas + 5, [bsl(a, b) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_shr_u), do: {ctx, gas + 5, [log_shr(a, b) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_shr_u), do: {ctx, gas + 5, [bsr(a, b) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_shr_s), do: {ctx, gas + 5, [bsr(a, Integer.mod(b, 32)) | stack]} - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_shr_s), do: {ctx, gas + 5, [bsr(a, Integer.mod(b, 64)) | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :i32_eq), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :i64_eq), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :i64_ne), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :i64_le_s), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :i64_ge_s), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :i32_lt_u), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :i64_lt_u), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :i32_gt_u), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :i64_gt_u), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :i32_le_u), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :i64_le_u), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :i32_ge_u), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :i64_ge_u), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :f32_eq), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :f64_eq), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :i32_ne), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :f32_lt), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :f64_lt), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :f32_le) , do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :f64_le), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :f32_ge), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :f64_ge), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :f32_gt), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :f64_gt), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :f32_ne), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [_, _ | stack], _opts, :f64_ne), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [0 | stack], _opts, :i32_eqz), do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [_ | stack], _opts, :i32_eqz), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [0 | stack], _opts, :i64_eqz), do: {ctx, gas + 3, [1 | stack]} - defp exec_inst(ctx, gas, [_ | stack], _opts, :i64_eqz), do: {ctx, gas + 3, [0 | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f32_nearest), do: {ctx, gas + 5, [round(a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f64_nearest), do: {ctx, gas + 5, [round(a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f32_trunc), do: {ctx, gas + 5, [trunc(a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f64_trunc), do: {ctx, gas + 5, [trunc(a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f32_floor), do: {ctx, gas + 5, [Float.floor(a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f64_floor), do: {ctx, gas + 5, [Float.floor(a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f32_neg), do: {ctx, gas + 5, [float_point_op(a * -1) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f64_neg), do: {ctx, gas + 5, [float_point_op(a * -1) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f32_abs), do: {ctx, gas + 5, [float_point_op(abs(a)) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f64_abs), do: {ctx, gas + 5, [float_point_op(abs(a)) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f32_sqrt), do: {ctx, gas + 5, [float_point_op(:math.sqrt(a)) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f64_sqrt), do: {ctx, gas + 5, [float_point_op(:math.sqrt(a)) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :i32_popcnt), do: {ctx, gas + 5, [popcnt(a, 32) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :i64_popcnt), do: {ctx, gas + 5, [popcnt(a, 64) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f32_ceil), do: {ctx, gas + 5, [float_point_op(Float.ceil(a)) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f64_ceil), do: {ctx, gas + 5, [float_point_op(Float.ceil(a)) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :i32_wrap_i64), do: {ctx, gas + 5, [bin_wrap(:i64, :i32, a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :i32_trunc_u_f32), do: {ctx, gas + 5, [bin_trunc(:f32, :i32, a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :i32_trunc_s_f32), do: {ctx, gas + 5, [bin_trunc(:f32, :i32, a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :i32_trunc_u_f64), do: {ctx, gas + 5, [bin_trunc(:f32, :i32, a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :i32_trunc_s_f64), do: {ctx, gas + 5, [bin_trunc(:f32, :i32, a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :i64_trunc_u_f32), do: {ctx, gas + 5, [bin_trunc(:f32, :i64, a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :i64_trunc_s_f32), do: {ctx, gas + 5, [bin_trunc(:f32, :i64, a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :i64_trunc_u_f64), do: {ctx, gas + 5, [bin_trunc(:f64, :i64, a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :i64_trunc_s_f64), do: {ctx, gas + 5, [bin_trunc(:f64, :i64, a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f32_convert_s_i32), do: {ctx, gas + 5, [float_point_op(a * 1.000) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f32_convert_u_i32), do: {ctx, gas + 5, [float_point_op(band(a, 0xFFFFFFFF) * 1.000000) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f32_convert_s_i64), do: {ctx, gas + 5, [float_point_op(a * 1.000000) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f32_convert_u_i64), do: {ctx, gas + 5, [float_point_op(band(a, 0xFFFFFFFFFFFFFF) * 1.000000) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f64_convert_s_i64), do: {ctx, gas + 5, [float_point_op(a * 1.000000) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f64_convert_u_i64), do: {ctx, gas + 5, [float_point_op(band(a, 0xFFFFFFFFFFFFFF) * 1.000000) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f64_convert_s_i32), do: {ctx, gas + 5, [float_point_op(a * 1.000000) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f64_convert_u_i32), do: {ctx, gas + 5, [float_point_op(band(a, 0xFFFFFFFF) * 1.000000) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :i64_extend_u_i32), do: {ctx, gas + 5, [round(:math.pow(2, 32) + a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :i64_extend_s_i32), do: {ctx, gas + 5, [band(a, 0xFFFFFFFFFFFFFFFF) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f32_demote_f64), do: {ctx, gas + 5, [float_demote(a * 1.0000000) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f64_promote_f32), do: {ctx, gas + 5, [float_promote(a * 1.0000000) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :i32_reinterpret_f32), do: {ctx, gas + 5, [reint(:f32, :i32, a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :i64_reinterpret_f32), do: {ctx, gas + 5, [reint(:f32, :i64, a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f64_reinterpret_i64), do: {ctx, gas + 5, [reint(:f64, :i64, a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :f32_reinterpret_i32), do: {ctx, gas + 5, [reint(:f32, :i32, a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :i32_clz), do: {ctx, gas + 5, [count_bits(:l, a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :i64_clz), do: {ctx, gas + 5, [count_bits(:l, a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :i32_ctz), do: {ctx, gas + 5, [count_bits(:t, a) | stack]} - defp exec_inst(ctx, gas, [a | stack], _opts, :i64_ctz), do: {ctx, gas + 5, [count_bits(:t, a) | stack]} - defp exec_inst(ctx, gas, [0, b, _ | stack], _opts, :select), do: {ctx, gas, [b | stack]} - defp exec_inst(ctx, gas, [1, _ | stack], _opts, :select), do: {ctx, gas, stack} - defp exec_inst(ctx, gas, [1 | stack], _opts, {:br_if, label_idx}), do: break_to(ctx, gas, stack, label_idx) - defp exec_inst(ctx, gas, [_ | stack], _opts, {:br_if, _label_idx}), do: {ctx, gas, stack} - defp exec_inst(ctx, gas, stack, _opts, {:i32_const, i32}), do: {ctx, gas + 3, [i32 | stack]} - defp exec_inst(ctx, gas, stack, _opts, {:i64_const, i64}), do: {ctx, gas + 3, [i64 | stack]} - defp exec_inst(ctx, gas, stack, _opts, {:f32_const, f32}), do: {ctx, gas + 3, [f32 | stack]} - defp exec_inst(ctx, gas, stack, _opts, {:f64_const, f64}), do: {ctx, gas + 3, [f64 | stack]} - defp exec_inst({_frame, vm, _n} = ctx, gas, stack, _opts, :current_memory), do: {ctx, gas + 3, [length(vm.memory.pages) | stack]} - defp exec_inst({frame, _vm, _n} = ctx, gas, stack, _opts, {:get_local, idx}), do: {ctx, gas + 3, [elem(frame.locals, idx) | stack]} - defp exec_inst({_frame, vm, _n} = ctx, gas, stack, _opts, {:get_global, idx}), do: {ctx, gas + 3, [Enum.at(vm.globals, idx) | stack]} - defp exec_inst(ctx, gas, [_ | stack], _opts, :drop), do: {ctx, gas, stack} - defp exec_inst(ctx, gas, stack, _opts, {:br, label_idx}), do: break_to(ctx, gas, stack, label_idx) - defp exec_inst({%{labels: []} = frame, vm, n}, gas, stack, _opts, :end), do: {{frame, vm, n}, gas, stack} - defp exec_inst({frame, vm, _n}, gas, stack, _opts, {:else, end_idx}), do: {{frame, vm, end_idx}, gas, stack} - defp exec_inst({frame, vm, _n}, gas, stack, _opts, :return), do: {{frame, vm, -10}, gas, stack} - defp exec_inst(ctx, gas, stack, _opts, :unreachable), do: {ctx, gas, stack} - defp exec_inst(ctx, gas, stack, _opts, :nop), do: {ctx, gas, stack} - defp exec_inst(_ctx, _gas, [0 | _], _opts, :i32_div_u), do: trap("Divide by zero in i32.div_u") - defp exec_inst(_ctx, _gas, [0 | _], _opts, :i32_rem_s), do: trap("Divide by zero in i32.rem_s") - defp exec_inst(_ctx, _gas, [0 | _], _opts, :i64_rem_s), do: trap("Divide by zero in i64.rem_s") - defp exec_inst(_ctx, _gas, [0 | _], _opts, :i64_div_u), do: trap("Divide by zero in i64.div_u") - defp exec_inst(_ctx, _gas, [0 | _], _opts, :i32_rem_u), do: trap("Divide by zero in i32.rem_u") - defp exec_inst(_ctx, _gas, [0 | _], _opts, :i64_rem_u), do: trap("Divide by zero in i64.rem_u") - - defp exec_inst({frame, vm, n}, gas, [1 | stack], _opts, {:if, _type, _else_idx, end_idx}) do - labels = [{n, end_idx} | frame.labels] - snapshots = [stack | frame.snapshots] + # Begin i32 Instructions ===================================================== - {{Map.merge(frame, %{labels: labels, snapshots: snapshots}), vm, n}, gas + 2, stack} + defop i32_const(immediates: [i32]) do + {ctx, gas + Gas.cost(:i32_const), [<> | stack]} end - defp exec_inst({frame, vm, _n}, gas, [_val | stack], _opts, {:if, _type, else_idx, end_idx}) do - next_instr = if else_idx != :none, do: else_idx, else: end_idx - {{frame, vm, next_instr}, gas + 2, stack} + defop i32_add(<>, <>) do + {ctx, gas + Gas.cost(:i32_add), [<<(a + b)::integer-32-little>> | stack]} end - defp exec_inst({frame, vm, n}, gas, stack, _opts, :end) do - [corresponding_label | labels] = frame.labels + defop i32_sub(<>, <>) do + {ctx, gas + Gas.cost(:i32_sub), [<<(a - b)::integer-32-little>> | stack]} + end - case corresponding_label do - {:loop, _instr} -> {{Map.put(frame, :labels, labels), vm, n}, gas, stack} - _ -> {{frame, vm, n}, gas, stack} - end + defop i32_mul(<>, <>) do + {ctx, gas + Gas.cost(:i32_mul), [<<(a * b)::integer-32-little>> | stack]} end - defp exec_inst({frame, vm, n}, gas, stack, opts, {:call, funcidx}) do - %{^funcidx => func_addr} = frame.module.funcaddrs + defop i32_div_s(<>, <>) do + if b == 0, do: trap("Divide by zero in i32.div_s") + if a / b >= 2147483648, do: trap("Out of bounds in i32.div_s") - # TODO: Maybe this shouldn't pass the existing stack in? - {vm, gas, stack} = create_frame_and_execute(vm, func_addr, frame.gas_limit, opts, gas, stack) + res = <> - {{frame, vm, n}, gas, stack} + {ctx, gas + Gas.cost(:i32_div_s), [res | stack]} end - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_div_s) do - j1 = sign_value(a, 32) - j2 = sign_value(b, 32) + defop i32_div_u(<>, <>) do + if b == 0, do: trap("Divide by zero in i32.div_s") - if j2 == 0 do - trap("Divide by zero in i32.div_s") - else - if j1 / j2 == 2147483648 do - trap("Out of bounds in i32.div_s") - else - res = trunc(j1 / j2) - ans = sign_value(res, 32) + res = <> - {ctx, gas + 5, [ans | stack]} - end - end + {ctx, gas + Gas.cost(:i32_div_u), [res | stack]} end - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_div_s) do - j1 = sign_value(a, 64) - j2 = sign_value(b, 64) + defop i32_rem_s(<>, <>) do + if b == 0, do: trap("Divide by zero in i32.rem_s") - if j2 == 0 do - trap("Divide by zero in i64.div_s") - else - if j1 / j2 == 9.223372036854776e18 do - trap("Out of bounds in i64.div_s") - else - res = trunc(j1 / j2) - ans = sign_value(res, 64) + {ctx, gas + Gas.cost(:i32_rem_s), [<> | stack]} + end - {ctx, gas + 5, [ans | stack]} - end - end + defop i32_rem_u(<>, <>) do + if b == 0, do: trap("Divide by zero in i32.rem_u") + + {ctx, gas + Gas.cost(:i32_rem_u), [<> | stack]} end - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_div_u) do - rem = a - (b * trunc(a / b)) - result = Integer.floor_div((a - rem), b) - {ctx, gas + 5, [result | stack]} + defop i32_rotl(<>, <>) do + {ctx, gas + Gas.cost(:i32_rotl), [<> | stack]} end - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_rem_s) do - j1 = sign_value(a, 32) - j2 = sign_value(b, 32) + defop i32_rotr(<>, <>) do + {ctx, gas + Gas.cost(:i32_rotr), [<> | stack]} + end - rem = j1 - (j2 * trunc(j1 / j2)) + defop i32_and(<>, <>) do + {ctx, gas + Gas.cost(:i32_and), [<<(a &&& b)::integer-32-little>> | stack]} + end - {ctx, gas + 5, [rem | stack]} + defop i32_or(<>, <>) do + {ctx, gas + Gas.cost(:i32_or), [<<(a ||| b)::integer-32-little>> | stack]} end - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_rem_s) do - j1 = sign_value(a, 64) - j2 = sign_value(b, 64) + defop i32_xor(<>, <>) do + {ctx, gas + Gas.cost(:i32_xor), [<> | stack]} + end - rem = j1 - (j2 * trunc(j1 / j2)) - res = 1.8446744073709552e19 - rem + defop i32_shl(<>, <>) do + {ctx, gas + Gas.cost(:i32_shl), [<<(a <<< b)::integer-32-little>> | stack]} + end - {ctx, gas + 5, [res | stack]} + defop i32_shr_u(<>, <>) do + {ctx, gas + Gas.cost(:i32_shr_u), [<<(a >>> b)::integer-32-little>> | stack]} end - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_div_u) do - rem = a - (b * trunc(a / b)) - result = Integer.floor_div((a - rem), b) - {ctx, gas + 5, [result | stack]} + defop i32_shr_s(<>, <>) do + {ctx, gas + Gas.cost(:i32_shr_s), [<<(a >>> b)::integer-32-little-signed>> | stack]} end - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_rem_u) do - c = - a - |> Kernel./(b) - |> trunc() - |> Kernel.*(b) + defop i32_eq(a, b) do + result = if a === b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:i32_eq), [result | stack]} + end - res = a - c + defop i32_ne(a, b) do + result = if a !== b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> - {ctx, gas + 5, [res | stack]} + {ctx, gas + Gas.cost(:i32_ne), [result | stack]} end - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_rem_u) do - c = - a - |> Kernel./(b) - |> trunc() - |> Kernel.*(b) + defop i32_eqz(a) do + result = if a === <<0, 0, 0, 0>>, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:i32_eqz), [result | stack]} + end - res = a - c + defop i32_lt_u(<>, <>) do + result = if a < b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> - {ctx, gas + 5, [res | stack]} + {ctx, gas + Gas.cost(:i32_lt_u), [result | stack]} end - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_le_s) do - val = if sign_value(a, 32) <= sign_value(b, 32), do: 1, else: 0 - {ctx, gas + 3, [val | stack]} + defop i32_gt_u(<>, <>) do + result = if a > b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:i32_gt_u), [result | stack]} end - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_ge_s) do - val = if sign_value(a, 32) >= sign_value(b, 32), do: 1, else: 0 - {ctx, gas + 3, [val | stack]} + defop i32_le_u(<>, <>) do + result = if a <= b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:i32_le_u), [result | stack]} end - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_lt_s) do - val = if sign_value(a, 32) < sign_value(b, 32), do: 1, else: 0 - {ctx, gas + 3, [val | stack]} + defop i32_ge_u(<>, <>) do + result = if a >= b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:i32_ge_u), [result | stack]} end - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_lt_s) do - val = if sign_value(a, 64) < sign_value(b, 64), do: 1, else: 0 - {ctx, gas + 3, [val | stack]} + defop i32_le_s(<>, <>) do + result = if a <= b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:i32_le_s), [result | stack]} end - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i32_gt_s) do - val = if sign_value(a, 32) > sign_value(b, 32), do: 1, else: 0 - {ctx, gas + 3, [val | stack]} + defop i32_ge_s(<>, <>) do + result = if a >= b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:i32_ge_s), [result | stack]} end - defp exec_inst(ctx, gas, [b, a | stack], _opts, :i64_gt_s) do - val = if sign_value(a, 64) > sign_value(b, 64), do: 1, else: 0 - {ctx, gas + 3, [val | stack]} + defop i32_lt_s(<>, <>) do + result = if a < b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:i32_lt_s), [result | stack]} end - defp exec_inst({frame, vm, n}, gas, [value | stack], _opts, {:set_global, idx}) do - globals = List.replace_at(vm.globals, idx, value) + defop i32_gt_s(<>, <>) do + result = if a > b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> - {{frame, Map.put(vm, :globals, globals), n}, gas + 3, stack} + {ctx, gas + Gas.cost(:i32_gt_s), [result | stack]} end - defp exec_inst({frame, vm, n}, gas, [value | stack], _opts, {:set_local, idx}) do - locals = put_elem(frame.locals, idx, value) + defop i32_popcnt(i32) do + count = + (for <>, do: bit) + |> Enum.reject(& &1 !== 1) + |> length() - {{Map.put(frame, :locals, locals), vm, n}, gas + 3, stack} + {ctx, gas + Gas.cost(:i32_popcnt, count), [<> | stack]} end - defp exec_inst({frame, vm, n}, gas, [value | _] = stack, _opts, {:tee_local, idx}) do - locals = put_elem(frame.locals, idx, value) + defop i32_ctz(i32) do + num_zeros = + (for <>, do: bit) + |> trailing_zeros() - {{Map.put(frame, :locals, locals), vm, n}, gas + 3, stack} + {ctx, gas + Gas.cost(:i32_ctz, num_zeros), [<> | stack]} end - defp exec_inst({frame, vm, n}, gas, [pages | stack], _opts, :grow_memory) do - {{frame, Map.put(vm, :memory, Memory.grow(vm.memory, pages)), n}, gas + 3, [length(vm.memory) | stack]} + defop i32_clz(i32) do + num_zeros = + (for <>, do: bit) + |> leading_zeros() + + {ctx, gas + Gas.cost(:i32_clz, num_zeros), [<> | stack]} end - defp exec_inst({frame, vm, _n} = ctx, gas, [address | stack], _opts, {:i32_load8_s, _alignment, offset}) do + defop i32_load(<>, immediates: [_align, offset]) do + {frame, vm, _n} = ctx + + # TODO: Should this be the first memory in the module? Can this reference an imported memory? mem_addr = hd(frame.module.memaddrs) - <> = + i32 = vm.store.mems |> Enum.at(mem_addr) - |> Memory.get_at(address + offset, 1) + |> Memory.get_at(i32addr + offset, 4) - {ctx, gas + 5, [bin_wrap_signed(:i32, :i8, i8) | stack]} + {ctx, gas + Gas.cost(:i32_load), [i32 | stack]} end - defp exec_inst({frame, vm, _n} = ctx, gas, [address | stack], _opts, {:i32_load16_s, _alignment, offset}) do + defop i32_load8_s(<>, immediates: [_align, offset]) do + {frame, vm, _n} = ctx + + # TODO: Should this be the first memory in the module? Can this reference an imported memory? mem_addr = hd(frame.module.memaddrs) - <> = + i8bin = vm.store.mems |> Enum.at(mem_addr) - |> Memory.get_at(address + offset, 2) + |> Memory.get_at(i32addr + offset, 1) + + <> = i8bin - {ctx, gas + 5, [bin_wrap_signed(:i32, :i16, i16) | stack]} + sign = if i8 >= 0, do: 0, else: 255 + + {ctx, gas + Gas.cost(:i32_load8_s), [i8bin <> <> | stack]} end - defp exec_inst({frame, vm, _n} = ctx, gas, [address | stack], _opts, {:i64_load8_s, _alignment, offset}) do + defop i32_load16_s(<>, immediates: [_align, offset]) do + {frame, vm, _n} = ctx + + # TODO: Should this be the first memory in the module? Can this reference an imported memory? mem_addr = hd(frame.module.memaddrs) - <> = + i16bin = vm.store.mems |> Enum.at(mem_addr) - |> Memory.get_at(address + offset, 1) + |> Memory.get_at(i32addr + offset, 2) + + <> = i16bin - {ctx, gas + 5, [bin_wrap_signed(:i64, :i8, i8) | stack]} + sign = if i16 >= 0, do: 0, else: 255 + + {ctx, gas + Gas.cost(:i32_load16_s), [i16bin <> <> | stack]} end - defp exec_inst({frame, vm, _n} = ctx, gas, [address | stack], _opts, {:i64_load16_s, _alignment, offset}) do + defop i32_load8_u(<>, immediates: [_align, offset]) do + {frame, vm, _n} = ctx + + # TODO: Should this be the first memory in the module? Can this reference an imported memory? mem_addr = hd(frame.module.memaddrs) - <> = + i8 = vm.store.mems |> Enum.at(mem_addr) - |> Memory.get_at(address + offset, 2) + |> Memory.get_at(i32addr + offset, 1) - {ctx, gas + 5, [bin_wrap_signed(:i64, :i16, i16) | stack]} + {ctx, gas + Gas.cost(:i32_load8_u), [i8 <> <<0, 0, 0>> | stack]} end - defp exec_inst({frame, vm, _n} = ctx, gas, [address | stack], _opts, {:i64_load32_s, _alignment, offset}) do + defop i32_load16_u(<>, immediates: [_align, offset]) do + {frame, vm, _n} = ctx + + # TODO: Should this be the first memory in the module? Can this reference an imported memory? mem_addr = hd(frame.module.memaddrs) - <> = + i16 = vm.store.mems |> Enum.at(mem_addr) - |> Memory.get_at(address + offset, 4) + |> Memory.get_at(i32addr + offset, 2) - {ctx, gas + 5, [bin_wrap_signed(:i64, :i32, i32) | stack]} + {ctx, gas + Gas.cost(:i32_load16_u), [i16 <> <<0, 0>> | stack]} end - defp exec_inst({frame, vm, _n} = ctx, gas, [address | stack], _opts, {:i32_load8_u, _alignment, offset}) do + defop i32_store(value, <>, immediates: [_align, offset]) do + {frame, vm, n} = ctx + + # TODO: Should this be the first memory in the module? Can this reference an imported memory? mem_addr = hd(frame.module.memaddrs) - <> = + mem = vm.store.mems |> Enum.at(mem_addr) - |> Memory.get_at(address + offset, 1) + |> Memory.put_at(i32addr + offset, value) + + store_mems = List.replace_at(vm.store.mems, mem_addr, mem) + store = Map.put(vm.store, :mems, store_mems) - {ctx, gas + 5, [bin_wrap_unsigned(:i32, :i8, abs(i8)) | stack]} + {{frame, Map.put(vm, :store, store), n}, gas + Gas.cost(:i32_store), stack} end - defp exec_inst({frame, vm, _n} = ctx, gas, [address | stack], _opts, {:i32_load16_u, _alignment, offset}) do + defop i32_store8(value, <>, immediates: [_align, offset]) do + {frame, vm, n} = ctx + + # TODO: Should this be the first memory in the module? Can this reference an imported memory? mem_addr = hd(frame.module.memaddrs) - <> = + # Value is little endian, so grabbing the first byte is effectively wrapping + <> = value + + mem = vm.store.mems |> Enum.at(mem_addr) - |> Memory.get_at(address + offset, 2) + |> Memory.put_at(i32addr + offset, i8) - {ctx, gas + 5, [bin_wrap_unsigned(:i32, :i16, abs(i16)) | stack]} + store_mems = List.replace_at(vm.store.mems, mem_addr, mem) + store = Map.put(vm.store, :mems, store_mems) + + {{frame, Map.put(vm, :store, store), n}, gas + Gas.cost(:i32_store8), stack} end - defp exec_inst({frame, vm, _n} = ctx, gas, [address | stack], _opts, {:i64_load8_u, _alignment, offset}) do + defop i32_store16(value, <>, immediates: [_align, offset]) do + {frame, vm, n} = ctx + + # TODO: Should this be the first memory in the module? Can this reference an imported memory? mem_addr = hd(frame.module.memaddrs) - <> = + # Value is little endian, so grabbing the first 2 bytes is effectively wrapping + <> = value + + mem = vm.store.mems |> Enum.at(mem_addr) - |> Memory.get_at(address + offset, 1) + |> Memory.put_at(i32addr + offset, i16) + + store_mems = List.replace_at(vm.store.mems, mem_addr, mem) + store = Map.put(vm.store, :mems, store_mems) + + {{frame, Map.put(vm, :store, store), n}, gas + Gas.cost(:i32_store16), stack} + end + + defop i32_trunc_u_f32(<>) do + {ctx, gas + Gas.cost(:i32_trunc_u_f32), [<> | stack]} + end + + defop i32_trunc_s_f32(<>) do + {ctx, gas + Gas.cost(:i32_trunc_s_f32), [<> | stack]} + end + + defop i32_trunc_u_f64(<>) do + {ctx, gas + Gas.cost(:i32_trunc_u_f32), [<> | stack]} + end + + defop i32_trunc_s_f64(<>) do + {ctx, gas + Gas.cost(:i32_trunc_s_f64), [<> | stack]} + end + + # We don't actually need to do anything here, the value is already in binary, + # we'll just read it in as a float in the next instruction that uses this value. + defop i32_reinterpret_f32 do + {ctx, gas + Gas.cost(:i32_reinterpret_f32), stack} + end + + defop i32_wrap_i64(<>) do + {ctx, gas + Gas.cost(:i32_wrap_i64), [i32 | stack]} + end + + # End i32 Instructions ======================================================= + # Begin i64 Instructions ===================================================== + + defop i64_const(immediates: [i64]) do + {ctx, gas + Gas.cost(:i64_const), [<> | stack]} + end + + defop i64_add(<>, <>) do + {ctx, gas + Gas.cost(:i64_add), [<<(a + b)::integer-64-little>> | stack]} + end + + defop i64_sub(<>, <>) do + {ctx, gas + Gas.cost(:i64_sub), [<<(a - b)::integer-64-little>> | stack]} + end + + defop i64_mul(<>, <>) do + {ctx, gas + Gas.cost(:i64_mul), [<<(a * b)::integer-64-little>> | stack]} + end + + defop i64_div_s(<>, <>) do + if b == 0, do: trap("Divide by zero in i64.div_s") + if a / b == 9.223372036854776e18, do: trap("Out of bounds in i64.div_s") + + {ctx, gas + Gas.cost(:i64_div_s), [<> | stack]} + end + + defop i64_div_u(<>, <>) do + if b == 0, do: trap("Divide by zero in i64.div_u") + + {ctx, gas + Gas.cost(:i64_div_u), [<> | stack]} + end + + defop i64_rem_s(<>, <>) do + if b == 0, do: trap("Divide by zero in i64.rem_s") + + {ctx, gas + Gas.cost(:i64_rem_s), [<> | stack]} + end + + defop i64_rem_u(<>, <>) do + if b == 0, do: trap("Divide by zero in i64.rem_u") + + {ctx, gas + Gas.cost(:i64_rem_u), [<> | stack]} + end + + defop i64_rotl(<>, <>) do + {ctx, gas + Gas.cost(:i64_rotl), [<> | stack]} + end + + defop i64_rotr(<>, <>) do + {ctx, gas + Gas.cost(:i64_rotr), [<> | stack]} + end + + defop i64_and(<>, <>) do + {ctx, gas + Gas.cost(:i64_and), [<<(a &&& b)::integer-64-little>> | stack]} + end + + defop i64_or(<>, <>) do + {ctx, gas + Gas.cost(:i64_or), [<<(a ||| b)::integer-64-little>> | stack]} + end + + defop i64_xor(<>, <>) do + {ctx, gas + Gas.cost(:i64_xor), [<> | stack]} + end + + defop i64_shl(<>, <>) do + {ctx, gas + Gas.cost(:i64_shl), [<<(a <<< b)::integer-64-little>> | stack]} + end + + defop i64_shr_u(<>, <>) do + {ctx, gas + Gas.cost(:i64_shr_u), [<<(a >>> b)::integer-64-little>> | stack]} + end + + defop i64_shr_s(<>, <>) do + {ctx, gas + Gas.cost(:i64_shr_s), [<<(a >>> b)::integer-64-little-signed>> | stack]} + end + + defop i64_eq(b, a) do + result = if a === b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:i64_eq), [result | stack]} + end + + defop i64_ne(b, a) do + result = if a !== b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:i64_eq), [result | stack]} + end + + defop i64_eqz(<>) do + result = if a === 0, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:i64_eqz), [result| stack]} + end + + defop i64_lt_u(<>, <>) do + result = if a < b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:i64_lt_u), [result | stack]} + end + + defop i64_gt_u(<>, <>) do + result = if a > b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:i64_gt_u), [result | stack]} + end + + defop i64_le_u(<>, <>) do + result = if a <= b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:i64_le_u), [result | stack]} + end + + defop i64_ge_u(<>, <>) do + result = if a >= b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:i64_ge_u), [result | stack]} + end + + defop i64_le_s(<>, <>) do + result = if a <= b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:i64_le_s), [result | stack]} + end + + defop i64_ge_s(<>, <>) do + result = if a >= b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:i64_ge_s), [result | stack]} + end + + defop i64_lt_s(<>, <>) do + result = if a < b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:i64_lt_s), [result | stack]} + end + + defop i64_gt_s(<>, <>) do + result = if a > b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:i64_gt_s), [result | stack]} + end + + defop i64_popcnt(i64) do + count = + (for <>, do: bit) + |> Enum.reject(& &1 !== 1) + |> length() + + {ctx, gas + Gas.cost(:i64_popcnt, count), [<> | stack]} + end + + defop i64_clz(i64) do + num_zeros = + (for <>, do: bit) + |> leading_zeros() + + {ctx, gas + Gas.cost(:i64_clz, num_zeros), [<> | stack]} + end + + defop i64_ctz(i64) do + num_zeros = + (for <>, do: bit) + |> trailing_zeros() - {ctx, gas + 5, [bin_wrap_unsigned(:i64, :i8, abs(i8)) | stack]} + {ctx, gas + Gas.cost(:i64_ctz, num_zeros), [<> | stack]} end - defp exec_inst({frame, vm, _n} = ctx, gas, [address | stack], _opts, {:i64_load16_u, _alignment, offset}) do + defop i64_load(<>, immediates: [_align, offset]) do + {frame, vm, _n} = ctx mem_addr = hd(frame.module.memaddrs) - <> = + i64 = vm.store.mems |> Enum.at(mem_addr) - |> Memory.get_at(address + offset, 2) + |> Memory.get_at(i32addr + offset, 8) - {ctx, gas + 5, [bin_wrap_unsigned(:i64, :i16, abs(i16)) | stack]} + {ctx, gas + Gas.cost(:i64_load), [i64 | stack]} end - defp exec_inst({frame, vm, _n} = ctx, gas, [address | stack], _opts, {:i64_load32_u, _alignment, offset}) do + defop i64_load8_s(<>, immediates: [_align, offset]) do + {frame, vm, _n} = ctx mem_addr = hd(frame.module.memaddrs) - <> = + i8 = vm.store.mems |> Enum.at(mem_addr) - |> Memory.get_at(address + offset, 4) + |> Memory.get_at(i32addr + offset, 1) - {ctx, gas + 5, [bin_wrap_unsigned(:i64, :i32, abs(i32)) | stack]} + sign = if i8 >= 0, do: 0, else: 255 + + {ctx, gas + Gas.cost(:i64_load8_s), [i8 <> <> | stack]} end - defp exec_inst({frame, vm, n}, gas, [value, address | stack], _opts, {:i32_store, _alignment, offset}) do + defop i64_load16_s(<>, immediates: [_align, offset]) do + {frame, vm, _n} = ctx mem_addr = hd(frame.module.memaddrs) - mem = + i16 = vm.store.mems |> Enum.at(mem_addr) - |> Memory.put_at(address + offset, <>) + |> Memory.get_at(i32addr + offset, 2) - store_mems = List.replace_at(vm.store.mems, mem_addr, mem) - store = Map.put(vm.store, :mems, store_mems) + sign = if i16 >= 0, do: 0, else: 255 - {{frame, Map.put(vm, :store, store), n}, gas + 3, stack} + {ctx, gas + Gas.cost(:i64_load16_s), [i16 <> <> | stack]} end - defp exec_inst({frame, vm, n}, gas, [value, address | stack], _opts, {:i32_store8, _alignment, offset}) do + defop i64_load32_s(<>, immediates: [_align, offset]) do + {frame, vm, _n} = ctx mem_addr = hd(frame.module.memaddrs) - mem = + i32 = vm.store.mems |> Enum.at(mem_addr) - |> Memory.put_at(address + offset, <>) + |> Memory.get_at(i32addr + offset, 4) - store_mems = List.replace_at(vm.store.mems, mem_addr, mem) - store = Map.put(vm.store, :mems, store_mems) + sign = if i32 >= 0, do: 0, else: 255 - {{frame, Map.put(vm, :store, store), n}, gas + 3, stack} + {ctx, gas + Gas.cost(:i64_load32_s), [i32 <> <> | stack]} end - defp exec_inst({frame, vm, n}, gas, [value, address | stack], _opts, {:i32_store16, _alignment, offset}) do + defop i64_load8_u(<>, immediates: [_align, offset]) do + {frame, vm, _n} = ctx mem_addr = hd(frame.module.memaddrs) - value = - <> - |> Binary.to_list - |> Enum.reverse() - |> Binary.from_list + i8 = + vm.store.mems + |> Enum.at(mem_addr) + |> Memory.get_at(i32addr + offset, 1) - mem = + {ctx, gas + Gas.cost(:i64_load8_u), [i8 <> <<0, 0, 0, 0, 0, 0, 0>> | stack]} + end + + defop i64_load16_u(<>, immediates: [_align, offset]) do + {frame, vm, _n} = ctx + mem_addr = hd(frame.module.memaddrs) + + i16 = vm.store.mems |> Enum.at(mem_addr) - |> Memory.put_at(address + offset, value) + |> Memory.get_at(i32addr + offset, 2) - store_mems = List.replace_at(vm.store.mems, mem_addr, mem) - store = Map.put(vm.store, :mems, store_mems) + {ctx, gas + Gas.cost(:i64_load16_u), [i16 <> <<0, 0, 0, 0, 0, 0>> | stack]} + end - {{frame, Map.put(vm, :store, store), n}, gas + 3, stack} + defop i64_load32_u(<>, immediates: [_align, offset]) do + {frame, vm, _n} = ctx + mem_addr = hd(frame.module.memaddrs) + + i32 = + vm.store.mems + |> Enum.at(mem_addr) + |> Memory.get_at(i32addr + offset, 4) + + {ctx, gas + Gas.cost(:i64_load32_u), [i32 <> <<0, 0, 0, 0>> | stack]} end - defp exec_inst({frame, vm, n}, gas, [value, address | stack], _opts, {:i64_store8, _alignment, offset}) do + defop i64_store(value, <>, immediates: [_align, offset]) do + {frame, vm, n} = ctx mem_addr = hd(frame.module.memaddrs) - value = <> mem = vm.store.mems |> Enum.at(mem_addr) - |> Memory.put_at(address + offset, value) + |> Memory.put_at(i32addr + offset, value) store_mems = List.replace_at(vm.store.mems, mem_addr, mem) store = Map.put(vm.store, :mems, store_mems) - {{frame, Map.put(vm, :store, store), n}, gas + 3, stack} + {{frame, Map.put(vm, :store, store), n}, gas + Gas.cost(:i64_store), stack} end - defp exec_inst({frame, vm, n}, gas, [value, address | stack], _opts, {:i64_store16, _alignment, offset}) do + defop i64_store8(value, <>, immediates: [_align, offset]) do + {frame, vm, n} = ctx mem_addr = hd(frame.module.memaddrs) - value = - <> - |> Binary.to_list - |> Enum.reverse() - |> Binary.from_list + <> = value mem = vm.store.mems |> Enum.at(mem_addr) - |> Memory.put_at(address + offset, value) + |> Memory.put_at(i32addr + offset, i8) store_mems = List.replace_at(vm.store.mems, mem_addr, mem) store = Map.put(vm.store, :mems, store_mems) - {{frame, Map.put(vm, :store, store), n}, gas + 5, stack} + {{frame, Map.put(vm, :store, store), n}, gas + Gas.cost(:i64_store8), stack} end - defp exec_inst({frame, vm, n}, gas, [value, address | stack], _opts, {:i64_store32, _alignment, offset}) do + defop i64_store16(value, <>, immediates: [_align, offset]) do + {frame, vm, n} = ctx mem_addr = hd(frame.module.memaddrs) - value = - <> - |> Binary.to_list - |> Enum.reverse() - |> Binary.from_list + <> = value mem = vm.store.mems |> Enum.at(mem_addr) - |> Memory.put_at(address + offset, value) + |> Memory.put_at(i32addr + offset, i16) store_mems = List.replace_at(vm.store.mems, mem_addr, mem) store = Map.put(vm.store, :mems, store_mems) - {{frame, Map.put(vm, :store, store), n}, gas + 5, stack} + {{frame, Map.put(vm, :store, store), n}, gas + Gas.cost(:i64_store16), stack} end - defp exec_inst({frame, vm, n}, gas, [value, address | stack], _opts, {:i64_store, _alignment, offset}) do + defop i64_store32(value, <>, immediates: [_align, offset]) do + {frame, vm, n} = ctx mem_addr = hd(frame.module.memaddrs) + <> = value + mem = vm.store.mems |> Enum.at(mem_addr) - |> Memory.put_at(address + offset, <>) + |> Memory.put_at(i32addr + offset, i32) store_mems = List.replace_at(vm.store.mems, mem_addr, mem) store = Map.put(vm.store, :mems, store_mems) - {{frame, Map.put(vm, :store, store), n}, gas + 3, stack} + {{frame, Map.put(vm, :store, store), n}, gas + Gas.cost(:i64_store32), stack} + end + + defop i64_trunc_u_f32(<>) do + {ctx, gas + Gas.cost(:i64_trunc_u_f32), [<> | stack]} + end + + defop i64_trunc_s_f32(<>) do + {ctx, gas + Gas.cost(:i64_trunc_s_f32), [<> | stack]} + end + + defop i64_trunc_u_f64(<>) do + {ctx, gas + Gas.cost(:i64_trunc_u_f64), [<> | stack]} + end + + defop i64_trunc_s_f64(<>) do + {ctx, gas + Gas.cost(:i64_trunc_s_f64), [<> | stack]} + end + + defop i64_extend_u_i32(i32) do + {ctx, gas + Gas.cost(:i64_extend_u_i32), [i32 <> <<0, 0, 0, 0>> | stack]} + end + + defop i64_extend_s_i32(i32a) do + <> = i32a + + sign = if i32 >= 0, do: 0, else: 255 + + {ctx, gas + Gas.cost(:i64_extend_s_i32), [i32a <> <> | stack]} + end + + defop i64_reinterpret_f64 do + {ctx, gas + Gas.cost(:i64_reinterpret_f32), stack} + end + + # End i64 Instructions ======================================================= + # Begin f32 Instructions ===================================================== + + defop f32_const(immediates: [f32]) do + {ctx, gas + Gas.cost(:f32_const), [<> | stack]} + end + + defop f32_lt(<>, <>) do + result = if a < b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:f32_lt), [result | stack]} + end + + defop f32_le(<>, <>) do + result = if a <= b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:f32_le), [result | stack]} + end + + defop f32_ge(<>, <>) do + result = if a >= b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:f32_ge), [result | stack]} end - defp exec_inst({frame, vm, n}, gas, [value, address | stack], _opts, {:f32_store, _alignment, offset}) do + defop f32_gt(<>, <>) do + result = if a > b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:f32_gt), [result | stack]} + end + + defop f32_add(<>, <>) do + {ctx, gas + Gas.cost(:f32_add), [<<(a + b)::float-32-little>> | stack]} + end + + defop f32_sub(<>, <>) do + {ctx, gas + Gas.cost(:f32_sub), [<<(a - b)::float-32-little>> | stack]} + end + + defop f32_mul(<>, <>) do + {ctx, gas + Gas.cost(:f32_mul), [<<(a * b)::float-32-little>> | stack]} + end + + defop f32_div(<>, <>) do + if b == 0 do + trap("Divide by zero in f32.div") + end + + {ctx, gas + Gas.cost(:f32_div), [<<(a / b)::float-32-little>> | stack]} + end + + defop f32_sqrt(<>) do + {ctx, gas + Gas.cost(:f32_sqrt), [<<:math.sqrt(a)::float-32-little>> | stack]} + end + + defop f32_nearest(<>) do + {ctx, gas + Gas.cost(:f32_nearest), [<> | stack]} + end + + defop f32_trunc(<>) do + {ctx, gas + Gas.cost(:f32_trunc), [<> | stack]} + end + + defop f32_floor(<>) do + {ctx, gas + Gas.cost(:f32_floor), [<> | stack]} + end + + defop f32_ceil(<>) do + {ctx, gas + Gas.cost(:f32_ceil), [<> | stack]} + end + + defop f32_neg(<>) do + result = if a == 0.0, do: 0.0, else: a * -1 + + {ctx, gas + Gas.cost(:f32_neg), [<> | stack]} + end + + defop f32_abs(<>) do + {ctx, gas + Gas.cost(:f32_abs), [<> | stack]} + end + + defop f32_min(<>, <>) do + {ctx, gas + Gas.cost(:f32_min), [<> | stack]} + end + + defop f32_max(<>, <>) do + {ctx, gas + Gas.cost(:f32_max), [<> | stack]} + end + + defop f32_load(<>, immediates: [_align, offset]) do + {frame, vm, _n} = ctx + mem_addr = hd(frame.module.memaddrs) + + f32 = + vm.store.mems + |> Enum.at(mem_addr) + |> Memory.get_at(i32addr + offset, 4) + + {ctx, gas + Gas.cost(:f32_load), [f32 | stack]} + end + + defop f32_store(value, <>, immediates: [_align, offset]) do + {frame, vm, n} = ctx mem_addr = hd(frame.module.memaddrs) mem = vm.store.mems |> Enum.at(mem_addr) - |> Memory.put_at(address + offset, <>) + |> Memory.put_at(i32addr + offset, value) store_mems = List.replace_at(vm.store.mems, mem_addr, mem) store = Map.put(vm.store, :mems, store_mems) - {{frame, Map.put(vm, :store, store), n}, gas + 3, stack} + {{frame, Map.put(vm, :store, store), n}, gas + Gas.cost(:f32_store), stack} + end + + defop f32_eq(a, b) do + result = if a === b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:f32_eq), [result | stack]} + end + + defop f32_ne(a, b) do + result = if a !== b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:f32_eq), [result | stack]} + end + + defop f32_copysign(<>, <>) do + magnitude = abs(a) + sign = if b >= 0, do: 1, else: -1 + + result = magnitude * sign + + # This needs to be here because of a weird bug (?) where 0.0 * -1 would be + # <<0, 0, 0, 128>> instead of <<0, 0, 0, 0>>, even though both were 0.0 + result = if result == 0.0, do: 0.0, else: result + + {ctx, gas + Gas.cost(:f32_copysign), [<> | stack]} + end + + defop f32_convert_s_i32(<>) do + {ctx, gas + Gas.cost(:f32_convert_s_i32), [<<(a * 1.0)::float-32-little>> | stack]} + end + + defop f32_convert_u_i32(<>) do + {ctx, gas + Gas.cost(:f32_convert_u_i32), [<<(a * 1.0)::float-32-little>> | stack]} + end + + defop f32_convert_s_i64(<>) do + {ctx, gas + Gas.cost(:f32_convert_s_i64), [<<(a * 1.0)::float-32-little>> | stack]} + end + + defop f32_convert_u_i64(<>) do + {ctx, gas + Gas.cost(:f32_convert_u_i64), [<<(a * 1.0)::float-32-little>> | stack]} + end + + # TODO: Revisit this -- it's a naive solution that has a few issues (can + # break with very large numbers) + defop f32_demote_f64(<>) do + {ctx, gas + Gas.cost(:f32_demote_f64), [<> | stack]} + end + + defop f32_reinterpret_i32(a) do + {ctx, gas + Gas.cost(:f32_reinterpret_i32), [a | stack]} + end + + # End f32 Instructions ======================================================= + # Begin f64 Instructions ===================================================== + + defop f64_const(immediates: [f64]) do + {ctx, gas + Gas.cost(:f64_const), [<> | stack]} + end + + defop f64_add(<>, <>) do + {ctx, gas + Gas.cost(:f64_add), [<<(a + b)::float-64-little>> | stack]} + end + + defop f64_sub(<>, <>) do + {ctx, gas + Gas.cost(:f64_sub), [<<(a - b)::float-64-little>> | stack]} + end + + defop f64_mul(<>, <>) do + {ctx, gas + Gas.cost(:f64_mul), [<<(a * b)::float-64-little>> | stack]} end - defp exec_inst({frame, vm, n}, gas, [value, address | stack], _opts, {:f64_store, _alignment, offset}) do + defop f64_min(<>, <>) do + {ctx, gas + Gas.cost(:f64_min), [<> | stack]} + end + + defop f64_max(<>, <>) do + {ctx, gas + Gas.cost(:f64_max), [<> | stack]} + end + + defop f64_lt(<>, <>) do + result = if a < b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:f64_lt), [result | stack]} + end + + defop f64_le(<>, <>) do + result = if a <= b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:f64_le), [result | stack]} + end + + defop f64_ge(<>, <>) do + result = if a >= b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:f64_ge), [result | stack]} + end + + defop f64_gt(<>, <>) do + result = if a > b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> + + {ctx, gas + Gas.cost(:f64_gt), [result | stack]} + end + + defop f64_store(value, <>, immediates: [_align, offset]) do + {frame, vm, n} = ctx mem_addr = hd(frame.module.memaddrs) mem = vm.store.mems |> Enum.at(mem_addr) - |> Memory.put_at(address + offset, <>) + |> Memory.put_at(i32addr + offset, value) store_mems = List.replace_at(vm.store.mems, mem_addr, mem) store = Map.put(vm.store, :mems, store_mems) - {{frame, Map.put(vm, :store, store), n}, gas + 3, stack} + {{frame, Map.put(vm, :store, store), n}, gas + Gas.cost(:f64_store), stack} end - defp exec_inst({frame, vm, _n} = ctx, gas, [address | stack], _opts, {:i32_load, _alignment, offset}) do + defop f64_load(<>, immediates: [_align, offset]) do + {frame, vm, _n} = ctx mem_addr = hd(frame.module.memaddrs) - <> = + f64 = vm.store.mems |> Enum.at(mem_addr) - |> Memory.get_at(address + offset, 4) + |> Memory.get_at(i32addr + offset, 8) - {ctx, gas + 3, [i32 | stack]} + {ctx, gas + Gas.cost(:f64_load), [f64 | stack]} end - defp exec_inst({frame, vm, _n} = ctx, gas, [address | stack], _opts, {:i64_load, _alignment, offset}) do - mem_addr = hd(frame.module.memaddrs) + defop f64_eq(a, b) do + result = if a === b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> - <> = - vm.store.mems - |> Enum.at(mem_addr) - |> Memory.get_at(address + offset, 8) + {ctx, gas + Gas.cost(:f64_eq), [result | stack]} + end + + defop f64_ne(a, b) do + result = if a !== b, do: <<1, 0, 0, 0>>, else: <<0, 0, 0, 0>> - {ctx, gas + 3, [i64 | stack]} + {ctx, gas + Gas.cost(:f64_ne), [result | stack]} end - defp exec_inst({frame, vm, _n} = ctx, gas, [address | stack], _opts, {:f32_load, _alignment, offset}) do - mem_addr = hd(frame.module.memaddrs) + defop f64_copysign(<>, <>) do + magnitude = abs(a) + sign = if b >= 0, do: 1, else: -1 - <> = - vm.store.mems - |> Enum.at(mem_addr) - |> Memory.get_at(address + offset, 4) + result = if magnitude == 0.0, do: 0.0, else: magnitude * sign - {ctx, gas + 3, [f32 | stack]} + {ctx, gas + Gas.cost(:f64_copysign), [<> | stack]} end - defp exec_inst({frame, vm, _n} = ctx, gas, [address | stack], _opts, {:f64_load, _alignment, offset}) do - mem_addr = hd(frame.module.memaddrs) + defop f64_nearest(<>) do + {ctx, gas + Gas.cost(:f64_nearest), [<> | stack]} + end - <> = - vm.store.mems - |> Enum.at(mem_addr) - |> Memory.get_at(address + offset, 8) + defop f64_trunc(<>) do + {ctx, gas + Gas.cost(:f64_trunc), [<> | stack]} + end + + defop f64_floor(<>) do + {ctx, gas + Gas.cost(:f64_floor), [<> | stack]} + end - {ctx, gas + 3, [f64 | stack]} + defop f64_neg(<>) do + result = if a == 0.0, do: 0.0, else: a * -1 + + {ctx, gas + Gas.cost(:f64_neg), [<> | stack]} + end + + defop f64_abs(<>) do + {ctx, gas + Gas.cost(:f64_abs), [<> | stack]} + end + + defop f64_sqrt(<>) do + {ctx, gas + Gas.cost(:f64_sqrt), [<<:math.sqrt(a)::float-64-little>> | stack]} + end + + defop f64_ceil(<>) do + {ctx, gas + Gas.cost(:f64_ceil), [<> | stack]} + end + + defop f64_convert_s_i64(<>) do + {ctx, gas + Gas.cost(:f32_convert_s_i64), [<> | stack]} + end + + defop f64_convert_u_i64(<>) do + {ctx, gas + Gas.cost(:f32_convert_u_i64), [<> | stack]} + end + + defop f64_convert_s_i32(<>) do + {ctx, gas + Gas.cost(:f32_convert_s_i32), [<> | stack]} end - defp exec_inst({frame, vm, n}, gas, stack, _opts, {:loop, _result_type}) do + defop f64_convert_u_i32(<>) do + {ctx, gas + Gas.cost(:f64_convert_u_i32), [<> | stack]} + end + + defop f64_promote_f32(<>) do + {ctx, gas + Gas.cost(:f64_promote_f32), [<> | stack]} + end + + defop f64_reinterpret_i64(a) do + {ctx, gas + Gas.cost(:f64_reinterpret_i64), [a | stack]} + end + + # End f64 Instructions ======================================================= + # Begin Type Agnostic Instructions =========================================== + + defop call(immediates: [funcidx]) do + {frame, vm, n} = ctx + + %{^funcidx => func_addr} = frame.module.funcaddrs + + # TODO: Maybe this shouldn't pass the existing stack in? + {_outputs, {vm, gas, stack}} = create_frame_and_execute(vm, func_addr, frame.gas_limit, opts, gas, stack) + + {{frame, vm, n}, gas + Gas.cost(:call), stack} + end + + defop set_global(value, immediates: [idx]) do + {frame, vm, n} = ctx + globals = List.replace_at(vm.globals, idx, value) + + {{frame, Map.put(vm, :globals, globals), n}, gas + Gas.cost(:set_global), stack} + end + + defop set_local(value, immediates: [idx]) do + {frame, vm, n} = ctx + locals = put_elem(frame.locals, idx, value) + + {{Map.put(frame, :locals, locals), vm, n}, gas + Gas.cost(:set_local), stack} + end + + defop get_local(immediates: [idx]) do + {frame, _vm, _n} = ctx + {ctx, gas + Gas.cost(:get_local), [elem(frame.locals, idx) | stack]} + end + + defop get_global(immediates: [idx]) do + {_frame, vm, _n} = ctx + {ctx, gas + Gas.cost(:get_global), [Enum.at(vm.globals, idx) | stack]} + end + + defop tee_local(immediates: [idx]) do + {frame, vm, n} = ctx + [value | _] = stack + locals = put_elem(frame.locals, idx, value) + + {{Map.put(frame, :locals, locals), vm, n}, gas + Gas.cost(:tee_local), stack} + end + + defop grow_memory(<>) do + {frame, vm, n} = ctx + memory = Memory.grow(vm.memory, pages) + vm = Map.put(vm, :memory, memory) + + {{frame, vm, n}, gas + Gas.cost(:grow_memory), [length(vm.memory) | stack]} + end + + defop current_memory do + {_frame, vm, _n} = ctx + {ctx, gas + Gas.cost(:current_memory), [length(vm.memory.pages) | stack]} + end + + defop loop(immediates: [_result_type]) do + {frame, vm, n} = ctx labels = [{n, n} | frame.labels] snapshots = [stack | frame.snapshots] + frame = Map.merge(frame, %{labels: labels, snapshots: snapshots}) - {{Map.merge(frame, %{labels: labels, snapshots: snapshots}), vm, n}, gas + 2, stack} + {{frame, vm, n}, gas + Gas.cost(:loop), stack} end - defp exec_inst({frame, vm, n}, gas, stack, _opts, {:block, _result_type, end_idx}) do + defop block(immediates: [_result_type, end_idx]) do + {frame, vm, n} = ctx labels = [{n, end_idx - 1} | frame.labels] snapshots = [stack | frame.snapshots] + frame = Map.merge(frame, %{labels: labels, snapshots: snapshots}) + + {{frame, vm, n}, gas + Gas.cost(:block), stack} + end + + defop select(condition, b, a) do + stack = if condition == <<1, 0, 0, 0>>, do: [a | stack], else: [b | stack] + + {ctx, gas + Gas.cost(:select), stack} + end + + defop br_if(condition, immediates: [label_idx]) do + if condition == <<1, 0, 0, 0>> do + break_to(ctx, gas + Gas.cost(:br_if), stack, label_idx) + else + {ctx, gas + Gas.cost(:br_if), stack} + end + end + + defop drop(_) do + {ctx, gas + Gas.cost(:drop), stack} + end + + defop br(immediates: [label_idx]) do + break_to(ctx, gas + Gas.cost(:br), stack, label_idx) + end + + defop return do + {frame, vm, _n} = ctx + {{frame, vm, -10}, gas + Gas.cost(:return), stack} + end + + defop unreachable do + {ctx, gas + Gas.cost(:unreachable), stack} + end + + defop nop do + {ctx, gas + Gas.cost(:nop), stack} + end + + defp instruction({frame, vm, n}, gas, [<<1, 0, 0, 0>> | stack], _opts, {:if, _type, _else_idx, end_idx}) do + labels = [{n, end_idx} | frame.labels] + snapshots = [stack | frame.snapshots] + + frame = Map.merge(frame, %{labels: labels, snapshots: snapshots}) + + {{frame, vm, n}, gas + Gas.cost(:if), stack} + end + + defp instruction({frame, vm, _n}, gas, [_ | stack], _opts, {:if, _type, else_idx, end_idx}) do + next_instr = if else_idx != :none, do: else_idx, else: end_idx + + {{frame, vm, next_instr}, gas + Gas.cost(:if), stack} + end - {{Map.merge(frame, %{labels: labels, snapshots: snapshots}), vm, n}, gas + 2, stack} + # This just skips to end because the only time an "else" instruction + # is evaluated is immediately following the execution of an "if" body, which + # means we don't actually want to execute the "else" body. The "if" opcode + # will jump to the body of the else branch if needed. + defp instruction({frame, vm, _n}, gas, stack, _opts, {:else, end_idx}) do + {{frame, vm, end_idx}, gas + Gas.cost(:else), stack} end - defp exec_inst({frame, vm, n}, gas, stack, _opts, :end) do + defp instruction({%{labels: []} = frame, vm, n}, gas, stack, _opts, :end), do: {{frame, vm, n}, gas + Gas.cost(:end, true), stack} + defp instruction({frame, vm, n}, gas, stack, _opts, :end) do [_ | labels] = frame.labels [_ | snapshots] = frame.snapshots - {{Map.merge(frame, %{labels: labels, snapshots: snapshots}), vm, n}, gas + 2, stack} + {{Map.merge(frame, %{labels: labels, snapshots: snapshots}), vm, n}, gas + Gas.cost(:end, false), stack} end - defp exec_inst(ctx, gas, stack, opts, op) do + defp instruction(ctx, gas, stack, opts, op) do IO.inspect op IEx.pry end @@ -768,42 +1288,9 @@ defmodule AlchemyVM.Executor do {{frame, vm, next_instr}, gas + 2, stack} end - # Reference https://lemire.me/blog/2017/05/29/unsigned-vs-signed-integer-arithmetic/ - defp reint(:f32, :i32, float), do: reint(float) - defp reint(:f32, :i64, float), do: reint(float) - defp reint(:f64, :i64, float), do: reint(float) - defp reint(float) do - float - |> :erlang.float_to_binary() - |> :binary.decode_unsigned() - end - - defp sign_value(integer, n), do: sign_value(integer, n, :math.pow(2, 31), :math.pow(2, 32)) - defp sign_value(integer, _n, lower, _upper) when integer >= 0 and integer < lower, do: integer - defp sign_value(integer, _n, lower, _upper) when integer < 0 and integer > -lower, do: integer - defp sign_value(integer, _n, lower, upper) when integer > lower and integer < upper, do: :math.pow(2, 32) + integer - defp sign_value(integer, _n, lower, upper) when integer > -lower and integer < -upper, do: :math.pow(2, 32) + integer - - defp popcnt(integer, 32), do: popcnt(<>) - defp popcnt(integer, 64), do: popcnt(<>) - defp popcnt(binary) do - binary - |> Binary.to_list() - |> Enum.reject(& &1 == 0) - |> Enum.count() - end - defp rotl(number, shift), do: (number <<< shift) ||| (number >>> (0x1F &&& (32 + ~~~(shift + 1)))) &&& ~~~(0xFFFFFFFF <<< shift) defp rotr(number, shift), do: (number >>> shift) ||| (number <<< (0x1F &&& (32 + ~~~(-shift + 1)))) &&& ~~~(0xFFFFFFFF <<< -shift) - def float_point_op(number) do - D.set_context(%D.Context{D.get_context | precision: 6}) - - number - |> :erlang.float_to_binary([decimals: 6]) - |> D.new() - end - def float_demote(number) do D.set_context(%D.Context{D.get_context | precision: 6}) @@ -812,104 +1299,16 @@ defmodule AlchemyVM.Executor do |> D.new() end - def float_promote(number) do - D.set_context(%D.Context{D.get_context | precision: 6}) - - number - |> :erlang.float_to_binary([decimals: 6]) - |> D.new() - end - - defp copysign(a, b) do - a_truth = - to_string(a) - |> String.codepoints - |> Enum.any?(&(&1 == "-")) - - b_truth = - to_string(b) - |> String.codepoints - |> Enum.any?(&(&1 == "-")) - - if a_truth == true && b_truth == true || a_truth == false && b_truth == false do - a - else - if a_truth == true && b_truth == false || a_truth == false && b_truth == true do - b * -1 - end - end - end - defp trap(reason), do: raise "Runtime Error -- #{reason}" - defp check_value([0, 0, 0, 0]), do: 4 - defp check_value([0, 0, 0, _]), do: 3 - defp check_value([0, 0, _, _]), do: 2 - defp check_value([0, _, _, _]), do: 1 - defp check_value(_), do: 0 - - defp count_bits(:l, number) do - <> - |> Binary.to_list - |> check_value - end - - defp count_bits(:t, number) do - <> - |> Binary.to_list - |> Enum.reverse - |> check_value - end - - defp wrap_to_value(:i8, integer), do: integer &&& 0xFF - defp wrap_to_value(:i16, integer), do: integer &&& 0xFFFF - defp wrap_to_value(:i32, integer), do: integer &&& 0xFFFFFFFF - - defp bin_wrap(:i64, :i32, integer) do - <> - |> Binary.to_list() - |> Enum.reverse - |> Binary.from_list - |> :binary.decode_unsigned() - |> Bitwise.band(0xFFFFFFFF) - end - - defp bin_wrap(:i8, integer), do: :binary.decode_unsigned(<>) - defp bin_wrap(:i16, integer), do: :binary.decode_unsigned(<>) - defp bin_wrap(:i32, integer), do: :binary.decode_unsigned(<>) - - defp bin_wrap_signed(:i32, :i8, integer), do: bin_wrap(:i8, integer) &&& 0xFFFFFFFF - defp bin_wrap_signed(:i32, :i16, integer), do: bin_wrap(:i16, integer) &&& 0xFFFFFFFF - defp bin_wrap_signed(:i64, :i8, integer), do: bin_wrap(:i8, integer) &&& 0xFFFFFFFFFFFFFFFF - defp bin_wrap_signed(:i64, :i16, integer), do: bin_wrap(:i16, integer) &&& 0xFFFFFFFFFFFFFFFF - defp bin_wrap_signed(:i64, :i32, integer), do: bin_wrap(:i32, integer) &&& 0xFFFFFFFFFFFFFFFF - - defp bin_wrap_unsigned(:i32, :i8, integer), do: bin_wrap(:i8, integer) &&& 0xFF - defp bin_wrap_unsigned(:i32, :i16, integer), do: bin_wrap(:i16, integer) &&& 0xFFFF - defp bin_wrap_unsigned(:i64, :i8, integer), do: bin_wrap(:i8, integer) &&& 0xFFFF - defp bin_wrap_unsigned(:i64, :i16, integer), do: bin_wrap(:i16, integer) &&& 0xFFFF - defp bin_wrap_unsigned(:i64, :i32, integer), do: bin_wrap(:i32, integer) &&& 0xFFFFFFFF - - defp bin_trunc(:f32, :i32, float), do: round(float) - defp bin_trunc(:f32, :i64, float), do: round(float) - defp bin_trunc(:f64, :i64, float), do: round(float) - - defp log_shr(integer, shift) do - bin = - integer - |> Integer.to_string(2) - |> String.codepoints - |> Enum.reverse - |> Enum.drop((shift)) - |> Enum.map(fn str -> String.to_integer(str) end) - - bin_size = Enum.count(bin) - target = 32 - bin_size - shift - zero_leading_map = Enum.map(1..target, fn _ -> 1 end) - - Integer.undigits(zero_leading_map ++ bin, 2) + defp trailing_zeros(bin_list) do + bin_list + |> Enum.reverse() + |> leading_zeros() end + defp leading_zeros(bin_list), do: Enum.find_index(bin_list, & &1 == 1) + defp create_entry(instruction) when not is_tuple(instruction), do: to_string(instruction) defp create_entry({instruction, _variable}), do: create_entry(instruction) defp create_entry({:if, _rtype, _else_idx, _end_idx}), do: create_entry(:if) diff --git a/lib/execution/module_instance.ex b/lib/execution/module_instance.ex index cc03826..63be66f 100644 --- a/lib/execution/module_instance.ex +++ b/lib/execution/module_instance.ex @@ -108,8 +108,7 @@ defmodule AlchemyVM.ModuleInstance do typeidx = Enum.at(module.function_types, idx) type = Enum.at(module.types, typeidx) - locals = Enum.flat_map(func.locals, & List.duplicate(0, &1.count)) - + locals = Enum.flat_map(func.locals, & List.duplicate(<<0::integer-32-little>>, &1.count)) {type, ref, func.body, locals} end) diff --git a/test/executor_test.exs b/test/executor_test.exs index b3da4aa..e9973fe 100644 --- a/test/executor_test.exs +++ b/test/executor_test.exs @@ -7,835 +7,798 @@ defmodule AlchemyVM.ExecutorTest do test "32 bit integers with add properly" do {:ok, pid} = AlchemyVM.start() AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "i32__add", [4, 2]) == {:ok, 9, 6} + + assert {:ok, _gas, 6} = AlchemyVM.execute(pid, "i32__add", [4, 2]) end test "64 bit integers with add properly" do {:ok, pid} = AlchemyVM.start() AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "i64__add", [4, 2]) == {:ok, 9, 6} + + assert {:ok, _gas, 6} = AlchemyVM.execute(pid, "i64__add", [4, 2]) end test "32 bit integers with mult properly" do {:ok, pid} = AlchemyVM.start() AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "i32__mul", [4, 2]) == {:ok, 11, 8} + + assert {:ok, _gas, 8} = AlchemyVM.execute(pid, "i32__mul", [4, 2]) end test "64 bit integers with mult properly" do {:ok, pid} = AlchemyVM.start() AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "i64__mul", [4, 2]) == {:ok, 11, 8} + + assert {:ok, _gas, 8} = AlchemyVM.execute(pid, "i64__mul", [4, 2]) end test "32 bit integers with sub properly" do {:ok, pid} = AlchemyVM.start() AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "i32__sub", [4, 2]) == {:ok, 9, -2} + + assert {:ok, _gas, -2} = AlchemyVM.execute(pid, "i32__sub", [4, 2]) end test "64 bit integers with sub properly" do {:ok, pid} = AlchemyVM.start() AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "i64__sub", [4, 2]) == {:ok, 9, -2} - end - - #### END OF Basic Numeric Operations - - #### Basic div Operations - - test "32 bit unsgined int can divide properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - result = AlchemyVM.execute(pid, "i32__div_u", [2, 4]) - assert result == {:ok, 11, 2} - end - - test "32 bit signed int can divide properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - {:ok, 11, result} = AlchemyVM.execute(pid, "i32__div_s", [2, -4]) - answer = :math.pow(2, 32) + (result) - assert answer == 4294967294 - end - - test "32 bit float can divide properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - result = AlchemyVM.execute(pid, "f32__div", [2.0, 4.0]) - assert result == {:ok, 11, AlchemyVM.Executor.float_point_op(2.0)} - end - #### End Basic Div Operations - - #### Basic REM Operations + assert {:ok, _gas, -2} = AlchemyVM.execute(pid, "i64__sub", [4, 2]) + end + + #### END OF Basic Numeric Operations + + #### Basic div Operations + + test "32 bit unsgined int can divide properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + assert {:ok, _gas, 2} = AlchemyVM.execute(pid, "i32__div_u", [2, 4]) + end + + test "32 bit signed int can divide properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, -2} = AlchemyVM.execute(pid, "i32__div_s", [2, -4]) + end + + test "32 bit float can divide properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 2.0} = AlchemyVM.execute(pid, "f32__div", [2.0, 4.0]) + end + #### End Basic Div Operations + + #### Basic REM Operations + + test "32 bit uint can rem properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i32__rem_u", [2, 5]) + end + + test "64 bit uint can rem properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i64__rem_u", [2, 5]) + end + + test "32 bit sint can rem properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, -1} = AlchemyVM.execute(pid, "i32__rem_s", [2, -5]) + end + + test "64 bit sint can rem properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, -1} = AlchemyVM.execute(pid, "i64__rem_s", [2, -5]) + end + + #### End Basic Rem Operations + + #### Basic popcnt Operations + + test "32 bit int can popcnt properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i32__popcnt", [128]) + end + + test "64 bit int can popcnt properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i64__popcnt", [128]) + end + + #### End Basic PopCnt Operations + + + #### Basic Bitwise Operations + + test "32 bit int can Conjucture properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i32__and", [11, 5]) + end + + test "64 bit int can Conjucture properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i64__and", [11, 5]) + end + + test "32 bit int can or properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 15} = AlchemyVM.execute(pid, "i32__or", [11, 5]) + end + + test "64 bit int can or properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 15} = AlchemyVM.execute(pid, "i64__or", [11, 5]) + end + + test "32 bit int can xor properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 14} = AlchemyVM.execute(pid, "i32__xor", [11, 5]) + end + + test "64 bit int can xor properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + expected = 14 + assert {:ok, _gas, ^expected} = AlchemyVM.execute(pid, "i64__xor", [11, 5]) + end + + test "32 bit int / ns can shl properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, -800} = AlchemyVM.execute(pid, "i32__shl", [3, -100]) + end + + test "64 bit int can shl properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, -800} = AlchemyVM.execute(pid, "i64__shl", [3, -100]) + end + + test "32 bit sint can shr properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, -13} = AlchemyVM.execute(pid, "i32__shr_s", [3, -100]) + end + + test "64 bit sint can shr properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, -13} = AlchemyVM.execute(pid, "i64__shr_s", [3, -100]) + end + + test "32 bit uint can shr properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 12} = AlchemyVM.execute(pid, "i32__shr_u", [3, 100]) + end + + test "64 bit uint can shr properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 0} = AlchemyVM.execute(pid, "i64__shr_u", [5, 6]) + end + + test "32 bit uint can rotl properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, -793} = AlchemyVM.execute(pid, "i32__rotl", [-100, 3]) + end + + test "32 bit uint can rotr properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 4} = AlchemyVM.execute(pid, "i32__rotr", [16, 2]) + end + + + test "Call instruction works properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/nested_func_call.wasm") + + assert {:ok, _gas, -7367} = AlchemyVM.execute(pid, "nested_func_call", [0, 32]) + end + + ### End simnple BitWise Ops + + ### Begin Float Operations + + test "32 bit float with add properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 6.0} = AlchemyVM.execute(pid, "f32__add", [4.0, 2.0]) + end + + test "64 bit float with add properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 6.0} = AlchemyVM.execute(pid, "f64__add", [4.0, 2.0]) + end + + test "32 bit float with sub properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, -2.0} = AlchemyVM.execute(pid, "f32__sub", [4.0, 2.0]) + end + + test "64 bit float with sub properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, -2.0} = AlchemyVM.execute(pid, "f64__sub", [4.0, 2.0]) + end + + test "32 bit float with mult properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 8.0} = AlchemyVM.execute(pid, "f32__mul", [4.0, 2.0]) + end + + test "64 bit float with mult properly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 8.0} = AlchemyVM.execute(pid, "f64__mul", [4.0, 2.0]) + end + + test "32 bit Floats Ceil Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, -1.0} = AlchemyVM.execute(pid, "f32__ceil", [-1.75]) + end + + test "32 bit Floats Min Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 0.0} = AlchemyVM.execute(pid, "f32__min", [0.0, 0.0]) + end + + test "32 bit Floats Max Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 0.0} = AlchemyVM.execute(pid, "f32__max", [0.0, 0.0]) + end + + test "64 bit Floats Min Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 0.0} = AlchemyVM.execute(pid, "f64__min", [0.0, 0.0]) + end + + test "64 bit Floats Max Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 0.0} = AlchemyVM.execute(pid, "f64__max", [0.0, 0.0]) + end + + test "32 bit Floats copysign Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 0.0} = AlchemyVM.execute(pid, "f32__copysign", [0.0, 0.0]) + assert {:ok, _gas, 0.0} = AlchemyVM.execute(pid, "f32__copysign", [-1.0, 0.0]) + assert {:ok, _gas, -1.0} = AlchemyVM.execute(pid, "f32__copysign", [-1.0, 1.0]) + end + + test "64 bit Floats copysign Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 0.0} = AlchemyVM.execute(pid, "f64__copysign", [0.0, 0.0]) + assert {:ok, _gas, 0.0} = AlchemyVM.execute(pid, "f64__copysign", [-1.0, 0.0]) + assert {:ok, _gas, -1.0} = AlchemyVM.execute(pid, "f64__copysign", [-1.0, 1.0]) + end + + test "32 bit Floats abs Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 0.0} = AlchemyVM.execute(pid, "f32__abs", [0.0]) + assert {:ok, _gas, 1.0} = AlchemyVM.execute(pid, "f32__abs", [-1.0]) + end + + test "64 bit Floats abs Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 1.0} = AlchemyVM.execute(pid, "f64__abs", [-1.0]) + end + + test "32 bit Floats neg Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 0.0} = AlchemyVM.execute(pid, "f32__neg", [0.0]) + assert {:ok, _gas, 1.0} = AlchemyVM.execute(pid, "f32__neg", [-1.0]) + end + + test "64 bit Floats neg Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 0.0} = AlchemyVM.execute(pid, "f64__neg", [0.0]) + assert {:ok, _gas, 1.0} = AlchemyVM.execute(pid, "f64__neg", [-1.0]) + end + + ### End of Basic Float Point Operations + + ### Begin Complex Integer Operations + test "32 bit Integer lt_u Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i32__lt_u", [1, 0]) + assert {:ok, _gas, 0} = AlchemyVM.execute(pid, "i32__lt_u", [0, 1]) + end + + test "64 bit Integer lt_u Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i64__lt_u", [1, 0]) + assert {:ok, _gas, 0} = AlchemyVM.execute(pid, "i64__lt_u", [0, 1]) + end + + test "32 bit Integer lt_s Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i32__lt_s", [2, 1]) + assert {:ok, _gas, 0} = AlchemyVM.execute(pid, "i32__lt_s", [1, 2]) + end + + test "64 bit Integer lt_s Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i64__lt_s", [2, 1]) + assert {:ok, _gas, 0} = AlchemyVM.execute(pid, "i64__lt_s", [1, 2]) + end + + test "32 bit Integer gt_u Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 0} = AlchemyVM.execute(pid, "i32__gt_u", [2, 1]) + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i32__gt_u", [1, 2]) + end + + test "64 bit Integer gt_u Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 0} = AlchemyVM.execute(pid, "i64__gt_u", [2, 1]) + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i64__gt_u", [1, 2]) + end + + test "32 bit Integer gt_s Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 0} = AlchemyVM.execute(pid, "i32__gt_u", [2, 1]) + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i32__gt_u", [1, 2]) + end + + test "64 bit Integer gt_s Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 0} = AlchemyVM.execute(pid, "i64__gt_s", [2, 1]) + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i64__gt_s", [1, 2]) + end + + test "32 bit Integer le_u Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i32__le_u", [2, 1]) + assert {:ok, _gas, 0} = AlchemyVM.execute(pid, "i32__le_u", [1, 2]) + end + + test "64 bit Integer le_u Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i64__le_u", [2, 1]) + assert {:ok, _gas, 0} = AlchemyVM.execute(pid, "i64__le_u", [1, 2]) + end + + test "32 bit Integer ge_u Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 0} = AlchemyVM.execute(pid, "i32__ge_u", [2, 1]) + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i32__ge_u", [1, 2]) + end + + test "64 bit Integer ge_u Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 0} = AlchemyVM.execute(pid, "i64__ge_u", [2, 1]) + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i64__ge_u", [1, 2]) + end + + test "32 bit Integer clz Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 6} = AlchemyVM.execute(pid, "i32__clz", [2]) + end + + test "64 bit Integer clz Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 6} = AlchemyVM.execute(pid, "i64__clz", [2]) + end + + test "32 bit Integer ctz Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 25} = AlchemyVM.execute(pid, "i32__ctz", [2]) + end + + test "64 bit Integer ctz Works Correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 57} = AlchemyVM.execute(pid, "i64__ctz", [2]) + end + + ### END COMPLEX INTEGER + + ### BEGIN PARAMETRIC TEST + test "if statement works" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/if_2.wasm") + + assert {status, _gas, 1} = AlchemyVM.execute(pid, "ifOne", [0]) + end + + ### END PARAMTERIC TEST + + ### Begin Memory Tests + test "32 Store 8 works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/memory.wasm") + + assert {:ok, _gas, -16909061} = AlchemyVM.execute(pid, "i32_store8", []) + end + + test "64 Store 8 works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/memory.wasm") + + assert {:ok, _gas, 4278058235} = AlchemyVM.execute(pid, "i64_store8", []) + end + + test "32 Store 16 works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/memory.wasm") + + assert {:ok, _gas, -859059511} = AlchemyVM.execute(pid, "i32_store16", []) + end + + test "64 Store 16 works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/memory.wasm") + + assert {:ok, _gas, 3435907785} = AlchemyVM.execute(pid, "i64_store16", []) + end + + test "64 Store 32 works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/memory.wasm") + + assert {:ok, _gas, 4294843840} = AlchemyVM.execute(pid, "i64_store32", []) + end + + test "32 Store works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/memory.wasm") + + assert {:ok, _gas, -123456} = AlchemyVM.execute(pid, "i32_store", []) + end + + test "32 load8_s works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/load.wasm") + + assert {:ok, _gas, -1} = AlchemyVM.execute(pid, "i32_load8_s", []) + end + + test "32 load8_u works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/load.wasm") + + assert {:ok, _gas, 255} = AlchemyVM.execute(pid, "i32_load8_u", []) + end + + test "32 load16_u works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/load.wasm") + + assert {:ok, _gas, 65535} = AlchemyVM.execute(pid, "i32_load16_u", []) + end + + test "64 load8_u works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/load.wasm") + + assert {:ok, _gas, 255} = AlchemyVM.execute(pid, "i64_load8_u", []) + end + + test "64 load16_u works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/load.wasm") + + assert {:ok, _gas, 65535} = AlchemyVM.execute(pid, "i64_load16_u", []) + end + + test "64 load32_u works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/load.wasm") + + assert {:ok, _gas, 4294967295} = AlchemyVM.execute(pid, "i64_load32_u", []) + end + + + test "32 load16_s works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/load.wasm") + + assert {:ok, _gas, -1} = AlchemyVM.execute(pid, "i32_load16_s", []) + end + + test "64 load8_s works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/load.wasm") + + assert {:ok, _gas, 255} = AlchemyVM.execute(pid, "i64_load8_s", []) + end + + test "64 load16_s works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/load.wasm") + + assert {:ok, _gas, 65535} = AlchemyVM.execute(pid, "i64_load16_s", []) + end + + test "64 load32_s works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/load.wasm") + + assert {:ok, _gas, 4294967295} = AlchemyVM.execute(pid, "i64_load32_s", []) + end + + ### End Memory Tests + + ### Begin Wrapping & Trunc Tests + test "32 wrap 64 works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap.wasm") + + assert {:ok, _gas, -1} = AlchemyVM.execute(pid, "i32_wrap_i64", []) + end + + test "f32 trunc i32 U works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap.wasm") + + assert {:ok, _gas, -1294967296} = AlchemyVM.execute(pid, "i32_trunc_u_f32", []) + end + + test "f32 trunc i32 S works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap.wasm") + + assert {:ok, _gas, -100} = AlchemyVM.execute(pid, "i32_trunc_s_f32", []) + end - test "32 bit uint can rem properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "i32__rem_u", [2, 5]) == {:ok, 11, 1} - end - - test "64 bit uint can rem properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "i64__rem_u", [2, 5]) == {:ok, 11, 1} - end + test "f64 trunc i32 U works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap.wasm") - test "32 bit sint can rem properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - {:ok, _gas, res} = AlchemyVM.execute(pid, "i32__rem_s", [2, -5]) - answer = :math.pow(2, 32) + res - assert Kernel.round(answer) == 4294967295 - end + assert {:ok, _gas, -1294967296} = AlchemyVM.execute(pid, "i32_trunc_u_f64", []) + end - test "64 bit sint can rem properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "i64__rem_s", [2, -5]) == {:ok, 11, 1.8446744073709552e19} - end - - #### End Basic Rem Operations - - #### Basic popcnt Operations - - test "32 bit int can popcnt properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "i32__popcnt", [128]) == {:ok, 8, 1} - end - - test "64 bit int can popcnt properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "i64__popcnt", [128]) == {:ok, 8, 1} - end - - #### End Basic PopCnt Operations + test "f64 trunc i32 S works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap.wasm") + assert {:ok, _gas, -100} = AlchemyVM.execute(pid, "i32_trunc_s_f64", []) + end - #### Basic Bitwise Operations - - test "32 bit int can Conjucture properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "i32__and", [11, 5]) == {:ok, 9, 1} - end - - test "64 bit int can Conjucture properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "i64__and", [11, 5]) == {:ok, 9, 1} - end - - test "32 bit int can or properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "i32__or", [11, 5]) == {:ok, 9, 15} - end - - test "64 bit int can or properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "i64__or", [11, 5]) == {:ok, 9, 15} - end - - test "32 bit int can xor properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "i32__xor", [11, 5]) == {:ok, 9, 14} - end - - test "64 bit int can xor properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "i64__xor", [11, 5]) == {:ok, 9, 14} - end - - test "32 bit int / ns can shl properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - {:ok, _gas, result} = AlchemyVM.execute(pid, "i32__shl", [3, -100]) - - answer = :math.pow(2, 32) + result - - assert Kernel.round(answer) == 4294966496 - end - - test "64 bit int can shl properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - {:ok, _gas, result} = AlchemyVM.execute(pid, "i64__shl", [3, -100]) - - answer = Bitwise.band(result, 0xFFFFFFFFFFFFFFFF) - - assert round(answer) == 18446744073709550816 - end - - test "32 bit sint can shr properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - {:ok, _gas, result} = AlchemyVM.execute(pid, "i32__shr_s", [3, -100]) - - assert Bitwise.band(result, 0xFFFFFFFF) == 4294967283 - end - - test "64 bit sint can shr properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - {:ok, _gas, result} = AlchemyVM.execute(pid, "i64__shr_s", [3, -100]) - - assert Bitwise.band(result, 0xFFFFFFFFFFFFFFFF) == 18446744073709551603 - end - - # NW - test "32 bit uint can shr properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert {:ok, _gas, 536870899} = AlchemyVM.execute(pid, "i32__shr_u", [3, 100]) - end - - # NW - test "64 bit uint can shr properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - - assert {:ok, _gas, 0} = AlchemyVM.execute(pid, "i64__shr_u", [5, 6]) - end - - test "32 bit uint can rotl properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - {:ok, _gas, result} = AlchemyVM.execute(pid, "i32__rotl", [-100, 3]) - - answer = :math.pow(2, 32) + result - - assert answer == 4294966503 - end - - test "32 bit uint can rotr properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert {:ok, _gas, 4} = AlchemyVM.execute(pid, "i32__rotr", [16, 2]) - end - - - test "Call instruction works properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/nested_func_call.wasm") - assert {:ok, _gas, -7367} = AlchemyVM.execute(pid, "nested_func_call", [0, 32]) - end - - ### End simnple BitWise Ops - - ### Begin Float Operations - - test "32 bit float with add properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "f32__add", [4.0, 2.0]) == {:ok, 9, AlchemyVM.Executor.float_point_op(6.0)} - end - - test "64 bit float with add properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "f64__add", [4.0, 2.0]) == {:ok, 9, AlchemyVM.Executor.float_point_op(6.0)} - end - - test "32 bit float with sub properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "f32__sub", [4.0, 2.0]) == {:ok, 9, AlchemyVM.Executor.float_point_op(2.0)} - end - - test "64 bit float with sub properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "f64__sub", [4.0, 2.0]) == {:ok, 9, AlchemyVM.Executor.float_point_op(2.0)} - end + test "f32 trunc i64 U works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap.wasm") - test "32 bit float with mult properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "f32__mul", [4.0, 2.0]) == {:ok, 11, AlchemyVM.Executor.float_point_op(8.0)} - end - - test "64 bit float with mult properly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "f64__mul", [4.0, 2.0]) == {:ok, 11, AlchemyVM.Executor.float_point_op(8.0)} - end + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i64_trunc_u_f32", []) + end - test "32 bit Floats Ceil Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - - assert AlchemyVM.execute(pid, "f32__ceil", [-1.75]) == {:ok, 8, AlchemyVM.Executor.float_point_op(-1.000000)} - end - - test "32 bit Floats Min Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + test "f32 trunc i64 S works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap.wasm") - assert AlchemyVM.execute(pid, "f32__min", [0.00, 0.00]) == {:ok, 11, AlchemyVM.Executor.float_point_op(0.0)} - end - - test "32 bit Floats Max Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - - assert AlchemyVM.execute(pid, "f32__max", [0.00, 0.00]) == {:ok, 11, AlchemyVM.Executor.float_point_op(0.0)} - end - - test "64 bit Floats Min Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - - assert AlchemyVM.execute(pid, "f64__min", [0.00, 0.00]) == {:ok, 11, AlchemyVM.Executor.float_point_op(0.0)} - end + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i64_trunc_s_f32", []) + end - test "64 bit Floats Max Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + test "f64 trunc i64 U works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap.wasm") - assert AlchemyVM.execute(pid, "f64__max", [0.00, 0.00]) == {:ok, 11, AlchemyVM.Executor.float_point_op(0.0)} - end + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i64_trunc_u_f64", []) + end - test "32 bit Floats copysign Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + test "f64 trunc i64 S works correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap.wasm") - assert AlchemyVM.execute(pid, "f32__copysign", [0.0, 0.0]) == {:ok, 11, AlchemyVM.Executor.float_point_op(0.0)} - assert AlchemyVM.execute(pid, "f32__copysign", [-1.0, 0.0]) == {:ok, 11, AlchemyVM.Executor.float_point_op(0.0)} - assert AlchemyVM.execute(pid, "f32__copysign", [-1.0, 1.0]) == {:ok, 11, AlchemyVM.Executor.float_point_op(-1.0)} - end + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i64_trunc_s_f64", []) + end - test "64 bit Floats copysign Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - - assert AlchemyVM.execute(pid, "f64__copysign", [0.0, 0.0]) == {:ok, 11, AlchemyVM.Executor.float_point_op(0.0)} - assert AlchemyVM.execute(pid, "f64__copysign", [-1.0, 0.0]) == {:ok, 11, AlchemyVM.Executor.float_point_op(0.0)} - assert AlchemyVM.execute(pid, "f64__copysign", [-1.0, 1.0]) == {:ok, 11, AlchemyVM.Executor.float_point_op(-1.0)} - end + test "f32 i32 S Convert works" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - test "32 bit Floats abs Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - - assert AlchemyVM.execute(pid, "f32__abs", [0.0]) == {:ok, 8, AlchemyVM.Executor.float_point_op(0.0)} - assert AlchemyVM.execute(pid, "f32__abs", [-1.0]) == {:ok, 8, AlchemyVM.Executor.float_point_op(1.0)} - end - - test "64 bit Floats abs Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - - assert AlchemyVM.execute(pid, "f64__abs", [-1.0]) == {:ok, 8, AlchemyVM.Executor.float_point_op(1.0)} - end - - test "32 bit Floats neg Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - - assert AlchemyVM.execute(pid, "f32__neg", [0.0]) == {:ok, 8, AlchemyVM.Executor.float_point_op(0.0)} - assert AlchemyVM.execute(pid, "f32__neg", [-1.0]) == {:ok, 8, AlchemyVM.Executor.float_point_op(1.0)} - end + assert {:ok, _gas, -1.0} = AlchemyVM.execute(pid, "f32_convert_s_i32", []) + end - test "64 bit Floats neg Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + test "f32 i32 U Convert works" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - assert AlchemyVM.execute(pid, "f64__neg", [0.0]) == {:ok, 8, AlchemyVM.Executor.float_point_op(0.0)} - assert AlchemyVM.execute(pid, "f64__neg", [-1.0]) == {:ok, 8, AlchemyVM.Executor.float_point_op(1.0)} - end + assert {:ok, _gas, 4294967296.0} = AlchemyVM.execute(pid, "f32_convert_u_i32", []) + end - ### End of Basic Float Point Operations + test "f32 i64 S Convert works" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - ### Begin Complex Integer Operations - test "32 bit Integer lt_u Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + assert {:ok, _gas, 0.0} = AlchemyVM.execute(pid, "f32_convert_s_i64", []) + end - assert AlchemyVM.execute(pid, "i32__lt_u", [1, 0]) == {:ok, 9, 1} - assert AlchemyVM.execute(pid, "i32__lt_u", [0, 1]) == {:ok, 9, 0} - end + test "f32 i64 U Convert works" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - test "64 bit Integer lt_u Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + assert {:ok, _gas, 0.0} = AlchemyVM.execute(pid, "f32_convert_u_i64", []) + end - assert AlchemyVM.execute(pid, "i64__lt_u", [1, 0]) == {:ok, 9, 1} - assert AlchemyVM.execute(pid, "i64__lt_u", [0, 1]) == {:ok, 9, 0} - end + test "f64 i64 S Convert works" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - test "32 bit Integer lt_s Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + assert {:ok, _gas, 0.0} = AlchemyVM.execute(pid, "f64_convert_s_i64", []) + end - assert AlchemyVM.execute(pid, "i32__lt_s", [2, 1]) == {:ok, 9, 1} - assert AlchemyVM.execute(pid, "i32__lt_s", [1, 2]) == {:ok, 9, 0} - end + test "f64 i64 U Convert works" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - test "64 bit Integer lt_s Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + assert {:ok, _gas, 0.0} = AlchemyVM.execute(pid, "f64_convert_u_i64", []) + end - assert AlchemyVM.execute(pid, "i64__lt_s", [2, 1]) == {:ok, 9, 1} - assert AlchemyVM.execute(pid, "i64__lt_s", [1, 2]) == {:ok, 9, 0} - end + test "f64 i32 S Convert works" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - test "32 bit Integer gt_u Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + assert {:ok, _gas, -1.0} = AlchemyVM.execute(pid, "f64_convert_s_i32", []) + end - assert AlchemyVM.execute(pid, "i32__gt_u", [2, 1]) == {:ok, 9, 0} - assert AlchemyVM.execute(pid, "i32__gt_u", [1, 2]) == {:ok, 9, 1} - end + test "f64 i32 U Convert works" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - test "64 bit Integer gt_u Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + assert {:ok, _gas, 4294967295.0} = AlchemyVM.execute(pid, "f64_convert_u_i32", []) + end - assert AlchemyVM.execute(pid, "i64__gt_u", [2, 1]) == {:ok, 9, 0} - assert AlchemyVM.execute(pid, "i64__gt_u", [1, 2]) == {:ok, 9, 1} - end + test "i64 extend i32 u works" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") + + assert {:ok, _gas, 4294967295} = AlchemyVM.execute(pid, "i64_extend_u_i32", []) + end + + test "i64 extend i32 s works" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") + + assert {:ok, _gas, -1} = AlchemyVM.execute(pid, "i64_extend_s_i32", []) + end - test "32 bit Integer gt_s Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + test "f32 demote f64 works" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - assert AlchemyVM.execute(pid, "i32__gt_u", [2, 1]) == {:ok, 9, 0} - assert AlchemyVM.execute(pid, "i32__gt_u", [1, 2]) == {:ok, 9, 1} - end + assert {:ok, _gas, 12345679.0} = AlchemyVM.execute(pid, "f32_demote_f64", []) + end - test "64 bit Integer gt_s Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + test "f64 promote f32 works" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - assert AlchemyVM.execute(pid, "i64__gt_s", [2, 1]) == {:ok, 9, 0} - assert AlchemyVM.execute(pid, "i64__gt_s", [1, 2]) == {:ok, 9, 1} - end - - test "32 bit Integer le_u Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + assert {:ok, _gas, 12345679.0} = AlchemyVM.execute(pid, "f64_demote_f32", []) + end + + ### End Wrapping & Trunc Tests + + ### Begin Compare tests + test "i64 eq true works" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/compare.wasm") - assert AlchemyVM.execute(pid, "i32__le_u", [2, 1]) == {:ok, 9, 1} - assert AlchemyVM.execute(pid, "i32__le_u", [1, 2]) == {:ok, 9, 0} - end - - test "64 bit Integer le_u Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - - assert AlchemyVM.execute(pid, "i64__le_u", [2, 1]) == {:ok, 9, 1} - assert AlchemyVM.execute(pid, "i64__le_u", [1, 2]) == {:ok, 9, 0} - end - - test "32 bit Integer ge_u Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - - assert AlchemyVM.execute(pid, "i32__ge_u", [2, 1]) == {:ok, 9, 0} - assert AlchemyVM.execute(pid, "i32__ge_u", [1, 2]) == {:ok, 9, 1} - end - - test "64 bit Integer ge_u Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - - assert AlchemyVM.execute(pid, "i64__ge_u", [2, 1]) == {:ok, 9, 0} - assert AlchemyVM.execute(pid, "i64__ge_u", [1, 2]) == {:ok, 9, 1} - end - - test "32 bit Integer clz Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - - assert AlchemyVM.execute(pid, "i32__clz", [2]) == {:ok, 8, 3} - end - - test "64 bit Integer clz Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - - assert AlchemyVM.execute(pid, "i64__clz", [2]) == {:ok, 8, 3} - end - - test "32 bit Integer ctz Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - - assert AlchemyVM.execute(pid, "i32__ctz", [2]) == {:ok, 8, 0} - end - - test "64 bit Integer ctz Works Correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - - assert AlchemyVM.execute(pid, "i64__ctz", [2]) == {:ok, 8, 0} - end - - ### END COMPLEX INTEGER - - ### BEGIN PARAMETRIC TEST - test "if statement works" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/if_2.wasm") - assert {status, _gas, 1} = AlchemyVM.execute(pid, "ifOne", [0]) - end - - ### END PARAMTERIC TEST - - ### Begin Memory Tests - test "32 Store 8 works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/memory.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "i32_store8", []) - - answer = - answer - |> :binary.encode_unsigned() - |> Binary.to_list() - |> Enum.reverse - |> Binary.from_list - |> :binary.decode_unsigned() - - assert answer == 4278058235 - end - - test "64 Store 8 works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/memory.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "i64_store8", []) - - answer = - answer - |> :binary.encode_unsigned() - |> Binary.to_list() - |> Enum.reverse - |> Binary.from_list - |> :binary.decode_unsigned() - - assert answer == 4278058235 - end - - test "32 Store 16 works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/memory.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "i32_store16", []) - - answer = - answer - |> :binary.encode_unsigned() - |> Binary.to_list() - |> Enum.reverse - |> Binary.from_list - |> :binary.decode_unsigned() - - assert answer == 3435907785 - end - - test "64 Store 16 works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/memory.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "i64_store16", []) - - answer = - answer - |> :binary.encode_unsigned() - |> Binary.to_list() - |> Enum.reverse - |> Binary.from_list - |> :binary.decode_unsigned() - - assert answer == 3435907785 - end - - test "64 Store 32 works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/memory.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "i64_store32", []) - - answer = - answer - |> :binary.encode_unsigned() - |> Binary.to_list() - |> Enum.reverse - |> Binary.from_list - |> :binary.decode_unsigned() - - assert answer == 4294843840 - end - - test "32 Store works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/memory.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "i32_store", []) - assert answer == 4294843840 - end - - test "32 load8_s works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/load.wasm") - assert {:ok, _gas, 255} = AlchemyVM.execute(pid, "i32_load8_s", []) - end - - test "32 load8_u works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/load.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "i32_load8_u", []) - assert answer == 255 - end - - test "32 load16_u works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/load.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "i32_load16_u", []) - assert answer == 65535 - end - - test "64 load8_u works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/load.wasm") - assert {:ok, _gas, 255} = AlchemyVM.execute(pid, "i64_load8_u", []) - end - - test "64 load16_u works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/load.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "i64_load16_u", []) - assert answer == 65535 - end - - test "64 load32_u works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/load.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "i64_load32_u", []) - assert answer == 4294967295 - end - - - test "32 load16_s works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/load.wasm") - assert {:ok, _gas, 65535} = AlchemyVM.execute(pid, "i32_load16_s", []) - end - - test "64 load8_s works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/load.wasm") - assert {:ok, _gas, 255} = AlchemyVM.execute(pid, "i64_load8_s", []) - end - - test "64 load16_s works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/load.wasm") - assert {:ok, _gas, 65535} = AlchemyVM.execute(pid, "i64_load16_s", []) - end - - test "64 load32_s works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/load.wasm") - assert {:ok, _gas, 4294967295} = AlchemyVM.execute(pid, "i64_load32_s", []) - end - - ### End Memory Tests - - ### Begin Wrapping & Trunc Tests - test "32 wrap 64 works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap.wasm") - assert {:ok, _gas, 4294967295} = AlchemyVM.execute(pid, "i32_wrap_i64", []) - end - - test "f32 trunc i32 U works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap.wasm") - assert {:ok, _gas, 3000000000} = AlchemyVM.execute(pid, "i32_trunc_u_f32", []) - end - - test "f32 trunc i32 S works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "i32_trunc_s_f32", []) - - answer = Bitwise.band(answer, 0xFFFFFFFF) - assert answer == 4294967196 - end - - test "f64 trunc i32 U works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "i32_trunc_u_f64", []) - assert answer == 3000000000 - end - - test "f64 trunc i32 S works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "i32_trunc_s_f64", []) - - answer = Bitwise.band(answer, 0xFFFFFFFF) - assert answer == 4294967196 - end - - test "f32 trunc i64 U works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap.wasm") - assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i64_trunc_u_f32", []) - end - - test "f32 trunc i64 S works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "i64_trunc_s_f32", []) - - answer = Bitwise.band(answer, 0xFFFFFFFF) - assert answer == 1 - end - - test "f64 trunc i64 U works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap.wasm") - assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i64_trunc_u_f64", []) - end - - test "f64 trunc i64 S works correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "i64_trunc_s_f64", []) - - answer = Bitwise.band(answer, 0xFFFFFFFF) - assert answer == 1 - end - - test "f32 i32 S Convert works" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "f32_convert_s_i32", []) - - assert answer == AlchemyVM.Executor.float_point_op(-1 * 1.000000) - end - - test "f32 i32 U Convert works" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "f32_convert_u_i32", []) - - assert answer == AlchemyVM.Executor.float_point_op(4294967295 * 1.000000) - end - - test "f32 i64 S Convert works" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "f32_convert_s_i64", []) - - assert answer == AlchemyVM.Executor.float_point_op(0 * 1.000000) - end - - test "f32 i64 U Convert works" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "f32_convert_u_i64", []) - - assert answer == AlchemyVM.Executor.float_point_op(0 * 1.000000) - end - - test "f64 i64 S Convert works" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "f32_convert_s_i64", []) - - assert answer == AlchemyVM.Executor.float_point_op(0 * 1.000000) - end - - test "f64 i64 U Convert works" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "f32_convert_u_i64", []) - - assert answer == AlchemyVM.Executor.float_point_op(0 * 1.000000) - end - - test "f64 i32 S Convert works" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "f32_convert_s_i32", []) - - assert answer == AlchemyVM.Executor.float_point_op(-1 * 1.000000) - end - - test "f64 i32 U Convert works" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "f32_convert_u_i32", []) - - assert answer == AlchemyVM.Executor.float_point_op(4294967295 * 1.000000) - end - - test "i64 extend i32 u works" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - assert {:ok, _gas, 4294967295} = AlchemyVM.execute(pid, "i64_extend_u_i32", []) - end - - test "i64 extend i32 s works" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - assert {:ok, _gas, 18446744073709551615} = AlchemyVM.execute(pid, "i64_extend_s_i32", []) - end - - test "f32 demote f64 works" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "f32_demote_f64", []) - - assert answer == AlchemyVM.Executor.float_point_op(123456789 * 1.0) - end - - test "f64 promote f32 works" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/wrap_fixed.wasm") - {:ok, _gas, answer} = AlchemyVM.execute(pid, "f64_demote_f32", []) - - assert answer == AlchemyVM.Executor.float_point_op(12345679 * 1.0) - end - - ### End Wrapping & Trunc Tests - - ### Begin Compare tests - test "i64 eq true works" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/compare.wasm") - assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i64_eq_true", []) - end - - test "i64 eq false works" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/compare.wasm") - assert {:ok, _gas, 0} = AlchemyVM.execute(pid, "i64_eq_false", []) - end - - ### END COMPARE - - ### START GAS SYSTEM - test "gas instantiates correctly" do - {:ok, pid} = AlchemyVM.start() - AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") - assert AlchemyVM.execute(pid, "i32__add", [2, 4]) == {:ok, 9, 6} - end - - test "gas instantiates with limit correctly" do - # {:ok, pid} = AlchemyVM.start() - #AlchemyVM.load_file(pid, "test/fixtures/wasm/add_2.wasm") - #{status, gas_cost, stack_value} = AlchemyVM.execute(pid, "_Z3addPi", [4], [gas_limit: 1]) - #{status, gas_cost2, stack_value2} = AlchemyVM.execute(pid, "_Z3addPi", [4], [gas_limit: 10]) - # assert gas_cost == 0 - #assert gas_cost2 == 3 - #assert stack_value == 4 - #assert stack_value2 == 4 - end + assert {:ok, _gas, 1} = AlchemyVM.execute(pid, "i64_eq_true", []) + end + + test "i64 eq false works" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/compare.wasm") + + assert {:ok, _gas, 0} = AlchemyVM.execute(pid, "i64_eq_false", []) + end + + ### END COMPARE + + ### START GAS SYSTEM + test "gas instantiates correctly" do + {:ok, pid} = AlchemyVM.start() + AlchemyVM.load_file(pid, "test/fixtures/wasm/types.wasm") + + assert {:ok, _gas, 6} = AlchemyVM.execute(pid, "i32__add", [2, 4]) + end + + test "gas instantiates with limit correctly" do + # {:ok, pid} = AlchemyVM.start() + #AlchemyVM.load_file(pid, "test/fixtures/wasm/add_2.wasm") + #{status, gas_cost, stack_value} = AlchemyVM.execute(pid, "_Z3addPi", [4], [gas_limit: 1]) + #{status, gas_cost2, stack_value2} = AlchemyVM.execute(pid, "_Z3addPi", [4], [gas_limit: 10]) + # assert gas_cost == 0 + #assert gas_cost2 == 3 + #assert stack_value == 4 + #assert stack_value2 == 4 + end end diff --git a/test/fixtures/hostfuncs/host.ex b/test/fixtures/hostfuncs/host.ex index 92cdfbb..5944c10 100644 --- a/test/fixtures/hostfuncs/host.ex +++ b/test/fixtures/hostfuncs/host.ex @@ -2,17 +2,17 @@ defmodule Host do use AlchemyVM.HostFunction defhost function0(_a, _b) do - AlchemyVM.HostFunction.API.update_memory(ctx, "memory1", 0, <<0, 50, 130, 53>>) # 3310133 - AlchemyVM.HostFunction.API.update_memory(ctx, "memory1", 32, <<0, 102, 20, 75>>) # 6689867 + AlchemyVM.HostFunction.API.update_memory(ctx, "memory1", 0, <<53, 130, 50, 0>>) # 3310133 + AlchemyVM.HostFunction.API.update_memory(ctx, "memory1", 32, <<75, 20, 102, 0>>) # 6689867 end defhost function1 do IO.puts "HI" end - defhost fill_mem_at_locations(addr1, addr2) do - AlchemyVM.HostFunction.API.update_memory(ctx, "memory1", addr1, <<0, 50, 130, 53>>) # 3310133 - AlchemyVM.HostFunction.API.update_memory(ctx, "memory1", addr2, <<0, 102, 20, 75>>) # 6689867 + defhost fill_mem_at_locations(<>, <>) do + AlchemyVM.HostFunction.API.update_memory(ctx, "memory1", addr1, <<53, 130, 50, 0>>) # 3310133 + AlchemyVM.HostFunction.API.update_memory(ctx, "memory1", addr2, <<75, 20, 102, 0>>) # 6689867 end defhost log(a) do diff --git a/test/fixtures/hostfuncs/math.ex b/test/fixtures/hostfuncs/math.ex index 5699e38..1e28b6d 100644 --- a/test/fixtures/hostfuncs/math.ex +++ b/test/fixtures/hostfuncs/math.ex @@ -1,19 +1,19 @@ defmodule Math do use AlchemyVM.HostFunction - defhost add(a, b) do - a + b + defhost add(<>, <>) do + <<(a + b)::integer-32-little>> end - defhost subtract(b, a) do - a - b + defhost subtract(<>, <>) do + <<(a - b)::integer-32-little>> end - defhost multiply(a, b) do - a * b + defhost multiply(<>, <>) do + <<(a * b)::integer-32-little>> end - defhost divide(b, a) do - a / b + defhost divide(<>, <>) do + <> end end diff --git a/test/fixtures/wasm/memory.wasm b/test/fixtures/wasm/memory.wasm index 041361d..b04b65b 100644 Binary files a/test/fixtures/wasm/memory.wasm and b/test/fixtures/wasm/memory.wasm differ diff --git a/test/fixtures/wat/memory.wat b/test/fixtures/wat/memory.wat index 277a873..51f2e1e 100644 --- a/test/fixtures/wat/memory.wat +++ b/test/fixtures/wat/memory.wat @@ -34,7 +34,7 @@ i32.const 0 i32.load) - (func (export "i64_store8") (result i32) + (func (export "i64_store8") (result i64) i32.const 0 i64.const 0xeeeeeeeeeeeeeefb i64.store8 @@ -48,9 +48,9 @@ i64.const 0xeeeeeeeeeeeeeefe i64.store8 i32.const 0 - i32.load) + i64.load) - (func (export "i64_store16") (result i32) + (func (export "i64_store16") (result i64) i32.const 0 i64.const 0xeeeeeeeeeeeecac9 i64.store16 @@ -58,14 +58,14 @@ i64.const 0xeeeeeeeeeeeecccb i64.store16 i32.const 0 - i32.load) + i64.load) - (func (export "i64_store32") (result i32) + (func (export "i64_store32") (result i64) i32.const 0 i64.const -123456 i64.store32 i32.const 0 - i32.load) + i64.load) (func (export "i64_store") (result i64) i32.const 0 diff --git a/test/host_function_test.exs b/test/host_function_test.exs index f543e53..61d31bd 100644 --- a/test/host_function_test.exs +++ b/test/host_function_test.exs @@ -23,15 +23,14 @@ defmodule AlchemyVM.HostFunctionTest do AlchemyVM.load_file(pid, "test/fixtures/wasm/host_func_math.wasm", imports) - assert 55 == Math.hostfunc("add", [5, 50], nil) - assert 45 == Math.hostfunc("subtract", [5, 50], nil) - assert 250 == Math.hostfunc("multiply", [5, 50], nil) - assert 10.0 == Math.hostfunc("divide", [5, 50], nil) - + assert <<55::integer-32-little>> == Math.hostfunc("add", [<<5::integer-32-little>>, <<50::integer-32-little>>], nil) + assert <<45::integer-32-little>> == Math.hostfunc("subtract", [<<5::integer-32-little>>, <<50::integer-32-little>>], nil) + assert <<250::integer-32-little>> == Math.hostfunc("multiply", [<<5::integer-32-little>>, <<50::integer-32-little>>], nil) + assert <<10::integer-32-little>> == Math.hostfunc("divide", [<<5::integer-32-little>>, <<50::integer-32-little>>], nil) assert {:ok, _gas, 55} = AlchemyVM.execute(pid, "add", [5, 50]) assert {:ok, _gas, 45} = AlchemyVM.execute(pid, "subtract", [5, 50]) assert {:ok, _gas, 250} = AlchemyVM.execute(pid, "multiply", [5, 50]) - assert {:ok, _gas, 10.0} = AlchemyVM.execute(pid, "divide", [5, 50]) + assert {:ok, _gas, 10} = AlchemyVM.execute(pid, "divide", [5, 50]) end test "Host funcs with numerical return values add to the stack automatically" do diff --git a/test/program_test.exs b/test/program_test.exs index 97b98bd..90a29c4 100644 --- a/test/program_test.exs +++ b/test/program_test.exs @@ -18,7 +18,8 @@ defmodule AlchemyVM.ProgramTest do {:ok, pid} = AlchemyVM.start() AlchemyVM.load_file(pid, "test/fixtures/wasm/int_div.wasm") - assert {:ok, _gas, -2} = AlchemyVM.execute(pid, "main", [-4], [trace: true]) + expected = -2 + assert {:ok, _gas, ^expected} = AlchemyVM.execute(pid, "main", [-4], [trace: true]) {_status, text} = './trace.log'