[stringrefs] Implement string.const

Current implementation doesn't cache the result, however.

Bug: v8:12868
Change-Id: Idd5eb7bbb49d018fec82a80bffb5288c0b6ee0f8
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3695377
Commit-Queue: Andy Wingo <wingo@igalia.com>
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81006}
This commit is contained in:
Andy Wingo 2022-06-08 17:39:39 +02:00 committed by V8 LUCI CQ
parent 62159ea316
commit c842874cb5
16 changed files with 171 additions and 44 deletions

View File

@ -42,6 +42,7 @@ extern runtime WasmStringNewWtf8(
Context, WasmInstanceObject, Smi, Number, Number): String;
extern runtime WasmStringNewWtf16(
Context, WasmInstanceObject, Smi, Number, Number): String;
extern runtime WasmStringConst(Context, WasmInstanceObject, Smi): String;
}
namespace unsafe {
@ -795,4 +796,9 @@ builtin WasmStringNewWtf16(
LoadContextFromInstance(instance), instance, SmiFromUint32(memory),
WasmUint32ToNumber(offset), WasmUint32ToNumber(size));
}
builtin WasmStringConst(index: uint32): String {
const instance = LoadInstanceFromFrame();
tail runtime::WasmStringConst(
LoadContextFromInstance(instance), instance, SmiFromUint32(index));
}
}

View File

@ -5752,6 +5752,11 @@ Node* WasmGraphBuilder::StringNewWtf16(uint32_t memory, Node* offset,
gasm_->Uint32Constant(memory), offset, size);
}
Node* WasmGraphBuilder::StringConst(uint32_t index) {
return gasm_->CallBuiltin(Builtin::kWasmStringConst, Operator::kNoDeopt,
gasm_->Uint32Constant(index));
}
// 1 bit V8 Smi tag, 31 bits V8 Smi shift, 1 bit i31ref high-bit truncation.
constexpr int kI31To32BitSmiShift = 33;

View File

@ -540,6 +540,7 @@ class WasmGraphBuilder {
Node** no_match_control, Node** no_match_effect);
Node* StringNewWtf8(uint32_t memory, Node* offset, Node* size);
Node* StringNewWtf16(uint32_t memory, Node* offset, Node* size);
Node* StringConst(uint32_t index);
Node* IsNull(Node* object);
Node* TypeGuard(Node* value, wasm::ValueType type);

View File

@ -886,5 +886,32 @@ RUNTIME_FUNCTION(Runtime_WasmStringNewWtf16) {
return *result;
}
// Returns the new string if the operation succeeds. Otherwise traps.
RUNTIME_FUNCTION(Runtime_WasmStringConst) {
ClearThreadInWasmScope flag_scope(isolate);
DCHECK_EQ(2, args.length());
HandleScope scope(isolate);
Handle<WasmInstanceObject> instance = args.at<WasmInstanceObject>(0);
static_assert(
base::IsInRange(wasm::kV8MaxWasmStringLiterals, 0, Smi::kMaxValue));
uint32_t index = args.positive_smi_value_at(1);
DCHECK_LT(index, instance->module()->stringref_literals.size());
const wasm::WasmStringRefLiteral& literal =
instance->module()->stringref_literals[index];
const base::Vector<const uint8_t> module_bytes =
instance->module_object().native_module()->wire_bytes();
const base::Vector<const uint8_t> string_bytes =
module_bytes.SubVector(literal.source.offset(),
literal.source.offset() + literal.source.length());
// TODO(12868): Override any exception with an uncatchable-by-wasm trap?
// TODO(12868): No need to re-validate WTF-8. Also, result should be cached.
Handle<String> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result, isolate->factory()->NewStringFromWtf8(string_bytes));
return *result;
}
} // namespace internal
} // namespace v8

View File

