diff --git a/src/builtins/wasm.tq b/src/builtins/wasm.tq index a3887c6c51..7531de43df 100644 --- a/src/builtins/wasm.tq +++ b/src/builtins/wasm.tq @@ -1133,4 +1133,25 @@ builtin WasmStringViewIterRewind( view.offset = offset; return rewound; } +builtin WasmStringViewIterSlice( + view: WasmStringViewIter, codepoints: uint32): String { + const string = view.string; + const start = view.offset; + let end = view.offset; + let advanced: uint32 = 0; + while (advanced < codepoints) { + if (end == Unsigned(string.length)) break; + advanced = advanced + 1; + if (end + 1 < Unsigned(string.length) && + IsLeadSurrogate(StringCharCodeAt(string, Convert(end))) && + IsTrailSurrogate(StringCharCodeAt(string, Convert(end + 1)))) { + end = end + 2; + } else { + end = end + 1; + } + } + return (start == end) ? + kEmptyString : + string::SubString(string, Convert(start), Convert(end)); +} } diff --git a/src/compiler/wasm-compiler.cc b/src/compiler/wasm-compiler.cc index cc0a36076d..a372106af0 100644 --- a/src/compiler/wasm-compiler.cc +++ b/src/compiler/wasm-compiler.cc @@ -6007,6 +6007,15 @@ Node* WasmGraphBuilder::StringViewIterRewind(Node* view, Operator::kNoDeopt, view, codepoints); } +Node* WasmGraphBuilder::StringViewIterSlice(Node* view, CheckForNull null_check, + Node* codepoints, + wasm::WasmCodePosition position) { + if (null_check == kWithNullCheck) view = AssertNotNull(view, position); + + return gasm_->CallBuiltin(Builtin::kWasmStringViewIterSlice, + Operator::kNoDeopt, view, codepoints); +} + // 1 bit V8 Smi tag, 31 bits V8 Smi shift, 1 bit i31ref high-bit truncation. constexpr int kI31To32BitSmiShift = 33; diff --git a/src/compiler/wasm-compiler.h b/src/compiler/wasm-compiler.h index 7632aa87ab..6ef30e2405 100644 --- a/src/compiler/wasm-compiler.h +++ b/src/compiler/wasm-compiler.h @@ -596,6 +596,8 @@ class WasmGraphBuilder { wasm::WasmCodePosition position); Node* StringViewIterRewind(Node* view, CheckForNull null_check, Node* codepoints, wasm::WasmCodePosition position); + Node* StringViewIterSlice(Node* view, CheckForNull null_check, + Node* codepoints, wasm::WasmCodePosition position); Node* IsNull(Node* object); Node* TypeGuard(Node* value, wasm::ValueType type); diff --git a/src/wasm/baseline/liftoff-compiler.cc b/src/wasm/baseline/liftoff-compiler.cc index 72fdd69c64..726fa6d447 100644 --- a/src/wasm/baseline/liftoff-compiler.cc +++ b/src/wasm/baseline/liftoff-compiler.cc @@ -6918,7 +6918,27 @@ class LiftoffCompiler { void StringViewIterSlice(FullDecoder* decoder, const Value& view, const Value& codepoints, Value* result) { - UNIMPLEMENTED(); + LiftoffRegList pinned; + + LiftoffAssembler::VarState& codepoints_var = + __ cache_state()->stack_state.end()[-1]; + + LiftoffRegister view_reg = pinned.set( + __ LoadToRegister(__ cache_state()->stack_state.end()[-2], pinned)); + MaybeEmitNullCheck(decoder, view_reg.gp(), pinned, view.type); + LiftoffAssembler::VarState view_var(kRef, view_reg, 0); + + CallRuntimeStub(WasmCode::kWasmStringViewIterSlice, + MakeSig::Returns(kRef).Params(kRef, kI32), + { + view_var, + codepoints_var, + }, + decoder->position()); + RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill); + + LiftoffRegister result_reg(kReturnRegister0); + __ PushRegister(kRef, result_reg); } void Forward(FullDecoder* decoder, const Value& from, Value* to) { diff --git a/src/wasm/graph-builder-interface.cc b/src/wasm/graph-builder-interface.cc index d45140140a..3521d64499 100644 --- a/src/wasm/graph-builder-interface.cc +++ b/src/wasm/graph-builder-interface.cc @@ -1575,7 +1575,9 @@ class WasmGraphBuildingInterface { void StringViewIterSlice(FullDecoder* decoder, const Value& view, const Value& codepoints, Value* result) { - UNIMPLEMENTED(); + SetAndTypeNode(result, builder_->StringViewIterSlice( + view.node, NullCheckFor(view.type), + codepoints.node, decoder->position())); } void Forward(FullDecoder* decoder, const Value& from, Value* to) { diff --git a/src/wasm/wasm-code-manager.h b/src/wasm/wasm-code-manager.h index e27ed877ea..3d1e4be0ef 100644 --- a/src/wasm/wasm-code-manager.h +++ b/src/wasm/wasm-code-manager.h @@ -147,7 +147,8 @@ struct WasmModule; V(WasmStringAsIter) \ V(WasmStringViewIterNext) \ V(WasmStringViewIterAdvance) \ - V(WasmStringViewIterRewind) + V(WasmStringViewIterRewind) \ + V(WasmStringViewIterSlice) // Sorted, disjoint and non-overlapping memory regions. A region is of the // form [start, end). So there's no [start, end), [end, other_end), diff --git a/test/mjsunit/wasm/stringrefs-exec.js b/test/mjsunit/wasm/stringrefs-exec.js index 9b89573113..99a3aa9bb1 100644 --- a/test/mjsunit/wasm/stringrefs-exec.js +++ b/test/mjsunit/wasm/stringrefs-exec.js @@ -17,6 +17,7 @@ let kSig_i_wiii = makeSig([kWasmStringRef, 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]); @@ -1101,6 +1102,22 @@ function makeWtf16TestDataSegment() { kGCPrefix, kExprStringViewIterRewind ]); + builder.addFunction("slice", kSig_w_i) + .exportFunc() + .addBody([ + kExprGlobalGet, global.index, + kExprLocalGet, 0, + kGCPrefix, kExprStringViewIterSlice + ]); + + builder.addFunction("slice_null", kSig_w_i) + .exportFunc() + .addBody([ + kExprRefNull, kStringViewIterCode, + kExprI32Const, 0, + kGCPrefix, kExprStringViewIterSlice + ]); + let instance = builder.instantiate(); for (let str of interestingStrings) { @@ -1131,6 +1148,22 @@ function makeWtf16TestDataSegment() { 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(), @@ -1141,4 +1174,6 @@ function makeWtf16TestDataSegment() { 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"); })();