[stringrefs] Implement string.encode_wtf16_array

Bug: v8:12868
Change-Id: I4229cefc4dfdb29214712aeef18841092cdf9e87
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3748653
Commit-Queue: Andy Wingo <wingo@igalia.com>
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81600}
This commit is contained in:
Andy Wingo 2022-07-08 10:06:34 +02:00 committed by V8 LUCI CQ
parent 2b2ce1d8be
commit 9662376ad9
7 changed files with 155 additions and 3 deletions

View File

@ -881,6 +881,40 @@ builtin WasmStringEncodeWtf16(
WasmUint32ToNumber(offset), SmiConstant(0), SmiFromInt32(string.length));
return Unsigned(string.length);
}
builtin WasmStringEncodeWtf16Array(
string: String, array: WasmArray, start: uint32): uint32 {
try {
if (start > array.length) goto OffsetOutOfRange;
if (array.length - start < Unsigned(string.length)) goto OffsetOutOfRange;
const byteOffset: intptr = kWasmArrayHeaderSize +
torque_internal::TimesSizeOf<char16>(Convert<intptr>(start));
const arrayContent = torque_internal::unsafe::NewMutableSlice<char16>(
array, byteOffset, Convert<intptr>(string.length));
try {
StringToSlice(string) otherwise OneByte, TwoByte;
} label OneByte(slice: ConstSlice<char8>) {
let fromIt = slice.Iterator();
let toIt = arrayContent.Iterator();
while (true) {
let toRef = toIt.NextReference() otherwise break;
*toRef = %RawDownCast<char16>(Convert<uint16>(fromIt.NextNotEmpty()));
}
} label TwoByte(slice: ConstSlice<char16>) {
let fromIt = slice.Iterator();
let toIt = arrayContent.Iterator();
while (true) {
let toRef = toIt.NextReference() otherwise break;
*toRef = fromIt.NextNotEmpty();
}
}
return Unsigned(string.length);
} label OffsetOutOfRange deferred {
const error = MessageTemplate::kWasmTrapArrayOutOfBounds;
runtime::ThrowWasmError(LoadContextFromFrame(), SmiConstant(error));
unreachable;
}
}
builtin WasmStringConcat(a: String, b: String): String {
const context = LoadContextFromFrame();
return a + b;

View File

@ -5853,6 +5853,20 @@ Node* WasmGraphBuilder::StringEncodeWtf16(uint32_t memory, Node* string,
string, offset, gasm_->SmiConstant(memory));
}
Node* WasmGraphBuilder::StringEncodeWtf16Array(
Node* string, CheckForNull string_null_check, Node* array,
CheckForNull array_null_check, Node* start,
wasm::WasmCodePosition position) {
if (string_null_check == kWithNullCheck) {
string = AssertNotNull(string, position);
}
if (array_null_check == kWithNullCheck) {
array = AssertNotNull(array, position);
}
return gasm_->CallBuiltin(Builtin::kWasmStringEncodeWtf16Array,
Operator::kNoDeopt, string, array, start);
}
Node* WasmGraphBuilder::StringConcat(Node* head, CheckForNull head_null_check,
Node* tail, CheckForNull tail_null_check,
wasm::WasmCodePosition position) {

View File

@ -558,6 +558,9 @@ class WasmGraphBuilder {
Node* StringEncodeWtf16(uint32_t memory, Node* string,
CheckForNull null_check, Node* offset,
wasm::WasmCodePosition position);
Node* StringEncodeWtf16Array(Node* string, CheckForNull string_null_check,
Node* array, CheckForNull array_null_check,
Node* start, wasm::WasmCodePosition position);
Node* StringConcat(Node* head, CheckForNull head_null_check, Node* tail,
CheckForNull tail_null_check,
wasm::WasmCodePosition position);

View File

@ -6472,7 +6472,34 @@ class LiftoffCompiler {
void StringEncodeWtf16Array(FullDecoder* decoder, const Value& str,
const Value& array, const Value& start,
Value* result) {
UNIMPLEMENTED();
LiftoffRegList pinned;
LiftoffRegister array_reg = pinned.set(
__ LoadToRegister(__ cache_state()->stack_state.end()[-2], pinned));
MaybeEmitNullCheck(decoder, array_reg.gp(), pinned, array.type);
LiftoffAssembler::VarState array_var(kRef, array_reg, 0);
LiftoffRegister string_reg = pinned.set(
__ LoadToRegister(__ cache_state()->stack_state.end()[-3], pinned));
MaybeEmitNullCheck(decoder, string_reg.gp(), pinned, str.type);
LiftoffAssembler::VarState string_var(kRef, string_reg, 0);
LiftoffAssembler::VarState& start_var =
__ cache_state()->stack_state.end()[-1];
CallRuntimeStub(WasmCode::kWasmStringEncodeWtf16Array,
MakeSig::Returns(kI32).Params(kRef, kRef, kI32),
{
string_var,
array_var,
start_var,
},
decoder->position());
__ DropValues(3);
RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
LiftoffRegister result_reg(kReturnRegister0);
__ PushRegister(kI32, result_reg);
}
void StringConcat(FullDecoder* decoder, const Value& head, const Value& tail,

View File

@ -1465,7 +1465,9 @@ class WasmGraphBuildingInterface {
void StringEncodeWtf16Array(FullDecoder* decoder, const Value& str,
const Value& array, const Value& start,
Value* result) {
UNIMPLEMENTED();
result->node = builder_->StringEncodeWtf16Array(
str.node, NullCheckFor(str.type), array.node, NullCheckFor(array.type),
start.node, decoder->position());
}
void StringConcat(FullDecoder* decoder, const Value& head, const Value& tail,

View File

@ -138,7 +138,8 @@ struct WasmModule;
V(WasmStringViewWtf16Slice) \
V(WasmStringNewWtf8Array) \
V(WasmStringNewWtf16Array) \
V(WasmStringEncodeWtf8Array)
V(WasmStringEncodeWtf8Array) \
V(WasmStringEncodeWtf16Array)
// Sorted, disjoint and non-overlapping memory regions. A region is of the
// form [start, end). So there's no [start, end), [end, other_end),

View File

@ -362,3 +362,74 @@ function makeWtf16TestDataSegment() {
WebAssembly.RuntimeError, message);
}
})();
(function TestStringEncodeWtf16Array() {
let builder = new WasmModuleBuilder();
let i16_array = builder.addArray(kWasmI16, true);
let kSig_w_wii =
makeSig([kWasmStringRef, kWasmI32, kWasmI32],
[kWasmStringRef]);
// Allocate an array and encode into it. Then decode it.
// (str, length, offset=0) -> str
builder.addFunction("encode", kSig_w_wii)
.exportFunc()
.addLocals(wasmRefNullType(i16_array), 1)
.addLocals(kWasmI32, 1)
.addBody([
// Allocate buffer.
kExprLocalGet, 1,
kGCPrefix, kExprArrayNewDefault, i16_array,
kExprLocalSet, 3,
// Write buffer, store number of code units written.
kExprLocalGet, 0,
kExprLocalGet, 3,
kExprLocalGet, 2,
kGCPrefix, kExprStringEncodeWtf16Array,
kExprLocalSet, 4,
// Read buffer.
kExprLocalGet, 3,
kExprLocalGet, 2,
kExprLocalGet, 2, kExprLocalGet, 4, kExprI32Add,
kGCPrefix, kExprStringNewWtf16Array,
]);
builder.addFunction("encode_null_string", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kStringRefCode,
kExprI32Const, 0, kGCPrefix, kExprArrayNewDefault, i16_array,
kExprI32Const, 0,
kGCPrefix, kExprStringEncodeWtf16Array
]);
builder.addFunction("encode_null_array", kSig_i_v)
.exportFunc()
.addBody([
kExprI32Const, 0, kGCPrefix, kExprArrayNewDefault, i16_array,
kExprI32Const, 0, kExprI32Const, 0,
kGCPrefix, kExprStringNewWtf16Array,
kExprRefNull, i16_array,
kExprI32Const, 0,
kGCPrefix, kExprStringEncodeWtf16Array
]);
let instance = builder.instantiate();
for (let str of interestingStrings) {
assertEquals(str, instance.exports.encode(str, str.length, 0));
assertEquals(str, instance.exports.encode(str, str.length + 20, 10));
}
assertThrows(() => instance.exports.encode_null_array(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
assertThrows(() => instance.exports.encode_null_string(),
WebAssembly.RuntimeError, "dereferencing a null pointer");
for (let str of interestingStrings) {
let message = "array element access out of bounds";
assertThrows(() => instance.exports.encode(str, str.length, 1),
WebAssembly.RuntimeError, message);
}
})();