using System.Reflection; using System.Runtime.Loader; using Mono.Cecil; using Mono.Cecil.Cil; internal static class PeriodicSchedulerLogPatchChecks { public static void Run(AssemblyDefinition before, AssemblyDefinition after, Action check) { var original = Target(before); var patched = Target(after); var oldInstructions = original.Body.Instructions; var newInstructions = patched.Body.Instructions; check("PeriodicSchedulerExactlyLogAndTerminalTimesliceInstructionsChanged", oldInstructions.Count == newInstructions.Count && original.Body.CodeSize + 8 == patched.Body.CodeSize && original.Body.MaxStackSize == patched.Body.MaxStackSize && original.Body.InitLocals == patched.Body.InitLocals && original.Body.Variables.Select(local => local.VariableType.FullName).SequenceEqual(patched.Body.Variables.Select(local => local.VariableType.FullName)) && original.Body.ExceptionHandlers.Count == 0 && patched.Body.ExceptionHandlers.Count == 0 && oldInstructions.Where((instruction, index) => index != PeriodicSchedulerLogPatch.ChangedInstructionIndex && index != PeriodicSchedulerLogPatch.TimesliceInstructionIndex) .All(instruction => InstructionShape(instruction, original) == InstructionShape(newInstructions[oldInstructions.IndexOf(instruction)], patched)) && oldInstructions[PeriodicSchedulerLogPatch.ChangedInstructionIndex].GetSize() == newInstructions[PeriodicSchedulerLogPatch.ChangedInstructionIndex].GetSize()); var originalReachable = Reachable(original); var patchedReachable = Reachable(patched); check("PeriodicSchedulerOriginalSerialPrintsReachablePositiveControl", originalReachable.Count(IsSerialWrite) == 5); check("PeriodicSchedulerAllSerialPrintsUnreachable", patchedReachable.All(instruction => !IsSerialWrite(instruction))); check("PeriodicSchedulerTickCounterIncrementRemainsReachable", newInstructions.Take(4).All(patchedReachable.Contains) && newInstructions[3].OpCode == OpCodes.Stsfld && newInstructions[3].Operand is FieldReference counter && counter.Name == "s_onTickLogCount"); check("PeriodicSchedulerRuntimePassBoostAndGlobalAccountingRemainReachable", new[] { "set_TotalRuntime", "set_Pass", "set_IsBoosted", "UpdateGlobalPass" }.All(name => patchedReachable.Any(instruction => instruction.Operand is MethodReference call && call.Name == name))); check("PeriodicSchedulerOtherMethodsUnchanged", original.DeclaringType.Methods.Where(method => method.Name != "OnTick").All(method => Il(method) == Il(patched.DeclaringType.Methods.Single(candidate => candidate.FullName == method.FullName)))); string prior = Il(patched); check("PeriodicSchedulerRepeatedApplyVerifiesWithoutMutation", PeriodicSchedulerLogPatch.Apply(after) && prior == Il(patched)); using (var roundtrip = Clone(after)) check("PeriodicSchedulerRoundtripIsIdempotent", PeriodicSchedulerLogPatch.Apply(roundtrip)); using (var logOnly = Clone(after)) { var terminal = Target(logOnly).Body.Instructions[PeriodicSchedulerLogPatch.TimesliceInstructionIndex]; terminal.OpCode = OpCodes.Ldloc_2; terminal.Operand = null; check("PeriodicSchedulerVerifiedLogOnlyMigrationCompletesTimeslice", !PeriodicSchedulerLogPatch.Apply(logOnly) && PeriodicSchedulerLogPatch.Apply(logOnly) && Il(Target(logOnly)) == Il(patched)); } CheckRejected("PeriodicSchedulerChangedModuloRejected", before, assembly => Target(assembly).Body.Instructions.Single(instruction => instruction.OpCode == OpCodes.Ldc_I4_S && Equals(instruction.Operand, (sbyte)100)).Operand = (sbyte)101); CheckRejected("PeriodicSchedulerChangedAccountingRejected", before, assembly => Target(assembly).Body.Instructions[1].OpCode = OpCodes.Ldc_I4_2); CheckRejected("PeriodicSchedulerChangedAccountingDivisorRejected", before, assembly => Target(assembly).Body.Instructions.Single(instruction => instruction.OpCode == OpCodes.Ldc_I4 && Equals(instruction.Operand, 10_000_000)).Operand = 1_000_000); CheckRejected("PeriodicSchedulerChangedTailThresholdRejected", after, assembly => Target(assembly).Body.Instructions[PeriodicSchedulerLogPatch.TimesliceInstructionIndex].Operand = 2_000_000L); CheckRejected("PeriodicSchedulerChangedTailComparisonRejected", after, assembly => Target(assembly).Body.Instructions[104].OpCode = OpCodes.Clt); CheckRejected("PeriodicSchedulerChangedDebugMessageRejected", before, assembly => Target(assembly).Body.Instructions.Single(instruction => instruction.OpCode == OpCodes.Ldstr && Equals(instruction.Operand, " runQ=")).Operand = " changed="); CheckRejected("PeriodicSchedulerUnknownVersionRejected", before, assembly => assembly.Name.Version = new Version(3, 0, 95, 0)); CheckRejected("PeriodicSchedulerChangedPatchedDestinationRejected", after, assembly => Target(assembly).Body.Instructions[PeriodicSchedulerLogPatch.ChangedInstructionIndex].Operand = Target(assembly).Body.Instructions[83]); CheckRejected("PeriodicSchedulerPartialOrUnknownPatchRejected", after, assembly => { var entry = Target(assembly).Body.Instructions[PeriodicSchedulerLogPatch.ChangedInstructionIndex]; entry.OpCode = OpCodes.Nop; entry.Operand = null; }); // Run the real original/patched callback IL on CoreCLR. Only referenced // data types and method tokens are replaced with these pure host doubles; // no scheduler, interrupt or hardware operations run on the host. using var originalFixture = ExecutableFixture(original); using var patchedFixture = ExecutableFixture(patched); using var originalRunner = new Runner(originalFixture); using var patchedRunner = new Runner(patchedFixture); Case[] cases = [ new("BelowForcedTimeslice", 99, 999_999), new("ForcedTimesliceElapsed", 99, 1_000_000), new("QuantumElapsed", 99, 10_000_000), new("LowerPassPreempts", 99, 1_000_000, NextPass: 0), new("HigherPassKeepsCurrent", 99, 1_000_000, NextPass: 1_000_000), new("BoostDecay", 99, 1_000_000, Boosted: true), new("NullCurrent", 99, 1_000_000, HasCurrent: false), new("NullCpuData", 99, 1_000_000, HasCpu: false), new("ExitedThread", 99, 1_000_000, HasThreadData: false, NextPass: 0), new("CounterOverflow", uint.MaxValue, 1_000_000) ]; var observations = cases.Select(test => (Test: test, Original: originalRunner.Run(test), Patched: patchedRunner.Run(test))).ToArray(); foreach (var observation in observations) check("PeriodicSchedulerActualIl_" + observation.Test.Name, observation.Patched.SerialWrites == 0 && observation.Patched with { Preempt = observation.Original.Preempt } == observation.Original with { SerialWrites = 0 } && observation.Patched.Counter == unchecked(observation.Test.Counter + 1)); check("PeriodicSchedulerExecutablePositiveControlActuallyPrinted", observations[1].Original.SerialWrites == 5 && observations[1].Original.TotalRuntime == 1_000_100 && observations[1].Original.Pass == 602 && !observations[1].Original.Preempt && observations[1].Original.GlobalUpdates == 1); check("PeriodicSchedulerOneMsForcesRotationButSubMsDoesNot", !observations[0].Patched.Preempt && observations[1].Patched.Preempt && !observations[1].Original.Preempt && observations[4].Patched.Preempt && !observations[4].Original.Preempt); check("PeriodicSchedulerPreemptionAndBoostExecutableControl", observations[2].Patched.Preempt && observations[3].Patched.Preempt && !observations[5].Patched.Boosted && !observations[6].Patched.Preempt && !observations[7].Patched.Preempt && observations[8].Patched.Preempt); var oldFair = originalRunner.RunTwoRunnableThreads(0); var newFair = patchedRunner.RunTwoRunnableThreads(0); var oldGap = originalRunner.RunTwoRunnableThreads(1_000_000_000_000); var newGap = patchedRunner.RunTwoRunnableThreads(1_000_000_000_000); check("PeriodicSchedulerTwoRunnableThreadsBothProgressWithRealOneMsAccounting", newFair.FirstTicks == 50 && newFair.SecondTicks == 50 && newFair.FirstRuntime == 50_000_000 && newFair.SecondRuntime == 50_000_000 && newFair.MaximumSameThreadRun == 1); check("PeriodicSchedulerLargeWallGapPassDifferenceCannotMonopolizeRunnablePair", oldGap.FirstTicks == 100 && oldGap.SecondTicks == 0 && newGap.FirstTicks == 50 && newGap.SecondTicks == 50 && newGap.MaximumSameThreadRun == 1 && newGap.FirstRuntime + newGap.SecondRuntime == 100_000_000); check("PeriodicSchedulerOriginalFairPairPositiveControlProgresses", oldFair.FirstTicks > 0 && oldFair.SecondTicks > 0); void CheckRejected(string name, AssemblyDefinition source, Action alter) { using var changed = Clone(source); alter(changed); string unchanged = Il(Target(changed)); bool rejected; try { PeriodicSchedulerLogPatch.Apply(changed); rejected = false; } catch (InvalidOperationException) { rejected = true; } check(name, rejected && unchanged == Il(Target(changed))); } } private static MethodDefinition Target(AssemblyDefinition assembly) => assembly.MainModule.Types.Single(type => type.FullName == PeriodicSchedulerLogPatch.SchedulerType).Methods.Single(method => method.Name == "OnTick"); private static string Il(MethodDefinition method) => !method.HasBody ? "" : string.Join("\n", method.Body.Instructions.Select(instruction => instruction.ToString())); private static string InstructionShape(Instruction instruction, MethodDefinition method) => instruction.OpCode.Code + ":" + (instruction.Operand switch { Instruction branch => "#" + method.Body.Instructions.IndexOf(branch), VariableDefinition local => "$" + local.Index, MemberReference member => member.FullName, null => "", var operand => operand.ToString() }); private static bool IsSerialWrite(Instruction instruction) => instruction.Operand is MethodReference call && call.DeclaringType.FullName == "Cosmos.Kernel.Core.IO.Serial"; private static AssemblyDefinition Clone(AssemblyDefinition assembly) { // Cecil reads method bodies lazily; the returned assembly retains this // backing stream until its own disposal. var stream = new MemoryStream(); assembly.Write(stream); stream.Position = 0; return AssemblyDefinition.ReadAssembly(stream, new ReaderParameters { InMemory = true, ReadingMode = ReadingMode.Immediate }); } private static HashSet Reachable(MethodDefinition method) { var found = new HashSet(); var queue = new Queue(); queue.Enqueue(method.Body.Instructions[0]); while (queue.TryDequeue(out var instruction)) { if (!found.Add(instruction)) continue; if (instruction.Operand is Instruction target) queue.Enqueue(target); if (instruction.Operand is Instruction[] targets) foreach (var branch in targets) queue.Enqueue(branch); if (instruction.OpCode.FlowControl is not (FlowControl.Branch or FlowControl.Return or FlowControl.Throw) && instruction.Next is { } next) queue.Enqueue(next); } return found; } private static AssemblyDefinition ExecutableFixture(MethodDefinition source) { var fixture = AssemblyDefinition.CreateAssembly(new AssemblyNameDefinition("SchedulerFixture_" + Guid.NewGuid().ToString("N"), new Version(1, 0)), "SchedulerFixture", ModuleKind.Dll); var module = fixture.MainModule; var type = new TypeDefinition("Fixture", "Callback", Mono.Cecil.TypeAttributes.Public | Mono.Cecil.TypeAttributes.Abstract | Mono.Cecil.TypeAttributes.Sealed, module.TypeSystem.Object); module.Types.Add(type); var counter = new FieldDefinition("Counter", Mono.Cecil.FieldAttributes.Public | Mono.Cecil.FieldAttributes.Static, module.TypeSystem.UInt32); type.Fields.Add(counter); var method = new MethodDefinition("OnTick", Mono.Cecil.MethodAttributes.Public | Mono.Cecil.MethodAttributes.Static, module.TypeSystem.Boolean); foreach (var parameter in new[] { typeof(PeriodicSchedulerFixture.Scheduler), typeof(PeriodicSchedulerFixture.CpuState), typeof(PeriodicSchedulerFixture.SchedulerThread), typeof(ulong) }) method.Parameters.Add(new("arg" + method.Parameters.Count, Mono.Cecil.ParameterAttributes.None, module.ImportReference(parameter))); type.Methods.Add(method); method.Body.InitLocals = source.Body.InitLocals; method.Body.MaxStackSize = source.Body.MaxStackSize; var locals = source.Body.Variables.ToDictionary(local => local, local => new VariableDefinition(local.VariableType.FullName switch { "Cosmos.Kernel.Core.Scheduler.Stride.StrideCpuData" => module.ImportReference(typeof(PeriodicSchedulerFixture.CpuData)), "Cosmos.Kernel.Core.Scheduler.Stride.StrideThreadData" => module.ImportReference(typeof(PeriodicSchedulerFixture.ThreadData)), "System.UInt64" => module.TypeSystem.UInt64, _ => throw new InvalidOperationException("Unknown scheduler fixture local type.") })); foreach (var local in locals.Values) method.Body.Variables.Add(local); var instructions = source.Body.Instructions.ToDictionary(instruction => instruction, _ => Instruction.Create(OpCodes.Nop)); foreach (var instruction in source.Body.Instructions) { var clone = instructions[instruction]; clone.OpCode = instruction.OpCode; clone.Operand = instruction.Operand switch { Instruction branch => instructions[branch], Instruction[] branches => branches.Select(branch => instructions[branch]).ToArray(), VariableDefinition local => locals[local], ParameterDefinition parameter => method.Parameters[parameter.Index + 1], FieldReference field when field.Name == "s_onTickLogCount" => counter, MethodReference call => FixtureCall(call), null => null, sbyte or byte or int or long or string => instruction.Operand, _ => throw new InvalidOperationException("Unknown scheduler fixture operand.") }; method.Body.Instructions.Add(clone); } return fixture; MethodReference FixtureCall(MethodReference call) { Type hostType = call.DeclaringType.FullName switch { PeriodicSchedulerLogPatch.SchedulerType when call.Name is "CpuDataOf" or "ThreadDataOf" => typeof(PeriodicSchedulerFixture), PeriodicSchedulerLogPatch.SchedulerType => typeof(PeriodicSchedulerFixture.Scheduler), "Cosmos.Kernel.Core.Scheduler.SchedulerThread" => typeof(PeriodicSchedulerFixture.SchedulerThread), "Cosmos.Kernel.Core.Scheduler.Stride.StrideThreadData" => typeof(PeriodicSchedulerFixture.ThreadData), "Cosmos.Kernel.Core.Scheduler.Stride.StrideCpuData" => typeof(PeriodicSchedulerFixture.CpuData), "System.Collections.Generic.List`1" => typeof(List), "Cosmos.Kernel.Core.IO.Serial" => typeof(PeriodicSchedulerFixture), _ => throw new InvalidOperationException("Unknown scheduler fixture call type: " + call.FullName) }; return module.ImportReference(hostType.GetMethod(call.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static) ?? throw new InvalidOperationException("Missing scheduler fixture method: " + call.FullName)); } } private sealed class Runner : IDisposable { private readonly AssemblyLoadContext context = new("SchedulerCallbackControl", isCollectible: true); private readonly MethodInfo callback; private readonly FieldInfo counter; public Runner(AssemblyDefinition fixture) { using var bytes = new MemoryStream(); fixture.Write(bytes); bytes.Position = 0; var type = context.LoadFromStream(bytes).GetType("Fixture.Callback", throwOnError: true)!; callback = type.GetMethod("OnTick")!; counter = type.GetField("Counter")!; } public Observation Run(Case test) { var scheduler = new PeriodicSchedulerFixture.Scheduler { Now = 10_000_000 }; var data = new PeriodicSchedulerFixture.ThreadData { Pass = 500, Stride = 1024, IsBoosted = test.Boosted }; var current = test.HasCurrent ? new PeriodicSchedulerFixture.SchedulerThread { Id = 7, TotalRuntime = 100, Data = test.HasThreadData ? data : null } : null; var cpuData = test.HasCpu ? new PeriodicSchedulerFixture.CpuData() : null; if (cpuData is not null && test.NextPass is { } pass) cpuData.RunQueue.Add(new() { Id = 8, Data = new() { Pass = pass } }); var cpu = new PeriodicSchedulerFixture.CpuState { Data = cpuData }; PeriodicSchedulerFixture.SerialWrites = 0; counter.SetValue(null, test.Counter); bool preempt = (bool)callback.Invoke(null, [scheduler, cpu, current, test.Elapsed])!; return new(preempt, (uint)counter.GetValue(null)!, current?.TotalRuntime ?? 0, data.Pass, data.IsBoosted, scheduler.GlobalUpdates, PeriodicSchedulerFixture.SerialWrites); } public FairnessObservation RunTwoRunnableThreads(long secondInitialPass) { var scheduler = new PeriodicSchedulerFixture.Scheduler { Now = 10_000_000 }; var first = new PeriodicSchedulerFixture.SchedulerThread { Id = 7, Data = new() { Stride = 1024, Pass = 0 } }; var second = new PeriodicSchedulerFixture.SchedulerThread { Id = 8, Data = new() { Stride = 1024, Pass = secondInitialPass } }; var data = new PeriodicSchedulerFixture.CpuData(); var cpu = new PeriodicSchedulerFixture.CpuState { Data = data }; var current = first; data.RunQueue.Add(second); int firstTicks = 0, secondTicks = 0, consecutive = 0, maximumConsecutive = 0; uint previous = 0; for (int tick = 0; tick < 100; tick++) { if (current == first) firstTicks++; else secondTicks++; consecutive = previous == current.Id ? consecutive + 1 : 1; previous = current.Id; maximumConsecutive = Math.Max(maximumConsecutive, consecutive); bool preempt = (bool)callback.Invoke(null, [scheduler, cpu, current, 1_000_000UL])!; scheduler.Now += 1_000_000; if (!preempt) continue; // With exactly two runnable threads, pinned PickNext removes // the sole queued entry before OnThreadYield inserts prev. var next = data.RunQueue[0]; data.RunQueue.RemoveAt(0); data.RunQueue.Add(current); current = next; } return new(firstTicks, secondTicks, first.TotalRuntime, second.TotalRuntime, maximumConsecutive); } public void Dispose() => context.Unload(); } private sealed record Case(string Name, uint Counter, ulong Elapsed, long? NextPass = null, bool Boosted = false, bool HasCurrent = true, bool HasCpu = true, bool HasThreadData = true); private sealed record Observation(bool Preempt, uint Counter, ulong TotalRuntime, long Pass, bool Boosted, int GlobalUpdates, int SerialWrites); private sealed record FairnessObservation(int FirstTicks, int SecondTicks, ulong FirstRuntime, ulong SecondRuntime, int MaximumSameThreadRun); } public static class PeriodicSchedulerFixture { public static int SerialWrites; public static void WriteString(string text) => SerialWrites++; public static void WriteNumber(uint number, bool hex) => SerialWrites++; public static CpuData? CpuDataOf(CpuState cpu) => cpu.Data; public static ThreadData? ThreadDataOf(SchedulerThread thread) => thread.Data; public sealed class Scheduler { public ulong Now; public int GlobalUpdates; public ulong GetTimestamp() => Now; public void UpdateGlobalPass(CpuData cpu) => GlobalUpdates++; } public sealed class CpuState { public CpuData? Data; } public sealed class CpuData { public List RunQueue { get; } = []; } public sealed class SchedulerThread { public uint Id { get; set; } public ulong TotalRuntime { get; set; } public ThreadData? Data; } public sealed class ThreadData { public long Pass { get; set; } public ulong Stride { get; set; } public bool IsBoosted { get; set; } public ulong LastWakeup { get; set; } } }