[wasm] Implement handling of exported/imported exceptions.

This implements the proper semantics for matching exported/imported
exceptions by using the notion of an "exception tag" that is global to
the system. It can be used to match exceptions in one module against
exceptions declared and/or thrown in another module (or instance).

R=clemensh@chromium.org
TEST=mjsunit/wasm/exceptions-shared
BUG=v8:8091

Change-Id: I37586d7be5d5e6169b3418dfbc415b26dd4750dd
Reviewed-on: https://chromium-review.googlesource.com/1226976
Commit-Queue: Michael Starzinger <mstarzinger@chromium.org>
Reviewed-by: Clemens Hammacher <clemensh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#55940}
diff --git a/src/compiler/wasm-compiler.cc b/src/compiler/wasm-compiler.cc
index a97cce7..7b14f94 100644
--- a/src/compiler/wasm-compiler.cc
+++ b/src/compiler/wasm-compiler.cc
@@ -2043,13 +2043,13 @@
   return encoded_size;
 }
 
-Node* WasmGraphBuilder::Throw(uint32_t tag,
+Node* WasmGraphBuilder::Throw(uint32_t exception_index,
                               const wasm::WasmException* exception,
                               const Vector<Node*> values) {
   SetNeedsStackCheck();
   uint32_t encoded_size = GetExceptionEncodedSize(exception);
   Node* create_parameters[] = {
-      BuildChangeUint31ToSmi(ConvertExceptionTagToRuntimeId(tag)),
+      LoadExceptionTagFromTable(exception_index),
       BuildChangeUint31ToSmi(Uint32Constant(encoded_size))};
   Node* except_obj =
       BuildCallToRuntime(Runtime::kWasmThrowCreate, create_parameters,
@@ -2126,16 +2126,22 @@
   return result;
 }
 
-Node* WasmGraphBuilder::ConvertExceptionTagToRuntimeId(uint32_t tag) {
-  // TODO(kschimpf): Handle exceptions from different modules, when they are
-  // linked at runtime.
-  return Uint32Constant(tag);
+Node* WasmGraphBuilder::ExceptionTagEqual(Node* caught_tag,
+                                          Node* expected_tag) {
+  MachineOperatorBuilder* machine = mcgraph()->machine();
+  return graph()->NewNode(machine->WordEqual(), caught_tag, expected_tag);
 }
 
-Node* WasmGraphBuilder::GetExceptionRuntimeId(Node* except_obj) {
+Node* WasmGraphBuilder::LoadExceptionTagFromTable(uint32_t exception_index) {
+  Node* exceptions_table =
+      LOAD_INSTANCE_FIELD(ExceptionsTable, MachineType::TaggedPointer());
+  Node* tag = LOAD_FIXED_ARRAY_SLOT(exceptions_table, exception_index);
+  return tag;
+}
+
+Node* WasmGraphBuilder::GetExceptionTag(Node* except_obj) {
   SetNeedsStackCheck();
-  return BuildChangeSmiToInt32(
-      BuildCallToRuntime(Runtime::kWasmGetExceptionRuntimeId, &except_obj, 1));
+  return BuildCallToRuntime(Runtime::kWasmExceptionGetTag, &except_obj, 1);
 }
 
 Node** WasmGraphBuilder::GetExceptionValues(
diff --git a/src/compiler/wasm-compiler.h b/src/compiler/wasm-compiler.h
index 0968abe..5599ed6 100644
--- a/src/compiler/wasm-compiler.h
+++ b/src/compiler/wasm-compiler.h
@@ -160,11 +160,12 @@
   Node* Unop(wasm::WasmOpcode opcode, Node* input,
              wasm::WasmCodePosition position = wasm::kNoCodePosition);
   Node* GrowMemory(Node* input);
-  Node* Throw(uint32_t tag, const wasm::WasmException* exception,
+  Node* Throw(uint32_t exception_index, const wasm::WasmException* exception,
               const Vector<Node*> values);
   Node* Rethrow(Node* except_obj);
-  Node* ConvertExceptionTagToRuntimeId(uint32_t tag);
-  Node* GetExceptionRuntimeId(Node* except_obj);
+  Node* ExceptionTagEqual(Node* caught_tag, Node* expected_tag);
+  Node* LoadExceptionTagFromTable(uint32_t exception_index);
+  Node* GetExceptionTag(Node* except_obj);
   Node** GetExceptionValues(Node* except_obj,
                             const wasm::WasmException* except_decl);
   bool IsPhiWithMerge(Node* phi, Node* merge);
diff --git a/src/heap-symbols.h b/src/heap-symbols.h
index 8cdd8ff..4403192 100644
--- a/src/heap-symbols.h
+++ b/src/heap-symbols.h
@@ -288,7 +288,7 @@
   V(sealed_symbol)                     \
   V(stack_trace_symbol)                \
   V(strict_function_transition_symbol) \
-  V(wasm_exception_runtime_id_symbol)  \
+  V(wasm_exception_tag_symbol)         \
   V(wasm_exception_values_symbol)      \
   V(uninitialized_symbol)
 
diff --git a/src/runtime/runtime-test.cc b/src/runtime/runtime-test.cc
index 9e436ff..f00d8ba 100644
--- a/src/runtime/runtime-test.cc
+++ b/src/runtime/runtime-test.cc
@@ -844,12 +844,19 @@
 
 RUNTIME_FUNCTION(Runtime_GetWasmExceptionId) {
   HandleScope scope(isolate);
-  DCHECK_EQ(1, args.length());
+  DCHECK_EQ(2, args.length());
   CONVERT_ARG_HANDLE_CHECKED(JSReceiver, exception, 0);
-  RETURN_RESULT_OR_FAILURE(
-      isolate, JSReceiver::GetProperty(
-                   isolate, exception,
-                   isolate->factory()->wasm_exception_runtime_id_symbol()));
+  CONVERT_ARG_HANDLE_CHECKED(WasmInstanceObject, instance, 1);
+  Handle<Object> tag;
+  if (JSReceiver::GetProperty(isolate, exception,
+                              isolate->factory()->wasm_exception_tag_symbol())
+          .ToHandle(&tag)) {
+    Handle<FixedArray> exceptions_table(instance->exceptions_table(), isolate);
+    for (int index = 0; index < exceptions_table->length(); ++index) {
+      if (exceptions_table->get(index) == *tag) return Smi::FromInt(index);
+    }
+  }
+  return ReadOnlyRoots(isolate).undefined_value();
 }
 
 RUNTIME_FUNCTION(Runtime_GetWasmExceptionValues) {
diff --git a/src/runtime/runtime-wasm.cc b/src/runtime/runtime-wasm.cc
index afda688..36d90ee 100644
--- a/src/runtime/runtime-wasm.cc
+++ b/src/runtime/runtime-wasm.cc
@@ -118,12 +118,12 @@
   Handle<Object> exception = isolate->factory()->NewWasmRuntimeError(
       static_cast<MessageTemplate::Template>(
           MessageTemplate::kWasmExceptionError));
-  CONVERT_ARG_HANDLE_CHECKED(Smi, id, 0);
-  CHECK(!JSReceiver::SetProperty(
-             isolate, exception,
-             isolate->factory()->wasm_exception_runtime_id_symbol(), id,
-             LanguageMode::kStrict)
-             .is_null());
+  CONVERT_ARG_HANDLE_CHECKED(HeapObject, tag, 0);
+  CHECK(
+      !JSReceiver::SetProperty(isolate, exception,
+                               isolate->factory()->wasm_exception_tag_symbol(),
+                               tag, LanguageMode::kStrict)
+           .is_null());
   CONVERT_SMI_ARG_CHECKED(size, 1);
   Handle<JSTypedArray> values =
       isolate->factory()->NewJSTypedArray(ElementsKind::UINT16_ELEMENTS, size);
@@ -146,7 +146,7 @@
   return isolate->Throw(*except_obj);
 }
 
-RUNTIME_FUNCTION(Runtime_WasmGetExceptionRuntimeId) {
+RUNTIME_FUNCTION(Runtime_WasmExceptionGetTag) {
   // TODO(kschimpf): Can this be replaced with equivalent TurboFan code/calls.
   HandleScope scope(isolate);
   DCHECK_EQ(1, args.length());
@@ -156,16 +156,13 @@
   if (!except_obj.is_null() && except_obj->IsJSReceiver()) {
     Handle<JSReceiver> exception(JSReceiver::cast(*except_obj), isolate);
     Handle<Object> tag;
-    if (JSReceiver::GetProperty(
-            isolate, exception,
-            isolate->factory()->wasm_exception_runtime_id_symbol())
+    if (JSReceiver::GetProperty(isolate, exception,
+                                isolate->factory()->wasm_exception_tag_symbol())
             .ToHandle(&tag)) {
-      if (tag->IsSmi()) {
-        return *tag;
-      }
+      return *tag;
     }
   }
-  return Smi::FromInt(wasm::kInvalidExceptionTag);
+  return ReadOnlyRoots(isolate).undefined_value();
 }
 
 RUNTIME_FUNCTION(Runtime_WasmExceptionGetElement) {
diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h
index 8c55302..3417906 100644
--- a/src/runtime/runtime.h
+++ b/src/runtime/runtime.h
@@ -473,7 +473,7 @@
   F(GetDeoptCount, 1, 1)                      \
   F(GetOptimizationStatus, -1, 1)             \
   F(GetUndetectable, 0, 1)                    \
-  F(GetWasmExceptionId, 1, 1)                 \
+  F(GetWasmExceptionId, 2, 1)                 \
   F(GetWasmExceptionValues, 1, 1)             \
   F(GetWasmRecoveredTrapCount, 0, 1)          \
   F(GlobalPrint, 1, 1)                        \
@@ -541,18 +541,18 @@
   F(TypedArraySet, 2, 1)                 \
   F(TypedArraySortFast, 1, 1)
 
-#define FOR_EACH_INTRINSIC_WASM(F)   \
-  F(ThrowWasmError, 1, 1)            \
-  F(ThrowWasmStackOverflow, 0, 1)    \
-  F(WasmExceptionGetElement, 2, 1)   \
-  F(WasmExceptionSetElement, 3, 1)   \
-  F(WasmGetExceptionRuntimeId, 1, 1) \
-  F(WasmGrowMemory, 2, 1)            \
-  F(WasmRunInterpreter, 2, 1)        \
-  F(WasmStackGuard, 0, 1)            \
-  F(WasmThrow, 1, 1)                 \
-  F(WasmThrowCreate, 2, 1)           \
-  F(WasmThrowTypeError, 0, 1)        \
+#define FOR_EACH_INTRINSIC_WASM(F) \
+  F(ThrowWasmError, 1, 1)          \
+  F(ThrowWasmStackOverflow, 0, 1)  \
+  F(WasmExceptionGetElement, 2, 1) \
+  F(WasmExceptionSetElement, 3, 1) \
+  F(WasmExceptionGetTag, 1, 1)     \
+  F(WasmGrowMemory, 2, 1)          \
+  F(WasmRunInterpreter, 2, 1)      \
+  F(WasmStackGuard, 0, 1)          \
+  F(WasmThrow, 1, 1)               \
+  F(WasmThrowCreate, 2, 1)         \
+  F(WasmThrowTypeError, 0, 1)      \
   F(WasmCompileLazy, 2, 1)
 
 #define FOR_EACH_INTRINSIC_RETURN_PAIR(F) \
diff --git a/src/wasm/function-body-decoder.cc b/src/wasm/function-body-decoder.cc
index 3e27fef..2c5ea46 100644
--- a/src/wasm/function-body-decoder.cc
+++ b/src/wasm/function-body-decoder.cc
@@ -443,11 +443,11 @@
     TFNode* if_catch = nullptr;
     TFNode* if_no_catch = nullptr;
     if (exception != nullptr) {
-      // Get the exception and see if wanted exception.
-      TFNode* caught_tag = BUILD(GetExceptionRuntimeId, exception);
-      TFNode* exception_tag = BUILD(ConvertExceptionTagToRuntimeId, imm.index);
-      TFNode* compare_i32 = BUILD(Binop, kExprI32Eq, caught_tag, exception_tag);
-      BUILD(BranchNoHint, compare_i32, &if_catch, &if_no_catch);
+      // Get the exception tag and see if it matches the expected one.
+      TFNode* caught_tag = BUILD(GetExceptionTag, exception);
+      TFNode* exception_tag = BUILD(LoadExceptionTagFromTable, imm.index);
+      TFNode* compare = BUILD(ExceptionTagEqual, caught_tag, exception_tag);
+      BUILD(BranchNoHint, compare, &if_catch, &if_no_catch);
     }
 
     SsaEnv* if_no_catch_env = Split(decoder, ssa_env_);
diff --git a/src/wasm/module-compiler.cc b/src/wasm/module-compiler.cc
index f74b5fe..9e26411 100644
--- a/src/wasm/module-compiler.cc
+++ b/src/wasm/module-compiler.cc
@@ -325,6 +325,10 @@
   void InitializeTables(Handle<WasmInstanceObject> instance);
 
   void LoadTableSegments(Handle<WasmInstanceObject> instance);
+
+  // Creates new exception tags for all exceptions. Note that some tags might
+  // already exist if they were imported, those tags will be re-used.
+  void InitializeExceptions(Handle<WasmInstanceObject> instance);
 };
 
 }  // namespace
@@ -1076,6 +1080,17 @@
   }
 
   //--------------------------------------------------------------------------
+  // Set up the exception table used for exception tag checks.
+  //--------------------------------------------------------------------------
+  int exceptions_count = static_cast<int>(module_->exceptions.size());
+  if (exceptions_count > 0) {
+    Handle<FixedArray> exception_table =
+        isolate_->factory()->NewFixedArray(exceptions_count, TENURED);
+    instance->set_exceptions_table(*exception_table);
+    exception_wrappers_.resize(exceptions_count);
+  }
+
+  //--------------------------------------------------------------------------
   // Reserve the metadata for indirect function tables.
   //--------------------------------------------------------------------------
   int table_count = static_cast<int>(module_->tables.size());
@@ -1100,6 +1115,13 @@
   }
 
   //--------------------------------------------------------------------------
+  // Initialize the exceptions table.
+  //--------------------------------------------------------------------------
+  if (exceptions_count > 0) {
+    InitializeExceptions(instance);
+  }
+
+  //--------------------------------------------------------------------------
   // Create the WebAssembly.Memory object.
   //--------------------------------------------------------------------------
   if (module_->has_memory) {
@@ -1740,8 +1762,10 @@
                           index, module_name, import_name);
           return -1;
         }
-        // TODO(mstarzinger): Actually add imported exceptions to the instance
-        // exception table, making sure to preserve object identity.
+        Object* exception_tag = imported_exception->exception_tag();
+        DCHECK(instance->exceptions_table()->get(import.index)->IsUndefined());
+        instance->exceptions_table()->set(import.index, exception_tag);
+        exception_wrappers_[import.index] = imported_exception;
         break;
       }
       default:
@@ -1861,10 +1885,6 @@
     }
   }
 
-  // TODO(mstarzinger): The {exception_wrappers_} table is only needed until we
-  // have an exception table per instance which can then be used directly.
-  exception_wrappers_.resize(module_->exceptions.size());
-
   Handle<JSObject> exports_object;
   bool is_asm_js = false;
   switch (module_->origin) {
@@ -2021,7 +2041,11 @@
         const WasmException& exception = module_->exceptions[exp.index];
         Handle<WasmExceptionObject> wrapper = exception_wrappers_[exp.index];
         if (wrapper.is_null()) {
-          wrapper = WasmExceptionObject::New(isolate_, exception.sig);
+          Handle<HeapObject> exception_tag(
+              HeapObject::cast(instance->exceptions_table()->get(exp.index)),
+              isolate_);
+          wrapper =
+              WasmExceptionObject::New(isolate_, exception.sig, exception_tag);
           exception_wrappers_[exp.index] = wrapper;
         }
         desc.set_value(wrapper);
@@ -2143,6 +2167,20 @@
   }
 }
 
