Version 3.3.0-88.0.dev

Merge 1afa57fc484cb35b10609e2e95e92c14f9ab3d3f into dev
diff --git a/pkg/front_end/lib/src/fasta/source/outline_builder.dart b/pkg/front_end/lib/src/fasta/source/outline_builder.dart
index 79a54ee..96b0017 100644
--- a/pkg/front_end/lib/src/fasta/source/outline_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/outline_builder.dart
@@ -2816,15 +2816,29 @@
         }
       }
     }
-    if (formals == null &&
-        declarationContext == DeclarationContext.ExtensionType &&
-        kind == MemberKind.PrimaryConstructor) {
+    if (declarationContext == DeclarationContext.ExtensionType &&
+        kind == MemberKind.PrimaryConstructor &&
+        formals == null) {
       // In case of primary constructors of extension types, an error is
       // reported by the parser if the formals together with the parentheses
       // around them are missing. To distinguish that case from the case of the
       // formal parameters present, but lacking the representation field, we
       // pass the empty list further along instead of `null`.
       formals = const [];
+    } else if ((declarationContext == DeclarationContext.ExtensionType &&
+                kind == MemberKind.PrimaryConstructor ||
+            declarationContext ==
+                DeclarationContext.ExtensionTypeConstructor) &&
+        formals != null) {
+      for (FormalParameterBuilder formal in formals) {
+        if (formal.isSuperInitializingFormal) {
+          libraryBuilder.addProblem(
+              messageExtensionTypeConstructorWithSuperFormalParameter,
+              formal.charOffset,
+              formal.name.length,
+              formal.fileUri);
+        }
+      }
     }
     push(beginToken.charOffset);
     push(formals ?? NullValues.FormalParameters);
diff --git a/pkg/front_end/lib/src/fasta/source/source_constructor_builder.dart b/pkg/front_end/lib/src/fasta/source/source_constructor_builder.dart
index efebf90..66b36a6 100644
--- a/pkg/front_end/lib/src/fasta/source/source_constructor_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_constructor_builder.dart
@@ -35,7 +35,6 @@
     show
         LocatedMessage,
         Message,
-        messageExtensionTypeConstructorWithSuperFormalParameter,
         messageMoreThanOneSuperInitializer,
         messageRedirectingConstructorWithAnotherInitializer,
         messageRedirectingConstructorWithMultipleRedirectInitializers,
@@ -1176,11 +1175,6 @@
         if (formal.isSuperInitializingFormal) {
           TypeBuilder formalTypeBuilder = formal.type;
           if (formalTypeBuilder is InferableTypeBuilder) {
-            libraryBuilder.addProblem(
-                messageExtensionTypeConstructorWithSuperFormalParameter,
-                formal.charOffset,
-                formal.name.length,
-                formal.fileUri);
             formalTypeBuilder.registerType(const InvalidType());
           }
         }
diff --git a/pkg/front_end/testcases/extension_types/issue53212.dart b/pkg/front_end/testcases/extension_types/issue53212.dart
index 67e465d..008a0b9 100644
--- a/pkg/front_end/testcases/extension_types/issue53212.dart
+++ b/pkg/front_end/testcases/extension_types/issue53212.dart
@@ -13,3 +13,5 @@
 extension type E3(int foo) {
   E3.named(this.foo, [super.bar = null]);
 }
+
+extension type E4(super.foo) {} // Error.
diff --git a/pkg/front_end/testcases/extension_types/issue53212.dart.strong.expect b/pkg/front_end/testcases/extension_types/issue53212.dart.strong.expect
index 88f11ab..16b5101 100644
--- a/pkg/front_end/testcases/extension_types/issue53212.dart.strong.expect
+++ b/pkg/front_end/testcases/extension_types/issue53212.dart.strong.expect
@@ -14,6 +14,14 @@
 //   E3.named(this.foo, [super.bar = null]);
 //                             ^^^
 //
+// pkg/front_end/testcases/extension_types/issue53212.dart:17:25: Error: Extension type constructors can't declare super formal parameters.
+// extension type E4(super.foo) {} // Error.
+//                         ^^^
+//
+// pkg/front_end/testcases/extension_types/issue53212.dart:17:25: Error: Expected a representation type.
+// extension type E4(super.foo) {} // Error.
+//                         ^^^
+//
 import self as self;
 import "dart:core" as core;
 
@@ -38,6 +46,11 @@
   constructor named = self::E3|constructor#named;
   constructor tearoff named = self::E3|constructor#_#named#tearOff;
 }
+extension type E4(dynamic foo) {
+  abstract inline-class-member representation-field get foo() → dynamic;
+  constructor • = self::E4|constructor#;
+  constructor tearoff • = self::E4|constructor#_#new#tearOff;
+}
 static inline-class-member method E1|constructor#(core::int foo) → self::E1 /* = core::int */ {
   lowered final self::E1 /* = core::int */ #this = foo;
   return #this;
@@ -74,6 +87,12 @@
 }
 static inline-class-member method E3|constructor#_#named#tearOff(core::int foo, [has-declared-initializer invalid-type bar]) → self::E3 /* = core::int */
   return self::E3|constructor#named(foo, bar);
+static inline-class-member method E4|constructor#(dynamic foo) → self::E4 /* = dynamic */ {
+  lowered final self::E4 /* = dynamic */ #this = foo;
+  return #this;
+}
+static inline-class-member method E4|constructor#_#new#tearOff(dynamic foo) → self::E4 /* = dynamic */
+  return self::E4|constructor#(foo);
 
 constants  {
   #C1 = null
diff --git a/pkg/front_end/testcases/extension_types/issue53212.dart.strong.transformed.expect b/pkg/front_end/testcases/extension_types/issue53212.dart.strong.transformed.expect
index 88f11ab..16b5101 100644
--- a/pkg/front_end/testcases/extension_types/issue53212.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/extension_types/issue53212.dart.strong.transformed.expect
@@ -14,6 +14,14 @@
 //   E3.named(this.foo, [super.bar = null]);
 //                             ^^^
 //
+// pkg/front_end/testcases/extension_types/issue53212.dart:17:25: Error: Extension type constructors can't declare super formal parameters.
+// extension type E4(super.foo) {} // Error.
+//                         ^^^
+//
+// pkg/front_end/testcases/extension_types/issue53212.dart:17:25: Error: Expected a representation type.
+// extension type E4(super.foo) {} // Error.
+//                         ^^^
+//
 import self as self;
 import "dart:core" as core;
 
@@ -38,6 +46,11 @@
   constructor named = self::E3|constructor#named;
   constructor tearoff named = self::E3|constructor#_#named#tearOff;
 }
+extension type E4(dynamic foo) {
+  abstract inline-class-member representation-field get foo() → dynamic;
+  constructor • = self::E4|constructor#;
+  constructor tearoff • = self::E4|constructor#_#new#tearOff;
+}
 static inline-class-member method E1|constructor#(core::int foo) → self::E1 /* = core::int */ {
   lowered final self::E1 /* = core::int */ #this = foo;
   return #this;
@@ -74,6 +87,12 @@
 }
 static inline-class-member method E3|constructor#_#named#tearOff(core::int foo, [has-declared-initializer invalid-type bar]) → self::E3 /* = core::int */
   return self::E3|constructor#named(foo, bar);
