Add ref.cast_nop_static (#4656)

This unsafe experimental instruction is semantically equivalent to
ref.cast_static, but V8 will unsafely turn it into a nop. This is meant to help
us measure cast overhead more precisely than we can by globally turning all
casts into nops.
diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py
index 29ce838..5576960 100755
--- a/scripts/gen-s-parser.py
+++ b/scripts/gen-s-parser.py
@@ -573,6 +573,7 @@
     ("ref.test_static",      "makeRefTestStatic(s)"),
     ("ref.cast",             "makeRefCast(s)"),
     ("ref.cast_static",      "makeRefCastStatic(s)"),
+    ("ref.cast_nop_static",  "makeRefCastNopStatic(s)"),
     ("br_on_null",           "makeBrOn(s, BrOnNull)"),
     ("br_on_non_null",       "makeBrOn(s, BrOnNonNull)"),
     ("br_on_cast",           "makeBrOn(s, BrOnCast)"),
diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc
index d9505bc..4111969 100644
--- a/src/gen-s-parser.inc
+++ b/src/gen-s-parser.inc
@@ -3027,9 +3027,17 @@
                   case '\0':
                     if (strcmp(op, "ref.cast") == 0) { return makeRefCast(s); }
                     goto parse_error;
-                  case '_':
-                    if (strcmp(op, "ref.cast_static") == 0) { return makeRefCastStatic(s); }
-                    goto parse_error;
+                  case '_': {
+                    switch (op[9]) {
+                      case 'n':
+                        if (strcmp(op, "ref.cast_nop_static") == 0) { return makeRefCastNopStatic(s); }
+                        goto parse_error;
+                      case 's':
+                        if (strcmp(op, "ref.cast_static") == 0) { return makeRefCastStatic(s); }
+                        goto parse_error;
+                      default: goto parse_error;
+                    }
+                  }
                   default: goto parse_error;
                 }
               }
diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp
index 9a4235c..ce70f50 100644
--- a/src/passes/Print.cpp
+++ b/src/passes/Print.cpp
@@ -1984,7 +1984,11 @@
     if (curr->rtt) {
       printMedium(o, "ref.cast");
     } else {
-      printMedium(o, "ref.cast_static ");
+      if (curr->safety == RefCast::Unsafe) {
+        printMedium(o, "ref.cast_nop_static ");
+      } else {
+        printMedium(o, "ref.cast_static ");
+      }
       printHeapType(o, curr->intendedType, wasm);
     }
   }
diff --git a/src/wasm-binary.h b/src/wasm-binary.h
index 12deeac..58dcd22 100644
--- a/src/wasm-binary.h
+++ b/src/wasm-binary.h
@@ -1112,6 +1112,7 @@
   RefCastStatic = 0x45,
   BrOnCastStatic = 0x46,
   BrOnCastStaticFail = 0x47,
+  RefCastNopStatic = 0x48,
   RefIsFunc = 0x50,
   RefIsData = 0x51,
   RefIsI31 = 0x52,
diff --git a/src/wasm-builder.h b/src/wasm-builder.h
index de26886..86de1f7 100644
--- a/src/wasm-builder.h
+++ b/src/wasm-builder.h
@@ -810,10 +810,13 @@
     ret->finalize();
     return ret;
   }
