Try precise effects for heap
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..e450a75
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,118 @@
+#!/bin/bash
+#
+# Runs Binaryen optimization stages as defined in j2wasm_application.bzl
+# Usage: ./run_wasm_opts.sh <input.wat>
+#
+# Make sure 'wasm-opt' is in your PATH. You can get it via:
+# blaze build //third_party/java_src/j2cl/third_party:binaryen
+# and then run like this:
+# WASM_OPT=./blaze-bin/third_party/java_src/j2cl/third_party/wasm-opt ./run_wasm_opts.sh <input.wat>
+#
+# Alternatively, binaryen may be available via apt:
+# sudo apt-get install binaryen
+
+set -euo pipefail
+
+if [[ -z "${1-}" ]]; then
+  echo "Usage: $0 <input.wat>"
+  exit 1
+fi
+
+INPUT_WAT=$1
+S1_OUT=intermediate1.wasm
+S2_OUT=intermediate2.wasm
+FINAL_OUT=output.wasm
+WASM_OPT=${WASM_OPT:-wasm-opt}
+
+echo "Using wasm-opt: $(which "$WASM_OPT" || echo "$WASM_OPT")"
+
+COMMON_ARGS=(
+  --enable-exception-handling
+  --enable-gc
+  --enable-reference-types
+  --enable-sign-ext
+  --enable-strings
+  --enable-nontrapping-float-to-int
+  --enable-bulk-memory
+  --closed-world
+  --traps-never-happen
+)
+
+STAGE1_ARGS=(
+  --merge-j2cl-itables
+  "--no-inline=*_<once>_*"
+  --generate-global-effects
+  -O3
+  --cfp-reftest
+  --optimize-j2cl
+  --gufa
+  --unsubtyping
+  -O3
+  --cfp-reftest
+  --optimize-j2cl
+  -O3
+  --cfp-reftest
+  --optimize-j2cl
+)
+
+STAGE2_ARGS=(
+  "--no-inline=*_<once>_*"
+  --generate-global-effects
+  --partial-inlining-ifs=4
+  -fimfs=25
+  --gufa
+  --unsubtyping
+  -O3
+  --cfp-reftest
+  --optimize-j2cl
+  -O3
+  --cfp-reftest
+  --optimize-j2cl
+  -O3
+  --cfp-reftest
+  --optimize-j2cl
+  --gufa
+  --unsubtyping
+  -O3
+  --cfp-reftest
+  --optimize-j2cl
+  -O3
+  --cfp-reftest
+  --optimize-j2cl
+)
+
+STAGE3_ARGS=(
+  "--no-full-inline=*_<once>_*"
+  --generate-global-effects
+  --partial-inlining-ifs=4
+  --intrinsic-lowering
+  --gufa
+  --unsubtyping
+  -O3
+  --cfp-reftest
+  --optimize-j2cl
+  -O3
+  --optimize-j2cl
+  --cfp-reftest
+  --type-merging
+  -O3
+  --cfp-reftest
+  --optimize-j2cl
+  --string-lowering-magic-imports-assert
+  --remove-unused-module-elements
+  --reorder-globals
+  --type-finalizing
+  --reorder-types
+)
+
+echo "Running stage 1: $INPUT_WAT -> $S1_OUT"
+"$WASM_OPT" "$INPUT_WAT" "${COMMON_ARGS[@]}" "${STAGE1_ARGS[@]}" --debuginfo -o "$S1_OUT"
+
+echo "Running stage 2: $S1_OUT -> $S2_OUT"
+"$WASM_OPT" "$S1_OUT" "${COMMON_ARGS[@]}" "${STAGE2_ARGS[@]}" --debuginfo -o "$S2_OUT"
+
+echo "Running stage 3: $S2_OUT -> $FINAL_OUT"
+"$WASM_OPT" "$S2_OUT" "${COMMON_ARGS[@]}" "${STAGE3_ARGS[@]}" -o "$FINAL_OUT"
+
+echo "Done. Intermediate files are $S1_OUT and $S2_OUT."
+echo "Final output written to $FINAL_OUT"
diff --git a/src/ir/effects.h b/src/ir/effects.h
index 56cea0a..c8246b3 100644
--- a/src/ir/effects.h
+++ b/src/ir/effects.h
@@ -89,6 +89,11 @@
   bool readsSharedMutableArray : 1;
   bool writesSharedArray : 1;
 
+  std::vector<std::pair<HeapType, Index>> structsWrittenList;
+  std::vector<std::pair<HeapType, Index>> structsReadList;
+  std::vector<HeapType> arraysWrittenList;
+  std::vector<HeapType> arraysReadList;
+
   // A trap, either from an unreachable instruction, or from an implicit trap
   // that we do not ignore (see below).
   //
@@ -347,19 +352,70 @@
         ((writesSharedMemory || calls) && other.accessesSharedMemory()) ||
         ((other.writesSharedMemory || other.calls) && accessesSharedMemory()) ||
         ((writesTable || calls) && other.accessesTable()) ||
-        ((other.writesTable || other.calls) && accessesTable()) ||
-        ((writesStruct || calls) && other.accessesMutableStruct()) ||
-        ((other.writesStruct || other.calls) && accessesMutableStruct()) ||
-        ((writesSharedStruct || calls) &&
-         other.accessesSharedMutableStruct()) ||
-        ((other.writesSharedStruct || other.calls) &&
-         accessesSharedMutableStruct()) ||
-        ((writesArray || calls) && other.accessesArray()) ||
-        ((other.writesArray || other.calls) && accessesArray()) ||
-        ((writesSharedArray || calls) && other.accessesSharedArray()) ||
-        ((other.writesSharedArray || other.calls) && accessesSharedArray())) {
+        ((other.writesTable || other.calls) && accessesTable())) {
       return true;
     }
