1 /*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "ecmascript/pgo_profiler/pgo_profiler.h"
17 #include <chrono>
18
19 #include "ecmascript/js_function.h"
20 #include "ecmascript/pgo_profiler/pgo_profiler_manager.h"
21
22 namespace panda::ecmascript {
ProfileCall(JSTaggedType func,JSTaggedType callTarget,int32_t pcOffset,SampleMode mode,int32_t incCount)23 void PGOProfiler::ProfileCall(JSTaggedType func, JSTaggedType callTarget, int32_t pcOffset, SampleMode mode,
24 int32_t incCount)
25 {
26 if (!isEnable_) {
27 return;
28 }
29 DISALLOW_GARBAGE_COLLECTION;
30 JSTaggedValue calleeFunc(callTarget);
31 if (!calleeFunc.IsJSFunction()) {
32 return;
33 }
34 if (!JSFunction::Cast(calleeFunc)->GetMethod().IsMethod()) {
35 return;
36 }
37 auto calleeMethod = Method::Cast(JSFunction::Cast(calleeFunc)->GetMethod());
38 JSTaggedValue calleeRecordNameValue = JSFunction::Cast(calleeFunc)->GetRecordName();
39 if (calleeRecordNameValue.IsHole()) {
40 return;
41 }
42 CString calleeRecordName = ConvertToString(calleeRecordNameValue);
43 if (recordInfos_->AddMethod(calleeRecordName, calleeMethod, mode, incCount)) {
44 methodCount_++;
45 }
46 JSTaggedValue currentFunc(func);
47 if (pcOffset > 0 && currentFunc.IsJSFunction() && JSFunction::Cast(currentFunc)->GetMethod().IsMethod()) {
48 auto currentMethod = Method::Cast(JSFunction::Cast(currentFunc)->GetMethod());
49 JSTaggedValue currentRecordNameValue = JSFunction::Cast(currentFunc)->GetRecordName();
50 if (currentRecordNameValue.IsHole()) {
51 return;
52 }
53 if (calleeMethod->IsNativeWithCallField()) {
54 return;
55 }
56 CString currentRecordName = ConvertToString(currentRecordNameValue);
57 // Only mark the call in the same record now
58 if (currentRecordName != calleeRecordName) {
59 return;
60 }
61 auto currentMethodId = currentMethod->GetMethodId();
62 PGOSampleType calleeMethodOffset = PGOSampleType::CreateClassType(calleeMethod->GetMethodId().GetOffset());
63 recordInfos_->AddCallTargetType(currentRecordName, currentMethodId, pcOffset, calleeMethodOffset);
64 }
65 auto interval = std::chrono::system_clock::now() - saveTimestamp_;
66 // Merged every 10 methods and merge interval greater than minimal interval
67 if (methodCount_ >= MERGED_EVERY_COUNT && interval > MERGED_MIN_INTERVAL) {
68 LOG_ECMA(DEBUG) << "Sample: post task to save profiler";
69 PGOProfilerManager::GetInstance()->Merge(this);
70 PGOProfilerManager::GetInstance()->AsynSave();
71 SetSaveTimestamp(std::chrono::system_clock::now());
72 methodCount_ = 0;
73 }
74 }
75
ProfileOpType(JSTaggedType func,int32_t offset,uint32_t type)76 void PGOProfiler::ProfileOpType(JSTaggedType func, int32_t offset, uint32_t type)
77 {
78 if (!isEnable_) {
79 return;
80 }
81
82 DISALLOW_GARBAGE_COLLECTION;
83 JSTaggedValue funcValue(func);
84 if (funcValue.IsJSFunction() && JSFunction::Cast(funcValue)->GetMethod().IsMethod()) {
85 auto jsMethod = Method::Cast(JSFunction::Cast(funcValue)->GetMethod());
86 JSTaggedValue recordNameValue = JSFunction::Cast(funcValue)->GetRecordName();
87 if (recordNameValue.IsHole()) {
88 return;
89 }
90 CString recordName = ConvertToString(recordNameValue);
91 recordInfos_->AddType(recordName, jsMethod->GetMethodId(), offset, PGOSampleType(type));
92 }
93 }
94
ProfileDefineClass(JSThread * thread,JSTaggedType func,int32_t offset,JSTaggedType ctor)95 void PGOProfiler::ProfileDefineClass(JSThread *thread, JSTaggedType func, int32_t offset, JSTaggedType ctor)
96 {
97 if (!isEnable_) {
98 return;
99 }
100
101 DISALLOW_GARBAGE_COLLECTION;
102 JSTaggedValue funcValue(func);
103 if (funcValue.IsJSFunction()) {
104 JSFunction *funcFunction = JSFunction::Cast(funcValue);
105 JSTaggedValue recordNameValue = funcFunction->GetRecordName();
106 if (recordNameValue.IsHole()) {
107 return;
108 }
109 CString recordName = ConvertToString(recordNameValue);
110
111 auto method = funcFunction->GetMethod();
112 if (!method.IsMethod()) {
113 return;
114 }
115 auto jsMethod = Method::Cast(method);
116 auto funcMethodId = jsMethod->GetMethodId();
117
118 JSHandle<JSTaggedValue> ctorValue(thread, JSTaggedValue(ctor));
119 if (!ctorValue->IsJSFunction()) {
120 return;
121 }
122 JSFunction *ctorFunction = JSFunction::Cast(ctorValue->GetTaggedObject());
123 auto ctorMethod = ctorFunction->GetMethod();
124 if (!ctorMethod.IsMethod()) {
125 return;
126 }
127 auto ctorJSMethod = Method::Cast(ctorMethod);
128 int32_t ctorMethodId = static_cast<int32_t>(ctorJSMethod->GetMethodId().GetOffset());
129 auto currentType = PGOSampleType::CreateClassType(ctorMethodId);
130
131 auto superFuncValue = JSTaggedValue::GetPrototype(thread, ctorValue);
132 RETURN_IF_ABRUPT_COMPLETION(thread);
133 PGOSampleType superType = PGOSampleType::CreateClassType(0);
134 if (superFuncValue.IsJSFunction()) {
135 auto superFuncFunction = JSFunction::Cast(superFuncValue);
136 if (superFuncFunction->GetMethod().IsMethod()) {
137 auto superMethod = Method::Cast(superFuncFunction->GetMethod());
138 auto superMethodId = superMethod->GetMethodId().GetOffset();
139 superType = PGOSampleType::CreateClassType(superMethodId);
140 }
141 }
142 recordInfos_->AddDefine(recordName, funcMethodId, offset, currentType, superType);
143
144 auto prototype = ctorFunction->GetProtoOrHClass();
145 if (!prototype.IsJSObject()) {
146 return;
147 }
148 auto prototypeObj = JSObject::Cast(prototype);
149 auto prototypeHClass = JSTaggedType(prototypeObj->GetClass());
150 recordInfos_->AddLayout(currentType, prototypeHClass, PGOObjKind::PROTOTYPE);
151
152 auto ctorHClass = JSTaggedType(ctorFunction->GetJSHClass());
153 recordInfos_->AddLayout(currentType, ctorHClass, PGOObjKind::CONSTRUCTOR);
154 }
155 }
156
ProfileCreateObject(JSTaggedType func,int32_t offset,JSTaggedType newObj,int32_t traceId)157 void PGOProfiler::ProfileCreateObject(JSTaggedType func, int32_t offset, JSTaggedType newObj, int32_t traceId)
158 {
159 if (!isEnable_) {
160 return;
161 }
162
163 DISALLOW_GARBAGE_COLLECTION;
164 JSTaggedValue funcValue(func);
165 if (funcValue.IsJSFunction()) {
166 JSFunction *funcFunction = JSFunction::Cast(funcValue);
167 JSTaggedValue recordNameValue = funcFunction->GetRecordName();
168 if (recordNameValue.IsHole()) {
169 return;
170 }
171 CString recordName = ConvertToString(recordNameValue);
172
173 auto method = funcFunction->GetMethod();
174 if (!method.IsMethod()) {
175 return;
176 }
177 auto jsMethod = Method::Cast(method);
178 auto funcMethodId = jsMethod->GetMethodId();
179
180 auto newObjValue = JSTaggedValue(newObj);
181 if (!newObjValue.IsJSObject()) {
182 return;
183 }
184 auto newHClass = JSObject::Cast(newObjValue) ->GetJSHClass();
185 if (newHClass->IsJSArray()) {
186 auto array = JSArray::Cast(newObjValue);
187 auto currentType = PGOSampleType::CreateClassType(array->GetTraceIndex());
188 auto superType = PGOSampleType::CreateClassType(0);
189 recordInfos_->AddDefine(recordName, funcMethodId, offset, currentType, superType);
190 PGOObjKind kind = PGOObjKind::ELEMENT;
191 recordInfos_->AddLayout(currentType, JSTaggedType(newHClass), kind);
192 } else {
193 InsertLiteralId(JSTaggedType(newHClass), traceId);
194 auto currentType = PGOSampleType::CreateClassType(traceId);
195 auto superType = PGOSampleType::CreateClassType(0);
196 recordInfos_->AddDefine(recordName, funcMethodId, offset, currentType, superType);
197 PGOObjKind kind = PGOObjKind::LOCAL;
198 recordInfos_->AddLayout(currentType, JSTaggedType(newHClass), kind);
199 }
200 }
201 }
202
ProfileObjLayout(JSThread * thread,JSTaggedType func,int32_t offset,JSTaggedType object,bool store)203 void PGOProfiler::ProfileObjLayout(JSThread *thread, JSTaggedType func, int32_t offset, JSTaggedType object, bool store)
204 {
205 if (!isEnable_) {
206 return;
207 }
208
209 DISALLOW_GARBAGE_COLLECTION;
210 JSTaggedValue funcValue(func);
211 if (funcValue.IsJSFunction()) {
212 JSFunction *funcFunction = JSFunction::Cast(funcValue);
213 auto method = funcFunction->GetMethod();
214 if (!method.IsMethod()) {
215 return;
216 }
217 auto jsMethod = Method::Cast(method);
218 JSTaggedValue recordNameValue = funcFunction->GetRecordName();
219 if (recordNameValue.IsHole()) {
220 return;
221 }
222 CString recordName = ConvertToString(recordNameValue);
223
224 auto holder = JSTaggedValue(object);
225 auto hclass = holder.GetTaggedObject()->GetClass();
226 auto ctor = JSTaggedValue::Undefined();
227 PGOObjKind kind = PGOObjKind::LOCAL;
228 if (hclass->IsClassPrototype()) {
229 ctor = JSObject::GetCtorFromPrototype(thread, holder);
230 kind = PGOObjKind::PROTOTYPE;
231 } else if (hclass->IsClassConstructor()) {
232 ctor = holder;
233 kind = PGOObjKind::CONSTRUCTOR;
234 } else if (hclass->IsLiteral()) {
235 auto iter = traceIds_.find(JSTaggedType(hclass));
236 if (iter != traceIds_.end()) {
237 PGOObjectInfo info(ClassType(iter->second), kind);
238 auto methodId = jsMethod->GetMethodId();
239 recordInfos_->AddObjectInfo(recordName, methodId, offset, info);
240 if (store) {
241 auto type = PGOSampleType::CreateClassType(iter->second);
242 recordInfos_->AddLayout(type, JSTaggedType(hclass), kind);
243 }
244 }
245 return;
246 } else if (hclass->IsJSArray()) {
247 kind = PGOObjKind::ELEMENT;
248 auto array = JSArray::Cast(holder);
249 if (array->GetTraceIndex() != 0) {
250 PGOObjectInfo info(ClassType(array->GetTraceIndex()), kind);
251 auto methodId = jsMethod->GetMethodId();
252 recordInfos_->AddObjectInfo(recordName, methodId, offset, info);
253 if (store) {
254 auto type = PGOSampleType::CreateClassType(array->GetTraceIndex());
255 recordInfos_->AddLayout(type, JSTaggedType(hclass), kind);
256 }
257 }
258 return;
259 } else {
260 auto prototype = hclass->GetProto();
261 ctor = JSObject::GetCtorFromPrototype(thread, prototype);
262 }
263
264 if (ctor.IsJSFunction()) {
265 auto ctorFunc = JSFunction::Cast(ctor);
266 auto ctorMethod = ctorFunc->GetMethod();
267 if (!ctorMethod.IsMethod()) {
268 return;
269 }
270 auto ctorJSMethod = Method::Cast(ctorMethod);
271 auto methodId = ctorJSMethod->GetMethodId();
272 PGOObjectInfo info(ClassType(methodId.GetOffset()), kind);
273 recordInfos_->AddObjectInfo(recordName, jsMethod->GetMethodId(), offset, info);
274 if (store) {
275 PGOSampleType type = PGOSampleType::CreateClassType(methodId.GetOffset());
276 recordInfos_->AddLayout(type, JSTaggedType(hclass), kind);
277 }
278 }
279 }
280 }
281
InsertLiteralId(JSTaggedType hclass,int32_t traceId)282 void PGOProfiler::InsertLiteralId(JSTaggedType hclass, int32_t traceId)
283 {
284 if (!isEnable_) {
285 return;
286 }
287 auto iter = traceIds_.find(hclass);
288 if (iter != traceIds_.end() && iter->second != traceId) {
289 traceIds_.erase(iter);
290 }
291 traceIds_.emplace(hclass, traceId);
292 }
293
ProcessReferences(const WeakRootVisitor & visitor)294 void PGOProfiler::ProcessReferences(const WeakRootVisitor &visitor)
295 {
296 if (!isEnable_) {
297 return;
298 }
299 for (auto iter = traceIds_.begin(); iter != traceIds_.end();) {
300 JSTaggedType object = iter->first;
301 auto fwd = visitor(reinterpret_cast<TaggedObject *>(object));
302 if (fwd == nullptr) {
303 iter = traceIds_.erase(iter);
304 continue;
305 }
306 if (fwd != reinterpret_cast<TaggedObject *>(object)) {
307 UNREACHABLE();
308 }
309 ++iter;
310 }
311 }
312 } // namespace panda::ecmascript
313