+void InstanceBuilder::InitializeExceptions(
+    Handle<WasmInstanceObject> instance) {
+  Handle<FixedArray> exceptions_table(instance->exceptions_table(), isolate_);
+  for (int index = 0; index < exceptions_table->length(); ++index) {
+    if (!exceptions_table->get(index)->IsUndefined(isolate_)) continue;
+    // TODO(mstarzinger): Tags provide an object identity for each exception,
+    // using {JSObject} here is gigantic hack and we should use a dedicated
+    // object with a much lighter footprint for this purpose here.
+    Handle<HeapObject> exception_tag =
+        isolate_->factory()->NewJSObjectWithNullProto();
+    exceptions_table->set(index, *exception_tag);
+  }
+}
+
 AsyncCompileJob::AsyncCompileJob(
     Isolate* isolate, const WasmFeatures& enabled,
     std::unique_ptr<byte[]> bytes_copy, size_t length, Handle<Context> context,
diff --git a/src/wasm/wasm-constants.h b/src/wasm/wasm-constants.h
index 2fd30f0..7a3fb35 100644
--- a/src/wasm/wasm-constants.h
+++ b/src/wasm/wasm-constants.h
@@ -79,7 +79,6 @@
 constexpr size_t kWasmPageSize = 0x10000;
 constexpr uint32_t kWasmPageSizeLog2 = 16;
 static_assert(kWasmPageSize == size_t{1} << kWasmPageSizeLog2, "consistency");
-constexpr int kInvalidExceptionTag = -1;
 
 // TODO(wasm): Wrap WasmCodePosition in a struct.
 using WasmCodePosition = int;
diff --git a/src/wasm/wasm-objects-inl.h b/src/wasm/wasm-objects-inl.h
index 4c079ff..0144b8a 100644
--- a/src/wasm/wasm-objects-inl.h
+++ b/src/wasm/wasm-objects-inl.h
@@ -185,6 +185,8 @@
                    FixedArray, kIndirectFunctionTableInstancesOffset)
 OPTIONAL_ACCESSORS(WasmInstanceObject, managed_native_allocations, Foreign,
                    kManagedNativeAllocationsOffset)
