[dart2wasm] Minify more errors

Transform front-end generated `throw` expressions (as indicated by the
kernel node's `forErrorHandling` flag) to error throwing functions that,
in minify mode, throw without details.

ACX demo sizes: (`-O2 --minify`)

- Before: 12,841,863 bytes
- After: 12,396,399 bytes
- Diff: -445,464 bytes, -3.46%

Issue: https://github.com/dart-lang/sdk/issues/60432
Change-Id: I3c0bfebd5a9cb460af312dafb63b5a0c9aeda22e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/430380
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
diff --git a/pkg/dart2wasm/lib/transformers.dart b/pkg/dart2wasm/lib/transformers.dart
index 0552186..af643d6 100644
--- a/pkg/dart2wasm/lib/transformers.dart
+++ b/pkg/dart2wasm/lib/transformers.dart
@@ -72,6 +72,55 @@
 
   CoreTypes get coreTypes => env.coreTypes;
 
+  /// Maps error handling function, constructor, factory references to functions
+  /// that, when minifying, throw errors without details, saving binary space.
+  ///
+  /// Calls to these error handling function etc. references are transformed
+  /// when they are introduced by the front-end, as indicated by
+  /// [Throw.forErrorHandling].
+  late final Map<Reference, Procedure> _errorHandlingFunctions = {
+    coreTypes.index
+            .getConstructor('dart:_internal', 'LateError', 'fieldADI')
+            .reference:
+        coreTypes.index.getTopLevelProcedure(
+            'dart:_error_utils', '_throwLateErrorFieldADI'),
+    coreTypes.index
+            .getConstructor('dart:_internal', 'LateError', 'localADI')
+            .reference:
+        coreTypes.index.getTopLevelProcedure(
+            'dart:_error_utils', '_throwLateErrorLocalADI'),
+    coreTypes.index
+            .getConstructor('dart:_internal', 'LateError', 'fieldNI')
+            .reference:
+        coreTypes.index.getTopLevelProcedure(
+            'dart:_error_utils', '_throwLateErrorFieldNI'),
+    coreTypes.index
+            .getConstructor('dart:_internal', 'LateError', 'localNI')
+            .reference:
+        coreTypes.index.getTopLevelProcedure(
+            'dart:_error_utils', '_throwLateErrorLocalNI'),
+    coreTypes.index
+            .getConstructor('dart:_internal', 'LateError', 'fieldAI')
+            .reference:
+        coreTypes.index.getTopLevelProcedure(
+            'dart:_error_utils', '_throwLateErrorFieldAI'),
+    coreTypes.index
+            .getConstructor('dart:_internal', 'LateError', 'localAI')
+            .reference:
+        coreTypes.index.getTopLevelProcedure(
+            'dart:_error_utils', '_throwLateErrorLocalAI'),
+    coreTypes.index
+            .getProcedure('dart:core', 'NoSuchMethodError', 'withInvocation')
+            .reference:
+        coreTypes.index.getTopLevelProcedure(
+            'dart:_error_utils', '_throwNoSuchMethodErrorWithInvocation'),
+    coreTypes.index
+            .getConstructor('dart:_internal', 'ReachabilityError', '')
+            .reference:
+        coreTypes.index.getTopLevelProcedure(
+            'dart:_error_utils', '_throwReachabilityError'),
+  };
+
   _WasmTransformer(CoreTypes coreTypes, ClassHierarchy hierarchy)
       : env = TypeEnvironment(coreTypes, hierarchy),
         _nonNullableTypeType = coreTypes.index
@@ -812,6 +861,28 @@
           StringLiteral(import.name!)
         ]));
   }