+static inline-class-member method E4|constructor#(dynamic foo) → self::E4 /* = dynamic */ {
+  lowered final self::E4 /* = dynamic */ #this = foo;
+  return #this;
+}
+static inline-class-member method E4|constructor#_#new#tearOff(dynamic foo) → self::E4 /* = dynamic */
+  return self::E4|constructor#(foo);
 
 constants  {
   #C1 = null
diff --git a/pkg/front_end/testcases/extension_types/issue53212.dart.textual_outline.expect b/pkg/front_end/testcases/extension_types/issue53212.dart.textual_outline.expect
index 4097d32..962d9bd 100644
--- a/pkg/front_end/testcases/extension_types/issue53212.dart.textual_outline.expect
+++ b/pkg/front_end/testcases/extension_types/issue53212.dart.textual_outline.expect
@@ -7,3 +7,4 @@
 extension type E3(int foo) {
   E3.named(this.foo, [super.bar = null]);
 }
+extension type E4(super.foo) {}
diff --git a/pkg/front_end/testcases/extension_types/issue53212.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/extension_types/issue53212.dart.textual_outline_modelled.expect
index 4097d32..962d9bd 100644
--- a/pkg/front_end/testcases/extension_types/issue53212.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/extension_types/issue53212.dart.textual_outline_modelled.expect
@@ -7,3 +7,4 @@
 extension type E3(int foo) {
   E3.named(this.foo, [super.bar = null]);
 }
+extension type E4(super.foo) {}
diff --git a/pkg/front_end/testcases/extension_types/issue53212.dart.weak.expect b/pkg/front_end/testcases/extension_types/issue53212.dart.weak.expect
index 88f11ab..16b5101 100644
--- a/pkg/front_end/testcases/extension_types/issue53212.dart.weak.expect
+++ b/pkg/front_end/testcases/extension_types/issue53212.dart.weak.expect
@@ -14,6 +14,14 @@
 //   E3.named(this.foo, [super.bar = null]);
 //                             ^^^
 //
+// pkg/front_end/testcases/extension_types/issue53212.dart:17:25: Error: Extension type constructors can't declare super formal parameters.
+// extension type E4(super.foo) {} // Error.
+//                         ^^^
+//
+// pkg/front_end/testcases/extension_types/issue53212.dart:17:25: Error: Expected a representation type.
+// extension type E4(super.foo) {} // Error.
+//                         ^^^
+//
 import self as self;
 import "dart:core" as core;
 
@@ -38,6 +46,11 @@
   constructor named = self::E3|constructor#named;
   constructor tearoff named = self::E3|constructor#_#named#tearOff;
 }
+extension type E4(dynamic foo) {
+  abstract inline-class-member representation-field get foo() → dynamic;
+  constructor • = self::E4|constructor#;
+  constructor tearoff • = self::E4|constructor#_#new#tearOff;
+}
 static inline-class-member method E1|constructor#(core::int foo) → self::E1 /* = core::int */ {
   lowered final self::E1 /* = core::int */ #this = foo;
   return #this;
@@ -74,6 +87,12 @@
 }
 static inline-class-member method E3|constructor#_#named#tearOff(core::int foo, [has-declared-initializer invalid-type bar]) → self::E3 /* = core::int */
   return self::E3|constructor#named(foo, bar);
+static inline-class-member method E4|constructor#(dynamic foo) → self::E4 /* = dynamic */ {
+  lowered final self::E4 /* = dynamic */ #this = foo;
+  return #this;
+}
+static inline-class-member method E4|constructor#_#new#tearOff(dynamic foo) → self::E4 /* = dynamic */
+  return self::E4|constructor#(foo);
 
 constants  {
   #C1 = null
diff --git a/pkg/front_end/testcases/extension_types/issue53212.dart.weak.modular.expect b/pkg/front_end/testcases/extension_types/issue53212.dart.weak.modular.expect
index 88f11ab..16b5101 100644
--- a/pkg/front_end/testcases/extension_types/issue53212.dart.weak.modular.expect
+++ b/pkg/front_end/testcases/extension_types/issue53212.dart.weak.modular.expect
@@ -14,6 +14,14 @@
 //   E3.named(this.foo, [super.bar = null]);
 //                             ^^^
 //
+// pkg/front_end/testcases/extension_types/issue53212.dart:17:25: Error: Extension type constructors can't declare super formal parameters.
+// extension type E4(super.foo) {} // Error.
+//                         ^^^
+//
+// pkg/front_end/testcases/extension_types/issue53212.dart:17:25: Error: Expected a representation type.
+// extension type E4(super.foo) {} // Error.
+//                         ^^^
+//
 import self as self;
 import "dart:core" as core;
 
@@ -38,6 +46,11 @@
   constructor named = self::E3|constructor#named;
   constructor tearoff named = self::E3|constructor#_#named#tearOff;
 }
+extension type E4(dynamic foo) {
+  abstract inline-class-member representation-field get foo() → dynamic;
+  constructor • = self::E4|constructor#;
+  constructor tearoff • = self::E4|constructor#_#new#tearOff;
+}
 static inline-class-member method E1|constructor#(core::int foo) → self::E1 /* = core::int */ {
   lowered final self::E1 /* = core::int */ #this = foo;
   return #this;
@@ -74,6 +87,12 @@
 }
 static inline-class-member method E3|constructor#_#named#tearOff(core::int foo, [has-declared-initializer invalid-type bar]) → self::E3 /* = core::int */
   return self::E3|constructor#named(foo, bar);
+static inline-class-member method E4|constructor#(dynamic foo) → self::E4 /* = dynamic */ {
+  lowered final self::E4 /* = dynamic */ #this = foo;
+  return #this;
+}
+static inline-class-member method E4|constructor#_#new#tearOff(dynamic foo) → self::E4 /* = dynamic */
+  return self::E4|constructor#(foo);
 
 constants  {
   #C1 = null
diff --git a/pkg/front_end/testcases/extension_types/issue53212.dart.weak.outline.expect b/pkg/front_end/testcases/extension_types/issue53212.dart.weak.outline.expect
index 292f458..afb8f67 100644
--- a/pkg/front_end/testcases/extension_types/issue53212.dart.weak.outline.expect
+++ b/pkg/front_end/testcases/extension_types/issue53212.dart.weak.outline.expect
@@ -14,6 +14,14 @@
 //   E3.named(this.foo, [super.bar = null]);
 //                             ^^^
 //
+// pkg/front_end/testcases/extension_types/issue53212.dart:17:25: Error: Extension type constructors can't declare super formal parameters.
+// extension type E4(super.foo) {} // Error.
+//                         ^^^
+//
+// pkg/front_end/testcases/extension_types/issue53212.dart:17:25: Error: Expected a representation type.
+// extension type E4(super.foo) {} // Error.
+//                         ^^^
+//
 import self as self;
 import "dart:core" as core;
 
@@ -38,6 +46,11 @@
   constructor named = self::E3|constructor#named;
   constructor tearoff named = self::E3|constructor#_#named#tearOff;
 }
+extension type E4(dynamic foo) {
+  abstract inline-class-member representation-field get foo() → dynamic;
+  constructor • = self::E4|constructor#;
+  constructor tearoff • = self::E4|constructor#_#new#tearOff;
+}
 static inline-class-member method E1|constructor#(core::int foo) → self::E1 /* = core::int */
   ;
 static inline-class-member method E1|constructor#_#new#tearOff(core::int foo) → self::E1 /* = core::int */
@@ -62,3 +75,7 @@
   ;
 static inline-class-member method E3|constructor#_#named#tearOff(core::int foo, [has-declared-initializer invalid-type bar]) → self::E3 /* = core::int */
   return self::E3|constructor#named(foo, bar);
+static inline-class-member method E4|constructor#(dynamic foo) → self::E4 /* = dynamic */
+  ;
+static inline-class-member method E4|constructor#_#new#tearOff(dynamic foo) → self::E4 /* = dynamic */
+  return self::E4|constructor#(foo);
diff --git a/pkg/front_end/testcases/extension_types/issue53212.dart.weak.transformed.expect b/pkg/front_end/testcases/extension_types/issue53212.dart.weak.transformed.expect
index 88f11ab..16b5101 100644
--- a/pkg/front_end/testcases/extension_types/issue53212.dart.weak.transformed.expect
+++ b/pkg/front_end/testcases/extension_types/issue53212.dart.weak.transformed.expect
@@ -14,6 +14,14 @@
 //   E3.named(this.foo, [super.bar = null]);
 //                             ^^^
 //
