[dart2wasm] Make use of TFA-inferred types in more cases

Avoid falling back to variable type if the variable is `final` and has a
more precise inferred type.

Use unboxing information on methods to make unboxed return types
when TFA concludes it's int/double.

This will partially fix a performance regression in SplayHarder
benchmark where a parameter of type `num` was unboxed to `double` but
the local variable was kept as `num` and therefore always caused a box
allocation. After this change the box allocation is moved to the place
where we call `double.toString()`.

TEST=Updated expectation files.

Change-Id: I0252cf86ada9a8affbb46a31e913504930057650
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/365560
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart
index 7a52892..5584f31 100644
--- a/pkg/dart2wasm/lib/code_generator.dart
+++ b/pkg/dart2wasm/lib/code_generator.dart
@@ -385,7 +385,7 @@
           );
         }
       }
-      if (!isForwarder) {
+      if (!isForwarder && !variable.isFinal) {
         // We now have a precise local that can contain the values passed by
         // callers, but the body may assign less precise types to this variable,
         // so we may introduce another local variable that is less precise.
diff --git a/pkg/dart2wasm/lib/dispatch_table.dart b/pkg/dart2wasm/lib/dispatch_table.dart
index bfea39e..2cfb1ea 100644
--- a/pkg/dart2wasm/lib/dispatch_table.dart
+++ b/pkg/dart2wasm/lib/dispatch_table.dart
@@ -101,7 +101,7 @@
         if (target.isImplicitGetter) {
           positional = const [];
           named = const {};
-          returns = [member.getterType];
+          returns = [translator.typeOfReturnValue(member)];
         } else {
           positional = [member.setterType];
           named = const {};
@@ -124,7 +124,9 @@
             for (VariableDeclaration param in function.namedParameters)
               param.name!: typeForParam(param, param.isRequired)
           };
-          returns = target.isSetter ? const [] : [function.returnType];
+          returns = target.isSetter
+              ? const []
+              : [translator.typeOfReturnValue(member)];
         }
       }
       assert(returns.length <= outputSets.length);
diff --git a/pkg/dart2wasm/lib/functions.dart b/pkg/dart2wasm/lib/functions.dart
index 7876d9c..aa49338 100644
--- a/pkg/dart2wasm/lib/functions.dart
+++ b/pkg/dart2wasm/lib/functions.dart
@@ -64,7 +64,7 @@
           // `WebAssembly.Function`.
           m.types.splitRecursionGroup();
           w.FunctionType ftype = _makeFunctionType(
-              translator, member.reference, [member.function.returnType], null,
+              translator, member.reference, null,
               isImportOrExport: true);
           m.types.splitRecursionGroup();
           _functions[member.reference] =
@@ -82,8 +82,7 @@
         // publicly exposed types to be defined in separate recursion groups
         // from GC types.
         m.types.splitRecursionGroup();
-        _makeFunctionType(
-            translator, member.reference, [member.function.returnType], null,
+        _makeFunctionType(translator, member.reference, null,
             isImportOrExport: true);
         m.types.splitRecursionGroup();
       }
@@ -106,9 +105,8 @@
         _worklist.add(target);
         assert(!node.isInstanceMember);
         assert(!node.isGetter);
-        w.FunctionType ftype = _makeFunctionType(
-            translator, target, [node.function.returnType], null,
-            isImportOrExport: true);
+        w.FunctionType ftype =
+            _makeFunctionType(translator, target, null, isImportOrExport: true);
         w.BaseFunction function = m.functions.define(ftype, "$node");
         _functions[target] = function;
         m.exports.export(export.value, function);
