From 03796e9aaac994353b58909df18b794d358dc7f3 Mon Sep 17 00:00:00 2001 From: Kelvin Nishikawa Date: Tue, 26 Nov 2024 22:42:17 -0800 Subject: [PATCH 01/24] Refactoring ProcessThread into an async task --- Wacs.Core/Instructions/IInstruction.cs | 14 +- Wacs.Core/Instructions/InstructionBase.cs | 16 +- Wacs.Core/Runtime/WasmRuntimeExecution.cs | 270 +++++++++++----------- 3 files changed, 165 insertions(+), 135 deletions(-) diff --git a/Wacs.Core/Instructions/IInstruction.cs b/Wacs.Core/Instructions/IInstruction.cs index a51158a..75119e3 100644 --- a/Wacs.Core/Instructions/IInstruction.cs +++ b/Wacs.Core/Instructions/IInstruction.cs @@ -15,6 +15,7 @@ // */ using System.IO; +using System.Threading.Tasks; using Wacs.Core.Instructions.Numeric; using Wacs.Core.OpCodes; using Wacs.Core.Runtime; @@ -30,13 +31,22 @@ public interface IInstruction public ByteCode Op { get; } void Validate(IWasmValidationContext context); - + /// - /// Executes the instruction within the given execution context. + /// Synchronously executes the instruction within the given execution context. /// /// The execution context in which to execute the instruction. + /// The effective number of wasm instructions executed int Execute(ExecContext context); + /// + /// Asynchronously wraps the Execute function. + /// Individual instructions may override to perform async functions. + /// + /// + /// ValueTask containing the effective number of wasm instructions executed + public ValueTask ExecuteAsync(ExecContext context); + /// /// Parses an instruction from a binary reader. /// diff --git a/Wacs.Core/Instructions/InstructionBase.cs b/Wacs.Core/Instructions/InstructionBase.cs index c569741..33bf2f1 100644 --- a/Wacs.Core/Instructions/InstructionBase.cs +++ b/Wacs.Core/Instructions/InstructionBase.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using System.IO; +using System.Threading.Tasks; using Wacs.Core.OpCodes; using Wacs.Core.Runtime; using Wacs.Core.Validation; @@ -35,10 +36,22 @@ public abstract class InstructionBase : IInstruction public abstract void Validate(IWasmValidationContext context); /// - /// Executes the instruction within the given execution context. + /// Synchronously executes the instruction within the given execution context. /// /// The execution context in which to execute the instruction. + /// The effective number of wasm instructions executed public abstract int Execute(ExecContext context); + + /// + /// Asynchronously wraps the Execute function. + /// Individual instructions may override to perform async functions. + /// + /// + /// ValueTask containing the effective number of wasm instructions executed + public virtual ValueTask ExecuteAsync(ExecContext context) + { + return new ValueTask(Execute(context)); + } /// /// Instructions are responsible for parsing their binary representation. @@ -53,5 +66,6 @@ public abstract class InstructionBase : IInstruction public virtual string RenderText(ExecContext? context) => Op.GetMnemonic(); protected static Stack _aside = new(); + } } \ No newline at end of file diff --git a/Wacs.Core/Runtime/WasmRuntimeExecution.cs b/Wacs.Core/Runtime/WasmRuntimeExecution.cs index cb19351..a004590 100644 --- a/Wacs.Core/Runtime/WasmRuntimeExecution.cs +++ b/Wacs.Core/Runtime/WasmRuntimeExecution.cs @@ -17,6 +17,8 @@ using System; using System.Linq; using System.Reflection; +using System.Runtime.ExceptionServices; +using System.Threading.Tasks; using Wacs.Core.Instructions; using Wacs.Core.OpCodes; using Wacs.Core.Runtime.Exceptions; @@ -28,9 +30,58 @@ namespace Wacs.Core.Runtime { public partial class WasmRuntime { - private IInstruction? lastInstruction = null; + public TDelegate CreateInvoker(FuncAddr funcAddr, InvokerOptions? options = default) + where TDelegate : Delegate + { + options ??= new InvokerOptions(); + var funcInst = Context.Store[funcAddr]; + var funcType = funcInst.Type; + + if (funcType.ResultType.Types.Length > 1) + throw new WasmRuntimeException("Binding multiple return values from wasm are not yet supported."); + + Delegates.ValidateFunctionTypeCompatibility(funcType, typeof(TDelegate)); + var inner = CreateInvoker(funcAddr, options); + var genericDelegate = Delegates.AnonymousFunctionFromType(funcType, args => + { + try + { + Value[] results = funcType.ParameterTypes.Arity == 0 + ? inner() + : (Value[])GenericFuncsInvoke.Invoke(inner, args); + if (funcType.ResultType.Types.Length == 1) + return results[0]; + return results; + } + catch (TargetInvocationException exc) + { //Propagate out any exceptions + throw exc.InnerException; + } + }); + + return (TDelegate)Delegates.CreateTypedDelegate(genericDelegate, typeof(TDelegate)); + } + + //No type checking, but you can get multiple return values + public Delegates.StackFunc CreateStackInvoker(FuncAddr funcAddr, InvokerOptions? options = default) + { + options ??= new InvokerOptions(); + var invoker = CreateInvoker(funcAddr, options); + var funcInst = Context.Store[funcAddr]; + var funcType = funcInst.Type; + object[] p = new object[funcType.ParameterTypes.Arity]; + + return valueParams => + { + for (int i = 0; i < funcType.ParameterTypes.Arity; ++i) + p[i] = valueParams[i]; + + return invoker(p); + }; + } + private Delegates.GenericFuncs CreateInvoker(FuncAddr funcAddr, InvokerOptions options) { return GenericDelegate; @@ -48,60 +99,47 @@ Value[] GenericDelegate(params object[] args) } Context.ProcessTimer.Restart(); - Context.Invoke(funcAddr); - Context.steps = 0; - long highwatermark = 0; bool fastPath = options.UseFastPath(); - try { if (fastPath) { - int comp = 0; - do - { - comp = ProcessThread(); - Context.steps += comp; - if (options.GasLimit > 0) - { - if (Context.steps >= options.GasLimit) - { - throw new InsufficientGasException( - $"Invocation ran out of gas (limit:{options.GasLimit})."); - } - } - } while (comp > 0); + Task thread = ProcessThreadAsync(options.GasLimit); + thread.Wait(); } else { - long comp = 0; - do - { - comp = ProcessThreadWithOptions(options); - Context.steps += comp; + Task thread = ProcessThreadWithOptions(options); + thread.Wait(); + } + } + catch (AggregateException agg) + { + var exc = agg.InnerException; + + Context.ProcessTimer.Stop(); + Context.InstructionTimer.Stop(); + if (options.LogProgressEvery > 0) + Console.Error.WriteLine(); + if (options.CollectStats != StatsDetail.None) + PrintStats(options); + if (options.LogGas) + Console.Error.WriteLine($"Process used {Context.steps} gas. {Context.ProcessTimer.Elapsed}"); - if (options.GasLimit > 0) - { - if (Context.steps >= options.GasLimit) - { - throw new InsufficientGasException( - $"Invocation ran out of gas (limit:{options.GasLimit})."); - } - } + if (options.CalculateLineNumbers) + { + var ptr = Context.ComputePointerPath(); + var path = string.Join(".", ptr.Select(t => $"{t.Item1.Capitalize()}[{t.Item2}]")); + (int line, string instruction) = Context.Frame.Module.Repr.CalculateLine(path); - if (options.LogProgressEvery > 0) - { - highwatermark += comp; - if (highwatermark >= options.LogProgressEvery) - { - highwatermark -= options.LogProgressEvery; - Console.Error.Write('.'); - } - } - } while (comp > 0); + ExceptionDispatchInfo.Throw(new TrapException(exc.Message + $":line {line} instruction #{Context.steps}\n{path}")); } + + //Flush the stack before throwing... + Context.FlushCallStack(); + ExceptionDispatchInfo.Throw(exc); } catch (TrapException exc) { @@ -120,12 +158,12 @@ Value[] GenericDelegate(params object[] args) var path = string.Join(".", ptr.Select(t => $"{t.Item1.Capitalize()}[{t.Item2}]")); (int line, string instruction) = Context.Frame.Module.Repr.CalculateLine(path); - throw new TrapException(exc.Message + $":line {line} instruction #{Context.steps}\n{path}"); + ExceptionDispatchInfo.Throw(new TrapException(exc.Message + $":line {line} instruction #{Context.steps}\n{path}")); } //Flush the stack before throwing... Context.FlushCallStack(); - throw; + ExceptionDispatchInfo.Throw(exc); } catch (SignalException exc) { @@ -152,7 +190,7 @@ Value[] GenericDelegate(params object[] args) var exType = exc.GetType(); var ctr = exType.GetConstructor(new Type[] { typeof(int), typeof(string) }); - throw ctr?.Invoke(new object[] { exc.Signal, message }) as Exception ?? exc; + ExceptionDispatchInfo.Throw(ctr?.Invoke(new object[] { exc.Signal, message }) as Exception ?? exc); } catch (WasmRuntimeException) { @@ -174,104 +212,72 @@ Value[] GenericDelegate(params object[] args) return results; } } - - public TDelegate CreateInvoker(FuncAddr funcAddr, InvokerOptions? options = default) - where TDelegate : Delegate + + public async Task ProcessThreadAsync(long gasLimit) { - options ??= new InvokerOptions(); - var funcInst = Context.Store[funcAddr]; - var funcType = funcInst.Type; - - if (funcType.ResultType.Types.Length > 1) - throw new WasmRuntimeException("Binding multiple return values from wasm are not yet supported."); - - Delegates.ValidateFunctionTypeCompatibility(funcType, typeof(TDelegate)); - var inner = CreateInvoker(funcAddr, options); - var genericDelegate = Delegates.AnonymousFunctionFromType(funcType, args => + if (gasLimit <= 0) gasLimit = long.MaxValue; + while (Context.Next() is { } inst) { - try - { - Value[] results = funcType.ParameterTypes.Arity == 0 - ? inner() - : (Value[])GenericFuncsInvoke.Invoke(inner, args); - if (funcType.ResultType.Types.Length == 1) - return results[0]; - return results; - } - catch (TargetInvocationException exc) - { //Propagate out any exceptions - throw exc.InnerException; - } - }); - - return (TDelegate)Delegates.CreateTypedDelegate(genericDelegate, typeof(TDelegate)); + int work = await inst.ExecuteAsync(Context); + Context.steps += work; + if (Context.steps >= gasLimit) + throw new InsufficientGasException($"Invocation ran out of gas (limit:{gasLimit})."); + } } - //No type checking, but you can get multiple return values - public Delegates.StackFunc CreateStackInvoker(FuncAddr funcAddr, InvokerOptions? options = default) + public async Task ProcessThreadWithOptions(InvokerOptions options) { - options ??= new InvokerOptions(); - var invoker = CreateInvoker(funcAddr, options); - var funcInst = Context.Store[funcAddr]; - var funcType = funcInst.Type; - object[] p = new object[funcType.ParameterTypes.Arity]; - - return valueParams => + long highwatermark = 0; + long gasLimit = options.GasLimit > 0 ? options.GasLimit : long.MaxValue; + while (Context.Next() is { } inst) { - for (int i = 0; i < funcType.ParameterTypes.Arity; ++i) - p[i] = valueParams[i]; + //Trace execution + if (options.LogInstructionExecution != InstructionLogging.None) + { + LogPreInstruction(options, inst); + } - return invoker(p); - }; - } + int work = 0; + if (options.CollectStats == StatsDetail.Instruction) + { + Context.InstructionTimer.Restart(); + work = await inst.ExecuteAsync(Context); + Context.InstructionTimer.Stop(); + Context.steps += work; - public int ProcessThread() - { - var inst = Context.Next(); - if (inst == null) - return 0; - - return inst.Execute(Context); - } + var st = Context.Stats[(ushort)inst.Op]; + st.count += work; + st.duration += Context.InstructionTimer.ElapsedTicks; + Context.Stats[(ushort)inst.Op] = st; + } + else + { + Context.InstructionTimer.Start(); + work = await inst.ExecuteAsync(Context); + Context.InstructionTimer.Stop(); + Context.steps += work; + } - public long ProcessThreadWithOptions(InvokerOptions options) - { - var inst = Context.Next(); - if (inst == null) - return 0; + if (options.LogInstructionExecution.Has(InstructionLogging.Computes)) + { + LogPostInstruction(options, inst); + } - //Trace execution - if (options.LogInstructionExecution != InstructionLogging.None) - { - LogPreInstruction(options, inst); - } - - long steps = 0; - if (options.CollectStats == StatsDetail.Instruction) - { - Context.InstructionTimer.Restart(); - steps += inst.Execute(Context); - Context.InstructionTimer.Stop(); - - var st = Context.Stats[(ushort)inst.Op]; - st.count += steps; - st.duration += Context.InstructionTimer.ElapsedTicks; - Context.Stats[(ushort)inst.Op] = st; - } - else - { - Context.InstructionTimer.Start(); - steps += inst.Execute(Context); - Context.InstructionTimer.Stop(); - } - - if (options.LogInstructionExecution.Has(InstructionLogging.Computes)) - { - LogPostInstruction(options, inst); + lastInstruction = inst; + + if (Context.steps >= gasLimit) + throw new InsufficientGasException($"Invocation ran out of gas (limit:{gasLimit})."); + + if (options.LogProgressEvery > 0) + { + highwatermark += work; + if (highwatermark >= options.LogProgressEvery) + { + highwatermark -= options.LogProgressEvery; + Console.Error.Write('.'); + } + } } - - lastInstruction = inst; - return steps; } private void LogPreInstruction(InvokerOptions options, IInstruction inst) From d3ba981814427cde4e931ec32f26145b4afa6fe3 Mon Sep 17 00:00:00 2001 From: Kelvin Nishikawa Date: Wed, 27 Nov 2024 14:02:16 -0800 Subject: [PATCH 02/24] Adding support for Async boundary calls (Task/JSPI) --- Wacs.Core/Instructions/Control.cs | 74 +++++++++++++ Wacs.Core/Instructions/InstructionBase.cs | 7 +- Wacs.Core/Instructions/TailCall.cs | 102 ++++++++++++++++++ Wacs.Core/Modules/Sections/StackRenderer.cs | 2 +- Wacs.Core/Runtime/ExecContext.cs | 43 ++++++-- Wacs.Core/Runtime/OpStack.cs | 4 +- Wacs.Core/Runtime/Store.cs | 4 +- Wacs.Core/Runtime/Types/HostFunction.cs | 58 +++++++++- Wacs.Core/Runtime/WasmRuntimeBinding.cs | 25 ++++- Wacs.Core/Runtime/WasmRuntimeExecution.cs | 31 ++++-- Wacs.Core/Runtime/WasmRuntimeInstantiation.cs | 4 +- 11 files changed, 323 insertions(+), 31 deletions(-) diff --git a/Wacs.Core/Instructions/Control.cs b/Wacs.Core/Instructions/Control.cs index 0716c25..3b7cda4 100644 --- a/Wacs.Core/Instructions/Control.cs +++ b/Wacs.Core/Instructions/Control.cs @@ -19,9 +19,11 @@ using System.IO; using System.Linq; using System.Text; +using System.Threading.Tasks; using FluentValidation; using Wacs.Core.OpCodes; using Wacs.Core.Runtime; +using Wacs.Core.Runtime.Exceptions; using Wacs.Core.Runtime.Types; using Wacs.Core.Types; using Wacs.Core.Utilities; @@ -736,6 +738,11 @@ public override int Execute(ExecContext context) //0x10 public class InstCall : InstructionBase, ICallInstruction { + public InstCall() + { + IsAsync = true; + } + public override ByteCode Op => OpCode.Call; public FuncIdx X; @@ -769,6 +776,17 @@ public override int Execute(ExecContext context) $"Instruction call failed. Function address for {X} was not in the Context."); var a = context.Frame.Module.FuncAddrs[X]; context.Invoke(a); + + throw new WasmRuntimeException("Synchronous execution path not allowed."); + return 1; + } + + public override async ValueTask ExecuteAsync(ExecContext context) + { + context.Assert( context.Frame.Module.FuncAddrs.Contains(X), + $"Instruction call failed. Function address for {X} was not in the Context."); + var a = context.Frame.Module.FuncAddrs[X]; + await context.Invoke(a); return 1; } @@ -822,6 +840,11 @@ public override string RenderText(ExecContext? context) //0x11 public class InstCallIndirect : InstructionBase, ICallInstruction { + public InstCallIndirect() + { + IsAsync = true; + } + public override ByteCode Op => OpCode.CallIndirect; private TypeIdx Y; @@ -918,6 +941,57 @@ public override int Execute(ExecContext context) throw new TrapException($"Instruction call_indirect failed. Expected FunctionType differed."); //19. context.Invoke(a); + throw new WasmRuntimeException("Synchronous execution path not allowed"); + return 1; + } + + public override async ValueTask ExecuteAsync(ExecContext context) + { + //2. + context.Assert( context.Frame.Module.TableAddrs.Contains(X), + $"Instruction call_indirect failed. Table {X} was not in the Context."); + //3. + var ta = context.Frame.Module.TableAddrs[X]; + //4. + context.Assert( context.Store.Contains(ta), + $"Instruction call_indirect failed. TableInstance {ta} was not in the Store."); + //5. + var tab = context.Store[ta]; + //6. + context.Assert( context.Frame.Module.Types.Contains(Y), + $"Instruction call_indirect failed. Function Type {Y} was not in the Context."); + //7. + var ftExpect = context.Frame.Module.Types[Y]; + //8. + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + //9. + uint i = context.OpStack.PopU32(); + //10. + if (i >= tab.Elements.Count) + throw new TrapException($"Instruction call_indirect could not find element {i}"); + //11. + var r = tab.Elements[(int)i]; + //12. + if (r.IsNullRef) + throw new TrapException($"Instruction call_indirect NullReference."); + //13. + context.Assert( r.Type == ValType.Funcref, + $"Instruction call_indirect failed. Element was not a FuncRef"); + //14. + var a = (FuncAddr)r; + //15. + context.Assert( context.Store.Contains(a), + $"Instruction call_indirect failed. Validation of table mutation failed."); + //16. + var funcInst = context.Store[a]; + //17. + var ftActual = funcInst.Type; + //18. + if (!ftExpect.Matches(ftActual)) + throw new TrapException($"Instruction call_indirect failed. Expected FunctionType differed."); + //19. + await context.Invoke(a); return 1; } diff --git a/Wacs.Core/Instructions/InstructionBase.cs b/Wacs.Core/Instructions/InstructionBase.cs index 33bf2f1..61042fb 100644 --- a/Wacs.Core/Instructions/InstructionBase.cs +++ b/Wacs.Core/Instructions/InstructionBase.cs @@ -19,6 +19,7 @@ using System.Threading.Tasks; using Wacs.Core.OpCodes; using Wacs.Core.Runtime; +using Wacs.Core.Runtime.Exceptions; using Wacs.Core.Validation; namespace Wacs.Core.Instructions @@ -35,6 +36,8 @@ public abstract class InstructionBase : IInstruction public abstract void Validate(IWasmValidationContext context); + public bool IsAsync = false; + /// /// Synchronously executes the instruction within the given execution context. /// @@ -48,9 +51,9 @@ public abstract class InstructionBase : IInstruction /// /// /// ValueTask containing the effective number of wasm instructions executed - public virtual ValueTask ExecuteAsync(ExecContext context) + public virtual async ValueTask ExecuteAsync(ExecContext context) { - return new ValueTask(Execute(context)); + throw new WasmRuntimeException("Async Execution must be explicitly implemented"); } /// diff --git a/Wacs.Core/Instructions/TailCall.cs b/Wacs.Core/Instructions/TailCall.cs index f7308ad..f81371e 100644 --- a/Wacs.Core/Instructions/TailCall.cs +++ b/Wacs.Core/Instructions/TailCall.cs @@ -17,8 +17,10 @@ using System.Collections.Generic; using System.IO; using System.Text; +using System.Threading.Tasks; using Wacs.Core.OpCodes; using Wacs.Core.Runtime; +using Wacs.Core.Runtime.Exceptions; using Wacs.Core.Runtime.Types; using Wacs.Core.Types; using Wacs.Core.Utilities; @@ -28,6 +30,10 @@ namespace Wacs.Core.Instructions { public class InstReturnCall : InstructionBase, ICallInstruction { + public InstReturnCall() + { + IsAsync = true; + } public override ByteCode Op => OpCode.ReturnCall; public FuncIdx X; @@ -79,6 +85,31 @@ public override int Execute(ExecContext context) //Call context.Invoke(a); + //Reuse the pointer from the outgoing function + context.Frame.ContinuationAddress = address; + + throw new WasmRuntimeException("Synchronous execution path not allowed"); + return 1; + } + + public override async ValueTask ExecuteAsync(ExecContext context) + { + //Fetch the Module first because we might exhaust the call stack + context.Assert( context.Frame.Module.FuncAddrs.Contains(X), + $"Instruction call failed. Function address for {X} was not in the Context."); + var a = context.Frame.Module.FuncAddrs[X]; + + //Return + // Split stack will preserve the operands, don't bother moving them. + // Stack values = new Stack(); + // context.OpStack.PopResults(context.Frame.Type.ResultType, ref values); + var address = context.PopFrame(); + //Push back operands + // context.OpStack.Push(values); + + //Call + await context.Invoke(a); + //Reuse the pointer from the outgoing function context.Frame.ContinuationAddress = address; return 1; @@ -89,6 +120,7 @@ public override int Execute(ExecContext context) /// public override IInstruction Parse(BinaryReader reader) { + IsAsync = true; X = (FuncIdx)reader.ReadLeb128_u32(); return this; } @@ -134,6 +166,11 @@ public override string RenderText(ExecContext? context) //0x11 public class InstReturnCallIndirect : InstructionBase, ICallInstruction { + public InstReturnCallIndirect() + { + IsAsync = true; + } + public override ByteCode Op => OpCode.ReturnCallIndirect; private TypeIdx Y; @@ -247,6 +284,71 @@ public override int Execute(ExecContext context) //19. context.Invoke(a); + //Reuse the pointer from the outgoing function + context.Frame.ContinuationAddress = address; + + throw new WasmRuntimeException("Synchronous execution path not allowed"); + return 1; + } + + public override async ValueTask ExecuteAsync(ExecContext context) + { + //Call Indirect + //2. + context.Assert( context.Frame.Module.TableAddrs.Contains(X), + $"Instruction call_indirect failed. Table {X} was not in the Context."); + //3. + var ta = context.Frame.Module.TableAddrs[X]; + //4. + context.Assert( context.Store.Contains(ta), + $"Instruction call_indirect failed. TableInstance {ta} was not in the Store."); + //5. + var tab = context.Store[ta]; + //6. + context.Assert( context.Frame.Module.Types.Contains(Y), + $"Instruction call_indirect failed. Function Type {Y} was not in the Context."); + //7. + var ftExpect = context.Frame.Module.Types[Y]; + //8. + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + //9. + uint i = context.OpStack.PopU32(); + //10. + if (i >= tab.Elements.Count) + throw new TrapException($"Instruction call_indirect could not find element {i}"); + //11. + var r = tab.Elements[(int)i]; + //12. + if (r.IsNullRef) + throw new TrapException($"Instruction call_indirect NullReference."); + //13. + context.Assert( r.Type == ValType.Funcref, + $"Instruction call_indirect failed. Element was not a FuncRef"); + //14. + var a = (FuncAddr)r; + //15. + context.Assert( context.Store.Contains(a), + $"Instruction call_indirect failed. Validation of table mutation failed."); + //16. + var funcInst = context.Store[a]; + //17. + var ftActual = funcInst.Type; + //18. + if (!ftExpect.Matches(ftActual)) + throw new TrapException($"Instruction call_indirect failed. Expected FunctionType differed."); + + //Return + // Split stack will preserve the operands, don't bother moving them. + // Stack values = new Stack(); + // context.OpStack.PopResults(context.Frame.Type.ResultType, ref values); + var address = context.PopFrame(); + //Push back operands + // context.OpStack.Push(values); + + //19. + await context.Invoke(a); + //Reuse the pointer from the outgoing function context.Frame.ContinuationAddress = address; return 1; diff --git a/Wacs.Core/Modules/Sections/StackRenderer.cs b/Wacs.Core/Modules/Sections/StackRenderer.cs index 21042b8..028340c 100644 --- a/Wacs.Core/Modules/Sections/StackRenderer.cs +++ b/Wacs.Core/Modules/Sections/StackRenderer.cs @@ -233,7 +233,7 @@ private ExecContext BuildDummyContext(Module module, ModuleInstance moduleInst, { case Module.ImportDesc.FuncDesc funcDesc: var funcSig = moduleInst.Types[funcDesc.TypeIndex]; - var funcAddr = store.AllocateHostFunction(entityId, funcSig, typeof(FakeHostDelegate), fakeHostFunc); + var funcAddr = store.AllocateHostFunction(entityId, funcSig, typeof(FakeHostDelegate), fakeHostFunc, false); moduleInst.FuncAddrs.Add(funcAddr); break; default: break; diff --git a/Wacs.Core/Runtime/ExecContext.cs b/Wacs.Core/Runtime/ExecContext.cs index 25e5530..34faa89 100644 --- a/Wacs.Core/Runtime/ExecContext.cs +++ b/Wacs.Core/Runtime/ExecContext.cs @@ -22,6 +22,7 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using System.Threading.Tasks; using Microsoft.Extensions.ObjectPool; using Wacs.Core.Instructions; using Wacs.Core.OpCodes; @@ -220,11 +221,12 @@ public void ExitBlock() } // @Spec 4.4.10.1 Function Invocation - public void Invoke(FuncAddr addr) + public async Task Invoke(FuncAddr addr) { //1. Assert( Store.Contains(addr), $"Failure in Function Invocation. Address does not exist {addr}"); + //2. var funcInst = Store[addr]; switch (funcInst) @@ -233,8 +235,11 @@ public void Invoke(FuncAddr addr) Invoke(wasmFunc, wasmFunc.Index); return; case HostFunction hostFunc: - Invoke(hostFunc); - break; + if (hostFunc.IsAsync) + await InvokeAsync(hostFunc); + else + Invoke(hostFunc); + return; } } @@ -292,17 +297,19 @@ private void Invoke(FunctionInstance wasmFunc, FuncIdx idx) private void Invoke(HostFunction hostFunc) { var funcType = hostFunc.Type; - - //Write the ExecContext to the first parameter if needed - var paramBuf = hostFunc.GetParameterBuf(this); //Fetch the parameters - OpStack.PopScalars(funcType.ParameterTypes, paramBuf); + OpStack.PopScalars(funcType.ParameterTypes, hostFunc.ParameterBuffer, hostFunc.PassExecContext?1:0); + if (hostFunc.PassExecContext) + { + hostFunc.ParameterBuffer[0] = this; + } + //Pass them hostFunc.Invoke(hostFunc.ParameterBuffer, OpStack); } - + // @Spec 4.4.10.2. Returning from a function public void FunctionReturn() { @@ -318,15 +325,31 @@ public void FunctionReturn() //8. ResumeSequence(address); } + + private async ValueTask InvokeAsync(HostFunction hostFunc) + { + var funcType = hostFunc.Type; + + //Fetch the parameters + OpStack.PopScalars(funcType.ParameterTypes, hostFunc.ParameterBuffer, hostFunc.PassExecContext?1:0); - public IInstruction? Next() + if (hostFunc.PassExecContext) + { + hostFunc.ParameterBuffer[0] = this; + } + + //Pass them + await hostFunc.InvokeAsync(hostFunc.ParameterBuffer, OpStack); + } + + public InstructionBase? Next() { //Advance to the next instruction first. if (++_sequenceIndex >= _currentSequence.Count) return null; //Critical path, using direct array access - return _currentSequence._instructions[_sequenceIndex]; + return _currentSequence._instructions[_sequenceIndex] as InstructionBase; } public List<(string, int)> ComputePointerPath() diff --git a/Wacs.Core/Runtime/OpStack.cs b/Wacs.Core/Runtime/OpStack.cs index daf555e..b6c3ae8 100644 --- a/Wacs.Core/Runtime/OpStack.cs +++ b/Wacs.Core/Runtime/OpStack.cs @@ -212,9 +212,9 @@ public void PopResults(int arity, ref Stack results) } } - public void PopScalars(ResultType type, Span targetBuf) + public void PopScalars(ResultType type, object[] targetBuf, int firstParameter) { - for (int i = type.Arity - 1; i >= 0; --i) + for (int i = type.Arity - 1 + firstParameter; i >= firstParameter; --i) { targetBuf[i] = PopAny().Scalar; } diff --git a/Wacs.Core/Runtime/Store.cs b/Wacs.Core/Runtime/Store.cs index c94d07b..562cb20 100644 --- a/Wacs.Core/Runtime/Store.cs +++ b/Wacs.Core/Runtime/Store.cs @@ -108,9 +108,9 @@ public FuncAddr AllocateWasmFunction(Module.Function func, ModuleInstance module return funcAddr; } - public FuncAddr AllocateHostFunction((string module, string entity) id, FunctionType funcType, Type delType, Delegate hostFunc) + public FuncAddr AllocateHostFunction((string module, string entity) id, FunctionType funcType, Type delType, Delegate hostFunc, bool isAsync) { - var funcInst = new HostFunction(id, funcType, delType, hostFunc); + var funcInst = new HostFunction(id, funcType, delType, hostFunc, isAsync); var funcAddr = AddFunction(funcInst); return funcAddr; } diff --git a/Wacs.Core/Runtime/Types/HostFunction.cs b/Wacs.Core/Runtime/Types/HostFunction.cs index e2d9ef3..20c3f48 100644 --- a/Wacs.Core/Runtime/Types/HostFunction.cs +++ b/Wacs.Core/Runtime/Types/HostFunction.cs @@ -16,7 +16,9 @@ using System; using System.Reflection; +using System.Threading.Tasks; using Wacs.Core.Attributes; +using Wacs.Core.Runtime.Exceptions; using Wacs.Core.Types; namespace Wacs.Core.Runtime.Types @@ -36,6 +38,8 @@ public class HostFunction : IFunctionInstance private readonly MethodInfo _invoker; + private readonly bool _isAsync; + private ConversionHelper?[] _parameterConversions = null!; private ConversionHelper?[] _resultConversions = null!; @@ -45,15 +49,17 @@ public class HostFunction : IFunctionInstance /// @Spec 4.5.3.2. Host Functions /// Initializes a new instance of the class. /// + /// The bound export name /// The function type. /// The System.Type of the delegate must match type. /// The delegate representing the host function. - /// True if the specified function type had Store as the first type - public HostFunction((string module, string entity) id, FunctionType type, Type delType, Delegate hostFunction) + /// True if the function returns a System.Threading.Task + public HostFunction((string module, string entity) id, FunctionType type, Type delType, Delegate hostFunction, bool isAsync) { Type = type; _hostFunction = hostFunction; _invoker = delType.GetMethod("Invoke")!; + _isAsync = isAsync; (ModuleName, Name) = id; var invokerParams = _invoker.GetParameters(); @@ -89,6 +95,8 @@ public HostFunction((string module, string entity) id, FunctionType type, Type d public void SetName(string value) {} public string Id => $"{ModuleName}.{Name}"; + public bool IsAsync => _isAsync; + public bool IsExport { get => true; @@ -243,15 +251,61 @@ public void Invoke(object[] args, OpStack opStack) args[i] = _parameterConversions[i]?.Invoke(args[i]) ?? args[i]; } + if (IsAsync) + throw new WasmRuntimeException("Cannot synchronously execute Async Function"); try { var returnValue = _invoker.Invoke(_hostFunction, args); + int outArgs = Type.ResultType.Types.Length; + int j = 0; + if (_captureReturn) + { + outArgs -= 1; + if (_resultConversions[j] != null) + returnValue = _resultConversions[j]?.Invoke(returnValue) ?? returnValue; + opStack.PushValue(new Value(returnValue)); + ++j; + } + + int idx = args.Length - outArgs; + for (; idx < args.Length; ++idx, ++j) + { + var returnVal = args[idx]; + if (_resultConversions[j] != null) + returnVal = _resultConversions[j]?.Invoke(returnVal) ?? returnVal; + opStack.PushValue(new Value(returnVal)); + } + } + catch (TargetInvocationException ex) + { + throw ex.InnerException!; + } + } + + public async ValueTask InvokeAsync(object[] args, OpStack opStack) + { + for (int i = 0; i < _parameterConversions.Length; ++i) + { + args[i] = _parameterConversions[i]?.Invoke(args[i]) ?? args[i]; + } + + if (!IsAsync) + throw new WasmRuntimeException("Cannot asynchronously execute Synchronous Function"); + + try + { + var task = _invoker.Invoke(_hostFunction, args) as Task; + await task; int outArgs = Type.ResultType.Types.Length; int j = 0; if (_captureReturn) { outArgs -= 1; + + var resultProperty = task.GetType().GetProperty("Result"); + var returnValue = resultProperty?.GetValue(task) ?? null; + if (_resultConversions[j] != null) returnValue = _resultConversions[j]?.Invoke(returnValue) ?? returnValue; opStack.PushValue(new Value(returnValue)); diff --git a/Wacs.Core/Runtime/WasmRuntimeBinding.cs b/Wacs.Core/Runtime/WasmRuntimeBinding.cs index a978f9b..bbb27ed 100644 --- a/Wacs.Core/Runtime/WasmRuntimeBinding.cs +++ b/Wacs.Core/Runtime/WasmRuntimeBinding.cs @@ -19,6 +19,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using Wacs.Core.Runtime.Exceptions; using Wacs.Core.Runtime.Types; using Wacs.Core.Types; @@ -114,7 +115,6 @@ public FuncAddr GetExportedFunction((string module, string entity) id) private IAddress? GetBoundEntity((string module, string entity) id) => _entityBindings.GetValueOrDefault(id); - // [RequiresUnreferencedCode("Uses reflection to match parameters for binding")] public void BindHostFunction((string module, string entity) id, TDelegate func) where TDelegate : Delegate { @@ -131,11 +131,28 @@ public void BindHostFunction((string module, string entity) id, TDele .ToArray() ?? Array.Empty(); + var paramValTypes = new ResultType(paramTypes); + var returnTypeInfo = funcType.GetMethod("Invoke")?.ReturnType; + ValType returnType = ValType.Nil; + bool isAsync = false; + if (returnTypeInfo is not null) + { + if (returnTypeInfo.BaseType == typeof(Task)) + { + isAsync = true; + if (returnTypeInfo.IsGenericType) + { + returnType = returnTypeInfo.GenericTypeArguments[0].ToValType(); + } + } + else + { + returnType = returnTypeInfo.ToValType(); + } + } - var paramValTypes = new ResultType(paramTypes); var outValTypes = outTypes.Select(t => ValTypeUtilities.UnpackRef(t)).ToArray(); - var returnType = returnTypeInfo?.ToValType() ?? ValType.Nil; if (returnType != ValType.Nil) { @@ -159,7 +176,7 @@ public void BindHostFunction((string module, string entity) id, TDele Store.OpenTransaction(); var type = new FunctionType(paramValTypes, returnValType); - var funcAddr = AllocateHostFunc(Store, id, type, funcType, func); + var funcAddr = AllocateHostFunc(Store, id, type, funcType, func, isAsync); Store.CommitTransaction(); _entityBindings[id] = funcAddr; } diff --git a/Wacs.Core/Runtime/WasmRuntimeExecution.cs b/Wacs.Core/Runtime/WasmRuntimeExecution.cs index a004590..d04967e 100644 --- a/Wacs.Core/Runtime/WasmRuntimeExecution.cs +++ b/Wacs.Core/Runtime/WasmRuntimeExecution.cs @@ -46,9 +46,10 @@ public TDelegate CreateInvoker(FuncAddr funcAddr, InvokerOptions? opt var inner = CreateInvoker(funcAddr, options); var genericDelegate = Delegates.AnonymousFunctionFromType(funcType, args => { + Value[] results = null!; try { - Value[] results = funcType.ParameterTypes.Arity == 0 + results = funcType.ParameterTypes.Arity == 0 ? inner() : (Value[])GenericFuncsInvoke.Invoke(inner, args); if (funcType.ResultType.Types.Length == 1) @@ -57,7 +58,9 @@ public TDelegate CreateInvoker(FuncAddr funcAddr, InvokerOptions? opt } catch (TargetInvocationException exc) { //Propagate out any exceptions - throw exc.InnerException; + ExceptionDispatchInfo.Throw(exc.InnerException); + //This won't happen + return results; } }); @@ -99,7 +102,10 @@ Value[] GenericDelegate(params object[] args) } Context.ProcessTimer.Restart(); - Context.Invoke(funcAddr); + + var task = Context.Invoke(funcAddr); + task.Wait(); + Context.steps = 0; bool fastPath = options.UseFastPath(); try @@ -218,7 +224,12 @@ public async Task ProcessThreadAsync(long gasLimit) if (gasLimit <= 0) gasLimit = long.MaxValue; while (Context.Next() is { } inst) { - int work = await inst.ExecuteAsync(Context); + int work; + if (inst.IsAsync) + work = await inst.ExecuteAsync(Context); + else + work = inst.Execute(Context); + Context.steps += work; if (Context.steps >= gasLimit) throw new InsufficientGasException($"Invocation ran out of gas (limit:{gasLimit})."); @@ -241,7 +252,12 @@ public async Task ProcessThreadWithOptions(InvokerOptions options) if (options.CollectStats == StatsDetail.Instruction) { Context.InstructionTimer.Restart(); - work = await inst.ExecuteAsync(Context); + + if (inst.IsAsync) + work = await inst.ExecuteAsync(Context); + else + work = inst.Execute(Context); + Context.InstructionTimer.Stop(); Context.steps += work; @@ -253,7 +269,10 @@ public async Task ProcessThreadWithOptions(InvokerOptions options) else { Context.InstructionTimer.Start(); - work = await inst.ExecuteAsync(Context); + if (inst.IsAsync) + work = await inst.ExecuteAsync(Context); + else + work = inst.Execute(Context); Context.InstructionTimer.Stop(); Context.steps += work; } diff --git a/Wacs.Core/Runtime/WasmRuntimeInstantiation.cs b/Wacs.Core/Runtime/WasmRuntimeInstantiation.cs index 2269e7f..ac88559 100644 --- a/Wacs.Core/Runtime/WasmRuntimeInstantiation.cs +++ b/Wacs.Core/Runtime/WasmRuntimeInstantiation.cs @@ -412,9 +412,9 @@ private static FuncAddr AllocateWasmFunc(Store store, Module.Function func, Modu /// /// @Spec 4.5.3.2. Host Functions /// - private static FuncAddr AllocateHostFunc(Store store, (string module, string entity) id, FunctionType funcType, Type delType, Delegate hostFunc) + private static FuncAddr AllocateHostFunc(Store store, (string module, string entity) id, FunctionType funcType, Type delType, Delegate hostFunc, bool isAsync) { - return store.AllocateHostFunction(id, funcType, delType, hostFunc); + return store.AllocateHostFunction(id, funcType, delType, hostFunc, isAsync); } /// From 71e556263bb1f9bae3599430e8c21835d64c769b Mon Sep 17 00:00:00 2001 From: Kelvin Nishikawa Date: Wed, 27 Nov 2024 14:02:37 -0800 Subject: [PATCH 03/24] Adding test for Async boundary call --- Spec.Test/BindingTests.cs | 57 ++++++++++++++++----- Spec.Test/engine/binding.wasm | Bin 162 -> 211 bytes Spec.Test/engine/binding.wat | 94 ++++++++++++++++++---------------- 3 files changed, 94 insertions(+), 57 deletions(-) diff --git a/Spec.Test/BindingTests.cs b/Spec.Test/BindingTests.cs index efb92fc..e8d17d4 100644 --- a/Spec.Test/BindingTests.cs +++ b/Spec.Test/BindingTests.cs @@ -16,6 +16,7 @@ using System; using System.IO; +using System.Threading.Tasks; using Wacs.Core; using Wacs.Core.Runtime; using Xunit; @@ -24,11 +25,27 @@ namespace Spec.Test { public class BindingTests { + delegate int HostInOut(int a, out int b); + static int BoundHost(int a, out int b) + { + b = a; + int c = a * 2; + return c; + } + delegate Task HostAsyncInRet(int a); + static async Task BoundAsyncHost(int a) + { + await Task.Delay(1000); // Simulate work + return a*2; + } + + [Fact] public void BindStackBinder() { var runtime = new WasmRuntime(); - runtime.BindHostFunction(("env","bound_host"), BoundHost); + runtime.BindHostFunction(("env","bound_host"), BoundHost); + runtime.BindHostFunction(("env","bound_async_host"), BoundAsyncHost); using var fileStream = new FileStream("../../../engine/binding.wasm", FileMode.Open); var module = BinaryModuleParser.ParseWasm(fileStream); var moduleInst = runtime.InstantiateModule(module); @@ -49,7 +66,8 @@ public void BindStackBinder() public void BindParamAndResultI32() { var runtime = new WasmRuntime(); - runtime.BindHostFunction(("env","bound_host"), BoundHost); + runtime.BindHostFunction(("env","bound_host"), BoundHost); + runtime.BindHostFunction(("env","bound_async_host"), BoundAsyncHost); using var fileStream = new FileStream("../../../engine/binding.wasm", FileMode.Open); var module = BinaryModuleParser.ParseWasm(fileStream); var moduleInst = runtime.InstantiateModule(module); @@ -67,7 +85,8 @@ public void BindParamAndResultI32() public void BindParamAndResultF32() { var runtime = new WasmRuntime(); - runtime.BindHostFunction(("env","bound_host"), BoundHost); + runtime.BindHostFunction(("env","bound_host"), BoundHost); + runtime.BindHostFunction(("env","bound_async_host"), BoundAsyncHost); using var fileStream = new FileStream("../../../engine/binding.wasm", FileMode.Open); var module = BinaryModuleParser.ParseWasm(fileStream); var moduleInst = runtime.InstantiateModule(module); @@ -80,19 +99,13 @@ public void BindParamAndResultF32() Assert.Equal(2f * 2f, invoker(2f)); Assert.Equal(3f * 2f, invoker(3f)); } - - static int BoundHost(int a, out int b) - { - b = a; - int c = a * 2; - return c; - } - + [Fact] public void BindHostFunction() { var runtime = new WasmRuntime(); - runtime.BindHostFunction(("env","bound_host"), BoundHost); + runtime.BindHostFunction(("env","bound_host"), BoundHost); + runtime.BindHostFunction(("env","bound_async_host"), BoundAsyncHost); using var fileStream = new FileStream("../../../engine/binding.wasm", FileMode.Open); var module = BinaryModuleParser.ParseWasm(fileStream); @@ -105,6 +118,24 @@ public void BindHostFunction() Assert.Equal(10 + 20, invoker(10)); } - delegate int hostInOut(int a, out int b); + + [Fact] + public void BindHostAsyncFunction() + { + var runtime = new WasmRuntime(); + runtime.BindHostFunction(("env","bound_host"), BoundHost); + runtime.BindHostFunction(("env","bound_async_host"), BoundAsyncHost); + + using var fileStream = new FileStream("../../../engine/binding.wasm", FileMode.Open); + var module = BinaryModuleParser.ParseWasm(fileStream); + var moduleInst = runtime.InstantiateModule(module); + runtime.RegisterModule("binding", moduleInst); + + var fa = runtime.GetExportedFunction(("binding", "call_async_host")); + var invoker = runtime.CreateInvoker>(fa); + + Assert.Equal(10*2, invoker(10)); + } + } } \ No newline at end of file diff --git a/Spec.Test/engine/binding.wasm b/Spec.Test/engine/binding.wasm index cd8289e63a33e2f54d7dabd3d07dcafbfab9119e..6331096907c3a16639d6e5f044cf426d2352a79e 100644 GIT binary patch delta 150 zcmZ3)c$sm6dc7tSb8220S5kgyUP^pMesKu{Ge|%HB9K^InU@R^W@Kh#Wn^MxU}QIB zWzIA~$%#2R5G5?kCKV*uB_|~3Ji`+Iow=8+L?iY!C?wF VNQgy%A&YxrsuDXJkR!mz4FI)BA@u+N delta 55 zcmcc2xQKCry0Q=>b8220S5kgyUP^pMesKu{Gczj-10xe7yF3fCNredm Date: Wed, 27 Nov 2024 14:03:28 -0800 Subject: [PATCH 04/24] Feature.Detect adding a hardcoded list of supported features --- Feature.Detect/DetectFeatures.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Feature.Detect/DetectFeatures.cs b/Feature.Detect/DetectFeatures.cs index c6fcae5..e5a542b 100644 --- a/Feature.Detect/DetectFeatures.cs +++ b/Feature.Detect/DetectFeatures.cs @@ -1,5 +1,7 @@ ο»Ώusing System; +using System.Collections.Generic; using System.IO; +using System.Linq; using Spec.Test; using Wacs.Core; using Wacs.Core.Runtime; @@ -40,7 +42,13 @@ public void Detect(FeatureJson.FeatureJson file) } else { - Assert.Fail($"{file.Name} not supported."); + var supportedJsParadigms = new List + { + "jspi" + }; + + var supported = file.Features?.All(feature => supportedJsParadigms.Contains(feature)) ?? false; + Assert.True(supported, $"{file.Name} not supported."); } } } From 0b98c33fc805917cdc31321d0b6aaa7aa1a15715 Mon Sep 17 00:00:00 2001 From: Kelvin Nishikawa Date: Wed, 27 Nov 2024 16:21:01 -0800 Subject: [PATCH 05/24] Tweaking Feature.Detect to show conceptually supported JS features --- Feature.Detect/trx-to-markdown.js | 13 +++++++------ README.md | 4 ++-- features.md | 4 ++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Feature.Detect/trx-to-markdown.js b/Feature.Detect/trx-to-markdown.js index 04ad6aa..3320643 100644 --- a/Feature.Detect/trx-to-markdown.js +++ b/Feature.Detect/trx-to-markdown.js @@ -60,11 +60,11 @@ class TrxToMarkdown { testDef['Phase'] = 0; } + testDef['outcome'] = test.outcome; if (testDef['Source'].includes('in WebAssembly')) - testDef['outcome'] = 'Javascript'; - else - testDef['outcome'] = test.outcome; - + testDef['outcome'] = 'JS.' + testDef['outcome']; + else if (testDef.Id == 'big-int') + testDef['outcome'] = 'JS.' + testDef['outcome']; tests.push(testDef); }); @@ -96,9 +96,10 @@ class TrxToMarkdown { let status = '❔'; switch (testDef['outcome']) { - case 'Passed': status = 'βœ…'; break; - case 'Javascript': status = '🌐'; break; case 'Failed': status = '❌'; break; + case 'Passed': status = 'βœ…'; break; + case 'JS.Failed': status = '🌐'; break; + case 'JS.Passed': status = '✳️'; break; } markdown.push(`|[${testDef['Name']}](${testDef['Proposal']})|${testDef['Features']}|${status}|`); }); diff --git a/README.md b/README.md index 3675941..5ef635f 100644 --- a/README.md +++ b/README.md @@ -263,7 +263,7 @@ Harnessed results from [wasm-feature-detect](https://github.com/GoogleChromeLabs |Proposal |Features| | |------|-------|----| |Phase 5| -|[JavaScript BigInt to WebAssembly i64 integration](https://github.com/WebAssembly/JS-BigInt-integration)||βœ…| +|[JavaScript BigInt to WebAssembly i64 integration](https://github.com/WebAssembly/JS-BigInt-integration)||✳️| |[Bulk memory operations](https://github.com/webassembly/bulk-memory-operations)||βœ…| |[Extended Constant Expressions](https://github.com/WebAssembly/extended-const)|extended_const|βœ…| |[Garbage collection](https://github.com/WebAssembly/gc)|gc|❌| @@ -283,7 +283,7 @@ Harnessed results from [wasm-feature-detect](https://github.com/GoogleChromeLabs |[Memory64](https://github.com/WebAssembly/memory64)|memory64|❌| |[Threads](https://github.com/webassembly/threads)|threads|❌| |Phase 3| -|[JS Promise Integration](https://github.com/WebAssembly/js-promise-integration)|jspi|🌐| +|[JS Promise Integration](https://github.com/WebAssembly/js-promise-integration)|jspi|✳️| |[Type Reflection for WebAssembly JavaScript API](https://github.com/WebAssembly/js-types)|type-reflection|🌐| || |[Legacy Exception Handling]( https://github.com/WebAssembly/exception-handling)|exceptions|❌| diff --git a/features.md b/features.md index 6b4177b..4f2279e 100644 --- a/features.md +++ b/features.md @@ -1,7 +1,7 @@ |Proposal |Features| | |------|-------|----| |Phase 5| -|[JavaScript BigInt to WebAssembly i64 integration](https://github.com/WebAssembly/JS-BigInt-integration)||βœ…| +|[JavaScript BigInt to WebAssembly i64 integration](https://github.com/WebAssembly/JS-BigInt-integration)||✳️| |[Bulk memory operations](https://github.com/webassembly/bulk-memory-operations)||βœ…| |[Extended Constant Expressions](https://github.com/WebAssembly/extended-const)|extended_const|βœ…| |[Garbage collection](https://github.com/WebAssembly/gc)|gc|❌| @@ -21,7 +21,7 @@ |[Memory64](https://github.com/WebAssembly/memory64)|memory64|❌| |[Threads](https://github.com/webassembly/threads)|threads|❌| |Phase 3| -|[JS Promise Integration](https://github.com/WebAssembly/js-promise-integration)|jspi|🌐| +|[JS Promise Integration](https://github.com/WebAssembly/js-promise-integration)|jspi|✳️| |[Type Reflection for WebAssembly JavaScript API](https://github.com/WebAssembly/js-types)|type-reflection|🌐| || |[Legacy Exception Handling]( https://github.com/WebAssembly/exception-handling)|exceptions|❌| From 6efff7cf8387d690e2e843e0e753a285a4327319 Mon Sep 17 00:00:00 2001 From: Kelvin Nishikawa Date: Wed, 27 Nov 2024 17:20:52 -0800 Subject: [PATCH 06/24] Optimizing Transpiled value unwrap, fields for properties --- .../Transpiler/AggregateTypeCast.cs | 79 ++++++++++++++++--- Wacs.Core/Modules/InstructionSequence.cs | 4 +- Wacs.Core/Runtime/ExecContext.cs | 4 +- .../Runtime/Transpiler/FunctionTranspiler.cs | 46 +++++------ 4 files changed, 94 insertions(+), 39 deletions(-) diff --git a/Wacs.Core/Instructions/Transpiler/AggregateTypeCast.cs b/Wacs.Core/Instructions/Transpiler/AggregateTypeCast.cs index bfb10d8..2c8b403 100644 --- a/Wacs.Core/Instructions/Transpiler/AggregateTypeCast.cs +++ b/Wacs.Core/Instructions/Transpiler/AggregateTypeCast.cs @@ -74,22 +74,77 @@ public WrapValue(ITypedValueProducer inA) public Func GetFunc => _func; } - - public class UnwrapValue : ITypedValueProducer - where T : struct + + public abstract class UnwrapValue : ITypedValueProducer { - private readonly ITypedValueProducer _inA; - public UnwrapValue(ITypedValueProducer inA) + protected ITypedValueProducer InA; + protected UnwrapValue(ITypedValueProducer inA) { - _inA = inA; - var func = _inA.GetFunc; - _func = context => (T)func(context).CastScalar(); + InA = inA; } - public int CalculateSize() => _inA.CalculateSize(); - - private Func _func; - public Func GetFunc => _func; + public int CalculateSize() => InA.CalculateSize(); + public abstract Func GetFunc { get; } + } + + public class UnwrapValueI32 : UnwrapValue + { + public UnwrapValueI32(ITypedValueProducer inA) : base(inA) + { + var func = InA.GetFunc; + GetFunc = context => func(context).Int32; + } + public override Func GetFunc { get; } + } + + public class UnwrapValueU32 : UnwrapValue + { + public UnwrapValueU32(ITypedValueProducer inA) : base(inA) + { + var func = InA.GetFunc; + GetFunc = context => func(context).UInt32; + } + public override Func GetFunc { get; } + } + + public class UnwrapValueF32 : UnwrapValue + { + public UnwrapValueF32(ITypedValueProducer inA) : base(inA) + { + var func = InA.GetFunc; + GetFunc = context => func(context).Float32; + } + public override Func GetFunc { get; } + } + + public class UnwrapValueI64 : UnwrapValue + { + public UnwrapValueI64(ITypedValueProducer inA) : base(inA) + { + var func = InA.GetFunc; + GetFunc = context => func(context).Int64; + } + public override Func GetFunc { get; } + } + + public class UnwrapValueU64 : UnwrapValue + { + public UnwrapValueU64(ITypedValueProducer inA) : base(inA) + { + var func = InA.GetFunc; + GetFunc = context => func(context).UInt64; + } + public override Func GetFunc { get; } + } + + public class UnwrapValueF64 : UnwrapValue + { + public UnwrapValueF64(ITypedValueProducer inA) : base(inA) + { + var func = InA.GetFunc; + GetFunc = context => func(context).Float64; + } + public override Func GetFunc { get; } } public class CastToI32 : ITypedValueProducer diff --git a/Wacs.Core/Modules/InstructionSequence.cs b/Wacs.Core/Modules/InstructionSequence.cs index dacb984..f20215f 100644 --- a/Wacs.Core/Modules/InstructionSequence.cs +++ b/Wacs.Core/Modules/InstructionSequence.cs @@ -33,13 +33,13 @@ public class InstructionSequence : IEnumerable public static readonly InstructionSequence Empty = new(new List()); //public for direct array access on critical path - public readonly IInstruction[] _instructions; + public readonly InstructionBase[] _instructions; public readonly int Count; public InstructionSequence(IList list) { - _instructions = list.ToArray(); + _instructions = list.Cast().ToArray(); Count = _instructions.Length; } diff --git a/Wacs.Core/Runtime/ExecContext.cs b/Wacs.Core/Runtime/ExecContext.cs index 34faa89..928be0b 100644 --- a/Wacs.Core/Runtime/ExecContext.cs +++ b/Wacs.Core/Runtime/ExecContext.cs @@ -85,8 +85,8 @@ public ExecContext(Store store, RuntimeAttributes? attributes = default) OpStack = new(Attributes.MaxOpStack); } - public Stopwatch ProcessTimer { get; set; } = new(); - public Stopwatch InstructionTimer { get; set; } = new(); + public readonly Stopwatch ProcessTimer = new(); + public readonly Stopwatch InstructionTimer = new(); public IInstructionFactory InstructionFactory => Attributes.InstructionFactory; diff --git a/Wacs.Core/Runtime/Transpiler/FunctionTranspiler.cs b/Wacs.Core/Runtime/Transpiler/FunctionTranspiler.cs index 7558074..4d58db4 100644 --- a/Wacs.Core/Runtime/Transpiler/FunctionTranspiler.cs +++ b/Wacs.Core/Runtime/Transpiler/FunctionTranspiler.cs @@ -183,7 +183,7 @@ private static IInstruction BindU32(IOptimizationTarget inst, Stack p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueU32(p), ITypedValueProducer p => new CastToU32(p), ITypedValueProducer p => p, _ => null @@ -210,7 +210,7 @@ private static IInstruction BindI32(IInstruction inst, Stack stack if (stack.Count < 1) return inst; var top = stack.Pop(); var intProducer = top switch { - ITypedValueProducer p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueI32(p), ITypedValueProducer p => new CastToI32(p), ITypedValueProducer p => p, _ => null @@ -235,14 +235,14 @@ private static IInstruction BindI32I32(IInstruction inst, Stack st var i1 = stack.Pop(); var i2Producer = i2 switch { - ITypedValueProducer p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueI32(p), ITypedValueProducer p => new CastToI32(p), ITypedValueProducer p => p, _ => null }; var i1Producer = i1 switch { - ITypedValueProducer p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueI32(p), ITypedValueProducer p => new CastToI32(p), ITypedValueProducer p => p, _ => null @@ -268,14 +268,14 @@ private static IInstruction BindU32U32(IInstruction inst, Stack st var i1 = stack.Pop(); var i2Producer = i2 switch { - ITypedValueProducer p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueU32(p), ITypedValueProducer p => new CastToU32(p), ITypedValueProducer p => p, _ => null }; var i1Producer = i1 switch { - ITypedValueProducer p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueU32(p), ITypedValueProducer p => new CastToU32(p), ITypedValueProducer p => p, _ => null @@ -301,14 +301,14 @@ private static IInstruction BindU32I32(IInstruction inst, Stack st var i1 = stack.Pop(); var i2Producer = i2 switch { - ITypedValueProducer p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueI32(p), ITypedValueProducer p => new CastToI32(p), ITypedValueProducer p => p, _ => null }; var i1Producer = i1 switch { - ITypedValueProducer p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueU32(p), ITypedValueProducer p => new CastToU32(p), ITypedValueProducer p => p, _ => null @@ -331,7 +331,7 @@ private static IInstruction BindU64(IOptimizationTarget inst, Stack p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueU64(p), ITypedValueProducer p => new CastToU64(p), ITypedValueProducer p => p, _ => null @@ -356,7 +356,7 @@ private static IInstruction BindI64(IInstruction inst, Stack stack if (stack.Count < 1) return inst; var top = stack.Pop(); var longProducer = top switch { - ITypedValueProducer p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueI64(p), ITypedValueProducer p => new CastToI64(p), ITypedValueProducer p => p, _ => null @@ -383,14 +383,14 @@ private static IInstruction BindI64I64(IInstruction inst, Stack st var i1 = stack.Pop(); var i2Producer = i2 switch { - ITypedValueProducer p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueI64(p), ITypedValueProducer p => new CastToI64(p), ITypedValueProducer p => p, _ => null }; var i1Producer = i1 switch { - ITypedValueProducer p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueI64(p), ITypedValueProducer p => new CastToI64(p), ITypedValueProducer p => p, _ => null @@ -418,14 +418,14 @@ private static IInstruction BindU64U64(IInstruction inst, Stack st var i1 = stack.Pop(); var i2Producer = i2 switch { - ITypedValueProducer p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueU64(p), ITypedValueProducer p => new CastToU64(p), ITypedValueProducer p => p, _ => null }; var i1Producer = i1 switch { - ITypedValueProducer p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueU64(p), ITypedValueProducer p => new CastToU64(p), ITypedValueProducer p => p, _ => null @@ -453,14 +453,14 @@ private static IInstruction BindU64I64(IInstruction inst, Stack st var i1 = stack.Pop(); var i2Producer = i2 switch { - ITypedValueProducer p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueI64(p), ITypedValueProducer p => new CastToI64(p), ITypedValueProducer p => p, _ => null }; var i1Producer = i1 switch { - ITypedValueProducer p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueU64(p), ITypedValueProducer p => new CastToU64(p), ITypedValueProducer p => p, _ => null @@ -499,7 +499,7 @@ private static IInstruction BindU32Value(IInstruction inst, Stack }; var i1Producer = i1 switch { - ITypedValueProducer p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueU32(p), ITypedValueProducer p => new CastToU32(p), ITypedValueProducer p => p, _ => null @@ -531,7 +531,7 @@ private static IInstruction BindF32(IOptimizationTarget inst, Stack p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueF32(p), ITypedValueProducer p => p, _ => null }; @@ -554,13 +554,13 @@ private static IInstruction BindF32F32(IInstruction inst, Stack st var i1 = stack.Pop(); var i2Producer = i2 switch { - ITypedValueProducer p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueF32(p), ITypedValueProducer p => p, _ => null }; var i1Producer = i1 switch { - ITypedValueProducer p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueF32(p), ITypedValueProducer p => p, _ => null }; @@ -585,7 +585,7 @@ private static IInstruction BindF64(IOptimizationTarget inst, Stack p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueF64(p), ITypedValueProducer p => p, _ => null }; @@ -608,13 +608,13 @@ private static IInstruction BindF64F64(IInstruction inst, Stack st var i1 = stack.Pop(); var i2Producer = i2 switch { - ITypedValueProducer p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueF64(p), ITypedValueProducer p => p, _ => null }; var i1Producer = i1 switch { - ITypedValueProducer p => new UnwrapValue(p), + ITypedValueProducer p => new UnwrapValueF64(p), ITypedValueProducer p => p, _ => null }; From 4f67628c463bff83f6f7b87bc644eb4f12252cd2 Mon Sep 17 00:00:00 2001 From: Kelvin Nishikawa Date: Wed, 27 Nov 2024 17:21:38 -0800 Subject: [PATCH 07/24] Adding IsAoTCompatible flag to projects --- Wacs.Core/Wacs.Core.csproj | 1 + Wacs.WASIp1/Wacs.WASIp1.csproj | 1 + 2 files changed, 2 insertions(+) diff --git a/Wacs.Core/Wacs.Core.csproj b/Wacs.Core/Wacs.Core.csproj index 9d3f324..65ee31f 100644 --- a/Wacs.Core/Wacs.Core.csproj +++ b/Wacs.Core/Wacs.Core.csproj @@ -22,6 +22,7 @@ true git netstandard2.1;net8.0 + true diff --git a/Wacs.WASIp1/Wacs.WASIp1.csproj b/Wacs.WASIp1/Wacs.WASIp1.csproj index 9515136..852e87b 100644 --- a/Wacs.WASIp1/Wacs.WASIp1.csproj +++ b/Wacs.WASIp1/Wacs.WASIp1.csproj @@ -17,6 +17,7 @@ 0.9.3 git netstandard2.1;net8.0 + true From 5e7d0e528d8b523921b8e79b1c5551e7a86a1cf7 Mon Sep 17 00:00:00 2001 From: Kelvin Nishikawa Date: Wed, 27 Nov 2024 18:05:20 -0800 Subject: [PATCH 08/24] Optimization properties and fields for direct access --- Wacs.Core/Runtime/ExecContext.cs | 21 +++++++--- Wacs.Core/Runtime/WasmRuntimeExecution.cs | 42 +++++++++---------- .../Utilities/InstructionLoggingExtension.cs | 26 ------------ 3 files changed, 36 insertions(+), 53 deletions(-) delete mode 100644 Wacs.Core/Utilities/InstructionLoggingExtension.cs diff --git a/Wacs.Core/Runtime/ExecContext.cs b/Wacs.Core/Runtime/ExecContext.cs index 928be0b..aa6fb12 100644 --- a/Wacs.Core/Runtime/ExecContext.cs +++ b/Wacs.Core/Runtime/ExecContext.cs @@ -57,6 +57,8 @@ public class ExecContext private Stack _asideVals = new(); private InstructionSequence _currentSequence; + private InstructionBase[] _sequenceInstructions; + private int _sequenceCount; private int _sequenceIndex; public InstructionPointer GetPointer() => new(_currentSequence, _sequenceIndex); @@ -80,6 +82,8 @@ public ExecContext(Store store, RuntimeAttributes? attributes = default) _hostReturnSequence = InstructionSequence.Empty; _currentSequence = _hostReturnSequence; + _sequenceCount = _currentSequence.Count; + _sequenceInstructions = _currentSequence._instructions; _sequenceIndex = -1; OpStack = new(Attributes.MaxOpStack); @@ -170,6 +174,8 @@ public void FlushCallStack() Frame = NullFrame; _currentSequence = _hostReturnSequence; + _sequenceCount = _currentSequence.Count; + _sequenceInstructions = _currentSequence._instructions; _sequenceIndex = -1; } @@ -177,6 +183,8 @@ public void FlushCallStack() private void EnterSequence(InstructionSequence seq) { _currentSequence = seq; + _sequenceCount = _currentSequence.Count; + _sequenceInstructions = _currentSequence._instructions; _sequenceIndex = -1; } @@ -184,6 +192,8 @@ private void EnterSequence(InstructionSequence seq) public void ResumeSequence(InstructionPointer pointer) { _currentSequence = pointer.Sequence; + _sequenceCount = _currentSequence.Count; + _sequenceInstructions = _currentSequence._instructions; _sequenceIndex = pointer.Index; } @@ -191,7 +201,7 @@ public void ResumeSequence(InstructionPointer pointer) public void FastForwardSequence() { //Go to penultimate instruction since we pre-increment on pointer advance. - _sequenceIndex = _currentSequence.Count - 2; + _sequenceIndex = _sequenceCount - 2; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -345,11 +355,10 @@ private async ValueTask InvokeAsync(HostFunction hostFunc) public InstructionBase? Next() { //Advance to the next instruction first. - if (++_sequenceIndex >= _currentSequence.Count) - return null; - - //Critical path, using direct array access - return _currentSequence._instructions[_sequenceIndex] as InstructionBase; + return (++_sequenceIndex < _sequenceCount) + //Critical path, using direct array access + ? _sequenceInstructions[_sequenceIndex] + : null; } public List<(string, int)> ComputePointerPath() diff --git a/Wacs.Core/Runtime/WasmRuntimeExecution.cs b/Wacs.Core/Runtime/WasmRuntimeExecution.cs index d04967e..2709e93 100644 --- a/Wacs.Core/Runtime/WasmRuntimeExecution.cs +++ b/Wacs.Core/Runtime/WasmRuntimeExecution.cs @@ -277,7 +277,7 @@ public async Task ProcessThreadWithOptions(InvokerOptions options) Context.steps += work; } - if (options.LogInstructionExecution.Has(InstructionLogging.Computes)) + if (((int)options.LogInstructionExecution & (int)InstructionLogging.Computes) != 0) { LogPostInstruction(options, inst); } @@ -308,30 +308,30 @@ private void LogPreInstruction(InvokerOptions options, IInstruction inst) case var _ when IInstruction.IsVar(inst): break; case var _ when IInstruction.IsLoad(inst): break; - case OpCode.Call when options.LogInstructionExecution.Has(InstructionLogging.Binds) && IInstruction.IsBound(Context, inst): - case OpCode.CallIndirect when options.LogInstructionExecution.Has(InstructionLogging.Binds) && IInstruction.IsBound(Context, inst): - // case OpCode.CallRef when options.LogInstructionExecution.Has(InstructionLogging.Binds) && IInstruction.IsBound(Context, inst): + case OpCode.Call when ((int)options.LogInstructionExecution&(int)InstructionLogging.Binds)!=0 && IInstruction.IsBound(Context, inst): + case OpCode.CallIndirect when ((int)options.LogInstructionExecution&(int)InstructionLogging.Binds)!=0 && IInstruction.IsBound(Context, inst): + // case OpCode.CallRef when options.LogInstructionExecution&(int)InstructionLogging.Binds) && IInstruction.IsBound(Context, inst): - case OpCode.Call when options.LogInstructionExecution.Has(InstructionLogging.Calls): - case OpCode.CallIndirect when options.LogInstructionExecution.Has(InstructionLogging.Calls): - // case OpCode.CallRef when options.LogInstructionExecution.Has(InstructionLogging.Calls): - case OpCode.Return when options.LogInstructionExecution.Has(InstructionLogging.Calls): - case OpCode.ReturnCallIndirect when options.LogInstructionExecution.Has(InstructionLogging.Calls): - case OpCode.ReturnCall when options.LogInstructionExecution.Has(InstructionLogging.Calls): - case OpCode.End when options.LogInstructionExecution.Has(InstructionLogging.Calls) && Context.GetEndFor() == OpCode.Func: + case OpCode.Call when ((int)options.LogInstructionExecution&(int)InstructionLogging.Calls)!=0: + case OpCode.CallIndirect when ((int)options.LogInstructionExecution&(int)InstructionLogging.Calls)!=0: + // case OpCode.CallRef when options.LogInstructionExecution&(int)InstructionLogging.Calls): + case OpCode.Return when ((int)options.LogInstructionExecution&(int)InstructionLogging.Calls)!=0: + case OpCode.ReturnCallIndirect when ((int)options.LogInstructionExecution&(int)InstructionLogging.Calls)!=0: + case OpCode.ReturnCall when ((int)options.LogInstructionExecution&(int)InstructionLogging.Calls)!=0: + case OpCode.End when ((int)options.LogInstructionExecution&(int)InstructionLogging.Calls)!=0 && Context.GetEndFor() == OpCode.Func: - case OpCode.Block when options.LogInstructionExecution.Has(InstructionLogging.Blocks): - case OpCode.Loop when options.LogInstructionExecution.Has(InstructionLogging.Blocks): - case OpCode.If when options.LogInstructionExecution.Has(InstructionLogging.Blocks): - case OpCode.Else when options.LogInstructionExecution.Has(InstructionLogging.Blocks): - case OpCode.End when options.LogInstructionExecution.Has(InstructionLogging.Blocks) && Context.GetEndFor() == OpCode.Block: + case OpCode.Block when ((int)options.LogInstructionExecution&(int)InstructionLogging.Blocks)!=0: + case OpCode.Loop when ((int)options.LogInstructionExecution&(int)InstructionLogging.Blocks)!=0: + case OpCode.If when ((int)options.LogInstructionExecution&(int)InstructionLogging.Blocks)!=0: + case OpCode.Else when ((int)options.LogInstructionExecution&(int)InstructionLogging.Blocks)!=0: + case OpCode.End when ((int)options.LogInstructionExecution&(int)InstructionLogging.Blocks)!=0 && Context.GetEndFor() == OpCode.Block: - case OpCode.Br when options.LogInstructionExecution.Has(InstructionLogging.Branches): - case OpCode.BrIf when options.LogInstructionExecution.Has(InstructionLogging.Branches): - case OpCode.BrTable when options.LogInstructionExecution.Has(InstructionLogging.Branches): + case OpCode.Br when ((int)options.LogInstructionExecution&(int)InstructionLogging.Branches)!=0: + case OpCode.BrIf when ((int)options.LogInstructionExecution&(int)InstructionLogging.Branches)!=0: + case OpCode.BrTable when ((int)options.LogInstructionExecution&(int)InstructionLogging.Branches)!=0: - case var _ when IInstruction.IsBranch(lastInstruction) && options.LogInstructionExecution.Has(InstructionLogging.Branches): - case var _ when options.LogInstructionExecution.Has(InstructionLogging.Computes): + case var _ when IInstruction.IsBranch(lastInstruction) && ((int)options.LogInstructionExecution&(int)InstructionLogging.Branches)!=0: + case var _ when ((int)options.LogInstructionExecution&(int)InstructionLogging.Computes)!=0: string location = ""; if (options.CalculateLineNumbers) { diff --git a/Wacs.Core/Utilities/InstructionLoggingExtension.cs b/Wacs.Core/Utilities/InstructionLoggingExtension.cs deleted file mode 100644 index d90050f..0000000 --- a/Wacs.Core/Utilities/InstructionLoggingExtension.cs +++ /dev/null @@ -1,26 +0,0 @@ -// /* -// * Copyright 2024 Kelvin Nishikawa -// * -// * Licensed under the Apache License, Version 2.0 (the "License"); -// * you may not use this file except in compliance with the License. -// * You may obtain a copy of the License at -// * -// * http://www.apache.org/licenses/LICENSE-2.0 -// * -// * Unless required by applicable law or agreed to in writing, software -// * distributed under the License is distributed on an "AS IS" BASIS, -// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// * See the License for the specific language governing permissions and -// * limitations under the License. -// */ - -using Wacs.Core.Runtime; - -namespace Wacs.Core.Utilities -{ - public static class InstructionLoggingExtension - { - public static bool Has(this InstructionLogging logging, InstructionLogging flags) => - ((int)logging & (int)flags) != 0; - } -} \ No newline at end of file From a0618f614e16a3c552e99454f3c66c9b6972e37a Mon Sep 17 00:00:00 2001 From: Kelvin Nishikawa Date: Wed, 27 Nov 2024 18:34:45 -0800 Subject: [PATCH 09/24] Optimizing Transpiled value wrap, reduce boxing --- .../Transpiler/AggregateTypeCast.cs | 113 ++++++++++++------ .../Runtime/Transpiler/FunctionTranspiler.cs | 18 +-- Wacs.Core/Runtime/Value.cs | 8 ++ Wacs.Core/Types/ValType.cs | 5 + 4 files changed, 101 insertions(+), 43 deletions(-) diff --git a/Wacs.Core/Instructions/Transpiler/AggregateTypeCast.cs b/Wacs.Core/Instructions/Transpiler/AggregateTypeCast.cs index 2c8b403..afb7f42 100644 --- a/Wacs.Core/Instructions/Transpiler/AggregateTypeCast.cs +++ b/Wacs.Core/Instructions/Transpiler/AggregateTypeCast.cs @@ -25,56 +25,101 @@ public class InstAggregateValue : InstAggregate1_0 where T : struct { public InstAggregateValue(ITypedValueProducer inA, ValType type, INodeConsumer consumer) - : base(new Wrapper(inA, type), consumer) {} + : base(GetWrapper(inA, type), consumer) {} - class Wrapper : ITypedValueProducer - { - private readonly ITypedValueProducer _inA; - - public Wrapper(ITypedValueProducer inA, ValType type) + private static ITypedValueProducer GetWrapper(ITypedValueProducer inA, ValType type) => + type switch { - _inA = inA; - if (typeof(T) == typeof(Value)) - { - _func = ((ITypedValueProducer)_inA).GetFunc; - } - else - { - var func = _inA.GetFunc; - _func = context => new Value(func(context)); - } - } - public int CalculateSize() => _inA.CalculateSize(); - - private Func _func; - public Func GetFunc => _func; - } + ValType.Nil => (ITypedValueProducer)inA, + ValType.I32 => new WrapValueI32((ITypedValueProducer)inA), + ValType.I64 => new WrapValueI64((ITypedValueProducer)inA), + ValType.F32 => new WrapValueF32((ITypedValueProducer)inA), + ValType.F64 => new WrapValueF64((ITypedValueProducer)inA), + ValType.U32 => new WrapValueU32((ITypedValueProducer)inA), + ValType.U64 => new WrapValueU64((ITypedValueProducer)inA), + ValType.V128 => new WrapValueV128((ITypedValueProducer)inA), + _ => throw new ArgumentException($"Unsupported ValType: {type}") + }; } - public class WrapValue : ITypedValueProducer + public abstract class WrapValue : ITypedValueProducer where T : struct { private readonly ITypedValueProducer _inA; - public WrapValue(ITypedValueProducer inA) + protected WrapValue(ITypedValueProducer inA) { _inA = inA; - if (typeof(T) == typeof(Value)) - { - _func = ((ITypedValueProducer)_inA).GetFunc; - } - else - { - var func = _inA.GetFunc; - _func = context => new Value(func(context)); - } } public int CalculateSize() => _inA.CalculateSize(); - private Func _func; + protected Func _func = null!; public Func GetFunc => _func; } + public class WrapValueI32 : WrapValue + { + public WrapValueI32(ITypedValueProducer inA) : base(inA) + { + var func = inA.GetFunc; + _func = context => new Value(func(context)); + } + } + + public class WrapValueU32 : WrapValue + { + public WrapValueU32(ITypedValueProducer inA) : base(inA) + { + var func = inA.GetFunc; + _func = context => new Value(func(context)); + } + } + + public class WrapValueI64 : WrapValue + { + public WrapValueI64(ITypedValueProducer inA) : base(inA) + { + var func = inA.GetFunc; + _func = context => new Value(func(context)); + } + } + + public class WrapValueU64 : WrapValue + { + public WrapValueU64(ITypedValueProducer inA) : base(inA) + { + var func = inA.GetFunc; + _func = context => new Value(func(context)); + } + } + + public class WrapValueF32 : WrapValue + { + public WrapValueF32(ITypedValueProducer inA) : base(inA) + { + var func = inA.GetFunc; + _func = context => new Value(func(context)); + } + } + + public class WrapValueF64 : WrapValue + { + public WrapValueF64(ITypedValueProducer inA) : base(inA) + { + var func = inA.GetFunc; + _func = context => new Value(func(context)); + } + } + + public class WrapValueV128 : WrapValue + { + public WrapValueV128(ITypedValueProducer inA) : base(inA) + { + var func = inA.GetFunc; + _func = context => new Value(func(context)); + } + } + public abstract class UnwrapValue : ITypedValueProducer { protected ITypedValueProducer InA; diff --git a/Wacs.Core/Runtime/Transpiler/FunctionTranspiler.cs b/Wacs.Core/Runtime/Transpiler/FunctionTranspiler.cs index 4d58db4..a63864d 100644 --- a/Wacs.Core/Runtime/Transpiler/FunctionTranspiler.cs +++ b/Wacs.Core/Runtime/Transpiler/FunctionTranspiler.cs @@ -163,11 +163,11 @@ private static IInstruction BindAnyValue(IOptimizationTarget inst, Stack intProducer: return new InstAggregateValue(intProducer, ValType.I32, nodeConsumer); case ITypedValueProducer uintProducer: - return new InstAggregateValue(uintProducer, ValType.I32, nodeConsumer); + return new InstAggregateValue(uintProducer, ValType.U32, nodeConsumer); case ITypedValueProducer longProducer: return new InstAggregateValue(longProducer, ValType.I64, nodeConsumer); case ITypedValueProducer ulongProducer: - return new InstAggregateValue(ulongProducer, ValType.I64, nodeConsumer); + return new InstAggregateValue(ulongProducer, ValType.U64, nodeConsumer); case ITypedValueProducer floatProducer: return new InstAggregateValue(floatProducer, ValType.F32, nodeConsumer); case ITypedValueProducer doubleProducer: @@ -488,13 +488,13 @@ private static IInstruction BindU32Value(IInstruction inst, Stack var i2Producer = i2 switch { ITypedValueProducer p => p, - ITypedValueProducer p => new WrapValue(p), - ITypedValueProducer p => new WrapValue(p), - ITypedValueProducer p => new WrapValue(p), - ITypedValueProducer p => new WrapValue(p), - ITypedValueProducer p => new WrapValue(p), - ITypedValueProducer p => new WrapValue(p), - ITypedValueProducer p => new WrapValue(p), + ITypedValueProducer p => new WrapValueI32(p), + ITypedValueProducer p => new WrapValueU32(p), + ITypedValueProducer p => new WrapValueI64(p), + ITypedValueProducer p => new WrapValueU64(p), + ITypedValueProducer p => new WrapValueF32(p), + ITypedValueProducer p => new WrapValueF64(p), + ITypedValueProducer p => new WrapValueV128(p), _ => null, }; var i1Producer = i1 switch diff --git a/Wacs.Core/Runtime/Value.cs b/Wacs.Core/Runtime/Value.cs index 8fbecb7..02c6eef 100644 --- a/Wacs.Core/Runtime/Value.cs +++ b/Wacs.Core/Runtime/Value.cs @@ -379,9 +379,17 @@ public Value(ValType type, object externalValue) case ValType.I32: Int32 = (int)externalValue; break; + case ValType.U32: //Special case for transpiler + Type = ValType.I32; + UInt32 = (uint)externalValue; + break; case ValType.I64: Int64 = (long)externalValue; break; + case ValType.U64: //Special case for transpiler + Type = ValType.I64; + UInt64 = (ulong)externalValue; + break; case ValType.F32: Float32 = (float)externalValue; break; diff --git a/Wacs.Core/Types/ValType.cs b/Wacs.Core/Types/ValType.cs index 2844461..323bac6 100644 --- a/Wacs.Core/Types/ValType.cs +++ b/Wacs.Core/Types/ValType.cs @@ -83,6 +83,11 @@ public enum ValType : byte //Special types + + //Special case for transpiler + U32 = 0xFB, + U64 = 0xFA, + [WatToken("Unknown")] Unknown = 0xFC, //for validation ExecContext = 0xFD, From cd317f06d7bf0f2bfd323ad4608d1d0cabe8b338 Mon Sep 17 00:00:00 2001 From: Kelvin Nishikawa Date: Wed, 27 Nov 2024 18:57:49 -0800 Subject: [PATCH 10/24] Cleaning up stats instrumentation --- Wacs.Core/Runtime/InvokerOptions.cs | 2 +- Wacs.Core/Runtime/WasmRuntimeExecution.cs | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Wacs.Core/Runtime/InvokerOptions.cs b/Wacs.Core/Runtime/InvokerOptions.cs index 3e5651f..1816595 100644 --- a/Wacs.Core/Runtime/InvokerOptions.cs +++ b/Wacs.Core/Runtime/InvokerOptions.cs @@ -60,7 +60,7 @@ public bool UseFastPath() return false; if (ShowPath) return false; - if (CollectStats != StatsDetail.None) + if (CollectStats == StatsDetail.Instruction) return false; return true; } diff --git a/Wacs.Core/Runtime/WasmRuntimeExecution.cs b/Wacs.Core/Runtime/WasmRuntimeExecution.cs index 2709e93..7ef1850 100644 --- a/Wacs.Core/Runtime/WasmRuntimeExecution.cs +++ b/Wacs.Core/Runtime/WasmRuntimeExecution.cs @@ -102,6 +102,7 @@ Value[] GenericDelegate(params object[] args) } Context.ProcessTimer.Restart(); + Context.InstructionTimer.Restart(); var task = Context.Invoke(funcAddr); task.Wait(); @@ -124,7 +125,6 @@ Value[] GenericDelegate(params object[] args) catch (AggregateException agg) { var exc = agg.InnerException; - Context.ProcessTimer.Stop(); Context.InstructionTimer.Stop(); if (options.LogProgressEvery > 0) @@ -403,16 +403,23 @@ private void PrintStats(InvokerOptions options) TimeSpan overheadTime = new TimeSpan(overheadTicks/100); double overheadPercent = 100.0 * overheadTicks / procTicks; double execPercent = 100.0 * execTicks / procTicks; - string overheadLabel = $"({overheadPercent:#0.###}%) {overheadTime:g}"; + string overheadLabel = $" overhead:({overheadPercent:#0.###}%) {overheadTime.TotalSeconds:#0.###}s"; string totalLabel = " total duration"; string totalInst = $"{totalExecs}"; - string totalPercent = $"{execPercent:#0.###}%t".PadLeft(8,' '); + string totalPercent = $" ({execPercent:#0.###}%t)".PadLeft(8,' '); string avgTime = $"{execTime.TotalMilliseconds * 1000000.0/totalExecs:#0.###}ns/i"; double instPerSec = totalExecs * 1000.0 / totalTime.TotalMilliseconds; - string velocity = $"{instPerSec.SiSuffix("0.###")}i/s"; + string velocity = $"{instPerSec.SiSuffix("0.###")}ips"; + + if (options.CollectStats == StatsDetail.Total) + { + overheadLabel = ""; + totalPercent = ""; + } + Console.Error.WriteLine($"Execution Stats:"); - Console.Error.WriteLine($"{totalLabel}: {totalInst}| ({totalPercent}) {execTime.TotalSeconds:#0.###}s {avgTime} {velocity} overhead:{overheadLabel} total proctime:{totalTime.TotalSeconds:#0.###}s"); + Console.Error.WriteLine($"{totalLabel}: {totalInst}|{totalPercent} {execTime.TotalSeconds:#0.###}s {avgTime} {velocity}{overheadLabel} proctime:{totalTime.TotalSeconds:#0.###}s"); var orderedStats = Context.Stats .Where(bdc => bdc.Value.count != 0) .OrderBy(bdc => -bdc.Value.count); From 479bb626b3fed9a4db9c399ae071d66f0e4fbd08 Mon Sep 17 00:00:00 2001 From: Kelvin Nishikawa Date: Wed, 27 Nov 2024 21:48:49 -0800 Subject: [PATCH 11/24] Optimizing more transpiled boxes --- .../Transpiler/AggregateTypeCast.cs | 9 ++ .../Transpiler/InstAggregate1_1.cs | 16 ++- .../Transpiler/InstAggregate2_1.cs | 17 ++- Wacs.Core/Runtime/ExecContext.cs | 119 ++++-------------- Wacs.Core/Runtime/Types/FunctionInstance.cs | 57 ++++++++- 5 files changed, 118 insertions(+), 100 deletions(-) diff --git a/Wacs.Core/Instructions/Transpiler/AggregateTypeCast.cs b/Wacs.Core/Instructions/Transpiler/AggregateTypeCast.cs index afb7f42..e237e20 100644 --- a/Wacs.Core/Instructions/Transpiler/AggregateTypeCast.cs +++ b/Wacs.Core/Instructions/Transpiler/AggregateTypeCast.cs @@ -57,6 +57,15 @@ protected WrapValue(ITypedValueProducer inA) } + public class NakedValue : WrapValue + { + public NakedValue(ITypedValueProducer inA) : base(inA) + { + var func = inA.GetFunc; + _func = context => func(context); + } + } + public class WrapValueI32 : WrapValue { public WrapValueI32(ITypedValueProducer inA) : base(inA) diff --git a/Wacs.Core/Instructions/Transpiler/InstAggregate1_1.cs b/Wacs.Core/Instructions/Transpiler/InstAggregate1_1.cs index 793f8fd..2f6dab7 100644 --- a/Wacs.Core/Instructions/Transpiler/InstAggregate1_1.cs +++ b/Wacs.Core/Instructions/Transpiler/InstAggregate1_1.cs @@ -15,6 +15,7 @@ // */ using System; +using System.IO; using Wacs.Core.Runtime; using Wacs.Core.OpCodes; using Wacs.Core.Types; @@ -27,6 +28,7 @@ public class InstAggregate1_1 : InstructionBase, ITypedValueProducer _in1; private readonly Func _compute; + private readonly Func _wrap; public int CalculateSize() => Size; public readonly int Size; @@ -35,8 +37,17 @@ public InstAggregate1_1(ITypedValueProducer in1, INodeComputer c { _in1 = in1.GetFunc; _compute = compute.GetFunc; - Size = in1.CalculateSize() + 1; + + if (typeof(TOut) == typeof(int)) _wrap = new WrapValueI32((ITypedValueProducer)this).GetFunc; + else if (typeof(TOut) == typeof(uint)) _wrap = new WrapValueU32((ITypedValueProducer)this).GetFunc; + else if (typeof(TOut) == typeof(long)) _wrap = new WrapValueI64((ITypedValueProducer)this).GetFunc; + else if (typeof(TOut) == typeof(ulong)) _wrap = new WrapValueU64((ITypedValueProducer)this).GetFunc; + else if (typeof(TOut) == typeof(float)) _wrap = new WrapValueF32((ITypedValueProducer)this).GetFunc; + else if (typeof(TOut) == typeof(double)) _wrap = new WrapValueF64((ITypedValueProducer)this).GetFunc; + else if (typeof(TOut) == typeof(V128)) _wrap = new WrapValueV128((ITypedValueProducer)this).GetFunc; + else if (typeof(TOut) == typeof(Value)) _wrap = new NakedValue((ITypedValueProducer)this).GetFunc; + else throw new InvalidDataException($"Could not bind aggregate type {typeof(TOut)}"); } public TOut Run(ExecContext context) => _compute(context, _in1(context)); @@ -50,8 +61,7 @@ public override void Validate(IWasmValidationContext context) public override int Execute(ExecContext context) { - TOut value = Run(context); - context.OpStack.PushValue(new Value(value)); + context.OpStack.PushValue(_wrap(context)); return Size; } } diff --git a/Wacs.Core/Instructions/Transpiler/InstAggregate2_1.cs b/Wacs.Core/Instructions/Transpiler/InstAggregate2_1.cs index ac79433..ac24652 100644 --- a/Wacs.Core/Instructions/Transpiler/InstAggregate2_1.cs +++ b/Wacs.Core/Instructions/Transpiler/InstAggregate2_1.cs @@ -15,6 +15,7 @@ // */ using System; +using System.IO; using Wacs.Core.Runtime; using Wacs.Core.OpCodes; using Wacs.Core.Types; @@ -28,17 +29,28 @@ public class InstAggregate2_1 : InstructionBase, ITypedValueProd private readonly Func _in1; private readonly Func _in2; private readonly Func _compute; + private readonly Func _wrap; public int CalculateSize() => Size; public readonly int Size; + public InstAggregate2_1(ITypedValueProducer in1, ITypedValueProducer in2, INodeComputer compute) { _in1 = in1.GetFunc; _in2 = in2.GetFunc; _compute = compute.GetFunc; - Size = in1.CalculateSize() + in2.CalculateSize() + 1; + + if (typeof(TOut) == typeof(int)) _wrap = new WrapValueI32((ITypedValueProducer)this).GetFunc; + else if (typeof(TOut) == typeof(uint)) _wrap = new WrapValueU32((ITypedValueProducer)this).GetFunc; + else if (typeof(TOut) == typeof(long)) _wrap = new WrapValueI64((ITypedValueProducer)this).GetFunc; + else if (typeof(TOut) == typeof(ulong)) _wrap = new WrapValueU64((ITypedValueProducer)this).GetFunc; + else if (typeof(TOut) == typeof(float)) _wrap = new WrapValueF32((ITypedValueProducer)this).GetFunc; + else if (typeof(TOut) == typeof(double)) _wrap = new WrapValueF64((ITypedValueProducer)this).GetFunc; + else if (typeof(TOut) == typeof(V128)) _wrap = new WrapValueV128((ITypedValueProducer)this).GetFunc; + else if (typeof(TOut) == typeof(Value)) _wrap = new NakedValue((ITypedValueProducer)this).GetFunc; + else throw new InvalidDataException($"Could not bind aggregate type {typeof(TOut)}"); } public TOut Run(ExecContext context) => _compute(context, _in1(context), _in2(context)); @@ -52,8 +64,7 @@ public override void Validate(IWasmValidationContext context) public override int Execute(ExecContext context) { - TOut value = Run(context); - context.OpStack.PushValue(new Value(value)); + context.OpStack.PushValue(_wrap(context)); return Size; } } diff --git a/Wacs.Core/Runtime/ExecContext.cs b/Wacs.Core/Runtime/ExecContext.cs index aa6fb12..0564953 100644 --- a/Wacs.Core/Runtime/ExecContext.cs +++ b/Wacs.Core/Runtime/ExecContext.cs @@ -54,8 +54,6 @@ public class ExecContext public readonly Store Store; - private Stack _asideVals = new(); - private InstructionSequence _currentSequence; private InstructionBase[] _sequenceInstructions; private int _sequenceCount; @@ -178,17 +176,15 @@ public void FlushCallStack() _sequenceInstructions = _currentSequence._instructions; _sequenceIndex = -1; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void EnterSequence(InstructionSequence seq) + + public void EnterSequence(InstructionSequence seq) { _currentSequence = seq; _sequenceCount = _currentSequence.Count; _sequenceInstructions = _currentSequence._instructions; _sequenceIndex = -1; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ResumeSequence(InstructionPointer pointer) { _currentSequence = pointer.Sequence; @@ -196,15 +192,13 @@ public void ResumeSequence(InstructionPointer pointer) _sequenceInstructions = _currentSequence._instructions; _sequenceIndex = pointer.Index; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FastForwardSequence() { //Go to penultimate instruction since we pre-increment on pointer advance. _sequenceIndex = _sequenceCount - 2; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RewindSequence() { //Go back to the first instruction in the sequence @@ -242,83 +236,39 @@ public async Task Invoke(FuncAddr addr) switch (funcInst) { case FunctionInstance wasmFunc: - Invoke(wasmFunc, wasmFunc.Index); + wasmFunc.Invoke(this); return; case HostFunction hostFunc: + { + var funcType = hostFunc.Type; + + //Fetch the parameters + OpStack.PopScalars(funcType.ParameterTypes, hostFunc.ParameterBuffer, hostFunc.PassExecContext?1:0); + + if (hostFunc.PassExecContext) + { + hostFunc.ParameterBuffer[0] = this; + } if (hostFunc.IsAsync) - await InvokeAsync(hostFunc); + { + + //Pass them + await hostFunc.InvokeAsync(hostFunc.ParameterBuffer, OpStack); + } else - Invoke(hostFunc); - return; + { + //Pass them + hostFunc.Invoke(hostFunc.ParameterBuffer, OpStack); + } + } return; } } private void Invoke(FunctionInstance wasmFunc, FuncIdx idx) { - //3. - var funcType = wasmFunc.Type; - //4. - var t = wasmFunc.Locals; - //5. *Instructions will be handled in EnterSequence below - //var seq = wasmFunc.Definition.Body; - //6. - Assert( OpStack.Count >= funcType.ParameterTypes.Arity, - $"Function invocation failed. Operand Stack underflow."); - //7. - Assert(_asideVals.Count == 0, - $"Shared temporary stack had values left in it."); - OpStack.PopResults(funcType.ParameterTypes, ref _asideVals); - //8. - //Push the frame and operate on the frame on the stack. - var frame = ReserveFrame(wasmFunc.Module, funcType, idx, t); - // frame.FuncId = wasmFunc.Id; - - int li = 0; - int localCount = funcType.ParameterTypes.Arity + t.Length; - //Load parameters - while (_asideVals.Count > 0) - { - // frame.Locals.Set((LocalIdx)li, _asideVals.Pop()); - frame.Locals.Data[li] = _asideVals.Pop(); - li += 1; - } - //Set the Locals to default - for (int ti = 0; li < localCount; ++li, ++ti) - { - // frame.Locals.Set((LocalIdx)li, new Value(t[ti])); - frame.Locals.Data[li] = new Value(t[ti]); - } - - //9. - PushFrame(frame); - - //10. - frame.PushLabel(wasmFunc.Body.LabelTarget); - - frame.ReturnLabel.Arity = funcType.ResultType.Arity; - frame.ReturnLabel.Instruction = OpCode.Func; - frame.ReturnLabel.ContinuationAddress = new InstructionPointer(_currentSequence, _sequenceIndex); - frame.ReturnLabel.StackHeight = 0; - - - EnterSequence(wasmFunc.Body.Instructions); + } - private void Invoke(HostFunction hostFunc) - { - var funcType = hostFunc.Type; - - //Fetch the parameters - OpStack.PopScalars(funcType.ParameterTypes, hostFunc.ParameterBuffer, hostFunc.PassExecContext?1:0); - - if (hostFunc.PassExecContext) - { - hostFunc.ParameterBuffer[0] = this; - } - - //Pass them - hostFunc.Invoke(hostFunc.ParameterBuffer, OpStack); - } // @Spec 4.4.10.2. Returning from a function public void FunctionReturn() @@ -336,21 +286,6 @@ public void FunctionReturn() ResumeSequence(address); } - private async ValueTask InvokeAsync(HostFunction hostFunc) - { - var funcType = hostFunc.Type; - - //Fetch the parameters - OpStack.PopScalars(funcType.ParameterTypes, hostFunc.ParameterBuffer, hostFunc.PassExecContext?1:0); - - if (hostFunc.PassExecContext) - { - hostFunc.ParameterBuffer[0] = this; - } - - //Pass them - await hostFunc.InvokeAsync(hostFunc.ParameterBuffer, OpStack); - } public InstructionBase? Next() { diff --git a/Wacs.Core/Runtime/Types/FunctionInstance.cs b/Wacs.Core/Runtime/Types/FunctionInstance.cs index 958d0d8..888ed98 100644 --- a/Wacs.Core/Runtime/Types/FunctionInstance.cs +++ b/Wacs.Core/Runtime/Types/FunctionInstance.cs @@ -14,6 +14,7 @@ // * limitations under the License. // */ +using System.Collections.Generic; using FluentValidation; using FluentValidation.Internal; using Wacs.Core.OpCodes; @@ -70,7 +71,6 @@ public void SetBody(Expression body) var vContext = new StackCalculator(Module, Definition); Body.PrecomputeLabels(vContext); } - //Copied from the static Definition public ValType[] Locals; @@ -79,12 +79,65 @@ public void SetBody(Expression body) public string ModuleName => Module.Name; public string Name { get; set; } = ""; - public FunctionType Type { get; } public void SetName(string value) => Name = value; public string Id => string.IsNullOrEmpty(Name)?"":$"{ModuleName}.{Name}"; public bool IsExport { get; set; } + private static Stack _asideVals = new(); + + private readonly static ByteCode LabelInst = OpCode.Func; + + public void Invoke(ExecContext context) + { + //3. + var funcType = Type; + //4. + var t = Locals; + //5. *Instructions will be handled in EnterSequence below + //var seq = Body.Instructions; + //6. + context.Assert( context.OpStack.Count >= funcType.ParameterTypes.Arity, + $"Function invocation failed. Operand Stack underflow."); + //7. + context.Assert(_asideVals.Count == 0, + $"Shared temporary stack had values left in it."); + context.OpStack.PopResults(funcType.ParameterTypes, ref _asideVals); + //8. + //Push the frame and operate on the frame on the stack. + var frame = context.ReserveFrame(Module, funcType, Index, t); + // frame.FuncId = wasmFunc.Id; + + int li = 0; + int localCount = funcType.ParameterTypes.Arity + t.Length; + //Load parameters + while (_asideVals.Count > 0) + { + // frame.Locals.Set((LocalIdx)li, _asideVals.Pop()); + frame.Locals.Data[li] = _asideVals.Pop(); + li += 1; + } + //Set the Locals to default + for (int ti = 0; li < localCount; ++li, ++ti) + { + // frame.Locals.Set((LocalIdx)li, new Value(t[ti])); + frame.Locals.Data[li] = new Value(t[ti]); + } + + //9. + context.PushFrame(frame); + + //10. + frame.PushLabel(Body.LabelTarget); + + frame.ReturnLabel.Arity = funcType.ResultType.Arity; + frame.ReturnLabel.Instruction = LabelInst; + frame.ReturnLabel.ContinuationAddress = context.GetPointer(); + frame.ReturnLabel.StackHeight = 0; + + context.EnterSequence(Body.Instructions); + } + public override string ToString() => $"FunctionInstance[{Id}] (Type: {Type}, IsExport: {IsExport})"; } } \ No newline at end of file From 42701f6766009f7e807fd3172de60d030e5fdcef Mon Sep 17 00:00:00 2001 From: Kelvin Nishikawa Date: Wed, 27 Nov 2024 22:27:04 -0800 Subject: [PATCH 12/24] Code style clean up + removing gas accumulator from Execute --- Feature.Detect/DetectFeatures.cs | 19 ++- Feature.Detect/FeatureJson/FeatureJson.cs | 1 - Spec.Test/BindingTests.cs | 14 ++- Spec.Test/WastTests.cs | 2 +- Wacs.Console/CommandLineOptions.cs | 2 +- Wacs.Core/Instructions/Control.cs | 99 +++++++--------- Wacs.Core/Instructions/GlobalVariable.cs | 23 ++-- Wacs.Core/Instructions/Humanize.cs | 1 - Wacs.Core/Instructions/IBlockInstruction.cs | 1 - Wacs.Core/Instructions/IConstInstruction.cs | 2 +- Wacs.Core/Instructions/IInstruction.cs | 6 +- Wacs.Core/Instructions/InstExpressionProxy.cs | 11 +- Wacs.Core/Instructions/InstructionBase.cs | 16 +-- Wacs.Core/Instructions/LocalVariable.cs | 44 ++++--- Wacs.Core/Instructions/Memory.cs | 109 +++++++++-------- Wacs.Core/Instructions/MemoryBulk.cs | 36 +++--- Wacs.Core/Instructions/Numeric/Const.cs | 41 +++---- Wacs.Core/Instructions/Numeric/Conversion.cs | 37 +++--- Wacs.Core/Instructions/Numeric/F32BinOp.cs | 18 +-- Wacs.Core/Instructions/Numeric/F32RelOp.cs | 20 ++-- Wacs.Core/Instructions/Numeric/F32UnOp.cs | 18 +-- Wacs.Core/Instructions/Numeric/F64BinOp.cs | 20 ++-- Wacs.Core/Instructions/Numeric/F64RelOp.cs | 19 +-- Wacs.Core/Instructions/Numeric/F64UnOp.cs | 16 +-- Wacs.Core/Instructions/Numeric/I32BinOp.cs | 112 +++++++++--------- Wacs.Core/Instructions/Numeric/I32RelOp.cs | 53 ++++----- .../Instructions/Numeric/I32SignExtension.cs | 19 +-- Wacs.Core/Instructions/Numeric/I32UnOp.cs | 22 ++-- Wacs.Core/Instructions/Numeric/I64BinOp.cs | 112 +++++++++--------- Wacs.Core/Instructions/Numeric/I64RelOp.cs | 65 +++++----- .../Instructions/Numeric/I64SignExtension.cs | 21 ++-- Wacs.Core/Instructions/Numeric/I64UnOp.cs | 18 +-- Wacs.Core/Instructions/Numeric/ITestOp.cs | 42 +++---- Wacs.Core/Instructions/Numeric/NumericInst.cs | 20 ++-- Wacs.Core/Instructions/Parametric.cs | 14 +-- Wacs.Core/Instructions/Reference.cs | 9 +- Wacs.Core/Instructions/Runtime.cs | 3 +- Wacs.Core/Instructions/SIMD/VConst.cs | 5 +- Wacs.Core/Instructions/SIMD/VLaneOp.cs | 6 +- Wacs.Core/Instructions/SIMD/VMemory.cs | 58 ++++----- Wacs.Core/Instructions/SIMD/ViShuffleOp.cs | 3 +- Wacs.Core/Instructions/Table.cs | 49 ++++---- Wacs.Core/Instructions/TailCall.cs | 30 +++-- .../Transpiler/AggregateTypeCast.cs | 50 ++++---- .../Transpiler/InstAggregate1_0.cs | 20 +--- .../Transpiler/InstAggregate1_1.cs | 21 ++-- .../Transpiler/InstAggregate2_0.cs | 17 +-- .../Transpiler/InstAggregate2_1.cs | 22 ++-- .../Transpiler/InstStackProducer.cs | 20 ++-- .../Instructions/Transpiler/Interfaces.cs | 8 +- Wacs.Core/Modules/Sections/FunctionSection.cs | 2 +- Wacs.Core/Modules/Sections/GlobalSection.cs | 4 +- Wacs.Core/Modules/Sections/StackCalculator.cs | 41 ++++--- Wacs.Core/Modules/Sections/StackRenderer.cs | 7 +- .../Exceptions/InsufficientGasException.cs | 1 - Wacs.Core/Runtime/ExecContext.cs | 28 ++--- Wacs.Core/Runtime/Frame.cs | 15 ++- Wacs.Core/Runtime/Label.cs | 4 +- Wacs.Core/Runtime/OpStack.cs | 2 +- Wacs.Core/Runtime/RuntimeAttributes.cs | 8 +- Wacs.Core/Runtime/Store.cs | 8 +- .../Runtime/Transpiler/FunctionTranspiler.cs | 28 ++--- Wacs.Core/Runtime/Types/FunctionInstance.cs | 57 +++++---- Wacs.Core/Runtime/Types/GlobalInstance.cs | 3 +- Wacs.Core/Runtime/Types/HostFunction.cs | 11 +- Wacs.Core/Runtime/Types/TableInstance.cs | 10 +- Wacs.Core/Runtime/WasmRuntimeExecution.cs | 31 +++-- Wacs.Core/Runtime/WasmRuntimeInstantiation.cs | 2 - Wacs.Core/Runtime/WasmRuntimeTranspiler.cs | 2 +- Wacs.Core/Types/Expression.cs | 21 ++-- Wacs.Core/Types/GlobalType.cs | 12 +- Wacs.Core/Types/IndexSpace.cs | 19 ++- Wacs.Core/Types/Limits.cs | 20 ++-- Wacs.Core/Types/MemoryType.cs | 10 +- Wacs.Core/Types/TableType.cs | 10 +- Wacs.Core/Utilities/ObjectPoolExtension.cs | 1 - Wacs.Core/Utilities/StackPoolPolicy.cs | 3 +- Wacs.Core/Validation/WasmValidationContext.cs | 4 +- Wacs.WASIp1/Sock.cs | 5 +- Wacs.WASIp1/State.cs | 8 +- Wacs.WASIp1/Types/IoVec.cs | 1 - Wacs.WASIp1/Wasi.cs | 4 +- 82 files changed, 854 insertions(+), 922 deletions(-) diff --git a/Feature.Detect/DetectFeatures.cs b/Feature.Detect/DetectFeatures.cs index e5a542b..78d6c68 100644 --- a/Feature.Detect/DetectFeatures.cs +++ b/Feature.Detect/DetectFeatures.cs @@ -1,4 +1,20 @@ -ο»Ώusing System; +ο»Ώ// /* +// * Copyright 2024 Kelvin Nishikawa +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ + +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -7,7 +23,6 @@ using Wacs.Core.Runtime; using Wacs.Core.Types; using Xunit; -using Xunit.Abstractions; namespace Feature.Detect { diff --git a/Feature.Detect/FeatureJson/FeatureJson.cs b/Feature.Detect/FeatureJson/FeatureJson.cs index f16312d..4ef48ef 100644 --- a/Feature.Detect/FeatureJson/FeatureJson.cs +++ b/Feature.Detect/FeatureJson/FeatureJson.cs @@ -52,7 +52,6 @@ public override string ToString() .Append("}"); return sb.ToString(); } - } public class Options diff --git a/Spec.Test/BindingTests.cs b/Spec.Test/BindingTests.cs index e8d17d4..0274144 100644 --- a/Spec.Test/BindingTests.cs +++ b/Spec.Test/BindingTests.cs @@ -25,21 +25,20 @@ namespace Spec.Test { public class BindingTests { - delegate int HostInOut(int a, out int b); static int BoundHost(int a, out int b) { b = a; int c = a * 2; return c; } - delegate Task HostAsyncInRet(int a); + static async Task BoundAsyncHost(int a) { await Task.Delay(1000); // Simulate work return a*2; } - - + + [Fact] public void BindStackBinder() { @@ -99,7 +98,7 @@ public void BindParamAndResultF32() Assert.Equal(2f * 2f, invoker(2f)); Assert.Equal(3f * 2f, invoker(3f)); } - + [Fact] public void BindHostFunction() { @@ -118,7 +117,7 @@ public void BindHostFunction() Assert.Equal(10 + 20, invoker(10)); } - + [Fact] public void BindHostAsyncFunction() { @@ -137,5 +136,8 @@ public void BindHostAsyncFunction() Assert.Equal(10*2, invoker(10)); } + delegate int HostInOut(int a, out int b); + + delegate Task HostAsyncInRet(int a); } } \ No newline at end of file diff --git a/Spec.Test/WastTests.cs b/Spec.Test/WastTests.cs index 7b853ee..786ab39 100644 --- a/Spec.Test/WastTests.cs +++ b/Spec.Test/WastTests.cs @@ -62,7 +62,7 @@ public void RunWast(WastJson.WastJson file) } } } - + [Theory] [ClassData(typeof(WastJsonTestData))] public void RunWastTranspiled(WastJson.WastJson file) diff --git a/Wacs.Console/CommandLineOptions.cs b/Wacs.Console/CommandLineOptions.cs index 4256436..08defa5 100644 --- a/Wacs.Console/CommandLineOptions.cs +++ b/Wacs.Console/CommandLineOptions.cs @@ -61,7 +61,7 @@ public class CommandLineOptions [Option('i', "invoke", HelpText = "Call a specific function.")] public string InvokeFunction { get; set; } = ""; - + [Option('t', "transpiler", HelpText = "Invoke the transpiler on instantiated module")] public bool Transpile { get; set; } diff --git a/Wacs.Core/Instructions/Control.cs b/Wacs.Core/Instructions/Control.cs index 3b7cda4..ba4e9b9 100644 --- a/Wacs.Core/Instructions/Control.cs +++ b/Wacs.Core/Instructions/Control.cs @@ -46,7 +46,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.8.2. unreachable - public override int Execute(ExecContext context) => + public override void Execute(ExecContext context) => throw new TrapException("unreachable"); } @@ -62,26 +62,23 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.8.1. nop - public override int Execute(ExecContext context) - { - return 1; - } + public override void Execute(ExecContext context) {} } //0x02 public class InstBlock : BlockTarget, IBlockInstruction { private static readonly ByteCode BlockOp = OpCode.Block; - public override ByteCode Op => BlockOp; private Block Block; - + public override ByteCode Op => BlockOp; + public BlockType Type => Block.Type; public int Count => 1; public int Size => 1 + Block.Size; public Block GetBlock(int idx) => Block; - + // @Spec 3.3.8.3 block public override void Validate(IWasmValidationContext context) { @@ -110,11 +107,10 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.8.3. block - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { if (Block.Instructions.Count != 0) context.EnterBlock(this, Block); - return 1; } /// @@ -128,7 +124,7 @@ public override IInstruction Parse(BinaryReader reader) ); return this; } - + public BlockTarget Immediate(BlockType type, InstructionSequence sequence) { Block = new Block( @@ -149,8 +145,8 @@ public override string RenderText(ExecContext? context) public class InstLoop : BlockTarget, IBlockInstruction { private static readonly ByteCode LoopOp = OpCode.Loop; - public override ByteCode Op => LoopOp; private Block Block = null!; + public override ByteCode Op => LoopOp; public BlockType Type => Block.Type; @@ -158,7 +154,7 @@ public class InstLoop : BlockTarget, IBlockInstruction public int Size => 1 + Block.Size; public Block GetBlock(int idx) => Block; - + // @Spec 3.3.8.4. loop public override void Validate(IWasmValidationContext context) { @@ -187,11 +183,10 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.8.4. loop - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { if (Block.Instructions.Count != 0) context.EnterBlock(this, Block); - return 1; } /// @@ -205,7 +200,7 @@ public override IInstruction Parse(BinaryReader reader) ); return this; } - + public BlockTarget Immediate(BlockType type, InstructionSequence sequence) { Block = new Block( @@ -227,18 +222,18 @@ public class InstIf : BlockTarget, IBlockInstruction { private static readonly ByteCode IfOp = OpCode.If; private static readonly ByteCode ElseOp = OpCode.Else; - public override ByteCode Op => IfOp; - private Block IfBlock = Block.Empty; private Block ElseBlock = Block.Empty; + private Block IfBlock = Block.Empty; + public override ByteCode Op => IfOp; + - public BlockType Type => IfBlock.Type; public int Count => ElseBlock.Length == 0 ? 1 : 2; public int Size => 1 + IfBlock.Size + ElseBlock.Size; public Block GetBlock(int idx) => idx == 0 ? IfBlock : ElseBlock; - + // @Spec 3.3.8.5 if public override void Validate(IWasmValidationContext context) { @@ -283,7 +278,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.8.5. if - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { int c = context.OpStack.PopI32(); if (c != 0) @@ -296,7 +291,6 @@ public override int Execute(ExecContext context) if (ElseBlock.Instructions.Count != 0) context.EnterBlock(this, ElseBlock); } - return 1; } /// @@ -325,7 +319,7 @@ public override IInstruction Parse(BinaryReader reader) return this; } - + public BlockTarget Immediate(BlockType type, InstructionSequence ifSeq, InstructionSequence elseSeq) { IfBlock = new Block( @@ -367,7 +361,7 @@ public override void Validate(IWasmValidationContext context) context.OpStack.ReturnResults(frame.EndTypes); } - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { var label = context.Frame.Label; switch (label.Instruction.x00) @@ -386,7 +380,6 @@ public override int Execute(ExecContext context) //Do nothing break; } - return 1; } public override string RenderText(ExecContext? context) @@ -436,10 +429,10 @@ public override string RenderText(ExecContext? context) public class InstBranch : InstructionBase, IBranchInstruction { private static Stack _asideVals = new(); - public override ByteCode Op => OpCode.Br; private LabelIdx L; - + public override ByteCode Op => OpCode.Br; + // @Spec 3.3.8.6. br l public override void Validate(IWasmValidationContext context) { @@ -459,10 +452,9 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.8.6. br l - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { ExecuteInstruction(context, L); - return 1; } public static void ExecuteInstruction(ExecContext context, LabelIdx labelIndex) @@ -532,10 +524,9 @@ public override string RenderText(ExecContext? context) //0x0D public class InstBranchIf : InstructionBase, IBranchInstruction { + public LabelIdx L; public override ByteCode Op => OpCode.BrIf; - public LabelIdx L; - // @Spec 3.3.8.7. br_if public override void Validate(IWasmValidationContext context) { @@ -556,14 +547,13 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.8.7. br_if - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { int c = context.OpStack.PopI32(); if (c != 0) { InstBranch.ExecuteInstruction(context, L); } - return 1; } /// @@ -592,10 +582,10 @@ public override string RenderText(ExecContext? context) //0x0E public class InstBranchTable : InstructionBase, IBranchInstruction { - public override ByteCode Op => OpCode.BrTable; + private LabelIdx Ln; //Default m private LabelIdx[] Ls = null!; - private LabelIdx Ln; //Default m + public override ByteCode Op => OpCode.BrTable; // @Spec 3.3.8.8. br_table public override void Validate(IWasmValidationContext context) @@ -636,7 +626,7 @@ public override void Validate(IWasmValidationContext context) /// @Spec 4.4.8.8. br_table /// /// - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //2. int i = context.OpStack.PopI32(); @@ -651,7 +641,6 @@ public override int Execute(ExecContext context) { InstBranch.ExecuteInstruction(context, Ln); } - return 1; } private static LabelIdx ParseLabelIndex(BinaryReader reader) => @@ -711,7 +700,7 @@ public class InstReturn : InstructionBase { public static readonly InstReturn Inst = new(); public override ByteCode Op => OpCode.Return; - + // @Spec 3.3.8.9. return public override void Validate(IWasmValidationContext context) { @@ -722,7 +711,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.8.9. return - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { context.Assert( context.OpStack.Count >= context.Frame.Arity, $"Instruction return failed. Operand stack underflow"); @@ -731,21 +720,20 @@ public override int Execute(ExecContext context) // context.OpStack.PopResults(context.Frame.Type.ResultType, ref values); var address = context.PopFrame(); context.ResumeSequence(address); - return 1; } } //0x10 public class InstCall : InstructionBase, ICallInstruction { + public FuncIdx X; + public InstCall() { IsAsync = true; } - - public override ByteCode Op => OpCode.Call; - public FuncIdx X; + public override ByteCode Op => OpCode.Call; public bool IsBound(ExecContext context) { @@ -770,7 +758,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.8.10. call - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { context.Assert( context.Frame.Module.FuncAddrs.Contains(X), $"Instruction call failed. Function address for {X} was not in the Context."); @@ -778,16 +766,14 @@ public override int Execute(ExecContext context) context.Invoke(a); throw new WasmRuntimeException("Synchronous execution path not allowed."); - return 1; } - - public override async ValueTask ExecuteAsync(ExecContext context) + + public override async ValueTask ExecuteAsync(ExecContext context) { context.Assert( context.Frame.Module.FuncAddrs.Contains(X), $"Instruction call failed. Function address for {X} was not in the Context."); var a = context.Frame.Module.FuncAddrs[X]; await context.Invoke(a); - return 1; } /// @@ -840,15 +826,16 @@ public override string RenderText(ExecContext? context) //0x11 public class InstCallIndirect : InstructionBase, ICallInstruction { + private TableIdx X; + + private TypeIdx Y; + public InstCallIndirect() { IsAsync = true; } - - public override ByteCode Op => OpCode.CallIndirect; - private TypeIdx Y; - private TableIdx X; + public override ByteCode Op => OpCode.CallIndirect; public bool IsBound(ExecContext context) { @@ -894,7 +881,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.8.11. call_indirect - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //2. context.Assert( context.Frame.Module.TableAddrs.Contains(X), @@ -942,10 +929,9 @@ public override int Execute(ExecContext context) //19. context.Invoke(a); throw new WasmRuntimeException("Synchronous execution path not allowed"); - return 1; } - - public override async ValueTask ExecuteAsync(ExecContext context) + + public override async ValueTask ExecuteAsync(ExecContext context) { //2. context.Assert( context.Frame.Module.TableAddrs.Contains(X), @@ -992,7 +978,6 @@ public override async ValueTask ExecuteAsync(ExecContext context) throw new TrapException($"Instruction call_indirect failed. Expected FunctionType differed."); //19. await context.Invoke(a); - return 1; } /// diff --git a/Wacs.Core/Instructions/GlobalVariable.cs b/Wacs.Core/Instructions/GlobalVariable.cs index 0abbd7b..0c38cc2 100644 --- a/Wacs.Core/Instructions/GlobalVariable.cs +++ b/Wacs.Core/Instructions/GlobalVariable.cs @@ -28,12 +28,16 @@ namespace Wacs.Core.Instructions { public class InstGlobalGet : InstructionBase, IContextConstInstruction, IVarInstruction, ITypedValueProducer { - public override ByteCode Op => OpCode.GlobalGet; private GlobalIdx Index; + public override ByteCode Op => OpCode.GlobalGet; public bool IsConstant(IWasmValidationContext? context) => context == null || context.Globals.Contains(Index) && context.Globals[Index].IsImport && context.Globals[Index].Type.Mutability == Mutability.Immutable; + public Func GetFunc => FetchFromGlobals; + + public int CalculateSize() => 1; + public override IInstruction Parse(BinaryReader reader) { Index = (GlobalIdx)reader.ReadLeb128_u32(); @@ -67,11 +71,10 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.5.4. global.get - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { var val = FetchFromGlobals(context); context.OpStack.PushValue(val); - return 1; } public Value FetchFromGlobals(ExecContext context) @@ -91,20 +94,17 @@ public Value FetchFromGlobals(ExecContext context) //7. return val; } - - public Func GetFunc => FetchFromGlobals; - - public int CalculateSize() => 1; } public class InstGlobalSet : InstructionBase, IContextConstInstruction, IVarInstruction, INodeConsumer { - public override ByteCode Op => OpCode.GlobalSet; private GlobalIdx Index; public bool IsConstant(IWasmValidationContext? context) => context == null || context.Globals.Contains(Index) && context.Globals[Index].IsImport && context.Globals[Index].Type.Mutability == Mutability.Immutable; + public override ByteCode Op => OpCode.GlobalSet; + public override IInstruction Parse(BinaryReader reader) { Index = (GlobalIdx)reader.ReadLeb128_u32(); @@ -142,7 +142,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.5.5. global.set - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //2. context.Assert( context.Frame.Module.GlobalAddrs.Contains(Index), @@ -160,9 +160,10 @@ public override int Execute(ExecContext context) //7. var val = context.OpStack.PopType(glob.Type.ContentType); SetGlobal(context, val); - return 1; } + public Action GetFunc => SetGlobal; + public void SetGlobal(ExecContext context, Value value) { //2. @@ -179,8 +180,6 @@ public void SetGlobal(ExecContext context, Value value) //8. glob.Value = value; } - - public Action GetFunc => SetGlobal; } } \ No newline at end of file diff --git a/Wacs.Core/Instructions/Humanize.cs b/Wacs.Core/Instructions/Humanize.cs index a50acfe..322ad23 100644 --- a/Wacs.Core/Instructions/Humanize.cs +++ b/Wacs.Core/Instructions/Humanize.cs @@ -34,6 +34,5 @@ public static string SiSuffix(this double number, string format = "0.#") return string.Format("{0}{1}", formattedValue, suffixes[magnitude]); } - } } \ No newline at end of file diff --git a/Wacs.Core/Instructions/IBlockInstruction.cs b/Wacs.Core/Instructions/IBlockInstruction.cs index 73a5c39..ea2232e 100644 --- a/Wacs.Core/Instructions/IBlockInstruction.cs +++ b/Wacs.Core/Instructions/IBlockInstruction.cs @@ -15,7 +15,6 @@ // */ using Wacs.Core.Runtime; -using Wacs.Core.Runtime.Types; using Wacs.Core.Types; namespace Wacs.Core.Instructions diff --git a/Wacs.Core/Instructions/IConstInstruction.cs b/Wacs.Core/Instructions/IConstInstruction.cs index 3bec9f9..5aa1113 100644 --- a/Wacs.Core/Instructions/IConstInstruction.cs +++ b/Wacs.Core/Instructions/IConstInstruction.cs @@ -18,7 +18,7 @@ namespace Wacs.Core.Instructions { - public interface IConstInstruction { } + public interface IConstInstruction {} public interface IContextConstInstruction { diff --git a/Wacs.Core/Instructions/IInstruction.cs b/Wacs.Core/Instructions/IInstruction.cs index 75119e3..ea3e815 100644 --- a/Wacs.Core/Instructions/IInstruction.cs +++ b/Wacs.Core/Instructions/IInstruction.cs @@ -31,13 +31,13 @@ public interface IInstruction public ByteCode Op { get; } void Validate(IWasmValidationContext context); - + /// /// Synchronously executes the instruction within the given execution context. /// /// The execution context in which to execute the instruction. /// The effective number of wasm instructions executed - int Execute(ExecContext context); + void Execute(ExecContext context); /// /// Asynchronously wraps the Execute function. @@ -45,7 +45,7 @@ public interface IInstruction /// /// /// ValueTask containing the effective number of wasm instructions executed - public ValueTask ExecuteAsync(ExecContext context); + public ValueTask ExecuteAsync(ExecContext context); /// /// Parses an instruction from a binary reader. diff --git a/Wacs.Core/Instructions/InstExpressionProxy.cs b/Wacs.Core/Instructions/InstExpressionProxy.cs index 1e56564..279b685 100644 --- a/Wacs.Core/Instructions/InstExpressionProxy.cs +++ b/Wacs.Core/Instructions/InstExpressionProxy.cs @@ -16,22 +16,23 @@ using Wacs.Core.OpCodes; using Wacs.Core.Runtime; -using Wacs.Core.Types; using Wacs.Core.Validation; namespace Wacs.Core.Instructions { public class InstExpressionProxy : BlockTarget { - private ByteCode _op = OpCode.Expr; - public override ByteCode Op => _op; - public override void Validate(IWasmValidationContext context) { } - public override int Execute(ExecContext context) => 0; + private readonly ByteCode _op = OpCode.Expr; public InstExpressionProxy(Label label) { EnclosingBlock = this; Label = label; } + + public override ByteCode Op => _op; + public override void Validate(IWasmValidationContext context) { } + + public override void Execute(ExecContext context) { } } } \ No newline at end of file diff --git a/Wacs.Core/Instructions/InstructionBase.cs b/Wacs.Core/Instructions/InstructionBase.cs index 61042fb..ed52743 100644 --- a/Wacs.Core/Instructions/InstructionBase.cs +++ b/Wacs.Core/Instructions/InstructionBase.cs @@ -29,6 +29,11 @@ namespace Wacs.Core.Instructions /// public abstract class InstructionBase : IInstruction { + protected static Stack _aside = new(); + + public bool IsAsync = false; + public int Size = 1; + /// /// Gets the opcode associated with the instruction. /// @@ -36,22 +41,20 @@ public abstract class InstructionBase : IInstruction public abstract void Validate(IWasmValidationContext context); - public bool IsAsync = false; - /// /// Synchronously executes the instruction within the given execution context. /// /// The execution context in which to execute the instruction. /// The effective number of wasm instructions executed - public abstract int Execute(ExecContext context); - + public abstract void Execute(ExecContext context); + /// /// Asynchronously wraps the Execute function. /// Individual instructions may override to perform async functions. /// /// /// ValueTask containing the effective number of wasm instructions executed - public virtual async ValueTask ExecuteAsync(ExecContext context) + public virtual async ValueTask ExecuteAsync(ExecContext context) { throw new WasmRuntimeException("Async Execution must be explicitly implemented"); } @@ -67,8 +70,5 @@ public virtual async ValueTask ExecuteAsync(ExecContext context) public virtual IInstruction Parse(BinaryReader reader) => this; public virtual string RenderText(ExecContext? context) => Op.GetMnemonic(); - - protected static Stack _aside = new(); - } } \ No newline at end of file diff --git a/Wacs.Core/Instructions/LocalVariable.cs b/Wacs.Core/Instructions/LocalVariable.cs index 0bc457d..c70e70b 100644 --- a/Wacs.Core/Instructions/LocalVariable.cs +++ b/Wacs.Core/Instructions/LocalVariable.cs @@ -28,8 +28,12 @@ namespace Wacs.Core.Instructions { public class InstLocalGet : InstructionBase, IVarInstruction, ITypedValueProducer { - public override ByteCode Op => OpCode.LocalGet; private LocalIdx Index; + public override ByteCode Op => OpCode.LocalGet; + + public Func GetFunc => FetchFromLocals; + + public int CalculateSize() => 1; public override IInstruction Parse(BinaryReader reader) { @@ -66,14 +70,13 @@ public override void Validate(IWasmValidationContext context) var value = context.Locals.Get(Index); context.OpStack.PushType(value.Type); } - - public override int Execute(ExecContext context) + + public override void Execute(ExecContext context) { var value = FetchFromLocals(context); context.OpStack.PushValue(value); - return 1; } - + // @Spec 4.4.5.1. local.get public Value FetchFromLocals(ExecContext context) { @@ -85,16 +88,12 @@ public Value FetchFromLocals(ExecContext context) var value = context.Frame.Locals.Data[Index.Value]; return value; } - - public Func GetFunc => FetchFromLocals; - - public int CalculateSize() => 1; } public class InstLocalSet : InstructionBase, IVarInstruction, INodeConsumer { - public override ByteCode Op => OpCode.LocalSet; private LocalIdx Index; + public override ByteCode Op => OpCode.LocalSet; public override IInstruction Parse(BinaryReader reader) { @@ -102,12 +101,6 @@ public override IInstruction Parse(BinaryReader reader) return this; } - public IInstruction Immediate(LocalIdx idx) - { - Index = idx; - return this; - } - public override string RenderText(ExecContext? context) { if (context == null) @@ -132,7 +125,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.5.2. local.set - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //2. context.Assert( context.Frame.Locals.Contains(Index), @@ -146,7 +139,14 @@ public override int Execute(ExecContext context) //4. var value = context.OpStack.PopType(type); SetLocal(context, value); - return 1; + } + + public Action GetFunc => SetLocal; + + public IInstruction Immediate(LocalIdx idx) + { + Index = idx; + return this; } public void SetLocal(ExecContext context, Value value) @@ -155,14 +155,12 @@ public void SetLocal(ExecContext context, Value value) // context.Frame.Locals.Set(Index, value); context.Frame.Locals.Data[Index.Value] = value; } - - public Action GetFunc => SetLocal; } public class InstLocalTee : InstructionBase, IVarInstruction { - public override ByteCode Op => OpCode.LocalTee; private LocalIdx Index; + public override ByteCode Op => OpCode.LocalTee; public LocalIdx GetIndex() => Index; @@ -200,7 +198,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.5.3. local.tee - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //1. context.Assert( context.OpStack.HasValue, @@ -228,8 +226,6 @@ public override int Execute(ExecContext context) //5. // context.Frame.Locals.Set(Index, value); context.Frame.Locals.Data[Index.Value] = value; - return 1; } - } } \ No newline at end of file diff --git a/Wacs.Core/Instructions/Memory.cs b/Wacs.Core/Instructions/Memory.cs index 597b459..fb3590e 100644 --- a/Wacs.Core/Instructions/Memory.cs +++ b/Wacs.Core/Instructions/Memory.cs @@ -90,8 +90,8 @@ public override void Validate(IWasmValidationContext context) context.OpStack.PushType(Type); } - - public override int Execute(ExecContext context) + + public override void Execute(ExecContext context) { //6. context.Assert( context.OpStack.Peek().IsI32, @@ -99,7 +99,27 @@ public override int Execute(ExecContext context) uint offset = context.OpStack.PopU32(); var value = FetchFromMemory(context, offset); context.OpStack.PushValue(value); - return 1; + } + + public Func GetFunc => FetchFromMemory; + + public override IInstruction Parse(BinaryReader reader) + { + M = MemArg.Parse(reader); + return this; + } + + public override string RenderText(ExecContext? context) + { + if (context != null) + { + if (context.Attributes.Live && context.OpStack.Count > 0) + { + var loadedValue = context.OpStack.Peek(); + return $"{base.RenderText(context)}{M.ToWat(WidthT)} (;>{loadedValue}<;)"; + } + } + return $"{base.RenderText(context)}{M.ToWat(WidthT)}"; } // @Spec 4.4.7.1. t.load and t.loadN_sx @@ -173,34 +193,13 @@ public Value FetchFromMemory(ExecContext context, uint offset) } } - public Func GetFunc => FetchFromMemory; - public int CalculateSize() => 1; - + public IInstruction Immediate(MemArg m) { M = m; return this; } - - public override IInstruction Parse(BinaryReader reader) - { - M = MemArg.Parse(reader); - return this; - } - - public override string RenderText(ExecContext? context) - { - if (context != null) - { - if (context.Attributes.Live && context.OpStack.Count > 0) - { - var loadedValue = context.OpStack.Peek(); - return $"{base.RenderText(context)}{M.ToWat(WidthT)} (;>{loadedValue}<;)"; - } - } - return $"{base.RenderText(context)}{M.ToWat(WidthT)}"; - } } public class InstMemoryStore : InstructionBase, INodeConsumer @@ -236,12 +235,6 @@ public InstMemoryStore(ValType type, BitWidth width) => _ => throw new InvalidDataException($"InstMemoryStore instruction is malformed: {Type}"), }; - public IInstruction Immediate(MemArg m) - { - M = m; - return this; - } - /// /// @Spec 3.3.7.3. t.store /// @Spec 3.3.7.4. t.storeN @@ -260,7 +253,7 @@ public override void Validate(IWasmValidationContext context) // @Spec 4.4.7.6. t.store // @Spec 4.4.7.6. t.storeN - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //6. context.Assert( context.OpStack.Peek().Type == Type, @@ -273,8 +266,34 @@ public override int Execute(ExecContext context) uint offset = context.OpStack.PopU32(); SetMemoryValue(context, offset, c); - - return 1; + } + + + public Action GetFunc => SetMemoryValue; + + public override IInstruction Parse(BinaryReader reader) + { + M = MemArg.Parse(reader); + return this; + } + + public override string RenderText(ExecContext? context) + { + if (context != null) + { + if (context.Attributes.Live && context.OpStack.Count > 0) + { + var storeValue = context.OpStack.Peek(); + return $"{base.RenderText(context)}{M.ToWat(TWidth)} (;>{storeValue}<;)"; + } + } + return $"{base.RenderText(context)}{M.ToWat(TWidth)}"; + } + + public IInstruction Immediate(MemArg m) + { + M = m; + return this; } public void SetMemoryValue(ExecContext context, uint offset, Value c) @@ -359,30 +378,8 @@ public void SetMemoryValue(ExecContext context, uint offset, Value c) break; } } - - - public Action GetFunc => SetMemoryValue; public int CalculateSize() => 1; - - public override IInstruction Parse(BinaryReader reader) - { - M = MemArg.Parse(reader); - return this; - } - - public override string RenderText(ExecContext? context) - { - if (context != null) - { - if (context.Attributes.Live && context.OpStack.Count > 0) - { - var storeValue = context.OpStack.Peek(); - return $"{base.RenderText(context)}{M.ToWat(TWidth)} (;>{storeValue}<;)"; - } - } - return $"{base.RenderText(context)}{M.ToWat(TWidth)}"; - } } } \ No newline at end of file diff --git a/Wacs.Core/Instructions/MemoryBulk.cs b/Wacs.Core/Instructions/MemoryBulk.cs index 5bba447..3963634 100644 --- a/Wacs.Core/Instructions/MemoryBulk.cs +++ b/Wacs.Core/Instructions/MemoryBulk.cs @@ -28,9 +28,8 @@ namespace Wacs.Core.Instructions //0x3F public class InstMemorySize : InstructionBase { - public override ByteCode Op => OpCode.MemorySize; - private MemIdx M; + public override ByteCode Op => OpCode.MemorySize; /// /// @Spec 3.3.7.10. memory.size @@ -43,7 +42,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.7.8. memory.size - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //2. context.Assert( context.Frame.Module.MemAddrs.Contains(M), @@ -59,7 +58,6 @@ public override int Execute(ExecContext context) uint sz = (uint)mem.Size; //7. context.OpStack.PushU32(sz); - return 1; } public override IInstruction Parse(BinaryReader reader) @@ -72,8 +70,8 @@ public override IInstruction Parse(BinaryReader reader) //0x40 public class InstMemoryGrow : InstructionBase { - public override ByteCode Op => OpCode.MemoryGrow; private MemIdx M; + public override ByteCode Op => OpCode.MemoryGrow; /// /// @Spec 3.3.7.11. memory.grow @@ -87,7 +85,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.7.9. memory.grow - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //2. context.Assert( context.Frame.Module.MemAddrs.Contains(M), @@ -117,7 +115,6 @@ public override int Execute(ExecContext context) { context.OpStack.PushI32(err); } - return 1; } public override IInstruction Parse(BinaryReader reader) @@ -130,9 +127,9 @@ public override IInstruction Parse(BinaryReader reader) //0xFC_08 public class InstMemoryInit : InstructionBase { - public override ByteCode Op => ExtCode.MemoryInit; private DataIdx X; private MemIdx Y; + public override ByteCode Op => ExtCode.MemoryInit; /// /// @Spec 3.3.7.14. memory.init @@ -151,7 +148,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.7.12. memory.init x - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //2. context.Assert( context.Frame.Module.MemAddrs.Contains(Y), @@ -201,7 +198,7 @@ public override int Execute(ExecContext context) throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory overflow."); //17. if (n == 0) - return 1; + return; //18. byte b = data.Data[s]; //19. @@ -247,8 +244,8 @@ public IInstruction Immediate(DataIdx x) //0xFC_09 public class InstDataDrop : InstructionBase { - public override ByteCode Op => ExtCode.DataDrop; private DataIdx X; + public override ByteCode Op => ExtCode.DataDrop; /// /// @Spec 3.3.7.15. data.drop @@ -260,7 +257,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.7.13 - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //2. context.Assert( context.Frame.Module.DataAddrs.Contains(X), @@ -272,7 +269,6 @@ public override int Execute(ExecContext context) $"Instruction {Op.GetMnemonic()} failed. Address for Data {X} was not in the Store."); //5. context.Store.DropData(a); - return 1; } public override IInstruction Parse(BinaryReader reader) @@ -293,9 +289,9 @@ public IInstruction Immediate(DataIdx x) //0xFC_0A public class InstMemoryCopy : InstructionBase { - public override ByteCode Op => ExtCode.MemoryCopy; - private MemIdx SrcX; private MemIdx DstY; + private MemIdx SrcX; + public override ByteCode Op => ExtCode.MemoryCopy; /// /// @Spec 3.3.7.13. memory.copy @@ -313,7 +309,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.7.11. memory.copy - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //2. context.Assert( context.Frame.Module.MemAddrs.Contains(SrcX), @@ -362,7 +358,7 @@ public override int Execute(ExecContext context) $"Instruction {Op.GetMnemonic()} failed. Destination memory overflow."); //13. if (n == 0) - return 1; + return; //14. if (d <= s) { @@ -417,8 +413,8 @@ public override IInstruction Parse(BinaryReader reader) //0xFC_0B public class InstMemoryFill : InstructionBase { - public override ByteCode Op => ExtCode.MemoryFill; private MemIdx X; + public override ByteCode Op => ExtCode.MemoryFill; /// /// @Spec 3.3.7.12. memory.fill @@ -434,7 +430,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.7.10. memory.fill - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //2. context.Assert( context.Frame.Module.MemAddrs.Contains(X), @@ -468,7 +464,7 @@ public override int Execute(ExecContext context) throw new TrapException("Instruction memory.fill failed. Buffer overflow"); //13. if (n == 0) - return 1; + return; //14. context.OpStack.PushU32((uint)d); //15. diff --git a/Wacs.Core/Instructions/Numeric/Const.cs b/Wacs.Core/Instructions/Numeric/Const.cs index fcc9a7d..1ea57d2 100644 --- a/Wacs.Core/Instructions/Numeric/Const.cs +++ b/Wacs.Core/Instructions/Numeric/Const.cs @@ -28,8 +28,10 @@ namespace Wacs.Core.Instructions.Numeric //0x41 public class InstI32Const : InstructionBase, IConstInstruction, ITypedValueProducer { - public override ByteCode Op => OpCode.I32Const; private int Value; + public override ByteCode Op => OpCode.I32Const; + public Func GetFunc => FetchImmediate; + public int CalculateSize() => 1; /// /// @Spec 3.3.1.1 t.const @@ -41,10 +43,9 @@ public override void Validate(IWasmValidationContext context) => /// /// @Spec 4.4.1.1. t.const c /// - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { context.OpStack.PushI32(Value); - return 1; } public override IInstruction Parse(BinaryReader reader) { @@ -59,16 +60,16 @@ public IInstruction Immediate(int value) } public int FetchImmediate(ExecContext context) => Value; - public Func GetFunc => FetchImmediate; - public int CalculateSize() => 1; public override string RenderText(ExecContext? context) => $"{base.RenderText(context)} {Value}"; } //0x42 public class InstI64Const : InstructionBase, IConstInstruction, ITypedValueProducer { - public override ByteCode Op => OpCode.I64Const; private long Value; + public override ByteCode Op => OpCode.I64Const; + public Func GetFunc => FetchImmediate; + public int CalculateSize() => 1; /// /// @Spec 3.3.1.1 t.const @@ -80,19 +81,17 @@ public override void Validate(IWasmValidationContext context) => /// /// @Spec 4.4.1.1. t.const c /// - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { context.OpStack.PushI64(Value); - return 1; } public override IInstruction Parse(BinaryReader reader) { Value = reader.ReadLeb128_s64(); return this; } + public long FetchImmediate(ExecContext context) => Value; - public Func GetFunc => FetchImmediate; - public int CalculateSize() => 1; public override string RenderText(ExecContext? context) => $"{base.RenderText(context)} {Value}"; } @@ -100,8 +99,10 @@ public override IInstruction Parse(BinaryReader reader) { //0x43 public class InstF32Const : InstructionBase, IConstInstruction, ITypedValueProducer { - public override ByteCode Op => OpCode.F32Const; private float Value; + public override ByteCode Op => OpCode.F32Const; + public Func GetFunc => FetchImmediate; + public int CalculateSize() => 1; /// /// @Spec 3.3.1.1 t.const @@ -113,10 +114,9 @@ public override void Validate(IWasmValidationContext context) => /// /// @Spec 4.4.1.1. t.const c /// - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { context.OpStack.PushF32(Value); - return 1; } public override IInstruction Parse(BinaryReader reader) { @@ -125,9 +125,7 @@ public override IInstruction Parse(BinaryReader reader) { } public float FetchImmediate(ExecContext context) => Value; - public Func GetFunc => FetchImmediate; - public int CalculateSize() => 1; - + public override string RenderText(ExecContext? context) { var sourceText = Value.ToString(CultureInfo.InvariantCulture).ToLower(); @@ -142,8 +140,10 @@ public override string RenderText(ExecContext? context) //0x44 public class InstF64Const : InstructionBase, IConstInstruction, ITypedValueProducer { - public override ByteCode Op => OpCode.F64Const; private double Value; + public override ByteCode Op => OpCode.F64Const; + public Func GetFunc => FetchImmediate; + public int CalculateSize() => 1; /// /// @Spec 3.3.1.1 t.const @@ -155,20 +155,17 @@ public override void Validate(IWasmValidationContext context) => /// /// @Spec 4.4.1.1. t.const c /// - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { context.OpStack.PushF64(Value); - return 1; } public override IInstruction Parse(BinaryReader reader) { Value = reader.Read_f64(); return this; } - + public double FetchImmediate(ExecContext context) => Value; - public Func GetFunc => FetchImmediate; - public int CalculateSize() => 1; public override string RenderText(ExecContext? context) { diff --git a/Wacs.Core/Instructions/Numeric/Conversion.cs b/Wacs.Core/Instructions/Numeric/Conversion.cs index 45eda78..9fb63d5 100644 --- a/Wacs.Core/Instructions/Numeric/Conversion.cs +++ b/Wacs.Core/Instructions/Numeric/Conversion.cs @@ -57,25 +57,24 @@ public class InstConvert : InstructionBase public static readonly InstConvert I64ReinterpretF64 = new(OpCode.I64ReinterpretF64 , (Func )ExecuteI64ReinterpretF64, NumericInst.ValidateOperands(pop: ValType.F64, push: ValType.I64)); public static readonly InstConvert F32ReinterpretI32 = new(OpCode.F32ReinterpretI32 , (Func )ExecuteF32ReinterpretI32, NumericInst.ValidateOperands(pop: ValType.I32, push: ValType.F32)); public static readonly InstConvert F64ReinterpretI64 = new(OpCode.F64ReinterpretI64 , (Func )ExecuteF64ReinterpretI64, NumericInst.ValidateOperands(pop: ValType.I64, push: ValType.F64)); + private readonly Executor _executor; - public override ByteCode Op { get; } - private readonly NumericInst.ValidationDelegate _validate; - delegate void Executor(ExecContext context); - private Executor _executor; - + private InstConvert(ByteCode op, Delegate execute, NumericInst.ValidationDelegate validate) { Op = op; _executor = CreateExecutor(execute); _validate = validate; } - + + public override ByteCode Op { get; } + public override void Validate(IWasmValidationContext context) => _validate(context); - public override int Execute(ExecContext context) + + public override void Execute(ExecContext context) { _executor(context); - return 1; } private Executor CreateExecutor(Delegate execute) @@ -112,9 +111,9 @@ private Executor CreateExecutor(Delegate execute) _ => throw new InvalidDataException($"Cannot create delegate from type {execute.GetType()}") }; } - + private static int ExecuteI32WrapI64(long value) => unchecked((int)value); - + private static int ExecuteI32TruncF32S(float value) { if (float.IsNaN(value) || float.IsInfinity(value)) @@ -127,7 +126,7 @@ private static int ExecuteI32TruncF32S(float value) return (int)truncated; } - + private static uint ExecuteI32TruncF32U(float value) { if (float.IsNaN(value) || float.IsInfinity(value)) @@ -140,7 +139,7 @@ private static uint ExecuteI32TruncF32U(float value) return (uint)truncated; } - + private static int ExecuteI32TruncF64S(double value) { if (double.IsNaN(value) || double.IsInfinity(value)) @@ -153,7 +152,7 @@ private static int ExecuteI32TruncF64S(double value) return (int)truncated; } - + private static uint ExecuteI32TruncF64U(double value) { if (double.IsNaN(value) || double.IsInfinity(value)) @@ -166,7 +165,7 @@ private static uint ExecuteI32TruncF64U(double value) return (uint)truncated; } - + private static long ExecuteI64ExtendI32S(int value) => value; private static ulong ExecuteI64ExtendI32U(uint value) => value; @@ -187,7 +186,7 @@ private static long ExecuteI64TruncF32S(float value) return (long)truncated; } - + private static ulong ExecuteI64TruncF32U(float value) { if (float.IsNaN(value) || float.IsInfinity(value)) @@ -202,7 +201,7 @@ private static ulong ExecuteI64TruncF32U(float value) return (ulong)truncated; } - + private static long ExecuteI64TruncF64S(double value) { if (double.IsNaN(value) || double.IsInfinity(value)) @@ -219,7 +218,7 @@ private static long ExecuteI64TruncF64S(double value) return (long)truncated; } - + private static ulong ExecuteI64TruncF64U(double value) { if (double.IsNaN(value) || double.IsInfinity(value)) @@ -234,7 +233,7 @@ private static ulong ExecuteI64TruncF64U(double value) return (ulong)truncated; } - + private static float ExecuteF32ConvertI32S(int value) => value; private static float ExecuteF32ConvertI32U(uint value) => value; @@ -266,5 +265,7 @@ private static float ExecuteF32ReinterpretI32(int value) => private static double ExecuteF64ReinterpretI64(long value) => MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1))[0]; + + delegate void Executor(ExecContext context); } } \ No newline at end of file diff --git a/Wacs.Core/Instructions/Numeric/F32BinOp.cs b/Wacs.Core/Instructions/Numeric/F32BinOp.cs index 1ffa996..ee3fe5d 100644 --- a/Wacs.Core/Instructions/Numeric/F32BinOp.cs +++ b/Wacs.Core/Instructions/Numeric/F32BinOp.cs @@ -52,28 +52,29 @@ public class InstF32BinOp : InstructionBase, INodeComputer public static readonly InstF32BinOp F32Copysign = new(OpCode.F32Copysign, ExecuteF32Copysign, NumericInst.ValidateOperands(pop1: ValType.F32, pop2: ValType.F32, push: ValType.F32)); - public override ByteCode Op { get; } - + private readonly Func _execute; + private readonly NumericInst.ValidationDelegate _validate; - private Func _execute; - + private InstF32BinOp(ByteCode op, Func execute, NumericInst.ValidationDelegate validate) { Op = op; _execute = execute; _validate = validate; } - + + public override ByteCode Op { get; } + public override void Validate(IWasmValidationContext context) => _validate(context); - public override int Execute(ExecContext context) + + public override void Execute(ExecContext context) { float z2 = context.OpStack.PopF32(); float z1 = context.OpStack.PopF32(); float result = _execute(z1, z2); context.OpStack.PushF32(result); - return 1; } - + public Func GetFunc => (_, i1, i2) => _execute(i1, i2); private static float ExecuteF32Add(float z1, float z2) => z1 + z2; @@ -106,6 +107,5 @@ private static float ExecuteF32Copysign(float z1, float z2) // Convert the result bits back to float return MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref resultBits, 1))[0]; } - } } \ No newline at end of file diff --git a/Wacs.Core/Instructions/Numeric/F32RelOp.cs b/Wacs.Core/Instructions/Numeric/F32RelOp.cs index cb44f7c..7ee1c97 100644 --- a/Wacs.Core/Instructions/Numeric/F32RelOp.cs +++ b/Wacs.Core/Instructions/Numeric/F32RelOp.cs @@ -15,7 +15,6 @@ // */ using System; -using System.IO; using Wacs.Core.Instructions.Transpiler; using Wacs.Core.OpCodes; using Wacs.Core.Runtime; @@ -44,30 +43,31 @@ public class InstF32RelOp : InstructionBase, INodeComputer public static readonly InstF32RelOp F32Ge = new(OpCode.F32Ge, ExecuteF32Ge, NumericInst.ValidateOperands(pop1: ValType.F32, pop2: ValType.F32, push: ValType.I32)); - public override ByteCode Op { get; } - + private readonly Func _execute; + private readonly NumericInst.ValidationDelegate _validate; - private Func _execute; - + private InstF32RelOp(ByteCode op, Func execute, NumericInst.ValidationDelegate validate) { Op = op; _execute = execute; _validate = validate; } - + + public override ByteCode Op { get; } + public override void Validate(IWasmValidationContext context) => _validate(context); - public override int Execute(ExecContext context) + + public override void Execute(ExecContext context) { float i2 = context.OpStack.PopF32(); float i1 = context.OpStack.PopF32(); int result = _execute(i1, i2); context.OpStack.PushI32(result); - return 1; } - + public Func GetFunc => (_, i1, i2) => _execute(i1, i2); - + private static int ExecuteF32Eq(float i1, float i2) => i1 == i2 ? 1 : 0; diff --git a/Wacs.Core/Instructions/Numeric/F32UnOp.cs b/Wacs.Core/Instructions/Numeric/F32UnOp.cs index 31c6bf2..c26ec52 100644 --- a/Wacs.Core/Instructions/Numeric/F32UnOp.cs +++ b/Wacs.Core/Instructions/Numeric/F32UnOp.cs @@ -33,28 +33,28 @@ public class InstF32UnOp : InstructionBase, INodeComputer public static readonly InstF32UnOp F32Trunc = new(OpCode.F32Trunc , ExecuteF32Trunc , NumericInst.ValidateOperands(pop: ValType.F32, push: ValType.F32)); public static readonly InstF32UnOp F32Nearest = new(OpCode.F32Nearest , ExecuteF32Nearest , NumericInst.ValidateOperands(pop: ValType.F32, push: ValType.F32)); public static readonly InstF32UnOp F32Sqrt = new(OpCode.F32Sqrt , ExecuteF32Sqrt , NumericInst.ValidateOperands(pop: ValType.F32, push: ValType.F32)); - - public override ByteCode Op { get; } - + private readonly Func _execute; + private readonly NumericInst.ValidationDelegate _validate; - private Func _execute; - + private InstF32UnOp(ByteCode op, Func execute, NumericInst.ValidationDelegate validate) { Op = op; _execute = execute; _validate = validate; } - + + public override ByteCode Op { get; } + public override void Validate(IWasmValidationContext context) => _validate(context); - public override int Execute(ExecContext context) + + public override void Execute(ExecContext context) { float a = context.OpStack.PopF32(); float result = _execute(a); context.OpStack.PushF32(result); - return 1; } - + public Func GetFunc => (_, i1) => _execute(i1); private static float ExecuteF32Abs(float a) => Math.Abs(a); diff --git a/Wacs.Core/Instructions/Numeric/F64BinOp.cs b/Wacs.Core/Instructions/Numeric/F64BinOp.cs index 9af7193..5807ed6 100644 --- a/Wacs.Core/Instructions/Numeric/F64BinOp.cs +++ b/Wacs.Core/Instructions/Numeric/F64BinOp.cs @@ -26,7 +26,6 @@ namespace Wacs.Core.Instructions.Numeric { public class InstF64BinOp : InstructionBase, INodeComputer { - // Mask for the sign bit (most significant bit) private const ulong F64SignMask = 0x8000_0000_0000_0000; private const ulong F64NotSignMask = ~F64SignMask; @@ -54,29 +53,30 @@ public class InstF64BinOp : InstructionBase, INodeComputer public static readonly InstF64BinOp F64Copysign = new(OpCode.F64Copysign, ExecuteF64Copysign, NumericInst.ValidateOperands(pop1: ValType.F64, pop2: ValType.F64, push: ValType.F64)); - - public override ByteCode Op { get; } - + private readonly Func _execute; + private readonly NumericInst.ValidationDelegate _validate; - private Func _execute; - + private InstF64BinOp(ByteCode op, Func execute, NumericInst.ValidationDelegate validate) { Op = op; _execute = execute; _validate = validate; } - + + + public override ByteCode Op { get; } + public override void Validate(IWasmValidationContext context) => _validate(context); - public override int Execute(ExecContext context) + + public override void Execute(ExecContext context) { double z2 = context.OpStack.PopF64(); double z1 = context.OpStack.PopF64(); double result = _execute(z1, z2); context.OpStack.PushF64(result); - return 1; } - + public Func GetFunc => (_, i1, i2) => _execute(i1, i2); private static double ExecuteF64Add(double z1, double z2) => z1 + z2; diff --git a/Wacs.Core/Instructions/Numeric/F64RelOp.cs b/Wacs.Core/Instructions/Numeric/F64RelOp.cs index 338f746..9cce342 100644 --- a/Wacs.Core/Instructions/Numeric/F64RelOp.cs +++ b/Wacs.Core/Instructions/Numeric/F64RelOp.cs @@ -43,29 +43,30 @@ public class InstF64RelOp : InstructionBase, INodeComputer public static readonly InstF64RelOp F64Ge = new(OpCode.F64Ge, ExecuteF64Ge, NumericInst.ValidateOperands(pop1: ValType.F64, pop2: ValType.F64, push: ValType.I32)); - - public override ByteCode Op { get; } - + private readonly Func _execute; + private readonly NumericInst.ValidationDelegate _validate; - private Func _execute; - + private InstF64RelOp(ByteCode op, Func execute, NumericInst.ValidationDelegate validate) { Op = op; _execute = execute; _validate = validate; } - + + + public override ByteCode Op { get; } + public override void Validate(IWasmValidationContext context) => _validate(context); - public override int Execute(ExecContext context) + + public override void Execute(ExecContext context) { double i2 = context.OpStack.PopF64(); double i1 = context.OpStack.PopF64(); int result = _execute(i1, i2); context.OpStack.PushI32(result); - return 1; } - + public Func GetFunc => (_, i1, i2) => _execute(i1, i2); private static int ExecuteF64Eq(double i1, double i2) => i1 == i2 ? 1 : 0; diff --git a/Wacs.Core/Instructions/Numeric/F64UnOp.cs b/Wacs.Core/Instructions/Numeric/F64UnOp.cs index 681910a..37bd21f 100644 --- a/Wacs.Core/Instructions/Numeric/F64UnOp.cs +++ b/Wacs.Core/Instructions/Numeric/F64UnOp.cs @@ -33,28 +33,28 @@ public class InstF64UnOp : InstructionBase, INodeComputer public static readonly InstF64UnOp F64Trunc = new(OpCode.F64Trunc , ExecuteF64Trunc , NumericInst.ValidateOperands(pop: ValType.F64, push: ValType.F64)); public static readonly InstF64UnOp F64Nearest = new(OpCode.F64Nearest , ExecuteF64Nearest , NumericInst.ValidateOperands(pop: ValType.F64, push: ValType.F64)); public static readonly InstF64UnOp F64Sqrt = new(OpCode.F64Sqrt , ExecuteF64Sqrt , NumericInst.ValidateOperands(pop: ValType.F64, push: ValType.F64)); + private readonly Func _execute; - public override ByteCode Op { get; } - private readonly NumericInst.ValidationDelegate _validate; - private Func _execute; - + private InstF64UnOp(ByteCode op, Func execute, NumericInst.ValidationDelegate validate) { Op = op; _execute = execute; _validate = validate; } - + + public override ByteCode Op { get; } + public override void Validate(IWasmValidationContext context) => _validate(context); - public override int Execute(ExecContext context) + + public override void Execute(ExecContext context) { double a = context.OpStack.PopF64(); double result = _execute(a); context.OpStack.PushF64(result); - return 1; } - + public Func GetFunc => (_, i1) => _execute(i1); private static double ExecuteF64Abs(double a) => Math.Abs(a); diff --git a/Wacs.Core/Instructions/Numeric/I32BinOp.cs b/Wacs.Core/Instructions/Numeric/I32BinOp.cs index 46b898a..627f15e 100644 --- a/Wacs.Core/Instructions/Numeric/I32BinOp.cs +++ b/Wacs.Core/Instructions/Numeric/I32BinOp.cs @@ -15,7 +15,6 @@ // */ using System; -using System.IO; using Wacs.Core.Instructions.Transpiler; using Wacs.Core.OpCodes; using Wacs.Core.Runtime; @@ -73,9 +72,7 @@ public abstract class InstI32BinOp : InstructionBase, IConstOpInstruction public static readonly InstI32BinOp I32Rotr = new Mixed(OpCode.I32Rotr, ExecuteI32Rotr, NumericInst.ValidateOperands(pop1: ValType.I32, pop2: ValType.I32, push: ValType.I32)); - public override ByteCode Op { get; } private readonly NumericInst.ValidationDelegate _validate; - public bool IsConstant { get; } private InstI32BinOp(ByteCode op, NumericInst.ValidationDelegate validate, bool isConst = false) @@ -85,60 +82,9 @@ private InstI32BinOp(ByteCode op, NumericInst.ValidationDelegate validate, bool IsConstant = isConst; } - private class Signed : InstI32BinOp, INodeComputer - { - private Func _execute; - public Signed(ByteCode op, Func execute, NumericInst.ValidationDelegate validate, - bool isConst = false) : base(op, validate, isConst) => _execute = execute; - - public override int Execute(ExecContext context) - { - int i2 = context.OpStack.PopI32(); - int i1 = context.OpStack.PopI32(); - int result = _execute(i1, i2); - context.OpStack.PushI32(result); - return 1; - } - - public Func GetFunc => (_, i1, i2) => _execute(i1, i2); - } - - private class Unsigned : InstI32BinOp, INodeComputer - { - private Func _execute; - public Unsigned(ByteCode op, Func execute, NumericInst.ValidationDelegate validate, - bool isConst = false) : base(op, validate, isConst) => _execute = execute; - - public override int Execute(ExecContext context) - { - uint i2 = context.OpStack.PopU32(); - uint i1 = context.OpStack.PopU32(); - uint result = _execute(i1, i2); - context.OpStack.PushU32(result); - return 1; - } - - public Func GetFunc => (_, i1, i2) => _execute(i1, i2); - } - - private class Mixed : InstI32BinOp, INodeComputer - { - private Func _execute; - public Mixed(ByteCode op, Func execute, NumericInst.ValidationDelegate validate, - bool isConst = false) : base(op, validate, isConst) => _execute = execute; + public override ByteCode Op { get; } + public bool IsConstant { get; } - public override int Execute(ExecContext context) - { - int i2 = context.OpStack.PopI32(); - uint i1 = context.OpStack.PopU32(); - uint result = _execute(i1, i2); - context.OpStack.PushU32(result); - return 1; - } - - public Func GetFunc => (_, i1, i2) => _execute(i1, i2); - } - public override void Validate(IWasmValidationContext context) => _validate(context); // @Spec 4.3.2.3. iadd @@ -231,5 +177,59 @@ private static uint ExecuteI32Rotr(uint i1, int i2) int k = i2 & 0x1F; return (i1 >> k) | (i1 << (32 - k)); } + + private class Signed : InstI32BinOp, INodeComputer + { + private readonly Func _execute; + + public Signed(ByteCode op, Func execute, NumericInst.ValidationDelegate validate, + bool isConst = false) : base(op, validate, isConst) => _execute = execute; + + public override void Execute(ExecContext context) + { + int i2 = context.OpStack.PopI32(); + int i1 = context.OpStack.PopI32(); + int result = _execute(i1, i2); + context.OpStack.PushI32(result); + } + + public Func GetFunc => (_, i1, i2) => _execute(i1, i2); + } + + private class Unsigned : InstI32BinOp, INodeComputer + { + private readonly Func _execute; + + public Unsigned(ByteCode op, Func execute, NumericInst.ValidationDelegate validate, + bool isConst = false) : base(op, validate, isConst) => _execute = execute; + + public override void Execute(ExecContext context) + { + uint i2 = context.OpStack.PopU32(); + uint i1 = context.OpStack.PopU32(); + uint result = _execute(i1, i2); + context.OpStack.PushU32(result); + } + + public Func GetFunc => (_, i1, i2) => _execute(i1, i2); + } + + private class Mixed : InstI32BinOp, INodeComputer + { + private readonly Func _execute; + + public Mixed(ByteCode op, Func execute, NumericInst.ValidationDelegate validate, + bool isConst = false) : base(op, validate, isConst) => _execute = execute; + + public override void Execute(ExecContext context) + { + int i2 = context.OpStack.PopI32(); + uint i1 = context.OpStack.PopU32(); + uint result = _execute(i1, i2); + context.OpStack.PushU32(result); + } + + public Func GetFunc => (_, i1, i2) => _execute(i1, i2); + } } } \ No newline at end of file diff --git a/Wacs.Core/Instructions/Numeric/I32RelOp.cs b/Wacs.Core/Instructions/Numeric/I32RelOp.cs index 63654d5..84bce8e 100644 --- a/Wacs.Core/Instructions/Numeric/I32RelOp.cs +++ b/Wacs.Core/Instructions/Numeric/I32RelOp.cs @@ -15,7 +15,6 @@ // */ using System; -using System.IO; using Wacs.Core.Instructions.Transpiler; using Wacs.Core.OpCodes; using Wacs.Core.Runtime; @@ -57,63 +56,63 @@ public abstract class InstI32RelOp : InstructionBase public static readonly InstI32RelOp I32GeU = new Unsigned(OpCode.I32GeU, ExecuteI32GeU, NumericInst.ValidateOperands(pop1: ValType.I32, pop2: ValType.I32, push: ValType.I32)); - public override ByteCode Op { get; } - private readonly NumericInst.ValidationDelegate _validate; - + private InstI32RelOp(ByteCode op, NumericInst.ValidationDelegate validate) { Op = op; _validate = validate; } - + + public override ByteCode Op { get; } + + public override void Validate(IWasmValidationContext context) => _validate(context); + + private static int ExecuteI32Eq(int i1, int i2) => i1 == i2 ? 1 : 0; + private static int ExecuteI32Ne(int i1, int i2) => i1 != i2 ? 1 : 0; + private static int ExecuteI32LtS(int i1, int i2) => i1 < i2 ? 1 : 0; + private static int ExecuteI32LtU(uint i1, uint i2) => i1 < i2 ? 1 : 0; + private static int ExecuteI32GtS(int i1, int i2) => i1 > i2 ? 1 : 0; + private static int ExecuteI32GtU(uint i1, uint i2) => i1 > i2 ? 1 : 0; + private static int ExecuteI32LeS(int i1, int i2) => i1 <= i2 ? 1 : 0; + private static int ExecuteI32LeU(uint i1, uint i2) => i1 <= i2 ? 1 : 0; + private static int ExecuteI32GeS(int i1, int i2) => i1 >= i2 ? 1 : 0; + private static int ExecuteI32GeU(uint i1, uint i2) => i1 >= i2 ? 1 : 0; + private class Signed : InstI32RelOp, INodeComputer { - private Func _execute; + private readonly Func _execute; + public Signed(ByteCode op, Func execute, NumericInst.ValidationDelegate validate) : base(op, validate) => _execute = execute; - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { int i2 = context.OpStack.PopI32(); int i1 = context.OpStack.PopI32(); int result = _execute(i1, i2); context.OpStack.PushI32(result); - return 1; } - + public Func GetFunc => (_, i1, i2) => _execute(i1, i2); } - + private class Unsigned : InstI32RelOp, INodeComputer { - private Func _execute; + private readonly Func _execute; + public Unsigned(ByteCode op, Func execute, NumericInst.ValidationDelegate validate) : base(op, validate) => _execute = execute; - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { uint i2 = context.OpStack.PopU32(); uint i1 = context.OpStack.PopU32(); int result = _execute(i1, i2); context.OpStack.PushI32(result); - return 1; } - + public Func GetFunc => (_, i1, i2) => _execute(i1, i2); } - - public override void Validate(IWasmValidationContext context) => _validate(context); - - private static int ExecuteI32Eq(int i1, int i2) => i1 == i2 ? 1 : 0; - private static int ExecuteI32Ne(int i1, int i2) => i1 != i2 ? 1 : 0; - private static int ExecuteI32LtS(int i1, int i2) => i1 < i2 ? 1 : 0; - private static int ExecuteI32LtU(uint i1, uint i2) => i1 < i2 ? 1 : 0; - private static int ExecuteI32GtS(int i1, int i2) => i1 > i2 ? 1 : 0; - private static int ExecuteI32GtU(uint i1, uint i2) => i1 > i2 ? 1 : 0; - private static int ExecuteI32LeS(int i1, int i2) => i1 <= i2 ? 1 : 0; - private static int ExecuteI32LeU(uint i1, uint i2) => i1 <= i2 ? 1 : 0; - private static int ExecuteI32GeS(int i1, int i2) => i1 >= i2 ? 1 : 0; - private static int ExecuteI32GeU(uint i1, uint i2) => i1 >= i2 ? 1 : 0; } } \ No newline at end of file diff --git a/Wacs.Core/Instructions/Numeric/I32SignExtension.cs b/Wacs.Core/Instructions/Numeric/I32SignExtension.cs index a2a2f5f..b520d31 100644 --- a/Wacs.Core/Instructions/Numeric/I32SignExtension.cs +++ b/Wacs.Core/Instructions/Numeric/I32SignExtension.cs @@ -38,28 +38,29 @@ public class InstI32SignExtend : InstructionBase, INodeComputer public static readonly InstI32SignExtend I32Extend16S = new(OpCode.I32Extend16S, ExecuteI32Extend16S, NumericInst.ValidateOperands(pop: ValType.I32, push: ValType.I32)); - - public override ByteCode Op { get; } - + + private readonly Func _execute; + private readonly NumericInst.ValidationDelegate _validate; - private Func _execute; - + private InstI32SignExtend(ByteCode op, Func execute, NumericInst.ValidationDelegate validate) { Op = op; _execute = execute; _validate = validate; } - + + public override ByteCode Op { get; } + public override void Validate(IWasmValidationContext context) => _validate(context); - public override int Execute(ExecContext context) + + public override void Execute(ExecContext context) { uint value = context.OpStack.PopU32(); uint result = _execute(value); context.OpStack.PushI32((int)result); - return 1; } - + public Func GetFunc => (_, i1) => _execute(i1); private static uint ExecuteI32Extend8S(uint value) => diff --git a/Wacs.Core/Instructions/Numeric/I32UnOp.cs b/Wacs.Core/Instructions/Numeric/I32UnOp.cs index bfcdd25..f6aed0e 100644 --- a/Wacs.Core/Instructions/Numeric/I32UnOp.cs +++ b/Wacs.Core/Instructions/Numeric/I32UnOp.cs @@ -29,30 +29,30 @@ public class InstI32UnOp : InstructionBase, INodeComputer public static readonly InstI32UnOp I32Clz = new(OpCode.I32Clz , ExecuteI32Clz , NumericInst.ValidateOperands(pop: ValType.I32, push: ValType.I32)); public static readonly InstI32UnOp I32Ctz = new(OpCode.I32Ctz , ExecuteI32Ctz , NumericInst.ValidateOperands(pop: ValType.I32, push: ValType.I32)); public static readonly InstI32UnOp I32Popcnt = new(OpCode.I32Popcnt , ExecuteI32Popcnt , NumericInst.ValidateOperands(pop: ValType.I32, push: ValType.I32)); - - public override ByteCode Op { get; } - + private readonly Func _execute; + private readonly NumericInst.ValidationDelegate _validate; - private Func _execute; - + private InstI32UnOp(ByteCode op, Func execute, NumericInst.ValidationDelegate validate) { Op = op; _execute = execute; _validate = validate; } - + + public override ByteCode Op { get; } + public override void Validate(IWasmValidationContext context) => _validate(context); - public override int Execute(ExecContext context) + + public override void Execute(ExecContext context) { uint x = context.OpStack.PopU32(); uint result = _execute(x); context.OpStack.PushU32(result); - return 1; } - + public Func GetFunc => (_, i1) => _execute(i1); - + // @Spec 4.3.2.20 iclz private static uint ExecuteI32Clz(uint x) { @@ -102,7 +102,5 @@ private static uint ExecuteI32Popcnt(uint x) } return count; } - - } } \ No newline at end of file diff --git a/Wacs.Core/Instructions/Numeric/I64BinOp.cs b/Wacs.Core/Instructions/Numeric/I64BinOp.cs index 1bd7d5d..69c7d2d 100644 --- a/Wacs.Core/Instructions/Numeric/I64BinOp.cs +++ b/Wacs.Core/Instructions/Numeric/I64BinOp.cs @@ -72,9 +72,7 @@ public abstract class InstI64BinOp : InstructionBase, IConstOpInstruction public static readonly InstI64BinOp I64Rotr = new Mixed(OpCode.I64Rotr, ExecuteI64Rotr, NumericInst.ValidateOperands(pop1: ValType.I64, pop2: ValType.I64, push: ValType.I64)); - public override ByteCode Op { get; } private readonly NumericInst.ValidationDelegate _validate; - public bool IsConstant { get; } private InstI64BinOp(ByteCode op, NumericInst.ValidationDelegate validate, bool isConst = false) { @@ -83,61 +81,11 @@ private InstI64BinOp(ByteCode op, NumericInst.ValidationDelegate validate, bool IsConstant = isConst; } - private class Signed : InstI64BinOp, INodeComputer - { - private Func _execute; - public Signed(ByteCode op, Func execute, NumericInst.ValidationDelegate validate, - bool isConst = false) : base(op, validate, isConst) => _execute = execute; - - public override int Execute(ExecContext context) - { - long i2 = context.OpStack.PopI64(); - long i1 = context.OpStack.PopI64(); - long result = _execute(i1, i2); - context.OpStack.PushI64(result); - return 1; - } - public Func GetFunc => (_, i1, i2) => _execute(i1, i2); - } - - private class Unsigned : InstI64BinOp, INodeComputer - { - private Func _execute; - public Unsigned(ByteCode op, Func execute, NumericInst.ValidationDelegate validate, - bool isConst = false) : base(op, validate, isConst) => _execute = execute; - - public override int Execute(ExecContext context) - { - ulong i2 = context.OpStack.PopU64(); - ulong i1 = context.OpStack.PopU64(); - ulong result = _execute(i1, i2); - context.OpStack.PushU64(result); - return 1; - } - - public Func GetFunc => (_, i1, i2) => _execute(i1, i2); - } - - private class Mixed : InstI64BinOp, INodeComputer - { - private Func _execute; - public Mixed(ByteCode op, Func execute, NumericInst.ValidationDelegate validate, - bool isConst = false) : base(op, validate, isConst) => _execute = execute; + public override ByteCode Op { get; } + public bool IsConstant { get; } - public override int Execute(ExecContext context) - { - long i2 = context.OpStack.PopI64(); - ulong i1 = context.OpStack.PopU64(); - ulong result = _execute(i1, i2); - context.OpStack.PushU64(result); - return 1; - } - - public Func GetFunc => (_, i1, i2) => _execute(i1, i2); - } - public override void Validate(IWasmValidationContext context) => _validate(context); - + private static long ExecuteI64Add(long i1, long i2) => i1 + i2; private static long ExecuteI64Sub(long i1, long i2) => i1 - i2; @@ -202,5 +150,59 @@ private static ulong ExecuteI64Rotr(ulong i1, long i2) int k = (int)i2 & 0x3F; return (i1 >> k) | (i1 << (64 - k)); } + + private class Signed : InstI64BinOp, INodeComputer + { + private readonly Func _execute; + + public Signed(ByteCode op, Func execute, NumericInst.ValidationDelegate validate, + bool isConst = false) : base(op, validate, isConst) => _execute = execute; + + public override void Execute(ExecContext context) + { + long i2 = context.OpStack.PopI64(); + long i1 = context.OpStack.PopI64(); + long result = _execute(i1, i2); + context.OpStack.PushI64(result); + } + + public Func GetFunc => (_, i1, i2) => _execute(i1, i2); + } + + private class Unsigned : InstI64BinOp, INodeComputer + { + private readonly Func _execute; + + public Unsigned(ByteCode op, Func execute, NumericInst.ValidationDelegate validate, + bool isConst = false) : base(op, validate, isConst) => _execute = execute; + + public override void Execute(ExecContext context) + { + ulong i2 = context.OpStack.PopU64(); + ulong i1 = context.OpStack.PopU64(); + ulong result = _execute(i1, i2); + context.OpStack.PushU64(result); + } + + public Func GetFunc => (_, i1, i2) => _execute(i1, i2); + } + + private class Mixed : InstI64BinOp, INodeComputer + { + private readonly Func _execute; + + public Mixed(ByteCode op, Func execute, NumericInst.ValidationDelegate validate, + bool isConst = false) : base(op, validate, isConst) => _execute = execute; + + public override void Execute(ExecContext context) + { + long i2 = context.OpStack.PopI64(); + ulong i1 = context.OpStack.PopU64(); + ulong result = _execute(i1, i2); + context.OpStack.PushU64(result); + } + + public Func GetFunc => (_, i1, i2) => _execute(i1, i2); + } } } \ No newline at end of file diff --git a/Wacs.Core/Instructions/Numeric/I64RelOp.cs b/Wacs.Core/Instructions/Numeric/I64RelOp.cs index 93ef175..7cf6343 100644 --- a/Wacs.Core/Instructions/Numeric/I64RelOp.cs +++ b/Wacs.Core/Instructions/Numeric/I64RelOp.cs @@ -56,69 +56,72 @@ public abstract class InstI64RelOp : InstructionBase public static readonly InstI64RelOp I64GeU = new Unsigned(OpCode.I64GeU, ExecuteI64GeU, NumericInst.ValidateOperands(pop1: ValType.I64, pop2: ValType.I64, push: ValType.I32)); - public override ByteCode Op { get; } private readonly NumericInst.ValidationDelegate _validate; + private InstI64RelOp(ByteCode op, NumericInst.ValidationDelegate validate) { Op = op; _validate = validate; } + public override ByteCode Op { get; } + + public override void Validate(IWasmValidationContext context) => _validate(context); + + private static int ExecuteI64Eq(long i1, long i2) => i1 == i2 ? 1 : 0; + + private static int ExecuteI64Ne(long i1, long i2) => i1 != i2 ? 1 : 0; + + private static int ExecuteI64LtS(long i1, long i2) => i1 < i2 ? 1 : 0; + + private static int ExecuteI64LtU(ulong i1, ulong i2) => i1 < i2 ? 1 : 0; + + private static int ExecuteI64GtS(long i1, long i2) => i1 > i2 ? 1 : 0; + + private static int ExecuteI64GtU(ulong i1, ulong i2) => i1 > i2 ? 1 : 0; + + private static int ExecuteI64LeS(long i1, long i2) => i1 <= i2 ? 1 : 0; + + private static int ExecuteI64LeU(ulong i1, ulong i2) => i1 <= i2 ? 1 : 0; + + private static int ExecuteI64GeS(long i1, long i2) => i1 >= i2 ? 1 : 0; + + private static int ExecuteI64GeU(ulong i1, ulong i2) => i1 >= i2 ? 1 : 0; + private class Signed : InstI64RelOp, INodeComputer { - private Func _execute; + private readonly Func _execute; + public Signed(ByteCode op, Func execute, NumericInst.ValidationDelegate validate) : base(op, validate) => _execute = execute; - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { long i2 = context.OpStack.PopI64(); long i1 = context.OpStack.PopI64(); int result = _execute(i1, i2); context.OpStack.PushI32(result); - return 1; } - + public Func GetFunc => (_, i1, i2) => _execute(i1, i2); } - + private class Unsigned : InstI64RelOp, INodeComputer { - private Func _execute; + private readonly Func _execute; + public Unsigned(ByteCode op, Func execute, NumericInst.ValidationDelegate validate) : base(op, validate) => _execute = execute; - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { ulong i2 = context.OpStack.PopU64(); ulong i1 = context.OpStack.PopU64(); int result = _execute(i1, i2); context.OpStack.PushI32(result); - return 1; } + public Func GetFunc => (_, i1, i2) => _execute(i1, i2); } - - public override void Validate(IWasmValidationContext context) => _validate(context); - - private static int ExecuteI64Eq(long i1, long i2) => i1 == i2 ? 1 : 0; - - private static int ExecuteI64Ne(long i1, long i2) => i1 != i2 ? 1 : 0; - - private static int ExecuteI64LtS(long i1, long i2) => i1 < i2 ? 1 : 0; - - private static int ExecuteI64LtU(ulong i1, ulong i2) => i1 < i2 ? 1 : 0; - - private static int ExecuteI64GtS(long i1, long i2) => i1 > i2 ? 1 : 0; - - private static int ExecuteI64GtU(ulong i1, ulong i2) => i1 > i2 ? 1 : 0; - - private static int ExecuteI64LeS(long i1, long i2) => i1 <= i2 ? 1 : 0; - - private static int ExecuteI64LeU(ulong i1, ulong i2) => i1 <= i2 ? 1 : 0; - - private static int ExecuteI64GeS(long i1, long i2) => i1 >= i2 ? 1 : 0; - - private static int ExecuteI64GeU(ulong i1, ulong i2) => i1 >= i2 ? 1 : 0; } } \ No newline at end of file diff --git a/Wacs.Core/Instructions/Numeric/I64SignExtension.cs b/Wacs.Core/Instructions/Numeric/I64SignExtension.cs index 2087f0f..dbc566e 100644 --- a/Wacs.Core/Instructions/Numeric/I64SignExtension.cs +++ b/Wacs.Core/Instructions/Numeric/I64SignExtension.cs @@ -46,30 +46,31 @@ public class InstI64SignExtend : InstructionBase, INodeComputer public static readonly InstI64SignExtend I64Extend32S = new(OpCode.I64Extend32S, ExecuteI64Extend32S, NumericInst.ValidateOperands(pop: ValType.I64, push: ValType.I64)); - - public override ByteCode Op { get; } - + + private readonly Func _execute; + private readonly NumericInst.ValidationDelegate _validate; - private Func _execute; - + private InstI64SignExtend(ByteCode op, Func execute, NumericInst.ValidationDelegate validate) { Op = op; _execute = execute; _validate = validate; } - + + public override ByteCode Op { get; } + public override void Validate(IWasmValidationContext context) => _validate(context); - public override int Execute(ExecContext context) + + public override void Execute(ExecContext context) { uint value = context.OpStack.PopU32(); ulong result = _execute(value); context.OpStack.PushI64((long)result); - return 1; } - + public Func GetFunc => (_, i1) => _execute(i1); - + private static ulong ExecuteI64Extend8S(uint value) => (value & ByteSign) != 0 ? I64ByteExtend | value diff --git a/Wacs.Core/Instructions/Numeric/I64UnOp.cs b/Wacs.Core/Instructions/Numeric/I64UnOp.cs index bccb3e0..a40b3c8 100644 --- a/Wacs.Core/Instructions/Numeric/I64UnOp.cs +++ b/Wacs.Core/Instructions/Numeric/I64UnOp.cs @@ -29,28 +29,28 @@ public class InstI64UnOp : InstructionBase, INodeComputer public static readonly InstI64UnOp I64Clz = new(OpCode.I64Clz , ExecuteI64Clz , NumericInst.ValidateOperands(pop: ValType.I64, push: ValType.I64)); public static readonly InstI64UnOp I64Ctz = new(OpCode.I64Ctz , ExecuteI64Ctz , NumericInst.ValidateOperands(pop: ValType.I64, push: ValType.I64)); public static readonly InstI64UnOp I64Popcnt = new(OpCode.I64Popcnt , ExecuteI64Popcnt , NumericInst.ValidateOperands(pop: ValType.I64, push: ValType.I64)); - - public override ByteCode Op { get; } - + private readonly Func _execute; + private readonly NumericInst.ValidationDelegate _validate; - private Func _execute; - + private InstI64UnOp(ByteCode op, Func execute, NumericInst.ValidationDelegate validate) { Op = op; _execute = execute; _validate = validate; } - + + public override ByteCode Op { get; } + public override void Validate(IWasmValidationContext context) => _validate(context); - public override int Execute(ExecContext context) + + public override void Execute(ExecContext context) { ulong x = context.OpStack.PopU64(); ulong result = _execute(x); context.OpStack.PushU64(result); - return 1; } - + public Func GetFunc => (_, i1) => _execute(i1); // @Spec 4.3.2.20 iclz diff --git a/Wacs.Core/Instructions/Numeric/ITestOp.cs b/Wacs.Core/Instructions/Numeric/ITestOp.cs index edd48d6..6264b3a 100644 --- a/Wacs.Core/Instructions/Numeric/ITestOp.cs +++ b/Wacs.Core/Instructions/Numeric/ITestOp.cs @@ -27,60 +27,60 @@ public class InstI32TestOp : InstructionBase, INodeComputer { // @Spec 3.3.1.4 i.testop public static readonly InstI32TestOp I32Eqz = new(OpCode.I32Eqz, ExecuteI32Eqz, NumericInst.ValidateOperands(pop: ValType.I32, push: ValType.I32)); - - public override ByteCode Op { get; } + private readonly Func _execute; private readonly NumericInst.ValidationDelegate _validate; - private Func _execute; - + private InstI32TestOp(ByteCode op, Func execute, NumericInst.ValidationDelegate validate) { Op = op; _execute = execute; _validate = validate; } - + + public override ByteCode Op { get; } + public override void Validate(IWasmValidationContext context) => _validate(context); - public override int Execute(ExecContext context) + + public override void Execute(ExecContext context) { int i = context.OpStack.PopI32(); int result = _execute(i); context.OpStack.PushI32(result); - return 1; } - + + public Func GetFunc => (_, i1) => _execute(i1); + // @Spec 4.6.1.4. t.testop private static int ExecuteI32Eqz(int i) => i == 0 ? 1 : 0; - - public Func GetFunc => (_, i1) => _execute(i1); } public class InstI64TestOp : InstructionBase, INodeComputer { - public static readonly InstI64TestOp I64Eqz = new(OpCode.I64Eqz, ExecuteI64Eqz, NumericInst.ValidateOperands(pop: ValType.I64, push: ValType.I32)); - public override ByteCode Op { get; } - + private readonly Func _execute; + private readonly NumericInst.ValidationDelegate _validate; - private Func _execute; - + private InstI64TestOp(ByteCode op, Func execute, NumericInst.ValidationDelegate validate) { Op = op; _execute = execute; _validate = validate; } - + + public override ByteCode Op { get; } + public override void Validate(IWasmValidationContext context) => _validate(context); - public override int Execute(ExecContext context) + + public override void Execute(ExecContext context) { long i = context.OpStack.PopI64(); int result = _execute(i); context.OpStack.PushI32(result); - return 1; } - - private static int ExecuteI64Eqz(long i) => i == 0 ? 1 : 0; - + public Func GetFunc => (_, i1) => _execute(i1); + + private static int ExecuteI64Eqz(long i) => i == 0 ? 1 : 0; } } \ No newline at end of file diff --git a/Wacs.Core/Instructions/Numeric/NumericInst.cs b/Wacs.Core/Instructions/Numeric/NumericInst.cs index 2661c49..7685d6d 100644 --- a/Wacs.Core/Instructions/Numeric/NumericInst.cs +++ b/Wacs.Core/Instructions/Numeric/NumericInst.cs @@ -23,24 +23,26 @@ namespace Wacs.Core.Instructions.Numeric { public partial class NumericInst : InstructionBase, IConstOpInstruction { + public delegate void ExecuteDelegate(ExecContext context); + + public delegate void ValidationDelegate(IWasmValidationContext context); + private readonly ExecuteDelegate _execute; private readonly ValidationDelegate _validate; - private readonly bool _isConst; - private NumericInst(ByteCode op, ExecuteDelegate execute, ValidationDelegate validate, bool isConst = false) => - (Op, _execute, _validate, _isConst) = (op, execute, validate, isConst); - - public bool IsConstant => _isConst; + (Op, _execute, _validate, IsConstant) = (op, execute, validate, isConst); public override ByteCode Op { get; } + public bool IsConstant { get; } + public override void Validate(IWasmValidationContext context) => _validate(context); - public override int Execute(ExecContext context) + + public override void Execute(ExecContext context) { _execute(context); - return 1; } public override string RenderText(ExecContext? context) @@ -80,9 +82,5 @@ public static ValidationDelegate ValidateOperands(ValType pop1, ValType pop2, Va context.OpStack.PopType(pop1); context.OpStack.PushType(push); }; - - public delegate void ExecuteDelegate(ExecContext context); - - public delegate void ValidationDelegate(IWasmValidationContext context); } } \ No newline at end of file diff --git a/Wacs.Core/Instructions/Parametric.cs b/Wacs.Core/Instructions/Parametric.cs index 23b9a9a..d277039 100644 --- a/Wacs.Core/Instructions/Parametric.cs +++ b/Wacs.Core/Instructions/Parametric.cs @@ -44,10 +44,9 @@ public override void Validate(IWasmValidationContext context) /// /// @Spec 4.4.4.1. drop /// - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { - Value _ = context.OpStack.PopAny(); - return 1; + context.OpStack.PopAny(); } } @@ -56,12 +55,12 @@ public class InstSelect : InstructionBase { public static readonly InstSelect InstWithoutTypes = new(); - public InstSelect(bool withTypes = false) => WithTypes = withTypes; - public override ByteCode Op => OpCode.Select; - private readonly bool WithTypes; private ValType[] Types = Array.Empty(); + public InstSelect(bool withTypes = false) => WithTypes = withTypes; + public override ByteCode Op => OpCode.Select; + /// /// @Spec 3.3.4.2. select /// @Spec Appendix A.3 #validation-of-opcode-sequencesβ‘  @@ -107,13 +106,12 @@ public override void Validate(IWasmValidationContext context) /// /// @Spec 4.4.4.2. select /// - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { int c = context.OpStack.PopI32(); Value val2 = context.OpStack.PopAny(); Value val1 = context.OpStack.PopAny(); context.OpStack.PushValue(c != 0 ? val1 : val2); - return 1; } public override IInstruction Parse(BinaryReader reader) diff --git a/Wacs.Core/Instructions/Reference.cs b/Wacs.Core/Instructions/Reference.cs index 3d92c47..3174668 100644 --- a/Wacs.Core/Instructions/Reference.cs +++ b/Wacs.Core/Instructions/Reference.cs @@ -25,9 +25,8 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.2.1. ref.null t - public override int Execute(ExecContext context) { + public override void Execute(ExecContext context) { context.OpStack.PushRef(Value.RefNull(Type)); - return 1; } public override IInstruction Parse(BinaryReader reader) @@ -52,14 +51,13 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.2.2. ref.is_null - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { context.Assert( context.OpStack.Peek().IsRef, $"Instruction ref.is_null failed. Expected reftype on top of the stack."); Value val = context.OpStack.PopRefType(); int booleanResult = val.IsNullRef ? 1 : 0; context.OpStack.PushI32(booleanResult); - return 1; } public static readonly InstRefIsNull Inst = new InstRefIsNull(); @@ -87,13 +85,12 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.2.3. ref.func x - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { context.Assert( context.Frame?.Module.FuncAddrs.Contains(FunctionIndex), $"Instruction ref.func failed. Could not find function address in the context"); var a = context.Frame!.Module.FuncAddrs[FunctionIndex]; context.OpStack.PushFuncref(new Value(ValType.Funcref, a.Value)); - return 1; } public override IInstruction Parse(BinaryReader reader) diff --git a/Wacs.Core/Instructions/Runtime.cs b/Wacs.Core/Instructions/Runtime.cs index 4d65e96..dc74677 100644 --- a/Wacs.Core/Instructions/Runtime.cs +++ b/Wacs.Core/Instructions/Runtime.cs @@ -30,11 +30,10 @@ public override void Validate(IWasmValidationContext context) throw new ValidationException($"This instruction should never be present in modules."); } - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //Notify the runtime? context.RewindSequence(); - return 1; } } } \ No newline at end of file diff --git a/Wacs.Core/Instructions/SIMD/VConst.cs b/Wacs.Core/Instructions/SIMD/VConst.cs index 97b72f1..4ecc6cc 100644 --- a/Wacs.Core/Instructions/SIMD/VConst.cs +++ b/Wacs.Core/Instructions/SIMD/VConst.cs @@ -24,8 +24,8 @@ namespace Wacs.Core.Instructions.Simd //0x41 public class InstV128Const : InstructionBase, IConstInstruction { - public override ByteCode Op => SimdCode.V128Const; private V128 V128; + public override ByteCode Op => SimdCode.V128Const; /// /// @Spec 3.3.1.1 t.const @@ -37,10 +37,9 @@ public override void Validate(IWasmValidationContext context) => /// /// @Spec 4.4.1.1. t.const c /// - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { context.OpStack.PushV128(V128); - return 1; } public override IInstruction Parse(BinaryReader reader) diff --git a/Wacs.Core/Instructions/SIMD/VLaneOp.cs b/Wacs.Core/Instructions/SIMD/VLaneOp.cs index c7cd6bf..f0f3778 100644 --- a/Wacs.Core/Instructions/SIMD/VLaneOp.cs +++ b/Wacs.Core/Instructions/SIMD/VLaneOp.cs @@ -29,12 +29,12 @@ public class InstLaneOp : InstructionBase { private readonly ExecuteDelegate _execute; private readonly ValidationDelegate _validate; + private LaneIdx X; private InstLaneOp(ByteCode op, ExecuteDelegate execute, ValidationDelegate validate) => (Op, _execute, _validate) = (op, execute, validate); public override ByteCode Op { get; } - private LaneIdx X; public static InstLaneOp I8x16ExtractLaneS() => new(SimdCode.I8x16ExtractLaneS, ExecuteI8x16ExtractLaneS, ValidateFromLane(V128Shape.I8x16)); public static InstLaneOp I8x16ExtractLaneU() => new(SimdCode.I8x16ExtractLaneU, ExecuteI8x16ExtractLaneU, ValidateFromLane(V128Shape.I8x16)); public static InstLaneOp I16x8ExtractLaneS() => new(SimdCode.I16x8ExtractLaneS, ExecuteI16x8ExtractLaneS, ValidateFromLane(V128Shape.I16x8)); @@ -156,10 +156,10 @@ public static void ExecuteF64x2ReplaceLane(ExecContext context, LaneIdx laneidx) } public override void Validate(IWasmValidationContext context) => _validate(context, Op, X); - public override int Execute(ExecContext context) + + public override void Execute(ExecContext context) { _execute(context, X); - return 1; } public override IInstruction Parse(BinaryReader reader) diff --git a/Wacs.Core/Instructions/SIMD/VMemory.cs b/Wacs.Core/Instructions/SIMD/VMemory.cs index f365044..bc7e92c 100644 --- a/Wacs.Core/Instructions/SIMD/VMemory.cs +++ b/Wacs.Core/Instructions/SIMD/VMemory.cs @@ -27,6 +27,11 @@ namespace Wacs.Core.Instructions.SIMD { public class InstMemoryLoadMxN : InstructionBase { + private readonly int CountN; + + private readonly BitWidth WidthT; + private MemArg M; + public InstMemoryLoadMxN(BitWidth width, int count) => (WidthT, CountN) = (width, count); @@ -53,10 +58,6 @@ public InstMemoryLoadMxN(BitWidth width, int count) => _ => throw new InvalidDataException($"InstMemoryLoadMxN instruction is malformed: {WidthT}x{CountN}"), }; - private readonly BitWidth WidthT; - private readonly int CountN; - private MemArg M; - /// /// @Spec 3.3.7.5. v128.loadNxM_sx memarg /// @@ -72,7 +73,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.7.2. v128.loadMxN_sx memarg - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //2. context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), @@ -117,7 +118,6 @@ public override int Execute(ExecContext context) } //15. context.OpStack.PushV128((V128)c); - return 1; } public IInstruction Immediate(MemArg m) @@ -148,6 +148,9 @@ public override string RenderText(ExecContext? context) public class InstMemoryLoadSplat : InstructionBase { + private readonly BitWidth WidthN; + + private MemArg M; public InstMemoryLoadSplat(BitWidth width) => WidthN = width; public override ByteCode Op => WidthN switch @@ -159,10 +162,6 @@ public class InstMemoryLoadSplat : InstructionBase _ => throw new InvalidDataException($"InstMemoryLoad instruction is malformed: {WidthN}"), }; - private readonly BitWidth WidthN; - - private MemArg M; - /// /// @Spec 3.3.7.6. v128.loadN_splat /// @@ -178,7 +177,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.7.3. v128.loadN_splat - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //2. context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), @@ -223,7 +222,6 @@ public override int Execute(ExecContext context) break; default: throw new ArgumentOutOfRangeException(); } - return 1; } public IInstruction Immediate(MemArg m) @@ -254,6 +252,9 @@ public override string RenderText(ExecContext? context) public class InstMemoryLoadZero : InstructionBase { + private readonly BitWidth WidthN; + + private MemArg M; public InstMemoryLoadZero(BitWidth width) => WidthN = width; public override ByteCode Op => WidthN switch @@ -265,10 +266,6 @@ public class InstMemoryLoadZero : InstructionBase _ => throw new InvalidDataException($"InstMemoryLoad instruction is malformed: {WidthN}"), }; - private readonly BitWidth WidthN; - - private MemArg M; - /// /// @Spec 3.3.7.7. v128.loadN_zero memarg /// @@ -284,7 +281,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.7.7. v128.loadN_zero memarg - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //2. context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), @@ -321,7 +318,6 @@ public override int Execute(ExecContext context) break; default: throw new ArgumentOutOfRangeException(); } - return 1; } public IInstruction Immediate(MemArg m, LaneIdx l) @@ -352,6 +348,11 @@ public override string RenderText(ExecContext? context) public class InstMemoryLoadLane : InstructionBase { + private readonly BitWidth WidthN; + + private MemArg M; + + private LaneIdx X; public InstMemoryLoadLane(BitWidth width) => WidthN = width; public override ByteCode Op => WidthN switch @@ -363,12 +364,6 @@ public class InstMemoryLoadLane : InstructionBase _ => throw new InvalidDataException($"InstMemoryLoad instruction is malformed: {WidthN}"), }; - private BitWidth WidthN; - - private MemArg M; - - private LaneIdx X; - /// /// @Spec 3.3.7.8. v128.loadN_lane memarge laneidx /// @@ -387,7 +382,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.7.5. v128.loadN_lane memarg x - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //2. @@ -428,7 +423,6 @@ public override int Execute(ExecContext context) } //17. context.OpStack.PushV128(value); - return 1; } public IInstruction Immediate(MemArg m, LaneIdx l) @@ -461,6 +455,10 @@ public override string RenderText(ExecContext? context) public class InstMemoryStoreLane : InstructionBase { + private readonly BitWidth WidthN; + private MemArg M; + + private LaneIdx X; public InstMemoryStoreLane(BitWidth width) => WidthN = width; public override ByteCode Op => WidthN switch @@ -472,11 +470,6 @@ public class InstMemoryStoreLane : InstructionBase _ => throw new InvalidDataException($"InstMemoryLoad instruction is malformed: {WidthN}"), }; - private readonly BitWidth WidthN; - private MemArg M; - - private LaneIdx X; - public IInstruction Immediate(MemArg m) { M = m; @@ -499,7 +492,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.7.7. v128.storeN_lane memarg x - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //2. context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), @@ -550,7 +543,6 @@ public override int Execute(ExecContext context) cU64.CopyTo(bs); break; } - return 1; } public override IInstruction Parse(BinaryReader reader) diff --git a/Wacs.Core/Instructions/SIMD/ViShuffleOp.cs b/Wacs.Core/Instructions/SIMD/ViShuffleOp.cs index a29dcdd..397c012 100644 --- a/Wacs.Core/Instructions/SIMD/ViShuffleOp.cs +++ b/Wacs.Core/Instructions/SIMD/ViShuffleOp.cs @@ -44,7 +44,7 @@ public override void Validate(IWasmValidationContext context) /// @Spec 4.4.3.7. i8x16.shuffle x /// /// - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { V128 b = context.OpStack.PopV128(); V128 a = context.OpStack.PopV128(); @@ -55,7 +55,6 @@ public override int Execute(ExecContext context) result[i] = laneIndex < 16 ? a[laneIndex] : b[(byte)(laneIndex - 16)]; } context.OpStack.PushV128(result); - return 1; } public static V128 ParseLanes(BinaryReader reader) => diff --git a/Wacs.Core/Instructions/Table.cs b/Wacs.Core/Instructions/Table.cs index 608feaf..147420c 100644 --- a/Wacs.Core/Instructions/Table.cs +++ b/Wacs.Core/Instructions/Table.cs @@ -29,8 +29,8 @@ namespace Wacs.Core.Instructions // 0x25 public class InstTableGet : InstructionBase { - public override ByteCode Op => OpCode.TableGet; private TableIdx X; + public override ByteCode Op => OpCode.TableGet; // @Spec 3.3.6.1. table.get public override void Validate(IWasmValidationContext context) @@ -43,9 +43,9 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.6.1. table.get - public override int Execute(ExecContext context) => ExecuteInstruction(context, X); + public override void Execute(ExecContext context) => ExecuteInstruction(context, X); - public static int ExecuteInstruction(ExecContext context, TableIdx tableIndex) + public static void ExecuteInstruction(ExecContext context, TableIdx tableIndex) { //2. context.Assert( context.Frame.Module.TableAddrs.Contains(tableIndex), @@ -73,7 +73,6 @@ public static int ExecuteInstruction(ExecContext context, TableIdx tableIndex) var val = tab.Elements[(int)i]; //10. context.OpStack.PushValue(val); - return 1; } // @Spec 5.4.5. Table Instructions @@ -89,8 +88,8 @@ public override IInstruction Parse(BinaryReader reader) // 0x26 public class InstTableSet : InstructionBase { - public override ByteCode Op => OpCode.TableSet; private TableIdx X; + public override ByteCode Op => OpCode.TableSet; // @Spec 3.3.6.2. table.set public override void Validate(IWasmValidationContext context) @@ -103,9 +102,9 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.6.2. table.set - public override int Execute(ExecContext context) => ExecuteInstruction(context, X); + public override void Execute(ExecContext context) => ExecuteInstruction(context, X); - public static int ExecuteInstruction(ExecContext context, TableIdx tableIndex) + public static void ExecuteInstruction(ExecContext context, TableIdx tableIndex) { //2. context.Assert( context.Frame.Module.TableAddrs.Contains(tableIndex), @@ -136,7 +135,6 @@ public static int ExecuteInstruction(ExecContext context, TableIdx tableIndex) //11. tab.Elements[(int)i] = val; - return 1; } // @Spec 5.4.5. Table Instructions @@ -152,9 +150,9 @@ public override IInstruction Parse(BinaryReader reader) // 0xFC0C public class InstTableInit : InstructionBase { - public override ByteCode Op => ExtCode.TableInit; private TableIdx X; private ElemIdx Y; + public override ByteCode Op => ExtCode.TableInit; // @Spec 3.3.6.7. table.init x y public override void Validate(IWasmValidationContext context) @@ -173,7 +171,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.6.7. table.init x y - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //2. context.Assert( context.Frame.Module.TableAddrs.Contains(X), @@ -219,7 +217,7 @@ public override int Execute(ExecContext context) } else if (n == 0) { - return 1; + return; } //18. @@ -268,8 +266,8 @@ public IInstruction Immediate(TableIdx x, ElemIdx y) // 0xFC0F public class InstElemDrop : InstructionBase { - public override ByteCode Op => ExtCode.ElemDrop; private ElemIdx X; + public override ByteCode Op => ExtCode.ElemDrop; // @Spec 3.3.6.8. elem.drop x public override void Validate(IWasmValidationContext context) @@ -279,7 +277,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.6.8. elem.drop x - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //2. context.Assert( context.Frame.Module.ElemAddrs.Contains(X), @@ -291,7 +289,6 @@ public override int Execute(ExecContext context) $"Instruction elem.drop failed. Element {a} was not in the Store."); //5. context.Store.DropElement(a); - return 1; } // @Spec 5.4.5. Table Instructions @@ -313,9 +310,9 @@ public IInstruction Immediate(ElemIdx value) // 0xFC0E public class InstTableCopy : InstructionBase { - public override ByteCode Op => ExtCode.TableCopy; - private TableIdx SrcY; private TableIdx DstX; + private TableIdx SrcY; + public override ByteCode Op => ExtCode.TableCopy; // @Spec 3.3.6.6. table.copy public override void Validate(IWasmValidationContext context) @@ -334,7 +331,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.6.6. table.copy - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //2. context.Assert( context.Frame.Module.TableAddrs.Contains(DstX), @@ -383,7 +380,7 @@ public override int Execute(ExecContext context) //17. else if (n == 0) { - return 1; + return; } //18. @@ -439,8 +436,8 @@ public override IInstruction Parse(BinaryReader reader) // 0xFC0F public class InstTableGrow : InstructionBase { - public override ByteCode Op => ExtCode.TableGrow; private TableIdx X; + public override ByteCode Op => ExtCode.TableGrow; // @Spec 3.3.6.4. table.grow x public override void Validate(IWasmValidationContext context) @@ -454,7 +451,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.6.4. table.grow x - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //2. context.Assert( context.Frame.Module.TableAddrs.Contains(X), @@ -490,7 +487,6 @@ public override int Execute(ExecContext context) const int err = -1; context.OpStack.PushI32(err); } - return 1; } // @Spec 5.4.5. Table Instructions @@ -506,8 +502,8 @@ public override IInstruction Parse(BinaryReader reader) // 0xFC10 public class InstTableSize : InstructionBase { - public override ByteCode Op => ExtCode.TableSize; private TableIdx X; + public override ByteCode Op => ExtCode.TableSize; // @Spec 3.3.6.3. table.size x public override void Validate(IWasmValidationContext context) @@ -518,7 +514,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.6.3. table.size x - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //2. context.Assert( context.Frame.Module.TableAddrs.Contains(X), @@ -535,7 +531,6 @@ public override int Execute(ExecContext context) int sz = tab.Elements.Count; //7. context.OpStack.PushI32(sz); - return 1; } // @Spec 5.4.5. Table Instructions @@ -551,8 +546,8 @@ public override IInstruction Parse(BinaryReader reader) // 0xFC11 public class InstTableFill : InstructionBase { - public override ByteCode Op => ExtCode.TableFill; private TableIdx X; + public override ByteCode Op => ExtCode.TableFill; // @Spec 3.3.6.5. table.fill public override void Validate(IWasmValidationContext context) @@ -566,7 +561,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.6.5. table.fill - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //2. context.Assert( context.Frame.Module.TableAddrs.Contains(X), @@ -605,7 +600,7 @@ public override int Execute(ExecContext context) } else if (n == 0) { - return 1; + return; } //13. diff --git a/Wacs.Core/Instructions/TailCall.cs b/Wacs.Core/Instructions/TailCall.cs index f81371e..f9852f7 100644 --- a/Wacs.Core/Instructions/TailCall.cs +++ b/Wacs.Core/Instructions/TailCall.cs @@ -30,13 +30,14 @@ namespace Wacs.Core.Instructions { public class InstReturnCall : InstructionBase, ICallInstruction { + public FuncIdx X; + public InstReturnCall() { IsAsync = true; } - public override ByteCode Op => OpCode.ReturnCall; - public FuncIdx X; + public override ByteCode Op => OpCode.ReturnCall; public bool IsBound(ExecContext context) { @@ -67,7 +68,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.8.10. call - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //Fetch the Module first because we might exhaust the call stack context.Assert( context.Frame.Module.FuncAddrs.Contains(X), @@ -89,10 +90,9 @@ public override int Execute(ExecContext context) context.Frame.ContinuationAddress = address; throw new WasmRuntimeException("Synchronous execution path not allowed"); - return 1; } - - public override async ValueTask ExecuteAsync(ExecContext context) + + public override async ValueTask ExecuteAsync(ExecContext context) { //Fetch the Module first because we might exhaust the call stack context.Assert( context.Frame.Module.FuncAddrs.Contains(X), @@ -112,7 +112,6 @@ public override async ValueTask ExecuteAsync(ExecContext context) //Reuse the pointer from the outgoing function context.Frame.ContinuationAddress = address; - return 1; } /// @@ -166,15 +165,16 @@ public override string RenderText(ExecContext? context) //0x11 public class InstReturnCallIndirect : InstructionBase, ICallInstruction { + private TableIdx X; + + private TypeIdx Y; + public InstReturnCallIndirect() { IsAsync = true; } - - public override ByteCode Op => OpCode.ReturnCallIndirect; - private TypeIdx Y; - private TableIdx X; + public override ByteCode Op => OpCode.ReturnCallIndirect; public bool IsBound(ExecContext context) { @@ -226,7 +226,7 @@ public override void Validate(IWasmValidationContext context) } // @Spec 4.4.8.11. call_indirect - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { //Call Indirect //2. @@ -288,10 +288,9 @@ public override int Execute(ExecContext context) context.Frame.ContinuationAddress = address; throw new WasmRuntimeException("Synchronous execution path not allowed"); - return 1; } - - public override async ValueTask ExecuteAsync(ExecContext context) + + public override async ValueTask ExecuteAsync(ExecContext context) { //Call Indirect //2. @@ -351,7 +350,6 @@ public override async ValueTask ExecuteAsync(ExecContext context) //Reuse the pointer from the outgoing function context.Frame.ContinuationAddress = address; - return 1; } /// diff --git a/Wacs.Core/Instructions/Transpiler/AggregateTypeCast.cs b/Wacs.Core/Instructions/Transpiler/AggregateTypeCast.cs index e237e20..91cf599 100644 --- a/Wacs.Core/Instructions/Transpiler/AggregateTypeCast.cs +++ b/Wacs.Core/Instructions/Transpiler/AggregateTypeCast.cs @@ -46,15 +46,16 @@ public abstract class WrapValue : ITypedValueProducer where T : struct { private readonly ITypedValueProducer _inA; + + protected Func _func = null!; + protected WrapValue(ITypedValueProducer inA) { _inA = inA; } - public int CalculateSize() => _inA.CalculateSize(); - protected Func _func = null!; + public int CalculateSize() => _inA.CalculateSize(); public Func GetFunc => _func; - } public class NakedValue : WrapValue @@ -132,6 +133,7 @@ public WrapValueV128(ITypedValueProducer inA) : base(inA) public abstract class UnwrapValue : ITypedValueProducer { protected ITypedValueProducer InA; + protected UnwrapValue(ITypedValueProducer inA) { InA = inA; @@ -148,6 +150,7 @@ public UnwrapValueI32(ITypedValueProducer inA) : base(inA) var func = InA.GetFunc; GetFunc = context => func(context).Int32; } + public override Func GetFunc { get; } } @@ -158,6 +161,7 @@ public UnwrapValueU32(ITypedValueProducer inA) : base(inA) var func = InA.GetFunc; GetFunc = context => func(context).UInt32; } + public override Func GetFunc { get; } } @@ -168,6 +172,7 @@ public UnwrapValueF32(ITypedValueProducer inA) : base(inA) var func = InA.GetFunc; GetFunc = context => func(context).Float32; } + public override Func GetFunc { get; } } @@ -178,6 +183,7 @@ public UnwrapValueI64(ITypedValueProducer inA) : base(inA) var func = InA.GetFunc; GetFunc = context => func(context).Int64; } + public override Func GetFunc { get; } } @@ -188,6 +194,7 @@ public UnwrapValueU64(ITypedValueProducer inA) : base(inA) var func = InA.GetFunc; GetFunc = context => func(context).UInt64; } + public override Func GetFunc { get; } } @@ -198,6 +205,7 @@ public UnwrapValueF64(ITypedValueProducer inA) : base(inA) var func = InA.GetFunc; GetFunc = context => func(context).Float64; } + public override Func GetFunc { get; } } @@ -205,17 +213,18 @@ public class CastToI32 : ITypedValueProducer where T : struct { private readonly ITypedValueProducer _inA; + public CastToI32(ITypedValueProducer inA) { _inA = inA; if (typeof(T) == typeof(int)) { - _func = ((ITypedValueProducer)_inA).GetFunc; + GetFunc = ((ITypedValueProducer)_inA).GetFunc; } else if (typeof(T) == typeof(uint)) { var func = _inA.GetFunc as Func; - _func = context => (int)func!(context); + GetFunc = context => (int)func!(context); } else { @@ -224,26 +233,25 @@ public CastToI32(ITypedValueProducer inA) } public int CalculateSize() => _inA.CalculateSize(); - - private Func _func; - public Func GetFunc => _func; + public Func GetFunc { get; } } public class CastToU32 : ITypedValueProducer where T : struct { private readonly ITypedValueProducer _inA; + public CastToU32(ITypedValueProducer inA) { _inA = inA; if (typeof(T) == typeof(uint)) { - _func = ((ITypedValueProducer)_inA).GetFunc; + GetFunc = ((ITypedValueProducer)_inA).GetFunc; } else if (typeof(T) == typeof(int)) { var func = _inA.GetFunc as Func; - _func = context => (uint)func!(context); + GetFunc = context => (uint)func!(context); } else { @@ -252,26 +260,25 @@ public CastToU32(ITypedValueProducer inA) } public int CalculateSize() => _inA.CalculateSize(); - - private Func _func; - public Func GetFunc => _func; + public Func GetFunc { get; } } public class CastToI64 : ITypedValueProducer where T : struct { private readonly ITypedValueProducer _inA; + public CastToI64(ITypedValueProducer inA) { _inA = inA; if (typeof(T) == typeof(long)) { - _func = ((ITypedValueProducer)_inA).GetFunc; + GetFunc = ((ITypedValueProducer)_inA).GetFunc; } else if (typeof(T) == typeof(ulong)) { var func = _inA.GetFunc as Func; - _func = context => (long)func!(context); + GetFunc = context => (long)func!(context); } else { @@ -280,26 +287,25 @@ public CastToI64(ITypedValueProducer inA) } public int CalculateSize() => _inA.CalculateSize(); - - private Func _func; - public Func GetFunc => _func; + public Func GetFunc { get; } } public class CastToU64 : ITypedValueProducer where T : struct { private readonly ITypedValueProducer _inA; + public CastToU64(ITypedValueProducer inA) { _inA = inA; if (typeof(T) == typeof(ulong)) { - _func = ((ITypedValueProducer)_inA).GetFunc; + GetFunc = ((ITypedValueProducer)_inA).GetFunc; } else if (typeof(T) == typeof(long)) { var func = _inA.GetFunc as Func; - _func = context => (ulong)func!(context); + GetFunc = context => (ulong)func!(context); } else { @@ -308,8 +314,6 @@ public CastToU64(ITypedValueProducer inA) } public int CalculateSize() => _inA.CalculateSize(); - - private Func _func; - public Func GetFunc => _func; + public Func GetFunc { get; } } } \ No newline at end of file diff --git a/Wacs.Core/Instructions/Transpiler/InstAggregate1_0.cs b/Wacs.Core/Instructions/Transpiler/InstAggregate1_0.cs index 1195194..0d1c307 100644 --- a/Wacs.Core/Instructions/Transpiler/InstAggregate1_0.cs +++ b/Wacs.Core/Instructions/Transpiler/InstAggregate1_0.cs @@ -15,21 +15,17 @@ // */ using System; -using Wacs.Core.Runtime; using Wacs.Core.OpCodes; -using Wacs.Core.Types; +using Wacs.Core.Runtime; using Wacs.Core.Validation; namespace Wacs.Core.Instructions.Transpiler { public class InstAggregate1_0 : InstructionBase { - private readonly Func _inA; private readonly Action _compute; - - public int CalculateSize => Size; - public readonly int Size; - + private readonly Func _inA; + public InstAggregate1_0(ITypedValueProducer inA, INodeConsumer consumer) { _inA = inA.GetFunc; @@ -37,20 +33,16 @@ public InstAggregate1_0(ITypedValueProducer inA, INodeConsumer consume Size = inA.CalculateSize() + 1; } - - public void Run(ExecContext context) => _compute(context, _inA(context)); - - public Action GetFunc => Run; public override ByteCode Op => OpCode.Aggr; + public override void Validate(IWasmValidationContext context) { context.Assert(false, "Validation of transpiled instructions not supported."); } - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { - Run(context); - return Size; + _compute(context, _inA(context)); } } diff --git a/Wacs.Core/Instructions/Transpiler/InstAggregate1_1.cs b/Wacs.Core/Instructions/Transpiler/InstAggregate1_1.cs index 2f6dab7..7b8fb10 100644 --- a/Wacs.Core/Instructions/Transpiler/InstAggregate1_1.cs +++ b/Wacs.Core/Instructions/Transpiler/InstAggregate1_1.cs @@ -16,9 +16,8 @@ using System; using System.IO; -using Wacs.Core.Runtime; using Wacs.Core.OpCodes; -using Wacs.Core.Types; +using Wacs.Core.Runtime; using Wacs.Core.Validation; namespace Wacs.Core.Instructions.Transpiler @@ -26,13 +25,10 @@ namespace Wacs.Core.Instructions.Transpiler public class InstAggregate1_1 : InstructionBase, ITypedValueProducer where TOut : struct { - private readonly Func _in1; private readonly Func _compute; + private readonly Func _in1; private readonly Func _wrap; - public int CalculateSize() => Size; - public readonly int Size; - public InstAggregate1_1(ITypedValueProducer in1, INodeComputer compute) { _in1 = in1.GetFunc; @@ -50,19 +46,22 @@ public InstAggregate1_1(ITypedValueProducer in1, INodeComputer c else throw new InvalidDataException($"Could not bind aggregate type {typeof(TOut)}"); } - public TOut Run(ExecContext context) => _compute(context, _in1(context)); - - public Func GetFunc => Run; public override ByteCode Op => OpCode.Aggr; + + public int CalculateSize() => Size; + + public Func GetFunc => Run; + + public TOut Run(ExecContext context) => _compute(context, _in1(context)); + public override void Validate(IWasmValidationContext context) { context.Assert(false, "Validation of transpiled instructions not supported."); } - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { context.OpStack.PushValue(_wrap(context)); - return Size; } } } \ No newline at end of file diff --git a/Wacs.Core/Instructions/Transpiler/InstAggregate2_0.cs b/Wacs.Core/Instructions/Transpiler/InstAggregate2_0.cs index 92baf42..0a720b3 100644 --- a/Wacs.Core/Instructions/Transpiler/InstAggregate2_0.cs +++ b/Wacs.Core/Instructions/Transpiler/InstAggregate2_0.cs @@ -15,22 +15,18 @@ // */ using System; -using Wacs.Core.Runtime; using Wacs.Core.OpCodes; -using Wacs.Core.Types; +using Wacs.Core.Runtime; using Wacs.Core.Validation; namespace Wacs.Core.Instructions.Transpiler { public class InstAggregate2_0 : InstructionBase { + private readonly Action _compute; private readonly Func _in1; private readonly Func _in2; - private readonly Action _compute; - public int CalculateSize() => Size; - public readonly int Size; - public InstAggregate2_0(ITypedValueProducer in1, ITypedValueProducer in2, INodeConsumer compute) { _in1 = in1.GetFunc; @@ -40,19 +36,16 @@ public InstAggregate2_0(ITypedValueProducer in1, ITypedValueProducer Size = in1.CalculateSize() + in2.CalculateSize() + 1; } - public void Run(ExecContext context) => _compute(context, _in1(context), _in2(context)); - - public Action GetFunc => Run; public override ByteCode Op => OpCode.Aggr; + public override void Validate(IWasmValidationContext context) { context.Assert(false, "Validation of transpiled instructions not supported."); } - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { - Run(context); - return Size; + _compute(context, _in1(context), _in2(context)); } } diff --git a/Wacs.Core/Instructions/Transpiler/InstAggregate2_1.cs b/Wacs.Core/Instructions/Transpiler/InstAggregate2_1.cs index ac24652..dea2cc3 100644 --- a/Wacs.Core/Instructions/Transpiler/InstAggregate2_1.cs +++ b/Wacs.Core/Instructions/Transpiler/InstAggregate2_1.cs @@ -16,9 +16,8 @@ using System; using System.IO; -using Wacs.Core.Runtime; using Wacs.Core.OpCodes; -using Wacs.Core.Types; +using Wacs.Core.Runtime; using Wacs.Core.Validation; namespace Wacs.Core.Instructions.Transpiler @@ -26,14 +25,10 @@ namespace Wacs.Core.Instructions.Transpiler public class InstAggregate2_1 : InstructionBase, ITypedValueProducer where TOut : struct { + private readonly Func _compute; private readonly Func _in1; private readonly Func _in2; - private readonly Func _compute; private readonly Func _wrap; - - public int CalculateSize() => Size; - public readonly int Size; - public InstAggregate2_1(ITypedValueProducer in1, ITypedValueProducer in2, INodeComputer compute) { @@ -53,19 +48,22 @@ public InstAggregate2_1(ITypedValueProducer in1, ITypedValueProducer else throw new InvalidDataException($"Could not bind aggregate type {typeof(TOut)}"); } - public TOut Run(ExecContext context) => _compute(context, _in1(context), _in2(context)); - - public Func GetFunc => Run; public override ByteCode Op => OpCode.Aggr; + + public int CalculateSize() => Size; + + public Func GetFunc => Run; + + public TOut Run(ExecContext context) => _compute(context, _in1(context), _in2(context)); + public override void Validate(IWasmValidationContext context) { context.Assert(false, "Validation of transpiled instructions not supported."); } - public override int Execute(ExecContext context) + public override void Execute(ExecContext context) { context.OpStack.PushValue(_wrap(context)); - return Size; } } diff --git a/Wacs.Core/Instructions/Transpiler/InstStackProducer.cs b/Wacs.Core/Instructions/Transpiler/InstStackProducer.cs index 329a499..918299a 100644 --- a/Wacs.Core/Instructions/Transpiler/InstStackProducer.cs +++ b/Wacs.Core/Instructions/Transpiler/InstStackProducer.cs @@ -25,12 +25,19 @@ namespace Wacs.Core.Instructions.Transpiler public class InstStackProducer : InstructionBase, ITypedValueProducer where T : struct { + private readonly ValType _type; + public InstStackProducer() { _type = typeof(T).ToValType(); + Size = 0; } - - private readonly ValType _type; + + public override ByteCode Op => OpCode.StackVal; + + public Func GetFunc => FetchFromStack; + + public int CalculateSize() => 0; public T FetchFromStack(ExecContext context) { @@ -40,18 +47,11 @@ public T FetchFromStack(ExecContext context) return (T)Convert.ChangeType(boxedValue, typeof(T)); } - public Func GetFunc => FetchFromStack; - - public int CalculateSize() => 1; - public override ByteCode Op => OpCode.StackVal; public override void Validate(IWasmValidationContext context) { context.OpStack.PopType(_type); } - public override int Execute(ExecContext context) - { - return 0; - } + public override void Execute(ExecContext context) {} } } \ No newline at end of file diff --git a/Wacs.Core/Instructions/Transpiler/Interfaces.cs b/Wacs.Core/Instructions/Transpiler/Interfaces.cs index 311b680..e9b8c3f 100644 --- a/Wacs.Core/Instructions/Transpiler/Interfaces.cs +++ b/Wacs.Core/Instructions/Transpiler/Interfaces.cs @@ -25,17 +25,17 @@ public interface IInstructionAnalog } - public interface IConvertableValueProducer { } + public interface IConvertableValueProducer {} public interface ITypedValueProducer : IInstructionAnalog, IConvertableValueProducer { public Func GetFunc { get; } } - public interface IOptimizationTarget : IInstruction { } + public interface IOptimizationTarget : IInstruction {} - public interface IValueConsumer { } - public interface IValueConsumer { } + public interface IValueConsumer {} + public interface IValueConsumer {} public interface INodeConsumer : IValueConsumer, IOptimizationTarget { diff --git a/Wacs.Core/Modules/Sections/FunctionSection.cs b/Wacs.Core/Modules/Sections/FunctionSection.cs index dd0f079..2bc11a8 100644 --- a/Wacs.Core/Modules/Sections/FunctionSection.cs +++ b/Wacs.Core/Modules/Sections/FunctionSection.cs @@ -38,11 +38,11 @@ public partial class Module /// public class Function : IRenderable { + public FuncIdx Index; public bool IsFullyDeclared = false; public bool IsImport = false; public string Id { get; set; } = ""; - public FuncIdx Index; //Function Section only parses the type indices public TypeIdx TypeIndex { get; internal set; } diff --git a/Wacs.Core/Modules/Sections/GlobalSection.cs b/Wacs.Core/Modules/Sections/GlobalSection.cs index 27916a7..793abbf 100644 --- a/Wacs.Core/Modules/Sections/GlobalSection.cs +++ b/Wacs.Core/Modules/Sections/GlobalSection.cs @@ -41,14 +41,14 @@ public class Global : IRenderable public readonly Expression Initializer; public readonly GlobalType Type; + public bool IsImport; + public Global(GlobalType type) => (Type, Initializer) = (type, Expression.Empty); private Global(BinaryReader reader) => (Type, Initializer) = (GlobalType.Parse(reader), Expression.ParseInitializer(reader)); - public bool IsImport; - public string Id { get; set; } = ""; public void RenderText(StreamWriter writer, Module module, string indent) diff --git a/Wacs.Core/Modules/Sections/StackCalculator.cs b/Wacs.Core/Modules/Sections/StackCalculator.cs index c521d35..d42d927 100644 --- a/Wacs.Core/Modules/Sections/StackCalculator.cs +++ b/Wacs.Core/Modules/Sections/StackCalculator.cs @@ -14,7 +14,6 @@ // * limitations under the License. // */ -using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -113,7 +112,11 @@ public void PopValues(ResultType types, bool keep = true) public class StackCalculator: IWasmValidationContext { private static readonly object NonNull = new(); - + + private static Stack _aside = new(); + + internal int stackHeight = 0; + public StackCalculator(ModuleInstance moduleInst, Module.Function func) { Types = new TypesSpace(moduleInst.Repr); @@ -144,22 +147,6 @@ public StackCalculator(ModuleInstance moduleInst, Module.Function func) }; } - internal int stackHeight = 0; - public void Clear() - { - stackHeight = 0; - } - public void Push(ValType val) - { - stackHeight += 1; - } - - public Value Pop(ValType type) - { - stackHeight -= 1; - return new Value(type); - } - public RuntimeAttributes Attributes { get; } public IValidationOpStack OpStack { get; } public FuncIdx FunctionIndex { get; } @@ -173,6 +160,7 @@ public Value Pop(ValType type) public LocalsSpace Locals { get; } public ElementsSpace Elements { get; set; } public DataValidationSpace Datas { get; set; } + public bool ContainsLabel(uint label) { return true; @@ -195,7 +183,6 @@ public void PushControlFrame(ByteCode opCode, FunctionType types) OpStack.PushResult(types.ParameterTypes); } - private static Stack _aside = new(); public ValidationControlFrame PopControlFrame() { if (ControlStack.Count == 0) @@ -220,5 +207,21 @@ public void Assert(bool factIsTrue, string formatString, params object[] args) { public void ValidateBlock(Block instructionBlock, int index = 0) {} + + public void Clear() + { + stackHeight = 0; + } + + public void Push(ValType val) + { + stackHeight += 1; + } + + public Value Pop(ValType type) + { + stackHeight -= 1; + return new Value(type); + } } } \ No newline at end of file diff --git a/Wacs.Core/Modules/Sections/StackRenderer.cs b/Wacs.Core/Modules/Sections/StackRenderer.cs index 028340c..40fbe3b 100644 --- a/Wacs.Core/Modules/Sections/StackRenderer.cs +++ b/Wacs.Core/Modules/Sections/StackRenderer.cs @@ -70,7 +70,7 @@ public void PushValues(Stack vals) { public Value PopType(ValType type) => _context.Pop(type); public Value PopAny() => _context.Pop(ValType.Nil); - + public void PopValues(ResultType types, ref Stack aside) { foreach (var type in types.Types.Reverse()) @@ -114,6 +114,8 @@ public void PopValues(ResultType types, bool keep = true) public class FakeContext : IWasmValidationContext { private static readonly object NonNull = new(); + + private static Stack _aside = new(); private readonly FakeOpStack _opStack; private readonly Stack fakeStack = new(); @@ -156,7 +158,7 @@ public FakeContext(Module module, Module.Function func) public IValidationOpStack OpStack => _opStack; - + public void Assert(bool factIsTrue, string formatString, params object[] args) { } public void Assert([NotNull] object? objIsNotNull, string formatString, params object[] args) { objIsNotNull = NonNull; } @@ -179,7 +181,6 @@ public void PushControlFrame(ByteCode opCode, FunctionType types) OpStack.PushResult(types.ParameterTypes); } - private static Stack _aside = new(); public ValidationControlFrame PopControlFrame() { if (ControlStack.Count == 0) diff --git a/Wacs.Core/Runtime/Exceptions/InsufficientGasException.cs b/Wacs.Core/Runtime/Exceptions/InsufficientGasException.cs index 527910c..2f20e69 100644 --- a/Wacs.Core/Runtime/Exceptions/InsufficientGasException.cs +++ b/Wacs.Core/Runtime/Exceptions/InsufficientGasException.cs @@ -14,7 +14,6 @@ // * limitations under the License. // */ -using System; using Wacs.Core.Runtime.Types; namespace Wacs.Core.Runtime.Exceptions diff --git a/Wacs.Core/Runtime/ExecContext.cs b/Wacs.Core/Runtime/ExecContext.cs index 0564953..dcd9e6f 100644 --- a/Wacs.Core/Runtime/ExecContext.cs +++ b/Wacs.Core/Runtime/ExecContext.cs @@ -19,9 +19,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Linq; -using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.Extensions.ObjectPool; using Wacs.Core.Instructions; @@ -47,18 +45,20 @@ public class ExecContext private readonly Stack _callStack; private readonly ObjectPool _framePool; private readonly InstructionSequence _hostReturnSequence; - + private readonly ArrayPool _localsDataPool; public readonly RuntimeAttributes Attributes; + public readonly Stopwatch InstructionTimer = new(); public readonly OpStack OpStack; + public readonly Stopwatch ProcessTimer = new(); + public readonly Store Store; private InstructionSequence _currentSequence; - private InstructionBase[] _sequenceInstructions; private int _sequenceCount; private int _sequenceIndex; - public InstructionPointer GetPointer() => new(_currentSequence, _sequenceIndex); + private InstructionBase[] _sequenceInstructions; public Frame Frame = NullFrame; @@ -87,12 +87,10 @@ public ExecContext(Store store, RuntimeAttributes? attributes = default) OpStack = new(Attributes.MaxOpStack); } - public readonly Stopwatch ProcessTimer = new(); - public readonly Stopwatch InstructionTimer = new(); - public IInstructionFactory InstructionFactory => Attributes.InstructionFactory; public MemoryInstance DefaultMemory => Store[Frame.Module.MemAddrs[default]]; + public InstructionPointer GetPointer() => new(_currentSequence, _sequenceIndex); [Conditional("STRICT_EXECUTION")] public void Assert([NotNull] object? objIsNotNull, string message) @@ -176,7 +174,7 @@ public void FlushCallStack() _sequenceInstructions = _currentSequence._instructions; _sequenceIndex = -1; } - + public void EnterSequence(InstructionSequence seq) { _currentSequence = seq; @@ -184,7 +182,7 @@ public void EnterSequence(InstructionSequence seq) _sequenceInstructions = _currentSequence._instructions; _sequenceIndex = -1; } - + public void ResumeSequence(InstructionPointer pointer) { _currentSequence = pointer.Sequence; @@ -192,13 +190,13 @@ public void ResumeSequence(InstructionPointer pointer) _sequenceInstructions = _currentSequence._instructions; _sequenceIndex = pointer.Index; } - + public void FastForwardSequence() { //Go to penultimate instruction since we pre-increment on pointer advance. _sequenceIndex = _sequenceCount - 2; } - + public void RewindSequence() { //Go back to the first instruction in the sequence @@ -269,7 +267,7 @@ private void Invoke(FunctionInstance wasmFunc, FuncIdx idx) } - + // @Spec 4.4.10.2. Returning from a function public void FunctionReturn() { @@ -285,8 +283,8 @@ public void FunctionReturn() //8. ResumeSequence(address); } - - + + public InstructionBase? Next() { //Advance to the next instruction first. diff --git a/Wacs.Core/Runtime/Frame.cs b/Wacs.Core/Runtime/Frame.cs index 46d0314..ad714a9 100644 --- a/Wacs.Core/Runtime/Frame.cs +++ b/Wacs.Core/Runtime/Frame.cs @@ -15,7 +15,6 @@ // */ using System.Buffers; -using System.Collections; using System.Collections.Generic; using System.IO; using Wacs.Core.Instructions; @@ -32,18 +31,18 @@ public class Frame : IPoolable public string FuncId = ""; public FuncIdx Index; + + public Label Label; + public int LabelCount = 0; public LocalsSpace Locals; public ModuleInstance Module = null!; - public FunctionType Type = null!; - - public Label Label; public Label ReturnLabel = new(); - public BlockTarget TopLabel; - public int LabelCount = 0; public int StackHeight; - + public BlockTarget TopLabel; + public FunctionType Type = null!; + public int Arity => Type.ResultType.Arity; public void Clear() @@ -96,7 +95,7 @@ public void ClearLabels() TopLabel = default!; LabelCount = 0; } - + public void PushLabel(BlockTarget target) { TopLabel = target; diff --git a/Wacs.Core/Runtime/Label.cs b/Wacs.Core/Runtime/Label.cs index 2b874ed..f594fcc 100644 --- a/Wacs.Core/Runtime/Label.cs +++ b/Wacs.Core/Runtime/Label.cs @@ -14,7 +14,6 @@ // * limitations under the License. // */ -using System.Collections; using Wacs.Core.OpCodes; namespace Wacs.Core.Runtime @@ -44,11 +43,10 @@ public bool Equals(Label other) Instruction.Equals(other.Instruction) && ContinuationAddress.Equals(other.ContinuationAddress); } - + public override string ToString() { return $"Label(Instruction: {Instruction}, Arity: {Arity}, StackHeight: {StackHeight}, ContinuationAddress: {ContinuationAddress})"; } - } } \ No newline at end of file diff --git a/Wacs.Core/Runtime/OpStack.cs b/Wacs.Core/Runtime/OpStack.cs index b6c3ae8..ebec0cd 100644 --- a/Wacs.Core/Runtime/OpStack.cs +++ b/Wacs.Core/Runtime/OpStack.cs @@ -25,8 +25,8 @@ namespace Wacs.Core.Runtime public class OpStack { private readonly Stack _stack; - public int Count; private readonly int _stackLimit; + public int Count; public OpStack(int limit) { diff --git a/Wacs.Core/Runtime/RuntimeAttributes.cs b/Wacs.Core/Runtime/RuntimeAttributes.cs index 68f394b..4d523ac 100644 --- a/Wacs.Core/Runtime/RuntimeAttributes.cs +++ b/Wacs.Core/Runtime/RuntimeAttributes.cs @@ -23,15 +23,15 @@ public class RuntimeAttributes public bool Configure_RefTypes = false; public int GrowCallStack = 512; + public int GrowLabelsStack = 512; + public int InitialCallStack = 512; + public int InitialLabelsStack = 2048; public bool Live = true; + public int LocalPoolSize = 64; public int MaxCallStack = 2048; - public int GrowLabelsStack = 512; - public int InitialLabelsStack = 2048; - public int MaxFunctionLocals = 2048; - public int LocalPoolSize = 64; public int MaxOpStack = 1024; public IInstructionFactory InstructionFactory { get; set; } = SpecFactory.Factory; diff --git a/Wacs.Core/Runtime/Store.cs b/Wacs.Core/Runtime/Store.cs index 562cb20..fff37a3 100644 --- a/Wacs.Core/Runtime/Store.cs +++ b/Wacs.Core/Runtime/Store.cs @@ -31,13 +31,13 @@ public class Store private readonly List Funcs = new(); private readonly List Globals = new(); - - private MemoryInstance?[] Mems = new MemoryInstance[32]; - private int MemsCount = 0; - + private readonly List Tables = new(); private StoreTransaction? CurrentTransaction = null; + private MemoryInstance?[] Mems = new MemoryInstance[32]; + private int MemsCount = 0; + public IFunctionInstance this[FuncAddr addr] => CurrentTransaction?.Funcs.GetValueOrDefault(addr)??Funcs[addr.Value]; diff --git a/Wacs.Core/Runtime/Transpiler/FunctionTranspiler.cs b/Wacs.Core/Runtime/Transpiler/FunctionTranspiler.cs index a63864d..a4f4ed2 100644 --- a/Wacs.Core/Runtime/Transpiler/FunctionTranspiler.cs +++ b/Wacs.Core/Runtime/Transpiler/FunctionTranspiler.cs @@ -14,11 +14,9 @@ // * limitations under the License. // */ -using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Reflection; using Wacs.Core.Instructions; using Wacs.Core.Instructions.Transpiler; using Wacs.Core.Runtime.Types; @@ -226,7 +224,7 @@ private static IInstruction BindI32(IInstruction inst, Stack stack stack.Push(top); return inst; } - + private static IInstruction BindI32I32(IInstruction inst, Stack stack, IValueConsumer intConsumer) { if (stack.Count < 2) return inst; @@ -260,7 +258,7 @@ private static IInstruction BindI32I32(IInstruction inst, Stack st stack.Push(i2); return inst; } - + private static IInstruction BindU32U32(IInstruction inst, Stack stack, IValueConsumer intConsumer) { if (stack.Count < 2) return inst; @@ -293,7 +291,7 @@ private static IInstruction BindU32U32(IInstruction inst, Stack st stack.Push(i2); return inst; } - + private static IInstruction BindU32I32(IInstruction inst, Stack stack, IValueConsumer intConsumer) { if (stack.Count < 2) return inst; @@ -326,6 +324,7 @@ private static IInstruction BindU32I32(IInstruction inst, Stack st stack.Push(i2); return inst; } + private static IInstruction BindU64(IOptimizationTarget inst, Stack stack, IValueConsumer ulongConsumer) { if (stack.Count < 1) return inst; @@ -374,7 +373,7 @@ private static IInstruction BindI64(IInstruction inst, Stack stack stack.Push(top); return inst; } - + private static IInstruction BindI64I64(IInstruction inst, Stack stack, IValueConsumer longConsumer) { if (stack.Count < 2) return inst; @@ -410,7 +409,7 @@ private static IInstruction BindI64I64(IInstruction inst, Stack st stack.Push(i2); return inst; } - + private static IInstruction BindU64U64(IInstruction inst, Stack stack, IValueConsumer longConsumer) { if (stack.Count < 2) return inst; @@ -445,7 +444,7 @@ private static IInstruction BindU64U64(IInstruction inst, Stack st stack.Push(i2); return inst; } - + private static IInstruction BindU64I64(IInstruction inst, Stack stack, IValueConsumer longConsumer) { if (stack.Count < 2) return inst; @@ -478,8 +477,8 @@ private static IInstruction BindU64I64(IInstruction inst, Stack st stack.Push(i2); return inst; } - - + + private static IInstruction BindU32Value(IInstruction inst, Stack stack, IValueConsumer intConsumer) { if (stack.Count < 1) return inst; @@ -525,7 +524,7 @@ private static IInstruction BindU32Value(IInstruction inst, Stack stack.Push(i2); return inst; } - + private static IInstruction BindF32(IOptimizationTarget inst, Stack stack, IValueConsumer floatConsumer) { if (stack.Count < 1) return inst; @@ -546,7 +545,7 @@ private static IInstruction BindF32(IOptimizationTarget inst, Stack stack, IValueConsumer intConsumer) { if (stack.Count < 2) return inst; @@ -579,7 +578,7 @@ private static IInstruction BindF32F32(IInstruction inst, Stack st stack.Push(i2); return inst; } - + private static IInstruction BindF64(IOptimizationTarget inst, Stack stack, IValueConsumer doubleConsumer) { if (stack.Count < 1) return inst; @@ -600,7 +599,7 @@ private static IInstruction BindF64(IOptimizationTarget inst, Stack stack, IValueConsumer intConsumer) { if (stack.Count < 2) return inst; @@ -633,6 +632,5 @@ private static IInstruction BindF64F64(IInstruction inst, Stack st stack.Push(i2); return inst; } - } } \ No newline at end of file diff --git a/Wacs.Core/Runtime/Types/FunctionInstance.cs b/Wacs.Core/Runtime/Types/FunctionInstance.cs index 888ed98..d1777a4 100644 --- a/Wacs.Core/Runtime/Types/FunctionInstance.cs +++ b/Wacs.Core/Runtime/Types/FunctionInstance.cs @@ -15,11 +15,8 @@ // */ using System.Collections.Generic; -using FluentValidation; -using FluentValidation.Internal; using Wacs.Core.OpCodes; using Wacs.Core.Types; -using Wacs.Core.Validation; namespace Wacs.Core.Runtime.Types { @@ -29,6 +26,26 @@ namespace Wacs.Core.Runtime.Types /// public class FunctionInstance : IFunctionInstance { + private static Stack _asideVals = new(); + + private readonly static ByteCode LabelInst = OpCode.Func; + + /// + /// The function definition containing the raw code and locals. + /// + public readonly Module.Function Definition; + + public readonly FuncIdx Index; + + public readonly ModuleInstance Module; + + //Copied from the static Definition + //Can be processed with optimization passes + public Expression Body; + + //Copied from the static Definition + public ValType[] Locals; + /// /// @Spec 4.5.3.1. Functions /// Initializes a new instance of the class. @@ -48,16 +65,12 @@ public FunctionInstance(ModuleInstance module, Module.Function definition) Name = Definition.Id; } - public readonly ModuleInstance Module; - - /// - /// The function definition containing the raw code and locals. - /// - public readonly Module.Function Definition; - - //Copied from the static Definition - //Can be processed with optimization passes - public Expression Body; + public string ModuleName => Module.Name; + public string Name { get; set; } = ""; + public FunctionType Type { get; } + public void SetName(string value) => Name = value; + public string Id => string.IsNullOrEmpty(Name)?"":$"{ModuleName}.{Name}"; + public bool IsExport { get; set; } /// /// Sets Body and precomputes labels @@ -72,22 +85,6 @@ public void SetBody(Expression body) Body.PrecomputeLabels(vContext); } - //Copied from the static Definition - public ValType[] Locals; - - public readonly FuncIdx Index; - - public string ModuleName => Module.Name; - public string Name { get; set; } = ""; - public FunctionType Type { get; } - public void SetName(string value) => Name = value; - public string Id => string.IsNullOrEmpty(Name)?"":$"{ModuleName}.{Name}"; - public bool IsExport { get; set; } - - private static Stack _asideVals = new(); - - private readonly static ByteCode LabelInst = OpCode.Func; - public void Invoke(ExecContext context) { //3. @@ -137,7 +134,7 @@ public void Invoke(ExecContext context) context.EnterSequence(Body.Instructions); } - + public override string ToString() => $"FunctionInstance[{Id}] (Type: {Type}, IsExport: {IsExport})"; } } \ No newline at end of file diff --git a/Wacs.Core/Runtime/Types/GlobalInstance.cs b/Wacs.Core/Runtime/Types/GlobalInstance.cs index a675c3c..228a5c9 100644 --- a/Wacs.Core/Runtime/Types/GlobalInstance.cs +++ b/Wacs.Core/Runtime/Types/GlobalInstance.cs @@ -24,6 +24,7 @@ namespace Wacs.Core.Runtime.Types /// public class GlobalInstance { + public readonly GlobalType Type; private Value _value; public GlobalInstance(GlobalType type, Value initialValue) @@ -32,8 +33,6 @@ public GlobalInstance(GlobalType type, Value initialValue) _value = initialValue; } - public readonly GlobalType Type; - public Value Value { get => _value; diff --git a/Wacs.Core/Runtime/Types/HostFunction.cs b/Wacs.Core/Runtime/Types/HostFunction.cs index 20c3f48..b0cd487 100644 --- a/Wacs.Core/Runtime/Types/HostFunction.cs +++ b/Wacs.Core/Runtime/Types/HostFunction.cs @@ -38,8 +38,6 @@ public class HostFunction : IFunctionInstance private readonly MethodInfo _invoker; - private readonly bool _isAsync; - private ConversionHelper?[] _parameterConversions = null!; private ConversionHelper?[] _resultConversions = null!; @@ -59,7 +57,7 @@ public HostFunction((string module, string entity) id, FunctionType type, Type d Type = type; _hostFunction = hostFunction; _invoker = delType.GetMethod("Invoke")!; - _isAsync = isAsync; + IsAsync = isAsync; (ModuleName, Name) = id; var invokerParams = _invoker.GetParameters(); @@ -91,12 +89,13 @@ public HostFunction((string module, string entity) id, FunctionType type, Type d public bool PassExecContext { get; set; } public string ModuleName { get; } + + public bool IsAsync { get; } + public string Name { get; } public void SetName(string value) {} public string Id => $"{ModuleName}.{Name}"; - public bool IsAsync => _isAsync; - public bool IsExport { get => true; @@ -281,7 +280,7 @@ public void Invoke(object[] args, OpStack opStack) throw ex.InnerException!; } } - + public async ValueTask InvokeAsync(object[] args, OpStack opStack) { for (int i = 0; i < _parameterConversions.Length; ++i) diff --git a/Wacs.Core/Runtime/Types/TableInstance.cs b/Wacs.Core/Runtime/Types/TableInstance.cs index e0a64a5..fc40621 100644 --- a/Wacs.Core/Runtime/Types/TableInstance.cs +++ b/Wacs.Core/Runtime/Types/TableInstance.cs @@ -27,6 +27,11 @@ namespace Wacs.Core.Runtime.Types /// public class TableInstance { + //The actual data array, filled in by InstantiateModule with table.init instructions + public readonly List Elements; + + public readonly TableType Type; + /// /// @Spec 4.5.3.3. Tables /// @Spec 4.5.3.10. Modules @@ -50,11 +55,6 @@ public TableInstance(TableType type, Value refVal) private TableInstance(TableType type, IEnumerable elems) => (Type, Elements) = ((TableType)type.Clone(), elems.ToList()); - public readonly TableType Type; - - //The actual data array, filled in by InstantiateModule with table.init instructions - public readonly List Elements; - public TableInstance Clone() => new(Type, Elements); /// diff --git a/Wacs.Core/Runtime/WasmRuntimeExecution.cs b/Wacs.Core/Runtime/WasmRuntimeExecution.cs index 7ef1850..afcdff5 100644 --- a/Wacs.Core/Runtime/WasmRuntimeExecution.cs +++ b/Wacs.Core/Runtime/WasmRuntimeExecution.cs @@ -31,7 +31,7 @@ namespace Wacs.Core.Runtime public partial class WasmRuntime { private IInstruction? lastInstruction = null; - + public TDelegate CreateInvoker(FuncAddr funcAddr, InvokerOptions? options = default) where TDelegate : Delegate { @@ -218,19 +218,18 @@ Value[] GenericDelegate(params object[] args) return results; } } - + public async Task ProcessThreadAsync(long gasLimit) { if (gasLimit <= 0) gasLimit = long.MaxValue; while (Context.Next() is { } inst) { - int work; if (inst.IsAsync) - work = await inst.ExecuteAsync(Context); + await inst.ExecuteAsync(Context); else - work = inst.Execute(Context); - - Context.steps += work; + inst.Execute(Context); + + Context.steps += inst.Size; if (Context.steps >= gasLimit) throw new InsufficientGasException($"Invocation ran out of gas (limit:{gasLimit})."); } @@ -248,21 +247,20 @@ public async Task ProcessThreadWithOptions(InvokerOptions options) LogPreInstruction(options, inst); } - int work = 0; if (options.CollectStats == StatsDetail.Instruction) { Context.InstructionTimer.Restart(); if (inst.IsAsync) - work = await inst.ExecuteAsync(Context); + await inst.ExecuteAsync(Context); else - work = inst.Execute(Context); + inst.Execute(Context); Context.InstructionTimer.Stop(); - Context.steps += work; + Context.steps += inst.Size; var st = Context.Stats[(ushort)inst.Op]; - st.count += work; + st.count += inst.Size; st.duration += Context.InstructionTimer.ElapsedTicks; Context.Stats[(ushort)inst.Op] = st; } @@ -270,11 +268,11 @@ public async Task ProcessThreadWithOptions(InvokerOptions options) { Context.InstructionTimer.Start(); if (inst.IsAsync) - work = await inst.ExecuteAsync(Context); + await inst.ExecuteAsync(Context); else - work = inst.Execute(Context); + inst.Execute(Context); Context.InstructionTimer.Stop(); - Context.steps += work; + Context.steps += inst.Size; } if (((int)options.LogInstructionExecution & (int)InstructionLogging.Computes) != 0) @@ -289,7 +287,7 @@ public async Task ProcessThreadWithOptions(InvokerOptions options) if (options.LogProgressEvery > 0) { - highwatermark += work; + highwatermark += inst.Size; if (highwatermark >= options.LogProgressEvery) { highwatermark -= options.LogProgressEvery; @@ -435,6 +433,5 @@ private void PrintStats(InvokerOptions options) Console.Error.WriteLine($"{label}: {execsLabel}| ({percentLabel}) {instTime.TotalMilliseconds:#0.000}ms {instAve}"); } } - } } \ No newline at end of file diff --git a/Wacs.Core/Runtime/WasmRuntimeInstantiation.cs b/Wacs.Core/Runtime/WasmRuntimeInstantiation.cs index ac88559..3bb2ffd 100644 --- a/Wacs.Core/Runtime/WasmRuntimeInstantiation.cs +++ b/Wacs.Core/Runtime/WasmRuntimeInstantiation.cs @@ -27,8 +27,6 @@ using Wacs.Core.Runtime.Exceptions; using Wacs.Core.Runtime.Types; using Wacs.Core.Types; -using Wacs.Core.Utilities; -using Wacs.Core.WASIp1; // using System.Diagnostics.CodeAnalysis; diff --git a/Wacs.Core/Runtime/WasmRuntimeTranspiler.cs b/Wacs.Core/Runtime/WasmRuntimeTranspiler.cs index a53b2df..d636bbd 100644 --- a/Wacs.Core/Runtime/WasmRuntimeTranspiler.cs +++ b/Wacs.Core/Runtime/WasmRuntimeTranspiler.cs @@ -22,7 +22,7 @@ namespace Wacs.Core.Runtime public partial class WasmRuntime { public bool TranspileModules = false; - + public void TranspileModule(ModuleInstance moduleInstance) { foreach (var funcAddr in moduleInstance.FuncAddrs) diff --git a/Wacs.Core/Types/Expression.cs b/Wacs.Core/Types/Expression.cs index 640a05c..44e16cd 100644 --- a/Wacs.Core/Types/Expression.cs +++ b/Wacs.Core/Types/Expression.cs @@ -22,7 +22,6 @@ using Wacs.Core.Instructions; using Wacs.Core.OpCodes; using Wacs.Core.Runtime; -using Wacs.Core.Runtime.Types; using Wacs.Core.Utilities; using Wacs.Core.Validation; @@ -34,6 +33,11 @@ namespace Wacs.Core.Types public class Expression { public static readonly Expression Empty = new(0, InstructionSequence.Empty, true); + public readonly InstructionSequence Instructions; + + public readonly bool IsStatic; + + public InstExpressionProxy LabelTarget; /// /// For parsing normal Code sections @@ -52,7 +56,7 @@ private Expression(InstructionSequence seq, bool isStatic) StackHeight = -1, }); } - + /// /// Manual construction (from optimizers or statics) /// @@ -86,11 +90,13 @@ public Expression(IInstruction single, int arity) }); } + public int Size => Instructions.Size; + public void PrecomputeLabels(IWasmValidationContext vContext) { LinkLabelTarget(vContext, Instructions, LabelTarget); } - + private void LinkLabelTarget(IWasmValidationContext vContext, InstructionSequence seq, BlockTarget enclosingTarget) { for (int i = 0; i < seq.Count; ++i) @@ -135,13 +141,6 @@ private void LinkLabelTarget(IWasmValidationContext vContext, InstructionSequenc } } - public InstExpressionProxy LabelTarget; - - public readonly bool IsStatic; - public readonly InstructionSequence Instructions; - - public int Size => Instructions.Size; - /// /// Leaves the result on the OpStack /// @@ -166,7 +165,7 @@ public void ExecuteInitializer(ExecContext context) /// public static Expression Parse(BinaryReader reader) => new(new InstructionSequence(reader.ParseUntil(BinaryModuleParser.ParseInstruction, IInstruction.IsEnd)), true); - + public static Expression ParseInitializer(BinaryReader reader) => new(1, new InstructionSequence(reader.ParseUntil(BinaryModuleParser.ParseInstruction, IInstruction.IsEnd)), true); diff --git a/Wacs.Core/Types/GlobalType.cs b/Wacs.Core/Types/GlobalType.cs index ce1156a..a42f11e 100644 --- a/Wacs.Core/Types/GlobalType.cs +++ b/Wacs.Core/Types/GlobalType.cs @@ -26,18 +26,18 @@ namespace Wacs.Core.Types /// public class GlobalType { - public GlobalType(ValType valtype, Mutability mut) => - (ContentType, Mutability) = (valtype, mut); + /// + /// The value type of the global variable. + /// + public readonly ValType ContentType; /// /// The mutability of the global variable (immutable or mutable). /// public readonly Mutability Mutability; - /// - /// The value type of the global variable. - /// - public readonly ValType ContentType; + public GlobalType(ValType valtype, Mutability mut) => + (ContentType, Mutability) = (valtype, mut); public ResultType ResultType => ContentType.SingleResult(); diff --git a/Wacs.Core/Types/IndexSpace.cs b/Wacs.Core/Types/IndexSpace.cs index ad6ff93..cffe22a 100644 --- a/Wacs.Core/Types/IndexSpace.cs +++ b/Wacs.Core/Types/IndexSpace.cs @@ -15,6 +15,7 @@ // */ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using Wacs.Core.Runtime; @@ -32,13 +33,12 @@ public FuncAddr this[FuncIdx idx] set => _space[(int)idx.Value] = value; } + public IEnumerator GetEnumerator() => _space.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public bool Contains(FuncIdx idx) => idx.Value < _space.Count; public void Add(FuncAddr element) => _space.Add(element); - - public IEnumerator GetEnumerator() => _space.GetEnumerator(); - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); - } public class TableAddrs @@ -58,8 +58,8 @@ public TableAddr this[TableIdx idx] public class MemAddrs { - private bool _final; private List? _build = new(); + private bool _final; private MemAddr[]? _space; public MemAddr this[MemIdx idx] => _space![(int)idx.Value]; @@ -251,10 +251,9 @@ public override bool Contains(GlobalIdx idx) => public struct LocalsSpace { public Value[]? Data; - private int _capacity; - public int Capacity => _capacity; - + public int Capacity { get; } + public Value Get(LocalIdx idx) { if (Data == null) @@ -273,7 +272,7 @@ public void Set(LocalIdx idx, Value value) public LocalsSpace(Value[] data, ValType[] parameters, ValType[] locals) { - _capacity = parameters.Length + locals.Length; + Capacity = parameters.Length + locals.Length; Data = data; int idx = 0; foreach (var t in parameters) @@ -287,7 +286,7 @@ public LocalsSpace(Value[] data, ValType[] parameters, ValType[] locals) } public bool Contains(LocalIdx idx) => - idx.Value < _capacity; + idx.Value < Capacity; } public class ElementsSpace : AbstractIndexSpace diff --git a/Wacs.Core/Types/Limits.cs b/Wacs.Core/Types/Limits.cs index e5979ab..6ee38cf 100644 --- a/Wacs.Core/Types/Limits.cs +++ b/Wacs.Core/Types/Limits.cs @@ -27,6 +27,16 @@ namespace Wacs.Core.Types /// public class Limits : ICloneable { + /// + /// The optional maximum number of units. If MaxValue, there is no specified maximum. + /// + public uint? Maximum; + + /// + /// The minimum number of units (e.g., pages for memory). + /// + public uint Minimum; + /// /// Initializes a new instance of the class with the specified minimum and optional maximum. /// @@ -42,16 +52,6 @@ public Limits(Limits copy) { Maximum = copy.Maximum; } - /// - /// The minimum number of units (e.g., pages for memory). - /// - public uint Minimum; - - /// - /// The optional maximum number of units. If MaxValue, there is no specified maximum. - /// - public uint? Maximum; - public object Clone() => new Limits(this); /// diff --git a/Wacs.Core/Types/MemoryType.cs b/Wacs.Core/Types/MemoryType.cs index 5a4f838..1215f97 100644 --- a/Wacs.Core/Types/MemoryType.cs +++ b/Wacs.Core/Types/MemoryType.cs @@ -26,6 +26,11 @@ namespace Wacs.Core.Types /// public class MemoryType : IRenderable { + /// + /// The limits specifying the minimum and optional maximum number of memory pages. + /// + public readonly Limits Limits; + /// /// Initializes a new instance of the class with the specified limits. /// @@ -43,11 +48,6 @@ public MemoryType(uint minimum, uint? maximum = null) Limits = new Limits(minimum, maximum); } - /// - /// The limits specifying the minimum and optional maximum number of memory pages. - /// - public readonly Limits Limits; - public string Id { get; set; } = ""; public void RenderText(StreamWriter writer, Module module, string indent) diff --git a/Wacs.Core/Types/TableType.cs b/Wacs.Core/Types/TableType.cs index 6215397..c03752d 100644 --- a/Wacs.Core/Types/TableType.cs +++ b/Wacs.Core/Types/TableType.cs @@ -28,6 +28,11 @@ namespace Wacs.Core.Types /// public class TableType : ICloneable, IRenderable { + /// + /// The limits specifying the minimum and optional maximum number of elements. + /// + public Limits Limits = null!; + private TableType() { } @@ -38,11 +43,6 @@ public TableType(ReferenceType elementType, Limits limits) => private TableType(BinaryReader reader) => (ElementType, Limits) = (ReferenceTypeParser.Parse(reader), Limits.Parse(reader)); - /// - /// The limits specifying the minimum and optional maximum number of elements. - /// - public Limits Limits = null!; - /// /// The element type of the table (e.g., funcref or externref). /// diff --git a/Wacs.Core/Utilities/ObjectPoolExtension.cs b/Wacs.Core/Utilities/ObjectPoolExtension.cs index 55e3a1c..06c259b 100644 --- a/Wacs.Core/Utilities/ObjectPoolExtension.cs +++ b/Wacs.Core/Utilities/ObjectPoolExtension.cs @@ -14,7 +14,6 @@ // * limitations under the License. // */ -using System; using System.Collections.Generic; using Microsoft.Extensions.ObjectPool; diff --git a/Wacs.Core/Utilities/StackPoolPolicy.cs b/Wacs.Core/Utilities/StackPoolPolicy.cs index 5d41394..c9e6854 100644 --- a/Wacs.Core/Utilities/StackPoolPolicy.cs +++ b/Wacs.Core/Utilities/StackPoolPolicy.cs @@ -15,8 +15,6 @@ // */ using Microsoft.Extensions.ObjectPool; -using Wacs.Core.Runtime; -using Wacs.Core.Types; namespace Wacs.Core.Utilities { @@ -24,6 +22,7 @@ public class StackPoolPolicy : PooledObjectPolicy where T : class, IPoolable, new() { public override T Create() => new T(); + public override bool Return(T? obj) { if (obj == null) diff --git a/Wacs.Core/Validation/WasmValidationContext.cs b/Wacs.Core/Validation/WasmValidationContext.cs index 071313a..6963d40 100644 --- a/Wacs.Core/Validation/WasmValidationContext.cs +++ b/Wacs.Core/Validation/WasmValidationContext.cs @@ -33,6 +33,7 @@ namespace Wacs.Core.Validation /// public class WasmValidationContext : IWasmValidationContext { + private static Stack _aside = new(); private readonly Stack _contextStack = new(); /// @@ -101,7 +102,7 @@ public void Assert(bool factIsTrue, string formatString, params object[] args) if (factIsTrue) return; throw new ValidationException(string.Format(formatString, args)); } - + public void Assert([NotNull] object? objIsNotNull, string formatString, params object[] args) { if (objIsNotNull != null) return; @@ -151,7 +152,6 @@ public void PushControlFrame(ByteCode opCode, FunctionType types) OpStack.PushResult(types.ParameterTypes); } - private static Stack _aside = new(); public ValidationControlFrame PopControlFrame() { if (ControlStack.Count == 0) diff --git a/Wacs.WASIp1/Sock.cs b/Wacs.WASIp1/Sock.cs index 2342ed6..ca64542 100644 --- a/Wacs.WASIp1/Sock.cs +++ b/Wacs.WASIp1/Sock.cs @@ -27,9 +27,9 @@ namespace Wacs.WASIp1 public class Sock : IBindable { private readonly State _state; - + public Sock(State state) => _state = state; - + public void BindToRuntime(WasmRuntime runtime) { string module = "wasi_snapshot_preview1"; @@ -63,6 +63,5 @@ public ErrNo SockShutdown(ExecContext ctx, fd sock, SdFlags how) { return ErrNo.NotSup; } - } } \ No newline at end of file diff --git a/Wacs.WASIp1/State.cs b/Wacs.WASIp1/State.cs index f17e323..a9724b5 100644 --- a/Wacs.WASIp1/State.cs +++ b/Wacs.WASIp1/State.cs @@ -27,6 +27,10 @@ public class State public readonly CancellationTokenSource Cts = new(); private uint nextFd = 0; + public int nextSocketDescriptor = 1; + + + public Dictionary socketTable = new Dictionary(); public int ExitCode { get; set; } public int LastSignal { get; set; } @@ -43,9 +47,5 @@ public uint GetNextFd { public VirtualPathMapper PathMapper { get; set; } = new(); - - - public Dictionary socketTable = new Dictionary(); - public int nextSocketDescriptor = 1; } } \ No newline at end of file diff --git a/Wacs.WASIp1/Types/IoVec.cs b/Wacs.WASIp1/Types/IoVec.cs index de3272d..5dcaf1e 100644 --- a/Wacs.WASIp1/Types/IoVec.cs +++ b/Wacs.WASIp1/Types/IoVec.cs @@ -14,7 +14,6 @@ // * limitations under the License. // */ -using System; using System.Runtime.InteropServices; using Wacs.Core.Attributes; using Wacs.Core.Runtime; diff --git a/Wacs.WASIp1/Wasi.cs b/Wacs.WASIp1/Wasi.cs index eda16b3..5228305 100644 --- a/Wacs.WASIp1/Wasi.cs +++ b/Wacs.WASIp1/Wasi.cs @@ -25,11 +25,11 @@ public class Wasi private readonly Env _env; private readonly Filesystem _fs; + private readonly Poll _poll; private readonly Proc _proc; private readonly Random _random; - private readonly State _state; - private readonly Poll _poll; private readonly Sock _sock; + private readonly State _state; public Wasi(WasiConfiguration config) { From ab41c8dd84d88f6bf9776009613bd55df3f08bbe Mon Sep 17 00:00:00 2001 From: Kelvin Nishikawa Date: Thu, 28 Nov 2024 00:59:44 -0800 Subject: [PATCH 13/24] More transpiler instructions --- Wacs.Core/Instructions/Control.cs | 34 +++++-- Wacs.Core/Instructions/Parametric.cs | 13 ++- .../Transpiler/InstAggregate3_1.cs | 76 ++++++++++++++++ .../Transpiler/InstStackProducer.cs | 70 ++++++++++++--- .../Instructions/Transpiler/Interfaces.cs | 7 +- .../Runtime/Transpiler/FunctionTranspiler.cs | 88 ++++++++++++++++++- 6 files changed, 263 insertions(+), 25 deletions(-) create mode 100644 Wacs.Core/Instructions/Transpiler/InstAggregate3_1.cs diff --git a/Wacs.Core/Instructions/Control.cs b/Wacs.Core/Instructions/Control.cs index ba4e9b9..a12a1f5 100644 --- a/Wacs.Core/Instructions/Control.cs +++ b/Wacs.Core/Instructions/Control.cs @@ -21,6 +21,7 @@ using System.Text; using System.Threading.Tasks; using FluentValidation; +using Wacs.Core.Instructions.Transpiler; using Wacs.Core.OpCodes; using Wacs.Core.Runtime; using Wacs.Core.Runtime.Exceptions; @@ -222,8 +223,9 @@ public class InstIf : BlockTarget, IBlockInstruction { private static readonly ByteCode IfOp = OpCode.If; private static readonly ByteCode ElseOp = OpCode.Else; - private Block ElseBlock = Block.Empty; private Block IfBlock = Block.Empty; + private Block ElseBlock = Block.Empty; + private int ElseCount; public override ByteCode Op => IfOp; @@ -276,19 +278,18 @@ public override void Validate(IWasmValidationContext context) "Instruction loop invalid. BlockType {0} did not exist in the Context.",IfBlock.Type); } } - + // @Spec 4.4.8.5. if public override void Execute(ExecContext context) { int c = context.OpStack.PopI32(); if (c != 0) { - if (IfBlock.Instructions.Count != 0) - context.EnterBlock(this, IfBlock); + context.EnterBlock(this, IfBlock); } else { - if (ElseBlock.Instructions.Count != 0) + if (ElseCount != 0) context.EnterBlock(this, ElseBlock); } } @@ -316,7 +317,8 @@ public override IInstruction Parse(BinaryReader reader) { throw new FormatException($"If block did not terminate correctly."); } - + + ElseCount = ElseBlock.Instructions.Count; return this; } @@ -330,6 +332,7 @@ public BlockTarget Immediate(BlockType type, InstructionSequence ifSeq, Instruct type: type, seq: elseSeq ); + ElseCount = ElseBlock.Instructions.Count; return this; } } @@ -522,7 +525,7 @@ public override string RenderText(ExecContext? context) } //0x0D - public class InstBranchIf : InstructionBase, IBranchInstruction + public class InstBranchIf : InstructionBase, IBranchInstruction, INodeConsumer { public LabelIdx L; public override ByteCode Op => OpCode.BrIf; @@ -550,12 +553,19 @@ public override void Validate(IWasmValidationContext context) public override void Execute(ExecContext context) { int c = context.OpStack.PopI32(); + BranchIf(context, c); + } + + private void BranchIf(ExecContext context, int c) + { if (c != 0) { InstBranch.ExecuteInstruction(context, L); } } + public Action GetFunc => BranchIf; + /// /// @Spec 5.4.1 Control Instructions /// @@ -580,7 +590,7 @@ public override string RenderText(ExecContext? context) } //0x0E - public class InstBranchTable : InstructionBase, IBranchInstruction + public class InstBranchTable : InstructionBase, IBranchInstruction, INodeConsumer { private LabelIdx Ln; //Default m @@ -630,6 +640,12 @@ public override void Execute(ExecContext context) { //2. int i = context.OpStack.PopI32(); + BranchTable(context, i); + } + + + private void BranchTable(ExecContext context, int i) + { //3. if (i >= 0 && i < Ls.Length) { @@ -643,6 +659,8 @@ public override void Execute(ExecContext context) } } + public Action GetFunc => BranchTable; + private static LabelIdx ParseLabelIndex(BinaryReader reader) => (LabelIdx)reader.ReadLeb128_u32(); diff --git a/Wacs.Core/Instructions/Parametric.cs b/Wacs.Core/Instructions/Parametric.cs index d277039..f938240 100644 --- a/Wacs.Core/Instructions/Parametric.cs +++ b/Wacs.Core/Instructions/Parametric.cs @@ -17,6 +17,7 @@ using System; using System.IO; using FluentValidation; +using Wacs.Core.Instructions.Transpiler; using Wacs.Core.OpCodes; using Wacs.Core.Runtime; using Wacs.Core.Types; @@ -27,7 +28,7 @@ namespace Wacs.Core.Instructions { //0x1A - public class InstDrop : InstructionBase + public class InstDrop : InstructionBase, INodeConsumer { public static readonly InstDrop Inst = new(); public override ByteCode Op => OpCode.Drop; @@ -48,10 +49,12 @@ public override void Execute(ExecContext context) { context.OpStack.PopAny(); } + + public Action GetFunc => (_, _) => { }; } //0x1B - public class InstSelect : InstructionBase + public class InstSelect : InstructionBase, INodeComputer { public static readonly InstSelect InstWithoutTypes = new(); @@ -111,9 +114,13 @@ public override void Execute(ExecContext context) int c = context.OpStack.PopI32(); Value val2 = context.OpStack.PopAny(); Value val1 = context.OpStack.PopAny(); - context.OpStack.PushValue(c != 0 ? val1 : val2); + context.OpStack.PushValue(Select(context, val1, val2, c)); } + private Value Select(ExecContext _, Value val1, Value val2, int c) => + c != 0 ? val1 : val2; + + public Func GetFunc => Select; public override IInstruction Parse(BinaryReader reader) { if (WithTypes) { diff --git a/Wacs.Core/Instructions/Transpiler/InstAggregate3_1.cs b/Wacs.Core/Instructions/Transpiler/InstAggregate3_1.cs new file mode 100644 index 0000000..0cd0faa --- /dev/null +++ b/Wacs.Core/Instructions/Transpiler/InstAggregate3_1.cs @@ -0,0 +1,76 @@ +// /* +// * Copyright 2024 Kelvin Nishikawa +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ + +using System; +using System.IO; +using Wacs.Core.OpCodes; +using Wacs.Core.Runtime; +using Wacs.Core.Validation; + +namespace Wacs.Core.Instructions.Transpiler +{ + public class InstAggregate3_1 : InstructionBase, ITypedValueProducer + where TOut : struct + { + private readonly Func _compute; + private readonly Func _in1; + private readonly Func _in2; + private readonly Func _in3; + private readonly Func _wrap; + + public InstAggregate3_1( + ITypedValueProducer in1, + ITypedValueProducer in2, + ITypedValueProducer in3, + INodeComputer compute) + { + _in1 = in1.GetFunc; + _in2 = in2.GetFunc; + _in3 = in3.GetFunc; + _compute = compute.GetFunc; + Size = in1.CalculateSize() + in2.CalculateSize() + 1; + + if (typeof(TOut) == typeof(int)) _wrap = new WrapValueI32((ITypedValueProducer)this).GetFunc; + else if (typeof(TOut) == typeof(uint)) _wrap = new WrapValueU32((ITypedValueProducer)this).GetFunc; + else if (typeof(TOut) == typeof(long)) _wrap = new WrapValueI64((ITypedValueProducer)this).GetFunc; + else if (typeof(TOut) == typeof(ulong)) _wrap = new WrapValueU64((ITypedValueProducer)this).GetFunc; + else if (typeof(TOut) == typeof(float)) _wrap = new WrapValueF32((ITypedValueProducer)this).GetFunc; + else if (typeof(TOut) == typeof(double)) _wrap = new WrapValueF64((ITypedValueProducer)this).GetFunc; + else if (typeof(TOut) == typeof(V128)) _wrap = new WrapValueV128((ITypedValueProducer)this).GetFunc; + else if (typeof(TOut) == typeof(Value)) _wrap = new NakedValue((ITypedValueProducer)this).GetFunc; + else throw new InvalidDataException($"Could not bind aggregate type {typeof(TOut)}"); + } + + public override ByteCode Op => OpCode.Aggr; + + public int CalculateSize() => Size; + + public Func GetFunc => Run; + + public TOut Run(ExecContext context) => _compute(context, _in1(context), _in2(context), _in3(context)); + + public override void Validate(IWasmValidationContext context) + { + context.Assert(false, "Validation of transpiled instructions not supported."); + } + + public override void Execute(ExecContext context) + { + context.OpStack.PushValue(_wrap(context)); + } + } + +} \ No newline at end of file diff --git a/Wacs.Core/Instructions/Transpiler/InstStackProducer.cs b/Wacs.Core/Instructions/Transpiler/InstStackProducer.cs index 918299a..a21027b 100644 --- a/Wacs.Core/Instructions/Transpiler/InstStackProducer.cs +++ b/Wacs.Core/Instructions/Transpiler/InstStackProducer.cs @@ -22,34 +22,80 @@ namespace Wacs.Core.Instructions.Transpiler { - public class InstStackProducer : InstructionBase, ITypedValueProducer - where T : struct + public class InstStackProducerValue : InstructionBase, ITypedValueProducer { - private readonly ValType _type; - - public InstStackProducer() + public InstStackProducerValue() { - _type = typeof(T).ToValType(); Size = 0; } public override ByteCode Op => OpCode.StackVal; - public Func GetFunc => FetchFromStack; + public Func GetFunc => FetchFromStack; public int CalculateSize() => 0; - public T FetchFromStack(ExecContext context) + public Value FetchFromStack(ExecContext context) { //Get the type as a boxed scalar - var boxedValue = context.OpStack.PopType(_type).Scalar; - // Unbox with Convert.ChangeType - return (T)Convert.ChangeType(boxedValue, typeof(T)); + return context.OpStack.PopAny(); + } + + public override void Validate(IWasmValidationContext context) + { + context.OpStack.PopAny(); + } + + public override void Execute(ExecContext context) {} + } + + public class InstStackProducerU32 : InstructionBase, ITypedValueProducer + { + public InstStackProducerU32() + { + Size = 0; + } + + public override ByteCode Op => OpCode.StackVal; + + public Func GetFunc => FetchFromStack; + + public int CalculateSize() => 0; + + public uint FetchFromStack(ExecContext context) + { + return context.OpStack.PopU32(); + } + + public override void Validate(IWasmValidationContext context) + { + context.OpStack.PopI32(); + } + + public override void Execute(ExecContext context) {} + } + + public class InstStackProducerI32 : InstructionBase, ITypedValueProducer + { + public InstStackProducerI32() + { + Size = 0; + } + + public override ByteCode Op => OpCode.StackVal; + + public Func GetFunc => FetchFromStack; + + public int CalculateSize() => 0; + + public int FetchFromStack(ExecContext context) + { + return context.OpStack.PopI32(); } public override void Validate(IWasmValidationContext context) { - context.OpStack.PopType(_type); + context.OpStack.PopI32(); } public override void Execute(ExecContext context) {} diff --git a/Wacs.Core/Instructions/Transpiler/Interfaces.cs b/Wacs.Core/Instructions/Transpiler/Interfaces.cs index e9b8c3f..e60c641 100644 --- a/Wacs.Core/Instructions/Transpiler/Interfaces.cs +++ b/Wacs.Core/Instructions/Transpiler/Interfaces.cs @@ -36,6 +36,7 @@ public interface IOptimizationTarget : IInstruction {} public interface IValueConsumer {} public interface IValueConsumer {} + public interface IValueConsumer {} public interface INodeConsumer : IValueConsumer, IOptimizationTarget { @@ -55,6 +56,10 @@ public interface INodeComputer : IValueConsumer GetFunc { get; } } - + + public interface INodeComputer : IValueConsumer, IOptimizationTarget + { + public Func GetFunc { get; } + } } \ No newline at end of file diff --git a/Wacs.Core/Runtime/Transpiler/FunctionTranspiler.cs b/Wacs.Core/Runtime/Transpiler/FunctionTranspiler.cs index a4f4ed2..862203d 100644 --- a/Wacs.Core/Runtime/Transpiler/FunctionTranspiler.cs +++ b/Wacs.Core/Runtime/Transpiler/FunctionTranspiler.cs @@ -14,8 +14,10 @@ // * limitations under the License. // */ +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Linq; using Wacs.Core.Instructions; using Wacs.Core.Instructions.Transpiler; @@ -86,6 +88,7 @@ private static InstructionSequence OptimizeSequence(InstructionSequence seq) var newIf = new InstIf().Immediate(instIf.Type, newIfSeq, newElseSeq); //copy stackheight newIf.Label = new Label(instIf.Label); + stack.Push(newIf); break; case InstLocalTee instTee: @@ -145,6 +148,9 @@ private static IInstruction OptimizeInstruction(IOptimizationTarget inst, Stack< //Memory Store case IValueConsumer valueConsumer: return BindU32Value(inst, stack, valueConsumer); + //Select + case IValueConsumer valueConsumer: return BindValueValueI32(inst, stack, valueConsumer); + default: return inst; } } @@ -508,7 +514,7 @@ private static IInstruction BindU32Value(IInstruction inst, Stack { if (i1Producer == null) { - i1Producer = new CastToU32(new InstStackProducer()); + i1Producer = new InstStackProducerU32(); if (i1 != null) stack.Push(i1); i1 = null; } @@ -524,6 +530,86 @@ private static IInstruction BindU32Value(IInstruction inst, Stack stack.Push(i2); return inst; } + + private static IInstruction BindValueValueI32(IInstruction inst, Stack stack, IValueConsumer valueConsumer) + { + Stack operands = new(); + + bool tryStack = true; + ITypedValueProducer? i1Producer = null; + ITypedValueProducer? i2Producer = null; + ITypedValueProducer? cProducer = null; + + if (stack.Count > 0 && tryStack) + { + var c = stack.Pop(); + operands.Push(c); + cProducer = c switch + { + ITypedValueProducer p => new UnwrapValueI32(p), + ITypedValueProducer p => new CastToI32(p), + ITypedValueProducer p => p, + _ => null + }; + if (cProducer == null) + tryStack = false; + } + if (stack.Count > 0 && tryStack) + { + var i2 = stack.Pop(); + operands.Push(i2); + i2Producer = i2 switch + { + ITypedValueProducer p => p, + ITypedValueProducer p => new WrapValueI32(p), + ITypedValueProducer p => new WrapValueU32(p), + ITypedValueProducer p => new WrapValueI64(p), + ITypedValueProducer p => new WrapValueU64(p), + ITypedValueProducer p => new WrapValueF32(p), + ITypedValueProducer p => new WrapValueF64(p), + ITypedValueProducer p => new WrapValueV128(p), + _ => null, + }; + if (i2Producer == null) + tryStack = false; + } + if (stack.Count > 0 && tryStack) + { + var i1 = stack.Pop(); + operands.Push(i1); + i1Producer = i1 switch + { + ITypedValueProducer p => p, + ITypedValueProducer p => new WrapValueI32(p), + ITypedValueProducer p => new WrapValueU32(p), + ITypedValueProducer p => new WrapValueI64(p), + ITypedValueProducer p => new WrapValueU64(p), + ITypedValueProducer p => new WrapValueF32(p), + ITypedValueProducer p => new WrapValueF64(p), + ITypedValueProducer p => new WrapValueV128(p), + _ => null, + }; + if (i1Producer == null) + stack.Push(operands.Pop()); + } + + if (cProducer is not null && i2Producer is not null) + { + i1Producer ??= new InstStackProducerValue(); + switch (valueConsumer) { + case INodeComputer nodeComputer: + return new InstAggregate3_1(i1Producer,i2Producer, cProducer, nodeComputer); + } + } + + while (operands.Count > 0) + stack.Push(operands.Pop()); + + return inst; + } + + + private static IInstruction BindF32(IOptimizationTarget inst, Stack stack, IValueConsumer floatConsumer) { From 3c153c89920d673abcf6271fde97d0d0df7f79b8 Mon Sep 17 00:00:00 2001 From: Kelvin Nishikawa Date: Thu, 28 Nov 2024 22:44:50 -0800 Subject: [PATCH 14/24] Lifting Async invocation for synchronous calls --- Wacs.Core/Instructions/Control.cs | 7 +-- Wacs.Core/Instructions/TailCall.cs | 8 +--- Wacs.Core/Runtime/ExecContext.cs | 34 ++++++++++++-- Wacs.Core/Runtime/Types/FunctionInstance.cs | 2 + Wacs.Core/Runtime/Types/IFunctionInstance.cs | 1 + Wacs.Core/Runtime/WasmRuntimeExecution.cs | 2 +- Wacs.Core/Runtime/WasmRuntimeInstantiation.cs | 47 +++++++++++++++++-- 7 files changed, 81 insertions(+), 20 deletions(-) diff --git a/Wacs.Core/Instructions/Control.cs b/Wacs.Core/Instructions/Control.cs index a12a1f5..8ab8745 100644 --- a/Wacs.Core/Instructions/Control.cs +++ b/Wacs.Core/Instructions/Control.cs @@ -782,8 +782,6 @@ public override void Execute(ExecContext context) $"Instruction call failed. Function address for {X} was not in the Context."); var a = context.Frame.Module.FuncAddrs[X]; context.Invoke(a); - - throw new WasmRuntimeException("Synchronous execution path not allowed."); } public override async ValueTask ExecuteAsync(ExecContext context) @@ -791,7 +789,7 @@ public override async ValueTask ExecuteAsync(ExecContext context) context.Assert( context.Frame.Module.FuncAddrs.Contains(X), $"Instruction call failed. Function address for {X} was not in the Context."); var a = context.Frame.Module.FuncAddrs[X]; - await context.Invoke(a); + await context.InvokeAsync(a); } /// @@ -946,7 +944,6 @@ public override void Execute(ExecContext context) throw new TrapException($"Instruction call_indirect failed. Expected FunctionType differed."); //19. context.Invoke(a); - throw new WasmRuntimeException("Synchronous execution path not allowed"); } public override async ValueTask ExecuteAsync(ExecContext context) @@ -995,7 +992,7 @@ public override async ValueTask ExecuteAsync(ExecContext context) if (!ftExpect.Matches(ftActual)) throw new TrapException($"Instruction call_indirect failed. Expected FunctionType differed."); //19. - await context.Invoke(a); + await context.InvokeAsync(a); } /// diff --git a/Wacs.Core/Instructions/TailCall.cs b/Wacs.Core/Instructions/TailCall.cs index f9852f7..420b54f 100644 --- a/Wacs.Core/Instructions/TailCall.cs +++ b/Wacs.Core/Instructions/TailCall.cs @@ -88,8 +88,6 @@ public override void Execute(ExecContext context) //Reuse the pointer from the outgoing function context.Frame.ContinuationAddress = address; - - throw new WasmRuntimeException("Synchronous execution path not allowed"); } public override async ValueTask ExecuteAsync(ExecContext context) @@ -108,7 +106,7 @@ public override async ValueTask ExecuteAsync(ExecContext context) // context.OpStack.Push(values); //Call - await context.Invoke(a); + await context.InvokeAsync(a); //Reuse the pointer from the outgoing function context.Frame.ContinuationAddress = address; @@ -286,8 +284,6 @@ public override void Execute(ExecContext context) //Reuse the pointer from the outgoing function context.Frame.ContinuationAddress = address; - - throw new WasmRuntimeException("Synchronous execution path not allowed"); } public override async ValueTask ExecuteAsync(ExecContext context) @@ -346,7 +342,7 @@ public override async ValueTask ExecuteAsync(ExecContext context) // context.OpStack.Push(values); //19. - await context.Invoke(a); + await context.InvokeAsync(a); //Reuse the pointer from the outgoing function context.Frame.ContinuationAddress = address; diff --git a/Wacs.Core/Runtime/ExecContext.cs b/Wacs.Core/Runtime/ExecContext.cs index dcd9e6f..179b689 100644 --- a/Wacs.Core/Runtime/ExecContext.cs +++ b/Wacs.Core/Runtime/ExecContext.cs @@ -223,7 +223,7 @@ public void ExitBlock() } // @Spec 4.4.10.1 Function Invocation - public async Task Invoke(FuncAddr addr) + public async Task InvokeAsync(FuncAddr addr) { //1. Assert( Store.Contains(addr), @@ -249,7 +249,6 @@ public async Task Invoke(FuncAddr addr) } if (hostFunc.IsAsync) { - //Pass them await hostFunc.InvokeAsync(hostFunc.ParameterBuffer, OpStack); } @@ -262,12 +261,37 @@ public async Task Invoke(FuncAddr addr) } } - private void Invoke(FunctionInstance wasmFunc, FuncIdx idx) + public void Invoke(FuncAddr addr) { - + //1. + Assert( Store.Contains(addr), + $"Failure in Function Invocation. Address does not exist {addr}"); + + //2. + var funcInst = Store[addr]; + if (funcInst.IsAsync) + throw new WasmRuntimeException("Cannot call asynchronous function synchronously"); + + switch (funcInst) + { + case FunctionInstance wasmFunc: + wasmFunc.Invoke(this); + return; + case HostFunction hostFunc: + { + var funcType = hostFunc.Type; + //Fetch the parameters + OpStack.PopScalars(funcType.ParameterTypes, hostFunc.ParameterBuffer, hostFunc.PassExecContext?1:0); + if (hostFunc.PassExecContext) + { + hostFunc.ParameterBuffer[0] = this; + } + //Pass them + hostFunc.Invoke(hostFunc.ParameterBuffer, OpStack); + } return; + } } - // @Spec 4.4.10.2. Returning from a function public void FunctionReturn() { diff --git a/Wacs.Core/Runtime/Types/FunctionInstance.cs b/Wacs.Core/Runtime/Types/FunctionInstance.cs index d1777a4..0ace4e7 100644 --- a/Wacs.Core/Runtime/Types/FunctionInstance.cs +++ b/Wacs.Core/Runtime/Types/FunctionInstance.cs @@ -72,6 +72,8 @@ public FunctionInstance(ModuleInstance module, Module.Function definition) public string Id => string.IsNullOrEmpty(Name)?"":$"{ModuleName}.{Name}"; public bool IsExport { get; set; } + public bool IsAsync => false; + /// /// Sets Body and precomputes labels /// diff --git a/Wacs.Core/Runtime/Types/IFunctionInstance.cs b/Wacs.Core/Runtime/Types/IFunctionInstance.cs index 4f4482e..0991260 100644 --- a/Wacs.Core/Runtime/Types/IFunctionInstance.cs +++ b/Wacs.Core/Runtime/Types/IFunctionInstance.cs @@ -25,6 +25,7 @@ public interface IFunctionInstance { FunctionType Type { get; } public string Id { get; } + public bool IsAsync { get; } public string Name { get; } public bool IsExport { get; set; } public void SetName(string name); diff --git a/Wacs.Core/Runtime/WasmRuntimeExecution.cs b/Wacs.Core/Runtime/WasmRuntimeExecution.cs index afcdff5..b2849ae 100644 --- a/Wacs.Core/Runtime/WasmRuntimeExecution.cs +++ b/Wacs.Core/Runtime/WasmRuntimeExecution.cs @@ -104,7 +104,7 @@ Value[] GenericDelegate(params object[] args) Context.ProcessTimer.Restart(); Context.InstructionTimer.Restart(); - var task = Context.Invoke(funcAddr); + var task = Context.InvokeAsync(funcAddr); task.Wait(); Context.steps = 0; diff --git a/Wacs.Core/Runtime/WasmRuntimeInstantiation.cs b/Wacs.Core/Runtime/WasmRuntimeInstantiation.cs index 3bb2ffd..3b0c1a4 100644 --- a/Wacs.Core/Runtime/WasmRuntimeInstantiation.cs +++ b/Wacs.Core/Runtime/WasmRuntimeInstantiation.cs @@ -315,7 +315,11 @@ public ModuleInstance InstantiateModule(Module module, RuntimeOptions? options = break; } } - + + if (TranspileModules) + TranspileModule(moduleInstance); + + SynchronizeFunctionCalls(moduleInstance); //17. if (module.StartIndex != FuncIdx.Default) @@ -356,8 +360,6 @@ public ModuleInstance InstantiateModule(Module module, RuntimeOptions? options = _moduleInstances.Add(moduleInstance); - if (TranspileModules) - TranspileModule(moduleInstance); } catch (WasmRuntimeException exc) { @@ -397,7 +399,46 @@ public ModuleInstance InstantiateModule(Module module, RuntimeOptions? options = } return moduleInstance; } + + public void SynchronizeFunctionCalls(ModuleInstance moduleInstance) + { + foreach (var funcAddr in moduleInstance.FuncAddrs) + { + var instance = Store[funcAddr]; + if (instance is FunctionInstance { Definition: { IsImport: false } } functionInstance) + if (functionInstance.Module == moduleInstance) + { + var expr = functionInstance.Body; + SynchronizeInstructions(moduleInstance, expr.Instructions); + } + } + } + private void SynchronizeInstructions(ModuleInstance moduleInstance, InstructionSequence seq) + { + foreach (var inst in seq) + { + switch (inst) + { + case InstCall instCall: + var addr = moduleInstance.FuncAddrs[instCall.X]; + var funcInst = Store[addr]; + if (funcInst is FunctionInstance) + instCall.IsAsync = false; + break; + case InstBlock instBlock: + SynchronizeInstructions(moduleInstance, instBlock.GetBlock(0).Instructions); + break; + case InstLoop instLoop: + SynchronizeInstructions(moduleInstance, instLoop.GetBlock(0).Instructions); + break; + case InstIf instIf: + SynchronizeInstructions(moduleInstance, instIf.GetBlock(0).Instructions); + SynchronizeInstructions(moduleInstance, instIf.GetBlock(1).Instructions); + break; + } + } + } /// /// @Spec 4.5.3.1. Functions From 4e29129f10fe623b33f41af019f48ea85946b0d9 Mon Sep 17 00:00:00 2001 From: Kelvin Nishikawa Date: Thu, 28 Nov 2024 22:52:36 -0800 Subject: [PATCH 15/24] Fanning out MemoryOps for direct value passing --- Wacs.Core/Instructions/Memory.cs | 314 +++--------------- Wacs.Core/Instructions/Memory/FMemoryLoad.cs | 93 ++++++ Wacs.Core/Instructions/Memory/FMemoryStore.cs | 115 +++++++ .../Instructions/Memory/I32MemoryLoad.cs | 194 +++++++++++ .../Instructions/Memory/I64MemoryLoad.cs | 259 +++++++++++++++ Wacs.Core/Instructions/Memory/InstI32Store.cs | 156 +++++++++ Wacs.Core/Instructions/Memory/InstI64Store.cs | 201 +++++++++++ Wacs.Core/Instructions/SIMD/VMemory.cs | 78 +++++ .../Instructions/SpecFactory/SpecFactory.cs | 47 +-- .../Instructions/SpecFactory/SpecFactoryFD.cs | 5 +- .../Transpiler/AggregateTypeCast.cs | 11 + .../Runtime/Transpiler/FunctionTranspiler.cs | 146 +++++++- 12 files changed, 1310 insertions(+), 309 deletions(-) create mode 100644 Wacs.Core/Instructions/Memory/FMemoryLoad.cs create mode 100644 Wacs.Core/Instructions/Memory/FMemoryStore.cs create mode 100644 Wacs.Core/Instructions/Memory/I32MemoryLoad.cs create mode 100644 Wacs.Core/Instructions/Memory/I64MemoryLoad.cs create mode 100644 Wacs.Core/Instructions/Memory/InstI32Store.cs create mode 100644 Wacs.Core/Instructions/Memory/InstI64Store.cs diff --git a/Wacs.Core/Instructions/Memory.cs b/Wacs.Core/Instructions/Memory.cs index fb3590e..2374bc6 100644 --- a/Wacs.Core/Instructions/Memory.cs +++ b/Wacs.Core/Instructions/Memory.cs @@ -27,53 +27,23 @@ // 5.4.6 Memory Instructions namespace Wacs.Core.Instructions { - public class InstMemoryLoad : InstructionBase, INodeComputer + public abstract class InstMemoryLoad : InstructionBase { private readonly ValType Type; private readonly BitWidth WidthT; + protected readonly int WidthTByteSize; - private MemArg M; + protected MemArg M; - public InstMemoryLoad(ValType type, BitWidth width) => - (Type, WidthT) = (type, width); + public override ByteCode Op { get; } - public override ByteCode Op => Type switch + protected InstMemoryLoad(ValType type, BitWidth width, ByteCode opcode) { - ValType.I32 => WidthT switch - { - BitWidth.U32 => OpCode.I32Load, //0x28 - BitWidth.S8 => OpCode.I32Load8S, //0x2C - BitWidth.U8 => OpCode.I32Load8U, //0x2D - BitWidth.S16 => OpCode.I32Load16S, //0x2E - BitWidth.U16 => OpCode.I32Load16U, //0x2F - _ => throw new InvalidDataException($"InstMemoryLoad instruction is malformed: {Type} {WidthT}") - }, - ValType.I64 => WidthT switch - { - BitWidth.U64 => OpCode.I64Load, //0x29 - BitWidth.S8 => OpCode.I64Load8S, //0x30 - BitWidth.U8 => OpCode.I64Load8U, //0x31 - BitWidth.S16 => OpCode.I64Load16S, //0x32 - BitWidth.U16 => OpCode.I64Load16U, //0x33 - BitWidth.S32 => OpCode.I64Load32S, //0x34 - BitWidth.U32 => OpCode.I64Load32U, //0x35 - _ => throw new InvalidDataException($"InstMemoryLoad instruction is malformed: {Type} {WidthT}") - }, - ValType.V128 => WidthT switch - { - BitWidth.S8 => SimdCode.V128Load8x8S, - BitWidth.U8 => SimdCode.V128Load8x8U, - BitWidth.S16 => SimdCode.V128Load16x4S, - BitWidth.U16 => SimdCode.V128Load16x4U, - BitWidth.S32 => SimdCode.V128Load32x2S, - BitWidth.U32 => SimdCode.V128Load32x2U, - BitWidth.V128 => SimdCode.V128Load, - _ => throw new InvalidDataException($"InstMemoryLoad instruction is malformed: {Type} {WidthT}") - }, - ValType.F32 => OpCode.F32Load, //0x2A - ValType.F64 => OpCode.F64Load, //0x2B - _ => throw new InvalidDataException($"InstMemoryLoad instruction is malformed: {Type}"), - }; + Type = type; + WidthT = width; + WidthTByteSize = WidthT.ByteSize(); + Op = opcode; + } /// /// @Spec 3.3.7.1. t.load @@ -83,32 +53,25 @@ public override void Validate(IWasmValidationContext context) { context.Assert(context.Mems.Contains(M.M), "Instruction {0} failed with invalid context memory 0.",Op.GetMnemonic()); - context.Assert(M.Align.LinearSize() <= WidthT.ByteSize(), + context.Assert(M.Align.LinearSize() <= WidthTByteSize, "Instruction {0} failed with invalid alignment {1} <= {2}/8",Op.GetMnemonic(),M.Align.LinearSize(),WidthT); context.OpStack.PopI32(); context.OpStack.PushType(Type); } - - public override void Execute(ExecContext context) - { - //6. - context.Assert( context.OpStack.Peek().IsI32, - $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); - uint offset = context.OpStack.PopU32(); - var value = FetchFromMemory(context, offset); - context.OpStack.PushValue(value); - } - - public Func GetFunc => FetchFromMemory; - public override IInstruction Parse(BinaryReader reader) { M = MemArg.Parse(reader); return this; } + public IInstruction Immediate(MemArg m) + { + M = m; + return this; + } + public override string RenderText(ExecContext? context) { if (context != null) @@ -121,119 +84,26 @@ public override string RenderText(ExecContext? context) } return $"{base.RenderText(context)}{M.ToWat(WidthT)}"; } - - // @Spec 4.4.7.1. t.load and t.loadN_sx - public Value FetchFromMemory(ExecContext context, uint offset) - { - //2. - context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), - $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); - //3. - var a = context.Frame.Module.MemAddrs[M.M]; - //4. - context.Assert( context.Store.Contains(a), - $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); - //5. - var mem = context.Store[a]; - //7. - long i = offset; - //8. - long ea = (long)i + (long)M.Offset; - //9. - int n = WidthT.ByteSize(); - //10. - if (ea + n > mem.Data.Length) - throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer {ea}+{n} out of bounds ({mem.Data.Length})."); - //11. - var bs = mem.Data.AsSpan((int)ea, n); - //12,13,14 - switch (Type) - { - case ValType.F32: - { - float cF32 = BitConverter.ToSingle(bs); - return new Value(cF32); - } - case ValType.F64: - { - double cF64 = BitConverter.ToDouble(bs); - return new Value(cF64); - } - case ValType.V128: - { - V128 v128 = new V128(bs); - return new Value(v128); - } - default: - switch (WidthT) - { - case BitWidth.S8: - int cS8 = (sbyte)bs[0]; - return new Value(Type, cS8); - case BitWidth.S16: - int cS16 = BitConverter.ToInt16(bs); - return new Value(Type, cS16); - case BitWidth.S32: - int cS32 = BitConverter.ToInt32(bs); - return new Value(Type, cS32); - case BitWidth.U8: - uint cU8 = bs[0]; - return new Value(Type, cU8); - case BitWidth.U16: - uint cU16 = BitConverter.ToUInt16(bs); - return new Value(Type, cU16); - case BitWidth.U32: - uint cU32 = BitConverter.ToUInt32(bs); - return new Value(Type, cU32); - case BitWidth.U64: - ulong cU64 = BitConverter.ToUInt64(bs); - return new Value(Type, cU64); - default: throw new ArgumentOutOfRangeException(); - } - } - } - + public int CalculateSize() => 1; - - public IInstruction Immediate(MemArg m) - { - M = m; - return this; - } } - public class InstMemoryStore : InstructionBase, INodeConsumer + public abstract class InstMemoryStore : InstructionBase { - private readonly BitWidth TWidth; - - private readonly ValType Type; - private MemArg M; - - public InstMemoryStore(ValType type, BitWidth width) => - (Type, TWidth) = (type, width); + protected readonly ValType Type; + private readonly BitWidth WidthT; + protected readonly int WidthTByteSize; + protected MemArg M; - public override ByteCode Op => Type switch + public InstMemoryStore(ValType type, BitWidth widthT, ByteCode opcode) { - ValType.I32 => TWidth switch - { - BitWidth.U8 => OpCode.I32Store8, - BitWidth.U16 => OpCode.I32Store16, - BitWidth.U32 => OpCode.I32Store, - _ => throw new InvalidDataException($"InstMemoryStore instruction is malformed: {Type} {TWidth}") - }, - ValType.I64 => TWidth switch - { - BitWidth.U8 => OpCode.I64Store8, - BitWidth.U16 => OpCode.I64Store16, - BitWidth.U32 => OpCode.I64Store32, - BitWidth.U64 => OpCode.I64Store, - _ => throw new InvalidDataException($"InstMemoryStore instruction is malformed: {Type} {TWidth}") - }, - ValType.F32 => OpCode.F32Store, - ValType.F64 => OpCode.F64Store, - ValType.V128 => SimdCode.V128Store, - _ => throw new InvalidDataException($"InstMemoryStore instruction is malformed: {Type}"), - }; + Type = type; + WidthT = widthT; + WidthTByteSize = WidthT.ByteSize(); + Op = opcode; + } + + public override ByteCode Op { get; } /// /// @Spec 3.3.7.3. t.store @@ -243,39 +113,24 @@ public override void Validate(IWasmValidationContext context) { context.Assert(context.Mems.Contains(M.M), "Instruction {0} failed with invalid context memory 0.",Op.GetMnemonic()); - context.Assert(M.Align.LinearSize() <= TWidth.ByteSize(), - "Instruction {0} failed with invalid alignment {1} <= {2}/8",Op.GetMnemonic(),M.Align.LinearSize(),TWidth); + context.Assert(M.Align.LinearSize() <= WidthT.ByteSize(), + "Instruction {0} failed with invalid alignment {1} <= {2}/8",Op.GetMnemonic(),M.Align.LinearSize(),WidthT); //Pop parameters from right to left context.OpStack.PopType(Type); context.OpStack.PopI32(); } - // @Spec 4.4.7.6. t.store - // @Spec 4.4.7.6. t.storeN - public override void Execute(ExecContext context) - { - //6. - context.Assert( context.OpStack.Peek().Type == Type, - $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); - //7. - var c = context.OpStack.PopAny(); - //8. - context.Assert( context.OpStack.Peek().IsI32, - $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); - uint offset = context.OpStack.PopU32(); - - SetMemoryValue(context, offset, c); - } - - - public Action GetFunc => SetMemoryValue; - public override IInstruction Parse(BinaryReader reader) { M = MemArg.Parse(reader); return this; } + public IInstruction Immediate(MemArg m) + { + M = m; + return this; + } public override string RenderText(ExecContext? context) { @@ -284,99 +139,10 @@ public override string RenderText(ExecContext? context) if (context.Attributes.Live && context.OpStack.Count > 0) { var storeValue = context.OpStack.Peek(); - return $"{base.RenderText(context)}{M.ToWat(TWidth)} (;>{storeValue}<;)"; + return $"{base.RenderText(context)}{M.ToWat(WidthT)} (;>{storeValue}<;)"; } } - return $"{base.RenderText(context)}{M.ToWat(TWidth)}"; - } - - public IInstruction Immediate(MemArg m) - { - M = m; - return this; - } - - public void SetMemoryValue(ExecContext context, uint offset, Value c) - { - //2. - context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), - $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); - //3. - var a = context.Frame.Module.MemAddrs[M.M]; - //4. - context.Assert( context.Store.Contains(a), - $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); - //5. - var mem = context.Store[a]; - - //9. - long i = offset; - //10. - long ea = i + M.Offset; - //11. - // We set the Width in the InstructionFactory - // Floating point width will always match their type - //12. - if (ea + TWidth.ByteSize() > mem.Data.Length) - throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer out of bounds."); - //13,14,15 - Span bs = mem.Data.AsSpan((int)ea, TWidth.ByteSize()); - switch (TWidth) - { - case BitWidth.S8: - case BitWidth.U8: - byte cU8 = (byte)(0xFF & c.Int32); - bs[0] = cU8; - break; - case BitWidth.S16: - short cI16 = (short)c.Int32; -#if NETSTANDARD2_1 - MemoryMarshal.Write(bs, ref cI16); -#else - MemoryMarshal.Write(bs, in cI16); // Assume you can change to 'in' -#endif - break; - case BitWidth.U16: - ushort cU16 = (ushort)c.UInt32; -#if NETSTANDARD2_1 - MemoryMarshal.Write(bs, ref cU16); -#else - MemoryMarshal.Write(bs, in cU16); -#endif - break; - case BitWidth.S32: - int cI32 = c.Int32; -#if NETSTANDARD2_1 - MemoryMarshal.Write(bs, ref cI32); -#else - MemoryMarshal.Write(bs, in cI32); -#endif - break; - case BitWidth.U32: - uint cU32 = c.UInt32; -#if NETSTANDARD2_1 - MemoryMarshal.Write(bs, ref cU32); -#else - MemoryMarshal.Write(bs, in cU32); -#endif - break; - case BitWidth.U64: - ulong cU64 = c.UInt64; -#if NETSTANDARD2_1 - MemoryMarshal.Write(bs, ref cU64); -#else - MemoryMarshal.Write(bs, in cU64); -#endif - break; - case BitWidth.V128: - V128 cV128 = c.V128; -#if NETSTANDARD2_1 - MemoryMarshal.Write(bs, ref cV128); -#else - MemoryMarshal.Write(bs, in cV128); -#endif - break; - } + return $"{base.RenderText(context)}{M.ToWat(WidthT)}"; } public int CalculateSize() => 1; diff --git a/Wacs.Core/Instructions/Memory/FMemoryLoad.cs b/Wacs.Core/Instructions/Memory/FMemoryLoad.cs new file mode 100644 index 0000000..cb971d5 --- /dev/null +++ b/Wacs.Core/Instructions/Memory/FMemoryLoad.cs @@ -0,0 +1,93 @@ +// /* +// * Copyright 2024 Kelvin Nishikawa +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ + +using System; +using System.Runtime.InteropServices; +using Wacs.Core.Instructions.Transpiler; +using Wacs.Core.OpCodes; +using Wacs.Core.Runtime; +using Wacs.Core.Runtime.Types; +using Wacs.Core.Types; + +namespace Wacs.Core.Instructions.Memory +{ + public class InstF32Load : InstMemoryLoad, INodeComputer + { + public InstF32Load() : base(ValType.F32, BitWidth.U32, OpCode.F32Load) {} + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + float value = FetchFromMemory(context, offset); + context.OpStack.PushValue(value); + } + + //@Spec 4.4.7.1. t.load and t.loadN_sx + public float FetchFromMemory(ExecContext context, uint offset) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + long i = offset; + long ea = (long)i + (long)M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer {ea}+{WidthTByteSize} out of bounds ({mem.Data.Length})."); + var bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + + return MemoryMarshal.Read(bs); + } + + public Func GetFunc => FetchFromMemory; + } + + + public class InstF64Load : InstMemoryLoad, INodeComputer + { + public InstF64Load() : base(ValType.F64, BitWidth.U64, OpCode.F64Load) {} + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + double value = FetchFromMemory(context, offset); + context.OpStack.PushValue(value); + } + + //@Spec 4.4.7.1. t.load and t.loadN_sx + public double FetchFromMemory(ExecContext context, uint offset) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + long i = offset; + long ea = (long)i + (long)M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer {ea}+{WidthTByteSize} out of bounds ({mem.Data.Length})."); + var bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + + return MemoryMarshal.Read(bs); + } + + public Func GetFunc => FetchFromMemory; + } +} \ No newline at end of file diff --git a/Wacs.Core/Instructions/Memory/FMemoryStore.cs b/Wacs.Core/Instructions/Memory/FMemoryStore.cs new file mode 100644 index 0000000..42db6ce --- /dev/null +++ b/Wacs.Core/Instructions/Memory/FMemoryStore.cs @@ -0,0 +1,115 @@ +// /* +// * Copyright 2024 Kelvin Nishikawa +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ + +using System; +using System.Runtime.InteropServices; +using Wacs.Core.Instructions.Transpiler; +using Wacs.Core.OpCodes; +using Wacs.Core.Runtime; +using Wacs.Core.Runtime.Types; +using Wacs.Core.Types; + +namespace Wacs.Core.Instructions.Memory +{ + public class InstF32Store : InstMemoryStore, INodeConsumer + { + public InstF32Store() : base(ValType.F32, BitWidth.U32, OpCode.F32Store) { } + + // @Spec 4.4.7.6. t.store + // @Spec 4.4.7.6. t.storeN + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().Type == Type, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + float c = context.OpStack.PopF32(); + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + + SetMemoryValue(context, offset, c); + } + + public void SetMemoryValue(ExecContext context, uint offset, float cF32) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + + long i = offset; + long ea = i + M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer out of bounds."); + //13,14,15 + Span bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + +#if NETSTANDARD2_1 + MemoryMarshal.Write(bs, ref cF32); +#else + MemoryMarshal.Write(bs, in cF32); +#endif + } + + public Action GetFunc => SetMemoryValue; + } + + public class InstF64Store : InstMemoryStore, INodeConsumer + { + public InstF64Store() : base(ValType.F64, BitWidth.U64, OpCode.F64Store) { } + + // @Spec 4.4.7.6. t.store + // @Spec 4.4.7.6. t.storeN + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().Type == Type, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + double c = context.OpStack.PopF64(); + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + + SetMemoryValue(context, offset, c); + } + + public void SetMemoryValue(ExecContext context, uint offset, double cF64) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + + long i = offset; + long ea = i + M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer out of bounds."); + //13,14,15 + Span bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + +#if NETSTANDARD2_1 + MemoryMarshal.Write(bs, ref cF64); +#else + MemoryMarshal.Write(bs, in cF64); +#endif + } + + public Action GetFunc => SetMemoryValue; + } + +} \ No newline at end of file diff --git a/Wacs.Core/Instructions/Memory/I32MemoryLoad.cs b/Wacs.Core/Instructions/Memory/I32MemoryLoad.cs new file mode 100644 index 0000000..29abfb7 --- /dev/null +++ b/Wacs.Core/Instructions/Memory/I32MemoryLoad.cs @@ -0,0 +1,194 @@ +// /* +// * Copyright 2024 Kelvin Nishikawa +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ + +using System; +using System.Runtime.InteropServices; +using Wacs.Core.Instructions.Transpiler; +using Wacs.Core.OpCodes; +using Wacs.Core.Runtime; +using Wacs.Core.Runtime.Types; +using Wacs.Core.Types; + +namespace Wacs.Core.Instructions.Memory +{ + public class InstI32Load : InstMemoryLoad, INodeComputer + { + public InstI32Load() : base(ValType.I32, BitWidth.U32, OpCode.I32Load) {} + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + uint value = FetchFromMemory(context, offset); + context.OpStack.PushValue(value); + } + + //@Spec 4.4.7.1. t.load and t.loadN_sx + public uint FetchFromMemory(ExecContext context, uint offset) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + long i = offset; + long ea = (long)i + (long)M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer {ea}+{WidthTByteSize} out of bounds ({mem.Data.Length})."); + var bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + + return MemoryMarshal.Read(bs); + } + + public Func GetFunc => FetchFromMemory; + } + + public class InstI32Load8S : InstMemoryLoad, INodeComputer + { + public InstI32Load8S() : base(ValType.I32, BitWidth.S8, OpCode.I32Load8S) {} + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + int value = FetchFromMemory(context, offset); + context.OpStack.PushValue(value); + } + + //@Spec 4.4.7.1. t.load and t.loadN_sx + public int FetchFromMemory(ExecContext context, uint offset) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + long i = offset; + long ea = (long)i + (long)M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer {ea}+{WidthTByteSize} out of bounds ({mem.Data.Length})."); + var bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + + int cS8 = (sbyte)bs[0]; + return cS8; + } + + public Func GetFunc => FetchFromMemory; + } + + public class InstI32Load8U : InstMemoryLoad, INodeComputer + { + public InstI32Load8U() : base(ValType.I32, BitWidth.U8, OpCode.I32Load8U) {} + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + uint value = FetchFromMemory(context, offset); + context.OpStack.PushValue(value); + } + + //@Spec 4.4.7.1. t.load and t.loadN_sx + public uint FetchFromMemory(ExecContext context, uint offset) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + long i = offset; + long ea = (long)i + (long)M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer {ea}+{WidthTByteSize} out of bounds ({mem.Data.Length})."); + var bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + + + uint cU8 = bs[0]; + return cU8; + } + + public Func GetFunc => FetchFromMemory; + } + + public class InstI32Load16S : InstMemoryLoad, INodeComputer + { + public InstI32Load16S() : base(ValType.I32, BitWidth.S16, OpCode.I32Load16S) {} + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + int value = FetchFromMemory(context, offset); + context.OpStack.PushValue(value); + } + + //@Spec 4.4.7.1. t.load and t.loadN_sx + public int FetchFromMemory(ExecContext context, uint offset) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + long i = offset; + long ea = (long)i + (long)M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer {ea}+{WidthTByteSize} out of bounds ({mem.Data.Length})."); + var bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + + return MemoryMarshal.Read(bs); + } + + public Func GetFunc => FetchFromMemory; + } + + public class InstI32Load16U : InstMemoryLoad, INodeComputer + { + public InstI32Load16U() : base(ValType.I32, BitWidth.U16, OpCode.I32Load16U) {} + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + uint value = FetchFromMemory(context, offset); + context.OpStack.PushValue(value); + } + + //@Spec 4.4.7.1. t.load and t.loadN_sx + public uint FetchFromMemory(ExecContext context, uint offset) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + long i = offset; + long ea = (long)i + (long)M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer {ea}+{WidthTByteSize} out of bounds ({mem.Data.Length})."); + var bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + + return MemoryMarshal.Read(bs); + } + + public Func GetFunc => FetchFromMemory; + } +} \ No newline at end of file diff --git a/Wacs.Core/Instructions/Memory/I64MemoryLoad.cs b/Wacs.Core/Instructions/Memory/I64MemoryLoad.cs new file mode 100644 index 0000000..a63eb4d --- /dev/null +++ b/Wacs.Core/Instructions/Memory/I64MemoryLoad.cs @@ -0,0 +1,259 @@ +// /* +// * Copyright 2024 Kelvin Nishikawa +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ + +using System; +using System.Runtime.InteropServices; +using Wacs.Core.Instructions.Transpiler; +using Wacs.Core.OpCodes; +using Wacs.Core.Runtime; +using Wacs.Core.Runtime.Types; +using Wacs.Core.Types; + +namespace Wacs.Core.Instructions.Memory +{ + public class InstI64Load : InstMemoryLoad, INodeComputer + { + public InstI64Load() : base(ValType.I64, BitWidth.U64, OpCode.I64Load) {} + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + ulong value = FetchFromMemory(context, offset); + context.OpStack.PushValue(value); + } + + //@Spec 4.4.7.1. t.load and t.loadN_sx + public ulong FetchFromMemory(ExecContext context, uint offset) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + long i = offset; + long ea = (long)i + (long)M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer {ea}+{WidthTByteSize} out of bounds ({mem.Data.Length})."); + var bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + + return MemoryMarshal.Read(bs); + } + + public Func GetFunc => FetchFromMemory; + } + + public class InstI64Load8S : InstMemoryLoad, INodeComputer + { + public InstI64Load8S() : base(ValType.I64, BitWidth.S8, OpCode.I64Load8S) {} + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + long value = FetchFromMemory(context, offset); + context.OpStack.PushValue(value); + } + + //@Spec 4.4.7.1. t.load and t.loadN_sx + public long FetchFromMemory(ExecContext context, uint offset) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + long i = offset; + long ea = (long)i + (long)M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer {ea}+{WidthTByteSize} out of bounds ({mem.Data.Length})."); + var bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + + int cS8 = (sbyte)bs[0]; + return cS8; + } + + public Func GetFunc => FetchFromMemory; + } + + public class InstI64Load8U : InstMemoryLoad, INodeComputer + { + public InstI64Load8U() : base(ValType.I64, BitWidth.U8, OpCode.I64Load8U) {} + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + ulong value = FetchFromMemory(context, offset); + context.OpStack.PushValue(value); + } + + //@Spec 4.4.7.1. t.load and t.loadN_sx + public ulong FetchFromMemory(ExecContext context, uint offset) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + long i = offset; + long ea = (long)i + (long)M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer {ea}+{WidthTByteSize} out of bounds ({mem.Data.Length})."); + var bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + + uint cU8 = bs[0]; + return cU8; + } + + public Func GetFunc => FetchFromMemory; + } + + public class InstI64Load16S : InstMemoryLoad, INodeComputer + { + public InstI64Load16S() : base(ValType.I64, BitWidth.S16, OpCode.I64Load16S) {} + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + long value = FetchFromMemory(context, offset); + context.OpStack.PushValue(value); + } + + //@Spec 4.4.7.1. t.load and t.loadN_sx + public long FetchFromMemory(ExecContext context, uint offset) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + long i = offset; + long ea = (long)i + (long)M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer {ea}+{WidthTByteSize} out of bounds ({mem.Data.Length})."); + var bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + + return MemoryMarshal.Read(bs); + } + + public Func GetFunc => FetchFromMemory; + } + + public class InstI64Load16U : InstMemoryLoad, INodeComputer + { + public InstI64Load16U() : base(ValType.I64, BitWidth.U16, OpCode.I64Load16U) {} + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + ulong value = FetchFromMemory(context, offset); + context.OpStack.PushValue(value); + } + + //@Spec 4.4.7.1. t.load and t.loadN_sx + public ulong FetchFromMemory(ExecContext context, uint offset) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + long i = offset; + long ea = (long)i + (long)M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer {ea}+{WidthTByteSize} out of bounds ({mem.Data.Length})."); + var bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + + return MemoryMarshal.Read(bs); + } + + public Func GetFunc => FetchFromMemory; + } + + public class InstI64Load32S : InstMemoryLoad, INodeComputer + { + public InstI64Load32S() : base(ValType.I64, BitWidth.S32, OpCode.I64Load16S) {} + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + long value = FetchFromMemory(context, offset); + context.OpStack.PushValue(value); + } + + //@Spec 4.4.7.1. t.load and t.loadN_sx + public long FetchFromMemory(ExecContext context, uint offset) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + long i = offset; + long ea = (long)i + (long)M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer {ea}+{WidthTByteSize} out of bounds ({mem.Data.Length})."); + var bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + + return MemoryMarshal.Read(bs); + } + + public Func GetFunc => FetchFromMemory; + } + + public class InstI64Load32U : InstMemoryLoad, INodeComputer + { + public InstI64Load32U() : base(ValType.I64, BitWidth.U32, OpCode.I64Load16U) {} + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + ulong value = FetchFromMemory(context, offset); + context.OpStack.PushValue(value); + } + + //@Spec 4.4.7.1. t.load and t.loadN_sx + public ulong FetchFromMemory(ExecContext context, uint offset) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + long i = offset; + long ea = (long)i + (long)M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer {ea}+{WidthTByteSize} out of bounds ({mem.Data.Length})."); + var bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + + return MemoryMarshal.Read(bs); + } + + public Func GetFunc => FetchFromMemory; + } +} \ No newline at end of file diff --git a/Wacs.Core/Instructions/Memory/InstI32Store.cs b/Wacs.Core/Instructions/Memory/InstI32Store.cs new file mode 100644 index 0000000..9923652 --- /dev/null +++ b/Wacs.Core/Instructions/Memory/InstI32Store.cs @@ -0,0 +1,156 @@ +// /* +// * Copyright 2024 Kelvin Nishikawa +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ + +using System; +using System.Runtime.InteropServices; +using Wacs.Core.Instructions.Transpiler; +using Wacs.Core.OpCodes; +using Wacs.Core.Runtime; +using Wacs.Core.Runtime.Types; +using Wacs.Core.Types; + +namespace Wacs.Core.Instructions.Memory +{ + public class InstI32Store : InstMemoryStore, INodeConsumer + { + public InstI32Store() : base(ValType.I32, BitWidth.U32, OpCode.I32Store) { } + + // @Spec 4.4.7.6. t.store + // @Spec 4.4.7.6. t.storeN + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().Type == Type, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint c = context.OpStack.PopU32(); + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + + SetMemoryValue(context, offset, c); + } + + public void SetMemoryValue(ExecContext context, uint offset, uint cU32) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + + long i = offset; + long ea = i + M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer out of bounds."); + //13,14,15 + Span bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + +#if NETSTANDARD2_1 + MemoryMarshal.Write(bs, ref cU32); +#else + MemoryMarshal.Write(bs, in cU32); +#endif + } + + public Action GetFunc => SetMemoryValue; + } + + public class InstI32Store8 : InstMemoryStore, INodeConsumer + { + public InstI32Store8() : base(ValType.I32, BitWidth.U8, OpCode.I32Store8) { } + + // @Spec 4.4.7.6. t.store + // @Spec 4.4.7.6. t.storeN + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().Type == Type, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint c = context.OpStack.PopU32(); + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + + SetMemoryValue(context, offset, c); + } + + public void SetMemoryValue(ExecContext context, uint offset, uint cU32) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + + long i = offset; + long ea = i + M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer out of bounds."); + //13,14,15 + Span bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + + byte cU8 = (byte)(0xFF & cU32); + bs[0] = cU8; + } + + public Action GetFunc => SetMemoryValue; + } + + public class InstI32Store16 : InstMemoryStore, INodeConsumer + { + public InstI32Store16() : base(ValType.I32, BitWidth.U16, OpCode.I32Store16) { } + + // @Spec 4.4.7.6. t.store + // @Spec 4.4.7.6. t.storeN + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().Type == Type, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint c = context.OpStack.PopU32(); + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + + SetMemoryValue(context, offset, c); + } + + public void SetMemoryValue(ExecContext context, uint offset, uint cU32) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + + long i = offset; + long ea = i + M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer out of bounds."); + //13,14,15 + Span bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + + ushort cI16 = (ushort)cU32; +#if NETSTANDARD2_1 + MemoryMarshal.Write(bs, ref cI16); +#else + MemoryMarshal.Write(bs, in cI16); // Assume you can change to 'in' +#endif + } + + public Action GetFunc => SetMemoryValue; + } +} \ No newline at end of file diff --git a/Wacs.Core/Instructions/Memory/InstI64Store.cs b/Wacs.Core/Instructions/Memory/InstI64Store.cs new file mode 100644 index 0000000..f19768e --- /dev/null +++ b/Wacs.Core/Instructions/Memory/InstI64Store.cs @@ -0,0 +1,201 @@ +// /* +// * Copyright 2024 Kelvin Nishikawa +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ + +using System; +using System.Runtime.InteropServices; +using Wacs.Core.Instructions.Transpiler; +using Wacs.Core.OpCodes; +using Wacs.Core.Runtime; +using Wacs.Core.Runtime.Types; +using Wacs.Core.Types; + +namespace Wacs.Core.Instructions.Memory +{ + public class InstI64Store : InstMemoryStore, INodeConsumer + { + public InstI64Store() : base(ValType.I64, BitWidth.U64, OpCode.I64Store) { } + + // @Spec 4.4.7.6. t.store + // @Spec 4.4.7.6. t.storeN + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().Type == Type, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + ulong c = context.OpStack.PopU64(); + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + + SetMemoryValue(context, offset, c); + } + + public void SetMemoryValue(ExecContext context, uint offset, ulong cU64) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + + long i = offset; + long ea = i + M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer out of bounds."); + //13,14,15 + Span bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + +#if NETSTANDARD2_1 + MemoryMarshal.Write(bs, ref cU64); +#else + MemoryMarshal.Write(bs, in cU64); +#endif + } + + public Action GetFunc => SetMemoryValue; + } + + public class InstI64Store8 : InstMemoryStore, INodeConsumer + { + public InstI64Store8() : base(ValType.I64, BitWidth.U8, OpCode.I64Store8) { } + + // @Spec 4.4.7.6. t.store + // @Spec 4.4.7.6. t.storeN + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().Type == Type, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + ulong c = context.OpStack.PopU64(); + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + + SetMemoryValue(context, offset, c); + } + + public void SetMemoryValue(ExecContext context, uint offset, ulong cU64) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + + long i = offset; + long ea = i + M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer out of bounds."); + //13,14,15 + Span bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + + byte cU8 = (byte)(0xFF & cU64); + bs[0] = cU8; + } + + public Action GetFunc => SetMemoryValue; + } + + public class InstI64Store16 : InstMemoryStore, INodeConsumer + { + public InstI64Store16() : base(ValType.I64, BitWidth.U16, OpCode.I64Store16) { } + + // @Spec 4.4.7.6. t.store + // @Spec 4.4.7.6. t.storeN + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().Type == Type, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + ulong c = context.OpStack.PopU64(); + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + + SetMemoryValue(context, offset, c); + } + + public void SetMemoryValue(ExecContext context, uint offset, ulong cU64) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + + long i = offset; + long ea = i + M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer out of bounds."); + //13,14,15 + Span bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + + ushort cI16 = (ushort)cU64; +#if NETSTANDARD2_1 + MemoryMarshal.Write(bs, ref cI16); +#else + MemoryMarshal.Write(bs, in cI16); // Assume you can change to 'in' +#endif + } + + public Action GetFunc => SetMemoryValue; + } + + public class InstI64Store32 : InstMemoryStore, INodeConsumer + { + public InstI64Store32() : base(ValType.I64, BitWidth.U32, OpCode.I64Store32) { } + + // @Spec 4.4.7.6. t.store + // @Spec 4.4.7.6. t.storeN + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().Type == Type, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + ulong c = context.OpStack.PopU64(); + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + + SetMemoryValue(context, offset, c); + } + + public void SetMemoryValue(ExecContext context, uint offset, ulong cU64) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + + long i = offset; + long ea = i + M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer out of bounds."); + //13,14,15 + Span bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + + uint cI32 = (uint)cU64; +#if NETSTANDARD2_1 + MemoryMarshal.Write(bs, ref cI32); +#else + MemoryMarshal.Write(bs, in cI32); // Assume you can change to 'in' +#endif + } + + public Action GetFunc => SetMemoryValue; + } +} \ No newline at end of file diff --git a/Wacs.Core/Instructions/SIMD/VMemory.cs b/Wacs.Core/Instructions/SIMD/VMemory.cs index bc7e92c..cdd5866 100644 --- a/Wacs.Core/Instructions/SIMD/VMemory.cs +++ b/Wacs.Core/Instructions/SIMD/VMemory.cs @@ -16,6 +16,8 @@ using System; using System.IO; +using System.Runtime.InteropServices; +using Wacs.Core.Instructions.Transpiler; using Wacs.Core.OpCodes; using Wacs.Core.Runtime; using Wacs.Core.Runtime.Types; @@ -25,6 +27,82 @@ namespace Wacs.Core.Instructions.SIMD { + public class InstV128Load : InstMemoryLoad, INodeComputer + { + public InstV128Load() : base(ValType.V128, BitWidth.V128, SimdCode.V128Load) {} + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + V128 value = FetchFromMemory(context, offset); + context.OpStack.PushValue(value); + } + + //@Spec 4.4.7.1. t.load and t.loadN_sx + public V128 FetchFromMemory(ExecContext context, uint offset) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + long i = offset; + long ea = (long)i + (long)M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer {ea}+{WidthTByteSize} out of bounds ({mem.Data.Length})."); + var bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + return new V128(bs); + } + + public Func GetFunc => FetchFromMemory; + } + + public class InstV128Store : InstMemoryStore, INodeConsumer + { + public InstV128Store() : base(ValType.V128, BitWidth.V128, SimdCode.V128Store) { } + + // @Spec 4.4.7.6. t.store + // @Spec 4.4.7.6. t.storeN + public override void Execute(ExecContext context) + { + context.Assert( context.OpStack.Peek().Type == Type, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + V128 c = context.OpStack.PopV128(); + context.Assert( context.OpStack.Peek().IsI32, + $"Instruction {Op.GetMnemonic()} failed. Wrong type on stack."); + uint offset = context.OpStack.PopU32(); + + SetMemoryValue(context, offset, c); + } + + public void SetMemoryValue(ExecContext context, uint offset, V128 cV128) + { + context.Assert( context.Frame.Module.MemAddrs.Contains(M.M), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 did not exist in the context."); + var a = context.Frame.Module.MemAddrs[M.M]; + context.Assert( context.Store.Contains(a), + $"Instruction {Op.GetMnemonic()} failed. Address for Memory 0 was not in the Store."); + var mem = context.Store[a]; + + long i = offset; + long ea = i + M.Offset; + if (ea + WidthTByteSize > mem.Data.Length) + throw new TrapException($"Instruction {Op.GetMnemonic()} failed. Memory pointer out of bounds."); + //13,14,15 + Span bs = mem.Data.AsSpan((int)ea, WidthTByteSize); + +#if NETSTANDARD2_1 + MemoryMarshal.Write(bs, ref cV128); +#else + MemoryMarshal.Write(bs, in cV128); +#endif + } + + public Action GetFunc => SetMemoryValue; + } + public class InstMemoryLoadMxN : InstructionBase { private readonly int CountN; diff --git a/Wacs.Core/Instructions/SpecFactory/SpecFactory.cs b/Wacs.Core/Instructions/SpecFactory/SpecFactory.cs index 9123b49..72d0fd4 100644 --- a/Wacs.Core/Instructions/SpecFactory/SpecFactory.cs +++ b/Wacs.Core/Instructions/SpecFactory/SpecFactory.cs @@ -15,6 +15,7 @@ // */ using System; +using Wacs.Core.Instructions.Memory; using Wacs.Core.Instructions.Numeric; using Wacs.Core.OpCodes; using Wacs.Core.Types; @@ -81,30 +82,30 @@ public T CreateInstruction(ByteCode code) OpCode.TableSet => new InstTableSet(), //Memory Instructions - OpCode.I32Load => new InstMemoryLoad(ValType.I32, BitWidth.U32), - OpCode.I64Load => new InstMemoryLoad(ValType.I64, BitWidth.U64), - OpCode.F32Load => new InstMemoryLoad(ValType.F32, BitWidth.U32), - OpCode.F64Load => new InstMemoryLoad(ValType.F64, BitWidth.U64), - OpCode.I32Load8S => new InstMemoryLoad(ValType.I32, BitWidth.S8), - OpCode.I32Load8U => new InstMemoryLoad(ValType.I32, BitWidth.U8), - OpCode.I32Load16S => new InstMemoryLoad(ValType.I32, BitWidth.S16), - OpCode.I32Load16U => new InstMemoryLoad(ValType.I32, BitWidth.U16), - OpCode.I64Load8S => new InstMemoryLoad(ValType.I64, BitWidth.S8), - OpCode.I64Load8U => new InstMemoryLoad(ValType.I64, BitWidth.U8), - OpCode.I64Load16S => new InstMemoryLoad(ValType.I64, BitWidth.S16), - OpCode.I64Load16U => new InstMemoryLoad(ValType.I64, BitWidth.U16), - OpCode.I64Load32S => new InstMemoryLoad(ValType.I64, BitWidth.S32), - OpCode.I64Load32U => new InstMemoryLoad(ValType.I64, BitWidth.U32), + OpCode.I32Load => new InstI32Load(), + OpCode.I64Load => new InstI64Load(), + OpCode.F32Load => new InstF32Load(), + OpCode.F64Load => new InstF64Load(), + OpCode.I32Load8S => new InstI32Load8S(), + OpCode.I32Load8U => new InstI32Load8U(), + OpCode.I32Load16S => new InstI32Load16S(), + OpCode.I32Load16U => new InstI32Load16U(), + OpCode.I64Load8S => new InstI64Load8S(), + OpCode.I64Load8U => new InstI64Load8U(), + OpCode.I64Load16S => new InstI64Load16S(), + OpCode.I64Load16U => new InstI64Load16U(), + OpCode.I64Load32S => new InstI64Load32S(), + OpCode.I64Load32U => new InstI64Load32U(), - OpCode.I32Store => new InstMemoryStore(ValType.I32, BitWidth.U32), - OpCode.I64Store => new InstMemoryStore(ValType.I64, BitWidth.U64), - OpCode.F32Store => new InstMemoryStore(ValType.F32, BitWidth.U32), - OpCode.F64Store => new InstMemoryStore(ValType.F64, BitWidth.U64), - OpCode.I32Store8 => new InstMemoryStore(ValType.I32, BitWidth.U8), - OpCode.I32Store16 => new InstMemoryStore(ValType.I32, BitWidth.U16), - OpCode.I64Store8 => new InstMemoryStore(ValType.I64, BitWidth.U8), - OpCode.I64Store16 => new InstMemoryStore(ValType.I64, BitWidth.U16), - OpCode.I64Store32 => new InstMemoryStore(ValType.I64, BitWidth.U32), + OpCode.I32Store => new InstI32Store(), + OpCode.I64Store => new InstI64Store(), + OpCode.F32Store => new InstF32Store(), + OpCode.F64Store => new InstF64Store(), + OpCode.I32Store8 => new InstI32Store8(), + OpCode.I32Store16 => new InstI32Store16(), + OpCode.I64Store8 => new InstI64Store8(), + OpCode.I64Store16 => new InstI64Store16(), + OpCode.I64Store32 => new InstI64Store32(), OpCode.MemorySize => new InstMemorySize(), OpCode.MemoryGrow => new InstMemoryGrow(), diff --git a/Wacs.Core/Instructions/SpecFactory/SpecFactoryFD.cs b/Wacs.Core/Instructions/SpecFactory/SpecFactoryFD.cs index 552ffec..57b7651 100644 --- a/Wacs.Core/Instructions/SpecFactory/SpecFactoryFD.cs +++ b/Wacs.Core/Instructions/SpecFactory/SpecFactoryFD.cs @@ -15,6 +15,7 @@ // */ using System.IO; +using Wacs.Core.Instructions.Memory; using Wacs.Core.Instructions.Numeric; using Wacs.Core.Instructions.Simd; using Wacs.Core.Instructions.SIMD; @@ -30,8 +31,8 @@ public partial class SpecFactory SimdCode.V128Const => new InstV128Const(), //Memory - SimdCode.V128Load => new InstMemoryLoad(ValType.V128, BitWidth.V128), - SimdCode.V128Store => new InstMemoryStore(ValType.V128, BitWidth.V128), + SimdCode.V128Load => new InstV128Load(), + SimdCode.V128Store => new InstV128Store(), SimdCode.V128Load8x8S => new InstMemoryLoadMxN(BitWidth.S8, 8), SimdCode.V128Load8x8U => new InstMemoryLoadMxN(BitWidth.U8, 8), SimdCode.V128Load16x4S => new InstMemoryLoadMxN(BitWidth.S16, 4), diff --git a/Wacs.Core/Instructions/Transpiler/AggregateTypeCast.cs b/Wacs.Core/Instructions/Transpiler/AggregateTypeCast.cs index 91cf599..f580575 100644 --- a/Wacs.Core/Instructions/Transpiler/AggregateTypeCast.cs +++ b/Wacs.Core/Instructions/Transpiler/AggregateTypeCast.cs @@ -209,6 +209,17 @@ public UnwrapValueF64(ITypedValueProducer inA) : base(inA) public override Func GetFunc { get; } } + public class UnwrapValueV128 : UnwrapValue + { + public UnwrapValueV128(ITypedValueProducer inA) : base(inA) + { + var func = InA.GetFunc; + GetFunc = context => func(context).V128; + } + + public override Func GetFunc { get; } + } + public class CastToI32 : ITypedValueProducer where T : struct { diff --git a/Wacs.Core/Runtime/Transpiler/FunctionTranspiler.cs b/Wacs.Core/Runtime/Transpiler/FunctionTranspiler.cs index 862203d..e23103b 100644 --- a/Wacs.Core/Runtime/Transpiler/FunctionTranspiler.cs +++ b/Wacs.Core/Runtime/Transpiler/FunctionTranspiler.cs @@ -146,7 +146,10 @@ private static IInstruction OptimizeInstruction(IOptimizationTarget inst, Stack< case IValueConsumer doubleConsumer: return BindF64F64(inst, stack, doubleConsumer); //Memory Store - case IValueConsumer valueConsumer: return BindU32Value(inst, stack, valueConsumer); + case IValueConsumer memConsumer: return BindU32U64(inst, stack, memConsumer); + case IValueConsumer memConsumer: return BindU32F32(inst, stack, memConsumer); + case IValueConsumer memConsumer: return BindU32F64(inst, stack, memConsumer); + case IValueConsumer memConsumer: return BindU32V128(inst, stack, memConsumer); //Select case IValueConsumer valueConsumer: return BindValueValueI32(inst, stack, valueConsumer); @@ -265,7 +268,7 @@ private static IInstruction BindI32I32(IInstruction inst, Stack st return inst; } - private static IInstruction BindU32U32(IInstruction inst, Stack stack, IValueConsumer intConsumer) + private static IInstruction BindU32U32(IInstruction inst, Stack stack, IValueConsumer uintConsumer) { if (stack.Count < 2) return inst; var i2 = stack.Pop(); @@ -287,7 +290,9 @@ private static IInstruction BindU32U32(IInstruction inst, Stack st if (i1Producer != null && i2Producer != null) { - switch (intConsumer) { + switch (uintConsumer) { + case INodeConsumer memConsumer: + return new InstAggregate2_0(i1Producer, i2Producer, memConsumer); case INodeComputer uintComputer: return new InstAggregate2_1(i1Producer, i2Producer, uintComputer); } @@ -298,6 +303,131 @@ private static IInstruction BindU32U32(IInstruction inst, Stack st return inst; } + private static IInstruction BindU32U64(IInstruction inst, Stack stack, IValueConsumer ulongConsumer) + { + if (stack.Count < 2) return inst; + var i2 = stack.Pop(); + var i1 = stack.Pop(); + + var i2Producer = i2 switch { + ITypedValueProducer p => new UnwrapValueU64(p), + ITypedValueProducer p => new CastToU64(p), + ITypedValueProducer p => p, + _ => null + }; + var i1Producer = i1 switch + { + ITypedValueProducer p => new UnwrapValueU32(p), + ITypedValueProducer p => new CastToU32(p), + ITypedValueProducer p => p, + _ => null + }; + + if (i1Producer != null && i2Producer != null) + { + switch (ulongConsumer) { + case INodeConsumer memConsumer: + return new InstAggregate2_0(i1Producer, i2Producer, memConsumer); + } + } + + stack.Push(i1); + stack.Push(i2); + return inst; + } + private static IInstruction BindU32F32(IInstruction inst, Stack stack, IValueConsumer floatConsumer) + { + if (stack.Count < 2) return inst; + var i2 = stack.Pop(); + var i1 = stack.Pop(); + + var i2Producer = i2 switch { + ITypedValueProducer p => new UnwrapValueF32(p), + ITypedValueProducer p => p, + _ => null + }; + var i1Producer = i1 switch + { + ITypedValueProducer p => new UnwrapValueU32(p), + ITypedValueProducer p => new CastToU32(p), + ITypedValueProducer p => p, + _ => null + }; + + if (i1Producer != null && i2Producer != null) + { + switch (floatConsumer) { + case INodeConsumer memConsumer: + return new InstAggregate2_0(i1Producer, i2Producer, memConsumer); + } + } + + stack.Push(i1); + stack.Push(i2); + return inst; + } + private static IInstruction BindU32F64(IInstruction inst, Stack stack, IValueConsumer doubleConsumer) + { + if (stack.Count < 2) return inst; + var i2 = stack.Pop(); + var i1 = stack.Pop(); + + var i2Producer = i2 switch { + ITypedValueProducer p => new UnwrapValueF64(p), + ITypedValueProducer p => p, + _ => null + }; + var i1Producer = i1 switch + { + ITypedValueProducer p => new UnwrapValueU32(p), + ITypedValueProducer p => new CastToU32(p), + ITypedValueProducer p => p, + _ => null + }; + + if (i1Producer != null && i2Producer != null) + { + switch (doubleConsumer) { + case INodeConsumer memConsumer: + return new InstAggregate2_0(i1Producer, i2Producer, memConsumer); + } + } + + stack.Push(i1); + stack.Push(i2); + return inst; + } + private static IInstruction BindU32V128(IInstruction inst, Stack stack, IValueConsumer vecConsumer) + { + if (stack.Count < 2) return inst; + var i2 = stack.Pop(); + var i1 = stack.Pop(); + + var i2Producer = i2 switch { + ITypedValueProducer p => new UnwrapValueV128(p), + ITypedValueProducer p => p, + _ => null + }; + var i1Producer = i1 switch + { + ITypedValueProducer p => new UnwrapValueU32(p), + ITypedValueProducer p => new CastToU32(p), + ITypedValueProducer p => p, + _ => null + }; + + if (i1Producer != null && i2Producer != null) + { + switch (vecConsumer) { + case INodeConsumer memConsumer: + return new InstAggregate2_0(i1Producer, i2Producer, memConsumer); + } + } + + stack.Push(i1); + stack.Push(i2); + return inst; + } private static IInstruction BindU32I32(IInstruction inst, Stack stack, IValueConsumer intConsumer) { if (stack.Count < 2) return inst; @@ -483,14 +613,13 @@ private static IInstruction BindU64I64(IInstruction inst, Stack st stack.Push(i2); return inst; } - - + private static IInstruction BindU32Value(IInstruction inst, Stack stack, IValueConsumer intConsumer) { if (stack.Count < 1) return inst; var i2 = stack.Pop(); var i1 = stack.Count > 0 ? stack.Pop() : null; - + var i2Producer = i2 switch { ITypedValueProducer p => p, ITypedValueProducer p => new WrapValueI32(p), @@ -523,7 +652,7 @@ private static IInstruction BindU32Value(IInstruction inst, Stack case INodeConsumer nodeConsumer: return new InstAggregate2_0(i1Producer, i2Producer, nodeConsumer); } - + } if (i1 != null) stack.Push(i1); @@ -608,9 +737,6 @@ private static IInstruction BindValueValueI32(IInstruction inst, Stack stack, IValueConsumer floatConsumer) { if (stack.Count < 1) return inst; From dee9a771c6c8056ead1764cd5ef1d2069cf9708c Mon Sep 17 00:00:00 2001 From: Kelvin Nishikawa Date: Fri, 29 Nov 2024 00:31:52 -0800 Subject: [PATCH 16/24] Manually inline Sequence within ExecContext --- Wacs.Core/Runtime/ExecContext.cs | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/Wacs.Core/Runtime/ExecContext.cs b/Wacs.Core/Runtime/ExecContext.cs index 179b689..d85a0e3 100644 --- a/Wacs.Core/Runtime/ExecContext.cs +++ b/Wacs.Core/Runtime/ExecContext.cs @@ -210,7 +210,13 @@ public void EnterBlock(BlockTarget target, Block block) Frame.PushLabel(target); //Sets the Pointer to the start of the block sequence - EnterSequence(block.Instructions); + // EnterSequence(block.Instructions); + + //Manually inline EnterSequence + _currentSequence = block.Instructions; + _sequenceCount = _currentSequence.Count; + _sequenceInstructions = _currentSequence._instructions; + _sequenceIndex = -1; } // @Spec 4.4.9.2. Exit Block @@ -219,7 +225,14 @@ public void ExitBlock() var addr = Frame.PopLabels(0); // We manage separate stacks, so we don't need to relocate the operands // var vals = OpStack.PopResults(label.Type); - ResumeSequence(addr); + + // ResumeSequence(addr); + + //Manually inline ResumeSequence + _currentSequence = addr.Sequence; + _sequenceCount = _currentSequence.Count; + _sequenceInstructions = _currentSequence._instructions; + _sequenceIndex = addr.Index; } // @Spec 4.4.10.1 Function Invocation @@ -305,7 +318,14 @@ public void FunctionReturn() var address = PopFrame(); //7. split stack, values left in place //8. - ResumeSequence(address); + + //ResumeSequence(address); + + //Manually inline ResumeSequence + _currentSequence = address.Sequence; + _sequenceCount = _currentSequence.Count; + _sequenceInstructions = _currentSequence._instructions; + _sequenceIndex = address.Index; } From 012619ccd7375b7c583241eba5a91b4b441114b4 Mon Sep 17 00:00:00 2001 From: Kelvin Nishikawa Date: Fri, 29 Nov 2024 00:53:08 -0800 Subject: [PATCH 17/24] Updating README.md a bit --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5ef635f..627c917 100644 --- a/README.md +++ b/README.md @@ -228,15 +228,21 @@ How does this differ from executing the wasm instructions linearly with the WACS - The CLR's implementation can use hardware more effectively (use registers instead of heap memory) - Avoids instruction fetching and dispatch -In my testing, this leads to roughly 60% higher instruction processing throughput (10Mips -> 16Mips). These gains are situational however. +In my testing, this leads to roughly 60% higher instruction processing throughput (128Mips -> 210Mips). These gains are situational however. Linking of the instructions into a tree cannot 100% be determined across block boundaries. So in these cases, the transpiler just passes the sequence through unaltered. So WASM code with lots of function calls or branches will see less benefit. -There's still some headroom for optimization. Optimization is an ongoing process and I have a few other strategies yet to implement. +### Prebaked Block Labels +The design of the WASM VM includes block labelling for branch instructions and a heterogeneous operand/control stack. +WACS uses a split stack that separates operands and control. This enables us to make some key optimizations: +- Non-flushing branch jumps. We can leave operands on the stack if intermediate states don't interfere. +- Precomputed block labels. We can ditch the control frame's label stack entirely! +- Modern C# ObjectPools and ArrayPools minimize unavoidable allocation + +Optimization is an ongoing process and I have a few other strategies yet to implement. My plan for 1.0 includes: - Prebaked super-instructions for memory operations -- Replace some object pools with pre-computed statics - Implement the above transpiling for SIMD instructions (currently only i32/i64/f32/f64 instructions are optimized) - Provide an API for 3rd party super-instruction optimization From 70f7773f2114b521a78066c824773d065ee63da3 Mon Sep 17 00:00:00 2001 From: Kelvin Nishikawa Date: Sat, 30 Nov 2024 15:31:19 -0800 Subject: [PATCH 18/24] Implementing async wasm invocation, (stack invoker only) --- Wacs.Core/Runtime/Delegates.cs | 15 +- Wacs.Core/Runtime/OpStack.cs | 8 + Wacs.Core/Runtime/Value.cs | 3 + Wacs.Core/Runtime/WasmRuntimeExecution.cs | 150 +++++++++++++++++- Wacs.Core/Runtime/WasmRuntimeInstantiation.cs | 1 + 5 files changed, 171 insertions(+), 6 deletions(-) diff --git a/Wacs.Core/Runtime/Delegates.cs b/Wacs.Core/Runtime/Delegates.cs index ca0d998..e583b8c 100644 --- a/Wacs.Core/Runtime/Delegates.cs +++ b/Wacs.Core/Runtime/Delegates.cs @@ -17,6 +17,7 @@ using System; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using Wacs.Core.Types; using Expression = System.Linq.Expressions.Expression; @@ -28,6 +29,8 @@ public static class Delegates public delegate Value[] GenericFuncs(params object[] args); + public delegate Task GenericFuncsAsync(params Value[] args); + public delegate Value[] StackFunc(Value[] parameters); public static void ValidateFunctionTypeCompatibility(FunctionType functionType, Type delegateType) @@ -45,6 +48,14 @@ public static void ValidateFunctionTypeCompatibility(FunctionType functionType, ParameterInfo[] parameters = invokeMethod.GetParameters(); Type returnType = invokeMethod.ReturnType; + if (returnType.BaseType == typeof(Task)) + { + if (returnType.IsGenericType) + { + returnType = returnType.GenericTypeArguments[0]; + } + } + // Check if the number of parameters matches if (parameters.Length != functionType.ParameterTypes.Types.Length) @@ -87,7 +98,7 @@ public static void ValidateFunctionTypeCompatibility(FunctionType functionType, .GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy) .FirstOrDefault(m => m.Name == "op_Implicit" && m.ReturnType == expectedReturnType); // Check if returnType has an implicit conversion operator to expectedReturnType - if (implicitOp is null) + if (implicitOp is null && returnType != typeof(object)) { throw new ArgumentException( $"Return type mismatch. Expected return type is {expectedReturnType.Name}, but delegate returns {returnType.Name}."); @@ -137,7 +148,7 @@ public static Delegate AnonymousFunctionFromType(FunctionType functionType, Gene _ => throw new NotSupportedException($"Cannot auto-bind function signature: ({string.Join(", ", paramTypes)}) -> ({string.Join(", ", resultTypes)})") }; } - + public static Delegate CreateTypedDelegate(Delegate genericDelegate, Type desiredDelegateType) { var genericMethod = typeof(Delegates).GetMethod(nameof(CreateTypedDelegateInternal), BindingFlags.NonPublic | BindingFlags.Static); diff --git a/Wacs.Core/Runtime/OpStack.cs b/Wacs.Core/Runtime/OpStack.cs index ebec0cd..0ae4785 100644 --- a/Wacs.Core/Runtime/OpStack.cs +++ b/Wacs.Core/Runtime/OpStack.cs @@ -227,6 +227,14 @@ public void PopScalars(ResultType type, Span targetBuf) targetBuf[i] = PopAny(); } } + + public void PushValues(Value[] scalars) + { + for (int i = 0, l = scalars.Length; i < l; ++i) + { + PushValue(scalars[i]); + } + } public void PushScalars(ResultType type, object[] scalars) { diff --git a/Wacs.Core/Runtime/Value.cs b/Wacs.Core/Runtime/Value.cs index 02c6eef..043dc42 100644 --- a/Wacs.Core/Runtime/Value.cs +++ b/Wacs.Core/Runtime/Value.cs @@ -453,6 +453,9 @@ public object CastScalar() public bool IsV128 => Type == ValType.V128; public bool IsRef => Type == ValType.Funcref || Type == ValType.Externref; public bool IsNullRef => IsRef && Ptr == -1; + + public static object ToObject(Value value) => value.Scalar; + public static implicit operator Value(int value) => new(value); public static implicit operator Value(uint value) => new(value); diff --git a/Wacs.Core/Runtime/WasmRuntimeExecution.cs b/Wacs.Core/Runtime/WasmRuntimeExecution.cs index b2849ae..d216072 100644 --- a/Wacs.Core/Runtime/WasmRuntimeExecution.cs +++ b/Wacs.Core/Runtime/WasmRuntimeExecution.cs @@ -84,7 +84,143 @@ public Delegates.StackFunc CreateStackInvoker(FuncAddr funcAddr, InvokerOptions? return invoker(p); }; } + + public Delegates.GenericFuncsAsync CreateStackInvokerAsync(FuncAddr funcAddr, InvokerOptions? options = default) + { + options ??= new InvokerOptions(); + return CreateInvokerAsync(funcAddr, options); + } + + private Delegates.GenericFuncsAsync CreateInvokerAsync(FuncAddr funcAddr, InvokerOptions options) + { + return GenericDelegateAsync; + async Task GenericDelegateAsync(params Value[] args) + { + var funcInst = Context.Store[funcAddr]; + var funcType = funcInst.Type; + + Context.OpStack.PushValues(args); + + if (options.CollectStats != StatsDetail.None) + { + Context.ResetStats(); + Context.InstructionTimer.Reset(); + } + + Context.ProcessTimer.Restart(); + Context.InstructionTimer.Restart(); + + await Context.InvokeAsync(funcAddr); + + Context.steps = 0; + bool fastPath = options.UseFastPath(); + try + { + if (fastPath) + { + await ProcessThreadAsync(options.GasLimit); + } + else + { + await ProcessThreadWithOptions(options); + } + } + catch (AggregateException agg) + { + var exc = agg.InnerException; + Context.ProcessTimer.Stop(); + Context.InstructionTimer.Stop(); + if (options.LogProgressEvery > 0) + Console.Error.WriteLine(); + if (options.CollectStats != StatsDetail.None) + PrintStats(options); + if (options.LogGas) + Console.Error.WriteLine($"Process used {Context.steps} gas. {Context.ProcessTimer.Elapsed}"); + + if (options.CalculateLineNumbers) + { + var ptr = Context.ComputePointerPath(); + var path = string.Join(".", ptr.Select(t => $"{t.Item1.Capitalize()}[{t.Item2}]")); + (int line, string instruction) = Context.Frame.Module.Repr.CalculateLine(path); + + ExceptionDispatchInfo.Throw(new TrapException(exc.Message + $":line {line} instruction #{Context.steps}\n{path}")); + } + + //Flush the stack before throwing... + Context.FlushCallStack(); + ExceptionDispatchInfo.Throw(exc); + } + catch (TrapException exc) + { + Context.ProcessTimer.Stop(); + Context.InstructionTimer.Stop(); + if (options.LogProgressEvery > 0) + Console.Error.WriteLine(); + if (options.CollectStats != StatsDetail.None) + PrintStats(options); + if (options.LogGas) + Console.Error.WriteLine($"Process used {Context.steps} gas. {Context.ProcessTimer.Elapsed}"); + if (options.CalculateLineNumbers) + { + var ptr = Context.ComputePointerPath(); + var path = string.Join(".", ptr.Select(t => $"{t.Item1.Capitalize()}[{t.Item2}]")); + (int line, string instruction) = Context.Frame.Module.Repr.CalculateLine(path); + + ExceptionDispatchInfo.Throw(new TrapException(exc.Message + $":line {line} instruction #{Context.steps}\n{path}")); + } + + //Flush the stack before throwing... + Context.FlushCallStack(); + ExceptionDispatchInfo.Throw(exc); + } + catch (SignalException exc) + { + Context.ProcessTimer.Stop(); + Context.InstructionTimer.Stop(); + if (options.LogProgressEvery > 0) + Console.Error.WriteLine(); + if (options.CollectStats != StatsDetail.None) + PrintStats(options); + if (options.LogGas) + Console.Error.WriteLine($"Process used {Context.steps} gas. {Context.ProcessTimer.Elapsed}"); + + string message = exc.Message; + if (options.CalculateLineNumbers) + { + var ptr = Context.ComputePointerPath(); + var path = string.Join(".", ptr.Select(t => $"{t.Item1.Capitalize()}[{t.Item2}]")); + (int line, string instruction) = Context.Frame.Module.Repr.CalculateLine(path); + message = exc.Message + $":line {line} instruction #{Context.steps}\n{path}"; + } + + //Flush the stack before throwing... + Context.FlushCallStack(); + + var exType = exc.GetType(); + var ctr = exType.GetConstructor(new Type[] { typeof(int), typeof(string) }); + ExceptionDispatchInfo.Throw(ctr?.Invoke(new object[] { exc.Signal, message }) as Exception ?? exc); + } + catch (WasmRuntimeException) + { + //Maybe Log? + Context.FlushCallStack(); + throw; + } + + Context.ProcessTimer.Stop(); + Context.InstructionTimer.Stop(); + if (options.LogProgressEvery > 0) Console.Error.WriteLine("done."); + if (options.CollectStats != StatsDetail.None) PrintStats(options); + if (options.LogGas) Console.Error.WriteLine($"Process used {Context.steps} gas. {Context.ProcessTimer.Elapsed}"); + + Value[] results = new Value[funcType.ResultType.Arity]; + Context.OpStack.PopScalars(funcType.ResultType, results); + + return results; + } + } + private Delegates.GenericFuncs CreateInvoker(FuncAddr funcAddr, InvokerOptions options) { return GenericDelegate; @@ -225,13 +361,19 @@ public async Task ProcessThreadAsync(long gasLimit) while (Context.Next() is { } inst) { if (inst.IsAsync) + { await inst.ExecuteAsync(Context); + Context.steps += inst.Size; + if (Context.steps >= gasLimit) + throw new InsufficientGasException($"Invocation ran out of gas (limit:{gasLimit})."); + } else + { inst.Execute(Context); - - Context.steps += inst.Size; - if (Context.steps >= gasLimit) - throw new InsufficientGasException($"Invocation ran out of gas (limit:{gasLimit})."); + Context.steps += inst.Size; + if (Context.steps >= gasLimit) + throw new InsufficientGasException($"Invocation ran out of gas (limit:{gasLimit})."); + } } } diff --git a/Wacs.Core/Runtime/WasmRuntimeInstantiation.cs b/Wacs.Core/Runtime/WasmRuntimeInstantiation.cs index 3b0c1a4..7f5ea65 100644 --- a/Wacs.Core/Runtime/WasmRuntimeInstantiation.cs +++ b/Wacs.Core/Runtime/WasmRuntimeInstantiation.cs @@ -35,6 +35,7 @@ namespace Wacs.Core.Runtime public partial class WasmRuntime { private static readonly MethodInfo GenericFuncsInvoke = typeof(Delegates.GenericFuncs).GetMethod("Invoke")!; + private static readonly MethodInfo GenericFuncsAsyncInvoke = typeof(Delegates.GenericFuncsAsync).GetMethod("Invoke")!; private readonly Dictionary<(string module, string entity), IAddress?> _entityBindings = new(); private readonly List _moduleInstances = new(); From d141d1aae1aaf8513c10291a5f38a252111257e8 Mon Sep 17 00:00:00 2001 From: Kelvin Nishikawa Date: Sat, 30 Nov 2024 15:31:30 -0800 Subject: [PATCH 19/24] Adding async wasm test --- Spec.Test/BindingTests.cs | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/Spec.Test/BindingTests.cs b/Spec.Test/BindingTests.cs index 0274144..792ba42 100644 --- a/Spec.Test/BindingTests.cs +++ b/Spec.Test/BindingTests.cs @@ -15,6 +15,7 @@ // */ using System; +using System.Diagnostics; using System.IO; using System.Threading.Tasks; using Wacs.Core; @@ -119,7 +120,7 @@ public void BindHostFunction() [Fact] - public void BindHostAsyncFunction() + public void BindAsyncHostFunction() { var runtime = new WasmRuntime(); runtime.BindHostFunction(("env","bound_host"), BoundHost); @@ -131,10 +132,44 @@ public void BindHostAsyncFunction() runtime.RegisterModule("binding", moduleInst); var fa = runtime.GetExportedFunction(("binding", "call_async_host")); + + //Host function is async, Wasm function is called synchronously var invoker = runtime.CreateInvoker>(fa); Assert.Equal(10*2, invoker(10)); } + + [Fact] + public async Task BindAsyncWasmFunction() + { + var runtime = new WasmRuntime(); + runtime.BindHostFunction(("env","bound_host"), BoundHost); + + runtime.BindHostFunction(("env","bound_async_host"), static async a => + { + await Task.Delay(3_000); // Simulate work + return a*2; + }); + + using var fileStream = new FileStream("../../../engine/binding.wasm", FileMode.Open); + var module = BinaryModuleParser.ParseWasm(fileStream); + var moduleInst = runtime.InstantiateModule(module); + runtime.RegisterModule("binding", moduleInst); + + var fa = runtime.GetExportedFunction(("binding", "call_async_host")); + + //Host function is async, Wasm function is called asynchronously + var invoker = runtime.CreateStackInvokerAsync(fa); + + var stopwatch = new Stopwatch(); + + stopwatch.Start(); + var results = await invoker(10); + stopwatch.Stop(); + + Assert.InRange(stopwatch.ElapsedMilliseconds, 3000, 3500); + Assert.Equal(10*2, results[0].Int32); + } delegate int HostInOut(int a, out int b); From 9301651ef29c4839d704c4b28e44342e31753af8 Mon Sep 17 00:00:00 2001 From: Kelvin Nishikawa Date: Sat, 30 Nov 2024 15:47:15 -0800 Subject: [PATCH 20/24] WACS-v0.3.0 --- .run/Wacs.Core Publish Wacs.Core.dll.run.xml | 2 +- .run/Wacs.Core Publish Wacs.WASIp1.dll.run.xml | 2 +- CHANGELOG.md | 4 ++++ Wacs.Core/Wacs.Core.csproj | 5 ++--- unity | 2 +- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.run/Wacs.Core Publish Wacs.Core.dll.run.xml b/.run/Wacs.Core Publish Wacs.Core.dll.run.xml index 9ab2962..391cba8 100644 --- a/.run/Wacs.Core Publish Wacs.Core.dll.run.xml +++ b/.run/Wacs.Core Publish Wacs.Core.dll.run.xml @@ -1,6 +1,6 @@ -