Back to Home

Tanish Samir Desai

tanishdesai37@gmail.com | Vadodara, India

Blog

I write about systems, virtualization, and open-source engineering.

Keeping the TLB Across Context Switches: x86 PCID Support in COCONUT-SVSM

Last year I contributed to QEMU outside the official Google Summer of Code program. This year I came back through the front door. I was accepted into GSoC 2026 with the QEMU organization for a large, 12-week project: add x86 Process Context Identifier (PCID) support to COCONUT-SVSM, mentored by Luigi Leonardi and Jörg Rödel.

Whom do you trust in the era of the cloud?

When a workload runs in the cloud it sits on hardware you do not own, under a hypervisor you did not write. The practical question is who, exactly, is in the trusted computing base?

Should you trust the guest operating system? A general-purpose kernel is millions of lines of code. A bug in a driver or a syscall path can leak the workload. Should you trust the cloud provider? They provision the host, hold admin credentials, and can attach a debugger to the hypervisor. Should you trust the hypervisor? In a conventional VM it sees guest memory in the clear, so a compromised or curious host can read keys, tokens, and customer data.

This is the question that stayed with me through my fourth year. I started looking for an answer and came across confidential computing: the idea of trusting the smallest useful layer, the hardware, and treating the OS, hypervisor, and cloud provider as untrusted. COCONUT-SVSM is built for that model: it runs inside the confidential VM and provides trusted services, such as a virtual TPM, so the guest never has to ask the host. Hardware like AMD SEV-SNP encrypts guest memory so the hypervisor cannot read it.

That was my primary reason for choosing this GSoC project. Along with the problem I was allotted, I learned a lot, and that is why trust should be done at the smallest abstraction possible.

Why PCIDs exist

A TLB entry is a cached virtual-to-physical translation. Without a tag, the CPU has no way to know which address space an entry belongs to, so reloading CR3 means invalidating the whole cache. Process Context Identifiers let the processor tag each TLB entry with the active page-table root. Unrelated tasks can then keep their translations resident across a switch, and a dead address space can be flushed by ID instead of by wiping everyone else's cache.

COCONUT-SVSM did not use PCIDs at all. Every task root ran untagged, so each switch paid that full-flush cost even when the outgoing and incoming tasks shared nothing. Assigning an ID per root is not a speedup by itself, it is the groundwork: it is what later allows a flush to target a single address space instead of all of them. It also creates an obligation. When a PCID is handed out again, nothing tagged with that ID may still be valid anywhere in the system, or the new owner would inherit translations from a dead page-table root.

What I built

The work followed the three phases in the proposal, with one extra kernel change that landed early.

Phase 1 — a shared CPUID crate. I created coconut-svsm/cpufeature, a no_std Rust crate of x86 CPUID feature descriptors. The descriptors are generated from the X86-CPUID XML database. Upstream x86-cpuid-db has no Rust emitter and no COCONUT leaves, so I maintain a downstream fork that adds both. Shared types go through rust-common.xslt into common.rs. All leaf constants go through rustleaves.xslt into leaves.rs as flat CpuidFeature values. cpufeature consumes those files. Call sites pass the generated constants into cpu_has_feat() / cpu_get_feat() instead of maintaining magic numbers in the kernel. Platform dispatch goes through a CpuidBackend trait: native and TDX use the default backend; SNP routes architectural leaves through the CPUID table and hypervisor leaves through the GHCB. Feature results are cached for the SVSM lifetime, so a hypervisor cannot change the CPUID answer after boot.

  • cpufeature#1 — initial crate for x86 CPUID feature descriptors (merged)
  • svsm#1137 — migrate kernel CPUID detection onto the crate (merged)
  • cpufeature#3 — GitHub Actions for Rust, PR compliance, and dependency review (merged)

Phase 2 — assign a PCID to each address space. svsm#1157 gives every TaskMM a TaskPcid from a global pool of 4096 IDs. Tasks that share an address space share its PCID. PCID 0 stays reserved for per-CPU transitional page tables and as the fallback when the pool is exhausted. CR3 bit 63 is set on PCID-tagged roots so a switch does not flush that PCID's entries.

The pool needed its own allocator. A spinlock around a tree bitmap is the wrong shape for 4096 one-bit IDs that are claimed on address-space create and released on destroy. I designed FlatBitmapAllocator for that case: a flat array of atomic words, one bit per slot. Allocation walks until it finds a word that is not all-ones, takes the complement so free bits show up as ones, picks the first with trailing_zeros, and claims it with a CAS. A lost race retries the same word. Free is a fetch_and that clears the bit. Padding bits past capacity are marked used at construction so they can never be handed out. Freeing a PCID is RAII: when the address space is destroyed, TaskPcid::drop first broadcasts a PCID-targeted TLB shootdown on every CPU, then returns the bit to the pool. That keeps the invariant that a recycled ID has no live translations under its tag. This PR is under review and is the last piece I expect to land from the summer.

