diff --git a/pkg/dart2bytecode/lib/assembler.dart b/pkg/dart2bytecode/lib/assembler.dart index 18783887c9b..ceaa0b687ea 100644 --- a/pkg/dart2bytecode/lib/assembler.dart +++ b/pkg/dart2bytecode/lib/assembler.dart @@ -755,4 +755,14 @@ class BytecodeAssembler { void emitJumpIfInitialized(Label label) { _emitJumpInstruction(Opcode.kJumpIfInitialized, label); } + + @pragma('vm:prefer-inline') + void emitAllocateRecord(int rd) { + _emitInstructionD(Opcode.kAllocateRecord, rd); + } + + @pragma('vm:prefer-inline') + void emitLoadRecordField(int rd) { + _emitInstructionD(Opcode.kLoadRecordField, rd); + } } diff --git a/pkg/dart2bytecode/lib/bytecode_generator.dart b/pkg/dart2bytecode/lib/bytecode_generator.dart index b0a1a558be6..e7fff4403d8 100644 --- a/pkg/dart2bytecode/lib/bytecode_generator.dart +++ b/pkg/dart2bytecode/lib/bytecode_generator.dart @@ -4252,17 +4252,39 @@ class BytecodeGenerator extends RecursiveVisitor { @override void visitRecordIndexGet(RecordIndexGet node) { - _unimplemented(node, 'RecordIndexGet'); + _generateNode(node.receiver); + asm.emitLoadRecordField(node.index); } @override void visitRecordNameGet(RecordNameGet node) { - _unimplemented(node, 'RecordNameGet'); + final type = node.receiverType; + final namedFields = type.named; + final name = node.name; + int fieldIndex = -1; + for (int i = 0; i < namedFields.length; ++i) { + if (namedFields[i].name == name) { + fieldIndex = type.positional.length + i; + break; + } + } + if (fieldIndex < 0) { + throw 'Unable to find record field "$name" in $type'; + } + _generateNode(node.receiver); + asm.emitLoadRecordField(fieldIndex); } @override void visitRecordLiteral(RecordLiteral node) { - _unimplemented(node, 'RecordLiteral'); + assert(!node.isConst); + for (final expr in node.positional) { + _generateNode(expr); + } + for (final expr in node.named) { + _generateNode(expr.value); + } + asm.emitAllocateRecord(cp.addType(node.recordType)); } @override diff --git a/pkg/dart2bytecode/lib/dbc.dart b/pkg/dart2bytecode/lib/dbc.dart index ef9fcef105c..d549588aa09 100644 --- a/pkg/dart2bytecode/lib/dbc.dart +++ b/pkg/dart2bytecode/lib/dbc.dart @@ -209,6 +209,12 @@ enum Opcode { kCompareDoubleLt, kCompareDoubleGe, kCompareDoubleLe, + + // Records + kAllocateRecord, + kAllocateRecord_Wide, + kLoadRecordField, + kLoadRecordField_Wide, } /// Compact variants of opcodes are always even. @@ -456,6 +462,10 @@ const Map BytecodeFormats = const { Encoding.k0, const [Operand.none, Operand.none, Operand.none]), Opcode.kCompareDoubleLe: const Format( Encoding.k0, const [Operand.none, Operand.none, Operand.none]), + Opcode.kAllocateRecord: const Format( + Encoding.kD, const [Operand.lit, Operand.none, Operand.none]), + Opcode.kLoadRecordField: const Format( + Encoding.kD, const [Operand.imm, Operand.none, Operand.none]), }; // Should match constant in runtime/vm/stack_frame_kbc.h. diff --git a/pkg/dart2bytecode/lib/object_table.dart b/pkg/dart2bytecode/lib/object_table.dart index 24d8c2e14aa..97d88f8d257 100644 --- a/pkg/dart2bytecode/lib/object_table.dart +++ b/pkg/dart2bytecode/lib/object_table.dart @@ -308,6 +308,7 @@ enum ConstTag { kString, kMap, kSet, + kRecord, } enum TypeTag { @@ -1405,6 +1406,7 @@ class _ConstObjectHandle extends ObjectHandle { } break; case ConstTag.kList: + case ConstTag.kRecord: case ConstTag.kMap: case ConstTag.kSet: { @@ -1461,6 +1463,7 @@ class _ConstObjectHandle extends ObjectHandle { reader.readPackedObject(), reader.readPackedObject()))); break; case ConstTag.kList: + case ConstTag.kRecord: case ConstTag.kMap: case ConstTag.kSet: type = reader.readPackedObject(); @@ -1503,6 +1506,7 @@ class _ConstObjectHandle extends ObjectHandle { } break; case ConstTag.kList: + case ConstTag.kRecord: case ConstTag.kMap: case ConstTag.kSet: { @@ -1579,6 +1583,7 @@ class _ConstObjectHandle extends ObjectHandle { _combineHashes(type.hashCode, mapHashCode(fieldValues)); } case ConstTag.kList: + case ConstTag.kRecord: case ConstTag.kMap: case ConstTag.kSet: { @@ -1610,6 +1615,7 @@ class _ConstObjectHandle extends ObjectHandle { case ConstTag.kInstance: return this.type == other.type && mapEquals(this.value, other.value); case ConstTag.kList: + case ConstTag.kRecord: case ConstTag.kMap: case ConstTag.kSet: return this.type == other.type && listEquals(this.value, other.value); @@ -1634,6 +1640,8 @@ class _ConstObjectHandle extends ObjectHandle { return 'const $type $value'; case ConstTag.kList: return 'const List<$type> $value'; + case ConstTag.kRecord: + return 'const Record<$type> $value'; case ConstTag.kMap: return 'const Map<$type> $value'; case ConstTag.kSet: @@ -2382,6 +2390,16 @@ class _NodeVisitor extends VisitorDefault objectTable.getHandles(node.entries), objectTable.getHandle(node.typeArgument))); + @override + ObjectHandle? visitRecordConstant(RecordConstant node) => + objectTable.getOrAddObject(new _ConstObjectHandle( + ConstTag.kRecord, + objectTable.getHandles([ + ...node.positional, + ...node.named.values, + ]), + objectTable.getHandle(node.recordType))); + @override ObjectHandle? visitMapConstant(MapConstant node) => objectTable.getOrAddObject(new _ConstObjectHandle( diff --git a/runtime/vm/bytecode_reader.cc b/runtime/vm/bytecode_reader.cc index 8dea449eddf..77c21196635 100644 --- a/runtime/vm/bytecode_reader.cc +++ b/runtime/vm/bytecode_reader.cc @@ -1064,6 +1064,7 @@ ObjectPtr BytecodeReaderHelper::ReadConstObject(intptr_t tag) { kString, kMap, kSet, + kRecord, }; switch (tag) { @@ -1221,6 +1222,20 @@ ObjectPtr BytecodeReaderHelper::ReadConstObject(intptr_t tag) { } return Canonicalize(set); } + case kRecord: { + const RecordType& record_type = + RecordType::CheckedHandle(Z, ReadObject()); + const intptr_t num_fields = reader_.ReadUInt(); + ASSERT(num_fields == record_type.NumFields()); + const RecordShape shape = record_type.shape(); + const auto& record = Record::Handle(Z, Record::New(shape)); + Object& value = Object::Handle(Z); + for (intptr_t i = 0; i < num_fields; ++i) { + value = ReadObject(); + record.SetFieldAt(i, value); + } + return Canonicalize(record); + } default: UNREACHABLE(); } @@ -1337,7 +1352,44 @@ ObjectPtr BytecodeReaderHelper::ReadType(intptr_t tag, /* has_positional_param_names = */ false, /* has_parameter_flags */ false); } - case kRecordType: + case kRecordType: { + const intptr_t num_positional = reader_.ReadUInt(); + const intptr_t num_named = reader_.ReadUInt(); + + const intptr_t num_fields = num_positional + num_named; + const Array& field_types = + Array::Handle(Z, Array::New(num_fields, Heap::kOld)); + const Array& field_names = + (num_named == 0) + ? Object::empty_array() + : Array::Handle(Z, Array::New(num_named, Heap::kOld)); + AbstractType& type = AbstractType::Handle(Z); + + intptr_t pos = 0; + for (intptr_t i = 0; i < num_positional; ++i) { + type ^= ReadObject(); + field_types.SetAt(pos++, type); + } + + if (num_named > 0) { + String& name = String::Handle(Z); + for (intptr_t i = 0; i < num_named; ++i) { + name ^= ReadObject(); + field_names.SetAt(i, name); + type ^= ReadObject(); + field_types.SetAt(pos++, type); + } + field_names.MakeImmutable(); + } + + const RecordShape shape = + RecordShape::Register(thread_, num_fields, field_names); + + type = RecordType::New(shape, field_types, nullability); + type.SetIsFinalized(); + return type.Canonicalize(thread_); + } + UNIMPLEMENTED(); default: UNREACHABLE(); diff --git a/runtime/vm/constants_kbc.h b/runtime/vm/constants_kbc.h index d520558d12f..81ff2ab1494 100644 --- a/runtime/vm/constants_kbc.h +++ b/runtime/vm/constants_kbc.h @@ -638,6 +638,10 @@ namespace dart { V(CompareDoubleLt, 0, ORDN, ___, ___, ___) \ V(CompareDoubleGe, 0, ORDN, ___, ___, ___) \ V(CompareDoubleLe, 0, ORDN, ___, ___, ___) \ + V(AllocateRecord, D, ORDN, lit, ___, ___) \ + V(AllocateRecord_Wide, D, WIDE, lit, ___, ___) \ + V(LoadRecordField, D, ORDN, num, ___, ___) \ + V(LoadRecordField_Wide, D, WIDE, num, ___, ___) \ // These bytecodes are only generated within the VM. Reassigning their // opcodes is not a breaking change. diff --git a/runtime/vm/interpreter.cc b/runtime/vm/interpreter.cc index 8bec376b10e..093208a0e57 100644 --- a/runtime/vm/interpreter.cc +++ b/runtime/vm/interpreter.cc @@ -1413,6 +1413,33 @@ bool Interpreter::AllocateArray(Thread* thread, return InvokeRuntime(thread, this, DRT_AllocateArray, args); } +// Allocate a Record with the given shape and put it into SP[0]. +// Returns false on exception. +bool Interpreter::AllocateRecord(Thread* thread, + RecordShape shape, + const KBCInstr* pc, + ObjectPtr* FP, + ObjectPtr* SP) { + const intptr_t num_fields = shape.num_fields(); + RecordPtr result; + if (TryAllocate(thread, kRecordCid, Record::InstanceSize(num_fields), + reinterpret_cast(&result))) { + result->untag()->set_shape(shape.AsSmi()); + ObjectPtr null_value = Object::null(); + for (intptr_t i = 0; i < num_fields; i++) { + result->untag()->set_field(i, null_value, thread); + } + SP[0] = result; + return true; + } else { + SP[0] = 0; // Space for the result. + SP[1] = shape.AsSmi(); + Exit(thread, FP, SP + 2, pc); + NativeArguments args(thread, 1, SP + 1, SP); + return InvokeRuntime(thread, this, DRT_AllocateRecord, args); + } +} + // Allocate a _Context with the given length and put it into SP[0]. // Returns false on exception. bool Interpreter::AllocateContext(Thread* thread, @@ -2293,6 +2320,14 @@ SwitchDispatch: DISPATCH(); } + { + BYTECODE(LoadRecordField, D); + const intptr_t field_index = rD; + RecordPtr record = Record::RawCast(SP[0]); + SP[0] = record->untag()->field(field_index); + DISPATCH(); + } + { BYTECODE(AllocateContext, A_E); ++SP; @@ -2411,6 +2446,24 @@ SwitchDispatch: DISPATCH(); } + { + BYTECODE(AllocateRecord, D); + RecordTypePtr type = RecordType::RawCast(LOAD_CONSTANT(rD)); + RecordShape shape(Smi::RawCast(type->untag()->shape())); + ++SP; + if (!AllocateRecord(thread, shape, pc, FP, SP)) { + HANDLE_EXCEPTION; + } + RecordPtr record = Record::RawCast(SP[0]); + const intptr_t num_fields = shape.num_fields(); + for (intptr_t i = 0; i < num_fields; ++i) { + record->untag()->set_field(i, SP[-num_fields + i], thread); + } + SP -= num_fields; + SP[0] = record; + DISPATCH(); + } + { BYTECODE(AssertAssignable, A_E); // Stack: instance, type, instantiator type args, function type args, name diff --git a/runtime/vm/interpreter.h b/runtime/vm/interpreter.h index f5affedbe60..d0830e450e3 100644 --- a/runtime/vm/interpreter.h +++ b/runtime/vm/interpreter.h @@ -10,6 +10,7 @@ #include "vm/compiler/method_recognizer.h" #include "vm/constants_kbc.h" +#include "vm/object.h" #include "vm/tagged_pointer.h" namespace dart { @@ -229,6 +230,11 @@ class Interpreter { const KBCInstr* pc, ObjectPtr* FP, ObjectPtr* SP); + bool AllocateRecord(Thread* thread, + RecordShape shape, + const KBCInstr* pc, + ObjectPtr* FP, + ObjectPtr* SP); bool AllocateContext(Thread* thread, intptr_t num_variables, const KBCInstr* pc,