Support explicit endianness in Cranelift IR MemFlags

WebAssembly memory operations are by definition little-endian even on
big-endian target platforms.  However, other memory accesses will require
native target endianness (e.g. to access parts of the VMContext that is
also accessed by VM native code).  This means on big-endian targets,
the code generator will have to handle both little- and big-endian
memory accesses.  However, there is currently no way to encode that
distinction into the Cranelift IR that describes memory accesses.

This patch provides such a way by adding an (optional) explicit
endianness marker to an instance of MemFlags.  Since each Cranelift IR
instruction that describes memory accesses already has an instance of
MemFlags attached, this can now be used to provide endianness
information.

Note that by default, memory accesses will continue to use the native
target ISA endianness.  To override this to specify an explicit
endianness, a MemFlags value that was built using the set_endianness
routine must be used.  This patch does so for accesses that implement
WebAssembly memory operations.

This patch addresses issue #2124.
This commit is contained in:
Ulrich Weigand
2020-12-09 16:23:10 +01:00
parent 2cec20aa57
commit 467a1af83a
5 changed files with 83 additions and 12 deletions

View File

@@ -6,15 +6,31 @@ enum FlagBit {
Notrap, Notrap,
Aligned, Aligned,
Readonly, Readonly,
LittleEndian,
BigEndian,
} }
const NAMES: [&str; 3] = ["notrap", "aligned", "readonly"]; const NAMES: [&str; 5] = ["notrap", "aligned", "readonly", "little", "big"];
/// Endianness of a memory access.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub enum Endianness {
/// Little-endian
Little,
/// Big-endian
Big,
}
/// Flags for memory operations like load/store. /// Flags for memory operations like load/store.
/// ///
/// Each of these flags introduce a limited form of undefined behavior. The flags each enable /// Each of these flags introduce a limited form of undefined behavior. The flags each enable
/// certain optimizations that need to make additional assumptions. Generally, the semantics of a /// certain optimizations that need to make additional assumptions. Generally, the semantics of a
/// program does not change when a flag is removed, but adding a flag will. /// program does not change when a flag is removed, but adding a flag will.
///
/// In addition, the flags determine the endianness of the memory access. By default,
/// any memory access uses the native endianness determined by the target ISA. This can
/// be overridden for individual accesses by explicitly specifying little- or big-endian
/// semantics via the flags.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct MemFlags { pub struct MemFlags {
bits: u8, bits: u8,
@@ -48,16 +64,48 @@ impl MemFlags {
/// Set a flag bit by name. /// Set a flag bit by name.
/// ///
/// Returns true if the flag was found and set, false for an unknown flag name. /// Returns true if the flag was found and set, false for an unknown flag name.
/// Will also return false when trying to set inconsistent endianness flags.
pub fn set_by_name(&mut self, name: &str) -> bool { pub fn set_by_name(&mut self, name: &str) -> bool {
match NAMES.iter().position(|&s| s == name) { match NAMES.iter().position(|&s| s == name) {
Some(bit) => { Some(bit) => {
self.bits |= 1 << bit; let bits = self.bits | 1 << bit;
if (bits & (1 << FlagBit::LittleEndian as usize)) != 0
&& (bits & (1 << FlagBit::BigEndian as usize)) != 0
{
false
} else {
self.bits = bits;
true true
} }
}
None => false, None => false,
} }
} }
/// Return endianness of the memory access. This will return the endianness
/// explicitly specified by the flags if any, and will default to the native
/// endianness otherwise. The native endianness has to be provided by the
/// caller since it is not explicitly encoded in CLIF IR -- this allows a
/// front end to create IR without having to know the target endianness.
pub fn endianness(self, native_endianness: Endianness) -> Endianness {
if self.read(FlagBit::LittleEndian) {
Endianness::Little
} else if self.read(FlagBit::BigEndian) {
Endianness::Big
} else {
native_endianness
}
}
/// Set endianness of the memory access.
pub fn set_endianness(&mut self, endianness: Endianness) {
match endianness {
Endianness::Little => self.set(FlagBit::LittleEndian),
Endianness::Big => self.set(FlagBit::BigEndian),
};
assert!(!(self.read(FlagBit::LittleEndian) && self.read(FlagBit::BigEndian)));
}
/// Test if the `notrap` flag is set. /// Test if the `notrap` flag is set.
/// ///
/// Normally, trapping is part of the semantics of a load/store operation. If the platform /// Normally, trapping is part of the semantics of a load/store operation. If the platform

View File

@@ -50,7 +50,7 @@ pub use crate::ir::instructions::{
pub use crate::ir::jumptable::JumpTableData; pub use crate::ir::jumptable::JumpTableData;
pub use crate::ir::layout::Layout; pub use crate::ir::layout::Layout;
pub use crate::ir::libcall::{get_probestack_funcref, LibCall}; pub use crate::ir::libcall::{get_probestack_funcref, LibCall};
pub use crate::ir::memflags::MemFlags; pub use crate::ir::memflags::{Endianness, MemFlags};
pub use crate::ir::progpoint::{ExpandedProgramPoint, ProgramOrder, ProgramPoint}; pub use crate::ir::progpoint::{ExpandedProgramPoint, ProgramOrder, ProgramPoint};
pub use crate::ir::sourceloc::SourceLoc; pub use crate::ir::sourceloc::SourceLoc;
pub use crate::ir::stackslot::{StackLayoutInfo, StackSlotData, StackSlotKind, StackSlots}; pub use crate::ir::stackslot::{StackLayoutInfo, StackSlotData, StackSlotKind, StackSlots};

View File

@@ -235,6 +235,14 @@ pub trait TargetIsa: fmt::Display + Send + Sync {
CallConv::triple_default(self.triple()) CallConv::triple_default(self.triple())
} }
/// Get the endianness of this ISA.
fn endianness(&self) -> ir::Endianness {
match self.triple().endianness().unwrap() {
target_lexicon::Endianness::Little => ir::Endianness::Little,
target_lexicon::Endianness::Big => ir::Endianness::Big,
}
}
/// Get the pointer type of this ISA. /// Get the pointer type of this ISA.
fn pointer_type(&self) -> ir::Type { fn pointer_type(&self) -> ir::Type {
ir::Type::int(u16::from(self.pointer_bits())).unwrap() ir::Type::int(u16::from(self.pointer_bits())).unwrap()

View File

@@ -659,7 +659,7 @@ fn narrow_load(
inst: ir::Inst, inst: ir::Inst,
func: &mut ir::Function, func: &mut ir::Function,
_cfg: &mut ControlFlowGraph, _cfg: &mut ControlFlowGraph,
_isa: &dyn TargetIsa, isa: &dyn TargetIsa,
) { ) {
let mut pos = FuncCursor::new(func).at_inst(inst); let mut pos = FuncCursor::new(func).at_inst(inst);
pos.use_srcloc(inst); pos.use_srcloc(inst);
@@ -684,6 +684,10 @@ fn narrow_load(
ptr, ptr,
offset.try_add_i64(8).expect("load offset overflow"), offset.try_add_i64(8).expect("load offset overflow"),
); );
let (al, ah) = match flags.endianness(isa.endianness()) {
ir::Endianness::Little => (al, ah),
ir::Endianness::Big => (ah, al),
};
pos.func.dfg.replace(inst).iconcat(al, ah); pos.func.dfg.replace(inst).iconcat(al, ah);
} }
@@ -692,7 +696,7 @@ fn narrow_store(
inst: ir::Inst, inst: ir::Inst,
func: &mut ir::Function, func: &mut ir::Function,
_cfg: &mut ControlFlowGraph, _cfg: &mut ControlFlowGraph,
_isa: &dyn TargetIsa, isa: &dyn TargetIsa,
) { ) {
let mut pos = FuncCursor::new(func).at_inst(inst); let mut pos = FuncCursor::new(func).at_inst(inst);
pos.use_srcloc(inst); pos.use_srcloc(inst);
@@ -708,6 +712,10 @@ fn narrow_store(
}; };
let (al, ah) = pos.ins().isplit(val); let (al, ah) = pos.ins().isplit(val);
let (al, ah) = match flags.endianness(isa.endianness()) {
ir::Endianness::Little => (al, ah),
ir::Endianness::Big => (ah, al),
};
pos.ins().store(flags, al, ptr, offset); pos.ins().store(flags, al, ptr, offset);
pos.ins().store( pos.ins().store(
flags, flags,

View File

@@ -2056,7 +2056,9 @@ fn prepare_load<FE: FuncEnvironment + ?Sized>(
// Note that we don't set `is_aligned` here, even if the load instruction's // Note that we don't set `is_aligned` here, even if the load instruction's
// alignment immediate says it's aligned, because WebAssembly's immediate // alignment immediate says it's aligned, because WebAssembly's immediate
// field is just a hint, while Cranelift's aligned flag needs a guarantee. // field is just a hint, while Cranelift's aligned flag needs a guarantee.
let flags = MemFlags::new(); // WebAssembly memory accesses are always little-endian.
let mut flags = MemFlags::new();
flags.set_endianness(ir::Endianness::Little);
Ok((flags, base, offset.into())) Ok((flags, base, offset.into()))
} }
@@ -2103,7 +2105,8 @@ fn translate_store<FE: FuncEnvironment + ?Sized>(
builder, builder,
); );
// See the comments in `prepare_load` about the flags. // See the comments in `prepare_load` about the flags.
let flags = MemFlags::new(); let mut flags = MemFlags::new();
flags.set_endianness(ir::Endianness::Little);
builder builder
.ins() .ins()
.Store(opcode, val_ty, flags, offset.into(), val, base); .Store(opcode, val_ty, flags, offset.into(), val, base);
@@ -2207,7 +2210,8 @@ fn translate_atomic_rmw<FE: FuncEnvironment + ?Sized>(
finalise_atomic_mem_addr(linear_mem_addr, memarg, access_ty, builder, state, environ)?; finalise_atomic_mem_addr(linear_mem_addr, memarg, access_ty, builder, state, environ)?;
// See the comments in `prepare_load` about the flags. // See the comments in `prepare_load` about the flags.
let flags = MemFlags::new(); let mut flags = MemFlags::new();
flags.set_endianness(ir::Endianness::Little);
let mut res = builder let mut res = builder
.ins() .ins()
.atomic_rmw(access_ty, flags, op, final_effective_address, arg2); .atomic_rmw(access_ty, flags, op, final_effective_address, arg2);
@@ -2260,7 +2264,8 @@ fn translate_atomic_cas<FE: FuncEnvironment + ?Sized>(
finalise_atomic_mem_addr(linear_mem_addr, memarg, access_ty, builder, state, environ)?; finalise_atomic_mem_addr(linear_mem_addr, memarg, access_ty, builder, state, environ)?;
// See the comments in `prepare_load` about the flags. // See the comments in `prepare_load` about the flags.
let flags = MemFlags::new(); let mut flags = MemFlags::new();
flags.set_endianness(ir::Endianness::Little);
let mut res = builder let mut res = builder
.ins() .ins()
.atomic_cas(flags, final_effective_address, expected, replacement); .atomic_cas(flags, final_effective_address, expected, replacement);
@@ -2302,7 +2307,8 @@ fn translate_atomic_load<FE: FuncEnvironment + ?Sized>(
finalise_atomic_mem_addr(linear_mem_addr, memarg, access_ty, builder, state, environ)?; finalise_atomic_mem_addr(linear_mem_addr, memarg, access_ty, builder, state, environ)?;
// See the comments in `prepare_load` about the flags. // See the comments in `prepare_load` about the flags.
let flags = MemFlags::new(); let mut flags = MemFlags::new();
flags.set_endianness(ir::Endianness::Little);
let mut res = builder let mut res = builder
.ins() .ins()
.atomic_load(access_ty, flags, final_effective_address); .atomic_load(access_ty, flags, final_effective_address);
@@ -2348,7 +2354,8 @@ fn translate_atomic_store<FE: FuncEnvironment + ?Sized>(
finalise_atomic_mem_addr(linear_mem_addr, memarg, access_ty, builder, state, environ)?; finalise_atomic_mem_addr(linear_mem_addr, memarg, access_ty, builder, state, environ)?;
// See the comments in `prepare_load` about the flags. // See the comments in `prepare_load` about the flags.
let flags = MemFlags::new(); let mut flags = MemFlags::new();
flags.set_endianness(ir::Endianness::Little);
builder builder
.ins() .ins()
.atomic_store(flags, data, final_effective_address); .atomic_store(flags, data, final_effective_address);