+OPTIONAL_ACCESSORS(WasmInstanceObject, exceptions_table, FixedArray,
+                   kExceptionsTableOffset)
 ACCESSORS(WasmInstanceObject, undefined_value, Oddball, kUndefinedValueOffset)
 ACCESSORS(WasmInstanceObject, null_value, Oddball, kNullValueOffset)
 ACCESSORS(WasmInstanceObject, centry_stub, Code, kCEntryStubOffset)
@@ -210,6 +212,7 @@
 // WasmExceptionObject
 ACCESSORS(WasmExceptionObject, serialized_signature, PodArray<wasm::ValueType>,
           kSerializedSignatureOffset)
+ACCESSORS(WasmExceptionObject, exception_tag, HeapObject, kExceptionTagOffset)
 
 // WasmExportedFunctionData
 ACCESSORS(WasmExportedFunctionData, wrapper_code, Code, kWrapperCodeOffset)
diff --git a/src/wasm/wasm-objects.cc b/src/wasm/wasm-objects.cc
index 4547a5b9..9d0e20a 100644
--- a/src/wasm/wasm-objects.cc
+++ b/src/wasm/wasm-objects.cc
@@ -1309,7 +1309,8 @@
 
 // static
 Handle<WasmExceptionObject> WasmExceptionObject::New(
-    Isolate* isolate, const wasm::FunctionSig* sig) {
+    Isolate* isolate, const wasm::FunctionSig* sig,
+    Handle<HeapObject> exception_tag) {
   Handle<JSFunction> exception_cons(
       isolate->native_context()->wasm_exception_constructor(), isolate);
   Handle<JSObject> exception_object =
@@ -1328,6 +1329,7 @@
     serialized_sig->set(index++, param);
   }
   exception->set_serialized_signature(*serialized_sig);
