[vm, interpreter] Support unboxed fields.

Bug: FL-208
Change-Id: Ia6d6b913ccfc3b279ae89666bfb6f494a098b102
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/96523
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Ryan Macnak <rmacnak@google.com>
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.cc b/runtime/vm/compiler/backend/flow_graph_compiler.cc
index 9313103..4d70754 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.cc
@@ -174,14 +174,13 @@
       (SupportsUnboxedDoubles() && (field.guarded_cid() == kDoubleCid)) ||
       (SupportsUnboxedSimd128() && (field.guarded_cid() == kFloat32x4Cid)) ||
       (SupportsUnboxedSimd128() && (field.guarded_cid() == kFloat64x2Cid));
-  return field.is_unboxing_candidate() && !field.is_final() &&
-         !field.is_nullable() && valid_class;
+  return field.is_unboxing_candidate() && !field.is_nullable() && valid_class;
 }
 
 bool FlowGraphCompiler::IsPotentialUnboxedField(const Field& field) {
   return field.is_unboxing_candidate() &&
          (FlowGraphCompiler::IsUnboxedField(field) ||
-          (!field.is_final() && (field.guarded_cid() == kIllegalCid)));
+          (field.guarded_cid() == kIllegalCid));
 }
 
 void FlowGraphCompiler::InitCompiler() {
diff --git a/runtime/vm/interpreter.cc b/runtime/vm/interpreter.cc
index 49a8ddc..3e897e7 100644
--- a/runtime/vm/interpreter.cc
+++ b/runtime/vm/interpreter.cc
@@ -584,8 +584,40 @@
       RawInstance* instance = reinterpret_cast<RawInstance*>(*call_base);
       RawField* field = reinterpret_cast<RawField*>(function->ptr()->data_);
       intptr_t offset_in_words = Smi::Value(field->ptr()->value_.offset_);
+      RawObject* value =
+          reinterpret_cast<RawObject**>(instance->ptr())[offset_in_words];
+
+      const bool unboxing =
+          (field->ptr()->is_nullable_ != kNullCid) &&
+          Field::UnboxingCandidateBit::decode(field->ptr()->kind_bits_);
+      classid_t guarded_cid = field->ptr()->guarded_cid_;
+      if (unboxing && (guarded_cid == kDoubleCid)) {
+        double raw_value = Double::RawCast(value)->ptr()->value_;
+        if (!AllocateDoubleBox(thread, raw_value, *pc, *FP, *SP)) {
+          *invoked = true;
+          return false;
+        }
+        value = Double::RawCast(*SP[0]);
+      } else if (unboxing && (guarded_cid == kFloat32x4Cid)) {
+        simd128_value_t raw_value;
+        raw_value.readFrom(Float32x4::RawCast(value)->ptr()->value_);
+        if (!AllocateFloat32x4Box(thread, raw_value, *pc, *FP, *SP)) {
+          *invoked = true;
+          return false;
+        }
+        value = Float32x4::RawCast(*SP[0]);
+      } else if (unboxing && (guarded_cid == kFloat64x2Cid)) {
+        simd128_value_t raw_value;
+        raw_value.readFrom(Float64x2::RawCast(value)->ptr()->value_);
+        if (!AllocateFloat64x2Box(thread, raw_value, *pc, *FP, *SP)) {
+          *invoked = true;
+          return false;
+        }
+        value = Float64x2::RawCast(*SP[0]);
+      }
+
       *SP = call_base;
-      **SP = reinterpret_cast<RawObject**>(instance->ptr())[offset_in_words];
+      **SP = value;
       *invoked = true;
       return true;
     }
@@ -669,9 +701,39 @@
         instance = reinterpret_cast<RawInstance*>(call_base[0]);
         value = call_base[1];
       }
