Bug 1459900 - Struct types: read, write, validate. r=luke
☠☠ backed out by f7ec87cfa1bc ☠ ☠
authorLars T Hansen <lhansen@mozilla.com>
Tue, 08 May 2018 14:18:30 +0200
changeset 477481 82874b00bab05e115287d1fbe8549c11c523d5de
parent 477480 4072740124c8aa15d5439983862e61281d437364
child 477482 f7ec87cfa1bce4db98df6e4dee6b44e42bd96252
push id9385
push userdluca@mozilla.com
push dateFri, 22 Jun 2018 15:47:18 +0000
treeherdermozilla-beta@82a9a1027e2b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1459900
milestone62.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 1459900 - Struct types: read, write, validate. r=luke We introduce a simple "struct" type definition for Wasm. A struct has fields of primitive types, including anyref, but no other information, notably no information about subtype relationships. The syntax is: (type $tname (struct (field $fname i32) ...)) where the $fnames are currently ignored. (In the future, the $fnames will denote the field numbers of their fields within the structure and will be used by the struct.get and struct.set instructions in the text format. If any $fname is bound in multiple structures the bindings must resolve to the same field number and field type.) To record the type information there is a new StructType type in WasmTypes.h. We generalize the SigWithId table in ModuleEnvironment to instead be a TypeDef table, where a TypeDef is a tagged union of SigWithId and StructType. Similarly, there is a new AstTypeDef base class for AstSig and AstStruct, and the sigs_ table in AstModule becomes a types_ table. When the ModuleEnvironment is about to be destroyed we move the StructType types into a dense structTypes_ table in the Module; a later patch will make use of these types. The structTypes_ get serialized and deserialized with the module.
js/src/jit-test/tests/wasm/binary.js
js/src/jit-test/tests/wasm/gc/structs.js
js/src/wasm/AsmJS.cpp
js/src/wasm/WasmAST.h
js/src/wasm/WasmBaselineCompile.cpp
js/src/wasm/WasmBinaryConstants.h
js/src/wasm/WasmBinaryToAST.cpp
js/src/wasm/WasmBinaryToText.cpp
js/src/wasm/WasmGenerator.cpp
js/src/wasm/WasmIonCompile.cpp
js/src/wasm/WasmModule.cpp
js/src/wasm/WasmModule.h
js/src/wasm/WasmOpIter.h
js/src/wasm/WasmTextToBinary.cpp
js/src/wasm/WasmTypes.cpp
js/src/wasm/WasmTypes.h
js/src/wasm/WasmValidate.cpp
js/src/wasm/WasmValidate.h
--- a/js/src/jit-test/tests/wasm/binary.js
+++ b/js/src/jit-test/tests/wasm/binary.js
@@ -276,18 +276,18 @@ function customSection(name, ...body) {
     return { name: userDefinedId, body: [...string(name), ...body] };
 }
 
 const v2vSig = {args:[], ret:VoidCode};
 const v2vSigSection = sigSection([v2vSig]);
 const i2vSig = {args:[I32Code], ret:VoidCode};
 const v2vBody = funcBody({locals:[], body:[]});
 