+  exception->set_exception_tag(*exception_tag);
 
   return exception;
 }
diff --git a/src/wasm/wasm-objects.h b/src/wasm/wasm-objects.h
index fba0e88..360d8ed 100644
--- a/src/wasm/wasm-objects.h
+++ b/src/wasm/wasm-objects.h
@@ -387,6 +387,7 @@
   DECL_ACCESSORS(imported_function_callables, FixedArray)
   DECL_OPTIONAL_ACCESSORS(indirect_function_table_instances, FixedArray)
   DECL_OPTIONAL_ACCESSORS(managed_native_allocations, Foreign)
+  DECL_OPTIONAL_ACCESSORS(exceptions_table, FixedArray)
   DECL_ACCESSORS(undefined_value, Oddball)
   DECL_ACCESSORS(null_value, Oddball)
   DECL_ACCESSORS(centry_stub, Code)
@@ -422,6 +423,7 @@
   V(kImportedFunctionCallablesOffset, kPointerSize)                     \
   V(kIndirectFunctionTableInstancesOffset, kPointerSize)                \
   V(kManagedNativeAllocationsOffset, kPointerSize)                      \
+  V(kExceptionsTableOffset, kPointerSize)                               \
   V(kUndefinedValueOffset, kPointerSize)                                \
   V(kNullValueOffset, kPointerSize)                                     \
   V(kCEntryStubOffset, kPointerSize)                                    \
