* Copyright (c) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ecmascript/jit/compile_decision.h"
#include "ecmascript/jspandafile/js_pandafile.h"
#include "ecmascript/ic/profile_type_info.h"
#include "ecmascript/platform/aot_crash_info.h"
namespace panda::ecmascript {
std::string GetFileName(const std::string& fileName, bool isApp)
{
if (isApp) {
std::string pathOnMobile;
std::string sanboxPath = panda::os::file::File::GetExtendedFilePath(AotCrashInfo::GetSandBoxPath());
if (ecmascript::RealPath(sanboxPath, pathOnMobile, false)) {
return pathOnMobile + "/method_compiled_by_jit.cfg";
} else {
LOG_JIT(ERROR) << "get method_compiled_by_jit.cfg path fail: " << sanboxPath;
return "";
}
}
return fileName;
}
void MethodNameCollector::Init(EcmaVM *vm)
{
if (vm == nullptr || isInit_) {
return;
}
if (!(vm->GetJSOptions().IsEnableJitMethodCollect())) {
return;
}
enable_ = true;
std::string fileName = vm->GetJSOptions().GetJitMethodPath();
fileName = GetFileName(fileName, vm->GetJSOptions().IsEnableAPPJIT());
if (fileName.empty()) {
return;
}
file_.open(fileName.c_str(), std::ofstream::out | std::ofstream::trunc);
if (!file_.is_open()) {
LOG_JIT(ERROR) << "open method_compiled_by_jit.cfg fail: " << fileName;
return;
}
LOG_JIT(INFO) << "open method_compiled_by_jit.cfg succ: " << fileName;
isInit_ = true;
}
void MethodNameCollector::Collect(const std::string& methodFullName) const
{
if (enable_ && isInit_) {
file_ << methodFullName << std::endl;
}
}
MethodNameCollector::~MethodNameCollector()
{
if (enable_ && isInit_) {
ASSERT(file_.is_open());
file_.close();
}
isInit_ = false;
enable_ = false;
}
void MethodNameFilter::Init(EcmaVM *vm)
{
if (vm == nullptr || isInit_) {
return;
}
if (!(vm->GetJSOptions().IsEnableJitMethodFilter())) {
return;
}
enable_ = true;
std::string fileName = vm->GetJSOptions().GetJitMethodPath();
fileName = GetFileName(fileName, vm->GetJSOptions().IsEnableAPPJIT());
if (fileName.empty()) {
return;
}
std::ifstream file(fileName.c_str());
if (!file.is_open()) {
LOG_JIT(INFO) << "open method_compiled_by_jit.cfg fail: " << fileName;
return;
}
LOG_JIT(INFO) << "open method_compiled_by_jit.cfg succ: " << fileName;
std::string methodFullName;
while (getline(file, methodFullName)) {
if (methodFullName.empty()) {
continue;
}
methodFullNames.insert(methodFullName);
}
if (methodFullNames.empty()) {
LOG_JIT(INFO) << "the number of method names is 0.";
return;
}
isInit_ = true;
}
bool MethodNameFilter::NeedCompiledByJit(const std::string& methodFullName) const
{
if (enable_ && isInit_) {
return methodFullNames.find(methodFullName) != methodFullNames.end();
}
return true;
}
MethodNameFilter::~MethodNameFilter()
{
methodFullNames.clear();
enable_ = false;
isInit_ = false;
}
MethodNameCollector CompileDecision::methodNameCollector;
MethodNameFilter CompileDecision::methodNameFilter;
CompileDecision::CompileDecision(EcmaVM *vm, JSHandle<JSFunction> &jsFunction, CompilerTier tier,
int32_t osrOffset, JitCompileMode mode) : vm_(vm), jsFunction_(jsFunction),
tier_(tier), osrOffset_(osrOffset), compileMode_(mode) { }
CString CompileDecision::GetMethodInfo() const
{
uint32_t codeSize = GetCodeSize();
return GetMethodName() + ", bytecode size:" + ToCString(codeSize);
}
CString CompileDecision::GetMethodName() const
{
JSThread *thread = vm_->GetJSThread();
Method *method = Method::Cast(jsFunction_->GetMethod(thread).GetTaggedObject());
ASSERT(method != nullptr);
auto jSPandaFile = method->GetJSPandaFile(thread);
CString fileDesc;
if (jSPandaFile != nullptr) {
fileDesc = jSPandaFile->GetJSPandaFileDesc();
}
return fileDesc + ":" + method->GetRecordNameStr(thread) + "." + CString(method->GetMethodName(thread));
}
uint32_t CompileDecision::GetCodeSize() const
{
JSThread *thread = vm_->GetJSThread();
Method *method = Method::Cast(jsFunction_->GetMethod(thread).GetTaggedObject());
return method->GetCodeSize(thread);
}
bool CompileDecision::Decision()
{
return IsGoodCompilationRequest();
}
bool CompileDecision::IsGoodCompilationRequest() const
{
if (!CheckJsFunctionStatus()) {
return false;
}
if (!IsJsFunctionSupportCompile()) {
DisableJitCompile();
return false;
}
if (!CheckVmState()) {
return false;
}
return true;
}
bool CompileDecision::IsJsFunctionSupportCompile() const
{
JSThread *thread = vm_->GetJSThread();
JSTaggedValue funcEnv = jsFunction_->GetLexicalEnv(thread);
JSTaggedValue globalEnv = BaseEnv::Cast(funcEnv.GetTaggedObject())->GetGlobalEnv(thread);
if (globalEnv.IsHole()) {
return false;
}
if (!IsSupportFunctionKind()) {
return false;
}
uint32_t maxSize = 9000;
if (vm_->GetJSOptions().IsEnableJitFastCompile()) {
maxSize = 15;
}
if (GetCodeSize() > maxSize && !(vm_->GetJSOptions().IsEnableForceJitCompileMain() && compileMode_.IsSync())) {
LOG_JIT(DEBUG) << tier_ << "skip jit task, as too large:" << GetMethodInfo();
return false;
}
Method *method = Method::Cast(jsFunction_->GetMethod(vm_->GetJSThread()).GetTaggedObject());
if (vm_->IsEnableOsr() && osrOffset_ != MachineCode::INVALID_OSR_OFFSET &&
method->HasCatchBlock(vm_->GetJSThread())) {
LOG_JIT(DEBUG) << "skip jit task, as osr does not support catch blocks: " << GetMethodInfo();
return false;
}
#if ECMASCRIPT_ENABLE_ARK_STEED
if (method->HasCatchBlock(vm_->GetJSThread())) {
LOG_JIT(DEBUG) << "skip jit task, as ArkSteed does not support catch blocks: " << GetMethodInfo();
return false;
}
#endif
methodNameCollector.Collect(std::string(GetMethodName()));
if (!methodNameFilter.NeedCompiledByJit(std::string(GetMethodName()))) {
LOG_JIT(DEBUG) << "skip jit task, as not the compilation target:" << GetMethodInfo();
return false;
}
return true;
}
bool CompileDecision::IsSupportFunctionKind() const
{
Method *method = Method::Cast(jsFunction_->GetMethod(vm_->GetJSThread()).GetTaggedObject());
if (jsFunction_.GetTaggedValue().IsJSSharedFunction()) {
LOG_JIT(DEBUG) << tier_ << "method does not support compile shared function:" << GetMethodInfo();
return false;
}
FunctionKind kind = method->GetFunctionKind();
switch (kind) {
case FunctionKind::NORMAL_FUNCTION:
case FunctionKind::GETTER_FUNCTION:
case FunctionKind::SETTER_FUNCTION:
case FunctionKind::ARROW_FUNCTION:
case FunctionKind::BASE_CONSTRUCTOR:
case FunctionKind::CLASS_CONSTRUCTOR:
case FunctionKind::DERIVED_CONSTRUCTOR:
case FunctionKind::NONE_FUNCTION:
return true;
default:
break;
}
LOG_JIT(DEBUG) << tier_ << "method does not support jit:" << GetMethodInfo() << ", kind:" << static_cast<int>(kind);
return false;
}
bool CompileDecision::CheckJsFunctionStatus() const
{
if (tier_.IsFastJit() && jsFunction_->IsJitCompiling()) {
return false;
}
if (tier_.IsBaseLine() && jsFunction_->IsBaselinejitCompiling()) {
return false;
}
if (tier_.IsFastJit() && jsFunction_->IsCompiledCode()) {
JSTaggedValue machineCode = jsFunction_->GetMachineCode(vm_->GetJSThread());
if (machineCode.IsMachineCodeObject() &&
MachineCode::Cast(machineCode.GetTaggedObject())->GetOSROffset() == MachineCode::INVALID_OSR_OFFSET) {
return false;
}
return true;
}
if (tier_.IsBaseLine() && !jsFunction_->GetBaselineCode(vm_->GetJSThread()).IsUndefined()) {
return false;
}
return true;
}
void CompileDecision::DisableJitCompile() const
{
jsFunction_->SetJitHotnessCnt(vm_->GetJSThread(), ProfileTypeInfo::JIT_DISABLE_FLAG);
}
bool CompileDecision::CheckVmState() const
{
if (vm_->GetJSThread()->IsMachineCodeLowMemory()) {
LOG_JIT(DEBUG) << tier_ << "skip jit task, as low code memory:" << GetMethodInfo();
return false;
}
return true;
}
}