[stringrefs] Fix inlining, and some corner case bugs
1) Inlining functions that contain stringref operations require builtin calls to be marked as kNoThrow appropriately (or have exception handling support in the graph). 2) Some overly-large inputs for string creation hit DCHECKs before getting to the places where they would have thrown an orderly exception. 3) We still had a known issue that some exceptions thrown by JS-focused code were erroneously catchable by Wasm. 4) When string.concat attempted to create a too-long string, it ran into a DCHECK because we didn't clear the "thread in wasm" flag. 5) The builtin call for string.concat was erroneously marked as kEliminatable, which could cause the trap get eliminated. Bug: v8:12868 Change-Id: Iad3ada0e2465bfd8f3d00bb064c32049d6b19d87 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3902522 Auto-Submit: Jakob Kummerow <jkummerow@chromium.org> Commit-Queue: Andy Wingo <wingo@igalia.com> Reviewed-by: Andy Wingo <wingo@igalia.com> Cr-Commit-Position: refs/heads/main@{#83292}
This commit is contained in:
parent
e06001f2aa
commit
0661a0dd8f
@ -5725,36 +5725,49 @@ void WasmGraphBuilder::ArrayCopy(Node* dst_array, Node* dst_index,
|
||||
gasm_->Bind(&skip);
|
||||
}
|
||||
|
||||
// General rules for operator properties for builtin calls:
|
||||
// - Use kEliminatable if it can neither throw a catchable exception nor trap.
|
||||
// - Use kNoDeopt | kNoThrow if it can trap (because in that case, eliminating
|
||||
// it would avoid the trap and thereby observably change the code's behavior
|
||||
// compared to its unoptimized version).
|
||||
// - If you don't use kNoThrow (nor kEliminatable which implies it), then you
|
||||
// must also set up control nodes for the throwing case, e.g. by using
|
||||
// WasmGraphBuildingInterface::CheckForException().
|
||||
|
||||
Node* WasmGraphBuilder::StringNewWtf8(uint32_t memory,
|
||||
unibrow::Utf8Variant variant,
|
||||
Node* offset, Node* size) {
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringNewWtf8, Operator::kNoDeopt,
|
||||
offset, size, gasm_->SmiConstant(memory),
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringNewWtf8,
|
||||
Operator::kNoDeopt | Operator::kNoThrow, offset,
|
||||
size, gasm_->SmiConstant(memory),
|
||||
gasm_->SmiConstant(static_cast<int32_t>(variant)));
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::StringNewWtf8Array(unibrow::Utf8Variant variant,
|
||||
Node* array, Node* start,
|
||||
Node* end) {
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringNewWtf8Array,
|
||||
Operator::kNoDeopt, start, end, array,
|
||||
gasm_->SmiConstant(static_cast<int32_t>(variant)));
|
||||
return gasm_->CallBuiltin(
|
||||
Builtin::kWasmStringNewWtf8Array, Operator::kNoDeopt | Operator::kNoThrow,
|
||||
start, end, array, gasm_->SmiConstant(static_cast<int32_t>(variant)));
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::StringNewWtf16(uint32_t memory, Node* offset,
|
||||
Node* size) {
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringNewWtf16, Operator::kNoDeopt,
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringNewWtf16,
|
||||
Operator::kNoDeopt | Operator::kNoThrow,
|
||||
gasm_->Uint32Constant(memory), offset, size);
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::StringNewWtf16Array(Node* array, Node* start,
|
||||
Node* end) {
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringNewWtf16Array,
|
||||
Operator::kNoDeopt, array, start, end);
|
||||
Operator::kNoDeopt | Operator::kNoThrow, array,
|
||||
start, end);
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::StringConst(uint32_t index) {
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringConst, Operator::kNoDeopt,
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringConst,
|
||||
Operator::kNoDeopt | Operator::kNoThrow,
|
||||
gasm_->Uint32Constant(index));
|
||||
}
|
||||
|
||||
@ -5763,8 +5776,8 @@ Node* WasmGraphBuilder::StringMeasureUtf8(Node* string, CheckForNull null_check,
|
||||
if (null_check == kWithNullCheck) {
|
||||
string = AssertNotNull(string, position);
|
||||
}
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringMeasureUtf8, Operator::kNoDeopt,
|
||||
string);
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringMeasureUtf8,
|
||||
Operator::kEliminatable, string);
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::StringMeasureWtf8(Node* string, CheckForNull null_check,
|
||||
@ -5772,8 +5785,8 @@ Node* WasmGraphBuilder::StringMeasureWtf8(Node* string, CheckForNull null_check,
|
||||
if (null_check == kWithNullCheck) {
|
||||
string = AssertNotNull(string, position);
|
||||
}
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringMeasureWtf8, Operator::kNoDeopt,
|
||||
string);
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringMeasureWtf8,
|
||||
Operator::kEliminatable, string);
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::StringMeasureWtf16(Node* string,
|
||||
@ -5795,8 +5808,9 @@ Node* WasmGraphBuilder::StringEncodeWtf8(uint32_t memory,
|
||||
if (null_check == kWithNullCheck) {
|
||||
string = AssertNotNull(string, position);
|
||||
}
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringEncodeWtf8, Operator::kNoDeopt,
|
||||
string, offset, gasm_->SmiConstant(memory),
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringEncodeWtf8,
|
||||
Operator::kNoDeopt | Operator::kNoThrow, string,
|
||||
offset, gasm_->SmiConstant(memory),
|
||||
gasm_->SmiConstant(static_cast<int32_t>(variant)));
|
||||
}
|
||||
|
||||
@ -5811,7 +5825,8 @@ Node* WasmGraphBuilder::StringEncodeWtf8Array(
|
||||
array = AssertNotNull(array, position);
|
||||
}
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringEncodeWtf8Array,
|
||||
Operator::kNoDeopt, string, array, start,
|
||||
Operator::kNoDeopt | Operator::kNoThrow, string,
|
||||
array, start,
|
||||
gasm_->SmiConstant(static_cast<int32_t>(variant)));
|
||||
}
|
||||
|
||||
@ -5821,8 +5836,9 @@ Node* WasmGraphBuilder::StringEncodeWtf16(uint32_t memory, Node* string,
|
||||
if (null_check == kWithNullCheck) {
|
||||
string = AssertNotNull(string, position);
|
||||
}
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringEncodeWtf16, Operator::kNoDeopt,
|
||||
string, offset, gasm_->SmiConstant(memory));
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringEncodeWtf16,
|
||||
Operator::kNoDeopt | Operator::kNoThrow, string,
|
||||
offset, gasm_->SmiConstant(memory));
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::StringEncodeWtf16Array(
|
||||
@ -5836,7 +5852,8 @@ Node* WasmGraphBuilder::StringEncodeWtf16Array(
|
||||
array = AssertNotNull(array, position);
|
||||
}
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringEncodeWtf16Array,
|
||||
Operator::kNoDeopt, string, array, start);
|
||||
Operator::kNoDeopt | Operator::kNoThrow, string,
|
||||
array, start);
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::StringConcat(Node* head, CheckForNull head_null_check,
|
||||
@ -5845,7 +5862,8 @@ Node* WasmGraphBuilder::StringConcat(Node* head, CheckForNull head_null_check,
|
||||
if (head_null_check == kWithNullCheck) head = AssertNotNull(head, position);
|
||||
if (tail_null_check == kWithNullCheck) tail = AssertNotNull(tail, position);
|
||||
return gasm_->CallBuiltin(
|
||||
Builtin::kStringAdd_CheckNone, Operator::kEliminatable, head, tail,
|
||||
Builtin::kStringAdd_CheckNone, Operator::kNoDeopt | Operator::kNoThrow,
|
||||
head, tail,
|
||||
LOAD_INSTANCE_FIELD(NativeContext, MachineType::TaggedPointer()));
|
||||
}
|
||||
|
||||
@ -5862,7 +5880,7 @@ Node* WasmGraphBuilder::StringEqual(Node* a, CheckForNull a_null_check, Node* b,
|
||||
gasm_->GotoIf(gasm_->IsNull(b), &done, Int32Constant(0));
|
||||
}
|
||||
gasm_->Goto(&done, gasm_->CallBuiltin(Builtin::kWasmStringEqual,
|
||||
Operator::kNoDeopt, a, b));
|
||||
Operator::kEliminatable, a, b));
|
||||
gasm_->Bind(&done);
|
||||
return done.PhiAt(0);
|
||||
}
|
||||
@ -5872,14 +5890,14 @@ Node* WasmGraphBuilder::StringIsUSVSequence(Node* str, CheckForNull null_check,
|
||||
if (null_check == kWithNullCheck) str = AssertNotNull(str, position);
|
||||
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringIsUSVSequence,
|
||||
Operator::kNoDeopt, str);
|
||||
Operator::kEliminatable, str);
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::StringAsWtf8(Node* str, CheckForNull null_check,
|
||||
wasm::WasmCodePosition position) {
|
||||
if (null_check == kWithNullCheck) str = AssertNotNull(str, position);
|
||||
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringAsWtf8, Operator::kNoDeopt,
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringAsWtf8, Operator::kEliminatable,
|
||||
str);
|
||||
}
|
||||
|
||||
@ -5890,7 +5908,7 @@ Node* WasmGraphBuilder::StringViewWtf8Advance(Node* view,
|
||||
if (null_check == kWithNullCheck) view = AssertNotNull(view, position);
|
||||
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringViewWtf8Advance,
|
||||
Operator::kNoDeopt, view, pos, bytes);
|
||||
Operator::kEliminatable, view, pos, bytes);
|
||||
}
|
||||
|
||||
void WasmGraphBuilder::StringViewWtf8Encode(
|
||||
@ -5901,8 +5919,9 @@ void WasmGraphBuilder::StringViewWtf8Encode(
|
||||
view = AssertNotNull(view, position);
|
||||
}
|
||||
Node* pair =
|
||||
gasm_->CallBuiltin(Builtin::kWasmStringViewWtf8Encode, Operator::kNoDeopt,
|
||||
addr, pos, bytes, view, gasm_->SmiConstant(memory),
|
||||
gasm_->CallBuiltin(Builtin::kWasmStringViewWtf8Encode,
|
||||
Operator::kNoDeopt | Operator::kNoThrow, addr, pos,
|
||||
bytes, view, gasm_->SmiConstant(memory),
|
||||
gasm_->SmiConstant(static_cast<int32_t>(variant)));
|
||||
*next_pos = gasm_->Projection(0, pair);
|
||||
*bytes_written = gasm_->Projection(1, pair);
|
||||
@ -5915,7 +5934,7 @@ Node* WasmGraphBuilder::StringViewWtf8Slice(Node* view, CheckForNull null_check,
|
||||
view = AssertNotNull(view, position);
|
||||
}
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringViewWtf8Slice,
|
||||
Operator::kNoDeopt, view, pos, bytes);
|
||||
Operator::kEliminatable, view, pos, bytes);
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::StringViewWtf16GetCodeUnit(
|
||||
@ -5925,7 +5944,8 @@ Node* WasmGraphBuilder::StringViewWtf16GetCodeUnit(
|
||||
string = AssertNotNull(string, position);
|
||||
}
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringViewWtf16GetCodeUnit,
|
||||
Operator::kNoDeopt, string, offset);
|
||||
Operator::kNoDeopt | Operator::kNoThrow, string,
|
||||
offset);
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::StringViewWtf16Encode(uint32_t memory, Node* string,
|
||||
@ -5937,8 +5957,9 @@ Node* WasmGraphBuilder::StringViewWtf16Encode(uint32_t memory, Node* string,
|
||||
string = AssertNotNull(string, position);
|
||||
}
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringViewWtf16Encode,
|
||||
Operator::kNoDeopt, offset, start, codeunits,
|
||||
string, gasm_->SmiConstant(memory));
|
||||
Operator::kNoDeopt | Operator::kNoThrow, offset,
|
||||
start, codeunits, string,
|
||||
gasm_->SmiConstant(memory));
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::StringViewWtf16Slice(Node* string,
|
||||
@ -5949,14 +5970,14 @@ Node* WasmGraphBuilder::StringViewWtf16Slice(Node* string,
|
||||
string = AssertNotNull(string, position);
|
||||
}
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringViewWtf16Slice,
|
||||
Operator::kNoDeopt, string, start, end);
|
||||
Operator::kEliminatable, string, start, end);
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::StringAsIter(Node* str, CheckForNull null_check,
|
||||
wasm::WasmCodePosition position) {
|
||||
if (null_check == kWithNullCheck) str = AssertNotNull(str, position);
|
||||
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringAsIter, Operator::kNoDeopt,
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringAsIter, Operator::kEliminatable,
|
||||
str);
|
||||
}
|
||||
|
||||
@ -5965,7 +5986,7 @@ Node* WasmGraphBuilder::StringViewIterNext(Node* view, CheckForNull null_check,
|
||||
if (null_check == kWithNullCheck) view = AssertNotNull(view, position);
|
||||
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringViewIterNext,
|
||||
Operator::kNoDeopt, view);
|
||||
Operator::kEliminatable, view);
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::StringViewIterAdvance(Node* view,
|
||||
@ -5975,7 +5996,7 @@ Node* WasmGraphBuilder::StringViewIterAdvance(Node* view,
|
||||
if (null_check == kWithNullCheck) view = AssertNotNull(view, position);
|
||||
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringViewIterAdvance,
|
||||
Operator::kNoDeopt, view, codepoints);
|
||||
Operator::kEliminatable, view, codepoints);
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::StringViewIterRewind(Node* view,
|
||||
@ -5985,7 +6006,7 @@ Node* WasmGraphBuilder::StringViewIterRewind(Node* view,
|
||||
if (null_check == kWithNullCheck) view = AssertNotNull(view, position);
|
||||
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringViewIterRewind,
|
||||
Operator::kNoDeopt, view, codepoints);
|
||||
Operator::kEliminatable, view, codepoints);
|
||||
}
|
||||
|
||||
Node* WasmGraphBuilder::StringViewIterSlice(Node* view, CheckForNull null_check,
|
||||
@ -5994,7 +6015,7 @@ Node* WasmGraphBuilder::StringViewIterSlice(Node* view, CheckForNull null_check,
|
||||
if (null_check == kWithNullCheck) view = AssertNotNull(view, position);
|
||||
|
||||
return gasm_->CallBuiltin(Builtin::kWasmStringViewIterSlice,
|
||||
Operator::kNoDeopt, view, codepoints);
|
||||
Operator::kEliminatable, view, codepoints);
|
||||
}
|
||||
|
||||
// 1 bit V8 Smi tag, 31 bits V8 Smi shift, 1 bit i31ref high-bit truncation.
|
||||
|
@ -775,6 +775,11 @@ MaybeHandle<String> NewStringFromUtf8Variant(Isolate* isolate,
|
||||
MaybeHandle<String> Factory::NewStringFromUtf8(
|
||||
const base::Vector<const uint8_t>& string,
|
||||
unibrow::Utf8Variant utf8_variant, AllocationType allocation) {
|
||||
if (string.size() > kMaxInt) {
|
||||
// The Utf8Decode can't handle longer inputs, and we couldn't create
|
||||
// strings from them anyway.
|
||||
THROW_NEW_ERROR(isolate(), NewInvalidStringLengthError(), String);
|
||||
}
|
||||
auto peek_bytes = [&]() -> base::Vector<const uint8_t> { return string; };
|
||||
return NewStringFromUtf8Variant(isolate(), peek_bytes, utf8_variant,
|
||||
allocation);
|
||||
@ -793,6 +798,8 @@ MaybeHandle<String> Factory::NewStringFromUtf8(
|
||||
DCHECK_EQ(sizeof(uint8_t), array->type()->element_type().value_kind_size());
|
||||
DCHECK_LE(start, end);
|
||||
DCHECK_LE(end, array->length());
|
||||
// {end - start} can never be more than what the Utf8Decoder can handle.
|
||||
static_assert(WasmArray::MaxLength(sizeof(uint8_t)) <= kMaxInt);
|
||||
auto peek_bytes = [&]() -> base::Vector<const uint8_t> {
|
||||
const uint8_t* contents =
|
||||
reinterpret_cast<const uint8_t*>(array->ElementAddress(0));
|
||||
@ -807,6 +814,8 @@ MaybeHandle<String> Factory::NewStringFromUtf8(
|
||||
unibrow::Utf8Variant utf8_variant, AllocationType allocation) {
|
||||
DCHECK_LE(start, end);
|
||||
DCHECK_LE(end, array->length());
|
||||
// {end - start} can never be more than what the Utf8Decoder can handle.
|
||||
static_assert(ByteArray::kMaxLength <= kMaxInt);
|
||||
auto peek_bytes = [&]() -> base::Vector<const uint8_t> {
|
||||
const uint8_t* contents =
|
||||
reinterpret_cast<const uint8_t*>(array->GetDataStartAddress());
|
||||
@ -839,6 +848,8 @@ MaybeHandle<String> Factory::NewStringFromUtf16(Handle<WasmArray> array,
|
||||
DCHECK_EQ(sizeof(uint16_t), array->type()->element_type().value_kind_size());
|
||||
DCHECK_LE(start, end);
|
||||
DCHECK_LE(end, array->length());
|
||||
// {end - start} can never be more than what the Utf8Decoder can handle.
|
||||
static_assert(WasmArray::MaxLength(sizeof(uint16_t)) <= kMaxInt);
|
||||
auto peek_bytes = [&]() -> base::Vector<const uint16_t> {
|
||||
const uint16_t* contents =
|
||||
reinterpret_cast<const uint16_t*>(array->ElementAddress(0));
|
||||
|
@ -19,8 +19,8 @@
|
||||
#include "src/utils/ostreams.h"
|
||||
|
||||
#if V8_ENABLE_WEBASSEMBLY
|
||||
// TODO(jkummerow): Drop this when the "SaveAndClearThreadInWasmFlag"
|
||||
// short-term mitigation is no longer needed.
|
||||
// TODO(chromium:1236668): Drop this when the "SaveAndClearThreadInWasmFlag"
|
||||
// approach is no longer needed.
|
||||
#include "src/trap-handler/trap-handler.h"
|
||||
#endif // V8_ENABLE_WEBASSEMBLY
|
||||
|
||||
@ -418,7 +418,7 @@ RUNTIME_FUNCTION(Runtime_BytecodeBudgetInterruptWithStackCheck_Maglev) {
|
||||
namespace {
|
||||
|
||||
#if V8_ENABLE_WEBASSEMBLY
|
||||
class SaveAndClearThreadInWasmFlag {
|
||||
class V8_NODISCARD SaveAndClearThreadInWasmFlag {
|
||||
public:
|
||||
SaveAndClearThreadInWasmFlag() {
|
||||
if (trap_handler::IsTrapHandlerEnabled()) {
|
||||
@ -460,10 +460,10 @@ RUNTIME_FUNCTION(Runtime_AllocateInYoungGeneration) {
|
||||
}
|
||||
|
||||
#if V8_ENABLE_WEBASSEMBLY
|
||||
// Short-term mitigation for crbug.com/1236668. When this is called from
|
||||
// WasmGC code, clear the "thread in wasm" flag, which is important in case
|
||||
// any GC needs to happen.
|
||||
// TODO(jkummerow): Find a better fix, likely by replacing the global flag.
|
||||
// When this is called from WasmGC code, clear the "thread in wasm" flag,
|
||||
// which is important in case any GC needs to happen.
|
||||
// TODO(chromium:1236668): Find a better fix, likely by replacing the global
|
||||
// flag.
|
||||
SaveAndClearThreadInWasmFlag clear_wasm_flag;
|
||||
#endif // V8_ENABLE_WEBASSEMBLY
|
||||
|
||||
|
@ -11,9 +11,46 @@
|
||||
#include "src/objects/smi.h"
|
||||
#include "src/strings/string-builder-inl.h"
|
||||
|
||||
#if V8_ENABLE_WEBASSEMBLY
|
||||
// TODO(chromium:1236668): Drop this when the "SaveAndClearThreadInWasmFlag"
|
||||
// approach is no longer needed.
|
||||
#include "src/trap-handler/trap-handler.h"
|
||||
#endif // V8_ENABLE_WEBASSEMBLY
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
namespace {
|
||||
|
||||
#if V8_ENABLE_WEBASSEMBLY
|
||||
class V8_NODISCARD SaveAndClearThreadInWasmFlag {
|
||||
public:
|
||||
explicit SaveAndClearThreadInWasmFlag(Isolate* isolate) : isolate_(isolate) {
|
||||
if (trap_handler::IsTrapHandlerEnabled()) {
|
||||
if (trap_handler::IsThreadInWasm()) {
|
||||
thread_was_in_wasm_ = true;
|
||||
trap_handler::ClearThreadInWasm();
|
||||
}
|
||||
}
|
||||
}
|
||||
~SaveAndClearThreadInWasmFlag() {
|
||||
if (thread_was_in_wasm_ && !isolate_->has_pending_exception()) {
|
||||
trap_handler::SetThreadInWasm();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool thread_was_in_wasm_{false};
|
||||
Isolate* isolate_;
|
||||
};
|
||||
#define CLEAR_THREAD_IN_WASM_SCOPE \
|
||||
SaveAndClearThreadInWasmFlag non_wasm_scope(isolate)
|
||||
#else
|
||||
#define CLEAR_THREAD_IN_WASM_SCOPE (void)0
|
||||
#endif // V8_ENABLE_WEBASSEMBLY
|
||||
|
||||
} // namespace
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_GetSubstitution) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(5, args.length());
|
||||
@ -154,6 +191,8 @@ RUNTIME_FUNCTION(Runtime_StringSubstring) {
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_StringAdd) {
|
||||
// This is used by Wasm stringrefs.
|
||||
CLEAR_THREAD_IN_WASM_SCOPE;
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(2, args.length());
|
||||
Handle<String> str1 = args.at<String>(0);
|
||||
|
@ -870,6 +870,27 @@ RUNTIME_FUNCTION(Runtime_WasmCreateResumePromise) {
|
||||
return *result;
|
||||
}
|
||||
|
||||
#define RETURN_RESULT_OR_TRAP(call) \
|
||||
do { \
|
||||
Handle<Object> result; \
|
||||
if (!(call).ToHandle(&result)) { \
|
||||
DCHECK(isolate->has_pending_exception()); \
|
||||
/* Mark any exception as uncatchable by Wasm. */ \
|
||||
Handle<JSObject> exception(JSObject::cast(isolate->pending_exception()), \
|
||||
isolate); \
|
||||
Handle<Name> uncatchable = \
|
||||
isolate->factory()->wasm_uncatchable_symbol(); \
|
||||
LookupIterator it(isolate, exception, uncatchable, LookupIterator::OWN); \
|
||||
if (!JSReceiver::HasProperty(&it).FromJust()) { \
|
||||
JSObject::AddProperty(isolate, exception, uncatchable, \
|
||||
isolate->factory()->true_value(), NONE); \
|
||||
} \
|
||||
return ReadOnlyRoots(isolate).exception(); \
|
||||
} \
|
||||
DCHECK(!isolate->has_pending_exception()); \
|
||||
return *result; \
|
||||
} while (false)
|
||||
|
||||
// Returns the new string if the operation succeeds. Otherwise throws an
|
||||
// exception and returns an empty result.
|
||||
RUNTIME_FUNCTION(Runtime_WasmStringNewWtf8) {
|
||||
@ -896,8 +917,8 @@ RUNTIME_FUNCTION(Runtime_WasmStringNewWtf8) {
|
||||
|
||||
const base::Vector<const uint8_t> bytes{instance.memory_start() + offset,
|
||||
size};
|
||||
RETURN_RESULT_OR_FAILURE(
|
||||
isolate, isolate->factory()->NewStringFromUtf8(bytes, utf8_variant));
|
||||
RETURN_RESULT_OR_TRAP(
|
||||
isolate->factory()->NewStringFromUtf8(bytes, utf8_variant));
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_WasmStringNewWtf8Array) {
|
||||
@ -913,8 +934,8 @@ RUNTIME_FUNCTION(Runtime_WasmStringNewWtf8Array) {
|
||||
static_cast<uint32_t>(unibrow::Utf8Variant::kLastUtf8Variant));
|
||||
auto utf8_variant = static_cast<unibrow::Utf8Variant>(utf8_variant_value);
|
||||
|
||||
RETURN_RESULT_OR_FAILURE(isolate, isolate->factory()->NewStringFromUtf8(
|
||||
array, start, end, utf8_variant));
|
||||
RETURN_RESULT_OR_TRAP(
|
||||
isolate->factory()->NewStringFromUtf8(array, start, end, utf8_variant));
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_WasmStringNewWtf16) {
|
||||
@ -940,9 +961,7 @@ RUNTIME_FUNCTION(Runtime_WasmStringNewWtf16) {
|
||||
|
||||
const byte* bytes = instance.memory_start() + offset;
|
||||
const base::uc16* codeunits = reinterpret_cast<const base::uc16*>(bytes);
|
||||
// TODO(12868): Override any exception with an uncatchable-by-wasm trap.
|
||||
RETURN_RESULT_OR_FAILURE(isolate,
|
||||
isolate->factory()->NewStringFromTwoByteLittleEndian(
|
||||
RETURN_RESULT_OR_TRAP(isolate->factory()->NewStringFromTwoByteLittleEndian(
|
||||
{codeunits, size_in_codeunits}));
|
||||
}
|
||||
|
||||
@ -954,9 +973,8 @@ RUNTIME_FUNCTION(Runtime_WasmStringNewWtf16Array) {
|
||||
uint32_t start = NumberToUint32(args[1]);
|
||||
uint32_t end = NumberToUint32(args[2]);
|
||||
|
||||
// TODO(12868): Override any exception with an uncatchable-by-wasm trap.
|
||||
RETURN_RESULT_OR_FAILURE(
|
||||
isolate, isolate->factory()->NewStringFromUtf16(array, start, end));
|
||||
RETURN_RESULT_OR_TRAP(
|
||||
isolate->factory()->NewStringFromUtf16(array, start, end));
|
||||
}
|
||||
|
||||
// Returns the new string if the operation succeeds. Otherwise traps.
|
||||
@ -1291,9 +1309,12 @@ RUNTIME_FUNCTION(Runtime_WasmStringViewWtf8Slice) {
|
||||
DCHECK_LT(start, end);
|
||||
DCHECK(base::IsInBounds<size_t>(start, end - start, array->length()));
|
||||
|
||||
RETURN_RESULT_OR_FAILURE(isolate,
|
||||
isolate->factory()->NewStringFromUtf8(
|
||||
array, start, end, unibrow::Utf8Variant::kWtf8));
|
||||
// This can't throw because the result can't be too long if the input wasn't,
|
||||
// and encoding failures are ruled out too because {start}/{end} are aligned.
|
||||
return *isolate->factory()
|
||||
->NewStringFromUtf8(array, start, end,
|
||||
unibrow::Utf8Variant::kWtf8)
|
||||
.ToHandleChecked();
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
@ -1000,7 +1000,7 @@ class WasmArray : public TorqueGeneratedWasmArray<WasmArray, WasmObject> {
|
||||
inline uint32_t element_offset(uint32_t index);
|
||||
inline Address ElementAddress(uint32_t index);
|
||||
|
||||
static int MaxLength(uint32_t element_size_bytes) {
|
||||
static constexpr int MaxLength(uint32_t element_size_bytes) {
|
||||
// The total object size must fit into a Smi, for filler objects. To make
|
||||
// the behavior of Wasm programs independent from the Smi configuration,
|
||||
// we hard-code the smaller of the two supported ranges.
|
||||
|
98
test/mjsunit/wasm/stringrefs-regressions.js
Normal file
98
test/mjsunit/wasm/stringrefs-regressions.js
Normal file
@ -0,0 +1,98 @@
|
||||
// Copyright 2022 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Flags: --experimental-wasm-stringref --allow-natives-syntax
|
||||
// We just want speculative inlining, but the "stress" variant doesn't like
|
||||
// that flag for some reason, so use the GC flag which implies it.
|
||||
// Flags: --experimental-wasm-gc
|
||||
|
||||
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
|
||||
|
||||
let kSig_w_v = makeSig([], [kWasmStringRef]);
|
||||
let kSig_w_i = makeSig([kWasmI32], [kWasmStringRef]);
|
||||
let kSig_v_w = makeSig([kWasmStringRef], []);
|
||||
|
||||
(function () {
|
||||
let huge_builder = new WasmModuleBuilder();
|
||||
huge_builder.addMemory(65001, undefined, false, false);
|
||||
|
||||
huge_builder.addFunction("huge", kSig_w_v).exportFunc().addBody([
|
||||
kExprI32Const, 0, // address
|
||||
...wasmI32Const(65000 * 65536), // bytes
|
||||
...GCInstr(kExprStringNewUtf8), 0 // memory index
|
||||
]);
|
||||
|
||||
let callee = huge_builder.addFunction("callee", kSig_w_i).addBody([
|
||||
kExprI32Const, 0, // address
|
||||
kExprLocalGet, 0, // bytes
|
||||
...GCInstr(kExprStringNewUtf8), 0 // memory index
|
||||
]);
|
||||
|
||||
let caller = huge_builder.addFunction("caller", kSig_i_i).exportFunc()
|
||||
.addBody([
|
||||
kExprTry, kWasmI32,
|
||||
kExprLocalGet, 0,
|
||||
kExprCallFunction, callee.index,
|
||||
kExprDrop,
|
||||
kExprI32Const, 1,
|
||||
kExprCatchAll,
|
||||
kExprI32Const, 0,
|
||||
kExprEnd
|
||||
]);
|
||||
|
||||
let instance;
|
||||
try {
|
||||
instance = huge_builder.instantiate();
|
||||
// On 64-bit platforms, expect to see this message.
|
||||
console.log("Instantiation successful, proceeding.");
|
||||
} catch (e) {
|
||||
// 32-bit builds don't have enough virtual memory, that's OK.
|
||||
assertInstanceof(e, RangeError);
|
||||
assertMatches(/Cannot allocate Wasm memory for new instance/, e.message,
|
||||
'Error message');
|
||||
return;
|
||||
}
|
||||
|
||||
// Bug 1: The Utf8Decoder can't handle more than kMaxInt bytes as input.
|
||||
assertThrows(() => instance.exports.huge(), RangeError);
|
||||
|
||||
// Bug 2: Exceptions created by the JS-focused strings infrastructure must
|
||||
// be marked as uncatchable by Wasm.
|
||||
let f1 = instance.exports.caller;
|
||||
assertThrows(() => f1(2147483647), RangeError);
|
||||
|
||||
// Bug 3: Builtin calls that have neither a kNoThrow annotation nor exception-
|
||||
// handling support make the Wasm inliner sad.
|
||||
for (let i = 0; i < 20; i++) f1(10);
|
||||
%WasmTierUpFunction(instance, caller.index);
|
||||
f1(10);
|
||||
})();
|
||||
|
||||
let builder = new WasmModuleBuilder();
|
||||
|
||||
let concat_body = [];
|
||||
// This doubles the string 26 times, i.e. multiplies its length with a factor
|
||||
// of ~65 million.
|
||||
for (let i = 0; i < 26; i++) {
|
||||
concat_body.push(...[
|
||||
kExprLocalGet, 0, kExprLocalGet, 0,
|
||||
...GCInstr(kExprStringConcat),
|
||||
kExprLocalSet, 0
|
||||
]);
|
||||
}
|
||||
|
||||
let concat =
|
||||
builder.addFunction('concat', kSig_v_w).exportFunc().addBody(concat_body);
|
||||
|
||||
let instance = builder.instantiate();
|
||||
|
||||
// Bug 4: Throwing in StringAdd must clear the "thread in wasm" bit.
|
||||
let f2 = instance.exports.concat;
|
||||
assertThrows(() => f2("1234567890")); // 650M characters is too much.
|
||||
|
||||
// Bug 5: Operations that can trap must not be marked as kEliminatable,
|
||||
// otherwise the trap may be eliminated.
|
||||
for (let i = 0; i < 3; i++) f2("a"); // 65M characters is okay.
|
||||
%WasmTierUpFunction(instance, concat.index);
|
||||
assertThrows(() => f2("1234567890")); // Optimized code still traps.
|
Loading…
Reference in New Issue
Block a user