-      instance->StorePointer(
-          reinterpret_cast<RawObject**>(instance->ptr()) + offset_in_words,
-          value, thread);
+
+      const bool unboxing =
+          (field->ptr()->is_nullable_ != kNullCid) &&
+          Field::UnboxingCandidateBit::decode(field->ptr()->kind_bits_);
+      classid_t guarded_cid = field->ptr()->guarded_cid_;
+      if (unboxing && (guarded_cid == kDoubleCid)) {
+        double raw_value = Double::RawCast(value)->ptr()->value_;
+        RawDouble* box =
+            *(reinterpret_cast<RawDouble**>(instance->ptr()) + offset_in_words);
+        ASSERT(box != null_value);  // Non-initializing store.
+        box->ptr()->value_ = raw_value;
+      } else if (unboxing && (guarded_cid == kFloat32x4Cid)) {
+        simd128_value_t raw_value;
+        raw_value.readFrom(Float32x4::RawCast(value)->ptr()->value_);
+        RawFloat32x4* box =
+            *(reinterpret_cast<RawFloat32x4**>(instance->ptr()) +
+              offset_in_words);
+        ASSERT(box != null_value);  // Non-initializing store.
+        raw_value.writeTo(box->ptr()->value_);
+      } else if (unboxing && (guarded_cid == kFloat64x2Cid)) {
+        simd128_value_t raw_value;
+        raw_value.readFrom(Float64x2::RawCast(value)->ptr()->value_);
+        RawFloat64x2* box =
+            *(reinterpret_cast<RawFloat64x2**>(instance->ptr()) +
+              offset_in_words);
+        ASSERT(box != null_value);  // Non-initializing store.
+        raw_value.writeTo(box->ptr()->value_);
+      } else {
+        instance->StorePointer(
+            reinterpret_cast<RawObject**>(instance->ptr()) + offset_in_words,
+            value, thread);
+      }
+
       *SP = call_base;
       **SP = null_value;
       *invoked = true;
@@ -1350,8 +1412,111 @@
     if (!InvokeRuntime(thread, this, DRT_AllocateObject, args)) {
       return false;
     }
