Skip to content

Commit

Permalink
feat: use indirectbr for resuming (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
DaniPopes authored Jul 17, 2024
1 parent fde1cf4 commit 3018390
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 55 deletions.
2 changes: 2 additions & 0 deletions crates/revmc-backend/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ pub trait Builder: BackendTypes + TypeMethods {
fn seal_all_blocks(&mut self);
fn set_current_block_cold(&mut self);
fn current_block(&mut self) -> Option<Self::BasicBlock>;
fn block_addr(&mut self, block: Self::BasicBlock) -> Option<Self::Value>;

fn add_comment_to_current_inst(&mut self, comment: &str);

Expand Down Expand Up @@ -292,6 +293,7 @@ pub trait Builder: BackendTypes + TypeMethods {
targets: &[(u64, Self::BasicBlock)],
default_is_cold: bool,
);
fn br_indirect(&mut self, address: Self::Value, destinations: &[Self::BasicBlock]);
fn phi(&mut self, ty: Self::Type, incoming: &[(Self::Value, Self::BasicBlock)]) -> Self::Value;
fn select(
&mut self,
Expand Down
32 changes: 22 additions & 10 deletions crates/revmc-context/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub struct EvmContext<'a> {
/// An index that is used internally to keep track of where execution should resume.
/// `0` is the initial state.
#[doc(hidden)]
pub resume_at: u32,
pub resume_at: usize,
}

impl fmt::Debug for EvmContext<'_> {
Expand All @@ -61,6 +61,10 @@ impl<'a> EvmContext<'a> {
host: &'b mut dyn HostExt,
) -> (Self, &'a mut EvmStack, &'a mut usize) {
let (stack, stack_len) = EvmStack::from_interpreter_stack(&mut interpreter.stack);
let resume_at = ResumeAt::load(
interpreter.instruction_pointer,
interpreter.contract.bytecode.original_byte_slice(),
);
let this = Self {
memory: &mut interpreter.shared_memory,
contract: &mut interpreter.contract,
Expand All @@ -69,7 +73,7 @@ impl<'a> EvmContext<'a> {
next_action: &mut interpreter.next_action,
return_data: &interpreter.return_data_buffer,
is_static: interpreter.is_static,
resume_at: ResumeAt::load(interpreter.instruction_pointer),
resume_at,
};
(this, stack, stack_len)
}
Expand Down Expand Up @@ -264,7 +268,13 @@ impl EvmCompilerFn {
}

let resume_at = ecx.resume_at;
// Set in EXTCALL soft failure.
let return_data_is_empty = ecx.return_data.is_empty();

ResumeAt::store(&mut interpreter.instruction_pointer, resume_at);
if return_data_is_empty {
interpreter.return_data_buffer.clear();
}

interpreter.instruction_result = result;
if interpreter.next_action.is_some() {
Expand All @@ -286,6 +296,8 @@ impl EvmCompilerFn {
///
/// These conditions are enforced at runtime if `debug_assertions` is set to `true`.
///
/// Use of this method is discouraged, as setup and cleanup need to be done manually.
///
/// # Safety
///
/// The caller must ensure that the arguments are valid and that the function is safe to call.
Expand All @@ -309,6 +321,8 @@ impl EvmCompilerFn {

/// Same as [`call`](Self::call) but with `#[inline(never)]`.
///
/// Use of this method is discouraged, as setup and cleanup need to be done manually.
///
/// # Safety
///
/// See [`call`](Self::call).
Expand Down Expand Up @@ -718,18 +732,16 @@ impl EvmWord {
struct ResumeAt;

impl ResumeAt {
fn load(ip: *const u8) -> u32 {
// Arbitrary limit.
// TODO: Use upper bits?
if (ip as usize) <= u16::MAX as usize {
ip as u32
} else {
fn load(ip: *const u8, code: &[u8]) -> usize {
if code.as_ptr_range().contains(&ip) {
0
} else {
ip as usize
}
}

fn store(ip: &mut *const u8, value: u32) {
*ip = value as _;
fn store(ip: &mut *const u8, value: usize) {
*ip = value as *const u8;
}
}

Expand Down
8 changes: 8 additions & 0 deletions crates/revmc-cranelift/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,10 @@ impl<'a> Builder for EvmCraneliftBuilder<'a> {
self.bcx.current_block()
}

fn block_addr(&mut self, _block: Self::BasicBlock) -> Option<Self::Value> {
None
}

fn add_comment_to_current_inst(&mut self, comment: &str) {
let Some(block) = self.bcx.current_block() else { return };
let Some(inst) = self.bcx.func.layout.last_inst(block) else { return };
Expand Down Expand Up @@ -510,6 +514,10 @@ impl<'a> Builder for EvmCraneliftBuilder<'a> {
switch.emit(&mut self.bcx, index, default)
}

fn br_indirect(&mut self, _address: Self::Value, _destinations: &[Self::BasicBlock]) {
unimplemented!()
}

fn phi(&mut self, ty: Self::Type, incoming: &[(Self::Value, Self::BasicBlock)]) -> Self::Value {
let current = self.current_block().unwrap();
let param = self.bcx.append_block_param(current, ty);
Expand Down
8 changes: 8 additions & 0 deletions crates/revmc-llvm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,10 @@ impl<'a, 'ctx> Builder for EvmLlvmBuilder<'a, 'ctx> {
self.bcx.get_insert_block()
}

fn block_addr(&mut self, block: Self::BasicBlock) -> Option<Self::Value> {
unsafe { block.get_address().map(Into::into) }
}

fn add_comment_to_current_inst(&mut self, comment: &str) {
let Some(block) = self.current_block() else { return };
let Some(ins) = block.get_last_instruction() else { return };
Expand Down Expand Up @@ -763,6 +767,10 @@ impl<'a, 'ctx> Builder for EvmLlvmBuilder<'a, 'ctx> {
}
}

fn br_indirect(&mut self, address: Self::Value, destinations: &[Self::BasicBlock]) {
let _ = self.bcx.build_indirect_branch(address, destinations).unwrap();
}

fn phi(&mut self, ty: Self::Type, incoming: &[(Self::Value, Self::BasicBlock)]) -> Self::Value {
let incoming = incoming
.iter()
Expand Down
2 changes: 1 addition & 1 deletion crates/revmc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ revm-interpreter.workspace = true
revm-primitives.workspace = true

bitflags = "2.5"
bitvec = "1"
bitvec = "1.0"
rustc-hash.workspace = true
tracing.workspace = true

Expand Down
97 changes: 66 additions & 31 deletions crates/revmc/src/compiler/translate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,15 @@ impl Default for FcxConfig {
type Incoming<B> = Vec<(<B as BackendTypes>::Value, <B as BackendTypes>::BasicBlock)>;

/// A list of `switch` targets.
#[allow(dead_code)]
type SwitchTargets<B> = Vec<(u64, <B as BackendTypes>::BasicBlock)>;

#[derive(Clone, Copy, PartialEq, Eq)]
enum ResumeKind {
Blocks,
Indexes,
}

pub(super) struct FunctionCx<'a, B: Backend> {
// Configuration.
config: FcxConfig,
Expand Down Expand Up @@ -100,8 +107,10 @@ pub(super) struct FunctionCx<'a, B: Backend> {
/// The return block that all return instructions branch to.
return_block: Option<B::BasicBlock>,

/// The kind of resume mechanism to use.
resume_kind: ResumeKind,
/// `resume_block` switch values.
resume_blocks: SwitchTargets<B>,
resume_blocks: Vec<B::BasicBlock>,
/// `suspend_block` incoming values.
suspend_blocks: Incoming<B>,
/// The suspend block that all suspend instructions branch to.
Expand Down Expand Up @@ -251,6 +260,7 @@ impl<'a, B: Backend> FunctionCx<'a, B> {
incoming_returns: Vec::new(),
return_block: Some(return_block),

resume_kind: ResumeKind::Indexes,
resume_blocks: Vec::new(),
suspend_blocks: Vec::new(),
suspend_block,
Expand Down Expand Up @@ -296,10 +306,6 @@ impl<'a, B: Backend> FunctionCx<'a, B> {
let post_entry_block = fx.bcx.create_block_after(current_block, "entry.post");
let resume_block = fx.bcx.create_block_after(post_entry_block, "resume");
fx.bcx.br(post_entry_block);
// Important: set the first resume target to be the start of the instructions.
if fx.bytecode.will_suspend() {
fx.add_resume_at(first_inst_block);
}

// Translate individual instructions into their respective blocks.
for (inst, _) in bytecode.iter_insts() {
Expand All @@ -309,7 +315,6 @@ impl<'a, B: Backend> FunctionCx<'a, B> {
// Finalize the dynamic jump table.
fx.bcx.switch_to_block(unreachable_block);
fx.bcx.unreachable();
let i32_type = fx.bcx.type_int(32);
if bytecode.has_dynamic_jumps() {
fx.bcx.switch_to_block(fx.dynamic_jump_table);
// TODO: Manually reduce to i32?
Expand Down Expand Up @@ -349,47 +354,69 @@ impl<'a, B: Backend> FunctionCx<'a, B> {
}
};
if bytecode.will_suspend() {
let get_ecx_resume_at = |fx: &mut Self| {
let offset =
fx.bcx.iconst(fx.isize_type, mem::offset_of!(EvmContext<'_>, resume_at) as i64);
let name = "ecx.resume_at.addr";
fx.bcx.gep(fx.i8_type, fx.ecx, &[offset], name)
let get_ecx_resume_at_ptr = |fx: &mut Self| {
fx.get_field(
fx.ecx,
mem::offset_of!(EvmContext<'_>, resume_at),
"ecx.resume_at.addr",
)
};

let kind = fx.resume_kind;
let resume_ty = match kind {
ResumeKind::Blocks => fx.bcx.type_ptr(),
ResumeKind::Indexes => fx.isize_type,
};

// Resume block: load the `resume_at` value and switch to the corresponding block.
// Invalid values are treated as unreachable.
{
let default = fx.bcx.create_block_after(resume_block, "resume_invalid");
fx.bcx.switch_to_block(default);
fx.call_panic("invalid `resume_at` value");

// Special-case the zero block to load 0 into the length if possible.
let resume_is_zero_block =
fx.bcx.create_block_after(resume_block, "resume_is_zero");
// Special-case the no resume case to load 0 into the length if possible.
let no_resume_block = fx.bcx.create_block_after(resume_block, "no_resume");

fx.bcx.switch_to_block(post_entry_block);
let resume_at = get_ecx_resume_at(&mut fx);
let resume_at = fx.bcx.load(i32_type, resume_at, "resume_at");
let is_resume_zero = fx.bcx.icmp_imm(IntCC::Equal, resume_at, 0);
fx.bcx.brif(is_resume_zero, resume_is_zero_block, resume_block);
let resume_at = get_ecx_resume_at_ptr(&mut fx);
let resume_at = fx.bcx.load(resume_ty, resume_at, "ecx.resume_at");
let no_resume = match kind {
ResumeKind::Blocks => fx.bcx.is_null(resume_at),
ResumeKind::Indexes => fx.bcx.icmp_imm(IntCC::Equal, resume_at, 0),
};
fx.bcx.brif(no_resume, no_resume_block, resume_block);

fx.bcx.switch_to_block(resume_is_zero_block);
fx.bcx.switch_to_block(no_resume_block);
load_len_at_start(&mut fx);
fx.bcx.br(first_inst_block);

// Dispatch to the resume block.
fx.bcx.switch_to_block(resume_block);
let stack_len = fx.bcx.load(fx.isize_type, stack_len_arg, "stack_len");
fx.stack_len.store(&mut fx.bcx, stack_len);
let targets = &fx.resume_blocks[1..]; // Zero case is handled above.
fx.bcx.switch(resume_at, default, targets, true);
match kind {
ResumeKind::Blocks => {
fx.bcx.br_indirect(resume_at, &fx.resume_blocks);
}
ResumeKind::Indexes => {
let default = fx.bcx.create_block_after(resume_block, "resume_invalid");
fx.bcx.switch_to_block(default);
fx.call_panic("invalid `resume_at` value");

fx.bcx.switch_to_block(resume_block);
let targets = fx
.resume_blocks
.iter()
.enumerate()
.map(|(i, b)| (i as u64 + 1, *b))
.collect::<Vec<_>>();
fx.bcx.switch(resume_at, default, &targets, true);
}
}
}

// Suspend block: store the `resume_at` value and return `CallOrCreate`.
{
fx.bcx.switch_to_block(fx.suspend_block);
let resume_value = fx.bcx.phi(i32_type, &fx.suspend_blocks);
let resume_at = get_ecx_resume_at(&mut fx);
let resume_value = fx.bcx.phi(resume_ty, &fx.suspend_blocks);
let resume_at = get_ecx_resume_at_ptr(&mut fx);
fx.bcx.store(resume_value, resume_at);

fx.build_return_imm(InstructionResult::CallOrCreate);
Expand Down Expand Up @@ -1120,20 +1147,28 @@ impl<'a, B: Backend> FunctionCx<'a, B> {
/// Suspend execution, storing the resume point in the context.
fn suspend(&mut self) {
// Register the next instruction as the resume block.
let idx = self.resume_blocks.len();
let value = self.add_resume_at(self.inst_entries[self.current_inst + 1]);

// Register the current block as the suspend block.
let value = self.bcx.iconst(self.bcx.type_int(32), value as i64);
let value = match value {
Some(value) => value,
None => self.bcx.iconst(self.isize_type, idx as i64 + 1),
};
self.suspend_blocks.push((value, self.bcx.current_block().unwrap()));

// Branch to the suspend block.
self.bcx.br(self.suspend_block);
}

/// Adds a resume point and returns its index.
fn add_resume_at(&mut self, block: B::BasicBlock) -> usize {
let value = self.resume_blocks.len();
self.resume_blocks.push((value as u64, block));
fn add_resume_at(&mut self, block: B::BasicBlock) -> Option<B::Value> {
let value = self.bcx.block_addr(block);
if self.resume_blocks.is_empty() {
self.resume_kind =
if value.is_some() { ResumeKind::Blocks } else { ResumeKind::Indexes };
}
self.resume_blocks.push(block);
value
}

Expand Down
Loading

0 comments on commit 3018390

Please sign in to comment.