Initial commit

This commit is contained in:
2026-07-03 16:31:37 +07:00
commit 899c7c637d
1939 changed files with 641750 additions and 0 deletions

View File

@@ -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);
}
}