Files
sdk/runtime/vm/compiler/method_recognizer.cc
T
Alexander Markov 0af6dde79d Reland "[vm] Replace array GetIndexed graph intrinsics with flow graph builder implementation"
This is a reland of commit d24b5d1f5e

On top of the original change, the following is fixed and improved:

1) Flow graph builder (FGB) body is now also applied to
   dynamic invocation forwarders (similarly to graph
   intrinsics).

2) Frame can be omitted for functions which have a
   call on a shared slow path. This is needed to make
   FGB implementation of GetIndexed frameless, as it has
   GenericCheckBound which calls on shared slow path.
   (Graph intrinsics are frameless).

3) Range analysis is enabled for force-optimized functions,
   so more efficient code can be generated for boxing
   instructions. Range analysis is fixed to avoid crashes
   and correctly intersect ranges with constant boundaries
   (needed for some force-optimized FFI functions).

4) EliminateStackOverflowChecks pass is enabled for
   force-optimized functions so CheckStackOverflow can be
   eliminated.

Original change's description:
> [vm] Replace array GetIndexed graph intrinsics with flow graph builder implementation
>
> _Array, _GrowableList, internal and external typed data 'operator []'
> are now implemented in the flow graph builder.
>
> Unlike graph intrinsics, flow graph created in the flow graph builder
> can be used by the inliner. Corresponding graph intrinsics and native
> methods are removed.
>
> Also, this change adds missing external typed data indexing operations.
>
> TEST=ci
>
> Change-Id: Ic19784481feadf54c096a587413e67b4e18353dc
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/359940
> Reviewed-by: Ryan Macnak <rmacnak@google.com>
> Commit-Queue: Alexander Markov <alexmarkov@google.com>

TEST=ci

Change-Id: I04ef008a04238d432683d7543cd047e35bad17c3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/360560
Reviewed-by: Tess Strickland <sstrickl@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
2024-04-02 20:41:19 +00:00

457 lines
16 KiB
C++

