[dart2wasm] Use polyfill for string constants if builtin isn't available

This makes us always emit strings into the `<app>.wasm` module only.
If the runtime doesn't support `js-string` builtin (and we
don't have `--require-js-string` builtin flag on) then we use a
JS Proxy object to resolve the string imports.

Now we only emit string constants in the `<app>.mjs` file iff those
cannot be encoded in the `<app>.wasm` file due to being invalid
utf-8 (such as unpaired surrogates, ...)

Change-Id: I7f4a0d61238e847c0c7dccadfa9e473f76512dc1
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/426462
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
diff --git a/pkg/dart2wasm/lib/compile.dart b/pkg/dart2wasm/lib/compile.dart
index edc6fd4..c0fbc32 100644
--- a/pkg/dart2wasm/lib/compile.dart
+++ b/pkg/dart2wasm/lib/compile.dart
@@ -342,6 +342,7 @@
   final jsRuntime = isDynamicSubmodule
       ? jsRuntimeFinalizer.generateDynamicSubmodule(
           translator.functions.translatedProcedures,
+          translator.options.requireJsStringBuiltin,
           translator.internalizedStringsForJSRuntime)
       : jsRuntimeFinalizer.generate(
           translator.functions.translatedProcedures,
diff --git a/pkg/dart2wasm/lib/js/runtime_generator.dart b/pkg/dart2wasm/lib/js/runtime_generator.dart
index d425a93..b3c7a36 100644
--- a/pkg/dart2wasm/lib/js/runtime_generator.dart
+++ b/pkg/dart2wasm/lib/js/runtime_generator.dart
@@ -85,13 +85,23 @@
     return jsMethods.toString();
   }
 
-  String _generateInternalizedStrings(List<String> constantStrings) {
-    if (constantStrings.isEmpty) return '';
-    return '''
-      s: [
-        ${constantStrings.map(escape).join(',\n')}
-      ],
-''';
+  String _generateInternalizedStrings(
+      bool requireJsBuiltin, List<String> constantStrings) {
+    final sb = StringBuffer();
+    String indent = '';
+    if (constantStrings.isNotEmpty) {
+      sb.writeln('s: [');
+      indent = '      ';
+      for (final c in constantStrings) {
+        sb.writeln('$indent  ${escape(c)},');
+      }
+      sb.writeln('$indent],');
+    }
+    if (!requireJsBuiltin) {
+      sb.writeln(
+          '${indent}S: new Proxy({}, { get(_, prop) { return prop; } }),');
+    }
+    return '$sb';
   }
 
   String generate(
@@ -106,7 +116,8 @@
       if (requireJsBuiltin) 'importedStringConstants: \'S\'',
     ];
 
-    String internalizedStrings = _generateInternalizedStrings(constantStrings);
+    String internalizedStrings =
+        _generateInternalizedStrings(requireJsBuiltin, constantStrings);
 
     final jsStringBuiltinPolyfillImportVars = {
       'JS_POLYFILL_IMPORT':
@@ -135,14 +146,14 @@
     });
   }
 
-  String generateDynamicSubmodule(
-      Iterable<Procedure> translatedProcedures, List<String> constantStrings) {
+  String generateDynamicSubmodule(Iterable<Procedure> translatedProcedures,
+      bool requireJsStringBuiltin, List<String> constantStrings) {
     final jsMethods = generateJsMethods(translatedProcedures);
 
     return dynamicSubmoduleJsImportTemplate.instantiate({
       'JS_METHODS': jsMethods,
       'IMPORTED_JS_STRINGS_IN_MJS':
-          _generateInternalizedStrings(constantStrings),
+          _generateInternalizedStrings(requireJsStringBuiltin, constantStrings),
     });
   }
 }
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
index d43b4b7..87cd2c9 100644
--- a/pkg/dart2wasm/lib/translator.dart
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -1803,7 +1803,7 @@
       return false;
     }
 
-    if (!options.requireJsStringBuiltin || hasUnpairedSurrogate(s)) {
+    if (hasUnpairedSurrogate(s)) {
       // Unpaired surrogates can't be encoded as UTF-8, import them from JS
       // runtime.
       final i = internalizedStringsForJSRuntime.length;