-assertErrorMessage(() => wasmEval(moduleWithSections([ {name: typeId, body: U32MAX_LEB } ])), CompileError, /too many signatures/);
-assertErrorMessage(() => wasmEval(moduleWithSections([ {name: typeId, body: [1, 0], } ])), CompileError, /expected function form/);
+assertErrorMessage(() => wasmEval(moduleWithSections([ {name: typeId, body: U32MAX_LEB } ])), CompileError, /too many types/);
+assertErrorMessage(() => wasmEval(moduleWithSections([ {name: typeId, body: [1, 0], } ])), CompileError, /expected type form/);
 assertErrorMessage(() => wasmEval(moduleWithSections([ {name: typeId, body: [1, FuncCode, ...U32MAX_LEB], } ])), CompileError, /too many arguments in signature/);
 
 assertThrowsInstanceOf(() => wasmEval(moduleWithSections([{name: typeId, body: [1]}])), CompileError);
 assertThrowsInstanceOf(() => wasmEval(moduleWithSections([{name: typeId, body: [1, 1, 0]}])), CompileError);
 
 wasmEval(moduleWithSections([sigSection([])]));
 wasmEval(moduleWithSections([v2vSigSection]));
 wasmEval(moduleWithSections([sigSection([i2vSig])]));
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/structs.js
@@ -0,0 +1,162 @@
+if (!wasmGcEnabled()) {
+    assertErrorMessage(() => wasmEvalText(`(module (type $s (struct)))`),
+                       WebAssembly.CompileError, /Structure types not enabled/);
+    quit();
+}
+
+var bin = wasmTextToBinary(
+    `(module
+
+      (table 2 anyfunc)
+      (elem (i32.const 0) $doit $doitagain)
+
+      ;; Type array has a mix of types
+
+      (type $f1 (func (param i32) (result i32)))
+
+      (type $point (struct
+                    (field $point_x i32)
+                    (field $point_y i32)))
+
+      (type $f2 (func (param f64) (result f64)))
+
+      (type $int_node (struct
+                       (field $intbox_val (mut i32))
+                       (field $intbox_next (mut anyref))))
+
+      ;; Test all the types.
+
+      (type $omni (struct
+                   (field $omni_i32 i32)
+                   (field $omni_i32m (mut i32))
+                   (field $omni_i64 i64)
+                   (field $omni_i64m (mut i64))
+                   (field $omni_f32 f32)
+                   (field $omni_f32m (mut f32))
+                   (field $omni_f64 f64)
+                   (field $omni_f64m (mut f64))
+                   (field $omni_anyref anyref)
+                   (field $omni_anyrefm (mut anyref))))
+
+      ;; Various ways to reference a type in the middle of the
+      ;; type array, make sure we get the right one
+
+      (func $x1 (import "m" "x1") (type $f1))
+      (func $x2 (import "m" "x2") (type $f2))
+
+      (func (export "hello") (param f64) (param i32) (result f64)
+       (call_indirect $f2 (get_local 0) (get_local 1)))
+
+      (func $doit (param f64) (result f64)
+       (f64.sqrt (get_local 0)))
+
+      (func $doitagain (param f64) (result f64)
+       (f64.mul (get_local 0) (get_local 0)))
+
+      (func (export "x1") (param i32) (result i32)
+       (call $x1 (get_local 0)))
+
+      (func (export "x2") (param f64) (result f64)
+       (call $x2 (get_local 0)))
+     )`)
+
+var mod = new WebAssembly.Module(bin);
+var ins = new WebAssembly.Instance(mod, {m:{x1(x){ return x*3 }, x2(x){ return Math.PI }}}).exports;
+
+assertEq(ins.hello(4.0, 0), 2.0)
+assertEq(ins.hello(4.0, 1), 16.0)
+
+assertEq(ins.x1(12), 36)
+assertEq(ins.x2(8), Math.PI)
+
+// Crude but at least checks that we have *something*.
+
+var txt = wasmBinaryToText(bin);
+var re = /\(type\s+\$[a-z0-9]+\s+\(struct/gm;
+assertEq(Array.isArray(re.exec(txt)), true);
+assertEq(Array.isArray(re.exec(txt)), true);
+assertEq(Array.isArray(re.exec(txt)), true);
+assertEq(Array.isArray(re.exec(txt)), false);
+
+// The field name is optional, so this should work.
+
+wasmEvalText(`
+(module
+ (type $s (struct (field i32))))
+`)
+
+// Empty structs are OK.
+
+wasmEvalText(`
+(module
+ (type $s (struct)))
+`)
+
+// Bogus type definition syntax.
+
+assertErrorMessage(() => wasmEvalText(`
+(module
+ (type $s))
+`),
+SyntaxError, /parsing wasm text/);
+
+assertErrorMessage(() => wasmEvalText(`
+(module
+ (type $s (field $x i32)))
+`),
+SyntaxError, /bad type definition/);
+
+assertErrorMessage(() => wasmEvalText(`
+(module
+ (type $s (struct (field $x i31))))
+`),
+SyntaxError, /parsing wasm text/);
+
+assertErrorMessage(() => wasmEvalText(`
+(module
+ (type $s (struct (fjeld $x i32))))
+`),
+SyntaxError, /parsing wasm text/);
+
+assertErrorMessage(() => wasmEvalText(`
+(module
+ (type $s (struct abracadabra)))
+`),
+SyntaxError, /parsing wasm text/);
+
+// Function should not reference struct type: syntactic test
+
+assertErrorMessage(() => wasmEvalText(`
+(module
+ (type $s (struct))
+ (type $f (func (param i32) (result i32)))
+ (func (type 0) (param i32) (result i32) (unreachable)))
+`),
+WebAssembly.CompileError, /signature index references non-signature/);
+
+// Function should not reference struct type: binary test
+
+var bad = new Uint8Array([0x00, 0x61, 0x73, 0x6d,
+                          0x01, 0x00, 0x00, 0x00,
+
+                          0x01,                   // Type section
+                          0x03,                   // Section size
+                          0x01,                   // One type
+                          0x50,                   // Struct
+                          0x00,                   // Zero fields
+
+                          0x03,                   // Function section
+                          0x02,                   // Section size
+                          0x01,                   // One function
+                          0x00,                   // Type of function
+
+                          0x0a,                   // Code section
+                          0x05,                   // Section size
+                          0x01,                   // One body
+                          0x03,                   // Body size
+                          0x00,                   // Zero locals
+                          0x00,                   // UNREACHABLE
+                          0x0b]);                 // END
+
+assertErrorMessage(() => new WebAssembly.Module(bad),
+                   WebAssembly.CompileError, /signature index references non-signature/);
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -1633,27 +1633,27 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED Modu
         PropertyName* name;
         Scalar::Type type;
     };
 
   private:
     class HashableSig
     {
         uint32_t sigIndex_;
-        const SigWithIdVector& sigs_;
+        const TypeDefVector& types_;
 
       public:
-        HashableSig(uint32_t sigIndex, const SigWithIdVector& sigs)
-          : sigIndex_(sigIndex), sigs_(sigs)
+        HashableSig(uint32_t sigIndex, const TypeDefVector& types)
+          : sigIndex_(sigIndex), types_(types)
         {}
         uint32_t sigIndex() const {
             return sigIndex_;
         }
         const Sig& sig() const {
-            return sigs_[sigIndex_];
+            return types_[sigIndex_].funcType();
         }
 
         // Implement HashPolicy:
         typedef const Sig& Lookup;
         static HashNumber hash(Lookup l) {
             return l.hash();
         }
         static bool match(HashableSig lhs, Lookup rhs) {
@@ -1661,18 +1661,18 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED Modu
         }
     };
 
     class NamedSig : public HashableSig
     {
         PropertyName* name_;
 
       public:
-        NamedSig(PropertyName* name, uint32_t sigIndex, const SigWithIdVector& sigs)
-          : HashableSig(sigIndex, sigs), name_(name)
+        NamedSig(PropertyName* name, uint32_t sigIndex, const TypeDefVector& types)
+          : HashableSig(sigIndex, types), name_(name)
         {}
         PropertyName* name() const {
             return name_;
         }
 
         // Implement HashPolicy:
         struct Lookup {
             PropertyName* name;
@@ -1750,32 +1750,32 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED Modu
     }
     bool addStandardLibrarySimdOpName(const char* name, SimdOperation op) {
         JSAtom* atom = Atomize(cx_, name, strlen(name));
         if (!atom)
             return false;
         return standardLibrarySimdOpNames_.putNew(atom->asPropertyName(), op);
     }
     bool newSig(Sig&& sig, uint32_t* sigIndex) {
-        if (env_.sigs.length() >= MaxTypes)
+        if (env_.types.length() >= MaxTypes)
             return failCurrentOffset("too many signatures");
 
-        *sigIndex = env_.sigs.length();
-        return env_.sigs.append(std::move(sig));
+        *sigIndex = env_.types.length();
+        return env_.types.append(std::move(sig));
     }
     bool declareSig(Sig&& sig, uint32_t* sigIndex) {
         SigSet::AddPtr p = sigSet_.lookupForAdd(sig);
         if (p) {
             *sigIndex = p->sigIndex();
-            MOZ_ASSERT(env_.sigs[*sigIndex] == sig);
+            MOZ_ASSERT(env_.types[*sigIndex].funcType() == sig);
             return true;
         }
 
         return newSig(std::move(sig), sigIndex) &&
-               sigSet_.add(p, HashableSig(*sigIndex, env_.sigs));
+               sigSet_.add(p, HashableSig(*sigIndex, env_.types));
     }
 
   public:
     ModuleValidator(JSContext* cx, AsmJSParser& parser, ParseNode* moduleFunctionNode)
       : cx_(cx),
         parser_(parser),
         moduleFunctionNode_(moduleFunctionNode),
         moduleFunctionName_(FunctionName(moduleFunctionNode)),
@@ -2303,17 +2303,17 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED Modu
 
         if (!asmJSMetadata_->asmJSImports.emplaceBack(ffiIndex))
             return false;
 
         uint32_t sigIndex;
         if (!declareSig(std::move(sig), &sigIndex))
             return false;
 
-        return funcImportMap_.add(p, NamedSig(name, sigIndex, env_.sigs), *importIndex);
+        return funcImportMap_.add(p, NamedSig(name, sigIndex, env_.types), *importIndex);
     }
 
     bool tryConstantAccess(uint64_t start, uint64_t width) {
         MOZ_ASSERT(UINT64_MAX - start > width);
         uint64_t len = start + width;
         if (len > uint64_t(INT32_MAX) + 1)
             return false;
         len = RoundUpToNextValidAsmJSHeapLength(len);
@@ -2452,22 +2452,22 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED Modu
     }
     SharedModule finish() {
         MOZ_ASSERT(env_.funcSigs.empty());
         if (!env_.funcSigs.resize(funcImportMap_.count() + funcDefs_.length()))
             return nullptr;
         for (FuncImportMap::Range r = funcImportMap_.all(); !r.empty(); r.popFront()) {
             uint32_t funcIndex = r.front().value();
             MOZ_ASSERT(!env_.funcSigs[funcIndex]);
-            env_.funcSigs[funcIndex] = &env_.sigs[r.front().key().sigIndex()];
+            env_.funcSigs[funcIndex] = &env_.types[r.front().key().sigIndex()].funcType();
         }
         for (const Func& func : funcDefs_) {
             uint32_t funcIndex = funcImportMap_.count() + func.funcDefIndex();
             MOZ_ASSERT(!env_.funcSigs[funcIndex]);
-            env_.funcSigs[funcIndex] = &env_.sigs[func.sigIndex()];
+            env_.funcSigs[funcIndex] = &env_.types[func.sigIndex()].funcType();
         }
 
         if (!env_.funcImportGlobalDataOffsets.resize(funcImportMap_.count()))
             return nullptr;
 
         asmJSMetadata_->usesSimd = simdPresent_;
 
         MOZ_ASSERT(asmJSMetadata_->asmJSFuncNames.empty());
@@ -4888,17 +4888,17 @@ CheckFunctionSignature(ModuleValidator& 
 
     ModuleValidator::Func* existing = m.lookupFuncDef(name);
     if (!existing) {
         if (!CheckModuleLevelName(m, usepn, name))
             return false;
         return m.addFuncDef(name, usepn->pn_pos.begin, std::move(sig), func);
     }
 
-    const SigWithId& existingSig = m.env().sigs[existing->sigIndex()];
+    const SigWithId& existingSig = m.env().types[existing->sigIndex()].funcType();
 
     if (!CheckSignatureAgainstExisting(m, usepn, sig, existingSig))
         return false;
 
     *func = existing;
     return true;
 }
 
@@ -4946,17 +4946,17 @@ CheckFuncPtrTableAgainstExisting(ModuleV
     if (const ModuleValidator::Global* existing = m.lookupGlobal(name)) {
         if (existing->which() != ModuleValidator::Global::Table)
             return m.failName(usepn, "'%s' is not a function-pointer table", name);
 
         ModuleValidator::Table& table = m.table(existing->tableIndex());
         if (mask != table.mask())
             return m.failf(usepn, "mask does not match previous value (%u)", table.mask());
 
-        if (!CheckSignatureAgainstExisting(m, usepn, sig, m.env().sigs[table.sigIndex()]))
+        if (!CheckSignatureAgainstExisting(m, usepn, sig, m.env().types[table.sigIndex()].funcType()))
             return false;
 
         *tableIndex = existing->tableIndex();
         return true;
     }
 
     if (!CheckModuleLevelName(m, usepn, name))
         return false;
@@ -7371,17 +7371,17 @@ CheckFuncPtrTable(ModuleValidator& m, Pa
         if (!elem->isKind(ParseNodeKind::Name))
             return m.fail(elem, "function-pointer table's elements must be names of functions");
 
         PropertyName* funcName = elem->name();
         const ModuleValidator::Func* func = m.lookupFuncDef(funcName);
         if (!func)
             return m.fail(elem, "function-pointer table's elements must be names of functions");
 
-        const Sig& funcSig = m.env().sigs[func->sigIndex()];
+        const Sig& funcSig = m.env().types[func->sigIndex()].funcType();
         if (sig) {
             if (*sig != funcSig)
                 return m.fail(elem, "all functions in table must have same signature");
         } else {
             sig = &funcSig;
         }
 
         if (!elemFuncDefIndices.append(func->funcDefIndex()))
--- a/js/src/wasm/WasmAST.h
+++ b/js/src/wasm/WasmAST.h
@@ -121,33 +121,58 @@ typedef AstVector<AstRef> AstRefVector;
 
 struct AstBase
 {
     void* operator new(size_t numBytes, LifoAlloc& astLifo) throw() {
         return astLifo.alloc(numBytes);
     }
 };
 
-class AstSig : public AstBase
+class AstSig;
+class AstStruct;
+
+class AstTypeDef : public AstBase
+{
+  protected:
+    enum class Which { IsSig, IsStruct };
+
+  private:
+    Which which_;
+
+  public:
+    AstTypeDef(Which which) : which_(which) {}
+
+    bool isSig() const { return which_ == Which::IsSig; }
+    bool isStruct() const { return which_ == Which::IsStruct; }
+    inline AstSig& asSig();
+    inline AstStruct& asStruct();
+    inline const AstSig& asSig() const;
+    inline const AstStruct& asStruct() const;
+};
+
+class AstSig : public AstTypeDef
 {
     AstName name_;
     AstValTypeVector args_;
     ExprType ret_;
 
   public:
     explicit AstSig(LifoAlloc& lifo)
-      : args_(lifo),
+      : AstTypeDef(Which::IsSig),
+        args_(lifo),
         ret_(ExprType::Void)
     {}
     AstSig(AstValTypeVector&& args, ExprType ret)
-      : args_(std::move(args)),
+      : AstTypeDef(Which::IsSig),
+        args_(std::move(args)),
         ret_(ret)
     {}
     AstSig(AstName name, AstSig&& rhs)
-      : name_(name),
+      : AstTypeDef(Which::IsSig),
+        name_(name),
         args_(std::move(rhs.args_)),
         ret_(rhs.ret_)
     {}
     const AstValTypeVector& args() const {
         return args_;
     }
     ExprType ret() const {
         return ret_;
@@ -166,16 +191,78 @@ class AstSig : public AstBase
             hn = mozilla::AddToHash(hn, vt.code());
         return hn;
     }
     static bool match(const AstSig* lhs, Lookup rhs) {
         return *lhs == rhs;
     }
 };
 
+class AstStruct : public AstTypeDef
+{
+    AstName          name_;
+    AstNameVector    fieldNames_;
+    AstValTypeVector fieldTypes_;
+
+  public:
+    explicit AstStruct(LifoAlloc& lifo)
+      : AstTypeDef(Which::IsStruct),
+        fieldNames_(lifo),
+        fieldTypes_(lifo)
+    {}
+    AstStruct(AstNameVector&& names, AstValTypeVector&& types)
+      : AstTypeDef(Which::IsStruct),
+        fieldNames_(std::move(names)),
+        fieldTypes_(std::move(types))
+    {}
+    AstStruct(AstName name, AstStruct&& rhs)
+      : AstTypeDef(Which::IsStruct),
+        name_(name),
+        fieldNames_(std::move(rhs.fieldNames_)),
+        fieldTypes_(std::move(rhs.fieldTypes_))
+    {}
+    AstName name() const {
+        return name_;
+    }
+    const AstNameVector& fieldNames() const {
+        return fieldNames_;
+    }
+    const AstValTypeVector& fieldTypes() const {
+        return fieldTypes_;
+    }
+};
+
+inline AstSig&
+AstTypeDef::asSig()
+{
+    MOZ_ASSERT(isSig());
+    return *static_cast<AstSig*>(this);
+}
+
+inline AstStruct&
+AstTypeDef::asStruct()
+{
+    MOZ_ASSERT(isStruct());
+    return *static_cast<AstStruct*>(this);
+}
+
+inline const AstSig&
+AstTypeDef::asSig() const
+{
+    MOZ_ASSERT(isSig());
+    return *static_cast<const AstSig*>(this);
+}
+
+inline const AstStruct&
+AstTypeDef::asStruct() const
+{
+    MOZ_ASSERT(isStruct());
+    return *static_cast<const AstStruct*>(this);
+}
+
 const uint32_t AstNodeUnknownOffset = 0;
 
 class AstNode : public AstBase
 {
     uint32_t offset_; // if applicable, offset in the binary format file
 
   public:
     AstNode() : offset_(AstNodeUnknownOffset) {}
@@ -943,25 +1030,25 @@ struct AstResizable
 };
 
 class AstModule : public AstNode
 {
   public:
     typedef AstVector<AstFunc*> FuncVector;
     typedef AstVector<AstImport*> ImportVector;
     typedef AstVector<AstExport*> ExportVector;
-    typedef AstVector<AstSig*> SigVector;
+    typedef AstVector<AstTypeDef*> TypeDefVector;
     typedef AstVector<AstName> NameVector;
     typedef AstVector<AstResizable> AstResizableVector;
 
   private:
     typedef AstHashMap<AstSig*, uint32_t, AstSig> SigMap;
 
     LifoAlloc&           lifo_;
-    SigVector            sigs_;
+    TypeDefVector        types_;
     SigMap               sigMap_;
     ImportVector         imports_;
     NameVector           funcImportNames_;
     AstResizableVector   tables_;
     AstResizableVector   memories_;
     ExportVector         exports_;
     Maybe<AstStartFunc>  startFunc_;
     FuncVector           funcs_;
@@ -969,17 +1056,17 @@ class AstModule : public AstNode
     AstElemSegmentVector elemSegments_;
     AstGlobalVector      globals_;
 
     size_t numGlobalImports_;
 
   public:
     explicit AstModule(LifoAlloc& lifo)
       : lifo_(lifo),
-        sigs_(lifo),
+        types_(lifo),
         sigMap_(lifo),
         imports_(lifo),
         funcImportNames_(lifo),
         tables_(lifo),
         memories_(lifo),
         exports_(lifo),
         funcs_(lifo),
         dataSegments_(lifo),
@@ -1033,38 +1120,41 @@ class AstModule : public AstNode
         return *startFunc_;
     }
     bool declare(AstSig&& sig, uint32_t* sigIndex) {
         SigMap::AddPtr p = sigMap_.lookupForAdd(sig);
         if (p) {
             *sigIndex = p->value();
             return true;
         }
-        *sigIndex = sigs_.length();
+        *sigIndex = types_.length();
         auto* lifoSig = new (lifo_) AstSig(AstName(), std::move(sig));
         return lifoSig &&
-               sigs_.append(lifoSig) &&
-               sigMap_.add(p, sigs_.back(), *sigIndex);
+               types_.append(lifoSig) &&
+               sigMap_.add(p, static_cast<AstSig*>(types_.back()), *sigIndex);
     }
     bool append(AstSig* sig) {
-        uint32_t sigIndex = sigs_.length();
-        if (!sigs_.append(sig))
+        uint32_t sigIndex = types_.length();
+        if (!types_.append(sig))
             return false;
         SigMap::AddPtr p = sigMap_.lookupForAdd(*sig);
         return p || sigMap_.add(p, sig, sigIndex);
     }
-    const SigVector& sigs() const {
-        return sigs_;
+    const TypeDefVector& types() const {
+        return types_;
     }
     bool append(AstFunc* func) {
         return funcs_.append(func);
     }
     const FuncVector& funcs() const {
         return funcs_;
     }
+    bool append(AstStruct* str) {
+        return types_.append(str);
+    }
     bool append(AstImport* imp) {
         switch (imp->kind()) {
           case DefinitionKind::Function:
             if (!funcImportNames_.append(imp->name()))
                 return false;
             break;
           case DefinitionKind::Table:
             if (!tables_.append(AstResizable(imp->limits(), true)))
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -3757,17 +3757,17 @@ class BaseCompiler final : public BaseCo
         CallSiteDesc desc(call.lineOrBytecode, CallSiteDesc::Symbolic);
         masm.call(desc, callee);
     }
 
     // Precondition: sync()
 
     void callIndirect(uint32_t sigIndex, const Stk& indexVal, const FunctionCall& call)
     {
-        const SigWithId& sig = env_.sigs[sigIndex];
+        const SigWithId& sig = env_.types[sigIndex].funcType();
         MOZ_ASSERT(sig.id.kind() != SigIdDesc::Kind::None);
 
         MOZ_ASSERT(env_.tables.length() == 1);
         const TableDesc& table = env_.tables[0];
 
         loadI32(indexVal, RegI32(WasmTableCallIndexReg));
 
         CallSiteDesc desc(call.lineOrBytecode, CallSiteDesc::Dynamic);
@@ -7856,17 +7856,17 @@ BaseCompiler::emitCallIndirect()
     if (!iter_.readCallIndirect(&sigIndex, &callee_, &args_))
         return false;
 
     if (deadCode_)
         return true;
 
     sync();
 
-    const SigWithId& sig = env_.sigs[sigIndex];
+    const SigWithId& sig = env_.types[sigIndex].funcType();
 
     // Stack: ... arg1 .. argn callee
 
     uint32_t numArgs = sig.args().length();
     size_t stackSpace = stackConsumed(numArgs + 1);
 
     // The arguments must be at the stack top for emitCallArgs, so pop the
     // callee if it is on top.  Note this only pops the compiler's stack,
@@ -8825,16 +8825,19 @@ BaseCompiler::emitInstanceCall(uint32_t 
         }
         passArg(t, peek(numArgs - i), &baselineCall);
     }
     builtinInstanceMethodCall(builtin, instanceArg, baselineCall);
     endCall(baselineCall, stackSpace);
 
     popValueStackBy(numArgs);
 
+    // Note, a number of clients of emitInstanceCall currently assume that the
+    // following operation does not destroy ReturnReg.
+
     pushReturnedIfNonVoid(baselineCall, retType);
 }
 
 bool
 BaseCompiler::emitGrowMemory()
 {
     uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
 
--- a/js/src/wasm/WasmBinaryConstants.h
+++ b/js/src/wasm/WasmBinaryConstants.h
@@ -63,16 +63,19 @@ enum class TypeCode
     AnyFunc                              = 0x70,  // SLEB128(-0x10)
 
     // A reference to any type.
     AnyRef                               = 0x6f,
 
     // Type constructor for function types
     Func                                 = 0x60,  // SLEB128(-0x20)
 
+    // Type constructor for structure types - unofficial
+    Struct                               = 0x50,  // SLEB128(-0x30)
+
     // Special code representing the block signature ()->()
     BlockVoid                            = 0x40,  // SLEB128(-0x40)
 
     Limit                                = 0x80
 };
 
 // The representation of a null reference value throughout the compiler.
 
@@ -598,16 +601,17 @@ static const unsigned MaxFuncs          
 static const unsigned MaxImports             =   100000;
 static const unsigned MaxExports             =   100000;
 static const unsigned MaxGlobals             =  1000000;
 static const unsigned MaxDataSegments        =   100000;
 static const unsigned MaxElemSegments        = 10000000;
 static const unsigned MaxTableMaximumLength  = 10000000;
 static const unsigned MaxLocals              =    50000;
 static const unsigned MaxParams              =     1000;
+static const unsigned MaxStructFields        =     1000;
 static const unsigned MaxMemoryMaximumPages  =    65536;
 static const unsigned MaxStringBytes         =   100000;
 static const unsigned MaxModuleBytes         = 1024 * 1024 * 1024;
 static const unsigned MaxFunctionBytes       =  7654321;
 
 // These limits pertain to our WebAssembly implementation only.
 
 static const unsigned MaxTableInitialLength  = 10000000;
--- a/js/src/wasm/WasmBinaryToAST.cpp
+++ b/js/src/wasm/WasmBinaryToAST.cpp
@@ -92,22 +92,22 @@ class AstDecodeContext
     DepthStack depths_;
     const ValTypeVector* locals_;
     AstNameVector blockLabels_;
     uint32_t currentLabelIndex_;
     ExprType retType_;
 
   public:
     AstDecodeContext(JSContext* cx, LifoAlloc& lifo, Decoder& d, AstModule& module,
-                     bool generateNames)
+                     bool generateNames, HasGcTypes hasGcTypes)
      : cx(cx),
        lifo(lifo),
        d(d),
        generateNames(generateNames),
-       env_(CompileMode::Once, Tier::Ion, DebugEnabled::False, HasGcTypes::False,
+       env_(CompileMode::Once, Tier::Ion, DebugEnabled::False, hasGcTypes,
             cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()
             ? Shareable::True
             : Shareable::False),
        module_(module),
        iter_(nullptr),
        exprs_(lifo),
        depths_(lifo),
        locals_(nullptr),
@@ -346,17 +346,17 @@ AstDecodeCallIndirect(AstDecodeContext& 
         return true;
 
     AstDecodeStackItem index = c.popCopy();
 
     AstRef sigRef;
     if (!GenerateRef(c, AstName(u"type"), sigIndex, &sigRef))
         return false;
 
-    const SigWithId& sig = c.env().sigs[sigIndex];
+    const SigWithId& sig = c.env().types[sigIndex].funcType();
     AstExprVector args(c.lifo);
     if (!AstDecodeCallArgs(c, sig, &args))
         return false;
 
     AstCallIndirect* call = new(c.lifo) AstCallIndirect(sigRef, sig.ret(), std::move(args), index.expr);
     if (!call)
         return false;
 
@@ -1953,36 +1953,69 @@ AstDecodeFunctionBody(AstDecodeContext &
 
     return true;
 }
 
 /*****************************************************************************/
 // wasm decoding and generation
 
 static bool
-AstCreateSignatures(AstDecodeContext& c)
+AstCreateTypes(AstDecodeContext& c)
 {
-    SigWithIdVector& sigs = c.env().sigs;
+    uint32_t typeIndexForNames = 0;
+    for (const TypeDef& td : c.env().types) {
+        if (td.isFuncType()) {
+            const Sig& sig = td.funcType();
+
+            AstValTypeVector args(c.lifo);
+            if (!args.appendAll(sig.args()))
+                return false;
+
+            AstSig sigNoName(std::move(args), sig.ret());
 
-    for (size_t sigIndex = 0; sigIndex < sigs.length(); sigIndex++) {
-        const Sig& sig = sigs[sigIndex];
+            AstName sigName;
+            if (!GenerateName(c, AstName(u"type"), typeIndexForNames, &sigName))
+                return false;
+
+            AstSig* astSig = new(c.lifo) AstSig(sigName, std::move(sigNoName));
+            if (!astSig || !c.module().append(astSig))
+                return false;
+        } else if (td.isStructType()) {
+            const StructType& str = td.structType();
+
+            AstValTypeVector fieldTypes(c.lifo);
+            if (!fieldTypes.appendAll(str.fields_))
+                return false;
 
-        AstValTypeVector args(c.lifo);
-        if (!args.appendAll(sig.args()))
-            return false;
+            AstNameVector fieldNames(c.lifo);
+            if (!fieldNames.resize(fieldTypes.length()))
+                return false;
 
-        AstSig sigNoName(std::move(args), sig.ret());
+            // The multiplication ensures that generated field names are unique
+            // within the module, though the resulting namespace is very sparse.
+
+            for (size_t fieldIndex = 0; fieldIndex < fieldTypes.length(); fieldIndex++) {
+                size_t idx = (typeIndexForNames * MaxStructFields) + fieldIndex;
+                if (!GenerateName(c, AstName(u"f"), idx, &fieldNames[fieldIndex]))
+                    return false;
+            }
 
-        AstName sigName;
-        if (!GenerateName(c, AstName(u"type"), sigIndex, &sigName))
-            return false;
+            AstStruct structNoName(std::move(fieldNames), std::move(fieldTypes));
+
+            AstName structName;
+            if (!GenerateName(c, AstName(u"type"), typeIndexForNames, &structName))
+                return false;
 
-        AstSig* astSig = new(c.lifo) AstSig(sigName, std::move(sigNoName));
-        if (!astSig || !c.module().append(astSig))
-            return false;
+            AstStruct* astStruct = new(c.lifo) AstStruct(structName, std::move(structNoName));
+            if (!astStruct || !c.module().append(astStruct))
+                return false;
+        } else {
+            MOZ_CRASH();
+        }
+        typeIndexForNames++;
     }
 
     return true;
 }
 
 static bool
 ToAstName(AstDecodeContext& c, const char* name, AstName* out)
 {
@@ -2225,17 +2258,17 @@ AstCreateElems(AstDecodeContext &c)
 }
 
 static bool
 AstDecodeEnvironment(AstDecodeContext& c)
 {
     if (!DecodeModuleEnvironment(c.d, &c.env()))
         return false;
 
-    if (!AstCreateSignatures(c))
+    if (!AstCreateTypes(c))
         return false;
 
     if (!AstCreateImports(c))
         return false;
 
     if (!AstCreateTables(c))
         return false;
 
@@ -2328,17 +2361,17 @@ wasm::BinaryToAst(JSContext* cx, const u
                   AstModule** module)
 {
     AstModule* result = new(lifo) AstModule(lifo);
     if (!result || !result->init())
         return false;
 
     UniqueChars error;
     Decoder d(bytes, bytes + length, 0, &error, nullptr, /* resilient */ true);
-    AstDecodeContext c(cx, lifo, d, *result, true);
+    AstDecodeContext c(cx, lifo, d, *result, /* generateNames */ true, HasGcTypes::True);
 
     if (!AstDecodeEnvironment(c) ||
         !AstDecodeCodeSection(c) ||
         !AstDecodeModuleTail(c))
     {
         if (error) {
             JS_ReportErrorNumberUTF8(c.cx, GetErrorMessage, nullptr, JSMSG_WASM_COMPILE_ERROR,
                                      error.get());
--- a/js/src/wasm/WasmBinaryToText.cpp
+++ b/js/src/wasm/WasmBinaryToText.cpp
@@ -186,16 +186,17 @@ static bool
 RenderExprType(WasmRenderContext& c, ExprType type)
 {
     switch (type) {
       case ExprType::Void: return true; // ignoring void
       case ExprType::I32: return c.buffer.append("i32");
       case ExprType::I64: return c.buffer.append("i64");
       case ExprType::F32: return c.buffer.append("f32");
       case ExprType::F64: return c.buffer.append("f64");
+      case ExprType::AnyRef: return c.buffer.append("anyref");
       default:;
     }
 
     MOZ_CRASH("bad type");
 }
 
 static bool
 RenderValType(WasmRenderContext& c, ValType type)
@@ -205,16 +206,22 @@ RenderValType(WasmRenderContext& c, ValT
 
 static bool
 RenderName(WasmRenderContext& c, const AstName& name)
 {
     return c.buffer.append(name.begin(), name.end());
 }
 
 static bool
+RenderNonemptyName(WasmRenderContext& c, const AstName& name)
+{
+    return name.empty() || (RenderName(c, name) && c.buffer.append(' '));
+}
+
+static bool
 RenderRef(WasmRenderContext& c, const AstRef& ref)
 {
     if (ref.name().empty())
         return RenderInt32(c, ref.index());
 
     return RenderName(c, ref.name());
 }
 
@@ -1476,22 +1483,18 @@ RenderSignature(WasmRenderContext& c, co
 {
     uint32_t paramsNum = sig.args().length();
 
     if (maybeLocals) {
         for (uint32_t i = 0; i < paramsNum; i++) {
             if (!c.buffer.append(" (param "))
                 return false;
             const AstName& name = (*maybeLocals)[i];
-            if (!name.empty()) {
-                if (!RenderName(c, name))
-                    return false;
-                if (!c.buffer.append(" "))
-                    return false;
-            }
+            if (!RenderNonemptyName(c, name))
+                return false;
             ValType arg = sig.args()[i];
             if (!RenderValType(c, arg))
                 return false;
             if (!c.buffer.append(")"))
                 return false;
         }
     } else if (paramsNum > 0) {
         if (!c.buffer.append(" (param"))
@@ -1513,44 +1516,86 @@ RenderSignature(WasmRenderContext& c, co
             return false;
         if (!c.buffer.append(")"))
             return false;
     }
     return true;
 }
 
 static bool
-RenderTypeSection(WasmRenderContext& c, const AstModule::SigVector& sigs)
+RenderFields(WasmRenderContext& c, const AstStruct& str)
 {
-    uint32_t numSigs = sigs.length();
-    if (!numSigs)
-        return true;
+    const AstNameVector& fieldNames = str.fieldNames();
+    const AstValTypeVector& fieldTypes = str.fieldTypes();
 
-    for (uint32_t sigIndex = 0; sigIndex < numSigs; sigIndex++) {
-        const AstSig* sig = sigs[sigIndex];
+    for (uint32_t fieldIndex = 0; fieldIndex < fieldTypes.length(); fieldIndex++) {
+        if (!c.buffer.append("\n"))
+            return false;
         if (!RenderIndent(c))
             return false;
-        if (!c.buffer.append("(type"))
+        if (!c.buffer.append("(field "))
+            return false;
+        if (!RenderNonemptyName(c, fieldNames[fieldIndex]))
             return false;
-        if (!sig->name().empty()) {
-            if (!c.buffer.append(" "))
-                return false;
-            if (!RenderName(c, sig->name()))
-                return false;
-        }
-        if (!c.buffer.append(" (func"))
+        if (!RenderValType(c, fieldTypes[fieldIndex]))
             return false;
-        if (!RenderSignature(c, *sig))
-            return false;
-        if (!c.buffer.append("))\n"))
+        if (!c.buffer.append(')'))
             return false;
     }
     return true;
 }
 
+template<size_t ArrayLength>
+static bool
+RenderTypeStart(WasmRenderContext& c, const AstName& name, const char (&keyword)[ArrayLength])
+{
+    if (!RenderIndent(c))
+        return false;
+    if (!c.buffer.append("(type "))
+        return false;
+    if (!RenderNonemptyName(c, name))
+        return false;
+    if (!c.buffer.append("("))
+        return false;
+    return c.buffer.append(keyword);
+}
+
+static bool
+RenderTypeEnd(WasmRenderContext& c)
+{
+    return c.buffer.append("))\n");
+}
+
+static bool
+RenderTypeSection(WasmRenderContext& c, const AstModule::TypeDefVector& types)
+{
+    for (uint32_t typeIndex = 0; typeIndex < types.length(); typeIndex++) {
+        const AstTypeDef* type = types[typeIndex];
+        if (type->isSig()) {
+            const AstSig* sig = &type->asSig();
+            if (!RenderTypeStart(c, sig->name(), "func"))
+                return false;
+            if (!RenderSignature(c, *sig))
+                return false;
+        } else {
+            const AstStruct* strukt = &type->asStruct();
+            if (!RenderTypeStart(c, strukt->name(), "struct"))
+                return false;
+            c.indent++;
+            if (!RenderFields(c, *strukt))
+                return false;
+            c.indent--;
+        }
+        if (!RenderTypeEnd(c))
+            return false;
+    }
+
+    return true;
+}
+
 static bool
 RenderLimits(WasmRenderContext& c, const Limits& limits)
 {
     if (!RenderInt32(c, limits.initial))
         return false;
     if (limits.maximum) {
         if (!c.buffer.append(" "))
             return false;
@@ -1750,17 +1795,17 @@ RenderImport(WasmRenderContext& c, AstIm
 
     if (!c.buffer.append("\" "))
         return false;
 
     switch (import.kind()) {
       case DefinitionKind::Function: {
         if (!c.buffer.append("(func"))
             return false;
-        const AstSig* sig = module.sigs()[import.funcSig().index()];
+        const AstSig* sig = &module.types()[import.funcSig().index()]->asSig();
         if (!RenderSignature(c, *sig))
             return false;
         if (!c.buffer.append(")"))
             return false;
         break;
       }
       case DefinitionKind::Table: {
         if (!RenderResizableTable(c, import.limits()))
@@ -1853,19 +1898,19 @@ RenderExportSection(WasmRenderContext& c
     for (uint32_t i = 0; i < numExports; i++) {
         if (!RenderExport(c, *exports[i], funcImportNames, funcs))
             return false;
     }
     return true;
 }
 
 static bool
-RenderFunctionBody(WasmRenderContext& c, AstFunc& func, const AstModule::SigVector& sigs)
+RenderFunctionBody(WasmRenderContext& c, AstFunc& func, const AstModule::TypeDefVector& types)
 {
-    const AstSig* sig = sigs[func.sig().index()];
+    const AstSig* sig = &types[func.sig().index()]->asSig();
 
     uint32_t argsNum = sig->args().length();
     uint32_t localsNum = func.vars().length();
     if (localsNum > 0) {
         if (!RenderIndent(c))
             return false;
         for (uint32_t i = 0; i < localsNum; i++) {
             if (!c.buffer.append("(local "))
@@ -1899,23 +1944,23 @@ RenderFunctionBody(WasmRenderContext& c,
             return false;
     }
 
     return true;
 }
 
 static bool
 RenderCodeSection(WasmRenderContext& c, const AstModule::FuncVector& funcs,
-                  const AstModule::SigVector& sigs)
+                  const AstModule::TypeDefVector& types)
 {
     uint32_t numFuncBodies = funcs.length();
     for (uint32_t funcIndex = 0; funcIndex < numFuncBodies; funcIndex++) {
         AstFunc* func = funcs[funcIndex];
         uint32_t sigIndex = func->sig().index();
-        AstSig* sig = sigs[sigIndex];
+        AstSig* sig = &types[sigIndex]->asSig();
 
         if (!RenderIndent(c))
             return false;
         if (!c.buffer.append("(func "))
             return false;
         if (!func->name().empty()) {
             if (!RenderName(c, func->name()))
                 return false;
@@ -1924,17 +1969,17 @@ RenderCodeSection(WasmRenderContext& c, 
         if (!RenderSignature(c, *sig, &(func->locals())))
             return false;
         if (!c.buffer.append("\n"))
             return false;
 
         c.currentFuncIndex = funcIndex;
 
         c.indent++;
-        if (!RenderFunctionBody(c, *func, sigs))
+        if (!RenderFunctionBody(c, *func, types))
             return false;
         c.indent--;
         if (!RenderIndent(c))
             return false;
         if (!c.buffer.append(")\n"))
             return false;
     }
 
@@ -2021,17 +2066,17 @@ RenderStartSection(WasmRenderContext& c,
 static bool
 RenderModule(WasmRenderContext& c, AstModule& module)
 {
     if (!c.buffer.append("(module\n"))
         return false;
 
     c.indent++;
 
-    if (!RenderTypeSection(c, module.sigs()))
+    if (!RenderTypeSection(c, module.types()))
         return false;
 
     if (!RenderImportSection(c, module))
         return false;
 
     if (!RenderTableSection(c, module))
         return false;
 
@@ -2045,17 +2090,17 @@ RenderModule(WasmRenderContext& c, AstMo
         return false;
 
     if (!RenderStartSection(c, module))
         return false;
 
     if (!RenderElemSection(c, module))
         return false;
 
-    if (!RenderCodeSection(c, module.funcs(), module.sigs()))
+    if (!RenderCodeSection(c, module.funcs(), module.types()))
         return false;
 
     if (!RenderDataSection(c, module))
         return false;
 
     c.indent--;
 
     if (!c.buffer.append(")"))
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -238,17 +238,21 @@ ModuleGenerator::init(Metadata* maybeAsm
     }
 
     for (TableDesc& table : env_->tables) {
         if (!allocateGlobalBytes(sizeof(TableTls), sizeof(void*), &table.globalDataOffset))
             return false;
     }
 
     if (!isAsmJS()) {
-        for (SigWithId& sig : env_->sigs) {
+        for (TypeDef& td : env_->types) {
+            if (!td.isFuncType())
+                continue;
+
+            SigWithId& sig = td.funcType();
             if (SigIdDesc::isGlobal(sig)) {
                 uint32_t globalDataOffset;
                 if (!allocateGlobalBytes(sizeof(void*), sizeof(void*), &globalDataOffset))
                     return false;
 
                 sig.id = SigIdDesc::global(sig, globalDataOffset);
 
                 Sig copy;
@@ -974,24 +978,31 @@ ModuleGenerator::finishModule(const Shar
     auto codeTier = js::MakeUnique<CodeTier>(std::move(metadataTier_), std::move(moduleSegment));
     if (!codeTier)
         return nullptr;
 
     MutableCode code = js_new<Code>(std::move(codeTier), *metadata_, std::move(jumpTables));
     if (!code || !code->initialize(bytecode, *linkDataTier_))
         return nullptr;
 
+    StructTypeVector structTypes;
+    for (TypeDef& td : env_->types) {
+        if (td.isStructType() && !structTypes.append(std::move(td.structType())))
+            return nullptr;
+    }
+
     SharedModule module(js_new<Module>(std::move(assumptions_),
                                        *code,
                                        std::move(maybeDebuggingBytes),
                                        LinkData(std::move(linkDataTier_)),
                                        std::move(env_->imports),
                                        std::move(env_->exports),
                                        std::move(env_->dataSegments),
                                        std::move(env_->elemSegments),
+                                       std::move(structTypes),
                                        bytecode));
     if (!module)
         return nullptr;
 
     if (mode() == CompileMode::Tier1)
         module->startTier2(*compileArgs_);
 
     return module;
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -1207,17 +1207,17 @@ class FunctionCompiler
     bool callIndirect(uint32_t sigIndex, MDefinition* index, const CallCompileState& call,
                       MDefinition** def)
     {
         if (inDeadCode()) {
             *def = nullptr;
             return true;
         }
 
-        const SigWithId& sig = env_.sigs[sigIndex];
+        const SigWithId& sig = env_.types[sigIndex].funcType();
 
         CalleeDesc callee;
         if (env_.isAsmJS()) {
             MOZ_ASSERT(sig.id.kind() == SigIdDesc::Kind::None);
             const TableDesc& table = env_.tables[env_.asmJSSigToTableIndex[sigIndex]];
             MOZ_ASSERT(IsPowerOfTwo(table.limits.initial));
             MOZ_ASSERT(!table.external);
 
@@ -2230,17 +2230,17 @@ EmitCallIndirect(FunctionCompiler& f, bo
     } else {
         if (!f.iter().readCallIndirect(&sigIndex, &callee, &args))
             return false;
     }
 
     if (f.inDeadCode())
         return true;
 
-    const Sig& sig = f.env().sigs[sigIndex];
+    const Sig& sig = f.env().types[sigIndex].funcType();
 
     CallCompileState call(f, lineOrBytecode);
     if (!EmitCallArgs(f, sig, args, &call))
         return false;
 
     MDefinition* def;
     if (!f.callIndirect(sigIndex, callee, call, &def))
         return false;
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -393,16 +393,17 @@ Module::compiledSerializedSize() const
         return 0;
 
     return assumptions_.serializedSize() +
            linkData_.serializedSize() +
            SerializedVectorSize(imports_) +
            SerializedVectorSize(exports_) +
            SerializedPodVectorSize(dataSegments_) +
            SerializedVectorSize(elemSegments_) +
+           SerializedVectorSize(structTypes_) +
            code_->serializedSize();
 }
 
 /* virtual */ void
 Module::compiledSerialize(uint8_t* compiledBegin, size_t compiledSize) const
 {
     MOZ_ASSERT(!tiering_.lock()->active);
 
@@ -418,16 +419,17 @@ Module::compiledSerialize(uint8_t* compi
 
     uint8_t* cursor = compiledBegin;
     cursor = assumptions_.serialize(cursor);
     cursor = linkData_.serialize(cursor);
     cursor = SerializeVector(cursor, imports_);
     cursor = SerializeVector(cursor, exports_);
     cursor = SerializePodVector(cursor, dataSegments_);
     cursor = SerializeVector(cursor, elemSegments_);
+    cursor = SerializeVector(cursor, structTypes_);
     cursor = code_->serialize(cursor, linkData_);
     MOZ_RELEASE_ASSERT(cursor == compiledBegin + compiledSize);
 }
 
 /* static */ bool
 Module::assumptionsMatch(const Assumptions& current, const uint8_t* compiledBegin, size_t remain)
 {
     Assumptions cached;
@@ -486,27 +488,33 @@ Module::deserialize(const uint8_t* bytec
     if (!cursor)
         return nullptr;
 
     SharedCode code;
     cursor = Code::deserialize(cursor, *bytecode, linkData, *metadata, &code);
     if (!cursor)
         return nullptr;
 
+    StructTypeVector structTypes;
+    cursor = DeserializeVector(cursor, &structTypes);
+    if (!cursor)
+        return nullptr;
+
     MOZ_RELEASE_ASSERT(cursor == compiledBegin + compiledSize);
     MOZ_RELEASE_ASSERT(!!maybeMetadata == code->metadata().isAsmJS());
 
     return js_new<Module>(std::move(assumptions),
                           *code,
                           nullptr,            // Serialized code is never debuggable
                           std::move(linkData),
                           std::move(imports),
                           std::move(exports),
                           std::move(dataSegments),
                           std::move(elemSegments),
+                          std::move(structTypes),
                           *bytecode);
 }
 
 /* virtual */ JSObject*
 Module::createObject(JSContext* cx)
 {
     if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_WebAssembly))
         return nullptr;
@@ -622,16 +630,17 @@ Module::addSizeOfMisc(MallocSizeOf mallo
     code_->addSizeOfMiscIfNotSeen(mallocSizeOf, seenMetadata, seenCode, code, data);
     *data += mallocSizeOf(this) +
              assumptions_.sizeOfExcludingThis(mallocSizeOf) +
              linkData_.sizeOfExcludingThis(mallocSizeOf) +
              SizeOfVectorExcludingThis(imports_, mallocSizeOf) +
              SizeOfVectorExcludingThis(exports_, mallocSizeOf) +
              dataSegments_.sizeOfExcludingThis(mallocSizeOf) +
              SizeOfVectorExcludingThis(elemSegments_, mallocSizeOf) +
+             SizeOfVectorExcludingThis(structTypes_, mallocSizeOf) +
              bytecode_->sizeOfIncludingThisIfNotSeen(mallocSizeOf, seenBytes);
     if (unlinkedCodeForDebugging_)
         *data += unlinkedCodeForDebugging_->sizeOfExcludingThis(mallocSizeOf);
 }
 
 
 // Extracting machine code as JS object. The result has the "code" property, as
 // a Uint8Array, and the "segments" property as array objects. The objects
--- a/js/src/wasm/WasmModule.h
+++ b/js/src/wasm/WasmModule.h
@@ -130,16 +130,17 @@ class Module : public JS::WasmModule
     const Assumptions       assumptions_;
     const SharedCode        code_;
     const UniqueConstBytes  unlinkedCodeForDebugging_;
     const LinkData          linkData_;
     const ImportVector      imports_;
     const ExportVector      exports_;
     const DataSegmentVector dataSegments_;
     const ElemSegmentVector elemSegments_;
+    const StructTypeVector  structTypes_;
     const SharedBytes       bytecode_;
     ExclusiveTiering        tiering_;
 
     // `codeIsBusy_` is set to false initially and then to true when `code_` is
     // already being used for an instance and can't be shared because it may be
     // patched by the debugger. Subsequent instances must then create copies
     // by linking the `unlinkedCodeForDebugging_`.
 
@@ -165,25 +166,27 @@ class Module : public JS::WasmModule
     Module(Assumptions&& assumptions,
            const Code& code,
            UniqueConstBytes unlinkedCodeForDebugging,
            LinkData&& linkData,
            ImportVector&& imports,
            ExportVector&& exports,
            DataSegmentVector&& dataSegments,
            ElemSegmentVector&& elemSegments,
+           StructTypeVector&& structTypes,
            const ShareableBytes& bytecode)
       : assumptions_(std::move(assumptions)),
         code_(&code),
         unlinkedCodeForDebugging_(std::move(unlinkedCodeForDebugging)),
         linkData_(std::move(linkData)),
         imports_(std::move(imports)),
         exports_(std::move(exports)),
         dataSegments_(std::move(dataSegments)),
         elemSegments_(std::move(elemSegments)),
+        structTypes_(std::move(structTypes)),
         bytecode_(&bytecode),
         tiering_(mutexid::WasmModuleTieringLock),
         codeIsBusy_(false)
     {
         MOZ_ASSERT_IF(metadata().debugEnabled, unlinkedCodeForDebugging_);
     }
     ~Module() override { /* Note: can be called on any thread */ }
 
--- a/js/src/wasm/WasmOpIter.h
+++ b/js/src/wasm/WasmOpIter.h
@@ -1727,30 +1727,33 @@ OpIter<Policy>::readCallIndirect(uint32_
     MOZ_ASSERT(Classify(op_) == OpKind::CallIndirect);
 
     if (!env_.tables.length())
         return fail("can't call_indirect without a table");
 
     if (!readVarU32(sigIndex))
         return fail("unable to read call_indirect signature index");
 
-    if (*sigIndex >= env_.numSigs())
+    if (*sigIndex >= env_.numTypes())
         return fail("signature index out of range");
 
     uint8_t flags;
     if (!readFixedU8(&flags))
         return false;
 
     if (flags != uint8_t(MemoryTableFlags::Default))
         return fail("unexpected flags");
 
     if (!popWithType(ValType::I32, callee))
         return false;
 
-    const Sig& sig = env_.sigs[*sigIndex];
+    if (!env_.types[*sigIndex].isFuncType())
+        return fail("expected signature type");
+
+    const Sig& sig = env_.types[*sigIndex].funcType();
 
     if (!popCallArgs(sig.args(), argValues))
         return false;
 
     return push(sig.ret());
 }
 
 template <typename Policy>
@@ -1784,20 +1787,23 @@ template <typename Policy>
 inline bool
 OpIter<Policy>::readOldCallIndirect(uint32_t* sigIndex, Value* callee, ValueVector* argValues)
 {
     MOZ_ASSERT(Classify(op_) == OpKind::OldCallIndirect);
 
     if (!readVarU32(sigIndex))
         return fail("unable to read call_indirect signature index");
 
-    if (*sigIndex >= env_.numSigs())
+    if (*sigIndex >= env_.numTypes())
         return fail("signature index out of range");
 
-    const Sig& sig = env_.sigs[*sigIndex];
+    if (!env_.types[*sigIndex].isFuncType())
+        return fail("expected signature type");
+
+    const Sig& sig = env_.types[*sigIndex].funcType();
 
     if (!popCallArgs(sig.args(), argValues))
         return false;
 
     if (!popWithType(ValType::I32, callee))
         return false;
 
     if (!push(sig.ret()))
--- a/js/src/wasm/WasmTextToBinary.cpp
+++ b/js/src/wasm/WasmTextToBinary.cpp
@@ -89,16 +89,17 @@ class WasmToken
         End,
         EndOfFile,
         Equal,
         Error,
         Export,
 #ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
         ExtraConversionOpcode,
 #endif
+        Field,
         Float,
         Func,
         GetGlobal,
         GetLocal,
         Global,
         GrowMemory,
         If,
         Import,
@@ -122,16 +123,17 @@ class WasmToken
         RefNull,
         Result,
         Return,
         SetGlobal,
         SetLocal,
         Shared,
         SignedInteger,
         Start,
+        Struct,
         Store,
         Table,
         TeeLocal,
         TernaryOpcode,
         Text,
         Then,
         Type,
         UnaryOpcode,
@@ -359,16 +361,17 @@ class WasmToken
           case Data:
           case Elem:
           case Else:
           case EndOfFile:
           case Equal:
           case End:
           case Error:
           case Export:
+          case Field:
           case Float:
           case Func:
           case Global:
           case Mutable:
           case Import:
           case Index:
           case Memory:
           case NegativeZero:
@@ -377,16 +380,17 @@ class WasmToken
           case Name:
           case Offset:
           case OpenParen:
           case Param:
           case Result:
           case Shared:
           case SignedInteger:
           case Start:
+          case Struct:
           case Table:
           case Text:
           case Then:
           case Type:
           case UnsignedInteger:
           case ValueType:
             return false;
           case Invalid:
@@ -946,16 +950,19 @@ WasmTokenStream::next()
             return WasmToken(WasmToken::Else, begin, cur_);
         if (consume(u"end"))
             return WasmToken(WasmToken::End, begin, cur_);
         if (consume(u"export"))
             return WasmToken(WasmToken::Export, begin, cur_);
         break;
 
       case 'f':
+        if (consume(u"field"))
+            return WasmToken(WasmToken::Field, begin, cur_);
+
         if (consume(u"func"))
             return WasmToken(WasmToken::Func, begin, cur_);
 
         if (consume(u"f32")) {
             if (!consume(u"."))
                 return WasmToken(WasmToken::ValueType, ValType::F32, begin, cur_);
 
             switch (*cur_) {
@@ -1703,16 +1710,18 @@ WasmTokenStream::next()
         if (consume(u"set_local"))
             return WasmToken(WasmToken::SetLocal, begin, cur_);
 #ifdef ENABLE_WASM_THREAD_OPS
         if (consume(u"shared"))
             return WasmToken(WasmToken::Shared, begin, cur_);
 #endif
         if (consume(u"start"))
             return WasmToken(WasmToken::Start, begin, cur_);
+        if (consume(u"struct"))
+            return WasmToken(WasmToken::Struct, begin, cur_);
         break;
 
       case 't':
         if (consume(u"table"))
             return WasmToken(WasmToken::Table, begin, cur_);
         if (consume(u"tee_local"))
             return WasmToken(WasmToken::TeeLocal, begin, cur_);
         if (consume(u"then"))
@@ -3326,34 +3335,81 @@ ParseFunc(WasmParseContext& c, AstModule
             return false;
         sigRef.setIndex(sigIndex);
     }
 
     auto* func = new(c.lifo) AstFunc(funcName, sigRef, std::move(vars), std::move(locals), std::move(body));
     return func && module->append(func);
 }
 
-static AstSig*
+static bool
+ParseGlobalType(WasmParseContext& c, WasmToken* typeToken, bool* isMutable);
+
+static bool
+ParseStructFields(WasmParseContext& c, AstStruct* str)
+{
+    AstNameVector    names(c.lifo);
+    AstValTypeVector types(c.lifo);
+
+    while (true) {
+        if (!c.ts.getIf(WasmToken::OpenParen))
+            break;
+
+        if (!c.ts.match(WasmToken::Field, c.error))
+            return false;
+
+        AstName name = c.ts.getIfName();
+
+        WasmToken typeToken;
+        bool isMutable;
+        if (!ParseGlobalType(c, &typeToken, &isMutable))
+            return false;
+        if (!c.ts.match(WasmToken::CloseParen, c.error))
+            return false;
+
+        if (!names.append(name))
+            return false;
+        if (!types.append(typeToken.valueType()))
+            return false;
+    }
+
+    *str = AstStruct(std::move(names), std::move(types));
+    return true;
+}
+
+static AstTypeDef*
 ParseTypeDef(WasmParseContext& c)
 {
     AstName name = c.ts.getIfName();
 
     if (!c.ts.match(WasmToken::OpenParen, c.error))
         return nullptr;
-    if (!c.ts.match(WasmToken::Func, c.error))
+
+    AstTypeDef* type = nullptr;
+    if (c.ts.getIf(WasmToken::Func)) {
+        AstSig sig(c.lifo);
+        if (!ParseFuncSig(c, &sig))
+            return nullptr;
+
+        type = new(c.lifo) AstSig(name, std::move(sig));
+    } else if (c.ts.getIf(WasmToken::Struct)) {
+        AstStruct str(c.lifo);
+        if (!ParseStructFields(c, &str))
+            return nullptr;
+
+        type = new(c.lifo) AstStruct(name, std::move(str));
+    } else {
+        c.ts.generateError(c.ts.peek(), "bad type definition", c.error);
         return nullptr;
-
-    AstSig sig(c.lifo);
-    if (!ParseFuncSig(c, &sig))
-        return nullptr;
+    }
 
     if (!c.ts.match(WasmToken::CloseParen, c.error))
         return nullptr;
 
-    return new(c.lifo) AstSig(name, std::move(sig));
+    return type;
 }
 
 static bool
 MaybeParseOwnerIndex(WasmParseContext& c)
 {
     if (c.ts.peek().kind() == WasmToken::Index) {
         WasmToken elemIndex = c.ts.get();
         if (elemIndex.index()) {
@@ -3917,18 +3973,20 @@ ParseModule(const char16_t* text, uintpt
         return ParseBinaryModule(c, module);
     }
 
     while (c.ts.getIf(WasmToken::OpenParen)) {
         WasmToken section = c.ts.get();
 
         switch (section.kind()) {
           case WasmToken::Type: {
-            AstSig* sig = ParseTypeDef(c);
-            if (!sig || !module->append(sig))
+            AstTypeDef* typeDef = ParseTypeDef(c);
+            if (!typeDef)
+                return nullptr;
+            if (!module->append(static_cast<AstSig*>(typeDef)))
                 return nullptr;
             break;
           }
           case WasmToken::Start: {
             if (!ParseStartFunc(c, section, module))
                 return nullptr;
             break;
           }
@@ -4003,16 +4061,17 @@ class Resolver
     UniqueChars* error_;
     AstNameMap varMap_;
     AstNameMap globalMap_;
     AstNameMap sigMap_;
     AstNameMap funcMap_;
     AstNameMap importMap_;
     AstNameMap tableMap_;
     AstNameMap memoryMap_;
+    AstNameMap typeMap_;
     AstNameVector targetStack_;
 
     bool registerName(AstNameMap& map, AstName name, size_t index) {
         AstNameMap::AddPtr p = map.lookupForAdd(name);
         if (!p) {
             if (!map.add(p, name, index))
                 return false;
         } else {
@@ -4040,24 +4099,26 @@ class Resolver
       : error_(error),
         varMap_(lifo),
         globalMap_(lifo),
         sigMap_(lifo),
         funcMap_(lifo),
         importMap_(lifo),
         tableMap_(lifo),
         memoryMap_(lifo),
+        typeMap_(lifo),
         targetStack_(lifo)
     {}
     bool init() {
         return sigMap_.init() &&
                funcMap_.init() &&
                importMap_.init() &&
                tableMap_.init() &&
                memoryMap_.init() &&
+               typeMap_.init() &&
                varMap_.init() &&
                globalMap_.init();
     }
     void beginFunc() {
         varMap_.clear();
         MOZ_ASSERT(targetStack_.empty());
     }
 
@@ -4067,16 +4128,17 @@ class Resolver
     }
 
     REGISTER(Sig, sigMap_)
     REGISTER(Func, funcMap_)
     REGISTER(Var, varMap_)
     REGISTER(Global, globalMap_)
     REGISTER(Table, tableMap_)
     REGISTER(Memory, memoryMap_)
+    REGISTER(Type, typeMap_)
 
 #undef REGISTER
 
     bool pushTarget(AstName name) {
         return targetStack_.append(name);
     }
     void popTarget(AstName name) {
         MOZ_ASSERT(targetStack_.back() == name);
@@ -4092,16 +4154,17 @@ class Resolver
     }
 
     RESOLVE(sigMap_, Signature)
     RESOLVE(funcMap_, Function)
     RESOLVE(varMap_, Local)
     RESOLVE(globalMap_, Global)
     RESOLVE(tableMap_, Table)
     RESOLVE(memoryMap_, Memory)
+    RESOLVE(typeMap_, Type)
 
 #undef RESOLVE
 
     bool resolveBranchTarget(AstRef& ref) {
         if (ref.name().empty())
             return true;
         for (size_t i = 0, e = targetStack_.length(); i < e; i++) {
             if (targetStack_[e - i - 1] == ref.name()) {
@@ -4533,21 +4596,28 @@ ResolveFunc(Resolver& r, AstFunc& func)
 static bool
 ResolveModule(LifoAlloc& lifo, AstModule* module, UniqueChars* error)
 {
     Resolver r(lifo, error);
 
     if (!r.init())
         return false;
 
-    size_t numSigs = module->sigs().length();
-    for (size_t i = 0; i < numSigs; i++) {
-        AstSig* sig = module->sigs()[i];
-        if (!r.registerSigName(sig->name(), i))
-            return r.fail("duplicate signature");
+    size_t numTypes = module->types().length();
+    for (size_t i = 0; i < numTypes; i++) {
+        AstTypeDef* ty = module->types()[i];
+        if (ty->isSig()) {
+            AstSig* sig = static_cast<AstSig*>(ty);
+            if (!r.registerSigName(sig->name(), i))
+                return r.fail("duplicate signature");
+        } else if (ty->isStruct()) {
+            AstStruct* str = static_cast<AstStruct*>(ty);
+            if (!r.registerTypeName(str->name(), i))
+                return r.fail("duplicate struct");
+        }
     }
 
     size_t lastFuncIndex = 0;
     size_t lastGlobalIndex = 0;
     size_t lastMemoryIndex = 0;
     size_t lastTableIndex = 0;
     for (AstImport* imp : module->imports()) {
         switch (imp->kind()) {
@@ -5164,44 +5234,61 @@ EncodeExpr(Encoder& e, AstExpr& expr)
 }
 
 /*****************************************************************************/
 // wasm AST binary serialization
 
 static bool
 EncodeTypeSection(Encoder& e, AstModule& module)
 {
-    if (module.sigs().empty())
+    if (module.types().empty())
         return true;
 
     size_t offset;
     if (!e.startSection(SectionId::Type, &offset))
         return false;
 
-    if (!e.writeVarU32(module.sigs().length()))
+    if (!e.writeVarU32(module.types().length()))
         return false;
 
-    for (AstSig* sig : module.sigs()) {
-        if (!e.writeVarU32(uint32_t(TypeCode::Func)))
-            return false;
-
-        if (!e.writeVarU32(sig->args().length()))
-            return false;
-
-        for (ValType t : sig->args()) {
-            if (!e.writeValType(t))
+    for (AstTypeDef* ty : module.types()) {
+        if (ty->isSig()) {
+            AstSig* sig = static_cast<AstSig*>(ty);
+            if (!e.writeVarU32(uint32_t(TypeCode::Func)))
+                return false;
+
+            if (!e.writeVarU32(sig->args().length()))
+                return false;
+
+            for (ValType t : sig->args()) {
+                if (!e.writeValType(t))
+                    return false;
+            }
+
+            if (!e.writeVarU32(!IsVoid(sig->ret())))
                 return false;
-        }
-
-        if (!e.writeVarU32(!IsVoid(sig->ret())))
-            return false;
-
-        if (!IsVoid(sig->ret())) {
-            if (!e.writeValType(NonVoidToValType(sig->ret())))
+
+            if (!IsVoid(sig->ret())) {
+                if (!e.writeValType(NonVoidToValType(sig->ret())))
+                    return false;
+            }
+        } else if (ty->isStruct()) {
+            AstStruct* str = static_cast<AstStruct*>(ty);
+            if (!e.writeVarU32(uint32_t(TypeCode::Struct)))
                 return false;
+
+            if (!e.writeVarU32(str->fieldTypes().length()))
+                return false;
+
+            for (ValType t : str->fieldTypes()) {
+                if (!e.writeValType(t))
+                    return false;
+            }
+        } else {
+            MOZ_CRASH();
         }
     }
 
     e.finishSection(offset);
     return true;
 }
 
 static bool
--- a/js/src/wasm/WasmTypes.cpp
+++ b/js/src/wasm/WasmTypes.cpp
@@ -323,16 +323,46 @@ SigWithId::deserialize(const uint8_t* cu
 
 size_t
 SigWithId::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
     return Sig::sizeOfExcludingThis(mallocSizeOf);
 }
 
 size_t
+StructType::serializedSize() const
+{
+    return SerializedPodVectorSize(fields_) +
+           SerializedPodVectorSize(fieldOffsets_);
+}
+
+uint8_t*
+StructType::serialize(uint8_t* cursor) const
+{
+    cursor = SerializePodVector(cursor, fields_);
+    cursor = SerializePodVector(cursor, fieldOffsets_);
+    return cursor;
+}
+
+const uint8_t*
+StructType::deserialize(const uint8_t* cursor)
+{
+    (cursor = DeserializePodVector(cursor, &fields_));
+    (cursor = DeserializePodVector(cursor, &fieldOffsets_));
+    return cursor;
+}
+
+size_t
+StructType::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
+{
+    return fields_.sizeOfExcludingThis(mallocSizeOf) +
+           fieldOffsets_.sizeOfExcludingThis(mallocSizeOf);
+}
+
+size_t
 Import::serializedSize() const
 {
     return module.serializedSize() +
            field.serializedSize() +
            sizeof(kind);
 }
 
 uint8_t*
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -19,17 +19,16 @@
 #ifndef wasm_types_h
 #define wasm_types_h
 
 #include "mozilla/Alignment.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/HashFunctions.h"
 #include "mozilla/Maybe.h"
-#include "mozilla/Move.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/Unused.h"
 
 #include "NamespaceImports.h"
 
 #include "ds/LifoAlloc.h"
 #include "jit/IonTypes.h"
 #include "js/RefCounted.h"
@@ -748,16 +747,41 @@ class Sig
 
 struct SigHashPolicy
 {
     typedef const Sig& Lookup;
     static HashNumber hash(Lookup sig) { return sig.hash(); }
     static bool match(const Sig* lhs, Lookup rhs) { return *lhs == rhs; }
 };
 
+// Structure type.
+//
+// The Module owns a dense array of Struct values that represent the structure
+// types that the module knows about.  It is created from the sparse array of
+// types in the ModuleEnvironment when the Module is created.
+
+class StructType
+{
+  public:
+    ValTypeVector fields_;       // Scalar types of fields
+    Uint32Vector  fieldOffsets_; // Byte offsets into an object for corresponding field
+
+  public:
+    StructType() : fields_(), fieldOffsets_() {}
+
+    StructType(ValTypeVector&& fields, Uint32Vector&& fieldOffsets)
+      : fields_(std::move(fields)),
+        fieldOffsets_(std::move(fieldOffsets))
+    {}
+
+    WASM_DECLARE_SERIALIZABLE(StructType)
+};
+
+typedef Vector<StructType, 0, SystemAllocPolicy> StructTypeVector;
+
 // An InitExpr describes a deferred initializer expression, used to initialize
 // a global or a table element offset. Such expressions are created during
 // decoding and actually executed on module instantiation.
 
 class InitExpr
 {
   public:
     enum class Kind {
@@ -1108,16 +1132,107 @@ struct SigWithId : Sig
     void operator=(Sig&& rhs) { Sig::operator=(std::move(rhs)); }
 
     WASM_DECLARE_SERIALIZABLE(SigWithId)
 };
 
 typedef Vector<SigWithId, 0, SystemAllocPolicy> SigWithIdVector;
 typedef Vector<const SigWithId*, 0, SystemAllocPolicy> SigWithIdPtrVector;
 
+// A tagged container for the various types that can be present in a wasm
+// module's type section.
+
+class TypeDef
+{
+    enum { IsFuncType, IsStructType, IsNone } tag_;
+    union {
+        SigWithId  funcType_;
+        StructType structType_;
+    };
+
+  public:
+    TypeDef() : tag_(IsNone), structType_(StructType()) {}
+
+    TypeDef(Sig&& sig) : tag_(IsFuncType), funcType_(SigWithId(std::move(sig))) {}
+
+    TypeDef(StructType&& structType) : tag_(IsStructType), structType_(std::move(structType)) {}
+
+    TypeDef(TypeDef&& td) : tag_(td.tag_), structType_(StructType()) {
+        switch (tag_) {
+          case IsFuncType:   funcType_ = std::move(td.funcType_); break;
+          case IsStructType: structType_ = std::move(td.structType_); break;
+          case IsNone:       break;
+        }
+    }
+
+    ~TypeDef() {
+        switch (tag_) {
+          case IsFuncType:   funcType_.~SigWithId(); break;
+          case IsStructType: structType_.~StructType(); break;
+          case IsNone:       break;
+        }
+    }
+
+    TypeDef& operator=(TypeDef&& that) {
+        tag_ = that.tag_;
+        switch (tag_) {
+          case IsFuncType:   funcType_ = std::move(that.funcType_); break;
+          case IsStructType: structType_ = std::move(that.structType_); break;
+          case IsNone:       break;
+        }
+        return *this;
+    }
+
+    bool isFuncType() const {
+        return tag_ == IsFuncType;
+    }
+
+    bool isStructType() const {
+        return tag_ == IsStructType;
+    }
+
+    const SigWithId& funcType() const {
+        MOZ_ASSERT(isFuncType());
+        return funcType_;
+    }
+
+    SigWithId& funcType() {
+        MOZ_ASSERT(isFuncType());
+        return funcType_;
+    }
+
+    // p has to point to the sig_ embedded within a TypeDef for this to be
+    // valid.
+    static const TypeDef* fromSigWithIdPtr(const SigWithId* p) {
+        const TypeDef* q = (const TypeDef*)((char*)p - offsetof(TypeDef, funcType_));
+        MOZ_ASSERT(q->tag_ == IsFuncType);
+        return q;
+    }
+
+    const StructType& structType() const {
+        MOZ_ASSERT(isStructType());
+        return structType_;
+    }
+
+    StructType& structType() {
+        MOZ_ASSERT(isStructType());
+        return structType_;
+    }
+
+    // p has to point to the struct_ embedded within a TypeDef for this to be
+    // valid.
+    static const TypeDef* fromStructPtr(const StructType* p) {
+        const TypeDef* q = (const TypeDef*)((char*)p - offsetof(TypeDef, structType_));
+        MOZ_ASSERT(q->tag_ == IsStructType);
+        return q;
+    }
+};
+
+typedef Vector<TypeDef, 0, SystemAllocPolicy> TypeDefVector;
+
 // A wasm::Trap represents a wasm-defined trap that can occur during execution
 // which triggers a WebAssembly.RuntimeError. Generated code may jump to a Trap
 // symbolically, passing the bytecode offset to report as the trap offset. The
 // generated jump will be bound to a tiny stub which fills the offset and
 // then jumps to a per-Trap shared stub at the end of the module.
 
 enum class Trap
 {
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -1059,73 +1059,123 @@ DecodePreamble(Decoder& d)
         return d.failf("binary version 0x%" PRIx32 " does not match expected version 0x%" PRIx32,
                        u32, EncodingVersion);
     }
 
     return true;
 }
 
 static bool
+DecodeFuncType(Decoder& d, ModuleEnvironment* env, uint32_t typeIndex)
+{
+    uint32_t numArgs;
+    if (!d.readVarU32(&numArgs))
+        return d.fail("bad number of function args");
+
+    if (numArgs > MaxParams)
+        return d.fail("too many arguments in signature");
+
+    ValTypeVector args;
+    if (!args.resize(numArgs))
+        return false;
+
+    for (uint32_t i = 0; i < numArgs; i++) {
+        if (!DecodeValType(d, ModuleKind::Wasm, env->gcTypesEnabled, &args[i]))
+            return false;
+    }
+
+    uint32_t numRets;
+    if (!d.readVarU32(&numRets))
+        return d.fail("bad number of function returns");
+
+    if (numRets > 1)
+        return d.fail("too many returns in signature");
+
+    ExprType result = ExprType::Void;
+
+    if (numRets == 1) {
+        ValType type;
+        if (!DecodeValType(d, ModuleKind::Wasm, env->gcTypesEnabled, &type))
+            return false;
+
+        result = ToExprType(type);
+    }
+
+    env->types[typeIndex] = TypeDef(Sig(std::move(args), result));
+    return true;
+}
+
+static bool
+DecodeStructType(Decoder& d, ModuleEnvironment* env, uint32_t typeIndex)
+{
+    if (env->gcTypesEnabled == HasGcTypes::False)
+        return d.fail("Structure types not enabled");
+
+    uint32_t numFields;
+    if (!d.readVarU32(&numFields))
+        return d.fail("Bad number of fields");
+
+    if (numFields > MaxStructFields)
+        return d.fail("too many fields in structure");
+
+    ValTypeVector fields;
+    if (!fields.resize(numFields))
+        return false;
+
+    Uint32Vector fieldOffsets;
+    if (!fieldOffsets.resize(numFields))
+        return false;
+
+    // TODO (subsequent patch): lay out the fields.
+
+    for (uint32_t i = 0; i < numFields; i++) {
+        if (!DecodeValType(d, ModuleKind::Wasm, env->gcTypesEnabled, &fields[i]))
+            return false;
+    }
+
+    env->types[typeIndex] = TypeDef(StructType(std::move(fields), std::move(fieldOffsets)));
+    return true;
+}
+
+static bool
 DecodeTypeSection(Decoder& d, ModuleEnvironment* env)
 {
     MaybeSectionRange range;
     if (!d.startSection(SectionId::Type, env, &range, "type"))
         return false;
     if (!range)
         return true;
 
-    uint32_t numSigs;
-    if (!d.readVarU32(&numSigs))
-        return d.fail("expected number of signatures");
+    uint32_t numTypes;
+    if (!d.readVarU32(&numTypes))
+        return d.fail("expected number of types");
 
-    if (numSigs > MaxTypes)
-        return d.fail("too many signatures");
+    if (numTypes > MaxTypes)
+        return d.fail("too many types");
 
-    if (!env->sigs.resize(numSigs))
+    if (!env->types.resize(numTypes))
         return false;
 
-    for (uint32_t sigIndex = 0; sigIndex < numSigs; sigIndex++) {
+    for (uint32_t typeIndex = 0; typeIndex < numTypes; typeIndex++) {
         uint8_t form;
-        if (!d.readFixedU8(&form) || form != uint8_t(TypeCode::Func))
-            return d.fail("expected function form");
-
-        uint32_t numArgs;
-        if (!d.readVarU32(&numArgs))
-            return d.fail("bad number of function args");
+        if (!d.readFixedU8(&form))
+            return d.fail("expected type form");
 
-        if (numArgs > MaxParams)
-            return d.fail("too many arguments in signature");
-
-        ValTypeVector args;
-        if (!args.resize(numArgs))
-            return false;
-
-        for (uint32_t i = 0; i < numArgs; i++) {
-            if (!DecodeValType(d, ModuleKind::Wasm, env->gcTypesEnabled, &args[i]))
+        switch (form) {
+          case uint8_t(TypeCode::Func):
+            if (!DecodeFuncType(d, env, typeIndex))
                 return false;
+            break;
+          case uint8_t(TypeCode::Struct):
+            if (!DecodeStructType(d, env, typeIndex))
+                return false;
+            break;
+          default:
+            return d.fail("expected type form");
         }
-
-        uint32_t numRets;
-        if (!d.readVarU32(&numRets))
-            return d.fail("bad number of function returns");
-
-        if (numRets > 1)
-            return d.fail("too many returns in signature");
-
-        ExprType result = ExprType::Void;
-
-        if (numRets == 1) {
-            ValType type;
-            if (!DecodeValType(d, ModuleKind::Wasm, env->gcTypesEnabled, &type))
-                return false;
-
-            result = ToExprType(type);
-        }
-
-        env->sigs[sigIndex] = Sig(std::move(args), result);
     }
 
     return d.finishSection(*range, "type");
 }
 
 static UniqueChars
 DecodeName(Decoder& d)
 {
@@ -1149,24 +1199,27 @@ DecodeName(Decoder& d)
 
     memcpy(name.get(), bytes, numBytes);
     name[numBytes] = '\0';
 
     return name;
 }
 
 static bool
-DecodeSignatureIndex(Decoder& d, const SigWithIdVector& sigs, uint32_t* sigIndex)
+DecodeSignatureIndex(Decoder& d, const TypeDefVector& types, uint32_t* sigIndex)
 {
     if (!d.readVarU32(sigIndex))
         return d.fail("expected signature index");
 
-    if (*sigIndex >= sigs.length())
+    if (*sigIndex >= types.length())
         return d.fail("signature index out of range");
 
+    if (!types[*sigIndex].isFuncType())
+        return d.fail("signature index references non-signature");
+
     return true;
 }
 
 static bool
 DecodeLimits(Decoder& d, Limits* limits, Shareable allowShared = Shareable::False)
 {
     uint8_t flags;
     if (!d.readFixedU8(&flags))
@@ -1330,19 +1383,19 @@ DecodeImport(Decoder& d, ModuleEnvironme
     if (!d.readFixedU8(&rawImportKind))
         return d.fail("failed to read import kind");
 
     DefinitionKind importKind = DefinitionKind(rawImportKind);
 
     switch (importKind) {
       case DefinitionKind::Function: {
         uint32_t sigIndex;
-        if (!DecodeSignatureIndex(d, env->sigs, &sigIndex))
+        if (!DecodeSignatureIndex(d, env->types, &sigIndex))
             return false;
-        if (!env->funcSigs.append(&env->sigs[sigIndex]))
+        if (!env->funcSigs.append(&env->types[sigIndex].funcType()))
             return false;
         if (env->funcSigs.length() > MaxFuncs)
             return d.fail("too many functions");
         break;
       }
       case DefinitionKind::Table: {
         if (!DecodeTableLimits(d, &env->tables))
             return false;
@@ -1423,19 +1476,19 @@ DecodeFunctionSection(Decoder& d, Module
     if (!numFuncs.isValid() || numFuncs.value() > MaxFuncs)
         return d.fail("too many functions");
 
     if (!env->funcSigs.reserve(numFuncs.value()))
         return false;
 
     for (uint32_t i = 0; i < numDefs; i++) {
         uint32_t sigIndex;
-        if (!DecodeSignatureIndex(d, env->sigs, &sigIndex))
+        if (!DecodeSignatureIndex(d, env->types, &sigIndex))
             return false;
-        env->funcSigs.infallibleAppend(&env->sigs[sigIndex]);
+        env->funcSigs.infallibleAppend(&env->types[sigIndex].funcType());
     }
 
     return d.finishSection(*range, "function");
 }
 
 static bool
 DecodeTableSection(Decoder& d, ModuleEnvironment* env)
 {
--- a/js/src/wasm/WasmValidate.h
+++ b/js/src/wasm/WasmValidate.h
@@ -63,17 +63,17 @@ struct ModuleEnvironment
     const HasGcTypes          gcTypesEnabled;
     const Tier                tier;
 
     // Module fields decoded from the module environment (or initialized while
     // validating an asm.js module) and immutable during compilation:
     MemoryUsage               memoryUsage;
     uint32_t                  minMemoryLength;
     Maybe<uint32_t>           maxMemoryLength;
-    SigWithIdVector           sigs;
+    TypeDefVector             types;
     SigWithIdPtrVector        funcSigs;
     Uint32Vector              funcImportGlobalDataOffsets;
     GlobalDescVector          globals;
     TableDescVector           tables;
     Uint32Vector              asmJSSigToTableIndex;
     ImportVector              imports;
     ExportVector              exports;
     Maybe<uint32_t>           startFuncIndex;
@@ -100,18 +100,18 @@ struct ModuleEnvironment
         tier(tier),
         memoryUsage(MemoryUsage::None),
         minMemoryLength(0)
     {}
 
     size_t numTables() const {
         return tables.length();
     }
-    size_t numSigs() const {
-        return sigs.length();
+    size_t numTypes() const {
+        return types.length();
     }
     size_t numFuncs() const {
         return funcSigs.length();
     }
     size_t numFuncImports() const {
         return funcImportGlobalDataOffsets.length();
     }
     size_t numFuncDefs() const {
@@ -128,17 +128,17 @@ struct ModuleEnvironment
     }
     bool debugEnabled() const {
         return debug == DebugEnabled::True;
     }
     bool funcIsImport(uint32_t funcIndex) const {
         return funcIndex < funcImportGlobalDataOffsets.length();
     }
     uint32_t funcIndexToSigIndex(uint32_t funcIndex) const {
-        return funcSigs[funcIndex] - sigs.begin();
+        return TypeDef::fromSigWithIdPtr(funcSigs[funcIndex]) - types.begin();
     }
 };
 
 // The Encoder class appends bytes to the Bytes object it is given during
 // construction. The client is responsible for the Bytes's lifetime and must
 // keep the Bytes alive as long as the Encoder is used.
 
 class Encoder