@@ -475,10 +477,12 @@
   DECL_CAST(WasmExceptionObject)
 
   DECL_ACCESSORS(serialized_signature, PodArray<wasm::ValueType>)
+  DECL_ACCESSORS(exception_tag, HeapObject)
 
 // Layout description.
 #define WASM_EXCEPTION_OBJECT_FIELDS(V)       \
   V(kSerializedSignatureOffset, kPointerSize) \
+  V(kExceptionTagOffset, kPointerSize)        \
   V(kSize, 0)
 
   DEFINE_FIELD_OFFSET_CONSTANTS(JSObject::kHeaderSize,
@@ -490,7 +494,8 @@
   bool IsSignatureEqual(const wasm::FunctionSig* sig);
 
   static Handle<WasmExceptionObject> New(Isolate* isolate,
-                                         const wasm::FunctionSig* sig);
+                                         const wasm::FunctionSig* sig,
+                                         Handle<HeapObject> exception_tag);
 };
 
 // A WASM function that is wrapped and exported to JavaScript.
diff --git a/test/mjsunit/wasm/exceptions-import.js b/test/mjsunit/wasm/exceptions-import.js
index b123c70..b527672 100644
--- a/test/mjsunit/wasm/exceptions-import.js
+++ b/test/mjsunit/wasm/exceptions-import.js
@@ -40,8 +40,7 @@
 
   assertTrue(except1 < except3 && except2 < except3);
   assertEquals(undefined, instance.exports.ex1);
