summaryrefslogtreecommitdiff
path: root/src/wasm/wasm-stack.cpp
diff options
context:
space:
mode:
authorThomas Lively <tlively@google.com>2024-05-15 12:05:33 -0700
committerGitHub <noreply@github.com>2024-05-15 12:05:33 -0700
commitef4b57c2a491a2193435dccdc9305f6a79965715 (patch)
tree491562613897a0c467a456e05e8a92234509e02c /src/wasm/wasm-stack.cpp
parent8a5dc1880d962a7c31a7a219720be343a0866e5c (diff)
downloadbinaryen-ef4b57c2a491a2193435dccdc9305f6a79965715.tar.gz
binaryen-ef4b57c2a491a2193435dccdc9305f6a79965715.tar.bz2
binaryen-ef4b57c2a491a2193435dccdc9305f6a79965715.zip
[Strings] Remove stringview types and instructions (#6579)
The stringview types from the stringref proposal have three irregularities that break common invariants and require pervasive special casing to handle properly: they are supertypes of `none` but not subtypes of `any`, they cannot be the targets of casts, and they cannot be used to construct nullable references. At the same time, the stringref proposal has been superseded by the imported strings proposal, which does not have these irregularities. The cost of maintaing and improving our support for stringview types is no longer worth the benefit of supporting them. Simplify the code base by entirely removing the stringview types and related instructions that do not have analogues in the imported strings proposal and do not make sense in the absense of stringviews. Three remaining instructions, `stringview_wtf16.get_codeunit`, `stringview_wtf16.slice`, and `stringview_wtf16.length` take stringview operands in the stringref proposal but cannot be removed because they lower to operations from the imported strings proposal. These instructions are changed to take stringref operands in Binaryen IR, and to allow a graceful upgrade path for users of these instructions, the text and binary parsers still accept but ignore `string.as_wtf16`, which is the instruction used to convert stringrefs to stringviews. The binary writer emits code sequences that use scratch locals and `string.as_wtf16` to keep the output valid. Future PRs will further align binaryen with the imported strings proposal instead of the stringref proposal, for example by making `string` a subtype of `extern` instead of a subtype of `any` and by removing additional instructions that do not have analogues in the imported strings proposal.
Diffstat (limited to 'src/wasm/wasm-stack.cpp')
-rw-r--r--src/wasm/wasm-stack.cpp166
1 files changed, 92 insertions, 74 deletions
diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp
index 53a25d52b..a817e5294 100644
--- a/src/wasm/wasm-stack.cpp
+++ b/src/wasm/wasm-stack.cpp
@@ -89,6 +89,11 @@ void BinaryInstWriter::visitCallIndirect(CallIndirect* curr) {
}
void BinaryInstWriter::visitLocalGet(LocalGet* curr) {
+ if (deferredGets.count(curr)) {
+ // This local.get will be emitted as part of the instruction that consumes
+ // it.
+ return;
+ }
if (auto it = extractedGets.find(curr); it != extractedGets.end()) {
// We have a tuple of locals to get, but we will only end up using one of
// them, so we can just emit that one.
@@ -2089,24 +2094,6 @@ void BinaryInstWriter::visitRefTest(RefTest* curr) {
}
void BinaryInstWriter::visitRefCast(RefCast* curr) {
- // We allow ref.cast of string views, but V8 does not. Work around that by
- // emitting a ref.as_non_null (or nothing).
- auto type = curr->type;
- if (type.isRef()) {
- auto heapType = type.getHeapType();
- if (heapType == HeapType::stringview_wtf8 ||
- heapType == HeapType::stringview_wtf16 ||
- heapType == HeapType::stringview_iter) {
- // We cannot cast string views to/from anything, so the input must also
- // be a view.
- assert(curr->ref->type.getHeapType() == heapType);
- if (type.isNonNullable() && curr->ref->type.isNullable()) {
- o << int8_t(BinaryConsts::RefAsNonNull);
- }
- return;
- }
- }
-
o << int8_t(BinaryConsts::GCPrefix);
if (curr->type.isNullable()) {
o << U32LEB(BinaryConsts::RefCastNull);
@@ -2385,9 +2372,6 @@ void BinaryInstWriter::visitStringMeasure(StringMeasure* curr) {
case StringMeasureIsUSV:
o << U32LEB(BinaryConsts::StringIsUSV);
break;
- case StringMeasureWTF16View:
- o << U32LEB(BinaryConsts::StringViewWTF16Length);
- break;
case StringMeasureHash:
o << U32LEB(BinaryConsts::StringHash);
break;
@@ -2455,69 +2439,66 @@ void BinaryInstWriter::visitStringEq(StringEq* curr) {
}
}
-void BinaryInstWriter::visitStringAs(StringAs* curr) {
- o << int8_t(BinaryConsts::GCPrefix);
- switch (curr->op) {
- case StringAsWTF8:
- o << U32LEB(BinaryConsts::StringAsWTF8);
- break;
- case StringAsWTF16:
- o << U32LEB(BinaryConsts::StringAsWTF16);
- break;
- case StringAsIter:
- o << U32LEB(BinaryConsts::StringAsIter);
- break;
- default:
- WASM_UNREACHABLE("invalid string.as*");
+void BinaryInstWriter::visitStringWTF16Get(StringWTF16Get* curr) {
+ // We need to convert the ref operand to a stringview, but it is under the pos
+ // operand. Put the i32 in a scratch local, emit the conversion, then get the
+ // i32 back onto the stack. If `pos` is a local.get anyway, then we can skip
+ // the scratch local.
+ bool posDeferred = false;
+ Index posIndex;
+ if (auto* get = curr->pos->dynCast<LocalGet>()) {
+ assert(deferredGets.count(get));
+ posDeferred = true;
+ posIndex = mappedLocals[{get->index, 0}];
+ } else {
+ posIndex = scratchLocals[Type::i32];
}
-}
-
-void BinaryInstWriter::visitStringWTF8Advance(StringWTF8Advance* curr) {
- o << int8_t(BinaryConsts::GCPrefix)
- << U32LEB(BinaryConsts::StringViewWTF8Advance);
-}
-void BinaryInstWriter::visitStringWTF16Get(StringWTF16Get* curr) {
+ if (!posDeferred) {
+ o << int8_t(BinaryConsts::LocalSet) << U32LEB(posIndex);
+ }
+ o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::StringAsWTF16);
+ o << int8_t(BinaryConsts::LocalGet) << U32LEB(posIndex);
o << int8_t(BinaryConsts::GCPrefix)
<< U32LEB(BinaryConsts::StringViewWTF16GetCodePoint);
}
-void BinaryInstWriter::visitStringIterNext(StringIterNext* curr) {
- o << int8_t(BinaryConsts::GCPrefix)
- << U32LEB(BinaryConsts::StringViewIterNext);
-}
-
-void BinaryInstWriter::visitStringIterMove(StringIterMove* curr) {
- o << int8_t(BinaryConsts::GCPrefix);
- switch (curr->op) {
- case StringIterMoveAdvance:
- o << U32LEB(BinaryConsts::StringViewIterAdvance);
- break;
- case StringIterMoveRewind:
- o << U32LEB(BinaryConsts::StringViewIterRewind);
- break;
- default:
- WASM_UNREACHABLE("invalid string.move*");
- }
-}
-
void BinaryInstWriter::visitStringSliceWTF(StringSliceWTF* curr) {
- o << int8_t(BinaryConsts::GCPrefix);
- switch (curr->op) {
- case StringSliceWTF8:
- o << U32LEB(BinaryConsts::StringViewWTF8Slice);
- break;
- case StringSliceWTF16:
- o << U32LEB(BinaryConsts::StringViewWTF16Slice);
- break;
- default:
- WASM_UNREACHABLE("invalid string.move*");
+ // We need to convert the ref operand to a stringview, but it is buried under
+ // the start and end operands. Put the i32s in scratch locals, emit the
+ // conversion, then get the i32s back onto the stack. If either `start` or
+ // `end` is already a local.get, then we can skip its scratch local.
+ bool startDeferred = false, endDeferred = false;
+ Index startIndex, endIndex;
+ if (auto* get = curr->start->dynCast<LocalGet>()) {
+ assert(deferredGets.count(get));
+ startDeferred = true;
+ startIndex = mappedLocals[{get->index, 0}];
+ } else {
+ startIndex = scratchLocals[Type::i32];
+ }
+ if (auto* get = curr->end->dynCast<LocalGet>()) {
+ assert(deferredGets.count(get));
+ endDeferred = true;
+ endIndex = mappedLocals[{get->index, 0}];
+ } else {
+ endIndex = scratchLocals[Type::i32];
+ if (!startDeferred) {
+ ++endIndex;
+ }
}
-}
-void BinaryInstWriter::visitStringSliceIter(StringSliceIter* curr) {
+ if (!endDeferred) {
+ o << int8_t(BinaryConsts::LocalSet) << U32LEB(endIndex);
+ }
+ if (!startDeferred) {
+ o << int8_t(BinaryConsts::LocalSet) << U32LEB(startIndex);
+ }
+ o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::StringAsWTF16);
+ o << int8_t(BinaryConsts::LocalGet) << U32LEB(startIndex);
+ o << int8_t(BinaryConsts::LocalGet) << U32LEB(endIndex);
o << int8_t(BinaryConsts::GCPrefix)
- << U32LEB(BinaryConsts::StringViewIterSlice);
+ << U32LEB(BinaryConsts::StringViewWTF16Slice);
}
void BinaryInstWriter::visitContBind(ContBind* curr) {
@@ -2707,6 +2688,43 @@ InsertOrderedMap<Type, Index> BinaryInstWriter::countScratchLocals() {
count = std::max(count, 1u);
}
}
+
+ void visitStringWTF16Get(StringWTF16Get* curr) {
+ if (curr->type == Type::unreachable) {
+ return;
+ }
+ // If `pos` already a local.get, we can defer emitting that local.get
+ // instead of using a scratch local.
+ if (auto* get = curr->pos->dynCast<LocalGet>()) {
+ parent.deferredGets.insert(get);
+ return;
+ }
+ // Scratch local to hold the `pos` value while we emit a stringview
+ // conversion for the `ref` value.
+ auto& count = scratches[Type::i32];
+ count = std::max(count, 1u);
+ }
+
+ void visitStringSliceWTF(StringSliceWTF* curr) {
+ if (curr->type == Type::unreachable) {
+ return;
+ }
+ // If `start` or `end` are already local.gets, we can defer emitting those
+ // gets instead of using scratch locals.
+ Index numScratches = 2;
+ if (auto* get = curr->start->dynCast<LocalGet>()) {
+ parent.deferredGets.insert(get);
+ --numScratches;
+ }
+ if (auto* get = curr->end->dynCast<LocalGet>()) {
+ parent.deferredGets.insert(get);
+ --numScratches;
+ }
+ // Scratch locals to hold the `start` and `end` values while we emit a
+ // stringview conversion for the `ref` value.
+ auto& count = scratches[Type::i32];
+ count = std::max(count, numScratches);
+ }
};
ScratchLocalFinder finder(*this);