[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);
+}