[stringrefs] Implement stringview_wtf16.encode

Bug: v8:12868
Change-Id: I9b7cbd3851b3819bcc2c32e273ddae16b9d812ca
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3702266
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Andy Wingo <wingo@igalia.com>
Cr-Commit-Position: refs/heads/main@{#81144}
This commit is contained in:
Andy Wingo 2022-06-14 13:38:14 +02:00 committed by V8 LUCI CQ
parent a6b7f1f3f4
commit 9efa9e3c92
7 changed files with 171 additions and 23 deletions

View File

@ -841,6 +841,19 @@ transitioning builtin WasmStringViewWtf16GetCodeUnit(
unreachable;
}
}
builtin WasmStringViewWtf16Encode(
offset: uint32, start: uint32, length: uint32, string: String,
memory: Smi): JSAny {
const instance = LoadInstanceFromFrame();
const clampedStart =
start < Unsigned(string.length) ? start : Unsigned(string.length);
const maxLength = Unsigned(string.length) - clampedStart;
const clampedLength = length < maxLength ? length : maxLength;
tail runtime::WasmStringEncodeWtf16(
LoadContextFromInstance(instance), instance, memory, string,
WasmUint32ToNumber(offset), SmiFromUint32(clampedStart),
SmiFromUint32(clampedLength));
}
transitioning builtin WasmStringViewWtf16Slice(
string: String, start: uint32, end: uint32): String {
const length = Unsigned(string.length);

View File

@ -5819,6 +5819,19 @@ Node* WasmGraphBuilder::StringViewWtf16GetCodeUnit(
Operator::kNoDeopt, string, offset);
}
Node* WasmGraphBuilder::StringViewWtf16Encode(uint32_t memory, Node* string,
CheckForNull null_check,
Node* offset, Node* start,
Node* codeunits,
wasm::WasmCodePosition position) {
if (null_check == kWithNullCheck) {
string = AssertNotNull(string, position);
}
return gasm_->CallBuiltin(Builtin::kWasmStringViewWtf16Encode,
Operator::kNoDeopt, offset, start, codeunits,
string, gasm_->SmiConstant(memory));
}
Node* WasmGraphBuilder::StringViewWtf16Slice(Node* string,
CheckForNull null_check,
Node* start, Node* end,

View File

@ -556,6 +556,10 @@ class WasmGraphBuilder {
Node* StringViewWtf16GetCodeUnit(Node* string, CheckForNull null_check,
Node* offset,
wasm::WasmCodePosition position);
Node* StringViewWtf16Encode(uint32_t memory, Node* string,
CheckForNull null_check, Node* offset,
Node* start, Node* length,
wasm::WasmCodePosition position);
Node* StringViewWtf16Slice(Node* string, CheckForNull null_check, Node* start,
Node* end, wasm::WasmCodePosition position);
Node* IsNull(Node* object);

View File

@ -6254,9 +6254,39 @@ class LiftoffCompiler {
void StringViewWtf16Encode(FullDecoder* decoder,
const MemoryIndexImmediate<validate>& imm,
const Value& view, const Value& addr,
const Value& view, const Value& offset,
const Value& pos, const Value& codeunits) {
UNIMPLEMENTED();
LiftoffRegList pinned;
LiftoffAssembler::VarState& codeunits_var =
__ cache_state()->stack_state.end()[-1];
LiftoffAssembler::VarState& pos_var =
__ cache_state()->stack_state.end()[-2];
LiftoffAssembler::VarState& offset_var =
__ cache_state()->stack_state.end()[-3];
LiftoffRegister view_reg = pinned.set(
__ LoadToRegister(__ cache_state()->stack_state.end()[-4], pinned));
MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type);
LiftoffAssembler::VarState view_var(kRef, view_reg, 0);
LiftoffRegister memory_reg =
pinned.set(__ GetUnusedRegister(kGpReg, pinned));
LoadSmi(memory_reg, imm.index);
LiftoffAssembler::VarState memory_var(kPointerKind, memory_reg, 0);
CallRuntimeStub(WasmCode::kWasmStringViewWtf16Encode,
MakeSig::Params(kI32, kI32, kI32, kRef, kSmiKind),
{
offset_var,
pos_var,
codeunits_var,
view_var,
memory_var,
},
decoder->position());
__ DropValues(4);
RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
}
void StringViewWtf16Slice(FullDecoder* decoder, const Value& view,

View File

@ -1488,9 +1488,11 @@ class WasmGraphBuildingInterface {
void StringViewWtf16Encode(FullDecoder* decoder,
const MemoryIndexImmediate<validate>& imm,
const Value& view, const Value& addr,
const Value& view, const Value& offset,
const Value& pos, const Value& codeunits) {
UNIMPLEMENTED();
builder_->StringViewWtf16Encode(
imm.index, view.node, NullCheckFor(view.type), offset.node, pos.node,
codeunits.node, decoder->position());
}
void StringViewWtf16Slice(FullDecoder* decoder, const Value& view,

View File

@ -131,6 +131,7 @@ struct WasmModule;
V(WasmStringEncodeWtf8) \
V(WasmStringEncodeWtf16) \
V(WasmStringViewWtf16GetCodeUnit) \
V(WasmStringViewWtf16Encode) \
V(WasmStringViewWtf16Slice)
// Sorted, disjoint and non-overlapping memory regions. A region is of the

View File

@ -13,6 +13,8 @@ let kSig_i_wi = makeSig([kWasmStringRef, kWasmI32], [kWasmI32]);
let kSig_w_wii = makeSig([kWasmStringRef, kWasmI32, kWasmI32],
[kWasmStringRef]);
let kSig_v_wi = makeSig([kWasmStringRef, kWasmI32], []);
let kSig_v_wiii = makeSig([kWasmStringRef, kWasmI32, kWasmI32, kWasmI32],
[]);
function encodeWtf8(str) {
// String iterator coalesces surrogate pairs.
@ -434,7 +436,9 @@ function HasIsolatedSurrogate(str) {
(function TestStringViewWtf16() {
let builder = new WasmModuleBuilder();
builder.addFunction("string_view_wtf16_length", kSig_i_w)
builder.addMemory(1, undefined, true /* exported */, false);
builder.addFunction("length", kSig_i_w)
.exportFunc()
.addBody([
kExprLocalGet, 0,
@ -442,14 +446,14 @@ function HasIsolatedSurrogate(str) {
kGCPrefix, kExprStringViewWtf16Length
]);
builder.addFunction("string_view_wtf16_length_null", kSig_i_v)
builder.addFunction("length_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kStringViewWtf16Code,
kGCPrefix, kExprStringViewWtf16Length
]);
builder.addFunction("string_view_wtf16_get_codeunit", kSig_i_wi)
builder.addFunction("get_codeunit", kSig_i_wi)
.exportFunc()
.addBody([
kExprLocalGet, 0,
@ -458,7 +462,7 @@ function HasIsolatedSurrogate(str) {
kGCPrefix, kExprStringViewWtf16GetCodeunit
]);
builder.addFunction("string_view_wtf16_get_codeunit_null", kSig_i_v)
builder.addFunction("get_codeunit_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kStringViewWtf16Code,
@ -466,7 +470,28 @@ function HasIsolatedSurrogate(str) {
kGCPrefix, kExprStringViewWtf16GetCodeunit
]);
builder.addFunction("string_view_wtf16_slice", kSig_w_wii)
builder.addFunction("encode", kSig_v_wiii)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprStringAsWtf16,
kExprLocalGet, 1,
kExprLocalGet, 2,
kExprLocalGet, 3,
kGCPrefix, kExprStringViewWtf16Encode, 0
]);
builder.addFunction("encode_null", kSig_v_v)
.exportFunc()
.addBody([
kExprRefNull, kStringViewWtf16Code,
kExprI32Const, 0,
kExprI32Const, 0,
kExprI32Const, 0,
kGCPrefix, kExprStringViewWtf16Encode, 0
]);
builder.addFunction("slice", kSig_w_wii)
.exportFunc()
.addBody([
kExprLocalGet, 0,
@ -476,7 +501,7 @@ function HasIsolatedSurrogate(str) {
kGCPrefix, kExprStringViewWtf16Slice
]);
builder.addFunction("string_view_wtf16_slice_null", kSig_w_v)
builder.addFunction("slice_null", kSig_w_v)
.exportFunc()
.addBody([
kExprRefNull, kStringViewWtf16Code,
@ -486,28 +511,88 @@ function HasIsolatedSurrogate(str) {
]);
let instance = builder.instantiate();
let memory = new Uint8Array(instance.exports.memory.buffer);
for (let str of interestingStrings) {
assertEquals(str.length, instance.exports.string_view_wtf16_length(str));
assertEquals(str.length, instance.exports.length(str));
for (let i = 0; i < str.length; i++) {
assertEquals(str.charCodeAt(i),
instance.exports.string_view_wtf16_get_codeunit(str, i));
instance.exports.get_codeunit(str, i));
}
assertEquals(str, instance.exports.string_view_wtf16_slice(str, 0, -1));
assertEquals(str, instance.exports.slice(str, 0, -1));
}
assertEquals("f", instance.exports.string_view_wtf16_slice("foo", 0, 0));
assertEquals("fo", instance.exports.string_view_wtf16_slice("foo", 0, 1));
assertEquals("foo", instance.exports.string_view_wtf16_slice("foo", 0, 2));
assertEquals("oo", instance.exports.string_view_wtf16_slice("foo", 1, 2));
assertEquals("oo", instance.exports.string_view_wtf16_slice("foo", 1, 100));
assertEquals("", instance.exports.string_view_wtf16_slice("foo", 1, 0));
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));
}
assertThrows(() => instance.exports.string_view_wtf16_length_null(),
for (let offset of [0, 42, memory.length - bytes.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("f", instance.exports.slice("foo", 0, 0));
assertEquals("fo", instance.exports.slice("foo", 0, 1));
assertEquals("foo", instance.exports.slice("foo", 0, 2));
assertEquals("oo", instance.exports.slice("foo", 1, 2));
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.string_view_wtf16_get_codeunit_null(),
assertThrows(() => instance.exports.get_codeunit_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
assertThrows(() => instance.exports.string_view_wtf16_get_codeunit("", 0),
assertThrows(() => instance.exports.get_codeunit("", 0),
WebAssembly.RuntimeError, "string offset out of bounds");
assertThrows(() => instance.exports.string_view_wtf16_slice_null(),
assertThrows(() => instance.exports.encode_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
assertThrows(() => instance.exports.slice_null(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
})();