[wasm-stringrefs] Add string.hash instruction

Bug: v8:12868
Change-Id: I12ee551fbffc25c591d618f1957bbabbceff255e
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4217413
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Matthias Liedtke <mliedtke@chromium.org>
Cr-Commit-Position: refs/heads/main@{#85633}
This commit is contained in:
Matthias Liedtke 2023-02-02 16:53:48 +01:00 committed by V8 LUCI CQ
parent 31654be902
commit 14f1ec8cdd
13 changed files with 128 additions and 2 deletions

View File

@ -61,6 +61,7 @@ extern runtime WasmStringViewWtf8Slice(
Context, ByteArray, Number, Number): String;
extern runtime WasmStringCompare(NoContext, String, String): Smi;
extern runtime WasmStringFromCodePoint(Context, Number): String;
extern runtime WasmStringHash(NoContext, String): Smi;
extern runtime WasmJSToWasmObject(Context, JSAny, Smi): JSAny;
}
@ -1265,6 +1266,11 @@ builtin WasmStringFromCodePoint(codePoint: uint32): String {
LoadContextFromFrame(), WasmUint32ToNumber(codePoint));
}
builtin WasmStringHash(string: String): int32 {
const result = runtime::WasmStringHash(kNoContext, string);
return SmiToInt32(result);
}
builtin WasmExternInternalize(externObject: JSAny): JSAny {
const instance = LoadInstanceFromFrame();
const context = LoadContextFromInstance(instance);

View File

@ -39,6 +39,7 @@
#include "src/logging/counters.h"
#include "src/objects/heap-number.h"
#include "src/objects/instance-type.h"
#include "src/objects/name.h"
#include "src/objects/string.h"
#include "src/roots/roots.h"
#include "src/tracing/trace-event.h"
@ -6288,6 +6289,40 @@ Node* WasmGraphBuilder::StringFromCodePoint(Node* code_point) {
Operator::kEliminatable, code_point);
}
Node* WasmGraphBuilder::StringHash(Node* string, CheckForNull null_check,
wasm::WasmCodePosition position) {
if (null_check == kWithNullCheck) {
string = AssertNotNull(string, position);
}
auto runtime_label = gasm_->MakeLabel();
auto end_label = gasm_->MakeLabel(MachineRepresentation::kWord32);
Node* raw_hash = gasm_->LoadFromObject(
MachineType::Int32(), string,
wasm::ObjectAccess::ToTagged(Name::kRawHashFieldOffset));
Node* hash_not_computed_mask =
gasm_->Int32Constant(static_cast<int32_t>(Name::kHashNotComputedMask));
static_assert(Name::HashFieldTypeBits::kShift == 0);
Node* hash_not_computed = gasm_->Word32And(raw_hash, hash_not_computed_mask);
gasm_->GotoIf(hash_not_computed, &runtime_label);
// Fast path if hash is already computed: Decode raw hash value.
static_assert(Name::HashBits::kLastUsedBit == kBitsPerInt - 1);
Node* hash = gasm_->Word32Shr(
raw_hash,
gasm_->Int32Constant(static_cast<int32_t>(Name::HashBits::kShift)));
gasm_->Goto(&end_label, hash);
gasm_->Bind(&runtime_label);
Node* hash_runtime = gasm_->CallBuiltin(Builtin::kWasmStringHash,
Operator::kEliminatable, string);
gasm_->Goto(&end_label, hash_runtime);
gasm_->Bind(&end_label);
return end_label.PhiAt(0);
}
Node* WasmGraphBuilder::I31New(Node* input) {
if constexpr (SmiValuesAre31Bits()) {
return gasm_->Word32Shl(input, gasm_->BuildSmiShiftBitsConstant32());

View File

@ -598,6 +598,8 @@ class WasmGraphBuilder {
CheckForNull null_check_rhs,
wasm::WasmCodePosition position);
Node* StringFromCodePoint(Node* code_point);
Node* StringHash(Node* string, CheckForNull null_check,
wasm::WasmCodePosition position);
Node* IsNull(Node* object);
Node* TypeGuard(Node* value, wasm::ValueType type);

View File

@ -1400,5 +1400,13 @@ RUNTIME_FUNCTION(Runtime_WasmStringFromCodePoint) {
return *result;
}
RUNTIME_FUNCTION(Runtime_WasmStringHash) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(1, args.length());
String string(String::cast(args[0]));
uint32_t hash = string.EnsureHash();
return Smi::FromInt(static_cast<int>(hash));
}
} // namespace internal
} // namespace v8

View File

