Commit 18393c8c authored by Andy Wingo's avatar Andy Wingo Committed by V8 LUCI CQ

[stringrefs] Implement string.measure_utf8, string.measure_wtf8

Bug: v8:12868
Change-Id: I13b32086e736490c54b583a514ac3592179adf7f
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3695381
Commit-Queue: Andy Wingo <wingo@igalia.com>
Reviewed-by: 's avatarTobias Tebbi <tebbi@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81033}
parent 8fdb91cd
......@@ -43,6 +43,8 @@ extern runtime WasmStringNewWtf8(
extern runtime WasmStringNewWtf16(
Context, WasmInstanceObject, Smi, Number, Number): String;
extern runtime WasmStringConst(Context, WasmInstanceObject, Smi): String;
extern runtime WasmStringMeasureUtf8(Context, String): Number;
extern runtime WasmStringMeasureWtf8(Context, String): Number;
}
namespace unsafe {
......@@ -801,4 +803,12 @@ builtin WasmStringConst(index: uint32): String {
tail runtime::WasmStringConst(
LoadContextFromInstance(instance), instance, SmiFromUint32(index));
}
builtin WasmStringMeasureUtf8(string: String): int32 {
const result = runtime::WasmStringMeasureUtf8(LoadContextFromFrame(), string);
return Signed(ChangeNumberToUint32(result));
}
builtin WasmStringMeasureWtf8(string: String): int32 {
const result = runtime::WasmStringMeasureWtf8(LoadContextFromFrame(), string);
return Signed(ChangeNumberToUint32(result));
}
}
......@@ -3310,7 +3310,7 @@ void Pipeline::GenerateCodeForWasmFunction(
}
const bool is_asm_js = is_asmjs_module(module);
if (FLAG_experimental_wasm_gc) {
if (FLAG_experimental_wasm_gc || FLAG_experimental_wasm_stringref) {
pipeline.Run<WasmTypingPhase>(function_index);
pipeline.RunPrintAndVerify(WasmTypingPhase::phase_name(), true);
if (FLAG_wasm_opt) {
......
......@@ -5757,6 +5757,24 @@ Node* WasmGraphBuilder::StringConst(uint32_t index) {
gasm_->Uint32Constant(index));
}
Node* WasmGraphBuilder::StringMeasureUtf8(Node* string, CheckForNull null_check,
wasm::WasmCodePosition position) {
if (null_check == kWithNullCheck) {
string = AssertNotNull(string, position);
}
return gasm_->CallBuiltin(Builtin::kWasmStringMeasureUtf8, Operator::kNoDeopt,
string);
}
Node* WasmGraphBuilder::StringMeasureWtf8(Node* string, CheckForNull null_check,
wasm::WasmCodePosition position) {
if (null_check == kWithNullCheck) {
string = AssertNotNull(string, position);
}
return gasm_->CallBuiltin(Builtin::kWasmStringMeasureWtf8, Operator::kNoDeopt,
string);
}
// 1 bit V8 Smi tag, 31 bits V8 Smi shift, 1 bit i31ref high-bit truncation.
constexpr int kI31To32BitSmiShift = 33;
......
......@@ -541,6 +541,10 @@ class WasmGraphBuilder {
Node* StringNewWtf8(uint32_t memory, Node* offset, Node* size);
Node* StringNewWtf16(uint32_t memory, Node* offset, Node* size);
Node* StringConst(uint32_t index);
Node* StringMeasureUtf8(Node* string, CheckForNull null_check,
wasm::WasmCodePosition position);
Node* StringMeasureWtf8(Node* string, CheckForNull null_check,
wasm::WasmCodePosition position);
Node* IsNull(Node* object);
Node* TypeGuard(Node* value, wasm::ValueType type);
......
......@@ -15,6 +15,7 @@
#include "src/numbers/conversions.h"
#include "src/objects/objects-inl.h"
#include "src/runtime/runtime-utils.h"
#include "src/strings/unicode-inl.h"
#include "src/trap-handler/trap-handler.h"
#include "src/wasm/module-compiler.h"
#include "src/wasm/stacks.h"
......@@ -913,5 +914,70 @@ RUNTIME_FUNCTION(Runtime_WasmStringConst) {
return *result;
}
namespace {
template <typename T>
int MeasureWtf8(base::Vector<const T> wtf16) {
int previous = unibrow::Utf16::kNoPreviousCharacter;
int length = 0;
DCHECK(wtf16.size() <= String::kMaxLength);
static_assert(String::kMaxLength <=
(kMaxInt / unibrow::Utf8::kMaxEncodedSize));
for (size_t i = 0; i < wtf16.size(); i++) {
int current = wtf16[i];
length += unibrow::Utf8::Length(current, previous);
previous = current;
}
return length;
}
} // namespace
RUNTIME_FUNCTION(Runtime_WasmStringMeasureUtf8) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(1, args.length());
HandleScope scope(isolate);
Handle<String> string = args.at<String>(0);
string = String::Flatten(isolate, string);
int length;
{
DisallowGarbageCollection no_gc;
String::FlatContent content = string->GetFlatContent(no_gc);
DCHECK(content.IsFlat());
if (content.IsOneByte()) {
length = MeasureWtf8(content.ToOneByteVector());
} else {
base::Vector<const base::uc16> code_units = content.ToUC16Vector();
if (unibrow::Utf16::HasUnpairedSurrogate(code_units.begin(),
code_units.size())) {
length = -1;
} else {
length = MeasureWtf8(code_units);
}
}
}
return *isolate->factory()->NewNumberFromInt(length);
}
RUNTIME_FUNCTION(Runtime_WasmStringMeasureWtf8) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(1, args.length());
HandleScope scope(isolate);
Handle<String> string = args.at<String>(0);
string = String::Flatten(isolate, string);
int length;
{
DisallowGarbageCollection no_gc;
String::FlatContent content = string->GetFlatContent(no_gc);
DCHECK(content.IsFlat());
if (content.IsOneByte()) {
length = MeasureWtf8(content.ToOneByteVector());
} else {
length = MeasureWtf8(content.ToUC16Vector());
}
}
return *isolate->factory()->NewNumberFromInt(length);
}
} // namespace internal
} // namespace v8
......@@ -614,7 +614,9 @@ namespace internal {
F(WasmCreateResumePromise, 2, 1) \
F(WasmStringNewWtf8, 4, 1) \
F(WasmStringNewWtf16, 4, 1) \
F(WasmStringConst, 2, 1)
F(WasmStringConst, 2, 1) \
F(WasmStringMeasureUtf8, 1, 1) \
F(WasmStringMeasureWtf8, 1, 1)
#define FOR_EACH_INTRINSIC_WASM_TEST(F, I) \
F(DeserializeWasmModule, 2, 1) \
......
......@@ -6082,12 +6082,40 @@ class LiftoffCompiler {
void StringMeasureUtf8(FullDecoder* decoder, const Value& str,
Value* result) {
UNIMPLEMENTED();
LiftoffRegList pinned;
LiftoffRegister string_reg = pinned.set(__ PopToRegister(pinned));
MaybeEmitNullCheck(decoder, string_reg.gp(), pinned, str.type);
LiftoffAssembler::VarState string_var(kRef, string_reg, 0);
CallRuntimeStub(WasmCode::kWasmStringMeasureUtf8,
MakeSig::Returns(kI32).Params(kRef),
{
string_var,
},
decoder->position());
RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
LiftoffRegister result_reg(kReturnRegister0);
__ PushRegister(kI32, result_reg);
}
void StringMeasureWtf8(FullDecoder* decoder, const Value& str,
Value* result) {
UNIMPLEMENTED();
LiftoffRegList pinned;
LiftoffRegister string_reg = pinned.set(__ PopToRegister(pinned));
MaybeEmitNullCheck(decoder, string_reg.gp(), pinned, str.type);
LiftoffAssembler::VarState string_var(kRef, string_reg, 0);
CallRuntimeStub(WasmCode::kWasmStringMeasureWtf8,
MakeSig::Returns(kI32).Params(kRef),
{
string_var,
},
decoder->position());
RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
LiftoffRegister result_reg(kReturnRegister0);
__ PushRegister(kI32, result_reg);
}
void StringMeasureWtf16(FullDecoder* decoder, const Value& str,
......
......@@ -1413,12 +1413,14 @@ class WasmGraphBuildingInterface {
void StringMeasureUtf8(FullDecoder* decoder, const Value& str,
Value* result) {
UNIMPLEMENTED();
result->node = builder_->StringMeasureUtf8(str.node, NullCheckFor(str.type),
decoder->position());
}
void StringMeasureWtf8(FullDecoder* decoder, const Value& str,
Value* result) {
UNIMPLEMENTED();
result->node = builder_->StringMeasureWtf8(str.node, NullCheckFor(str.type),
decoder->position());
}
void StringMeasureWtf16(FullDecoder* decoder, const Value& str,
......
......@@ -125,7 +125,9 @@ struct WasmModule;
V(WasmSuspend) \
V(WasmStringNewWtf8) \
V(WasmStringNewWtf16) \
V(WasmStringConst)
V(WasmStringConst) \
V(WasmStringMeasureUtf8) \
V(WasmStringMeasureWtf8)
// Sorted, disjoint and non-overlapping memory regions. A region is of the
// form [start, end). So there's no [start, end), [end, other_end),
......
......@@ -8,6 +8,7 @@ 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]);
function encodeWtf8(str) {
// String iterator coalesces surrogate pairs.
......@@ -158,3 +159,59 @@ function makeWtf16TestDataSegment() {
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,
kGCPrefix, kExprStringMeasureUtf8
]);
builder.addFunction("string_measure_wtf8", kSig_i_w)
.exportFunc()
.addBody([
kExprLocalGet, 0,
kGCPrefix, kExprStringMeasureWtf8
]);
builder.addFunction("string_measure_utf8_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kStringRefCode,
kGCPrefix, kExprStringMeasureUtf8
]);
builder.addFunction("string_measure_wtf8_null", kSig_i_v)
.exportFunc()
.addBody([
kExprRefNull, kStringRefCode,
kGCPrefix, kExprStringMeasureWtf8
]);
function HasIsolatedSurrogate(str) {
for (let codepoint of str) {
let value = codepoint.codePointAt(0);
if (0xD800 <= value && value <= 0xDFFF) return true;
}
return false;
}
let instance = builder.instantiate();
for (let str of interestingStrings) {
let wtf8 = encodeWtf8(str);
assertEquals(wtf8.length, instance.exports.string_measure_wtf8(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");
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment