Explore impact of use `zerocopy` in `gdbstub_arch` deserialize impls
At the moment, most Arch implementations in gdbstub_arch use a variant of the following field-by-field pattern to deserialize registers from the wire into a Rust struct:
let mut regs = bytes[0..0x80]
.chunks_exact(8)
.map(|x| u64::from_le_bytes(x.try_into().unwrap()));
for reg in self.regs.iter_mut() {
*reg = regs.next().ok_or(())?;
}
self.rip = u64::from_le_bytes(bytes[0x80..0x88].try_into().unwrap());
self.eflags = u32::from_le_bytes(bytes[0x88..0x8C].try_into().unwrap());
self.segments.gdb_deserialize(&bytes[0x8C..0xA4])?;
While I haven't looked at the generated asm yet, my suspicion is that LLVM isn't going to be clever enough to realize that this entire block of code can be implemented as a single memcpy from the bytes buffer, into the backing register struct (especially since, at this time, the backing structs aren't actually marked at repr(C, packed)).
It seems plausible that if we leverage zerocopy to write a safe impl of a buf-to-struct memcpy, we might see a noticeable simplification and reduction in generated asm.
Unfortunately - this would necessitate a breaking change, as adding repr(C, packed) to structs, and/or rearranging the layout of existing register structs would be a semver incompatible refactor.
An interesting follow-up thought: would it be possible to enable a truly zerocopy path here? e.g: change gdbstub APIs to expose the register struct with a lifetime tied back to the underlying packet buffer?
Regarding the serialize path - things are complicated by the fact that serialization is currently a streaming operation, where the gdb_serialize code invokes a callback in order to write out hex-bytes one at a time.
Theoretically, if we refactor the APIs to require the gdb_serialize code to require the use of zerocopy, it would be possible to tweak the contract to have gdbstub bulk-convert a returned &[u8].
More thought is required here.