+
+  @override
+  TreeNode visitThrow(Throw node) {
+    node.transformChildren(this);
+    if (node.forErrorHandling) {
+      final expression = node.expression;
+      if (expression is ConstructorInvocation) {
+        final throwFunction =
+            _errorHandlingFunctions[expression.targetReference];
+        if (throwFunction != null) {
+          return StaticInvocation(throwFunction, expression.arguments);
+        }
+      } else if (expression is StaticInvocation) {
+        final throwFunction =
+            _errorHandlingFunctions[expression.targetReference];
+        if (throwFunction != null) {
+          return StaticInvocation(throwFunction, expression.arguments);
+        }
+      }
+    }
+    return node;
+  }
 }
 
 class _AsyncStarFrame {
diff --git a/pkg/front_end/testcases/dart2wasm/inference_update_2/issue52452.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2wasm/inference_update_2/issue52452.dart.strong.transformed.expect
index 94c3211..fd6a3c9 100644
--- a/pkg/front_end/testcases/dart2wasm/inference_update_2/issue52452.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/dart2wasm/inference_update_2/issue52452.dart.strong.transformed.expect
@@ -1,7 +1,7 @@
 library;
 import self as self;
 import "dart:core" as core;
-import "dart:_internal" as _in;
+import "dart:_error_utils" as _er;
 
 class C extends core::Object {
   final field core::int? _f2;
@@ -36,16 +36,16 @@
   field core::int? _#M1#_f4 = null;
   field core::bool _#M1#_f4#isSet = false;
   get _f2() → core::int?
-    return this.{self::M1::_#M1#_f2#isSet}{core::bool} ?{core::int?} this.{self::M1::_#M1#_f2}{core::int?} : throw{for-error-handling} new _in::LateError::fieldNI("_f2");
+    return this.{self::M1::_#M1#_f2#isSet}{core::bool} ?{core::int?} this.{self::M1::_#M1#_f2}{core::int?} : _er::_throwLateErrorFieldNI("_f2");
   set _f2(final core::int? _f2#param) → void {
     this.{self::M1::_#M1#_f2#isSet} = true;
     this.{self::M1::_#M1#_f2} = _f2#param;
   }
   get _f3() → core::int?
-    return this.{self::M1::_#M1#_f3#isSet}{core::bool} ?{core::int?} this.{self::M1::_#M1#_f3}{core::int?} : throw{for-error-handling} new _in::LateError::fieldNI("_f3");
+    return this.{self::M1::_#M1#_f3#isSet}{core::bool} ?{core::int?} this.{self::M1::_#M1#_f3}{core::int?} : _er::_throwLateErrorFieldNI("_f3");
   set _f3(final core::int? _f3#param) → void
     if(this.{self::M1::_#M1#_f3#isSet}{core::bool})
-      throw{for-error-handling} new _in::LateError::fieldAI("_f3");
+      _er::_throwLateErrorFieldAI("_f3");
     else {
       this.{self::M1::_#M1#_f3#isSet} = true;
       this.{self::M1::_#M1#_f3} = _f3#param;
@@ -54,7 +54,7 @@
     if(!this.{self::M1::_#M1#_f4#isSet}{core::bool}) {
       final core::int? #t1 = 0;
       if(this.{self::M1::_#M1#_f4#isSet}{core::bool})
-        throw{for-error-handling} new _in::LateError::fieldADI("_f4");
+        _er::_throwLateErrorFieldADI("_f4");
       this.{self::M1::_#M1#_f4} = #t1;
       this.{self::M1::_#M1#_f4#isSet} = true;
     }
@@ -94,14 +94,14 @@
     : super self::B::•(i)
     ;
   get _f2() → core::int?
-    return this.{self::M1::_#M1#_f2#isSet}{core::bool} ?{core::int?} this.{self::M1::_#M1#_f2}{core::int?} : throw{for-error-handling} new _in::LateError::fieldNI("_f2");
+    return this.{self::M1::_#M1#_f2#isSet}{core::bool} ?{core::int?} this.{self::M1::_#M1#_f2}{core::int?} : _er::_throwLateErrorFieldNI("_f2");
   get _f3() → core::int?
-    return this.{self::M1::_#M1#_f3#isSet}{core::bool} ?{core::int?} this.{self::M1::_#M1#_f3}{core::int?} : throw{for-error-handling} new _in::LateError::fieldNI("_f3");
+    return this.{self::M1::_#M1#_f3#isSet}{core::bool} ?{core::int?} this.{self::M1::_#M1#_f3}{core::int?} : _er::_throwLateErrorFieldNI("_f3");
   get _f4() → core::int? {
     if(!this.{self::M1::_#M1#_f4#isSet}{core::bool}) {
       final core::int? #t2 = 0;
       if(this.{self::M1::_#M1#_f4#isSet}{core::bool})
-        throw{for-error-handling} new _in::LateError::fieldADI("_f4");
+        _er::_throwLateErrorFieldADI("_f4");
       this.{self::M1::_#M1#_f4} = #t2;
       this.{self::M1::_#M1#_f4#isSet} = true;
     }
@@ -113,7 +113,7 @@
   }
   set _f3(final core::int? _f3#param) → void
     if(this.{self::M1::_#M1#_f3#isSet}{core::bool})
-      throw{for-error-handling} new _in::LateError::fieldAI("_f3");
+      _er::_throwLateErrorFieldAI("_f3");
     else {
       this.{self::M1::_#M1#_f3#isSet} = true;
       this.{self::M1::_#M1#_f3} = _f3#param;
diff --git a/sdk/lib/_internal/wasm/lib/error_utils.dart b/sdk/lib/_internal/wasm/lib/error_utils.dart
index 4cbb401..f2c8408 100644
--- a/sdk/lib/_internal/wasm/lib/error_utils.dart
+++ b/sdk/lib/_internal/wasm/lib/error_utils.dart
@@ -168,6 +168,49 @@
   throw ArgumentError.notNull();
 }
 
+Never _throwLateErrorFieldADI(String fieldName) {
+  if (minify) throw _lateErrorFieldADI;
+  throw LateError.fieldADI(fieldName);
+}
+
+Never _throwLateErrorLocalADI(String fieldName) {
+  if (minify) throw _lateErrorLocalADI;
+  throw LateError.localADI(fieldName);
+}
+
+Never _throwLateErrorFieldNI(String fieldName) {
+  if (minify) throw _lateErrorFieldNI;
+  throw LateError.fieldNI(fieldName);
+}
+
+Never _throwLateErrorLocalNI(String fieldName) {
+  if (minify) throw _lateErrorLocalNI;
+  throw LateError.localNI(fieldName);
+}
+
+Never _throwLateErrorFieldAI(String fieldName) {
+  if (minify) throw _lateErrorFieldAI;
+  throw LateError.fieldAI(fieldName);
+}
+
+Never _throwLateErrorLocalAI(String fieldName) {
+  if (minify) throw _lateErrorLocalAI;
+  throw LateError.localAI(fieldName);
+}
+
+Never _throwNoSuchMethodErrorWithInvocation(
+  Object? receiver,
+  Invocation invocation,
+) {
+  if (minify) throw _noSuchMethodErrorWithoutDetails;
+  throw NoSuchMethodError.withInvocation(receiver, invocation);
+}
+
+Never _throwReachabilityError([String? _message]) {
+  if (minify) throw _reachabilityError;
+  throw ReachabilityError(_message);
+}
+
 const _indexErrorWithoutDetails = _ErrorWithoutDetails(
   'IndexError (details omitted due to --minify)',
 );
@@ -183,10 +226,44 @@
 const _negativeOrZeroValueErrorWithoutDetails = _ErrorWithoutDetails(
   'Value was negative or zero (details omitted due to --minify)',
 );
-const _nullErrorWithoutDetails = _ErrorWithoutDetails(
+const _nullErrorWithoutDetails = _LateErrorWithoutDetails(
   'Value must not be null (details omitted due to --minify)',
 );
 
+const _lateErrorFieldADI = _LateErrorWithoutDetails(
+  'LateInitializationError: Field has been assigned during initialization (details omitted due to --minify)',
+);
+
+const _lateErrorLocalADI = _LateErrorWithoutDetails(
+  'LateInitializationError: Local has been assigned during initialization (details omitted due to --minify)',
+);
+
+const _lateErrorFieldNI = _LateErrorWithoutDetails(
+  'LateInitializationError: Field has not been initialized (details omitted due to --minify)',
+);
+
+const _lateErrorLocalNI = _LateErrorWithoutDetails(
+  'LateInitializationError: Local has not been initialized (details omitted due to --minify)',
+);
+
+const _lateErrorFieldAI = _LateErrorWithoutDetails(
+  'LateInitializationError: Field has already been initialized (details omitted due to --minify)',
+);
+
+const _lateErrorLocalAI = _LateErrorWithoutDetails(
+  'LateInitializationError: Local has already been initialized (details omitted due to --minify)',
+);
+
+const _reachabilityError = _LateErrorWithoutDetails(
+  'ReachabilityError (details omitted due to --minify)',
+);
+
+const _NoSuchMethodErrorWithoutDetails _noSuchMethodErrorWithoutDetails =
+    _NoSuchMethodErrorWithoutDetails();
+
+const _TypeErrorWithoutDetails typeErrorWithoutDetails =
+    _TypeErrorWithoutDetails();
+
 class _ErrorWithoutDetails implements Error {
   final String _message;
   const _ErrorWithoutDetails(this._message);
@@ -196,9 +273,6 @@
   String toString() => _message;
 }
 
-const _TypeErrorWithoutDetails typeErrorWithoutDetails =
-    _TypeErrorWithoutDetails();
-
 class _TypeErrorWithoutDetails implements TypeError {
   const _TypeErrorWithoutDetails();
 
@@ -207,3 +281,21 @@
   String toString() =>
       'Runtime type check failed (details omitted due to --minify)';
 }
+
+class _NoSuchMethodErrorWithoutDetails implements NoSuchMethodError {
+  const _NoSuchMethodErrorWithoutDetails();
+
+  StackTrace? get stackTrace => null;
+
+  String toString() => 'NoSuchMethodError (details omitted due to --minify)';
+}
+
+class _LateErrorWithoutDetails implements LateError {
+  final String _message;
+
+  const _LateErrorWithoutDetails(this._message);
+
+  StackTrace? get stackTrace => null;
+
+  String toString() => _message;
+}