[vm] Specialize allocation for small records

This change introduces specialized stubs and IL instruction for
allocating records with 2 or 3 fields. This makes allocation of
small records slightly faster compared to a construction of similar
class instances and makes code size of record allocation smaller.

Benchmark:
MultipleReturns.NotInlined.Record(RunTime) 77150 -> 66222
MultipleReturns.NotInlined.RecordNamed(RunTime) 78073 -> 67044
MultipleReturns.Forwarded.Record(RunTime) 97130 -> 77635
MultipleReturns.Forwarded.RecordNamed(RunTime) 96495 -> 77904

TEST=ci

Issue: https://github.com/dart-lang/sdk/issues/49719
Change-Id: I8ed7add06b39ba79dfd78bbe2afaefe606cc505b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/266420
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
This commit is contained in:
Alexander Markov 2022-11-03 15:06:35 +00:00 committed by Commit Queue
parent e417e3c3f7
commit b9dfd1a651
21 changed files with 3528 additions and 3144 deletions

View file

@ -942,6 +942,11 @@ void ConstantPropagator::VisitAllocateRecord(AllocateRecordInstr* instr) {
SetValue(instr, non_constant_);
}
void ConstantPropagator::VisitAllocateSmallRecord(
AllocateSmallRecordInstr* instr) {
SetValue(instr, non_constant_);
}
void ConstantPropagator::VisitLoadUntagged(LoadUntaggedInstr* instr) {
SetValue(instr, non_constant_);
}

View file

@ -7679,6 +7679,68 @@ void AllocateRecordInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
locs(), deopt_id(), env());
}
LocationSummary* AllocateSmallRecordInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
ASSERT(num_fields() == 2 || num_fields() == 3);
const intptr_t kNumInputs = InputCount();
const intptr_t kNumTemps = 0;
LocationSummary* locs = new (zone)
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kCall);
if (has_named_fields()) {
locs->set_in(
0, Location::RegisterLocation(AllocateSmallRecordABI::kFieldNamesReg));
locs->set_in(
1, Location::RegisterLocation(AllocateSmallRecordABI::kValue0Reg));
locs->set_in(
2, Location::RegisterLocation(AllocateSmallRecordABI::kValue1Reg));
if (num_fields() > 2) {
locs->set_in(
3, Location::RegisterLocation(AllocateSmallRecordABI::kValue2Reg));
}
} else {
locs->set_in(
0, Location::RegisterLocation(AllocateSmallRecordABI::kValue0Reg));
locs->set_in(
1, Location::RegisterLocation(AllocateSmallRecordABI::kValue1Reg));
if (num_fields() > 2) {
locs->set_in(
2, Location::RegisterLocation(AllocateSmallRecordABI::kValue2Reg));
}
}
locs->set_out(0, Location::RegisterLocation(AllocateRecordABI::kResultReg));
return locs;
}
void AllocateSmallRecordInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
auto object_store = compiler->isolate_group()->object_store();
Code& stub = Code::ZoneHandle(compiler->zone());
if (has_named_fields()) {
switch (num_fields()) {
case 2:
stub = object_store->allocate_record2_named_stub();
break;
case 3:
stub = object_store->allocate_record3_named_stub();
break;
default:
UNREACHABLE();
}
} else {
switch (num_fields()) {
case 2:
stub = object_store->allocate_record2_stub();
break;
case 3:
stub = object_store->allocate_record3_stub();
break;
default:
UNREACHABLE();
}
}
compiler->GenerateStubCall(source(), stub, UntaggedPcDescriptors::kOther,
locs(), deopt_id(), env());
}
#undef __
} // namespace dart

View file

