Chromium Code Reviews| Index: runtime/vm/precompiler.cc |
| diff --git a/runtime/vm/precompiler.cc b/runtime/vm/precompiler.cc |
| index 65b6166499409ef1490454add290f36d4f301c79..a3c978f87833467fcef37ae2204f0574cf1307ab 100644 |
| --- a/runtime/vm/precompiler.cc |
| +++ b/runtime/vm/precompiler.cc |
| @@ -140,12 +140,19 @@ Precompiler::Precompiler(Thread* thread, bool reset_fields) : |
| selector_count_(0), |
| dropped_function_count_(0), |
| dropped_field_count_(0), |
| + dropped_class_count_(0), |
| + dropped_typearg_count_(0), |
| + dropped_type_count_(0), |
| + dropped_library_count_(0), |
| libraries_(GrowableObjectArray::Handle(I->object_store()->libraries())), |
| pending_functions_( |
| GrowableObjectArray::Handle(GrowableObjectArray::New())), |
| sent_selectors_(), |
| enqueued_functions_(), |
| fields_to_retain_(), |
| + classes_to_retain_(), |
| + typeargs_to_retain_(), |
| + types_to_retain_(), |
| error_(Error::Handle()) { |
| } |
| @@ -166,7 +173,7 @@ void Precompiler::DoCompileAll( |
| const intptr_t kPrecompilerRounds = 1; |
| for (intptr_t round = 0; round < kPrecompilerRounds; round++) { |
| if (FLAG_trace_precompiler) { |
| - OS::Print("Precompiler round %" Pd "\n", round); |
| + THR_Print("Precompiler round %" Pd "\n", round); |
| } |
| if (round > 0) { |
| @@ -197,8 +204,12 @@ void Precompiler::DoCompileAll( |
| DropFunctions(); |
| DropFields(); |
| - |
| - // TODO(rmacnak): DropEmptyClasses(); |
| + TraceTypesFromRetainedClasses(); |
| + DropTypes(); |
| + DropTypeArguments(); |
| + DropClasses(); |
| + // TODO(rmacnak): Probably don't need to remember imports/exports either. |
| + DropLibraries(); |
| BindStaticCalls(); |
| @@ -219,15 +230,17 @@ void Precompiler::DoCompileAll( |
| intptr_t dropped_symbols_count = Symbols::Compact(I); |
| if (FLAG_trace_precompiler) { |
| - THR_Print("Precompiled %" Pd " functions, %" Pd " dynamic types," |
| - " %" Pd " dynamic selectors.\n Dropped %" Pd " functions, %" Pd |
| - " fields, %" Pd " symbols.\n", |
| - function_count_, |
| - class_count_, |
| - selector_count_, |
| - dropped_function_count_, |
| - dropped_field_count_, |
| - dropped_symbols_count); |
| + THR_Print("Precompiled %" Pd " functions,", function_count_); |
| + THR_Print(" %" Pd " dynamic types,", class_count_); |
| + THR_Print(" %" Pd " dynamic selectors.\n", selector_count_); |
| + |
| + THR_Print("Dropped %" Pd " functions,", dropped_function_count_); |
| + THR_Print(" %" Pd " fields,", dropped_field_count_); |
| + THR_Print(" %" Pd " symbols,", dropped_symbols_count); |
| + THR_Print(" %" Pd " types,", dropped_type_count_); |
| + THR_Print(" %" Pd " type arguments,", dropped_typearg_count_); |
| + THR_Print(" %" Pd " classes,", dropped_class_count_); |
| + THR_Print(" %" Pd " libraries.\n", dropped_library_count_); |
| } |
| } |
| @@ -236,6 +249,7 @@ void Precompiler::ClearAllCode() { |
| class ClearCodeFunctionVisitor : public FunctionVisitor { |
| void VisitFunction(const Function& function) { |
| function.ClearCode(); |
| + function.ClearICDataArray(); |
| } |
| }; |
| ClearCodeFunctionVisitor visitor; |
| @@ -252,84 +266,25 @@ void Precompiler::AddRoots(Dart_QualifiedFunctionName embedder_entry_points[]) { |
| AddSelector(Symbols::Call()); // For speed, not correctness. |
| // Allocated from C++. |
| - static const intptr_t kExternallyAllocatedCids[] = { |
| - kBoolCid, |
| - kNullCid, |
| - kClosureCid, |
| - |
| - kSmiCid, |
| - kMintCid, |
| - kBigintCid, |
| - kDoubleCid, |
| - |
| - kOneByteStringCid, |
| - kTwoByteStringCid, |
| - kExternalOneByteStringCid, |
| - kExternalTwoByteStringCid, |
| - |
| - kArrayCid, |
| - kImmutableArrayCid, |
| - kGrowableObjectArrayCid, |
| - kLinkedHashMapCid, |
| - |
| - kTypedDataUint8ClampedArrayCid, |
| - kTypedDataUint8ArrayCid, |
| - kTypedDataUint16ArrayCid, |
| - kTypedDataUint32ArrayCid, |
| - kTypedDataUint64ArrayCid, |
| - kTypedDataInt8ArrayCid, |
| - kTypedDataInt16ArrayCid, |
| - kTypedDataInt32ArrayCid, |
| - kTypedDataInt64ArrayCid, |
| - |
| - kExternalTypedDataUint8ArrayCid, |
| - kExternalTypedDataUint16ArrayCid, |
| - kExternalTypedDataUint32ArrayCid, |
| - kExternalTypedDataUint64ArrayCid, |
| - kExternalTypedDataInt8ArrayCid, |
| - kExternalTypedDataInt16ArrayCid, |
| - kExternalTypedDataInt32ArrayCid, |
| - kExternalTypedDataInt64ArrayCid, |
| - |
| - kTypedDataFloat32ArrayCid, |
| - kTypedDataFloat64ArrayCid, |
| - |
| - kTypedDataFloat32x4ArrayCid, |
| - kTypedDataInt32x4ArrayCid, |
| - kTypedDataFloat64x2ArrayCid, |
| - |
| - kInt32x4Cid, |
| - kFloat32x4Cid, |
| - kFloat64x2Cid, |
| - |
| - kTypeCid, |
| - kFunctionTypeCid, |
| - kTypeRefCid, |
| - kTypeParameterCid, |
| - kBoundedTypeCid, |
| - kLibraryPrefixCid, |
| - |
| - kJSRegExpCid, |
| - kUserTagCid, |
| - kStacktraceCid, |
| - kWeakPropertyCid, |
| - kCapabilityCid, |
| - ReceivePort::kClassId, |
| - SendPort::kClassId, |
| - |
| - kIllegalCid |
| - }; |
| - |
| Class& cls = Class::Handle(Z); |
| - for (intptr_t i = 0; kExternallyAllocatedCids[i] != kIllegalCid; i++) { |
| - cls = isolate()->class_table()->At(kExternallyAllocatedCids[i]); |
| + for (intptr_t cid = kInstanceCid; cid < kNumPredefinedCids; cid++) { |
| + ASSERT(isolate()->class_table()->IsValidIndex(cid)); |
| + if (!isolate()->class_table()->HasValidClassAt(cid)) { |
| + continue; |
| + } |
| + if ((cid == kDynamicCid) || |
| + (cid == kVoidCid) || |
| + (cid == kFreeListElement)) { |
| + continue; |
| + } |
| + cls = isolate()->class_table()->At(cid); |
| AddInstantiatedClass(cls); |
| } |
| Dart_QualifiedFunctionName vm_entry_points[] = { |
| // Functions |
| { "dart:async", "::", "_setScheduleImmediateClosure" }, |
| - { "dart:core", "::", "_completeDeferredLoads"}, |
| + { "dart:core", "::", "_completeDeferredLoads" }, |
| { "dart:core", "AbstractClassInstantiationError", |
| "AbstractClassInstantiationError._create" }, |
| { "dart:core", "ArgumentError", "ArgumentError." }, |
| @@ -483,6 +438,8 @@ void Precompiler::ProcessFunction(const Function& function) { |
| if (!error_.IsNull()) { |
| Jump(error_); |
| } |
| + // Used in the JIT to save type-feedback across compilations. |
| + function.ClearICDataArray(); |
| } else { |
| if (FLAG_trace_precompiler) { |
| // This function was compiled from somewhere other than Precompiler, |
| @@ -510,7 +467,7 @@ void Precompiler::AddCalleesOf(const Function& function) { |
| for (intptr_t i = 0; i < table.Length(); i++) { |
| entry = table.At(i); |
| if (entry.IsFunction()) { |
| - target ^= table.At(i); |
| + target ^= entry.raw(); |
| AddFunction(target); |
| } |
| } |
| @@ -532,9 +489,8 @@ void Precompiler::AddCalleesOf(const Function& function) { |
| entry = pool.ObjectAt(i); |
| if (entry.IsICData()) { |
| call_site ^= entry.raw(); |
| - if (call_site.NumberOfChecks() == 1) { |
| - // Probably a static call. |
| - target = call_site.GetTargetAt(0); |
| + for (intptr_t j = 0; j < call_site.NumberOfChecks(); j++) { |
| + target = call_site.GetTargetAt(j); |
| AddFunction(target); |
| if (!target.is_static()) { |
| // Super call (should not enqueue selector) or dynamic call with a |
| @@ -542,7 +498,8 @@ void Precompiler::AddCalleesOf(const Function& function) { |
| selector = call_site.target_name(); |
| AddSelector(selector); |
| } |
| - } else { |
| + } |
| + if (call_site.NumberOfChecks() == 0) { |
| // A dynamic call. |
| selector = call_site.target_name(); |
| AddSelector(selector); |
| @@ -563,7 +520,11 @@ void Precompiler::AddCalleesOf(const Function& function) { |
| } else if (entry.IsInstance()) { |
| // Const object, literal or args descriptor. |
| instance ^= entry.raw(); |
| - AddConstObject(instance); |
| + if (entry.IsAbstractType()) { |
| + AddType(AbstractType::Cast(entry)); |
| + } else { |
| + AddConstObject(instance); |
| + } |
| } else if (entry.IsFunction()) { |
| // Local closure function. |
| target ^= entry.raw(); |
| @@ -574,12 +535,88 @@ void Precompiler::AddCalleesOf(const Function& function) { |
| cls ^= target_code.owner(); |
| AddInstantiatedClass(cls); |
| } |
| + } else if (entry.IsTypeArguments()) { |
| + AddTypeArguments(TypeArguments::Cast(entry)); |
| } |
| } |
| } |
| } |
| +void Precompiler::AddTypesOf(const Class& cls) { |
| + if (cls.IsNull()) return; |
| + if (classes_to_retain_.Lookup(&cls) != NULL) return; |
| + |
| + classes_to_retain_.Insert(&Class::ZoneHandle(Z, cls.raw())); |
| + |
| + Array& interfaces = Array::Handle(Z, cls.interfaces()); |
| + AbstractType& type = AbstractType::Handle(Z); |
| + for (intptr_t i = 0; i < interfaces.Length(); i++) { |
| + type ^= interfaces.At(i); |
| + AddType(type); |
| + } |
| + |
| + AddTypeArguments(TypeArguments::Handle(Z, cls.type_parameters())); |
| + |
| + type = cls.super_type(); |
| + AddType(type); |
| + |
| + type = cls.mixin(); |
| + AddType(type); |
|
regis
2016/02/19 20:54:07
I am not sure, but if cls is a typedef, you may al
rmacnak
2016/02/20 01:35:39
Done.
|
| +} |
| + |
| + |
| +void Precompiler::AddTypesOf(const Function& function) { |
| + AbstractType& type = AbstractType::Handle(Z); |
| + type = function.result_type(); |
| + AddType(type); |
| + for (intptr_t i = 0; i < function.NumParameters(); i++) { |
| + type = function.ParameterTypeAt(i); |
| + AddType(type); |
| + } |
| +} |
| + |
| + |
| +void Precompiler::AddType(const AbstractType& abstype) { |
| + if (abstype.IsNull()) return; |
| + |
| + if (types_to_retain_.Lookup(&abstype) != NULL) return; |
| + types_to_retain_.Insert(&AbstractType::ZoneHandle(Z, abstype.raw())); |
| + |
| + if (abstype.IsType()) { |
| + const Type& type = Type::Cast(abstype); |
| + const Class& cls = Class::Handle(Z, type.type_class()); |
| + AddTypesOf(cls); |
| + |
| + const TypeArguments& vector = TypeArguments::Handle(Z, abstype.arguments()); |
| + AddTypeArguments(vector); |
|
regis
2016/02/19 20:54:07
Bad indentation.
rmacnak
2016/02/20 01:35:39
Done.
|
| + } else if (abstype.IsFunctionType()) { |
| + const FunctionType& func_type = FunctionType::Cast(abstype); |
| + const Class& cls = Class::Handle(Z, func_type.scope_class()); |
| + AddTypesOf(cls); |
| + const Function& func = Function::Handle(Z, func_type.signature()); |
| + AddTypesOf(func); |
| + |
| + const TypeArguments& vector = TypeArguments::Handle(Z, abstype.arguments()); |
| + AddTypeArguments(vector); |
| + } |
|
regis
2016/02/19 20:54:07
What about BoundedType and TypeRef?
rmacnak
2016/02/20 01:35:39
Added.
|
| +} |
| + |
| + |
| +void Precompiler::AddTypeArguments(const TypeArguments& args) { |
| + if (args.IsNull()) return; |
| + |
| + if (typeargs_to_retain_.Lookup(&args) != NULL) return; |
| + typeargs_to_retain_.Insert(&TypeArguments::ZoneHandle(Z, args.raw())); |
| + |
| + AbstractType& arg = AbstractType::Handle(Z); |
| + for (intptr_t i = 0; i < args.Length(); i++) { |
| + arg = args.TypeAt(i); |
| + AddType(arg); |
| + } |
| +} |
| + |
| + |
| void Precompiler::AddConstObject(const Instance& instance) { |
| const Class& cls = Class::Handle(Z, instance.clazz()); |
| AddInstantiatedClass(cls); |
| @@ -682,7 +719,7 @@ RawFunction* Precompiler::CompileStaticInitializer(const Field& field) { |
| ASSERT(field.is_static()); |
| if (field.HasPrecompiledInitializer()) { |
| // TODO(rmacnak): Investigate why this happens for _enum_names. |
| - OS::Print("Warning: Ignoring repeated request for initializer for %s\n", |
| + THR_Print("Warning: Ignoring repeated request for initializer for %s\n", |
| field.ToCString()); |
| return Function::null(); |
| } |
| @@ -1142,6 +1179,7 @@ void Precompiler::DropFunctions() { |
| if (retain) { |
| retained_functions.Add(function); |
| function.DropUncompiledImplicitClosureFunction(); |
| + AddTypesOf(function); |
| } else { |
| bool top_level = cls.IsTopLevel(); |
| if (top_level && |
| @@ -1173,8 +1211,10 @@ void Precompiler::DropFunctions() { |
| retained_functions = GrowableObjectArray::New(); |
| for (intptr_t j = 0; j < closures.Length(); j++) { |
| function ^= closures.At(j); |
| - if (function.HasCode()) { |
| + bool retain = function.HasCode(); |
| + if (retain) { |
| retained_functions.Add(function); |
| + AddTypesOf(function); |
| } else { |
| dropped_function_count_++; |
| if (FLAG_trace_precompiler) { |
| @@ -1194,6 +1234,7 @@ void Precompiler::DropFields() { |
| Field& field = Field::Handle(Z); |
| GrowableObjectArray& retained_fields = GrowableObjectArray::Handle(Z); |
| String& name = String::Handle(Z); |
| + AbstractType& type = AbstractType::Handle(Z); |
| for (intptr_t i = 0; i < libraries_.Length(); i++) { |
| lib ^= libraries_.At(i); |
| @@ -1211,6 +1252,8 @@ void Precompiler::DropFields() { |
| bool retain = fields_to_retain_.Lookup(&field) != NULL; |
| if (retain) { |
| retained_fields.Add(field); |
| + type = field.type(); |
| + AddType(type); |
| } else { |
| bool top_level = cls.IsTopLevel(); |
| if (top_level) { |
| @@ -1237,6 +1280,248 @@ void Precompiler::DropFields() { |
| } |
| +void Precompiler::DropTypes() { |
| + Library& lib = Library::Handle(Z); |
| + Class& cls = Class::Handle(Z); |
| + Object& obj = Object::Handle(Z); |
| + Array& arr = Array::Handle(Z); |
| + GrowableObjectArray& retained_types = GrowableObjectArray::Handle(Z); |
| + AbstractType& type = AbstractType::Handle(Z); |
| + |
| + for (intptr_t i = 0; i < libraries_.Length(); i++) { |
| + lib ^= libraries_.At(i); |
| + ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate); |
| + while (it.HasNext()) { |
| + cls = it.GetNextClass(); |
| + if (cls.IsDynamicClass()) { |
| + continue; // class 'dynamic' is in the read-only VM isolate. |
| + } |
| + obj = cls.canonical_types(); |
| + if (!obj.IsArray()) { |
| + // Class only has one type, keep it. |
| + } else { |
| + // Class has many types. |
| + arr ^= obj.raw(); |
| + retained_types = GrowableObjectArray::New(); |
| + |
| + // Always keep the first one. |
| + ASSERT(arr.Length() >= 1); |
| + obj = arr.At(0); |
| + retained_types.Add(obj); |
| + |
| + for (intptr_t i = 1; i < arr.Length(); i++) { |
| + obj = arr.At(i); |
| + if (obj.IsNull()) { |
| + continue; |
| + } |
| + type ^= obj.raw(); |
| + bool retain = types_to_retain_.Lookup(&type) != NULL; |
| + if (retain) { |
| + retained_types.Add(type); |
| + } else { |
| + dropped_type_count_++; |
| + } |
| + } |
| + arr = Array::MakeArray(retained_types); |
| + cls.set_canonical_types(arr); |
| + } |
| + } |
| + } |
| +} |
| + |
| + |
| +void Precompiler::DropTypeArguments() { |
| + const Array& typeargs_table = |
| + Array::Handle(Z, I->object_store()->canonical_type_arguments()); |
| + GrowableObjectArray& retained_typeargs = |
| + GrowableObjectArray::Handle(Z, GrowableObjectArray::New()); |
| + TypeArguments& typeargs = TypeArguments::Handle(Z); |
| + for (intptr_t i = 0; i < (typeargs_table.Length() - 1); i++) { |
| + typeargs ^= typeargs_table.At(i); |
| + bool retain = typeargs_to_retain_.Lookup(&typeargs) != NULL; |
| + if (retain) { |
| + retained_typeargs.Add(typeargs); |
| + } else { |
| + dropped_typearg_count_++; |
| + } |
| + } |
| + |
| + const intptr_t dict_size = |
| + Utils::RoundUpToPowerOfTwo(retained_typeargs.Length() * 4 / 3); |
| + const Array& new_table = Array::Handle(Z, Array::New(dict_size + 1)); |
| + |
| + Object& element = Object::Handle(Z); |
| + for (intptr_t i = 0; i < retained_typeargs.Length(); i++) { |
| + typeargs ^= retained_typeargs.At(i); |
| + intptr_t hash = typeargs.Hash(); |
| + intptr_t index = hash & (dict_size - 1); |
| + element = new_table.At(index); |
| + while (!element.IsNull()) { |
| + index = (index + 1) & (dict_size - 1); |
| + element = new_table.At(index); |
| + } |
| + new_table.SetAt(index, typeargs); |
| + } |
| + |
| + const Smi& used = Smi::Handle(Z, Smi::New(retained_typeargs.Length())); |
| + new_table.SetAt(dict_size, used); |
| + |
| + I->object_store()->set_canonical_type_arguments(new_table); |
| +} |
| + |
| + |
| +void Precompiler::TraceTypesFromRetainedClasses() { |
| + Library& lib = Library::Handle(Z); |
| + Class& cls = Class::Handle(Z); |
| + Array& members = Array::Handle(Z); |
| + |
| + for (intptr_t i = 0; i < libraries_.Length(); i++) { |
| + lib ^= libraries_.At(i); |
| + ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate); |
| + while (it.HasNext()) { |
| + cls = it.GetNextClass(); |
| + if (cls.IsDynamicClass()) { |
| + continue; // class 'dynamic' is in the read-only VM isolate. |
| + } |
| + |
| + // The subclasses array is only needed for CHA. |
| + cls.ClearDirectSubclasses(); |
| + |
| + bool retain = false; |
| + members = cls.fields(); |
| + if (members.Length() > 0) { |
| + retain = true; |
| + } |
| + members = cls.functions(); |
| + if (members.Length() > 0) { |
| + retain = true; |
| + } |
| + if (cls.is_allocated()) { |
| + retain = true; |
| + } |
| + if (cls.is_enum_class()) { |
| + // Enum classes have live instances, so we cannot unregister |
| + // them. |
| + retain = true; |
| + } |
| + members = cls.constants(); |
| + if (members.Length() > 0) { |
| + // --compile_all? |
| + retain = true; |
| + } |
| + |
| + if (retain) { |
| + AddTypesOf(cls); |
| + } |
| + } |
| + } |
| +} |
| + |
| + |
| +void Precompiler::DropClasses() { |
| + Library& lib = Library::Handle(Z); |
| + Class& cls = Class::Handle(Z); |
| + Array& members = Array::Handle(Z); |
| + String& name = String::Handle(Z); |
| + |
| +#if defined(DEBUG) |
| + { |
| + // Force GC for allocation stats. |
| + I->heap()->CollectAllGarbage(); |
| + } |
| +#endif |
| + |
| + ClassTable* class_table = I->class_table(); |
| + intptr_t num_cids = class_table->NumCids(); |
| + |
| + for (intptr_t cid = kNumPredefinedCids; cid < num_cids; cid++) { |
| + if (!class_table->IsValidIndex(cid)) continue; |
| + if (!class_table->HasValidClassAt(cid)) continue; |
| + |
| + cls = class_table->At(cid); |
| + ASSERT(!cls.IsNull()); |
| + |
| + if (cls.IsTopLevel()) { |
| + // Top-level classes are referenced specially from their library. They |
| + // will only be removed as a consquence of an entire library being |
|
regis
2016/02/19 20:54:07
consequence
rmacnak
2016/02/20 01:35:39
Done.
|
| + // removed. |
| + continue; |
| + } |
| + if (cls.is_enum_class()) { |
| + // Enum classes have live instances, so we cannot unregister |
| + // them. |
| + continue; |
| + } |
| + members = cls.constants(); |
| + if (members.Length() > 0) { |
| + // --compile_all? |
| + continue; |
| + } |
| + |
| + bool retain = classes_to_retain_.Lookup(&cls) != NULL; |
| + if (retain) { |
| + continue; |
| + } |
| + |
| +#if defined(DEBUG) |
| + intptr_t instances = |
| + class_table->StatsWithUpdatedSize(cid)->post_gc.new_count + |
| + class_table->StatsWithUpdatedSize(cid)->post_gc.old_count; |
| + if (instances != 0) { |
| + FATAL2("Want to drop class %s, but it has %" Pd " instances\n", |
| + cls.ToCString(), |
| + instances); |
| + } |
| +#endif |
| + |
| + dropped_class_count_++; |
| + if (FLAG_trace_precompiler) { |
| + THR_Print("Precompilation dropping %ld %s\n", cid, cls.ToCString()); |
| + } |
| + |
| +#if defined(DEBUG) |
| + class_table->Unregister(cid); |
| +#endif |
| + cls.set_id(kIllegalCid); // We check this when serializing. |
| + |
| + lib = cls.library(); |
| + name = cls.DictionaryName(); |
| + lib.RemoveObject(cls, name); |
| + } |
| +} |
| + |
| + |
| +void Precompiler::DropLibraries() { |
| + const GrowableObjectArray& retained_libraries = |
| + GrowableObjectArray::Handle(Z, GrowableObjectArray::New()); |
| + Library& lib = Library::Handle(Z); |
| + |
| + for (intptr_t i = 0; i < libraries_.Length(); i++) { |
| + lib ^= libraries_.At(i); |
| + lib.DropDependencies(); |
| + intptr_t entries = 0; |
| + DictionaryIterator it(lib); |
| + while (it.HasNext()) { |
| + it.GetNext(); |
| + entries++; |
| + } |
| + if (entries > 0) { |
| + lib.set_index(retained_libraries.Length()); |
| + retained_libraries.Add(lib); |
| + } else { |
| + dropped_library_count_++; |
| + lib.set_index(-1); |
| + if (FLAG_trace_precompiler) { |
| + THR_Print("Precompilation dropping %s\n", lib.ToCString()); |
| + } |
| + } |
| + } |
| + |
| + I->object_store()->set_libraries(retained_libraries); |
| + libraries_ = retained_libraries.raw(); |
| +} |
| + |
| + |
| void Precompiler::BindStaticCalls() { |
| class BindStaticCallsVisitor : public FunctionVisitor { |
| public: |