-  // TODO(mstarzinger): Enable once identity of imported exception is preserved.
-  //assertSame(exported, instance.exports.ex2);
+  assertSame(exported, instance.exports.ex2);
   assertNotSame(exported, instance.exports.ex3);
 })();
 
diff --git a/test/mjsunit/wasm/exceptions-shared.js b/test/mjsunit/wasm/exceptions-shared.js
new file mode 100644
index 0000000..f2a5b56
--- /dev/null
+++ b/test/mjsunit/wasm/exceptions-shared.js
@@ -0,0 +1,158 @@
+// Copyright 2018 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --expose-wasm --experimental-wasm-eh
+
+load("test/mjsunit/wasm/wasm-constants.js");
+load("test/mjsunit/wasm/wasm-module-builder.js");
+
+// Helper function to return a new exported exception with the {kSig_v_v} type
+// signature from an anonymous module. The underlying module is thrown away.
+function NewExportedException() {
+  let builder = new WasmModuleBuilder();
+  let except = builder.addException(kSig_v_v);
+  builder.addExportOfKind("ex", kExternalException, except);
+  let instance = builder.instantiate();
+  return instance.exports.ex;
+}
+
+// Check that an instance matches an exception thrown by itself, even when the
+// exception is re-thrown by a regular JavaScript function.
+(function TestSingleInstance() {
+  print(arguments.callee.name);
+  let builder = new WasmModuleBuilder();
+  let sig_index = builder.addType(kSig_v_v);
+  let fun = builder.addImport("m", "f", sig_index);
+  let except = builder.addException(kSig_v_v);
+  builder.addFunction("throw", kSig_v_v)
+      .addBody([
+        kExprThrow, except
+      ]).exportFunc();
+  builder.addFunction("catch", kSig_v_v)
+      .addBody([
+        kExprTry, kWasmStmt,
+          kExprCallFunction, fun,
+        kExprCatch, except,
+        kExprEnd,
+      ]).exportFunc();
+  let ex_obj = new Error("my exception");
+  let instance = builder.instantiate({ m: { f: function() { throw ex_obj }}});
+
+  assertThrows(() => instance.exports.throw(), WebAssembly.RuntimeError);
+  assertThrowsEquals(() => instance.exports.catch(), ex_obj);
+  try {
+    instance.exports.throw();
+  } catch (e) {
+    ex_obj = e;
+  }
+  assertDoesNotThrow(() => instance.exports.catch());
+})();
+
+// Check that two instances distinguish their individual exceptions if they are
+// not shared, even when declared by the same underlying module.
+(function TestMultiInstanceNonShared() {
+  print(arguments.callee.name);
+  let builder = new WasmModuleBuilder();
+  let sig_index = builder.addType(kSig_v_v);
+  let fun = builder.addImport("m", "f", sig_index);
+  let except = builder.addException(kSig_v_v);
+  builder.addFunction("throw", kSig_v_v)
+      .addBody([
+        kExprThrow, except
+      ]).exportFunc();
+  builder.addFunction("catch", kSig_v_v)
+      .addBody([
+        kExprTry, kWasmStmt,
+          kExprCallFunction, fun,
+        kExprCatch, except,
+        kExprEnd,
+      ]).exportFunc();
+  let ex_obj = new Error("my exception");
+  let instance1 = builder.instantiate({ m: { f: assertUnreachable }});
+  let instance2 = builder.instantiate({ m: { f: function() { throw ex_obj }}});
+
+  assertThrows(() => instance1.exports.throw(), WebAssembly.RuntimeError);
+  assertThrowsEquals(() => instance2.exports.catch(), ex_obj);
+  try {
+    instance1.exports.throw();
+  } catch (e) {
+    ex_obj = e;
+  }
+  assertThrowsEquals(() => instance2.exports.catch(), ex_obj);
+})();
+
+// Check that two instances match their exceptions if they are shared properly,
+// even if the local exception index of export and import is different.
+(function TestMultiInstanceShared() {
+  print(arguments.callee.name);
+  let builder = new WasmModuleBuilder();
+  let sig_index = builder.addType(kSig_v_v);
+  let fun = builder.addImport("m", "f", sig_index);
+  let except1 = builder.addImportedException("m", "ex1", kSig_v_v);
+  let except2 = builder.addException(kSig_v_v);
+  builder.addExportOfKind("ex2", kExternalException, except2);
+  builder.addFunction("throw", kSig_v_v)
+      .addBody([
+        kExprThrow, except2
+      ]).exportFunc();
+  builder.addFunction("catch", kSig_v_v)
+      .addBody([
+        kExprTry, kWasmStmt,
+          kExprCallFunction, fun,
+        kExprCatch, except1,
+        kExprEnd,
+      ]).exportFunc();
+  let ex_obj = new Error("my exception");
+  let instance1 = builder.instantiate({ m: { f: assertUnreachable,
+                                             ex1: NewExportedException() }});
+  let instance2 = builder.instantiate({ m: { f: function() { throw ex_obj },
+                                             ex1: instance1.exports.ex2 }});
+
+  assertThrows(() => instance1.exports.throw(), WebAssembly.RuntimeError);
+  assertThrowsEquals(() => instance2.exports.catch(), ex_obj);
+  try {
+    instance1.exports.throw();
+  } catch (e) {
+    ex_obj = e;
+  }
+  assertDoesNotThrow(() => instance2.exports.catch());
+})();
+
+// Check that two instances based on different modules match their exceptions if
+// they are shared properly, even if the local exception index is different.
+(function TestMultiModuleShared() {
+  print(arguments.callee.name);
+  let builder1 = new WasmModuleBuilder();
+  let except1 = builder1.addException(kSig_v_v);
+  let except2 = builder1.addException(kSig_v_v);
+  builder1.addExportOfKind("ex", kExternalException, except2);
+  builder1.addFunction("throw", kSig_v_v)
+      .addBody([
+        kExprThrow, except2
+      ]).exportFunc();
+  let builder2 = new WasmModuleBuilder();
+  let sig_index = builder2.addType(kSig_v_v);
+  let fun = builder2.addImport("m", "f", sig_index);
+  let except = builder2.addImportedException("m", "ex", kSig_v_v);
+  builder2.addFunction("catch", kSig_v_v)
+      .addBody([
+        kExprTry, kWasmStmt,
+          kExprCallFunction, fun,
+        kExprCatch, except,
+        kExprEnd,
+      ]).exportFunc();
+  let ex_obj = new Error("my exception");
+  let instance1 = builder1.instantiate();
+  let instance2 = builder2.instantiate({ m: { f: function() { throw ex_obj },
+                                              ex: instance1.exports.ex }});
+
+  assertThrows(() => instance1.exports.throw(), WebAssembly.RuntimeError);
+  assertThrowsEquals(() => instance2.exports.catch(), ex_obj);
+  try {
+    instance1.exports.throw();
+  } catch (e) {
+    ex_obj = e;
+  }
+  assertDoesNotThrow(() => instance2.exports.catch());
+})();
diff --git a/test/mjsunit/wasm/exceptions.js b/test/mjsunit/wasm/exceptions.js
index 1979b46..68631f4 100644
--- a/test/mjsunit/wasm/exceptions.js
+++ b/test/mjsunit/wasm/exceptions.js
@@ -7,7 +7,7 @@
 load("test/mjsunit/wasm/wasm-constants.js");
 load("test/mjsunit/wasm/wasm-module-builder.js");
 