-  RefCast* makeRefCast(Expression* ref, HeapType intendedType) {
+
+  RefCast*
+  makeRefCast(Expression* ref, HeapType intendedType, RefCast::Safety safety) {
     auto* ret = wasm.allocator.alloc<RefCast>();
     ret->ref = ref;
     ret->intendedType = intendedType;
+    ret->safety = safety;
     ret->finalize();
     return ret;
   }
diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h
index d6f3b2f..61383f0 100644
--- a/src/wasm-s-parser.h
+++ b/src/wasm-s-parser.h
@@ -282,6 +282,7 @@
   Expression* makeRefTestStatic(Element& s);
   Expression* makeRefCast(Element& s);
   Expression* makeRefCastStatic(Element& s);
+  Expression* makeRefCastNopStatic(Element& s);
   Expression* makeBrOn(Element& s, BrOnOp op);
   Expression* makeBrOnStatic(Element& s, BrOnOp op);
   Expression* makeRttCanon(Element& s);
diff --git a/src/wasm.h b/src/wasm.h
index 3b736fc..b8e3d67 100644
--- a/src/wasm.h
+++ b/src/wasm.h
@@ -1457,6 +1457,11 @@
   Expression* rtt = nullptr;
   HeapType intendedType;
 
+  // Support the unsafe `ref.cast_nop_static` to enable precise cast overhead
+  // measurements.
+  enum Safety { Safe, Unsafe };
+  Safety safety;
+
   void finalize();
 
   // Returns the type we intend to cast to.
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index 60a4bad..9cd7e79 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -6712,10 +6712,13 @@
     auto* ref = popNonVoidExpression();
     out = Builder(wasm).makeRefCast(ref, rtt);
     return true;
-  } else if (code == BinaryConsts::RefCastStatic) {
+  } else if (code == BinaryConsts::RefCastStatic ||
+             code == BinaryConsts::RefCastNopStatic) {
     auto intendedType = getIndexedHeapType();
     auto* ref = popNonVoidExpression();
-    out = Builder(wasm).makeRefCast(ref, intendedType);
+    auto safety =
+      code == BinaryConsts::RefCastNopStatic ? RefCast::Unsafe : RefCast::Safe;
+    out = Builder(wasm).makeRefCast(ref, intendedType, safety);
     return true;
   }
   return false;
diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp
index 02da4f9..8680ff8 100644
--- a/src/wasm/wasm-s-parser.cpp
+++ b/src/wasm/wasm-s-parser.cpp
@@ -2702,7 +2702,13 @@
 Expression* SExpressionWasmBuilder::makeRefCastStatic(Element& s) {
   auto heapType = parseHeapType(*s[1]);
   auto* ref = parseExpression(*s[2]);
-  return Builder(wasm).makeRefCast(ref, heapType);
+  return Builder(wasm).makeRefCast(ref, heapType, RefCast::Safe);
+}
+
+Expression* SExpressionWasmBuilder::makeRefCastNopStatic(Element& s) {
+  auto heapType = parseHeapType(*s[1]);
+  auto* ref = parseExpression(*s[2]);
+  return Builder(wasm).makeRefCast(ref, heapType, RefCast::Unsafe);
 }
 
 Expression* SExpressionWasmBuilder::makeBrOn(Element& s, BrOnOp op) {
diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp
index 8afeb67..8588a04 100644
--- a/src/wasm/wasm-stack.cpp
+++ b/src/wasm/wasm-stack.cpp
@@ -2051,7 +2051,11 @@
   if (curr->rtt) {
     o << U32LEB(BinaryConsts::RefCast);
   } else {
-    o << U32LEB(BinaryConsts::RefCastStatic);
+    if (curr->safety == RefCast::Unsafe) {
+      o << U32LEB(BinaryConsts::RefCastNopStatic);
+    } else {
+      o << U32LEB(BinaryConsts::RefCastStatic);
+    }
     parent.writeIndexedHeapType(curr->intendedType);
   }
 }
diff --git a/test/lit/ref-cast-nop.wast b/test/lit/ref-cast-nop.wast
new file mode 100644
index 0000000..6e49508
--- /dev/null
+++ b/test/lit/ref-cast-nop.wast
@@ -0,0 +1,18 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+
+;; RUN: wasm-opt -all %s -S --roundtrip -o - | filecheck %s
+
+(module
+ ;; CHECK:      (type $struct (struct (field i32)))
+ (type $struct (struct i32))
+ ;; CHECK:      (func $ref.cast_nop_static (param $x (ref any)) (result (ref $struct))
+ ;; CHECK-NEXT:  (ref.cast_nop_static $struct
+ ;; CHECK-NEXT:   (local.get $x)
+ ;; CHECK-NEXT:  )
+ ;; CHECK-NEXT: )
+ (func $ref.cast_nop_static (param $x (ref any)) (result (ref $struct))
+  (ref.cast_nop_static $struct
+   (local.get $x)
+  )
+ )
+)