Add host functions and/or guest utilities to deal with nested VecObjects
#1836
salaheldinsoliman
started this conversation in
Core Advancement Proposals
Replies: 1 comment
-
|
The host functions seem reasonable to me, but I think to really weigh up the cost benefit, some hard numbers are needed for how bad in terms of resource costs the status quo is. For a double nested Vec, the cost is a couple host functions which does not seem significant, it's an N multiplier on cost were N is small I believe. |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Add host functions and/or guest utilities to deal with nested
VecObjectsIntroduction
In this discussion, I will explain the differences in how Solidity and Soroban deal with arrays (both memory and storage).
Then, I will explain which approaches Solang(A Solidity compiler to Soroban) explored to support memory and storage arrays, and the issues encountered along the way.
Finally, I will lay out my proposed solutions, which may involve adding host functions that manipulate or read
VecObjects.Background
Arrays: Solidity vs. Soroban dialect
Passing an array as a function parameter: In Solidity, when a memory array is passed as a function parameter, its bytes are ABI-encoded. The compiler allocates memory, decodes the array, and copies it into memory.
In Soroban, arrays are most often passed to public functions as
VecObjects. The host provides functions to read and mutate theVecObject. At this stage, no copying to linear memory is needed.Reading/mutating an array in memory: In Solidity, if the array is in memory, reading or mutating it involves pointer calculations and
mload/mstore.In Soroban (if the developer created a new
VecObjectand did not usealloc), reading/mutatingVecObjects involves host function calls (vec_get,vec_set, etc.). The same concept applies after storing theVecObjecthandle in the ledger as well.Storing a memory array in the ledger: In Solidity, copying a memory array to the ledger involves an O(n) loop, where each element is stored separately, each with a separate
SSTORE(the opcode for storing a value).In Soroban, the guest can store an entire
VecObjectin the ledger via a single call to theput_contract_datahost function.With the above concepts in mind, let's discuss the implications for nested Solidity arrays.
Implications for nested arrays
A Solidity developer would expect the cost (and mechanics) of setting an element in a nested Solidity array to be as follows:
D * index calculations + D * load/store, whereDis the number of dimensions.nis the number of elements.In Soroban, however, random access of an element in a nested
VecObjectinvolves:Meanwhile, copying a nested
VecObjectto storage is O(1) (a single call toput_contract_data).In Solang, we do not try to forcefully mimic Solidity's memory and storage layout for supported targets. Instead, we support the syntax and explain how things work under the hood on the target VM compared to Solidity.
This leaves Solang with a few options when dealing with arrays in memory and storage.
Possible routes to support storage and memory arrays in Solidity, and open questions/issues with each one
Route 1: Deal with
VecObjects as-is, but request extra host functions from the Stellar core teamThe first route is to deal with
VecObjects as-is. Solang would not copy anything to the guest linear memory by default; it would translate array access (storage or memory) tovec_get/vec_set. Of course, it must be made clear to developers that the array is not literally in memory—only its handle is.The issue with that approach is that Solidity syntax makes nested arrays appealing (whether in memory or storage). On the other hand, accessing an element in a nested
VecObjectwould require crossing the host <=> guest boundary multiple times, which is more costly than a Solidity developer would expect.Solang already supports
allocin linear memory, but I would argue that Soroban would not encourage developers to rely on that most of the time.What I'm proposing is the host function
vec_get_in/vec_set_in:{ "export": "...", "name": "vec_get_in", "args": [ { "name": "v", "type": "VecObject" }, { "name": "indices", "type": "VecObject" } ], "return": "Val", "docs": "Returns the element at the ordered `indices` of the vector. Traps if any index is out of bounds, or if the vector's depth does not match the indices length." }I would also argue that having this host function would simplify nested
VecObjectaccess in the Rust contracts SDK as well:)Route 2: Mimic Solidity by unpacking a received
VecObjectinto linear memoryIn this route, a Solidity memory array would literally exist in the guest linear memory (not in the host) via
vec_unpack_to_linear_memory. Storing the array in the ledger would be done by storing each element separately in storage, the Solidity way.The overhead would be unpacking into memory and decoding the values, which is already expected in Solidity. However, the same issue remains: how can we unpack a nested
VecObjectinto linear memory without doing lots of work in the guest?Therefore, this route would require an implementation such as
deep_vec_unpack_to_linear_memory, which handles nestedVecObjects.Summary
I'm more inclined to follow Route 1. Although it strays away from Solidity's memory and storage layout for vectors, it makes full use of Soroban's VM design. What do you think? Are there other routes to explore?
Beta Was this translation helpful? Give feedback.
All reactions