From b43f8984367275dfacf85ae0889dde41f7dc268f Mon Sep 17 00:00:00 2001 From: Jeremy Nguyen Date: Mon, 9 Nov 2026 00:36:05 -0800 Subject: [PATCH 1/6] [Strings] Handle fromCharCode and cast imports in StringLifting/Lowering StringLifting: fromCharCode(x) becomes string.from_code_point(x & 0xFFFF) and cast becomes non-nullable ref.cast to string. StringLowering: string.from_code_point(x & 0xFFFF) or equivalent becomes fromCharCode(x) and non-nullable ref.cast to string becomes cast(). Nullable ref.cast to string checks ref.is_null beforehand. --- src/passes/StringLifting.cpp | 28 +++ src/passes/StringLowering.cpp | 99 ++++++++ test/lit/passes/string-gathering.wast | 108 ++++---- .../string-lifting-lowering-roundtrip.wast | 88 +++++++ test/lit/passes/string-lifting-section.wast | 26 +- test/lit/passes/string-lifting.wast | 79 ++++-- ...string-lowering-imports-custom-module.wast | 30 ++- .../passes/string-lowering-instructions.wast | 233 +++++++++++++++--- test/lit/passes/string-lowering_types.wast | 104 ++++---- 9 files changed, 625 insertions(+), 170 deletions(-) create mode 100644 test/lit/passes/string-lifting-lowering-roundtrip.wast diff --git a/src/passes/StringLifting.cpp b/src/passes/StringLifting.cpp index 2c8572c025a..253af5c0fd2 100644 --- a/src/passes/StringLifting.cpp +++ b/src/passes/StringLifting.cpp @@ -49,6 +49,8 @@ struct StringLifting : public Pass { Name lengthImport; Name charCodeAtImport; Name substringImport; + Name fromCharCodeImport; + Name castImport; void run(Module* module) override { // Whether we found any work to do. @@ -195,6 +197,18 @@ struct StringLifting : public Pass { } substringImport = func->name; found = true; + } else if (func->base == "fromCharCode") { + if (type.getHeapType() != Signature(i32, refExtern)) { + Fatal() << "StringLifting: bad type for fromCharCode: " << type; + } + fromCharCodeImport = func->name; + found = true; + } else if (func->base == "cast") { + if (type.getHeapType() != Signature(externref, refExtern)) { + Fatal() << "StringLifting: bad type for cast: " << type; + } + castImport = func->name; + found = true; } else { std::cerr << "warning: unknown strings import: " << func->base << '\n'; } @@ -276,6 +290,20 @@ struct StringLifting : public Pass { .makeStringSliceWTF(curr->operands[0], curr->operands[1], curr->operands[2])); + } else if (curr->target == parent.fromCharCodeImport) { + // String.fromCharCode(0x10000) => '\x00' + // String.fromCodePoint(0x10000) => '𐀀' + // Hence, String.fromCharCode(x) == String.fromCodePoint(x & 0xFFFF) + Builder builder(*getModule()); + replaceCurrent(builder.makeStringNew( + StringNewFromCodePoint, + builder.makeBinary(AndInt32, + curr->operands[0], + builder.makeConst(Literal(int32_t(0xFFFF)))))); + } else if (curr->target == parent.castImport) { + replaceCurrent(Builder(*getModule()) + .makeRefCast(curr->operands[0], + Type(HeapType::string, NonNullable))); } } diff --git a/src/passes/StringLowering.cpp b/src/passes/StringLowering.cpp index ccb2f0366df..624c8b0883d 100644 --- a/src/passes/StringLowering.cpp +++ b/src/passes/StringLowering.cpp @@ -219,6 +219,9 @@ struct StringLowering : public StringGathering { // First, run the gathering operation so all string.consts are in one place. StringGathering::run(module); + // Find casts to string before they are turned to externref by updateTypes + findStringCasts(module); + // Remove all HeapType::string etc. in favor of externref. updateTypes(module); @@ -360,6 +363,63 @@ struct StringLowering : public StringGathering { Name lengthImport; Name charCodeAtImport; Name substringImport; + Name fromCharCodeImport; + Name castImport; + + // All the casts to string, so we can replace them + std::vector> stringCasts; + + void findStringCasts(Module* module) { + using Casts = std::vector; + + struct CastWalker : public PostWalker { + Casts& casts; + + CastWalker(Casts& casts) : casts(casts) {} + + void visitRefCast(RefCast* curr) { + if (curr->type.isRef() && + curr->type.getHeapType() == HeapType::string && !curr->desc) { + casts.push_back(getCurrentPointer()); + } + } + }; + + // Casts cannot be found outside of a function (e.g. a global initializer) + // since ref.cast is not a constant expression. + ModuleUtils::ParallelFunctionAnalysis analysis( + *module, [&](Function* func, Casts& casts) { + if (!func->imported()) { + CastWalker(casts).walk(func->body); + } + }); + + for (auto& [func, casts] : analysis.map) { + for (auto** castPtr : casts) { + stringCasts.push_back({func, castPtr}); + } + } + } + + // Replaces string casts with imported cast function call + void lowerStringCast(Module* module, Function* func, Expression** castp) { + Builder builder(*module); + auto* cast = (*castp)->cast(); + if (cast->type.isNonNullableRef()) { + *castp = builder.makeCall(castImport, {cast->ref}, nnExt); + return; + } + // (if (ref.is_null (local.tee $temp (..))) + // (then (ref.null noextern)) + // (else (call $cast (local.get $temp))) + // ) + auto temp = Builder::addVar(func, nullExt); + *castp = builder.makeIf( + builder.makeRefIsNull(builder.makeLocalTee(temp, cast->ref, nullExt)), + builder.makeRefNull(HeapType::ext), + builder.makeCall( + castImport, {builder.makeLocalGet(temp, nullExt)}, nnExt)); + } // Creates an imported string function, returning its name (which is equal to // the true name of the import, if there is no conflict). @@ -404,6 +464,15 @@ struct StringLowering : public StringGathering { // string.substring: string, start, end -> string substringImport = addImport(module, "substring", {nullExt, Type::i32, Type::i32}, nnExt); + // string.fromCharCode: charCode -> ext + fromCharCodeImport = addImport(module, "fromCharCode", Type::i32, nnExt); + // string.cast: externref -> string + castImport = addImport(module, "cast", nullExt, nnExt); + + // Lowering casts cannot be done in parallel since it may add locals + for (auto& [func, castPtr] : stringCasts) { + lowerStringCast(module, func, castPtr); + } // Replace the string instructions in parallel. struct Replacer : public WalkerPass> { @@ -417,6 +486,31 @@ struct StringLowering : public StringGathering { Replacer(StringLowering& lowering) : lowering(lowering) {} + // Returns the charCode if it is a code point with mask 0xFFFF in either + // order, or nullptr otherwise. + static Expression* getCharCode(Expression* curr) { + auto* binary = curr->dynCast(); + if (!binary || binary->op != AndInt32) { + return nullptr; + } + + auto isCharCodeMask = [](Expression* expr) { + if (auto* constExpr = expr->dynCast()) { + return constExpr->value.geti32() == 0xFFFF; + } else { + return false; + } + }; + + if (isCharCodeMask(binary->right)) { + return binary->left; + } + if (isCharCodeMask(binary->left)) { + return binary->right; + } + return nullptr; + } + void visitStringNew(StringNew* curr) { Builder builder(*getModule()); switch (curr->op) { @@ -426,6 +520,11 @@ struct StringLowering : public StringGathering { lowering.nnExt)); return; case StringNewFromCodePoint: + if (auto* charCode = getCharCode(curr->ref)) { + replaceCurrent(builder.makeCall( + lowering.fromCharCodeImport, {charCode}, lowering.nnExt)); + return; + } replaceCurrent(builder.makeCall( lowering.fromCodePointImport, {curr->ref}, lowering.nnExt)); return; diff --git a/test/lit/passes/string-gathering.wast b/test/lit/passes/string-gathering.wast index c8724ab27d2..951d6a0a3f3 100644 --- a/test/lit/passes/string-gathering.wast +++ b/test/lit/passes/string-gathering.wast @@ -29,13 +29,13 @@ ;; LOWER: (type $1 (func)) - ;; LOWER: (type $2 (func (param externref externref) (result i32))) + ;; LOWER: (type $2 (func (param i32) (result (ref extern)))) - ;; LOWER: (type $3 (func (param externref) (result i32))) + ;; LOWER: (type $3 (func (param externref externref) (result i32))) - ;; LOWER: (type $4 (func (param (ref null $0) i32 i32) (result (ref extern)))) + ;; LOWER: (type $4 (func (param externref) (result i32))) - ;; LOWER: (type $5 (func (param i32) (result (ref extern)))) + ;; LOWER: (type $5 (func (param (ref null $0) i32 i32) (result (ref extern)))) ;; LOWER: (type $6 (func (param externref externref) (result (ref extern)))) @@ -45,32 +45,38 @@ ;; LOWER: (type $9 (func (param externref i32 i32) (result (ref extern)))) + ;; LOWER: (type $10 (func (param externref) (result (ref extern)))) + ;; LOWER: (import "string.const" "0" (global $global (ref extern))) ;; LOWER: (import "string.const" "1" (global $"string.const_\"bar\"" (ref extern))) ;; LOWER: (import "string.const" "2" (global $"string.const_\"other\"" (ref extern))) - ;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $4) (param (ref null $0) i32 i32) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $5) (param (ref null $0) i32 i32) (result (ref extern)))) - ;; LOWER: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $5) (param i32) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $2) (param i32) (result (ref extern)))) ;; LOWER: (import "wasm:js-string" "concat" (func $concat (type $6) (param externref externref) (result (ref extern)))) ;; LOWER: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $7) (param externref (ref null $0) i32) (result i32))) - ;; LOWER: (import "wasm:js-string" "equals" (func $equals (type $2) (param externref externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "equals" (func $equals (type $3) (param externref externref) (result i32))) - ;; LOWER: (import "wasm:js-string" "test" (func $test (type $3) (param externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "test" (func $test (type $4) (param externref) (result i32))) - ;; LOWER: (import "wasm:js-string" "compare" (func $compare (type $2) (param externref externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "compare" (func $compare (type $3) (param externref externref) (result i32))) - ;; LOWER: (import "wasm:js-string" "length" (func $length (type $3) (param externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "length" (func $length (type $4) (param externref) (result i32))) ;; LOWER: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $8) (param externref i32) (result i32))) ;; LOWER: (import "wasm:js-string" "substring" (func $substring (type $9) (param externref i32 i32) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "fromCharCode" (func $fromCharCode (type $2) (param i32) (result (ref extern)))) + + ;; LOWER: (import "wasm:js-string" "cast" (func $cast (type $10) (param externref) (result (ref extern)))) + ;; LOWER: (global $global2 externref (global.get $"string.const_\"bar\"")) (global $global2 (ref null string) (string.const "bar")) @@ -151,13 +157,13 @@ ;; CHECK: (import "a" "b" (global $import (ref string))) ;; LOWER: (type $0 (array (mut i16))) - ;; LOWER: (type $1 (func (param externref externref) (result i32))) + ;; LOWER: (type $1 (func (param i32) (result (ref extern)))) - ;; LOWER: (type $2 (func (param externref) (result i32))) + ;; LOWER: (type $2 (func (param externref externref) (result i32))) - ;; LOWER: (type $3 (func (param (ref null $0) i32 i32) (result (ref extern)))) + ;; LOWER: (type $3 (func (param externref) (result i32))) - ;; LOWER: (type $4 (func (param i32) (result (ref extern)))) + ;; LOWER: (type $4 (func (param (ref null $0) i32 i32) (result (ref extern)))) ;; LOWER: (type $5 (func (param externref externref) (result (ref extern)))) @@ -167,6 +173,8 @@ ;; LOWER: (type $8 (func (param externref i32 i32) (result (ref extern)))) + ;; LOWER: (type $9 (func (param externref) (result (ref extern)))) + ;; LOWER: (import "string.const" "0" (global $global1 (ref extern))) ;; LOWER: (import "string.const" "1" (global $global4 (ref extern))) @@ -180,26 +188,30 @@ ;; CHECK: (global $global4 (ref string) (string.const "bar")) ;; CHECK: (global $global2 (ref string) (global.get $global1)) - ;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $3) (param (ref null $0) i32 i32) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $4) (param (ref null $0) i32 i32) (result (ref extern)))) - ;; LOWER: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $4) (param i32) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $1) (param i32) (result (ref extern)))) ;; LOWER: (import "wasm:js-string" "concat" (func $concat (type $5) (param externref externref) (result (ref extern)))) ;; LOWER: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $6) (param externref (ref null $0) i32) (result i32))) - ;; LOWER: (import "wasm:js-string" "equals" (func $equals (type $1) (param externref externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "equals" (func $equals (type $2) (param externref externref) (result i32))) - ;; LOWER: (import "wasm:js-string" "test" (func $test (type $2) (param externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "test" (func $test (type $3) (param externref) (result i32))) - ;; LOWER: (import "wasm:js-string" "compare" (func $compare (type $1) (param externref externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "compare" (func $compare (type $2) (param externref externref) (result i32))) - ;; LOWER: (import "wasm:js-string" "length" (func $length (type $2) (param externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "length" (func $length (type $3) (param externref) (result i32))) ;; LOWER: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $7) (param externref i32) (result i32))) ;; LOWER: (import "wasm:js-string" "substring" (func $substring (type $8) (param externref i32 i32) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "fromCharCode" (func $fromCharCode (type $1) (param i32) (result (ref extern)))) + + ;; LOWER: (import "wasm:js-string" "cast" (func $cast (type $9) (param externref) (result (ref extern)))) + ;; LOWER: (global $global2 (ref extern) (global.get $global1)) (global $global2 (ref string) (string.const "foo")) @@ -227,15 +239,15 @@ ;; CHECK: (global $global (mut (ref string)) (global.get $"string.const_\"foo\"")) ;; LOWER: (type $0 (array (mut i16))) - ;; LOWER: (type $1 (func (param externref externref) (result i32))) + ;; LOWER: (type $1 (func (param i32) (result (ref extern)))) - ;; LOWER: (type $2 (func (param externref) (result i32))) + ;; LOWER: (type $2 (func (param externref externref) (result i32))) - ;; LOWER: (type $3 (func)) + ;; LOWER: (type $3 (func (param externref) (result i32))) - ;; LOWER: (type $4 (func (param (ref null $0) i32 i32) (result (ref extern)))) + ;; LOWER: (type $4 (func)) - ;; LOWER: (type $5 (func (param i32) (result (ref extern)))) + ;; LOWER: (type $5 (func (param (ref null $0) i32 i32) (result (ref extern)))) ;; LOWER: (type $6 (func (param externref externref) (result (ref extern)))) @@ -245,28 +257,34 @@ ;; LOWER: (type $9 (func (param externref i32 i32) (result (ref extern)))) + ;; LOWER: (type $10 (func (param externref) (result (ref extern)))) + ;; LOWER: (import "string.const" "0" (global $"string.const_\"foo\"" (ref extern))) - ;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $4) (param (ref null $0) i32 i32) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $5) (param (ref null $0) i32 i32) (result (ref extern)))) - ;; LOWER: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $5) (param i32) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $1) (param i32) (result (ref extern)))) ;; LOWER: (import "wasm:js-string" "concat" (func $concat (type $6) (param externref externref) (result (ref extern)))) ;; LOWER: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $7) (param externref (ref null $0) i32) (result i32))) - ;; LOWER: (import "wasm:js-string" "equals" (func $equals (type $1) (param externref externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "equals" (func $equals (type $2) (param externref externref) (result i32))) - ;; LOWER: (import "wasm:js-string" "test" (func $test (type $2) (param externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "test" (func $test (type $3) (param externref) (result i32))) - ;; LOWER: (import "wasm:js-string" "compare" (func $compare (type $1) (param externref externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "compare" (func $compare (type $2) (param externref externref) (result i32))) - ;; LOWER: (import "wasm:js-string" "length" (func $length (type $2) (param externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "length" (func $length (type $3) (param externref) (result i32))) ;; LOWER: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $8) (param externref i32) (result i32))) ;; LOWER: (import "wasm:js-string" "substring" (func $substring (type $9) (param externref i32 i32) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "fromCharCode" (func $fromCharCode (type $1) (param i32) (result (ref extern)))) + + ;; LOWER: (import "wasm:js-string" "cast" (func $cast (type $10) (param externref) (result (ref extern)))) + ;; LOWER: (global $global (mut (ref extern)) (global.get $"string.const_\"foo\"")) (global $global (mut (ref string)) (string.const "foo")) @@ -275,7 +293,7 @@ ;; CHECK-NEXT: (global.get $"string.const_\"foo\"") ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; LOWER: (func $a (type $3) + ;; LOWER: (func $a (type $4) ;; LOWER-NEXT: (drop ;; LOWER-NEXT: (global.get $"string.const_\"foo\"") ;; LOWER-NEXT: ) @@ -304,13 +322,13 @@ ;; CHECK: (global $struct (ref $struct) (struct.new $struct ;; CHECK-NEXT: (global.get $string) ;; CHECK-NEXT: )) - ;; LOWER: (type $2 (func (param externref externref) (result i32))) + ;; LOWER: (type $2 (func (param i32) (result (ref extern)))) - ;; LOWER: (type $3 (func (param externref) (result i32))) + ;; LOWER: (type $3 (func (param externref externref) (result i32))) - ;; LOWER: (type $4 (func (param (ref null $0) i32 i32) (result (ref extern)))) + ;; LOWER: (type $4 (func (param externref) (result i32))) - ;; LOWER: (type $5 (func (param i32) (result (ref extern)))) + ;; LOWER: (type $5 (func (param (ref null $0) i32 i32) (result (ref extern)))) ;; LOWER: (type $6 (func (param externref externref) (result (ref extern)))) @@ -320,28 +338,34 @@ ;; LOWER: (type $9 (func (param externref i32 i32) (result (ref extern)))) + ;; LOWER: (type $10 (func (param externref) (result (ref extern)))) + ;; LOWER: (import "string.const" "0" (global $string (ref extern))) - ;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $4) (param (ref null $0) i32 i32) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $5) (param (ref null $0) i32 i32) (result (ref extern)))) - ;; LOWER: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $5) (param i32) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $2) (param i32) (result (ref extern)))) ;; LOWER: (import "wasm:js-string" "concat" (func $concat (type $6) (param externref externref) (result (ref extern)))) ;; LOWER: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $7) (param externref (ref null $0) i32) (result i32))) - ;; LOWER: (import "wasm:js-string" "equals" (func $equals (type $2) (param externref externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "equals" (func $equals (type $3) (param externref externref) (result i32))) - ;; LOWER: (import "wasm:js-string" "test" (func $test (type $3) (param externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "test" (func $test (type $4) (param externref) (result i32))) - ;; LOWER: (import "wasm:js-string" "compare" (func $compare (type $2) (param externref externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "compare" (func $compare (type $3) (param externref externref) (result i32))) - ;; LOWER: (import "wasm:js-string" "length" (func $length (type $3) (param externref) (result i32))) + ;; LOWER: (import "wasm:js-string" "length" (func $length (type $4) (param externref) (result i32))) ;; LOWER: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $8) (param externref i32) (result i32))) ;; LOWER: (import "wasm:js-string" "substring" (func $substring (type $9) (param externref i32 i32) (result (ref extern)))) + ;; LOWER: (import "wasm:js-string" "fromCharCode" (func $fromCharCode (type $2) (param i32) (result (ref extern)))) + + ;; LOWER: (import "wasm:js-string" "cast" (func $cast (type $10) (param externref) (result (ref extern)))) + ;; LOWER: (global $struct (ref $struct) (struct.new $struct ;; LOWER-NEXT: (global.get $string) ;; LOWER-NEXT: )) diff --git a/test/lit/passes/string-lifting-lowering-roundtrip.wast b/test/lit/passes/string-lifting-lowering-roundtrip.wast new file mode 100644 index 00000000000..d3c660fe87d --- /dev/null +++ b/test/lit/passes/string-lifting-lowering-roundtrip.wast @@ -0,0 +1,88 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: wasm-opt %s -all --string-lifting --string-lowering -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (func (param i32) (result (ref extern)))) + + ;; CHECK: (type $1 (array (mut i16))) + + ;; CHECK: (type $2 (func (param externref) (result (ref extern)))) + + ;; CHECK: (type $3 (func (param externref externref) (result i32))) + + ;; CHECK: (type $4 (func (param externref) (result i32))) + + ;; CHECK: (rec + ;; CHECK-NEXT: (type $5 (func (param externref))) + + ;; CHECK: (type $6 (func (param i32))) + + ;; CHECK: (type $7 (func (param (ref null $1) i32 i32) (result (ref extern)))) + + ;; CHECK: (type $8 (func (param externref externref) (result (ref extern)))) + + ;; CHECK: (type $9 (func (param externref (ref null $1) i32) (result i32))) + + ;; CHECK: (type $10 (func (param externref i32) (result i32))) + + ;; CHECK: (type $11 (func (param externref i32 i32) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "fromCharCode" (func $fromCharCode (type $0) (param i32) (result (ref extern)))) + (import "wasm:js-string" "fromCharCode" (func $fromCharCode (param i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "cast" (func $cast (type $2) (param externref) (result (ref extern)))) + (import "wasm:js-string" "cast" (func $cast (param externref) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $7) (param (ref null $1) i32 i32) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $0) (param i32) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $8) (param externref externref) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $9) (param externref (ref null $1) i32) (result i32))) + + ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $3) (param externref externref) (result i32))) + + ;; CHECK: (import "wasm:js-string" "test" (func $test (type $4) (param externref) (result i32))) + + ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $3) (param externref externref) (result i32))) + + ;; CHECK: (import "wasm:js-string" "length" (func $length (type $4) (param externref) (result i32))) + + ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $10) (param externref i32) (result i32))) + + ;; CHECK: (import "wasm:js-string" "substring" (func $substring (type $11) (param externref i32 i32) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "fromCharCode" (func $fromCharCode_14 (type $0) (param i32) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "cast" (func $cast_15 (type $2) (param externref) (result (ref extern)))) + + ;; CHECK: (func $from_char_code (type $6) (param $x i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $fromCharCode_14 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $from_char_code (param $x i32) + (drop + (call $fromCharCode + (local.get $x) + ) + ) + ) + + ;; CHECK: (func $cast_test (type $5) (param $x externref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $cast_15 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast_test (param $x externref) + (drop + (call $cast + (local.get $x) + ) + ) + ) +) diff --git a/test/lit/passes/string-lifting-section.wast b/test/lit/passes/string-lifting-section.wast index 5406494432a..669bb4a1b5d 100644 --- a/test/lit/passes/string-lifting-section.wast +++ b/test/lit/passes/string-lifting-section.wast @@ -9,13 +9,13 @@ ;; CHECK: (type $1 (func)) - ;; CHECK: (type $2 (func (param externref externref) (result i32))) + ;; CHECK: (type $2 (func (param i32) (result (ref extern)))) - ;; CHECK: (type $3 (func (param externref) (result i32))) + ;; CHECK: (type $3 (func (param externref externref) (result i32))) - ;; CHECK: (type $4 (func (param (ref null $0) i32 i32) (result (ref extern)))) + ;; CHECK: (type $4 (func (param externref) (result i32))) - ;; CHECK: (type $5 (func (param i32) (result (ref extern)))) + ;; CHECK: (type $5 (func (param (ref null $0) i32 i32) (result (ref extern)))) ;; CHECK: (type $6 (func (param externref externref) (result (ref extern)))) @@ -25,6 +25,8 @@ ;; CHECK: (type $9 (func (param externref i32 i32) (result (ref extern)))) + ;; CHECK: (type $10 (func (param externref) (result (ref extern)))) + ;; CHECK: (import "string.const" "0" (global $"string.const_\"bar\"" (ref extern))) ;; CHECK: (import "string.const" "1" (global $"string.const_\"foo\"" (ref extern))) @@ -39,26 +41,30 @@ ;; CHECK: (import "string.const" "6" (global $"string.const_\"z\\\\\"" (ref extern))) - ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $4) (param (ref null $0) i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $5) (param (ref null $0) i32 i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $5) (param i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $2) (param i32) (result (ref extern)))) ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $6) (param externref externref) (result (ref extern)))) ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $7) (param externref (ref null $0) i32) (result i32))) - ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $2) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $3) (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "test" (func $test (type $3) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "test" (func $test (type $4) (param externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $2) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $3) (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "length" (func $length (type $3) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "length" (func $length (type $4) (param externref) (result i32))) ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $8) (param externref i32) (result i32))) ;; CHECK: (import "wasm:js-string" "substring" (func $substring (type $9) (param externref i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCharCode" (func $fromCharCode (type $2) (param i32) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "cast" (func $cast (type $10) (param externref) (result (ref extern)))) + ;; CHECK: (func $consts (type $1) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (string.const "foo") diff --git a/test/lit/passes/string-lifting.wast b/test/lit/passes/string-lifting.wast index da537c59e98..2d915502101 100644 --- a/test/lit/passes/string-lifting.wast +++ b/test/lit/passes/string-lifting.wast @@ -12,40 +12,44 @@ ;; CHECK: (type $3 (func (param externref i32 i32) (result (ref extern)))) - ;; CHECK: (type $4 (func (param externref externref) (result (ref extern)))) + ;; CHECK: (type $4 (func (param i32) (result (ref extern)))) - ;; CHECK: (type $5 (func (param (ref null $array16) i32 i32) (result (ref extern)))) + ;; CHECK: (type $5 (func (param externref externref) (result (ref extern)))) - ;; CHECK: (type $6 (func (param i32) (result (ref extern)))) + ;; CHECK: (type $6 (func (param externref) (result externref))) - ;; CHECK: (type $7 (func (param externref (ref null $array16) i32) (result i32))) + ;; CHECK: (type $7 (func (param (ref null $array16) i32 i32) (result (ref extern)))) - ;; CHECK: (type $8 (func (param externref i32) (result i32))) + ;; CHECK: (type $8 (func (param externref (ref null $array16) i32) (result i32))) - ;; CHECK: (type $9 (func)) + ;; CHECK: (type $9 (func (param externref i32) (result i32))) - ;; CHECK: (type $10 (func (param (ref $array16)))) + ;; CHECK: (type $10 (func (param externref) (result (ref extern)))) - ;; CHECK: (type $11 (func (result externref))) + ;; CHECK: (type $11 (func)) - ;; CHECK: (type $12 (func (param externref (ref $array16)) (result i32))) + ;; CHECK: (type $12 (func (param (ref $array16)))) - ;; CHECK: (type $13 (func (param externref) (result externref))) + ;; CHECK: (type $13 (func (result externref))) - ;; CHECK: (type $14 (func (param externref))) + ;; CHECK: (type $14 (func (param externref (ref $array16)) (result i32))) + + ;; CHECK: (type $15 (func (param i32) (result externref))) + + ;; CHECK: (type $16 (func (param externref))) ;; CHECK: (import "\'" "foo" (global $string_foo (ref extern))) (import "\'" "foo" (global $string_foo (ref extern))) ;; CHECK: (import "\'" "bar" (global $string_bar (ref extern))) (import "\'" "bar" (global $string_bar (ref extern))) - ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $5) (param (ref null $array16) i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $7) (param (ref null $array16) i32 i32) (result (ref extern)))) (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (param (ref null $array16) i32 i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $6) (param i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $4) (param i32) (result (ref extern)))) (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (param i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $4) (param externref externref) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $5) (param externref externref) (result (ref extern)))) (import "wasm:js-string" "concat" (func $concat (param externref externref) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $7) (param externref (ref null $array16) i32) (result i32))) + ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $8) (param externref (ref null $array16) i32) (result i32))) (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (param externref (ref null $array16) i32) (result i32))) ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $2) (param externref externref) (result i32))) (import "wasm:js-string" "equals" (func $equals (param externref externref) (result i32))) @@ -55,10 +59,14 @@ (import "wasm:js-string" "test" (func $test (param externref) (result i32))) ;; CHECK: (import "wasm:js-string" "length" (func $length (type $0) (param externref) (result i32))) (import "wasm:js-string" "length" (func $length (param externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $8) (param externref i32) (result i32))) + ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $9) (param externref i32) (result i32))) (import "wasm:js-string" "charCodeAt" (func $charCodeAt (param externref i32) (result i32))) ;; CHECK: (import "wasm:js-string" "substring" (func $substring_foo (type $3) (param externref i32 i32) (result (ref extern)))) (import "wasm:js-string" "substring" (func $substring_foo (param externref i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCharCode" (func $fromCharCode (type $4) (param i32) (result (ref extern)))) + (import "wasm:js-string" "fromCharCode" (func $fromCharCode (param i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "cast" (func $cast (type $10) (param externref) (result (ref extern)))) + (import "wasm:js-string" "cast" (func $cast (param externref) (result (ref extern)))) ;; A function from the right module, but an unsupported name. ;; CHECK: (import "wasm:js-string" "wrong-name" (func $wrong-base (type $3) (param externref i32 i32) (result (ref extern)))) @@ -68,7 +76,7 @@ ;; CHECK: (import "oops" "substring" (func $wrong-module (type $3) (param externref i32 i32) (result (ref extern)))) (import "oops" "substring" (func $wrong-module (param externref i32 i32) (result (ref extern)))) - ;; CHECK: (func $func (type $9) + ;; CHECK: (func $func (type $11) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (string.const "foo") ;; CHECK-NEXT: ) @@ -94,7 +102,7 @@ ) ) - ;; CHECK: (func $string.new.gc (type $10) (param $ref (ref $array16)) + ;; CHECK: (func $string.new.gc (type $12) (param $ref (ref $array16)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (string.new_wtf16_array ;; CHECK-NEXT: (local.get $ref) @@ -113,7 +121,7 @@ ) ) - ;; CHECK: (func $string.from_code_point (type $11) (result externref) + ;; CHECK: (func $string.from_code_point (type $13) (result externref) ;; CHECK-NEXT: (string.from_code_point ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) @@ -124,7 +132,7 @@ ) ) - ;; CHECK: (func $string.concat (type $4) (param $a externref) (param $b externref) (result (ref extern)) + ;; CHECK: (func $string.concat (type $5) (param $a externref) (param $b externref) (result (ref extern)) ;; CHECK-NEXT: (string.concat ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: (local.get $b) @@ -137,7 +145,7 @@ ) ) - ;; CHECK: (func $string.encode (type $12) (param $ref externref) (param $array16 (ref $array16)) (result i32) + ;; CHECK: (func $string.encode (type $14) (param $ref externref) (param $array16 (ref $array16)) (result i32) ;; CHECK-NEXT: (string.encode_wtf16_array ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (local.get $array16) @@ -213,7 +221,7 @@ ) ) - ;; CHECK: (func $string.slice (type $13) (param $ref externref) (result externref) + ;; CHECK: (func $string.slice (type $6) (param $ref externref) (result externref) ;; CHECK-NEXT: (stringview_wtf16.slice ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (i32.const 2) @@ -230,7 +238,32 @@ ) ) - ;; CHECK: (func $wrong (type $14) (param $ref externref) + ;; CHECK: (func $string.from_char_code (type $15) (param $x i32) (result externref) + ;; CHECK-NEXT: (string.from_code_point + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 65535) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string.from_char_code (param $x i32) (result externref) + (call $fromCharCode + (local.get $x) + ) + ) + + ;; CHECK: (func $string.cast (type $6) (param $x externref) (result externref) + ;; CHECK-NEXT: (ref.cast (ref string) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string.cast (param $x externref) (result externref) + (call $cast + (local.get $x) + ) + ) + + ;; CHECK: (func $wrong (type $16) (param $ref externref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $wrong-base ;; CHECK-NEXT: (local.get $ref) diff --git a/test/lit/passes/string-lowering-imports-custom-module.wast b/test/lit/passes/string-lowering-imports-custom-module.wast index 2d82bfb74d8..f983887c9be 100644 --- a/test/lit/passes/string-lowering-imports-custom-module.wast +++ b/test/lit/passes/string-lowering-imports-custom-module.wast @@ -9,15 +9,15 @@ (module ;; CHECK: (type $0 (array (mut i16))) - ;; CHECK: (type $1 (func (param externref externref) (result i32))) + ;; CHECK: (type $1 (func (param i32) (result (ref extern)))) - ;; CHECK: (type $2 (func (param externref) (result i32))) + ;; CHECK: (type $2 (func (param externref externref) (result i32))) - ;; CHECK: (type $3 (func)) + ;; CHECK: (type $3 (func (param externref) (result i32))) - ;; CHECK: (type $4 (func (param (ref null $0) i32 i32) (result (ref extern)))) + ;; CHECK: (type $4 (func)) - ;; CHECK: (type $5 (func (param i32) (result (ref extern)))) + ;; CHECK: (type $5 (func (param (ref null $0) i32 i32) (result (ref extern)))) ;; CHECK: (type $6 (func (param externref externref) (result (ref extern)))) @@ -27,31 +27,37 @@ ;; CHECK: (type $9 (func (param externref i32 i32) (result (ref extern)))) + ;; CHECK: (type $10 (func (param externref) (result (ref extern)))) + ;; CHECK: (import "strings" "foo" (global $"string.const_\"foo\"" (ref extern))) - ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $4) (param (ref null $0) i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $5) (param (ref null $0) i32 i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $5) (param i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $1) (param i32) (result (ref extern)))) ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $6) (param externref externref) (result (ref extern)))) ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $7) (param externref (ref null $0) i32) (result i32))) - ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $1) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $2) (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "test" (func $test (type $2) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "test" (func $test (type $3) (param externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $1) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $2) (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "length" (func $length (type $2) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "length" (func $length (type $3) (param externref) (result i32))) ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $8) (param externref i32) (result i32))) ;; CHECK: (import "wasm:js-string" "substring" (func $substring (type $9) (param externref i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCharCode" (func $fromCharCode (type $1) (param i32) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "cast" (func $cast (type $10) (param externref) (result (ref extern)))) + ;; CHECK: (export "const" (func $const)) - ;; CHECK: (func $const (type $3) + ;; CHECK: (func $const (type $4) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (global.get $"string.const_\"foo\"") ;; CHECK-NEXT: ) diff --git a/test/lit/passes/string-lowering-instructions.wast b/test/lit/passes/string-lowering-instructions.wast index 23303838d20..85a4912519c 100644 --- a/test/lit/passes/string-lowering-instructions.wast +++ b/test/lit/passes/string-lowering-instructions.wast @@ -29,65 +29,77 @@ ;; CHECK: (type $8 (func (param externref) (result i32))) - ;; CHECK: (type $9 (func (param externref externref) (result (ref extern)))) + ;; CHECK: (type $9 (func (param anyref) (result (ref i31)))) - ;; CHECK: (type $10 (func (param externref (ref $array16)) (result i32))) + ;; CHECK: (type $10 (func (param externref externref) (result (ref extern)))) - ;; CHECK: (type $11 (func (param externref externref) (result i32))) + ;; CHECK: (type $11 (func (param externref (ref $array16)) (result i32))) - ;; CHECK: (type $12 (func (param externref) (result externref))) + ;; CHECK: (type $12 (func (param externref externref) (result i32))) - ;; CHECK: (type $13 (func (param externref))) + ;; CHECK: (type $13 (func (param externref) (result externref))) - ;; CHECK: (type $14 (func)) + ;; CHECK: (type $14 (func (param i32) (result externref))) - ;; CHECK: (type $15 (func (param externref) (result i32))) + ;; CHECK: (type $15 (func (param externref) (result (ref extern)))) - ;; CHECK: (type $16 (func (result externref))) + ;; CHECK: (type $16 (func (param externref))) - ;; CHECK: (type $17 (func (param externref externref) (result i32))) + ;; CHECK: (type $17 (func)) - ;; CHECK: (type $18 (func (param externref i32 externref))) + ;; CHECK: (type $18 (func (param externref) (result i32))) - ;; CHECK: (type $19 (func (param (ref null $array16) i32 i32) (result (ref extern)))) + ;; CHECK: (type $19 (func (result externref))) ;; CHECK: (type $20 (func (param i32) (result (ref extern)))) - ;; CHECK: (type $21 (func (param externref externref) (result (ref extern)))) + ;; CHECK: (type $21 (func (param externref externref) (result i32))) - ;; CHECK: (type $22 (func (param externref (ref null $array16) i32) (result i32))) + ;; CHECK: (type $22 (func (param externref i32 externref))) - ;; CHECK: (type $23 (func (param externref i32) (result i32))) + ;; CHECK: (type $23 (func (param (ref null $array16) i32 i32) (result (ref extern)))) - ;; CHECK: (type $24 (func (param externref i32 i32) (result (ref extern)))) + ;; CHECK: (type $24 (func (param externref externref) (result (ref extern)))) + + ;; CHECK: (type $25 (func (param externref (ref null $array16) i32) (result i32))) + + ;; CHECK: (type $26 (func (param externref i32) (result i32))) + + ;; CHECK: (type $27 (func (param externref i32 i32) (result (ref extern)))) + + ;; CHECK: (type $28 (func (param externref) (result (ref extern)))) ;; CHECK: (import "string.const" "0" (global $"string.const_\"exported\"" (ref extern))) ;; CHECK: (import "string.const" "1" (global $"string.const_\"value\"" (ref extern))) - ;; CHECK: (import "colliding" "name" (func $fromCodePoint (type $14))) + ;; CHECK: (import "colliding" "name" (func $fromCodePoint (type $17))) (import "colliding" "name" (func $fromCodePoint)) - ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $19) (param (ref null $array16) i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $23) (param (ref null $array16) i32 i32) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint_27 (type $20) (param i32) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $24) (param externref externref) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint_19 (type $20) (param i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $25) (param externref (ref null $array16) i32) (result i32))) - ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $21) (param externref externref) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $21) (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $22) (param externref (ref null $array16) i32) (result i32))) + ;; CHECK: (import "wasm:js-string" "test" (func $test (type $18) (param externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $17) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $21) (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "test" (func $test (type $15) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "length" (func $length (type $18) (param externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $17) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $26) (param externref i32) (result i32))) - ;; CHECK: (import "wasm:js-string" "length" (func $length (type $15) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "substring" (func $substring (type $27) (param externref i32 i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $23) (param externref i32) (result i32))) + ;; CHECK: (import "wasm:js-string" "fromCharCode" (func $fromCharCode (type $20) (param i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "substring" (func $substring (type $24) (param externref i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "cast" (func $cast (type $28) (param externref) (result (ref extern)))) ;; CHECK: (global $string externref (ref.null noextern)) (global $string stringref (ref.null string)) ;; Test we update global nulls. @@ -115,8 +127,8 @@ ) ) - ;; CHECK: (func $string.from_code_point (type $16) (result externref) - ;; CHECK-NEXT: (call $fromCodePoint_19 + ;; CHECK: (func $string.from_code_point (type $19) (result externref) + ;; CHECK-NEXT: (call $fromCodePoint_27 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -126,7 +138,7 @@ ) ) - ;; CHECK: (func $string.concat (type $9) (param $0 externref) (param $1 externref) (result (ref extern)) + ;; CHECK: (func $string.concat (type $10) (param $0 externref) (param $1 externref) (result (ref extern)) ;; CHECK-NEXT: (call $concat ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (local.get $1) @@ -139,7 +151,7 @@ ) ) - ;; CHECK: (func $string.encode (type $10) (param $ref externref) (param $array16 (ref $array16)) (result i32) + ;; CHECK: (func $string.encode (type $11) (param $ref externref) (param $array16 (ref $array16)) (result i32) ;; CHECK-NEXT: (call $intoCharCodeArray ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (local.get $array16) @@ -154,7 +166,7 @@ ) ) - ;; CHECK: (func $string.eq (type $11) (param $a externref) (param $b externref) (result i32) + ;; CHECK: (func $string.eq (type $12) (param $a externref) (param $b externref) (result i32) ;; CHECK-NEXT: (call $equals ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: (local.get $b) @@ -167,7 +179,7 @@ ) ) - ;; CHECK: (func $string.compare (type $11) (param $a externref) (param $b externref) (result i32) + ;; CHECK: (func $string.compare (type $12) (param $a externref) (param $b externref) (result i32) ;; CHECK-NEXT: (call $compare ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: (local.get $b) @@ -215,7 +227,7 @@ ) ) - ;; CHECK: (func $string.slice (type $12) (param $ref externref) (result externref) + ;; CHECK: (func $string.slice (type $13) (param $ref externref) (result externref) ;; CHECK-NEXT: (call $substring ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (i32.const 2) @@ -230,7 +242,148 @@ ) ) - ;; CHECK: (func $if.string (type $12) (param $ref externref) (result externref) + ;; CHECK: (func $string.from_char_code (type $14) (param $x i32) (result externref) + ;; CHECK-NEXT: (call $fromCharCode + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string.from_char_code (param $x i32) (result stringref) + ;; A code point masked to 16 bits is equivalent to a char code, so lower to fromCharCode. + (string.from_code_point + (i32.and + (local.get $x) + (i32.const 0xffff) + ) + ) + ) + + ;; CHECK: (func $string.from_char_code.flipped (type $14) (param $x i32) (result externref) + ;; CHECK-NEXT: (call $fromCharCode + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string.from_char_code.flipped (param $x i32) (result stringref) + ;; As above, but with the order is flipped. + (string.from_code_point + (i32.and + (i32.const 0xffff) + (local.get $x) + ) + ) + ) + + ;; CHECK: (func $string.from_code_point.other-mask (type $14) (param $x i32) (result externref) + ;; CHECK-NEXT: (call $fromCodePoint_27 + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string.from_code_point.other-mask (param $x i32) (result stringref) + (string.from_code_point + (i32.and + (local.get $x) + (i32.const 0xff) + ) + ) + ) + + ;; CHECK: (func $string.cast (type $15) (param $x externref) (result (ref extern)) + ;; CHECK-NEXT: (call $cast + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string.cast (param $x externref) (result (ref string)) + (ref.cast (ref string) + (local.get $x) + ) + ) + + ;; CHECK: (func $string.cast.null (type $13) (param $x externref) (result externref) + ;; CHECK-NEXT: (local $1 externref) + ;; CHECK-NEXT: (if (result externref) + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (call $cast + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string.cast.null (param $x externref) (result stringref) + ;; We replace a nullable cast with conditional ref.is_null. + (ref.cast stringref + (local.get $x) + ) + ) + + ;; CHECK: (func $string.cast.nested (type $15) (param $x externref) (result (ref extern)) + ;; CHECK-NEXT: (local $1 externref) + ;; CHECK-NEXT: (call $cast + ;; CHECK-NEXT: (if (result externref) + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (call $cast + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string.cast.nested (param $x externref) (result (ref string)) + (ref.cast (ref string) + (ref.cast stringref + (local.get $x) + ) + ) + ) + + ;; CHECK: (func $string.cast.operand (type $10) (param $x externref) (param $y externref) (result (ref extern)) + ;; CHECK-NEXT: (call $concat + ;; CHECK-NEXT: (call $cast + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string.cast.operand (param $x externref) (param $y stringref) (result (ref string)) + ;; We replace this string cast with the imported cast function call. + (string.concat + (ref.cast (ref string) + (local.get $x) + ) + (local.get $y) + ) + ) + + ;; CHECK: (func $string.cast.other (type $9) (param $x anyref) (result (ref i31)) + ;; CHECK-NEXT: (ref.cast (ref i31) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string.cast.other (param $x anyref) (result (ref i31)) + ;; Test that casts to non-string types are not lowered + (ref.cast (ref i31) + (local.get $x) + ) + ) + + ;; CHECK: (func $if.string (type $13) (param $ref externref) (result externref) ;; CHECK-NEXT: (if (result externref) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then @@ -254,7 +407,7 @@ ) ) - ;; CHECK: (func $if.string.flip (type $12) (param $ref externref) (result externref) + ;; CHECK: (func $if.string.flip (type $13) (param $ref externref) (result externref) ;; CHECK-NEXT: (if (result externref) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then @@ -278,7 +431,7 @@ ) ) - ;; CHECK: (func $exported-string-returner (type $16) (result externref) + ;; CHECK: (func $exported-string-returner (type $19) (result externref) ;; CHECK-NEXT: (global.get $"string.const_\"exported\"") ;; CHECK-NEXT: ) (func $exported-string-returner (export "export.1") (result stringref) @@ -287,7 +440,7 @@ (string.const "exported") ) - ;; CHECK: (func $exported-string-receiver (type $18) (param $x externref) (param $y i32) (param $z externref) + ;; CHECK: (func $exported-string-receiver (type $22) (param $x externref) (param $y i32) (param $z externref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) @@ -312,7 +465,7 @@ ) ) - ;; CHECK: (func $use-struct-of-array (type $14) + ;; CHECK: (func $use-struct-of-array (type $17) ;; CHECK-NEXT: (local $array16 (ref $array16)) ;; CHECK-NEXT: (local $open (ref $array16-open)) ;; CHECK-NEXT: (local $child (ref $array16-child)) @@ -368,7 +521,7 @@ ) ) - ;; CHECK: (func $struct-of-string (type $14) + ;; CHECK: (func $struct-of-string (type $17) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $struct-of-string ;; CHECK-NEXT: (ref.null noextern) @@ -411,7 +564,7 @@ ) ) - ;; CHECK: (func $call-param-null (type $13) (param $str externref) + ;; CHECK: (func $call-param-null (type $16) (param $str externref) ;; CHECK-NEXT: (call $call-param-null ;; CHECK-NEXT: (ref.null noextern) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/string-lowering_types.wast b/test/lit/passes/string-lowering_types.wast index 9b20cafbe31..65fe5721ca2 100644 --- a/test/lit/passes/string-lowering_types.wast +++ b/test/lit/passes/string-lowering_types.wast @@ -10,24 +10,24 @@ (rec ;; CHECK: (type $0 (array (mut i16))) - ;; CHECK: (type $1 (func (param externref externref) (result i32))) + ;; CHECK: (type $1 (func (param i32) (result (ref extern)))) - ;; CHECK: (type $2 (func (param externref) (result i32))) + ;; CHECK: (type $2 (func (param externref externref) (result i32))) - ;; CHECK: (type $3 (func (param externref))) + ;; CHECK: (type $3 (func (param externref) (result i32))) - ;; CHECK: (type $4 (func (param (ref extern)))) + ;; CHECK: (type $4 (func (param externref))) - ;; CHECK: (type $5 (func)) + ;; CHECK: (type $5 (func (param (ref extern)))) + + ;; CHECK: (type $6 (func)) ;; CHECK: (type $private (struct (field externref))) (type $private (struct (field stringref))) ) (type $public (func (param stringref))) - ;; CHECK: (type $7 (func (param (ref null $0) i32 i32) (result (ref extern)))) - - ;; CHECK: (type $8 (func (param i32) (result (ref extern)))) + ;; CHECK: (type $8 (func (param (ref null $0) i32 i32) (result (ref extern)))) ;; CHECK: (type $9 (func (param externref externref) (result (ref extern)))) @@ -37,35 +37,41 @@ ;; CHECK: (type $12 (func (param externref i32 i32) (result (ref extern)))) - ;; CHECK: (import "a" "b" (func $import (type $3) (param externref))) + ;; CHECK: (type $13 (func (param externref) (result (ref extern)))) + + ;; CHECK: (import "a" "b" (func $import (type $4) (param externref))) (import "a" "b" (func $import (type $public) (param stringref))) - ;; CHECK: (import "a" "b" (func $import-implicit (type $4) (param (ref extern)))) + ;; CHECK: (import "a" "b" (func $import-implicit (type $5) (param (ref extern)))) (import "a" "b" (func $import-implicit (param (ref string)))) - ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $7) (param (ref null $0) i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $8) (param (ref null $0) i32 i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $8) (param i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $1) (param i32) (result (ref extern)))) ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $9) (param externref externref) (result (ref extern)))) ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $10) (param externref (ref null $0) i32) (result i32))) - ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $1) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $2) (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "test" (func $test (type $2) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "test" (func $test (type $3) (param externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $1) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $2) (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "length" (func $length (type $2) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "length" (func $length (type $3) (param externref) (result i32))) ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $11) (param externref i32) (result i32))) ;; CHECK: (import "wasm:js-string" "substring" (func $substring (type $12) (param externref i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCharCode" (func $fromCharCode (type $1) (param i32) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "cast" (func $cast (type $13) (param externref) (result (ref extern)))) + ;; CHECK: (export "export" (func $export)) - ;; CHECK: (func $export (type $5) + ;; CHECK: (func $export (type $6) ;; CHECK-NEXT: (local $0 (ref $private)) ;; CHECK-NEXT: ) (func $export (export "export") @@ -79,15 +85,15 @@ (module ;; CHECK: (type $0 (array (mut i16))) - ;; CHECK: (type $1 (func (param externref externref) (result i32))) + ;; CHECK: (type $1 (func (param i32) (result (ref extern)))) - ;; CHECK: (type $2 (func (param externref) (result i32))) + ;; CHECK: (type $2 (func (param externref externref) (result i32))) - ;; CHECK: (type $3 (func (result (ref extern)))) + ;; CHECK: (type $3 (func (param externref) (result i32))) - ;; CHECK: (type $4 (func (param (ref null $0) i32 i32) (result (ref extern)))) + ;; CHECK: (type $4 (func (result (ref extern)))) - ;; CHECK: (type $5 (func (param i32) (result (ref extern)))) + ;; CHECK: (type $5 (func (param (ref null $0) i32 i32) (result (ref extern)))) ;; CHECK: (type $6 (func (param externref externref) (result (ref extern)))) @@ -97,33 +103,39 @@ ;; CHECK: (type $9 (func (param externref i32 i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $4) (param (ref null $0) i32 i32) (result (ref extern)))) + ;; CHECK: (type $10 (func (param externref) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $5) (param (ref null $0) i32 i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $5) (param i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $1) (param i32) (result (ref extern)))) ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $6) (param externref externref) (result (ref extern)))) ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $7) (param externref (ref null $0) i32) (result i32))) - ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $1) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $2) (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "test" (func $test (type $2) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "test" (func $test (type $3) (param externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $1) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $2) (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "length" (func $length (type $2) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "length" (func $length (type $3) (param externref) (result i32))) ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $8) (param externref i32) (result i32))) ;; CHECK: (import "wasm:js-string" "substring" (func $substring (type $9) (param externref i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCharCode" (func $fromCharCode (type $1) (param i32) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "cast" (func $cast (type $10) (param externref) (result (ref extern)))) + ;; CHECK: (table $table 31 31 funcref) (table $table 31 31 funcref) ;; CHECK: (elem $elem (i32.const 0) $func) (elem $elem (i32.const 0) $func) - ;; CHECK: (func $func (type $3) (result (ref extern)) + ;; CHECK: (func $func (type $4) (result (ref extern)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.func $func) ;; CHECK-NEXT: ) @@ -145,17 +157,17 @@ ;; CHECK: (type $0 (array (mut i16))) - ;; CHECK: (type $1 (func (param externref externref) (result i32))) + ;; CHECK: (type $1 (func (param i32) (result (ref extern)))) - ;; CHECK: (type $2 (func (param externref) (result i32))) + ;; CHECK: (type $2 (func (param externref externref) (result i32))) - ;; CHECK: (type $3 (func (param (ref extern)))) + ;; CHECK: (type $3 (func (param externref) (result i32))) - ;; CHECK: (type $4 (func)) + ;; CHECK: (type $4 (func (param (ref extern)))) - ;; CHECK: (type $5 (func (param (ref null $0) i32 i32) (result (ref extern)))) + ;; CHECK: (type $5 (func)) - ;; CHECK: (type $6 (func (param i32) (result (ref extern)))) + ;; CHECK: (type $6 (func (param (ref null $0) i32 i32) (result (ref extern)))) ;; CHECK: (type $7 (func (param externref externref) (result (ref extern)))) @@ -165,31 +177,37 @@ ;; CHECK: (type $10 (func (param externref i32 i32) (result (ref extern)))) + ;; CHECK: (type $11 (func (param externref) (result (ref extern)))) + ;; CHECK: (import "string.const" "0" (global $"string.const_\"whoops\"" (ref extern))) - ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $5) (param (ref null $0) i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $6) (param (ref null $0) i32 i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $6) (param i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $1) (param i32) (result (ref extern)))) ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $7) (param externref externref) (result (ref extern)))) ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $8) (param externref (ref null $0) i32) (result i32))) - ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $1) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $2) (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "test" (func $test (type $2) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "test" (func $test (type $3) (param externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $1) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $2) (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "length" (func $length (type $2) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "length" (func $length (type $3) (param externref) (result i32))) ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $9) (param externref i32) (result i32))) ;; CHECK: (import "wasm:js-string" "substring" (func $substring (type $10) (param externref i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCharCode" (func $fromCharCode (type $1) (param i32) (result (ref extern)))) + + ;; CHECK: (import "wasm:js-string" "cast" (func $cast (type $11) (param externref) (result (ref extern)))) + ;; CHECK: (table $table 1 1 funcref) - ;; CHECK: (tag $tag (type $3) (param (ref extern))) + ;; CHECK: (tag $tag (type $4) (param (ref extern))) (tag $tag (type $str) (param (ref string))) (table $table 1 1 funcref) @@ -199,7 +217,7 @@ ;; CHECK: (export "table" (table $table)) (export "table" (table $table)) - ;; CHECK: (func $throw (type $4) + ;; CHECK: (func $throw (type $5) ;; CHECK-NEXT: (throw $tag ;; CHECK-NEXT: (global.get $"string.const_\"whoops\"") ;; CHECK-NEXT: ) From e55e99986428e9f954f685f7fa6458f3185ba42d Mon Sep 17 00:00:00 2001 From: Jeremy Nguyen Date: Mon, 9 Nov 2026 18:02:37 -0800 Subject: [PATCH 2/6] Update src/passes/StringLowering.cpp, test/lit/passes/string-lowering-instructions.wast Co-authored-by: Thomas Lively --- src/passes/StringLowering.cpp | 7 +++---- test/lit/passes/string-lowering-instructions.wast | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/passes/StringLowering.cpp b/src/passes/StringLowering.cpp index 624c8b0883d..9b7af7a6523 100644 --- a/src/passes/StringLowering.cpp +++ b/src/passes/StringLowering.cpp @@ -378,8 +378,8 @@ struct StringLowering : public StringGathering { CastWalker(Casts& casts) : casts(casts) {} void visitRefCast(RefCast* curr) { - if (curr->type.isRef() && - curr->type.getHeapType() == HeapType::string && !curr->desc) { + if (curr->type.isString()) { + assert(!curr->desc); casts.push_back(getCurrentPointer()); } } @@ -497,9 +497,8 @@ struct StringLowering : public StringGathering { auto isCharCodeMask = [](Expression* expr) { if (auto* constExpr = expr->dynCast()) { return constExpr->value.geti32() == 0xFFFF; - } else { - return false; } + return false; }; if (isCharCodeMask(binary->right)) { diff --git a/test/lit/passes/string-lowering-instructions.wast b/test/lit/passes/string-lowering-instructions.wast index 85a4912519c..afd0781629d 100644 --- a/test/lit/passes/string-lowering-instructions.wast +++ b/test/lit/passes/string-lowering-instructions.wast @@ -263,7 +263,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $string.from_char_code.flipped (param $x i32) (result stringref) - ;; As above, but with the order is flipped. + ;; As above, but with the order flipped. (string.from_code_point (i32.and (i32.const 0xffff) From 5b059d8344318c256f9c809dca861aefd5debd18 Mon Sep 17 00:00:00 2001 From: Jeremy Nguyen Date: Mon, 9 Nov 2026 22:42:06 -0800 Subject: [PATCH 3/6] [Strings] Add StringNewFromCharCode IR operation, update stringref cast behavior Add a StringNewFromCharCode IR operation. The equivalent stringref operation is string.from_code_point where the codePoint is masked (x & 0xFFFF). Allow ref.cast with strings when strings are enabled, otherwise GC is required. StringLifting: Refinalize IR whenever a call has been replaced (i.e. externref -> stringref). StringLowering: Move replaceInstructions before updateTypes and add visitRefCast in Replacer. --- src/gen-s-parser.inc | 21 ++- src/ir/child-typer.h | 1 + src/ir/effects.h | 3 + src/passes/Print.cpp | 3 + src/passes/StringLifting.cpp | 18 +- src/passes/StringLowering.cpp | 121 ++++---------- src/wasm-builder.h | 3 +- src/wasm-interpreter.h | 9 + src/wasm.h | 1 + src/wasm/wasm-ir-builder.cpp | 2 +- src/wasm/wasm-stack.cpp | 9 + src/wasm/wasm-validator.cpp | 22 ++- test/lit/exec/strings.wast | 36 ++++ test/lit/passes/string-lifting.wast | 46 +++++- .../passes/string-lowering-instructions.wast | 156 +++++++----------- 15 files changed, 237 insertions(+), 214 deletions(-) diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index 7d7e5887b2c..990a075bb90 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -5202,12 +5202,23 @@ switch (buf[0]) { default: goto parse_error; } } - case 'f': - if (op == "string.from_code_point"sv) { - CHECK_ERR(makeStringNew(ctx, pos, annotations, StringNewFromCodePoint)); - return Ok{}; + case 'f': { + switch (buf[13]) { + case 'h': + if (op == "string.from_char_code"sv) { + CHECK_ERR(makeStringNew(ctx, pos, annotations, StringNewFromCharCode)); + return Ok{}; + } + goto parse_error; + case 'o': + if (op == "string.from_code_point"sv) { + CHECK_ERR(makeStringNew(ctx, pos, annotations, StringNewFromCodePoint)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; } - goto parse_error; + } case 'm': { switch (buf[15]) { case 'u': diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 1e121cedce2..ee40698a0e1 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1306,6 +1306,7 @@ template struct ChildTyper : OverriddenVisitor { return; } case StringNewFromCodePoint: + case StringNewFromCharCode: note(&curr->ref, Type::i32); return; } diff --git a/src/ir/effects.h b/src/ir/effects.h index bd61f3c942b..874ec7b7e6e 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -1252,6 +1252,9 @@ class EffectAnalyzer { // Invalid code points. parent.implicitTrap = true; return; + case StringNewFromCharCode: + // Invalid code points are ok. + return; } } void visitStringConst(StringConst* curr) {} diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 2140e46adb0..b63aa44a2c4 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2582,6 +2582,9 @@ struct PrintExpressionContents case StringNewFromCodePoint: printMedium(o, "string.from_code_point"); break; + case StringNewFromCharCode: + printMedium(o, "string.from_char_code"); + break; default: WASM_UNREACHABLE("invalid string.new*"); } diff --git a/src/passes/StringLifting.cpp b/src/passes/StringLifting.cpp index 253af5c0fd2..d583de90133 100644 --- a/src/passes/StringLifting.cpp +++ b/src/passes/StringLifting.cpp @@ -291,20 +291,20 @@ struct StringLifting : public Pass { curr->operands[1], curr->operands[2])); } else if (curr->target == parent.fromCharCodeImport) { - // String.fromCharCode(0x10000) => '\x00' - // String.fromCodePoint(0x10000) => '𐀀' - // Hence, String.fromCharCode(x) == String.fromCodePoint(x & 0xFFFF) - Builder builder(*getModule()); - replaceCurrent(builder.makeStringNew( - StringNewFromCodePoint, - builder.makeBinary(AndInt32, - curr->operands[0], - builder.makeConst(Literal(int32_t(0xFFFF)))))); + replaceCurrent( + Builder(*getModule()) + .makeStringNew(StringNewFromCharCode, curr->operands[0])); } else if (curr->target == parent.castImport) { replaceCurrent(Builder(*getModule()) .makeRefCast(curr->operands[0], Type(HeapType::string, NonNullable))); } + + // An expression that previously had type externref was replaced by a + // subtype, stringref; refinalize the IR. + if (getCurrent() != curr) { + modified = true; + } } void visitFunction(Function* curr) { diff --git a/src/passes/StringLowering.cpp b/src/passes/StringLowering.cpp index 9b7af7a6523..76238fe65dd 100644 --- a/src/passes/StringLowering.cpp +++ b/src/passes/StringLowering.cpp @@ -219,8 +219,8 @@ struct StringLowering : public StringGathering { // First, run the gathering operation so all string.consts are in one place. StringGathering::run(module); - // Find casts to string before they are turned to externref by updateTypes - findStringCasts(module); + // Replace string.* etc. operations with imported ones. + replaceInstructions(module); // Remove all HeapType::string etc. in favor of externref. updateTypes(module); @@ -228,9 +228,6 @@ struct StringLowering : public StringGathering { // Lower the string.const globals into imports. makeImports(module); - // Replace string.* etc. operations with imported ones. - replaceInstructions(module); - // ReFinalize to apply all the above changes. ReFinalize().run(getPassRunner(), module); @@ -366,61 +363,6 @@ struct StringLowering : public StringGathering { Name fromCharCodeImport; Name castImport; - // All the casts to string, so we can replace them - std::vector> stringCasts; - - void findStringCasts(Module* module) { - using Casts = std::vector; - - struct CastWalker : public PostWalker { - Casts& casts; - - CastWalker(Casts& casts) : casts(casts) {} - - void visitRefCast(RefCast* curr) { - if (curr->type.isString()) { - assert(!curr->desc); - casts.push_back(getCurrentPointer()); - } - } - }; - - // Casts cannot be found outside of a function (e.g. a global initializer) - // since ref.cast is not a constant expression. - ModuleUtils::ParallelFunctionAnalysis analysis( - *module, [&](Function* func, Casts& casts) { - if (!func->imported()) { - CastWalker(casts).walk(func->body); - } - }); - - for (auto& [func, casts] : analysis.map) { - for (auto** castPtr : casts) { - stringCasts.push_back({func, castPtr}); - } - } - } - - // Replaces string casts with imported cast function call - void lowerStringCast(Module* module, Function* func, Expression** castp) { - Builder builder(*module); - auto* cast = (*castp)->cast(); - if (cast->type.isNonNullableRef()) { - *castp = builder.makeCall(castImport, {cast->ref}, nnExt); - return; - } - // (if (ref.is_null (local.tee $temp (..))) - // (then (ref.null noextern)) - // (else (call $cast (local.get $temp))) - // ) - auto temp = Builder::addVar(func, nullExt); - *castp = builder.makeIf( - builder.makeRefIsNull(builder.makeLocalTee(temp, cast->ref, nullExt)), - builder.makeRefNull(HeapType::ext), - builder.makeCall( - castImport, {builder.makeLocalGet(temp, nullExt)}, nnExt)); - } - // Creates an imported string function, returning its name (which is equal to // the true name of the import, if there is no conflict). Name addImport(Module* module, Name trueName, Type params, Type results) { @@ -469,11 +411,6 @@ struct StringLowering : public StringGathering { // string.cast: externref -> string castImport = addImport(module, "cast", nullExt, nnExt); - // Lowering casts cannot be done in parallel since it may add locals - for (auto& [func, castPtr] : stringCasts) { - lowerStringCast(module, func, castPtr); - } - // Replace the string instructions in parallel. struct Replacer : public WalkerPass> { bool isFunctionParallel() override { return true; } @@ -486,28 +423,35 @@ struct StringLowering : public StringGathering { Replacer(StringLowering& lowering) : lowering(lowering) {} - // Returns the charCode if it is a code point with mask 0xFFFF in either - // order, or nullptr otherwise. - static Expression* getCharCode(Expression* curr) { - auto* binary = curr->dynCast(); - if (!binary || binary->op != AndInt32) { - return nullptr; + void visitRefCast(RefCast* curr) { + // Ignore non-string RefCast. + if (!curr->type.isRef() || + curr->type.getHeapType() != HeapType::string) { + return; } - auto isCharCodeMask = [](Expression* expr) { - if (auto* constExpr = expr->dynCast()) { - return constExpr->value.geti32() == 0xFFFF; - } - return false; - }; - - if (isCharCodeMask(binary->right)) { - return binary->left; - } - if (isCharCodeMask(binary->left)) { - return binary->right; + Builder builder(*getModule()); + if (curr->type.isNonNullableRef()) { + replaceCurrent( + builder.makeCall(lowering.castImport, {curr->ref}, lowering.nnExt)); + return; } - return nullptr; + + // Casts cannot be found outside of a function (e.g. a global + // initializer) since ref.cast is not a constant expression. + assert(getFunction()); + // (if (ref.is_null (local.tee $temp (..))) + // (then (ref.null noextern)) + // (else (call $cast (local.get $temp))) + // ) + auto temp = Builder::addVar(getFunction(), lowering.nullExt); + replaceCurrent(builder.makeIf( + builder.makeRefIsNull( + builder.makeLocalTee(temp, curr->ref, lowering.nullExt)), + builder.makeRefNull(HeapType::ext), + builder.makeCall(lowering.castImport, + {builder.makeLocalGet(temp, lowering.nullExt)}, + lowering.nnExt))); } void visitStringNew(StringNew* curr) { @@ -519,14 +463,13 @@ struct StringLowering : public StringGathering { lowering.nnExt)); return; case StringNewFromCodePoint: - if (auto* charCode = getCharCode(curr->ref)) { - replaceCurrent(builder.makeCall( - lowering.fromCharCodeImport, {charCode}, lowering.nnExt)); - return; - } replaceCurrent(builder.makeCall( lowering.fromCodePointImport, {curr->ref}, lowering.nnExt)); return; + case StringNewFromCharCode: + replaceCurrent(builder.makeCall( + lowering.fromCharCodeImport, {curr->ref}, lowering.nnExt)); + return; default: WASM_UNREACHABLE("TODO: all of string.new*"); } diff --git a/src/wasm-builder.h b/src/wasm-builder.h index fc8218f0a46..dc5c3fc4e14 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1294,7 +1294,8 @@ class Builder { Expression* ref, Expression* start = nullptr, Expression* end = nullptr) { - assert((start && end) != (op == StringNewFromCodePoint)); + assert((start && end) != + (op == StringNewFromCodePoint || op == StringNewFromCharCode)); auto* ret = wasm.allocator.alloc(); ret->op = op; ret->ref = ref; diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index a1a976c8c85..7dad5c78ceb 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2753,6 +2753,15 @@ class ExpressionRunner : public OverriddenVisitor { std::string str = wtf16.str(); return Literal(str); } + case StringNewFromCharCode: { + // Only output the last 16 bits, and unlike StringNewFromCodePoint, do + // not trap on invalid code points. + uint32_t charCode = ptr.getSingleValue().getUnsigned() & 0xFFFF; + std::stringstream wtf16; + String::writeWTF16CodePoint(wtf16, charCode); + std::string str = wtf16.str(); + return Literal(str); + } default: // TODO: others return Flow(NONCONSTANT_FLOW); diff --git a/src/wasm.h b/src/wasm.h index 657433874fd..94b034f124a 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -625,6 +625,7 @@ enum StringNewOp { StringNewLossyUTF8Array, StringNewWTF16Array, StringNewFromCodePoint, + StringNewFromCharCode, }; enum StringMeasureOp { diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 5c2be29006f..93d14f39332 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2805,7 +2805,7 @@ Result<> IRBuilder::makeRefAs(RefAsOp op) { Result<> IRBuilder::makeStringNew(StringNewOp op) { StringNew curr; curr.op = op; - if (op == StringNewFromCodePoint) { + if (op == StringNewFromCodePoint || op == StringNewFromCharCode) { CHECK_ERR(visitStringNew(&curr)); push(builder.makeStringNew(op, curr.ref)); return Ok{}; diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 9009a425c7d..48d52f1ec46 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2995,6 +2995,14 @@ void BinaryInstWriter::visitStringNew(StringNew* curr) { emitUnreachable(); return; } + if (curr->op == StringNewFromCharCode) { + // This is not part of the spec, so use a bitmask to get the expected output + // String.fromCharCode(0x10000) => '\x00' + // String.fromCodePoint(0x10000) => '𐀀' + // Hence, String.fromCharCode(x) == String.fromCodePoint(x & 0xFFFF) + o << int8_t(BinaryConsts::I32Const) << S32LEB(0xFFFF) + << int8_t(BinaryConsts::I32And); + } o << static_cast(BinaryConsts::GCPrefix); switch (curr->op) { case StringNewLossyUTF8Array: @@ -3004,6 +3012,7 @@ void BinaryInstWriter::visitStringNew(StringNew* curr) { o << U32LEB(BinaryConsts::StringNewWTF16Array); break; case StringNewFromCodePoint: + case StringNewFromCharCode: o << U32LEB(BinaryConsts::StringFromCodePoint); break; default: diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index a8255e931e8..1fde411c6ea 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -3247,8 +3247,15 @@ void FunctionValidator::visitRefTest(RefTest* curr) { } void FunctionValidator::visitRefCast(RefCast* curr) { - shouldBeTrue( - getModule()->features.hasGC(), curr, "ref.cast requires gc [--enable-gc]"); + // Allows RefCast to strings when at least strings are enabled and not GC. + // Otherwise, RefCast requires GC to be enabled. + bool hasStringCasts = + curr->type.isRef() && + curr->type.getHeapType().isMaybeShared(HeapType::string); + shouldBeTrue(getModule()->features.hasGC() || + (hasStringCasts && getModule()->features.hasStrings()), + curr, + "ref.cast requires gc [--enable-gc]"); // Require descriptors to be valid even if the ref is unreachable. if (curr->desc && curr->desc->type != Type::unreachable) { @@ -4368,6 +4375,17 @@ void FunctionValidator::visitStringNew(StringNew* curr) { shouldBeTrue( !curr->end, curr, "string.from_code_point should not have end"); return; + case StringNewFromCharCode: + shouldBeEqualOrFirstIsUnreachable( + curr->ref->type, + Type(Type::i32), + curr, + "string.from_char_code char code must be i32"); + shouldBeTrue( + !curr->start, curr, "string.from_char_code should not have start"); + shouldBeTrue( + !curr->end, curr, "string.from_char_code should not have end"); + return; } WASM_UNREACHABLE("unexpected op"); } diff --git a/test/lit/exec/strings.wast b/test/lit/exec/strings.wast index 4a149a587a3..0de5c8965a4 100644 --- a/test/lit/exec/strings.wast +++ b/test/lit/exec/strings.wast @@ -469,6 +469,30 @@ ) ) + ;; CHECK: [fuzz-exec] export from_char_code + ;; CHECK-NEXT: [fuzz-exec] note result: from_char_code => string("A") + (func $from_char_code (export "from_char_code") (result stringref) + (string.from_char_code + (i32.const 65) + ) + ) + + ;; CHECK: [fuzz-exec] export from_char_code.no_trap + ;; CHECK-NEXT: [fuzz-exec] note result: from_char_code.no_trap => string("\uffff") + (func $from_char_code.no_trap (export "from_char_code.no_trap") (result stringref) + (string.from_char_code + (i32.const -1) + ) + ) + + ;; CHECK: [fuzz-exec] export from_char_code.upper_bits + ;; CHECK-NEXT: [fuzz-exec] note result: from_char_code.upper_bits => string("A") + (func $from_char_code.upper_bits (export "from_char_code.upper_bits") (result stringref) + (string.from_char_code + (i32.const 0x10041) + ) + ) + ;; CHECK: [fuzz-exec] export string.measure ;; CHECK-NEXT: [fuzz-exec] note result: string.measure => 5 (func $string.measure (export "string.measure") (result i32) @@ -620,6 +644,15 @@ ;; CHECK: [fuzz-exec] export invalid_code_point ;; CHECK-NEXT: [trap invalid code point] +;; CHECK: [fuzz-exec] export from_char_code +;; CHECK-NEXT: [fuzz-exec] note result: from_char_code => string("A") + +;; CHECK: [fuzz-exec] export from_char_code.no_trap +;; CHECK-NEXT: [fuzz-exec] note result: from_char_code.no_trap => string("\uffff") + +;; CHECK: [fuzz-exec] export from_char_code.upper_bits +;; CHECK-NEXT: [fuzz-exec] note result: from_char_code.upper_bits => string("A") + ;; CHECK: [fuzz-exec] export string.measure ;; CHECK-NEXT: [fuzz-exec] note result: string.measure => 5 @@ -648,6 +681,9 @@ ;; CHECK-NEXT: [fuzz-exec] comparing eq.3 ;; CHECK-NEXT: [fuzz-exec] comparing eq.4 ;; CHECK-NEXT: [fuzz-exec] comparing eq.5 +;; CHECK-NEXT: [fuzz-exec] comparing from_char_code +;; CHECK-NEXT: [fuzz-exec] comparing from_char_code.no_trap +;; CHECK-NEXT: [fuzz-exec] comparing from_char_code.upper_bits ;; CHECK-NEXT: [fuzz-exec] comparing get_codeunit ;; CHECK-NEXT: [fuzz-exec] comparing invalid_code_point ;; CHECK-NEXT: [fuzz-exec] comparing isolated_high_code_point diff --git a/test/lit/passes/string-lifting.wast b/test/lit/passes/string-lifting.wast index 2d915502101..b67125f429d 100644 --- a/test/lit/passes/string-lifting.wast +++ b/test/lit/passes/string-lifting.wast @@ -36,7 +36,9 @@ ;; CHECK: (type $15 (func (param i32) (result externref))) - ;; CHECK: (type $16 (func (param externref))) + ;; CHECK: (type $16 (func (param i32 i32) (result externref))) + + ;; CHECK: (type $17 (func (param externref))) ;; CHECK: (import "\'" "foo" (global $string_foo (ref extern))) (import "\'" "foo" (global $string_foo (ref extern))) @@ -239,11 +241,8 @@ ) ;; CHECK: (func $string.from_char_code (type $15) (param $x i32) (result externref) - ;; CHECK-NEXT: (string.from_code_point - ;; CHECK-NEXT: (i32.and - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 65535) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (string.from_char_code + ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $string.from_char_code (param $x i32) (result externref) @@ -263,7 +262,40 @@ ) ) - ;; CHECK: (func $wrong (type $16) (param $ref externref) + ;; CHECK: (func $refinalize (type $16) (param $x i32) (param $c i32) (result externref) + ;; CHECK-NEXT: (if (result (ref string)) + ;; CHECK-NEXT: (local.get $c) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (string.from_char_code + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (string.from_char_code + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $refinalize (param $x i32) (param $c i32) (result externref) + ;; Asserts IR is refinalized when externrefs (in this case, non-nullable) + ;; are replaced with stringrefs. + (if (result (ref extern)) + (local.get $c) + (then + (call $fromCharCode + (local.get $x) + ) + ) + (else + (call $fromCharCode + (local.get $x) + ) + ) + ) + ) + + ;; CHECK: (func $wrong (type $17) (param $ref externref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $wrong-base ;; CHECK-NEXT: (local.get $ref) diff --git a/test/lit/passes/string-lowering-instructions.wast b/test/lit/passes/string-lowering-instructions.wast index afd0781629d..0d925886a8a 100644 --- a/test/lit/passes/string-lowering-instructions.wast +++ b/test/lit/passes/string-lowering-instructions.wast @@ -27,79 +27,71 @@ ;; CHECK: (type $7 (func (param (ref $array16)))) - ;; CHECK: (type $8 (func (param externref) (result i32))) + ;; CHECK: (type $8 (func (param anyref) (result (ref i31)))) - ;; CHECK: (type $9 (func (param anyref) (result (ref i31)))) + ;; CHECK: (type $9 (func (param externref (ref $array16)) (result i32))) - ;; CHECK: (type $10 (func (param externref externref) (result (ref extern)))) + ;; CHECK: (type $10 (func (param externref) (result externref))) - ;; CHECK: (type $11 (func (param externref (ref $array16)) (result i32))) + ;; CHECK: (type $11 (func (param i32) (result externref))) - ;; CHECK: (type $12 (func (param externref externref) (result i32))) + ;; CHECK: (type $12 (func (param externref))) - ;; CHECK: (type $13 (func (param externref) (result externref))) + ;; CHECK: (type $13 (func)) - ;; CHECK: (type $14 (func (param i32) (result externref))) + ;; CHECK: (type $14 (func (param externref) (result i32))) - ;; CHECK: (type $15 (func (param externref) (result (ref extern)))) + ;; CHECK: (type $15 (func (param externref externref) (result i32))) - ;; CHECK: (type $16 (func (param externref))) + ;; CHECK: (type $16 (func (param externref externref) (result (ref extern)))) - ;; CHECK: (type $17 (func)) + ;; CHECK: (type $17 (func (param externref) (result (ref extern)))) - ;; CHECK: (type $18 (func (param externref) (result i32))) + ;; CHECK: (type $18 (func (result externref))) - ;; CHECK: (type $19 (func (result externref))) + ;; CHECK: (type $19 (func (param i32) (result (ref extern)))) - ;; CHECK: (type $20 (func (param i32) (result (ref extern)))) + ;; CHECK: (type $20 (func (param externref i32 externref))) - ;; CHECK: (type $21 (func (param externref externref) (result i32))) + ;; CHECK: (type $21 (func (param (ref null $array16) i32 i32) (result (ref extern)))) - ;; CHECK: (type $22 (func (param externref i32 externref))) + ;; CHECK: (type $22 (func (param externref (ref null $array16) i32) (result i32))) - ;; CHECK: (type $23 (func (param (ref null $array16) i32 i32) (result (ref extern)))) + ;; CHECK: (type $23 (func (param externref i32) (result i32))) - ;; CHECK: (type $24 (func (param externref externref) (result (ref extern)))) - - ;; CHECK: (type $25 (func (param externref (ref null $array16) i32) (result i32))) - - ;; CHECK: (type $26 (func (param externref i32) (result i32))) - - ;; CHECK: (type $27 (func (param externref i32 i32) (result (ref extern)))) - - ;; CHECK: (type $28 (func (param externref) (result (ref extern)))) + ;; CHECK: (type $24 (func (param externref i32 i32) (result (ref extern)))) ;; CHECK: (import "string.const" "0" (global $"string.const_\"exported\"" (ref extern))) ;; CHECK: (import "string.const" "1" (global $"string.const_\"value\"" (ref extern))) - ;; CHECK: (import "colliding" "name" (func $fromCodePoint (type $17))) + ;; CHECK: (import "colliding" "name" (func $fromCodePoint (type $13))) (import "colliding" "name" (func $fromCodePoint)) - ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $23) (param (ref null $array16) i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $21) (param (ref null $array16) i32 i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint_27 (type $20) (param i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint_25 (type $19) (param i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $24) (param externref externref) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "concat" (func $concat (type $16) (param externref externref) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $25) (param externref (ref null $array16) i32) (result i32))) + ;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $22) (param externref (ref null $array16) i32) (result i32))) - ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $21) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $15) (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "test" (func $test (type $18) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "test" (func $test (type $14) (param externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $21) (param externref externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $15) (param externref externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "length" (func $length (type $18) (param externref) (result i32))) + ;; CHECK: (import "wasm:js-string" "length" (func $length (type $14) (param externref) (result i32))) - ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $26) (param externref i32) (result i32))) + ;; CHECK: (import "wasm:js-string" "charCodeAt" (func $charCodeAt (type $23) (param externref i32) (result i32))) - ;; CHECK: (import "wasm:js-string" "substring" (func $substring (type $27) (param externref i32 i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "substring" (func $substring (type $24) (param externref i32 i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "fromCharCode" (func $fromCharCode (type $20) (param i32) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "fromCharCode" (func $fromCharCode (type $19) (param i32) (result (ref extern)))) - ;; CHECK: (import "wasm:js-string" "cast" (func $cast (type $28) (param externref) (result (ref extern)))) + ;; CHECK: (import "wasm:js-string" "cast" (func $cast (type $17) (param externref) (result (ref extern)))) ;; CHECK: (global $string externref (ref.null noextern)) (global $string stringref (ref.null string)) ;; Test we update global nulls. @@ -127,8 +119,8 @@ ) ) - ;; CHECK: (func $string.from_code_point (type $19) (result externref) - ;; CHECK-NEXT: (call $fromCodePoint_27 + ;; CHECK: (func $string.from_code_point (type $18) (result externref) + ;; CHECK-NEXT: (call $fromCodePoint_25 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -138,7 +130,7 @@ ) ) - ;; CHECK: (func $string.concat (type $10) (param $0 externref) (param $1 externref) (result (ref extern)) + ;; CHECK: (func $string.concat (type $16) (param $0 externref) (param $1 externref) (result (ref extern)) ;; CHECK-NEXT: (call $concat ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (local.get $1) @@ -151,7 +143,7 @@ ) ) - ;; CHECK: (func $string.encode (type $11) (param $ref externref) (param $array16 (ref $array16)) (result i32) + ;; CHECK: (func $string.encode (type $9) (param $ref externref) (param $array16 (ref $array16)) (result i32) ;; CHECK-NEXT: (call $intoCharCodeArray ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (local.get $array16) @@ -166,7 +158,7 @@ ) ) - ;; CHECK: (func $string.eq (type $12) (param $a externref) (param $b externref) (result i32) + ;; CHECK: (func $string.eq (type $15) (param $a externref) (param $b externref) (result i32) ;; CHECK-NEXT: (call $equals ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: (local.get $b) @@ -179,7 +171,7 @@ ) ) - ;; CHECK: (func $string.compare (type $12) (param $a externref) (param $b externref) (result i32) + ;; CHECK: (func $string.compare (type $15) (param $a externref) (param $b externref) (result i32) ;; CHECK-NEXT: (call $compare ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: (local.get $b) @@ -192,7 +184,7 @@ ) ) - ;; CHECK: (func $string-test (type $8) (param $str externref) (result i32) + ;; CHECK: (func $string-test (type $14) (param $str externref) (result i32) ;; CHECK-NEXT: (call $test ;; CHECK-NEXT: (local.get $str) ;; CHECK-NEXT: ) @@ -203,7 +195,7 @@ ) ) - ;; CHECK: (func $string.length (type $8) (param $ref externref) (result i32) + ;; CHECK: (func $string.length (type $14) (param $ref externref) (result i32) ;; CHECK-NEXT: (call $length ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) @@ -214,7 +206,7 @@ ) ) - ;; CHECK: (func $string.get_codeunit (type $8) (param $ref externref) (result i32) + ;; CHECK: (func $string.get_codeunit (type $14) (param $ref externref) (result i32) ;; CHECK-NEXT: (call $charCodeAt ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (i32.const 2) @@ -227,7 +219,7 @@ ) ) - ;; CHECK: (func $string.slice (type $13) (param $ref externref) (result externref) + ;; CHECK: (func $string.slice (type $10) (param $ref externref) (result externref) ;; CHECK-NEXT: (call $substring ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (i32.const 2) @@ -242,54 +234,18 @@ ) ) - ;; CHECK: (func $string.from_char_code (type $14) (param $x i32) (result externref) + ;; CHECK: (func $string.from_char_code (type $11) (param $x i32) (result externref) ;; CHECK-NEXT: (call $fromCharCode ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $string.from_char_code (param $x i32) (result stringref) - ;; A code point masked to 16 bits is equivalent to a char code, so lower to fromCharCode. - (string.from_code_point - (i32.and - (local.get $x) - (i32.const 0xffff) - ) - ) - ) - - ;; CHECK: (func $string.from_char_code.flipped (type $14) (param $x i32) (result externref) - ;; CHECK-NEXT: (call $fromCharCode - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $string.from_char_code.flipped (param $x i32) (result stringref) - ;; As above, but with the order flipped. - (string.from_code_point - (i32.and - (i32.const 0xffff) - (local.get $x) - ) - ) - ) - - ;; CHECK: (func $string.from_code_point.other-mask (type $14) (param $x i32) (result externref) - ;; CHECK-NEXT: (call $fromCodePoint_27 - ;; CHECK-NEXT: (i32.and - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 255) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $string.from_code_point.other-mask (param $x i32) (result stringref) - (string.from_code_point - (i32.and - (local.get $x) - (i32.const 0xff) - ) + (string.from_char_code + (local.get $x) ) ) - ;; CHECK: (func $string.cast (type $15) (param $x externref) (result (ref extern)) + ;; CHECK: (func $string.cast (type $17) (param $x externref) (result (ref extern)) ;; CHECK-NEXT: (call $cast ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) @@ -300,7 +256,7 @@ ) ) - ;; CHECK: (func $string.cast.null (type $13) (param $x externref) (result externref) + ;; CHECK: (func $string.cast.null (type $10) (param $x externref) (result externref) ;; CHECK-NEXT: (local $1 externref) ;; CHECK-NEXT: (if (result externref) ;; CHECK-NEXT: (ref.is_null @@ -325,7 +281,7 @@ ) ) - ;; CHECK: (func $string.cast.nested (type $15) (param $x externref) (result (ref extern)) + ;; CHECK: (func $string.cast.nested (type $17) (param $x externref) (result (ref extern)) ;; CHECK-NEXT: (local $1 externref) ;; CHECK-NEXT: (call $cast ;; CHECK-NEXT: (if (result externref) @@ -353,7 +309,7 @@ ) ) - ;; CHECK: (func $string.cast.operand (type $10) (param $x externref) (param $y externref) (result (ref extern)) + ;; CHECK: (func $string.cast.operand (type $16) (param $x externref) (param $y externref) (result (ref extern)) ;; CHECK-NEXT: (call $concat ;; CHECK-NEXT: (call $cast ;; CHECK-NEXT: (local.get $x) @@ -371,19 +327,19 @@ ) ) - ;; CHECK: (func $string.cast.other (type $9) (param $x anyref) (result (ref i31)) + ;; CHECK: (func $string.cast.other (type $8) (param $x anyref) (result (ref i31)) ;; CHECK-NEXT: (ref.cast (ref i31) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $string.cast.other (param $x anyref) (result (ref i31)) - ;; Test that casts to non-string types are not lowered + ;; Test that casts to non-string types are not lowered. (ref.cast (ref i31) (local.get $x) ) ) - ;; CHECK: (func $if.string (type $13) (param $ref externref) (result externref) + ;; CHECK: (func $if.string (type $10) (param $ref externref) (result externref) ;; CHECK-NEXT: (if (result externref) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then @@ -407,7 +363,7 @@ ) ) - ;; CHECK: (func $if.string.flip (type $13) (param $ref externref) (result externref) + ;; CHECK: (func $if.string.flip (type $10) (param $ref externref) (result externref) ;; CHECK-NEXT: (if (result externref) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then @@ -431,7 +387,7 @@ ) ) - ;; CHECK: (func $exported-string-returner (type $19) (result externref) + ;; CHECK: (func $exported-string-returner (type $18) (result externref) ;; CHECK-NEXT: (global.get $"string.const_\"exported\"") ;; CHECK-NEXT: ) (func $exported-string-returner (export "export.1") (result stringref) @@ -440,7 +396,7 @@ (string.const "exported") ) - ;; CHECK: (func $exported-string-receiver (type $22) (param $x externref) (param $y i32) (param $z externref) + ;; CHECK: (func $exported-string-receiver (type $20) (param $x externref) (param $y i32) (param $z externref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) @@ -465,7 +421,7 @@ ) ) - ;; CHECK: (func $use-struct-of-array (type $17) + ;; CHECK: (func $use-struct-of-array (type $13) ;; CHECK-NEXT: (local $array16 (ref $array16)) ;; CHECK-NEXT: (local $open (ref $array16-open)) ;; CHECK-NEXT: (local $child (ref $array16-child)) @@ -521,7 +477,7 @@ ) ) - ;; CHECK: (func $struct-of-string (type $17) + ;; CHECK: (func $struct-of-string (type $13) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $struct-of-string ;; CHECK-NEXT: (ref.null noextern) @@ -564,7 +520,7 @@ ) ) - ;; CHECK: (func $call-param-null (type $16) (param $str externref) + ;; CHECK: (func $call-param-null (type $12) (param $str externref) ;; CHECK-NEXT: (call $call-param-null ;; CHECK-NEXT: (ref.null noextern) ;; CHECK-NEXT: ) From 0b0de1a231646524d12876597b9709d7d40076a3 Mon Sep 17 00:00:00 2001 From: Jeremy Nguyen Date: Tue, 10 Nov 2026 15:07:56 -0800 Subject: [PATCH 4/6] Update src/passes/StringLowering.cpp Co-authored-by: Thomas Lively --- src/passes/StringLowering.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/passes/StringLowering.cpp b/src/passes/StringLowering.cpp index 76238fe65dd..bc382fbfdf2 100644 --- a/src/passes/StringLowering.cpp +++ b/src/passes/StringLowering.cpp @@ -425,8 +425,7 @@ struct StringLowering : public StringGathering { void visitRefCast(RefCast* curr) { // Ignore non-string RefCast. - if (!curr->type.isRef() || - curr->type.getHeapType() != HeapType::string) { + if (!curr->type.isString()) { return; } From 2f4d873b2ef34634cdf9696c7928eac29ce4c400 Mon Sep 17 00:00:00 2001 From: Jeremy Nguyen Date: Tue, 10 Nov 2026 15:11:43 -0800 Subject: [PATCH 5/6] Update src/wasm/wasm-validator.cpp Co-authored-by: Thomas Lively --- src/wasm/wasm-validator.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 1fde411c6ea..bf9776b4d49 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -3249,11 +3249,9 @@ void FunctionValidator::visitRefTest(RefTest* curr) { void FunctionValidator::visitRefCast(RefCast* curr) { // Allows RefCast to strings when at least strings are enabled and not GC. // Otherwise, RefCast requires GC to be enabled. - bool hasStringCasts = - curr->type.isRef() && - curr->type.getHeapType().isMaybeShared(HeapType::string); + bool isStringCast = curr->type.isString(); shouldBeTrue(getModule()->features.hasGC() || - (hasStringCasts && getModule()->features.hasStrings()), + (isStringCast && getModule()->features.hasStrings()), curr, "ref.cast requires gc [--enable-gc]"); From 1103fc2243e00d1095301e807f1b2f62e3028d75 Mon Sep 17 00:00:00 2001 From: Jeremy Nguyen Date: Wed, 11 Nov 2026 00:53:31 -0800 Subject: [PATCH 6/6] [Strings] Add dedicated opcode for StringFromCharCode, allow unreachable or string bottom type to be cast without GC StringFromCharCode now has opcode 0xab. Support unreachable casts and casts to the string bottom type (noextern) without requiring GC. --- scripts/gen-s-parser.py | 1 + src/wasm-binary.h | 4 + src/wasm/wasm-binary.cpp | 2 + src/wasm/wasm-stack.cpp | 12 +-- src/wasm/wasm-validator.cpp | 4 +- .../string-lifting-lowering-roundtrip.wast | 2 + test/lit/passes/string-lifting-no-gc.wast | 43 ++++++++++ test/lit/validation/ref-cast-no-gc.wast | 35 ++++++++ .../lit/validation/ref-cast-string-no-gc.wast | 86 +++++++++++++++++++ .../ref-cast-string-no-strings.wast | 27 ++++++ 10 files changed, 206 insertions(+), 10 deletions(-) create mode 100644 test/lit/passes/string-lifting-no-gc.wast create mode 100644 test/lit/validation/ref-cast-no-gc.wast create mode 100644 test/lit/validation/ref-cast-string-no-gc.wast create mode 100644 test/lit/validation/ref-cast-string-no-strings.wast diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 3012c7dd7ab..39c56241fbf 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -686,6 +686,7 @@ ("string.new_lossy_utf8_array", "makeStringNew(StringNewLossyUTF8Array)"), ("string.new_wtf16_array", "makeStringNew(StringNewWTF16Array)"), ("string.from_code_point", "makeStringNew(StringNewFromCodePoint)"), + ("string.from_char_code", "makeStringNew(StringNewFromCharCode)"), ("string.const", "makeStringConst()"), ("string.measure_utf8", "makeStringMeasure(StringMeasureUTF8)"), ("string.measure_wtf16", "makeStringMeasure(StringMeasureWTF16)"), diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 470f58be2e7..93cc987723d 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1270,6 +1270,10 @@ enum ASTNodes { StringCompare = 0xa8, StringFromCodePoint = 0xa9, StringHash = 0xaa, + // Not in stringref specification. Corresponds to JS string builtin + // from_char_code. The stringref proposal is not active, so we don't need to + // worry about compatibility. + StringFromCharCode = 0xab, StringNewWTF16Array = 0xb1, StringEncodeWTF16Array = 0xb3, StringNewLossyUTF8Array = 0xb4, diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index aede8e67b0d..c407da9b46a 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -5149,6 +5149,8 @@ Result<> WasmBinaryReader::readInst() { return builder.makeStringNew(StringNewWTF16Array); case BinaryConsts::StringFromCodePoint: return builder.makeStringNew(StringNewFromCodePoint); + case BinaryConsts::StringFromCharCode: + return builder.makeStringNew(StringNewFromCharCode); case BinaryConsts::StringAsWTF16: // This turns into nothing because we do not represent stringviews in // the IR. diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 48d52f1ec46..5d198e53075 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2995,14 +2995,6 @@ void BinaryInstWriter::visitStringNew(StringNew* curr) { emitUnreachable(); return; } - if (curr->op == StringNewFromCharCode) { - // This is not part of the spec, so use a bitmask to get the expected output - // String.fromCharCode(0x10000) => '\x00' - // String.fromCodePoint(0x10000) => '𐀀' - // Hence, String.fromCharCode(x) == String.fromCodePoint(x & 0xFFFF) - o << int8_t(BinaryConsts::I32Const) << S32LEB(0xFFFF) - << int8_t(BinaryConsts::I32And); - } o << static_cast(BinaryConsts::GCPrefix); switch (curr->op) { case StringNewLossyUTF8Array: @@ -3012,9 +3004,11 @@ void BinaryInstWriter::visitStringNew(StringNew* curr) { o << U32LEB(BinaryConsts::StringNewWTF16Array); break; case StringNewFromCodePoint: - case StringNewFromCharCode: o << U32LEB(BinaryConsts::StringFromCodePoint); break; + case StringNewFromCharCode: + o << U32LEB(BinaryConsts::StringFromCharCode); + break; default: WASM_UNREACHABLE("invalid string.new*"); } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index bf9776b4d49..f05f2ec96b8 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -3249,7 +3249,9 @@ void FunctionValidator::visitRefTest(RefTest* curr) { void FunctionValidator::visitRefCast(RefCast* curr) { // Allows RefCast to strings when at least strings are enabled and not GC. // Otherwise, RefCast requires GC to be enabled. - bool isStringCast = curr->type.isString(); + bool isStringCast = (curr->type.isString()) || + (curr->type == Type::unreachable) || + (curr->type.getHeapType().isMaybeShared(HeapType::noext)); shouldBeTrue(getModule()->features.hasGC() || (isStringCast && getModule()->features.hasStrings()), curr, diff --git a/test/lit/passes/string-lifting-lowering-roundtrip.wast b/test/lit/passes/string-lifting-lowering-roundtrip.wast index d3c660fe87d..a3a95162930 100644 --- a/test/lit/passes/string-lifting-lowering-roundtrip.wast +++ b/test/lit/passes/string-lifting-lowering-roundtrip.wast @@ -1,6 +1,8 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. ;; RUN: wasm-opt %s -all --string-lifting --string-lowering -S -o - | filecheck %s +;; TODO: Lowering re-adds all string imports, leading to duplicate imports when roundtripping. + (module ;; CHECK: (type $0 (func (param i32) (result (ref extern)))) diff --git a/test/lit/passes/string-lifting-no-gc.wast b/test/lit/passes/string-lifting-no-gc.wast new file mode 100644 index 00000000000..4483ee742bb --- /dev/null +++ b/test/lit/passes/string-lifting-no-gc.wast @@ -0,0 +1,43 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: wasm-opt %s --enable-reference-types --string-lifting -S -o - | filecheck %s + +(module + ;; CHECK: (import "wasm:js-string" "cast" (func $cast (param externref) (result (ref extern)))) + (import "wasm:js-string" "cast" (func $cast (param externref) (result (ref extern)))) + + ;; CHECK: (func $cast_test (param $x externref) (result externref) + ;; CHECK-NEXT: (ref.cast (ref string) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast_test (param $x externref) (result externref) + (call $cast + (local.get $x) + ) + ) + + ;; CHECK: (func $cast_null (result externref) + ;; CHECK-NEXT: (ref.cast (ref noextern) + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast_null (result externref) + (call $cast + (ref.null noextern) + ) + ) + + ;; CHECK: (func $cast_unreachable (result externref) + ;; CHECK-NEXT: (block ;; (replaces unreachable RefCast we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast_unreachable (result externref) + (call $cast + (unreachable) + ) + ) +) diff --git a/test/lit/validation/ref-cast-no-gc.wast b/test/lit/validation/ref-cast-no-gc.wast new file mode 100644 index 00000000000..b713ac4aac9 --- /dev/null +++ b/test/lit/validation/ref-cast-no-gc.wast @@ -0,0 +1,35 @@ +;; Test that casts without GC is a validation error. + +;; RUN: not wasm-opt %s --enable-reference-types --enable-strings 2>&1 | filecheck %s + +;; CHECK: [wasm-validator error in function cast-func] unexpected false: ref.cast requires gc [--enable-gc] +;; CHECK: [wasm-validator error in function cast-nullfunc] unexpected false: ref.cast requires gc [--enable-gc] +;; CHECK: [wasm-validator error in function cast-extern] unexpected false: ref.cast requires gc [--enable-gc] +;; CHECK-NOT: ref.cast requires gc + +(module + (func $cast-func (param $x funcref) (result funcref) + (ref.cast funcref + (local.get $x) + ) + ) + + (func $cast-nullfunc (param $x funcref) (result nullfuncref) + (ref.cast nullfuncref + (local.get $x) + ) + ) + + (func $cast-extern (param $x externref) (result (ref extern)) + (ref.cast (ref extern) + (local.get $x) + ) + ) + + ;; GC is disabled; nullexternref == externref + (func $cast-nullexternref (param $x externref) (result nullexternref) + (ref.cast nullexternref + (local.get $x) + ) + ) +) diff --git a/test/lit/validation/ref-cast-string-no-gc.wast b/test/lit/validation/ref-cast-string-no-gc.wast new file mode 100644 index 00000000000..6ac2c16bff1 --- /dev/null +++ b/test/lit/validation/ref-cast-string-no-gc.wast @@ -0,0 +1,86 @@ +;; Test that string casts without GC are allowed. + +;; RUN: wasm-opt %s --enable-reference-types --enable-strings -S -o - | filecheck %s + +(module + ;; CHECK: (func $cast-from-nullable (param $x externref) (result stringref) + ;; CHECK-NEXT: (ref.cast stringref + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-from-nullable (param $x externref) (result stringref) + (ref.cast stringref + (local.get $x) + ) + ) + + ;; CHECK: (func $cast-from-non-nullable (param $x externref) (result (ref string)) + ;; CHECK-NEXT: (ref.cast (ref string) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-from-non-nullable (param $x externref) (result (ref string)) + (ref.cast (ref string) + (local.get $x) + ) + ) + + ;; CHECK: (func $cast-from-string (param $x stringref) (result (ref string)) + ;; CHECK-NEXT: (ref.cast (ref string) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-from-string (param $x stringref) (result (ref string)) + (ref.cast (ref string) + (local.get $x) + ) + ) + + ;; CHECK: (func $cast-from-null (result (ref noextern)) + ;; CHECK-NEXT: (ref.cast (ref noextern) + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-from-null (result (ref noextern)) + ;; A cast of a null is refinalized to have the bottom type. + (ref.cast (ref string) + (ref.null noextern) + ) + ) + + ;; CHECK: (func $cast-from-unreachable (result (ref string)) + ;; CHECK-NEXT: (block ;; (replaces unreachable RefCast we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-from-unreachable (result (ref string)) + (ref.cast (ref string) + (unreachable) + ) + ) + + ;; CHECK: (func $cast-to-nullexternref (param $x externref) (result nullexternref) + ;; CHECK-NEXT: (ref.cast nullexternref + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-nullexternref (param $x externref) (result nullexternref) + (ref.cast nullexternref + (local.get $x) + ) + ) + + ;; CHECK: (func $cast-to-non-nullable-noextern (param $x externref) (result (ref noextern)) + ;; CHECK-NEXT: (ref.cast (ref noextern) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-to-non-nullable-noextern (param $x externref) (result (ref noextern)) + (ref.cast (ref noextern) + (local.get $x) + ) + ) +) diff --git a/test/lit/validation/ref-cast-string-no-strings.wast b/test/lit/validation/ref-cast-string-no-strings.wast new file mode 100644 index 00000000000..7c235f9d485 --- /dev/null +++ b/test/lit/validation/ref-cast-string-no-strings.wast @@ -0,0 +1,27 @@ +;; Test that string casts without strings is a validation error. + +;; RUN: not wasm-opt %s --enable-reference-types --disable-strings --disable-gc 2>&1 | filecheck %s + +;; CHECK: [wasm-validator error in function cast] unexpected false: ref.cast requires gc [--enable-gc] +;; CHECK: [wasm-validator error in function cast-nullexternref] unexpected false: ref.cast requires gc [--enable-gc] +;; CHECK: [wasm-validator error in function cast-unreachable] unexpected false: ref.cast requires gc [--enable-gc] + +(module + (func $cast (param $x externref) (result stringref) + (ref.cast stringref + (local.get $x) + ) + ) + + (func $cast-nullexternref (param $x externref) (result nullexternref) + (ref.cast nullexternref + (local.get $x) + ) + ) + + (func $cast-unreachable (result stringref) + (ref.cast stringref + (unreachable) + ) + ) +)