[VM] Unify AOT optimizations for int/double's

So far we had different optimization logic for int/double's depending on
whether they were [InstanceCallInstr]s or [StaticCalInstr]s.

The de-virtualization by TFA causes instance calls to be turned into static
calls which might cause different optimizations to apply.

This CL unifies the optimizations, so they are applied regardless of
whether the operation is an instance call or a static call.

Change-Id: Id4f229412e4d0f19835635bce8c9e5491cca8cb0
Reviewed-on: https://dart-review.googlesource.com/c/84433
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
diff --git a/runtime/vm/compiler/aot/aot_call_specializer.cc b/runtime/vm/compiler/aot/aot_call_specializer.cc
index 0ff81a0..4213020 100644
--- a/runtime/vm/compiler/aot/aot_call_specializer.cc
+++ b/runtime/vm/compiler/aot/aot_call_specializer.cc
@@ -287,16 +287,17 @@
   ASSERT(I->can_use_strong_mode_types());
   ASSERT((cid == kDoubleCid) || (cid == kMintCid));
 
-  const String& function_name =
-      (call->IsInstanceCall()
-           ? call->AsInstanceCall()->function_name()
-           : String::ZoneHandle(Z, call->AsStaticCall()->function().name()));
-
-  AddCheckNull(input, function_name, call->deopt_id(), call->env(), call);
+  if (input->Type()->is_nullable()) {
+    const String& function_name =
+        (call->IsInstanceCall()
+             ? call->AsInstanceCall()->function_name()
+             : String::ZoneHandle(Z, call->AsStaticCall()->function().name()));
+    AddCheckNull(input, function_name, call->deopt_id(), call->env(), call);
+  }
 
   input = input->CopyWithType(Z);
 