Related kernel work. Before the PCID series, I added svsm#1097 SecretSlice — a Box<[u8]> wrapper that zeroes attestation secrets on drop so decrypted key material does not linger in memory.

What was hard

The encodings for INVPCID and INVLPGB are easy to get silently wrong. I used Linux as the reference: arch/x86/include/asm/invpcid.h for the 16-byte descriptor and type-in-GPR convention, and arch/x86/include/asm/tlb.h for INVLPGB's RAX/ECX/EDX packing (including INVLPGB_FLAG_ASID and pcid << 16 | asid).

Ownership also moved during review. v1 hung the PCID on Task. Review feedback pushed it onto TaskMM, which is the right model: threads of the same task must share a tag. The allocator went from a [bool; 4096] behind a spinlock, to a tree bitmap, and finally to the atomic flat bitmap that is in the PR now. Feature bits for Invlpgb, Pcid, and Invpcid were rebased onto the new crate mid-series so the PCID work and the CPUID work stay on one path.

Thanks to Jörg Rödel, who pinned this during review. On Intel, INVLPG only invalidates the linear address in the PCID currently loaded on that CPU. There is no INVPCID type that drops one address from every PCID. So if a task runs on CPU A, migrates to CPU B, and then unmaps a page, CPU A can keep the old translation under the task's PCID while it is running someone else. The next CR3 write that keeps bit 63 set will not flush that PCID, and the task can reuse the stale entry when it returns. Mapping updates have to shoot down the owning address space's PCID on every CPU, and that shootdown has to finish before the physical page is freed. Until every VMR path does that, CR3 bit 63 stays clear so a switch still drops non-global entries the safe way.

What is left

Phase 2 is in review, not merged. Until #1157 lands, live task roots still share the untagged flush behavior on context switch.

A patch is also still open on x86-cpuid-db. It adds a generator for COCONUT-specific CPUID leaves so cpufeature can consume those descriptors from upstream. Until that lands, I am maintaining a downstream fork to generate them.

A year ago the work was invisible to GSoC's records. This summer it is the opposite: a crate that other COCONUT code can depend on, a kernel that no longer hand-maintains CPUID layouts, and a PCID allocator that makes cheaper context switches possible. That is the work I am submitting.

Acknowledgements

Thanks to my mentors, Jörg Rödel and Luigi Leonardi, for the reviews, the design discussions, and the patience this summer. Thanks to Google for sponsoring the work through Google Summer of Code. Thanks also to the other COCONUT-SVSM reviewers, Stefano Garzarella, Jon Lange, Oliver Steffen, and Peter Fang, whose comments shaped the patches that landed and the ones still in flight.

SVSM commits cpufeature crate PCID PR #1157 CPUID migration #1137 SecretSlice #1097

The Phantom Project of Google Summer of Code: My Journey Beyond the Recognition and Stipend

In May 2025, I faced one of the toughest choices of my student life. I had been selected for Google Summer of Code (GSoC) to work on QEMU, one of the most critical open-source projects powering modern cloud providers. But just days before the program began, I discovered that my campus internship rules at JPMorgan Chase & Co. wouldn't allow any "second job"—even an open-source initiative. The legal department made it clear: continuing with GSoC could mean a policy violation.

That left me with two choices: withdraw from GSoC completely, losing the project, mentorship, and Google's stipend, or walk away from the "official" program but still contribute out of sheer passion. I chose the second. I wrote to my mentor, Paolo Bonzini, explaining that I wanted to continue unpaid, just to complete the work. His response was simple but powerful: "Yes, of course you can. It's a pity that you won't get paid, but I guess it's a contribution you can put on your CV."

And that's how my phantom project began—outside the GSoC framework, but with the same rigor, same weekly calls, and even more freedom. Over the summer of 2025, I built and upstreamed patches that reshaped QEMU's tracing infrastructure:

  • Rust Tracing Backend: I built the entire Rust tracing infrastructure for QEMU by extending its tracetool. This enabled tracepoints for Rust-based device models like the PL011 UART and added support for the most widely used tracing backends — SimpleTrace, Syslog, Log, and Ftrace. This means future QEMU developers can write devices in Rust and still get full tracing capabilities, seamlessly integrating with existing workflows — a step toward safer systems programming in one of the most widely used virtualization platforms.
  • Cross-Platform Improvements: I contributed performance patches that improved macOS build stability and simplified backend generation.
  • Optimized SimpleTrace: By removing six redundant assembly instructions per tracepoint, I made QEMU's tracing faster and more efficient.

These contributions may sound niche, but for developers building cloud infrastructure, they make day-to-day debugging and performance tuning more reliable. In a way, this work became the backbone of my technical identity: contributing to the backbone of the cloud itself.

Looking back, not having the "Google" tag or stipend never felt like a loss. If anything, it made me double down on why I was doing this in the first place: not for the line on my résumé, but for the love of open source and the thrill of building something that thousands of developers will use for years.

That's why I call it The Phantom Project of GSoC: invisible to Google's official records, but very real in its impact on me—and, I hope, on QEMU.

Trace Tool Patch Series My Patchwork Tree