-function assertWasmThrows(runtime_id, values, code) {
+function assertWasmThrows(instance, runtime_id, values, code) {
   try {
     if (typeof code === 'function') {
       code();
@@ -16,7 +16,7 @@
     }
   } catch (e) {
     assertInstanceof(e, WebAssembly.RuntimeError);
-    var e_runtime_id = %GetWasmExceptionId(e);
+    var e_runtime_id = %GetWasmExceptionId(e, instance);
     assertTrue(Number.isInteger(e_runtime_id));
     assertEquals(e_runtime_id, runtime_id);
     var e_values = %GetWasmExceptionValues(e);
@@ -57,8 +57,8 @@
   let instance = builder.instantiate();
 
   assertEquals(1, instance.exports.throw_if_param_not_zero(0));
-  assertWasmThrows(except, [], () => instance.exports.throw_if_param_not_zero(10));
-  assertWasmThrows(except, [], () => instance.exports.throw_if_param_not_zero(-1));
+  assertWasmThrows(instance, except, [], () => instance.exports.throw_if_param_not_zero(10));
+  assertWasmThrows(instance, except, [], () => instance.exports.throw_if_param_not_zero(-1));
 })();
 
 // Test that empty try/catch blocks work.
@@ -135,7 +135,7 @@
 
   assertEquals(3, instance.exports.catch_different_exceptions(0));
   assertEquals(4, instance.exports.catch_different_exceptions(1));
-  assertWasmThrows(except3, [], () => instance.exports.catch_different_exceptions(2));
+  assertWasmThrows(instance, except3, [], () => instance.exports.catch_different_exceptions(2));
 })();
 
 // Test throwing an exception with multiple values.
