Consider adding a `cfe` instruction to the vm.
Motivation
While working to implement a load_contract() function for the stdlib, I need to wrap the ldc opcode.
ldc requires that the stack be empty when used, or the VM will panic. As there is no way to ensure this function will always be called first, we need to clear the stack manually.
This entails stashing the current stack values on the heap temporarily, and then restoring them later.
When we try to restore n bytes from the heap to the stack, we need to allocate n bytes.
Currently, the only way to allocate stack space is with the cfei opcode, but we don't know the value of n yet so we can't use an immediate.
Proposal
Add a cfe instruction, and possibly the corresponding cfs instruction as well.
It could be possible to allocate dynamic sizes now using a logarithmic expansion algorithm in a loop.
Pseudocode:
while sp - ssp < size {
let remaining = size - (sp - ssp)
if remaining > 256 then CFEI 256 else
if remaining > 128 then CFEI 128 else
if remaining > 64 then CFEI 64 else
....
if remaining > 1 then CFEI 1
}
Now that I've spelled it out it looks like a waste of gas (though it could be impoved by updating remaining as you go meaning a small enough allocation could happen in a single iteration).
TBH, the need to allocate an unknown amount of stack space is a pretty rare case -- otherwise you'd use the heap. In your case you're explicitly manipulating the stack which is particularly rare.
It might be safer to introduce instructions for creating a new stack. To save $ssp on the current stack and then change it to be at the top of free space, creating a fresh stack. Then it could be wiped away by restoring $sp and $ssp from that saved value quite easily. No copying to or from the heap required.
If we had regular push and pop it would be the equivalent to:
# new stack
push ssp
move ssp sp
# restore stack
move sp ssp
pop ssp
Moving to $ssp is disallowed, so these two operations could be new specific instructions.
It's been a while since this was opened, so I want to resurface it as it will be needed for supporting upgradeable contract patterns.
Recent IRL chats should be mentioned here:
- Perhaps we should relax the requirement that
$sp==$sspand instead just clobber the stack with the loaded code and set$spto the new$ssp. - Position independent code will be needed, most easily realised via relative jumps. #430
load_contract()could provide an API which puts a persistent context on the heap, relieving the need to copy back to the new stack or aCFE. It could also provide ways to call the loaded code. It really depends on the use cases.
Clobbering the stack is pretty hardcore and would require library support for a context switch. It would require a special handler, a bit like an interrupt, which would ensure that clobbering the stack is not harmful and the heap based context is preserved (or at least the pointer to it is). I imagine it'd be a bit like EXECV(3) from unix.
It would require a special handler, a bit like an interrupt, which would ensure that clobbering the stack is not harmful and the heap based context is preserved (or at least the pointer to it is). I imagine it'd be a bit like EXECV(3) from unix.
Would this be at the runtime / library level? Or would the VM need to do anything to support this?
No, this would be all compiler/library stuff.