// Copyright (c) 2012, 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. // Class for intrinsifying functions. #include "vm/compiler/intrinsifier.h" #include "vm/compiler/assembler/assembler.h" #include "vm/compiler/backend/flow_graph.h" #include "vm/compiler/backend/flow_graph_compiler.h" #include "vm/compiler/backend/il_printer.h" #include "vm/compiler/backend/linearscan.h" #include "vm/compiler/frontend/flow_graph_builder.h" #include "vm/compiler/jit/compiler.h" #include "vm/cpu.h" #include "vm/flags.h" #include "vm/object.h" #include "vm/parser.h" #include "vm/symbols.h" namespace dart { DEFINE_FLAG(bool, intrinsify, true, "Instrinsify when possible"); DEFINE_FLAG(bool, trace_intrinsifier, false, "Trace intrinsifier"); namespace compiler { bool Intrinsifier::CanIntrinsify(const Function& function) { if (FLAG_trace_intrinsifier) { THR_Print("CanIntrinsify %s ->", function.ToQualifiedCString()); } if (!FLAG_intrinsify) return false; // TODO(regis): We do not need to explicitly filter generic functions here, // unless there are errors we don't detect at link time. Revisit if necessary. if (function.IsClosureFunction()) { if (FLAG_trace_intrinsifier) { THR_Print("No, closure function.\n"); } return false; } // Can occur because of compile-all flag. if (function.is_external()) { if (FLAG_trace_intrinsifier) { THR_Print("No, external function.\n"); } return false; } if (!function.is_intrinsic() && !CanIntrinsifyFieldAccessor(function)) { if (FLAG_trace_intrinsifier) { THR_Print("No, not intrinsic function.\n"); } return false; } switch (function.recognized_kind()) { case MethodRecognizer::kInt64ArrayGetIndexed: case MethodRecognizer::kInt64ArraySetIndexed: case MethodRecognizer::kUint64ArrayGetIndexed: case MethodRecognizer::kUint64ArraySetIndexed: // TODO(ajcbik): consider 32-bit as well. if (target::kBitsPerWord == 64) { break; } if (FLAG_trace_intrinsifier) { THR_Print("No, 64-bit int intrinsic on 32-bit platform.\n"); } return false; default: break; } if (FLAG_trace_intrinsifier) { THR_Print("Yes.\n"); } return true; } bool Intrinsifier::CanIntrinsifyFieldAccessor(const Function& function) { const bool is_getter = function.IsImplicitGetterFunction(); const bool is_setter = function.IsImplicitSetterFunction(); if (!is_getter && !is_setter) return false; Field& field = Field::Handle(function.accessor_field()); ASSERT(!field.IsNull()); // The checks further down examine the field and its guard. // // In JIT mode we only intrinsify the field accessor if there is no active // guard, meaning the state transition has reached its final `kDynamicCid` // state (where it stays). // // If we intrinsify, the intrinsified code therefore does not depend on the // field guard and we do not add it to the guarded fields via // [ParsedFunction::AddToGuardedFields]. if (CompilerState::Current().should_clone_fields()) { field = field.CloneFromOriginal(); } // We only graph intrinsify implicit instance getters/setter for now. if (!field.is_instance()) return false; if (is_getter) { // We don't support complex getter cases. if (field.is_late() || field.needs_load_guard()) return false; if (FlowGraphCompiler::IsPotentialUnboxedField(field)) { if (function.HasUnboxedReturnValue()) { // In AOT mode: Unboxed fields contain the unboxed value and can be // returned in unboxed form. ASSERT(FLAG_precompiled_mode); } else { // In JIT mode: Unboxed fields contain a mutable box which we cannot // return. return false; } } else { // If the field is boxed, then we can either return the box directly or // unbox it and return unboxed representation. return true; } } else { ASSERT(is_setter); // We don't support complex setter cases. if (field.is_final()) { RELEASE_ASSERT(field.is_late()); return false; } // We only support cases where there is no need to check for argument types. // // Normally we have to check the parameter type. ASSERT(function.NeedsArgumentTypeChecks()); // Covariant parameter types have to be checked, which we don't support. if (field.is_covariant() || field.is_generic_covariant_impl()) return false; // If the incoming value is unboxed we only support real unboxed fields to // avoid the need for boxing (which we cannot do in the intrinsic). if (function.HasUnboxedParameters()) { ASSERT(FLAG_precompiled_mode); if (!FlowGraphCompiler::IsUnboxedField(field)) { return false; } } // We don't support field guards in graph intrinsic stores. if (!FLAG_precompiled_mode && field.guarded_cid() != kDynamicCid) { return false; } } return true; } struct IntrinsicDesc { const char* class_name; const char* function_name; }; struct LibraryInstrinsicsDesc { Library& library; IntrinsicDesc* intrinsics; }; #define DEFINE_INTRINSIC(class_name, function_name, destination, fp) \ {#class_name, #function_name}, // clang-format off static IntrinsicDesc core_intrinsics[] = { CORE_LIB_INTRINSIC_LIST(DEFINE_INTRINSIC) CORE_INTEGER_LIB_INTRINSIC_LIST(DEFINE_INTRINSIC) GRAPH_CORE_INTRINSICS_LIST(DEFINE_INTRINSIC) {nullptr, nullptr}, }; static IntrinsicDesc math_intrinsics[] = { MATH_LIB_INTRINSIC_LIST(DEFINE_INTRINSIC) GRAPH_MATH_LIB_INTRINSIC_LIST(DEFINE_INTRINSIC) {nullptr, nullptr}, }; static IntrinsicDesc typed_data_intrinsics[] = { GRAPH_TYPED_DATA_INTRINSICS_LIST(DEFINE_INTRINSIC) {nullptr, nullptr}, }; static IntrinsicDesc developer_intrinsics[] = { DEVELOPER_LIB_INTRINSIC_LIST(DEFINE_INTRINSIC) {nullptr, nullptr}, }; static IntrinsicDesc internal_intrinsics[] = { INTERNAL_LIB_INTRINSIC_LIST(DEFINE_INTRINSIC) {nullptr, nullptr}, }; // clang-format on void Intrinsifier::InitializeState() { Thread* thread = Thread::Current(); Zone* zone = thread->zone(); Library& lib = Library::Handle(zone); Class& cls = Class::Handle(zone); Function& func = Function::Handle(zone); String& str = String::Handle(zone); String& str2 = String::Handle(zone); Error& error = Error::Handle(zone); static const intptr_t kNumLibs = 5; LibraryInstrinsicsDesc intrinsics[kNumLibs] = { {Library::Handle(zone, Library::CoreLibrary()), core_intrinsics}, {Library::Handle(zone, Library::MathLibrary()), math_intrinsics}, {Library::Handle(zone, Library::TypedDataLibrary()), typed_data_intrinsics}, {Library::Handle(zone, Library::DeveloperLibrary()), developer_intrinsics}, {Library::Handle(zone, Library::InternalLibrary()), internal_intrinsics}, }; for (intptr_t i = 0; i < kNumLibs; i++) { lib = intrinsics[i].library.ptr(); for (IntrinsicDesc* intrinsic = intrinsics[i].intrinsics; intrinsic->class_name != nullptr; intrinsic++) { func = Function::null(); if (strcmp(intrinsic->class_name, "::") == 0) { str = String::New(intrinsic->function_name); func = lib.LookupFunctionAllowPrivate(str); } else { str = String::New(intrinsic->class_name); cls = lib.LookupClassAllowPrivate(str); ASSERT(FLAG_precompiled_mode || !cls.IsNull()); if (!cls.IsNull()) { error = cls.EnsureIsFinalized(thread); if (!error.IsNull()) { OS::PrintErr("%s\n", error.ToErrorCString()); } ASSERT(error.IsNull()); str = String::New(intrinsic->function_name); if (intrinsic->function_name[0] == '.') { str2 = String::New(intrinsic->class_name); str = String::Concat(str2, str); } func = cls.LookupFunctionAllowPrivate(str); } } if (!func.IsNull()) { func.set_is_intrinsic(true); } else if (!FLAG_precompiled_mode) { FATAL2("Intrinsifier failed to find method %s in class %s\n", intrinsic->function_name, intrinsic->class_name); } } } #undef SETUP_FUNCTION } // Returns true if fall-through code can be omitted. bool Intrinsifier::Intrinsify(const ParsedFunction& parsed_function, FlowGraphCompiler* compiler) { const Function& function = parsed_function.function(); if (!CanIntrinsify(function)) { return false; } if (GraphIntrinsifier::GraphIntrinsify(parsed_function, compiler)) { return compiler->intrinsic_slow_path_label()->IsUnused(); } #if !defined(HASH_IN_OBJECT_HEADER) // These two are more complicated on 32 bit platforms, where the // identity hash is not stored in the header of the object. We // therefore don't intrinsify them, falling back on the native C++ // implementations. if (function.recognized_kind() == MethodRecognizer::kObject_getHash || function.recognized_kind() == MethodRecognizer::kObject_setHash) { return false; } #endif #if !defined(PRODUCT) #define EMIT_BREAKPOINT() compiler->assembler()->Breakpoint() #else #define EMIT_BREAKPOINT() #endif #define EMIT_CASE(class_name, function_name, enum_name, fp) \ case MethodRecognizer::k##enum_name: { \ compiler->assembler()->Comment("Intrinsic"); \ Label normal_ir_body; \ const auto size_before = compiler->assembler()->CodeSize(); \ AsmIntrinsifier::enum_name(compiler->assembler(), &normal_ir_body); \ const auto size_after = compiler->assembler()->CodeSize(); \ if (size_before == size_after) return false; \ if (function.HasUnboxedParameters()) { \ FATAL1("Unsupported unboxed parameters in asm intrinsic %s", \ function.ToFullyQualifiedCString()); \ } \ if (function.HasUnboxedReturnValue()) { \ FATAL1("Unsupported unboxed return value in asm intrinsic %s", \ function.ToFullyQualifiedCString()); \ } \ if (!normal_ir_body.IsBound()) { \ EMIT_BREAKPOINT(); \ return true; \ } \ return false; \ } switch (function.recognized_kind()) { ALL_INTRINSICS_NO_INTEGER_LIB_LIST(EMIT_CASE); default: break; } switch (function.recognized_kind()) { CORE_INTEGER_LIB_INTRINSIC_LIST(EMIT_CASE) default: break; } #undef EMIT_BREAKPOINT #undef EMIT_INTRINSIC return false; } } // namespace compiler } // namespace dart