+// pkg/front_end/testcases/extension_types/issue53212.dart:17:25: Error: Extension type constructors can't declare super formal parameters.
+// extension type E4(super.foo) {} // Error.
+//                         ^^^
+//
+// pkg/front_end/testcases/extension_types/issue53212.dart:17:25: Error: Expected a representation type.
+// extension type E4(super.foo) {} // Error.
+//                         ^^^
+//
 import self as self;
 import "dart:core" as core;
 
@@ -38,6 +46,11 @@
   constructor named = self::E3|constructor#named;
   constructor tearoff named = self::E3|constructor#_#named#tearOff;
 }
+extension type E4(dynamic foo) {
+  abstract inline-class-member representation-field get foo() → dynamic;
+  constructor • = self::E4|constructor#;
+  constructor tearoff • = self::E4|constructor#_#new#tearOff;
+}
 static inline-class-member method E1|constructor#(core::int foo) → self::E1 /* = core::int */ {
   lowered final self::E1 /* = core::int */ #this = foo;
   return #this;
@@ -74,6 +87,12 @@
 }
 static inline-class-member method E3|constructor#_#named#tearOff(core::int foo, [has-declared-initializer invalid-type bar]) → self::E3 /* = core::int */
   return self::E3|constructor#named(foo, bar);
+static inline-class-member method E4|constructor#(dynamic foo) → self::E4 /* = dynamic */ {
+  lowered final self::E4 /* = dynamic */ #this = foo;
+  return #this;
+}
+static inline-class-member method E4|constructor#_#new#tearOff(dynamic foo) → self::E4 /* = dynamic */
+  return self::E4|constructor#(foo);
 
 constants  {
   #C1 = null
diff --git a/runtime/tests/vm/dart/address_local_pointer_il_test.dart b/runtime/tests/vm/dart/address_local_pointer_il_test.dart
index 0842756..eb290f5 100644
--- a/runtime/tests/vm/dart/address_local_pointer_il_test.dart
+++ b/runtime/tests/vm/dart/address_local_pointer_il_test.dart
@@ -17,32 +17,21 @@
 int identity(int address) => Pointer<Void>.fromAddress(address).address;
 
 void matchIL$identity(FlowGraph graph) {
-  graph.dump();
-  if (is32BitConfiguration) {
-    // The Dart int address is truncated before being returned.
-    graph.match([
-      match.block('Graph'),
-      match.block('Function', [
-        'address' << match.Parameter(index: 0),
-        'int32' <<
-            match.IntConverter('address',
-                from: 'int64', to: 'int32', is_truncating: true),
+  final retval = is32BitConfiguration ? 'retval' : 'address';
+  graph.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'address' << match.Parameter(index: 0),
+      if (is32BitConfiguration) ...[
+        // The Dart int address is truncated before being returned.
         'uint32' <<
-            match.IntConverter('int32',
-                from: 'int32', to: 'uint32', is_truncating: true),
+            match.IntConverter('address',
+                from: 'int64', to: 'uint32', is_truncating: true),
         'retval' << match.IntConverter('uint32', from: 'uint32', to: 'int64'),
-        match.Return('retval'),
-      ]),
-    ]);
-  } else {
-    graph.match([
-      match.block('Graph'),
-      match.block('Function', [
-        'address' << match.Parameter(index: 0),
-        match.Return('address'),
-      ]),
-    ]);
-  }
+      ],
+      match.Return(retval),
+    ]),
+  ]);
 }
 
 void main(List<String> args) {
diff --git a/runtime/tests/vm/dart/regress_306327173_il_test.dart b/runtime/tests/vm/dart/regress_306327173_il_test.dart
index a9206d3e..e3c22ff 100644
--- a/runtime/tests/vm/dart/regress_306327173_il_test.dart
+++ b/runtime/tests/vm/dart/regress_306327173_il_test.dart
@@ -30,12 +30,7 @@
       // and int64 on 64-bit arches.
       if (is32BitConfiguration) ...[
         // 'unboxed' needs to be converted to int64 before returning.
-        //
-        // Note: The first two conversions here should be fixed once all
-        // kUnboxedIntPtr uses are appropriately converted to kUnboxedFfiIntPtr.
-        'extra1' << match.IntConverter('unboxed', from: 'uint32', to: 'int32'),
-        'extra2' << match.IntConverter('extra1', from: 'int32', to: 'uint32'),
-        'address' << match.IntConverter('extra2', from: 'uint32', to: 'int64'),
+        'address' << match.IntConverter('unboxed', from: 'uint32', to: 'int64'),
       ],
       match.Return(retvalName),
     ]),
diff --git a/runtime/tests/vm/dart/unsigned_truncated_division_il_test.dart b/runtime/tests/vm/dart/unsigned_truncated_division_il_test.dart
new file mode 100644
index 0000000..e1c1154
--- /dev/null
+++ b/runtime/tests/vm/dart/unsigned_truncated_division_il_test.dart
@@ -0,0 +1,60 @@
+// Copyright (c) 2023, 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.
+
+// Verify that we don't leave in binary int operations that do not throw and
+// have a calculated range of a single value, but instead replace the operation
+// with that constant value.
+
+import 'package:expect/expect.dart';
+import 'package:vm/testing/il_matchers.dart';
+
+@pragma('vm:never-inline')
+@pragma('vm:testing:print-flow-graph')
+int testUnsignedTruncatingDivision(Iterable i) => i.length ~/ 32;
+
+void matchIL$testUnsignedTruncatingDivision(FlowGraph graph) {
+  graph.match([
+    match.block('Graph', [
+      if (is32BitConfiguration) ...[
+        'c5' << match.UnboxedConstant(value: 5, representation: 'int32'),
+      ] else ...[
+        'c32' << match.UnboxedConstant(value: 32, representation: 'int64'),
+      ],
+    ]),
+    match.block('Function', [
+      'it' << match.Parameter(index: 0),
+      'len' << match.LoadField('it', slot: 'GrowableObjectArray.length'),
+      if (is32BitConfiguration) ...[
+        // 32-bit architectures don't handle 64-bit truncated division natively.
+        // However, for powers of two, the runtime call
+        //   m ~/ 2^n
+        // gets replaced with
+        //   (m + ((m >> 63) & (2^n - 1))) >> n
+        // which works for both signed and unsigned values.
+        //
+        // In this specific case, with m = len and 2^n = 32,
+        //   (len + ((len >> 63) & 31)) >> 5
+        // However, the numerator len has a non-negative range since it's
+        // retrieved from the length slot of an Iterable. This means (len >> 63)
+        // is guaranteed to be 0, and the compiler should simplify this to
+        //   len >> 5
+        'unboxed_len' << match.UnboxInt32('len'),
+        'retval_32' << match.BinaryInt32Op('unboxed_len', 'c5', op_kind: '>>'),
+        'retval' << match.IntConverter('retval_32', from: 'int32', to: 'int64'),
+      ] else ...[
+        // 64-bit architectures do handle 64-bit truncated division natively,
+        // so it's just a single operation there.
+        'unboxed_len' << match.UnboxInt64('len'),
+        'retval' << match.BinaryInt64Op('unboxed_len', 'c32', op_kind: '~/'),
+      ],
+      match.Return('retval'),
+    ]),
+  ]);
+}
+
+void main(List<String> args) {
+  final len = args.isEmpty ? 100 : int.parse(args.first);
+  final list = List.generate(len, (i) => len - i);
+  Expect.equals(len ~/ 32, testUnsignedTruncatingDivision(list));
+}
diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc
index a99cef7..c2c1bca 100644
--- a/runtime/vm/compiler/backend/il.cc
+++ b/runtime/vm/compiler/backend/il.cc
@@ -1521,6 +1521,12 @@
   other->env()->DeepCopyTo(zone, this);
 }
 