-  if ((cid == kDoubleCid) && input->Type()->IsNullableInt()) {
+  if (cid == kDoubleCid && input->Type()->IsNullableInt()) {
     Definition* conversion = NULL;
 
     if (input->Type()->ToNullableCid() == kSmiCid) {
@@ -319,25 +320,28 @@
   return input;
 }
 
-Value* AotCallSpecializer::PrepareReceiverOfDevirtualizedCall(Value* input,
-                                                              intptr_t cid) {
-  ASSERT(I->can_use_strong_mode_types());
-  ASSERT((cid == kDoubleCid) || (cid == kMintCid));
+void AotCallSpecializer::StrengthenReachingType(Value* input, intptr_t cid) {
+  CompileType* old_type = input->Type();
+  CompileType* refined_type = old_type;
 
-  // Can't assert !input->Type()->is_nullable() here as PushArgument receives
-  // value prior to a CheckNull in case of devirtualized call.
-
-  input = input->CopyWithType(Z);
-
-  // Correct type of input if necessary.
-  // This correction is needed as VM may not be able to infer receiver type.
-  if ((cid == kIntegerCid) && !input->Type()->IsNullableInt()) {
-    input->SetReachingType(new (Z) CompileType(CompileType::Int()));
-  } else if ((cid == kDoubleCid) && !input->Type()->IsNullableDouble()) {
-    input->SetReachingType(new (Z) CompileType(CompileType::Double()));
+  CompileType type = CompileType::None();
+  if (cid == kSmiCid) {
+    type = CompileType::NullableSmi();
+    refined_type = CompileType::ComputeRefinedType(old_type, &type);
+  } else if (cid == kMintCid) {
+    type = CompileType::NullableMint();
+    refined_type = CompileType::ComputeRefinedType(old_type, &type);
+  } else if (cid == kIntegerCid && !input->Type()->IsNullableInt()) {
+    type = CompileType::NullableInt();
+    refined_type = CompileType::ComputeRefinedType(old_type, &type);
+  } else if (cid == kDoubleCid && !input->Type()->IsNullableDouble()) {
+    type = CompileType::NullableDouble();
+    refined_type = CompileType::ComputeRefinedType(old_type, &type);
   }
 
-  return input;
+  if (refined_type != old_type) {
+    input->SetReachingType(new (Z) CompileType(*refined_type));
+  }
 }
 
 // After replacing a call with a specialized instruction, make sure to
@@ -354,137 +358,183 @@
     InstanceCallInstr* instr) {
   ASSERT(I->can_use_strong_mode_types());
 
-  const intptr_t receiver_index = instr->FirstArgIndex();
   const Token::Kind op_kind = instr->token_kind();
-  Definition* replacement = NULL;
 
-  switch (op_kind) {
-    case Token::kEQ:
-    case Token::kNE:
-    case Token::kLT:
-    case Token::kLTE:
-    case Token::kGT:
-    case Token::kGTE: {
-      Value* left_value = instr->PushArgumentAt(receiver_index)->value();
-      Value* right_value = instr->PushArgumentAt(receiver_index + 1)->value();
-      CompileType* left_type = left_value->Type();
-      CompileType* right_type = right_value->Type();
-      if (left_type->IsNullableInt() && right_type->IsNullableInt()) {
+  return TryOptimizeIntegerOperation(instr, op_kind) ||
+         TryOptimizeDoubleOperation(instr, op_kind);
+}
+
+bool AotCallSpecializer::TryOptimizeStaticCallUsingStaticTypes(
+    StaticCallInstr* instr) {
+  ASSERT(I->can_use_strong_mode_types());
+
+  const String& name = String::Handle(Z, instr->function().name());
+  const Token::Kind op_kind = MethodTokenRecognizer::RecognizeTokenKind(name);
+
+  if (op_kind == Token::kEQ && TryReplaceWithHaveSameRuntimeType(instr)) {
+    return true;
+  }
+
+  // We only specialize instance methods for int/double operations.
+  const auto& target = instr->function();
+  if (!target.IsDynamicFunction()) {
+    return false;
+  }
+
+  // For de-virtualized instance calls, we strengthen the type here manually
+  // because it might not be attached to the receiver.
+  // See http://dartbug.com/35179 for preserving the receiver type information.
+  const Class& owner = Class::Handle(Z, target.Owner());
+  const intptr_t cid = owner.id();
+  if (cid == kSmiCid || cid == kMintCid || cid == kIntegerCid ||
+      cid == kDoubleCid) {
+    // Sometimes TFA de-virtualizes instance calls to static calls.  In such
+    // cases the VM might have a looser type on the receiver, so we explicitly
+    // tighten it (this is safe since it was proven that te receiver is either
+    // null or will end up with that target).
+    const intptr_t receiver_index = instr->FirstArgIndex();
+    if (instr->ArgumentCountWithoutTypeArgs() >= 1) {
+      StrengthenReachingType(instr->PushArgumentAt(receiver_index)->value(),
+                             cid);
+    }
+  }
+
+  return TryOptimizeIntegerOperation(instr, op_kind) ||
+         TryOptimizeDoubleOperation(instr, op_kind);
+}
+
+bool AotCallSpecializer::TryOptimizeIntegerOperation(TemplateDartCall<0>* instr,
+                                                     Token::Kind op_kind) {
+  if (instr->type_args_len() != 0) {
+    // Arithmetic operations don't have type arguments.
+    return false;
+  }
+
+  ASSERT(I->can_use_strong_mode_types());
+
+  Definition* replacement = NULL;
+  if (instr->ArgumentCount() == 2) {
+    Value* left_value = instr->PushArgumentAt(0)->value();
+    Value* right_value = instr->PushArgumentAt(1)->value();
+    CompileType* left_type = left_value->Type();
+    CompileType* right_type = right_value->Type();
+
+    // We only support binary operations on nullable integers.
+    if (!left_type->IsNullableInt() || !right_type->IsNullableInt()) {
+      return false;
+    }
+
+    switch (op_kind) {
+      case Token::kEQ:
+      case Token::kNE:
+      case Token::kLT:
+      case Token::kLTE:
+      case Token::kGT:
+      case Token::kGTE: {
+        const bool is_equality_op = Token::IsEqualityOperator(op_kind);
+        if (is_equality_op && (left_type->ToNullableCid() == kSmiCid ||
+                               right_type->ToNullableCid() == kSmiCid)) {
+          replacement = new (Z) StrictCompareInstr(
+              instr->token_pos(),
+              (op_kind == Token::kEQ) ? Token::kEQ_STRICT : Token::kNE_STRICT,
+              left_value->CopyWithType(Z), right_value->CopyWithType(Z),
+              /*needs_number_check=*/false, DeoptId::kNone);
+          break;
+        }
+
         if (FlowGraphCompiler::SupportsUnboxedInt64()) {
-          if (Token::IsRelationalOperator(op_kind)) {
+          if (is_equality_op && left_type->IsInt() && right_type->IsInt()) {
+            replacement = new (Z) EqualityCompareInstr(
+                instr->token_pos(), op_kind, left_value->CopyWithType(Z),
+                right_value->CopyWithType(Z), kMintCid, DeoptId::kNone,
+                Instruction::kNotSpeculative);
+            break;
+          } else if (Token::IsRelationalOperator(op_kind)) {
             left_value = PrepareStaticOpInput(left_value, kMintCid, instr);
             right_value = PrepareStaticOpInput(right_value, kMintCid, instr);
             replacement = new (Z) RelationalOpInstr(
                 instr->token_pos(), op_kind, left_value, right_value, kMintCid,
                 DeoptId::kNone, Instruction::kNotSpeculative);
-
+            break;
           } else {
             // TODO(dartbug.com/30480): Figure out how to handle null in
             // equality comparisons.
-            // replacement = new (Z) EqualityCompareInstr(
-            //     instr->token_pos(), op_kind, left_value->CopyWithType(Z),
-            //     right_value->CopyWithType(Z), kMintCid, DeoptId::kNone);
-            replacement = new (Z) CheckedSmiComparisonInstr(
-                instr->token_kind(), left_value->CopyWithType(Z),
-                right_value->CopyWithType(Z), instr);
-          }
-          // TODO(dartbug.com/30480): Enable comparisons with Smi.
-        } else if (false &&
-                   ((op_kind == Token::kEQ) || (op_kind == Token::kNE)) &&
-                   ((left_type->ToCid() == kSmiCid) ||
-                    (right_type->ToCid() == kSmiCid))) {
-          replacement = new (Z) StrictCompareInstr(
-              instr->token_pos(),
-              (op_kind == Token::kEQ) ? Token::kEQ_STRICT : Token::kNE_STRICT,
-              left_value->CopyWithType(Z), right_value->CopyWithType(Z),
-              /* number_check = */ false, DeoptId::kNone);
-        } else {
-          replacement = new (Z) CheckedSmiComparisonInstr(
-              instr->token_kind(), left_value->CopyWithType(Z),
-              right_value->CopyWithType(Z), instr);
-        }
-      } else if (FlowGraphCompiler::SupportsUnboxedDoubles() &&
-                 (left_type->IsNullableDouble() ||
-                  IsSupportedIntOperandForStaticDoubleOp(left_type)) &&
-                 (right_type->IsNullableDouble() ||
-                  IsSupportedIntOperandForStaticDoubleOp(right_type))) {
-        ASSERT(left_type->IsNullableDouble() || right_type->IsNullableDouble());
-        // TODO(dartbug.com/30480): Support == and != for doubles.
-        if ((op_kind == Token::kLT) || (op_kind == Token::kLTE) ||
-            (op_kind == Token::kGT) || (op_kind == Token::kGTE)) {
-          left_value = PrepareStaticOpInput(left_value, kDoubleCid, instr);
-          right_value = PrepareStaticOpInput(right_value, kDoubleCid, instr);
-          replacement = new (Z) RelationalOpInstr(
-              instr->token_pos(), op_kind, left_value, right_value, kDoubleCid,
-              DeoptId::kNone, Instruction::kNotSpeculative);
-        }
-      }
-      break;
-    }
-    case Token::kSHL:
-    case Token::kSHR:
-    case Token::kBIT_OR:
-    case Token::kBIT_XOR:
-    case Token::kBIT_AND:
-    case Token::kADD:
-    case Token::kSUB:
-    case Token::kMUL:
-    case Token::kDIV: {
-      if ((op_kind == Token::kDIV) &&
-          !FlowGraphCompiler::SupportsHardwareDivision()) {
-        return false;
-      }
-      Value* left_value = instr->PushArgumentAt(receiver_index)->value();
-      Value* right_value = instr->PushArgumentAt(receiver_index + 1)->value();
-      CompileType* left_type = left_value->Type();
-      CompileType* right_type = right_value->Type();
-      if (left_type->IsNullableInt() && right_type->IsNullableInt() &&
-          (op_kind != Token::kDIV)) {
-        if (FlowGraphCompiler::SupportsUnboxedInt64()) {
-          if ((op_kind == Token::kSHR) || (op_kind == Token::kSHL)) {
-            // TODO(dartbug.com/30480): Enable 64-bit integer shifts.
-            // replacement = new ShiftInt64OpInstr(
-            //     op_kind, left_value->CopyWithType(Z),
-            //     right_value->CopyWithType(Z), DeoptId::kNone);
-            replacement =
-                new (Z) CheckedSmiOpInstr(op_kind, left_value->CopyWithType(Z),
+            replacement = new (Z)
+                CheckedSmiComparisonInstr(op_kind, left_value->CopyWithType(Z),
                                           right_value->CopyWithType(Z), instr);
+            break;
+          }
+        } else {
+          replacement = new (Z)
+              CheckedSmiComparisonInstr(op_kind, left_value->CopyWithType(Z),
+                                        right_value->CopyWithType(Z), instr);
+          break;
+        }
+        break;
+      }
+#if defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_ARM64)
+        // TODO(ajcbik): 32-bit archs too?
+      case Token::kMOD:
+      case Token::kTRUNCDIV:
+#endif
+      case Token::kSHL:
+      case Token::kSHR:
+      case Token::kBIT_OR:
+      case Token::kBIT_XOR:
+      case Token::kBIT_AND:
+      case Token::kADD:
+      case Token::kSUB:
+      case Token::kMUL: {
+        if (FlowGraphCompiler::SupportsUnboxedInt64()) {
+          if (op_kind == Token::kSHR || op_kind == Token::kSHL) {
+            left_value = PrepareStaticOpInput(left_value, kMintCid, instr);
+            right_value = PrepareStaticOpInput(right_value, kMintCid, instr);
+            replacement = new (Z) ShiftInt64OpInstr(
+                op_kind, left_value, right_value, DeoptId::kNone);
+            break;
           } else {
             left_value = PrepareStaticOpInput(left_value, kMintCid, instr);
             right_value = PrepareStaticOpInput(right_value, kMintCid, instr);
             replacement = new (Z) BinaryInt64OpInstr(
                 op_kind, left_value, right_value, DeoptId::kNone,
                 Instruction::kNotSpeculative);
+            break;
           }
-        } else {
+        }
+        if (op_kind != Token::kMOD && op_kind != Token::kTRUNCDIV) {
           replacement =
               new (Z) CheckedSmiOpInstr(op_kind, left_value->CopyWithType(Z),
                                         right_value->CopyWithType(Z), instr);
+          break;
         }
-      } else if (FlowGraphCompiler::SupportsUnboxedDoubles() &&
-                 (left_type->IsNullableDouble() ||
-                  IsSupportedIntOperandForStaticDoubleOp(left_type)) &&
-                 (right_type->IsNullableDouble() ||
-                  IsSupportedIntOperandForStaticDoubleOp(right_type))) {
-        if ((op_kind == Token::kADD) || (op_kind == Token::kSUB) ||
-            (op_kind == Token::kMUL) || (op_kind == Token::kDIV)) {
-          ASSERT(left_type->IsNullableDouble() ||
-                 right_type->IsNullableDouble() || (op_kind == Token::kDIV));
-          left_value = PrepareStaticOpInput(left_value, kDoubleCid, instr);
-          right_value = PrepareStaticOpInput(right_value, kDoubleCid, instr);
-          replacement = new (Z) BinaryDoubleOpInstr(
-              op_kind, left_value, right_value, DeoptId::kNone,
-              instr->token_pos(), Instruction::kNotSpeculative);
-        }
+        break;
       }
-      break;
+
+      default:
+        break;
+    }
+  } else if (instr->ArgumentCount() == 1) {
+    Value* left_value = instr->PushArgumentAt(0)->value();
+    CompileType* left_type = left_value->Type();
+
+    // We only support unary operations on nullable integers.
+    if (!left_type->IsNullableInt()) {
+      return false;
     }
 
-    default:
-      break;
+    // Issue http://dartbug.com/35180 for adding support for Token::kBIT_NOT
+    // here.
+#ifndef TARGET_ARCH_DBC
+    if (op_kind == Token::kNEGATE) {
+      left_value = PrepareStaticOpInput(left_value, kMintCid, instr);
+      replacement =
+          new (Z) UnaryInt64OpInstr(Token::kNEGATE, left_value, DeoptId::kNone,
+                                    Instruction::kNotSpeculative);
+    }
+#endif
   }
 
-  if (replacement != NULL && !replacement->ComputeCanDeoptimize()) {
+  if (replacement != nullptr && !replacement->ComputeCanDeoptimize()) {
     if (FLAG_trace_strong_mode_types) {
       THR_Print("[Strong mode] Optimization: replacing %s with %s\n",
                 instr->ToCString(), replacement->ToCString());
@@ -497,47 +547,46 @@
   return false;
 }
 
-bool AotCallSpecializer::TryOptimizeStaticCallUsingStaticTypes(
-    StaticCallInstr* instr) {
-  ASSERT(I->can_use_strong_mode_types());
-  Definition* replacement = NULL;
-
-  const String& name = String::Handle(Z, instr->function().name());
-  const Token::Kind op_kind = MethodTokenRecognizer::RecognizeTokenKind(name);
-
-  if ((op_kind == Token::kEQ) && TryReplaceWithHaveSameRuntimeType(instr)) {
-    return true;
-  }
-
-  const Class& owner = Class::Handle(Z, instr->function().Owner());
-  if ((owner.id() != kIntegerCid) && (owner.id() != kDoubleCid)) {
+bool AotCallSpecializer::TryOptimizeDoubleOperation(TemplateDartCall<0>* instr,
+                                                    Token::Kind op_kind) {
+  if (instr->type_args_len() != 0) {
+    // Arithmetic operations don't have type arguments.
     return false;
   }
 
-  const intptr_t receiver_index = instr->FirstArgIndex();
+  Definition* replacement = NULL;
 
-  // Recognize double and int operators here as devirtualization can convert
-  // instance calls of these operators into static calls.
+  if (instr->ArgumentCount() == 2) {
+    if (!FlowGraphCompiler::SupportsUnboxedDoubles()) {
+      return false;
+    }
 
-  if (owner.id() == kIntegerCid) {
-    if (!FlowGraphCompiler::SupportsUnboxedInt64()) {
+    Value* left_value = instr->PushArgumentAt(0)->value();
+    Value* right_value = instr->PushArgumentAt(1)->value();
+    CompileType* left_type = left_value->Type();
+    CompileType* right_type = right_value->Type();
+
+    if (!left_type->IsNullableDouble() &&
+        !IsSupportedIntOperandForStaticDoubleOp(left_type)) {
+      return false;
+    }
+    if (!right_type->IsNullableDouble() &&
+        !IsSupportedIntOperandForStaticDoubleOp(right_type)) {
       return false;
     }
 
     switch (op_kind) {
       case Token::kEQ:
       case Token::kNE: {
-        Value* left_value = instr->PushArgumentAt(receiver_index)->value();
-        Value* right_value = instr->PushArgumentAt(receiver_index + 1)->value();
-        CompileType* right_type = right_value->Type();
-        // TODO(dartbug.com/32166): Support EQ, NE for nullable ints.
+        // TODO(dartbug.com/32166): Support EQ, NE for nullable doubles.
         // (requires null-aware comparison instruction).
-        if (right_type->IsNullableInt() && !right_type->is_nullable()) {
-          left_value = PrepareReceiverOfDevirtualizedCall(left_value, kMintCid);
-          right_value = PrepareStaticOpInput(right_value, kMintCid, instr);
+        if (right_type->IsDouble()) {
+          left_value = PrepareStaticOpInput(left_value, kDoubleCid, instr);
+          right_value = PrepareStaticOpInput(right_value, kDoubleCid, instr);
           replacement = new (Z) EqualityCompareInstr(
-              instr->token_pos(), op_kind, left_value, right_value, kMintCid,
+              instr->token_pos(), op_kind, left_value, right_value, kDoubleCid,
               DeoptId::kNone, Instruction::kNotSpeculative);
+          break;
         }
         break;
       }
@@ -545,164 +594,55 @@
       case Token::kLTE:
       case Token::kGT:
       case Token::kGTE: {
-        Value* left_value = instr->PushArgumentAt(receiver_index)->value();
-        Value* right_value = instr->PushArgumentAt(receiver_index + 1)->value();
-        CompileType* left_type = left_value->Type();
-        CompileType* right_type = right_value->Type();
-        if (right_type->IsNullableInt()) {
-          left_value = PrepareReceiverOfDevirtualizedCall(left_value, kMintCid);
-          right_value = PrepareStaticOpInput(right_value, kMintCid, instr);
-          replacement = new (Z) RelationalOpInstr(
-              instr->token_pos(), op_kind, left_value, right_value, kMintCid,
-              DeoptId::kNone, Instruction::kNotSpeculative);
-        } else if (FlowGraphCompiler::SupportsUnboxedDoubles() &&
-                   right_type->IsNullableDouble() &&
-                   IsSupportedIntOperandForStaticDoubleOp(left_type)) {
-          left_value = PrepareStaticOpInput(left_value, kDoubleCid, instr);
-          right_value = PrepareStaticOpInput(right_value, kDoubleCid, instr);
-          replacement = new (Z) RelationalOpInstr(
-              instr->token_pos(), op_kind, left_value, right_value, kDoubleCid,
-              DeoptId::kNone, Instruction::kNotSpeculative);
-        }
+        left_value = PrepareStaticOpInput(left_value, kDoubleCid, instr);
+        right_value = PrepareStaticOpInput(right_value, kDoubleCid, instr);
+        replacement = new (Z) RelationalOpInstr(
+            instr->token_pos(), op_kind, left_value, right_value, kDoubleCid,
+            DeoptId::kNone, Instruction::kNotSpeculative);
         break;
       }
-      case Token::kBIT_OR:
-      case Token::kBIT_XOR:
-      case Token::kBIT_AND:
       case Token::kADD:
       case Token::kSUB:
       case Token::kMUL:
-#if defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_ARM64)
-      // TODO(ajcbik): 32-bit archs too?
-      case Token::kMOD:
-      case Token::kTRUNCDIV:
-#endif
       case Token::kDIV: {
-        if ((op_kind == Token::kDIV) &&
+        if (op_kind == Token::kDIV &&
             !FlowGraphCompiler::SupportsHardwareDivision()) {
           return false;
         }
-        Value* left_value = instr->PushArgumentAt(receiver_index)->value();
-        Value* right_value = instr->PushArgumentAt(receiver_index + 1)->value();
-        CompileType* left_type = left_value->Type();
-        CompileType* right_type = right_value->Type();
-        if (right_type->IsNullableInt() && (op_kind != Token::kDIV)) {
-          left_value = PrepareReceiverOfDevirtualizedCall(left_value, kMintCid);
-          right_value = PrepareStaticOpInput(right_value, kMintCid, instr);
-          replacement = new (Z)
-              BinaryInt64OpInstr(op_kind, left_value, right_value,
-                                 DeoptId::kNone, Instruction::kNotSpeculative);
-        } else if (FlowGraphCompiler::SupportsUnboxedDoubles() &&
-                   right_type->IsNullableDouble() &&
-                   IsSupportedIntOperandForStaticDoubleOp(left_type)) {
-          if ((op_kind == Token::kADD) || (op_kind == Token::kSUB) ||
-              (op_kind == Token::kMUL) || (op_kind == Token::kDIV)) {
-            ASSERT(left_type->IsNullableDouble() ||
-                   right_type->IsNullableDouble() || (op_kind == Token::kDIV));
-            left_value = PrepareStaticOpInput(left_value, kDoubleCid, instr);
-            right_value = PrepareStaticOpInput(right_value, kDoubleCid, instr);
-            replacement = new (Z) BinaryDoubleOpInstr(
-                op_kind, left_value, right_value, DeoptId::kNone,
-                instr->token_pos(), Instruction::kNotSpeculative);
-          }
-        }
+        left_value = PrepareStaticOpInput(left_value, kDoubleCid, instr);
+        right_value = PrepareStaticOpInput(right_value, kDoubleCid, instr);
+        replacement = new (Z) BinaryDoubleOpInstr(
+            op_kind, left_value, right_value, DeoptId::kNone,
+            instr->token_pos(), Instruction::kNotSpeculative);
         break;
       }
-#ifndef TARGET_ARCH_DBC
-      case Token::kNEGATE: {
-        Value* left_value = instr->PushArgumentAt(receiver_index)->value();
-        left_value = PrepareReceiverOfDevirtualizedCall(left_value, kMintCid);
-        replacement = new (Z)
-            UnaryInt64OpInstr(Token::kNEGATE, left_value, DeoptId::kNone,
-                              Instruction::kNotSpeculative);
-        break;
-      }
-#endif
-      case Token::kSHL:
-      case Token::kSHR: {
-        Value* left_value = instr->PushArgumentAt(receiver_index)->value();
-        Value* right_value = instr->PushArgumentAt(receiver_index + 1)->value();
-        CompileType* right_type = right_value->Type();
-        if (right_type->IsNullableInt()) {
-          left_value = PrepareReceiverOfDevirtualizedCall(left_value, kMintCid);
-          right_value = PrepareStaticOpInput(right_value, kMintCid, instr);
-          replacement = new (Z) ShiftInt64OpInstr(op_kind, left_value,
-                                                  right_value, DeoptId::kNone);
-        }
-        break;
-      }
+
+      case Token::kBIT_OR:
+      case Token::kBIT_XOR:
+      case Token::kBIT_AND:
+      case Token::kMOD:
+      case Token::kTRUNCDIV:
       default:
         break;
     }
-  } else if ((owner.id() == kDoubleCid) &&
-             FlowGraphCompiler::SupportsUnboxedDoubles()) {
-    // TODO(dartbug.com/30480): Handle more double operations.
-    switch (op_kind) {
-      case Token::kEQ:
-      case Token::kNE: {
-        Value* left_value = instr->PushArgumentAt(receiver_index)->value();
-        Value* right_value = instr->PushArgumentAt(receiver_index + 1)->value();
-        CompileType* right_type = right_value->Type();
-        // TODO(dartbug.com/32166): Support EQ, NE for nullable doubles.
-        // (requires null-aware comparison instruction).
-        if (right_type->IsNullableDouble() && !right_type->is_nullable()) {
-          left_value =
-              PrepareReceiverOfDevirtualizedCall(left_value, kDoubleCid);
-          right_value = PrepareStaticOpInput(right_value, kDoubleCid, instr);
-          replacement = new (Z) EqualityCompareInstr(
-              instr->token_pos(), op_kind, left_value, right_value, kDoubleCid,
-              DeoptId::kNone, Instruction::kNotSpeculative);
-        }
-        break;
-      }
-      case Token::kLT:
-      case Token::kLTE:
-      case Token::kGT:
-      case Token::kGTE: {
-        Value* left_value = instr->PushArgumentAt(receiver_index)->value();
-        Value* right_value = instr->PushArgumentAt(receiver_index + 1)->value();
-        if (right_value->Type()->IsNullableDouble() ||
-            IsSupportedIntOperandForStaticDoubleOp(right_value->Type())) {
-          left_value =
-              PrepareReceiverOfDevirtualizedCall(left_value, kDoubleCid);
-          right_value = PrepareStaticOpInput(right_value, kDoubleCid, instr);
-          replacement = new (Z) RelationalOpInstr(
-              instr->token_pos(), op_kind, left_value, right_value, kDoubleCid,
-              DeoptId::kNone, Instruction::kNotSpeculative);
-        }
-        break;
-      }
-      case Token::kADD:
-      case Token::kSUB:
-      case Token::kMUL:
-      case Token::kDIV: {
-        Value* left_value = instr->PushArgumentAt(receiver_index)->value();
-        Value* right_value = instr->PushArgumentAt(receiver_index + 1)->value();
-        if (right_value->Type()->IsNullableDouble() ||
-            IsSupportedIntOperandForStaticDoubleOp(right_value->Type())) {
-          left_value =
-              PrepareReceiverOfDevirtualizedCall(left_value, kDoubleCid);
-          right_value = PrepareStaticOpInput(right_value, kDoubleCid, instr);
-          replacement = new (Z) BinaryDoubleOpInstr(
-              op_kind, left_value, right_value, DeoptId::kNone,
-              instr->token_pos(), Instruction::kNotSpeculative);
-        }
-        break;
-      }
-      case Token::kNEGATE: {
-        Value* left_value = instr->PushArgumentAt(receiver_index)->value();
-        left_value = PrepareReceiverOfDevirtualizedCall(left_value, kDoubleCid);
-        replacement = new (Z)
-            UnaryDoubleOpInstr(Token::kNEGATE, left_value, instr->deopt_id(),
-                               Instruction::kNotSpeculative);
-        break;
-      }
-      default:
-        break;
+  } else if (instr->ArgumentCount() == 1) {
+    Value* left_value = instr->PushArgumentAt(0)->value();
+    CompileType* left_type = left_value->Type();
+
+    // We only support unary operations on nullable doubles.
+    if (!left_type->IsNullableDouble()) {
+      return false;
+    }
+
+    if (op_kind == Token::kNEGATE) {
+      left_value = PrepareStaticOpInput(left_value, kDoubleCid, instr);
+      replacement = new (Z)
+          UnaryDoubleOpInstr(Token::kNEGATE, left_value, instr->deopt_id(),
+                             Instruction::kNotSpeculative);
     }
   }
 
-  if ((replacement != NULL) && !replacement->ComputeCanDeoptimize()) {
+  if (replacement != NULL && !replacement->ComputeCanDeoptimize()) {
     if (FLAG_trace_strong_mode_types) {
       THR_Print("[Strong mode] Optimization: replacing %s with %s\n",
                 instr->ToCString(), replacement->ToCString());
diff --git a/runtime/vm/compiler/aot/aot_call_specializer.h b/runtime/vm/compiler/aot/aot_call_specializer.h
index 5ffbea1..cbced5d 100644
--- a/runtime/vm/compiler/aot/aot_call_specializer.h
+++ b/runtime/vm/compiler/aot/aot_call_specializer.h
@@ -48,12 +48,22 @@
   bool IsSupportedIntOperandForStaticDoubleOp(CompileType* operand_type);
   Value* PrepareStaticOpInput(Value* input, intptr_t cid, Instruction* call);
 
-  Value* PrepareReceiverOfDevirtualizedCall(Value* input, intptr_t cid);
+  void StrengthenReachingType(Value* input, intptr_t cid);
 
   bool TryOptimizeInstanceCallUsingStaticTypes(InstanceCallInstr* instr);
 
   virtual bool TryOptimizeStaticCallUsingStaticTypes(StaticCallInstr* call);
 
+  // Try to replace a call with a more specialized instruction working on
+  // integers (e.g. BinaryInt64OpInstr, CheckedSmiComparisonInstr,
+  // RelationalOpInstr)
+  bool TryOptimizeIntegerOperation(TemplateDartCall<0>* call, Token::Kind kind);
+
+  // Try to replace a call with a more specialized instruction working on
+  // doubles (e.g. BinaryDoubleOpInstr, CheckedSmiComparisonInstr,
+  // RelationalOpInstr)
+  bool TryOptimizeDoubleOperation(TemplateDartCall<0>* call, Token::Kind kind);
+
   // Check if o.m(...) [call] is actually an invocation through a getter
   // o.get:m().call(...) given that the receiver of the call is a subclass
   // of the [receiver_class]. If it is - then expand it into
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index f7ee3a1..845f173 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -144,12 +144,28 @@
   // Create non-nullable Int type.
   static CompileType Int();
 
+  // Create nullable Int type.
+  static CompileType NullableInt();
+
   // Create non-nullable Smi type.
   static CompileType Smi();
 
+  // Create nullable Smi type.
+  static CompileType NullableSmi() {
+    return CreateNullable(kNullable, kSmiCid);
+  }
+
+  // Create nullable Mint type.
+  static CompileType NullableMint() {
+    return CreateNullable(kNullable, kMintCid);
+  }
+
   // Create non-nullable Double type.
   static CompileType Double();
 
+  // Create nullable Double type.
+  static CompileType NullableDouble();
+
   // Create non-nullable String type.
   static CompileType String();
 
@@ -172,10 +188,10 @@
   bool IsNone() const { return (cid_ == kIllegalCid) && (type_ == NULL); }
 
   // Returns true if value of this type is a non-nullable int.
-  bool IsInt() {
-    // A nullable int that isn't nullable is an int.
-    return !is_nullable() && IsNullableInt();
-  }
+  bool IsInt() { return !is_nullable() && IsNullableInt(); }
+
+  // Returns true if value of this type is a non-nullable double.
+  bool IsDouble() { return !is_nullable() && IsNullableDouble(); }
 
   // Returns true if value of this type is either int or null.
   bool IsNullableInt() {
@@ -3269,6 +3285,18 @@
     ASSERT(argument_names.IsZoneHandle() || argument_names.InVMHeap());
   }
 
+  RawString* Selector() {
+    // The Token::Kind we have does unfortunately not encode whether the call is
+    // a dyn: call or not.
+    if (auto static_call = this->AsStaticCall()) {
+      return static_call->ic_data()->target_name();
+    } else if (auto instance_call = this->AsInstanceCall()) {
+      return instance_call->ic_data()->target_name();
+    } else {
+      UNREACHABLE();
+    }
+  }
+
   intptr_t FirstArgIndex() const { return type_args_len_ > 0 ? 1 : 0; }
   Value* Receiver() const {
     return this->PushArgumentAt(FirstArgIndex())->value();
@@ -6332,14 +6360,14 @@
   CheckedSmiOpInstr(Token::Kind op_kind,
                     Value* left,
                     Value* right,
-                    InstanceCallInstr* call)
+                    TemplateDartCall<0>* call)
       : TemplateDefinition(call->deopt_id()), call_(call), op_kind_(op_kind) {
     ASSERT(call->type_args_len() == 0);
     SetInputAt(0, left);
     SetInputAt(1, right);
   }
 
-  InstanceCallInstr* call() const { return call_; }
+  TemplateDartCall<0>* call() const { return call_; }
   Token::Kind op_kind() const { return op_kind_; }
   Value* left() const { return inputs_[0]; }
   Value* right() const { return inputs_[1]; }
@@ -6357,7 +6385,7 @@
   DECLARE_INSTRUCTION(CheckedSmiOp)
 
  private:
-  InstanceCallInstr* call_;
+  TemplateDartCall<0>* call_;
   const Token::Kind op_kind_;
   DISALLOW_COPY_AND_ASSIGN(CheckedSmiOpInstr);
 };
@@ -6367,7 +6395,7 @@
   CheckedSmiComparisonInstr(Token::Kind op_kind,
                             Value* left,
                             Value* right,
-                            InstanceCallInstr* call)
+                            TemplateDartCall<0>* call)
       : TemplateComparison(call->token_pos(), op_kind, call->deopt_id()),
         call_(call),
         is_negated_(false) {
@@ -6376,7 +6404,7 @@
     SetInputAt(1, right);
   }
 
-  InstanceCallInstr* call() const { return call_; }
+  TemplateDartCall<0>* call() const { return call_; }
 
   virtual bool ComputeCanDeoptimize() const { return false; }
 
@@ -6413,7 +6441,7 @@
   virtual ComparisonInstr* CopyWithNewOperands(Value* left, Value* right);
 
  private:
-  InstanceCallInstr* call_;
+  TemplateDartCall<0>* call_;
   bool is_negated_;
   DISALLOW_COPY_AND_ASSIGN(CheckedSmiComparisonInstr);
 };
diff --git a/runtime/vm/compiler/backend/il_arm.cc b/runtime/vm/compiler/backend/il_arm.cc
index 59249f8..7f7b6de 100644
--- a/runtime/vm/compiler/backend/il_arm.cc
+++ b/runtime/vm/compiler/backend/il_arm.cc
@@ -3221,13 +3221,12 @@
     }
     __ Push(locs->in(0).reg());
     __ Push(locs->in(1).reg());
-    const String& selector =
-        String::Handle(instruction()->call()->ic_data()->target_name());
-    const Array& arguments_descriptor =
-        Array::Handle(instruction()->call()->ic_data()->arguments_descriptor());
+    const auto& selector = String::Handle(instruction()->call()->Selector());
+    const auto& arguments_descriptor = Array::Handle(
+        ArgumentsDescriptor::New(/*type_args_len=*/0, /*num_arguments=*/2));
     compiler->EmitMegamorphicInstanceCall(
         selector, arguments_descriptor, instruction()->call()->deopt_id(),
-        instruction()->call()->token_pos(), locs, try_index_, kNumSlowPathArgs);
+        instruction()->token_pos(), locs, try_index_, kNumSlowPathArgs);
     __ mov(result, Operand(R0));
     compiler->RestoreLiveRegisters(locs);
     __ b(exit_label());
@@ -3362,13 +3361,12 @@
     }
     __ Push(locs->in(0).reg());
     __ Push(locs->in(1).reg());
-    String& selector =
-        String::Handle(instruction()->call()->ic_data()->target_name());
-    const Array& arguments_descriptor =
-        Array::Handle(instruction()->call()->ic_data()->arguments_descriptor());
+    const auto& selector = String::Handle(instruction()->call()->Selector());
+    const auto& arguments_descriptor = Array::Handle(
+        ArgumentsDescriptor::New(/*type_args_len=*/0, /*num_arguments=*/2));
     compiler->EmitMegamorphicInstanceCall(
         selector, arguments_descriptor, instruction()->call()->deopt_id(),
-        instruction()->call()->token_pos(), locs, try_index_, kNumSlowPathArgs);
+        instruction()->token_pos(), locs, try_index_, kNumSlowPathArgs);
     __ mov(result, Operand(R0));
     compiler->RestoreLiveRegisters(locs);
     compiler->pending_deoptimization_env_ = NULL;
diff --git a/runtime/vm/compiler/backend/il_arm64.cc b/runtime/vm/compiler/backend/il_arm64.cc
index faff27a..b959d2f 100644
--- a/runtime/vm/compiler/backend/il_arm64.cc
+++ b/runtime/vm/compiler/backend/il_arm64.cc
@@ -2917,13 +2917,12 @@
     }
     __ Push(locs->in(0).reg());
     __ Push(locs->in(1).reg());
-    const String& selector =
-        String::Handle(instruction()->call()->ic_data()->target_name());
-    const Array& arguments_descriptor =
-        Array::Handle(instruction()->call()->ic_data()->arguments_descriptor());
+    const auto& selector = String::Handle(instruction()->call()->Selector());
+    const auto& arguments_descriptor = Array::Handle(
+        ArgumentsDescriptor::New(/*type_args_len=*/0, /*num_arguments=*/2));
     compiler->EmitMegamorphicInstanceCall(
         selector, arguments_descriptor, instruction()->call()->deopt_id(),
-        instruction()->call()->token_pos(), locs, try_index_, kNumSlowPathArgs);
+        instruction()->token_pos(), locs, try_index_, kNumSlowPathArgs);
     __ mov(result, R0);
     compiler->RestoreLiveRegisters(locs);
     __ b(exit_label());
@@ -3060,13 +3059,12 @@
     }
     __ Push(locs->in(0).reg());
     __ Push(locs->in(1).reg());
-    String& selector =
-        String::Handle(instruction()->call()->ic_data()->target_name());
-    const Array& arguments_descriptor =
-        Array::Handle(instruction()->call()->ic_data()->arguments_descriptor());
+    const auto& selector = String::Handle(instruction()->call()->Selector());
+    const auto& arguments_descriptor = Array::Handle(
+        ArgumentsDescriptor::New(/*type_args_len=*/0, /*num_arguments=*/2));
     compiler->EmitMegamorphicInstanceCall(
         selector, arguments_descriptor, instruction()->call()->deopt_id(),
-        instruction()->call()->token_pos(), locs, try_index_, kNumSlowPathArgs);
+        instruction()->token_pos(), locs, try_index_, kNumSlowPathArgs);
     __ mov(result, R0);
     compiler->RestoreLiveRegisters(locs);
     compiler->pending_deoptimization_env_ = NULL;
diff --git a/runtime/vm/compiler/backend/il_x64.cc b/runtime/vm/compiler/backend/il_x64.cc
index 0cc53b2..82e780a 100644
--- a/runtime/vm/compiler/backend/il_x64.cc
+++ b/runtime/vm/compiler/backend/il_x64.cc
@@ -2946,13 +2946,12 @@
     }
     __ pushq(locs->in(0).reg());
     __ pushq(locs->in(1).reg());
-    const String& selector =
-        String::Handle(instruction()->call()->ic_data()->target_name());
-    const Array& arguments_descriptor =
-        Array::Handle(instruction()->call()->ic_data()->arguments_descriptor());
+    const auto& selector = String::Handle(instruction()->call()->Selector());
+    const auto& arguments_descriptor = Array::Handle(
+        ArgumentsDescriptor::New(/*type_args_len=*/0, /*num_arguments=*/2));
     compiler->EmitMegamorphicInstanceCall(
         selector, arguments_descriptor, instruction()->call()->deopt_id(),
-        instruction()->call()->token_pos(), locs, try_index_, kNumSlowPathArgs);
+        instruction()->token_pos(), locs, try_index_, kNumSlowPathArgs);
     __ MoveRegister(result, RAX);
     compiler->RestoreLiveRegisters(locs);
     __ jmp(exit_label());
@@ -3113,13 +3112,14 @@
     }
     __ pushq(locs->in(0).reg());
     __ pushq(locs->in(1).reg());
-    String& selector =
-        String::Handle(instruction()->call()->ic_data()->target_name());
-    const Array& arguments_descriptor =
-        Array::Handle(instruction()->call()->ic_data()->arguments_descriptor());
+
+    const auto& selector = String::Handle(instruction()->call()->Selector());
+    const auto& arguments_descriptor = Array::Handle(
+        ArgumentsDescriptor::New(/*type_args_len=*/0, /*num_arguments=*/2));
+
     compiler->EmitMegamorphicInstanceCall(
         selector, arguments_descriptor, instruction()->call()->deopt_id(),
-        instruction()->call()->token_pos(), locs, try_index_, kNumSlowPathArgs);
+        instruction()->token_pos(), locs, try_index_, kNumSlowPathArgs);
     __ MoveRegister(result, RAX);
     compiler->RestoreLiveRegisters(locs);
     compiler->pending_deoptimization_env_ = NULL;
diff --git a/runtime/vm/compiler/backend/type_propagator.cc b/runtime/vm/compiler/backend/type_propagator.cc
index 37cf267..47537ee 100644
--- a/runtime/vm/compiler/backend/type_propagator.cc
+++ b/runtime/vm/compiler/backend/type_propagator.cc
@@ -651,6 +651,10 @@
   return FromAbstractType(Type::ZoneHandle(Type::IntType()), kNonNullable);
 }
 
+CompileType CompileType::NullableInt() {
+  return FromAbstractType(Type::ZoneHandle(Type::IntType()), kNullable);
+}
+
 CompileType CompileType::Smi() {
   return Create(kSmiCid, Type::ZoneHandle(Type::SmiType()));
 }
@@ -659,6 +663,10 @@
   return Create(kDoubleCid, Type::ZoneHandle(Type::Double()));
 }
 
+CompileType CompileType::NullableDouble() {
+  return FromAbstractType(Type::ZoneHandle(Type::Double()), kNullable);
+}
+
 CompileType CompileType::String() {
   return FromAbstractType(Type::ZoneHandle(Type::StringType()), kNonNullable);
 }