Replies: 6 comments
-
I thought maybe we could get away with a This is a bad idea because actually, you need to be able to derive an I kind of don't like any of those approaches and am leaning in favor of just eliminating the IPC String and using the Does open the door to the discussion about how we deal with messages that take multiple pages to send, and how do we deal with Strings that really should be strictly limited to a length because of other API issues. But I think maybe the right thing to do is to make the length limiting a thing that is done at a level of abstraction above String. Again, a bigger re-write of the code base, but I think cleaner and more maintainable overall. |
Beta Was this translation helpful? Give feedback.
-
The ability to work with flattened versions is very nice indeed. When this was first designed, we didn't even have libstd, so we didn't even have For performant messages, the best approach is still to manually serialize and deserialize like we do in libstd itself for networking. rkyv is used for convenience, so I'm largely okay with the sledgehammer approach. One thing to note -- libstd manually packs these structs in at least one case. The dns lookup process writes into a buffer and reads the response, so in that sense the existing behavior will need to be maintained. Fortunately I think that's the only place where that's used, since the other instances of Nice job getting the API to work with the new version. |
Beta Was this translation helpful? Give feedback.
-
I think there's actually a very important pair of questions to ask: What is this trying to accomplish, and what are the threat models? When you link two crates together, Rust will ensure the types map between them. It may decide to reorder fields, but even within the same crate things like pointers are valid. With C, when you link a library, you're relying on the header to not lie. It's perfectly valid to call a function with completely invalid arguments, and you might even get away with it some of the time. C does not enforce ABI compatibility, and considers the ABI boundary to be trustable. The use of a crate like
There are a number of options:
|
Beta Was this translation helpful? Give feedback.
-
I think the threat model in the case of I recall early on we tried using transmute on objects, but kept on getting tripped up on some subtleties - alignment, uninit, pinned things and pointers. It feels fragile, and it is
I think the main benefit of I suppose Own specialized system - I think we'd be basically re-inventing the |
Beta Was this translation helpful? Give feedback.
-
Hi! Is it worth it to migrate to a new rkyv?If the new
Is there a specific example in which this functionality comes in handy? What should we do about the xous-ipc String type, now that we have support for Rust String?
I've had some exposure to the IMO getting rid of We could leave it in place and discourage its usage via documentation, and follow the boyscout rule when we encounter usage in the wild: if new code touches a pre-existing I'm also expecting a natural decrease of Are there any API modifications we can do to make things more ergonomic (e.g. changing behavior of
|
Beta Was this translation helpful? Give feedback.
-
As a second option, I've come up with flatipc that combines (2) and (4). It allows for transparent usage of traits via The |
Beta Was this translation helpful? Give feedback.
-
Xous currently uses
rkyv
0.4.3 as a method for serializing Rust objects into memory messages, so that structures can be conveniently passed between processes.The subject of upgrading
rkyv
was tabled untilrkvv
"stabilized" - according to the maintainer, 0.8 is the big stabilization release (see rkyv/rkyv#67). As a side note, there's a whole bunch of dependabot flags generated (https://github.com/betrusted-io/xous-core/security/dependabot) related torkyv
. None of them actually apply to how we use it, but a side effect of upgrading is maybe we'll clear some of those too.So, now it's time to have a look inside
rkyv
and see where it's at.I've taken a stab at doing a trial integration of
rkyv
with the Xous API in a new directly I've created calledplanning
. The intention of theplanning
directory is a place where we can stick experiments that aren't built into the tree, but is in themain
branch of the repo so we can refer to it easily in discussions.The Opportunities
There are some significant opportunities that come from upgrading to rkyv-0.8:
Vec
,String
) between processes without having to manually serialize/deserialize them.flat
version of the structures - i.e., the zerocopy versions that are created with theas_flat()
method - are much more usable due to the ability toDerive
traits likeEq
,PartialEq
on themunsafe
operation in the transmutation of theBuffers
because the latest rkyv offers buffer validation. This comes at a computational overhead.A little more on
flat
versusoriginal
structures - as a reminder, this is the routine that generatesflat
versions:xous-core/xous-ipc/src/buffer.rs
Lines 228 to 238 in fd0eaaa
And this generates the
original
:xous-core/xous-ipc/src/buffer.rs
Lines 243 to 252 in fd0eaaa
Flat versions map the
ArchivedT
-style trait directly onto the received data pages mapped into the receiver's process space. This means that no time or space is wasted deserializing the data.Original versions do a full deserialization, creating effectively a second, redundant copy of the passed structure.
Unfortunately, to a first order, 98% of the IPC calls in Xous use the
original
version, because theflat
version has typeArchivedT
, but we actually needT
. The latestrkyv
creates an opportunity for moreflat
usage byDerive
-ing more useful attributes onArchivedT
, such asEq
,PartialEq
, etc. which means we can now pass someEnum
of say,EnumT
and on the receiving side do more operations onArchivedEnumT
because the traits now exist to do that.The Difficulties
An attempt was made to refactor in a
planning
crate theBuffer
routine fromxous-ipc
, which forms the backbone of IPC calls between crates in Xous. The code can be seen here:https://github.com/betrusted-io/xous-core/blob/main/planning/rkyv-migration2/src/buffer.rs
This version of
Buffer
creates a "sham" memory region whose intent is to demonstrate a complex, compound structure being mapped into a binary format, manipulated, and returned. I picked theTextView
object as it is one of the richest objects in the system which also includes a String-like object (uses the Xous IPC String in fact).The
Buffer
routine patches thesend_message
routine to "fake" the sending and instead just parse theTextView
.The supporting definitions for the
TextView
were copied here:https://github.com/betrusted-io/xous-core/blob/main/planning/rkyv-migration2/src/testcases.rs
In general, the good news is that the API more or less makes it through the migration intact. However, there's a couple of changes that might make sense, but I'm unsure on how to proceed, hence, the discussion.
String
routine entirely? Or leave it around for backward compatibility.into_buf()
routine so that it is "infallible"?Deprecating IPC String
The
xous-ipc
String was always a bit weird because you had to define the maximumString
length and the serialization is fallible in case you exceed the length of theString
. For example, inxous-names
, aRegistration
had to have a defined maximum length for the server name:xous-core/api/xous-api-names/src/api.rs
Lines 86 to 90 in fd0eaaa
In some cases, this is a feature, because if you are mapping to a fixed-length target, this exposes that limitation an the API level. But in other places, it's just an arbitrary number we whack in as a guess to try and round out the size of the mapped structure to not exceed a page, like this arbitrary limit on String length in USB console strings:
xous-core/services/usb-device-xous/src/api.rs
Lines 150 to 156 in fd0eaaa
or this wart in TextView:
xous-core/services/graphics-server/src/api/text.rs
Lines 116 to 120 in fd0eaaa
I think there is some merit to just remove the xous-ipc version of String and use only Rust native String, but it would touch almost every crate to strip out that function, so looking for thoughts and opinions about doing that.
Infallible
into_buf()
This is the current implementation of
into_buf()
:xous-core/xous-ipc/src/buffer.rs
Lines 169 to 179 in fd0eaaa
It fails only if there is some failure in serialization, which in practice I think there isn't ever one, so it's layer of error handling code that never gets used. Also, what do you do anyways if there's a serialization error? Is there even a reasonable way to propagate that up the stack?
A thought I have is perhaps to rework the API to make
into_buf()
infallible-or-panic. This has at least two ramifications off the top of my head.Error handling in serialization would need to be handled inside
into_buf()
. But I think that's a reasonable take? Becauserkyv
now supports variable-length structures, serialization can clearly fail. However, the recovery from that seems like it should be algorithmic and handled within theinto_buf()
function instead of kicking it back to every caller. The method would work like this:This may or may not be the right behavior. The advantage of "serialize or die" is that it's simple and works in almost every case. The disadvantage is that you're deprived of the opportunity to modify the serialization request in case there was a reasonable thing you could do to try to make it work. For example, perhaps if it was a block of text being passed around, you could break it into paragraphs and process it in smaller chunks. But I think in practice I think this is arguably a check that should be happening anyways, and perhaps upstream of a serialization attempt - basically look at an incoming text block and if it's larger than some hundreds of kilobytes, handle it differently.
The other ramification is it makes handling very large chunks of data less efficient, because you iteratively, with O(N) time, "discover" the true size of an object.
Perhaps one option is to leave
into_buf()
with the exact same behavior as before, but create new methods forinto_buf_autosize()
, and/orinto_buf_sized()
, where theautosize
option will do the iterative guessing of the objects size, andsized
would take an explicit argument where the user says "this will take X pages to send" and always allocates that number of pages, and if it doesn't fit, it gives up.Summary
The open questions are thus:
xous-ipc
String type, now that we have support for RustString
?into_buf()
)?bytecheck
feature and makeBuffer
transformations safe? It might seem like an obvious thing to do but it incurs a computational and code size cost on a slow system with limited memory, and we primarily userkyv
to send data between trusted servers inside the same machine. This would be a feature you'd want on any data coming over the network or stored somewhere you didn't trust, so it's not exactly clear to me what we'd be getting by turning it on.And perhaps some other questions I haven't thought of, will edit this as things come up.
Beta Was this translation helpful? Give feedback.
All reactions