Commit ee9e9423 authored by Jakob Gruber's avatar Jakob Gruber Committed by Commit Bot

[js-function] Systematic predicates to reason about available code

This CL adds more systematic predicates to JSFunction to reason about
available code kinds. Introduced terminology:

- Attached code kinds are accessible directly from the JSFunction
  itself.
- Available code kinds are either attached or accessible indirectly.
- The Active code kind is the one that would be executed on the next
  function execution.

Bug: v8:8888
Change-Id: I9468884dfe97a6cb73f8329b2b6cb62b622d3e7a
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2345966
Commit-Queue: Jakob Gruber <jgruber@chromium.org>
Auto-Submit: Jakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Reviewed-by: 's avatarLeszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/master@{#69325}
parent 388a317c
......@@ -89,50 +89,40 @@ class Flags final {
mask_type mask_;
};
#define DEFINE_OPERATORS_FOR_FLAGS(Type) \
inline Type operator&( \
Type::flag_type lhs, \
Type::flag_type rhs)ALLOW_UNUSED_TYPE V8_WARN_UNUSED_RESULT; \
inline Type operator&(Type::flag_type lhs, Type::flag_type rhs) { \
return Type(lhs) & rhs; \
} \
inline Type operator&( \
Type::flag_type lhs, \
const Type& rhs)ALLOW_UNUSED_TYPE V8_WARN_UNUSED_RESULT; \
inline Type operator&(Type::flag_type lhs, const Type& rhs) { \
return rhs & lhs; \
} \
inline void operator&(Type::flag_type lhs, \
Type::mask_type rhs)ALLOW_UNUSED_TYPE; \
inline void operator&(Type::flag_type lhs, Type::mask_type rhs) {} \
inline Type operator|(Type::flag_type lhs, Type::flag_type rhs) \
ALLOW_UNUSED_TYPE V8_WARN_UNUSED_RESULT; \
inline Type operator|(Type::flag_type lhs, Type::flag_type rhs) { \
return Type(lhs) | rhs; \
} \
inline Type operator|(Type::flag_type lhs, const Type& rhs) \
ALLOW_UNUSED_TYPE V8_WARN_UNUSED_RESULT; \
inline Type operator|(Type::flag_type lhs, const Type& rhs) { \
return rhs | lhs; \
} \
inline void operator|(Type::flag_type lhs, Type::mask_type rhs) \
ALLOW_UNUSED_TYPE; \
inline void operator|(Type::flag_type lhs, Type::mask_type rhs) {} \
inline Type operator^(Type::flag_type lhs, Type::flag_type rhs) \
ALLOW_UNUSED_TYPE V8_WARN_UNUSED_RESULT; \
inline Type operator^(Type::flag_type lhs, Type::flag_type rhs) { \
return Type(lhs) ^ rhs; \
} \
inline Type operator^(Type::flag_type lhs, const Type& rhs) \
ALLOW_UNUSED_TYPE V8_WARN_UNUSED_RESULT; \
inline Type operator^(Type::flag_type lhs, const Type& rhs) { \
return rhs ^ lhs; \
} \
inline void operator^(Type::flag_type lhs, Type::mask_type rhs) \
ALLOW_UNUSED_TYPE; \
inline void operator^(Type::flag_type lhs, Type::mask_type rhs) {} \
inline Type operator~(Type::flag_type val)ALLOW_UNUSED_TYPE; \
inline Type operator~(Type::flag_type val) { return ~Type(val); }
#define DEFINE_OPERATORS_FOR_FLAGS(Type) \
ALLOW_UNUSED_TYPE V8_WARN_UNUSED_RESULT inline constexpr Type operator&( \
Type::flag_type lhs, Type::flag_type rhs) { \
return Type(lhs) & rhs; \
} \
ALLOW_UNUSED_TYPE V8_WARN_UNUSED_RESULT inline constexpr Type operator&( \
Type::flag_type lhs, const Type& rhs) { \
return rhs & lhs; \
} \
ALLOW_UNUSED_TYPE inline void operator&(Type::flag_type lhs, \
Type::mask_type rhs) {} \
ALLOW_UNUSED_TYPE V8_WARN_UNUSED_RESULT inline constexpr Type operator|( \
Type::flag_type lhs, Type::flag_type rhs) { \
return Type(lhs) | rhs; \
} \
ALLOW_UNUSED_TYPE V8_WARN_UNUSED_RESULT inline constexpr Type operator|( \
Type::flag_type lhs, const Type& rhs) { \
return rhs | lhs; \
} \
ALLOW_UNUSED_TYPE inline void operator|(Type::flag_type lhs, \
Type::mask_type rhs) {} \
ALLOW_UNUSED_TYPE V8_WARN_UNUSED_RESULT inline constexpr Type operator^( \
Type::flag_type lhs, Type::flag_type rhs) { \
return Type(lhs) ^ rhs; \
} \
ALLOW_UNUSED_TYPE V8_WARN_UNUSED_RESULT inline constexpr Type operator^( \
Type::flag_type lhs, const Type& rhs) { \
return rhs ^ lhs; \
} \
ALLOW_UNUSED_TYPE inline void operator^(Type::flag_type lhs, \
Type::mask_type rhs) {} \
ALLOW_UNUSED_TYPE inline constexpr Type operator~(Type::flag_type val) { \
return ~Type(val); \
}
} // namespace base
} // namespace v8
......
......@@ -362,11 +362,13 @@ void Code::initialize_flags(CodeKind kind, bool has_unwinding_info,
}
inline bool Code::is_interpreter_trampoline_builtin() const {
bool is_interpreter_trampoline =
(builtin_index() == Builtins::kInterpreterEntryTrampoline ||
builtin_index() == Builtins::kInterpreterEnterBytecodeAdvance ||
builtin_index() == Builtins::kInterpreterEnterBytecodeDispatch);
return is_interpreter_trampoline;
// Check for kNoBuiltinId first to abort early when the current Code object
// is not a builtin.
const int index = builtin_index();
return index != Builtins::kNoBuiltinId &&
(index == Builtins::kInterpreterEntryTrampoline ||
index == Builtins::kInterpreterEnterBytecodeAdvance ||
index == Builtins::kInterpreterEnterBytecodeDispatch);
}
inline bool Code::checks_optimization_marker() const {
......
......@@ -5,6 +5,7 @@
#ifndef V8_OBJECTS_CODE_KIND_H_
#define V8_OBJECTS_CODE_KIND_H_
#include "src/base/flags.h"
#include "src/flags/flags.h"
namespace v8 {
......@@ -35,32 +36,36 @@ enum class CodeKind {
#undef DEFINE_CODE_KIND_ENUM
};
#define V(name) +1
#define V(...) +1
static constexpr int kCodeKindCount = CODE_KIND_LIST(V);
#undef V
const char* CodeKindToString(CodeKind kind);
inline bool CodeKindIsInterpretedJSFunction(CodeKind kind) {
inline constexpr bool CodeKindIsInterpretedJSFunction(CodeKind kind) {
return kind == CodeKind::INTERPRETED_FUNCTION;
}
inline bool CodeKindIsNativeContextIndependentJSFunction(CodeKind kind) {
inline constexpr bool CodeKindIsNativeContextIndependentJSFunction(
CodeKind kind) {
return kind == CodeKind::NATIVE_CONTEXT_INDEPENDENT;
}
inline bool CodeKindIsBuiltinOrJSFunction(CodeKind kind) {
return kind == CodeKind::BUILTIN || kind == CodeKind::INTERPRETED_FUNCTION ||
kind == CodeKind::OPTIMIZED_FUNCTION ||
inline constexpr bool CodeKindIsOptimizedJSFunction(CodeKind kind) {
return kind == CodeKind::OPTIMIZED_FUNCTION ||
kind == CodeKind::NATIVE_CONTEXT_INDEPENDENT;
}
inline bool CodeKindIsOptimizedJSFunction(CodeKind kind) {
return kind == CodeKind::OPTIMIZED_FUNCTION ||
kind == CodeKind::NATIVE_CONTEXT_INDEPENDENT;
inline constexpr bool CodeKindIsJSFunction(CodeKind kind) {
return kind == CodeKind::INTERPRETED_FUNCTION ||
CodeKindIsOptimizedJSFunction(kind);
}
inline bool CodeKindCanDeoptimize(CodeKind kind) {
inline constexpr bool CodeKindIsBuiltinOrJSFunction(CodeKind kind) {
return kind == CodeKind::BUILTIN || CodeKindIsJSFunction(kind);
}
inline constexpr bool CodeKindCanDeoptimize(CodeKind kind) {
// Even though NCI code does not deopt by itself at the time of writing,
// tests may trigger deopts manually and thus we cannot make a narrower
// distinction here.
......@@ -72,6 +77,32 @@ inline CodeKind CodeKindForTopTier() {
: CodeKind::OPTIMIZED_FUNCTION;
}
// The dedicated CodeKindFlag enum represents all code kinds in a format
// suitable for bit sets.
enum class CodeKindFlag {
#define V(name) name = 1 << static_cast<int>(CodeKind::name),
CODE_KIND_LIST(V)
#undef V
};
STATIC_ASSERT(kCodeKindCount <= kInt32Size * kBitsPerByte);
inline constexpr CodeKindFlag CodeKindToCodeKindFlag(CodeKind kind) {
#define V(name) kind == CodeKind::name ? CodeKindFlag::name:
return CODE_KIND_LIST(V) CodeKindFlag::INTERPRETED_FUNCTION;
#undef V
}
// CodeKinds represents a set of CodeKind.
using CodeKinds = base::Flags<CodeKindFlag>;
DEFINE_OPERATORS_FOR_FLAGS(CodeKinds)
static constexpr CodeKinds kJSFunctionCodeKindsMask{
CodeKindFlag::INTERPRETED_FUNCTION | CodeKindFlag::OPTIMIZED_FUNCTION |
CodeKindFlag::NATIVE_CONTEXT_INDEPENDENT};
static constexpr CodeKinds kOptimizedJSFunctionCodeKindsMask{
CodeKindFlag::OPTIMIZED_FUNCTION |
CodeKindFlag::NATIVE_CONTEXT_INDEPENDENT};
} // namespace internal
} // namespace v8
......
......@@ -36,23 +36,120 @@ ClosureFeedbackCellArray JSFunction::closure_feedback_cell_array() const {
return ClosureFeedbackCellArray::cast(raw_feedback_cell().value());
}
// Code objects that are marked for deoptimization are not considered to be
// optimized. This is because the JSFunction might have been already
// deoptimized but its code() still needs to be unlinked, which will happen on
// its next activation.
// TODO(jupvfranco): rename this function. Maybe RunOptimizedCode,
// or IsValidOptimizedCode.
bool JSFunction::IsOptimized() {
return is_compiled() && CodeKindIsOptimizedJSFunction(code().kind()) &&
!code().marked_for_deoptimization();
CodeKinds JSFunction::GetAttachedCodeKinds() const {
CodeKinds result;
// Note: There's a special case when bytecode has been aged away. After
// flushing the bytecode, the JSFunction will still have the interpreter
// entry trampoline attached, but the bytecode is no longer available.
if (code().is_interpreter_trampoline_builtin()) {
result |= CodeKindFlag::INTERPRETED_FUNCTION;
}
const CodeKind kind = code().kind();
if (!CodeKindIsOptimizedJSFunction(kind) ||
code().marked_for_deoptimization()) {
DCHECK_EQ((result & ~kJSFunctionCodeKindsMask), 0);
return result;
}
DCHECK(CodeKindIsOptimizedJSFunction(kind));
result |= CodeKindToCodeKindFlag(kind);
DCHECK_EQ((result & ~kJSFunctionCodeKindsMask), 0);
return result;
}
CodeKinds JSFunction::GetAvailableCodeKinds() const {
CodeKinds result = GetAttachedCodeKinds();
if ((result & CodeKindFlag::INTERPRETED_FUNCTION) == 0) {
// The SharedFunctionInfo could have attached bytecode.
if (shared().HasBytecodeArray()) {
result |= CodeKindFlag::INTERPRETED_FUNCTION;
}
}
if ((result & kOptimizedJSFunctionCodeKindsMask) == 0) {
// Check the optimized code cache.
if (has_feedback_vector() && feedback_vector().has_optimized_code() &&
!feedback_vector().optimized_code().marked_for_deoptimization()) {
Code code = feedback_vector().optimized_code();
DCHECK(CodeKindIsOptimizedJSFunction(code.kind()));
result |= CodeKindToCodeKindFlag(code.kind());
}
}
DCHECK_EQ((result & ~kJSFunctionCodeKindsMask), 0);
return result;
}
bool JSFunction::HasOptimizedCode() {
return IsOptimized() ||
(has_feedback_vector() && feedback_vector().has_optimized_code() &&
!feedback_vector().optimized_code().marked_for_deoptimization());
bool JSFunction::HasAttachedOptimizedCode() const {
CodeKinds result = GetAttachedCodeKinds();
return (result & kOptimizedJSFunctionCodeKindsMask) != 0;
}
bool JSFunction::HasAvailableOptimizedCode() const {
CodeKinds result = GetAvailableCodeKinds();
return (result & kOptimizedJSFunctionCodeKindsMask) != 0;
}
namespace {
// Returns false if no highest tier exists (i.e. the function is not compiled),
// otherwise returns true and sets highest_tier.
bool HighestTierOf(CodeKinds kinds, CodeKind* highest_tier) {
DCHECK_EQ((kinds & ~kJSFunctionCodeKindsMask), 0);
if ((kinds & CodeKindFlag::OPTIMIZED_FUNCTION) != 0) {
*highest_tier = CodeKind::OPTIMIZED_FUNCTION;
return true;
} else if ((kinds & CodeKindFlag::NATIVE_CONTEXT_INDEPENDENT) != 0) {
*highest_tier = CodeKind::NATIVE_CONTEXT_INDEPENDENT;
return true;
} else if ((kinds & CodeKindFlag::INTERPRETED_FUNCTION) != 0) {
*highest_tier = CodeKind::INTERPRETED_FUNCTION;
return true;
}
DCHECK_EQ(kinds, 0);
return false;
}
} // namespace
bool JSFunction::ActiveTierIsIgnition() const {
CodeKind highest_tier;
if (!HighestTierOf(GetAvailableCodeKinds(), &highest_tier)) return false;
bool result = (highest_tier == CodeKind::INTERPRETED_FUNCTION);
DCHECK_IMPLIES(result,
code().is_interpreter_trampoline_builtin() ||
(CodeKindIsOptimizedJSFunction(code().kind()) &&
code().marked_for_deoptimization()) ||
(code().builtin_index() == Builtins::kCompileLazy &&
shared().IsInterpreted()));
return result;
}
bool JSFunction::ActiveTierIsTurbofan() const {
CodeKind highest_tier;
if (!HighestTierOf(GetAvailableCodeKinds(), &highest_tier)) return false;
bool result = highest_tier == CodeKind::OPTIMIZED_FUNCTION;
DCHECK_IMPLIES(result, !code().marked_for_deoptimization());
return result;
}
bool JSFunction::ActiveTierIsNCI() const {
CodeKind highest_tier;
if (!HighestTierOf(GetAvailableCodeKinds(), &highest_tier)) return false;
bool result = highest_tier == CodeKind::NATIVE_CONTEXT_INDEPENDENT;
DCHECK_IMPLIES(result, !code().marked_for_deoptimization());
return result;
}
// TODO(jgruber): Replace these functions with the called functions.
bool JSFunction::IsOptimized() { return HasAttachedOptimizedCode(); }
bool JSFunction::HasOptimizedCode() { return HasAvailableOptimizedCode(); }
bool JSFunction::IsInterpreted() { return ActiveTierIsIgnition(); }
bool JSFunction::HasOptimizationMarker() {
return has_feedback_vector() && feedback_vector().has_optimization_marker();
}
......@@ -62,14 +159,6 @@ void JSFunction::ClearOptimizationMarker() {
feedback_vector().ClearOptimizationMarker();
}
// Optimized code marked for deoptimization will tier back down to running
// interpreted on its next activation, and already doesn't count as IsOptimized.
bool JSFunction::IsInterpreted() {
return is_compiled() && (code().is_interpreter_trampoline_builtin() ||
(CodeKindIsOptimizedJSFunction(code().kind()) &&
code().marked_for_deoptimization()));
}
bool JSFunction::ChecksOptimizationMarker() {
return code().checks_optimization_marker();
}
......
......@@ -5,6 +5,7 @@
#ifndef V8_OBJECTS_JS_FUNCTION_H_
#define V8_OBJECTS_JS_FUNCTION_H_
#include "src/objects/code-kind.h"
#include "src/objects/js-objects.h"
#include "torque-generated/class-definitions-tq.h"
#include "torque-generated/field-offsets-tq.h"
......@@ -89,6 +90,30 @@ class JSFunction : public JSFunctionOrBoundFunction {
// a Code object or a BytecodeArray.
V8_EXPORT_PRIVATE AbstractCode abstract_code();
// The predicates for querying code kinds related to this function have
// specific terminology:
//
// - Attached: all code kinds that are directly attached to this JSFunction
// object.
// - Available: all code kinds that are either attached or available through
// indirect means such as the feedback vector's optimized code cache.
// - Active: the single code kind that would be executed if this function
// were called in its current state. Note that there may not be an active
// code kind if the function is not compiled.
//
// Note: code objects that are marked_for_deoptimization are not part of the
// attached/available/active sets. This is because the JSFunction might have
// been already deoptimized but its code() still needs to be unlinked, which
// will happen on its next activation.
// True, iff any generated code kind is attached/available to this function.
bool HasAttachedOptimizedCode() const;
bool HasAvailableOptimizedCode() const;
bool ActiveTierIsIgnition() const;
bool ActiveTierIsTurbofan() const;
bool ActiveTierIsNCI() const;
// Tells whether or not this function is interpreted.
//
// Note: function->IsInterpreted() does not necessarily return the same value
......@@ -96,10 +121,6 @@ class JSFunction : public JSFunctionOrBoundFunction {
// optimized.
V8_EXPORT_PRIVATE bool IsInterpreted();
// Tells whether or not this function checks its optimization marker in its
// feedback vector.
bool ChecksOptimizationMarker();
// Tells whether or not this function holds optimized code.
//
// Note: Returning false does not necessarily mean that this function hasn't
......@@ -111,6 +132,10 @@ class JSFunction : public JSFunctionOrBoundFunction {
// feedback vector.
bool HasOptimizedCode();
// Tells whether or not this function checks its optimization marker in its
// feedback vector.
bool ChecksOptimizationMarker();
// Tells whether or not this function has a (non-zero) optimization marker.
bool HasOptimizationMarker();
......@@ -277,6 +302,20 @@ class JSFunction : public JSFunctionOrBoundFunction {
// Hide JSFunctionOrBoundFunction::kHeaderSize to avoid confusion.
static const int kHeaderSize;
// Returns the set of code kinds of compilation artifacts (bytecode,
// generated code) attached to this JSFunction.
// Note that attached code objects that are marked_for_deoptimization are not
// included in this set.
// TODO(jgruber): Currently at most one code kind can be attached. Consider
// adding a NOT_COMPILED kind and changing this function to simply return the
// kind if this becomes more convenient in the future.
CodeKinds GetAttachedCodeKinds() const;
// As above, but also considers locations outside of this JSFunction. For
// example the optimized code cache slot in the feedback vector, and the
// shared function info.
CodeKinds GetAvailableCodeKinds() const;
public:
static constexpr int kSizeWithoutPrototype = kPrototypeOrInitialMapOffset;
static constexpr int kSizeWithPrototype = FieldOffsets::kHeaderSize;
......
......@@ -195,11 +195,8 @@ class SharedFunctionInfo : public HeapObject {
// a Code object or a BytecodeArray.
inline AbstractCode abstract_code();
// Tells whether or not this shared function info is interpreted.
//
// Note: function->IsInterpreted() does not necessarily return the same value
// as function->shared()->IsInterpreted() because the closure might have been
// optimized.
// Tells whether or not this shared function info has an attached
// BytecodeArray.
inline bool IsInterpreted() const;
// Set up the link between shared function info and the script. The shared
......
......@@ -10,6 +10,7 @@
#include "src/execution/isolate-inl.h"
#include "src/init/bootstrapper.h"
#include "src/logging/counters.h"
#include "src/objects/code-kind.h"
#include "src/objects/js-regexp-inl.h"
#include "src/snapshot/context-deserializer.h"
#include "src/snapshot/context-serializer.h"
......@@ -250,18 +251,13 @@ void Snapshot::ClearReconstructableDataForSerialization(
continue; // Don't clear extensions, they cannot be recompiled.
}
// Also, clear out feedback vectors, or any optimized code.
// Note that checking for fun.IsOptimized() || fun.IsInterpreted() is
// not sufficient because the function can have a feedback vector even
// if it is not compiled (e.g. when the bytecode was flushed). On the
// other hand, only checking for the feedback vector is not sufficient
// because there can be multiple functions sharing the same feedback
// vector. So we need all these checks.
if (fun.IsOptimized() || fun.IsInterpreted() ||
!fun.raw_feedback_cell().value().IsUndefined()) {
// Also, clear out feedback vectors and any optimized code.
if (CodeKindIsJSFunction(fun.code().kind())) {
fun.set_code(*BUILTIN_CODE(isolate, CompileLazy));
}
if (!fun.raw_feedback_cell().value().IsUndefined()) {
fun.raw_feedback_cell().set_value(
i::ReadOnlyRoots(isolate).undefined_value());
fun.set_code(isolate->builtins()->builtin(i::Builtins::kCompileLazy));
}
#ifdef DEBUG
if (clear_recompilable_data) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment