Relax structs
diff --git a/src/passes/RelaxNullability.cpp b/src/passes/RelaxNullability.cpp
index 839a801..f19ae37 100644
--- a/src/passes/RelaxNullability.cpp
+++ b/src/passes/RelaxNullability.cpp
@@ -41,6 +41,7 @@
 #include "ir/child-typer.h"
 #include "ir/find_all.h"
 #include "ir/module-utils.h"
+#include "ir/type-updating.h"
 #include "ir/utils.h"
 
 namespace wasm {
@@ -199,6 +200,9 @@
     fixupReturn(&func->body, getModule(), func);
   }
 
+  // void visitStructGet(StructGet* curr) { changed |= makeNullable(curr->type);
+  // }
+
   void visitLocalGet(LocalGet* curr) { changed |= makeNullable(curr->type); }
 
   void visitLocalSet(LocalSet* curr) {
@@ -213,6 +217,7 @@
 
   void run(Module* module) override {
     auto referencedFuncs = ModuleUtils::getReferencedFuncs(*module);
+    relaxStructs(module, getPassOptions().worldMode);
     relaxFunctionParams(module, referencedFuncs);
 
     PassRunner runner(getPassRunner());
@@ -221,6 +226,35 @@
   }
 
 private:
+  void relaxStructs(Module* module, WorldMode worldMode) {
+    using TypeMap = GlobalTypeRewriter::TypeMap;
+    auto privateTypes = ModuleUtils::getPrivateHeapTypes(*module, worldMode);
+
+    TypeMap map;
+    for (HeapType old : privateTypes) {
+      if (old.isStruct()) {
+        bool changed = false;
+
+        std::vector<Field> relaxedFields;
+        relaxedFields.reserve(old.getStruct().fields.size());
+        for (auto field : old.getStruct().fields) {
+          changed |= makeNullable(field.type);
+          relaxedFields.push_back(field);
+        }
+        if (!changed) {
+          continue;
+        }
+
+        HeapType rewritten(Struct{std::move(relaxedFields)});
+        map.try_emplace(old, rewritten);
+      }
+    }
+
+    GlobalTypeRewriter rewriter(*module, worldMode);
+    // TODO: does this handle public/private?
+    rewriter.mapTypes(map);
+    rewriter.mapTypeNamesAndIndices(map);
+  }
   void
   relaxFunctionParams(Module* module,
                       const std::unordered_set<Function*>& referencedFuncs) {
diff --git a/test/lit/passes/relax-nullability-closed-world.wast b/test/lit/passes/relax-nullability-closed-world.wast
new file mode 100644
index 0000000..1a7a613
--- /dev/null
+++ b/test/lit/passes/relax-nullability-closed-world.wast
@@ -0,0 +1,65 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+
+;; RUN: foreach %s %t wasm-opt -all --closed-world --relax-nullability -S -o - | filecheck %s
+
+(module
+  ;; CHECK:      (type $private-struct (struct (field (mut anyref))))
+  (type $private-struct (struct (field (mut (ref any)))))
+
+  (rec
+    ;; CHECK:      (rec
+    ;; CHECK-NEXT:  (type $public-struct (struct (field (mut (ref any)))))
+    (type $public-struct (struct (field (mut (ref any)))))
+    ;; CHECK:       (type $brand (struct))
+    (type $brand (struct))
+  )
+
+  (func $public (import "" "") (param (ref $public-struct)) (result (ref $public-struct)))
+
+  ;; CHECK:      (type $3 (func (param (ref $public-struct)) (result (ref $public-struct))))
+
+  ;; CHECK:      (type $4 (func (param anyref)))
+
+  ;; CHECK:      (import "" "" (func $public (type $3) (param (ref $public-struct)) (result (ref $public-struct))))
+
+  ;; CHECK:      (func $private-is-relaxed (type $4) (param $ref anyref)
+  ;; CHECK-NEXT:  (local $private-relaxed (ref null $private-struct))
+  ;; CHECK-NEXT:  (local $public-not-relaxed (ref null $public-struct))
+  ;; CHECK-NEXT:  (local.set $private-relaxed
+  ;; CHECK-NEXT:   (struct.new $private-struct
+  ;; CHECK-NEXT:    (local.get $ref)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (struct.set $private-struct 0
+  ;; CHECK-NEXT:   (local.get $private-relaxed)
+  ;; CHECK-NEXT:   (local.get $ref)
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (local.set $public-not-relaxed
+  ;; CHECK-NEXT:   (struct.new $public-struct
+  ;; CHECK-NEXT:    (ref.as_non_null
+  ;; CHECK-NEXT:     (local.get $ref)
+  ;; CHECK-NEXT:    )
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT:  (struct.set $public-struct 0
+  ;; CHECK-NEXT:   (local.get $public-not-relaxed)
+  ;; CHECK-NEXT:   (ref.as_non_null
+  ;; CHECK-NEXT:    (local.get $ref)
+  ;; CHECK-NEXT:   )
+  ;; CHECK-NEXT:  )
+  ;; CHECK-NEXT: )
+  (func $private-is-relaxed (param $ref (ref any))
+    (local $private-relaxed (ref $private-struct))
+    (local $public-not-relaxed (ref $public-struct))
+
+    ;; The ref in the private struct is relaxed to nullable.
+    ;; struct.new and struct.set don't need ref.as_non_null
+    (local.set $private-relaxed (struct.new $private-struct (local.get $ref)))
+    (struct.set $private-struct 0 (local.get $private-relaxed) (local.get $ref))
+
+    ;; The public struct is not relaxed and struct.new and struct.set require
+    ;; ref.as_non_null.
+    (local.set $public-not-relaxed (struct.new $public-struct (local.get $ref)))
+    (struct.set $public-struct 0 (local.get $public-not-relaxed) (local.get $ref))
+  )
+)
diff --git a/test/lit/passes/relax-nullability.wast b/test/lit/passes/relax-nullability.wast
index 2a409d5..001c6cb 100644
--- a/test/lit/passes/relax-nullability.wast
+++ b/test/lit/passes/relax-nullability.wast
@@ -4,6 +4,22 @@
 
 ;; Locals, params, and blocks are relaxed.
 (module
+  ;; CHECK:      (type $func.0 (func (param (ref any) (ref $struct.0)) (result (ref any))))
+
+  ;; CHECK:      (type $func.0 (func (result anyref)))
+
+  ;; CHECK:      (type $struct.0 (struct))
+
+  ;; CHECK:      (type $func.0 (func (result (ref any))))
+
+  ;; CHECK:      (type $func.0 (func (param i32) (result (ref any))))
+
+  ;; CHECK:      (type $func.0 (func (param i32 (ref any) (ref any)) (result (ref any))))
+
+  ;; CHECK:      (type $func.0 (func (param (ref any)) (result (ref any))))
+
+  ;; CHECK:      (type $func.0 (func (param anyref)))
+
   ;; CHECK:      (type $struct (struct))
   (type $struct (struct))
 
@@ -179,6 +195,10 @@
 ;; call_ref/call_indirect.
 (module
   (type $struct (struct))
+  ;; CHECK:      (type $func.0 (func (param (ref any))))
+
+  ;; CHECK:      (type $func.0 (func))
+
   ;; CHECK:      (type $shared-sig (func (param (ref any)) (result (ref any))))
 
   ;; CHECK:      (type $1 (func (param anyref)))