-
Notifications
You must be signed in to change notification settings - Fork 57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
polkavm: Add a knob to control sbrk instruction enablement #171
Conversation
8e001a7
to
12d64e2
Compare
I think, the reason test failed on Compiler backend, vs passed on interpreter is that we have a slightly different logic of charging gas. On Interpreter backend, we charge per basic block, therefore charge includes cost of fallthrough. We can handle this in test, but charge mismatch would still happen. |
In general we always charge gas on a basic block basis, and when we enter a basic block we charge the gas. This applies to both the recompiler and the interpreter, and they should charge in the same way, otherwise it's a bug. (All of the tests which start with So, few points:
|
12d64e2
to
0616dd8
Compare
noted, thanks for the pointers! It looks like |
b314adb
to
447bfde
Compare
crates/polkavm/src/interpreter.rs
Outdated
#[allow(clippy::single_match)] | ||
match instruction.opcode() { | ||
polkavm_common::program::Opcode::sbrk => { | ||
if !self.module.allow_sbrk() { | ||
gas_visitor.start_new_basic_block(); | ||
is_properly_terminated = true; | ||
break; | ||
} | ||
} | ||
|
||
_ => {} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, instead of this why not do something like this at the start of the loop?
while let Some(mut instruction) = instructions.next() {
if !self.module.allow_sbrk() && matches!(instruction.kind, Instruction::sbrk(..) {
instruction.kind = Instruction::trap;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am happy to do it, but two reasons I did not do instruction replacement:
-
There is no precedence for instruction replacement pass in the interpreter code. Even for the instructions (unimplemented or dynamic paging flag gated instructions) where we could, we chose to do it in the
InstructionVisitor
. -
You mentioned about next instruction to be a valid place for jump if
sbrk
is treated astrap
. If we do instructions replacement without keeping track, we can't distinguish incorrect use oftrap
vs interpreter implicitly replacing some instruction withtrap
. Please correct if my understanding about this is incorrect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- There is no precedence for instruction replacement pass in the interpreter code.
But there is in the recompiler, no? Since that's exactly what you did there. (:
Even for the instructions (unimplemented or dynamic paging flag gated instructions) where we could, we chose to do it in the
InstructionVisitor
.
There's no deeper meaning to it; for e.g. loads/stores which have slightly different behavior depending on whether dynamic paging is enabled I did it in InstructionVisitor
because that's where it was the most convenient to do (and was faster, because we've already dispatched the interpreter to a load/store handler so it doesn't have to check for every instruction whether dynamic paging is enabled).
I suppose the problem here is that the "does this instruction end the basic block?" condition is determined outside of the instruction handler itself.
In other words, in the recompiler it works roughly like this:
fn handle_instruction_x() {
// ...
start_new_basic_block();
}
for instruction in instructions {
instruction.visit();
}
So over there just proxying a handler for instruction X into a handler for instruction Y is enough to "redirect" the instruction X to behave exactly the same as instruction Y.
However, in the interpreter it works like this:
fn handle_instruction_x() {
// ...
}
for instruction in instructions {
instruction.visit();
if instruction == x {
start_new_basic_block();
}
}
So here just changing the handler is not enough; you either need to:
a) change the handler and special case it in the loop (as you did here),
b) or immediately "swap" the instruction at the beginning of the loop (as I'm proposing) and leave everything else alone.
So I think (b) is cleaner/simpler/more convenient, at least for now (and it replicates what the recompiler is doing).
We could maybe refactor the interpreter so that it works similar to the recompiler (as in - the handler would return whether the instruction terminates the basic block or not, so then you wouldn't have to explicitly test for it in the compile_block
, which would be cleaner), but that's probably out of scope of this PR and we could do that later.
2. You mentioned about next instruction to be a valid place for jump if
sbrk
is treated astrap
. If we do instructions replacement without keeping track, we can't distinguish incorrect use oftrap
vs interpreter implicitly replacing some instruction withtrap
. Please correct if my understanding about this is incorrect.
Well, there's no such thing as "incorrect use of a trap". (: Every "unknown" instruction (and if sbrk
is disabled then it is essentially treated as unknown) is defined to be equivalent to a trap. Now, whether the program counter location after such a trap
constitutes a valid jump target - that's still somewhat an open question. But again, this behavior in general is somewhat broken right now and I'm working on tightening/finalizing the semantics for this and refactoring/rewriting the code for it, so you can just ignore it for now. For now it's fine if a disabled sbrk
behaves exactly the same as trap
and allows jumps after it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. Thanks for the detailed write-up!
447bfde
to
036ed41
Compare
Currently, we enable sbrk instruction by default, however we do want to a mechanism to control it for the guest. This patch aims to just do that. if sbrk is disabled, host should trap the guest program. A basic test is also added that confirms the same. Signed-off-by: Aman <[email protected]>
036ed41
to
033eeb9
Compare
Currently, we enable sbrk instruction by default, however we do want to a mechanism to control it for the guest. This patch aims to just do that.
if sbrk is disabled, host should trap the guest program.
A basic test is also added that confirms the same.
Note, since we charge gas per compile block, and not executed instruction, in the test we add a fallthrough().
Fixes: #167