+bool Instruction::CanEliminate(const BlockEntryInstr* block) const {
+  ASSERT(const_cast<Instruction*>(this)->GetBlock() == block);
+  return !MayHaveVisibleEffect() && !CanDeoptimize() &&
+         this != block->last_instruction();
+}
+
 bool Instruction::IsDominatedBy(Instruction* dom) {
   BlockEntryInstr* block = GetBlock();
   BlockEntryInstr* dom_block = dom->GetBlock();
@@ -1980,41 +1986,25 @@
                            RangeBoundary::kRangeBoundaryInt32);
 }
 
-bool UnboxInt32Instr::ComputeCanDeoptimize() const {
+bool UnboxIntegerInstr::ComputeCanDeoptimize() const {
   if (SpeculativeModeOfInputs() == kNotSpeculative) {
     return false;
   }
-  const intptr_t value_cid = value()->Type()->ToCid();
-  if (value_cid == kSmiCid) {
-    return (compiler::target::kSmiBits > 32) && !is_truncating() &&
-           !RangeUtils::Fits(value()->definition()->range(),
-                             RangeBoundary::kRangeBoundaryInt32);
-  } else if (value_cid == kMintCid) {
-    return !is_truncating() &&
-           !RangeUtils::Fits(value()->definition()->range(),
-                             RangeBoundary::kRangeBoundaryInt32);
-  } else if (is_truncating() && value()->definition()->IsBoxInteger()) {
-    return false;
-  } else if ((compiler::target::kSmiBits < 32) && value()->Type()->IsInt()) {
-    return !RangeUtils::Fits(value()->definition()->range(),
-                             RangeBoundary::kRangeBoundaryInt32);
-  } else {
+  if (!value()->Type()->IsInt()) {
     return true;
   }
-}
-
-bool UnboxUint32Instr::ComputeCanDeoptimize() const {
-  ASSERT(is_truncating());
-  if (SpeculativeModeOfInputs() == kNotSpeculative) {
+  if (representation() == kUnboxedInt64 || is_truncating()) {
     return false;
   }
-  if ((value()->Type()->ToCid() == kSmiCid) ||
-      (value()->Type()->ToCid() == kMintCid)) {
+  const intptr_t rep_bitsize =
+      RepresentationUtils::ValueSize(representation()) * kBitsPerByte;
+  if (value()->Type()->ToCid() == kSmiCid &&
+      compiler::target::kSmiBits <= rep_bitsize) {
     return false;
   }
-  // Check input value's range.
-  Range* value_range = value()->definition()->range();
-  return !RangeUtils::Fits(value_range, RangeBoundary::kRangeBoundaryInt64);
+  return !RangeUtils::IsWithin(value()->definition()->range(),
+                               RepresentationUtils::MinValue(representation()),
+                               RepresentationUtils::MaxValue(representation()));
 }
 
 bool BinaryInt32OpInstr::ComputeCanDeoptimize() const {
@@ -2072,9 +2062,18 @@
   return RangeUtils::IsWithin(shift_range(), 0, max);
 }
 
+bool BinaryIntegerOpInstr::RightIsNonZero() const {
+  if (right()->BindsToConstant()) {
+    const auto& constant = right()->BoundConstant();
+    if (!constant.IsInteger()) return false;
+    return Integer::Cast(constant).AsInt64Value() != 0;
+  }
+  return !RangeUtils::CanBeZero(right()->definition()->range());
+}
+
 bool BinaryIntegerOpInstr::RightIsPowerOfTwoConstant() const {
-  if (!right()->definition()->IsConstant()) return false;
-  const Object& constant = right()->definition()->AsConstant()->value();
+  if (!right()->BindsToConstant()) return false;
+  const Object& constant = right()->BoundConstant();
   if (!constant.IsSmi()) return false;
   const intptr_t int_value = Smi::Cast(constant).Value();
   ASSERT(int_value != kIntptrMin);
@@ -2340,7 +2339,35 @@
   return op;
 }
 
+Definition* UnaryIntegerOpInstr::Canonicalize(FlowGraph* flow_graph) {
+  // If range analysis has already determined a single possible value for
+  // this operation, then replace it if possible.
+  if (RangeUtils::IsSingleton(range()) && CanReplaceWithConstant()) {
+    const auto& value =
+        Integer::Handle(Integer::NewCanonical(range()->Singleton()));
+    auto* const replacement =
+        flow_graph->TryCreateConstantReplacementFor(this, value);
+    if (replacement != this) {
+      return replacement;
+    }
+  }
+
+  return this;
+}
+
 Definition* BinaryIntegerOpInstr::Canonicalize(FlowGraph* flow_graph) {
+  // If range analysis has already determined a single possible value for
+  // this operation, then replace it if possible.
+  if (RangeUtils::IsSingleton(range()) && CanReplaceWithConstant()) {
+    const auto& value =
+        Integer::Handle(Integer::NewCanonical(range()->Singleton()));
+    auto* const replacement =
+        flow_graph->TryCreateConstantReplacementFor(this, value);
+    if (replacement != this) {
+      return replacement;
+    }
+  }
+
   // If both operands are constants evaluate this expression. Might
   // occur due to load forwarding after constant propagation pass
   // have already been run.
@@ -3306,40 +3333,24 @@
     set_speculative_mode(kNotSpeculative);
   }
 
-  return this;
-}
-
-Definition* UnboxInt32Instr::Canonicalize(FlowGraph* flow_graph) {
-  Definition* replacement = UnboxIntegerInstr::Canonicalize(flow_graph);
-  if (replacement != this) {
-    return replacement;
-  }
-
-  ConstantInstr* c = value()->definition()->AsConstant();
-  if ((c != nullptr) && c->value().IsInteger()) {
-    if (!is_truncating()) {
-      // Check that constant fits into 32-bit integer.
-      const int64_t value = Integer::Cast(c->value()).AsInt64Value();
-      if (!Utils::IsInt(32, value)) {
-        return this;
+  if (value()->BindsToConstant()) {
+    const auto& obj = value()->BoundConstant();
+    if (obj.IsInteger()) {
+      if (representation() == kUnboxedInt64) {
+        return flow_graph->GetConstant(obj, representation());
+      }
+      const int64_t intval = Integer::Cast(obj).AsInt64Value();
+      if (RepresentationUtils::IsRepresentable(representation(), intval)) {
+        return flow_graph->GetConstant(obj, representation());
+      }
+      if (is_truncating()) {
+        const int64_t result = Evaluator::TruncateTo(intval, representation());
+        return flow_graph->GetConstant(
+            Integer::ZoneHandle(flow_graph->zone(),
+                                Integer::NewCanonical(result)),
+            representation());
       }
     }
-
-    return flow_graph->GetConstant(c->value(), kUnboxedInt32);
-  }
-
-  return this;
-}
-
-Definition* UnboxInt64Instr::Canonicalize(FlowGraph* flow_graph) {
-  Definition* replacement = UnboxIntegerInstr::Canonicalize(flow_graph);
-  if (replacement != this) {
-    return replacement;
-  }
-
-  ConstantInstr* c = value()->definition()->AsConstant();
-  if (c != nullptr && c->value().IsInteger()) {
-    return flow_graph->GetConstant(c->value(), kUnboxedInt64);
   }
 
   return this;
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index a825904..fa72095 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -1334,6 +1334,17 @@
 
   virtual bool MayThrow() const = 0;
 
+  // Returns true if instruction may have a "visible" effect,
+  virtual bool MayHaveVisibleEffect() const {
+    return HasUnknownSideEffects() || MayThrow();
+  }
+
+  // Returns true if this instruction can be eliminated if its result is not
+  // used without changing the behavior of the program. For Definitions,
+  // overwrite CanReplaceWithConstant() instead.
+  virtual bool CanEliminate(const BlockEntryInstr* block) const;
+  bool CanEliminate() { return CanEliminate(GetBlock()); }
+
   bool IsDominatedBy(Instruction* dom);
 
   void ClearEnv() { env_ = nullptr; }
@@ -2546,6 +2557,18 @@
   void AddInputUse(Value* value) { Value::AddToList(value, &input_use_list_); }
   void AddEnvUse(Value* value) { Value::AddToList(value, &env_use_list_); }
 
+  // Returns true if the definition can be replaced with a constant without
+  // changing the behavior of the program.
+  virtual bool CanReplaceWithConstant() const {
+    return !MayHaveVisibleEffect() && !CanDeoptimize();
+  }
+
+  virtual bool CanEliminate(const BlockEntryInstr* block) const {
+    // Basic blocks should not end in a definition, so treat this as replacing
+    // the definition with a constant (that is then unused).
+    return CanReplaceWithConstant();
+  }
+
   // Replace uses of this definition with uses of other definition or value.
   // Precondition: use lists must be properly calculated.
   // Postcondition: use lists and use values are still valid.
@@ -2998,6 +3021,7 @@
   }
   virtual bool ComputeCanDeoptimize() const { return false; }
   virtual bool HasUnknownSideEffects() const { return false; }
+  virtual bool MayHaveVisibleEffect() const { return true; }
 
   virtual bool AttributesEqual(const Instruction& other) const {
     return other.AsStoreIndexedUnsafe()->offset() == offset();
@@ -4053,6 +4077,10 @@
   virtual bool ComputeCanDeoptimize() const { return false; }
   virtual bool HasUnknownSideEffects() const { return false; }
 
+  virtual bool CanEliminate(const BlockEntryInstr* block) const {
+    return false;
+  }
+
   PRINT_OPERANDS_TO_SUPPORT
 
   DECLARE_EMPTY_SERIALIZATION(ReachabilityFenceInstr, TemplateInstruction)
@@ -4184,6 +4212,9 @@
   DECLARE_INSTRUCTION(UnboxedConstant)
   DECLARE_CUSTOM_SERIALIZATION(UnboxedConstantInstr)
 
+  DECLARE_ATTRIBUTES_NAMED(("value", "representation"),
+                           (&value(), representation()))
+
  private:
   const Representation representation_;
   uword
@@ -6160,6 +6191,10 @@
   virtual bool ComputeCanDeoptimize() const { return false; }
   virtual bool HasUnknownSideEffects() const { return false; }
 
+  virtual bool CanEliminate(const BlockEntryInstr* block) const {
+    return false;
+  }
+
 #define FIELD_LIST(F) F(const int32_t, offset_)
 
   DECLARE_INSTRUCTION_SERIALIZABLE_FIELDS(RawStoreFieldInstr,
@@ -6379,6 +6414,8 @@
   // are marked as having no side-effects.
   virtual bool HasUnknownSideEffects() const { return false; }
 
+  virtual bool MayHaveVisibleEffect() const { return true; }
+
   virtual Representation RequiredInputRepresentation(intptr_t index) const;
 
   virtual Instruction* Canonicalize(FlowGraph* flow_graph);
@@ -6643,6 +6680,8 @@
   // are marked as having no side-effects.
   virtual bool HasUnknownSideEffects() const { return false; }
 
+  virtual bool MayHaveVisibleEffect() const { return true; }
+
   virtual TokenPosition token_pos() const { return token_pos_; }
 
   PRINT_OPERANDS_TO_SUPPORT
@@ -7033,6 +7072,8 @@
 
   virtual bool HasUnknownSideEffects() const { return false; }
 
+  virtual bool MayHaveVisibleEffect() const { return true; }
+
   void PrintOperandsTo(BaseTextBuffer* f) const;
 
   virtual Instruction* Canonicalize(FlowGraph* flow_graph);
@@ -7597,6 +7638,7 @@
 
   virtual bool ComputeCanDeoptimize() const { return false; }
   virtual bool HasUnknownSideEffects() const { return false; }
+  virtual bool CanReplaceWithConstant() const { return false; }
 
   Location* locations() { return locations_; }
   void set_locations(Location* locations) { locations_ = locations; }
@@ -8511,6 +8553,8 @@
 
   void mark_truncating() { is_truncating_ = true; }
 
+  virtual bool ComputeCanDeoptimize() const;
+
   virtual CompileType ComputeType() const;
 
   virtual bool AttributesEqual(const Instruction& other) const {
@@ -8521,6 +8565,8 @@
 
   virtual Definition* Canonicalize(FlowGraph* flow_graph);
 
+  virtual void InferRange(RangeAnalysis* analysis, Range* range);
+
   DECLARE_ABSTRACT_INSTRUCTION(UnboxInteger)
 
   PRINT_OPERANDS_TO_SUPPORT
@@ -8570,10 +8616,6 @@
     ASSERT(is_truncating());
   }
 
-  virtual bool ComputeCanDeoptimize() const;
-
-  virtual void InferRange(RangeAnalysis* analysis, Range* range);
-
   DECLARE_INSTRUCTION_NO_BACKEND(UnboxUint32)
 
   DECLARE_EMPTY_SERIALIZATION(UnboxUint32Instr, UnboxInteger32Instr)
@@ -8594,12 +8636,6 @@
                             deopt_id,
                             speculative_mode) {}
 
-  virtual bool ComputeCanDeoptimize() const;
-
-  virtual void InferRange(RangeAnalysis* analysis, Range* range);
-
-  virtual Definition* Canonicalize(FlowGraph* flow_graph);
-
   DECLARE_INSTRUCTION_NO_BACKEND(UnboxInt32)
 
   DECLARE_EMPTY_SERIALIZATION(UnboxInt32Instr, UnboxInteger32Instr)
@@ -8619,18 +8655,6 @@
                           deopt_id,
                           speculative_mode) {}
 
-  virtual void InferRange(RangeAnalysis* analysis, Range* range);
-
-  virtual Definition* Canonicalize(FlowGraph* flow_graph);
-
-  virtual bool ComputeCanDeoptimize() const {
-    if (SpeculativeModeOfInputs() == kNotSpeculative) {
-      return false;
-    }
-
-    return !value()->Type()->IsInt();
-  }
-
   DECLARE_INSTRUCTION_NO_BACKEND(UnboxInt64)
 
   DECLARE_EMPTY_SERIALIZATION(UnboxInt64Instr, UnboxIntegerInstr)
@@ -8817,6 +8841,8 @@
     return GetDeoptId();
   }
 
+  DECLARE_ATTRIBUTE(op_kind())
+
   PRINT_OPERANDS_TO_SUPPORT
 
   DECLARE_INSTRUCTION(BinaryDoubleOp)
@@ -8998,6 +9024,8 @@
   Value* value() const { return inputs_[0]; }
   Token::Kind op_kind() const { return op_kind_; }
 
+  virtual Definition* Canonicalize(FlowGraph* flow_graph);
+
   virtual bool AttributesEqual(const Instruction& other) const {
     return other.AsUnaryIntegerOp()->op_kind() == op_kind();
   }
@@ -9012,6 +9040,8 @@
 
   DECLARE_ABSTRACT_INSTRUCTION(UnaryIntegerOp)
 
+  DECLARE_ATTRIBUTE(op_kind())
+
 #define FIELD_LIST(F) F(const Token::Kind, op_kind_)
 
   DECLARE_INSTRUCTION_SERIALIZABLE_FIELDS(UnaryIntegerOpInstr,
@@ -9167,6 +9197,10 @@
     set_can_overflow(false);
   }
 
+  // Returns true if right is either a non-zero Integer constant or has a range
+  // that does not include the possibility of being zero.
+  bool RightIsNonZero() const;
+
   // Returns true if right is a non-zero Smi constant which absolute value is
   // a power of two.
   bool RightIsPowerOfTwoConstant() const;
@@ -9183,6 +9217,8 @@
 
   DECLARE_ABSTRACT_INSTRUCTION(BinaryIntegerOp)
 
+  DECLARE_ATTRIBUTE(op_kind())
+
 #define FIELD_LIST(F)                                                          \
   F(const Token::Kind, op_kind_)                                               \
   F(bool, can_overflow_)                                                       \
@@ -9355,7 +9391,8 @@
   }
 
   virtual bool MayThrow() const {
-    return op_kind() == Token::kMOD || op_kind() == Token::kTRUNCDIV;
+    return (op_kind() == Token::kMOD || op_kind() == Token::kTRUNCDIV) &&
+           !RightIsNonZero();
   }
 
   virtual Representation representation() const { return kUnboxedInt64; }
@@ -9616,6 +9653,8 @@
            (representation_ == other_op->representation_);
   }
 
+  DECLARE_ATTRIBUTE(op_kind())
+
   PRINT_OPERANDS_TO_SUPPORT
 
 #define FIELD_LIST(F)                                                          \
@@ -9674,6 +9713,10 @@
 
   virtual bool HasUnknownSideEffects() const { return false; }
 
+  virtual bool CanEliminate(const BlockEntryInstr* block) const {
+    return false;
+  }
+
   virtual bool UseSharedSlowPathStub(bool is_optimizing) const {
     return SlowPathSharingSupported(is_optimizing);
   }
@@ -9978,6 +10021,8 @@
 
   DECLARE_INSTRUCTION(FloatCompare)
 
+  DECLARE_ATTRIBUTE(op_kind())
+
   virtual CompileType ComputeType() const;
 
   virtual bool ComputeCanDeoptimize() const { return false; }
diff --git a/runtime/vm/compiler/backend/locations.cc b/runtime/vm/compiler/backend/locations.cc
index 77cd5d0..4054c3d 100644
--- a/runtime/vm/compiler/backend/locations.cc
+++ b/runtime/vm/compiler/backend/locations.cc
@@ -121,6 +121,12 @@
 }
 #undef REP_MAX_VALUE_CLAUSE
 
+bool RepresentationUtils::IsRepresentable(Representation rep, int64_t value) {
+  const intptr_t bit_size = ValueSize(rep) * kBitsPerByte;
+  return IsUnsigned(rep) ? Utils::IsUint(bit_size, value)
+                         : Utils::IsInt(bit_size, value);
+}
+
 const char* Location::RepresentationToCString(Representation repr) {
   switch (repr) {
 #define REPR_CASE(Name, __, ___)                                               \
diff --git a/runtime/vm/compiler/backend/locations.h b/runtime/vm/compiler/backend/locations.h
index 2489b02..1a9d0ea 100644
--- a/runtime/vm/compiler/backend/locations.h
+++ b/runtime/vm/compiler/backend/locations.h
@@ -97,12 +97,16 @@
   static compiler::OperandSize OperandSize(Representation rep);
 
   // The minimum integral value that can be represented.
-  // Assumes that [rep] is a unboxed integer.
+  // Assumes that [rep] is an unboxed integer.
   static int64_t MinValue(Representation rep);
 
   // The maximum integral value that can be represented.
-  // Assumes that [rep] is a unboxed integer.
+  // Assumes that [rep] is an unboxed integer.
   static int64_t MaxValue(Representation rep);
+
+  // Whether the given value is representable in the given representation.
+  // Assumes that [rep] is an unboxed integer.
+  static bool IsRepresentable(Representation rep, int64_t value);
 };
 
 // The representation for word-sized unboxed fields.
diff --git a/runtime/vm/compiler/backend/range_analysis.cc b/runtime/vm/compiler/backend/range_analysis.cc
index d57cc36..90c4bac 100644
--- a/runtime/vm/compiler/backend/range_analysis.cc
+++ b/runtime/vm/compiler/backend/range_analysis.cc
@@ -24,6 +24,35 @@
 // Quick access to the locally defined zone() method.
 #define Z (zone())
 
+#if defined(DEBUG)
+static void CheckRangeForRepresentation(const Assert& assert,
+                                        const Instruction* instr,
+                                        const Range* range,
+                                        Representation rep) {
+  const Range other = Range::Full(rep);
+  if (!RangeUtils::IsWithin(range, &other)) {
+    assert.Fail(
+        "During range analysis for:\n  %s\n"
+        "expected range containing only %s-representable values, but got %s",
+        instr->ToCString(), RepresentationToCString(rep),
+        Range::ToCString(range));
+  }
+}
+
+#define ASSERT_VALID_RANGE_FOR_REPRESENTATION(instr, range, representation)    \
+  do {                                                                         \
+    CheckRangeForRepresentation(dart::Assert(__FILE__, __LINE__), instr,       \
+                                range, representation);                        \
+  } while (false)
+#else
+#define ASSERT_VALID_RANGE_FOR_REPRESENTATION(instr, range, representation)    \
+  do {                                                                         \
+    USE(instr);                                                                \
+    USE(range);                                                                \
+    USE(representation);                                                       \
+  } while (false)
+#endif
+
 void RangeAnalysis::Analyze() {
   CollectValues();
   InsertConstraints();
@@ -2121,6 +2150,19 @@
   return OnlyGreaterThanOrEqualTo(min_int) && OnlyLessThanOrEqualTo(max_int);
 }
 
+bool Range::IsWithin(const Range* other) const {
+  auto const lower_bound = other->min().LowerBound();
+  auto const upper_bound = other->max().UpperBound();
+  if (lower_bound.IsNegativeInfinity()) {
+    if (upper_bound.IsPositiveInfinity()) return true;
+    return OnlyLessThanOrEqualTo(other->max().ConstantValue());
+  } else if (upper_bound.IsPositiveInfinity()) {
+    return OnlyGreaterThanOrEqualTo(other->min().ConstantValue());
+  } else {
+    return IsWithin(other->min().ConstantValue(), other->max().ConstantValue());
+  }
+}
+
 bool Range::Overlaps(int64_t min_int, int64_t max_int) const {
   RangeBoundary lower = min().LowerBound();
   RangeBoundary upper = max().UpperBound();
@@ -2601,6 +2643,12 @@
     // Only Smi and Mint supported.
     FATAL("Unsupported type in: %s", ToCString());
   }
+
+  // If the representation also gives us range information, then refine
+  // the range from the type by using the intersection of the two.
+  if (RepresentationUtils::IsUnboxedInteger(representation())) {
+    *range = Range::Full(representation()).Intersect(range);
+  }
 }
 
 static bool DependsOnSymbol(const RangeBoundary& a, Definition* symbol) {
@@ -3079,60 +3127,35 @@
 
 void BoxIntegerInstr::InferRange(RangeAnalysis* analysis, Range* range) {
   const Range* value_range = value()->definition()->range();
-  if (!Range::IsUnknown(value_range)) {
+  if (Range::IsUnknown(value_range)) {
+    *range = Range::Full(from_representation());
+  } else {
+    ASSERT_VALID_RANGE_FOR_REPRESENTATION(value()->definition(), value_range,
+                                          from_representation());
     *range = *value_range;
   }
 }
 
-void UnboxInt32Instr::InferRange(RangeAnalysis* analysis, Range* range) {
-  if (value()->Type()->ToCid() == kSmiCid) {
-    const Range* value_range = analysis->GetSmiRange(value());
-    if (!Range::IsUnknown(value_range)) {
-      *range = *value_range;
-    }
-  } else if (RangeAnalysis::IsIntegerDefinition(value()->definition())) {
-    const Range* value_range = analysis->GetIntRange(value());
-    if (!Range::IsUnknown(value_range)) {
-      *range = *value_range;
-    }
-  } else if (value()->Type()->ToCid() == kSmiCid) {
-    *range = Range::Full(RangeBoundary::kRangeBoundarySmi);
-  } else {
-    *range = Range::Full(RangeBoundary::kRangeBoundaryInt32);
-  }
-}
+void UnboxIntegerInstr::InferRange(RangeAnalysis* analysis, Range* range) {
+  auto* const value_range = value()->Type()->ToCid() == kSmiCid
+                                ? analysis->GetSmiRange(value())
+                                : value()->definition()->range();
+  const Range to_range = Range::Full(representation());
 
-void UnboxUint32Instr::InferRange(RangeAnalysis* analysis, Range* range) {
-  const Range* value_range = nullptr;
-
-  if (value()->Type()->ToCid() == kSmiCid) {
-    value_range = analysis->GetSmiRange(value());
-  } else if (RangeAnalysis::IsIntegerDefinition(value()->definition())) {
-    value_range = analysis->GetIntRange(value());
-  } else {
-    *range = Range(RangeBoundary::FromConstant(0),
-                   RangeBoundary::FromConstant(kMaxUint32));
-    return;
-  }
-
-  if (!Range::IsUnknown(value_range)) {
-    if (value_range->IsPositive()) {
-      *range = *value_range;
-    } else {
-      *range = Range(RangeBoundary::FromConstant(0),
-                     RangeBoundary::FromConstant(kMaxUint32));
-    }
-  }
-}
-
-void UnboxInt64Instr::InferRange(RangeAnalysis* analysis, Range* range) {
-  const Range* value_range = value()->definition()->range();
-  if (value_range != nullptr) {
+  if (Range::IsUnknown(value_range)) {
+    *range = to_range;
+  } else if (value_range->IsWithin(&to_range)) {
     *range = *value_range;
-  } else if (!value()->definition()->IsInt64Definition() &&
-             (value()->definition()->Type()->ToCid() != kSmiCid)) {
-    *range = Range::Full(RangeBoundary::kRangeBoundaryInt64);
+  } else if (is_truncating()) {
+    // If truncating, then in most cases any non-representable values means
+    // no assumption can be made about the truncated value.
+    *range = to_range;
+  } else {
+    // When not truncating, then unboxing deoptimizes if the value is outside
+    // the range representation.
+    *range = value_range->Intersect(&to_range);
   }
+  ASSERT_VALID_RANGE_FOR_REPRESENTATION(this, range, representation());
 }
 
 void IntConverterInstr::InferRange(RangeAnalysis* analysis, Range* range) {
@@ -3140,13 +3163,14 @@
   ASSERT(RepresentationUtils::IsUnboxedInteger(to()));
   ASSERT(from() == kUntagged || RepresentationUtils::IsUnboxedInteger(from()));
 
-  auto* const value_range = value()->definition()->range();
+  const Range* const value_range = value()->definition()->range();
+  const Range to_range = Range::Full(to());
 
   if (from() == kUntagged) {
     ASSERT(value_range == nullptr);  // Not an integer-valued definition.
-    *range = Range::Full(to());
+    *range = to_range;
   } else if (Range::IsUnknown(value_range)) {
-    *range = Range::Full(to());
+    *range = to_range;
   } else if (RepresentationUtils::ValueSize(to()) >
                  RepresentationUtils::ValueSize(from()) &&
              (!RepresentationUtils::IsUnsigned(to()) ||
@@ -3161,29 +3185,21 @@
     // are the same size) or a larger value is being truncated. That means
     // we need to determine whether or not the value range lies within the
     // range of numbers that have the same representation (modulo truncation).
-    const int64_t min_overlap =
-        Utils::Maximum(RepresentationUtils::MinValue(from()),
-                       RepresentationUtils::MinValue(to()));
-    const int64_t max_overlap =
-        Utils::Minimum(RepresentationUtils::MaxValue(from()),
-                       RepresentationUtils::MaxValue(to()));
-
-    if (value_range->IsWithin(min_overlap, max_overlap)) {
+    const Range common_range = Range::Full(from()).Intersect(&to_range);
+    if (value_range->IsWithin(&common_range)) {
       *range = *value_range;
     } else {
       // In most cases, if there are non-representable values, then no
       // assumptions can be made about the converted value.
-      *range = Range::Full(to());
+      *range = to_range;
     }
   } else {
     // The conversion deoptimizes if the value is outside the range represented
     // by to(), so we can just take the intersection.
-    const auto& to_range = Range::Full(to());
     *range = value_range->Intersect(&to_range);
   }
 
-  ASSERT(RangeUtils::IsWithin(range, RepresentationUtils::MinValue(to()),
-                              RepresentationUtils::MaxValue(to())));
+  ASSERT_VALID_RANGE_FOR_REPRESENTATION(this, range, to());
 }
 
 void AssertAssignableInstr::InferRange(RangeAnalysis* analysis, Range* range) {
diff --git a/runtime/vm/compiler/backend/range_analysis.h b/runtime/vm/compiler/backend/range_analysis.h
index e3260e8..d37cb7b 100644
--- a/runtime/vm/compiler/backend/range_analysis.h
+++ b/runtime/vm/compiler/backend/range_analysis.h
@@ -429,12 +429,25 @@
   bool IsWithin(int64_t min_int, int64_t max_int) const;
 
   // Inclusive.
+  bool IsWithin(const Range* other) const;
+
+  // Inclusive.
   bool Overlaps(int64_t min_int, int64_t max_int) const;
 
   bool IsUnsatisfiable() const;
 
   bool IsFinite() const { return !min_.IsInfinity() && !max_.IsInfinity(); }
 
+  bool IsSingleton() const {
+    return min_.IsConstant() && max_.IsConstant() &&
+           min_.ConstantValue() == max_.ConstantValue();
+  }
+
+  int64_t Singleton() const {
+    ASSERT(IsSingleton());
+    return min_.ConstantValue();
+  }
+
   Range Intersect(const Range* other) const {
     return Range(RangeBoundary::IntersectionMin(min(), other->min()),
                  RangeBoundary::IntersectionMax(max(), other->max()));
@@ -560,10 +573,14 @@
     return !Range::IsUnknown(range) && range->Fits(size);
   }
 
-  static bool IsWithin(Range* range, int64_t min, int64_t max) {
+  static bool IsWithin(const Range* range, int64_t min, int64_t max) {
     return !Range::IsUnknown(range) && range->IsWithin(min, max);
   }
 
+  static bool IsWithin(const Range* range, const Range* other) {
+    return !Range::IsUnknown(range) && range->IsWithin(other);
+  }
+
   static bool IsPositive(Range* range) {
     return !Range::IsUnknown(range) && range->IsPositive();
   }
@@ -580,6 +597,10 @@
   static bool OnlyLessThanOrEqualTo(Range* range, intptr_t value) {
     return !Range::IsUnknown(range) && range->OnlyLessThanOrEqualTo(value);
   }
+
+  static bool IsSingleton(Range* range) {
+    return !Range::IsUnknown(range) && range->IsSingleton();
+  }
 };
 
 // Range analysis for integer values.
diff --git a/runtime/vm/compiler/backend/redundancy_elimination.cc b/runtime/vm/compiler/backend/redundancy_elimination.cc
index 60538db..9588bde 100644
--- a/runtime/vm/compiler/backend/redundancy_elimination.cc
+++ b/runtime/vm/compiler/backend/redundancy_elimination.cc
@@ -1534,19 +1534,6 @@
   }
 }
 
-// Returns true if instruction may have a "visible" effect,
-static bool MayHaveVisibleEffect(Instruction* instr) {
-  switch (instr->tag()) {
-    case Instruction::kStoreField:
-    case Instruction::kStoreStaticField:
-    case Instruction::kStoreIndexed:
-    case Instruction::kStoreIndexedUnsafe:
-      return true;
-    default:
-      return instr->HasUnknownSideEffects() || instr->MayThrow();
-  }
-}
-
 void LICM::Optimize() {
   if (flow_graph()->function().ProhibitsInstructionHoisting()) {
     // Do not hoist any.
@@ -1624,7 +1611,7 @@
         // effect invalidates the first "visible" effect flag.
         if (is_loop_invariant) {
           Hoist(&it, pre_header, current);
-        } else if (!seen_visible_effect && MayHaveVisibleEffect(current)) {
+        } else if (!seen_visible_effect && current->MayHaveVisibleEffect()) {
           seen_visible_effect = true;
         }
       }
@@ -4441,20 +4428,6 @@
   }
 }
 
-// Returns true if [current] instruction can be possibly eliminated
-// (if its result is not used).
-static bool CanEliminateInstruction(Instruction* current,
-                                    BlockEntryInstr* block) {
-  ASSERT(current->GetBlock() == block);
-  if (MayHaveVisibleEffect(current) || current->CanDeoptimize() ||
-      current == block->last_instruction() || current->IsMaterializeObject() ||
-      current->IsCheckStackOverflow() || current->IsReachabilityFence() ||
-      current->IsRawStoreField()) {
-    return false;
-  }
-  return true;
-}
-
 void DeadCodeElimination::EliminateDeadCode(FlowGraph* flow_graph) {
   GrowableArray<Instruction*> worklist;
   BitVector live(flow_graph->zone(), flow_graph->current_ssa_temp_index());
@@ -4468,7 +4441,7 @@
       ASSERT(!current->IsMoveArgument());
       // TODO(alexmarkov): take control dependencies into account and
       // eliminate dead branches/conditions.
-      if (!CanEliminateInstruction(current, block)) {
+      if (!current->CanEliminate(block)) {
         worklist.Add(current);
         if (Definition* def = current->AsDefinition()) {
           if (def->HasSSATemp()) {
@@ -4525,7 +4498,7 @@
     }
     for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
       Instruction* current = it.Current();
-      if (!CanEliminateInstruction(current, block)) {
+      if (!current->CanEliminate(block)) {
         continue;
       }
       ASSERT(!current->IsMoveArgument());
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc
index 17893dd..16c3edf 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.cc
+++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
@@ -1384,8 +1384,8 @@
         LocalVariable* pointer = MakeTemporary();
         body += LoadLocal(pointer);
         body += LoadLocal(address);
-        body += UnboxTruncate(kUnboxedIntPtr);
-        body += ConvertUnboxedToUntagged(kUnboxedIntPtr);
+        body += UnboxTruncate(kUnboxedFfiIntPtr);
+        body += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr);
         body += StoreNativeField(Slot::PointerBase_data(),
                                  InnerPointerAccess::kCannotBeInnerPointer,
                                  StoreFieldInstr::Kind::kInitializing);
@@ -1467,8 +1467,8 @@
       body += LoadLocal(MakeTemporary());  // Duplicate Pointer.
       body += LoadLocal(parsed_function_->RawParameterVariable(0));  // Address.
       body += CheckNullOptimized(String::ZoneHandle(Z, function.name()));
-      body += UnboxTruncate(kUnboxedIntPtr);
-      body += ConvertUnboxedToUntagged(kUnboxedIntPtr);
+      body += UnboxTruncate(kUnboxedFfiIntPtr);
+      body += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr);
       body += StoreNativeField(Slot::PointerBase_data(),
                                InnerPointerAccess::kCannotBeInnerPointer,
                                StoreFieldInstr::Kind::kInitializing);
@@ -1800,11 +1800,12 @@
   body += LoadLocal(typed_data);
   body += LoadNativeField(Slot::PointerBase_data(),
                           InnerPointerAccess::kMayBeInnerPointer);
-  body += ConvertUntaggedToUnboxed(kUnboxedIntPtr);
+  body += ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr);
   body += LoadLocal(offset_in_bytes);
-  body += UnboxTruncate(kUnboxedIntPtr);
-  body += BinaryIntegerOp(Token::kADD, kUnboxedIntPtr, /*is_truncating=*/true);
-  body += ConvertUnboxedToUntagged(kUnboxedIntPtr);
+  body += UnboxTruncate(kUnboxedFfiIntPtr);
+  body +=
+      BinaryIntegerOp(Token::kADD, kUnboxedFfiIntPtr, /*is_truncating=*/true);
+  body += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr);
   body += StoreNativeField(Slot::PointerBase_data(),
                            InnerPointerAccess::kMayBeInnerPointer,
                            StoreFieldInstr::Kind::kInitializing);
@@ -1865,27 +1866,27 @@
   call_memmove += LoadLocal(arg_to);
   call_memmove += LoadNativeField(Slot::PointerBase_data(),
                                   InnerPointerAccess::kMayBeInnerPointer);
-  call_memmove += ConvertUntaggedToUnboxed(kUnboxedIntPtr);
+  call_memmove += ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr);
   call_memmove += LoadLocal(arg_to_start);
   call_memmove += IntConstant(element_size);
   call_memmove += SmiBinaryOp(Token::kMUL, /*is_truncating=*/true);
-  call_memmove += UnboxTruncate(kUnboxedIntPtr);
+  call_memmove += UnboxTruncate(kUnboxedFfiIntPtr);
   call_memmove +=
-      BinaryIntegerOp(Token::kADD, kUnboxedIntPtr, /*is_truncating=*/true);
+      BinaryIntegerOp(Token::kADD, kUnboxedFfiIntPtr, /*is_truncating=*/true);
   call_memmove += LoadLocal(arg_from);
   call_memmove += LoadNativeField(Slot::PointerBase_data(),
                                   InnerPointerAccess::kMayBeInnerPointer);
-  call_memmove += ConvertUntaggedToUnboxed(kUnboxedIntPtr);
+  call_memmove += ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr);
   call_memmove += LoadLocal(arg_from_start);
   call_memmove += IntConstant(element_size);
   call_memmove += SmiBinaryOp(Token::kMUL, /*is_truncating=*/true);
-  call_memmove += UnboxTruncate(kUnboxedIntPtr);
+  call_memmove += UnboxTruncate(kUnboxedFfiIntPtr);
   call_memmove +=
-      BinaryIntegerOp(Token::kADD, kUnboxedIntPtr, /*is_truncating=*/true);
+      BinaryIntegerOp(Token::kADD, kUnboxedFfiIntPtr, /*is_truncating=*/true);
   call_memmove += LoadLocal(arg_count);
   call_memmove += IntConstant(element_size);
   call_memmove += SmiBinaryOp(Token::kMUL, /*is_truncating=*/true);
-  call_memmove += UnboxTruncate(kUnboxedIntPtr);
+  call_memmove += UnboxTruncate(kUnboxedFfiIntPtr);
   call_memmove += LoadThread();
   call_memmove += LoadUntagged(
       compiler::target::Thread::OffsetFromThread(&kMemoryMoveRuntimeEntry));
@@ -4529,8 +4530,8 @@
   LocalVariable* pointer = MakeTemporary();
   code += LoadLocal(pointer);
   code += LoadLocal(address);
-  code += UnboxTruncate(kUnboxedIntPtr);
-  code += ConvertUnboxedToUntagged(kUnboxedIntPtr);
+  code += UnboxTruncate(kUnboxedFfiIntPtr);
+  code += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr);
   code += StoreNativeField(Slot::PointerBase_data(),
                            InnerPointerAccess::kCannotBeInnerPointer,
                            StoreFieldInstr::Kind::kInitializing);
diff --git a/tools/VERSION b/tools/VERSION
index da7c5d7..175bfe4 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 3
 MINOR 3
 PATCH 0
-PRERELEASE 87
+PRERELEASE 88
 PRERELEASE_PATCH 0