Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/gen-s-parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)"),
Expand Down
21 changes: 16 additions & 5 deletions src/gen-s-parser.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand Down
1 change: 1 addition & 0 deletions src/ir/child-typer.h
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,7 @@ template<typename Subtype> struct ChildTyper : OverriddenVisitor<Subtype> {
return;
}
case StringNewFromCodePoint:
case StringNewFromCharCode:
note(&curr->ref, Type::i32);
return;
}
Expand Down
3 changes: 3 additions & 0 deletions src/ir/effects.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}
Expand Down
3 changes: 3 additions & 0 deletions src/passes/Print.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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*");
}
Expand Down
28 changes: 28 additions & 0 deletions src/passes/StringLifting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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';
}
Expand Down Expand Up @@ -276,6 +290,20 @@ struct StringLifting : public Pass {
.makeStringSliceWTF(curr->operands[0],
curr->operands[1],
curr->operands[2]));
} else if (curr->target == parent.fromCharCodeImport) {
replaceCurrent(
Builder(*getModule())
.makeStringNew(StringNewFromCharCode, curr->operands[0]));
} else if (curr->target == parent.castImport) {
Comment thread
tlively marked this conversation as resolved.
Resolved
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;
}
}

Expand Down
46 changes: 43 additions & 3 deletions src/passes/StringLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,15 +219,15 @@ struct StringLowering : public StringGathering {
// First, run the gathering operation so all string.consts are in one place.
StringGathering::run(module);

// Replace string.* etc. operations with imported ones.
replaceInstructions(module);

// Remove all HeapType::string etc. in favor of externref.
updateTypes(module);

// 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);

Expand Down Expand Up @@ -360,6 +360,8 @@ struct StringLowering : public StringGathering {
Name lengthImport;
Name charCodeAtImport;
Name substringImport;
Name fromCharCodeImport;
Name castImport;

// Creates an imported string function, returning its name (which is equal to
// the true name of the import, if there is no conflict).
Expand Down Expand Up @@ -404,6 +406,10 @@ 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);

// Replace the string instructions in parallel.
struct Replacer : public WalkerPass<PostWalker<Replacer>> {
Expand All @@ -417,6 +423,36 @@ struct StringLowering : public StringGathering {

Replacer(StringLowering& lowering) : lowering(lowering) {}

void visitRefCast(RefCast* curr) {
// Ignore non-string RefCast.
if (!curr->type.isString()) {
return;
}

Builder builder(*getModule());
if (curr->type.isNonNullableRef()) {
replaceCurrent(
builder.makeCall(lowering.castImport, {curr->ref}, lowering.nnExt));
return;
}

// 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) {
Builder builder(*getModule());
switch (curr->op) {
Expand All @@ -429,6 +465,10 @@ struct StringLowering : public StringGathering {
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*");
}
Expand Down
4 changes: 4 additions & 0 deletions src/wasm-binary.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion src/wasm-builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<StringNew>();
ret->op = op;
ret->ref = ref;
Expand Down
9 changes: 9 additions & 0 deletions src/wasm-interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -2753,6 +2753,15 @@ class ExpressionRunner : public OverriddenVisitor<SubType, Flow> {
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);
Expand Down
1 change: 1 addition & 0 deletions src/wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,7 @@ enum StringNewOp {
StringNewLossyUTF8Array,
StringNewWTF16Array,
StringNewFromCodePoint,
StringNewFromCharCode,
};

enum StringMeasureOp {
Expand Down
2 changes: 2 additions & 0 deletions src/wasm/wasm-binary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/wasm/wasm-ir-builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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{};
Expand Down
3 changes: 3 additions & 0 deletions src/wasm/wasm-stack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3006,6 +3006,9 @@ void BinaryInstWriter::visitStringNew(StringNew* curr) {
case StringNewFromCodePoint:
o << U32LEB(BinaryConsts::StringFromCodePoint);
break;
case StringNewFromCharCode:
o << U32LEB(BinaryConsts::StringFromCharCode);
break;
default:
WASM_UNREACHABLE("invalid string.new*");
}
Expand Down
22 changes: 20 additions & 2 deletions src/wasm/wasm-validator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 isStringCast = (curr->type.isString()) ||
(curr->type == Type::unreachable) ||
(curr->type.getHeapType().isMaybeShared(HeapType::noext));
shouldBeTrue(getModule()->features.hasGC() ||
(isStringCast && 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) {
Expand Down Expand Up @@ -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");
}
Expand Down
36 changes: 36 additions & 0 deletions test/lit/exec/strings.wast
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
Loading