@ -458,6 +458,7 @@ struct InstrAttrs {
M(AllocateObject, _) \
M(AllocateClosure, _) \
M(AllocateRecord, _) \
M(AllocateSmallRecord, _) \
M(AllocateTypedData, _) \
M(LoadField, _) \
M(LoadUntagged, kNoGC) \
@ -7026,6 +7027,82 @@ class AllocateRecordInstr : public TemplateAllocation<1> {
DISALLOW_COPY_AND_ASSIGN(AllocateRecordInstr);
};
// Allocates and initializes fields of a small record object
// (with 2 or 3 fields).
class AllocateSmallRecordInstr : public TemplateAllocation<4> {
public:
AllocateSmallRecordInstr(const InstructionSource& source,
intptr_t num_fields, // 2 or 3.
Value* field_names, // Optional.
Value* value0,
Value* value1,
Value* value2, // Optional.
intptr_t deopt_id)
: TemplateAllocation(source, deopt_id),
num_fields_(num_fields),
has_named_fields_(field_names != nullptr) {
ASSERT(num_fields == 2 || num_fields == 3);
ASSERT((num_fields > 2) == (value2 != nullptr));
if (has_named_fields_) {
SetInputAt(0, field_names);
SetInputAt(1, value0);
SetInputAt(2, value1);
if (num_fields > 2) {
SetInputAt(3, value2);
}
} else {
SetInputAt(0, value0);
SetInputAt(1, value1);
if (num_fields > 2) {
SetInputAt(2, value2);
}
}
}
DECLARE_INSTRUCTION(AllocateSmallRecord)
virtual CompileType ComputeType() const;
intptr_t num_fields() const { return num_fields_; }
bool has_named_fields() const { return has_named_fields_; }
virtual intptr_t InputCount() const {
return (has_named_fields_ ? 1 : 0) + num_fields_;
}
virtual const Slot* SlotForInput(intptr_t pos) {
if (has_named_fields_) {
if (pos == 0) {
return &Slot::Record_field_names();
} else {
return &Slot::GetRecordFieldSlot(
Thread::Current(), compiler::target::Record::field_offset(pos - 1));
}
} else {
return &Slot::GetRecordFieldSlot(
Thread::Current(), compiler::target::Record::field_offset(pos));
}
}
virtual bool HasUnknownSideEffects() const { return false; }
virtual bool WillAllocateNewOrRemembered() const {
return Heap::IsAllocatableInNewSpace(
compiler::target::Record::InstanceSize(num_fields_));
}
#define FIELD_LIST(F) \
F(const intptr_t, num_fields_) \
F(const bool, has_named_fields_)
DECLARE_INSTRUCTION_SERIALIZABLE_FIELDS(AllocateSmallRecordInstr,
TemplateAllocation,
FIELD_LIST)
#undef FIELD_LIST
private:
DISALLOW_COPY_AND_ASSIGN(AllocateSmallRecordInstr);
};
// This instruction captures the state of the object which had its allocation
// removed during the AllocationSinking pass.
// It does not produce any real code only deoptimization information.

View file

@ -3859,6 +3859,10 @@ void AllocationSinking::CreateMaterializationAt(
cls = &Class::ZoneHandle(
flow_graph_->isolate_group()->class_table()->At(kRecordCid));
num_elements = instr->num_fields();
} else if (auto instr = alloc->AsAllocateSmallRecord()) {
cls = &Class::ZoneHandle(
flow_graph_->isolate_group()->class_table()->At(kRecordCid));
num_elements = instr->num_fields();
} else {
UNREACHABLE();
}

View file

@ -1653,6 +1653,10 @@ CompileType AllocateRecordInstr::ComputeType() const {
return CompileType::FromCid(kRecordCid);
}
CompileType AllocateSmallRecordInstr::ComputeType() const {
return CompileType::FromCid(kRecordCid);
}
CompileType LoadUntaggedInstr::ComputeType() const {
return CompileType::Dynamic();
}

View file

@ -948,6 +948,21 @@ Fragment BaseFlowGraphBuilder::AllocateRecord(TokenPosition position,
return Fragment(allocate);
}
Fragment BaseFlowGraphBuilder::AllocateSmallRecord(TokenPosition position,
intptr_t num_fields,
bool has_named_fields) {
ASSERT(num_fields == 2 || num_fields == 3);
Value* value2 = (num_fields > 2) ? Pop() : nullptr;
Value* value1 = Pop();
Value* value0 = Pop();
Value* field_names = has_named_fields ? Pop() : nullptr;
AllocateSmallRecordInstr* allocate = new (Z) AllocateSmallRecordInstr(
InstructionSource(position), num_fields, field_names, value0, value1,
value2, GetNextDeoptId());
Push(allocate);
return Fragment(allocate);
}
Fragment BaseFlowGraphBuilder::AllocateTypedData(TokenPosition position,
classid_t class_id) {
Value* num_elements = Pop();

View file

@ -354,6 +354,9 @@ class BaseFlowGraphBuilder {
Fragment AllocateClosure(TokenPosition position = TokenPosition::kNoSource);
Fragment CreateArray();
Fragment AllocateRecord(TokenPosition position, intptr_t num_fields);
Fragment AllocateSmallRecord(TokenPosition position,
intptr_t num_fields,
bool has_named_fields);
Fragment AllocateTypedData(TokenPosition position, classid_t class_id);
Fragment InstantiateType(const AbstractType& type);
Fragment InstantiateTypeArguments(const TypeArguments& type_arguments);

View file

@ -4061,11 +4061,31 @@ Fragment StreamingFlowGraphBuilder::BuildRecordLiteral(TokenPosition* p) {
}
}
const intptr_t num_fields = positional_count + named_count;
// TODO(dartbug.com/49719): provide specialized allocation stubs for small
// records.
Fragment instructions;
if (num_fields == 2 ||
(num_fields == 3 && AllocateSmallRecordABI::kValue2Reg != kNoRegister)) {
// Generate specialized allocation for a small number of fields.
const bool has_named_fields = named_count > 0;
if (has_named_fields) {
instructions += Constant(*field_names);
}
for (intptr_t i = 0; i < positional_count; ++i) {
instructions += BuildExpression(); // read ith expression.
}
ReadListLength(); // read list length.
for (intptr_t i = 0; i < named_count; ++i) {
SkipStringReference(); // read ith name.
instructions += BuildExpression(); // read ith expression.
}
SkipDartType(); // read recordType.
instructions +=
B->AllocateSmallRecord(position, num_fields, has_named_fields);
return instructions;
}
instructions += Constant(*field_names);
instructions += B->AllocateRecord(position, num_fields);
LocalVariable* record = MakeTemporary();

File diff suppressed because it is too large Load diff

View file

@ -1156,6 +1156,104 @@ void StubCodeCompiler::GenerateAllocateRecordStub(Assembler* assembler) {
__ Ret();
}
void StubCodeCompiler::GenerateAllocateSmallRecordStub(Assembler* assembler,
intptr_t num_fields,
bool has_named_fields) {
ASSERT(num_fields == 2 || num_fields == 3);
const Register result_reg = AllocateSmallRecordABI::kResultReg;
const Register field_names_reg = AllocateSmallRecordABI::kFieldNamesReg;
const Register value0_reg = AllocateSmallRecordABI::kValue0Reg;
const Register value1_reg = AllocateSmallRecordABI::kValue1Reg;
const Register value2_reg = AllocateSmallRecordABI::kValue2Reg;
const Register temp_reg = AllocateSmallRecordABI::kTempReg;
Label slow_case;
if ((num_fields > 2) && (value2_reg == kNoRegister)) {
// Not implemented.
__ Breakpoint();
return;
}
#if defined(DEBUG)
// Need to account for the debug checks added by
// StoreCompressedIntoObjectNoBarrier.
const auto distance = Assembler::kFarJump;
#else
const auto distance = Assembler::kNearJump;
#endif
__ TryAllocateObject(kRecordCid, target::Record::InstanceSize(num_fields),
&slow_case, distance, result_reg, temp_reg);
__ LoadImmediate(temp_reg, num_fields);
__ StoreToOffset(
temp_reg, FieldAddress(result_reg, target::Record::num_fields_offset()),
kFourBytes);
if (!has_named_fields) {
__ LoadObject(field_names_reg, Object::empty_array());
}
__ StoreCompressedIntoObjectNoBarrier(
result_reg,
FieldAddress(result_reg, target::Record::field_names_offset()),
field_names_reg);
__ StoreCompressedIntoObjectNoBarrier(
result_reg, FieldAddress(result_reg, target::Record::field_offset(0)),
value0_reg);
__ StoreCompressedIntoObjectNoBarrier(
result_reg, FieldAddress(result_reg, target::Record::field_offset(1)),
value1_reg);
if (num_fields > 2) {
__ StoreCompressedIntoObjectNoBarrier(
result_reg, FieldAddress(result_reg, target::Record::field_offset(2)),
value2_reg);
}
__ Ret();
__ Bind(&slow_case);
__ EnterStubFrame();
__ PushObject(NullObject()); // Space on the stack for the return value.
__ PushObject(Smi::ZoneHandle(Smi::New(num_fields)));
if (has_named_fields) {
__ PushRegister(field_names_reg);
} else {
__ PushObject(Object::empty_array());
}
__ PushRegistersInOrder({value0_reg, value1_reg});
if (num_fields > 2) {
__ PushRegister(value2_reg);
} else {
__ PushObject(NullObject());
}
__ CallRuntime(kAllocateSmallRecordRuntimeEntry, 5);
__ Drop(5);
__ PopRegister(result_reg);
EnsureIsNewOrRemembered(assembler, /*preserve_registers=*/false);
__ LeaveStubFrame();
__ Ret();
}
void StubCodeCompiler::GenerateAllocateRecord2Stub(Assembler* assembler) {
GenerateAllocateSmallRecordStub(assembler, 2, /*has_named_fields=*/false);
}
void StubCodeCompiler::GenerateAllocateRecord2NamedStub(Assembler* assembler) {
GenerateAllocateSmallRecordStub(assembler, 2, /*has_named_fields=*/true);
}
void StubCodeCompiler::GenerateAllocateRecord3Stub(Assembler* assembler) {
GenerateAllocateSmallRecordStub(assembler, 3, /*has_named_fields=*/false);
}
void StubCodeCompiler::GenerateAllocateRecord3NamedStub(Assembler* assembler) {
GenerateAllocateSmallRecordStub(assembler, 3, /*has_named_fields=*/true);
}
// The UnhandledException class lives in the VM isolate, so it cannot cache
// an allocation stub for itself. Instead, we cache it in the stub code list.
void StubCodeCompiler::GenerateAllocateUnhandledExceptionStub(

View file

@ -175,6 +175,10 @@ class StubCodeCompiler : public AllStatic {
static void GenerateAllocateTypedDataArrayStub(Assembler* assembler,
intptr_t cid);
static void GenerateAllocateSmallRecordStub(Assembler* assembler,
intptr_t num_fields,
bool has_named_fields);
static void GenerateSharedStubGeneric(
Assembler* assembler,
bool save_fpu_registers,

View file

@ -523,6 +523,17 @@ struct AllocateRecordABI {
static const Register kTemp2Reg = R4;
};
// ABI for AllocateSmallRecordStub (AllocateRecord2, AllocateRecord2Named,
// AllocateRecord3, AllocateRecord3Named).
struct AllocateSmallRecordABI {
static const Register kResultReg = AllocateObjectABI::kResultReg;
static const Register kFieldNamesReg = R1;
static const Register kValue0Reg = R2;
static const Register kValue1Reg = R3;
static const Register kValue2Reg = R4;
static const Register kTempReg = R9;
};
// ABI for AllocateTypedDataArrayStub.
struct AllocateTypedDataArrayABI {
static const Register kResultReg = AllocateObjectABI::kResultReg;

View file

@ -357,6 +357,17 @@ struct AllocateRecordABI {
static const Register kTemp2Reg = R4;
};
// ABI for AllocateSmallRecordStub (AllocateRecord2, AllocateRecord2Named,
// AllocateRecord3, AllocateRecord3Named).
struct AllocateSmallRecordABI {
static const Register kResultReg = AllocateObjectABI::kResultReg;
static const Register kFieldNamesReg = R1;
static const Register kValue0Reg = R2;
static const Register kValue1Reg = R3;
static const Register kValue2Reg = R4;
static const Register kTempReg = R5;
};
// ABI for AllocateTypedDataArrayStub.
struct AllocateTypedDataArrayABI {
static const Register kResultReg = AllocateObjectABI::kResultReg;

View file

@ -253,6 +253,17 @@ struct AllocateRecordABI {
static const Register kTemp2Reg = EDI;
};
// ABI for AllocateSmallRecordStub (AllocateRecord2, AllocateRecord2Named,
// AllocateRecord3, AllocateRecord3Named).
struct AllocateSmallRecordABI {
static const Register kResultReg = AllocateObjectABI::kResultReg;
static const Register kFieldNamesReg = EBX;
static const Register kValue0Reg = ECX;
static const Register kValue1Reg = EDX;
static const Register kValue2Reg = kNoRegister;
static const Register kTempReg = EDI;
};
// ABI for AllocateTypedDataArrayStub.
struct AllocateTypedDataArrayABI {
static const Register kResultReg = AllocateObjectABI::kResultReg;

View file

@ -366,6 +366,17 @@ struct AllocateRecordABI {
static const Register kTemp2Reg = T4;
};
// ABI for AllocateSmallRecordStub (AllocateRecord2, AllocateRecord2Named,
// AllocateRecord3, AllocateRecord3Named).
struct AllocateSmallRecordABI {
static const Register kResultReg = AllocateObjectABI::kResultReg;
static const Register kFieldNamesReg = T2;
static const Register kValue0Reg = T3;
static const Register kValue1Reg = T4;
static const Register kValue2Reg = A1;
static const Register kTempReg = T1;
};
// ABI for AllocateTypedDataArrayStub.
struct AllocateTypedDataArrayABI {
static constexpr Register kResultReg = AllocateObjectABI::kResultReg;

View file

@ -328,6 +328,17 @@ struct AllocateRecordABI {
static const Register kTemp2Reg = RCX;
};
// ABI for AllocateSmallRecordStub (AllocateRecord2, AllocateRecord2Named,
// AllocateRecord3, AllocateRecord3Named).
struct AllocateSmallRecordABI {
static const Register kResultReg = AllocateObjectABI::kResultReg;
static const Register kFieldNamesReg = R10;
static const Register kValue0Reg = RBX;
static const Register kValue1Reg = RDX;
static const Register kValue2Reg = RCX;
static const Register kTempReg = RDI;
};
// ABI for AllocateTypedDataArrayStub.
struct AllocateTypedDataArrayABI {
static const Register kResultReg = AllocateObjectABI::kResultReg;

View file

@ -242,6 +242,10 @@ class ObjectPointerVisitor;
RW(Code, allocate_object_stub) \
RW(Code, allocate_object_parametrized_stub) \
RW(Code, allocate_record_stub) \
RW(Code, allocate_record2_stub) \
RW(Code, allocate_record2_named_stub) \
RW(Code, allocate_record3_stub) \
RW(Code, allocate_record3_named_stub) \
RW(Code, allocate_unhandled_exception_stub) \
RW(Code, clone_context_stub) \
RW(Code, write_barrier_wrappers_stub) \
@ -334,6 +338,10 @@ class ObjectPointerVisitor;
DO(allocate_object_stub, AllocateObject) \
DO(allocate_object_parametrized_stub, AllocateObjectParameterized) \
DO(allocate_record_stub, AllocateRecord) \
DO(allocate_record2_stub, AllocateRecord2) \
DO(allocate_record2_named_stub, AllocateRecord2Named) \
DO(allocate_record3_stub, AllocateRecord3) \
DO(allocate_record3_named_stub, AllocateRecord3Named) \
DO(allocate_unhandled_exception_stub, AllocateUnhandledException) \
DO(clone_context_stub, CloneContext) \
DO(call_closure_no_such_method_stub, CallClosureNoSuchMethod) \

View file

@ -733,6 +733,29 @@ DEFINE_RUNTIME_ENTRY(AllocateRecord, 2) {
arguments.SetReturn(record);
}
// Allocate a new small record instance and initialize its fields.
// Arg0: number of fields.
// Arg1: field names.
// Arg2-Arg4: field values.
// Return value: newly allocated record.
DEFINE_RUNTIME_ENTRY(AllocateSmallRecord, 5) {
const Smi& num_fields = Smi::CheckedHandle(zone, arguments.ArgAt(0));
const auto& field_names = Array::CheckedHandle(zone, arguments.ArgAt(1));
const auto& value0 = Instance::CheckedHandle(zone, arguments.ArgAt(2));
const auto& value1 = Instance::CheckedHandle(zone, arguments.ArgAt(3));
const auto& value2 = Instance::CheckedHandle(zone, arguments.ArgAt(4));
const Record& record =
Record::Handle(zone, Record::New(num_fields.Value(), field_names,
SpaceForRuntimeAllocation()));
ASSERT(num_fields.Value() == 2 || num_fields.Value() == 3);
record.SetFieldAt(0, value0);
record.SetFieldAt(1, value1);
if (num_fields.Value() > 2) {
record.SetFieldAt(2, value2);
}
arguments.SetReturn(record);
}
// Allocate a SuspendState object.
// Arg0: frame size.
// Arg1: existing SuspendState object or function data.

View file

@ -19,6 +19,7 @@ namespace dart {
V(AllocateContext) \
V(AllocateObject) \
V(AllocateRecord) \
V(AllocateSmallRecord) \
V(AllocateSuspendState) \
V(BoxDouble) \
V(BoxFloat32x4) \

View file

@ -57,6 +57,10 @@ namespace dart {
V(AllocateObjectParameterized) \
V(AllocateObjectSlow) \
V(AllocateRecord) \
V(AllocateRecord2) \
V(AllocateRecord2Named) \
V(AllocateRecord3) \
V(AllocateRecord3Named) \
V(AllocateUnhandledException) \
V(BoxDouble) \
V(BoxFloat32x4) \

View file

@ -171,6 +171,7 @@ class Thread;
V(ObjectPtr, object_null_, Object::null(), nullptr) \
V(BoolPtr, bool_true_, Object::bool_true().ptr(), nullptr) \
V(BoolPtr, bool_false_, Object::bool_false().ptr(), nullptr) \
V(ArrayPtr, empty_array_, Object::empty_array().ptr(), nullptr) \
V(TypePtr, dynamic_type_, Type::dynamic_type().ptr(), nullptr)
// List of VM-global objects/addresses cached in each Thread object.