blob: 6ef231fb8fd1912d13cad3a99854a6f2d5f07199 [file] [log] [blame] [edit]
// This program generates .wast code that contains all the spec tests for
// table.copy. See `Makefile`.
print_origin("generate_table_copy.js");
// This module "a" exports 5 functions ...
function emit_a() {
print(
`
(module
(func (export "ef0") (result i32) (i32.const 0))
(func (export "ef1") (result i32) (i32.const 1))
(func (export "ef2") (result i32) (i32.const 2))
(func (export "ef3") (result i32) (i32.const 3))
(func (export "ef4") (result i32) (i32.const 4))
)
(register "a")`);
}
// ... and this one imports those 5 functions. It adds 5 of its own, creates a
// 30 element table using both active and passive initialisers, with a mixture
// of the imported and local functions. |test| is exported. It uses the
// supplied |insn| to modify the table somehow. |check| will then indirect-call
// the table entry number specified as a parameter. That will either return a
// value 0 to 9 indicating the function called, or will throw an exception if
// the table entry is empty.
function emit_b(insn, t0, t1) {
print(
`
(module
(type (func (result i32))) ;; type #0
(import "a" "ef0" (func (result i32))) ;; index 0
(import "a" "ef1" (func (result i32)))
(import "a" "ef2" (func (result i32)))
(import "a" "ef3" (func (result i32)))
(import "a" "ef4" (func (result i32))) ;; index 4
(table $t0 30 30 funcref)
(table $t1 30 30 funcref)
(elem (table $t${t0}) (i32.const 2) func 3 1 4 1)
(elem funcref
(ref.func 2) (ref.func 7) (ref.func 1) (ref.func 8))
(elem (table $t${t0}) (i32.const 12) func 7 5 2 3 6)
(elem funcref
(ref.func 5) (ref.func 9) (ref.func 2) (ref.func 7) (ref.func 6))
(elem (table $t${t1}) (i32.const 3) func 1 3 1 4)
(elem (table $t${t1}) (i32.const 11) func 6 3 2 5 7)
(func (result i32) (i32.const 5)) ;; index 5
(func (result i32) (i32.const 6))
(func (result i32) (i32.const 7))
(func (result i32) (i32.const 8))
(func (result i32) (i32.const 9)) ;; index 9
(func (export "test")
${insn})
(func (export "check_t0") (param i32) (result i32)
(call_indirect $t${t0} (type 0) (local.get 0)))
(func (export "check_t1") (param i32) (result i32)
(call_indirect $t${t1} (type 0) (local.get 0)))
)
`);
}
// This is the test driver. It constructs the abovementioned module, using the
// given |instruction| to modify the table, and then probes the table by making
// indirect calls, one for each element of |expected_result_vector|. The
// results are compared to those in the vector.
//
// "dest_table" may be t0 or t1.
function tab_test(args, t0, t1, dest_table, expected_t0, expected_t1) {
if (typeof args != "string")
emit_b("(nop)", t0, t1);
else
emit_b(`(table.copy $t${dest_table} $t${t0} ${args})`, t0, t1);
print(`(invoke "test")`);
for (let i = 0; i < expected_t0.length; i++) {
let expected = expected_t0[i];
if (expected === undefined) {
print(`(assert_trap (invoke "check_t0" (i32.const ${i})) "uninitialized element")`);
} else {
print(`(assert_return (invoke "check_t0" (i32.const ${i})) (i32.const ${expected}))`);
}
}
for (let i = 0; i < expected_t1.length; i++) {
let expected = expected_t1[i];
if (expected === undefined) {
print(`(assert_trap (invoke "check_t1" (i32.const ${i})) "uninitialized element")`);
} else {
print(`(assert_return (invoke "check_t1" (i32.const ${i})) (i32.const ${expected}))`);
}
}
}
emit_a();
// Using 'e' for empty (undefined) spaces in the table, to make it easier
// to count through the vector entries when debugging.
let e = undefined;
for ( let table of [0,1] ) {
let other_table = (table + 1) % 2;
// Tests for copying in a single table.
// This just gives the initial state of the table, with its active
// initialisers applied
tab_test(false, table, other_table, table,
[e,e,3,1,4, 1,e,e,e,e, e,e,7,5,2, 3,6,e,e,e, e,e,e,e,e, e,e,e,e,e],
[e,e,e,1,3, 1,4,e,e,e, e,6,3,2,5, 7,e,e,e,e, e,e,e,e,e, e,e,e,e,e]);
// Copy non-null over non-null
tab_test("(i32.const 13) (i32.const 2) (i32.const 3)", table, other_table, table,
[e,e,3,1,4, 1,e,e,e,e, e,e,7,3,1, 4,6,e,e,e, e,e,e,e,e, e,e,e,e,e],
[e,e,e,1,3, 1,4,e,e,e, e,6,3,2,5, 7,e,e,e,e, e,e,e,e,e, e,e,e,e,e]);
// Copy non-null over null
tab_test("(i32.const 25) (i32.const 15) (i32.const 2)", table, other_table, table,
[e,e,3,1,4, 1,e,e,e,e, e,e,7,5,2, 3,6,e,e,e, e,e,e,e,e, 3,6,e,e,e],
[e,e,e,1,3, 1,4,e,e,e, e,6,3,2,5, 7,e,e,e,e, e,e,e,e,e, e,e,e,e,e]);
// Copy null over non-null
tab_test("(i32.const 13) (i32.const 25) (i32.const 3)", table, other_table, table,
[e,e,3,1,4, 1,e,e,e,e, e,e,7,e,e, e,6,e,e,e, e,e,e,e,e, e,e,e,e,e],
[e,e,e,1,3, 1,4,e,e,e, e,6,3,2,5, 7,e,e,e,e, e,e,e,e,e, e,e,e,e,e]);
// Copy null over null
tab_test("(i32.const 20) (i32.const 22) (i32.const 4)", table, other_table, table,
[e,e,3,1,4, 1,e,e,e,e, e,e,7,5,2, 3,6,e,e,e, e,e,e,e,e, e,e,e,e,e],
[e,e,e,1,3, 1,4,e,e,e, e,6,3,2,5, 7,e,e,e,e, e,e,e,e,e, e,e,e,e,e]);
// Copy null and non-null entries, non overlapping
tab_test("(i32.const 25) (i32.const 1) (i32.const 3)", table, other_table, table,
[e,e,3,1,4, 1,e,e,e,e, e,e,7,5,2, 3,6,e,e,e, e,e,e,e,e, e,3,1,e,e],
[e,e,e,1,3, 1,4,e,e,e, e,6,3,2,5, 7,e,e,e,e, e,e,e,e,e, e,e,e,e,e]);
// Copy null and non-null entries, overlapping, backwards
tab_test("(i32.const 10) (i32.const 12) (i32.const 7)", table, other_table, table,
[e,e,3,1,4, 1,e,e,e,e, 7,5,2,3,6, e,e,e,e,e, e,e,e,e,e, e,e,e,e,e],
[e,e,e,1,3, 1,4,e,e,e, e,6,3,2,5, 7,e,e,e,e, e,e,e,e,e, e,e,e,e,e]);
// Copy null and non-null entries, overlapping, forwards
tab_test("(i32.const 12) (i32.const 10) (i32.const 7)", table, other_table, table,
[e,e,3,1,4, 1,e,e,e,e, e,e,e,e,7, 5,2,3,6,e, e,e,e,e,e, e,e,e,e,e],
[e,e,e,1,3, 1,4,e,e,e, e,6,3,2,5, 7,e,e,e,e, e,e,e,e,e, e,e,e,e,e]);
// Tests for copying from one table to the other. Here, overlap and copy
// direction don't matter.
tab_test("(i32.const 10) (i32.const 0) (i32.const 20)", table, other_table, other_table,
[e,e,3,1,4, 1,e,e,e,e, e,e,7,5,2, 3,6,e,e,e, e,e,e,e,e, e,e,e,e,e],
[e,e,e,1,3, 1,4,e,e,e, e,e,3,1,4, 1,e,e,e,e, e,e,7,5,2, 3,6,e,e,e]);
}
// Out-of-bounds checks.
function do_test(insn1, insn2, errText)
{
print(`
(script
(module
(table $t0 30 30 funcref)
(table $t1 30 30 funcref)
(elem (table $t0) (i32.const 2) func 3 1 4 1)
(elem funcref
(ref.func 2) (ref.func 7) (ref.func 1) (ref.func 8))
(elem (table $t0) (i32.const 12) func 7 5 2 3 6)
(elem funcref
(ref.func 5) (ref.func 9) (ref.func 2) (ref.func 7) (ref.func 6))
(func (result i32) (i32.const 0))
(func (result i32) (i32.const 1))
(func (result i32) (i32.const 2))
(func (result i32) (i32.const 3))
(func (result i32) (i32.const 4))
(func (result i32) (i32.const 5))
(func (result i32) (i32.const 6))
(func (result i32) (i32.const 7))
(func (result i32) (i32.const 8))
(func (result i32) (i32.const 9))
(func (export "test")
${insn1}
${insn2}))
`);
if (errText !== undefined) {
print(`(assert_trap (invoke "test") "${errText}")`);
} else {
print(`(invoke "test")`);
}
print(')');
}
function tab_test2(insn1, insn2, errKind, errText) {
do_test(insn1, insn2, errKind, errText);
}
function tab_test_nofail(insn1, insn2) {
do_test(insn1, insn2, undefined, undefined);
}
for ( let dest of ["$t0","$t1"] ) {
// Here we test the boundary-failure cases. The table's valid indices are 0..29
// inclusive.
// copy: dst range invalid
tab_test2(`(table.copy ${dest} $t0 (i32.const 28) (i32.const 1) (i32.const 3))`,
"",
"out of bounds table access");
// copy: dst wraparound end of 32 bit offset space
tab_test2(`(table.copy ${dest} $t0 (i32.const 0xFFFFFFFE) (i32.const 1) (i32.const 2))`,
"",
"out of bounds table access");
// copy: src range invalid
tab_test2(`(table.copy ${dest} $t0 (i32.const 15) (i32.const 25) (i32.const 6))`,
"",
"out of bounds table access");
// copy: src wraparound end of 32 bit offset space
tab_test2(`(table.copy ${dest} $t0 (i32.const 15) (i32.const 0xFFFFFFFE) (i32.const 2))`,
"",
"out of bounds table access");
// copy: zero length with both offsets in-bounds is OK
tab_test_nofail(
`(table.copy ${dest} $t0 (i32.const 15) (i32.const 25) (i32.const 0))`,
"");
// copy: zero length with dst offset out of bounds at the end of the table is allowed
tab_test2(`(table.copy ${dest} $t0 (i32.const 30) (i32.const 15) (i32.const 0))`,
"",
undefined);
// copy: zero length with dst offset out of bounds past the end of the table is not allowed
tab_test2(`(table.copy ${dest} $t0 (i32.const 31) (i32.const 15) (i32.const 0))`,
"",
"out of bounds table access");
// copy: zero length with src offset out of bounds at the end of the table is allowed
tab_test2(`(table.copy ${dest} $t0 (i32.const 15) (i32.const 30) (i32.const 0))`,
"",
undefined);
// copy: zero length with src offset out of bounds past the end of the table is not allowed
tab_test2(`(table.copy ${dest} $t0 (i32.const 15) (i32.const 31) (i32.const 0))`,
"",
"out of bounds table access");
// copy: zero length with both dst and src offset out of bounds at the end of the table is allowed
tab_test2(`(table.copy ${dest} $t0 (i32.const 30) (i32.const 30) (i32.const 0))`,
"",
undefined);
// copy: zero length with both dst and src offset out of bounds past the end of the table is not allowed
tab_test2(`(table.copy ${dest} $t0 (i32.const 31) (i32.const 31) (i32.const 0))`,
"",
"out of bounds table access");
}
// table.copy: out of bounds of the table for the source or target, but should
// perform the operation up to the appropriate bound. Major cases:
//
// - non-overlapping regions
// - overlapping regions with src > dest
// - overlapping regions with src == dest
// - overlapping regions with src < dest
// - arithmetic overflow on src addresses
// - arithmetic overflow on target addresses
//
// for each of those,
//
// - src address oob
// - target address oob
// - both oob
const tbl_copy_len = 16;
function tbl_copy(min, max, srcOffs, targetOffs, len) {
let copyDown = srcOffs < targetOffs;
let tblLength = min;
let targetAvail = tblLength - targetOffs;
let srcAvail = tblLength - srcOffs;
let targetLim = targetOffs + Math.min(len, targetAvail, srcAvail);
let srcLim = srcOffs + Math.min(len, targetAvail, srcAvail);
print(
`
(script
(module
(type (func (result i32)))
(table ${min} ${max} funcref)
(elem (i32.const ${srcOffs})
${(function () {
var s = "";
for (let i=srcOffs, j=0; i < srcLim; i++, j++)
s += " $f" + j;
return s;
})()})
(func $f0 (export "f0") (result i32) (i32.const 0))
(func $f1 (export "f1") (result i32) (i32.const 1))
(func $f2 (export "f2") (result i32) (i32.const 2))
(func $f3 (export "f3") (result i32) (i32.const 3))
(func $f4 (export "f4") (result i32) (i32.const 4))
(func $f5 (export "f5") (result i32) (i32.const 5))
(func $f6 (export "f6") (result i32) (i32.const 6))
(func $f7 (export "f7") (result i32) (i32.const 7))
(func $f8 (export "f8") (result i32) (i32.const 8))
(func $f9 (export "f9") (result i32) (i32.const 9))
(func $f10 (export "f10") (result i32) (i32.const 10))
(func $f11 (export "f11") (result i32) (i32.const 11))
(func $f12 (export "f12") (result i32) (i32.const 12))
(func $f13 (export "f13") (result i32) (i32.const 13))
(func $f14 (export "f14") (result i32) (i32.const 14))
(func $f15 (export "f15") (result i32) (i32.const 15))
(func (export "test") (param $n i32) (result i32)
(call_indirect (type 0) (local.get $n)))
(func (export "run") (param $targetOffs i32) (param $srcOffs i32) (param $len i32)
(table.copy (local.get $targetOffs) (local.get $srcOffs) (local.get $len))))
`);
let immediateOOB = copyDown && (srcOffs + len > tblLength || targetOffs + len > tblLength);
print(`(assert_trap (invoke "run" (i32.const ${targetOffs}) (i32.const ${srcOffs}) (i32.const ${len}))
"out of bounds table access")`);
var s = 0;
var i = 0;
for (i=0; i < tblLength; i++ ) {
if (i >= srcOffs && i < srcLim) {
print(`(assert_return (invoke "test" (i32.const ${i})) (i32.const ${s++}))`);
continue;
}
print(`(assert_trap (invoke "test" (i32.const ${i})) "uninitialized element")`);
}
print(')');
}
// OOB target address, nonoverlapping
tbl_copy(tbl_copy_len*2, tbl_copy_len*4, 0, Math.floor(1.5*tbl_copy_len), tbl_copy_len);
tbl_copy(tbl_copy_len*2, tbl_copy_len*4, 0, Math.floor(1.5*tbl_copy_len)-1, tbl_copy_len-1);
// OOB source address, nonoverlapping
tbl_copy(tbl_copy_len*2, tbl_copy_len*4, Math.floor(1.5*tbl_copy_len), 0, tbl_copy_len);
tbl_copy(tbl_copy_len*2, tbl_copy_len*4, Math.floor(1.5*tbl_copy_len)-1, 0, tbl_copy_len-1);
// OOB target address, overlapping, src < target
tbl_copy(tbl_copy_len*2, tbl_copy_len*4, tbl_copy_len-5, Math.floor(1.5*tbl_copy_len), tbl_copy_len);
// OOB source address, overlapping, target < src
tbl_copy(tbl_copy_len*2, tbl_copy_len*4, Math.floor(1.5*tbl_copy_len), tbl_copy_len-5, tbl_copy_len);
// OOB both, overlapping, including src == target
tbl_copy(tbl_copy_len*2, tbl_copy_len*4, tbl_copy_len+5, Math.floor(1.5*tbl_copy_len), tbl_copy_len);
tbl_copy(tbl_copy_len*2, tbl_copy_len*4, Math.floor(1.5*tbl_copy_len), tbl_copy_len+5, tbl_copy_len);
tbl_copy(tbl_copy_len*2, tbl_copy_len*4, tbl_copy_len+5, tbl_copy_len+5, tbl_copy_len);
// Arithmetic overflow on source address.
tbl_copy(tbl_copy_len*8, tbl_copy_len*8, tbl_copy_len*7, 0, 0xFFFFFFE0);
// Arithmetic overflow on target adddress is an overlapping case.
tbl_copy(tbl_copy_len*8, tbl_copy_len*8, 0, tbl_copy_len*7, 0xFFFFFFE0);