-    *reinterpret_cast<int64_t*>(reinterpret_cast<uword>(SP[0]) -
-                                kHeapObjectTag + Mint::value_offset()) = value;
+    reinterpret_cast<RawMint*>(SP[0])->ptr()->value_ = value;
+    return true;
+  }
+}
+
+// Allocate _Double box for the given double value and puts it into SP[0].
+// Returns false on exception.
+DART_NOINLINE bool Interpreter::AllocateDoubleBox(Thread* thread,
+                                                  double value,
+                                                  uint32_t* pc,
+                                                  RawObject** FP,
+                                                  RawObject** SP) {
+  const intptr_t instance_size = Double::InstanceSize();
+  const uword start =
+      thread->heap()->new_space()->TryAllocateInTLAB(thread, instance_size);
+  if (LIKELY(start != 0)) {
+    uword tags = 0;
+    tags = RawObject::ClassIdTag::update(kDoubleCid, tags);
+    tags = RawObject::SizeTag::update(instance_size, tags);
+    tags = RawObject::NewBit::update(true, tags);
+    // Also writes zero in the hash_ field.
+    *reinterpret_cast<uword*>(start + Double::tags_offset()) = tags;
+    *reinterpret_cast<double*>(start + Double::value_offset()) = value;
+    SP[0] = reinterpret_cast<RawObject*>(start + kHeapObjectTag);
+    return true;
+  } else {
+    SP[0] = 0;  // Space for the result.
+    SP[1] = thread->isolate()->object_store()->double_class();  // Class object.
+    SP[2] = Object::null();  // Type arguments.
+    Exit(thread, FP, SP + 3, pc);
+    NativeArguments args(thread, 2, SP + 1, SP);
+    if (!InvokeRuntime(thread, this, DRT_AllocateObject, args)) {
+      return false;
+    }
+    reinterpret_cast<RawDouble*>(SP[0])->ptr()->value_ = value;
+    return true;
+  }
+}
+
+// Allocate _Float32x4 box for the given simd value and puts it into SP[0].
+// Returns false on exception.
+DART_NOINLINE bool Interpreter::AllocateFloat32x4Box(Thread* thread,
+                                                     simd128_value_t value,
+                                                     uint32_t* pc,
+                                                     RawObject** FP,
+                                                     RawObject** SP) {
+  const intptr_t instance_size = Float32x4::InstanceSize();
+  const uword start =
+      thread->heap()->new_space()->TryAllocateInTLAB(thread, instance_size);
+  if (LIKELY(start != 0)) {
+    uword tags = 0;
+    tags = RawObject::ClassIdTag::update(kFloat32x4Cid, tags);
+    tags = RawObject::SizeTag::update(instance_size, tags);
+    tags = RawObject::NewBit::update(true, tags);
+    // Also writes zero in the hash_ field.
+    *reinterpret_cast<uword*>(start + Float32x4::tags_offset()) = tags;
+    SP[0] = reinterpret_cast<RawObject*>(start + kHeapObjectTag);
+    value.writeTo(reinterpret_cast<RawFloat32x4*>(SP[0])->ptr()->value_);
+    return true;
+  } else {
+    SP[0] = 0;  // Space for the result.
+    SP[1] =
+        thread->isolate()->object_store()->float32x4_class();  // Class object.
+    SP[2] = Object::null();  // Type arguments.
+    Exit(thread, FP, SP + 3, pc);
+    NativeArguments args(thread, 2, SP + 1, SP);
+    if (!InvokeRuntime(thread, this, DRT_AllocateObject, args)) {
+      return false;
+    }
+    value.writeTo(reinterpret_cast<RawFloat32x4*>(SP[0])->ptr()->value_);
+    return true;
+  }
+}
+
+// Allocate _Float64x2 box for the given simd value and puts it into SP[0].
+// Returns false on exception.
+DART_NOINLINE bool Interpreter::AllocateFloat64x2Box(Thread* thread,
+                                                     simd128_value_t value,
+                                                     uint32_t* pc,
+                                                     RawObject** FP,
+                                                     RawObject** SP) {
+  const intptr_t instance_size = Float64x2::InstanceSize();
+  const uword start =
+      thread->heap()->new_space()->TryAllocateInTLAB(thread, instance_size);
+  if (LIKELY(start != 0)) {
+    uword tags = 0;
+    tags = RawObject::ClassIdTag::update(kFloat64x2Cid, tags);
+    tags = RawObject::SizeTag::update(instance_size, tags);
+    tags = RawObject::NewBit::update(true, tags);
+    // Also writes zero in the hash_ field.
+    *reinterpret_cast<uword*>(start + Float64x2::tags_offset()) = tags;
+    SP[0] = reinterpret_cast<RawObject*>(start + kHeapObjectTag);
+    value.writeTo(reinterpret_cast<RawFloat64x2*>(SP[0])->ptr()->value_);
+    return true;
+  } else {
+    SP[0] = 0;  // Space for the result.
+    SP[1] =
+        thread->isolate()->object_store()->float64x2_class();  // Class object.
+    SP[2] = Object::null();  // Type arguments.
+    Exit(thread, FP, SP + 3, pc);
+    NativeArguments args(thread, 2, SP + 1, SP);
+    if (!InvokeRuntime(thread, this, DRT_AllocateObject, args)) {
+      return false;
+    }
+    value.writeTo(reinterpret_cast<RawFloat32x4*>(SP[0])->ptr()->value_);
     return true;
   }
 }
@@ -2230,30 +2395,74 @@
     RawField* field = RAW_CAST(Field, LOAD_CONSTANT(rD + 1));
     RawInstance* instance = reinterpret_cast<RawInstance*>(SP[-1]);
     RawObject* value = reinterpret_cast<RawObject*>(SP[0]);
