Skip to content

Commit c2beb13

Browse files
committed
Partial work on WASM-to-DLL functionality.
1 parent 6da78e1 commit c2beb13

File tree

1 file changed

+124
-28
lines changed

1 file changed

+124
-28
lines changed

WebAssembly/Runtime/Compile.cs

Lines changed: 124 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public static InstanceCreator<TExports> FromBinary<TExports>(string path, Compil
7474
/// <summary>
7575
/// Uses streaming compilation to create an executable <see cref="Instance{TExports}"/> from a binary WebAssembly source.
7676
/// </summary>
77-
/// <param name="input">The source of data. The stream is left open after reading is complete.</param>
77+
/// <param name="input">The source of data. The stream is left open after reading is complete.</param>
7878
/// <returns>A function that creates instances on demand.</returns>
7979
/// <exception cref="ArgumentNullException"><paramref name="input"/> cannot be null.</exception>
8080
public static InstanceCreator<TExports> FromBinary<TExports>(Stream input)
@@ -86,7 +86,7 @@ public static InstanceCreator<TExports> FromBinary<TExports>(Stream input)
8686
/// <summary>
8787
/// Uses streaming compilation to create an executable <see cref="Instance{TExports}"/> from a binary WebAssembly source.
8888
/// </summary>
89-
/// <param name="input">The source of data. The stream is left open after reading is complete.</param>
89+
/// <param name="input">The source of data. The stream is left open after reading is complete.</param>
9090
/// <param name="configuration">Configures the compiler.</param>
9191
/// <returns>A function that creates instances on demand.</returns>
9292
/// <exception cref="ArgumentNullException">No parameters can be null.</exception>
@@ -102,8 +102,14 @@ public static InstanceCreator<TExports> FromBinary<TExports>(Stream input, Compi
102102
{
103103
try
104104
{
105+
CheckPreamble(reader);
106+
105107
constructor = FromBinary(
106-
reader,
108+
AssemblyBuilder.DefineDynamicAssembly(
109+
new AssemblyName("CompiledWebAssembly"),
110+
AssemblyBuilderAccess.RunAndCollect
111+
),
112+
reader,
107113
configuration,
108114
typeof(Instance<TExports>),
109115
typeof(TExports)
@@ -189,11 +195,85 @@ private readonly struct Local(Reader reader)
189195
FieldAttributes.InitOnly
190196
;
191197

198+
private static void CheckPreamble(Reader reader)
199+
{
200+
if (reader.ReadUInt32() != Module.Magic)
201+
throw new ModuleLoadException("File preamble magic value is incorrect.", 0);
202+
}
203+
204+
#if NET9_0_OR_GREATER
205+
/// <summary>
206+
/// Creates an <see cref="AssemblyBuilder"/> from a binary WASM source.
207+
/// </summary>
208+
/// <param name="input">The source of data. The stream is left open after reading is complete.</param>
209+
/// <param name="configuration">Configures the compiler.</param>
210+
/// <returns>The created assembly.</returns>
211+
public static PersistedAssemblyBuilder CreatePersistedAssembly(
212+
Stream input,
213+
CompilerConfiguration configuration
214+
)
215+
{
216+
ArgumentNullException.ThrowIfNull(configuration, nameof(configuration));
217+
218+
using var reader = new Reader(input);
219+
220+
try
221+
{
222+
CheckPreamble(reader);
223+
224+
var assembly = new PersistedAssemblyBuilder(
225+
new AssemblyName("CompiledWebAssembly"),
226+
typeof(object).Assembly
227+
);
228+
229+
var module = assembly.DefineDynamicModule("CompiledWebAssembly");
230+
231+
var importBuilder = module.DefineType("Imports");
232+
var instanceContainer = typeof(Instance<>).MakeGenericType(importBuilder);
233+
var exportContainer = module.DefineType("Exports");
234+
235+
FromBinary(assembly, reader, configuration, instanceContainer, exportContainer, module, importBuilder);
236+
237+
importBuilder.CreateType();
238+
exportContainer.CreateType();
239+
240+
return assembly;
241+
}
242+
catch (OverflowException x)
243+
#if DEBUG
244+
when (!System.Diagnostics.Debugger.IsAttached)
245+
#endif
246+
{
247+
throw new ModuleLoadException("Overflow encountered.", reader.Offset, x);
248+
}
249+
catch (EndOfStreamException x)
250+
#if DEBUG
251+
when (!System.Diagnostics.Debugger.IsAttached)
252+
#endif
253+
{
254+
throw new ModuleLoadException("Stream ended unexpectedly.", reader.Offset, x);
255+
}
256+
catch (Exception x) when (
257+
x is not CompilerException
258+
&& x is not ModuleLoadException
259+
#if DEBUG
260+
&& !System.Diagnostics.Debugger.IsAttached
261+
#endif
262+
)
263+
{
264+
throw new ModuleLoadException(x.Message, reader.Offset, x);
265+
}
266+
}
267+
#endif
268+
192269
private static ConstructorInfo FromBinary(
270+
AssemblyBuilder assembly,
193271
Reader reader,
194272
CompilerConfiguration configuration,
195273
Type instanceContainer,
196-
Type exportContainer
274+
Type exportContainer,
275+
ModuleBuilder? module = null,
276+
TypeBuilder? importBuilder = null
197277
)
198278
{
199279
#if NETSTANDARD
@@ -203,9 +283,6 @@ Type exportContainer
203283
ArgumentNullException.ThrowIfNull(configuration, nameof(configuration));
204284
#endif
205285

206-
if (reader.ReadUInt32() != Module.Magic)
207-
throw new ModuleLoadException("File preamble magic value is incorrect.", 0);
208-
209286
switch (reader.ReadUInt32())
210287
{
211288
case 0x1: //First release
@@ -222,12 +299,7 @@ Type exportContainer
222299
KeyValuePair<string, uint>[]? exportedFunctions = null;
223300
var previousSection = Section.None;
224301

225-
var module = AssemblyBuilder.DefineDynamicAssembly(
226-
new AssemblyName("CompiledWebAssembly"),
227-
AssemblyBuilderAccess.RunAndCollect
228-
)
229-
.DefineDynamicModule("CompiledWebAssembly")
230-
;
302+
module ??= assembly.DefineDynamicModule("CompiledWebAssembly");
231303

232304
var context = new CompilationContext(configuration);
233305
var exportsBuilder = context.CheckedExportsBuilder = module.DefineType("CompiledExports", ClassAttributes, exportContainer);
@@ -243,11 +315,18 @@ Type exportContainer
243315
);
244316
instanceConstructorIL = instanceConstructor.GetILGenerator();
245317
{
246-
var usableConstructor = exportContainer.GetTypeInfo().DeclaredConstructors.FirstOrDefault(c => c.GetParameters().Length == 0);
247-
if (usableConstructor != null)
318+
if (exportContainer is TypeBuilder buildableExportContainer)
248319
{
249-
instanceConstructorIL.Emit(OpCodes.Ldarg_0);
250-
instanceConstructorIL.Emit(OpCodes.Call, usableConstructor);
320+
var usableConstructor = buildableExportContainer.DefineDefaultConstructor(ConstructorAttributes);
321+
}
322+
else
323+
{
324+
var usableConstructor = exportContainer.GetTypeInfo().DeclaredConstructors.FirstOrDefault(c => c.GetParameters().Length == 0);
325+
if (usableConstructor != null)
326+
{
327+
instanceConstructorIL.Emit(OpCodes.Ldarg_0);
328+
instanceConstructorIL.Emit(OpCodes.Call, usableConstructor);
329+
}
251330
}
252331
}
253332
}
@@ -266,7 +345,7 @@ Type exportContainer
266345
while (reader.TryReadVarUInt7(out var id)) //At points where TryRead is used, the stream can safely end.
267346
{
268347
if (id != 0 && (Section)id < previousSection)
269-
throw new ModuleLoadException($"Sections out of order; section {(Section)id} encounterd after {previousSection}.", preSectionOffset);
348+
throw new ModuleLoadException($"Sections out of order; section {(Section)id} encountered after {previousSection}.", preSectionOffset);
270349
var payloadLength = reader.ReadVarUInt32();
271350

272351
switch ((Section)id)
@@ -356,7 +435,7 @@ Type exportContainer
356435

357436
if (functionTable == null)
358437
{
359-
// It's legal to have multiple tables, but the extra tables are inaccessble to the initial version of WebAssembly.
438+
// It's legal to have multiple tables, but the extra tables are inaccessible to the initial version of WebAssembly.
360439
var limits = new ResizableLimits(reader);
361440
functionTable = context.FunctionTable = CreateFunctionTableField(exportsBuilder);
362441
instanceConstructorIL.EmitLoadArg(0);
@@ -560,14 +639,31 @@ Type exportContainer
560639
il.Emit(OpCodes.Ldarg_0);
561640
il.Emit(OpCodes.Ldarg_1);
562641
il.Emit(OpCodes.Newobj, exportInfo.DeclaredConstructors.First());
563-
il.Emit(OpCodes.Call, instanceContainer
564-
.GetTypeInfo()
565-
.DeclaredConstructors
566-
.First(info => info.GetParameters()
567-
.FirstOrDefault()
568-
?.ParameterType == exportContainer
569-
)
570-
);
642+
643+
ConstructorInfo importConstructor;
644+
if (importBuilder is not null)
645+
{
646+
var importConstructorBuilder = importBuilder.DefineConstructor(ConstructorAttributes, CallingConventions.Standard, []);
647+
648+
var importConstructorIL = importConstructorBuilder.GetILGenerator();
649+
importConstructorIL.Emit(OpCodes.Ldarg_0);
650+
importConstructorIL.Emit(OpCodes.Call, typeof(object).GetConstructor([])!);
651+
importConstructorIL.Emit(OpCodes.Ret);
652+
653+
importConstructor = importConstructorBuilder;
654+
}
655+
else
656+
{
657+
importConstructor = instanceContainer
658+
.GetTypeInfo()
659+
.DeclaredConstructors
660+
.First(info => info.GetParameters()
661+
.FirstOrDefault()
662+
?.ParameterType == exportContainer
663+
);
664+
}
665+
666+
il.Emit(OpCodes.Call, importConstructor);
571667
il.Emit(OpCodes.Ret);
572668

573669
instance = instanceBuilder.CreateTypeInfo();
@@ -1243,7 +1339,7 @@ .. locals
12431339
}
12441340

12451341
if (reader.Offset - startingOffset != byteLength)
1246-
throw new ModuleLoadException($"Instruction sequence reader ended after readering {reader.Offset - startingOffset} characters, expected {byteLength}.", reader.Offset);
1342+
throw new ModuleLoadException($"Instruction sequence reader ended after reading {reader.Offset - startingOffset} characters, expected {byteLength}.", reader.Offset);
12471343
}
12481344
}
12491345

0 commit comments

Comments
 (0)