[dart2wasm] Add flag to allow experimental wasm interop.

Adds a flag users can use via

    dart compile wasm \
        --extra-compiler-option=--enable-experimental-wasm-interop

which allows code to import `dart:_wasm` and use import/export
pragmas.

Similar to how we have a way to import `dart:ffi`

    dart compile wasm \
        --extra-compiler-option=--enable-experimental-ffi

TEST=web/wasm/allow_import_export_pragmas_test (positive test)
TEST=web/wasm/reject_import_export_pragmas_test (negative test)

Change-Id: Ibdbbac6c3aa049b2759e96b7b749dd30ecc6aaed
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/370063
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Jackson Gardner <jacksongardner@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
diff --git a/pkg/dart2wasm/lib/compile.dart b/pkg/dart2wasm/lib/compile.dart
index 45a08ae..89ec6c0 100644
--- a/pkg/dart2wasm/lib/compile.dart
+++ b/pkg/dart2wasm/lib/compile.dart
@@ -82,6 +82,8 @@
   }
   final WasmTarget target = WasmTarget(
       enableExperimentalFfi: options.translatorOptions.enableExperimentalFfi,
+      enableExperimentalWasmInterop:
+          options.translatorOptions.enableExperimentalWasmInterop,
       removeAsserts: !options.translatorOptions.enableAsserts,
       mode: mode);
   CompilerOptions compilerOptions = CompilerOptions()
diff --git a/pkg/dart2wasm/lib/dart2wasm.dart b/pkg/dart2wasm/lib/dart2wasm.dart
index bda60de..d106088 100644
--- a/pkg/dart2wasm/lib/dart2wasm.dart
+++ b/pkg/dart2wasm/lib/dart2wasm.dart
@@ -55,6 +55,9 @@
   Flag("verify-type-checks",
       (o, value) => o.translatorOptions.verifyTypeChecks = value,
       defaultsTo: _d.translatorOptions.verifyTypeChecks),
+  Flag("enable-experimental-wasm-interop",
+      (o, value) => o.translatorOptions.enableExperimentalWasmInterop = value,
+      defaultsTo: _d.translatorOptions.enableExperimentalWasmInterop),
   IntOption(
       "inlining-limit", (o, value) => o.translatorOptions.inliningLimit = value,
       defaultsTo: "${_d.translatorOptions.inliningLimit}"),
