Skip to content

Commit ff3f994

Browse files
committed
Revert to 32-bit immediate offsets in heap_addr
This commit updates the generation of addresses in wasm code to always use 32-bit offsets for `heap_addr`, and if the calculated offset is bigger than 32-bits we emit a manual add with an overflow check.
1 parent 8d0dbd9 commit ff3f994

File tree

11 files changed

+78
-22
lines changed

11 files changed

+78
-22
lines changed

cranelift/codegen/meta/src/shared/formats.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ impl Formats {
273273
heap_addr: Builder::new("HeapAddr")
274274
.imm(&entities.heap)
275275
.value()
276-
.imm(&imm.uimm64)
276+
.imm(&imm.uimm32)
277277
.build(),
278278

279279
// Accessing a WebAssembly table.

cranelift/codegen/meta/src/shared/immediates.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ pub(crate) struct Immediates {
1414
/// counts on shift instructions.
1515
pub uimm8: OperandKind,
1616

17-
/// An unsigned 64-bit immediate integer operand.
18-
pub uimm64: OperandKind,
17+
/// An unsigned 32-bit immediate integer operand.
18+
pub uimm32: OperandKind,
1919

2020
/// An unsigned 128-bit immediate integer operand.
2121
///
@@ -97,8 +97,8 @@ impl Immediates {
9797
imm64: new_imm("imm", "ir::immediates::Imm64").with_doc("A 64-bit immediate integer."),
9898
uimm8: new_imm("imm", "ir::immediates::Uimm8")
9999
.with_doc("An 8-bit immediate unsigned integer."),
100-
uimm64: new_imm("imm", "ir::immediates::Uimm64")
101-
.with_doc("A 64-bit immediate unsigned integer."),
100+
uimm32: new_imm("imm", "ir::immediates::Uimm32")
101+
.with_doc("A 32-bit immediate unsigned integer."),
102102
uimm128: new_imm("imm", "ir::Immediate")
103103
.with_doc("A 128-bit immediate unsigned integer."),
104104
pool_constant: new_imm("constant_handle", "ir::Constant")

cranelift/codegen/meta/src/shared/instructions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1534,7 +1534,7 @@ pub(crate) fn define(
15341534

15351535
let H = &Operand::new("H", &entities.heap);
15361536
let p = &Operand::new("p", HeapOffset);
1537-
let Size = &Operand::new("Size", &imm.uimm64).with_doc("Size in bytes");
1537+
let Size = &Operand::new("Size", &imm.uimm32).with_doc("Size in bytes");
15381538

15391539
ig.push(
15401540
Inst::new(

cranelift/codegen/src/ir/immediates.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,12 @@ impl From<Uimm32> for u32 {
291291
}
292292
}
293293

294+
impl From<Uimm32> for u64 {
295+
fn from(val: Uimm32) -> u64 {
296+
val.0.into()
297+
}
298+
}
299+
294300
impl From<Uimm32> for i64 {
295301
fn from(val: Uimm32) -> i64 {
296302
i64::from(val.0)

cranelift/codegen/src/ir/instructions.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,8 +302,8 @@ impl InstructionData {
302302
// 32-bit
303303
&InstructionData::UnaryIeee32 { imm, .. } => Some(DataValue::from(imm)),
304304
&InstructionData::HeapAddr { imm, .. } => {
305-
let imm: u64 = imm.into();
306-
Some(DataValue::from(imm as i64)) // Note the switch from unsigned to signed.
305+
let imm: u32 = imm.into();
306+
Some(DataValue::from(imm as i32)) // Note the switch from unsigned to signed.
307307
}
308308
&InstructionData::Load { offset, .. }
309309
| &InstructionData::LoadComplex { offset, .. }

cranelift/codegen/src/legalizer/heap.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub fn expand_heap_addr(
2525
imm,
2626
} => {
2727
debug_assert_eq!(opcode, ir::Opcode::HeapAddr);
28-
(heap, arg, imm.into())
28+
(heap, arg, u64::from(imm))
2929
}
3030
_ => panic!("Wanted heap_addr: {}", func.dfg.display_inst(inst, None)),
3131
};

cranelift/reader/src/parser.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3064,7 +3064,7 @@ impl<'a> Parser<'a> {
30643064
self.match_token(Token::Comma, "expected ',' between operands")?;
30653065
let arg = self.match_value("expected SSA value heap address")?;
30663066
self.match_token(Token::Comma, "expected ',' between operands")?;
3067-
let imm = self.match_uimm64("expected 64-bit integer size")?;
3067+
let imm = self.match_uimm32("expected 32-bit integer size")?;
30683068
InstructionData::HeapAddr {
30693069
opcode,
30703070
heap,

cranelift/wasm/src/code_translator.rs

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2224,20 +2224,58 @@ fn prepare_addr<FE: FuncEnvironment + ?Sized>(
22242224
cmp::max(memarg.offset / offset_guard_size * offset_guard_size, 1)
22252225
};
22262226
debug_assert!(adjusted_offset > 0); // want to bounds check at least 1 byte
2227-
let base = builder
2228-
.ins()
2229-
.heap_addr(environ.pointer_type(), heap, addr, adjusted_offset);
2227+
let (addr, offset) = match u32::try_from(adjusted_offset) {
2228+
// If our adjusted offset fits within a u32, then we can place the
2229+
// entire offset into the offset of the `heap_addr` instruction. After
2230+
// the `heap_addr` instruction, though, we need to factor the the offset
2231+
// into the returned address. This is either an immediate if the offset
2232+
// further fits within `i32`, or a manual add instruction otherwise.
2233+
//
2234+
// Note that native instructions take a signed offset hence the switch
2235+
// to i32. Note also the lack of overflow checking in the offset
2236+
// addition, which should be ok since if `heap_addr` passed we're
2237+
// guaranteed that this won't overflow.
2238+
Ok(offset) => {
2239+
let base = builder
2240+
.ins()
2241+
.heap_addr(environ.pointer_type(), heap, addr, offset);
2242+
match i32::try_from(memarg.offset) {
2243+
Ok(val) => (base, val),
2244+
Err(_) => {
2245+
let adj = builder.ins().iadd_imm(base, i64::from(offset));
2246+
(adj, 0)
2247+
}
2248+
}
2249+
}
22302250

2231-
// Native load/store instructions take a signed `Offset32` immediate, so adjust the base
2232-
// pointer if necessary.
2233-
let (addr, offset) = match i32::try_from(memarg.offset) {
2234-
Ok(val) => (base, val),
2251+
// If the adjusted offset doesn't fit within a u32, then this gets
2252+
// pessimized a fair amount. We can't pass the fixed-sized offset to
2253+
// the `heap_addr` instruction, so we need to fold the offset into the
2254+
// address itself. In doing so though we need to check for overflow
2255+
// because that would mean the address is out-of-bounds.
2256+
//
2257+
// Once we have the effective address, offset already folded in, then
2258+
// `heap_addr` is used to verify that the address is indeed in-bounds.
2259+
// The access size of the `heap_addr` is what we were passed in from
2260+
// above.
2261+
//
2262+
// Note that this is generating what's likely to be at least two
2263+
// branches, one for the overflow and one for the bounds check itself.
2264+
// For now though that should hopefully be ok since 4gb+ offsets are
2265+
// relatively odd/rare.
22352266
Err(_) => {
2236-
// Note the switch from u64 offset to i64 here, but this should be
2237-
// ok because we're already guaranteed this won't overflow if we
2238-
// reach this point after the `heap_addr` instruction above.
2239-
let adj = builder.ins().iadd_imm(base, memarg.offset as i64);
2240-
(adj, 0)
2267+
let index_type = builder.func.heaps[heap].index_type;
2268+
let offset = builder.ins().iconst(index_type, memarg.offset as i64);
2269+
let (addr, overflow) = builder.ins().iadd_ifcout(addr, offset);
2270+
builder.ins().trapif(
2271+
environ.unsigned_add_overflow_condition(),
2272+
overflow,
2273+
ir::TrapCode::HeapOutOfBounds,
2274+
);
2275+
let base = builder
2276+
.ins()
2277+
.heap_addr(environ.pointer_type(), heap, addr, access_size);
2278+
(base, 0)
22412279
}
22422280
};
22432281

cranelift/wasm/src/environ/dummy.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,10 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
652652
) -> WasmResult<ir::Value> {
653653
Ok(pos.ins().iconst(I32, 0))
654654
}
655+
656+
fn unsigned_add_overflow_condition(&self) -> ir::condcodes::IntCC {
657+
unimplemented!()
658+
}
655659
}
656660

657661
impl TargetEnvironment for DummyEnvironment {

cranelift/wasm/src/environ/spec.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,10 @@ pub trait FuncEnvironment: TargetEnvironment {
697697
) -> WasmResult<()> {
698698
Ok(())
699699
}
700+
701+
/// Returns the target ISA's condition to check for unsigned addition
702+
/// overflowing.
703+
fn unsigned_add_overflow_condition(&self) -> ir::condcodes::IntCC;
700704
}
701705

702706
/// An object satisfying the `ModuleEnvironment` trait can be passed as argument to the

0 commit comments

Comments
 (0)