├── .gitignore ├── WasmLib ├── Decompilation │ ├── SourceCode │ │ ├── IExpression.cs │ │ ├── IHasBlocks.cs │ │ ├── VariableReferenceExpression.cs │ │ ├── GenericExpression.cs │ │ └── AssignmentExpression.cs │ ├── IDecompiler.cs │ ├── Intermediate │ │ ├── StateKind.cs │ │ ├── Graph │ │ │ ├── InstructionEdge.cs │ │ │ ├── ImpurityDependencyEdge.cs │ │ │ ├── StackVariableEdge.cs │ │ │ └── InstructionNode.cs │ │ ├── Instructions │ │ │ ├── SelectInstruction.cs │ │ │ ├── ImplicitReturnInstruction.cs │ │ │ ├── DropInstruction.cs │ │ │ ├── UnreachableInstruction.cs │ │ │ ├── BlockReturnInstruction.cs │ │ │ ├── ReturnInstruction.cs │ │ │ ├── TestOperationInstruction.cs │ │ │ ├── MemorySizeInstruction.cs │ │ │ ├── ConstInstruction.cs │ │ │ ├── BranchInstruction.cs │ │ │ ├── ControlBlockInstruction.cs │ │ │ ├── CallInstruction.cs │ │ │ ├── UnaryOperationInstruction.cs │ │ │ ├── VariableInstruction.cs │ │ │ ├── ConversionOperatorInstruction.cs │ │ │ ├── ComparisonOperationInstruction.cs │ │ │ ├── MemoryInstruction.cs │ │ │ └── BinaryOperationInstruction.cs │ │ ├── ControlBlock.cs │ │ ├── Variable.cs │ │ ├── IntermediateInstruction.cs │ │ ├── IntermediateContext.cs │ │ └── IntermediateConverter.cs │ ├── DisassemblingDecompiler.cs │ ├── IntermediateRepresentationDecompiler.cs │ └── GenericDecompiler.cs ├── Utils │ ├── IDeserializable.cs │ ├── UninitializedFieldException.cs │ ├── WrongInstructionPassedException.cs │ ├── StringHelper.cs │ ├── EnumUtils.cs │ ├── BinaryReaderExtensions.cs │ └── Disassembler.cs ├── FileFormat │ ├── ImportKind.cs │ ├── ExportKind.cs │ ├── SectionType.cs │ ├── ValueKind.cs │ ├── GlobalType.cs │ ├── Global.cs │ ├── DataSegment.cs │ ├── Limits.cs │ ├── Instructions │ │ ├── OperandKind.cs │ │ ├── Instruction.cs │ │ ├── Instruction_Utils.cs │ │ └── OpCode.cs │ ├── Element.cs │ ├── Export.cs │ ├── FunctionBody.cs │ ├── FunctionSignature.cs │ └── Import.cs ├── WasmLib.csproj └── WasmModule.cs ├── WasmTool ├── CommandLine │ ├── DecompilerKind.cs │ └── CliArguments.cs ├── WasmTool.csproj └── Program.cs ├── README.md ├── WasmLib.sln.DotSettings ├── .github └── FUNDING.yml ├── LICENSE └── WasmLib.sln /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | /packages/ 4 | .idea 5 | WasmLib.sln.DotSettings.user 6 | -------------------------------------------------------------------------------- /WasmLib/Decompilation/SourceCode/IExpression.cs: -------------------------------------------------------------------------------- 1 | namespace WasmLib.Decompilation.SourceCode 2 | { 3 | public interface IExpression 4 | { 5 | string GetStringRepresentation(); 6 | } 7 | } -------------------------------------------------------------------------------- /WasmLib/Utils/IDeserializable.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace WasmLib.Utils 4 | { 5 | internal interface IDeserializable 6 | { 7 | void Read(BinaryReader br); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /WasmTool/CommandLine/DecompilerKind.cs: -------------------------------------------------------------------------------- 1 | namespace WasmTool.CommandLine 2 | { 3 | public enum DecompilerKind 4 | { 5 | Disassembler, 6 | IntermediateRepresentation, 7 | Generic, 8 | } 9 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/IDecompiler.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace WasmLib.Decompilation 4 | { 5 | public interface IDecompiler 6 | { 7 | void DecompileFunction(StreamWriter output, int functionIndex); 8 | } 9 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/SourceCode/IHasBlocks.cs: -------------------------------------------------------------------------------- 1 | using Rivers; 2 | 3 | namespace WasmLib.Decompilation.SourceCode 4 | { 5 | public interface IHasBlocks 6 | { 7 | Graph? Block1 { get; } 8 | Graph? Block2 { get; } 9 | } 10 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WasmLib 2 | 3 | Wasm library, targeted at analyzing Unity il2cpp wasm binaries. Some functionality will be missing if I don't need it. 4 | 5 | This probably isn't of any use to you. If you want to decompile wasm files, see the official [wasm-decompile](https://v8.dev/blog/wasm-decompile). 6 | -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/StateKind.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WasmLib.Decompilation.Intermediate 4 | { 5 | [Flags] 6 | public enum StateKind : byte 7 | { 8 | None = 0, 9 | Locals = 1, 10 | Globals = 2, 11 | Memory = 4, 12 | All = 0xFF, 13 | } 14 | } -------------------------------------------------------------------------------- /WasmLib/FileFormat/ImportKind.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace WasmLib.FileFormat 4 | { 5 | public enum ImportKind : byte 6 | { 7 | [Description("func")] TypeIndex, 8 | [Description("table")] TableType, 9 | [Description("memory")] MemoryType, 10 | [Description("global")] GlobalType, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /WasmLib/FileFormat/ExportKind.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace WasmLib.FileFormat 4 | { 5 | public enum ExportKind : byte 6 | { 7 | [Description("func")] FuncIndex, 8 | [Description("table")] TableIndex, 9 | [Description("memory")] MemoryIndex, 10 | [Description("global")] GlobalIndex, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /WasmLib/FileFormat/SectionType.cs: -------------------------------------------------------------------------------- 1 | namespace WasmLib.FileFormat 2 | { 3 | public enum SectionType 4 | { 5 | Custom, 6 | Type, 7 | Import, 8 | Function, 9 | Table, 10 | LinearMemory, 11 | Global, 12 | Export, 13 | Start, 14 | Element, 15 | Code, 16 | Data, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /WasmLib/WasmLib.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.0 5 | 8 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /WasmLib/Utils/UninitializedFieldException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace WasmLib.Utils 5 | { 6 | internal class UninitializedFieldException : Exception 7 | { 8 | public UninitializedFieldException([CallerMemberName] string? fieldName = null) : base($"Tried to read {fieldName ?? "a field"} before it was assigned") { } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /WasmLib.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True -------------------------------------------------------------------------------- /WasmLib/Utils/WrongInstructionPassedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using WasmLib.FileFormat.Instructions; 3 | 4 | namespace WasmLib.Utils 5 | { 6 | internal class WrongInstructionPassedException : Exception 7 | { 8 | public WrongInstructionPassedException(Instruction instruction, string typeName) : base($"Passed incorrect instruction '{instruction}' to IR {typeName} constructor") { } 9 | } 10 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/SourceCode/VariableReferenceExpression.cs: -------------------------------------------------------------------------------- 1 | namespace WasmLib.Decompilation.SourceCode 2 | { 3 | public class VariableReferenceExpression : IExpression 4 | { 5 | public string Name { get; } 6 | 7 | public VariableReferenceExpression(string name) 8 | { 9 | Name = name; 10 | } 11 | 12 | public string GetStringRepresentation() => Name; 13 | } 14 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Graph/InstructionEdge.cs: -------------------------------------------------------------------------------- 1 | using Rivers; 2 | 3 | namespace WasmLib.Decompilation.Intermediate.Graph 4 | { 5 | public abstract class InstructionEdge : Edge 6 | { 7 | public new InstructionNode Source => (InstructionNode)base.Source; 8 | public new InstructionNode Target => (InstructionNode)base.Target; 9 | 10 | protected InstructionEdge(InstructionNode source, InstructionNode target) : base(source, target) { } 11 | } 12 | } -------------------------------------------------------------------------------- /WasmLib/FileFormat/ValueKind.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace WasmLib.FileFormat 4 | { 5 | public enum ValueKind : byte 6 | { 7 | [Description("i32")] I32 = 0x7f, 8 | [Description("i64")] I64 = 0x7e, 9 | [Description("f32")] F32 = 0x7d, 10 | [Description("f64")] F64 = 0x7c, 11 | 12 | /// Only used as block type 13 | Empty = 0x40, 14 | 15 | /// Only used internally 16 | Any = 0xFF, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Instructions/SelectInstruction.cs: -------------------------------------------------------------------------------- 1 | using WasmLib.FileFormat; 2 | 3 | namespace WasmLib.Decompilation.Intermediate.Instructions 4 | { 5 | public class SelectInstruction : IntermediateInstruction 6 | { 7 | public override ValueKind[] PopTypes => new[] {ValueKind.I32, ValueKind.Any, ValueKind.Any}; // TODO: shouldn't be any 8 | public override ValueKind[] PushTypes => new[] {ValueKind.Any}; 9 | 10 | public override string OperationStringFormat => "{0} ? {2} : {1}"; 11 | } 12 | } -------------------------------------------------------------------------------- /WasmTool/WasmTool.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.0 6 | 8 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Instructions/ImplicitReturnInstruction.cs: -------------------------------------------------------------------------------- 1 | using WasmLib.FileFormat; 2 | 3 | namespace WasmLib.Decompilation.Intermediate.Instructions 4 | { 5 | /// 6 | /// This is a pseudo-instruction to handle implicit returns at the end of a function 7 | /// 8 | public class ImplicitReturnInstruction : ReturnInstruction 9 | { 10 | public override bool IsImplicit => true; 11 | 12 | public ImplicitReturnInstruction(FunctionSignature signature) : base(signature) { } 13 | } 14 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Graph/ImpurityDependencyEdge.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace WasmLib.Decompilation.Intermediate.Graph 4 | { 5 | public class ImpurityDependencyEdge : InstructionEdge 6 | { 7 | public ImpurityDependencyEdge(InstructionNode source, InstructionNode target) : base(source, target) 8 | { 9 | AddUserData(); 10 | } 11 | 12 | [Conditional("DEBUG")] 13 | private void AddUserData() 14 | { 15 | UserData["style"] = "dotted"; 16 | UserData["color"] = "blue"; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Instructions/DropInstruction.cs: -------------------------------------------------------------------------------- 1 | using WasmLib.FileFormat; 2 | 3 | namespace WasmLib.Decompilation.Intermediate.Instructions 4 | { 5 | public class DropInstruction : IntermediateInstruction 6 | { 7 | public override ValueKind[] PopTypes => new[] {ValueKind.Any}; 8 | public override ValueKind[] PushTypes => new ValueKind[0]; 9 | 10 | public override bool CanInline => false; // not supposed to output anything 11 | 12 | public override string OperationStringFormat => string.Empty; 13 | public override string Comment => "drop {0}"; 14 | } 15 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/ControlBlock.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using WasmLib.FileFormat; 3 | 4 | namespace WasmLib.Decompilation.Intermediate 5 | { 6 | public class ControlBlock 7 | { 8 | public IReadOnlyList Instructions { get; } 9 | public ValueKind ValueKind { get; } 10 | 11 | public bool HasReturn => ValueKind != ValueKind.Empty; 12 | 13 | public ControlBlock(IReadOnlyList instructions, ValueKind valueKind) 14 | { 15 | Instructions = instructions; 16 | ValueKind = valueKind; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Variable.cs: -------------------------------------------------------------------------------- 1 | using WasmLib.FileFormat; 2 | using WasmLib.Utils; 3 | 4 | namespace WasmLib.Decompilation.Intermediate 5 | { 6 | public readonly struct Variable 7 | { 8 | public ValueKind Type { get; } 9 | public readonly uint Index; 10 | 11 | private Variable(ValueKind type, uint index) 12 | { 13 | Type = type; 14 | Index = index; 15 | } 16 | 17 | public static Variable Stack(ValueKind type, uint index) => new Variable(type, index); 18 | 19 | public override string ToString() => $"var{Index}_{EnumUtils.GetDescription(Type)}"; 20 | } 21 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Instructions/UnreachableInstruction.cs: -------------------------------------------------------------------------------- 1 | using WasmLib.FileFormat; 2 | 3 | namespace WasmLib.Decompilation.Intermediate.Instructions 4 | { 5 | public class UnreachableInstruction : IntermediateInstruction 6 | { 7 | public override ValueKind[] PopTypes => new ValueKind[0]; 8 | public override ValueKind[] PushTypes => new ValueKind[0]; 9 | 10 | public override bool RestOfBlockUnreachable => true; 11 | public override bool ModifiesControlFlow => true; 12 | public override bool CanBeInlined => false; 13 | 14 | public override string OperationStringFormat => "// UNREACHABLE"; 15 | } 16 | } -------------------------------------------------------------------------------- /WasmLib/FileFormat/GlobalType.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using WasmLib.Utils; 3 | 4 | namespace WasmLib.FileFormat 5 | { 6 | public readonly struct GlobalType 7 | { 8 | public bool Mutable { get; } 9 | public ValueKind ValueKind { get; } 10 | 11 | public GlobalType(ValueKind valueKind, bool mutable) 12 | { 13 | ValueKind = valueKind; 14 | Mutable = mutable; 15 | } 16 | 17 | public static GlobalType Read(BinaryReader br) => new GlobalType((ValueKind)br.ReadVarUint7(), br.ReadBoolean()); 18 | 19 | public override string ToString() => Mutable ? $"mut {ValueKind}" : $"{ValueKind}"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /WasmLib/FileFormat/Global.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using WasmLib.FileFormat.Instructions; 3 | using WasmLib.Utils; 4 | 5 | namespace WasmLib.FileFormat 6 | { 7 | public class Global : IDeserializable 8 | { 9 | public GlobalType GlobalType => globalType ?? throw new UninitializedFieldException(); 10 | public Instruction[] Expression => expression ?? throw new UninitializedFieldException(); 11 | 12 | private GlobalType? globalType; 13 | private Instruction[]? expression; 14 | 15 | public void Read(BinaryReader br) 16 | { 17 | globalType = GlobalType.Read(br); 18 | expression = br.ReadExpression(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Graph/StackVariableEdge.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using WasmLib.FileFormat; 3 | 4 | namespace WasmLib.Decompilation.Intermediate.Graph 5 | { 6 | public class StackVariableEdge : InstructionEdge 7 | { 8 | public ValueKind Type { get; } 9 | 10 | public StackVariableEdge(InstructionNode source, InstructionNode target, ValueKind type) : base(source, target) 11 | { 12 | Type = type; 13 | AddUserData(); 14 | } 15 | 16 | [Conditional("DEBUG")] 17 | private void AddUserData() 18 | { 19 | UserData["color"] = "red"; 20 | UserData["label"] = Type; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [holly-hacker] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | ko_fi: holly_hacker # Replace with a single Ko-fi username 5 | 6 | patreon: # Replace with a single Patreon username 7 | open_collective: # Replace with a single Open Collective username 8 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 9 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 10 | liberapay: # Replace with a single Liberapay username 11 | issuehunt: # Replace with a single IssueHunt username 12 | otechie: # Replace with a single Otechie username 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Instructions/BlockReturnInstruction.cs: -------------------------------------------------------------------------------- 1 | using WasmLib.FileFormat; 2 | 3 | namespace WasmLib.Decompilation.Intermediate.Instructions 4 | { 5 | public class BlockReturnInstruction : IntermediateInstruction 6 | { 7 | private readonly ValueKind type; 8 | 9 | public override ValueKind[] PopTypes => new[] {type}; 10 | public override ValueKind[] PushTypes => new ValueKind[0]; 11 | 12 | public override bool RestOfBlockUnreachable => true; 13 | public override bool ModifiesControlFlow => true; 14 | public override bool IsImplicit => true; 15 | 16 | public BlockReturnInstruction(ValueKind type) 17 | { 18 | this.type = type; 19 | } 20 | 21 | public override string OperationStringFormat => "block_return {0}"; 22 | } 23 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Instructions/ReturnInstruction.cs: -------------------------------------------------------------------------------- 1 | using WasmLib.FileFormat; 2 | 3 | namespace WasmLib.Decompilation.Intermediate.Instructions 4 | { 5 | public class ReturnInstruction : IntermediateInstruction 6 | { 7 | private readonly FunctionSignature signature; 8 | 9 | public override ValueKind[] PopTypes => signature.ReturnParameter; 10 | public override ValueKind[] PushTypes => new ValueKind[0]; 11 | 12 | public override bool RestOfBlockUnreachable => true; 13 | public override bool ModifiesControlFlow => true; 14 | 15 | public ReturnInstruction(FunctionSignature signature) 16 | { 17 | this.signature = signature; 18 | } 19 | 20 | public override string OperationStringFormat => signature.ReturnParameter.Length == 0 ? "return" : "return {0}"; 21 | } 22 | } -------------------------------------------------------------------------------- /WasmLib/FileFormat/DataSegment.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using WasmLib.FileFormat.Instructions; 3 | using WasmLib.Utils; 4 | 5 | namespace WasmLib.FileFormat 6 | { 7 | public class DataSegment : IDeserializable 8 | { 9 | public uint MemoryIndex => memoryIndex ?? throw new UninitializedFieldException(); 10 | public Instruction[] Expression => expression ?? throw new UninitializedFieldException(); 11 | public byte[] Data => data ?? throw new UninitializedFieldException(); 12 | 13 | private uint? memoryIndex; 14 | private Instruction[]? expression; 15 | private byte[]? data; 16 | 17 | public void Read(BinaryReader br) 18 | { 19 | memoryIndex = br.ReadVarUint32(); 20 | expression = br.ReadExpression(); 21 | data = br.ReadWasmByteArray(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /WasmLib/FileFormat/Limits.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using WasmLib.Utils; 3 | 4 | namespace WasmLib.FileFormat 5 | { 6 | public readonly struct Limits 7 | { 8 | public uint Min { get; } 9 | public uint? Max { get; } 10 | 11 | public Limits(uint min) 12 | { 13 | Min = min; 14 | Max = null; 15 | } 16 | 17 | public Limits(uint min, uint max) 18 | { 19 | Min = min; 20 | Max = max; 21 | } 22 | 23 | public static Limits Read(BinaryReader br) 24 | { 25 | return br.ReadBoolean() 26 | ? new Limits(br.ReadVarUint32(), br.ReadVarUint32()) 27 | : new Limits(br.ReadVarUint32()); 28 | } 29 | 30 | public override string ToString() => Max.HasValue ? $"[{Min}, {Max}]" : $"[{Min}, ϵ]"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /WasmLib/Utils/StringHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WasmLib.Utils 4 | { 5 | internal static class StringHelper 6 | { 7 | public static string SafeFormat(this string format, T[]? parameters) 8 | { 9 | if (parameters is null || parameters.Length == 0) { 10 | return format; 11 | } 12 | 13 | string s = format; 14 | 15 | for (int j = 0; j < parameters.Length; j++) { 16 | T param = parameters[j]; 17 | 18 | if (param is null) { 19 | throw new NullReferenceException($"Passed a null parameter to {nameof(SafeFormat)}"); 20 | } 21 | 22 | s = s.Replace($"{{{j}}}", param.ToString()); 23 | } 24 | 25 | return s; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/SourceCode/GenericExpression.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Rivers; 3 | using WasmLib.Decompilation.Intermediate; 4 | using WasmLib.Utils; 5 | 6 | namespace WasmLib.Decompilation.SourceCode 7 | { 8 | public class GenericExpression : IExpression, IHasBlocks 9 | { 10 | public IntermediateInstruction BaseInstruction { get; } 11 | public IExpression[]? Parameters { get; } 12 | public Graph? Block1 { get; } 13 | public Graph? Block2 { get; } 14 | 15 | public GenericExpression(IntermediateInstruction baseInstruction, IExpression[]? parameters = null, Graph? block1 = null, Graph? block2 = null) 16 | { 17 | BaseInstruction = baseInstruction; 18 | Parameters = parameters; 19 | Block1 = block1; 20 | Block2 = block2; 21 | } 22 | 23 | public string GetStringRepresentation() => BaseInstruction.OperationStringFormat.SafeFormat(Parameters?.Select(x => x.GetStringRepresentation()).ToArray()); 24 | } 25 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/SourceCode/AssignmentExpression.cs: -------------------------------------------------------------------------------- 1 | using Rivers; 2 | using WasmLib.FileFormat; 3 | using WasmLib.Utils; 4 | 5 | namespace WasmLib.Decompilation.SourceCode 6 | { 7 | public class AssignmentExpression : IExpression, IHasBlocks 8 | { 9 | public IExpression BaseExpression { get; } 10 | public VariableReferenceExpression Reference { get; } 11 | public string Name { get; } 12 | public Graph? Block1 => (BaseExpression as IHasBlocks)?.Block1; 13 | public Graph? Block2 => (BaseExpression as IHasBlocks)?.Block2; 14 | 15 | public AssignmentExpression(IExpression baseExpression, ValueKind type, int index) 16 | { 17 | BaseExpression = baseExpression; 18 | Name = $"{EnumUtils.GetDescription(type)}_{index}"; 19 | Reference = new VariableReferenceExpression(Name); 20 | } 21 | 22 | public string GetStringRepresentation() => $"{Reference.GetStringRepresentation()} = {BaseExpression.GetStringRepresentation()}"; 23 | } 24 | } -------------------------------------------------------------------------------- /WasmTool/CommandLine/CliArguments.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using EntryPoint; 3 | 4 | namespace WasmTool.CommandLine 5 | { 6 | [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")] 7 | [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] 8 | public class CliArguments : BaseCliArguments 9 | { 10 | private const string Name = "WasmTool"; 11 | 12 | public CliArguments() : base(Name) { } 13 | 14 | [Required] 15 | [OptionParameter("input", 'i')] 16 | public string InputFile { get; set; } = ""; 17 | 18 | [OptionParameter("output", 'o')] 19 | public string? OutputFile { get; set; } 20 | 21 | [OptionParameter("decompiler", 'd')] 22 | public DecompilerKind Decompiler { get; set; } = DecompilerKind.Generic; 23 | 24 | [OptionParameter("skip", 's')] 25 | public uint Skip { get; set; } 26 | 27 | [OptionParameter("count", 'n')] 28 | public uint Count { get; set; } = (uint)int.MaxValue; 29 | } 30 | } -------------------------------------------------------------------------------- /WasmLib/FileFormat/Instructions/OperandKind.cs: -------------------------------------------------------------------------------- 1 | namespace WasmLib.FileFormat.Instructions 2 | { 3 | public enum OperandKind 4 | { 5 | None, 6 | /// https://webassembly.github.io/spec/core/binary/types.html#binary-blocktype 7 | BlockType, 8 | /// 9 | /// Refers to nested control flow constructs, with 0 being the current "block" 10 | /// 11 | LabelIndex, 12 | /// for br_table 13 | BrTableOperand, 14 | FuncIndex, 15 | /// for call_indirect, has a 0x00 after it in case the call target is invalid 16 | IndirectCallTypeIndex, 17 | LocalIndex, 18 | GlobalIndex, 19 | /// u32 offset + u32 align 20 | MemArg, 21 | /// A single 0x00 byte 22 | /// for memory.size and memory.grow 23 | Zero, 24 | ImmediateI32, 25 | ImmediateI64, 26 | ImmediateF32, 27 | ImmediateF64, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 HoLLy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /WasmLib/FileFormat/Element.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.IO; 3 | using System.Linq; 4 | using WasmLib.FileFormat.Instructions; 5 | using WasmLib.Utils; 6 | 7 | namespace WasmLib.FileFormat 8 | { 9 | public class Element : IDeserializable 10 | { 11 | public uint TableIndex => tableIndex ?? throw new UninitializedFieldException(); 12 | public Instruction[] Expression => expression ?? throw new UninitializedFieldException(); 13 | public uint[] FunctionIndices => functionIndices ?? throw new UninitializedFieldException(); 14 | 15 | private uint? tableIndex; 16 | private Instruction[]? expression; 17 | private uint[]? functionIndices; 18 | 19 | public void Read(BinaryReader br) 20 | { 21 | tableIndex = br.ReadVarUint32(); 22 | Debug.Assert(TableIndex == 0, $"Only 1 table it allowed, but read table index of {TableIndex} in {nameof(Element)}"); 23 | 24 | expression = br.ReadExpression(); 25 | functionIndices = br.ReadVarUint32Array(); 26 | } 27 | 28 | public override string ToString() => $"(elem ({string.Join(";", Expression.Select(x => x.ToString()))} {TableIndex}))"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /WasmLib/FileFormat/Export.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using WasmLib.Utils; 4 | 5 | namespace WasmLib.FileFormat 6 | { 7 | public class Export : IDeserializable 8 | { 9 | public string Name => name ?? throw new UninitializedFieldException(); 10 | public ExportKind Kind => kind ?? throw new UninitializedFieldException(); 11 | public uint Index => index ?? throw new UninitializedFieldException(); 12 | 13 | private string? name; 14 | private ExportKind? kind; 15 | private uint? index; 16 | 17 | public void Read(BinaryReader br) 18 | { 19 | name = br.ReadIdentifier(); 20 | kind = (ExportKind)br.ReadVarUint7(); 21 | index = br.ReadVarUint32(); 22 | } 23 | 24 | public override string ToString() => Kind switch { 25 | ExportKind.FuncIndex => $"(export \"{Name}\" (func {Index}))", 26 | ExportKind.TableIndex => $"(export \"{Name}\" (table {Index}))", 27 | ExportKind.MemoryIndex => $"(export \"{Name}\" (memory {Index}))", 28 | ExportKind.GlobalIndex => $"(export \"{Name}\" (global {Index}))", 29 | _ => throw new ArgumentOutOfRangeException() 30 | }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /WasmLib.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WasmLib", "WasmLib\WasmLib.csproj", "{094F246E-B54C-487D-83D5-92C37FB228A4}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WasmTool", "WasmTool\WasmTool.csproj", "{B872FBF3-816A-4B02-B4FF-0B10055E547A}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {094F246E-B54C-487D-83D5-92C37FB228A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {094F246E-B54C-487D-83D5-92C37FB228A4}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {094F246E-B54C-487D-83D5-92C37FB228A4}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {094F246E-B54C-487D-83D5-92C37FB228A4}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {B872FBF3-816A-4B02-B4FF-0B10055E547A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {B872FBF3-816A-4B02-B4FF-0B10055E547A}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {B872FBF3-816A-4B02-B4FF-0B10055E547A}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {B872FBF3-816A-4B02-B4FF-0B10055E547A}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Instructions/TestOperationInstruction.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using WasmLib.FileFormat; 3 | using WasmLib.FileFormat.Instructions; 4 | using WasmLib.Utils; 5 | 6 | namespace WasmLib.Decompilation.Intermediate.Instructions 7 | { 8 | public class TestOperationInstruction : IntermediateInstruction 9 | { 10 | public ValueKind Type { get; } 11 | public OperationKind Operation { get; } 12 | 13 | public override ValueKind[] PopTypes => new[] {Type}; 14 | public override ValueKind[] PushTypes => new[] {ValueKind.I32}; 15 | 16 | public TestOperationInstruction(in Instruction instruction) 17 | { 18 | (Type, Operation) = instruction.OpCode switch { 19 | OpCode.I32Eqz => (ValueKind.I32, OperationKind.Eqz), 20 | OpCode.I64Eqz => (ValueKind.I64, OperationKind.Eqz), 21 | _ => throw new WrongInstructionPassedException(instruction, nameof(VariableInstruction)), 22 | }; 23 | } 24 | 25 | public override string OperationStringFormat => EnumUtils.GetDescription(Operation); 26 | 27 | public override string ToString() => Operation.ToString(); 28 | 29 | public enum OperationKind 30 | { 31 | [Description("{0} == 0")] Eqz, 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /WasmLib/FileFormat/FunctionBody.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using WasmLib.FileFormat.Instructions; 6 | using WasmLib.Utils; 7 | 8 | namespace WasmLib.FileFormat 9 | { 10 | public class FunctionBody : IDeserializable 11 | { 12 | public ValueKind[] Locals => locals ?? throw new UninitializedFieldException(); 13 | public Instruction[] Instructions => body ?? throw new UninitializedFieldException(); 14 | 15 | private ValueKind[]? locals; 16 | private Instruction[]? body; 17 | 18 | public void Read(BinaryReader br) 19 | { 20 | uint bodySize = br.ReadVarUint32(); 21 | var oldPos = br.BaseStream.Position; 22 | 23 | // read local declarations 24 | var localList = new List(); 25 | uint declarationCount = br.ReadVarUint32(); 26 | for (int i = 0; i < declarationCount; i++) { 27 | var repeat = br.ReadVarUint32(); 28 | var valKind = (ValueKind)br.ReadVarUint7(); 29 | localList.AddRange(Enumerable.Repeat(valKind, (int)repeat)); 30 | } 31 | 32 | locals = localList.ToArray(); 33 | 34 | // read remaining bytes as function body 35 | body = Disassembler.Disassemble(br, (uint)(bodySize - (br.BaseStream.Position - oldPos))).ToArray(); 36 | 37 | Debug.Assert(br.BaseStream.Position == oldPos + bodySize); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /WasmLib/FileFormat/FunctionSignature.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.IO; 3 | using System.Linq; 4 | using WasmLib.Utils; 5 | 6 | namespace WasmLib.FileFormat 7 | { 8 | public class FunctionSignature : IDeserializable 9 | { 10 | public ValueKind[] Parameters => parameters ?? throw new UninitializedFieldException(); 11 | public ValueKind[] ReturnParameter => returnParameter ?? throw new UninitializedFieldException(); 12 | 13 | private ValueKind[]? parameters, returnParameter; 14 | 15 | public void Read(BinaryReader br) 16 | { 17 | var type = br.ReadByte(); 18 | Debug.Assert(type == 0x60); 19 | 20 | parameters = br.ReadValueKindArray(); 21 | returnParameter = br.ReadValueKindArray(); 22 | Debug.Assert(ReturnParameter.Length <= 1, $"Only 1 return parameter is supported, found {ReturnParameter.Length} in {nameof(FunctionSignature)}"); 23 | } 24 | 25 | public override string ToString() => $"{(ReturnParameter.Any() ? EnumUtils.GetDescription(ReturnParameter[0]) : "void")}({string.Join(", ", Parameters.Select(EnumUtils.GetDescription))})"; 26 | 27 | public string ToString(string functionName) 28 | { 29 | string returnType = ReturnParameter.Any() ? EnumUtils.GetDescription(ReturnParameter[0]) : "void"; 30 | string args = string.Join(", ", Parameters.Select((kind, i) => $"{EnumUtils.GetDescription(kind)} param_{i}")); 31 | 32 | return $"{returnType} {functionName}({args})"; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Instructions/MemorySizeInstruction.cs: -------------------------------------------------------------------------------- 1 | using WasmLib.FileFormat; 2 | using WasmLib.FileFormat.Instructions; 3 | using WasmLib.Utils; 4 | 5 | namespace WasmLib.Decompilation.Intermediate.Instructions 6 | { 7 | public class MemorySizeInstruction : IntermediateInstruction 8 | { 9 | public OperationKind Operation { get; } 10 | 11 | public override ValueKind[] PopTypes => Operation == OperationKind.Grow ? new[] {ValueKind.I32} : new ValueKind[0]; 12 | public override ValueKind[] PushTypes => new[] {ValueKind.I32}; 13 | 14 | public override StateKind ModifiesState => Operation == OperationKind.Grow ? StateKind.Memory : StateKind.None; 15 | public override StateKind ReadsState => Operation == OperationKind.Size ? StateKind.Memory : StateKind.None; 16 | 17 | public MemorySizeInstruction(in Instruction instruction) 18 | { 19 | Operation = instruction.OpCode switch { 20 | OpCode.MemorySize => OperationKind.Size, 21 | OpCode.MemoryGrow => OperationKind.Grow, 22 | _ => throw new WrongInstructionPassedException(instruction, nameof(MemorySizeInstruction)), 23 | }; 24 | } 25 | 26 | public override string OperationStringFormat => Operation == OperationKind.Size ? "MEMORY.SIZE / PAGE_SIZE" : "MEMORY.GROW({0} * PAGE_SIZE)"; 27 | 28 | public override string ToString() => Operation.ToString(); 29 | 30 | public enum OperationKind 31 | { 32 | Size, 33 | Grow, 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /WasmLib/Utils/EnumUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Reflection; 6 | 7 | namespace WasmLib.Utils 8 | { 9 | public static class EnumUtils 10 | { 11 | private static readonly Dictionary CacheCollection = new Dictionary(); 12 | 13 | public static string GetDescription(T key) where T : Enum 14 | { 15 | // try get cache 16 | Dictionary cache; 17 | if (CacheCollection.TryGetValue(typeof(T), out IDictionary? cacheNoCast)) { 18 | cache = (Dictionary)cacheNoCast!; 19 | } 20 | else { 21 | CacheCollection[typeof(T)] = cache = new Dictionary(); 22 | } 23 | 24 | 25 | // try get string representation 26 | if (cache.TryGetValue(key, out string? s)) { 27 | return s!; 28 | } 29 | 30 | var name = Enum.GetName(typeof(T), key); 31 | 32 | if (name is null) { 33 | return key.ToString(); 34 | } 35 | 36 | var description = typeof(T) 37 | .GetField(name)? 38 | .GetCustomAttribute()? 39 | .Description; 40 | 41 | if (description is null) { 42 | return key.ToString(); 43 | } 44 | 45 | return cache[key] = description; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Instructions/ConstInstruction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using WasmLib.FileFormat; 3 | using WasmLib.FileFormat.Instructions; 4 | using WasmLib.Utils; 5 | 6 | namespace WasmLib.Decompilation.Intermediate.Instructions 7 | { 8 | public class ConstInstruction : IntermediateInstruction 9 | { 10 | public ValueKind Type { get; } 11 | public ulong RawOperand { get; } 12 | 13 | public override ValueKind[] PopTypes => new ValueKind[0]; 14 | public override ValueKind[] PushTypes => new[] {Type}; 15 | 16 | public ConstInstruction(in Instruction instruction) 17 | { 18 | Type = instruction.OpCode switch { 19 | OpCode.I32Const => ValueKind.I32, 20 | OpCode.I64Const => ValueKind.I64, 21 | OpCode.F32Const => ValueKind.F32, 22 | OpCode.F64Const => ValueKind.F64, 23 | _ => throw new WrongInstructionPassedException(instruction, nameof(VariableInstruction)), 24 | }; 25 | RawOperand = instruction.ULongOperand; 26 | } 27 | 28 | public override string OperationStringFormat => OperandString; 29 | 30 | private string OperandString => Type switch { 31 | ValueKind.I32 => $"0x{(uint)RawOperand:X}", 32 | ValueKind.I64 => $"0x{RawOperand:X}", 33 | ValueKind.F32 => $"{BitConverter.Int32BitsToSingle((int)RawOperand)}", 34 | ValueKind.F64 => $"{BitConverter.Int64BitsToDouble((long)RawOperand)}", 35 | _ => throw new ArgumentOutOfRangeException(), 36 | }; 37 | 38 | public override string ToString() => OperandString; 39 | } 40 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Graph/InstructionNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using Rivers; 5 | 6 | namespace WasmLib.Decompilation.Intermediate.Graph 7 | { 8 | public class InstructionNode : Node 9 | { 10 | public IntermediateInstruction Instruction { get; } 11 | public int Index { get; } 12 | 13 | public IEnumerable OutgoingVariableEdges => OutgoingEdges.OfType(); 14 | public IEnumerable IncomingVariableEdges => IncomingEdges.OfType(); 15 | public Rivers.Graph? Block1 { get; } 16 | public Rivers.Graph? Block2 { get; } 17 | 18 | public InstructionNode(IntermediateInstruction instruction, int idx, Rivers.Graph? block1 = null, Rivers.Graph? block2 = null) : base($"_{idx:X4}") 19 | { 20 | Instruction = instruction; 21 | Index = idx; 22 | Block1 = block1; 23 | Block2 = block2; 24 | AddUserData(); 25 | } 26 | 27 | [Conditional("DEBUG")] 28 | private void AddUserData() 29 | { 30 | // BUG: Rivers will not quote single words, which can cause parsers to fail if the output starts with a number 31 | // see Washi1337/Rivers#7 32 | string instructionString = Instruction.ToString(); 33 | UserData["label"] = char.IsDigit(instructionString[0]) && !instructionString.Contains(" ") 34 | ? $" {instructionString} " 35 | : instructionString; 36 | } 37 | 38 | public override string ToString() => Instruction.ToString(); 39 | } 40 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/DisassemblingDecompiler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using WasmLib.FileFormat; 4 | using WasmLib.FileFormat.Instructions; 5 | 6 | namespace WasmLib.Decompilation 7 | { 8 | public class DisassemblingDecompiler : IDecompiler 9 | { 10 | public WasmModule WasmModule { get; private set; } 11 | 12 | public DisassemblingDecompiler(WasmModule wasmModule) 13 | { 14 | WasmModule = wasmModule; 15 | } 16 | 17 | public void DecompileFunction(StreamWriter output, int functionIndex) 18 | { 19 | FunctionBody body = WasmModule.FunctionBodies[functionIndex]; 20 | FunctionSignature signature = WasmModule.FunctionTypes[WasmModule.Functions[functionIndex]]; 21 | int indent = 1; 22 | 23 | output.WriteLine($"fun_{functionIndex:X8}: # {signature}"); 24 | foreach (var instruction in body.Instructions) 25 | { 26 | indent += instruction.OpCode switch { 27 | OpCode.Else => -1, // temporary 28 | OpCode.End => -1, 29 | _ => 0, 30 | }; 31 | 32 | output.WriteLine(new string('\t', indent) + instruction); 33 | 34 | indent += instruction.OpCode switch { 35 | OpCode.Block => 1, 36 | OpCode.Loop => 1, 37 | OpCode.If => 1, 38 | OpCode.Else => 1, 39 | _ => 0, 40 | }; 41 | } 42 | 43 | // At the end, expect to be 1 indent level lower due to trailing `end` instruction 44 | if (indent != 0) { 45 | throw new Exception("Function body contains unbalanced branching instructions"); 46 | } 47 | 48 | output.WriteLine(); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Instructions/BranchInstruction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using WasmLib.FileFormat; 3 | using WasmLib.FileFormat.Instructions; 4 | using WasmLib.Utils; 5 | 6 | namespace WasmLib.Decompilation.Intermediate.Instructions 7 | { 8 | public class BranchInstruction : IntermediateInstruction 9 | { 10 | public BranchKind Kind { get; } 11 | public uint Label { get; } 12 | public uint[]? Labels { get; } 13 | 14 | public override ValueKind[] PopTypes => Kind == BranchKind.Conditional || Kind == BranchKind.Table ? new[] {ValueKind.I32} : new ValueKind[0]; 15 | public override ValueKind[] PushTypes => new ValueKind[0]; 16 | 17 | public override bool RestOfBlockUnreachable => Kind == BranchKind.Normal || Kind == BranchKind.Table; 18 | public override bool ModifiesControlFlow => true; 19 | 20 | public BranchInstruction(in Instruction instruction) 21 | { 22 | Kind = instruction.OpCode switch { 23 | OpCode.Br => BranchKind.Normal, 24 | OpCode.BrIf => BranchKind.Conditional, 25 | OpCode.BrTable => BranchKind.Table, 26 | _ => throw new WrongInstructionPassedException(instruction, nameof(BranchInstruction)), 27 | }; 28 | 29 | Label = instruction.UIntOperand; 30 | 31 | if (Kind == BranchKind.Table) { 32 | Labels = instruction.UIntArrayOperand; 33 | } 34 | } 35 | 36 | public override string OperationStringFormat => Kind switch { 37 | BranchKind.Normal => $"BRANCH {Label}", 38 | BranchKind.Conditional => $"BRANCH_IF({{0}}) {Label}", 39 | BranchKind.Table => $"BRANCH_TABLE {{{string.Join(", ", Labels ?? throw new Exception("Found no labels for br_table instruction"))}}}[{{0}}] ?? {Label}", 40 | _ => throw new IndexOutOfRangeException() 41 | }; 42 | 43 | public override string ToString() => $"Branch {Kind}"; 44 | 45 | public enum BranchKind 46 | { 47 | Normal, 48 | Conditional, 49 | Table, 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /WasmTool/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using EntryPoint; 5 | using WasmLib; 6 | using WasmLib.Decompilation; 7 | using WasmTool.CommandLine; 8 | 9 | namespace WasmTool 10 | { 11 | public static class Program 12 | { 13 | private static void Main(string[] args) 14 | { 15 | // print debug messages to console 16 | Trace.Listeners.Add(new ConsoleTraceListener()); 17 | 18 | var arguments = Cli.Parse(args); 19 | 20 | if (!File.Exists(arguments.InputFile)) { 21 | Console.WriteLine("Pass me a file as argument"); 22 | return; 23 | } 24 | 25 | var sw = Stopwatch.StartNew(); 26 | var wasmFile = WasmModule.Read(arguments.InputFile); 27 | sw.Stop(); 28 | Console.WriteLine($"Read in {sw.Elapsed}"); 29 | 30 | Console.WriteLine("wasm version: " + wasmFile.Version); 31 | 32 | sw = Stopwatch.StartNew(); 33 | Stream outputStream = arguments.OutputFile is null 34 | ? Console.OpenStandardOutput() 35 | : File.Open(arguments.OutputFile, FileMode.Create); 36 | using var w = new StreamWriter(outputStream); 37 | IDecompiler dec = arguments.Decompiler switch { 38 | DecompilerKind.Disassembler => (IDecompiler)new DisassemblingDecompiler(wasmFile), 39 | DecompilerKind.IntermediateRepresentation => new IntermediateRepresentationDecompiler(wasmFile), 40 | DecompilerKind.Generic => new GenericDecompiler(wasmFile), 41 | _ => throw new Exception("Invalid decompiler type specified"), 42 | }; 43 | 44 | for (uint i = arguments.Skip; i < Math.Min(arguments.Skip + arguments.Count, wasmFile.FunctionBodies.Length); i++) { 45 | Debug.WriteLine($"Decompiling function {i} (0x{i:X})"); 46 | dec.DecompileFunction(w, (int)i); 47 | } 48 | 49 | sw.Stop(); 50 | Console.WriteLine($"Written to {arguments.OutputFile} in {sw.Elapsed}"); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Instructions/ControlBlockInstruction.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel; 3 | using WasmLib.FileFormat; 4 | using WasmLib.FileFormat.Instructions; 5 | using WasmLib.Utils; 6 | 7 | namespace WasmLib.Decompilation.Intermediate.Instructions 8 | { 9 | public class ControlBlockInstruction : IntermediateInstruction 10 | { 11 | public ControlBlockKind Kind { get; } 12 | public ValueKind ValueKind { get; } 13 | 14 | public override ValueKind[] PopTypes => Kind == ControlBlockKind.If ? new[] {ValueKind.I32} : new ValueKind[0]; 15 | public override ValueKind[] PushTypes => ValueKind != ValueKind.Empty ? new[] {ValueKind} : new ValueKind[0]; 16 | 17 | public override bool ModifiesControlFlow => true; // TODO: could be optimized by checking if control blocks are pure 18 | public override StateKind ModifiesState => StateKind.All; // same as above 19 | public override StateKind ReadsState => StateKind.All; // same as above 20 | public override bool CanBeInlined => false; 21 | 22 | public ControlBlockInstruction(in Instruction instruction, IReadOnlyList block1, IReadOnlyList? block2) 23 | { 24 | Kind = instruction.OpCode switch { 25 | OpCode.Block => ControlBlockKind.Block, 26 | OpCode.Loop => ControlBlockKind.Loop, 27 | OpCode.If => ControlBlockKind.If, 28 | _ => throw new WrongInstructionPassedException(instruction, nameof(ControlBlockInstruction)) 29 | }; 30 | 31 | ValueKind = (ValueKind)instruction.UIntOperand; 32 | 33 | Block1 = new ControlBlock(block1, ValueKind); 34 | 35 | if (block2 != null) { 36 | Block2 = new ControlBlock(block2, ValueKind); 37 | } 38 | } 39 | 40 | public override string OperationStringFormat { 41 | get { 42 | string keyword = EnumUtils.GetDescription(Kind); 43 | 44 | if (Kind == ControlBlockKind.If) { 45 | keyword += " {0}"; 46 | } 47 | 48 | return keyword; 49 | } 50 | } 51 | 52 | public enum ControlBlockKind 53 | { 54 | [Description("block")] Block, 55 | [Description("loop")] Loop, 56 | [Description("if")] If, 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /WasmLib/FileFormat/Instructions/Instruction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using WasmLib.Utils; 4 | 5 | namespace WasmLib.FileFormat.Instructions 6 | { 7 | public readonly partial struct Instruction 8 | { 9 | public OpCode OpCode { get; } 10 | 11 | private readonly ulong operand; 12 | private readonly uint[]? uIntArrayOperand; 13 | 14 | public uint UIntOperand => (uint)operand; 15 | public int IntOperand => (int)operand; 16 | public ulong ULongOperand => operand; 17 | public long LongOperand => (long)operand; 18 | public uint[] UIntArrayOperand => uIntArrayOperand ?? throw new Exception($"Tried to get {nameof(UIntArrayOperand)} when {nameof(uIntArrayOperand)} was null"); 19 | 20 | public Instruction(OpCode opCode, ulong operand = 0, uint[]? uIntArrayOperand = null) 21 | { 22 | OpCode = opCode; 23 | this.operand = operand; 24 | this.uIntArrayOperand = uIntArrayOperand; 25 | } 26 | 27 | public override string ToString() 28 | { 29 | var opcodeString = EnumUtils.GetDescription(OpCode); 30 | 31 | switch (GetOperandKind(OpCode)) { 32 | case OperandKind.None: 33 | return opcodeString; 34 | case OperandKind.BrTableOperand: 35 | return $"{opcodeString} {string.Join("-", uIntArrayOperand!.Select(x => $"0x{x:X}"))} 0x{operand:x}"; 36 | case OperandKind.BlockType: 37 | return $"{opcodeString} {(ValueKind)operand}"; 38 | case OperandKind.LabelIndex: 39 | case OperandKind.FuncIndex: 40 | case OperandKind.IndirectCallTypeIndex: 41 | return $"{opcodeString} 0x{operand:X}"; 42 | case OperandKind.MemArg: 43 | return $"{opcodeString} 0x{operand & 0xFFFFFFFF00000000:X} 0x{operand & 0xFFFFFFFF:X}"; 44 | case OperandKind.Zero: 45 | case OperandKind.LocalIndex: 46 | case OperandKind.GlobalIndex: 47 | case OperandKind.ImmediateI32: 48 | case OperandKind.ImmediateI64: 49 | return $"{opcodeString} {operand}"; 50 | case OperandKind.ImmediateF32: 51 | return $"{opcodeString} {BitConverter.Int32BitsToSingle((int)operand)}"; 52 | case OperandKind.ImmediateF64: 53 | return $"{opcodeString} {BitConverter.Int64BitsToDouble((long)operand)}"; 54 | default: 55 | throw new ArgumentOutOfRangeException(); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/IntermediateInstruction.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using WasmLib.FileFormat; 3 | 4 | namespace WasmLib.Decompilation.Intermediate 5 | { 6 | public abstract class IntermediateInstruction 7 | { 8 | // TODO: abstract pop/pushtypes private for caching 9 | public abstract ValueKind[] PopTypes { get; } 10 | public abstract ValueKind[] PushTypes { get; } 11 | public virtual bool RestOfBlockUnreachable => false; 12 | public virtual bool ModifiesControlFlow => false; 13 | public virtual StateKind ModifiesState => StateKind.None; 14 | public virtual StateKind ReadsState => StateKind.None; 15 | public virtual bool CanInline => true; 16 | public virtual bool CanBeInlined => true; // only matters if PushTypes has items 17 | public virtual bool IsImplicit => false; 18 | 19 | public bool HasBlock => Block1 != null; 20 | 21 | public ControlBlock? Block1 { get; protected set; } 22 | public ControlBlock? Block2 { get; protected set; } 23 | 24 | public int PopCount => PopTypes.Length; 25 | public int PushCount => PushTypes.Length; 26 | 27 | public abstract string OperationStringFormat { get; } 28 | public virtual string? Comment => null; 29 | 30 | public override string ToString() => GetType().Name.Replace("Instruction", string.Empty); 31 | 32 | /// 33 | /// Determines whether the instruction stops 34 | /// the current instruction from being inlined. 35 | /// 36 | public bool HasConflictingState(IntermediateInstruction other, StateKind kinds = StateKind.All) 37 | { 38 | for (int i = 1; i < byte.MaxValue; i <<= 1) { 39 | StateKind kind = (StateKind)i; 40 | if (!kinds.HasFlag(kind)) { 41 | continue; 42 | } 43 | 44 | bool thisRead = ReadsState.HasFlag(kind); 45 | bool thisWrite = ModifiesState.HasFlag(kind); 46 | bool otherRead = other.ReadsState.HasFlag(kind); 47 | bool otherWrite = other.ModifiesState.HasFlag(kind); 48 | 49 | bool thisAccess = thisRead || thisWrite; 50 | bool otherAccess = otherRead || otherWrite; 51 | 52 | if (!thisAccess || !otherAccess) { 53 | continue; 54 | } 55 | 56 | if (!thisWrite && !otherWrite) { 57 | continue; 58 | } 59 | 60 | Debug.Assert(thisRead && otherWrite || thisWrite && otherRead || thisWrite && otherWrite); 61 | return true; 62 | } 63 | 64 | return false; 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /WasmLib/FileFormat/Import.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using WasmLib.Utils; 5 | 6 | namespace WasmLib.FileFormat 7 | { 8 | public class Import : IDeserializable 9 | { 10 | public string ModuleName => moduleName ?? throw new UninitializedFieldException(); 11 | public string ExportName => exportName ?? throw new UninitializedFieldException(); 12 | public ImportKind Kind => kind ?? throw new UninitializedFieldException(); 13 | 14 | private string? moduleName, exportName; 15 | private ImportKind? kind; 16 | 17 | /// When is 18 | public uint? SignatureIndex { get; private set; } 19 | 20 | /// When is 21 | public Limits? TableSize { get; private set; } 22 | 23 | /// When is 24 | public Limits? MemorySize { get; private set; } 25 | 26 | /// When is 27 | public GlobalType? GlobalType { get; private set; } 28 | 29 | public void Read(BinaryReader br) 30 | { 31 | moduleName = br.ReadIdentifier(); 32 | exportName = br.ReadIdentifier(); 33 | kind = (ImportKind)br.ReadVarUint7(); 34 | 35 | switch (Kind) { 36 | case ImportKind.TypeIndex: 37 | SignatureIndex = br.ReadVarUint32(); 38 | break; 39 | case ImportKind.TableType: 40 | byte elementType = br.ReadVarUint7(); 41 | Debug.Assert(elementType == 0x70); 42 | TableSize = Limits.Read(br); 43 | break; 44 | case ImportKind.MemoryType: 45 | MemorySize = Limits.Read(br); 46 | break; 47 | case ImportKind.GlobalType: 48 | GlobalType = FileFormat.GlobalType.Read(br); 49 | break; 50 | default: 51 | throw new ArgumentOutOfRangeException(); 52 | } 53 | } 54 | 55 | public override string ToString() => Kind switch { 56 | ImportKind.TypeIndex => $"(import \"{ModuleName}\" \"{ExportName}\" (func (type {SignatureIndex})))", 57 | ImportKind.TableType => $"(import \"{ModuleName}\" \"{ExportName}\" (table {TableSize?.Min} {TableSize?.Max}))", 58 | ImportKind.MemoryType => $"(import \"{ModuleName}\" \"{ExportName}\" (memory {MemorySize?.Min} {MemorySize?.Max}))", 59 | ImportKind.GlobalType => $"(import \"{ModuleName}\" \"{ExportName}\" (global {GlobalType}))", 60 | _ => throw new ArgumentOutOfRangeException() 61 | }; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /WasmLib/Utils/BinaryReaderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text; 5 | using WasmLib.FileFormat; 6 | using WasmLib.FileFormat.Instructions; 7 | 8 | namespace WasmLib.Utils 9 | { 10 | internal static class BinaryReaderExtensions 11 | { 12 | public static byte ReadVarUint7(this BinaryReader br) 13 | { 14 | byte read = br.ReadByte(); 15 | 16 | if ((read & 0x80) != 0) { 17 | throw new Exception("Exceeded maximum length for VarUint7"); 18 | } 19 | 20 | return (byte)(read & 0x7f); 21 | } 22 | 23 | public static uint ReadVarUint32(this BinaryReader br) 24 | { 25 | uint ret = 0; 26 | 27 | for (int i = 0; i < 5; i++) { 28 | byte read = br.ReadByte(); 29 | ret |= (read & 0x7Fu) << (i * 7); 30 | 31 | if ((read & 0x80) == 0) { 32 | return ret; 33 | } 34 | } 35 | 36 | throw new Exception("Exceeded maximum length for VarUint32"); 37 | } 38 | 39 | public static ulong ReadVarUint64(this BinaryReader br) 40 | { 41 | ulong ret = 0; 42 | 43 | for (int i = 0; i < 10; i++) { 44 | byte read = br.ReadByte(); 45 | ret |= (read & 0x7Fu) << (i * 7); 46 | 47 | if ((read & 0x80) == 0) { 48 | return ret; 49 | } 50 | } 51 | 52 | throw new Exception("Exceeded maximum length for VarUint32"); 53 | } 54 | 55 | public static int ReadVarInt32(this BinaryReader br) => (int)br.ReadVarUint32(); 56 | 57 | public static T[] ReadWasmArray(this BinaryReader br) where T : IDeserializable, new() 58 | { 59 | var arr = new T[br.ReadVarInt32()]; 60 | 61 | for (int i = 0; i < arr.Length; i++) { 62 | arr[i] = new T(); 63 | arr[i].Read(br); 64 | } 65 | 66 | return arr; 67 | } 68 | 69 | public static byte[] ReadWasmByteArray(this BinaryReader br) => br.ReadBytes(br.ReadVarInt32()); 70 | public static ValueKind[] ReadValueKindArray(this BinaryReader br) => br.ReadBytes(br.ReadVarInt32()).Cast().ToArray(); 71 | 72 | public static uint[] ReadVarUint32Array(this BinaryReader br) 73 | { 74 | var arr = new uint[br.ReadVarInt32()]; 75 | 76 | for (int i = 0; i < arr.Length; i++) { 77 | arr[i] = br.ReadVarUint32(); 78 | } 79 | 80 | return arr; 81 | } 82 | 83 | public static string ReadIdentifier(this BinaryReader br) => Encoding.UTF8.GetString(br.ReadWasmByteArray()); 84 | 85 | public static Instruction[] ReadExpression(this BinaryReader br) => Disassembler.DisassembleExpression(br).ToArray(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Instructions/CallInstruction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using WasmLib.FileFormat; 5 | using WasmLib.FileFormat.Instructions; 6 | using WasmLib.Utils; 7 | 8 | namespace WasmLib.Decompilation.Intermediate.Instructions 9 | { 10 | public class CallInstruction : IntermediateInstruction 11 | { 12 | public string? Name { get; } 13 | public bool IsIndirect { get; } 14 | public FunctionSignature Signature { get; } 15 | 16 | public override ValueKind[] PopTypes => (IsIndirect ? new[] {ValueKind.I32} : new ValueKind[0]).Concat(Signature.Parameters.Reverse()).ToArray(); 17 | public override ValueKind[] PushTypes => Signature.ReturnParameter.Reverse().ToArray(); 18 | 19 | public override bool CanBeInlined => false; // only for readability 20 | public override StateKind ModifiesState => StateKind.All & ~StateKind.Locals; // TODO: could be optimized by checking if referenced function is pure 21 | public override StateKind ReadsState => StateKind.All & ~StateKind.Locals; 22 | 23 | public CallInstruction(in Instruction instruction, WasmModule module) 24 | { 25 | uint index = instruction.UIntOperand; 26 | 27 | if (instruction.OpCode == OpCode.Call) { 28 | IsIndirect = false; 29 | Name = "fun_" + index.ToString("X8"); 30 | 31 | if (index <= module.ImportedFunctionCount) { 32 | var import = module.Imports.Where(x => x.Kind == ImportKind.TypeIndex).Skip((int)index).First(); 33 | 34 | Debug.Assert(import.SignatureIndex != null); 35 | Name = import.ModuleName + "." + import.ExportName; 36 | Signature = module.FunctionTypes[import.SignatureIndex.Value]; 37 | } 38 | else { 39 | // skipping signatures for imported functions 40 | Signature = module.FunctionTypes[module.Functions[index - module.ImportedFunctionCount]]; 41 | } 42 | } 43 | else if (instruction.OpCode == OpCode.CallIndirect) { 44 | IsIndirect = true; 45 | Signature = module.FunctionTypes[index]; 46 | } 47 | else { 48 | throw new WrongInstructionPassedException(instruction, nameof(CallInstruction)); 49 | } 50 | } 51 | 52 | public override string OperationStringFormat { 53 | get { 54 | if (Signature.ReturnParameter.Length > 1) { 55 | throw new Exception("Multiple return values not implemented"); 56 | } 57 | 58 | string name = IsIndirect ? "ELEM[{0}]" : Name!; 59 | string parameters = string.Join(", ", Enumerable.Range(IsIndirect ? 1 : 0, PopCount - (IsIndirect ? 1 : 0)).Select(i => $"{{{i}}}")); 60 | 61 | return $"{name}({parameters})"; 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /WasmLib/Utils/Disassembler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using WasmLib.FileFormat.Instructions; 6 | 7 | namespace WasmLib.Utils 8 | { 9 | public static class Disassembler 10 | { 11 | public static IEnumerable Disassemble(BinaryReader br, uint length) 12 | { 13 | long startPos = br.BaseStream.Position; 14 | while (startPos + length > br.BaseStream.Position) { 15 | yield return DisassembleInstruction(br); 16 | } 17 | } 18 | 19 | public static IEnumerable DisassembleExpression(BinaryReader br) 20 | { 21 | Instruction instr; 22 | do { 23 | yield return instr = DisassembleInstruction(br); 24 | } while (instr.OpCode != OpCode.End); 25 | } 26 | 27 | public static Instruction DisassembleInstruction(BinaryReader br) 28 | { 29 | var opcode = (OpCode)br.ReadByte(); 30 | 31 | switch (Instruction.GetOperandKind(opcode)) { 32 | case OperandKind.None: 33 | return new Instruction(opcode); 34 | case OperandKind.BlockType: 35 | return new Instruction(opcode, br.ReadVarUint7()); 36 | case OperandKind.LabelIndex: 37 | case OperandKind.FuncIndex: 38 | case OperandKind.LocalIndex: 39 | case OperandKind.GlobalIndex: 40 | return new Instruction(opcode, br.ReadVarUint32()); 41 | case OperandKind.IndirectCallTypeIndex: // has a 0x00 after it 42 | var instruction = new Instruction(opcode, br.ReadVarUint32()); 43 | byte trap = br.ReadByte(); 44 | Debug.Assert(trap == 0x00); 45 | return instruction; 46 | case OperandKind.BrTableOperand: 47 | return new Instruction(opcode, uIntArrayOperand: br.ReadVarUint32Array(), operand: br.ReadVarUint32()); 48 | case OperandKind.MemArg: 49 | return new Instruction(opcode, br.ReadVarUint32() | ((ulong)br.ReadVarUint32() << 32)); 50 | case OperandKind.Zero: 51 | byte zero = br.ReadByte(); 52 | Debug.Assert(zero == 0x00); 53 | return new Instruction(opcode); 54 | case OperandKind.ImmediateI32: 55 | return new Instruction(opcode, br.ReadVarUint32()); 56 | case OperandKind.ImmediateI64: 57 | return new Instruction(opcode, br.ReadVarUint64()); 58 | case OperandKind.ImmediateF32: 59 | return new Instruction(opcode, (ulong)BitConverter.SingleToInt32Bits(br.ReadSingle())); 60 | case OperandKind.ImmediateF64: 61 | return new Instruction(opcode, (ulong)BitConverter.DoubleToInt64Bits(br.ReadDouble())); 62 | default: 63 | throw new ArgumentOutOfRangeException(); 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Instructions/UnaryOperationInstruction.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using WasmLib.FileFormat; 3 | using WasmLib.FileFormat.Instructions; 4 | using WasmLib.Utils; 5 | 6 | namespace WasmLib.Decompilation.Intermediate.Instructions 7 | { 8 | public class UnaryOperationInstruction : IntermediateInstruction 9 | { 10 | public ValueKind Type { get; } 11 | public OperationKind Operation { get; } 12 | 13 | public override ValueKind[] PopTypes => new[] {Type}; 14 | public override ValueKind[] PushTypes => new[] {Type}; 15 | 16 | public UnaryOperationInstruction(in Instruction instruction) 17 | { 18 | (Type, Operation) = instruction.OpCode switch { 19 | OpCode.I32Clz => (ValueKind.I32, OperationKind.Clz), 20 | OpCode.I32Ctz => (ValueKind.I32, OperationKind.Ctz), 21 | OpCode.I32Popcnt => (ValueKind.I32, OperationKind.PopCnt), 22 | OpCode.I64Clz => (ValueKind.I64, OperationKind.Clz), 23 | OpCode.I64Ctz => (ValueKind.I64, OperationKind.Ctz), 24 | OpCode.I64Popcnt => (ValueKind.I64, OperationKind.PopCnt), 25 | 26 | OpCode.F32Abs => (ValueKind.F32, OperationKind.Abs), 27 | OpCode.F32Neg => (ValueKind.F32, OperationKind.Neg), 28 | OpCode.F32Ceil => (ValueKind.F32, OperationKind.Ceil), 29 | OpCode.F32Floor => (ValueKind.F32, OperationKind.Floor), 30 | OpCode.F32Trunc => (ValueKind.F32, OperationKind.Truncate), 31 | OpCode.F32Nearest => (ValueKind.F32, OperationKind.Nearest), 32 | OpCode.F32Sqrt => (ValueKind.F32, OperationKind.Sqrt), 33 | OpCode.F64Abs => (ValueKind.F64, OperationKind.Abs), 34 | OpCode.F64Neg => (ValueKind.F64, OperationKind.Neg), 35 | OpCode.F64Ceil => (ValueKind.F64, OperationKind.Ceil), 36 | OpCode.F64Floor => (ValueKind.F64, OperationKind.Floor), 37 | OpCode.F64Trunc => (ValueKind.F64, OperationKind.Truncate), 38 | OpCode.F64Nearest => (ValueKind.F64, OperationKind.Nearest), 39 | OpCode.F64Sqrt => (ValueKind.F64, OperationKind.Sqrt), 40 | 41 | _ => throw new WrongInstructionPassedException(instruction, nameof(UnaryOperationInstruction)), 42 | }; 43 | } 44 | 45 | public override string OperationStringFormat => EnumUtils.GetDescription(Operation) + (Operation == OperationKind.Neg ? "{0}" : "({0})"); 46 | 47 | public override string ToString() => Operation.ToString(); 48 | 49 | public enum OperationKind 50 | { 51 | [Description("clz")] Clz, 52 | [Description("ctz")] Ctz, 53 | [Description("popcnt")] PopCnt, 54 | 55 | [Description("abs")] Abs, 56 | [Description("-")] Neg, 57 | [Description("sqrt")] Sqrt, 58 | [Description("ceil")] Ceil, 59 | [Description("floor")] Floor, 60 | [Description("chop")] Truncate, 61 | [Description("roundeven")] Nearest, 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /WasmLib/FileFormat/Instructions/Instruction_Utils.cs: -------------------------------------------------------------------------------- 1 | namespace WasmLib.FileFormat.Instructions 2 | { 3 | public readonly partial struct Instruction 4 | { 5 | public static OperandKind GetOperandKind(OpCode instr) 6 | { 7 | switch (instr) { 8 | case OpCode.Unreachable: 9 | case OpCode.Nop: 10 | return OperandKind.None; 11 | 12 | case OpCode.Block: // these are special opcodes that serve as control flow markers 13 | case OpCode.Loop: 14 | case OpCode.If: 15 | return OperandKind.BlockType; 16 | case OpCode.Else: 17 | case OpCode.End: 18 | return OperandKind.None; 19 | 20 | case OpCode.Br: 21 | case OpCode.BrIf: 22 | return OperandKind.LabelIndex; 23 | case OpCode.BrTable: 24 | return OperandKind.BrTableOperand; 25 | 26 | case OpCode.Return: 27 | return OperandKind.None; 28 | case OpCode.Call: 29 | return OperandKind.FuncIndex; 30 | case OpCode.CallIndirect: 31 | return OperandKind.IndirectCallTypeIndex; 32 | 33 | case OpCode.Drop: 34 | case OpCode.Select: 35 | return OperandKind.None; 36 | 37 | case OpCode.LocalGet: 38 | case OpCode.LocalSet: 39 | case OpCode.LocalTee: 40 | return OperandKind.LocalIndex; 41 | case OpCode.GlobalGet: 42 | case OpCode.GlobalSet: 43 | return OperandKind.GlobalIndex; 44 | 45 | case OpCode.I32Load: 46 | case OpCode.I64Load: 47 | case OpCode.F32Load: 48 | case OpCode.F64Load: 49 | case OpCode.I32Load8S: 50 | case OpCode.I32Load8U: 51 | case OpCode.I32Load16S: 52 | case OpCode.I32Load16U: 53 | case OpCode.I64Load8S: 54 | case OpCode.I64Load8U: 55 | case OpCode.I64Load16S: 56 | case OpCode.I64Load16U: 57 | case OpCode.I64Load32S: 58 | case OpCode.I64Load32U: 59 | case OpCode.I32Store: 60 | case OpCode.I64Store: 61 | case OpCode.F32Store: 62 | case OpCode.F64Store: 63 | case OpCode.I32Store8: 64 | case OpCode.I32Store16: 65 | case OpCode.I64Store8: 66 | case OpCode.I64Store16: 67 | case OpCode.I64Store32: 68 | return OperandKind.MemArg; 69 | 70 | case OpCode.MemorySize: 71 | case OpCode.MemoryGrow: 72 | return OperandKind.Zero; 73 | 74 | case OpCode.I32Const: 75 | return OperandKind.ImmediateI32; 76 | case OpCode.I64Const: 77 | return OperandKind.ImmediateI64; 78 | case OpCode.F32Const: 79 | return OperandKind.ImmediateF32; 80 | case OpCode.F64Const: 81 | return OperandKind.ImmediateF64; 82 | 83 | default: // anything after 0x45 does not have an operand 84 | return OperandKind.None; 85 | } 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/IntermediateContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using WasmLib.FileFormat; 6 | 7 | namespace WasmLib.Decompilation.Intermediate 8 | { 9 | public struct IntermediateContext 10 | { 11 | public int Indentation { get; private set; } 12 | public FunctionSignature Signature { get; } 13 | public WasmModule WasmModule { get; } 14 | private readonly StreamWriter streamWriter; 15 | 16 | public Stack Stack { get; } 17 | public Stack StackIndices { get; } 18 | public bool RestOfBlockUnreachable { get; set; } 19 | 20 | private uint varCount; 21 | 22 | public IntermediateContext(FunctionSignature signature, WasmModule wasmModule, StreamWriter writer) 23 | { 24 | Indentation = 0; 25 | Signature = signature; 26 | WasmModule = wasmModule; 27 | streamWriter = writer; 28 | Stack = new Stack(); 29 | StackIndices = new Stack(); 30 | StackIndices.Push(0); 31 | varCount = 0; 32 | 33 | RestOfBlockUnreachable = false; 34 | } 35 | 36 | public Variable Peek() 37 | { 38 | Debug.Assert(Stack.Any(), "Tried to peek a value from an empty stack"); 39 | return Stack.Peek(); 40 | } 41 | 42 | public Variable Pop() 43 | { 44 | Debug.Assert(Stack.Any(), "Tried to pop a value from an empty stack"); 45 | Debug.Assert(Stack.Count > StackIndices.Peek(), $"Pop causes stack size to becomes less than {StackIndices.Count}, which is the minimum for this block"); 46 | return Stack.Pop(); 47 | } 48 | 49 | public Variable Push(ValueKind type) 50 | { 51 | Variable variable = Variable.Stack(type, varCount++); 52 | Stack.Push(variable); 53 | return variable; 54 | } 55 | 56 | public void EnterBlock() 57 | { 58 | Indent(); 59 | StackIndices.Push(Stack.Count); 60 | } 61 | 62 | public void ExitBlock() 63 | { 64 | DeIndent(); 65 | int previousStackSize = StackIndices.Pop(); 66 | 67 | if (!RestOfBlockUnreachable) { 68 | Debug.Assert(Stack.Count <= previousStackSize + 1); 69 | } 70 | 71 | RestOfBlockUnreachable = false; 72 | 73 | while (Stack.Count > previousStackSize) { 74 | Stack.Pop(); 75 | } 76 | } 77 | 78 | public void Indent() => Indentation++; 79 | 80 | public void DeIndent() 81 | { 82 | Indentation--; 83 | Debug.Assert(Indentation >= 0, "Tried to set indentation lower than zero"); 84 | } 85 | 86 | public void WriteFull(string s) 87 | { 88 | #if DEBUG 89 | streamWriter.Write(Stack.Any() ? $"/* {Stack.Count,2} */" : "/* */"); 90 | #endif 91 | 92 | // NOTE: can split these up if needed for performance reasons (eg. to prevent string interp/concat on caller side) 93 | for (int i = 0; i < Indentation + 1; i++) { 94 | streamWriter.Write('\t'); 95 | } 96 | 97 | streamWriter.Write(s); 98 | streamWriter.WriteLine(); 99 | 100 | #if DEBUG_FLUSH 101 | streamWriter.Flush(); 102 | #endif 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Instructions/VariableInstruction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using WasmLib.FileFormat; 6 | using WasmLib.FileFormat.Instructions; 7 | using WasmLib.Utils; 8 | 9 | namespace WasmLib.Decompilation.Intermediate.Instructions 10 | { 11 | public class VariableInstruction : IntermediateInstruction 12 | { 13 | private readonly WasmModule module; 14 | private readonly FunctionBody body; 15 | private readonly FunctionSignature signature; 16 | 17 | public TargetKind Target { get; } 18 | public ActionKind Action { get; } 19 | public uint Index { get; } 20 | public ValueKind Type => Target == TargetKind.Local ? GetLocal(Index) : GetGlobal(Index); 21 | 22 | public override ValueKind[] PopTypes => Action == ActionKind.Get ? new ValueKind[0] : new[] {Type}; 23 | public override ValueKind[] PushTypes => Action == ActionKind.Set ? new ValueKind[0] : new[] {Type}; 24 | 25 | private IEnumerable Locals => signature.Parameters.Concat(body.Locals); 26 | private IEnumerable Globals => module.ImportedGlobals.Concat(module.Globals.Select(x => x.GlobalType)).Select(x => x.ValueKind); 27 | 28 | public override StateKind ModifiesState => Action == ActionKind.Get ? StateKind.None : Target == TargetKind.Global ? StateKind.Globals : StateKind.Locals; 29 | public override StateKind ReadsState => Action == ActionKind.Set ? StateKind.None : Target == TargetKind.Global ? StateKind.Globals : StateKind.Locals; 30 | public override bool CanBeInlined => Action != ActionKind.Tee; // would cause assignments in comparisons 31 | 32 | public VariableInstruction(in Instruction instruction, WasmModule module, FunctionBody body, FunctionSignature signature) 33 | { 34 | this.module = module; 35 | this.body = body; 36 | this.signature = signature; 37 | (Target, Action) = instruction.OpCode switch { 38 | OpCode.LocalGet => (TargetKind.Local, ActionKind.Get), 39 | OpCode.LocalSet => (TargetKind.Local, ActionKind.Set), 40 | OpCode.LocalTee => (TargetKind.Local, ActionKind.Tee), 41 | OpCode.GlobalGet => (TargetKind.Global, ActionKind.Get), 42 | OpCode.GlobalSet => (TargetKind.Global, ActionKind.Set), 43 | _ => throw new WrongInstructionPassedException(instruction, nameof(VariableInstruction)), 44 | }; 45 | Index = instruction.UIntOperand; 46 | } 47 | 48 | public override string OperationStringFormat { 49 | get { 50 | Debug.Assert((Target == TargetKind.Local ? Locals.Count() : Globals.Count()) > Index); 51 | 52 | // NOTE: perhaps this naming stuff can be moved to a central location 53 | string varName = Target == TargetKind.Local ? $"local[{Index - signature.Parameters.Length}]" : $"global[{Index}]"; 54 | 55 | if (Target == TargetKind.Local && Index < signature.Parameters.Length) { 56 | varName = $"param_{Index}"; 57 | } 58 | 59 | return Action switch { 60 | ActionKind.Get => varName, 61 | ActionKind.Set => varName + " = {0}", 62 | ActionKind.Tee => varName + " = {0}", 63 | _ => throw new ArgumentOutOfRangeException() 64 | }; 65 | } 66 | } 67 | 68 | private ValueKind GetLocal(uint idx) => Locals.Skip((int)idx).First(); 69 | private ValueKind GetGlobal(uint idx) => Globals.Skip((int)idx).First(); 70 | 71 | public override string ToString() => $"{Action} {Target}[{Index}]"; 72 | 73 | public enum TargetKind 74 | { 75 | Local, 76 | Global, 77 | } 78 | 79 | public enum ActionKind 80 | { 81 | Get, 82 | Set, 83 | Tee, 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/IntermediateRepresentationDecompiler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using WasmLib.Decompilation.Intermediate; 6 | using WasmLib.FileFormat; 7 | using WasmLib.Utils; 8 | 9 | namespace WasmLib.Decompilation 10 | { 11 | public class IntermediateRepresentationDecompiler : IDecompiler 12 | { 13 | public WasmModule WasmModule { get; } 14 | 15 | public IntermediateRepresentationDecompiler(WasmModule wasmModule) 16 | { 17 | WasmModule = wasmModule; 18 | } 19 | 20 | public void DecompileFunction(StreamWriter output, int functionIndex) 21 | { 22 | FunctionBody body = WasmModule.FunctionBodies[functionIndex]; 23 | FunctionSignature signature = WasmModule.FunctionTypes[WasmModule.Functions[functionIndex]]; 24 | 25 | // get IR 26 | var context = new IntermediateContext(signature, WasmModule, output); 27 | List instructions = new IntermediateConverter(WasmModule, body, signature).Convert(); 28 | 29 | output.Write(signature.ToString($"fun_{functionIndex:X8}")); 30 | output.WriteLine(" {"); 31 | 32 | // write all IR while simulating the stack 33 | foreach (IntermediateInstruction instruction in instructions) { 34 | HandleInstruction(ref context, instruction); 35 | } 36 | 37 | output.WriteLine("}"); 38 | 39 | if (context.Indentation != 0) { 40 | throw new Exception("Function body has unbalanced indentation"); 41 | } 42 | 43 | if (context.Stack.Count != 0) { 44 | throw new Exception($"Unbalanced stack, found {context.Stack.Count} remaining values"); 45 | } 46 | 47 | output.WriteLine(); 48 | } 49 | 50 | private static void HandleInstruction(ref IntermediateContext context, IntermediateInstruction instruction) 51 | { 52 | if (context.RestOfBlockUnreachable && instruction.IsImplicit) { 53 | #if DEBUG 54 | context.WriteFull("// omitted implicit instruction because rest of block is unreachable"); 55 | #endif 56 | return; 57 | } 58 | 59 | var args = new Variable[instruction.PopCount]; 60 | 61 | for (int i = 0; i < instruction.PopCount; i++) { 62 | args[i] = context.Pop(); 63 | Debug.Assert(instruction.PopTypes[i] == args[i].Type || instruction.PopTypes[i] == ValueKind.Any || args[i].Type == ValueKind.Any); 64 | } 65 | 66 | context.RestOfBlockUnreachable = instruction.RestOfBlockUnreachable; 67 | 68 | // NOTE: really ugly and slow, but can't be replaced with string.format since input is dynamic and can contain {} 69 | string s = instruction.OperationStringFormat.SafeFormat(args); 70 | 71 | Debug.Assert(instruction.PushCount <= 1); 72 | if (instruction.PushCount > 0) { 73 | s = $"{context.Push(instruction.PushTypes[0])} = {s}"; 74 | } 75 | 76 | if (instruction.HasBlock) { 77 | s += " {"; 78 | } 79 | 80 | if (instruction.Comment != null) { 81 | s += " // " + instruction.Comment; 82 | } 83 | 84 | context.WriteFull(s); 85 | 86 | if (instruction.HasBlock) { 87 | HandleBlock(ref context, instruction.Block1!); 88 | 89 | if (instruction.Block2 != null) { 90 | context.WriteFull("} else {"); 91 | HandleBlock(ref context, instruction.Block2); 92 | } 93 | 94 | context.WriteFull("}"); 95 | } 96 | 97 | static void HandleBlock(ref IntermediateContext context2, in ControlBlock block) 98 | { 99 | context2.EnterBlock(); 100 | 101 | foreach (IntermediateInstruction instr in block.Instructions) { 102 | HandleInstruction(ref context2, instr); 103 | } 104 | 105 | Debug.Assert(!(block.HasReturn && !context2.RestOfBlockUnreachable)); 106 | 107 | context2.ExitBlock(); 108 | } 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Instructions/ConversionOperatorInstruction.cs: -------------------------------------------------------------------------------- 1 | using WasmLib.FileFormat; 2 | using WasmLib.FileFormat.Instructions; 3 | using WasmLib.Utils; 4 | 5 | namespace WasmLib.Decompilation.Intermediate.Instructions 6 | { 7 | public class ConversionOperatorInstruction : IntermediateInstruction 8 | { 9 | public OperationKind Operation { get; } 10 | /// t2 in t2.cvtop_t1_sx 11 | public ValueKind TargetType { get; } 12 | /// t1 in t2.cvtop_t1_sx 13 | public ValueKind SourceType { get; } 14 | public bool? IsSigned { get; } 15 | 16 | public override ValueKind[] PopTypes => new[] {SourceType}; 17 | public override ValueKind[] PushTypes => new[] {TargetType}; 18 | 19 | public ConversionOperatorInstruction(in Instruction instruction) 20 | { 21 | (Operation, TargetType, SourceType, IsSigned) = instruction.OpCode switch { 22 | OpCode.I32WrapI64 => (OperationKind.Wrap, ValueKind.I32, ValueKind.I64, (bool?)null), 23 | OpCode.I32TruncF32S => (OperationKind.Truncate, ValueKind.I32, ValueKind.F32, true), 24 | OpCode.I32TruncF32U => (OperationKind.Truncate, ValueKind.I32, ValueKind.F32, false), 25 | OpCode.I32TruncF64S => (OperationKind.Truncate, ValueKind.I32, ValueKind.F64, true), 26 | OpCode.I32TruncF64U => (OperationKind.Truncate, ValueKind.I32, ValueKind.F64, false), 27 | OpCode.I64ExtendI32S => (OperationKind.Extend, ValueKind.I64, ValueKind.I32, true), 28 | OpCode.I64ExtendI32U => (OperationKind.Extend, ValueKind.I64, ValueKind.I32, false), 29 | OpCode.I64TruncF32S => (OperationKind.Truncate, ValueKind.I64, ValueKind.F32, true), 30 | OpCode.I64TruncF32U => (OperationKind.Truncate, ValueKind.I64, ValueKind.F32, false), 31 | OpCode.I64TruncF64S => (OperationKind.Truncate, ValueKind.I64, ValueKind.F64, true), 32 | OpCode.I64TruncF64U => (OperationKind.Truncate, ValueKind.I64, ValueKind.F64, false), 33 | OpCode.F32ConvertI32S => (OperationKind.Convert, ValueKind.F32, ValueKind.I32, true), 34 | OpCode.F32ConvertI32U => (OperationKind.Convert, ValueKind.F32, ValueKind.I32, false), 35 | OpCode.F32ConvertI64S => (OperationKind.Convert, ValueKind.F32, ValueKind.I64, true), 36 | OpCode.F32ConvertI64U => (OperationKind.Convert, ValueKind.F32, ValueKind.I64, false), 37 | OpCode.F32DemoteF64 => (OperationKind.Demote, ValueKind.F32, ValueKind.F64, (bool?)null), 38 | OpCode.F64ConvertI32S => (OperationKind.Convert, ValueKind.F64, ValueKind.I32, true), 39 | OpCode.F64ConvertI32U => (OperationKind.Convert, ValueKind.F64, ValueKind.I32, false), 40 | OpCode.F64ConvertI64S => (OperationKind.Convert, ValueKind.F64, ValueKind.I64, true), 41 | OpCode.F64ConvertI64U => (OperationKind.Convert, ValueKind.F64, ValueKind.I64, false), 42 | OpCode.F64PromoteF32 => (OperationKind.Promote, ValueKind.F64, ValueKind.F32, (bool?)null), 43 | OpCode.I32ReinterpretF32 => (OperationKind.Reinterpret, ValueKind.I32, ValueKind.F32, (bool?)null), 44 | OpCode.I64ReinterpretF64 => (OperationKind.Reinterpret, ValueKind.I64, ValueKind.F64, (bool?)null), 45 | OpCode.F32ReinterpretI32 => (OperationKind.Reinterpret, ValueKind.F32, ValueKind.I32, (bool?)null), 46 | OpCode.F64ReinterpretI64 => (OperationKind.Reinterpret, ValueKind.F64, ValueKind.I64, (bool?)null), 47 | _ => throw new WrongInstructionPassedException(instruction, nameof(ConversionOperatorInstruction)), 48 | }; 49 | } 50 | 51 | public override string OperationStringFormat { 52 | get { 53 | string targetString = EnumUtils.GetDescription(TargetType); 54 | 55 | return Operation == OperationKind.Reinterpret 56 | ? $"*({targetString}*)&{{0}}" 57 | : $"({targetString}){{0}}"; 58 | } 59 | } 60 | 61 | public override string? Comment => IsSigned.HasValue ? $"signed: {IsSigned}" : null; 62 | 63 | public override string ToString() => $"{Operation} to {TargetType}"; 64 | 65 | public enum OperationKind 66 | { 67 | Wrap, 68 | Extend, 69 | Truncate, 70 | Convert, 71 | Demote, 72 | Promote, 73 | Reinterpret, 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Instructions/ComparisonOperationInstruction.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using WasmLib.FileFormat; 3 | using WasmLib.FileFormat.Instructions; 4 | using WasmLib.Utils; 5 | 6 | namespace WasmLib.Decompilation.Intermediate.Instructions 7 | { 8 | public class ComparisonOperationInstruction : IntermediateInstruction 9 | { 10 | public ValueKind Type { get; } 11 | public ComparisonKind Comparison { get; } 12 | public bool? IsSigned { get; } 13 | 14 | public override ValueKind[] PopTypes => new[] {Type, Type}; 15 | public override ValueKind[] PushTypes => new[] {ValueKind.I32}; 16 | 17 | public ComparisonOperationInstruction(in Instruction instruction) 18 | { 19 | (Type, Comparison, IsSigned) = instruction.OpCode switch { 20 | OpCode.I32Eq => (ValueKind.I32, ComparisonKind.Equal, (bool?)null), 21 | OpCode.I32Ne => (ValueKind.I32, ComparisonKind.NotEqual, (bool?)null), 22 | OpCode.I32LtS => (ValueKind.I32, ComparisonKind.LessThan, true), 23 | OpCode.I32LtU => (ValueKind.I32, ComparisonKind.LessThan, false), 24 | OpCode.I32GtS => (ValueKind.I32, ComparisonKind.GreaterThan, true), 25 | OpCode.I32GtU => (ValueKind.I32, ComparisonKind.GreaterThan, false), 26 | OpCode.I32LeS => (ValueKind.I32, ComparisonKind.LessThanOrEqual, true), 27 | OpCode.I32LeU => (ValueKind.I32, ComparisonKind.LessThanOrEqual, false), 28 | OpCode.I32GeS => (ValueKind.I32, ComparisonKind.GreaterThanOrEqual, true), 29 | OpCode.I32GeU => (ValueKind.I32, ComparisonKind.GreaterThanOrEqual, false), 30 | 31 | OpCode.I64Eq => (ValueKind.I64, ComparisonKind.Equal, (bool?)null), 32 | OpCode.I64Ne => (ValueKind.I64, ComparisonKind.NotEqual, (bool?)null), 33 | OpCode.I64LtS => (ValueKind.I64, ComparisonKind.LessThan, true), 34 | OpCode.I64LtU => (ValueKind.I64, ComparisonKind.LessThan, false), 35 | OpCode.I64GtS => (ValueKind.I64, ComparisonKind.GreaterThan, true), 36 | OpCode.I64GtU => (ValueKind.I64, ComparisonKind.GreaterThan, false), 37 | OpCode.I64LeS => (ValueKind.I64, ComparisonKind.LessThanOrEqual, true), 38 | OpCode.I64LeU => (ValueKind.I64, ComparisonKind.LessThanOrEqual, false), 39 | OpCode.I64GeS => (ValueKind.I64, ComparisonKind.GreaterThanOrEqual, true), 40 | OpCode.I64GeU => (ValueKind.I64, ComparisonKind.GreaterThanOrEqual, false), 41 | 42 | OpCode.F32Eq => (ValueKind.F32, ComparisonKind.Equal, (bool?)null), 43 | OpCode.F32Ne => (ValueKind.F32, ComparisonKind.NotEqual, (bool?)null), 44 | OpCode.F32Lt => (ValueKind.F32, ComparisonKind.LessThan, (bool?)null), 45 | OpCode.F32Gt => (ValueKind.F32, ComparisonKind.GreaterThan, (bool?)null), 46 | OpCode.F32Le => (ValueKind.F32, ComparisonKind.LessThanOrEqual, (bool?)null), 47 | OpCode.F32Ge => (ValueKind.F32, ComparisonKind.GreaterThanOrEqual, (bool?)null), 48 | 49 | OpCode.F64Eq => (ValueKind.F64, ComparisonKind.Equal, (bool?)null), 50 | OpCode.F64Ne => (ValueKind.F64, ComparisonKind.NotEqual, (bool?)null), 51 | OpCode.F64Lt => (ValueKind.F64, ComparisonKind.LessThan, (bool?)null), 52 | OpCode.F64Gt => (ValueKind.F64, ComparisonKind.GreaterThan, (bool?)null), 53 | OpCode.F64Le => (ValueKind.F64, ComparisonKind.LessThanOrEqual, (bool?)null), 54 | OpCode.F64Ge => (ValueKind.F64, ComparisonKind.GreaterThanOrEqual, (bool?)null), 55 | 56 | _ => throw new WrongInstructionPassedException(instruction, nameof(ComparisonOperationInstruction)), 57 | }; 58 | } 59 | 60 | public override string OperationStringFormat => $@"{{1}} {EnumUtils.GetDescription(Comparison)} {{0}}"; 61 | 62 | public override string? Comment => IsSigned switch { 63 | true => "signed comparison", 64 | false => "unsigned comparison", 65 | null => null, 66 | }; 67 | 68 | public override string ToString() => Comparison.ToString(); 69 | 70 | public enum ComparisonKind 71 | { 72 | [Description("==")] Equal, 73 | [Description("!=")] NotEqual, 74 | [Description("<")] LessThan, 75 | [Description(">")] GreaterThan, 76 | [Description("<=")] LessThanOrEqual, 77 | [Description(">=")] GreaterThanOrEqual, 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /WasmLib/WasmModule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using WasmLib.FileFormat; 7 | using WasmLib.Utils; 8 | 9 | namespace WasmLib 10 | { 11 | public class WasmModule 12 | { 13 | public int Version { get; private set; } 14 | 15 | public FunctionSignature[] FunctionTypes { get; private set; } = new FunctionSignature[0]; 16 | public Import[] Imports { get; private set; } = new Import[0]; 17 | public uint[] Functions { get; private set; } = new uint[0]; 18 | public Global[] Globals { get; private set; } = new Global[0]; 19 | public Export[] Exports { get; private set; } = new Export[0]; 20 | public Element[] Elements { get; private set; } = new Element[0]; 21 | public FunctionBody[] FunctionBodies { get; private set; } = new FunctionBody[0]; 22 | public DataSegment[] DataSegments { get; private set; } = new DataSegment[0]; 23 | 24 | public int ImportedFunctionCount { get; private set; } 25 | public GlobalType[] ImportedGlobals { get; private set; } = new GlobalType[0]; 26 | 27 | private WasmModule() { } 28 | 29 | public static WasmModule Read(string path) 30 | { 31 | using var fs = File.OpenRead(path); 32 | return Read(fs); 33 | } 34 | 35 | public static WasmModule Read(Stream stream) 36 | { 37 | var file = new WasmModule(); 38 | 39 | using (var br = new BinaryReader(stream, Encoding.UTF8, true)) { 40 | var magic = br.ReadBytes(4); 41 | if (!magic.SequenceEqual(Encoding.ASCII.GetBytes("\0asm"))) { 42 | throw new Exception("Invalid magic, expected '\\0asm'"); 43 | } 44 | 45 | file.Version = br.ReadInt32(); 46 | Debug.Assert(file.Version == 1); 47 | 48 | while (stream.Position < stream.Length) { 49 | var type = (SectionType)br.ReadVarUint7(); 50 | uint size = br.ReadVarUint32(); 51 | long oldPos = stream.Position; 52 | Debug.Assert(size <= stream.Length - stream.Position); 53 | 54 | switch (type) { 55 | // TODO: custom 56 | case SectionType.Type: 57 | file.FunctionTypes = br.ReadWasmArray(); 58 | break; 59 | case SectionType.Import: 60 | file.Imports = br.ReadWasmArray(); 61 | break; 62 | case SectionType.Function: 63 | file.Functions = br.ReadVarUint32Array(); 64 | break; 65 | // TODO: table 66 | // TODO: memory 67 | case SectionType.Global: 68 | file.Globals = br.ReadWasmArray(); 69 | break; 70 | case SectionType.Export: 71 | file.Exports = br.ReadWasmArray(); 72 | break; 73 | // TODO: start 74 | case SectionType.Element: 75 | file.Elements = br.ReadWasmArray(); 76 | break; 77 | case SectionType.Code: 78 | file.FunctionBodies = br.ReadWasmArray(); 79 | break; 80 | case SectionType.Data: 81 | file.DataSegments = br.ReadWasmArray(); 82 | break; 83 | default: 84 | throw new NotImplementedException(type.ToString()); 85 | } 86 | 87 | Debug.Assert(oldPos + size == stream.Position, $"Section size was {size}, but read {stream.Position - oldPos} bytes"); 88 | } 89 | } 90 | 91 | file.ImportedFunctionCount = file.Imports.Count(x => x.Kind == ImportKind.TypeIndex); 92 | file.ImportedGlobals = file.Imports 93 | .Where(x => x.Kind == ImportKind.GlobalType) 94 | .Select(x => 95 | x.GlobalType ?? 96 | throw new Exception( 97 | $"{nameof(Import.GlobalType)} had no value, but {nameof(Import.Kind)} was {nameof(ImportKind.GlobalType)}")) 98 | .ToArray(); 99 | 100 | return file; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Instructions/MemoryInstruction.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using WasmLib.FileFormat; 3 | using WasmLib.FileFormat.Instructions; 4 | using WasmLib.Utils; 5 | 6 | namespace WasmLib.Decompilation.Intermediate.Instructions 7 | { 8 | public class MemoryInstruction : IntermediateInstruction 9 | { 10 | public ActionKind Action { get; } 11 | public ValueKind Type { get; } 12 | public CastingKind Casting { get; } 13 | 14 | public uint Offset { get; } 15 | public uint Alignment { get; } 16 | 17 | public override ValueKind[] PopTypes => Action == ActionKind.Store ? new[] {Type, ValueKind.I32} : new[] {ValueKind.I32}; 18 | public override ValueKind[] PushTypes => Action == ActionKind.Load ? new[] {Type} : new ValueKind[0]; 19 | 20 | public override StateKind ModifiesState => Action == ActionKind.Store ? StateKind.Memory : StateKind.None; 21 | public override StateKind ReadsState => Action == ActionKind.Load ? StateKind.Memory : StateKind.None; 22 | 23 | public MemoryInstruction(in Instruction instruction) 24 | { 25 | (Action, Type, Casting) = instruction.OpCode switch { 26 | OpCode.I32Load => (ActionKind.Load, ValueKind.I32, CastingKind.Same), 27 | OpCode.I64Load => (ActionKind.Load, ValueKind.I64, CastingKind.Same), 28 | OpCode.F32Load => (ActionKind.Load, ValueKind.F32, CastingKind.Same), 29 | OpCode.F64Load => (ActionKind.Load, ValueKind.F64, CastingKind.Same), 30 | OpCode.I32Load8S => (ActionKind.Load, ValueKind.I32, CastingKind.ByteSigned), 31 | OpCode.I32Load8U => (ActionKind.Load, ValueKind.I32, CastingKind.ByteUnsigned), 32 | OpCode.I32Load16S => (ActionKind.Load, ValueKind.I32, CastingKind.ShortSigned), 33 | OpCode.I32Load16U => (ActionKind.Load, ValueKind.I32, CastingKind.ShortUnsigned), 34 | OpCode.I64Load8S => (ActionKind.Load, ValueKind.I64, CastingKind.ByteSigned), 35 | OpCode.I64Load8U => (ActionKind.Load, ValueKind.I64, CastingKind.ByteUnsigned), 36 | OpCode.I64Load16S => (ActionKind.Load, ValueKind.I64, CastingKind.ShortSigned), 37 | OpCode.I64Load16U => (ActionKind.Load, ValueKind.I64, CastingKind.ShortUnsigned), 38 | OpCode.I64Load32S => (ActionKind.Load, ValueKind.I64, CastingKind.IntSigned), 39 | OpCode.I64Load32U => (ActionKind.Load, ValueKind.I64, CastingKind.IntUnsigned), 40 | OpCode.I32Store => (ActionKind.Store, ValueKind.I32, CastingKind.Same), 41 | OpCode.I64Store => (ActionKind.Store, ValueKind.I64, CastingKind.Same), 42 | OpCode.F32Store => (ActionKind.Store, ValueKind.F32, CastingKind.Same), 43 | OpCode.F64Store => (ActionKind.Store, ValueKind.F64, CastingKind.Same), 44 | OpCode.I32Store8 => (ActionKind.Store, ValueKind.I32, CastingKind.Byte), 45 | OpCode.I32Store16 => (ActionKind.Store, ValueKind.I32, CastingKind.Short), 46 | OpCode.I64Store8 => (ActionKind.Store, ValueKind.I64, CastingKind.Byte), 47 | OpCode.I64Store16 => (ActionKind.Store, ValueKind.I64, CastingKind.Short), 48 | OpCode.I64Store32 => (ActionKind.Store, ValueKind.I64, CastingKind.Int), 49 | _ => throw new WrongInstructionPassedException(instruction, nameof(MemoryInstruction)) 50 | }; 51 | 52 | // TODO: this logic/responsibility should prob be moved inside Instruction 53 | var operand = instruction.ULongOperand; 54 | Offset = (uint)(operand & 0xFFFFFFFF); 55 | Alignment = (uint)((operand & 0xFFFFFFFF00000000) >> 32); 56 | } 57 | 58 | // TODO: handle casting 59 | public override string OperationStringFormat { 60 | get { 61 | string dereference = $"*({EnumUtils.GetDescription(Type)}*)({{{(Action == ActionKind.Load ? 0 : 1)}}} + 0x{Offset:X})"; // NOTE: could be optimized 62 | 63 | return Action == ActionKind.Store 64 | ? dereference + " = {0}" 65 | : dereference; 66 | } 67 | } 68 | 69 | public override string? Comment => Action switch { 70 | ActionKind.Store => $"Alignment: 0x{1 << (int)Alignment:X}", 71 | ActionKind.Load when Casting != CastingKind.Same => $"DECOMPILER WARNING: casting of type {Casting}", 72 | _ => null 73 | }; 74 | 75 | public override string ToString() => Action.ToString(); 76 | 77 | public enum ActionKind 78 | { 79 | [Description("load")] Load, 80 | [Description("store")] Store, 81 | } 82 | 83 | public enum CastingKind 84 | { 85 | Same, 86 | Byte, 87 | ByteSigned, 88 | ByteUnsigned, 89 | Short, 90 | ShortSigned, 91 | ShortUnsigned, 92 | Int, 93 | IntSigned, 94 | IntUnsigned, 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/Instructions/BinaryOperationInstruction.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using WasmLib.FileFormat; 3 | using WasmLib.FileFormat.Instructions; 4 | using WasmLib.Utils; 5 | 6 | namespace WasmLib.Decompilation.Intermediate.Instructions 7 | { 8 | public class BinaryOperationInstruction : IntermediateInstruction 9 | { 10 | public ValueKind Type { get; } 11 | public OperationKind Operation { get; } 12 | public bool? IsSigned { get; } 13 | 14 | public override ValueKind[] PopTypes => new[] {Type, Type}; 15 | public override ValueKind[] PushTypes => new[] {Type}; 16 | 17 | public BinaryOperationInstruction(in Instruction instruction) 18 | { 19 | (Type, Operation, IsSigned) = instruction.OpCode switch { 20 | // both 21 | OpCode.I32Add => (ValueKind.I32, OperationKind.Add, (bool?)null), 22 | OpCode.I64Add => (ValueKind.I64, OperationKind.Add, (bool?)null), 23 | OpCode.F32Add => (ValueKind.F32, OperationKind.Add, (bool?)null), 24 | OpCode.F64Add => (ValueKind.F64, OperationKind.Add, (bool?)null), 25 | OpCode.I32Sub => (ValueKind.I32, OperationKind.Sub, (bool?)null), 26 | OpCode.I64Sub => (ValueKind.I64, OperationKind.Sub, (bool?)null), 27 | OpCode.F32Sub => (ValueKind.F32, OperationKind.Sub, (bool?)null), 28 | OpCode.F64Sub => (ValueKind.F64, OperationKind.Sub, (bool?)null), 29 | OpCode.I32Mul => (ValueKind.I32, OperationKind.Mul, (bool?)null), 30 | OpCode.I64Mul => (ValueKind.I64, OperationKind.Mul, (bool?)null), 31 | OpCode.F32Mul => (ValueKind.F32, OperationKind.Mul, (bool?)null), 32 | OpCode.F64Mul => (ValueKind.F64, OperationKind.Mul, (bool?)null), 33 | // int 34 | OpCode.I32DivS => (ValueKind.I32, OperationKind.Div, true), 35 | OpCode.I32DivU => (ValueKind.I32, OperationKind.Div, false), 36 | OpCode.I32RemS => (ValueKind.I32, OperationKind.Rem, true), 37 | OpCode.I32RemU => (ValueKind.I32, OperationKind.Rem, false), 38 | OpCode.I64DivS => (ValueKind.I64, OperationKind.Div, true), 39 | OpCode.I64DivU => (ValueKind.I64, OperationKind.Div, false), 40 | OpCode.I64RemS => (ValueKind.I64, OperationKind.Rem, true), 41 | OpCode.I64RemU => (ValueKind.I64, OperationKind.Rem, false), 42 | OpCode.I32And => (ValueKind.I32, OperationKind.And, (bool?)null), 43 | OpCode.I64And => (ValueKind.I64, OperationKind.And, (bool?)null), 44 | OpCode.I32Or => (ValueKind.I32, OperationKind.Or, (bool?)null), 45 | OpCode.I64Or => (ValueKind.I64, OperationKind.Or, (bool?)null), 46 | OpCode.I32Xor => (ValueKind.I32, OperationKind.Xor, (bool?)null), 47 | OpCode.I64Xor => (ValueKind.I64, OperationKind.Xor, (bool?)null), 48 | OpCode.I32Shl => (ValueKind.I32, OperationKind.Shl, (bool?)null), 49 | OpCode.I64Shl => (ValueKind.I64, OperationKind.Shl, (bool?)null), 50 | OpCode.I32ShrS => (ValueKind.I32, OperationKind.ShrS, (bool?)null), 51 | OpCode.I64ShrS => (ValueKind.I64, OperationKind.ShrS, (bool?)null), 52 | OpCode.I32ShrU => (ValueKind.I32, OperationKind.ShrU, (bool?)null), 53 | OpCode.I64ShrU => (ValueKind.I64, OperationKind.ShrU, (bool?)null), 54 | OpCode.I32Rotl => (ValueKind.I32, OperationKind.RotL, (bool?)null), 55 | OpCode.I64Rotl => (ValueKind.I64, OperationKind.RotL, (bool?)null), 56 | OpCode.I32Rotr => (ValueKind.I32, OperationKind.RotR, (bool?)null), 57 | OpCode.I64Rotr => (ValueKind.I64, OperationKind.RotR, (bool?)null), 58 | // float 59 | OpCode.F32Div => (ValueKind.F32, OperationKind.Div, (bool?)null), 60 | OpCode.F32Min => (ValueKind.F32, OperationKind.Min, (bool?)null), 61 | OpCode.F32Max => (ValueKind.F32, OperationKind.Max, (bool?)null), 62 | OpCode.F32Copysign => (ValueKind.F32, OperationKind.CopySign, (bool?)null), 63 | OpCode.F64Div => (ValueKind.F64, OperationKind.Div, (bool?)null), 64 | OpCode.F64Min => (ValueKind.F64, OperationKind.Min, (bool?)null), 65 | OpCode.F64Max => (ValueKind.F64, OperationKind.Max, (bool?)null), 66 | OpCode.F64Copysign => (ValueKind.F64, OperationKind.CopySign, (bool?)null), 67 | _ => throw new WrongInstructionPassedException(instruction, nameof(VariableInstruction)), 68 | }; 69 | } 70 | 71 | public override string OperationStringFormat => Operation switch { 72 | OperationKind.Min => "min({1}, {0})", 73 | OperationKind.Max => "max({1}, {0})", 74 | OperationKind.CopySign => "copysign({1}, {0})", 75 | _ => $"{{1}} {EnumUtils.GetDescription(Operation)} {{0}}" 76 | }; 77 | 78 | public override string? Comment => IsSigned.HasValue ? $"{(IsSigned.Value ? "signed" : "unsigned")} operation" : null; 79 | 80 | public override string ToString() => Operation.ToString(); 81 | 82 | public enum OperationKind 83 | { 84 | // both 85 | [Description("+")] Add, 86 | [Description("*")] Sub, 87 | [Description("*")] Mul, 88 | [Description("/")] Div, 89 | // int 90 | [Description("%")] Rem, 91 | [Description("&")] And, 92 | [Description("|")] Or, 93 | [Description("^")] Xor, 94 | [Description("<<")] Shl, 95 | [Description(">>")] ShrS, 96 | [Description(">>>")] ShrU, 97 | [Description("ROTL")] RotL, 98 | [Description("ROTR")] RotR, 99 | // float 100 | Min, 101 | Max, 102 | CopySign, 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /WasmLib/Decompilation/GenericDecompiler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using Rivers; 6 | using Rivers.Analysis; 7 | using Rivers.Serialization.Dot; 8 | using WasmLib.Decompilation.Intermediate; 9 | using WasmLib.Decompilation.Intermediate.Graph; 10 | using WasmLib.Decompilation.Intermediate.Instructions; 11 | using WasmLib.Decompilation.SourceCode; 12 | using WasmLib.FileFormat; 13 | 14 | namespace WasmLib.Decompilation 15 | { 16 | public class GenericDecompiler : IDecompiler 17 | { 18 | public WasmModule WasmModule { get; } 19 | 20 | public GenericDecompiler(WasmModule wasmModule) 21 | { 22 | WasmModule = wasmModule; 23 | } 24 | 25 | public void DecompileFunction(StreamWriter output, int functionIndex) 26 | { 27 | FunctionBody body = WasmModule.FunctionBodies[functionIndex]; 28 | FunctionSignature signature = WasmModule.FunctionTypes[WasmModule.Functions[functionIndex]]; 29 | 30 | List instructions = new IntermediateConverter(WasmModule, body, signature).Convert(); 31 | 32 | Graph graph = CreateGraph(instructions); 33 | 34 | output.Write(signature.ToString($"fun_{functionIndex:X8}")); 35 | output.WriteLine(" {"); 36 | OutputAsCode(graph, output); 37 | output.WriteLine("}"); 38 | output.WriteLine(); 39 | 40 | // using var sw = new StreamWriter(File.Open("graph.dot", FileMode.Create)); 41 | // new DotWriter(sw).Write(graph); 42 | } 43 | 44 | private static Graph CreateGraph(IEnumerable instructions) 45 | { 46 | var graph = new Graph(); 47 | var stack = new Stack<(InstructionNode, ValueKind)>(); 48 | 49 | int instructionNum = 0; 50 | foreach (IntermediateInstruction instruction in instructions) { 51 | InstructionNode node = !instruction.HasBlock 52 | ? new InstructionNode(instruction, instructionNum++) 53 | : instruction.Block2 == null 54 | ? new InstructionNode(instruction, instructionNum++, CreateGraph(instruction.Block1!.Instructions)) 55 | : new InstructionNode(instruction, instructionNum++, CreateGraph(instruction.Block1!.Instructions), CreateGraph(instruction.Block2.Instructions)); 56 | 57 | graph.Nodes.Add(node); 58 | 59 | // TODO: investigate, how do I properly solve this 60 | if (instruction is ImplicitReturnInstruction && stack.Count < 1) { 61 | // ugh 62 | continue; 63 | } 64 | 65 | Debug.Assert(instruction.PushCount <= 1, "Instruction pushed multiple variables to stack, which shouldn't happen."); 66 | for (int i = 0; i < instruction.PopCount; i++) { 67 | (InstructionNode sourceInstruction, ValueKind type) = stack.Pop(); 68 | Debug.Assert(type == instruction.PopTypes[i] || type == ValueKind.Any || instruction.PopTypes[i] == ValueKind.Any); 69 | sourceInstruction.OutgoingEdges.Add(new StackVariableEdge(sourceInstruction, node, type)); 70 | } 71 | 72 | for (int i = 0; i < instruction.PushCount; i++) { 73 | stack.Push((node, instruction.PushTypes[i])); 74 | } 75 | 76 | // add order dependencies 77 | // TODO: add kind of impurity to node 78 | // NOTE: edges not required anymore for decompiled code output, only for checking if graph is connected 79 | // for control flow instructions, add reference to next and previous 80 | if (node.Instruction.ModifiesControlFlow) { 81 | InstructionNode? dependentInstruction = graph.Nodes.Cast().Reverse().Skip(1).FirstOrDefault(); 82 | dependentInstruction?.OutgoingEdges.Add(new ImpurityDependencyEdge(dependentInstruction, node)); 83 | } 84 | // add reference to previous if it was control flow 85 | var prev = graph.Nodes.OfType().Reverse().Skip(1).FirstOrDefault(); 86 | if (prev != null && prev.Instruction.ModifiesControlFlow) { 87 | prev.OutgoingEdges.Add(new ImpurityDependencyEdge(prev, node)); 88 | } 89 | if ((node.Instruction.ReadsState | node.Instruction.ModifiesState) != StateKind.None) { 90 | // attempt for every kind of state 91 | for (int i = 1; i < byte.MaxValue; i <<= 1) { 92 | InstructionNode? dependentInstruction = graph.Nodes 93 | .Cast() 94 | .Reverse().Skip(1) 95 | .FirstOrDefault(x => x.Instruction.HasConflictingState(node.Instruction, (StateKind)i)); 96 | dependentInstruction?.OutgoingEdges.Add(new ImpurityDependencyEdge(dependentInstruction, node)); 97 | } 98 | } 99 | } 100 | 101 | // BUG: see Washi1337/Rivers#6 102 | // Debug.Assert(!graph.IsCyclic(), "Got cyclic dependency in function!"); 103 | if (!graph.IsConnected()) { 104 | #if DEBUG 105 | using (StreamWriter sw = new StreamWriter(File.Open("temp_unconnected.dot", FileMode.Create))) new DotWriter(sw).Write(graph); 106 | #endif 107 | 108 | // TODO: remove subtrees with no side effects? 109 | Debugger.Break(); 110 | } 111 | 112 | return graph; 113 | } 114 | 115 | private static void OutputAsCode(Graph graph, TextWriter output, int tabCount = 1, Dictionary? varCounts = null) 116 | { 117 | varCounts ??= new Dictionary { 118 | {ValueKind.I32, 0}, 119 | {ValueKind.I64, 0}, 120 | {ValueKind.F32, 0}, 121 | {ValueKind.F64, 0}, 122 | }; 123 | 124 | var statements = new SortedDictionary(); 125 | foreach (var currentNode in graph.Nodes.OfType()) { 126 | var parameterEdges = currentNode.IncomingVariableEdges.ToArray(); 127 | 128 | // only handle if node consumes variables 129 | if (parameterEdges.Any()) { 130 | // for each dependency, check if it can be reached in a pure way 131 | // if so, inline it 132 | // if not, create intermediary statements 133 | var parameters = new IExpression[parameterEdges.Length]; 134 | for (int i = 0; i < parameterEdges.Length; i++) { 135 | var edge = parameterEdges[i]; 136 | var variableNode = edge.Source; 137 | 138 | // only inline if inlinee and inliner are not separated by instructions that would affect the inlinee 139 | var impureInstructions = graph.Nodes 140 | .OfType() 141 | .Where(n => n.Index > variableNode.Index && n.Index < currentNode.Index) 142 | .Where(n => n.Instruction.ModifiesControlFlow || variableNode.Instruction.HasConflictingState(n.Instruction)); 143 | 144 | if (currentNode.Instruction.CanInline && variableNode.Instruction.CanBeInlined && !impureInstructions.Any()) { 145 | parameters[i] = statements[variableNode.Index]; 146 | statements.Remove(variableNode.Index); 147 | } 148 | else { 149 | var assignment = new AssignmentExpression(statements[variableNode.Index], edge.Type, varCounts[edge.Type]++); 150 | statements[variableNode.Index] = assignment; 151 | parameters[i] = assignment.Reference; 152 | } 153 | } 154 | 155 | statements[currentNode.Index] = new GenericExpression(currentNode.Instruction, parameters, currentNode.Block1, currentNode.Block2); 156 | } 157 | else { 158 | statements[currentNode.Index] = new GenericExpression(currentNode.Instruction, null, currentNode.Block1, currentNode.Block2); 159 | } 160 | } 161 | 162 | foreach (IExpression expression in statements.Values) { 163 | var stringRepresentation = expression.GetStringRepresentation(); 164 | 165 | // don't write empty statements such as blocks 166 | if (string.IsNullOrEmpty(stringRepresentation)) { 167 | continue; 168 | } 169 | 170 | // TODO: support comments 171 | if (expression is IHasBlocks blocks && blocks.Block1 != null) { 172 | output.WriteLine(new string('\t', tabCount) + stringRepresentation + " {"); 173 | 174 | OutputAsCode(blocks.Block1, output, tabCount + 1, varCounts); 175 | 176 | if (blocks.Block2 == null) { 177 | output.WriteLine(new string('\t', tabCount) + "}"); 178 | } 179 | else { 180 | output.WriteLine(new string('\t', tabCount) + "} else {"); 181 | OutputAsCode(blocks.Block2, output, tabCount + 1, varCounts); 182 | output.WriteLine(new string('\t', tabCount) + "}"); 183 | } 184 | } 185 | else { 186 | output.WriteLine(new string('\t', tabCount) + stringRepresentation); 187 | } 188 | } 189 | } 190 | } 191 | } -------------------------------------------------------------------------------- /WasmLib/FileFormat/Instructions/OpCode.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace WasmLib.FileFormat.Instructions 5 | { 6 | /// 7 | /// Documentation at https://webassembly.github.io/spec/core/binary/instructions.html 8 | /// 9 | [SuppressMessage("ReSharper", "InconsistentNaming")] 10 | public enum OpCode 11 | { 12 | // Control Instructions 13 | [Description("unreachable")] Unreachable = 0x00, 14 | [Description("nop")] Nop = 0x01, 15 | [Description("block")] Block = 0x02, 16 | [Description("loop")] Loop = 0x03, 17 | [Description("if")] If = 0x04, 18 | [Description("else")] Else = 0x05, 19 | [Description("end")] End = 0x0B, 20 | [Description("br")] Br = 0x0C, 21 | [Description("br_if")] BrIf = 0x0D, 22 | [Description("br_table")] BrTable = 0x0E, 23 | [Description("return")] Return = 0x0F, 24 | [Description("call")] Call = 0x10, 25 | [Description("call_indirect")] CallIndirect = 0x11, 26 | 27 | // Parametric Instructions 28 | [Description("drop")] Drop = 0x1A, 29 | [Description("select")] Select = 0x1B, 30 | 31 | // Variable Instructions 32 | [Description("local.get")] LocalGet = 0x20, 33 | [Description("local.set")] LocalSet = 0x21, 34 | [Description("local.tee")] LocalTee = 0x22, 35 | [Description("global.get")] GlobalGet = 0x23, 36 | [Description("global.set")] GlobalSet = 0x24, 37 | 38 | // Memory Instructions 39 | [Description("i32.load")] I32Load = 0x28, 40 | [Description("i64.load")] I64Load = 0x29, 41 | [Description("f32.load")] F32Load = 0x2A, 42 | [Description("f64.load")] F64Load = 0x2B, 43 | [Description("i32.load8_s")] I32Load8S = 0x2C, 44 | [Description("i32.load8_u")] I32Load8U = 0x2D, 45 | [Description("i32.load16_s")] I32Load16S = 0x2E, 46 | [Description("i32.load16_u")] I32Load16U = 0x2F, 47 | [Description("i64.load8_s")] I64Load8S = 0x30, 48 | [Description("i64.load8_u")] I64Load8U = 0x31, 49 | [Description("i64.load16_s")] I64Load16S = 0x32, 50 | [Description("i64.load16_u")] I64Load16U = 0x33, 51 | [Description("i64.load32_s")] I64Load32S = 0x34, 52 | [Description("i64.load32_u")] I64Load32U = 0x35, 53 | 54 | [Description("i32.store")] I32Store = 0x36, 55 | [Description("i64.store")] I64Store = 0x37, 56 | [Description("f32.store")] F32Store = 0x38, 57 | [Description("f64.store")] F64Store = 0x39, 58 | [Description("i32.store8")] I32Store8 = 0x3A, 59 | [Description("i32.store16")] I32Store16 = 0x3B, 60 | [Description("i64.store8")] I64Store8 = 0x3C, 61 | [Description("i64.store16")] I64Store16 = 0x3D, 62 | [Description("i64.store32")] I64Store32 = 0x3E, 63 | 64 | [Description("memory.size")] MemorySize = 0x3F, 65 | [Description("memory.grow")] MemoryGrow = 0x40, 66 | 67 | // Numeric Instructions 68 | [Description("i32.const")] I32Const = 0x41, 69 | [Description("i64.const")] I64Const = 0x42, 70 | [Description("f32.const")] F32Const = 0x43, 71 | [Description("f64.const")] F64Const = 0x44, 72 | 73 | [Description("i32.eqz")] I32Eqz = 0x45, 74 | [Description("i32.eq")] I32Eq = 0x46, 75 | [Description("i32.ne")] I32Ne = 0x47, 76 | [Description("i32.lt_s")] I32LtS = 0x48, 77 | [Description("i32.lt_u")] I32LtU = 0x49, 78 | [Description("i32.gt_s")] I32GtS = 0x4A, 79 | [Description("i32.gt_u")] I32GtU = 0x4B, 80 | [Description("i32.le_s")] I32LeS = 0x4C, 81 | [Description("i32.le_u")] I32LeU = 0x4D, 82 | [Description("i32.ge_s")] I32GeS = 0x4E, 83 | [Description("i32.ge_u")] I32GeU = 0x4F, 84 | 85 | [Description("i64.eqz")] I64Eqz = 0x50, 86 | [Description("i64.eq")] I64Eq = 0x51, 87 | [Description("i64.ne")] I64Ne = 0x52, 88 | [Description("i64.lt_s")] I64LtS = 0x53, 89 | [Description("i64.lt_u")] I64LtU = 0x54, 90 | [Description("i64.gt_s")] I64GtS = 0x55, 91 | [Description("i64.gt_u")] I64GtU = 0x56, 92 | [Description("i64.le_s")] I64LeS = 0x57, 93 | [Description("i64.le_u")] I64LeU = 0x58, 94 | [Description("i64.ge_s")] I64GeS = 0x59, 95 | [Description("i64.ge_u")] I64GeU = 0x5A, 96 | 97 | [Description("f32.eq")] F32Eq = 0x5B, 98 | [Description("f32.ne")] F32Ne = 0x5C, 99 | [Description("f32.lt")] F32Lt = 0x5D, 100 | [Description("f32.gt")] F32Gt = 0x5E, 101 | [Description("f32.le")] F32Le = 0x5F, 102 | [Description("f32.ge")] F32Ge = 0x60, 103 | 104 | [Description("f64.eq")] F64Eq = 0x61, 105 | [Description("f64.ne")] F64Ne = 0x62, 106 | [Description("f64.lt")] F64Lt = 0x63, 107 | [Description("f64.gt")] F64Gt = 0x64, 108 | [Description("f64.le")] F64Le = 0x65, 109 | [Description("f64.ge")] F64Ge = 0x66, 110 | 111 | [Description("i32.clz")] I32Clz = 0x67, 112 | [Description("i32.ctz")] I32Ctz = 0x68, 113 | [Description("i32.popcnt")] I32Popcnt = 0x69, 114 | [Description("i32.add")] I32Add = 0x6A, 115 | [Description("i32.sub")] I32Sub = 0x6B, 116 | [Description("i32.mul")] I32Mul = 0x6C, 117 | [Description("i32.div_s")] I32DivS = 0x6D, 118 | [Description("i32.div_u")] I32DivU = 0x6E, 119 | [Description("i32.rem_s")] I32RemS = 0x6F, 120 | [Description("i32.rem_u")] I32RemU = 0x70, 121 | [Description("i32.and")] I32And = 0x71, 122 | [Description("i32.or")] I32Or = 0x72, 123 | [Description("i32.xor")] I32Xor = 0x73, 124 | [Description("i32.shl")] I32Shl = 0x74, 125 | [Description("i32.shr_s")] I32ShrS = 0x75, 126 | [Description("i32.shr_u")] I32ShrU = 0x76, 127 | [Description("i32.rotl")] I32Rotl = 0x77, 128 | [Description("i32.rotr")] I32Rotr = 0x78, 129 | 130 | [Description("i64.clz")] I64Clz = 0x79, 131 | [Description("i64.ctz")] I64Ctz = 0x7A, 132 | [Description("i64.popcnt")] I64Popcnt = 0x7B, 133 | [Description("i64.add")] I64Add = 0x7C, 134 | [Description("i64.sub")] I64Sub = 0x7D, 135 | [Description("i64.mul")] I64Mul = 0x7E, 136 | [Description("i64.div_s")] I64DivS = 0x7F, 137 | [Description("i64.div_u")] I64DivU = 0x80, 138 | [Description("i64.rem_s")] I64RemS = 0x81, 139 | [Description("i64.rem_u")] I64RemU = 0x82, 140 | [Description("i64.and")] I64And = 0x83, 141 | [Description("i64.or")] I64Or = 0x84, 142 | [Description("i64.xor")] I64Xor = 0x85, 143 | [Description("i64.shl")] I64Shl = 0x86, 144 | [Description("i64.shr_s")] I64ShrS = 0x87, 145 | [Description("i64.shr_u")] I64ShrU = 0x88, 146 | [Description("i64.rotl")] I64Rotl = 0x89, 147 | [Description("i64.rotr")] I64Rotr = 0x8A, 148 | 149 | [Description("f32.abs")] F32Abs = 0x8B, 150 | [Description("f32.neg")] F32Neg = 0x8C, 151 | [Description("f32.ceil")] F32Ceil = 0x8D, 152 | [Description("f32.floor")] F32Floor = 0x8E, 153 | [Description("f32.trunc")] F32Trunc = 0x8F, 154 | [Description("f32.nearest")] F32Nearest = 0x90, 155 | [Description("f32.sqrt")] F32Sqrt = 0x91, 156 | [Description("f32.add")] F32Add = 0x92, 157 | [Description("f32.sub")] F32Sub = 0x93, 158 | [Description("f32.mul")] F32Mul = 0x94, 159 | [Description("f32.div")] F32Div = 0x95, 160 | [Description("f32.min")] F32Min = 0x96, 161 | [Description("f32.max")] F32Max = 0x97, 162 | [Description("f32.copysign")] F32Copysign = 0x98, 163 | 164 | [Description("f64.abs")] F64Abs = 0x99, 165 | [Description("f64.neg")] F64Neg = 0x9A, 166 | [Description("f64.ceil")] F64Ceil = 0x9B, 167 | [Description("f64.floor")] F64Floor = 0x9C, 168 | [Description("f64.trunc")] F64Trunc = 0x9D, 169 | [Description("f64.nearest")] F64Nearest = 0x9E, 170 | [Description("f64.sqrt")] F64Sqrt = 0x9F, 171 | [Description("f64.add")] F64Add = 0xA0, 172 | [Description("f64.sub")] F64Sub = 0xA1, 173 | [Description("f64.mul")] F64Mul = 0xA2, 174 | [Description("f64.div")] F64Div = 0xA3, 175 | [Description("f64.min")] F64Min = 0xA4, 176 | [Description("f64.max")] F64Max = 0xA5, 177 | [Description("f64.copysign")] F64Copysign = 0xA6, 178 | 179 | [Description("i32.wrap_i64")] I32WrapI64 = 0xA7, 180 | [Description("i32.trunc_f32_s")] I32TruncF32S = 0xA8, 181 | [Description("i32.trunc_f32_u")] I32TruncF32U = 0xA9, 182 | [Description("i32.trunc_f64_s")] I32TruncF64S = 0xAA, 183 | [Description("i32.trunc_f64_u")] I32TruncF64U = 0xAB, 184 | [Description("i64.extend_i32_s")] I64ExtendI32S = 0xAC, 185 | [Description("i64.extend_i32_u")] I64ExtendI32U = 0xAD, 186 | [Description("i64.trunc_f32_s")] I64TruncF32S = 0xAE, 187 | [Description("i64.trunc_f32_u")] I64TruncF32U = 0xAF, 188 | [Description("i64.trunc_f64_s")] I64TruncF64S = 0xB0, 189 | [Description("i64.trunc_f64_u")] I64TruncF64U = 0xB1, 190 | [Description("f32.convert_i32_s")] F32ConvertI32S = 0xB2, 191 | [Description("f32.convert_i32_u")] F32ConvertI32U = 0xB3, 192 | [Description("f32.convert_i64_s")] F32ConvertI64S = 0xB4, 193 | [Description("f32.convert_i64_u")] F32ConvertI64U = 0xB5, 194 | [Description("f32.demote_f64")] F32DemoteF64 = 0xB6, 195 | [Description("f64.convert_i32_s")] F64ConvertI32S = 0xB7, 196 | [Description("f64.convert_i32_u")] F64ConvertI32U = 0xB8, 197 | [Description("f64.convert_i64_s")] F64ConvertI64S = 0xB9, 198 | [Description("f64.convert_i64_u")] F64ConvertI64U = 0xBA, 199 | [Description("f64.promote_f32")] F64PromoteF32 = 0xBB, 200 | [Description("i32.reinterpret_f32")] I32ReinterpretF32 = 0xBC, 201 | [Description("i64.reinterpret_f64")] I64ReinterpretF64 = 0xBD, 202 | [Description("f32.reinterpret_i32")] F32ReinterpretI32 = 0xBE, 203 | [Description("f64.reinterpret_i64")] F64ReinterpretI64 = 0xBF, 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /WasmLib/Decompilation/Intermediate/IntermediateConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using WasmLib.Decompilation.Intermediate.Instructions; 5 | using WasmLib.FileFormat; 6 | using WasmLib.FileFormat.Instructions; 7 | 8 | namespace WasmLib.Decompilation.Intermediate 9 | { 10 | public class IntermediateConverter 11 | { 12 | private readonly WasmModule wasmModule; 13 | private readonly FunctionBody function; 14 | private readonly FunctionSignature signature; 15 | 16 | public IntermediateConverter(WasmModule wasmModule, FunctionBody function, FunctionSignature signature) 17 | { 18 | this.wasmModule = wasmModule; 19 | this.function = function; 20 | this.signature = signature; 21 | } 22 | 23 | public List Convert() 24 | { 25 | int start = 0; 26 | var block = ConvertBlock(ref start); 27 | 28 | // add implicit return if required 29 | if (signature.ReturnParameter.Length != 0) { 30 | block.Add(new ImplicitReturnInstruction(signature)); 31 | } 32 | 33 | return block; 34 | } 35 | 36 | private List ConvertBlock(ref int i, bool allowElse = false, ValueKind blockReturnType = ValueKind.Empty) 37 | { 38 | var list = new List(); 39 | bool reachable = true; 40 | 41 | for (; i < function.Instructions.Length; i++) { 42 | Instruction instruction = function.Instructions[i]; 43 | switch (instruction.OpCode) { 44 | case OpCode.End: 45 | case OpCode.Else when allowElse: 46 | if (blockReturnType != ValueKind.Empty && reachable) { 47 | list.Add(new BlockReturnInstruction(blockReturnType)); 48 | } 49 | return list; 50 | case OpCode.Else: 51 | throw new Exception($"Unexpected `{instruction}` instruction, else is not allowed in the current block"); 52 | case OpCode.Block: 53 | case OpCode.Loop: 54 | case OpCode.If: 55 | i++; 56 | List list1 = ConvertBlock(ref i, instruction.OpCode == OpCode.If, (ValueKind)instruction.UIntOperand); 57 | List? list2 = null; 58 | 59 | var instr = function.Instructions[i]; 60 | if (instr.OpCode == OpCode.Else) { 61 | i++; 62 | list2 = ConvertBlock(ref i, false, (ValueKind)instruction.UIntOperand); 63 | } 64 | else { 65 | Debug.Assert(instr.OpCode == OpCode.End); 66 | } 67 | 68 | list.Add(new ControlBlockInstruction(instruction, list1, list2)); 69 | break; 70 | default: 71 | IntermediateInstruction? intermediateInstruction = ConvertInstruction(instruction); 72 | 73 | if (intermediateInstruction != null) { 74 | list.Add(intermediateInstruction); 75 | 76 | if (intermediateInstruction.RestOfBlockUnreachable) { 77 | reachable = false; 78 | } 79 | } 80 | 81 | break; 82 | } 83 | } 84 | 85 | throw new IndexOutOfRangeException("Tried to read past end of function body, is the `end` instruction missing?"); 86 | } 87 | 88 | public IntermediateInstruction? ConvertInstruction(Instruction instruction) 89 | { 90 | switch (instruction.OpCode) { 91 | case OpCode.Unreachable: 92 | return new UnreachableInstruction(); 93 | case OpCode.Nop: 94 | return null; 95 | 96 | case OpCode.Block: 97 | case OpCode.Loop: 98 | case OpCode.If: 99 | throw new Exception($"Encountered control flow instruction '{instruction}' in wrong loop"); 100 | case OpCode.Else: 101 | case OpCode.End: 102 | throw new Exception($"Encountered unexpected control flow instruction '{instruction}'"); 103 | 104 | case OpCode.Br: 105 | case OpCode.BrIf: 106 | case OpCode.BrTable: 107 | return new BranchInstruction(instruction); 108 | 109 | case OpCode.Return: 110 | return new ReturnInstruction(signature); 111 | case OpCode.Call: 112 | case OpCode.CallIndirect: 113 | return new CallInstruction(instruction, wasmModule); 114 | 115 | case OpCode.Drop: 116 | return new DropInstruction(); 117 | case OpCode.Select: 118 | return new SelectInstruction(); 119 | 120 | case OpCode.LocalGet: 121 | case OpCode.LocalSet: 122 | case OpCode.LocalTee: 123 | case OpCode.GlobalGet: 124 | case OpCode.GlobalSet: 125 | return new VariableInstruction(instruction, wasmModule, function, signature); 126 | 127 | case OpCode.I32Load: 128 | case OpCode.I64Load: 129 | case OpCode.F32Load: 130 | case OpCode.F64Load: 131 | case OpCode.I32Load8S: 132 | case OpCode.I32Load8U: 133 | case OpCode.I32Load16S: 134 | case OpCode.I32Load16U: 135 | case OpCode.I64Load8S: 136 | case OpCode.I64Load8U: 137 | case OpCode.I64Load16S: 138 | case OpCode.I64Load16U: 139 | case OpCode.I64Load32S: 140 | case OpCode.I64Load32U: 141 | case OpCode.I32Store: 142 | case OpCode.I64Store: 143 | case OpCode.F32Store: 144 | case OpCode.F64Store: 145 | case OpCode.I32Store8: 146 | case OpCode.I32Store16: 147 | case OpCode.I64Store8: 148 | case OpCode.I64Store16: 149 | case OpCode.I64Store32: 150 | return new MemoryInstruction(instruction); 151 | 152 | case OpCode.MemoryGrow: 153 | case OpCode.MemorySize: 154 | return new MemorySizeInstruction(instruction); 155 | 156 | case OpCode.I32Const: 157 | case OpCode.I64Const: 158 | case OpCode.F32Const: 159 | case OpCode.F64Const: 160 | return new ConstInstruction(instruction); 161 | 162 | case OpCode.I32Clz: 163 | case OpCode.I32Ctz: 164 | case OpCode.I32Popcnt: 165 | case OpCode.I64Clz: 166 | case OpCode.I64Ctz: 167 | case OpCode.I64Popcnt: 168 | 169 | case OpCode.F32Abs: 170 | case OpCode.F32Neg: 171 | case OpCode.F32Ceil: 172 | case OpCode.F32Floor: 173 | case OpCode.F32Trunc: 174 | case OpCode.F32Nearest: 175 | case OpCode.F32Sqrt: 176 | case OpCode.F64Abs: 177 | case OpCode.F64Neg: 178 | case OpCode.F64Ceil: 179 | case OpCode.F64Floor: 180 | case OpCode.F64Trunc: 181 | case OpCode.F64Nearest: 182 | case OpCode.F64Sqrt: 183 | return new UnaryOperationInstruction(instruction); 184 | 185 | case OpCode.I32Add: 186 | case OpCode.I64Add: 187 | case OpCode.F32Add: 188 | case OpCode.F64Add: 189 | case OpCode.I32Sub: 190 | case OpCode.I64Sub: 191 | case OpCode.F32Sub: 192 | case OpCode.F64Sub: 193 | case OpCode.I32Mul: 194 | case OpCode.I64Mul: 195 | case OpCode.F32Mul: 196 | case OpCode.F64Mul: 197 | 198 | case OpCode.I32DivS: 199 | case OpCode.I32DivU: 200 | case OpCode.I32RemS: 201 | case OpCode.I32RemU: 202 | case OpCode.I64DivS: 203 | case OpCode.I64DivU: 204 | case OpCode.I64RemS: 205 | case OpCode.I64RemU: 206 | case OpCode.I32And: 207 | case OpCode.I64And: 208 | case OpCode.I32Or: 209 | case OpCode.I64Or: 210 | case OpCode.I32Xor: 211 | case OpCode.I64Xor: 212 | case OpCode.I32Shl: 213 | case OpCode.I64Shl: 214 | case OpCode.I32ShrS: 215 | case OpCode.I64ShrS: 216 | case OpCode.I32ShrU: 217 | case OpCode.I64ShrU: 218 | case OpCode.I32Rotl: 219 | case OpCode.I64Rotl: 220 | case OpCode.I32Rotr: 221 | case OpCode.I64Rotr: 222 | 223 | case OpCode.F32Div: 224 | case OpCode.F32Min: 225 | case OpCode.F32Max: 226 | case OpCode.F32Copysign: 227 | case OpCode.F64Div: 228 | case OpCode.F64Min: 229 | case OpCode.F64Max: 230 | case OpCode.F64Copysign: 231 | return new BinaryOperationInstruction(instruction); 232 | 233 | 234 | case OpCode.I32Eq: 235 | case OpCode.I32Ne: 236 | case OpCode.I32LtS: 237 | case OpCode.I32LtU: 238 | case OpCode.I32GtS: 239 | case OpCode.I32GtU: 240 | case OpCode.I32LeS: 241 | case OpCode.I32LeU: 242 | case OpCode.I32GeS: 243 | case OpCode.I32GeU: 244 | 245 | case OpCode.I64Eq: 246 | case OpCode.I64Ne: 247 | case OpCode.I64LtS: 248 | case OpCode.I64LtU: 249 | case OpCode.I64GtS: 250 | case OpCode.I64GtU: 251 | case OpCode.I64LeS: 252 | case OpCode.I64LeU: 253 | case OpCode.I64GeS: 254 | case OpCode.I64GeU: 255 | 256 | case OpCode.F32Eq: 257 | case OpCode.F32Ne: 258 | case OpCode.F32Lt: 259 | case OpCode.F32Gt: 260 | case OpCode.F32Le: 261 | case OpCode.F32Ge: 262 | 263 | case OpCode.F64Eq: 264 | case OpCode.F64Ne: 265 | case OpCode.F64Lt: 266 | case OpCode.F64Gt: 267 | case OpCode.F64Le: 268 | case OpCode.F64Ge: 269 | return new ComparisonOperationInstruction(instruction); 270 | 271 | case OpCode.I32Eqz: 272 | case OpCode.I64Eqz: 273 | return new TestOperationInstruction(instruction); 274 | 275 | case OpCode.I32WrapI64: 276 | case OpCode.I32TruncF32S: 277 | case OpCode.I32TruncF32U: 278 | case OpCode.I32TruncF64S: 279 | case OpCode.I32TruncF64U: 280 | case OpCode.I64ExtendI32S: 281 | case OpCode.I64ExtendI32U: 282 | case OpCode.I64TruncF32S: 283 | case OpCode.I64TruncF32U: 284 | case OpCode.I64TruncF64S: 285 | case OpCode.I64TruncF64U: 286 | case OpCode.F32ConvertI32S: 287 | case OpCode.F32ConvertI32U: 288 | case OpCode.F32ConvertI64S: 289 | case OpCode.F32ConvertI64U: 290 | case OpCode.F32DemoteF64: 291 | case OpCode.F64ConvertI32S: 292 | case OpCode.F64ConvertI32U: 293 | case OpCode.F64ConvertI64S: 294 | case OpCode.F64ConvertI64U: 295 | case OpCode.F64PromoteF32: 296 | case OpCode.I32ReinterpretF32: 297 | case OpCode.I64ReinterpretF64: 298 | case OpCode.F32ReinterpretI32: 299 | case OpCode.F64ReinterpretI64: 300 | return new ConversionOperatorInstruction(instruction); 301 | 302 | default: 303 | throw new NotImplementedException($"Unimplemented instruction {instruction}"); 304 | } 305 | } 306 | } 307 | } --------------------------------------------------------------------------------