[vm/compiler] Fold IntConverter(Constant(...))

This is important for keeping shifts compiling properly on some
platforms: because some shift variants only support constant shift
distances, so an interfering IntConverter inserted by the
representation selection pass causes compilation to fail.

This fixes Meteor benchmark, which is currently crashing the compiler.

TEST=runtime/tests/vm/dart{,_2}/regress_lsl_with_constant.dart

Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-linux-release-simarm-try,vm-kernel-precomp-linux-release-simarm64-try
Change-Id: Ie2f316cd917a8ebd8d3d96f394b39eac4d135d95
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/196662
Commit-Queue: Vyacheslav Egorov <vegorov@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
diff --git a/runtime/tests/vm/dart/regress_lsl_with_constant_test.dart b/runtime/tests/vm/dart/regress_lsl_with_constant_test.dart
new file mode 100644
index 0000000..368a98b
--- /dev/null
+++ b/runtime/tests/vm/dart/regress_lsl_with_constant_test.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2021, 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.
+
+// Regression test which checks that shift left with constant operand compiles
+// correctly even if narrowed to Int32 shift.
+
+import 'dart:typed_data';
+
+import 'package:expect/expect.dart';
+
+const int N = 10;
+
+@pragma('vm:never-inline')
+void test(Int32List v) {
+  // The shape of the code here is choosen to trigger Int64->Int32
+  // narrowing in the range analysis.
+  v[0] = (v[0] & 0xFF) << (N - 1);
+}
+
+void main() {
+  final list = Int32List(1);
+  for (var i = 0; i < 10; i++) {
+    list[0] = i;
+    test(list);
+    Expect.equals(i << 9, list[0]);
+  }
+}
diff --git a/runtime/tests/vm/dart_2/regress_lsl_with_constant_test.dart b/runtime/tests/vm/dart_2/regress_lsl_with_constant_test.dart
new file mode 100644
index 0000000..368a98b
--- /dev/null
+++ b/runtime/tests/vm/dart_2/regress_lsl_with_constant_test.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2021, 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.
+
+// Regression test which checks that shift left with constant operand compiles
+// correctly even if narrowed to Int32 shift.
+
+import 'dart:typed_data';
+
+import 'package:expect/expect.dart';
+
+const int N = 10;
+
+@pragma('vm:never-inline')
+void test(Int32List v) {
+  // The shape of the code here is choosen to trigger Int64->Int32
+  // narrowing in the range analysis.
+  v[0] = (v[0] & 0xFF) << (N - 1);
+}
+
+void main() {
+  final list = Int32List(1);
+  for (var i = 0; i < 10; i++) {
+    list[0] = i;
+    test(list);
+    Expect.equals(i << 9, list[0]);
+  }
+}
diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc
index 85561c4..3e59fa6 100644
--- a/runtime/vm/compiler/backend/il.cc
+++ b/runtime/vm/compiler/backend/il.cc
@@ -3302,6 +3302,21 @@
 Definition* IntConverterInstr::Canonicalize(FlowGraph* flow_graph) {
   if (!HasUses()) return NULL;
 
+  // Fold IntConverter({Unboxed}Constant(...)) to UnboxedConstant.
+  if (auto constant = value()->definition()->AsConstant()) {
+    if (from() != kUntagged && to() != kUntagged &&
+        constant->representation() == from() && constant->value().IsInteger()) {
+      const int64_t value = Integer::Cast(constant->value()).AsInt64Value();
+      const int64_t result =
+          Evaluator::TruncateTo(Evaluator::TruncateTo(value, from()), to());
+      if (is_truncating() || (value == result)) {
+        auto& box = Integer::Handle(Integer::New(result, Heap::kOld));
+        box ^= box.Canonicalize(flow_graph->thread());
+        return flow_graph->GetConstant(box, to());
+      }
+    }
+  }
+
   IntConverterInstr* box_defn = value()->definition()->AsIntConverter();
   if ((box_defn != NULL) && (box_defn->representation() == from())) {
     // If the first convertion can erase bits (or deoptimize) we can't
diff --git a/runtime/vm/compiler/backend/il_printer.cc b/runtime/vm/compiler/backend/il_printer.cc
index b9907ec..6c903ec 100644
--- a/runtime/vm/compiler/backend/il_printer.cc
+++ b/runtime/vm/compiler/backend/il_printer.cc
@@ -845,6 +845,8 @@
     f->AddString(" {");
     for (intptr_t i = 0; i < defns.length(); ++i) {
       Definition* def = defns[i];
+      // Skip constants which are not used in the graph.
+      if (def->IsConstant() && !def->HasUses()) continue;
       f->AddString("\n      ");
       def->PrintTo(f);
     }