blob: 2c20549fe1f5b76a59c11c21ccb0b9012de25539 [file] [edit]
//@ runDefaultWasm("-m", "--useConcurrentJIT=0")
// A byte-at-a-time copy or fill loop can be entered by OSR part way through its run, with the
// pointers and the counter already advanced. What is left of the run then has to come out the same
// whether it is finished by the loop or by the bulk memory operation that replaces it, so each
// function here runs a count long enough to tier up in the middle of the loop.
import { instantiate } from "../wabt-wrapper.js";
import * as assert from "../assert.js";
const PAGE = 65536;
const COUNT = 30000;
const golden = new Uint8Array(PAGE);
for (let i = 0; i < PAGE; ++i)
golden[i] = (i * 31 + 7) & 0xff;
let wat = `
(module
(memory (export "mem") 1)
(global $d (mut i32) (i32.const 0))
(global $s (mut i32) (i32.const 0))
(global $n (mut i32) (i32.const 0))
;; for (; n; --n) mem[d++] = mem[s++];
(func (export "copyForward") (param i32 i32 i32)
(local i32)
local.get 2
if
local.get 0
local.set 3
loop
local.get 3
local.get 1
i32.load8_u
i32.store8
local.get 3
i32.const 1
i32.add
local.set 3
local.get 1
i32.const 1
i32.add
local.set 1
local.get 2
i32.const 1
i32.sub
local.tee 2
br_if 0
end
end
local.get 3
global.set $d
local.get 1
global.set $s
local.get 2
global.set $n)
;; for (; n; --n) mem[--d] = mem[--s]; -- the caller passes the ends of the regions.
(func (export "copyBackward") (param i32 i32 i32)
(local i32)
local.get 2
if
local.get 0
local.set 3
loop
local.get 3
i32.const 1
i32.sub
local.tee 3
local.get 1
i32.const 1
i32.sub
local.tee 1
i32.load8_u
i32.store8
local.get 2
i32.const 1
i32.sub
local.tee 2
br_if 0
end
end
local.get 3
global.set $d
local.get 1
global.set $s
local.get 2
global.set $n)
;; for (; n; --n) mem[d++] = v;
(func (export "fill") (param i32 i32 i32)
(local i32)
local.get 2
if
local.get 0
local.set 3
loop
local.get 3
local.get 1
i32.store8
local.get 3
i32.const 1
i32.add
local.set 3
local.get 2
i32.const 1
i32.sub
local.tee 2
br_if 0
end
end
local.get 3
global.set $d
local.get 2
global.set $n)
(func (export "readD") (result i32) global.get $d)
(func (export "readS") (result i32) global.get $s)
(func (export "readN") (result i32) global.get $n)
)
`;
async function test()
{
const instance = await instantiate(wat, {}, {});
const { copyForward, copyBackward, fill, readD, readS, readN, mem } = instance.exports;
const memory = new Uint8Array(mem.buffer);
const expected = new Uint8Array(PAGE);
function seed()
{
memory.set(golden);
expected.set(golden);
}
function assertMemoryMatches()
{
let i = 0;
while (i < PAGE && memory[i] === expected[i])
++i;
assert.eq(i, PAGE, "memory differs at index " + i);
}
// Overlapping with the destination below the source, which a bulk copy performs faithfully.
function copyMemmoveLike()
{
seed();
copyForward(0, 20000, COUNT);
expected.copyWithin(0, 20000, 20000 + COUNT);
assert.eq([readD(), readS(), readN()], [COUNT, 20000 + COUNT, 0]);
assertMemoryMatches();
}
// Overlapping the other way, which replicates a byte across the destination rather than moving
// the region, so the loop has to keep running after the OSR entry.
function copyReplicating()
{
seed();
copyForward(20000, 19999, COUNT);
for (let i = 0; i < COUNT; ++i)
expected[20000 + i] = expected[19999 + i];
assert.eq([readD(), readS(), readN()], [20000 + COUNT, 19999 + COUNT, 0]);
assertMemoryMatches();
}
function copyDownwards()
{
seed();
copyBackward(PAGE, PAGE - COUNT, COUNT);
expected.copyWithin(PAGE - COUNT, PAGE - 2 * COUNT, PAGE - COUNT);
assert.eq([readD(), readS(), readN()], [PAGE - COUNT, PAGE - 2 * COUNT, 0]);
assertMemoryMatches();
}
function fillRegion()
{
seed();
fill(1000, 0x5a, COUNT);
expected.fill(0x5a, 1000, 1000 + COUNT);
assert.eq([readD(), readN()], [1000 + COUNT, 0]);
assertMemoryMatches();
}
// The OSR entry happens on the run where the loop's own back edges tier the function up, which
// takes more than one run of a loop this long.
for (let repeat = 0; repeat < 4; ++repeat) {
copyMemmoveLike();
copyReplicating();
copyDownwards();
fillRegion();
}
}
await assert.asyncTest(test());