Code/Wasm/CodeSection.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using WasmBox.Wasm.Binary;
using WasmBox.Wasm.Instructions;
namespace WasmBox.Wasm {
/// <summary>
/// A type of section that contains a body for every function in the module.
/// </summary>
public sealed class CodeSection : Section {
/// <summary>
/// Creates an empty code section.
/// </summary>
public CodeSection() {
this.Bodies = new List<FunctionBody>();
this.ExtraPayload = new byte[0];
}
/// <summary>
/// Creates a code section from a sequence of function bodies.
/// </summary>
/// <param name="bodies">The code section's function codies.</param>
public CodeSection(IEnumerable<FunctionBody> bodies)
: this(bodies, new byte[0]) {
}
/// <summary>
/// Creates a code section from a sequence of function bodies and a
/// trailing payload.
/// </summary>
/// <param name="bodies">The code section's function bodies.</param>
/// <param name="extraPayload">
/// A sequence of bytes that have no intrinsic meaning; they are part
/// of the code section but are placed after the code section's actual contents.
/// </param>
public CodeSection(IEnumerable<FunctionBody> bodies, byte[] extraPayload) {
this.Bodies = new List<FunctionBody>(bodies);
this.ExtraPayload = extraPayload;
}
/// <inheritdoc/>
public override SectionName Name => new SectionName(SectionCode.Code);
/// <summary>
/// Gets the list of all values that are exported by this section.
/// </summary>
/// <returns>A list of all values exported by this section.</returns>
public List<FunctionBody> Bodies { get; private set; }
/// <summary>
/// Gets this function section's additional payload.
/// </summary>
/// <returns>The additional payload, as an array of bytes.</returns>
public byte[] ExtraPayload { get; set; }
/// <inheritdoc/>
public override void WritePayloadTo(BinaryWasmWriter writer) {
writer.WriteVarUInt32((uint)Bodies.Count);
foreach (var body in Bodies) {
body.WriteTo(writer);
}
writer.Writer.Write(ExtraPayload);
}
/// <summary>
/// Reads the code section with the given header.
/// </summary>
/// <param name="header">The section header.</param>
/// <param name="reader">A reader for a binary WebAssembly file.</param>
/// <returns>The parsed section.</returns>
public static CodeSection ReadSectionPayload(
SectionHeader header, BinaryWasmReader reader) {
long startPos = reader.Position;
// Read the function bodies.
uint count = reader.ReadVarUInt32();
var funcBodies = new List<FunctionBody>();
for (uint i = 0; i < count; i++) {
funcBodies.Add(FunctionBody.ReadFrom(reader));
}
// Skip any remaining bytes.
var extraPayload = reader.ReadRemainingPayload(startPos, header);
return new CodeSection(funcBodies, extraPayload);
}
/// <inheritdoc/>
public override void Dump(TextWriter writer) {
writer.Write(Name.ToString());
writer.Write("; number of entries: ");
writer.Write(Bodies.Count);
writer.WriteLine();
var indentedWriter = DumpHelpers.CreateIndentedTextWriter(writer);
for (int i = 0; i < Bodies.Count; i++) {
writer.Write("#{0}: ", i);
indentedWriter.WriteLine();
Bodies[i].Dump(indentedWriter);
}
if (ExtraPayload.Length > 0) {
writer.Write("Extra payload size: ");
writer.Write(ExtraPayload.Length);
writer.WriteLine();
DumpHelpers.DumpBytes(ExtraPayload, writer);
writer.WriteLine();
}
}
}
/// <summary>
/// An entry in a code section; defines a function body.
/// </summary>
public sealed class FunctionBody {
/// <summary>
/// Creates a function body from the given list of local entries
/// and a block instruction.
/// </summary>
/// <param name="locals">The list of local entries.</param>
/// <param name="body">The block instruction that serves as the function's body.</param>
public FunctionBody(IEnumerable<LocalEntry> locals, IEnumerable<Instruction> body)
: this(locals, body, new byte[0]) { }
/// <summary>
/// Creates a function body from the given list of local entries,
/// a list of instructions and the specified extra payload.
/// </summary>
/// <param name="locals">The list of local entries.</param>
/// <param name="body">The list of instructions that serves as the function's body.</param>
/// <param name="extraPayload">
/// The function body's extra payload, which is placed right after the function body.
/// </param>
public FunctionBody(IEnumerable<LocalEntry> locals, IEnumerable<Instruction> body, byte[] extraPayload) {
this.Locals = new List<LocalEntry>(locals);
this.BodyInstructions = new List<Instruction>(body);
this.ExtraPayload = extraPayload;
}
/// <summary>
/// Gets the list of local entries for this function body.
/// </summary>
/// <returns>The list of local entries.</returns>
public List<LocalEntry> Locals { get; private set; }
/// <summary>
/// Gets the function body's list of instructions.
/// </summary>
/// <returns>The list of function body instructions.</returns>
public List<Instruction> BodyInstructions { get; private set; }
/// <summary>
/// Gets this function body's additional payload.
/// </summary>
/// <returns>
/// The additional payload, as an array of bytes.
/// <c>null</c> indicates an empty additional payload.
/// </returns>
public byte[] ExtraPayload { get; set; }
/// <summary>
/// Checks if this function body has at least one byte of additional payload.
/// </summary>
public bool HasExtraPayload => ExtraPayload != null && ExtraPayload.Length > 0;
/// <summary>
/// Writes this function body to the given WebAssembly file writer.
/// </summary>
/// <param name="writer">The WebAssembly file writer.</param>
public void WriteTo(BinaryWasmWriter writer) {
writer.WriteLengthPrefixed(WriteContentsTo);
}
private void WriteContentsTo(BinaryWasmWriter writer) {
// Write the number of local entries to the file.
writer.WriteVarUInt32((uint)Locals.Count);
// Write the local variables to the file.
foreach (var local in Locals) {
local.WriteTo(writer);
}
// Write the body to the file.
Operators.Block.Create(WasmType.Empty, BodyInstructions).WriteContentsTo(writer);
if (HasExtraPayload) {
// If we have at least one byte of additional payload,
// then we should write it to the stream now.
writer.Writer.Write(ExtraPayload);
}
}
/// <summary>
/// Reads a function body from the given WebAssembly file reader.
/// </summary>
/// <param name="reader">The WebAssembly file reader to use.</param>
/// <returns>A function body.</returns>
public static FunctionBody ReadFrom(BinaryWasmReader reader) {
// Read the length of the function body definition.
uint funcBodyLength = reader.ReadVarUInt32();
// Save the function body's start position.
long startPos = reader.Position;
// Read the number of local entries.
uint localEntryCount = reader.ReadVarUInt32();
// Read local entries.
var localEntries = new List<LocalEntry>((int)localEntryCount);
for (uint i = 0; i < localEntryCount; i++) {
localEntries.Add(LocalEntry.ReadFrom(reader));
}
// Read the function's body block.
var body = Operators.Block.ReadBlockContents(WasmType.Empty, reader);
// Skip any remaining bytes.
var extraPayload = reader.ReadRemainingPayload(startPos, funcBodyLength);
return new FunctionBody(localEntries, body.Contents, extraPayload);
}
/// <summary>
/// Writes a textual representation of this function body to the given writer.
/// </summary>
/// <param name="writer">The writer to which text is written.</param>
public void Dump(TextWriter writer) {
if (Locals.Count > 0) {
writer.Write("- Local entries:");
var varEntryWriter = DumpHelpers.CreateIndentedTextWriter(writer);
for (int i = 0; i < Locals.Count; i++) {
varEntryWriter.WriteLine();
varEntryWriter.Write("#{0}: ", i);
Locals[i].Dump(varEntryWriter);
}
writer.WriteLine();
}
else {
writer.WriteLine("- No local entries");
}
if (BodyInstructions.Count > 0) {
writer.Write("- Function body:");
var instructionWriter = DumpHelpers.CreateIndentedTextWriter(writer);
foreach (var instr in BodyInstructions) {
instructionWriter.WriteLine();
instr.Dump(instructionWriter);
}
writer.WriteLine();
}
else {
writer.WriteLine("- Empty function body");
}
if (HasExtraPayload) {
writer.Write("- Extra payload size: ");
writer.Write(ExtraPayload.Length);
writer.WriteLine();
DumpHelpers.DumpBytes(ExtraPayload, writer);
writer.WriteLine();
}
}
}
/// <summary>
/// Describes a local entry. Each local entry declares a number of local variables
/// of a given type. It is legal to have several entries with the same type.
/// </summary>
public struct LocalEntry : IEquatable<LocalEntry> {
/// <summary>
/// Creates a new local entry that defines <c>LocalCount</c> variables of type
/// <c>LocalType</c>.
/// </summary>
/// <param name="localType">The type of the variables to define.</param>
/// <param name="localCount">The number of local variables to define.</param>
public LocalEntry(WasmValueType localType, uint localCount) {
this.LocalType = localType;
this.LocalCount = localCount;
}
/// <summary>
/// Gets the type of the local variables declared by this local entry.
/// </summary>
/// <returns>The type of the local variables declared by this local entry.</returns>
public WasmValueType LocalType { get; private set; }
/// <summary>
/// Gets the number of local variables defined by this local entry.
/// </summary>
/// <returns>The number of local variables defined by this local entry.</returns>
public uint LocalCount { get; private set; }
/// <summary>
/// Writes this local entry to the given WebAssembly file writer.
/// </summary>
/// <param name="writer">The WebAssembly file writer.</param>
public void WriteTo(BinaryWasmWriter writer) {
writer.WriteVarUInt32(LocalCount);
writer.WriteWasmValueType(LocalType);
}
/// <summary>
/// Reads a local entry from the given WebAssembly file reader.
/// </summary>
/// <param name="reader">The WebAssembly file reader.</param>
/// <returns>A local entry.</returns>
public static LocalEntry ReadFrom(BinaryWasmReader reader) {
var count = reader.ReadVarUInt32();
var type = reader.ReadWasmValueType();
return new LocalEntry(type, count);
}
/// <summary>
/// Writes a textual representation of this local entry to the given writer.
/// </summary>
/// <param name="writer">The writer to which text is written.</param>
public void Dump(TextWriter writer) {
writer.Write(LocalCount);
writer.Write(" x ");
DumpHelpers.DumpWasmType(LocalType, writer);
}
/// <inheritdoc/>
public override string ToString() {
var builder = new StringBuilder();
Dump(new StringWriter(builder));
return builder.ToString();
}
/// <inheritdoc/>
public override bool Equals(object obj) {
return obj is LocalEntry && Equals((LocalEntry)obj);
}
/// <inheritdoc/>
public override int GetHashCode() {
return ((int)LocalType << 16) | (int)LocalCount;
}
/// <summary>
/// Checks if this local entry declares the same type and
/// number of locals as the given local entry.
/// </summary>
/// <param name="other">The other local entry.</param>
/// <returns>
/// <c>true</c> if this local entry is the same as the given entry; otherwise, <c>false</c>.
/// </returns>
public bool Equals(LocalEntry other) {
return LocalType == other.LocalType && LocalCount == other.LocalCount;
}
}
}