-    SP -= 2;  // Drop instance and value.
     intptr_t offset_in_words = Smi::Value(field->ptr()->value_.offset_);
 
     if (thread->isolate()->use_field_guards() &&
         InterpreterHelpers::FieldNeedsGuardUpdate(field, value)) {
-      SP[1] = instance;  // Preserve.
-      SP[2] = 0;         // Unused result of runtime call.
-      SP[3] = field;
-      SP[4] = value;
-      Exit(thread, FP, SP + 5, pc);
-      NativeArguments args(thread, 2, /* argv */ SP + 3, /* retval */ SP + 2);
+      SP[1] = 0;  // Unused result of runtime call.
+      SP[2] = field;
+      SP[3] = value;
+      Exit(thread, FP, SP + 4, pc);
+      NativeArguments args(thread, 2, /* argv */ SP + 2, /* retval */ SP + 1);
       if (!InvokeRuntime(thread, this, DRT_UpdateFieldCid, args)) {
         HANDLE_EXCEPTION;
       }
 
       // Reload objects after the call which may trigger GC.
-      instance = reinterpret_cast<RawInstance*>(SP[1]);
-      value = SP[4];
+      field = RAW_CAST(Field, LOAD_CONSTANT(rD + 1));
+      instance = reinterpret_cast<RawInstance*>(SP[-1]);
+      value = SP[0];
     }
 
-    instance->StorePointer(
-        reinterpret_cast<RawObject**>(instance->ptr()) + offset_in_words, value,
-        thread);
+    const bool unboxing =
+        (field->ptr()->is_nullable_ != kNullCid) &&
+        Field::UnboxingCandidateBit::decode(field->ptr()->kind_bits_);
+    classid_t guarded_cid = field->ptr()->guarded_cid_;
+    if (unboxing && (guarded_cid == kDoubleCid)) {
+      double raw_value = Double::RawCast(value)->ptr()->value_;
+      ASSERT(*(reinterpret_cast<RawDouble**>(instance->ptr()) +
+               offset_in_words) == null_value);  // Initializing store.
+      if (!AllocateDoubleBox(thread, raw_value, pc, FP, SP)) {
+        HANDLE_EXCEPTION;
+      }
+      RawDouble* box = Double::RawCast(SP[0]);
+      instance = reinterpret_cast<RawInstance*>(SP[-1]);
+      instance->StorePointer(
+          reinterpret_cast<RawDouble**>(instance->ptr()) + offset_in_words, box,
+          thread);
+    } else if (unboxing && (guarded_cid == kFloat32x4Cid)) {
+      simd128_value_t raw_value;
+      raw_value.readFrom(Float32x4::RawCast(value)->ptr()->value_);
+      ASSERT(*(reinterpret_cast<RawFloat32x4**>(instance->ptr()) +
+               offset_in_words) == null_value);  // Initializing store.
+      if (!AllocateFloat32x4Box(thread, raw_value, pc, FP, SP)) {
+        HANDLE_EXCEPTION;
+      }
+      RawFloat32x4* box = Float32x4::RawCast(SP[0]);
+      instance = reinterpret_cast<RawInstance*>(SP[-1]);
+      instance->StorePointer(
+          reinterpret_cast<RawFloat32x4**>(instance->ptr()) + offset_in_words,
+          box, thread);
+    } else if (unboxing && (guarded_cid == kFloat64x2Cid)) {
+      simd128_value_t raw_value;
+      raw_value.readFrom(Float64x2::RawCast(value)->ptr()->value_);
+      ASSERT(*(reinterpret_cast<RawFloat64x2**>(instance->ptr()) +
+               offset_in_words) == null_value);  // Initializing store.
+      if (!AllocateFloat64x2Box(thread, raw_value, pc, FP, SP)) {
+        HANDLE_EXCEPTION;
+      }
+      RawFloat64x2* box = Float64x2::RawCast(SP[0]);
+      instance = reinterpret_cast<RawInstance*>(SP[-1]);
+      instance->StorePointer(
+          reinterpret_cast<RawFloat64x2**>(instance->ptr()) + offset_in_words,
+          box, thread);
+    } else {
+      instance->StorePointer(
+          reinterpret_cast<RawObject**>(instance->ptr()) + offset_in_words,
+          value, thread);
+    }
 
+    SP -= 2;  // Drop instance and value.
     DISPATCH();
   }
 