@@ -226,7 +224,7 @@
     if (!node.isInstanceMember) {
       if (target == node.fieldReference) {
         // Static field initializer function
-        return _makeFunctionType(translator, target, [node.type], null);
+        return _makeFunctionType(translator, target, null);
       }
       String kind = target == node.setterReference ? "setter" : "getter";
       throw "No implicit $kind function for static field: $node";
@@ -239,8 +237,7 @@
     assert(!node.isAbstract);
     return node.isInstanceMember
         ? translator.dispatchTable.selectorForTarget(node.reference).signature
-        : _makeFunctionType(
-            translator, target, [node.function.returnType], null);
+        : _makeFunctionType(translator, target, null);
   }
 
   @override
@@ -440,8 +437,8 @@
   return inputs;
 }
 
-w.FunctionType _makeFunctionType(Translator translator, Reference target,
-    List<DartType> returnTypes, w.ValueType? receiverType,
+w.FunctionType _makeFunctionType(
+    Translator translator, Reference target, w.ValueType? receiverType,
     {bool isImportOrExport = false}) {
   Member member = target.asMember;
 
@@ -461,12 +458,13 @@
       (isImportOrExport && t is VoidType) ||
       (t is InterfaceType && t.classNode == translator.wasmVoidClass);
 
-  final List<w.ValueType> outputs = emptyOutputList
-      ? const []
-      : returnTypes
-          .where((t) => !isVoidType(t))
-          .map((t) => translateType(t))
-          .toList();
+  final List<w.ValueType> outputs;
+  if (emptyOutputList) {
+    outputs = const [];
+  } else {
+    final DartType returnType = translator.typeOfReturnValue(member);
+    outputs = !isVoidType(returnType) ? [translateType(returnType)] : const [];
+  }
 
   return translator.m.types.defineFunction(inputs, outputs);
 }
diff --git a/pkg/dart2wasm/lib/transformers.dart b/pkg/dart2wasm/lib/transformers.dart
index 4f8471e..ec82b67 100644
--- a/pkg/dart2wasm/lib/transformers.dart
+++ b/pkg/dart2wasm/lib/transformers.dart
@@ -32,6 +32,7 @@
 
   Member? _currentMember;
   StaticTypeContext? _cachedTypeContext;
+  final Set<VariableDeclaration> _implicitFinalVariables = {};
 
   final Library _coreLibrary;
   final InterfaceType _nonNullableTypeType;
@@ -112,9 +113,13 @@
   defaultMember(Member node) {
     _currentMember = node;
     _cachedTypeContext = null;
+    _implicitFinalVariables.clear();
 
     final result = super.defaultMember(node);
 
+    for (final node in _implicitFinalVariables) {
+      node.isFinal = true;
+    }
     _currentMember = null;
     _cachedTypeContext = null;
     return result;
@@ -153,6 +158,20 @@
   }
 
   @override
+  visitVariableDeclaration(VariableDeclaration node) {
+    if (!node.isFinal) {
+      _implicitFinalVariables.add(node);
+    }
+    return super.visitVariableDeclaration(node);
+  }
+
+  @override
+  visitVariableSet(VariableSet node) {
+    _implicitFinalVariables.remove(node.variable);
+    return super.visitVariableSet(node);
+  }
+
+  @override
   TreeNode visitClass(Class cls) {
     // For every concrete class whose type parameters do not match the type
     // parameters of it's super class we embed a special virtual function
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
index de62490..7d4f330 100644
--- a/pkg/dart2wasm/lib/translator.dart
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -11,6 +11,7 @@
 import 'package:kernel/type_environment.dart';
 import 'package:vm/metadata/direct_call.dart';
 import 'package:vm/metadata/inferred_type.dart';
+import 'package:vm/metadata/unboxing_info.dart';
 import 'package:wasm_builder/wasm_builder.dart' as w;
 
 import 'class_info.dart';
@@ -79,6 +80,10 @@
       (component.metadata[InferredArgTypeMetadataRepository.repositoryTag]
               as InferredArgTypeMetadataRepository)
           .mapping;
+  late final Map<TreeNode, UnboxingInfoMetadata> unboxingInfoMetadata =
+      (component.metadata[UnboxingInfoMetadataRepository.repositoryTag]
+              as UnboxingInfoMetadataRepository)
+          .mapping;
 
   // Other parts of the global compiler state.
   @override
@@ -859,11 +864,10 @@
         return;
       }
       if (to != voidMarker) {
-        assert(to is w.RefType && to.nullable);
-        // This can happen when a void method has its return type overridden
-        // to return a value, in which case the selector signature will have a
-        // non-void return type to encompass all possible return values.
-        b.ref_null((to as w.RefType).heapType.bottomType);
+        // This can happen e.g. when a `return;` is guaranteed to be never taken
+        // but TFA didn't remove the dead code. In that case we synthesize a
+        // dummy value.
+        globals.instantiateDummyValue(b, to);
         return;
       }
     }
