Initial commit
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Emits a simple WebAssembly wrapper module around a given webcil payload.
|
||||
//
|
||||
// The entire wasm module is going to be unchanging, except for the data section which has 2 passive
|
||||
// segments. segment 0 is 4 bytes and contains the length of the webcil payload. segment 1 is of a
|
||||
// variable size and contains the webcil payload.
|
||||
//
|
||||
// The unchanging parts are stored as a "prefix" and "suffix" which contain the bytes for the following
|
||||
// WAT module, split into the parts that come before the data section, and the bytes that come after:
|
||||
//
|
||||
// (module
|
||||
// (data "\0\00\00\00") ;; data segment 0: payload size as a 4 byte LE uint32
|
||||
// (data "webcil Payload\cc") ;; data segment 1: webcil payload
|
||||
// (memory (import "webcil" "memory") 1)
|
||||
// (global (export "webcilVersion") i32 (i32.const 0))
|
||||
// (func (export "getWebcilSize") (param $destPtr i32) (result)
|
||||
// local.get $destPtr
|
||||
// i32.const 0
|
||||
// i32.const 4
|
||||
// memory.init 0)
|
||||
// (func (export "getWebcilPayload") (param $d i32) (param $n i32) (result)
|
||||
// local.get $d
|
||||
// i32.const 0
|
||||
// local.get $n
|
||||
// memory.init 1))
|
||||
namespace RobotNet10.ScriptEditor.Helpers.Webcil;
|
||||
|
||||
public class WebcilWasmWrapper
|
||||
{
|
||||
private readonly Stream _webcilPayloadStream;
|
||||
private readonly uint _webcilPayloadSize;
|
||||
|
||||
public WebcilWasmWrapper(Stream webcilPayloadStream)
|
||||
{
|
||||
_webcilPayloadStream = webcilPayloadStream;
|
||||
long len = webcilPayloadStream.Length;
|
||||
if (len > (long)uint.MaxValue)
|
||||
throw new InvalidOperationException("webcil payload too large");
|
||||
_webcilPayloadSize = (uint)len;
|
||||
}
|
||||
|
||||
public void WriteWasmWrappedWebcil(Stream outputStream)
|
||||
{
|
||||
WriteWasmHeader(outputStream);
|
||||
using (var writer = new BinaryWriter(outputStream, System.Text.Encoding.UTF8, leaveOpen: true))
|
||||
{
|
||||
WriteDataSection(writer);
|
||||
}
|
||||
WriteWasmSuffix(outputStream);
|
||||
}
|
||||
|
||||
//
|
||||
// Everything from the above wat module before the data section
|
||||
//
|
||||
// extracted by wasm-reader -s wrapper.wasm
|
||||
private static
|
||||
#if NET7_0_OR_GREATER
|
||||
ReadOnlyMemory<byte>
|
||||
#else
|
||||
byte[]
|
||||
#endif
|
||||
s_wasmWrapperPrefix = new byte[] {
|
||||
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x02, 0x60, 0x01, 0x7f, 0x00, 0x60, 0x02, 0x7f, 0x7f, 0x00, 0x02, 0x12, 0x01, 0x06, 0x77, 0x65, 0x62, 0x63, 0x69, 0x6c, 0x06, 0x6d,
|
||||
0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x01, 0x03, 0x03, 0x02, 0x00, 0x01, 0x06, 0x0b, 0x02, 0x7f, 0x00, 0x41, 0x00, 0x0b, 0x7f, 0x00, 0x41, 0x00, 0x0b, 0x07, 0x41, 0x04, 0x0d, 0x77, 0x65,
|
||||
0x62, 0x63, 0x69, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x03, 0x00, 0x0a, 0x77, 0x65, 0x62, 0x63, 0x69, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x03, 0x01, 0x0d, 0x67, 0x65, 0x74, 0x57, 0x65,
|
||||
0x62, 0x63, 0x69, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x00, 0x00, 0x10, 0x67, 0x65, 0x74, 0x57, 0x65, 0x62, 0x63, 0x69, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x00, 0x01, 0x0c, 0x01, 0x02,
|
||||
0x0a, 0x1b, 0x02, 0x0c, 0x00, 0x20, 0x00, 0x41, 0x00, 0x41, 0x04, 0xfc, 0x08, 0x00, 0x00, 0x0b, 0x0c, 0x00, 0x20, 0x00, 0x41, 0x00, 0x20, 0x01, 0xfc, 0x08, 0x01, 0x00, 0x0b,
|
||||
};
|
||||
//
|
||||
// Everything from the above wat module after the data section
|
||||
//
|
||||
// extracted by wasm-reader -s wrapper.wasm
|
||||
private static
|
||||
#if NET7_0_OR_GREATER
|
||||
ReadOnlyMemory<byte>
|
||||
#else
|
||||
byte[]
|
||||
#endif
|
||||
s_wasmWrapperSuffix = new byte[] {
|
||||
0x00, 0x1b, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x02, 0x14, 0x02, 0x00, 0x01, 0x00, 0x07, 0x64, 0x65, 0x73, 0x74, 0x50, 0x74, 0x72, 0x01, 0x02, 0x00, 0x01, 0x64, 0x01, 0x01, 0x6e,
|
||||
};
|
||||
|
||||
private static void WriteWasmHeader(Stream outputStream)
|
||||
{
|
||||
#if NET7_0_OR_GREATER
|
||||
outputStream.Write(s_wasmWrapperPrefix.Span);
|
||||
#else
|
||||
outputStream.Write(s_wasmWrapperPrefix, 0, s_wasmWrapperPrefix.Length);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void WriteWasmSuffix(Stream outputStream)
|
||||
{
|
||||
#if NET7_0_OR_GREATER
|
||||
outputStream.Write(s_wasmWrapperSuffix.Span);
|
||||
#else
|
||||
outputStream.Write(s_wasmWrapperSuffix, 0, s_wasmWrapperSuffix.Length);
|
||||
#endif
|
||||
}
|
||||
|
||||
// 1 byte to encode "passive" data segment
|
||||
private const uint SegmentCodeSize = 1;
|
||||
|
||||
// Align the payload start to a 4-byte boundary within the wrapper. If the runtime reads the
|
||||
// payload directly, instead of by instantiatng the wasm module, we don't want the WebAssembly
|
||||
// prefix to push some of the values inside the image to odd byte offsets as the runtime assumes
|
||||
// the image will be aligned.
|
||||
//
|
||||
// There are requirements in ECMA-335 (Section II.25.4) that fat method headers and method data
|
||||
// sections be 4-byte aligned.
|
||||
private const uint WebcilPayloadInternalAlignment = 4;
|
||||
|
||||
private void WriteDataSection(BinaryWriter writer)
|
||||
{
|
||||
|
||||
uint dataSectionSize = 0;
|
||||
// uleb128 encoding of number of segments
|
||||
dataSectionSize += 1; // there's always 2 segments which encodes to 1 byte
|
||||
// compute the segment 0 size:
|
||||
// segment 0 has 1 byte segment code, 1 byte of size and at least 4 bytes of payload
|
||||
uint segment0MinimumSize = SegmentCodeSize + 1 + 4;
|
||||
dataSectionSize += segment0MinimumSize;
|
||||
|
||||
// encode webcil size as a uleb128
|
||||
byte[] ulebWebcilPayloadSize = ULEB128Encode(_webcilPayloadSize);
|
||||
|
||||
// compute the segment 1 size:
|
||||
// segment 1 has 1 byte segment code, a uleb128 encoding of the webcilPayloadSize, and the payload
|
||||
// don't count the size of the payload yet
|
||||
checked
|
||||
{
|
||||
dataSectionSize += SegmentCodeSize + (uint)ulebWebcilPayloadSize.Length;
|
||||
}
|
||||
|
||||
// at this point the data section size includes everything except the data section code, the data section size and the webcil payload itself
|
||||
// and any extra padding that we may want to add to segment 0.
|
||||
// So we can compute the offset of the payload within the wasm module.
|
||||
byte[] putativeULEBDataSectionSize = ULEB128Encode(dataSectionSize + _webcilPayloadSize);
|
||||
uint payloadOffset = (uint)s_wasmWrapperPrefix.Length + 1 + (uint)putativeULEBDataSectionSize.Length + dataSectionSize ;
|
||||
|
||||
uint paddingSize = PadTo(payloadOffset, WebcilPayloadInternalAlignment);
|
||||
|
||||
if (paddingSize > 0)
|
||||
{
|
||||
checked
|
||||
{
|
||||
dataSectionSize += paddingSize;
|
||||
}
|
||||
}
|
||||
|
||||
checked
|
||||
{
|
||||
dataSectionSize += _webcilPayloadSize;
|
||||
}
|
||||
|
||||
byte[] ulebSectionSize = ULEB128Encode(dataSectionSize);
|
||||
|
||||
if (putativeULEBDataSectionSize.Length != ulebSectionSize.Length)
|
||||
throw new InvalidOperationException ("adding padding would cause data section's encoded length to chane"); // TODO: fixme: there's upto one extra byte to encode the section length - take away a padding byte.
|
||||
writer.Write((byte)11); // section Data
|
||||
writer.Write(ulebSectionSize, 0, ulebSectionSize.Length);
|
||||
|
||||
writer.Write((byte)2); // number of segments
|
||||
|
||||
// write segment 0
|
||||
writer.Write((byte)1); // passive segment
|
||||
if (paddingSize + 4 > 127) {
|
||||
throw new InvalidOperationException ("padding would cause segment 0 to need a multi-byte ULEB128 size encoding");
|
||||
}
|
||||
writer.Write((byte)(4 + paddingSize)); // segment size: 4 plus any padding
|
||||
writer.Write((uint)_webcilPayloadSize); // payload is an unsigned 32 bit number
|
||||
for (int i = 0; i < paddingSize; i++)
|
||||
writer.Write((byte)0);
|
||||
|
||||
// write segment 1
|
||||
writer.Write((byte)1); // passive segment
|
||||
writer.Write(ulebWebcilPayloadSize, 0, ulebWebcilPayloadSize.Length); // segment size: _webcilPayloadSize
|
||||
if (writer.BaseStream.Position % WebcilPayloadInternalAlignment != 0) {
|
||||
throw new Exception ($"predited offset {payloadOffset}, actual position {writer.BaseStream.Position}");
|
||||
}
|
||||
_webcilPayloadStream.CopyTo(writer.BaseStream); // payload is the entire webcil content
|
||||
}
|
||||
|
||||
private static byte[] ULEB128Encode(uint value)
|
||||
{
|
||||
uint n = value;
|
||||
int len = 0;
|
||||
do
|
||||
{
|
||||
n >>= 7;
|
||||
len++;
|
||||
} while (n != 0);
|
||||
byte[] arr = new byte[len];
|
||||
int i = 0;
|
||||
n = value;
|
||||
do
|
||||
{
|
||||
byte b = (byte)(n & 0x7f);
|
||||
n >>= 7;
|
||||
if (n != 0)
|
||||
b |= 0x80;
|
||||
arr[i++] = b;
|
||||
} while (n != 0);
|
||||
return arr;
|
||||
}
|
||||
|
||||
private static uint PadTo (uint value, uint align)
|
||||
{
|
||||
uint newValue = AlignTo(value, align);
|
||||
return newValue - value;
|
||||
}
|
||||
|
||||
private static uint AlignTo (uint value, uint align)
|
||||
{
|
||||
return (value + (align - 1)) & ~(align - 1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user