summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Lively <7121787+tlively@users.noreply.github.com>2022-08-29 12:48:46 -0700
committerGitHub <noreply@github.com>2022-08-29 12:48:46 -0700
commitbd630d707253a9838a3d0306e4be680942ff0715 (patch)
treeb51a786d3afd3ee97a78eb0d3923fb6ad59565d5
parent8108ce28e66f1002932f6e5dc9dd4f23c8b8a9f3 (diff)
downloadbinaryen-bd630d707253a9838a3d0306e4be680942ff0715.tar.gz
binaryen-bd630d707253a9838a3d0306e4be680942ff0715.tar.bz2
binaryen-bd630d707253a9838a3d0306e4be680942ff0715.zip
Implement `extern.externalize` and `extern.internalize` (#4975)
These new GC instructions infallibly convert between `extern` and `any` references now that those types are not in the same hierarchy.
-rw-r--r--CHANGELOG.md1
-rwxr-xr-xscripts/fuzz_opt.py4
-rwxr-xr-xscripts/gen-s-parser.py2
-rw-r--r--src/gen-s-parser.inc58
-rw-r--r--src/ir/effects.h4
-rw-r--r--src/passes/Print.cpp6
-rw-r--r--src/wasm-binary.h2
-rw-r--r--src/wasm-interpreter.h10
-rw-r--r--src/wasm.h2
-rw-r--r--src/wasm/wasm-binary.cpp11
-rw-r--r--src/wasm/wasm-stack.cpp8
-rw-r--r--src/wasm/wasm-validator.cpp35
-rw-r--r--src/wasm/wasm.cpp6
-rw-r--r--test/lit/extern-conversions.wast33
14 files changed, 171 insertions, 11 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 049652cfe..7e005f6e8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -29,6 +29,7 @@ Current Trunk
- HeapType::ext has been restored but is no longer a subtype of HeapType::any to
match the latest updates in the GC spec. (#4898)
- `i31ref` and `dataref` are now nullable to match the latest GC spec. (#4843)
+- Add support for `extern.externalize` and `extern.internalize`. (#4975)
v109
----
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py
index 3ff1cb554..d287bee97 100755
--- a/scripts/fuzz_opt.py
+++ b/scripts/fuzz_opt.py
@@ -270,8 +270,10 @@ def init_important_initial_contents():
INITIAL_CONTENTS_IGNORE = [
# not all relaxed SIMD instructions are implemented in the interpreter
'relaxed-simd.wast',
- # TODO fuzzer and interpreter support for strings
+ # TODO: fuzzer and interpreter support for strings
'strings.wast',
+ # TODO: fuzzer and interpreter support for extern conversions
+ 'extern-conversions.wast',
# ignore DWARF because it is incompatible with multivalue atm
'zlib.wasm',
'cubescript.wasm',
diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py
index bb56b76e3..93fc676e4 100755
--- a/scripts/gen-s-parser.py
+++ b/scripts/gen-s-parser.py
@@ -604,6 +604,8 @@ instructions = [
("ref.as_func", "makeRefAs(s, RefAsFunc)"),
("ref.as_data", "makeRefAs(s, RefAsData)"),
("ref.as_i31", "makeRefAs(s, RefAsI31)"),
+ ("extern.internalize", "makeRefAs(s, ExternInternalize)"),
+ ("extern.externalize", "makeRefAs(s, ExternExternalize)"),
("string.new_wtf8", "makeStringNew(s, StringNewWTF8)"),
("string.new_wtf16", "makeStringNew(s, StringNewWTF16)"),
("string.new_wtf8_array", "makeStringNew(s, StringNewWTF8Array)"),
diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc
index caf4f5578..4803f60b5 100644
--- a/src/gen-s-parser.inc
+++ b/src/gen-s-parser.inc
@@ -185,9 +185,25 @@ switch (op[0]) {
default: goto parse_error;
}
}
- case 'e':
- if (strcmp(op, "else") == 0) { return makeThenOrElse(s); }
- goto parse_error;
+ case 'e': {
+ switch (op[1]) {
+ case 'l':
+ if (strcmp(op, "else") == 0) { return makeThenOrElse(s); }
+ goto parse_error;
+ case 'x': {
+ switch (op[7]) {
+ case 'e':
+ if (strcmp(op, "extern.externalize") == 0) { return makeRefAs(s, ExternExternalize); }
+ goto parse_error;
+ case 'i':
+ if (strcmp(op, "extern.internalize") == 0) { return makeRefAs(s, ExternInternalize); }
+ goto parse_error;
+ default: goto parse_error;
+ }
+ }
+ default: goto parse_error;
+ }
+ }
case 'f': {
switch (op[1]) {
case '3': {
@@ -3816,13 +3832,37 @@ switch (op[0]) {
default: goto parse_error;
}
}
- case 'e':
- if (op == "else"sv) {
- auto ret = makeThenOrElse(ctx, in);
- CHECK_ERR(ret);
- return *ret;
+ case 'e': {
+ switch (op[1]) {
+ case 'l':
+ if (op == "else"sv) {
+ auto ret = makeThenOrElse(ctx, in);
+ CHECK_ERR(ret);
+ return *ret;
+ }
+ goto parse_error;
+ case 'x': {
+ switch (op[7]) {
+ case 'e':
+ if (op == "extern.externalize"sv) {
+ auto ret = makeRefAs(ctx, in, ExternExternalize);
+ CHECK_ERR(ret);
+ return *ret;
+ }
+ goto parse_error;
+ case 'i':
+ if (op == "extern.internalize"sv) {
+ auto ret = makeRefAs(ctx, in, ExternInternalize);
+ CHECK_ERR(ret);
+ return *ret;
+ }
+ goto parse_error;
+ default: goto parse_error;
+ }
+ }
+ default: goto parse_error;
}
- goto parse_error;
+ }
case 'f': {
switch (op[1]) {
case '3': {
diff --git a/src/ir/effects.h b/src/ir/effects.h
index 5e50441ac..f5fdc5184 100644
--- a/src/ir/effects.h
+++ b/src/ir/effects.h
@@ -726,6 +726,10 @@ private:
parent.implicitTrap = true;
}
void visitRefAs(RefAs* curr) {
+ if (curr->op == ExternInternalize || curr->op == ExternExternalize) {
+ // These conversions are infallible.
+ return;
+ }
// traps when the arg is not valid
parent.implicitTrap = true;
// Note: We could be more precise here and report the lack of a possible
diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp
index 5fb17af76..dc247c737 100644
--- a/src/passes/Print.cpp
+++ b/src/passes/Print.cpp
@@ -2214,6 +2214,12 @@ struct PrintExpressionContents
case RefAsI31:
printMedium(o, "ref.as_i31");
break;
+ case ExternInternalize:
+ printMedium(o, "extern.internalize");
+ break;
+ case ExternExternalize:
+ printMedium(o, "extern.externalize");
+ break;
default:
WASM_UNREACHABLE("invalid ref.is_*");
}
diff --git a/src/wasm-binary.h b/src/wasm-binary.h
index e150915a0..562196580 100644
--- a/src/wasm-binary.h
+++ b/src/wasm-binary.h
@@ -1125,6 +1125,8 @@ enum ASTNodes {
BrOnNonFunc = 0x63,
BrOnNonData = 0x64,
BrOnNonI31 = 0x65,
+ ExternInternalize = 0x70,
+ ExternExternalize = 0x71,
StringNewWTF8 = 0x80,
StringNewWTF16 = 0x81,
StringConst = 0x82,
diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h
index fec560e35..56336b237 100644
--- a/src/wasm-interpreter.h
+++ b/src/wasm-interpreter.h
@@ -1874,6 +1874,9 @@ public:
trap("not an i31");
}
break;
+ case ExternInternalize:
+ case ExternExternalize:
+ WASM_UNREACHABLE("unimplemented extern conversion");
default:
WASM_UNREACHABLE("unimplemented ref.as_*");
}
@@ -2226,6 +2229,13 @@ public:
Flow visitStringSliceIter(StringSliceIter* curr) {
return Flow(NONCONSTANT_FLOW);
}
+ Flow visitRefAs(RefAs* curr) {
+ // TODO: Remove this once interpretation is implemented.
+ if (curr->op == ExternInternalize || curr->op == ExternExternalize) {
+ return Flow(NONCONSTANT_FLOW);
+ }
+ return ExpressionRunner<SubType>::visitRefAs(curr);
+ }
void trap(const char* why) override { throw NonconstantException(); }
diff --git a/src/wasm.h b/src/wasm.h
index 62a63e493..7b895a0cf 100644
--- a/src/wasm.h
+++ b/src/wasm.h
@@ -568,6 +568,8 @@ enum RefAsOp {
RefAsFunc,
RefAsData,
RefAsI31,
+ ExternInternalize,
+ ExternExternalize,
};
enum BrOnOp {
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index df99fabb9..d449d4bdb 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -3978,7 +3978,9 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
}
if (opcode == BinaryConsts::RefAsFunc ||
opcode == BinaryConsts::RefAsData ||
- opcode == BinaryConsts::RefAsI31) {
+ opcode == BinaryConsts::RefAsI31 ||
+ opcode == BinaryConsts::ExternInternalize ||
+ opcode == BinaryConsts::ExternExternalize) {
visitRefAs((curr = allocator.alloc<RefAs>())->cast<RefAs>(), opcode);
break;
}
@@ -7347,6 +7349,12 @@ void WasmBinaryBuilder::visitRefAs(RefAs* curr, uint8_t code) {
case BinaryConsts::RefAsI31:
curr->op = RefAsI31;
break;
+ case BinaryConsts::ExternInternalize:
+ curr->op = ExternInternalize;
+ break;
+ case BinaryConsts::ExternExternalize:
+ curr->op = ExternExternalize;
+ break;
default:
WASM_UNREACHABLE("invalid code for ref.as_*");
}
@@ -7356,6 +7364,7 @@ void WasmBinaryBuilder::visitRefAs(RefAs* curr, uint8_t code) {
}
curr->finalize();
}
+
void WasmBinaryBuilder::throwError(std::string text) {
throw ParseException(text, 0, pos);
}
diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp
index 13bd09927..fddb7e41c 100644
--- a/src/wasm/wasm-stack.cpp
+++ b/src/wasm/wasm-stack.cpp
@@ -2170,6 +2170,14 @@ void BinaryInstWriter::visitRefAs(RefAs* curr) {
case RefAsI31:
o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::RefAsI31);
break;
+ case ExternInternalize:
+ o << int8_t(BinaryConsts::GCPrefix)
+ << U32LEB(BinaryConsts::ExternInternalize);
+ break;
+ case ExternExternalize:
+ o << int8_t(BinaryConsts::GCPrefix)
+ << U32LEB(BinaryConsts::ExternExternalize);
+ break;
default:
WASM_UNREACHABLE("invalid ref.as_*");
}
diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp
index 532529d2a..75cbcf53a 100644
--- a/src/wasm/wasm-validator.cpp
+++ b/src/wasm/wasm-validator.cpp
@@ -424,6 +424,7 @@ public:
void visitMemoryGrow(MemoryGrow* curr);
void visitRefNull(RefNull* curr);
void visitRefIs(RefIs* curr);
+ void visitRefAs(RefAs* curr);
void visitRefFunc(RefFunc* curr);
void visitRefEq(RefEq* curr);
void visitTableGet(TableGet* curr);
@@ -2125,6 +2126,40 @@ void FunctionValidator::visitRefIs(RefIs* curr) {
"ref.is_*'s argument should be a reference type");
}
+void FunctionValidator::visitRefAs(RefAs* curr) {
+ switch (curr->op) {
+ default:
+ // TODO: validate all the other ref.as_*
+ break;
+ case ExternInternalize: {
+ shouldBeTrue(getModule()->features.hasGC(),
+ curr,
+ "extern.internalize requries GC to be enabled");
+ if (curr->type == Type::unreachable) {
+ return;
+ }
+ shouldBeSubType(curr->value->type,
+ Type(HeapType::ext, Nullable),
+ curr->value,
+ "extern.internalize value should be an externref");
+ break;
+ }
+ case ExternExternalize: {
+ shouldBeTrue(getModule()->features.hasGC(),
+ curr,
+ "extern.externalize requries GC to be enabled");
+ if (curr->type == Type::unreachable) {
+ return;
+ }
+ shouldBeSubType(curr->value->type,
+ Type(HeapType::any, Nullable),
+ curr->value,
+ "extern.externalize value should be an anyref");
+ break;
+ }
+ }
+}
+
void FunctionValidator::visitRefFunc(RefFunc* curr) {
// If we are not in a function, this is a global location like a table. We
// allow RefFunc there as we represent tables that way regardless of what
diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp
index 8226e9079..56db8d7a9 100644
--- a/src/wasm/wasm.cpp
+++ b/src/wasm/wasm.cpp
@@ -1122,6 +1122,12 @@ void RefAs::finalize() {
case RefAsI31:
type = Type(HeapType::i31, NonNullable);
break;
+ case ExternInternalize:
+ type = Type(HeapType::any, value->type.getNullability());
+ break;
+ case ExternExternalize:
+ type = Type(HeapType::ext, value->type.getNullability());
+ break;
default:
WASM_UNREACHABLE("invalid ref.as_*");
}
diff --git a/test/lit/extern-conversions.wast b/test/lit/extern-conversions.wast
new file mode 100644
index 000000000..403b558f6
--- /dev/null
+++ b/test/lit/extern-conversions.wast
@@ -0,0 +1,33 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; Check that extern conversion instructions are emitted properly in the binary format.
+
+;; RUN: wasm-opt %s -all --roundtrip -S -o - | filecheck %s
+
+(module
+ ;; CHECK: (type $ref|any|_=>_ref|extern| (func (param (ref any)) (result (ref extern))))
+
+ ;; CHECK: (type $externref_=>_anyref (func (param externref) (result anyref)))
+
+ ;; CHECK: (func $extern.externalize (param $x (ref any)) (result (ref extern))
+ ;; CHECK-NEXT: (extern.externalize
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $extern.externalize (param $x (ref any)) (result (ref extern))
+ (extern.externalize
+ (local.get $x)
+ )
+ )
+
+ ;; CHECK: (func $extern.internalize (param $x externref) (result anyref)
+ ;; CHECK-NEXT: (extern.internalize
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $extern.internalize (param $x (ref null extern)) (result (ref null any))
+ (extern.internalize
+ (local.get $x)
+ )
+ )
+)