using Mono.Cecil; using Mono.Cecil.Cil; /// /// Single-CPU context virtualization for the pinned runtime's otherwise global /// exception dispatcher. All storage is allocated before a thread is runnable; /// the interrupt-side switch contains field copies and two tiny native calls. /// internal static class ThreadEhRuntimePatch { public const string SwitchName = "XfeSwitchThreadEhV1"; public const string FieldPrefix = "_xfeThreadEhV1_"; private static readonly string[] StateNames = ["s_isHandlingException", "s_activeCatchFramePointers", "s_activeCatchHandlers", "s_activeCatchExInfos", "s_activeCatchCount", "s_xfeEhTraceTypeCountV1", "s_xfeEhTraceTypesV1"]; public static bool Apply(AssemblyDefinition assembly) { var module = assembly.MainModule; var eh = module.Types.Single(type => type.FullName == "Cosmos.Kernel.Core.Runtime.ExceptionHandling.ExceptionHelper"); var thread = module.Types.Single(type => type.FullName == "Cosmos.Kernel.Core.Scheduler.SchedulerThread"); var cpu = module.Types.Single(type => type.FullName == "Cosmos.Kernel.Core.Scheduler.PerCpuState"); var setter = cpu.Methods.Single(method => method.Name == "set_CurrentThread"); var current = cpu.Fields.Single(field => field.Name == "k__BackingField"); var constructor = thread.Methods.Single(method => method.IsConstructor && !method.IsStatic); var originals = StateNames.Select(name => eh.Fields.Single(field => field.Name == name)).ToArray(); var existing = eh.Methods.SingleOrDefault(method => method.Name == SwitchName); if (existing is not null) { if (thread.Fields.Count(field => field.Name.StartsWith(FieldPrefix, StringComparison.Ordinal)) != StateNames.Length + 1 || setter.Body.Instructions.Count(instruction => instruction.Operand is MethodReference method && method.FullName == existing.FullName) != 1 || existing.Body.Instructions.Any(instruction => instruction.OpCode.Code is Code.Newobj or Code.Newarr or Code.Throw) || originals.Any(field => field.IsInitOnly) || constructor.Body.Instructions.Count(instruction => instruction.OpCode == OpCodes.Newarr) != 4 || eh.Methods.Count(method => method.Name == "XfeThreadEhContractVersionV1" || method.Name == "XfeCurrentSchedulerThreadIdV1") != 2) throw new InvalidOperationException("Unexpected per-thread EH patch shape."); return true; } if (constructor.Body.Instructions.Count != 3 || constructor.Body.Instructions[^1].OpCode != OpCodes.Ret || setter.Body.Instructions.Count != 4 || setter.Body.Instructions[2].OpCode != OpCodes.Stfld || setter.Body.Instructions[2].Operand is not FieldReference stored || stored.FullName != current.FullName) throw new InvalidOperationException("Pinned scheduler constructor/current-thread setter changed; refusing to guess."); var state = originals.Select(field => new FieldDefinition(FieldPrefix + field.Name, FieldAttributes.Assembly, field.FieldType)).ToArray(); foreach (var field in state) thread.Fields.Add(field); var nativeHead = new FieldDefinition(FieldPrefix + "NativeExInfoHead", FieldAttributes.Assembly, module.TypeSystem.UIntPtr); thread.Fields.Add(nativeHead); // The NativeAOT optimizer must not treat the currently installed arrays // as immutable globals: they change at each actual thread switch. foreach (var field in originals) field.Attributes &= ~FieldAttributes.InitOnly; var ctorIl = constructor.Body.GetILProcessor(); var ctorReturn = constructor.Body.Instructions[^1]; for (int index = 0; index < state.Length; index++) if (state[index].FieldType is ArrayType array) { ctorIl.InsertBefore(ctorReturn, ctorIl.Create(OpCodes.Ldarg_0)); ctorIl.InsertBefore(ctorReturn, ctorIl.Create(OpCodes.Ldc_I4, StateNames[index] == "s_xfeEhTraceTypesV1" ? EhRuntimePatch.TraceLimit : EhRuntimePatch.Limit)); ctorIl.InsertBefore(ctorReturn, ctorIl.Create(OpCodes.Newarr, array.ElementType)); ctorIl.InsertBefore(ctorReturn, ctorIl.Create(OpCodes.Stfld, state[index])); } constructor.Body.MaxStackSize = 2; var getHead = Import("XfeGetNativeExInfoHeadV1", "XfeRuntimeGetExInfoHead", module.TypeSystem.UIntPtr); var setHead = Import("XfeSetNativeExInfoHeadV1", "XfeRuntimeSetExInfoHead", module.TypeSystem.Void); setHead.Parameters.Add(new("value", ParameterAttributes.None, module.TypeSystem.UIntPtr)); var change = new MethodDefinition(SwitchName, MethodAttributes.Assembly | MethodAttributes.Static | MethodAttributes.HideBySig, module.TypeSystem.Void); change.Parameters.Add(new("previous", ParameterAttributes.None, thread)); change.Parameters.Add(new("next", ParameterAttributes.None, thread)); eh.Methods.Add(change); var il = change.Body.GetILProcessor(); var end = il.Create(OpCodes.Ret); var realSwitch = il.Create(OpCodes.Ldarg_0); il.Append(il.Create(OpCodes.Ldarg_1)); il.Append(il.Create(OpCodes.Brfalse, end)); il.Append(il.Create(OpCodes.Ldarg_0)); il.Append(il.Create(OpCodes.Ldarg_1)); il.Append(il.Create(OpCodes.Beq, end)); il.Append(il.Create(OpCodes.Ldarg_0)); il.Append(il.Create(OpCodes.Brtrue, realSwitch)); // The first null->idle assignment adopts the already running boot // thread's state instead of resetting a live boot exception chain. Save(1); il.Append(il.Create(OpCodes.Br, end)); il.Append(realSwitch); il.Append(il.Create(OpCodes.Pop)); Save(0); for (int index = 0; index < state.Length; index++) { il.Append(il.Create(OpCodes.Ldarg_1)); il.Append(il.Create(OpCodes.Ldfld, state[index])); il.Append(il.Create(OpCodes.Stsfld, originals[index])); } il.Append(il.Create(OpCodes.Ldarg_1)); il.Append(il.Create(OpCodes.Ldfld, nativeHead)); il.Append(il.Create(OpCodes.Call, setHead)); il.Append(end); change.Body.MaxStackSize = 2; var setterIl = setter.Body.GetILProcessor(); var first = setter.Body.Instructions[0]; setterIl.InsertBefore(first, setterIl.Create(OpCodes.Ldarg_0)); setterIl.InsertBefore(first, setterIl.Create(OpCodes.Ldfld, current)); setterIl.InsertBefore(first, setterIl.Create(OpCodes.Ldarg_1)); setterIl.InsertBefore(first, setterIl.Create(OpCodes.Call, change)); setter.Body.MaxStackSize = Math.Max(2, setter.Body.MaxStackSize); var version = new MethodDefinition("XfeThreadEhContractVersionV1", MethodAttributes.Assembly | MethodAttributes.Static | MethodAttributes.HideBySig, module.TypeSystem.Int32); var export = new CustomAttribute(module.ImportReference(typeof(System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute).GetConstructor(Type.EmptyTypes)!)); export.Fields.Add(new("EntryPoint", new CustomAttributeArgument(module.TypeSystem.String, "XfeRuntimeThreadEhContractVersion"))); version.CustomAttributes.Add(export); eh.Methods.Add(version); var versionIl = version.Body.GetILProcessor(); var disabled = versionIl.Create(OpCodes.Ldc_I4_0); var scheduler = module.Types.Single(type => type.FullName == "Cosmos.Kernel.Core.Scheduler.SchedulerManager"); versionIl.Append(versionIl.Create(OpCodes.Call, scheduler.Methods.Single(method => method.Name == "get_IsRunning"))); versionIl.Append(versionIl.Create(OpCodes.Brfalse, disabled)); versionIl.Append(versionIl.Create(OpCodes.Call, scheduler.Methods.Single(method => method.Name == "get_CpuCount"))); versionIl.Append(versionIl.Create(OpCodes.Ldc_I4_1)); versionIl.Append(versionIl.Create(OpCodes.Bne_Un, disabled)); versionIl.Append(versionIl.Create(OpCodes.Ldc_I4_1)); versionIl.Append(versionIl.Create(OpCodes.Ret)); versionIl.Append(disabled); versionIl.Append(versionIl.Create(OpCodes.Ret)); version.Body.MaxStackSize = 2; var identity = new MethodDefinition("XfeCurrentSchedulerThreadIdV1", MethodAttributes.Assembly | MethodAttributes.Static | MethodAttributes.HideBySig, module.TypeSystem.Int32); var identityExport = new CustomAttribute(module.ImportReference(typeof(System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute).GetConstructor(Type.EmptyTypes)!)); identityExport.Fields.Add(new("EntryPoint", new CustomAttributeArgument(module.TypeSystem.String, "XfeRuntimeCurrentSchedulerThreadId"))); identity.CustomAttributes.Add(identityExport); eh.Methods.Add(identity); var identityIl = identity.Body.GetILProcessor(); var haveCpu = identityIl.Create(OpCodes.Callvirt, cpu.Methods.Single(method => method.Name == "get_CurrentThread")); var haveThread = identityIl.Create(OpCodes.Callvirt, thread.Methods.Single(method => method.Name == "get_Id")); identityIl.Append(identityIl.Create(OpCodes.Call, scheduler.Methods.Single(method => method.Name == "get_CurrentCpuState"))); identityIl.Append(identityIl.Create(OpCodes.Dup)); identityIl.Append(identityIl.Create(OpCodes.Brtrue, haveCpu)); identityIl.Append(identityIl.Create(OpCodes.Pop)); identityIl.Append(identityIl.Create(OpCodes.Ldc_I4_0)); identityIl.Append(identityIl.Create(OpCodes.Ret)); identityIl.Append(haveCpu); identityIl.Append(identityIl.Create(OpCodes.Dup)); identityIl.Append(identityIl.Create(OpCodes.Brtrue, haveThread)); identityIl.Append(identityIl.Create(OpCodes.Pop)); identityIl.Append(identityIl.Create(OpCodes.Ldc_I4_0)); identityIl.Append(identityIl.Create(OpCodes.Ret)); identityIl.Append(haveThread); identityIl.Append(identityIl.Create(OpCodes.Ret)); identity.Body.MaxStackSize = 2; return false; void Save(int argument) { for (int index = 0; index < state.Length; index++) { il.Append(il.Create(argument == 0 ? OpCodes.Ldarg_0 : OpCodes.Ldarg_1)); il.Append(il.Create(OpCodes.Ldsfld, originals[index])); il.Append(il.Create(OpCodes.Stfld, state[index])); } il.Append(il.Create(argument == 0 ? OpCodes.Ldarg_0 : OpCodes.Ldarg_1)); il.Append(il.Create(OpCodes.Call, getHead)); il.Append(il.Create(OpCodes.Stfld, nativeHead)); } MethodDefinition Import(string name, string symbol, TypeReference returns) { var method = new MethodDefinition(name, MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.HideBySig | MethodAttributes.PInvokeImpl, returns) { ImplAttributes = MethodImplAttributes.PreserveSig }; var library = module.ModuleReferences.SingleOrDefault(reference => reference.Name == "libSystem.Native"); if (library is null) { library = new ModuleReference("libSystem.Native"); module.ModuleReferences.Add(library); } method.PInvokeInfo = new PInvokeInfo(PInvokeAttributes.CallConvCdecl | PInvokeAttributes.NoMangle, symbol, library); method.CustomAttributes.Add(new CustomAttribute(module.ImportReference(typeof(System.Runtime.InteropServices.SuppressGCTransitionAttribute).GetConstructor(Type.EmptyTypes)!))); eh.Methods.Add(method); return method; } } }