Source code
Revision control
Copy as Markdown
Other Tools
// Various tests for unaligned float accesses. These are specifically meant to
// test the SIGBUS handling on 32-bit ARM by exercising odd addresses and odd
// offsets.
// For a triple of (numBallast, ty, offset), create the text for a pair of
// functions "get_ty_offset" and "set_ty_offset" where each has numBallast live
// dummy values across the operation of interest to force the use of different
// register numbers. (This is primarily for the FP registers as ARM code
// generation currently always uses the same scratch register for the base
// address of the access.)
//
// These must be augmented with a memory. Memory addresses 0-255 are reserved
// for internal use by these functions. The memory must start as zero.
function makeLoadStore(numBallast, ty, offset) {
// The general idea of the ballast is that we occupy some FP registers and
// some int registers with non-dead values before we perform an operation,
// and then we consume the occupied registers after.
//
// In the case of load, the loaded result is stored back in memory before we
// consume the ballast, thus the ion regalloc will not simply always load
// the result into d0, but usually into some temp other than d0. Thus the
// amount of ballast affects the register. (Ditto baseline though the
// reasoning is simpler.)
//
// In the case of store, we keep the parameter value live until the end so
// that the tmp that we compute for the store is moved into a different
// register. The tmp has the same value as the parameter value but a
// non-JIT compiler can't know that.
let loadtxt =
`(func (export "get_${ty}_${offset}") (param $p i32) (result ${ty})
${ballast(() => `
(i32.const 8)
(i32.store (i32.const 8) (i32.add (i32.load (i32.const 8)) (i32.const 1)))
(${ty}.load (i32.const 8))`)}
(${ty}.store (i32.const 0) (${ty}.load offset=${offset} (local.get $p)))
${ballast(() => `
${ty}.store`)}
(${ty}.load (i32.const 0)))`;
// This will assume the value at mem[16] is zero.
let storetxt =
`(func (export "set_${ty}_${offset}") (param $p i32) (param $v ${ty})
(local $tmp ${ty})
${ballast(() => `
(i32.const 8)
(i32.store (i32.const 8) (i32.add (i32.load (i32.const 8)) (i32.const 1)))
(${ty}.load (i32.const 8))`)}
(local.set $tmp (${ty}.add (local.get $v) (${ty}.load (i32.const 16))))
(${ty}.store offset=${offset} (local.get $p) (local.get $tmp))
${ballast(() => `
${ty}.store`)}
(${ty}.store (i32.const 8) (local.get $v)))`;
return `${loadtxt}
${storetxt}`;
function ballast(thunk) {
let s = "";
for ( let i=0 ; i < numBallast; i++ )
s += thunk();
return s;
}
}
// The complexity here comes from trying to force the source/target FP registers
// in the FP access instruction to vary. For Baseline this is not hard; for Ion
// trickier.
function makeInstance(numBallast, offset) {
let txt =
`(module
(memory (export "memory") 1 1)
${makeLoadStore(numBallast, 'f64', offset)}
${makeLoadStore(numBallast, 'f32', offset)})`;
return new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(txt)));
}
// `offset` corresponds to the "offset" directive in the instruction
for ( let offset=0 ; offset < 8; offset++ ) {
// `numBallast` represents the amount of ballast registers we're trying to use,
// see comments above.
for ( let numBallast=0; numBallast < 16; numBallast++ ) {
let ins = makeInstance(numBallast, offset);
let mem = ins.exports.memory;
let buf = new DataView(mem.buffer);
// `i` represents the offset in the pointer from a proper boundary
for ( let i=0; i < 9; i++ ) {
let offs = 256+i;
let val = Math.PI+i;
buf.setFloat64(offs + offset, val, true);
assertEq(ins.exports["get_f64_" + offset](offs), val);
ins.exports["set_f64_" + offset](offs + 32, val);
assertEq(buf.getFloat64(offs + 32 + offset, true), val);
}
for ( let i=0; i < 9; i++ ) {
let offs = 512+i;
let val = Math.fround(Math.PI+i);
buf.setFloat32(offs + offset, val, true);
assertEq(ins.exports["get_f32_" + offset](offs), val);
ins.exports["set_f32_" + offset](offs + 32, val);
assertEq(buf.getFloat32(offs + 32 + offset, true), val);
}
}
}