private static ConstructorInfo FromBinary( Reader reader, System.Type instanceContainer, System.Type exportContainer, IEnumerable <RuntimeImport> imports ) { if (reader.ReadUInt32() != Module.Magic) { throw new ModuleLoadException("File preamble magic value is incorrect.", 0); } switch (reader.ReadUInt32()) { case 0x1: //First release case 0xd: //Final pre-release, binary format is identical with first release. break; default: throw new ModuleLoadException("Unsupported version, only version 0x1 and 0xd are accepted.", 4); } uint memoryPagesMinimum = 0; uint memoryPagesMaximum = 0; Signature[] signatures = null; Signature[] functionSignatures = null; KeyValuePair <string, uint>[] exportedFunctions = null; var previousSection = Section.None; var module = AssemblyBuilder.DefineDynamicAssembly( new AssemblyName("CompiledWebAssembly"), AssemblyBuilderAccess.RunAndCollect ) .DefineDynamicModule("CompiledWebAssembly") ; const TypeAttributes classAttributes = TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.BeforeFieldInit ; const MethodAttributes constructorAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName ; const MethodAttributes internalFunctionAttributes = MethodAttributes.Assembly | MethodAttributes.Static | MethodAttributes.HideBySig ; const MethodAttributes exportedFunctionAttributes = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.HideBySig ; var exportsBuilder = module.DefineType("CompiledExports", classAttributes, exportContainer); FieldBuilder memory = null; ILGenerator instanceConstructorIL; { var instanceConstructor = exportsBuilder.DefineConstructor(constructorAttributes, CallingConventions.Standard, System.Type.EmptyTypes); instanceConstructorIL = instanceConstructor.GetILGenerator(); { var usableConstructor = exportContainer.GetTypeInfo().DeclaredConstructors.FirstOrDefault(c => c.GetParameters().Length == 0); if (usableConstructor != null) { instanceConstructorIL.Emit(OpCodes.Ldarg_0); instanceConstructorIL.Emit(OpCodes.Call, usableConstructor); } } } var exports = exportsBuilder.AsType(); var importedFunctions = 0; MethodInfo[] internalFunctions = null; Indirect[] functionElements = null; GlobalInfo[] globalGetters = null; GlobalInfo[] globalSetters = null; CompilationContext context = null; while (reader.TryReadVarUInt7(out var id)) //At points where TryRead is used, the stream can safely end. { var payloadLength = reader.ReadVarUInt32(); if (id != 0 && (Section)id < previousSection) { throw new ModuleLoadException($"Sections out of order; section {(Section)id} encounterd after {previousSection}.", reader.Offset); } switch ((Section)id) { case Section.Type: { signatures = new Signature[reader.ReadVarUInt32()]; for (var i = 0; i < signatures.Length; i++) { signatures[i] = new Signature(reader, (uint)i); } } break; case Section.Import: { if (imports == null) { imports = Enumerable.Empty <RuntimeImport>(); } var importsByName = imports.ToDictionary(import => new Tuple <string, string>(import.ModuleName, import.FieldName)); var count = reader.ReadVarUInt32(); var functionImports = new List <MethodInfo>(checked ((int)count)); for (var i = 0; i < count; i++) { var moduleName = reader.ReadString(reader.ReadVarUInt32()); var fieldName = reader.ReadString(reader.ReadVarUInt32()); if (!importsByName.TryGetValue(new Tuple <string, string>(moduleName, fieldName), out var import)) { throw new CompilerException($"Import not found for {moduleName}::{fieldName}."); } var kind = (ExternalKind)reader.ReadByte(); switch (kind) { case ExternalKind.Function: var typeIndex = reader.ReadVarUInt32(); if (!(import is FunctionImport functionImport)) { throw new CompilerException($"{moduleName}::{fieldName} is expected to be a function, but provided import was not."); } if (!signatures[typeIndex].Equals(functionImport.Type)) { throw new CompilerException($"{moduleName}::{fieldName} did not match the required type signature."); } functionImports.Add(functionImport.Method); break; case ExternalKind.Table: case ExternalKind.Memory: case ExternalKind.Global: throw new ModuleLoadException($"Imported external kind of {kind} is not currently supported.", reader.Offset); default: throw new ModuleLoadException($"Imported external kind of {kind} is not recognized.", reader.Offset); } } importedFunctions = functionImports.Count; internalFunctions = functionImports.ToArray(); } break; case Section.Function: { functionSignatures = new Signature[reader.ReadVarUInt32()]; var importedFunctionCount = internalFunctions == null ? 0 : internalFunctions.Length; if (importedFunctionCount != 0) { Array.Resize(ref internalFunctions, checked (importedFunctionCount + functionSignatures.Length)); } else { internalFunctions = new MethodInfo[functionSignatures.Length]; } for (var i = 0; i < functionSignatures.Length; i++) { var signature = functionSignatures[i] = signatures[reader.ReadVarUInt32()]; var parms = signature.ParameterTypes.Concat(new[] { exports }).ToArray(); internalFunctions[importedFunctionCount + i] = exportsBuilder.DefineMethod( $"👻 {i}", internalFunctionAttributes, CallingConventions.Standard, signature.ReturnTypes.FirstOrDefault(), parms ); } } break; case Section.Table: { var count = reader.ReadVarUInt32(); for (var i = 0; i < count; i++) { var elementType = (ElementType)reader.ReadVarInt7(); switch (elementType) { default: throw new ModuleLoadException($"Element type {elementType} not supported.", reader.Offset); case ElementType.AnyFunction: var setFlags = (ResizableLimits.Flags)reader.ReadVarUInt32(); functionElements = new Indirect[reader.ReadVarUInt32()]; if ((setFlags & ResizableLimits.Flags.Maximum) != 0) { reader.ReadVarUInt32(); //Not used. } break; } } } break; case Section.Memory: { var count = reader.ReadVarUInt32(); if (count > 1) { throw new ModuleLoadException("Multiple memory values are not supported.", reader.Offset); } var setFlags = (ResizableLimits.Flags)reader.ReadVarUInt32(); memoryPagesMinimum = reader.ReadVarUInt32(); if ((setFlags & ResizableLimits.Flags.Maximum) != 0) { memoryPagesMaximum = Math.Min(reader.ReadVarUInt32(), uint.MaxValue / Memory.PageSize); } else { memoryPagesMaximum = uint.MaxValue / Memory.PageSize; } memory = exportsBuilder.DefineField("☣ Memory", typeof(Runtime.UnmanagedMemory), FieldAttributes.Private | FieldAttributes.InitOnly); instanceConstructorIL.Emit(OpCodes.Ldarg_0); Instructions.Int32Constant.Emit(instanceConstructorIL, (int)memoryPagesMinimum); Instructions.Int32Constant.Emit(instanceConstructorIL, (int)memoryPagesMaximum); instanceConstructorIL.Emit(OpCodes.Newobj, typeof(uint?).GetTypeInfo().DeclaredConstructors.Where(info => { var parms = info.GetParameters(); return(parms.Length == 1 && parms[0].ParameterType == typeof(uint)); }).First()); instanceConstructorIL.Emit(OpCodes.Newobj, typeof(Runtime.UnmanagedMemory).GetTypeInfo().DeclaredConstructors.Where(info => { var parms = info.GetParameters(); return(parms.Length == 2 && parms[0].ParameterType == typeof(uint) && parms[1].ParameterType == typeof(uint?)); }).First()); instanceConstructorIL.Emit(OpCodes.Stfld, memory); exportsBuilder.AddInterfaceImplementation(typeof(IDisposable)); var dispose = exportsBuilder.DefineMethod( "Dispose", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot, CallingConventions.HasThis, typeof(void), System.Type.EmptyTypes ); var disposeIL = dispose.GetILGenerator(); disposeIL.Emit(OpCodes.Ldarg_0); disposeIL.Emit(OpCodes.Ldfld, memory); disposeIL.Emit(OpCodes.Call, typeof(Runtime.UnmanagedMemory) .GetTypeInfo() .DeclaredMethods .Where(info => info.ReturnType == typeof(void) && info.GetParameters().Length == 0 && info.Name == nameof(Runtime.UnmanagedMemory.Dispose)) .First()); disposeIL.Emit(OpCodes.Ret); } break; case Section.Global: { var count = reader.ReadVarUInt32(); globalGetters = new GlobalInfo[count]; globalSetters = new GlobalInfo[count]; context = new CompilationContext( exportsBuilder, memory, functionSignatures, internalFunctions, signatures, functionElements, module, globalGetters, globalSetters ); var emptySignature = Signature.Empty; for (var i = 0; i < globalGetters.Length; i++) { var contentType = (ValueType)reader.ReadVarInt7(); var isMutable = reader.ReadVarUInt1() == 1; var getter = exportsBuilder.DefineMethod( $"🌍 Get {i}", internalFunctionAttributes, CallingConventions.Standard, contentType.ToSystemType(), isMutable ? new[] { exports } : null ); globalGetters[i] = new GlobalInfo(contentType, isMutable, getter); var il = getter.GetILGenerator(); var getterSignature = new Signature(contentType); if (isMutable == false) { context.Reset( il, getterSignature, getterSignature.RawParameterTypes ); foreach (var instruction in Instruction.ParseInitializerExpression(reader)) { instruction.Compile(context); context.Previous = instruction.OpCode; } } else //Mutable { var field = exportsBuilder.DefineField( $"🌍 {i}", contentType.ToSystemType(), FieldAttributes.Private | (isMutable ? 0 : FieldAttributes.InitOnly) ); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, field); il.Emit(OpCodes.Ret); var setter = exportsBuilder.DefineMethod( $"🌍 Set {i}", internalFunctionAttributes, CallingConventions.Standard, typeof(void), new[] { contentType.ToSystemType(), exports } ); il = setter.GetILGenerator(); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Stfld, field); il.Emit(OpCodes.Ret); globalSetters[i] = new GlobalInfo(contentType, isMutable, setter); context.Reset( instanceConstructorIL, emptySignature, emptySignature.RawParameterTypes ); context.EmitLoadThis(); var ended = false; foreach (var instruction in Instruction.ParseInitializerExpression(reader)) { if (ended) { throw new CompilerException("Only a single End is allowed within an initializer expression."); } if (instruction.OpCode == OpCode.End) { context.Emit(OpCodes.Stfld, field); ended = true; continue; } instruction.Compile(context); context.Previous = instruction.OpCode; } } } } break; case Section.Export: { var totalExports = reader.ReadVarUInt32(); var xFunctions = new List <KeyValuePair <string, uint> >((int)Math.Min(int.MaxValue, totalExports)); for (var i = 0; i < totalExports; i++) { var name = reader.ReadString(reader.ReadVarUInt32()); var kind = (ExternalKind)reader.ReadByte(); var index = reader.ReadVarUInt32(); switch (kind) { case ExternalKind.Function: xFunctions.Add(new KeyValuePair <string, uint>(name, index)); break; case ExternalKind.Memory: if (index != 0) { throw new ModuleLoadException($"Exported memory must be of index 0, found {index}.", reader.Offset); } if (memory == null) { throw new CompilerException("Cannot export linear memory when linear memory is not defined."); } var memoryGetter = exportsBuilder.DefineMethod("get_" + name, MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Virtual | MethodAttributes.Final, CallingConventions.HasThis, typeof(Runtime.UnmanagedMemory), System.Type.EmptyTypes ); var getterIL = memoryGetter.GetILGenerator(); getterIL.Emit(OpCodes.Ldarg_0); getterIL.Emit(OpCodes.Ldfld, memory); getterIL.Emit(OpCodes.Ret); exportsBuilder.DefineProperty(name, PropertyAttributes.None, typeof(Runtime.UnmanagedMemory), System.Type.EmptyTypes) .SetGetMethod(memoryGetter); break; default: throw new NotSupportedException($"Unsupported or unrecognized export kind {kind}."); } } exportedFunctions = xFunctions.ToArray(); } break; case Section.Element: { if (functionElements == null) { throw new ModuleLoadException("Element section found without an associated table section.", reader.Offset); } var count = reader.ReadVarUInt32(); for (var i = 0; i < count; i++) { var index = reader.ReadVarUInt32(); if (index != 0) { throw new ModuleLoadException($"Index value of anything other than 0 is not supported, {index} found.", reader.Offset); } { var initializer = Instruction.ParseInitializerExpression(reader).ToArray(); if (initializer.Length != 2 || !(initializer[0] is Instructions.Int32Constant c) || c.Value != 0 || !(initializer[1] is Instructions.End)) { throw new ModuleLoadException("Initializer expression support for the Element section is limited to a single Int32 constant of 0 followed by end.", reader.Offset); } } var elements = reader.ReadVarUInt32(); if (elements != functionElements.Length) { throw new ModuleLoadException($"Element count {elements} does not match the indication provided by the earlier table {functionElements.Length}.", reader.Offset); } for (var j = 0; j < functionElements.Length; j++) { var functionIndex = reader.ReadVarUInt32(); functionElements[j] = new Indirect( functionSignatures[functionIndex].TypeIndex, (MethodBuilder)internalFunctions[importedFunctions + functionIndex] ); } } } break; case Section.Code: { var functionBodies = reader.ReadVarUInt32(); if (functionBodies > 0 && functionSignatures == null) { throw new ModuleLoadException("Code section is invalid when Function section is missing.", reader.Offset); } if (functionBodies != functionSignatures.Length) { throw new ModuleLoadException($"Code section has {functionBodies} functions described but {functionSignatures.Length} were expected.", reader.Offset); } if (context == null) //Might have been created by the Global section, if present. { context = new CompilationContext( exportsBuilder, memory, functionSignatures, internalFunctions, signatures, functionElements, module, globalGetters, globalSetters ); } for (var functionBodyIndex = 0; functionBodyIndex < functionBodies; functionBodyIndex++) { var signature = functionSignatures[functionBodyIndex]; var byteLength = reader.ReadVarUInt32(); var startingOffset = reader.Offset; var locals = new Local[reader.ReadVarUInt32()]; for (var localIndex = 0; localIndex < locals.Length; localIndex++) { locals[localIndex] = new Local(reader); } var il = ((MethodBuilder)internalFunctions[importedFunctions + functionBodyIndex]).GetILGenerator(); context.Reset( il, signature, signature.RawParameterTypes.Concat( locals .SelectMany(local => Enumerable.Range(0, checked ((int)local.Count)).Select(_ => local.Type)) ).ToArray() ); foreach (var local in locals.SelectMany(local => Enumerable.Range(0, checked ((int)local.Count)).Select(_ => local.Type))) { il.DeclareLocal(local.ToSystemType()); } foreach (var instruction in Instruction.Parse(reader)) { instruction.Compile(context); context.Previous = instruction.OpCode; } if (reader.Offset - startingOffset != byteLength) { throw new ModuleLoadException($"Instruction sequence reader ended after readering {reader.Offset - startingOffset} characters, expected {byteLength}.", reader.Offset); } } } break; default: throw new ModuleLoadException($"Unrecognized section type {(Section)id}.", reader.Offset); } previousSection = (Section)id; } if (exportedFunctions != null) { for (var i = 0; i < exportedFunctions.Length; i++) { var exported = exportedFunctions[i]; var signature = functionSignatures[exported.Value - importedFunctions]; var method = exportsBuilder.DefineMethod( exported.Key, exportedFunctionAttributes, CallingConventions.HasThis, signature.ReturnTypes.FirstOrDefault(), signature.ParameterTypes ); var il = method.GetILGenerator(); for (var parm = 0; parm < signature.ParameterTypes.Length; parm++) { il.Emit(OpCodes.Ldarg, parm + 1); } il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, internalFunctions[exported.Value]); il.Emit(OpCodes.Ret); } } instanceConstructorIL.Emit(OpCodes.Ret); //Finish the constructor. var exportInfo = exportsBuilder.CreateTypeInfo(); TypeInfo instance; { var instanceBuilder = module.DefineType("CompiledInstance", classAttributes, instanceContainer); var instanceConstructor = instanceBuilder.DefineConstructor(constructorAttributes, CallingConventions.Standard, null); var il = instanceConstructor.GetILGenerator(); var memoryAllocated = checked (memoryPagesMaximum * Memory.PageSize); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Newobj, exportInfo.DeclaredConstructors.First()); il.Emit(OpCodes.Call, instanceContainer .GetTypeInfo() .DeclaredConstructors .First(info => info.GetParameters() .FirstOrDefault() ?.ParameterType == exportContainer ) ); il.Emit(OpCodes.Ret); instance = instanceBuilder.CreateTypeInfo(); } module.CreateGlobalFunctions(); return(instance.DeclaredConstructors.First()); }
internal abstract void Compile(CompilationContext context);