| // 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 && |
| FlowGraphCompiler::SupportsUnboxedInt64()) { |
| 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 (Field::ShouldCloneFields()) { |
| 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 the getter cannot return an unboxed value |
| // either (if it could, we would know the field itself can be unboxed). |
| RELEASE_ASSERT(!function.HasUnboxedReturnValue()); |
| } |
| } 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()); |
| // Dynamic call sites will go to dyn:set:* instead. |
| ASSERT(!function.CanReceiveDynamicInvocation()); |
| // 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[] = { |
| TYPED_DATA_LIB_INTRINSIC_LIST(DEFINE_INTRINSIC) |
| 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.raw(); |
| 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; |
| } |
| |
| ASSERT(!compiler->flow_graph().IsCompiledForOsr()); |
| 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 (!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 |