Context switch
A context switch moves a CPU from one thread to another. The registers of the thread leaving are saved where the kernel can find them again, usually on that thread's own stack or in its control block; the stack pointer is pointed at the arriving thread's saved state, and its registers are restored, so it carries on exactly where it stopped. Kernels reach a switch in two ways. An involuntary switch happens on an interrupt: the interrupt entry has already saved the interrupted thread's registers, so the kernel only has to return into a different saved state. A voluntary switch happens when a thread calls into the kernel to block or yield, through a routine that saves the registers itself.
A switch costs more than the registers it copies. The arriving thread finds the caches and the address translation buffers filled with the other thread's data, and runs slower until it has warmed them again, which is why a scheduler does not switch more often than its policy needs.
The trap is that deciding to switch and switching are separate steps. A scheduler can decide during a tick, or when a thread is woken, while the switch itself happens only when the kernel leaves the interrupt. Everything between the decision and the switch has to agree with it: the thread recorded as current, the thread put back in the queue and the stack the CPU will resume must all describe the same switch, or the CPU resumes a thread that the bookkeeping places somewhere else.
In Cosmos every switch happens on an interrupt exit. The interrupt stub saves the thread's registers, the SIMD state included, on the thread's stack; the manager calls PickNext, records where the old thread's state lies in its SchedulerThread.StackPointer and stages the new one, and the stub restores from it on the way out. A thread's first run, which has no saved frame to return into, jumps to its entry point instead. Any interrupt's exit can carry a switch: the timer tick when OnTick returns true, and any hardware interrupt once a wake, a block, a Thread.Yield or a policy swap has asked for a reschedule, in which case PickNext runs whatever OnTick answered, or without OnTick at all when the interrupt is not the tick. There is no voluntary switch: a thread that blocks raises an interrupt on its own CPU to switch away (the idle thread halts instead), and Thread.Yield returns at once and leaves the switch to the next interrupt exit. The policy never touches a register. See The tick and What there is not: a voluntary switch.