Bug 1471500 - Complete initial implementation of the bulk-memory proposal. Part 10 of 10. r=bbouvier.

Test cases for new instructions {mem,table}.{init,drop} and table.copy.

* New file passive-segs-nonboundary.js:
  This tests the new instructions for "normal" (non-boundary) cases enough to
  be reasonably convinced that the implementation works.  It does not test any
  exceptional/boundary cases, except for the case where we attempt to call
  through an empty table slot.

  This file also incorporates tests for memory.copy and memory.fill that were
  previously in memory-bulk.js, which, in turn, has been removed.

* New file passive-segs-boundary.js:
  This tests boundary / validation cases for the new instructions.  Almost all
  tests wind up throwing some kind of exception.

--HG--
extra : rebase_source : ec91c595c2f9af92ac440134cf965c736b90d03a
This commit is contained in:
Julian Seward 2018-09-10 15:09:33 +02:00
parent fe52931272
commit 4d15d8f78a
2 changed files with 601 additions and 1 deletions

View File

@ -0,0 +1,345 @@
if (!wasmBulkMemSupported())
quit(0);
// Perform a test which,
//
// * if errKind is defined, is expected to fail with an exception
// characterised by errKind and errText.
//
// * if errKind is undefined, is expected to succeed, in which case errKind
// and errText are ignored.
//
// The function body will be [insn1, insn2]. isMem controls whether the
// module is constructed with memory or table initializers. haveMemOrTable
// determines whether there is actually a memory or table to work with.
function do_test(insn1, insn2, errKind, errText, isMem, haveMemOrTable)
{
let preamble;
if (isMem) {
let mem_def = haveMemOrTable ? "(memory 1 1)" : "";
let mem_init = haveMemOrTable
? `(data (i32.const 2) "\\03\\01\\04\\01")
(data passive "\\02\\07\\01\\08")
(data (i32.const 12) "\\07\\05\\02\\03\\06")
(data passive "\\05\\09\\02\\07\\06")`
: "";
preamble
= `;; -------- Memories --------
${mem_def}
;; -------- Memory initialisers --------
${mem_init}
`;
} else {
let tab_def = haveMemOrTable ? "(table 30 30 anyfunc)" : "";
let tab_init = haveMemOrTable
? `(elem (i32.const 2) 3 1 4 1)
(elem passive 2 7 1 8)
(elem (i32.const 12) 7 5 2 3 6)
(elem passive 5 9 2 7 6)`
: "";
preamble
= `;; -------- Tables --------
${tab_def}
;; -------- Table initialisers --------
${tab_init}
;; ------ Functions (0..9) referred by the table/esegs ------
(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))
`;
}
let txt = "(module\n" + preamble +
`;; -------- testfn --------
(func (export "testfn")
${insn1}
${insn2}
)
)`;
if (!!errKind) {
assertErrorMessage(
() => {
let inst = wasmEvalText(txt);
inst.exports.testfn();
},
errKind,
errText
);
} else {
let inst = wasmEvalText(txt);
assertEq(undefined, inst.exports.testfn());
}
}
function mem_test(insn1, insn2, errKind, errText, haveMem=true) {
do_test(insn1, insn2, errKind, errText,
/*isMem=*/true, haveMem);
}
function mem_test_nofail(insn1, insn2) {
do_test(insn1, insn2, undefined, undefined,
/*isMem=*/true, /*haveMemOrTable=*/true);
}
function tab_test(insn1, insn2, errKind, errText, haveTab=true) {
do_test(insn1, insn2, errKind, errText,
/*isMem=*/false, haveTab);
}
function tab_test_nofail(insn1, insn2) {
do_test(insn1, insn2, undefined, undefined,
/*isMem=*/false, /*haveMemOrTable=*/true);
}
//---- memory.{drop,init} -------------------------------------------------
// drop with no memory
mem_test("memory.drop 3", "",
WebAssembly.CompileError, /can't touch memory without memory/,
false);
// init with no memory
mem_test("(memory.init 1 (i32.const 1234) (i32.const 1) (i32.const 1))", "",
WebAssembly.CompileError, /can't touch memory without memory/,
false);
// drop with data seg ix out of range
mem_test("memory.drop 4", "",
WebAssembly.CompileError, /memory.{drop,init} index out of range/);
// init with data seg ix out of range
mem_test("(memory.init 4 (i32.const 1234) (i32.const 1) (i32.const 1))", "",
WebAssembly.CompileError, /memory.{drop,init} index out of range/);
// drop with data seg ix indicating an active segment
mem_test("memory.drop 2", "",
WebAssembly.RuntimeError, /use of invalid passive data segment/);
// init with data seg ix indicating an active segment
mem_test("(memory.init 2 (i32.const 1234) (i32.const 1) (i32.const 1))", "",
WebAssembly.RuntimeError, /use of invalid passive data segment/);
// init, using a data seg ix more than once is OK
mem_test_nofail(
"(memory.init 1 (i32.const 1234) (i32.const 1) (i32.const 1))",
"(memory.init 1 (i32.const 4321) (i32.const 1) (i32.const 1))");
// drop, then drop
mem_test("memory.drop 1",
"memory.drop 1",
WebAssembly.RuntimeError, /use of invalid passive data segment/);
// drop, then init
mem_test("memory.drop 1",
"(memory.init 1 (i32.const 1234) (i32.const 1) (i32.const 1))",
WebAssembly.RuntimeError, /use of invalid passive data segment/);
// init: seg ix is valid passive, but length to copy > len of seg
mem_test("",
"(memory.init 1 (i32.const 1234) (i32.const 0) (i32.const 5))",
WebAssembly.RuntimeError, /index out of bounds/);
// init: seg ix is valid passive, but implies copying beyond end of seg
mem_test("",
"(memory.init 1 (i32.const 1234) (i32.const 2) (i32.const 3))",
WebAssembly.RuntimeError, /index out of bounds/);
// init: seg ix is valid passive, but implies copying beyond end of dst
mem_test("",
"(memory.init 1 (i32.const 0xFFFE) (i32.const 1) (i32.const 3))",
WebAssembly.RuntimeError, /index out of bounds/);
// init: seg ix is valid passive, zero len, but src offset out of bounds
mem_test("",
"(memory.init 1 (i32.const 1234) (i32.const 4) (i32.const 0))",
WebAssembly.RuntimeError, /index out of bounds/);
// init: seg ix is valid passive, zero len, but dst offset out of bounds
mem_test("",
"(memory.init 1 (i32.const 0x10000) (i32.const 2) (i32.const 0))",
WebAssembly.RuntimeError, /index out of bounds/);
// drop: too many args
mem_test("memory.drop 1 (i32.const 42)", "",
WebAssembly.CompileError,
/unused values not explicitly dropped by end of block/);
// init: too many args
mem_test("(memory.init 1 (i32.const 1) (i32.const 1) (i32.const 1) (i32.const 1))",
"",
SyntaxError, /parsing wasm text at/);
// init: too few args
mem_test("(memory.init 1 (i32.const 1) (i32.const 1))", "",
WebAssembly.CompileError,
/popping value from empty stack/);
// invalid argument types
{
const tys = ['i32', 'f32', 'i64', 'f64'];
for (let ty1 of tys) {
for (let ty2 of tys) {
for (let ty3 of tys) {
if (ty1 == 'i32' && ty2 == 'i32' && ty3 == 'i32')
continue; // this is the only valid case
let i1 = `(memory.init 1 (${ty1}.const 1) (${ty2}.const 1) (${ty3}.const 1))`;
mem_test(i1, "", WebAssembly.CompileError, /type mismatch/);
}}}
}
//---- table.{drop,init} --------------------------------------------------
// drop with no table
tab_test("table.drop 3", "",
WebAssembly.CompileError, /can't table.drop without a table/,
false);
// init with no table
tab_test("(table.init 1 (i32.const 12) (i32.const 1) (i32.const 1))", "",
WebAssembly.CompileError, /can't table.init without a table/,
false);
// drop with elem seg ix out of range
tab_test("table.drop 4", "",
WebAssembly.CompileError, /table.drop index out of range/);
// init with elem seg ix out of range
tab_test("(table.init 4 (i32.const 12) (i32.const 1) (i32.const 1))", "",
WebAssembly.CompileError, /table.init index out of range/);
// drop with elem seg ix indicating an active segment
tab_test("table.drop 2", "",
WebAssembly.RuntimeError, /use of invalid passive element segment/);
// init with elem seg ix indicating an active segment
tab_test("(table.init 2 (i32.const 12) (i32.const 1) (i32.const 1))", "",
WebAssembly.RuntimeError, /use of invalid passive element segment/);
// init, using an elem seg ix more than once is OK
tab_test_nofail(
"(table.init 1 (i32.const 12) (i32.const 1) (i32.const 1))",
"(table.init 1 (i32.const 21) (i32.const 1) (i32.const 1))");
// drop, then drop
tab_test("table.drop 1",
"table.drop 1",
WebAssembly.RuntimeError, /use of invalid passive element segment/);
// drop, then init
tab_test("table.drop 1",
"(table.init 1 (i32.const 12) (i32.const 1) (i32.const 1))",
WebAssembly.RuntimeError, /use of invalid passive element segment/);
// init: seg ix is valid passive, but length to copy > len of seg
tab_test("",
"(table.init 1 (i32.const 12) (i32.const 0) (i32.const 5))",
WebAssembly.RuntimeError, /index out of bounds/);
// init: seg ix is valid passive, but implies copying beyond end of seg
tab_test("",
"(table.init 1 (i32.const 12) (i32.const 2) (i32.const 3))",
WebAssembly.RuntimeError, /index out of bounds/);
// init: seg ix is valid passive, but implies copying beyond end of dst
tab_test("",
"(table.init 1 (i32.const 28) (i32.const 1) (i32.const 3))",
WebAssembly.RuntimeError, /index out of bounds/);
// init: seg ix is valid passive, zero len, but src offset out of bounds
tab_test("",
"(table.init 1 (i32.const 12) (i32.const 4) (i32.const 0))",
WebAssembly.RuntimeError, /index out of bounds/);
// init: seg ix is valid passive, zero len, but dst offset out of bounds
tab_test("",
"(table.init 1 (i32.const 30) (i32.const 2) (i32.const 0))",
WebAssembly.RuntimeError, /index out of bounds/);
// drop: too many args
tab_test("table.drop 1 (i32.const 42)", "",
WebAssembly.CompileError,
/unused values not explicitly dropped by end of block/);
// init: too many args
tab_test("(table.init 1 (i32.const 1) (i32.const 1) (i32.const 1) (i32.const 1))",
"",
SyntaxError, /parsing wasm text at/);
// init: too few args
tab_test("(table.init 1 (i32.const 1) (i32.const 1))", "",
WebAssembly.CompileError,
/popping value from empty stack/);
// invalid argument types
{
const tys = ['i32', 'f32', 'i64', 'f64'];
const ops = ['table.init 1', 'table.copy'];
for (let ty1 of tys) {
for (let ty2 of tys) {
for (let ty3 of tys) {
for (let op of ops) {
if (ty1 == 'i32' && ty2 == 'i32' && ty3 == 'i32')
continue; // this is the only valid case
let i1 = `(${op} (${ty1}.const 1) (${ty2}.const 1) (${ty3}.const 1))`;
tab_test(i1, "", WebAssembly.CompileError, /type mismatch/);
}}}}
}
//---- table.copy ---------------------------------------------------------
// There are no immediates here, only 3 dynamic args. So we're limited to
// runtime boundary checks.
// passive-segs-smoketest.js tests the normal, non-exception cases of
// table.copy. Here we just test the boundary-failure cases. The
// table's valid indices are 0 .. 29 inclusive.
// copy: dst range invalid
tab_test("(table.copy (i32.const 28) (i32.const 1) (i32.const 3))",
"",
WebAssembly.RuntimeError, /index out of bounds/);
// copy: dst wraparound end of 32 bit offset space
tab_test("(table.copy (i32.const 0xFFFFFFFE) (i32.const 1) (i32.const 2))",
"",
WebAssembly.RuntimeError, /index out of bounds/);
// copy: src range invalid
tab_test("(table.copy (i32.const 15) (i32.const 25) (i32.const 6))",
"",
WebAssembly.RuntimeError, /index out of bounds/);
// copy: src wraparound end of 32 bit offset space
tab_test("(table.copy (i32.const 15) (i32.const 0xFFFFFFFE) (i32.const 2))",
"",
WebAssembly.RuntimeError, /index out of bounds/);
// copy: zero length with both offsets in-bounds is OK
tab_test_nofail(
"(table.copy (i32.const 15) (i32.const 25) (i32.const 0))",
"");
// copy: zero length with dst offset out of bounds
tab_test("(table.copy (i32.const 30) (i32.const 15) (i32.const 0))",
"",
WebAssembly.RuntimeError, /index out of bounds/);
// copy: zero length with src offset out of bounds
tab_test("(table.copy (i32.const 15) (i32.const 30) (i32.const 0))",
"",
WebAssembly.RuntimeError, /index out of bounds/);