@@ -150,7 +150,7 @@
       ]).exportFunc();
   let instance = builder.instantiate();
 
-  assertWasmThrows(except, [0, 1, 0, 2], () => instance.exports.throw_1_2());
+  assertWasmThrows(instance, except, [0, 1, 0, 2], () => instance.exports.throw_1_2());
 })();
 
 // Test throwing/catching the i32 parameter value.
@@ -185,8 +185,8 @@
       ]).exportFunc();
   let instance = builder.instantiate();
 
-  assertWasmThrows(except, [0, 5], () => instance.exports.throw_param(5));
-  assertWasmThrows(except, [6, 31026], () => instance.exports.throw_param(424242));
+  assertWasmThrows(instance, except, [0, 5], () => instance.exports.throw_param(5));
+  assertWasmThrows(instance, except, [6, 31026], () => instance.exports.throw_param(424242));
 })();
 
 // Test throwing/catching the f32 parameter value.
@@ -220,8 +220,8 @@
       ]).exportFunc();
   let instance = builder.instantiate();
 
-  assertWasmThrows(except, [16544, 0], () => instance.exports.throw_param(5.0));
-  assertWasmThrows(except, [16680, 0], () => instance.exports.throw_param(10.5));
+  assertWasmThrows(instance, except, [16544, 0], () => instance.exports.throw_param(5.0));
+  assertWasmThrows(instance, except, [16680, 0], () => instance.exports.throw_param(10.5));
 })();
 
 // Test throwing/catching an I64 value
@@ -273,8 +273,8 @@
       ]).exportFunc();
   let instance = builder.instantiate();
 
-  assertWasmThrows(except, [0, 10, 0, 5], () => instance.exports.throw_param(10, 5));
-  assertWasmThrows(except, [65535, 65535, 0, 13], () => instance.exports.throw_param(-1, 13));
+  assertWasmThrows(instance, except, [0, 10, 0, 5], () => instance.exports.throw_param(10, 5));
+  assertWasmThrows(instance, except, [65535, 65535, 0, 13], () => instance.exports.throw_param(-1, 13));
 })();
 
 // Test throwing/catching the F64 parameter value
@@ -309,8 +309,8 @@
       ]).exportFunc();
   let instance = builder.instantiate();
 
-  assertWasmThrows(except, [16404, 0, 0, 0], () => instance.exports.throw_param(5.0));
-  assertWasmThrows(except, [16739, 4816, 0, 0], () => instance.exports.throw_param(10000000.5));
+  assertWasmThrows(instance, except, [16404, 0, 0, 0], () => instance.exports.throw_param(5.0));
+  assertWasmThrows(instance, except, [16739, 4816, 0, 0], () => instance.exports.throw_param(10000000.5));
 })();
 
 // Test the encoding of a computed parameter value.
@@ -334,8 +334,8 @@
       ]).exportFunc()
   let instance = builder.instantiate();
 
-  assertWasmThrows(except, [65535, 65536-8], () => instance.exports.throw_expr_with_params(1.5, 2.5, 4));
-  assertWasmThrows(except, [0, 12], () => instance.exports.throw_expr_with_params(5.7, 2.5, 4));
+  assertWasmThrows(instance, except, [65535, 65536-8], () => instance.exports.throw_expr_with_params(1.5, 2.5, 4));
+  assertWasmThrows(instance, except, [0, 12], () => instance.exports.throw_expr_with_params(5.7, 2.5, 4));
 })();
 
 // Now that we know catching works locally, we test catching exceptions that