@ -654,7 +654,8 @@ namespace internal {
F(WasmStringViewWtf8Encode, 6, 1) \
F(WasmStringViewWtf8Slice, 3, 1) \
F(WasmStringCompare, 2, 1) \
F(WasmStringFromCodePoint, 1, 1)
F(WasmStringFromCodePoint, 1, 1) \
F(WasmStringHash, 1, 1)
#define FOR_EACH_INTRINSIC_WASM_TEST(F, I) \
F(DeserializeWasmModule, 2, 1) \

View File

@ -7304,6 +7304,22 @@ class LiftoffCompiler {
__ PushRegister(kRef, result_reg);
}
void StringHash(FullDecoder* decoder, const Value& string, Value* result) {
LiftoffRegList pinned;
LiftoffRegister string_reg = pinned.set(
__ LoadToRegister(__ cache_state()->stack_state.end()[-1], pinned));
MaybeEmitNullCheck(decoder, string_reg.gp(), pinned, string.type);
LiftoffAssembler::VarState string_var(kRef, string_reg, 0);
CallRuntimeStub(WasmCode::kWasmStringHash,
MakeSig::Returns(kI32).Params(kRef), {string_var},
decoder->position());
LiftoffRegister result_reg(kReturnRegister0);
__ DropValues(1);
__ PushRegister(kI32, result_reg);
}
void Forward(FullDecoder* decoder, const Value& from, Value* to) {
// Nothing to do here.
}

View File

@ -1194,7 +1194,8 @@ struct ControlBase : public PcForErrors<ValidationTag::full_validation> {
F(StringViewIterSlice, const Value& view, const Value& codepoints, \
Value* result) \
F(StringCompare, const Value& lhs, const Value& rhs, Value* result) \
F(StringFromCodePoint, const Value& code_point, Value* result)
F(StringFromCodePoint, const Value& code_point, Value* result) \
F(StringHash, const Value& string, Value* result)
// This is a global constant invalid instruction trace, to be pointed at by
// the current instruction trace pointer in the default case
@ -2328,6 +2329,7 @@ class WasmDecoder : public Decoder {
case kExprStringEncodeWtf16Array:
case kExprStringCompare:
case kExprStringFromCodePoint:
case kExprStringHash:
return length;
default:
// This is unreachable except for malformed modules.
@ -2531,6 +2533,7 @@ class WasmDecoder : public Decoder {
case kExprStringViewWtf16Length:
case kExprStringViewIterNext:
case kExprStringFromCodePoint:
case kExprStringHash:
return { 1, 1 };
case kExprStringNewUtf8:
case kExprStringNewUtf8Try:
@ -6187,6 +6190,15 @@ class WasmFullDecoder : public WasmDecoder<ValidationTag, decoding_mode> {
Push(result);
return opcode_length;
}
case kExprStringHash: {
NON_CONST_ONLY
Value string = Peek(0, 0, kWasmStringRef);
Value result = CreateValue(kWasmI32);
CALL_INTERFACE_IF_OK_AND_REACHABLE(StringHash, string, &result);
Drop(1);
Push(result);
return opcode_length;
}
default:
this->DecodeError("invalid stringref opcode: %x", opcode);
return 0;

View File

@ -1799,6 +1799,12 @@ class WasmGraphBuildingInterface {
SetAndTypeNode(result, builder_->StringFromCodePoint(code_point.node));
}
void StringHash(FullDecoder* decoder, const Value& string, Value* result) {
SetAndTypeNode(result,
builder_->StringHash(string.node, NullCheckFor(string.type),
decoder->position()));
}
void Forward(FullDecoder* decoder, const Value& from, Value* to) {
if (from.type == to->type) {
to->node = from.node;

View File

@ -151,6 +151,7 @@ struct WasmModule;
V(WasmStringViewIterSlice) \
V(WasmStringCompare) \
V(WasmStringFromCodePoint) \
V(WasmStringHash) \
V(WasmExternInternalize)
// Sorted, disjoint and non-overlapping memory regions. A region is of the

View File

@ -765,6 +765,7 @@ bool V8_EXPORT_PRIVATE IsJSCompatibleSignature(const FunctionSig* sig);
V(StringViewIterSlice, 0xfba4, _, "stringview_iter.slice") \
V(StringCompare, 0xfba8, _, "string.compare") \
V(StringFromCodePoint, 0xfba9, _, "string.from_code_point") \
V(StringHash, 0xfbaa, _, "string.hash") \
V(StringNewUtf8Array, 0xfbb0, _, "string.new_utf8_array") \
V(StringNewWtf16Array, 0xfbb1, _, "string.new_wtf16_array") \
V(StringEncodeUtf8Array, 0xfbb2, _, "string.encode_utf8_array") \

View File

@ -1296,3 +1296,34 @@ function makeWtf16TestDataSegment() {
WebAssembly.RuntimeError, /Invalid code point [0-9]+/);
}
})();
(function TestStringHash() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
builder.addFunction("hash", kSig_i_w)
.exportFunc()
.addBody([
kExprLocalGet, 0,
...GCInstr(kExprStringHash),
]);
let hash = builder.instantiate().exports.hash;
assertEquals(hash(""), hash(""));
assertEquals(hash("foo"), hash("foo"));
assertEquals(hash("bar"), hash("bar"));
assertEquals(hash("123"), hash("123"));
// Assuming that hash collisions are very rare.
assertNotEquals(hash("foo"), hash("bar"));
// Test with cons strings.
assertEquals(hash("f" + "o" + "o"), hash("foo"));
assertEquals(hash("f" + 1), hash("f1"));
assertEquals(hash(new String(" foo ").trim()), hash("foo"));
assertEquals(hash(new String("xfoox").substring(1, 4)), hash("foo"));
// Test integer index hash.
let dummy_obj = {123: 456};
let index_string = "123";
assertEquals(456, dummy_obj[index_string]);
assertEquals(hash("1" + "23"), hash(index_string));
})();

View File

@ -257,6 +257,12 @@ let kSig_w_zi = makeSig([kWasmStringViewIter, kWasmI32],
...GCInstr(kExprStringFromCodePoint)
]);
builder.addFunction("string.hash", kSig_i_w)
.addBody([
kExprLocalGet, 0,
...GCInstr(kExprStringHash)
]);
let i8_array = builder.addArray(kWasmI8, true);
let i16_array = builder.addArray(kWasmI16, true);

View File

@ -569,6 +569,7 @@ let kExprStringViewIterRewind = 0xa3
let kExprStringViewIterSlice = 0xa4;
let kExprStringCompare = 0xa8;
let kExprStringFromCodePoint = 0xa9;
let kExprStringHash = 0xaa;
let kExprStringNewUtf8Array = 0xb0;
let kExprStringNewWtf16Array = 0xb1;
let kExprStringEncodeUtf8Array = 0xb2;