[vm/bytecode] Support exactness tracking in bytecode

MicroClosureCreateTearoffClassSecondTime(RunTime) 3922 us -> 2030 us.

Issue: https://github.com/dart-lang/sdk/issues/36429
Change-Id: I3a6f93950a469969d82243fd1744d5a4f7fb1e78
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/111868
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Alexander Markov 2019-08-03 17:36:31 +00:00 committed by commit-bot@chromium.org
parent df606cc566
commit ac45ee7e63
13 changed files with 217 additions and 43 deletions

View file

@ -381,6 +381,11 @@ class BytecodeAssembler {
_emitInstructionDF(Opcode.kInterfaceCall, rd, rf);
}
void emitInstantiatedInterfaceCall(int rd, int rf) {
emitSourcePosition();
_emitInstructionDF(Opcode.kInstantiatedInterfaceCall, rd, rf);
}
void emitUncheckedClosureCall(int rd, int rf) {
emitSourcePosition();
_emitInstructionDF(Opcode.kUncheckedClosureCall, rd, rf);

View file

@ -112,6 +112,14 @@ type ConstantInterfaceCall extends ConstantPoolEntry {
PackedObject argDesc;
}
// Occupies 3 entries in the constant pool.
type ConstantInstantiatedInterfaceCall extends ConstantPoolEntry {
Byte tag = 30;
PackedObject target;
PackedObject argDesc;
PackedObject staticReceiverType;
}
*/
enum ConstantTag {
@ -145,6 +153,7 @@ enum ConstantTag {
kObjectRef,
kDirectCall,
kInterfaceCall,
kInstantiatedInterfaceCall,
}
String constantTagToString(ConstantTag tag) =>
@ -199,6 +208,8 @@ abstract class ConstantPoolEntry {
return new ConstantDirectCall.read(reader);
case ConstantTag.kInterfaceCall:
return new ConstantInterfaceCall.read(reader);
case ConstantTag.kInstantiatedInterfaceCall:
return new ConstantInstantiatedInterfaceCall.read(reader);
// Make analyzer happy.
case ConstantTag.kUnused1:
case ConstantTag.kUnused2:
@ -639,6 +650,49 @@ class ConstantInterfaceCall extends ConstantPoolEntry {
this.argDesc == other.argDesc;
}
class ConstantInstantiatedInterfaceCall extends ConstantPoolEntry {
final ObjectHandle target;
final ObjectHandle argDesc;
final ObjectHandle staticReceiverType;
ConstantInstantiatedInterfaceCall(
this.target, this.argDesc, this.staticReceiverType);
// Reserve 2 extra slots (3 slots total).
int get numReservedEntries => 2;
@override
ConstantTag get tag => ConstantTag.kInstantiatedInterfaceCall;
@override
void writeValue(BufferedWriter writer) {
writer.writePackedObject(target);
writer.writePackedObject(argDesc);
writer.writePackedObject(staticReceiverType);
}
ConstantInstantiatedInterfaceCall.read(BufferedReader reader)
: target = reader.readPackedObject(),
argDesc = reader.readPackedObject(),
staticReceiverType = reader.readPackedObject();
@override
String toString() =>
"InstantiatedInterfaceCall '$target', $argDesc, receiver $staticReceiverType";
@override
int get hashCode => _combineHashes(
_combineHashes(target.hashCode, argDesc.hashCode),
staticReceiverType.hashCode);
@override
bool operator ==(other) =>
other is ConstantInstantiatedInterfaceCall &&
this.target == other.target &&
this.argDesc == other.argDesc &&
this.staticReceiverType == other.staticReceiverType;
}
/// Reserved constant pool entry.
class _ReservedConstantPoolEntry extends ConstantPoolEntry {
const _ReservedConstantPoolEntry();
@ -699,6 +753,15 @@ class ConstantPool {
isSetter: invocationKind == InvocationKind.setter),
argDesc));
int addInstantiatedInterfaceCall(InvocationKind invocationKind, Member target,
ObjectHandle argDesc, DartType staticReceiverType) =>
_add(new ConstantInstantiatedInterfaceCall(
objectTable.getMemberHandle(target,
isGetter: invocationKind == InvocationKind.getter,
isSetter: invocationKind == InvocationKind.setter),
argDesc,
objectTable.getHandle(staticReceiverType)));
int addInstanceCall(InvocationKind invocationKind, Member target,
Name targetName, ObjectHandle argDesc) =>
(target == null)

View file

@ -10,7 +10,7 @@ library vm.bytecode.dbc;
/// Before bumping current bytecode version format, make sure that
/// all users have switched to a VM which is able to consume new
/// version of bytecode.
const int currentBytecodeFormatVersion = 17;
const int currentBytecodeFormatVersion = 18;
enum Opcode {
kUnusedOpcode000,
@ -216,8 +216,8 @@ enum Opcode {
kInterfaceCall_Wide,
kUnused23, // Reserved for InterfaceCall1
kUnused24, // Reserved for InterfaceCall1_Wide
kUnused25, // Reserved for InterfaceCall2
kUnused26, // Reserved for InterfaceCall2_Wide
kInstantiatedInterfaceCall,
kInstantiatedInterfaceCall_Wide,
kUncheckedClosureCall,
kUncheckedClosureCall_Wide,
kUncheckedInterfaceCall,
@ -438,6 +438,8 @@ const Map<Opcode, Format> BytecodeFormats = const {
Encoding.kT, const [Operand.tgt, Operand.none, Operand.none]),
Opcode.kInterfaceCall: const Format(
Encoding.kDF, const [Operand.lit, Operand.imm, Operand.none]),
Opcode.kInstantiatedInterfaceCall: const Format(
Encoding.kDF, const [Operand.lit, Operand.imm, Operand.none]),
Opcode.kDynamicCall: const Format(
Encoding.kDF, const [Operand.lit, Operand.imm, Operand.none]),
Opcode.kNativeCall: const Format(
@ -607,6 +609,7 @@ bool isCall(Opcode opcode) {
switch (opcode) {
case Opcode.kDirectCall:
case Opcode.kInterfaceCall:
case Opcode.kInstantiatedInterfaceCall:
case Opcode.kUncheckedClosureCall:
case Opcode.kUncheckedInterfaceCall:
case Opcode.kDynamicCall:

View file

@ -39,9 +39,11 @@ import 'generics.dart'
flattenInstantiatorTypeArguments,
getDefaultFunctionTypeArguments,
getInstantiatorTypeArguments,
getStaticType,
hasFreeTypeParameters,
hasInstantiatorTypeArguments,
isAllDynamic,
isInstantiatedInterfaceCall,
isUncheckedCall,
isUncheckedClosureCall;
import 'local_variable_table.dart' show LocalVariableTable;
@ -2796,8 +2798,28 @@ class BytecodeGenerator extends RecursiveVisitor<Null> {
}
void _genInstanceCall(
int totalArgCount, int callCpIndex, bool isDynamic, bool isUnchecked,
[TreeNode context]) {
InvocationKind invocationKind,
Member interfaceTarget,
Name targetName,
Expression receiver,
int totalArgCount,
ObjectHandle argDesc) {
final isDynamic = interfaceTarget == null;
final isUnchecked = invocationKind != InvocationKind.getter &&
isUncheckedCall(interfaceTarget, receiver, typeEnvironment);
if (invocationKind != InvocationKind.getter && !isDynamic && !isUnchecked) {
final staticReceiverType = getStaticType(receiver, typeEnvironment);
if (isInstantiatedInterfaceCall(interfaceTarget, staticReceiverType)) {
final callCpIndex = cp.addInstantiatedInterfaceCall(
invocationKind, interfaceTarget, argDesc, staticReceiverType);
asm.emitInstantiatedInterfaceCall(callCpIndex, totalArgCount);
return;
}
}
final callCpIndex = cp.addInstanceCall(
invocationKind, interfaceTarget, targetName, argDesc);
if (isDynamic) {
assert(!isUnchecked);
asm.emitDynamicCall(callCpIndex, totalArgCount);
@ -2844,24 +2866,18 @@ class BytecodeGenerator extends RecursiveVisitor<Null> {
// interface target doesn't fully represent what is being called.
interfaceTarget = null;
}
final isDynamic = interfaceTarget == null;
final isUnchecked =
isUncheckedCall(interfaceTarget, node.receiver, typeEnvironment);
final argDesc =
objectTable.getArgDescHandleByArguments(args, hasReceiver: true);
final callCpIndex = cp.addInstanceCall(
InvocationKind.method, interfaceTarget, node.name, argDesc);
_genInstanceCall(totalArgCount, callCpIndex, isDynamic, isUnchecked, node);
_genInstanceCall(InvocationKind.method, interfaceTarget, node.name,
node.receiver, totalArgCount, argDesc);
}
@override
visitPropertyGet(PropertyGet node) {
_generateNode(node.receiver);
final isDynamic = node.interfaceTarget == null;
final argDesc = objectTable.getArgDescHandle(1);
final callCpIndex = cp.addInstanceCall(
InvocationKind.getter, node.interfaceTarget, node.name, argDesc);
_genInstanceCall(1, callCpIndex, isDynamic, /*isUnchecked=*/ false);
_genInstanceCall(InvocationKind.getter, node.interfaceTarget, node.name,
node.receiver, 1, argDesc);
}
@override
@ -2876,13 +2892,10 @@ class BytecodeGenerator extends RecursiveVisitor<Null> {
asm.emitStoreLocal(temp);
}
final isDynamic = node.interfaceTarget == null;
final isUnchecked =
isUncheckedCall(node.interfaceTarget, node.receiver, typeEnvironment);
final argDesc = objectTable.getArgDescHandle(2);
final callCpIndex = cp.addInstanceCall(
InvocationKind.setter, node.interfaceTarget, node.name, argDesc);
_genInstanceCall(2, callCpIndex, isDynamic, isUnchecked);
_genInstanceCall(InvocationKind.setter, node.interfaceTarget, node.name,
node.receiver, 2, argDesc);
asm.emitDrop1();
if (hasResult) {

View file

@ -94,6 +94,11 @@ bool isAllDynamic(List<DartType> typeArgs) {
return true;
}
bool isInstantiatedGenericType(DartType type) =>
(type is InterfaceType) &&
type.typeArguments.isNotEmpty &&
!hasFreeTypeParameters(type.typeArguments);
bool hasFreeTypeParameters(List<DartType> typeArgs) {
final findTypeParams = new FindFreeTypeParametersVisitor();
return typeArgs.any((t) => t.accept(findTypeParams));
@ -210,6 +215,23 @@ bool isUncheckedCall(Member interfaceTarget, Expression receiver,
return false;
}
/// If receiver type at run time matches static type we can omit argument type
/// checks. This condition can be efficiently tested if static receiver type is
/// fully instantiated (e.g. doesn't have type parameters).
/// [isInstantiatedInterfaceCall] tests if an instance call to
/// [interfaceTarget] with given [staticReceiverType] may benefit from
/// this optimization.
bool isInstantiatedInterfaceCall(
Member interfaceTarget, DartType staticReceiverType) {
// Providing instantiated receiver type wouldn't help in case of a
// dynamic call or call without any parameter type checks.
if (interfaceTarget == null ||
!_hasGenericCovariantParameters(interfaceTarget)) {
return false;
}
return isInstantiatedGenericType(staticReceiverType);
}
bool _hasGenericCovariantParameters(Member target) {
if (target is Field) {
return target.isGenericCovariantImpl;

View file

@ -1194,10 +1194,10 @@ L2:
Push r3
Push r0
StoreFieldTOS CP#5
InterfaceCall CP#17, 2
InstantiatedInterfaceCall CP#17, 2
Drop1
Push r4
AllocateClosure CP#19
AllocateClosure CP#20
StoreLocal r3
Push r3
PushNull
@ -1209,12 +1209,12 @@ L2:
PushConstant CP#12
StoreFieldTOS CP#13
Push r3
PushConstant CP#19
PushConstant CP#20
StoreFieldTOS CP#15
Push r3
Push r0
StoreFieldTOS CP#5
InterfaceCall CP#17, 2
InstantiatedInterfaceCall CP#17, 2
Drop1
Push r0
CloneContext 1, 1
@ -1257,13 +1257,14 @@ ConstantPool {
[14] = Reserved
[15] = InstanceField dart:core::_Closure::_function (field)
[16] = Reserved
[17] = InterfaceCall 'dart:core::List::add', ArgDesc num-args 2, num-type-args 0, names []
[17] = InstantiatedInterfaceCall 'dart:core::List::add', ArgDesc num-args 2, num-type-args 0, names [], receiver dart:core::List < dart:core::Function >
[18] = Reserved
[19] = ClosureFunction 1
[20] = Type dart:core::int
[21] = ObjectRef 'ii'
[22] = SubtypeTestCache
[23] = EndClosureFunctionScope
[19] = Reserved
[20] = ClosureFunction 1
[21] = Type dart:core::int
[22] = ObjectRef 'ii'
[23] = SubtypeTestCache
[24] = EndClosureFunctionScope
}
Closure #lib::C::testForLoop::'<anonymous closure>' () -> dart:core::int
ClosureCode {
@ -1290,11 +1291,11 @@ ClosureCode {
PopLocal r0
JumpIfUnchecked L1
Push FP[-5]
PushConstant CP#20
PushNull
PushNull
PushConstant CP#21
AssertAssignable 1, CP#22
PushNull
PushNull
PushConstant CP#22
AssertAssignable 1, CP#23
Drop1
L1:
Push r0

View file

@ -259,6 +259,8 @@ static intptr_t GetConstantPoolIndex(const KBCInstr* instr) {
case KernelBytecode::kDirectCall_Wide:
case KernelBytecode::kInterfaceCall:
case KernelBytecode::kInterfaceCall_Wide:
case KernelBytecode::kInstantiatedInterfaceCall:
case KernelBytecode::kInstantiatedInterfaceCall_Wide:
case KernelBytecode::kUncheckedClosureCall:
case KernelBytecode::kUncheckedClosureCall_Wide:
case KernelBytecode::kUncheckedInterfaceCall:

View file

@ -843,7 +843,8 @@ void BytecodeFlowGraphBuilder::BuildDirectCall() {
}
void BytecodeFlowGraphBuilder::BuildInterfaceCallCommon(
bool is_unchecked_call) {
bool is_unchecked_call,
bool is_instantiated_call) {
if (is_generating_interpreter()) {
UNIMPLEMENTED(); // TODO(alexmarkov): interpreter
}
@ -887,16 +888,34 @@ void BytecodeFlowGraphBuilder::BuildInterfaceCallCommon(
call->set_entry_kind(Code::EntryKind::kUnchecked);
}
if (is_instantiated_call) {
const AbstractType& static_receiver_type =
AbstractType::Cast(ConstantAt(DecodeOperandD(), 2).value());
call->set_receivers_static_type(&static_receiver_type);
} else {
const Class& owner = Class::Handle(Z, interface_target.Owner());
const AbstractType& type =
AbstractType::ZoneHandle(Z, owner.DeclarationType());
call->set_receivers_static_type(&type);
}
code_ <<= call;
B->Push(call);
}
void BytecodeFlowGraphBuilder::BuildInterfaceCall() {
BuildInterfaceCallCommon(/*is_unchecked_call=*/false);
BuildInterfaceCallCommon(/*is_unchecked_call=*/false,
/*is_instantiated_call=*/false);
}
void BytecodeFlowGraphBuilder::BuildInstantiatedInterfaceCall() {
BuildInterfaceCallCommon(/*is_unchecked_call=*/false,
/*is_instantiated_call=*/true);
}
void BytecodeFlowGraphBuilder::BuildUncheckedInterfaceCall() {
BuildInterfaceCallCommon(/*is_unchecked_call=*/true);
BuildInterfaceCallCommon(/*is_unchecked_call=*/true,
/*is_instantiated_call=*/false);
}
void BytecodeFlowGraphBuilder::BuildUncheckedClosureCall() {

View file

@ -147,7 +147,8 @@ class BytecodeFlowGraphBuilder {
int num_args);
void BuildIntOp(const String& name, Token::Kind token_kind, int num_args);
void BuildDoubleOp(const String& name, Token::Kind token_kind, int num_args);
void BuildInterfaceCallCommon(bool is_unchecked_call);
void BuildInterfaceCallCommon(bool is_unchecked_call,
bool is_instantiated_call);
void BuildInstruction(KernelBytecode::Opcode opcode);
void BuildFfiAsFunction();

View file

@ -717,6 +717,7 @@ intptr_t BytecodeReaderHelper::ReadConstantPool(const Function& function,
kObjectRef,
kDirectCall,
kInterfaceCall,
kInstantiatedInterfaceCall,
};
enum InvocationKind {
@ -884,6 +885,26 @@ intptr_t BytecodeReaderHelper::ReadConstantPool(const Function& function,
// The second entry is used for arguments descriptor.
obj = ReadObject();
} break;
case ConstantPoolTag::kInstantiatedInterfaceCall: {
elem = ReadObject();
ASSERT(elem.IsFunction());
// InstantiatedInterfaceCall constant occupies 3 entries:
// 1) Interface target.
pool.SetTypeAt(i, ObjectPool::EntryType::kTaggedObject,
ObjectPool::Patchability::kNotPatchable);
pool.SetObjectAt(i, elem);
++i;
ASSERT(i < obj_count);
// 2) Arguments descriptor.
obj = ReadObject();
pool.SetTypeAt(i, ObjectPool::EntryType::kTaggedObject,
ObjectPool::Patchability::kNotPatchable);
pool.SetObjectAt(i, obj);
++i;
ASSERT(i < obj_count);
// 3) Static receiver type.
obj = ReadObject();
} break;
default:
UNREACHABLE();
}

View file

@ -655,8 +655,8 @@ namespace dart {
V(InterfaceCall_Wide, D_F, WIDE, num, num, ___) \
V(Unused23, 0, RESV, ___, ___, ___) \
V(Unused24, 0, RESV, ___, ___, ___) \
V(Unused25, 0, RESV, ___, ___, ___) \
V(Unused26, 0, RESV, ___, ___, ___) \
V(InstantiatedInterfaceCall, D_F, ORDN, num, num, ___) \
V(InstantiatedInterfaceCall_Wide, D_F, WIDE, num, num, ___) \
V(UncheckedClosureCall, D_F, ORDN, num, num, ___) \
V(UncheckedClosureCall_Wide, D_F, WIDE, num, num, ___) \
V(UncheckedInterfaceCall, D_F, ORDN, num, num, ___) \
@ -749,7 +749,7 @@ class KernelBytecode {
// Maximum bytecode format version supported by VM.
// The range of supported versions should include version produced by bytecode
// generator (currentBytecodeFormatVersion in pkg/vm/lib/bytecode/dbc.dart).
static const intptr_t kMaxSupportedBytecodeFormatVersion = 17;
static const intptr_t kMaxSupportedBytecodeFormatVersion = 18;
enum Opcode {
#define DECLARE_BYTECODE(name, encoding, kind, op1, op2, op3) k##name,
@ -985,6 +985,8 @@ class KernelBytecode {
case KernelBytecode::kDirectCall_Wide:
case KernelBytecode::kInterfaceCall:
case KernelBytecode::kInterfaceCall_Wide:
case KernelBytecode::kInstantiatedInterfaceCall:
case KernelBytecode::kInstantiatedInterfaceCall_Wide:
case KernelBytecode::kUncheckedClosureCall:
case KernelBytecode::kUncheckedClosureCall_Wide:
case KernelBytecode::kUncheckedInterfaceCall:

View file

@ -1992,6 +1992,28 @@ SwitchDispatch:
DISPATCH();
}
{
BYTECODE(InstantiatedInterfaceCall, D_F);
DEBUG_CHECK;
{
const uint32_t argc = rF;
const uint32_t kidx = rD;
RawObject** call_base = SP - argc + 1;
RawObject** call_top = SP + 1;
InterpreterHelpers::IncrementUsageCounter(FrameFunction(FP));
RawString* target_name =
static_cast<RawFunction*>(LOAD_CONSTANT(kidx))->ptr()->name_;
argdesc_ = static_cast<RawArray*>(LOAD_CONSTANT(kidx + 1));
if (!InterfaceCall(thread, target_name, call_base, call_top, &pc, &FP,
&SP)) {
HANDLE_EXCEPTION;
}
}
DISPATCH();
}
{
BYTECODE(UncheckedClosureCall, D_F);

View file

@ -35,5 +35,5 @@ MINOR 5
PATCH 0
PRERELEASE 0
PRERELEASE_PATCH 0
ABI_VERSION 9
ABI_VERSION 10
OLDEST_SUPPORTED_ABI_VERSION 9