View File

@ -2,9 +2,264 @@
if (!wasmBulkMemSupported())
quit(0);
const Module = WebAssembly.Module;
const Instance = WebAssembly.Instance;
// Some non-boundary tests for {table,memory}.{init,drop,copy}. The table
// case is more complex and appears first. The memory case is a simplified
// version of it.
// This module exports 5 functions ..
let tab_expmod_t =
`(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))
)`;
// .. 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. |testfn| is exported. It uses the
// supplied |insn| to modify the table somehow, and then will 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 gen_tab_impmod_t(insn)
{
let t =
`(module
;; -------- Types --------
(type (func (result i32))) ;; type #0
;; -------- Tables --------
(table 30 30 anyfunc)
;; -------- Table initialisers --------
(elem (i32.const 2) 3 1 4 1)
(elem passive 2 7 1 8)
(elem (i32.const 12) 7 5 2 3 6)
(elem passive 5 9 2 7 6)
;; -------- Imports --------
(import "a" "if0" (result i32)) ;; index 0
(import "a" "if1" (result i32))
(import "a" "if2" (result i32))
(import "a" "if3" (result i32))
(import "a" "if4" (result i32)) ;; index 4
;; -------- Functions --------
(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 "testfn") (param i32) (result i32)
${insn}
;; call the selected table entry, which will either return a value,
;; or will cause an exception.
get_local 0 ;; callIx
call_indirect 0 ;; and its return value is our return value.
)
)`;
return t;
};
// 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.
function tab_test(instruction, expected_result_vector)
{
let tab_expmod_b = wasmTextToBinary(tab_expmod_t);
let tab_expmod_i = new Instance(new Module(tab_expmod_b));
let tab_impmod_t = gen_tab_impmod_t(instruction);
let tab_impmod_b = wasmTextToBinary(tab_impmod_t);
for (let i = 0; i < expected_result_vector.length; i++) {
let inst = new Instance(new Module(tab_impmod_b),
{a:{if0:tab_expmod_i.exports.ef0,
if1:tab_expmod_i.exports.ef1,
if2:tab_expmod_i.exports.ef2,
if3:tab_expmod_i.exports.ef3,
if4:tab_expmod_i.exports.ef4
}});
let expected = expected_result_vector[i];
let actual = undefined;
try {
actual = inst.exports.testfn(i);
assertEq(actual !== null, true);
} catch (e) {
if (!(e instanceof Error &&
e.message.match(/indirect call to null/)))
throw e;
// actual remains undefined
}
assertEq(actual, expected,
"tab_test fail: insn = '" + instruction + "', index = " +
i + ", expected = " + expected + ", actual = " + actual);
}
}
// Using 'e' for empty (undefined) spaces in the table, to make it easier
// to count through the vector entries when debugging.
let e = undefined;
// This just gives the initial state of the table, with its active
// initialisers applied
tab_test("nop",
[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]);
// Copy non-null over non-null
tab_test("(table.copy (i32.const 13) (i32.const 2) (i32.const 3))",
[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]);
// Copy non-null over null
tab_test("(table.copy (i32.const 25) (i32.const 15) (i32.const 2))",
[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]);
// Copy null over non-null
tab_test("(table.copy (i32.const 13) (i32.const 25) (i32.const 3))",
[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]);
// Copy null over null
tab_test("(table.copy (i32.const 20) (i32.const 22) (i32.const 4))",
[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]);
// Copy null and non-null entries, non overlapping
tab_test("(table.copy (i32.const 25) (i32.const 1) (i32.const 3))",
[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]);
// Copy null and non-null entries, overlapping, backwards
tab_test("(table.copy (i32.const 10) (i32.const 12) (i32.const 7))",
[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]);
// Copy null and non-null entries, overlapping, forwards
tab_test("(table.copy (i32.const 12) (i32.const 10) (i32.const 7))",
[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]);
// Passive init that overwrites all-null entries
tab_test("(table.init 1 (i32.const 7) (i32.const 0) (i32.const 4))",
[e,e,3,1,4, 1,e,2,7,1, 8,e,7,5,2, 3,6,e,e,e, e,e,e,e,e, e,e,e,e,e]);
// Passive init that overwrites existing active-init-created entries
tab_test("(table.init 3 (i32.const 15) (i32.const 1) (i32.const 3))",
[e,e,3,1,4, 1,e,e,e,e, e,e,7,5,2, 9,2,7,e,e, e,e,e,e,e, e,e,e,e,e]);
// Perform active and passive initialisation and then multiple copies
tab_test("(table.init 1 (i32.const 7) (i32.const 0) (i32.const 4)) \n" +
"table.drop 1 \n" +
"(table.init 3 (i32.const 15) (i32.const 1) (i32.const 3)) \n" +
"table.drop 3 \n" +
"(table.copy (i32.const 20) (i32.const 15) (i32.const 5)) \n" +
"(table.copy (i32.const 21) (i32.const 29) (i32.const 1)) \n" +
"(table.copy (i32.const 24) (i32.const 10) (i32.const 1)) \n" +
"(table.copy (i32.const 13) (i32.const 11) (i32.const 4)) \n" +
"(table.copy (i32.const 19) (i32.const 20) (i32.const 5))",
[e,e,3,1,4, 1,e,2,7,1, 8,e,7,e,7, 5,2,7,e,9, e,7,e,8,8, e,e,e,e,e]);
// And now a simplified version of the above, for memory.{init,drop,copy}.
function gen_mem_mod_t(insn)
{
let t =
`(module
;; -------- Memories --------
(memory (export "memory0") 1 1)
;; -------- Memory initialisers --------
(data (i32.const 2) "\\03\\01\\04\\01")
(data passive "\\02\\07\\01\\08")
(data (i32.const 12) "\\07\\05\\02\\03\\06")
(data passive "\\05\\09\\02\\07\\06")
(func (export "testfn")
${insn}
;; There's no return value. The JS driver can just pull out the
;; final memory and examine it.
)
)`;
return t;
};
function mem_test(instruction, expected_result_vector)
{
let mem_mod_t = gen_mem_mod_t(instruction);
let mem_mod_b = wasmTextToBinary(mem_mod_t);
let inst = new Instance(new Module(mem_mod_b));
inst.exports.testfn();
let buf = new Uint8Array(inst.exports.memory0.buffer);
for (let i = 0; i < expected_result_vector.length; i++) {
let expected = expected_result_vector[i];
let actual = buf[i];
assertEq(actual, expected,
"mem_test fail: insn = '" + instruction + "', index = " +
i + ", expected = " + expected + ", actual = " + actual);
}
}
e = 0;
// This just gives the initial state of the memory, with its active
// initialisers applied.
mem_test("nop",
[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]);
// Copy non-zero over non-zero
mem_test("(memory.copy (i32.const 13) (i32.const 2) (i32.const 3))",
[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]);
// Copy non-zero over zero
mem_test("(memory.copy (i32.const 25) (i32.const 15) (i32.const 2))",
[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]);
// Copy zero over non-zero
mem_test("(memory.copy (i32.const 13) (i32.const 25) (i32.const 3))",
[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]);
// Copy zero over zero
mem_test("(memory.copy (i32.const 20) (i32.const 22) (i32.const 4))",
[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]);
// Copy zero and non-zero entries, non overlapping
mem_test("(memory.copy (i32.const 25) (i32.const 1) (i32.const 3))",
[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]);
// Copy zero and non-zero entries, overlapping, backwards
mem_test("(memory.copy (i32.const 10) (i32.const 12) (i32.const 7))",
[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]);
// Copy zero and non-zero entries, overlapping, forwards
mem_test("(memory.copy (i32.const 12) (i32.const 10) (i32.const 7))",
[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]);
// Passive init that overwrites all-zero entries
mem_test("(memory.init 1 (i32.const 7) (i32.const 0) (i32.const 4))",
[e,e,3,1,4, 1,e,2,7,1, 8,e,7,5,2, 3,6,e,e,e, e,e,e,e,e, e,e,e,e,e]);
// Passive init that overwrites existing active-init-created entries
mem_test("(memory.init 3 (i32.const 15) (i32.const 1) (i32.const 3))",
[e,e,3,1,4, 1,e,e,e,e, e,e,7,5,2, 9,2,7,e,e, e,e,e,e,e, e,e,e,e,e]);
// Perform active and passive initialisation and then multiple copies
mem_test("(memory.init 1 (i32.const 7) (i32.const 0) (i32.const 4)) \n" +
"memory.drop 1 \n" +
"(memory.init 3 (i32.const 15) (i32.const 1) (i32.const 3)) \n" +
"memory.drop 3 \n" +
"(memory.copy (i32.const 20) (i32.const 15) (i32.const 5)) \n" +
"(memory.copy (i32.const 21) (i32.const 29) (i32.const 1)) \n" +
"(memory.copy (i32.const 24) (i32.const 10) (i32.const 1)) \n" +
"(memory.copy (i32.const 13) (i32.const 11) (i32.const 4)) \n" +
"(memory.copy (i32.const 19) (i32.const 20) (i32.const 5))",
[e,e,3,1,4, 1,e,2,7,1, 8,e,7,e,7, 5,2,7,e,9, e,7,e,8,8, e,e,e,e,e]);
//---------------------------------------------------------------------//
//---------------------------------------------------------------------//
// Validation tests
// Some further tests for memory.copy and memory.fill. First, validation
// tests.
//-----------------------------------------------------------
// Test helpers. Copied and simplified from binary.js.