v8/test/mjsunit/wasm/stringrefs-exec.js
Jakob Kummerow e441e21260 [stringrefs] Switch GC prefix decoding to LEB
So far, we decoded instructions with the 0xFB prefix as two-byte, i.e.
a single "u8" byte following the prefix.
This patch changes that to 0xFB + LEB, which is how all prefixed
instructions are supposed to do it. Currently this makes a difference
only for the stringref proposal (instructions 0x80 through 0xb3).

It has the unfortunate consequence that all stringref instructions need
three bytes for now. We expect them to go back to a two-byte encoding
scheme (while remaining LEB compliant) when their final encoding is
decided.

Bug: v8:12868
Change-Id: I603f60adae88e9b985cb65288d9eeb7f98da8138
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3825887
Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
Reviewed-by: Clemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/main@{#82400}
2022-08-11 14:07:13 +00:00

1185 lines
38 KiB
JavaScript

// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --experimental-wasm-stringref
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
let kSig_w_ii = makeSig([kWasmI32, kWasmI32], [kWasmStringRef]);
let kSig_w_v = makeSig([], [kWasmStringRef]);
let kSig_i_w = makeSig([kWasmStringRef], [kWasmI32]);
let kSig_i_wi = makeSig([kWasmStringRef, kWasmI32], [kWasmI32]);
let kSig_i_wii = makeSig([kWasmStringRef, kWasmI32, kWasmI32], [kWasmI32]);
let kSig_i_ww = makeSig([kWasmStringRef, kWasmStringRef], [kWasmI32]);
let kSig_i_wiii = makeSig([kWasmStringRef, kWasmI32, kWasmI32, kWasmI32],
[kWasmI32]);
let kSig_ii_wiii = makeSig([kWasmStringRef, kWasmI32, kWasmI32, kWasmI32],
[kWasmI32, kWasmI32]);
let kSig_v_w = makeSig([kWasmStringRef], []);
let kSig_w_i = makeSig([kWasmI32], [kWasmStringRef]);
let kSig_w_wii = makeSig([kWasmStringRef, kWasmI32, kWasmI32],
[kWasmStringRef]);
let kSig_w_ww = makeSig([kWasmStringRef, kWasmStringRef], [kWasmStringRef]);
let kSig_w_w = makeSig([kWasmStringRef], [kWasmStringRef]);
function encodeWtf8(str) {
// String iterator coalesces surrogate pairs.
let out = [];
for (let codepoint of str) {
codepoint = codepoint.codePointAt(0);
if (codepoint <= 0x7f) {
out.push(codepoint);
} else if (codepoint <= 0x7ff) {
out.push(0xc0 | (codepoint >> 6));
out.push(0x80 | (codepoint & 0x3f));
} else if (codepoint <= 0xffff) {
out.push(0xe0 | (codepoint >> 12));
out.push(0x80 | ((codepoint >> 6) & 0x3f));
out.push(0x80 | (codepoint & 0x3f));
} else if (codepoint <= 0x10ffff) {
out.push(0xf0 | (codepoint >> 18));
out.push(0x80 | ((codepoint >> 12) & 0x3f));
out.push(0x80 | ((codepoint >> 6) & 0x3f));
out.push(0x80 | (codepoint & 0x3f));
} else {
throw new Error("bad codepoint " + codepoint);
}
}
return out;
}
// Compute the string that corresponds to the valid WTF-8 bytes from
// start (inclusive) to end (exclusive).
function decodeWtf8(wtf8, start, end) {
let result = ''
while (start < end) {
let cp;
let b0 = wtf8[start];
if ((b0 & 0xC0) == 0x80) {
// The precondition is that we have valid WTF-8 bytes and that
// start and end are codepoint boundaries. Here we make a weak
// assertion about that invariant, that we don't start decoding
// with a continuation byte.
throw new Error('invalid wtf8');
}
if (b0 <= 0x7F) {
cp = b0;
start += 1;
} else if (b0 <= 0xDF) {
cp = (b0 & 0x1f) << 6;
cp |= (wtf8[start + 1] & 0x3f);
start += 2;
} else if (b0 <= 0xEF) {
cp = (b0 & 0x0f) << 12;
cp |= (wtf8[start + 1] & 0x3f) << 6;
cp |= (wtf8[start + 2] & 0x3f);
start += 3;
} else {
cp = (b0 & 0x07) << 18;
cp |= (wtf8[start + 1] & 0x3f) << 12;
cp |= (wtf8[start + 2] & 0x3f) << 6;
cp |= (wtf8[start + 3] & 0x3f);
start += 4;
}
result += String.fromCodePoint(cp);
}
assertEquals(start, end);
return result;
}
// We iterate over every one of these strings and every substring of it,
// so to keep test execution times fast on slow platforms, keep both this
// list and the individual strings reasonably short.
let interestingStrings = [
'',
'ascii',
'latin\xa91', // Latin-1.
'2 \ucccc b', // Two-byte.
'a \ud800\udc00 b', // Proper surrogate pair.
'a \ud800 b', // Lone lead surrogate.
'a \udc00 b', // Lone trail surrogate.
'\ud800 bc', // Lone lead surrogate at the start.
'\udc00 bc', // Lone trail surrogate at the start.
'ab \ud800', // Lone lead surrogate at the end.
'ab \udc00', // Lone trail surrogate at the end.
'a \udc00\ud800 b', // Swapped surrogate pair.
];
function IsSurrogate(codepoint) {
return 0xD800 <= codepoint && codepoint <= 0xDFFF
}
function HasIsolatedSurrogate(str) {
for (let codepoint of str) {
let value = codepoint.codePointAt(0);
if (IsSurrogate(value)) return true;
}
return false;
}
function ReplaceIsolatedSurrogates(str, replacement='\ufffd') {
let replaced = '';
for (let codepoint of str) {
replaced +=
IsSurrogate(codepoint.codePointAt(0)) ? replacement : codepoint;
}
return replaced;
}
function makeWtf8TestDataSegment() {
let data = []
let valid = {};
let invalid = {};
for (let str of interestingStrings) {
let bytes = encodeWtf8(str);
valid[str] = { offset: data.length, length: bytes.length };
for (let byte of bytes) {
data.push(byte);
}
}
for (let bytes of ['trailing high byte \xa9',
'interstitial high \xa9 byte',
'invalid \xc0 byte',
'invalid three-byte \xed\xd0\x80',
'surrogate \xed\xa0\x80\xed\xb0\x80 pair']) {
invalid[bytes] = { offset: data.length, length: bytes.length };
for (let i = 0; i < bytes.length; i++) {
data.push(bytes.charCodeAt(i));
}
}
return { valid, invalid, data: Uint8Array.from(data) };
};
(function TestStringNewWtf8() {
let builder = new WasmModuleBuilder();
builder.addMemory(1, undefined, false, false);
let data = makeWtf8TestDataSegment();
builder.addDataSegment(0, data.data);
builder.addFunction("string_new_utf8", kSig_w_ii)
.exportFunc()
.addBody([
kExprLocalGet, 0, kExprLocalGet, 1,
...GCInstr(kExprStringNewWtf8), 0, kWtf8PolicyReject
]);
builder.addFunction("string_new_wtf8", kSig_w_ii)
.exportFunc()
.addBody([
kExprLocalGet, 0, kExprLocalGet, 1,
...GCInstr(kExprStringNewWtf8), 0, kWtf8PolicyAccept
]);
builder.addFunction("string_new_utf8_sloppy", kSig_w_ii)
.exportFunc()
.addBody([
kExprLocalGet, 0, kExprLocalGet, 1,
...GCInstr(kExprStringNewWtf8), 0, kWtf8PolicyReplace
]);
let instance = builder.instantiate();
for (let [str, {offset, length}] of Object.entries(data.valid)) {
assertEquals(str, instance.exports.string_new_wtf8(offset, length));
if (HasIsolatedSurrogate(str)) {
assertThrows(() => instance.exports.string_new_utf8(offset, length),
WebAssembly.RuntimeError, "invalid UTF-8 string");
// Isolated surrogates have the three-byte pattern ED [A0,BF]
// [80,BF]. When the sloppy decoder gets to the second byte, it
// will reject the sequence, and then retry parsing at the second
// byte. Seeing the second byte can't start a sequence, it
// replaces the second byte and continues with the next, which
// also can't start a sequence. The result is that one isolated
// surrogate is replaced by three U+FFFD codepoints.
assertEquals(ReplaceIsolatedSurrogates(str, '\ufffd\ufffd\ufffd'),
instance.exports.string_new_utf8_sloppy(offset, length));
} else {
assertEquals(str, instance.exports.string_new_utf8(offset, length));
assertEquals(str,
instance.exports.string_new_utf8_sloppy(offset, length));
}
}
for (let [str, {offset, length}] of Object.entries(data.invalid)) {
assertThrows(() => instance.exports.string_new_wtf8(offset, length),
WebAssembly.RuntimeError, "invalid WTF-8 string");
assertThrows(() => instance.exports.string_new_utf8(offset, length),
WebAssembly.RuntimeError, "invalid UTF-8 string");
}
})();
function encodeWtf16LE(str) {
// String iterator coalesces surrogate pairs.
let out = [];
for (let i = 0; i < str.length; i++) {
codeunit = str.charCodeAt(i);
out.push(codeunit & 0xff)
out.push(codeunit >> 8);
}
return out;
}
function makeWtf16TestDataSegment() {
let data = []
let valid = {};
for (let str of interestingStrings) {
valid[str] = { offset: data.length, length: str.length };
for (let byte of encodeWtf16LE(str)) {
data.push(byte);
}
}
return { valid, data: Uint8Array.from(data) };
};
(function TestStringNewWtf16() {
let builder = new WasmModuleBuilder();
builder.addMemory(1, undefined, false, false);
let data = makeWtf16TestDataSegment();
builder.addDataSegment(0, data.data);
builder.addFunction("string_new_wtf16", kSig_w_ii)
.exportFunc()
.addBody([
kExprLocalGet, 0, kExprLocalGet, 1,
...GCInstr(kExprStringNewWtf16), 0
]);
let instance = builder.instantiate();
for (let [str, {offset, length}] of Object.entries(data.valid)) {
assertEquals(str, instance.exports.string_new_wtf16(offset, length));
}
})();
(function TestStringConst() {
let builder = new WasmModuleBuilder();
for (let [index, str] of interestingStrings.entries()) {
builder.addLiteralStringRef(encodeWtf8(str));
builder.addFunction("string_const" + index, kSig_w_v)
.exportFunc()
.addBody([...GCInstr(kExprStringConst), index]);
builder.addGlobal(kWasmStringRef, false,
[...GCInstr(kExprStringConst), index])
.exportAs("global" + index);
}
let instance = builder.instantiate();
for (let [index, str] of interestingStrings.entries()) {
assertEquals(str, instance.exports["string_const" + index]());
assertEquals(str, instance.exports["global" + index].value);
}
})();
(function TestStringMeasureUtf8AndWtf8() {
let builder = new WasmModuleBuilder();
builder.addFunction("string_measure_utf8", kSig_i_w)
.exportFunc()
.addBody([
kExprLocalGet, 0,
...GCInstr(kExprStringMeasureWtf8), 0
]);
builder.addFunction("string_measure_wtf8", kSig_i_w)
.exportFunc()
.addBody([
kExprLocalGet, 0,
...GCInstr(kExprStringMeasureWtf8), 1
]);
builder.addFunction("string_measure_wtf8_replace", kSig_i_w)
.exportFunc()
.addBody([
kExprLocalGet, 0,
...GCInstr(kExprStringMeasureWtf8), 2
]);
builder.addFunction("string_measure_utf8_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kStringRefCode,
...GCInstr(kExprStringMeasureWtf8), 0
]);
builder.addFunction("string_measure_wtf8_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kStringRefCode,
...GCInstr(kExprStringMeasureWtf8), 1
]);
builder.addFunction("string_measure_wtf8_replace_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kStringRefCode,
...GCInstr(kExprStringMeasureWtf8), 2
]);
let instance = builder.instantiate();
for (let str of interestingStrings) {
let wtf8 = encodeWtf8(str);
assertEquals(wtf8.length, instance.exports.string_measure_wtf8(str));
assertEquals(wtf8.length,
instance.exports.string_measure_wtf8_replace(str));
if (HasIsolatedSurrogate(str)) {
assertEquals(-1, instance.exports.string_measure_utf8(str));
} else {
assertEquals(wtf8.length, instance.exports.string_measure_utf8(str));
}
}
assertThrows(() => instance.exports.string_measure_utf8_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
assertThrows(() => instance.exports.string_measure_wtf8_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
assertThrows(() => instance.exports.string_measure_wtf8_replace_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
})();
(function TestStringMeasureWtf16() {
let builder = new WasmModuleBuilder();
builder.addFunction("string_measure_wtf16", kSig_i_w)
.exportFunc()
.addBody([
kExprLocalGet, 0,
...GCInstr(kExprStringMeasureWtf16)
]);
builder.addFunction("string_measure_wtf16_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kStringRefCode,
...GCInstr(kExprStringMeasureWtf16)
]);
let instance = builder.instantiate();
for (let str of interestingStrings) {
assertEquals(str.length, instance.exports.string_measure_wtf16(str));
}
assertThrows(() => instance.exports.string_measure_wtf16_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
})();
(function TestStringEncodeWtf8() {
let builder = new WasmModuleBuilder();
builder.addMemory(1, undefined, true /* exported */, false);
for (let [policy, name] of ["utf8", "wtf8", "replace"].entries()) {
builder.addFunction("encode_" + name, kSig_i_wi)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
...GCInstr(kExprStringEncodeWtf8), 0, policy,
]);
}
builder.addFunction("encode_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kStringRefCode,
kExprI32Const, 42,
...GCInstr(kExprStringEncodeWtf8), 0, 0,
]);
let instance = builder.instantiate();
let memory = new Uint8Array(instance.exports.memory.buffer);
function clearMemory(low, high) {
for (let i = low; i < high; i++) {
memory[i] = 0;
}
}
function assertMemoryBytesZero(low, high) {
for (let i = low; i < high; i++) {
assertEquals(0, memory[i]);
}
}
function checkMemory(offset, bytes) {
let slop = 64;
assertMemoryBytesZero(Math.max(0, offset - slop), offset);
for (let i = 0; i < bytes.length; i++) {
assertEquals(bytes[i], memory[offset + i]);
}
assertMemoryBytesZero(offset + bytes.length,
Math.min(memory.length,
offset + bytes.length + slop));
}
for (let str of interestingStrings) {
let wtf8 = encodeWtf8(str);
let offset = memory.length - wtf8.length;
assertEquals(wtf8.length, instance.exports.encode_wtf8(str, offset));
checkMemory(offset, wtf8);
clearMemory(offset, offset + wtf8.length);
}
for (let str of interestingStrings) {
let offset = 0;
if (HasIsolatedSurrogate(str)) {
assertThrows(() => instance.exports.encode_utf8(str, offset),
WebAssembly.RuntimeError,
"Failed to encode string as UTF-8: contains unpaired surrogate");
} else {
let wtf8 = encodeWtf8(str);
assertEquals(wtf8.length, instance.exports.encode_utf8(str, offset));
checkMemory(offset, wtf8);
clearMemory(offset, offset + wtf8.length);
}
}
for (let str of interestingStrings) {
let offset = 42;
let replaced = ReplaceIsolatedSurrogates(str);
if (!HasIsolatedSurrogate(str)) assertEquals(str, replaced);
let wtf8 = encodeWtf8(replaced);
assertEquals(wtf8.length, instance.exports.encode_replace(str, offset));
checkMemory(offset, wtf8);
clearMemory(offset, offset + wtf8.length);
}
assertThrows(() => instance.exports.encode_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
checkMemory(memory.length - 10, []);
for (let str of interestingStrings) {
let wtf8 = encodeWtf8(str);
let offset = memory.length - wtf8.length + 1;
assertThrows(() => instance.exports.encode_wtf8(str, offset),
WebAssembly.RuntimeError, "memory access out of bounds");
assertThrows(() => instance.exports.encode_utf8(str, offset),
WebAssembly.RuntimeError, "memory access out of bounds");
assertThrows(() => instance.exports.encode_replace(str, offset),
WebAssembly.RuntimeError, "memory access out of bounds");
checkMemory(offset - 1, []);
}
})();
(function TestStringEncodeWtf16() {
let builder = new WasmModuleBuilder();
builder.addMemory(1, undefined, true /* exported */, false);
builder.addFunction("encode_wtf16", kSig_i_wi)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
...GCInstr(kExprStringEncodeWtf16), 0,
]);
builder.addFunction("encode_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kStringRefCode,
kExprI32Const, 42,
...GCInstr(kExprStringEncodeWtf16), 0,
]);
let instance = builder.instantiate();
let memory = new Uint8Array(instance.exports.memory.buffer);
function clearMemory(low, high) {
for (let i = low; i < high; i++) {
memory[i] = 0;
}
}
function assertMemoryBytesZero(low, high) {
for (let i = low; i < high; i++) {
assertEquals(0, memory[i]);
}
}
function checkMemory(offset, bytes) {
let slop = 64;
assertMemoryBytesZero(Math.max(0, offset - slop), offset);
for (let i = 0; i < bytes.length; i++) {
assertEquals(bytes[i], memory[offset + i]);
}
assertMemoryBytesZero(offset + bytes.length,
Math.min(memory.length,
offset + bytes.length + slop));
}
for (let str of interestingStrings) {
let wtf16 = encodeWtf16LE(str);
let offset = memory.length - wtf16.length;
assertEquals(str.length, instance.exports.encode_wtf16(str, offset));
checkMemory(offset, wtf16);
clearMemory(offset, offset + wtf16.length);
}
for (let str of interestingStrings) {
let wtf16 = encodeWtf16LE(str);
let offset = 0;
assertEquals(str.length, instance.exports.encode_wtf16(str, offset));
checkMemory(offset, wtf16);
clearMemory(offset, offset + wtf16.length);
}
assertThrows(() => instance.exports.encode_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
checkMemory(memory.length - 10, []);
for (let str of interestingStrings) {
let offset = 1;
assertThrows(() => instance.exports.encode_wtf16(str, offset),
WebAssembly.RuntimeError,
"operation does not support unaligned accesses");
}
for (let str of interestingStrings) {
let wtf16 = encodeWtf16LE(str);
let offset = memory.length - wtf16.length + 2;
assertThrows(() => instance.exports.encode_wtf16(str, offset),
WebAssembly.RuntimeError, "memory access out of bounds");
checkMemory(offset - 2, []);
}
})();
(function TestStringConcat() {
let builder = new WasmModuleBuilder();
builder.addFunction("concat", kSig_w_ww)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
...GCInstr(kExprStringConcat)
]);
builder.addFunction("concat_null_head", kSig_w_w)
.exportFunc()
.addBody([
kExprRefNull, kStringRefCode,
kExprLocalGet, 0,
...GCInstr(kExprStringConcat)
]);
builder.addFunction("concat_null_tail", kSig_w_w)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprRefNull, kStringRefCode,
...GCInstr(kExprStringConcat)
]);
let instance = builder.instantiate();
for (let head of interestingStrings) {
for (let tail of interestingStrings) {
assertEquals(head + tail, instance.exports.concat(head, tail));
}
}
assertThrows(() => instance.exports.concat_null_head("hey"),
WebAssembly.RuntimeError, "dereferencing a null pointer");
assertThrows(() => instance.exports.concat_null_tail("hey"),
WebAssembly.RuntimeError, "dereferencing a null pointer");
})();
(function TestStringEq() {
let builder = new WasmModuleBuilder();
builder.addFunction("eq", kSig_i_ww)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprLocalGet, 1,
...GCInstr(kExprStringEq)
]);
builder.addFunction("eq_null_a", kSig_i_w)
.exportFunc()
.addBody([
kExprRefNull, kStringRefCode,
kExprLocalGet, 0,
...GCInstr(kExprStringEq)
]);
builder.addFunction("eq_null_b", kSig_i_w)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kExprRefNull, kStringRefCode,
...GCInstr(kExprStringEq)
]);
builder.addFunction("eq_both_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kStringRefCode,
kExprRefNull, kStringRefCode,
...GCInstr(kExprStringEq)
]);
let instance = builder.instantiate();
for (let head of interestingStrings) {
for (let tail of interestingStrings) {
let result = (head == tail)|0;
assertEquals(result, instance.exports.eq(head, tail));
assertEquals(result, instance.exports.eq(head + head, tail + tail));
}
assertEquals(0, instance.exports.eq_null_a(head))
assertEquals(0, instance.exports.eq_null_b(head))
}
assertEquals(1, instance.exports.eq_both_null());
})();
(function TestStringIsUSVSequence() {
let builder = new WasmModuleBuilder();
builder.addFunction("is_usv_sequence", kSig_i_w)
.exportFunc()
.addBody([
kExprLocalGet, 0,
...GCInstr(kExprStringIsUsvSequence)
]);
builder.addFunction("is_usv_sequence_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kStringRefCode,
...GCInstr(kExprStringIsUsvSequence)
]);
let instance = builder.instantiate();
for (let str of interestingStrings) {
assertEquals(HasIsolatedSurrogate(str) ? 0 : 1,
instance.exports.is_usv_sequence(str));
}
assertThrows(() => instance.exports.is_usv_sequence_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
})();
(function TestStringViewWtf16() {
let builder = new WasmModuleBuilder();
builder.addMemory(1, undefined, true /* exported */, false);
builder.addFunction("length", kSig_i_w)
.exportFunc()
.addBody([
kExprLocalGet, 0,
...GCInstr(kExprStringAsWtf16),
...GCInstr(kExprStringViewWtf16Length)
]);
builder.addFunction("length_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kStringViewWtf16Code,
...GCInstr(kExprStringViewWtf16Length)
]);
builder.addFunction("get_codeunit", kSig_i_wi)
.exportFunc()
.addBody([
kExprLocalGet, 0,
...GCInstr(kExprStringAsWtf16),
kExprLocalGet, 1,
...GCInstr(kExprStringViewWtf16GetCodeunit)
]);
builder.addFunction("get_codeunit_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kStringViewWtf16Code,
kExprI32Const, 0,
...GCInstr(kExprStringViewWtf16GetCodeunit)
]);
builder.addFunction("encode", kSig_i_wiii)
.exportFunc()
.addBody([
kExprLocalGet, 0,
...GCInstr(kExprStringAsWtf16),
kExprLocalGet, 1,
kExprLocalGet, 2,
kExprLocalGet, 3,
...GCInstr(kExprStringViewWtf16Encode), 0
]);
builder.addFunction("encode_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kStringViewWtf16Code,
kExprI32Const, 0,
kExprI32Const, 0,
kExprI32Const, 0,
...GCInstr(kExprStringViewWtf16Encode), 0
]);
builder.addFunction("slice", kSig_w_wii)
.exportFunc()
.addBody([
kExprLocalGet, 0,
...GCInstr(kExprStringAsWtf16),
kExprLocalGet, 1,
kExprLocalGet, 2,
...GCInstr(kExprStringViewWtf16Slice)
]);
builder.addFunction("slice_null", kSig_w_v)
.exportFunc()
.addBody([
kExprRefNull, kStringViewWtf16Code,
kExprI32Const, 0,
kExprI32Const, 0,
...GCInstr(kExprStringViewWtf16Slice)
]);
let instance = builder.instantiate();
let memory = new Uint8Array(instance.exports.memory.buffer);
for (let str of interestingStrings) {
assertEquals(str.length, instance.exports.length(str));
for (let i = 0; i < str.length; i++) {
assertEquals(str.charCodeAt(i),
instance.exports.get_codeunit(str, i));
}
assertEquals(str, instance.exports.slice(str, 0, -1));
}
function checkEncoding(str, slice, start, length) {
let bytes = encodeWtf16LE(slice);
function clearMemory(low, high) {
for (let i = low; i < high; i++) {
memory[i] = 0;
}
}
function assertMemoryBytesZero(low, high) {
for (let i = low; i < high; i++) {
assertEquals(0, memory[i]);
}
}
function checkMemory(offset, bytes) {
let slop = 64;
assertMemoryBytesZero(Math.max(0, offset - slop), offset);
for (let i = 0; i < bytes.length; i++) {
assertEquals(bytes[i], memory[offset + i]);
}
assertMemoryBytesZero(offset + bytes.length,
Math.min(memory.length,
offset + bytes.length + slop));
}
for (let offset of [0, 42, memory.length - bytes.length]) {
assertEquals(slice.length,
instance.exports.encode(str, offset, start, length));
checkMemory(offset, bytes);
clearMemory(offset, offset + bytes.length);
}
assertThrows(() => instance.exports.encode(str, 1, start, length),
WebAssembly.RuntimeError,
"operation does not support unaligned accesses");
assertThrows(
() => instance.exports.encode(str, memory.length - bytes.length + 2,
start, length),
WebAssembly.RuntimeError, "memory access out of bounds");
checkMemory(memory.length - bytes.length - 2, []);
}
checkEncoding("fox", "f", 0, 1);
checkEncoding("fox", "fo", 0, 2);
checkEncoding("fox", "fox", 0, 3);
checkEncoding("fox", "fox", 0, 300);
checkEncoding("fox", "", 1, 0);
checkEncoding("fox", "o", 1, 1);
checkEncoding("fox", "ox", 1, 2);
checkEncoding("fox", "ox", 1, 200);
checkEncoding("fox", "", 2, 0);
checkEncoding("fox", "x", 2, 1);
checkEncoding("fox", "x", 2, 2);
checkEncoding("fox", "", 3, 0);
checkEncoding("fox", "", 3, 1_000_000_000);
checkEncoding("fox", "", 1_000_000_000, 1_000_000_000);
checkEncoding("fox", "", 100, 100);
// Bounds checks before alignment checks.
assertThrows(() => instance.exports.encode("foo", memory.length - 1, 0, 3),
WebAssembly.RuntimeError, "memory access out of bounds");
assertEquals("", instance.exports.slice("foo", 0, 0));
assertEquals("f", instance.exports.slice("foo", 0, 1));
assertEquals("fo", instance.exports.slice("foo", 0, 2));
assertEquals("foo", instance.exports.slice("foo", 0, 3));
assertEquals("foo", instance.exports.slice("foo", 0, 4));
assertEquals("o", instance.exports.slice("foo", 1, 2));
assertEquals("oo", instance.exports.slice("foo", 1, 3));
assertEquals("oo", instance.exports.slice("foo", 1, 100));
assertEquals("", instance.exports.slice("foo", 1, 0));
assertThrows(() => instance.exports.length_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
assertThrows(() => instance.exports.get_codeunit_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
assertThrows(() => instance.exports.get_codeunit("", 0),
WebAssembly.RuntimeError, "string offset out of bounds");
assertThrows(() => instance.exports.encode_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
assertThrows(() => instance.exports.slice_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
})();
(function TestStringViewWtf8() {
let builder = new WasmModuleBuilder();
builder.addMemory(1, undefined, true /* exported */, false);
builder.addFunction("advance", kSig_i_wii)
.exportFunc()
.addBody([
kExprLocalGet, 0,
...GCInstr(kExprStringAsWtf8),
kExprLocalGet, 1,
kExprLocalGet, 2,
...GCInstr(kExprStringViewWtf8Advance)
]);
builder.addFunction("advance_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kStringViewWtf8Code,
kExprI32Const, 0,
kExprI32Const, 0,
...GCInstr(kExprStringViewWtf8Advance)
]);
for (let [name, policy] of Object.entries({utf8: kWtf8PolicyReject,
wtf8: kWtf8PolicyAccept,
replace: kWtf8PolicyReplace})) {
builder.addFunction(`encode_${name}`, kSig_ii_wiii)
.exportFunc()
.addBody([
kExprLocalGet, 0,
...GCInstr(kExprStringAsWtf8),
kExprLocalGet, 1,
kExprLocalGet, 2,
kExprLocalGet, 3,
...GCInstr(kExprStringViewWtf8Encode), 0, policy
]);
}
builder.addFunction("encode_null", kSig_v_v)
.exportFunc()
.addBody([
kExprRefNull, kStringViewWtf8Code,
kExprI32Const, 0,
kExprI32Const, 0,
kExprI32Const, 0,
...GCInstr(kExprStringViewWtf8Encode), 0, kWtf8PolicyAccept,
kExprDrop,
kExprDrop
]);
builder.addFunction(`slice`, kSig_w_wii)
.exportFunc()
.addBody([
kExprLocalGet, 0,
...GCInstr(kExprStringAsWtf8),
kExprLocalGet, 1,
kExprLocalGet, 2,
...GCInstr(kExprStringViewWtf8Slice)
]);
builder.addFunction("slice_null", kSig_v_v)
.exportFunc()
.addBody([
kExprRefNull, kStringViewWtf8Code,
kExprI32Const, 0,
kExprI32Const, 0,
...GCInstr(kExprStringViewWtf8Slice),
kExprDrop
]);
function Wtf8StartsCodepoint(wtf8, offset) {
return (wtf8[offset] & 0xc0) != 0x80;
}
function Wtf8PositionTreatment(wtf8, offset) {
while (offset < wtf8.length) {
if (Wtf8StartsCodepoint(wtf8, offset)) return offset;
offset++;
}
return wtf8.length;
}
function CodepointStart(wtf8, offset) {
if (offset >= wtf8.length) return wtf8.length;
while (!Wtf8StartsCodepoint(wtf8, offset)) {
offset--;
}
return offset;
}
let instance = builder.instantiate();
let memory = new Uint8Array(instance.exports.memory.buffer);
for (let pos = 0; pos < "ascii".length; pos++) {
assertEquals(pos + 1, instance.exports.advance("ascii", pos, 1));
}
for (let str of interestingStrings) {
let wtf8 = encodeWtf8(str);
assertEquals(wtf8.length, instance.exports.advance(str, 0, -1));
assertEquals(wtf8.length, instance.exports.advance(str, -1, 0));
assertEquals(wtf8.length, instance.exports.advance(str, 0, wtf8.length));
assertEquals(wtf8.length, instance.exports.advance(str, wtf8.length, 0));
assertEquals(wtf8.length,
instance.exports.advance(str, 0, wtf8.length + 1));
assertEquals(wtf8.length,
instance.exports.advance(str, wtf8.length + 1, 0));
for (let pos = 0; pos <= wtf8.length; pos++) {
for (let bytes = 0; bytes <= wtf8.length - pos; bytes++) {
assertEquals(
CodepointStart(wtf8, Wtf8PositionTreatment(wtf8, pos) + bytes),
instance.exports.advance(str, pos, bytes));
}
}
}
function clearMemory(low, high) {
for (let i = low; i < high; i++) {
memory[i] = 0;
}
}
function assertMemoryBytesZero(low, high) {
for (let i = low; i < high; i++) {
assertEquals(0, memory[i]);
}
}
function checkMemory(offset, bytes) {
let slop = 16;
assertMemoryBytesZero(Math.max(0, offset - slop), offset);
for (let i = 0; i < bytes.length; i++) {
assertEquals(bytes[i], memory[offset + i]);
}
assertMemoryBytesZero(offset + bytes.length,
Math.min(memory.length,
offset + bytes.length + slop));
}
function checkEncoding(variant, str, slice, start, length) {
let all_bytes = encodeWtf8(str);
let bytes = encodeWtf8(slice);
let encode = instance.exports[`encode_${variant}`];
let expected_start = Wtf8PositionTreatment(all_bytes, start);
let expected_end = CodepointStart(all_bytes, expected_start + bytes.length);
for (let offset of [0, 42, memory.length - bytes.length]) {
assertArrayEquals([expected_end, expected_end - expected_start],
encode(str, offset, start, length));
checkMemory(offset, bytes);
clearMemory(offset, offset + bytes.length);
}
assertThrows(() => encode(str, memory.length - bytes.length + 2,
start, length),
WebAssembly.RuntimeError, "memory access out of bounds");
checkMemory(memory.length - bytes.length - 2, []);
}
checkEncoding('utf8', "fox", "f", 0, 1);
checkEncoding('utf8', "fox", "fo", 0, 2);
checkEncoding('utf8', "fox", "fox", 0, 3);
checkEncoding('utf8', "fox", "fox", 0, 300);
checkEncoding('utf8', "fox", "", 1, 0);
checkEncoding('utf8', "fox", "o", 1, 1);
checkEncoding('utf8', "fox", "ox", 1, 2);
checkEncoding('utf8', "fox", "ox", 1, 200);
checkEncoding('utf8', "fox", "", 2, 0);
checkEncoding('utf8', "fox", "x", 2, 1);
checkEncoding('utf8', "fox", "x", 2, 2);
checkEncoding('utf8', "fox", "", 3, 0);
checkEncoding('utf8', "fox", "", 3, 1_000_000_000);
checkEncoding('utf8', "fox", "", 1_000_000_000, 1_000_000_000);
checkEncoding('utf8', "fox", "", 100, 100);
for (let str of interestingStrings) {
let wtf8 = encodeWtf8(str);
for (let pos = 0; pos <= wtf8.length; pos++) {
for (let bytes = 0; bytes <= wtf8.length - pos; bytes++) {
let start = Wtf8PositionTreatment(wtf8, pos);
let end = CodepointStart(wtf8, start + bytes);
let expected = decodeWtf8(wtf8, start, end);
checkEncoding('wtf8', str, expected, pos, bytes);
if (HasIsolatedSurrogate(expected)) {
assertThrows(() => instance.exports.encode_utf8(str, 0, pos, bytes),
WebAssembly.RuntimeError,
"Failed to encode string as UTF-8: " +
"contains unpaired surrogate");
checkEncoding('replace', str,
ReplaceIsolatedSurrogates(expected), pos, bytes);
} else {
checkEncoding('utf8', str, expected, pos, bytes);
checkEncoding('replace', str, expected, pos, bytes);
}
}
}
}
for (let str of interestingStrings) {
let wtf8 = encodeWtf8(str);
for (let start = 0; start <= wtf8.length; start++) {
for (let end = start; end <= wtf8.length; end++) {
let expected_slice = decodeWtf8(wtf8,
Wtf8PositionTreatment(wtf8, start),
Wtf8PositionTreatment(wtf8, end));
assertEquals(expected_slice, instance.exports.slice(str, start, end));
}
}
}
assertThrows(() => instance.exports.advance_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
assertThrows(() => instance.exports.encode_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
assertThrows(() => instance.exports.slice_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
})();
(function TestStringViewIter() {
let builder = new WasmModuleBuilder();
let global = builder.addGlobal(kWasmStringViewIter, true);
builder.addFunction("iterate", kSig_v_w)
.exportFunc()
.addBody([
kExprLocalGet, 0,
...GCInstr(kExprStringAsIter),
kExprGlobalSet, global.index
]);
builder.addFunction("iterate_null", kSig_v_v)
.exportFunc()
.addBody([
kExprRefNull, kStringRefCode,
...GCInstr(kExprStringAsIter),
kExprDrop
]);
builder.addFunction("next", kSig_i_v)
.exportFunc()
.addBody([
kExprGlobalGet, global.index,
...GCInstr(kExprStringViewIterNext)
]);
builder.addFunction("next_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kStringViewIterCode,
...GCInstr(kExprStringViewIterNext)
]);
builder.addFunction("advance", kSig_i_i)
.exportFunc()
.addBody([
kExprGlobalGet, global.index,
kExprLocalGet, 0,
...GCInstr(kExprStringViewIterAdvance)
]);
builder.addFunction("advance_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kStringViewIterCode,
kExprI32Const, 0,
...GCInstr(kExprStringViewIterAdvance)
]);
builder.addFunction("rewind", kSig_i_i)
.exportFunc()
.addBody([
kExprGlobalGet, global.index,
kExprLocalGet, 0,
...GCInstr(kExprStringViewIterRewind)
]);
builder.addFunction("rewind_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kStringViewIterCode,
kExprI32Const, 0,
...GCInstr(kExprStringViewIterRewind)
]);
builder.addFunction("slice", kSig_w_i)
.exportFunc()
.addBody([
kExprGlobalGet, global.index,
kExprLocalGet, 0,
...GCInstr(kExprStringViewIterSlice)
]);
builder.addFunction("slice_null", kSig_w_i)
.exportFunc()
.addBody([
kExprRefNull, kStringViewIterCode,
kExprI32Const, 0,
...GCInstr(kExprStringViewIterSlice)
]);
let instance = builder.instantiate();
for (let str of interestingStrings) {
let codepoints = [];
for (let codepoint of str) {
codepoints.push(codepoint.codePointAt(0));
}
instance.exports.iterate(str);
for (let codepoint of codepoints) {
assertEquals(codepoint, instance.exports.next());
}
assertEquals(-1, instance.exports.next());
assertEquals(-1, instance.exports.next());
for (let i = 1; i <= codepoints.length; i++) {
assertEquals(i, instance.exports.rewind(i));
assertEquals(codepoints[codepoints.length - i], instance.exports.next());
assertEquals(i - 1, instance.exports.advance(-1));
}
for (let i = 0; i < codepoints.length; i++) {
instance.exports.rewind(-1);
assertEquals(i, instance.exports.advance(i));
assertEquals(codepoints[i], instance.exports.next());
}
assertEquals(codepoints.length, instance.exports.rewind(-1));
assertEquals(0, instance.exports.rewind(-1));
assertEquals(codepoints.length, instance.exports.advance(-1));
assertEquals(0, instance.exports.advance(-1));
for (let start = 0; start <= codepoints.length; start++) {
for (let end = start; end <= codepoints.length; end++) {
let expected_slice =
String.fromCodePoint(...codepoints.slice(start, end));
instance.exports.iterate(str);
assertEquals(start, instance.exports.advance(start));
assertEquals(expected_slice, instance.exports.slice(end - start));
}
}
instance.exports.iterate(str);
assertEquals(str, instance.exports.slice(codepoints.length));
assertEquals(str, instance.exports.slice(-1));
assertEquals("", instance.exports.slice(0));
assertEquals(codepoints.length, instance.exports.advance(-1));
assertEquals("", instance.exports.slice(-1));
}
assertThrows(() => instance.exports.iterate_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
assertThrows(() => instance.exports.next_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
assertThrows(() => instance.exports.advance_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
assertThrows(() => instance.exports.rewind_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
assertThrows(() => instance.exports.slice_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
})();