@ -613,7 +613,8 @@ namespace internal {
F(WasmSyncStackLimit, 0, 1) \
F(WasmCreateResumePromise, 2, 1) \
F(WasmStringNewWtf8, 4, 1) \
F(WasmStringNewWtf16, 4, 1)
F(WasmStringNewWtf16, 4, 1) \
F(WasmStringConst, 2, 1)
#define FOR_EACH_INTRINSIC_WASM_TEST(F, I) \
F(DeserializeWasmModule, 2, 1) \

View File

@ -6063,7 +6063,22 @@ class LiftoffCompiler {
void StringConst(FullDecoder* decoder,
const StringConstImmediate<validate>& imm, Value* result) {
UNIMPLEMENTED();
LiftoffRegList pinned;
LiftoffRegister index_reg =
pinned.set(__ GetUnusedRegister(kGpReg, pinned));
__ LoadConstant(index_reg, WasmValue(static_cast<int32_t>(imm.index)));
LiftoffAssembler::VarState index_var(kI32, index_reg, 0);
CallRuntimeStub(WasmCode::kWasmStringConst,
MakeSig::Returns(kRef).Params(kI32),
{
index_var,
},
decoder->position());
RegisterDebugSideTableEntry(decoder, DebugSideTableBuilder::kDidSpill);
LiftoffRegister result_reg(kReturnRegister0);
__ PushRegister(kRef, result_reg);
}
void StringMeasureUtf8(FullDecoder* decoder, const Value& str,

View File

@ -966,27 +966,28 @@ struct ControlBase : public PcForErrors<validate> {
F(NextInstruction, WasmOpcode) \
F(Forward, const Value& from, Value* to)
#define INTERFACE_CONSTANT_FUNCTIONS(F) \
F(I32Const, Value* result, int32_t value) \
F(I64Const, Value* result, int64_t value) \
F(F32Const, Value* result, float value) \
F(F64Const, Value* result, double value) \
F(S128Const, Simd128Immediate<validate>& imm, Value* result) \
F(BinOp, WasmOpcode opcode, const Value& lhs, const Value& rhs, \
Value* result) \
F(RefNull, ValueType type, Value* result) \
F(RefFunc, uint32_t function_index, Value* result) \
F(GlobalGet, Value* result, const GlobalIndexImmediate<validate>& imm) \
F(StructNewWithRtt, const StructIndexImmediate<validate>& imm, \
const Value& rtt, const Value args[], Value* result) \
F(StructNewDefault, const StructIndexImmediate<validate>& imm, \
const Value& rtt, Value* result) \
F(ArrayInit, const ArrayIndexImmediate<validate>& imm, \
const base::Vector<Value>& elements, const Value& rtt, Value* result) \
F(ArrayInitFromSegment, const ArrayIndexImmediate<validate>& array_imm, \
const IndexImmediate<validate>& data_segment, const Value& offset, \
const Value& length, const Value& rtt, Value* result) \
F(RttCanon, uint32_t type_index, Value* result) \
#define INTERFACE_CONSTANT_FUNCTIONS(F) /* force 80 columns */ \
F(I32Const, Value* result, int32_t value) \
F(I64Const, Value* result, int64_t value) \
F(F32Const, Value* result, float value) \
F(F64Const, Value* result, double value) \
F(S128Const, Simd128Immediate<validate>& imm, Value* result) \
F(BinOp, WasmOpcode opcode, const Value& lhs, const Value& rhs, \
Value* result) \
F(RefNull, ValueType type, Value* result) \
F(RefFunc, uint32_t function_index, Value* result) \
F(GlobalGet, Value* result, const GlobalIndexImmediate<validate>& imm) \
F(StructNewWithRtt, const StructIndexImmediate<validate>& imm, \
const Value& rtt, const Value args[], Value* result) \
F(StructNewDefault, const StructIndexImmediate<validate>& imm, \
const Value& rtt, Value* result) \
F(ArrayInit, const ArrayIndexImmediate<validate>& imm, \
const base::Vector<Value>& elements, const Value& rtt, Value* result) \
F(ArrayInitFromSegment, const ArrayIndexImmediate<validate>& array_imm, \
const IndexImmediate<validate>& data_segment, const Value& offset, \
const Value& length, const Value& rtt, Value* result) \
F(RttCanon, uint32_t type_index, Value* result) \
F(StringConst, const StringConstImmediate<validate>& imm, Value* result) \
F(DoReturn, uint32_t drop_values)
#define INTERFACE_NON_CONSTANT_FUNCTIONS(F) /* force 80 columns */ \
@ -1135,7 +1136,6 @@ struct ControlBase : public PcForErrors<validate> {
const Value& offset, const Value& size, Value* result) \
F(StringNewWtf16, const MemoryIndexImmediate<validate>& imm, \
const Value& offset, const Value& size, Value* result) \
F(StringConst, const StringConstImmediate<validate>& imm, Value* result) \
F(StringMeasureUtf8, const Value& str, Value* result) \
F(StringMeasureWtf8, const Value& str, Value* result) \
F(StringMeasureWtf16, const Value& str, Value* result) \

View File

@ -1408,7 +1408,7 @@ class WasmGraphBuildingInterface {
void StringConst(FullDecoder* decoder,
const StringConstImmediate<validate>& imm, Value* result) {
UNIMPLEMENTED();
result->node = builder_->StringConst(imm.index);
}
void StringMeasureUtf8(FullDecoder* decoder, const Value& str,

View File

@ -132,6 +132,26 @@ void InitExprInterface::StructNewWithRtt(
ValueType::Ref(HeapType(imm.index), kNonNullable));
}
void InitExprInterface::StringConst(FullDecoder* decoder,
const StringConstImmediate<validate>& imm,
Value* result) {
if (!generate_value()) return;
static_assert(base::IsInRange(kV8MaxWasmStringLiterals, 0, Smi::kMaxValue));
DCHECK_LT(imm.index, module_->stringref_literals.size());
const wasm::WasmStringRefLiteral& literal =
module_->stringref_literals[imm.index];
const base::Vector<const uint8_t> module_bytes =
instance_->module_object().native_module()->wire_bytes();
const base::Vector<const uint8_t> string_bytes =
module_bytes.SubVector(literal.source.offset(),
literal.source.offset() + literal.source.length());
Handle<String> string =
isolate_->factory()->NewStringFromWtf8(string_bytes).ToHandleChecked();
result->runtime_value = WasmValue(string, kWasmStringRef);
}
namespace {
WasmValue DefaultValueForType(ValueType type, Isolate* isolate) {
switch (type.kind()) {

View File

@ -124,7 +124,8 @@ struct WasmModule;
V(WasmOnStackReplace) \
V(WasmSuspend) \
V(WasmStringNewWtf8) \
V(WasmStringNewWtf16)
V(WasmStringNewWtf16) \
V(WasmStringConst)
// 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

@ -47,6 +47,8 @@ ValueType WasmInitExpr::type(const WasmModule* module,
return ValueType::Ref(immediate().index, kNonNullable);
case kRttCanon:
return ValueType::Rtt(immediate().heap_type);
case kStringConst:
return ValueType::Ref(HeapType::kString, kNonNullable);
}
}

View File

@ -43,6 +43,7 @@ class WasmInitExpr : public ZoneObject {
kArrayInit,
kArrayInitStatic,
kRttCanon,
kStringConst,
};
union Immediate {
@ -147,6 +148,13 @@ class WasmInitExpr : public ZoneObject {
return expr;
}
static WasmInitExpr StringConst(uint32_t index) {
WasmInitExpr expr;
expr.kind_ = kStringConst;
expr.immediate_.index = index;
return expr;
}
Immediate immediate() const { return immediate_; }
Operator kind() const { return kind_; }
const ZoneVector<WasmInitExpr>* operands() const { return operands_; }
@ -159,6 +167,7 @@ class WasmInitExpr : public ZoneObject {
case kGlobalGet:
case kRefFuncConst:
case kRttCanon:
case kStringConst:
return immediate().index == other.immediate().index;
case kI32Const:
return immediate().i32_const == other.immediate().i32_const;

View File

@ -577,6 +577,11 @@ void WriteInitializerExpressionWithEnd(ZoneBuffer* buffer,
buffer->write_u8(static_cast<uint8_t>(kExprRttCanon));
buffer->write_i32v(static_cast<int32_t>(init.immediate().index));
break;
case WasmInitExpr::kStringConst:
buffer->write_u8(kGCPrefix);
buffer->write_u8(static_cast<uint8_t>(kExprStringConst));
buffer->write_u32v(init.immediate().index);
break;
}
}

View File

@ -416,6 +416,11 @@ class InitExprInterface {
result->init_expr = WasmInitExpr::RttCanon(type_index);
}
void StringConst(FullDecoder* decoder,
const StringConstImmediate<validate>& imm, Value* result) {
result->init_expr = WasmInitExpr::StringConst(imm.index);
}
void DoReturn(FullDecoder* decoder, uint32_t /*drop_values*/) {
// End decoding on "end".
decoder->set_end(decoder->pc() + 1);
@ -493,6 +498,9 @@ void AppendInitExpr(std::ostream& os, const WasmInitExpr& expr) {
case WasmInitExpr::kRttCanon:
os << "RttCanon(" << expr.immediate().index;
break;
case WasmInitExpr::kStringConst:
os << "StringConst(" << expr.immediate().index;
break;
}
if (append_operands) {

View File

@ -7,6 +7,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]);
function encodeWtf8(str) {
// String iterator coalesces surrogate pairs.
@ -34,18 +35,20 @@ function encodeWtf8(str) {
return out;
}
let interestingStrings = ['',
'ascii',
'latin \xa9 1',
'two \ucccc byte',
'surrogate \ud800\udc000 pair',
'isolated \ud800 leading',
'isolated \udc00 trailing'];
function makeWtf8TestDataSegment() {
let data = []
let valid = {};
let invalid = {};
for (let str of ['',
'ascii',
'latin \xa9 1',
'two \ucccc byte',
'surrogate \ud800\udc000 pair',
'isolated \ud800 leading',
'isolated \udc00 trailing']) {
for (let str of interestingStrings) {
let bytes = encodeWtf8(str);
valid[str] = { offset: data.length, length: bytes.length };
for (let byte of bytes) {
@ -73,7 +76,7 @@ function makeWtf8TestDataSegment() {
builder.addDataSegment(0, data.data);
builder.addFunction("string_new_wtf8", kSig_w_ii)
.exportAs("string_new_wtf8")
.exportFunc()
.addBody([
kExprLocalGet, 0, kExprLocalGet, 1,
kGCPrefix, kExprStringNewWtf8, 0
@ -104,13 +107,7 @@ function makeWtf16TestDataSegment() {
let data = []
let valid = {};
for (let str of ['',
'ascii',
'latin \xa9 1',
'two \ucccc byte',
'surrogate \ud800\udc000 pair',
'isolated \ud800 leading',
'isolated \udc00 trailing']) {
for (let str of interestingStrings) {
valid[str] = { offset: data.length, length: str.length };
for (let byte of encodeWtf16LE(str)) {
data.push(byte);
@ -128,7 +125,7 @@ function makeWtf16TestDataSegment() {
builder.addDataSegment(0, data.data);
builder.addFunction("string_new_wtf16", kSig_w_ii)
.exportAs("string_new_wtf16")
.exportFunc()
.addBody([
kExprLocalGet, 0, kExprLocalGet, 1,
kGCPrefix, kExprStringNewWtf16, 0
@ -139,3 +136,25 @@ function makeWtf16TestDataSegment() {
assertEquals(str, instance.exports.string_new_wtf16(offset, length));
}
})();
(function TestStringConst() {
let builder = new WasmModuleBuilder();
for (let [index, str] of interestingStrings.entries()) {
builder.addLiteralStringRef(encodeWtf8(str));
builder.addFunction("string_const" + index, kSig_w_v)
.exportFunc()
.addBody([
kGCPrefix, kExprStringConst, index
]);
builder.addGlobal(kWasmStringRef, false, WasmInitExpr.StringConst(index))
.exportAs("global" + index);
}
let instance = builder.instantiate();
for (let [index, str] of interestingStrings.entries()) {
assertEquals(str, instance.exports["string_const" + index]());
assertEquals(str, instance.exports["global" + index].value);
}
})();

View File

@ -1108,16 +1108,21 @@ class Binary {
break;
case kExprRttSub:
this.emit_init_expr_recursive(expr.parent);
this.emit_u8(kGcPrefix);
this.emit_u8(kGCPrefix);
this.emit_u8(kExprRttSub);
this.emit_u32v(expr.value);
break;
case kExprRttFreshSub:
this.emit_init_expr_recursive(expr.parent);
this.emit_u8(kGcPrefix);
this.emit_u8(kGCPrefix);
this.emit_u8(kExprRttFreshSub);
this.emit_u32v(expr.value);
break;
case kExprStringConst:
this.emit_u8(kGCPrefix);
this.emit_u8(kExprStringConst);
this.emit_u32v(expr.index);
break;
}
}
@ -1306,6 +1311,9 @@ class WasmInitExpr {
static RttFreshSub(type, parent) {
return {kind: kExprRttFreshSub, value: type, parent: parent};
}
static StringConst(index) {
return {kind: kExprStringConst, index};
}
static defaultFor(type) {
switch (type) {