@@ -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