@@ -990,11 +994,30 @@
     return node.type;
   }
 
+  DartType typeOfReturnValue(Member member) {
+    final unboxingInfo = unboxingInfoMetadata[member];
+    if (unboxingInfo != null) {
+      final returnInfo = unboxingInfo.returnInfo;
+      if (returnInfo.kind == UnboxingKind.int) {
+        return coreTypes.intRawType(Nullability.nonNullable);
+      }
+      if (returnInfo.kind == UnboxingKind.double) {
+        return coreTypes.doubleRawType(Nullability.nonNullable);
+      }
+    }
+    if (member is Field) return member.type;
+    return member.function!.returnType;
+  }
+
   w.ValueType translateTypeOfParameter(
       VariableDeclaration node, bool isRequired) {
     return translateType(typeOfParameterVariable(node, isRequired));
   }
 
+  w.ValueType translateTypeOfReturnValue(Member node) {
+    return translateType(typeOfReturnValue(node));
+  }
+
   w.ValueType translateTypeOfField(Field node) {
     return translateType(_inferredTypeOfField(node) ?? node.type);
   }
@@ -1012,7 +1035,11 @@
   }
 
   DartType? _inferredTypeOfLocalVariable(VariableDeclaration node) {
-    return _filterInferredType(node.type, inferredTypeMetadata[node]);
+    InferredType? inferredType = inferredTypeMetadata[node];
+    if (node.isFinal) {
+      inferredType ??= inferredTypeMetadata[node.initializer];
+    }
+    return _filterInferredType(node.type, inferredType);
   }
 
   DartType? _filterInferredType(
diff --git a/pkg/front_end/testcases/dart2wasm/for_in.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2wasm/for_in.dart.strong.transformed.expect
index 6aafc34..33f238d 100644
--- a/pkg/front_end/testcases/dart2wasm/for_in.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/dart2wasm/for_in.dart.strong.transformed.expect
@@ -3,22 +3,22 @@
 import "dart:core" as core;
 import "dart:async" as asy;
 
-static method method(core::Iterable<core::int> iterable) → dynamic {
+static method method(final core::Iterable<core::int> iterable) → dynamic {
   {
-    synthesized core::Iterator<core::int> #forIterator = iterable.{core::Iterable::iterator}{core::Iterator<core::int>};
+    final synthesized core::Iterator<core::int> #forIterator = iterable.{core::Iterable::iterator}{core::Iterator<core::int>};
     for (; #forIterator.{core::Iterator::moveNext}(){() → core::bool}; ) {
-      core::int i = #forIterator.{core::Iterator::current}{core::int};
+      final core::int i = #forIterator.{core::Iterator::current}{core::int};
       {
         core::print(i);
       }
     }
   }
 }
-static method asyncMethod(asy::Stream<core::int> stream) → dynamic async /* emittedValueType= dynamic */ {
+static method asyncMethod(final asy::Stream<core::int> stream) → dynamic async /* emittedValueType= dynamic */ {
   core::bool :async_temporary_0;
   dynamic :async_temporary_1;
   {
-    synthesized asy::_StreamIterator<core::int> #forIterator = new asy::_StreamIterator::•<core::int>(stream);
+    final synthesized asy::_StreamIterator<core::int> #forIterator = new asy::_StreamIterator::•<core::int>(stream);
     synthesized core::bool #jumpSentinel = #C1;
     {
       core::int #t1 = 0;
@@ -29,7 +29,7 @@
         for (; ; ) {
           :async_temporary_0 = await #forIterator.{asy::_StreamIterator::moveNext}(){() → asy::Future<core::bool>};
           if(#jumpSentinel = :async_temporary_0 as dynamic) {
-            core::int i = #forIterator.{asy::_StreamIterator::current}{core::int};
+            final core::int i = #forIterator.{asy::_StreamIterator::current}{core::int};
             {
               core::print(i);
             }
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 8417c49..52ea82d 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
@@ -7,29 +7,29 @@
   final field core::int? _f2;
   final field core::int? _f3;
   final field core::int? _f4;
-  constructor •(core::int? i) → self::C
+  constructor •(final core::int? i) → self::C
     : self::C::_f2 = i, self::C::_f3 = i, self::C::_f4 = i, super core::Object::•()
     ;
-  static method _#new#tearOff(core::int? i) → self::C
+  static method _#new#tearOff(final core::int? i) → self::C
     return new self::C::•(i);
 }
 class A extends core::Object {
-  constructor •(core::int? i) → self::A
+  constructor •(final core::int? i) → self::A
     : super core::Object::•()
     ;
-  static method _#new#tearOff(core::int? i) → self::A
+  static method _#new#tearOff(final core::int? i) → self::A
     return new self::A::•(i);
 }
 abstract class M3 extends core::Object /*isMixinDeclaration*/  {
 }
 abstract class _D&A&M3 extends self::A implements self::M3 /*isAnonymousMixin,isEliminatedMixin*/  {
-  synthetic constructor •(core::int? i) → self::_D&A&M3
+  synthetic constructor •(final core::int? i) → self::_D&A&M3
     : super self::A::•(i)
     ;
 }
 abstract class D extends self::_D&A&M3 {
   final field core::int? _f4;
-  constructor •(core::int? i) → self::D
+  constructor •(final core::int? i) → self::D
     : self::D::_f4 = i, super self::_D&A&M3::•(i)
     ;
 }
@@ -42,13 +42,13 @@
   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");
-  set _f2(core::int? _f2#param) → void {
+  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");
-  set _f3(core::int? _f3#param) → void
+  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");
     else {
@@ -67,10 +67,10 @@
   }
 }
 class B extends core::Object {
-  constructor •(core::int? i) → self::B
+  constructor •(final core::int? i) → self::B
     : super core::Object::•()
     ;
-  static method _#new#tearOff(core::int? i) → self::B
+  static method _#new#tearOff(final core::int? i) → self::B
     return new self::B::•(i);
 }
 abstract class _E&B&M1 extends self::B implements self::M1 /*isAnonymousMixin,isEliminatedMixin*/  {
@@ -80,7 +80,7 @@
   field core::bool _#M1#_f3#isSet = false;
   field core::int? _#M1#_f4 = null;
   field core::bool _#M1#_f4#isSet = false;
-  synthetic constructor •(core::int? i) → self::_E&B&M1
+  synthetic constructor •(final core::int? i) → self::_E&B&M1
     : super self::B::•(i)
     ;
   get _f2() → core::int?
@@ -97,11 +97,11 @@
     }
     return this.{self::M1::_#M1#_f4}{core::int?};
   }
-  set _f2(core::int? _f2#param) → void {
+  set _f2(final core::int? _f2#param) → void {
     this.{self::M1::_#M1#_f2#isSet} = true;
     this.{self::M1::_#M1#_f2} = _f2#param;
   }
-  set _f3(core::int? _f3#param) → void
+  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");
     else {
@@ -110,39 +110,39 @@
     }
 }
 class E extends self::_E&B&M1 implements self::D {
-  constructor •(core::int? i) → self::E
+  constructor •(final core::int? i) → self::E
     : super self::_E&B&M1::•(i)
     ;
-  static method _#new#tearOff(core::int? i) → self::E
+  static method _#new#tearOff(final core::int? i) → self::E
     return new self::E::•(i);
   @#C1
-  method noSuchMethod(core::Invocation invocation) → dynamic
+  method noSuchMethod(final core::Invocation invocation) → dynamic
     return super.{core::Object::noSuchMethod}(invocation);
 }
-static method acceptsInt(core::int x) → void {}
-static method testConflictWithNoSuchMethodForwarderIfImplementedInMixin(self::C c) → void {
+static method acceptsInt(final core::int x) → void {}
+static method testConflictWithNoSuchMethodForwarderIfImplementedInMixin(final self::C c) → void {
   if(!(c.{self::C::_f2}{core::int?} == null)) {
     core::int? x = c.{self::C::_f2}{core::int?};
     x = null;
   }
 }
-static method testNoConflictWithNoSuchMethodForwarderIfImplementedInMixin1(self::C c) → void {
+static method testNoConflictWithNoSuchMethodForwarderIfImplementedInMixin1(final self::C c) → void {
   if(!(c.{self::C::_f3}{core::int?} == null)) {
-    core::int x = let core::int? #t3 = c.{self::C::_f3}{core::int?} in #t3 == null ?{core::int} #t3 as{Unchecked} core::int : #t3{core::int};
+    final core::int x = let core::int? #t3 = c.{self::C::_f3}{core::int?} in #t3 == null ?{core::int} #t3 as{Unchecked} core::int : #t3{core::int};
     self::acceptsInt(x);
   }
 }
-static method testNoConflictWithNoSuchMethodForwarderIfImplementedInMixin2(self::C c) → void {
+static method testNoConflictWithNoSuchMethodForwarderIfImplementedInMixin2(final self::C c) → void {
   if(!(c.{self::C::_f4}{core::int?} == null)) {
-    core::int x = let core::int? #t4 = c.{self::C::_f4}{core::int?} in #t4 == null ?{core::int} #t4 as{Unchecked} core::int : #t4{core::int};
+    final core::int x = let core::int? #t4 = c.{self::C::_f4}{core::int?} in #t4 == null ?{core::int} #t4 as{Unchecked} core::int : #t4{core::int};
     self::acceptsInt(x);
   }
 }
 static method main() → dynamic {
   {
-    synthesized core::Iterator<self::C> #forIterator = <self::C>[new self::C::•(null), new self::C::•(0)].{core::Iterable::iterator}{core::Iterator<self::C>};
+    final synthesized core::Iterator<self::C> #forIterator = <self::C>[new self::C::•(null), new self::C::•(0)].{core::Iterable::iterator}{core::Iterator<self::C>};
     for (; #forIterator.{core::Iterator::moveNext}(){() → core::bool}; ) {
-      self::C c = #forIterator.{core::Iterator::current}{self::C};
+      final self::C c = #forIterator.{core::Iterator::current}{self::C};
       {
         self::testConflictWithNoSuchMethodForwarderIfImplementedInMixin(c);
         self::testNoConflictWithNoSuchMethodForwarderIfImplementedInMixin1(c);
diff --git a/pkg/front_end/testcases/dart2wasm/issue53239.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2wasm/issue53239.dart.strong.transformed.expect
index 2fb7e13..3ea53f0 100644
--- a/pkg/front_end/testcases/dart2wasm/issue53239.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/dart2wasm/issue53239.dart.strong.transformed.expect
@@ -4,11 +4,11 @@
 
 class C1 extends core::Object {
   final field core::int id;
-  constructor n1(core::int id, [core::String s = #C1]) → self::C1
+  constructor n1(final core::int id, [final core::String s = #C1]) → self::C1
     : self::C1::id = id, super core::Object::•() {
     self::log = s;
   }
-  static method _#n1#tearOff(core::int id, [core::String s = #C1]) → self::C1
+  static method _#n1#tearOff(final core::int id, [final core::String s = #C1]) → self::C1
     return new self::C1::n1(id, s);
 }
 extension type ET1(core::int id) {
@@ -19,30 +19,30 @@
   constructor tearoff n1 = self::ET1|constructor#_#n1#tearOff;
 }
 static field dynamic log;
-static extension-type-member method ET1|constructor#(core::int id) → self::ET1% /* erasure=core::int, declared=! */ {
+static extension-type-member method ET1|constructor#(final core::int id) → self::ET1% /* erasure=core::int, declared=! */ {
   lowered final self::ET1% /* erasure=core::int, declared=! */ #this = id;
   return #this;
 }
-static extension-type-member method ET1|constructor#_#new#tearOff(core::int id) → self::ET1% /* erasure=core::int, declared=! */
+static extension-type-member method ET1|constructor#_#new#tearOff(final core::int id) → self::ET1% /* erasure=core::int, declared=! */
   return self::ET1|constructor#(id);
-static extension-type-member method ET1|constructor#n1(core::int id, [core::String s = #C2]) → self::ET1% /* erasure=core::int, declared=! */ {
+static extension-type-member method ET1|constructor#n1(final core::int id, [final core::String s = #C2]) → self::ET1% /* erasure=core::int, declared=! */ {
   lowered final self::ET1% /* erasure=core::int, declared=! */ #this = id;
   {
     self::log = s;
   }
   return #this;
 }
-static extension-type-member method ET1|constructor#_#n1#tearOff(core::int id, [core::String s = #C2]) → self::ET1% /* erasure=core::int, declared=! */
+static extension-type-member method ET1|constructor#_#n1#tearOff(final core::int id, [final core::String s = #C2]) → self::ET1% /* erasure=core::int, declared=! */
   return self::ET1|constructor#n1(id, s);
 static method main() → dynamic {
-  (core::int, [core::String]) → self::C1 x = #C3;
+  final (core::int, [core::String]) → self::C1 x = #C3;
   x(0){(core::int, [core::String]) → self::C1};
   self::expect("a", self::log);
-  (core::int, [core::String]) → self::ET1% /* erasure=core::int, declared=! */ y = #C4;
+  final (core::int, [core::String]) → self::ET1% /* erasure=core::int, declared=! */ y = #C4;
   y(1){(core::int, [core::String]) → self::ET1% /* erasure=core::int, declared=! */};
   self::expect("b", self::log);
 }
-static method expect(dynamic expected, dynamic actual) → dynamic {
+static method expect(final dynamic expected, final dynamic actual) → dynamic {
   if(!(expected =={core::Object::==}{(core::Object) → core::bool} actual))
     throw "Expected ${expected}, actual ${actual}";
 }
diff --git a/pkg/front_end/testcases/dart2wasm/issue54069.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2wasm/issue54069.dart.strong.transformed.expect
index d5d60ca..7190b53 100644
--- a/pkg/front_end/testcases/dart2wasm/issue54069.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/dart2wasm/issue54069.dart.strong.transformed.expect
@@ -8,7 +8,7 @@
 
 static method getBinaryTestProto() → asy::Future<typ::Uint8List>
   return self::readFileWeb("test.binary.pb");
-static method readFileWeb(core::String path) → asy::Future<typ::Uint8List> async /* emittedValueType= typ::Uint8List */ {
+static method readFileWeb(final core::String path) → asy::Future<typ::Uint8List> async /* emittedValueType= typ::Uint8List */ {
   throw "";
 }
 static method runBench([typ::Uint8List? data = #C1]) → void async /* emittedValueType= void */ {
@@ -27,7 +27,7 @@
 static method main() → void async /* emittedValueType= void */ {
   typ::Uint8List :async_temporary_0;
   :async_temporary_0 = await self::getBinaryTestProto();
-  typ::Uint8List data = :async_temporary_0 as dynamic;
+  final typ::Uint8List data = :async_temporary_0 as dynamic;
   core::print("File successfully read, contents: ${data}");
   self::runBench(data);
 }
diff --git a/pkg/front_end/testcases/dart2wasm/issue55529.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2wasm/issue55529.dart.strong.transformed.expect
index cac766a..3a2b8ee 100644
--- a/pkg/front_end/testcases/dart2wasm/issue55529.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/dart2wasm/issue55529.dart.strong.transformed.expect
@@ -4,36 +4,36 @@
 
 class A1 extends core::Object {
   final field core::Object? a;
-  constructor •({required core::Object? a}) → self::A1
+  constructor •({required final core::Object? a}) → self::A1
     : self::A1::a = a, super core::Object::•()
     ;
-  static method _#new#tearOff({required core::Object? a}) → self::A1
+  static method _#new#tearOff({required final core::Object? a}) → self::A1
     return new self::A1::•(a: a);
 }
 class B1 extends self::A1 {
-  constructor •({core::Object? a = #C1}) → self::B1
+  constructor •({final core::Object? a = #C1}) → self::B1
     : super self::A1::•(a: a) {}
-  static method _#new#tearOff({core::Object? a = #C1}) → self::B1
+  static method _#new#tearOff({final core::Object? a = #C1}) → self::B1
     return new self::B1::•(a: a);
 }
 class A2 extends core::Object {
   final field core::Object? a;
-  constructor •(core::Object? a) → self::A2
+  constructor •(final core::Object? a) → self::A2
     : self::A2::a = a, super core::Object::•()
     ;
-  static method _#new#tearOff(core::Object? a) → self::A2
+  static method _#new#tearOff(final core::Object? a) → self::A2
     return new self::A2::•(a);
 }
 class B2 extends self::A2 {
-  constructor •([core::Object? a = #C1]) → self::B2
+  constructor •([final core::Object? a = #C1]) → self::B2
     : super self::A2::•(a)
     ;
-  static method _#new#tearOff([core::Object? a = #C1]) → self::B2
+  static method _#new#tearOff([final core::Object? a = #C1]) → self::B2
     return new self::B2::•(a);
 }
 static method main() → void {
-  ({a: core::Object?}) → self::B1 f1 = #C2;
-  ([core::Object?]) → self::B2 f2 = #C3;
+  final ({a: core::Object?}) → self::B1 f1 = #C2;
+  final ([core::Object?]) → self::B2 f2 = #C3;
 }
 
 constants  {
diff --git a/pkg/front_end/testcases/dart2wasm/yield.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2wasm/yield.dart.strong.transformed.expect
index 123755a..7ea2824 100644
--- a/pkg/front_end/testcases/dart2wasm/yield.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/dart2wasm/yield.dart.strong.transformed.expect
@@ -3,19 +3,19 @@
 import "dart:core" as core;
 import "dart:async" as asy;
 
-static method method(core::Iterable<core::int> iterable) → core::Iterable<core::int> sync* /* emittedValueType= core::int */ {
+static method method(final core::Iterable<core::int> iterable) → core::Iterable<core::int> sync* /* emittedValueType= core::int */ {
   yield 1;
   yield 2;
   yield* iterable;
 }
-static method asyncMethod(asy::Stream<core::int> stream) → asy::Stream<core::int> {
-  synthesized asy::StreamController<core::int> #controller = asy::StreamController::•<core::int>(sync: #C1);
-  synthesized () → asy::Future<void> #body = () → asy::Future<void> async /* emittedValueType= void */ {
+static method asyncMethod(final asy::Stream<core::int> stream) → asy::Stream<core::int> {
+  final synthesized asy::StreamController<core::int> #controller = asy::StreamController::•<core::int>(sync: #C1);
+  final synthesized () → asy::Future<void> #body = () → asy::Future<void> async /* emittedValueType= void */ {
     void :async_temporary_0;
     void :async_temporary_1;
     dynamic :async_temporary_2;
     synthesized asy::Completer<void>? #paused;
-    dynamic #onCancelCallback = () → void {
+    final dynamic #onCancelCallback = () → void {
       if(#paused == null) {
       }
       else {
@@ -62,7 +62,7 @@
               return;
           }
         }
-        on dynamic catch(dynamic #t4, core::StackTrace #t5) {
+        on dynamic catch(final dynamic #t4, final core::StackTrace #t5) {
           #controller.{asy::StreamController::addError}(#t4, #t5){(core::Object, [core::StackTrace?]) → void};
           #t4;
           #t5;
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/int_operations_dart2wasm.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/int_operations_dart2wasm.dart.expect
index 9fab9e8..872e73f 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/int_operations_dart2wasm.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/int_operations_dart2wasm.dart.expect
@@ -12,7 +12,7 @@
 static field core::int y = [@vm.inferred-type.metadata=dart.core::_BoxedInt] core::int::parse("3");
 
 [@vm.unboxing-info.metadata=(i,i,i)->b]
-static method use([@vm.inferred-arg-type.metadata=dart.core::_BoxedInt] dynamic a, [@vm.inferred-arg-type.metadata=dart.core::_BoxedInt] dynamic b, [@vm.inferred-arg-type.metadata=dart.core::_BoxedInt] dynamic c) → void {
+static method use([@vm.inferred-arg-type.metadata=dart.core::_BoxedInt] final dynamic a, [@vm.inferred-arg-type.metadata=dart.core::_BoxedInt] final dynamic b, [@vm.inferred-arg-type.metadata=dart.core::_BoxedInt] final dynamic c) → void {
   core::print(a);
   core::print(b);
   core::print(c);
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/records_dart2wasm.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/records_dart2wasm.dart.expect
index 56ebbff..b47330d 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/records_dart2wasm.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/records_dart2wasm.dart.expect
@@ -7,13 +7,13 @@
 static field dynamic list = <core::Object>["abc", (42, {foo42: "foo42"})];
 
 [@vm.unboxing-info.metadata=(i,i,i)->b]
-static method recordLiteral([@vm.inferred-arg-type.metadata=dart.core::_BoxedInt] dynamic x, [@vm.inferred-arg-type.metadata=dart.core::_BoxedInt] dynamic y, [@vm.inferred-arg-type.metadata=dart.core::_BoxedInt] dynamic z) → dynamic
+static method recordLiteral([@vm.inferred-arg-type.metadata=dart.core::_BoxedInt] final dynamic x, [@vm.inferred-arg-type.metadata=dart.core::_BoxedInt] final dynamic y, [@vm.inferred-arg-type.metadata=dart.core::_BoxedInt] final dynamic z) → dynamic
   return (x, y, {bar: z});
-static method recordFieldAccess1([@vm.inferred-arg-type.metadata=dart.core::Record_2](core::int, core::String) rec) → dynamic
+static method recordFieldAccess1([@vm.inferred-arg-type.metadata=dart.core::Record_2] final(core::int, core::String) rec) → dynamic
   return rec.$1{core::int};
-static method recordFieldAccess2([@vm.inferred-arg-type.metadata=dart.core::Record_0_a_b]({required a: core::int, required b: core::String}) rec) → dynamic
+static method recordFieldAccess2([@vm.inferred-arg-type.metadata=dart.core::Record_0_a_b] final({required a: core::int, required b: core::String}) rec) → dynamic
   return rec.a{core::int};
-static method recordDynamicFieldAccess(dynamic x) → dynamic
+static method recordDynamicFieldAccess(final dynamic x) → dynamic
   return x{dynamic}.foo42;
 static method main() → dynamic {
   core::print(#C4);