├── .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 | }
--------------------------------------------------------------------------------