// Copyright (c) 2014, 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.
#include "vm/compiler/method_recognizer.h"
#include "vm/log.h"
#include "vm/object.h"
#include "vm/reusable_handles.h"
#include "vm/symbols.h"
namespace dart {
intptr_t MethodRecognizer::NumArgsCheckedForStaticCall(
const Function& function) {
switch (function.recognized_kind()) {
case MethodRecognizer::kDoubleFromInteger:
case MethodRecognizer::kMathMin:
case MethodRecognizer::kMathMax:
return 2;
default:
return 0;
}
}
intptr_t MethodRecognizer::ResultCidFromPragma(
const Object& function_or_field) {
auto T = Thread::Current();
auto Z = T->zone();
auto& option = Object::Handle(Z);
if (Library::FindPragma(T, /*only_core=*/true, function_or_field,
Symbols::vm_exact_result_type(),
/*multiple=*/false, &option)) {
if (option.IsType()) {
return Type::Cast(option).type_class_id();
} else if (option.IsString()) {
auto& str = String::Cast(option);
// 'str' should match the pattern '([^#]+)#([^#\?]+)' where group 1
// is the library URI and group 2 is the class name.
bool parse_failure = false;
intptr_t library_end = -1;
for (intptr_t i = 0; i < str.Length(); ++i) {
if (str.CharAt(i) == '#') {
if (library_end != -1) {
parse_failure = true;
break;
} else {
library_end = i;
}
}
}
if (!parse_failure && library_end > 0) {
auto& tmp =
String::Handle(String::SubString(str, 0, library_end, Heap::kOld));
const auto& library = Library::Handle(Library::LookupLibrary(T, tmp));
if (!library.IsNull()) {
tmp = String::SubString(str, library_end + 1,
str.Length() - library_end - 1, Heap::kOld);
const auto& klass =
Class::Handle(library.LookupClassAllowPrivate(tmp));
if (!klass.IsNull()) {
return klass.id();
}
}
}
} else if (option.IsArray()) {
const Array& array = Array::Cast(option);
if (array.Length() > 0) {
const Object& type = Object::Handle(Array::Cast(option).At(0));
if (type.IsType()) {
return Type::Cast(type).type_class_id();
}
}
}
}
return kDynamicCid;
}
bool MethodRecognizer::HasNonNullableResultTypeFromPragma(
const Object& function_or_field) {
auto T = Thread::Current();
auto Z = T->zone();
auto& option = Object::Handle(Z);
if (Library::FindPragma(T, /*only_core=*/true, function_or_field,
Symbols::vm_non_nullable_result_type(),
/*multiple=*/false, &option)) {
return true;
}
// If nothing said otherwise, the return type is nullable.
return false;
}
intptr_t MethodRecognizer::MethodKindToReceiverCid(Kind kind) {
switch (kind) {
case kObjectArrayGetIndexed:
case kObjectArraySetIndexed:
case kObjectArraySetIndexedUnchecked:
return kArrayCid;
case kGrowableArrayGetIndexed:
case kGrowableArraySetIndexed:
case kGrowableArraySetIndexedUnchecked:
return kGrowableObjectArrayCid;
case kFloat32ArrayGetIndexed:
case kFloat32ArraySetIndexed:
return kTypedDataFloat32ArrayCid;
case kExternalFloat32ArrayGetIndexed:
return kExternalTypedDataFloat32ArrayCid;
case kFloat64ArrayGetIndexed:
case kFloat64ArraySetIndexed:
return kTypedDataFloat64ArrayCid;
case kExternalFloat64ArrayGetIndexed:
return kExternalTypedDataFloat64ArrayCid;
case kInt8ArrayGetIndexed:
case kInt8ArraySetIndexed:
return kTypedDataInt8ArrayCid;
case kExternalInt8ArrayGetIndexed:
return kExternalTypedDataInt8ArrayCid;
case kUint8ArrayGetIndexed:
case kUint8ArraySetIndexed:
return kTypedDataUint8ArrayCid;
case kUint8ClampedArrayGetIndexed:
case kUint8ClampedArraySetIndexed:
return kTypedDataUint8ClampedArrayCid;
case kExternalUint8ArrayGetIndexed:
case kExternalUint8ArraySetIndexed:
return kExternalTypedDataUint8ArrayCid;
case kExternalUint8ClampedArrayGetIndexed:
case kExternalUint8ClampedArraySetIndexed:
return kExternalTypedDataUint8ClampedArrayCid;
case kInt16ArrayGetIndexed:
case kInt16ArraySetIndexed:
return kTypedDataInt16ArrayCid;
case kExternalInt16ArrayGetIndexed:
return kExternalTypedDataInt16ArrayCid;
case kUint16ArrayGetIndexed:
case kUint16ArraySetIndexed:
return kTypedDataUint16ArrayCid;
case kExternalUint16ArrayGetIndexed:
return kExternalTypedDataUint16ArrayCid;
case kInt32ArrayGetIndexed:
case kInt32ArraySetIndexed:
return kTypedDataInt32ArrayCid;
case kExternalInt32ArrayGetIndexed:
return kExternalTypedDataInt32ArrayCid;
case kUint32ArrayGetIndexed:
case kUint32ArraySetIndexed:
return kTypedDataUint32ArrayCid;
case kExternalUint32ArrayGetIndexed:
return kExternalTypedDataUint32ArrayCid;
case kInt64ArrayGetIndexed:
case kInt64ArraySetIndexed:
return kTypedDataInt64ArrayCid;
case kExternalInt64ArrayGetIndexed:
return kExternalTypedDataInt64ArrayCid;
case kUint64ArrayGetIndexed:
case kUint64ArraySetIndexed:
return kTypedDataUint64ArrayCid;
case kExternalUint64ArrayGetIndexed:
return kExternalTypedDataUint64ArrayCid;
case kFloat32x4ArrayGetIndexed:
case kFloat32x4ArraySetIndexed:
return kTypedDataFloat32x4ArrayCid;
case kExternalFloat32x4ArrayGetIndexed:
return kExternalTypedDataFloat32x4ArrayCid;
case kInt32x4ArrayGetIndexed:
case kInt32x4ArraySetIndexed:
return kTypedDataInt32x4ArrayCid;
case kExternalInt32x4ArrayGetIndexed:
return kExternalTypedDataInt32x4ArrayCid;
case kFloat64x2ArrayGetIndexed:
case kFloat64x2ArraySetIndexed:
return kTypedDataFloat64x2ArrayCid;
case kExternalFloat64x2ArrayGetIndexed:
return kExternalTypedDataFloat64x2ArrayCid;
default:
break;
}
UNREACHABLE();
return kIllegalCid;
}
static const struct {
const char* const class_name;
const char* const function_name;
const char* const enum_name;
const uint32_t fp;
} recognized_methods[MethodRecognizer::kNumRecognizedMethods] = {
{"", "", "Unknown", 0},
#define RECOGNIZE_METHOD(class_name, function_name, enum_name, fp) \
{"" #class_name, "" #function_name, #enum_name, fp},
RECOGNIZED_LIST(RECOGNIZE_METHOD)
#undef RECOGNIZE_METHOD
};
const char* MethodRecognizer::KindToCString(Kind kind) {
if (kind >= kUnknown && kind < kNumRecognizedMethods)
return recognized_methods[kind].enum_name;
return "?";
}
const char* MethodRecognizer::KindToFunctionNameCString(Kind kind) {
if (kind >= kUnknown && kind < kNumRecognizedMethods)
return recognized_methods[kind].function_name;
return "?";
}
// Is this method marked with the vm:recognized pragma?
bool MethodRecognizer::IsMarkedAsRecognized(const Function& function,
const char* kind) {
const Function* functionp =
function.IsDynamicInvocationForwarder()
? &Function::Handle(function.ForwardingTarget())
: &function;
Object& options = Object::Handle();
bool is_recognized = Library::FindPragma(
Thread::Current(), /*only_core=*/true, *functionp,
Symbols::vm_recognized(), /*multiple=*/false, &options);
if (!is_recognized) return false;
if (kind == nullptr) return true;
ASSERT(options.IsString());
ASSERT(String::Cast(options).Equals("asm-intrinsic") ||
String::Cast(options).Equals("graph-intrinsic") ||
String::Cast(options).Equals("other"));
return String::Cast(options).Equals(kind);
}
void MethodRecognizer::InitializeState() {
GrowableArray<Library*> libs(3);
Libraries(&libs);
Function& func = Function::Handle();
bool fingerprints_match = true;
for (intptr_t i = 1; i < MethodRecognizer::kNumRecognizedMethods; i++) {
const MethodRecognizer::Kind kind = static_cast<MethodRecognizer::Kind>(i);
func = Library::GetFunction(libs, recognized_methods[i].class_name,
recognized_methods[i].function_name);
if (!func.IsNull()) {
fingerprints_match =
func.CheckSourceFingerprint(recognized_methods[i].fp) &&
fingerprints_match;
func.set_recognized_kind(kind);
switch (kind) {
#define RECOGNIZE_METHOD(class_name, function_name, enum_name, fp) \
case MethodRecognizer::k##enum_name: \
func.reset_unboxed_parameters_and_return(); \
break;
ALL_INTRINSICS_LIST(RECOGNIZE_METHOD)
#undef RECOGNIZE_METHOD
default:
break;
}
} else if (!FLAG_precompiled_mode) {
fingerprints_match = false;
OS::PrintErr("Missing %s::%s\n", recognized_methods[i].class_name,
recognized_methods[i].function_name);
}
}
#define SET_FUNCTION_BIT(class_name, function_name, dest, fp, setter, value) \
func = Library::GetFunction(libs, #class_name, #function_name); \
if (!func.IsNull()) { \
fingerprints_match = \
func.CheckSourceFingerprint(fp) && fingerprints_match; \
func.setter(value); \
} else if (!FLAG_precompiled_mode) { \
OS::PrintErr("Missing %s::%s\n", #class_name, #function_name); \
fingerprints_match = false; \
}
#define SET_IS_POLYMORPHIC_TARGET(class_name, function_name, dest, fp) \
SET_FUNCTION_BIT(class_name, function_name, dest, fp, \
set_is_polymorphic_target, true)
POLYMORPHIC_TARGET_LIST(SET_IS_POLYMORPHIC_TARGET);
#undef SET_RECOGNIZED_KIND
#undef SET_IS_POLYMORPHIC_TARGET
#undef SET_FUNCTION_BIT
if (!fingerprints_match) {
// Private names are mangled. Mangling depends on Library::private_key_.
// If registering a new bootstrap library, add at the end.
FATAL(
"FP mismatch while recognizing methods. If the behavior of "
"these functions has changed, then changes are also needed in "
"the VM's compiler. Otherwise the fingerprint can simply be "
"updated in recognized_methods_list.h\n");
}
}
void MethodRecognizer::Libraries(GrowableArray<Library*>* libs) {
libs->Add(&Library::ZoneHandle(Library::CoreLibrary()));
libs->Add(&Library::ZoneHandle(Library::CollectionLibrary()));
libs->Add(&Library::ZoneHandle(Library::MathLibrary()));
libs->Add(&Library::ZoneHandle(Library::TypedDataLibrary()));
libs->Add(&Library::ZoneHandle(Library::ConvertLibrary()));
libs->Add(&Library::ZoneHandle(Library::InternalLibrary()));
libs->Add(&Library::ZoneHandle(Library::IsolateLibrary()));
libs->Add(&Library::ZoneHandle(Library::DeveloperLibrary()));
libs->Add(&Library::ZoneHandle(Library::AsyncLibrary()));
libs->Add(&Library::ZoneHandle(Library::FfiLibrary()));
libs->Add(&Library::ZoneHandle(Library::NativeWrappersLibrary()));
}
static Token::Kind RecognizeTokenKindHelper(const String& name) {
if (name.ptr() == Symbols::Plus().ptr()) {
return Token::kADD;
} else if (name.ptr() == Symbols::Minus().ptr()) {
return Token::kSUB;
} else if (name.ptr() == Symbols::Star().ptr()) {
return Token::kMUL;
} else if (name.ptr() == Symbols::Slash().ptr()) {
return Token::kDIV;
} else if (name.ptr() == Symbols::TruncDivOperator().ptr()) {
return Token::kTRUNCDIV;
} else if (name.ptr() == Symbols::Percent().ptr()) {
return Token::kMOD;
} else if (name.ptr() == Symbols::BitOr().ptr()) {
return Token::kBIT_OR;
} else if (name.ptr() == Symbols::Ampersand().ptr()) {
return Token::kBIT_AND;
} else if (name.ptr() == Symbols::Caret().ptr()) {
return Token::kBIT_XOR;
} else if (name.ptr() == Symbols::LeftShiftOperator().ptr()) {
return Token::kSHL;
} else if (name.ptr() == Symbols::RightShiftOperator().ptr()) {
return Token::kSHR;
} else if (name.ptr() == Symbols::UnsignedRightShiftOperator().ptr()) {
return Token::kUSHR;
} else if (name.ptr() == Symbols::Tilde().ptr()) {
return Token::kBIT_NOT;
} else if (name.ptr() == Symbols::UnaryMinus().ptr()) {
return Token::kNEGATE;
} else if (name.ptr() == Symbols::EqualOperator().ptr()) {
return Token::kEQ;
} else if (name.ptr() == Symbols::Token(Token::kNE).ptr()) {
return Token::kNE;
} else if (name.ptr() == Symbols::LAngleBracket().ptr()) {
return Token::kLT;
} else if (name.ptr() == Symbols::RAngleBracket().ptr()) {
return Token::kGT;
} else if (name.ptr() == Symbols::LessEqualOperator().ptr()) {
return Token::kLTE;
} else if (name.ptr() == Symbols::GreaterEqualOperator().ptr()) {
return Token::kGTE;
} else if (Field::IsGetterName(name)) {
return Token::kGET;
} else if (Field::IsSetterName(name)) {
return Token::kSET;
}
return Token::kILLEGAL;
}
Token::Kind MethodTokenRecognizer::RecognizeTokenKind(const String& name) {
ASSERT(name.IsSymbol());
if (Function::IsDynamicInvocationForwarderName(name)) {
Thread* thread = Thread::Current();
const auto& demangled_name = String::Handle(
thread->zone(), Function::DemangleDynamicInvocationForwarderName(name));
return RecognizeTokenKindHelper(demangled_name);
} else {
return RecognizeTokenKindHelper(name);
}
}
#define RECOGNIZE_FACTORY(symbol, class_name, constructor_name, cid, fp) \
{Symbols::k##symbol##Id, cid, fp, #symbol ", " #cid}, // NOLINT
static const struct {
const intptr_t symbol_id;
const intptr_t cid;
const uint32_t finger_print;
const char* const name;
} factory_recognizer_list[] = {RECOGNIZED_LIST_FACTORY_LIST(RECOGNIZE_FACTORY){
Symbols::kIllegal, -1, 0, nullptr}};
#undef RECOGNIZE_FACTORY
intptr_t FactoryRecognizer::ResultCid(const Function& factory) {
ASSERT(factory.IsFactory());
const Class& function_class = Class::Handle(factory.Owner());
const Library& lib = Library::Handle(function_class.library());
ASSERT((lib.ptr() == Library::CoreLibrary()) ||
(lib.ptr() == Library::TypedDataLibrary()));
const String& factory_name = String::Handle(factory.name());
for (intptr_t i = 0;
factory_recognizer_list[i].symbol_id != Symbols::kIllegal; i++) {
if (String::EqualsIgnoringPrivateKey(
factory_name,
Symbols::Symbol(factory_recognizer_list[i].symbol_id))) {
return factory_recognizer_list[i].cid;
}
}
return kDynamicCid;
}
intptr_t FactoryRecognizer::GetResultCidOfListFactory(Zone* zone,
const Function& function,
intptr_t argument_count) {
if (!function.IsFactory()) {
return kDynamicCid;
}
const Class& owner = Class::Handle(zone, function.Owner());
if ((owner.library() != Library::CoreLibrary()) &&
(owner.library() != Library::TypedDataLibrary())) {
return kDynamicCid;
}
if (owner.Name() == Symbols::List().ptr()) {
if (function.name() == Symbols::ListFactory().ptr()) {
ASSERT(argument_count == 1 || argument_count == 2);
return (argument_count == 1) ? kGrowableObjectArrayCid : kArrayCid;
} else if (function.name() == Symbols::ListFilledFactory().ptr()) {
ASSERT(argument_count == 3 || argument_count == 4);
return (argument_count == 3) ? kArrayCid : kDynamicCid;
}
}
return ResultCid(function);
}
} // namespace dart