/src/hermes/include/hermes/VM/Interpreter.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) Meta Platforms, Inc. and affiliates. |
3 | | * |
4 | | * This source code is licensed under the MIT license found in the |
5 | | * LICENSE file in the root directory of this source tree. |
6 | | */ |
7 | | |
8 | | #ifndef HERMES_VM_INTERPRETER_H |
9 | | #define HERMES_VM_INTERPRETER_H |
10 | | #include <cstdint> |
11 | | |
12 | | #include "hermes/VM/Runtime.h" |
13 | | |
14 | | class CodeBlock; |
15 | | |
16 | | namespace hermes { |
17 | | namespace vm { |
18 | | /// This class is a convenience wrapper for the interpreter implementation that |
19 | | /// needs access to the private fields of Runtime, but doesn't belong in |
20 | | /// Runtime. |
21 | | class Interpreter { |
22 | | public: |
23 | | /// Allocate a generator for the specified function and the specified |
24 | | /// environment. \param funcIndex function index in the global function table. |
25 | | static CallResult<PseudoHandle<JSGenerator>> createGenerator_RJS( |
26 | | Runtime &runtime, |
27 | | RuntimeModule *runtimeModule, |
28 | | unsigned funcIndex, |
29 | | Handle<Environment> envHandle, |
30 | | NativeArgs args); |
31 | | |
32 | | /// Suspend the generator function and yield to the caller. |
33 | | /// \param resumeIP Is the IP where the generator should resume from when it |
34 | | /// is resumed. |
35 | | static void saveGenerator( |
36 | | Runtime &runtime, |
37 | | PinnedHermesValue *frameRegs, |
38 | | const Inst *resumeIP); |
39 | | |
40 | | /// Slow path for ReifyArguments resReg, lazyReg |
41 | | /// It assumes that the fast path has handled the case when 'lazyReg' is |
42 | | /// already initialized. It creates a new 'arguments' object and populates it |
43 | | /// with the argument values. |
44 | | static CallResult<Handle<Arguments>> reifyArgumentsSlowPath( |
45 | | Runtime &runtime, |
46 | | Handle<Callable> curFunction, |
47 | | bool strictMode); |
48 | | |
49 | | /// Slow path for GetArgumentsPropByVal resReg, propNameReg, lazyReg. |
50 | | /// |
51 | | /// It assumes that the "fast path" has already taken care of the case when |
52 | | /// the 'lazyReg' is still uninitialized and 'propNameReg' is a valid integer |
53 | | /// index less than 'argCount'. So we arrive here when either of these is |
54 | | /// true: |
55 | | /// - 'lazyReg' is initialized. |
56 | | /// - index is >= argCount |
57 | | /// - index is not an integer |
58 | | /// In the first case we simply perform a normal property get. In the latter |
59 | | /// we ultimately need to reify the arguments object, but we try to avoid |
60 | | /// doing that by: |
61 | | /// - checking if the property name is "length". In that case we can just |
62 | | /// return the value. |
63 | | /// - checking if the property name in the prototype is not an accessor. In |
64 | | /// that |
65 | | /// case we can also just return the value read from the prototype. |
66 | | /// Only if all else fails, we reify. |
67 | | /// The FRAME in question is obtained from \p runtime, and the registers |
68 | | /// \p lazyReg and \p valueReg are passed directly to make this function |
69 | | /// easier to use outside the interpeter. |
70 | | static CallResult<PseudoHandle<>> getArgumentsPropByValSlowPath_RJS( |
71 | | Runtime &runtime, |
72 | | PinnedHermesValue *lazyReg, |
73 | | PinnedHermesValue *valueReg, |
74 | | Handle<Callable> curFunction, |
75 | | bool strictMode); |
76 | | |
77 | | /// Implement the slow path of OpCode::Call/CallLong/Construct/ConstructLong. |
78 | | /// The callee frame must have been initialized already and the fast path |
79 | | /// (calling a \c JSFunction) must have been handled. |
80 | | /// This handles the rest of the cases (native function, bound funcation, and |
81 | | /// not even a function). |
82 | | /// \param callTarget the register containing the function object |
83 | | /// \return ExecutionStatus::EXCEPTION if the call threw. |
84 | | static CallResult<PseudoHandle<>> handleCallSlowPath( |
85 | | Runtime &runtime, |
86 | | PinnedHermesValue *callTarget); |
87 | | |
88 | | /// Fast path to get primitive value \p base's own properties by name \p id |
89 | | /// without boxing. |
90 | | /// Primitive own properties are properties fetching values from primitive |
91 | | /// value itself. |
92 | | /// Currently the only primitive own property is String.prototype.length. |
93 | | /// If the fast path property does not exist, return Empty. |
94 | | static PseudoHandle<> |
95 | | tryGetPrimitiveOwnPropertyById(Runtime &runtime, Handle<> base, SymbolID id); |
96 | | |
97 | | /// Implement OpCode::GetById/TryGetById when the base is not an object. |
98 | | static CallResult<PseudoHandle<>> |
99 | | getByIdTransient_RJS(Runtime &runtime, Handle<> base, SymbolID id); |
100 | | |
101 | | /// Fast path for getByValTransient() -- avoid boxing for \p base if it is |
102 | | /// string primitive and \p nameHandle is an array index. |
103 | | /// If the property does not exist, return Empty. |
104 | | static PseudoHandle<> |
105 | | getByValTransientFast(Runtime &runtime, Handle<> base, Handle<> nameHandle); |
106 | | |
107 | | /// Implement OpCode::GetByVal when the base is not an object. |
108 | | static CallResult<PseudoHandle<>> |
109 | | getByValTransient_RJS(Runtime &runtime, Handle<> base, Handle<> name); |
110 | | |
111 | | /// Implement OpCode::PutById/TryPutById when the base is not an object. |
112 | | static ExecutionStatus putByIdTransient_RJS( |
113 | | Runtime &runtime, |
114 | | Handle<> base, |
115 | | SymbolID id, |
116 | | Handle<> value, |
117 | | bool strictMode); |
118 | | |
119 | | /// Implement OpCode::PutByVal when the base is not an object. |
120 | | static ExecutionStatus putByValTransient_RJS( |
121 | | Runtime &runtime, |
122 | | Handle<> base, |
123 | | Handle<> name, |
124 | | Handle<> value, |
125 | | bool strictMode); |
126 | | |
127 | | /// Inlining this function is forbidden because it stores label values in a |
128 | | /// local static variable. Due to a bug in LLVM, it may sometimes be inlined |
129 | | /// anyway, so explicitly mark it as noinline. |
130 | | template <bool SingleStep, bool EnableCrashTrace> |
131 | | LLVM_ATTRIBUTE_NOINLINE static CallResult<HermesValue> interpretFunction( |
132 | | Runtime &runtime, |
133 | | InterpreterState &state); |
134 | | |
135 | | /// Populates an object with literal values from the object buffer. |
136 | | /// \param numLiterals the amount of literals to read from the buffer. |
137 | | /// \param keyBufferIndex the first element of the key buffer to read. |
138 | | /// \param valBufferIndex the first element of the val buffer to read. |
139 | | /// \return ExecutionStatus::EXCEPTION if the property definitions throw. |
140 | | static CallResult<PseudoHandle<>> createObjectFromBuffer( |
141 | | Runtime &runtime, |
142 | | CodeBlock *curCodeBlock, |
143 | | unsigned numLiterals, |
144 | | unsigned keyBufferIndex, |
145 | | unsigned valBufferIndex); |
146 | | |
147 | | /// Populates an array with literal values from the array buffer. |
148 | | /// \param numLiterals the amount of literals to read from the buffer. |
149 | | /// \param bufferIndex the first element of the buffer to read. |
150 | | /// \return ExecutionStatus::EXCEPTION if the property definitions throw. |
151 | | static CallResult<PseudoHandle<>> createArrayFromBuffer( |
152 | | Runtime &runtime, |
153 | | CodeBlock *curCodeBlock, |
154 | | unsigned numElements, |
155 | | unsigned numLiterals, |
156 | | unsigned bufferIndex); |
157 | | |
158 | | /// Implements global variable declaration as per ES2023 16.1.7.10.a.i. |
159 | | /// \return ExecutionStatus::EXCEPTION if the global object cannot be |
160 | | /// expanded. |
161 | | static ExecutionStatus declareGlobalVarImpl( |
162 | | Runtime &runtime, |
163 | | CodeBlock *curCodeBlock, |
164 | | const Inst *ip); |
165 | | |
166 | | #ifdef HERMES_ENABLE_DEBUGGER |
167 | | /// Wrapper around runDebugger() that reapplies the interpreter state. |
168 | | /// Constructs an interpreter state from the given \p codeBlock and \p ip. |
169 | | /// It then invokes the debugger and returns the new code block, and offset by |
170 | | /// reference, and updates frameRegs to its new value. Note this function is |
171 | | /// inline to allow the compiler to verify that the parameters do not escape, |
172 | | /// which might otherwise prevent them from being promoted to registers. |
173 | | LLVM_ATTRIBUTE_ALWAYS_INLINE |
174 | | static inline ExecutionStatus runDebuggerUpdatingState( |
175 | | Debugger::RunReason reason, |
176 | | Runtime &runtime, |
177 | | CodeBlock *&codeBlock, |
178 | | const Inst *&ip, |
179 | 0 | PinnedHermesValue *&frameRegs) { |
180 | | // Hack: if we are already debugging, do nothing. TODO: in the event that we |
181 | | // are already debugging and we get an async debugger request, abort the |
182 | | // current debugging command (e.g. eval something infinite). |
183 | 0 | if (runtime.debugger_.isDebugging()) |
184 | 0 | return ExecutionStatus::RETURNED; |
185 | 0 | uint32_t offset = codeBlock->getOffsetOf(ip); |
186 | 0 | InterpreterState state(codeBlock, offset); |
187 | 0 | ExecutionStatus status = runtime.debugger_.runDebugger(reason, state); |
188 | 0 | codeBlock = state.codeBlock; |
189 | 0 | ip = state.codeBlock->getOffsetPtr(state.offset); |
190 | 0 | frameRegs = &runtime.currentFrame_.getFirstLocalRef(); |
191 | 0 | return status; |
192 | 0 | } |
193 | | #endif |
194 | | |
195 | | //=========================================================================== |
196 | | // Out-of-line implementations of entire instructions. |
197 | | |
198 | | /// Partial implementation of ES6 18.2.1.1 |
199 | | /// `PerformEval(x, evalRealm, strictCaller=true, direct=true)`. |
200 | | /// The difference is that we don't support actual lexical scope, of course. |
201 | | static ExecutionStatus caseDirectEval( |
202 | | Runtime &runtime, |
203 | | PinnedHermesValue *frameRegs, |
204 | | const inst::Inst *ip); |
205 | | |
206 | | static ExecutionStatus casePutOwnByVal( |
207 | | Runtime &runtime, |
208 | | PinnedHermesValue *frameRegs, |
209 | | const inst::Inst *ip); |
210 | | |
211 | | static ExecutionStatus casePutOwnGetterSetterByVal( |
212 | | Runtime &runtime, |
213 | | PinnedHermesValue *frameRegs, |
214 | | const inst::Inst *ip); |
215 | | |
216 | | static ExecutionStatus caseIteratorBegin( |
217 | | Runtime &runtime, |
218 | | PinnedHermesValue *frameRegs, |
219 | | const inst::Inst *ip); |
220 | | static ExecutionStatus caseIteratorNext( |
221 | | Runtime &runtime, |
222 | | PinnedHermesValue *frameRegs, |
223 | | const inst::Inst *ip); |
224 | | |
225 | | static ExecutionStatus caseGetPNameList( |
226 | | Runtime &runtime, |
227 | | PinnedHermesValue *frameRegs, |
228 | | const Inst *ip); |
229 | | |
230 | | /// Evaluate callBuiltin and store the result in the register stack. it must |
231 | | /// must be invoked with CallBuiltin or CallBuiltinLong. \p op3 contains the |
232 | | /// value of operand3, which is the only difference in encoding between the |
233 | | /// two. |
234 | | static ExecutionStatus implCallBuiltin( |
235 | | Runtime &runtime, |
236 | | PinnedHermesValue *frameRegs, |
237 | | CodeBlock *curCodeBlock, |
238 | | uint32_t op3); |
239 | | }; |
240 | | |
241 | | #ifndef NDEBUG |
242 | | /// A tag used to instruct the output stream to dump more details about the |
243 | | /// HermesValue, like the length of the string, etc. |
244 | | struct DumpHermesValue { |
245 | | const HermesValue hv; |
246 | 0 | DumpHermesValue(HermesValue hv) : hv(hv) {} |
247 | | }; |
248 | | |
249 | | llvh::raw_ostream &operator<<(llvh::raw_ostream &OS, DumpHermesValue dhv); |
250 | | |
251 | | /// Dump the arguments from a callee frame. |
252 | | void dumpCallArguments( |
253 | | llvh::raw_ostream &OS, |
254 | | Runtime &runtime, |
255 | | StackFramePtr calleeFrame); |
256 | | |
257 | | #endif |
258 | | |
259 | | } // namespace vm |
260 | | } // namespace hermes |
261 | | |
262 | | #endif |