Bug 1510216 - Add WasmAnyRef type to the TypedObject system. r=luke
authorLars T Hansen <lhansen@mozilla.com>
Wed, 28 Nov 2018 12:47:19 +0100
changeset 451669 3e8268f13176bda200ed81decb06575aec6e0c04
parent 451668 c42679c7a94dc37f3c0284651847cc98673f1ab4
child 451670 0f7da6819c47553e217338f1b7967a60b976b873
push id35250
push userrgurzau@mozilla.com
push dateFri, 21 Dec 2018 16:36:26 +0000
treeherdermozilla-central@38dac70dcec6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1510216
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1510216 - Add WasmAnyRef type to the TypedObject system. r=luke We add support for the wasm "anyref" type to the TypedObject system, so that TypedObjects can represent wasm objects faithfully and we can get proper boxing/unboxing when JS writes and reads these properties. The new type is a reference type named "WasmAnyRef" / TYPE_WASM_ANYREF internally, and it also appears as TypedObject.WasmAnyRef in JS. Accesses to AnyRef fields are not optimized by the JIT at the moment, but call into intrinsic functions in the wasm subsystem for sundry predicates and boxing and unboxing. More can be done here. Currently the code knows that an anyref in wasm is a possibly-null JSObject* representing either an Object or a boxed value. More complexity will appear when we box more values in the pointer. There are "TODO/AnyRef-boxing" comments left in the code to mark places that must definitely be adapted.
js/src/builtin/TypedObject.cpp
js/src/builtin/TypedObject.h
js/src/builtin/TypedObject.js
js/src/builtin/TypedObjectConstants.h
js/src/jit-test/tests/wasm/gc/anyref-boxing-struct.js
js/src/jit/BaselineIC.cpp
js/src/jit/CacheIR.cpp
js/src/jit/CacheIRCompiler.cpp
js/src/jit/IonBuilder.cpp
js/src/vm/CommonPropertyNames.h
js/src/vm/SelfHosting.cpp
js/src/wasm/WasmModule.cpp
js/src/wasm/WasmTypes.cpp
js/src/wasm/WasmValidate.cpp
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -287,16 +287,19 @@ bool ScalarTypeDescr::call(JSContext* cx
 
 /* static */ TypeDescr* GlobalObject::getOrCreateReferenceTypeDescr(
     JSContext* cx, Handle<GlobalObject*> global, ReferenceType type) {
   int32_t slot = 0;
   switch (type) {
     case ReferenceType::TYPE_OBJECT:
       slot = TypedObjectModuleObject::ObjectDesc;
       break;
+    case ReferenceType::TYPE_WASM_ANYREF:
+      slot = TypedObjectModuleObject::WasmAnyRefDesc;
+      break;
     default:
       MOZ_CRASH("NYI");
   }
 
   Rooted<TypedObjectModuleObject*> module(
       cx, &GlobalObject::getOrCreateTypedObjectModule(cx, global)
                ->as<TypedObjectModuleObject>());
   if (!module) {
@@ -371,16 +374,21 @@ bool js::ReferenceTypeDescr::call(JSCont
     return false;
   }
 
   switch (descr->type()) {
     case ReferenceType::TYPE_ANY:
       args.rval().set(args[0]);
       return true;
 
+    case ReferenceType::TYPE_WASM_ANYREF:
+      // As a cast in JS, anyref is an identity operation.
+      args.rval().set(args[0]);
+      return true;
+
     case ReferenceType::TYPE_OBJECT: {
       RootedObject obj(cx, ToObject(cx, args[0]));
       if (!obj) {
         return false;
       }
       args.rval().setObject(*obj);
       return true;
     }
@@ -1361,16 +1369,21 @@ static JSObject* DefineMetaTypeDescr(JSC
   }
   module->initReservedSlot(TypedObjectModuleObject::Float64Desc, typeDescr);
 
   if (!JS_GetProperty(cx, module, "Object", &typeDescr)) {
     return false;
   }
   module->initReservedSlot(TypedObjectModuleObject::ObjectDesc, typeDescr);
 
+  if (!JS_GetProperty(cx, module, "WasmAnyRef", &typeDescr)) {
+    return false;
+  }
+  module->initReservedSlot(TypedObjectModuleObject::WasmAnyRefDesc, typeDescr);
+
   // ArrayType.
 
   RootedObject arrayType(cx);
   arrayType = DefineMetaTypeDescr<ArrayMetaTypeDescr>(
       cx, "ArrayType", global, module,
       TypedObjectModuleObject::ArrayTypePrototype);
   if (!arrayType) {
     return false;
@@ -2675,16 +2688,34 @@ bool StoreReferenceObject::store(JSConte
       return false;
     }
   }
 
   *heap = v.toObjectOrNull();
   return true;
 }
 
+bool StoreReferenceWasmAnyRef::store(JSContext* cx, GCPtrObject* heap,
+                                     const Value& v, TypedObject* obj,
+                                     jsid id) {
+  // At the moment, we allow:
+  // - null
+  // - a WasmValueBox object (a NativeObject subtype)
+  // - any other JSObject* that JS can talk about
+  //
+  // TODO/AnyRef-boxing: With boxed immediates and strings this will change.
+
+  MOZ_ASSERT(v.isObjectOrNull());
+
+  // We do not add any type information for anyref at this time.
+
+  *heap = v.toObjectOrNull();
+  return true;
+}
+
 bool StoreReferencestring::store(JSContext* cx, GCPtrString* heap,
                                  const Value& v, TypedObject* obj, jsid id) {
   MOZ_ASSERT(v.isString());  // or else Store_string is being misused
 
   // Note: string references are not reflected in type information for the
   // object.
   *heap = v.toString();
 
@@ -2698,16 +2729,26 @@ void LoadReferenceAny::load(GCPtrValue* 
 void LoadReferenceObject::load(GCPtrObject* heap, MutableHandleValue v) {
   if (*heap) {
     v.setObject(**heap);
   } else {
     v.setNull();
   }
 }
 
+void LoadReferenceWasmAnyRef::load(GCPtrObject* heap, MutableHandleValue v) {
+  // TODO/AnyRef-boxing: With boxed immediates and strings this will change.
+
+  if (*heap) {
+    v.setObject(**heap);
+  } else {
+    v.setNull();
+  }
+}
+
 void LoadReferencestring::load(GCPtrString* heap, MutableHandleValue v) {
   v.setString(*heap);
 }
 
 // I was using templates for this stuff instead of macros, but ran
 // into problems with the Unagi compiler.
 JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(JS_STORE_SCALAR_CLASS_IMPL)
 JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(JS_LOAD_SCALAR_CLASS_IMPL)
@@ -2775,16 +2816,17 @@ void MemoryInitVisitor::visitReference(R
                                        uint8_t* mem) {
   switch (descr.type()) {
     case ReferenceType::TYPE_ANY: {
       js::GCPtrValue* heapValue = reinterpret_cast<js::GCPtrValue*>(mem);
       heapValue->init(UndefinedValue());
       return;
     }
 
+    case ReferenceType::TYPE_WASM_ANYREF:
     case ReferenceType::TYPE_OBJECT: {
       js::GCPtrObject* objectPtr = reinterpret_cast<js::GCPtrObject*>(mem);
       objectPtr->init(nullptr);
       return;
     }
 
     case ReferenceType::TYPE_STRING: {
       js::GCPtrString* stringPtr = reinterpret_cast<js::GCPtrString*>(mem);
@@ -2836,16 +2878,19 @@ void MemoryTracingVisitor::visitReferenc
                                           uint8_t* mem) {
   switch (descr.type()) {
     case ReferenceType::TYPE_ANY: {
       GCPtrValue* heapValue = reinterpret_cast<js::GCPtrValue*>(mem);
       TraceEdge(trace_, heapValue, "reference-val");
       return;
     }
 
+    case ReferenceType::TYPE_WASM_ANYREF:
+      // TODO/AnyRef-boxing: With boxed immediates and strings the tracing code
+      // will be more complicated.  For now, tracing as an object is fine.
     case ReferenceType::TYPE_OBJECT: {
       GCPtrObject* objectPtr = reinterpret_cast<js::GCPtrObject*>(mem);
       TraceNullableEdge(trace_, objectPtr, "reference-obj");
       return;
     }
 
     case ReferenceType::TYPE_STRING: {
       GCPtrString* stringPtr = reinterpret_cast<js::GCPtrString*>(mem);
@@ -2876,23 +2921,28 @@ struct TraceListVisitor {
 
   bool fillList(Vector<int32_t>& entries);
 };
 
 }  // namespace
 
 void TraceListVisitor::visitReference(ReferenceTypeDescr& descr, uint8_t* mem) {
   VectorType* offsets;
+  // TODO/AnyRef-boxing: Once a WasmAnyRef is no longer just a JSObject*
+  // we must revisit this structure.
   switch (descr.type()) {
     case ReferenceType::TYPE_ANY:
       offsets = &valueOffsets;
       break;
     case ReferenceType::TYPE_OBJECT:
       offsets = &objectOffsets;
       break;
+    case ReferenceType::TYPE_WASM_ANYREF:
+      offsets = &objectOffsets;
+      break;
     case ReferenceType::TYPE_STRING:
       offsets = &stringOffsets;
       break;
     default:
       MOZ_CRASH("Invalid kind");
   }
 
   AutoEnterOOMUnsafeRegion oomUnsafe;
--- a/js/src/builtin/TypedObject.h
+++ b/js/src/builtin/TypedObject.h
@@ -181,18 +181,20 @@ class TypeDescr : public NativeObject {
   // Type descriptors may contain a list of their references for use during
   // scanning. Marking code is optimized to use this list to mark inline
   // typed objects, rather than the slower trace hook. This list is only
   // specified when (a) the descriptor is short enough that it can fit in an
   // InlineTypedObject, and (b) the descriptor contains at least one
   // reference. Otherwise its value is undefined.
   //
   // The list is three consecutive arrays of int32_t offsets, with each array
-  // terminated by -1. The arrays store offsets of string, object, and value
-  // references in the descriptor, in that order.
+  // terminated by -1. The arrays store offsets of string, object/anyref, and
+  // value references in the descriptor, in that order.
+  // TODO/AnyRef-boxing: once anyref has a more complicated structure, we must
+  // revisit this.
   MOZ_MUST_USE bool hasTraceList() const {
     return !getFixedSlot(JS_DESCR_SLOT_TRACE_LIST).isUndefined();
   }
   const int32_t* traceList() const {
     MOZ_ASSERT(hasTraceList());
     return reinterpret_cast<int32_t*>(
         getFixedSlot(JS_DESCR_SLOT_TRACE_LIST).toPrivate());
   }
@@ -279,16 +281,17 @@ class ScalarTypeDescr : public SimpleTyp
 // Must be in same order as the enum ScalarTypeDescr::Type:
 #define JS_FOR_EACH_SCALAR_TYPE_REPR(MACRO_)        \
   JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(MACRO_) \
   MACRO_(Scalar::Uint8Clamped, uint8_t, uint8Clamped)
 
 enum class ReferenceType {
   TYPE_ANY = JS_REFERENCETYPEREPR_ANY,
   TYPE_OBJECT = JS_REFERENCETYPEREPR_OBJECT,
+  TYPE_WASM_ANYREF = JS_REFERENCETYPEREPR_WASM_ANYREF,
   TYPE_STRING = JS_REFERENCETYPEREPR_STRING
 };
 
 // Type for reference type constructors like `Any`, `String`, and
 // `Object`. All such type constructors share a common js::Class and
 // JSFunctionSpec. All these types are opaque.
 class ReferenceTypeDescr : public SimpleTypeDescr {
  public:
@@ -308,19 +311,22 @@ class ReferenceTypeDescr : public Simple
     return (ReferenceType)getReservedSlot(JS_DESCR_SLOT_TYPE).toInt32();
   }
 
   const char* typeName() const { return typeName(type()); }
 
   static MOZ_MUST_USE bool call(JSContext* cx, unsigned argc, Value* vp);
 };
 
-#define JS_FOR_EACH_REFERENCE_TYPE_REPR(MACRO_)           \
-  MACRO_(ReferenceType::TYPE_ANY, GCPtrValue, Any)        \
-  MACRO_(ReferenceType::TYPE_OBJECT, GCPtrObject, Object) \
+// TODO/AnyRef-boxing: With boxed immediates and strings, GCPtrObject may not be
+// appropriate.
+#define JS_FOR_EACH_REFERENCE_TYPE_REPR(MACRO_)                    \
+  MACRO_(ReferenceType::TYPE_ANY, GCPtrValue, Any)                 \
+  MACRO_(ReferenceType::TYPE_WASM_ANYREF, GCPtrObject, WasmAnyRef) \
+  MACRO_(ReferenceType::TYPE_OBJECT, GCPtrObject, Object)          \
   MACRO_(ReferenceType::TYPE_STRING, GCPtrString, string)
 
 // Type descriptors whose instances are objects and hence which have
 // an associated `prototype` property.
 class ComplexTypeDescr : public TypeDescr {
  public:
   // Returns the prototype that instances of this type descriptor
   // will have.
@@ -504,16 +510,17 @@ class TypedObjectModuleObject : public N
   enum Slot {
     ArrayTypePrototype,
     StructTypePrototype,
     Int32Desc,
     Int64Desc,
     Float32Desc,
     Float64Desc,
     ObjectDesc,
+    WasmAnyRefDesc,
     SlotCount
   };
 
   static const Class class_;
 };
 
 /* Base type for transparent and opaque typed objects. */
 class TypedObject : public ShapedObject {
@@ -864,16 +871,49 @@ MOZ_MUST_USE bool ClampToUint8(JSContext
  * to the various builtin type descriptors. These are currently
  * exported as immutable properties so it is safe for self-hosted code
  * to access them; eventually this should be linked into the module
  * system.
  */
 MOZ_MUST_USE bool GetTypedObjectModule(JSContext* cx, unsigned argc, Value* vp);
 
 /*
+ * Usage: IsBoxedWasmAnyRef(Object) -> bool
+ *
+ * Return true iff object is an instance of the Wasm-internal type WasmValueBox.
+ */
+MOZ_MUST_USE bool IsBoxedWasmAnyRef(JSContext* cx, unsigned argc, Value* vp);
+
+/*
+ * Usage: IsBoxableWasmAnyRef(Value) -> bool
+ *
+ * Return true iff the value must be boxed into a WasmValueBox in order to be
+ * stored into an anyref field.  Values for which false is returned may be
+ * passed as they are to Store_WasmAnyRef and may therefore appear as results
+ * from Load_WasmAnyRef.
+ */
+MOZ_MUST_USE bool IsBoxableWasmAnyRef(JSContext* cx, unsigned argc, Value* vp);
+
+/*
+ * Usage: BoxWasmAnyRef(Value) -> Object
+ *
+ * Return a new WasmValueBox that holds `value`.  The value can be any value at
+ * all.
+ */
+MOZ_MUST_USE bool BoxWasmAnyRef(JSContext* cx, unsigned argc, Value* vp);
+
+/*
+ * Usage: UnboxBoxedWasmAnyRef(Object) -> Value
+ *
+ * The object must be a value for which IsBoxedWasmAnyRef returns true.
+ * Return the value stored in the box.
+ */
+MOZ_MUST_USE bool UnboxBoxedWasmAnyRef(JSContext* cx, unsigned argc, Value* vp);
+
+/*
  * Usage: Store_int8(targetDatum, targetOffset, value)
  *        ...
  *        Store_uint8(targetDatum, targetOffset, value)
  *        ...
  *        Store_float32(targetDatum, targetOffset, value)
  *        Store_float64(targetDatum, targetOffset, value)
  *
  * Intrinsic function. Stores `value` into the memory referenced by
--- a/js/src/builtin/TypedObject.js
+++ b/js/src/builtin/TypedObject.js
@@ -121,16 +121,22 @@ function TypedObjectGetReference(descr, 
   var type = DESCR_TYPE(descr);
   switch (type) {
   case JS_REFERENCETYPEREPR_ANY:
     return Load_Any(typedObj, offset | 0);
 
   case JS_REFERENCETYPEREPR_OBJECT:
     return Load_Object(typedObj, offset | 0);
 
+  case JS_REFERENCETYPEREPR_WASM_ANYREF:
+    var boxed = Load_WasmAnyRef(typedObj, offset | 0);
+    if (!IsBoxedWasmAnyRef(boxed))
+      return boxed;
+    return UnboxBoxedWasmAnyRef(boxed);
+
   case JS_REFERENCETYPEREPR_STRING:
     return Load_string(typedObj, offset | 0);
   }
 
   assert(false, "Unhandled scalar type: " + type);
   return undefined;
 }
 
@@ -255,16 +261,20 @@ function TypedObjectSetReference(descr, 
   switch (type) {
   case JS_REFERENCETYPEREPR_ANY:
     return Store_Any(typedObj, offset | 0, name, fromValue);
 
   case JS_REFERENCETYPEREPR_OBJECT:
     var value = (fromValue === null ? fromValue : ToObject(fromValue));
     return Store_Object(typedObj, offset | 0, name, value);
 
+  case JS_REFERENCETYPEREPR_WASM_ANYREF:
+    var value = (IsBoxableWasmAnyRef(fromValue) ? BoxWasmAnyRef(fromValue) : fromValue);
+    return Store_WasmAnyRef(typedObj, offset | 0, name, value);
+
   case JS_REFERENCETYPEREPR_STRING:
     return Store_string(typedObj, offset | 0, name, ToString(fromValue));
   }
 
   assert(false, "Unhandled scalar type: " + type);
   return undefined;
 }
 
--- a/js/src/builtin/TypedObjectConstants.h
+++ b/js/src/builtin/TypedObjectConstants.h
@@ -94,11 +94,12 @@
 #define JS_SCALARTYPEREPR_UINT8_CLAMPED 8
 
 // These constants are for use exclusively in JS code. In C++ code,
 // prefer ReferenceTypeRepresentation::TYPE_ANY etc, which allows
 // you to write a switch which will receive a warning if you omit a
 // case.
 #define JS_REFERENCETYPEREPR_ANY 0
 #define JS_REFERENCETYPEREPR_OBJECT 1
-#define JS_REFERENCETYPEREPR_STRING 2
+#define JS_REFERENCETYPEREPR_WASM_ANYREF 2
+#define JS_REFERENCETYPEREPR_STRING 3
 
 #endif
--- a/js/src/jit-test/tests/wasm/gc/anyref-boxing-struct.js
+++ b/js/src/jit-test/tests/wasm/gc/anyref-boxing-struct.js
@@ -3,16 +3,17 @@
 // Moving a JS value through a wasm anyref is a pair of boxing/unboxing
 // conversions that leaves the value unchanged.  There are many cases,
 // along these axes:
 //
 //  - global variables, see anyref-boxing.js
 //  - tables, see anyref-boxing.js
 //  - function parameters and returns, see anyref-boxing.js
 //  - struct fields [for the gc feature], this file
+//  - TypedObject fields when we construct with the anyref type; this file
 
 let VALUES = [null,
               undefined,
               true,
               false,
               {x:1337},
               ["abracadabra"],
               1337,
@@ -42,50 +43,144 @@ for (let v of VALUES)
 {
     let ins = wasmEvalText(
         `(module
            (gc_feature_opt_in 2)
            (type $S (struct (field $S.x (mut anyref))))
            (func (export "make") (param $v anyref) (result anyref)
              (struct.new $S (get_local $v))))`);
     let x = ins.exports.make(v);
-    // FIXME
-    // Does not work except for objects, we observe object values even for primitives
-    //assertEq(x._0, v);
+    assertEq(x._0, v);
 }
 
 // Write with JS setter, read with struct.get
 
 for (let v of VALUES)
 {
     let ins = wasmEvalText(
         `(module
            (gc_feature_opt_in 2)
            (type $S (struct (field $S.x (mut anyref))))
            (func (export "make") (result anyref)
              (struct.new $S (ref.null)))
            (func (export "get") (param $o anyref) (result anyref)
              (struct.get $S 0 (struct.narrow anyref (ref $S) (get_local $o)))))`);
     let x = ins.exports.make();
-    // FIXME
-    // Does not work except for things that convert to object.
-    //x._0 = v;
-    //assertEq(ins.exports.get(x), v);
+    x._0 = v;
+    assertEq(ins.exports.get(x), v);
 }
 
 // Write with JS constructor, read with struct.get
 
 for (let v of VALUES)
 {
     let ins = wasmEvalText(
         `(module
            (gc_feature_opt_in 2)
            (type $S (struct (field $S.x (mut anyref))))
            (func (export "make") (result anyref)
              (struct.new $S (ref.null)))
            (func (export "get") (param $o anyref) (result anyref)
              (struct.get $S 0 (struct.narrow anyref (ref $S) (get_local $o)))))`);
     let constructor = ins.exports.make().constructor;
-    // FIXME
-    // Does not work except for things that convert to object
-    //let x = new constructor({_0: v});
-    //assertEq(ins.exports.get(x), v);
+    let x = new constructor({_0: v});
+    assertEq(ins.exports.get(x), v);
+}
+
+// TypedObject.WasmAnyRef exists and is an identity operation
+
+assertEq(typeof TypedObject.WasmAnyRef, "function");
+for (let v of VALUES) {
+    assertEq(TypedObject.WasmAnyRef(v), v);
+}
+
+// We can create structures with WasmAnyRef type, if we want.
+// It's not totally obvious that we want to allow this long-term though.
+
+{
+    let constructor = new TypedObject.StructType({_0: TypedObject.WasmAnyRef});
+    let x = new constructor({_0: 3.25});
+    assertEq(x._0, 3.25);
+    let y = new constructor({_0: assertEq});
+    assertEq(y._0, assertEq);
+}
+
+// The default value of an anyref field should be null.
+
+{
+    let ins = wasmEvalText(
+        `(module
+           (gc_feature_opt_in 2)
+           (type $S (struct (field $S.x (mut anyref))))
+           (func (export "make") (result anyref)
+             (struct.new $S (ref.null))))`);
+    let constructor = ins.exports.make().constructor;
+    let x = new constructor();
+    assertEq(x._0, null);
+}
+
+// Here we should actually see an undefined value since undefined is
+// representable as AnyRef.
+
+{
+    let ins = wasmEvalText(
+        `(module
+           (gc_feature_opt_in 2)
+           (type $S (struct (field $S.x (mut anyref))))
+           (func (export "make") (result anyref)
+             (struct.new $S (ref.null))))`);
+    let constructor = ins.exports.make().constructor;
+    let x = new constructor({});
+    assertEq(x._0, undefined);
 }
+
+// Contrast the previous case with this, where the conversion of the undefined
+// value for the missing field _0 must fail.
+
+{
+    let constructor = new TypedObject.StructType({_0: TypedObject.Object});
+    assertErrorMessage(() => new constructor({}),
+                       TypeError,
+                       /can't convert undefined/);
+}
+
+// Try to make sure anyrefs are properly traced
+
+{
+    let fields = iota(10).map(() => `(field anyref)`).join(' ');
+    let params = iota(10).map((i) => `(param $${i} anyref)`).join(' ');
+    let args = iota(10).map((i) => `(get_local $${i})`).join(' ');
+    let txt = `(module
+                 (gc_feature_opt_in 2)
+                 (type $S (struct ${fields}))
+                 (func (export "make") ${params} (result anyref)
+                   (struct.new $S ${args})))`;
+    let ins = wasmEvalText(txt);
+    let x = ins.exports.make({x:0}, {x:1}, {x:2}, {x:3}, {x:4}, {x:5}, {x:6}, {x:7}, {x:8}, {x:9})
+    gc('shrinking');
+    assertEq(typeof x._0, "object");
+    assertEq(x._0.x, 0);
+    assertEq(typeof x._1, "object");
+    assertEq(x._1.x, 1);
+    assertEq(typeof x._2, "object");
+    assertEq(x._2.x, 2);
+    assertEq(typeof x._3, "object");
+    assertEq(x._3.x, 3);
+    assertEq(typeof x._4, "object");
+    assertEq(x._4.x, 4);
+    assertEq(typeof x._5, "object");
+    assertEq(x._5.x, 5);
+    assertEq(typeof x._6, "object");
+    assertEq(x._6.x, 6);
+    assertEq(typeof x._7, "object");
+    assertEq(x._7.x, 7);
+    assertEq(typeof x._8, "object");
+    assertEq(x._8.x, 8);
+    assertEq(typeof x._9, "object");
+    assertEq(x._9.x, 9);
+}
+
+function iota(k) {
+    let a = new Array(k);
+    for ( let i=0 ; i < k; i++ )
+        a[i] = i;
+    return a;
+}
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -1743,17 +1743,18 @@ static bool DoTypeUpdateFallback(JSConte
     ReferenceType type = fieldDescr->as<ReferenceTypeDescr>().type();
     if (type == ReferenceType::TYPE_ANY) {
       // Ignore undefined values, which are included implicitly in type
       // information for this property.
       if (value.isUndefined()) {
         addType = false;
       }
     } else {
-      MOZ_ASSERT(type == ReferenceType::TYPE_OBJECT);
+      MOZ_ASSERT(type == ReferenceType::TYPE_OBJECT ||
+                 type == ReferenceType::TYPE_WASM_ANYREF);
 
       // Ignore null values being written here. Null is included
       // implicitly in type information for this property. Note that
       // non-object, non-null values are not possible here, these
       // should have been filtered out by the IR emitter.
       if (value.isNull()) {
         addType = false;
       }
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -3640,16 +3640,25 @@ bool SetPropIRGenerator::tryAttachTypedO
     return false;
   }
 
   TypeDescr* fieldDescr = &structDescr->fieldDescr(fieldIndex);
   if (!fieldDescr->is<SimpleTypeDescr>()) {
     return false;
   }
 
+  if (fieldDescr->is<ReferenceTypeDescr>() &&
+      fieldDescr->as<ReferenceTypeDescr>().type() ==
+          ReferenceType::TYPE_WASM_ANYREF) {
+    // TODO/AnyRef-boxing: we can probably do better, in particular, code
+    // that stores object pointers and null in an anyref slot should be able
+    // to get a fast path.
+    return false;
+  }
+
   uint32_t fieldOffset = structDescr->fieldOffset(fieldIndex);
   TypedThingLayout layout = GetTypedThingLayout(obj->getClass());
 
   maybeEmitIdGuard(id);
   writer.guardNoDetachedTypedObjects();
   writer.guardGroupForLayout(objId, obj->group());
 
   typeCheckInfo_.set(obj->group(), id);
@@ -3672,16 +3681,18 @@ bool SetPropIRGenerator::tryAttachTypedO
     case ReferenceType::TYPE_ANY:
       break;
     case ReferenceType::TYPE_OBJECT:
       writer.guardIsObjectOrNull(rhsId);
       break;
     case ReferenceType::TYPE_STRING:
       writer.guardType(rhsId, JSVAL_TYPE_STRING);
       break;
+    case ReferenceType::TYPE_WASM_ANYREF:
+      MOZ_CRASH();
   }
 
   writer.storeTypedObjectReferenceProperty(objId, fieldOffset, layout, type,
                                            rhsId);
   writer.returnFromIC();
 
   trackAttached("TypedObject");
   return true;
--- a/js/src/jit/CacheIRCompiler.cpp
+++ b/js/src/jit/CacheIRCompiler.cpp
@@ -3106,16 +3106,19 @@ void CacheIRCompiler::emitLoadTypedObjec
                             /* allowDouble = */ true, scratch, nullptr);
   } else {
     ReferenceType type = ReferenceTypeFromSimpleTypeDescrKey(typeDescr);
     switch (type) {
       case ReferenceType::TYPE_ANY:
         masm.loadValue(fieldAddr, output.valueReg());
         break;
 
+      case ReferenceType::TYPE_WASM_ANYREF:
+        // TODO/AnyRef-boxing: With boxed immediates and strings this may be
+        // more complicated.
       case ReferenceType::TYPE_OBJECT: {
         Label notNull, done;
         masm.loadPtr(fieldAddr, scratch);
         masm.branchTestPtr(Assembler::NonZero, scratch, scratch, &notNull);
         masm.moveValue(NullValue(), output.valueReg());
         masm.jump(&done);
         masm.bind(&notNull);
         masm.tagValue(JSVAL_TYPE_OBJECT, scratch, output.valueReg());
@@ -3422,16 +3425,19 @@ void CacheIRCompiler::emitStoreTypedObje
   // Callers will post-barrier this store.
 
   switch (type) {
     case ReferenceType::TYPE_ANY:
       EmitPreBarrier(masm, dest, MIRType::Value);
       masm.storeValue(val, dest);
       break;
 
+    case ReferenceType::TYPE_WASM_ANYREF:
+      // TODO/AnyRef-boxing: With boxed immediates and strings this may be
+      // more complicated.
     case ReferenceType::TYPE_OBJECT: {
       EmitPreBarrier(masm, dest, MIRType::Object);
       Label isNull, done;
       masm.branchTestObject(Assembler::NotEqual, val, &isNull);
       masm.unboxObject(val, scratch);
       masm.storePtr(scratch, dest);
       masm.jump(&done);
       masm.bind(&isNull);
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -8355,16 +8355,20 @@ AbortReasonOr<Ok> IonBuilder::getElemTry
   uint32_t elemSize = ReferenceTypeDescr::size(elemType);
 
   LinearSum indexAsByteOffset(alloc());
   if (!checkTypedObjectIndexInBounds(elemSize, index, objPrediction,
                                      &indexAsByteOffset)) {
     return Ok();
   }
 
+  if (elemType == ReferenceType::TYPE_WASM_ANYREF) {
+    return Ok();
+  }
+
   trackOptimizationSuccess();
   *emitted = true;
 
   return pushReferenceLoadFromTypedObject(obj, indexAsByteOffset, elemType,
                                           nullptr);
 }
 
 AbortReasonOr<Ok> IonBuilder::pushScalarLoadFromTypedObject(
@@ -8453,16 +8457,19 @@ AbortReasonOr<Ok> IonBuilder::pushRefere
       break;
     }
     case ReferenceType::TYPE_STRING: {
       load =
           MLoadUnboxedString::New(alloc(), elements, scaledOffset, adjustment);
       observedTypes->addType(TypeSet::StringType(), alloc().lifoAlloc());
       break;
     }
+    case ReferenceType::TYPE_WASM_ANYREF: {
+      MOZ_CRASH();
+    }
   }
 
   current->add(load);
   current->push(load);
 
   return pushTypeBarrier(load, observedTypes, barrier);
 }
 
@@ -9333,16 +9340,20 @@ AbortReasonOr<Ok> IonBuilder::setElemTry
   uint32_t elemSize = ReferenceTypeDescr::size(elemType);
 
   LinearSum indexAsByteOffset(alloc());
   if (!checkTypedObjectIndexInBounds(elemSize, index, objPrediction,
                                      &indexAsByteOffset)) {
     return Ok();
   }
 
+  if (elemType == ReferenceType::TYPE_WASM_ANYREF) {
+    return Ok();
+  }
+
   return setPropTryReferenceTypedObjectValue(emitted, obj, indexAsByteOffset,
                                              elemType, value, nullptr);
 }
 
 AbortReasonOr<Ok> IonBuilder::setElemTryScalarElemOfTypedObject(
     bool* emitted, MDefinition* obj, MDefinition* index,
     TypedObjectPrediction objPrediction, MDefinition* value,
     TypedObjectPrediction elemPrediction, uint32_t elemSize) {
@@ -11034,16 +11045,20 @@ AbortReasonOr<Ok> IonBuilder::getPropTry
   ReferenceType fieldType = fieldPrediction.referenceType();
 
   TypeSet::ObjectKey* globalKey = TypeSet::ObjectKey::get(&script()->global());
   if (globalKey->hasFlags(constraints(),
                           OBJECT_FLAG_TYPED_OBJECT_HAS_DETACHED_BUFFER)) {
     return Ok();
   }
 
+  if (fieldType == ReferenceType::TYPE_WASM_ANYREF) {
+    return Ok();
+  }
+
   trackOptimizationSuccess();
   *emitted = true;
 
   LinearSum byteOffset(alloc());
   if (!byteOffset.add(fieldOffset)) {
     return abort(AbortReason::Disable, "Overflow of field offsets.");
   }
 
@@ -12218,16 +12233,20 @@ AbortReasonOr<Ok> IonBuilder::setPropTry
   ReferenceType fieldType = fieldPrediction.referenceType();
 
   TypeSet::ObjectKey* globalKey = TypeSet::ObjectKey::get(&script()->global());
   if (globalKey->hasFlags(constraints(),
                           OBJECT_FLAG_TYPED_OBJECT_HAS_DETACHED_BUFFER)) {
     return Ok();
   }
 
+  if (fieldType == ReferenceType::TYPE_WASM_ANYREF) {
+    return Ok();
+  }
+
   LinearSum byteOffset(alloc());
   if (!byteOffset.add(fieldOffset)) {
     return abort(AbortReason::Disable, "Overflow of field offset.");
   }
 
   return setPropTryReferenceTypedObjectValue(emitted, obj, byteOffset,
                                              fieldType, value, name);
 }
@@ -14109,16 +14128,18 @@ AbortReasonOr<Ok> IonBuilder::setPropTry
                                              value, typedObj, adjustment);
       break;
     case ReferenceType::TYPE_STRING:
       // See previous comment. The StoreUnboxedString type policy may insert
       // ToString instructions that require a post barrier.
       store = MStoreUnboxedString::New(alloc(), elements, scaledOffset, value,
                                        typedObj, adjustment);
       break;
+    case ReferenceType::TYPE_WASM_ANYREF:
+      MOZ_CRASH();
   }
 
   current->add(store);
   current->push(value);
 
   trackOptimizationSuccess();
   *emitted = true;
   return resumeAfter(store);
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -415,16 +415,17 @@
   MACRO(void, void_, "void")                                                   \
   MACRO(value, value, "value")                                                 \
   MACRO(valueOf, valueOf, "valueOf")                                           \
   MACRO(values, values, "values")                                              \
   MACRO(var, var, "var")                                                       \
   MACRO(variable, variable, "variable")                                        \
   MACRO(void0, void0, "(void 0)")                                              \
   MACRO(wasm, wasm, "wasm")                                                    \
+  MACRO(WasmAnyRef, WasmAnyRef, "WasmAnyRef")                                  \
   MACRO(wasmcall, wasmcall, "wasmcall")                                        \
   MACRO(watch, watch, "watch")                                                 \
   MACRO(WeakMapConstructorInit, WeakMapConstructorInit,                        \
         "WeakMapConstructorInit")                                              \
   MACRO(WeakSetConstructorInit, WeakSetConstructorInit,                        \
         "WeakSetConstructorInit")                                              \
   MACRO(WeakSet_add, WeakSet_add, "WeakSet_add")                               \
   MACRO(weekday, weekday, "weekday")                                           \
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2641,16 +2641,20 @@ static const JSFunctionSpec intrinsic_fu
                     js::ObjectIsTransparentTypedObject, 1, 0,
                     IntrinsicObjectIsTransparentTypedObject),
     JS_INLINABLE_FN("TypeDescrIsArrayType", js::TypeDescrIsArrayType, 1, 0,
                     IntrinsicTypeDescrIsArrayType),
     JS_INLINABLE_FN("TypeDescrIsSimpleType", js::TypeDescrIsSimpleType, 1, 0,
                     IntrinsicTypeDescrIsSimpleType),
     JS_INLINABLE_FN("SetTypedObjectOffset", js::SetTypedObjectOffset, 2, 0,
                     IntrinsicSetTypedObjectOffset),
+    JS_FN("IsBoxedWasmAnyRef", js::IsBoxedWasmAnyRef, 1, 0),
+    JS_FN("IsBoxableWasmAnyRef", js::IsBoxableWasmAnyRef, 1, 0),
+    JS_FN("BoxWasmAnyRef", js::BoxWasmAnyRef, 1, 0),
+    JS_FN("UnboxBoxedWasmAnyRef", js::UnboxBoxedWasmAnyRef, 1, 0),
 
 // clang-format off
 #define LOAD_AND_STORE_SCALAR_FN_DECLS(_constant, _type, _name)         \
     JS_FN("Store_" #_name, js::StoreScalar##_type::Func, 3, 0),         \
     JS_FN("Load_" #_name,  js::LoadScalar##_type::Func, 3, 0),
     JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(LOAD_AND_STORE_SCALAR_FN_DECLS)
 // clang-format on
 #undef LOAD_AND_STORE_SCALAR_FN_DECLS
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -1149,24 +1149,18 @@ static bool MakeStructField(JSContext* c
       t = GlobalObject::getOrCreateScalarTypeDescr(cx, cx->global(),
                                                    Scalar::Float64);
       break;
     case ValType::Ref:
       t = GlobalObject::getOrCreateReferenceTypeDescr(
           cx, cx->global(), ReferenceType::TYPE_OBJECT);
       break;
     case ValType::AnyRef:
-      // TODO/AnyRef-boxing: TYPE_OBJECT is not an appropriate representation
-      // for anyref in a structure.  So long as we box non-Object values in
-      // Objects, it is safe; however, boxing/unboxing operations on the JS
-      // side will be incorrect.  Once we use a tagged representation, using
-      // TYPE_OBJECT will be unsound.
-      ASSERT_ANYREF_IS_JSOBJECT;
       t = GlobalObject::getOrCreateReferenceTypeDescr(
-          cx, cx->global(), ReferenceType::TYPE_OBJECT);
+          cx, cx->global(), ReferenceType::TYPE_WASM_ANYREF);
       break;
     default:
       MOZ_CRASH("Bad field type");
   }
   MOZ_ASSERT(t != nullptr);
 
   if (!ids->append(INTERNED_STRING_TO_JSID(cx, str))) {
     return false;
--- a/js/src/wasm/WasmTypes.cpp
+++ b/js/src/wasm/WasmTypes.cpp
@@ -160,16 +160,48 @@ Value wasm::UnboxAnyRef(AnyRef val) {
   } else if (obj->is<WasmValueBox>()) {
     result = obj->as<WasmValueBox>().value();
   } else {
     result.setObjectOrNull(obj);
   }
   return result;
 }
 
+bool js::IsBoxedWasmAnyRef(JSContext* cx, unsigned argc, Value* vp) {
+  CallArgs args = CallArgsFromVp(argc, vp);
+  MOZ_ASSERT(args.length() == 1);
+  args.rval().setBoolean(args[0].isObject() &&
+                         args[0].toObject().is<WasmValueBox>());
+  return true;
+}
+
+bool js::IsBoxableWasmAnyRef(JSContext* cx, unsigned argc, Value* vp) {
+  CallArgs args = CallArgsFromVp(argc, vp);
+  MOZ_ASSERT(args.length() == 1);
+  args.rval().setBoolean(!(args[0].isObject() || args[0].isNull()));
+  return true;
+}
+
+bool js::BoxWasmAnyRef(JSContext* cx, unsigned argc, Value* vp) {
+  CallArgs args = CallArgsFromVp(argc, vp);
+  MOZ_ASSERT(args.length() == 1);
+  WasmValueBox* box = WasmValueBox::create(cx, args[0]);
+  if (!box) return false;
+  args.rval().setObject(*box);
+  return true;
+}
+
+bool js::UnboxBoxedWasmAnyRef(JSContext* cx, unsigned argc, Value* vp) {
+  CallArgs args = CallArgsFromVp(argc, vp);
+  MOZ_ASSERT(args.length() == 1);
+  WasmValueBox* box = &args[0].toObject().as<WasmValueBox>();
+  args.rval().set(box->value());
+  return true;
+}
+
 bool wasm::IsRoundingFunction(SymbolicAddress callee, jit::RoundingMode* mode) {
   switch (callee) {
     case SymbolicAddress::FloorD:
     case SymbolicAddress::FloorF:
       *mode = jit::RoundingMode::Down;
       return true;
     case SymbolicAddress::CeilD:
     case SymbolicAddress::CeilF:
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -1372,24 +1372,17 @@ static bool DecodeStructType(Decoder& d,
         break;
       case ValType::F64:
         offset = layout.addScalar(Scalar::Float64);
         break;
       case ValType::Ref:
         offset = layout.addReference(ReferenceType::TYPE_OBJECT);
         break;
       case ValType::AnyRef:
-        // TODO/AnyRef-boxing: TYPE_OBJECT is not an appropriate
-        // representation for anyref in a structure.  So long as we box
-        // non-Object values in Objects, it is safe; however,
-        // boxing/unboxing operations on the JS side will be incorrect.
-        // Once we use a tagged representation, using TYPE_OBJECT will be
-        // unsound.
-        ASSERT_ANYREF_IS_JSOBJECT;
-        offset = layout.addReference(ReferenceType::TYPE_OBJECT);
+        offset = layout.addReference(ReferenceType::TYPE_WASM_ANYREF);
         break;
       default:
         MOZ_CRASH("Unknown type");
     }
     if (!offset.isValid()) {
       return d.fail("Object too large");
     }