Files
wasmtime/cranelift/codegen/meta/src/cdsl/formats.rs
Nick Fitzgerald fc62d4ad65 Cranelift: Make heap_addr return calculated base + index + offset (#5231)
* Cranelift: Make `heap_addr` return calculated `base + index + offset`

Rather than return just the `base + index`.

(Note: I've chosen to use the nomenclature "index" for the dynamic operand and
"offset" for the static immediate.)

This move the addition of the `offset` into `heap_addr`, instead of leaving it
for the subsequent memory operation, so that we can Spectre-guard the full
address, and not allow speculative execution to read the first 4GiB of memory.

Before this commit, we were effectively doing

    load(spectre_guard(base + index) + offset)

Now we are effectively doing

    load(spectre_guard(base + index + offset))

Finally, this also corrects `heap_addr`'s documented semantics to say that it
returns an address that will trap on access if `index + offset + access_size` is
out of bounds for the given heap, rather than saying that the `heap_addr` itself
will trap. This matches the implemented behavior for static memories, and after
https://github.com/bytecodealliance/wasmtime/pull/5190 lands (which is blocked
on this commit) will also match the implemented behavior for dynamic memories.

* Update heap_addr docs

* Factor out `offset + size` to a helper
2022-11-09 19:53:51 +00:00

144 lines
4.7 KiB
Rust

use crate::cdsl::operands::OperandKind;
use std::fmt;
use std::rc::Rc;
/// An immediate field in an instruction format.
///
/// This corresponds to a single member of a variant of the `InstructionData`
/// data type.
#[derive(Debug)]
pub(crate) struct FormatField {
/// Immediate operand kind.
pub kind: OperandKind,
/// Member name in InstructionData variant.
pub member: &'static str,
}
/// Every instruction opcode has a corresponding instruction format which determines the number of
/// operands and their kinds. Instruction formats are identified structurally, i.e., the format of
/// an instruction is derived from the kinds of operands used in its declaration.
///
/// The instruction format stores two separate lists of operands: Immediates and values. Immediate
/// operands (including entity references) are represented as explicit members in the
/// `InstructionData` variants. The value operands are stored differently, depending on how many
/// there are. Beyond a certain point, instruction formats switch to an external value list for
/// storing value arguments. Value lists can hold an arbitrary number of values.
///
/// All instruction formats must be predefined in the meta shared/formats.rs module.
#[derive(Debug)]
pub(crate) struct InstructionFormat {
/// Instruction format name in CamelCase. This is used as a Rust variant name in both the
/// `InstructionData` and `InstructionFormat` enums.
pub name: &'static str,
pub num_value_operands: usize,
pub has_value_list: bool,
pub imm_fields: Vec<FormatField>,
/// Index of the value input operand that is used to infer the controlling type variable. By
/// default, this is `0`, the first `value` operand. The index is relative to the values only,
/// ignoring immediate operands.
pub typevar_operand: Option<usize>,
}
/// A tuple serving as a key to deduplicate InstructionFormat.
#[derive(Hash, PartialEq, Eq)]
pub(crate) struct FormatStructure {
pub num_value_operands: usize,
pub has_value_list: bool,
/// Tuples of (Rust field name / Rust type) for each immediate field.
pub imm_field_names: Vec<(&'static str, &'static str)>,
}
impl fmt::Display for InstructionFormat {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let imm_args = self
.imm_fields
.iter()
.map(|field| format!("{}: {}", field.member, field.kind.rust_type))
.collect::<Vec<_>>()
.join(", ");
fmt.write_fmt(format_args!(
"{}(imms=({}), vals={})",
self.name, imm_args, self.num_value_operands
))?;
Ok(())
}
}
impl InstructionFormat {
/// Returns a tuple that uniquely identifies the structure.
pub fn structure(&self) -> FormatStructure {
FormatStructure {
num_value_operands: self.num_value_operands,
has_value_list: self.has_value_list,
imm_field_names: self
.imm_fields
.iter()
.map(|field| (field.kind.rust_field_name, field.kind.rust_type))
.collect::<Vec<_>>(),
}
}
}
pub(crate) struct InstructionFormatBuilder(InstructionFormat);
impl InstructionFormatBuilder {
pub fn new(name: &'static str) -> Self {
Self(InstructionFormat {
name,
num_value_operands: 0,
has_value_list: false,
imm_fields: Vec::new(),
typevar_operand: None,
})
}
pub fn value(mut self) -> Self {
self.0.num_value_operands += 1;
self
}
pub fn varargs(mut self) -> Self {
self.0.has_value_list = true;
self
}
pub fn imm(mut self, operand_kind: &OperandKind) -> Self {
let field = FormatField {
kind: operand_kind.clone(),
member: operand_kind.rust_field_name,
};
self.0.imm_fields.push(field);
self
}
pub fn imm_with_name(mut self, name: &'static str, operand_kind: &OperandKind) -> Self {
let field = FormatField {
kind: operand_kind.clone(),
member: name,
};
self.0.imm_fields.push(field);
self
}
pub fn typevar_operand(mut self, operand_index: usize) -> Self {
assert!(self.0.typevar_operand.is_none());
assert!(operand_index < self.0.num_value_operands);
self.0.typevar_operand = Some(operand_index);
self
}
pub fn build(mut self) -> Rc<InstructionFormat> {
if self.0.typevar_operand.is_none() && self.0.num_value_operands > 0 {
// Default to the first value operand, if there's one.
self.0.typevar_operand = Some(0);
};
Rc::new(self.0)
}
}