@@ -2289,6 +2498,16 @@
 
   {
     BYTECODE(LoadFieldTOS, __D);
+#if defined(DEBUG)
+    // Currently only used to load closure fields, which are not unboxed.
+    // If used for general field, code for copying the mutable box must be
+    // added.
+    RawField* field = RAW_CAST(Field, LOAD_CONSTANT(rD + 1));
+    const bool unboxing =
+        (field->ptr()->is_nullable_ != kNullCid) &&
+        Field::UnboxingCandidateBit::decode(field->ptr()->kind_bits_);
+    ASSERT(!unboxing);
+#endif
     const uword offset_in_words =
         static_cast<uword>(Smi::Value(RAW_CAST(Smi, LOAD_CONSTANT(rD))));
     RawInstance* instance = static_cast<RawInstance*>(SP[0]);
diff --git a/runtime/vm/interpreter.h b/runtime/vm/interpreter.h
index 4e98f3e..effc48a 100644
--- a/runtime/vm/interpreter.h
+++ b/runtime/vm/interpreter.h
@@ -210,6 +210,21 @@
                         uint32_t* pc,
                         RawObject** FP,
                         RawObject** SP);
+  bool AllocateDoubleBox(Thread* thread,
+                         double value,
+                         uint32_t* pc,
+                         RawObject** FP,
+                         RawObject** SP);
+  bool AllocateFloat32x4Box(Thread* thread,
+                            simd128_value_t value,
+                            uint32_t* pc,
+                            RawObject** FP,
+                            RawObject** SP);
+  bool AllocateFloat64x2Box(Thread* thread,
+                            simd128_value_t value,
+                            uint32_t* pc,
+                            RawObject** FP,
+                            RawObject** SP);
 
 #if defined(DEBUG)
   // Returns true if tracing of executed instructions is enabled.
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 1f36f88..4393b19 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -8299,7 +8299,7 @@
   result.set_token_pos(token_pos);
   result.set_end_token_pos(end_token_pos);
   result.set_has_initializer(false);
-  result.set_is_unboxing_candidate(true);
+  result.set_is_unboxing_candidate(!is_final);
   result.set_initializer_changed_after_initialization(false);
   result.set_kernel_offset(0);
   result.set_has_pragma(false);
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 4cf556e..aa1147a 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -3311,6 +3311,7 @@
                             const Object& owner,
                             TokenPosition token_pos,
                             TokenPosition end_token_pos);
+  friend class Interpreter;              // Access to bit field.
   friend class StoreInstanceFieldInstr;  // Generated code access to bit field.
 
   enum {
diff --git a/tests/language_2/issue21957_test.dart b/tests/language_2/issue21957_double_test.dart
similarity index 100%
rename from tests/language_2/issue21957_test.dart
rename to tests/language_2/issue21957_double_test.dart
diff --git a/tests/language_2/issue21957_float32x4_test.dart b/tests/language_2/issue21957_float32x4_test.dart
new file mode 100644
index 0000000..f364261
--- /dev/null
+++ b/tests/language_2/issue21957_float32x4_test.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2019, 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.
+
+// Check slow path for PotentialUnboxedStore.
+// VMOptions=--optimization_counter_threshold=-1
+
+import "dart:typed_data";
+
+main() {
+  for (int i = 0; i < 1000000; i++) {
+    new A();
+  }
+}
+
+class A {
+  var a = new Float32x4(1.0, 2.0, 3.0, 4.0);
+}
diff --git a/tests/language_2/issue21957_float64x2_test.dart b/tests/language_2/issue21957_float64x2_test.dart
new file mode 100644
index 0000000..5b51722
--- /dev/null
+++ b/tests/language_2/issue21957_float64x2_test.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2019, 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.
+
+// Check slow path for PotentialUnboxedStore.
+// VMOptions=--optimization_counter_threshold=-1
+
+import "dart:typed_data";
+
+main() {
+  for (int i = 0; i < 1000000; i++) {
+    new A();
+  }
+}
+
+class A {
+  var a = new Float64x2(1.0, 2.0);
+}