diff --git a/pkg/dart2wasm/lib/target.dart b/pkg/dart2wasm/lib/target.dart
index 7342b26..4af0c96 100644
--- a/pkg/dart2wasm/lib/target.dart
+++ b/pkg/dart2wasm/lib/target.dart
@@ -91,12 +91,14 @@
 class WasmTarget extends Target {
   WasmTarget(
       {this.enableExperimentalFfi = true,
+      this.enableExperimentalWasmInterop = true,
       this.removeAsserts = false,
       this.mode = Mode.regular});
 
   final bool removeAsserts;
   final Mode mode;
   final bool enableExperimentalFfi;
+  final bool enableExperimentalWasmInterop;
   Class? _growableList;
   Class? _immutableList;
   Class? _wasmDefaultMap;
@@ -175,13 +177,28 @@
   bool mayDefineRestrictedType(Uri uri) => uri.isScheme('dart');
 
   @override
-  bool allowPlatformPrivateLibraryAccess(Uri importer, Uri imported) =>
-      super.allowPlatformPrivateLibraryAccess(importer, imported) ||
-      importer.path.contains('tests/web/wasm') ||
-      importer.isScheme('package') &&
-          (importer.path == 'js/js.dart' ||
-              importer.path.startsWith('ui/') &&
-                  imported.toString() == 'dart:_wasm');
+  bool allowPlatformPrivateLibraryAccess(Uri importer, Uri imported) {
+    if (super.allowPlatformPrivateLibraryAccess(importer, imported)) {
+      return true;
+    }
+
+    if (imported.toString() == 'dart:_wasm') {
+      return enableExperimentalWasmInterop;
+    }
+
+    final importerString = importer.toString();
+
+    // We have some tests that import dart:js*
+    if (importerString.contains('tests/web/wasm')) return true;
+
+    // Flutter's dart:ui is also package:ui (in test mode)
+    if (importerString.startsWith('package:ui/')) return true;
+
+    // package:js can import dart:js* & dart:_js_*
+    if (importerString.startsWith('package:js/')) return true;
+
+    return false;
+  }
 
   void _patchHostEndian(CoreTypes coreTypes) {
     // Fix Endian.host to be a const field equal to Endian.little instead of
@@ -247,10 +264,12 @@
       ReferenceFromIndex? referenceFromIndex,
       {void Function(String msg)? logger,
       ChangedStructureNotifier? changedStructureNotifier}) {
-    // Check `wasm:import` and `wasm:export` pragmas before FFI transforms as
-    // FFI transforms convert JS interop annotations to these pragmas.
-    _checkWasmImportExportPragmas(libraries, coreTypes,
-        diagnosticReporter as DiagnosticReporter<Message, LocatedMessage>);
+    if (!enableExperimentalWasmInterop) {
+      // Check `wasm:import` and `wasm:export` pragmas before FFI transforms as
+      // FFI transforms convert JS interop annotations to these pragmas.
+      _checkWasmImportExportPragmas(libraries, coreTypes,
+          diagnosticReporter as DiagnosticReporter<Message, LocatedMessage>);
+    }
 
     Set<Library> transitiveImportingJSInterop = {
       ...jsInteropHelper.calculateTransitiveImportsOfJsInteropIfUsed(
@@ -545,9 +564,7 @@
     if (importUri.isScheme('dart') ||
         (importUri.isScheme('package') &&
             JsInteropChecks.allowedInteropLibrariesInDart2WasmPackages
-                .any((pkg) => importUri.pathSegments.first == pkg)) ||
-        (importUri.path.contains('tests/web/wasm') &&
-            !importUri.path.contains('reject_import_export_pragmas'))) {
+                .any((pkg) => importUri.pathSegments.first == pkg))) {
       continue;
     }
 
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
index 938cca1..f103b14 100644
--- a/pkg/dart2wasm/lib/translator.dart
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -45,6 +45,7 @@
   bool verifyTypeChecks = false;
   bool verbose = false;
   bool enableExperimentalFfi = false;
+  bool enableExperimentalWasmInterop = false;
   int inliningLimit = 0;
   int? sharedMemoryMaxPages;
   List<int> watchPoints = [];
diff --git a/tests/web/wasm/allow_import_export_pragmas_test.dart b/tests/web/wasm/allow_import_export_pragmas_test.dart
new file mode 100644
index 0000000..57fea40
--- /dev/null
+++ b/tests/web/wasm/allow_import_export_pragmas_test.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// dart2wasmOptions=--enable-experimental-wasm-interop
+
+// Test that importing `dart:_wasm` and using import/export pragmas works if the
+// compiler is given the `--enable-experimental-wasm-interop` flag.
+
+import 'dart:_wasm';
+
+@pragma('wasm:export', 'f')
+void f() {}
+
+@pragma('wasm:import', 'g')
+external WasmI32 g(WasmI32 x);
+
+void main() {}
diff --git a/tests/web/wasm/callback_test.dart b/tests/web/wasm/callback_test.dart
index 7d49c93..118c177 100644
--- a/tests/web/wasm/callback_test.dart
+++ b/tests/web/wasm/callback_test.dart
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+// dart2wasmOptions=--extra-compiler-option=--enable-experimental-wasm-interop
+
 import 'dart:_wasm';
 import 'dart:js_util';
 
diff --git a/tests/web/wasm/js_exceptions_test.dart b/tests/web/wasm/js_exceptions_test.dart
index f31d7ff..7813de8 100644
--- a/tests/web/wasm/js_exceptions_test.dart
+++ b/tests/web/wasm/js_exceptions_test.dart
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+// dart2wasmOptions=--extra-compiler-option=--enable-experimental-wasm-interop
+
 import 'dart:js_interop';
 import 'dart:_wasm';
 
diff --git a/tests/web/wasm/table_test.dart b/tests/web/wasm/table_test.dart
index 91351ee..de0ef1a 100644
--- a/tests/web/wasm/table_test.dart
+++ b/tests/web/wasm/table_test.dart
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+// dart2wasmOptions=--extra-compiler-option=--enable-experimental-wasm-interop
+
 import 'dart:_wasm';
 
 import 'package:expect/expect.dart';
diff --git a/tests/web/wasm/wasm_types_test.dart b/tests/web/wasm/wasm_types_test.dart
index 44f843b..dd52244 100644
--- a/tests/web/wasm/wasm_types_test.dart
+++ b/tests/web/wasm/wasm_types_test.dart
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+// dart2wasmOptions=--extra-compiler-option=--enable-experimental-wasm-interop
+
 import 'dart:_wasm';
 
 import 'package:expect/expect.dart';