+
+    auto structsConflict = [&]() {
+      if ((calls && other.accessesMutableStruct()) ||
+          (other.calls && accessesMutableStruct()) ||
+          (calls && other.accessesSharedMutableStruct()) ||
+          (other.calls && accessesSharedMutableStruct())) {
+        return true;
+      }
+      auto overlap = [](const std::vector<std::pair<HeapType, Index>>& a,
+                        const std::vector<std::pair<HeapType, Index>>& b) {
+        for (auto& x : a) {
+          for (auto& y : b) {
+            if (x.second == y.second &&
+                (HeapType::isSubType(x.first, y.first) ||
+                 HeapType::isSubType(y.first, x.first))) {
+              return true;
+            }
+          }
+        }
+        return false;
+      };
+      if (overlap(structsWrittenList, other.structsWrittenList) ||
+          overlap(structsWrittenList, other.structsReadList) ||
+          overlap(structsReadList, other.structsWrittenList)) {
+        return true;
+      }
+      return false;
+    };
+    if (structsConflict()) {
+      return true;
+    }
+
+    auto arraysConflict = [&]() {
+      if ((calls && other.accessesArray()) ||
+          (other.calls && accessesArray()) ||
+          (calls && other.accessesSharedArray()) ||
+          (other.calls && accessesSharedArray())) {
+        return true;
+      }
+      auto overlap = [](const std::vector<HeapType>& a,
+                        const std::vector<HeapType>& b) {
+        for (auto& x : a) {
+          for (auto& y : b) {
+            if (HeapType::isSubType(x, y) || HeapType::isSubType(y, x)) {
+              return true;
+            }
+          }
+        }
+        return false;
+      };
+      if (overlap(arraysWrittenList, other.arraysWrittenList) ||
+          overlap(arraysWrittenList, other.arraysReadList) ||
+          overlap(arraysReadList, other.arraysWrittenList)) {
+        return true;
+      }
+      return false;
+    };
+    if (arraysConflict()) {
+      return true;
+    }
+
     // Cannot reorder anything before dangling pops.
     if (danglingPop) {
       return true;
@@ -477,6 +533,27 @@
     readOrder = std::max(readOrder, other.readOrder);
     writeOrder = std::max(writeOrder, other.writeOrder);
 
+    for (auto& x : other.structsWrittenList) {
+      if (std::find(structsWrittenList.begin(), structsWrittenList.end(), x) ==
+          structsWrittenList.end())
+        structsWrittenList.push_back(x);
+    }
+    for (auto& x : other.structsReadList) {
+      if (std::find(structsReadList.begin(), structsReadList.end(), x) ==
+          structsReadList.end())
+        structsReadList.push_back(x);
+    }
+    for (auto& x : other.arraysWrittenList) {
+      if (std::find(arraysWrittenList.begin(), arraysWrittenList.end(), x) ==
+          arraysWrittenList.end())
+        arraysWrittenList.push_back(x);
+    }
+    for (auto& x : other.arraysReadList) {
+      if (std::find(arraysReadList.begin(), arraysReadList.end(), x) ==
+          arraysReadList.end())
+        arraysReadList.push_back(x);
+    }
+
     for (auto i : other.localsRead) {
       localsRead.insert(i);
     }
@@ -631,6 +708,11 @@
     void readsStruct(HeapType type, Index index, MemoryOrder order) {
       assert(type.isStruct());
       if (type.getStruct().fields[index].mutable_ == Mutable) {
+        if (std::find(parent.structsReadList.begin(),
+                      parent.structsReadList.end(),
+                      std::pair<HeapType, Index>{type, index}) ==
+            parent.structsReadList.end())
+          parent.structsReadList.push_back({type, index});
         if (type.isShared()) {
           parent.readsSharedMutableStruct = true;
           parent.readOrder = std::max(parent.readOrder, order);
@@ -642,6 +724,11 @@
 
     void writesStruct(HeapType type, Index index, MemoryOrder order) {
       assert(type.isStruct());
+      if (std::find(parent.structsWrittenList.begin(),
+                    parent.structsWrittenList.end(),
+                    std::pair<HeapType, Index>{type, index}) ==
+          parent.structsWrittenList.end())
+        parent.structsWrittenList.push_back({type, index});
       if (type.isShared()) {
         parent.writesSharedStruct = true;
         parent.writeOrder = std::max(parent.writeOrder, order);
@@ -653,6 +740,10 @@
     void readsArray(HeapType type, MemoryOrder order) {
       assert(type.isArray());
       if (type.getArray().element.mutable_ == Mutable) {
+        if (std::find(parent.arraysReadList.begin(),
+                      parent.arraysReadList.end(),
+                      type) == parent.arraysReadList.end())
+          parent.arraysReadList.push_back(type);
         if (type.isShared()) {
           parent.readsSharedMutableArray = true;
           parent.readOrder = std::max(parent.readOrder, order);
@@ -664,6 +755,10 @@
 
     void writesArray(HeapType type, MemoryOrder order) {
       assert(type.isArray());
+      if (std::find(parent.arraysWrittenList.begin(),
+                    parent.arraysWrittenList.end(),
+                    type) == parent.arraysWrittenList.end())
+        parent.arraysWrittenList.push_back(type);
       if (type.isShared()) {
         parent.writesSharedArray = true;
         parent.writeOrder = std::max(parent.writeOrder, order);