Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | Copyright 2015 Google Inc. All rights reserved. |
3 | | |
4 | | Licensed under the Apache License, Version 2.0 (the "License"); |
5 | | you may not use this file except in compliance with the License. |
6 | | You may obtain a copy of the License at |
7 | | |
8 | | http://www.apache.org/licenses/LICENSE-2.0 |
9 | | |
10 | | Unless required by applicable law or agreed to in writing, software |
11 | | distributed under the License is distributed on an "AS IS" BASIS, |
12 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | See the License for the specific language governing permissions and |
14 | | limitations under the License. |
15 | | */ |
16 | | |
17 | | #include <cassert> |
18 | | #include <cmath> |
19 | | |
20 | | #include <memory> |
21 | | #include <set> |
22 | | #include <string> |
23 | | |
24 | | #include "desugarer.h" |
25 | | #include "json.h" |
26 | | #include "json.hpp" |
27 | | #include "md5.h" |
28 | | #include "parser.h" |
29 | | #include "ryml_std.hpp" // include this before any other ryml header |
30 | | #include "ryml.hpp" |
31 | | #include "state.h" |
32 | | #include "static_analysis.h" |
33 | | #include "string_utils.h" |
34 | | #include "vm.h" |
35 | | |
36 | | namespace jsonnet::internal { |
37 | | |
38 | | /** Macro that dumps an error and aborts the program regardless of |
39 | | * whether NDEBUG is defined. This should be used to mark codepaths |
40 | | * as unreachable. |
41 | | */ |
42 | | #define JSONNET_UNREACHABLE() \ |
43 | 0 | do { \ |
44 | 0 | std::cerr << __FILE__ << ":" << __LINE__ \ |
45 | 0 | << ": INTERNAL ERROR: reached unreachable code path" \ |
46 | 0 | << std::endl; \ |
47 | 0 | abort(); \ |
48 | 0 | } while (0) |
49 | | |
50 | | using json = nlohmann::json; |
51 | | |
52 | | namespace { |
53 | | |
54 | | /** Turn a path e.g. "/a/b/c" into a dir, e.g. "/a/b/". If there is no path returns "". |
55 | | */ |
56 | | std::string dir_name(const std::string &path) |
57 | 176 | { |
58 | 176 | size_t last_slash = path.rfind('/'); |
59 | 176 | if (last_slash != std::string::npos) { |
60 | 0 | return path.substr(0, last_slash + 1); |
61 | 0 | } |
62 | 176 | return ""; |
63 | 176 | } |
64 | | |
65 | | /** Stack frames. |
66 | | * |
67 | | * Of these, FRAME_CALL is the most special, as it is the only frame the stack |
68 | | * trace (for errors) displays. |
69 | | */ |
70 | | enum FrameKind { |
71 | | FRAME_APPLY_TARGET, // e in e(...) |
72 | | FRAME_BINARY_LEFT, // a in a + b |
73 | | FRAME_BINARY_RIGHT, // b in a + b |
74 | | FRAME_BINARY_OP, // a + b, with a and b already calculated |
75 | | FRAME_BUILTIN_FILTER, // When executing std.filter, used to hold intermediate state. |
76 | | FRAME_BUILTIN_FORCE_THUNKS, // When forcing builtin args, holds intermediate state. |
77 | | FRAME_CALL, // Used any time we have switched location in user code. |
78 | | FRAME_ERROR, // e in error e |
79 | | FRAME_IF, // e in if e then a else b |
80 | | FRAME_IN_SUPER_ELEMENT, // e in 'e in super' |
81 | | FRAME_INDEX_TARGET, // e in e[x] |
82 | | FRAME_INDEX_INDEX, // e in x[e] |
83 | | FRAME_INVARIANTS, // Caches the thunks that need to be executed one at a time. |
84 | | FRAME_LOCAL, // Stores thunk bindings as we execute e in local ...; e |
85 | | FRAME_OBJECT, // Stores intermediate state as we execute es in { [e]: ..., [e]: ... } |
86 | | FRAME_OBJECT_COMP_ARRAY, // e in {f:a for x in e] |
87 | | FRAME_OBJECT_COMP_ELEMENT, // Stores intermediate state when building object |
88 | | FRAME_STRING_CONCAT, // Stores intermediate state while co-ercing objects |
89 | | FRAME_SUPER_INDEX, // e in super[e] |
90 | | FRAME_UNARY, // e in -e |
91 | | FRAME_BUILTIN_JOIN_STRINGS, // When executing std.join over strings, used to hold intermediate state. |
92 | | FRAME_BUILTIN_JOIN_ARRAYS, // When executing std.join over arrays, used to hold intermediate state. |
93 | | FRAME_BUILTIN_DECODE_UTF8, // When executing std.decodeUTF8, used to hold intermediate state. |
94 | | }; |
95 | | |
96 | | /** A frame on the stack. |
97 | | * |
98 | | * Every time a subterm is evaluated, we first push a new stack frame to |
99 | | * store the continuation. |
100 | | * |
101 | | * The stack frame is a bit like a tagged union, except not as memory |
102 | | * efficient. The set of member variables that are actually used depends on |
103 | | * the value of the member variable kind. |
104 | | * |
105 | | * If the stack frame is of kind FRAME_CALL, then it counts towards the |
106 | | * maximum number of stack frames allowed. Other stack frames are not |
107 | | * counted. This is because FRAME_CALL exists where there is a branch in |
108 | | * the code, e.g. the forcing of a thunk, evaluation of a field, calling a |
109 | | * function, etc. |
110 | | * |
111 | | * The stack is used to mark objects during garbage |
112 | | * collection, so HeapObjects not referred to from the stack may be |
113 | | * prematurely collected. |
114 | | */ |
115 | | struct Frame { |
116 | | /** Tag (tagged union). */ |
117 | | FrameKind kind; |
118 | | |
119 | | /** The code we were executing before. */ |
120 | | const AST *ast; |
121 | | |
122 | | /** The location of the code we were executing before. |
123 | | * |
124 | | * location == ast->location when ast != nullptr |
125 | | */ |
126 | | LocationRange location; |
127 | | |
128 | | /** Reuse this stack frame for the purpose of tail call optimization. */ |
129 | | bool tailCall; |
130 | | |
131 | | /** Used for a variety of purposes. */ |
132 | | Value val; |
133 | | |
134 | | /** Used for a variety of purposes. */ |
135 | | Value val2; |
136 | | |
137 | | /** Used for a variety of purposes. */ |
138 | | DesugaredObject::Fields::const_iterator fit; |
139 | | |
140 | | /** Used for a variety of purposes. */ |
141 | | std::map<const Identifier *, HeapSimpleObject::Field> objectFields; |
142 | | |
143 | | /** Used for a variety of purposes. */ |
144 | | unsigned elementId; |
145 | | |
146 | | /** Used for a variety of purposes. */ |
147 | | std::map<const Identifier *, HeapThunk *> elements; |
148 | | |
149 | | /** Used for a variety of purposes. */ |
150 | | std::vector<HeapThunk *> thunks; |
151 | | |
152 | | /** Used for accumulating a joined string. */ |
153 | | UString str; |
154 | | bool first; |
155 | | |
156 | | /** Used for accumulating bytes */ |
157 | | std::string bytes; |
158 | | |
159 | | /** The context is used in error messages to attempt to find a reasonable name for the |
160 | | * object, function, or thunk value being executed. If it is a thunk, it is filled |
161 | | * with the value when the frame terminates. |
162 | | */ |
163 | | HeapEntity *context; |
164 | | |
165 | | /** The lexically nearest object we are in, or nullptr. Note |
166 | | * that this is not the same as context, because we could be inside a function, |
167 | | * inside an object and then context would be the function, but self would still point |
168 | | * to the object. |
169 | | */ |
170 | | HeapObject *self; |
171 | | |
172 | | /** The "super" level of self. Sometimes, we look upwards in the |
173 | | * inheritance tree, e.g. via an explicit use of super, or because a given field |
174 | | * has been inherited. When evaluating a field from one of these super objects, |
175 | | * we need to bind self to the concrete object (so self must point |
176 | | * there) but uses of super should be resolved relative to the object whose |
177 | | * field we are evaluating. Thus, we keep a second field for that. This is |
178 | | * usually 0, unless we are evaluating a super object's field. |
179 | | */ |
180 | | unsigned offset; |
181 | | |
182 | | /** A set of variables introduced at this point. */ |
183 | | BindingFrame bindings; |
184 | | |
185 | | Frame(const FrameKind &kind, const AST *ast) |
186 | | : kind(kind), |
187 | | ast(ast), |
188 | | location(ast->location), |
189 | | tailCall(false), |
190 | | elementId(0), |
191 | | context(NULL), |
192 | | self(NULL), |
193 | | offset(0) |
194 | 123M | { |
195 | 123M | val.t = Value::NULL_TYPE; |
196 | 123M | val2.t = Value::NULL_TYPE; |
197 | 123M | } |
198 | | |
199 | | Frame(const FrameKind &kind, const LocationRange &location) |
200 | | : kind(kind), |
201 | | ast(nullptr), |
202 | | location(location), |
203 | | tailCall(false), |
204 | | elementId(0), |
205 | | context(NULL), |
206 | | self(NULL), |
207 | | offset(0) |
208 | 112M | { |
209 | 112M | val.t = Value::NULL_TYPE; |
210 | 112M | val2.t = Value::NULL_TYPE; |
211 | 112M | } |
212 | | |
213 | | /** Mark everything visible from this frame. */ |
214 | | void mark(Heap &heap) const |
215 | 49.3M | { |
216 | 49.3M | heap.markFrom(val); |
217 | 49.3M | heap.markFrom(val2); |
218 | 49.3M | if (context) |
219 | 30.0M | heap.markFrom(context); |
220 | 49.3M | if (self) |
221 | 29.7M | heap.markFrom(self); |
222 | 49.3M | for (const auto &bind : bindings) |
223 | 40.5M | heap.markFrom(bind.second); |
224 | 49.3M | for (const auto &el : elements) |
225 | 0 | heap.markFrom(el.second); |
226 | 49.3M | for (const auto &th : thunks) |
227 | 4.71M | heap.markFrom(th); |
228 | 49.3M | } |
229 | | |
230 | | bool isCall(void) const |
231 | 678M | { |
232 | 678M | return kind == FRAME_CALL; |
233 | 678M | } |
234 | | }; |
235 | | |
236 | | /** The stack holds all the stack frames and manages the stack frame limit. */ |
237 | | class Stack { |
238 | | /** How many call frames are on the stack. */ |
239 | | unsigned calls; |
240 | | |
241 | | /** How many call frames should be allowed before aborting the program. */ |
242 | | unsigned limit; |
243 | | |
244 | | /** The stack frames. */ |
245 | | std::vector<Frame> stack; |
246 | | |
247 | | public: |
248 | 8.23k | Stack(unsigned limit) : calls(0), limit(limit) {} |
249 | | |
250 | 8.23k | ~Stack(void) {} |
251 | | |
252 | | unsigned size(void) |
253 | 342M | { |
254 | 342M | return stack.size(); |
255 | 342M | } |
256 | | |
257 | | /** Search for the closest variable in scope that matches the given name. */ |
258 | | HeapThunk *lookUpVar(const Identifier *id) |
259 | 1.30G | { |
260 | 1.51G | for (int i = stack.size() - 1; i >= 0; --i) { |
261 | 1.51G | const auto &binds = stack[i].bindings; |
262 | 1.51G | auto it = binds.find(id); |
263 | 1.51G | if (it != binds.end()) { |
264 | 1.30G | return it->second; |
265 | 1.30G | } |
266 | 208M | if (stack[i].isCall()) |
267 | 0 | break; |
268 | 208M | } |
269 | 0 | return nullptr; |
270 | 1.30G | } |
271 | | |
272 | | /** Mark everything visible from the stack (any frame). */ |
273 | | void mark(Heap &heap) |
274 | 226k | { |
275 | 49.3M | for (const auto &f : stack) { |
276 | 49.3M | f.mark(heap); |
277 | 49.3M | } |
278 | 226k | } |
279 | | |
280 | | Frame &top(void) |
281 | 1.20G | { |
282 | 1.20G | return stack.back(); |
283 | 1.20G | } |
284 | | |
285 | | const Frame &top(void) const |
286 | 0 | { |
287 | 0 | return stack.back(); |
288 | 0 | } |
289 | | |
290 | | void pop(void) |
291 | 231M | { |
292 | 231M | if (top().isCall()) |
293 | 88.5M | calls--; |
294 | 231M | stack.pop_back(); |
295 | 231M | } |
296 | | |
297 | | /** Attempt to find a name for a given heap entity. This may not be possible, but we try |
298 | | * reasonably hard. We look in the bindings for a variable in the closest scope that |
299 | | * happens to point at the entity in question. Otherwise, the best we can do is use its |
300 | | * type. |
301 | | */ |
302 | | std::string getName(unsigned from_here, const HeapEntity *e) |
303 | 785k | { |
304 | 785k | std::string name; |
305 | 2.40M | for (int i = from_here - 1; i >= 0; --i) { |
306 | 2.40M | const auto &f = stack[i]; |
307 | 2.40M | for (const auto &pair : f.bindings) { |
308 | 1.15M | HeapThunk *thunk = pair.second; |
309 | 1.15M | if (!thunk->filled) |
310 | 414k | continue; |
311 | 742k | if (!thunk->content.isHeap()) |
312 | 143k | continue; |
313 | 598k | if (e != thunk->content.v.h) |
314 | 460k | continue; |
315 | 138k | name = encode_utf8(pair.first->name); |
316 | 138k | } |
317 | | // Do not go into the next call frame, keep local reasoning. |
318 | 2.40M | if (f.isCall()) |
319 | 781k | break; |
320 | 2.40M | } |
321 | | |
322 | 785k | if (name == "") |
323 | 646k | name = "anonymous"; |
324 | 785k | if (dynamic_cast<const HeapObject *>(e)) { |
325 | 501k | return "object <" + name + ">"; |
326 | 501k | } else if (auto *thunk = dynamic_cast<const HeapThunk *>(e)) { |
327 | 141k | if (thunk->name == nullptr) { |
328 | 14.1k | return ""; // Argument of builtin, or root (since top level functions). |
329 | 127k | } else { |
330 | 127k | return "thunk <" + encode_utf8(thunk->name->name) + ">"; |
331 | 127k | } |
332 | 142k | } else { |
333 | 142k | const auto *func = static_cast<const HeapClosure *>(e); |
334 | 142k | if (func->body == nullptr) { |
335 | 0 | return "builtin function <" + func->builtinName + ">"; |
336 | 0 | } |
337 | 142k | return "function <" + name + ">"; |
338 | 142k | } |
339 | 785k | } |
340 | | |
341 | | /** Dump the stack. |
342 | | * |
343 | | * This is useful to help debug the VM in gdb. It is virtual to stop it |
344 | | * being removed by the compiler. |
345 | | */ |
346 | | virtual void dump(void) |
347 | 0 | { |
348 | 0 | for (std::size_t i = 0; i < stack.size(); ++i) { |
349 | 0 | std::cout << "stack[" << i << "] = " << stack[i].location << " (" << stack[i].kind |
350 | 0 | << ")" << std::endl; |
351 | 0 | } |
352 | 0 | std::cout << std::endl; |
353 | 0 | } |
354 | | |
355 | | /** Creates the error object for throwing, and also populates it with the stack trace. |
356 | | */ |
357 | | RuntimeError makeError(const LocationRange &loc, const std::string &msg) |
358 | 5.73k | { |
359 | 5.73k | std::vector<TraceFrame> stack_trace; |
360 | 5.73k | stack_trace.push_back(TraceFrame(loc)); |
361 | 2.44M | for (int i = stack.size() - 1; i >= 0; --i) { |
362 | 2.43M | const auto &f = stack[i]; |
363 | 2.43M | if (f.isCall()) { |
364 | 785k | if (f.context != nullptr) { |
365 | | // Give the last line a name. |
366 | 785k | stack_trace[stack_trace.size() - 1].name = getName(i, f.context); |
367 | 785k | } |
368 | 785k | if (f.location.isSet() || f.location.file.length() > 0) |
369 | 637k | stack_trace.push_back(TraceFrame(f.location)); |
370 | 785k | } |
371 | 2.43M | } |
372 | 5.73k | return RuntimeError(stack_trace, msg); |
373 | 5.73k | } |
374 | | |
375 | | /** New (non-call) frame. */ |
376 | | template <class... Args> |
377 | | void newFrame(Args... args) |
378 | 145M | { |
379 | 145M | stack.emplace_back(args...); |
380 | 145M | } vm.cpp:void jsonnet::internal::(anonymous namespace)::Stack::newFrame<jsonnet::internal::(anonymous namespace)::FrameKind, jsonnet::internal::AST const*>(jsonnet::internal::(anonymous namespace)::FrameKind, jsonnet::internal::AST const*) Line | Count | Source | 378 | 123M | { | 379 | 123M | stack.emplace_back(args...); | 380 | 123M | } |
vm.cpp:void jsonnet::internal::(anonymous namespace)::Stack::newFrame<jsonnet::internal::(anonymous namespace)::FrameKind, jsonnet::internal::LocationRange>(jsonnet::internal::(anonymous namespace)::FrameKind, jsonnet::internal::LocationRange) Line | Count | Source | 378 | 22.1M | { | 379 | 22.1M | stack.emplace_back(args...); | 380 | 22.1M | } |
|
381 | | |
382 | | /** If there is a tailstrict annotated frame followed by some locals, pop them all. */ |
383 | | void tailCallTrimStack(void) |
384 | 90.0M | { |
385 | 91.9M | for (int i = stack.size() - 1; i >= 0; --i) { |
386 | 90.5M | switch (stack[i].kind) { |
387 | 25.6M | case FRAME_CALL: { |
388 | 25.6M | if (!stack[i].tailCall || stack[i].thunks.size() > 0) { |
389 | 24.9M | return; |
390 | 24.9M | } |
391 | | // Remove all stack frames including this one. |
392 | 2.51M | while (stack.size() > unsigned(i)) |
393 | 1.83M | stack.pop_back(); |
394 | 675k | calls--; |
395 | 675k | return; |
396 | 25.6M | } break; |
397 | | |
398 | 1.90M | case FRAME_LOCAL: break; |
399 | | |
400 | 63.0M | default: return; |
401 | 90.5M | } |
402 | 90.5M | } |
403 | 90.0M | } |
404 | | |
405 | | /** New call frame. */ |
406 | | void newCall(const LocationRange &loc, HeapEntity *context, HeapObject *self, unsigned offset, |
407 | | const BindingFrame &up_values) |
408 | 90.0M | { |
409 | 90.0M | tailCallTrimStack(); |
410 | 90.0M | if (calls >= limit) { |
411 | 1.49k | throw makeError(loc, "max stack frames exceeded."); |
412 | 1.49k | } |
413 | 90.0M | stack.emplace_back(FRAME_CALL, loc); |
414 | 90.0M | calls++; |
415 | 90.0M | top().context = context; |
416 | 90.0M | top().self = self; |
417 | 90.0M | top().offset = offset; |
418 | 90.0M | top().bindings = up_values; |
419 | 90.0M | top().tailCall = false; |
420 | | |
421 | 90.0M | #ifndef NDEBUG |
422 | 90.0M | for (const auto &bind : up_values) { |
423 | 74.7M | if (bind.second == nullptr) { |
424 | 0 | std::cerr << "INTERNAL ERROR: No binding for variable " |
425 | 0 | << encode_utf8(bind.first->name) << std::endl; |
426 | 0 | std::abort(); |
427 | 0 | } |
428 | 74.7M | } |
429 | 90.0M | #endif |
430 | 90.0M | } |
431 | | |
432 | | /** Look up the stack to find the self binding. */ |
433 | | void getSelfBinding(HeapObject *&self, unsigned &offset) |
434 | 71.4M | { |
435 | 71.4M | self = nullptr; |
436 | 71.4M | offset = 0; |
437 | 234M | for (int i = stack.size() - 1; i >= 0; --i) { |
438 | 234M | if (stack[i].isCall()) { |
439 | 71.4M | self = stack[i].self; |
440 | 71.4M | offset = stack[i].offset; |
441 | 71.4M | return; |
442 | 71.4M | } |
443 | 234M | } |
444 | 71.4M | } |
445 | | |
446 | | /** Look up the stack to see if we're running assertions for this object. */ |
447 | | bool alreadyExecutingInvariants(HeapObject *self) |
448 | 22.2M | { |
449 | 6.41G | for (int i = stack.size() - 1; i >= 0; --i) { |
450 | 6.39G | if (stack[i].kind == FRAME_INVARIANTS) { |
451 | 27.0M | if (stack[i].self == self) |
452 | 32.9k | return true; |
453 | 27.0M | } |
454 | 6.39G | } |
455 | 22.1M | return false; |
456 | 22.2M | } |
457 | | }; |
458 | | |
459 | | /** Typedef to save some typing. */ |
460 | | typedef std::map<std::string, VmExt> ExtMap; |
461 | | |
462 | | /** Typedef to save some typing. */ |
463 | | typedef std::map<std::string, std::string> StrMap; |
464 | | |
465 | | class Interpreter; |
466 | | |
467 | | typedef const AST *(Interpreter::*BuiltinFunc)(const LocationRange &loc, |
468 | | const std::vector<Value> &args); |
469 | | |
470 | | /** Holds the intermediate state during execution and implements the necessary functions to |
471 | | * implement the semantics of the language. |
472 | | * |
473 | | * The garbage collector used is a simple stop-the-world mark and sweep collector. It runs upon |
474 | | * memory allocation if the heap is large enough and has grown enough since the last collection. |
475 | | * All reachable entities have their mark field incremented. Then all entities with the old |
476 | | * mark are removed from the heap. |
477 | | */ |
478 | | class Interpreter { |
479 | | /** The heap. */ |
480 | | Heap heap; |
481 | | |
482 | | /** The value last computed. */ |
483 | | Value scratch; |
484 | | |
485 | | /** The stack. */ |
486 | | Stack stack; |
487 | | |
488 | | /** Used to create ASTs if needed. |
489 | | * |
490 | | * This is used at import time, and in a few other cases. |
491 | | */ |
492 | | Allocator *alloc; |
493 | | |
494 | | /** Used to "name" thunks created to cache imports. */ |
495 | | const Identifier *idImport; |
496 | | |
497 | | /** Used to "name" thunks created on the inside of an array. */ |
498 | | const Identifier *idArrayElement; |
499 | | |
500 | | /** Used to "name" thunks created to execute invariants. */ |
501 | | const Identifier *idInvariant; |
502 | | |
503 | | /** Placehodler name for internal AST. */ |
504 | | const Identifier *idInternal; |
505 | | |
506 | | /** Used to "name" thunks created to convert JSON to Jsonnet objects. */ |
507 | | const Identifier *idJsonObjVar; |
508 | | |
509 | | const Identifier *idEmpty; |
510 | | |
511 | | /** Used to refer to idJsonObjVar. */ |
512 | | const AST *jsonObjVar; |
513 | | |
514 | | /* Standard Library AST */ |
515 | | const DesugaredObject *stdlibAST; |
516 | | HeapObject *stdObject; |
517 | | |
518 | | struct ImportCacheValue { |
519 | | std::string foundHere; |
520 | | std::string content; |
521 | | |
522 | | /** Thunk to store cached result of execution. |
523 | | * |
524 | | * Null if this file was only ever successfully imported with importstr/importbin. |
525 | | */ |
526 | | HeapThunk *thunk; |
527 | | }; |
528 | | |
529 | | /** Cache for imported Jsonnet files. */ |
530 | | std::map<std::pair<std::string, UString>, ImportCacheValue *> cachedImports; |
531 | | |
532 | | /** External variables for std.extVar. */ |
533 | | ExtMap externalVars; |
534 | | |
535 | | /** The callback used for loading imported files. */ |
536 | | VmNativeCallbackMap nativeCallbacks; |
537 | | |
538 | | /** The callback used for loading imported files. */ |
539 | | JsonnetImportCallback *importCallback; |
540 | | |
541 | | /** User context pointer for the import callback. */ |
542 | | void *importCallbackContext; |
543 | | |
544 | | /** Builtin functions by name. */ |
545 | | typedef std::map<std::string, BuiltinFunc> BuiltinMap; |
546 | | BuiltinMap builtins; |
547 | | |
548 | | /** Source values by name. Source values are values (usually functions) |
549 | | * implemented as Jsonnet source which we use internally in the interpreter. |
550 | | * In a sense they are the opposite of builtins. */ |
551 | | typedef std::map<std::string, HeapThunk *> SourceFuncMap; |
552 | | SourceFuncMap sourceVals; |
553 | | /* Just for memory management. */ |
554 | | std::vector<std::unique_ptr<Identifier>> sourceFuncIds; |
555 | | |
556 | | RuntimeError makeError(const LocationRange &loc, const std::string &msg) |
557 | 4.23k | { |
558 | 4.23k | return stack.makeError(loc, msg); |
559 | 4.23k | } |
560 | | |
561 | | /** Create an object on the heap, maybe collect garbage. |
562 | | * \param T Something under HeapEntity |
563 | | * \returns The new object |
564 | | */ |
565 | | template <class T, class... Args> |
566 | | T *makeHeap(Args &&... args) |
567 | 203M | { |
568 | 203M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); |
569 | 203M | if (heap.checkHeap()) { // Do a GC cycle? |
570 | | // Avoid the object we just made being collected. |
571 | 226k | heap.markFrom(r); |
572 | | |
573 | | // Mark from the stack. |
574 | 226k | stack.mark(heap); |
575 | | |
576 | | // Mark from the scratch register |
577 | 226k | heap.markFrom(scratch); |
578 | | |
579 | | // Mark from cached imports |
580 | 226k | for (const auto &pair : cachedImports) { |
581 | 0 | HeapThunk *thunk = pair.second->thunk; |
582 | 0 | if (thunk != nullptr) |
583 | 0 | heap.markFrom(thunk); |
584 | 0 | } |
585 | | |
586 | 34.5M | for (const auto &sourceVal : sourceVals) { |
587 | 34.5M | heap.markFrom(sourceVal.second); |
588 | 34.5M | } |
589 | | |
590 | | // Delete unreachable objects. |
591 | 226k | heap.sweep(); |
592 | 226k | } |
593 | 203M | return r; |
594 | 203M | } Unexecuted instantiation: vm.cpp:jsonnet::internal::(anonymous namespace)::HeapThunk* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapThunk, jsonnet::internal::Identifier const*&, jsonnet::internal::(anonymous namespace)::HeapObject* const&, unsigned int const&, jsonnet::internal::AST const* const&>(jsonnet::internal::Identifier const*&, jsonnet::internal::(anonymous namespace)::HeapObject* const&, unsigned int const&, jsonnet::internal::AST const* const&) Unexecuted instantiation: vm.cpp:jsonnet::internal::(anonymous namespace)::HeapThunk* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapThunk, jsonnet::internal::Identifier const* const&, decltype(nullptr), int, decltype(nullptr)>(jsonnet::internal::Identifier const* const&, decltype(nullptr)&&, int&&, decltype(nullptr)&&) vm.cpp:jsonnet::internal::(anonymous namespace)::HeapArray* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapArray, std::__1::vector<jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::allocator<jsonnet::internal::(anonymous namespace)::HeapThunk*> > const&>(std::__1::vector<jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::allocator<jsonnet::internal::(anonymous namespace)::HeapThunk*> > const&) Line | Count | Source | 567 | 3.72M | { | 568 | 3.72M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 569 | 3.72M | if (heap.checkHeap()) { // Do a GC cycle? | 570 | | // Avoid the object we just made being collected. | 571 | 5.17k | heap.markFrom(r); | 572 | | | 573 | | // Mark from the stack. | 574 | 5.17k | stack.mark(heap); | 575 | | | 576 | | // Mark from the scratch register | 577 | 5.17k | heap.markFrom(scratch); | 578 | | | 579 | | // Mark from cached imports | 580 | 5.17k | for (const auto &pair : cachedImports) { | 581 | 0 | HeapThunk *thunk = pair.second->thunk; | 582 | 0 | if (thunk != nullptr) | 583 | 0 | heap.markFrom(thunk); | 584 | 0 | } | 585 | | | 586 | 791k | for (const auto &sourceVal : sourceVals) { | 587 | 791k | heap.markFrom(sourceVal.second); | 588 | 791k | } | 589 | | | 590 | | // Delete unreachable objects. | 591 | 5.17k | heap.sweep(); | 592 | 5.17k | } | 593 | 3.72M | return r; | 594 | 3.72M | } |
vm.cpp:jsonnet::internal::(anonymous namespace)::HeapString* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapString, std::__1::basic_string<char32_t, std::__1::char_traits<char32_t>, std::__1::allocator<char32_t> > const&>(std::__1::basic_string<char32_t, std::__1::char_traits<char32_t>, std::__1::allocator<char32_t> > const&) Line | Count | Source | 567 | 80.1M | { | 568 | 80.1M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 569 | 80.1M | if (heap.checkHeap()) { // Do a GC cycle? | 570 | | // Avoid the object we just made being collected. | 571 | 89.9k | heap.markFrom(r); | 572 | | | 573 | | // Mark from the stack. | 574 | 89.9k | stack.mark(heap); | 575 | | | 576 | | // Mark from the scratch register | 577 | 89.9k | heap.markFrom(scratch); | 578 | | | 579 | | // Mark from cached imports | 580 | 89.9k | for (const auto &pair : cachedImports) { | 581 | 0 | HeapThunk *thunk = pair.second->thunk; | 582 | 0 | if (thunk != nullptr) | 583 | 0 | heap.markFrom(thunk); | 584 | 0 | } | 585 | | | 586 | 13.7M | for (const auto &sourceVal : sourceVals) { | 587 | 13.7M | heap.markFrom(sourceVal.second); | 588 | 13.7M | } | 589 | | | 590 | | // Delete unreachable objects. | 591 | 89.9k | heap.sweep(); | 592 | 89.9k | } | 593 | 80.1M | return r; | 594 | 80.1M | } |
vm.cpp:jsonnet::internal::(anonymous namespace)::HeapThunk* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapThunk, jsonnet::internal::Identifier const*&, decltype(nullptr), int, decltype(nullptr)>(jsonnet::internal::Identifier const*&, decltype(nullptr)&&, int&&, decltype(nullptr)&&) Line | Count | Source | 567 | 365k | { | 568 | 365k | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 569 | 365k | if (heap.checkHeap()) { // Do a GC cycle? | 570 | | // Avoid the object we just made being collected. | 571 | 498 | heap.markFrom(r); | 572 | | | 573 | | // Mark from the stack. | 574 | 498 | stack.mark(heap); | 575 | | | 576 | | // Mark from the scratch register | 577 | 498 | heap.markFrom(scratch); | 578 | | | 579 | | // Mark from cached imports | 580 | 498 | for (const auto &pair : cachedImports) { | 581 | 0 | HeapThunk *thunk = pair.second->thunk; | 582 | 0 | if (thunk != nullptr) | 583 | 0 | heap.markFrom(thunk); | 584 | 0 | } | 585 | | | 586 | 76.1k | for (const auto &sourceVal : sourceVals) { | 587 | 76.1k | heap.markFrom(sourceVal.second); | 588 | 76.1k | } | 589 | | | 590 | | // Delete unreachable objects. | 591 | 498 | heap.sweep(); | 592 | 498 | } | 593 | 365k | return r; | 594 | 365k | } |
vm.cpp:jsonnet::internal::(anonymous namespace)::HeapClosure* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapClosure, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >, decltype(nullptr), int, std::__1::vector<jsonnet::internal::(anonymous namespace)::HeapClosure::Param, std::__1::allocator<jsonnet::internal::(anonymous namespace)::HeapClosure::Param> > const&, jsonnet::internal::AST*&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&>(std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&&, decltype(nullptr)&&, int&&, std::__1::vector<jsonnet::internal::(anonymous namespace)::HeapClosure::Param, std::__1::allocator<jsonnet::internal::(anonymous namespace)::HeapClosure::Param> > const&, jsonnet::internal::AST*&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) Line | Count | Source | 567 | 16.7M | { | 568 | 16.7M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 569 | 16.7M | if (heap.checkHeap()) { // Do a GC cycle? | 570 | | // Avoid the object we just made being collected. | 571 | 24.4k | heap.markFrom(r); | 572 | | | 573 | | // Mark from the stack. | 574 | 24.4k | stack.mark(heap); | 575 | | | 576 | | // Mark from the scratch register | 577 | 24.4k | heap.markFrom(scratch); | 578 | | | 579 | | // Mark from cached imports | 580 | 24.4k | for (const auto &pair : cachedImports) { | 581 | 0 | HeapThunk *thunk = pair.second->thunk; | 582 | 0 | if (thunk != nullptr) | 583 | 0 | heap.markFrom(thunk); | 584 | 0 | } | 585 | | | 586 | 3.73M | for (const auto &sourceVal : sourceVals) { | 587 | 3.73M | heap.markFrom(sourceVal.second); | 588 | 3.73M | } | 589 | | | 590 | | // Delete unreachable objects. | 591 | 24.4k | heap.sweep(); | 592 | 24.4k | } | 593 | 16.7M | return r; | 594 | 16.7M | } |
Unexecuted instantiation: vm.cpp:jsonnet::internal::(anonymous namespace)::HeapComprehensionObject* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapComprehensionObject, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&, jsonnet::internal::AST const*&, jsonnet::internal::Identifier const*&, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&>(std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&, jsonnet::internal::AST const*&, jsonnet::internal::Identifier const*&, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&) vm.cpp:jsonnet::internal::(anonymous namespace)::HeapThunk* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapThunk, decltype(nullptr), decltype(nullptr), int, jsonnet::internal::AST const*>(decltype(nullptr)&&, decltype(nullptr)&&, int&&, jsonnet::internal::AST const*&&) Line | Count | Source | 567 | 8.23k | { | 568 | 8.23k | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 569 | 8.23k | if (heap.checkHeap()) { // Do a GC cycle? | 570 | | // Avoid the object we just made being collected. | 571 | 0 | heap.markFrom(r); | 572 | | | 573 | | // Mark from the stack. | 574 | 0 | stack.mark(heap); | 575 | | | 576 | | // Mark from the scratch register | 577 | 0 | heap.markFrom(scratch); | 578 | | | 579 | | // Mark from cached imports | 580 | 0 | for (const auto &pair : cachedImports) { | 581 | 0 | HeapThunk *thunk = pair.second->thunk; | 582 | 0 | if (thunk != nullptr) | 583 | 0 | heap.markFrom(thunk); | 584 | 0 | } | 585 | |
| 586 | 0 | for (const auto &sourceVal : sourceVals) { | 587 | 0 | heap.markFrom(sourceVal.second); | 588 | 0 | } | 589 | | | 590 | | // Delete unreachable objects. | 591 | 0 | heap.sweep(); | 592 | 0 | } | 593 | 8.23k | return r; | 594 | 8.23k | } |
vm.cpp:jsonnet::internal::(anonymous namespace)::HeapThunk* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapThunk, jsonnet::internal::Identifier*, jsonnet::internal::(anonymous namespace)::HeapObject*&, int, jsonnet::internal::AST* const&>(jsonnet::internal::Identifier*&&, jsonnet::internal::(anonymous namespace)::HeapObject*&, int&&, jsonnet::internal::AST* const&) Line | Count | Source | 567 | 1.25M | { | 568 | 1.25M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 569 | 1.25M | if (heap.checkHeap()) { // Do a GC cycle? | 570 | | // Avoid the object we just made being collected. | 571 | 0 | heap.markFrom(r); | 572 | | | 573 | | // Mark from the stack. | 574 | 0 | stack.mark(heap); | 575 | | | 576 | | // Mark from the scratch register | 577 | 0 | heap.markFrom(scratch); | 578 | | | 579 | | // Mark from cached imports | 580 | 0 | for (const auto &pair : cachedImports) { | 581 | 0 | HeapThunk *thunk = pair.second->thunk; | 582 | 0 | if (thunk != nullptr) | 583 | 0 | heap.markFrom(thunk); | 584 | 0 | } | 585 | |
| 586 | 0 | for (const auto &sourceVal : sourceVals) { | 587 | 0 | heap.markFrom(sourceVal.second); | 588 | 0 | } | 589 | | | 590 | | // Delete unreachable objects. | 591 | 0 | heap.sweep(); | 592 | 0 | } | 593 | 1.25M | return r; | 594 | 1.25M | } |
vm.cpp:jsonnet::internal::(anonymous namespace)::HeapThunk* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapThunk, jsonnet::internal::Identifier const*&, jsonnet::internal::(anonymous namespace)::HeapObject*&, unsigned int&, jsonnet::internal::AST* const&>(jsonnet::internal::Identifier const*&, jsonnet::internal::(anonymous namespace)::HeapObject*&, unsigned int&, jsonnet::internal::AST* const&) Line | Count | Source | 567 | 53.4M | { | 568 | 53.4M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 569 | 53.4M | if (heap.checkHeap()) { // Do a GC cycle? | 570 | | // Avoid the object we just made being collected. | 571 | 55.1k | heap.markFrom(r); | 572 | | | 573 | | // Mark from the stack. | 574 | 55.1k | stack.mark(heap); | 575 | | | 576 | | // Mark from the scratch register | 577 | 55.1k | heap.markFrom(scratch); | 578 | | | 579 | | // Mark from cached imports | 580 | 55.1k | for (const auto &pair : cachedImports) { | 581 | 0 | HeapThunk *thunk = pair.second->thunk; | 582 | 0 | if (thunk != nullptr) | 583 | 0 | heap.markFrom(thunk); | 584 | 0 | } | 585 | | | 586 | 8.43M | for (const auto &sourceVal : sourceVals) { | 587 | 8.43M | heap.markFrom(sourceVal.second); | 588 | 8.43M | } | 589 | | | 590 | | // Delete unreachable objects. | 591 | 55.1k | heap.sweep(); | 592 | 55.1k | } | 593 | 53.4M | return r; | 594 | 53.4M | } |
vm.cpp:jsonnet::internal::(anonymous namespace)::HeapClosure* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapClosure, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > > const&, jsonnet::internal::(anonymous namespace)::HeapObject*&, unsigned int&, std::__1::vector<jsonnet::internal::(anonymous namespace)::HeapClosure::Param, std::__1::allocator<jsonnet::internal::(anonymous namespace)::HeapClosure::Param> > const&, jsonnet::internal::AST*&, char const (&) [1]>(std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > > const&, jsonnet::internal::(anonymous namespace)::HeapObject*&, unsigned int&, std::__1::vector<jsonnet::internal::(anonymous namespace)::HeapClosure::Param, std::__1::allocator<jsonnet::internal::(anonymous namespace)::HeapClosure::Param> > const&, jsonnet::internal::AST*&, char const (&) [1]) Line | Count | Source | 567 | 3.89M | { | 568 | 3.89M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 569 | 3.89M | if (heap.checkHeap()) { // Do a GC cycle? | 570 | | // Avoid the object we just made being collected. | 571 | 5.05k | heap.markFrom(r); | 572 | | | 573 | | // Mark from the stack. | 574 | 5.05k | stack.mark(heap); | 575 | | | 576 | | // Mark from the scratch register | 577 | 5.05k | heap.markFrom(scratch); | 578 | | | 579 | | // Mark from cached imports | 580 | 5.05k | for (const auto &pair : cachedImports) { | 581 | 0 | HeapThunk *thunk = pair.second->thunk; | 582 | 0 | if (thunk != nullptr) | 583 | 0 | heap.markFrom(thunk); | 584 | 0 | } | 585 | | | 586 | 773k | for (const auto &sourceVal : sourceVals) { | 587 | 773k | heap.markFrom(sourceVal.second); | 588 | 773k | } | 589 | | | 590 | | // Delete unreachable objects. | 591 | 5.05k | heap.sweep(); | 592 | 5.05k | } | 593 | 3.89M | return r; | 594 | 3.89M | } |
Unexecuted instantiation: vm.cpp:jsonnet::internal::(anonymous namespace)::HeapThunk* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapThunk, jsonnet::internal::Identifier const*&, decltype(nullptr), int, jsonnet::internal::AST*&>(jsonnet::internal::Identifier const*&, decltype(nullptr)&&, int&&, jsonnet::internal::AST*&) vm.cpp:jsonnet::internal::(anonymous namespace)::HeapThunk* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapThunk, jsonnet::internal::Identifier const* const&, jsonnet::internal::(anonymous namespace)::HeapObject*&, unsigned int&, jsonnet::internal::AST* const&>(jsonnet::internal::Identifier const* const&, jsonnet::internal::(anonymous namespace)::HeapObject*&, unsigned int&, jsonnet::internal::AST* const&) Line | Count | Source | 567 | 36.1M | { | 568 | 36.1M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 569 | 36.1M | if (heap.checkHeap()) { // Do a GC cycle? | 570 | | // Avoid the object we just made being collected. | 571 | 37.1k | heap.markFrom(r); | 572 | | | 573 | | // Mark from the stack. | 574 | 37.1k | stack.mark(heap); | 575 | | | 576 | | // Mark from the scratch register | 577 | 37.1k | heap.markFrom(scratch); | 578 | | | 579 | | // Mark from cached imports | 580 | 37.1k | for (const auto &pair : cachedImports) { | 581 | 0 | HeapThunk *thunk = pair.second->thunk; | 582 | 0 | if (thunk != nullptr) | 583 | 0 | heap.markFrom(thunk); | 584 | 0 | } | 585 | | | 586 | 5.68M | for (const auto &sourceVal : sourceVals) { | 587 | 5.68M | heap.markFrom(sourceVal.second); | 588 | 5.68M | } | 589 | | | 590 | | // Delete unreachable objects. | 591 | 37.1k | heap.sweep(); | 592 | 37.1k | } | 593 | 36.1M | return r; | 594 | 36.1M | } |
vm.cpp:jsonnet::internal::(anonymous namespace)::HeapSimpleObject* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapSimpleObject, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapSimpleObject::Field, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapSimpleObject::Field> > >&, std::__1::list<jsonnet::internal::AST*, std::__1::allocator<jsonnet::internal::AST*> >&>(std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapSimpleObject::Field, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapSimpleObject::Field> > >&, std::__1::list<jsonnet::internal::AST*, std::__1::allocator<jsonnet::internal::AST*> >&) Line | Count | Source | 567 | 3.51M | { | 568 | 3.51M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 569 | 3.51M | if (heap.checkHeap()) { // Do a GC cycle? | 570 | | // Avoid the object we just made being collected. | 571 | 4.73k | heap.markFrom(r); | 572 | | | 573 | | // Mark from the stack. | 574 | 4.73k | stack.mark(heap); | 575 | | | 576 | | // Mark from the scratch register | 577 | 4.73k | heap.markFrom(scratch); | 578 | | | 579 | | // Mark from cached imports | 580 | 4.73k | for (const auto &pair : cachedImports) { | 581 | 0 | HeapThunk *thunk = pair.second->thunk; | 582 | 0 | if (thunk != nullptr) | 583 | 0 | heap.markFrom(thunk); | 584 | 0 | } | 585 | | | 586 | 724k | for (const auto &sourceVal : sourceVals) { | 587 | 724k | heap.markFrom(sourceVal.second); | 588 | 724k | } | 589 | | | 590 | | // Delete unreachable objects. | 591 | 4.73k | heap.sweep(); | 592 | 4.73k | } | 593 | 3.51M | return r; | 594 | 3.51M | } |
vm.cpp:jsonnet::internal::(anonymous namespace)::HeapThunk* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapThunk, jsonnet::internal::Identifier const*&, jsonnet::internal::(anonymous namespace)::HeapObject*&, unsigned int&, jsonnet::internal::AST const* const&>(jsonnet::internal::Identifier const*&, jsonnet::internal::(anonymous namespace)::HeapObject*&, unsigned int&, jsonnet::internal::AST const* const&) Line | Count | Source | 567 | 235 | { | 568 | 235 | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 569 | 235 | if (heap.checkHeap()) { // Do a GC cycle? | 570 | | // Avoid the object we just made being collected. | 571 | 0 | heap.markFrom(r); | 572 | | | 573 | | // Mark from the stack. | 574 | 0 | stack.mark(heap); | 575 | | | 576 | | // Mark from the scratch register | 577 | 0 | heap.markFrom(scratch); | 578 | | | 579 | | // Mark from cached imports | 580 | 0 | for (const auto &pair : cachedImports) { | 581 | 0 | HeapThunk *thunk = pair.second->thunk; | 582 | 0 | if (thunk != nullptr) | 583 | 0 | heap.markFrom(thunk); | 584 | 0 | } | 585 | |
| 586 | 0 | for (const auto &sourceVal : sourceVals) { | 587 | 0 | heap.markFrom(sourceVal.second); | 588 | 0 | } | 589 | | | 590 | | // Delete unreachable objects. | 591 | 0 | heap.sweep(); | 592 | 0 | } | 593 | 235 | return r; | 594 | 235 | } |
vm.cpp:jsonnet::internal::(anonymous namespace)::HeapExtendedObject* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapExtendedObject, jsonnet::internal::(anonymous namespace)::HeapObject*&, jsonnet::internal::(anonymous namespace)::HeapObject*&>(jsonnet::internal::(anonymous namespace)::HeapObject*&, jsonnet::internal::(anonymous namespace)::HeapObject*&) Line | Count | Source | 567 | 2.38M | { | 568 | 2.38M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 569 | 2.38M | if (heap.checkHeap()) { // Do a GC cycle? | 570 | | // Avoid the object we just made being collected. | 571 | 3.00k | heap.markFrom(r); | 572 | | | 573 | | // Mark from the stack. | 574 | 3.00k | stack.mark(heap); | 575 | | | 576 | | // Mark from the scratch register | 577 | 3.00k | heap.markFrom(scratch); | 578 | | | 579 | | // Mark from cached imports | 580 | 3.00k | for (const auto &pair : cachedImports) { | 581 | 0 | HeapThunk *thunk = pair.second->thunk; | 582 | 0 | if (thunk != nullptr) | 583 | 0 | heap.markFrom(thunk); | 584 | 0 | } | 585 | | | 586 | 459k | for (const auto &sourceVal : sourceVals) { | 587 | 459k | heap.markFrom(sourceVal.second); | 588 | 459k | } | 589 | | | 590 | | // Delete unreachable objects. | 591 | 3.00k | heap.sweep(); | 592 | 3.00k | } | 593 | 2.38M | return r; | 594 | 2.38M | } |
vm.cpp:jsonnet::internal::(anonymous namespace)::HeapThunk* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapThunk, jsonnet::internal::Identifier const*&, jsonnet::internal::(anonymous namespace)::HeapObject*&, unsigned int&, jsonnet::internal::AST*&>(jsonnet::internal::Identifier const*&, jsonnet::internal::(anonymous namespace)::HeapObject*&, unsigned int&, jsonnet::internal::AST*&) Line | Count | Source | 567 | 1.65M | { | 568 | 1.65M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 569 | 1.65M | if (heap.checkHeap()) { // Do a GC cycle? | 570 | | // Avoid the object we just made being collected. | 571 | 996 | heap.markFrom(r); | 572 | | | 573 | | // Mark from the stack. | 574 | 996 | stack.mark(heap); | 575 | | | 576 | | // Mark from the scratch register | 577 | 996 | heap.markFrom(scratch); | 578 | | | 579 | | // Mark from cached imports | 580 | 996 | for (const auto &pair : cachedImports) { | 581 | 0 | HeapThunk *thunk = pair.second->thunk; | 582 | 0 | if (thunk != nullptr) | 583 | 0 | heap.markFrom(thunk); | 584 | 0 | } | 585 | | | 586 | 152k | for (const auto &sourceVal : sourceVals) { | 587 | 152k | heap.markFrom(sourceVal.second); | 588 | 152k | } | 589 | | | 590 | | // Delete unreachable objects. | 591 | 996 | heap.sweep(); | 592 | 996 | } | 593 | 1.65M | return r; | 594 | 1.65M | } |
Unexecuted instantiation: vm.cpp:jsonnet::internal::(anonymous namespace)::HeapComprehensionObject* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapComprehensionObject, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&, jsonnet::internal::AST*&, jsonnet::internal::Identifier const*&, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&>(std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&, jsonnet::internal::AST*&, jsonnet::internal::Identifier const*&, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&) |
595 | | |
596 | | Value makeBoolean(bool v) |
597 | 22.3M | { |
598 | 22.3M | Value r; |
599 | 22.3M | r.t = Value::BOOLEAN; |
600 | 22.3M | r.v.b = v; |
601 | 22.3M | return r; |
602 | 22.3M | } |
603 | | |
604 | | Value makeNumber(double v) |
605 | 15.6M | { |
606 | 15.6M | Value r; |
607 | 15.6M | r.t = Value::NUMBER; |
608 | 15.6M | r.v.d = v; |
609 | 15.6M | return r; |
610 | 15.6M | } |
611 | | |
612 | | Value makeNumberCheck(const LocationRange &loc, double v) |
613 | 13.3M | { |
614 | 13.3M | if (std::isnan(v)) { |
615 | 0 | throw makeError(loc, "not a number"); |
616 | 0 | } |
617 | 13.3M | if (std::isinf(v)) { |
618 | 29 | throw makeError(loc, "overflow"); |
619 | 29 | } |
620 | 13.3M | return makeNumber(v); |
621 | 13.3M | } |
622 | | |
623 | | Value makeNull(void) |
624 | 228k | { |
625 | 228k | Value r; |
626 | 228k | r.t = Value::NULL_TYPE; |
627 | 228k | return r; |
628 | 228k | } |
629 | | |
630 | | Value makeArray(const std::vector<HeapThunk *> &v) |
631 | 3.72M | { |
632 | 3.72M | Value r; |
633 | 3.72M | r.t = Value::ARRAY; |
634 | 3.72M | r.v.h = makeHeap<HeapArray>(v); |
635 | 3.72M | return r; |
636 | 3.72M | } |
637 | | |
638 | | Value makeClosure(const BindingFrame &env, HeapObject *self, unsigned offset, |
639 | | const HeapClosure::Params ¶ms, AST *body) |
640 | 3.89M | { |
641 | 3.89M | Value r; |
642 | 3.89M | r.t = Value::FUNCTION; |
643 | 3.89M | r.v.h = makeHeap<HeapClosure>(env, self, offset, params, body, ""); |
644 | 3.89M | return r; |
645 | 3.89M | } |
646 | | |
647 | | Value makeNativeBuiltin(const std::string &name, const std::vector<std::string> ¶ms) |
648 | 0 | { |
649 | 0 | HeapClosure::Params hc_params; |
650 | 0 | for (const auto &p : params) { |
651 | 0 | hc_params.emplace_back(alloc->makeIdentifier(decode_utf8(p)), nullptr); |
652 | 0 | } |
653 | 0 | return makeBuiltin(name, hc_params); |
654 | 0 | } |
655 | | |
656 | | Value makeBuiltin(const std::string &name, const HeapClosure::Params ¶ms) |
657 | 16.7M | { |
658 | 16.7M | AST *body = nullptr; |
659 | 16.7M | Value r; |
660 | 16.7M | r.t = Value::FUNCTION; |
661 | 16.7M | r.v.h = makeHeap<HeapClosure>(BindingFrame(), nullptr, 0, params, body, name); |
662 | 16.7M | return r; |
663 | 16.7M | } |
664 | | |
665 | | template <class T, class... Args> |
666 | | Value makeObject(Args... args) |
667 | 5.89M | { |
668 | 5.89M | Value r; |
669 | 5.89M | r.t = Value::OBJECT; |
670 | 5.89M | r.v.h = makeHeap<T>(args...); |
671 | 5.89M | return r; |
672 | 5.89M | } Unexecuted instantiation: vm.cpp:jsonnet::internal::(anonymous namespace)::Value jsonnet::internal::(anonymous namespace)::Interpreter::makeObject<jsonnet::internal::(anonymous namespace)::HeapComprehensionObject, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >, jsonnet::internal::AST const*, jsonnet::internal::Identifier const*, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > > >(std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >, jsonnet::internal::AST const*, jsonnet::internal::Identifier const*, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >) vm.cpp:jsonnet::internal::(anonymous namespace)::Value jsonnet::internal::(anonymous namespace)::Interpreter::makeObject<jsonnet::internal::(anonymous namespace)::HeapSimpleObject, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapSimpleObject::Field, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapSimpleObject::Field> > >, std::__1::list<jsonnet::internal::AST*, std::__1::allocator<jsonnet::internal::AST*> > >(std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapSimpleObject::Field, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapSimpleObject::Field> > >, std::__1::list<jsonnet::internal::AST*, std::__1::allocator<jsonnet::internal::AST*> >) Line | Count | Source | 667 | 3.51M | { | 668 | 3.51M | Value r; | 669 | 3.51M | r.t = Value::OBJECT; | 670 | 3.51M | r.v.h = makeHeap<T>(args...); | 671 | 3.51M | return r; | 672 | 3.51M | } |
vm.cpp:jsonnet::internal::(anonymous namespace)::Value jsonnet::internal::(anonymous namespace)::Interpreter::makeObject<jsonnet::internal::(anonymous namespace)::HeapExtendedObject, jsonnet::internal::(anonymous namespace)::HeapObject*, jsonnet::internal::(anonymous namespace)::HeapObject*>(jsonnet::internal::(anonymous namespace)::HeapObject*, jsonnet::internal::(anonymous namespace)::HeapObject*) Line | Count | Source | 667 | 2.38M | { | 668 | 2.38M | Value r; | 669 | 2.38M | r.t = Value::OBJECT; | 670 | 2.38M | r.v.h = makeHeap<T>(args...); | 671 | 2.38M | return r; | 672 | 2.38M | } |
Unexecuted instantiation: vm.cpp:jsonnet::internal::(anonymous namespace)::Value jsonnet::internal::(anonymous namespace)::Interpreter::makeObject<jsonnet::internal::(anonymous namespace)::HeapComprehensionObject, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >, jsonnet::internal::AST*, jsonnet::internal::Identifier const*, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > > >(std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >, jsonnet::internal::AST*, jsonnet::internal::Identifier const*, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >) |
673 | | |
674 | | Value makeString(const UString &v) |
675 | 80.1M | { |
676 | 80.1M | Value r; |
677 | 80.1M | r.t = Value::STRING; |
678 | 80.1M | r.v.h = makeHeap<HeapString>(v); |
679 | 80.1M | return r; |
680 | 80.1M | } |
681 | | |
682 | | /** Auxiliary function of objectIndex. |
683 | | * |
684 | | * Traverse the object's tree from right to left, looking for an object |
685 | | * with the given field. Call with offset initially set to 0. |
686 | | * |
687 | | * \param f The field we're looking for. |
688 | | * \param start_from Step over this many leaves first. |
689 | | * \param counter Return the level of "super" that contained the field. |
690 | | * \returns The first object with the field, or nullptr if it could not be found. |
691 | | */ |
692 | | HeapLeafObject *findObject(const Identifier *f, HeapObject *curr, unsigned start_from, |
693 | | unsigned &counter) |
694 | 2.79G | { |
695 | 2.79G | if (auto *ext = dynamic_cast<HeapExtendedObject *>(curr)) { |
696 | 1.38G | auto *r = findObject(f, ext->right, start_from, counter); |
697 | 1.38G | if (r) |
698 | 13.3M | return r; |
699 | 1.37G | auto *l = findObject(f, ext->left, start_from, counter); |
700 | 1.37G | if (l) |
701 | 1.05G | return l; |
702 | 1.40G | } else { |
703 | 1.40G | if (counter >= start_from) { |
704 | 63.4M | if (auto *simp = dynamic_cast<HeapSimpleObject *>(curr)) { |
705 | 63.4M | auto it = simp->fields.find(f); |
706 | 63.4M | if (it != simp->fields.end()) { |
707 | 33.6M | return simp; |
708 | 33.6M | } |
709 | 63.4M | } else if (auto *comp = dynamic_cast<HeapComprehensionObject *>(curr)) { |
710 | 0 | auto it = comp->compValues.find(f); |
711 | 0 | if (it != comp->compValues.end()) { |
712 | 0 | return comp; |
713 | 0 | } |
714 | 0 | } |
715 | 63.4M | } |
716 | 1.37G | counter++; |
717 | 1.37G | } |
718 | 1.68G | return nullptr; |
719 | 2.79G | } |
720 | | |
721 | | typedef std::map<const Identifier *, ObjectField::Hide> IdHideMap; |
722 | | |
723 | | /** Auxiliary function. |
724 | | */ |
725 | | IdHideMap objectFieldsAux(const HeapObject *obj_) |
726 | 68.3M | { |
727 | 68.3M | IdHideMap r; |
728 | 68.3M | if (auto *obj = dynamic_cast<const HeapSimpleObject *>(obj_)) { |
729 | 34.7M | for (const auto &f : obj->fields) { |
730 | 25.4M | r[f.first] = f.second.hide; |
731 | 25.4M | } |
732 | | |
733 | 34.7M | } else if (auto *obj = dynamic_cast<const HeapExtendedObject *>(obj_)) { |
734 | 33.6M | r = objectFieldsAux(obj->right); |
735 | 67.0M | for (const auto &pair : objectFieldsAux(obj->left)) { |
736 | 67.0M | auto it = r.find(pair.first); |
737 | 67.0M | if (it == r.end()) { |
738 | | // First time it is seen |
739 | 43.3M | r[pair.first] = pair.second; |
740 | 43.3M | } else if (it->second == ObjectField::INHERIT) { |
741 | | // Seen before, but with inherited visibility so use new visibility |
742 | 23.2M | r[pair.first] = pair.second; |
743 | 23.2M | } |
744 | 67.0M | } |
745 | | |
746 | 33.6M | } else if (auto *obj = dynamic_cast<const HeapComprehensionObject *>(obj_)) { |
747 | 0 | for (const auto &f : obj->compValues) |
748 | 0 | r[f.first] = ObjectField::VISIBLE; |
749 | 0 | } |
750 | 68.3M | return r; |
751 | 68.3M | } |
752 | | |
753 | | /** Auxiliary function. |
754 | | */ |
755 | | std::set<const Identifier *> objectFields(const HeapObject *obj_, bool manifesting) |
756 | 1.13M | { |
757 | 1.13M | std::set<const Identifier *> r; |
758 | 1.73M | for (const auto &pair : objectFieldsAux(obj_)) { |
759 | 1.73M | if (!manifesting || pair.second != ObjectField::HIDDEN) |
760 | 1.68M | r.insert(pair.first); |
761 | 1.73M | } |
762 | 1.13M | return r; |
763 | 1.13M | } |
764 | | |
765 | | /** Import another Jsonnet file. |
766 | | * |
767 | | * If the file has already been imported, then use that version. This maintains |
768 | | * referential transparency in the case of writes to disk during execution. The |
769 | | * cache holds a thunk in order to cache the resulting value of execution. |
770 | | * |
771 | | * \param loc Location of the import statement. |
772 | | * \param file Path to the filename. |
773 | | */ |
774 | | HeapThunk *import(const LocationRange &loc, const LiteralString *file) |
775 | 150 | { |
776 | 150 | ImportCacheValue *input = importData(loc, file); |
777 | 150 | if (input->thunk == nullptr) { |
778 | 0 | Tokens tokens = jsonnet_lex(input->foundHere, input->content.c_str()); |
779 | 0 | AST *expr = jsonnet_parse(alloc, tokens); |
780 | 0 | jsonnet_desugar(alloc, expr, nullptr); |
781 | 0 | jsonnet_static_analysis(expr); |
782 | | // If no errors then populate cache. |
783 | 0 | auto *thunk = makeHeap<HeapThunk>(idImport, nullptr, 0, expr); |
784 | 0 | input->thunk = thunk; |
785 | 0 | } |
786 | 150 | return input->thunk; |
787 | 150 | } |
788 | | |
789 | | /** Import a file as a string or byte array. |
790 | | * |
791 | | * If the file has already been imported, then use that version. This maintains |
792 | | * referential transparency in the case of writes to disk during execution. |
793 | | * |
794 | | * \param loc Location of the import statement. |
795 | | * \param file Path to the filename. |
796 | | * \param found_here If non-null, used to store the actual path of the file |
797 | | */ |
798 | | ImportCacheValue *importData(const LocationRange &loc, const LiteralString *file) |
799 | 176 | { |
800 | 176 | std::string dir = dir_name(loc.file); |
801 | | |
802 | 176 | const UString &path = file->value; |
803 | | |
804 | 176 | std::pair<std::string, UString> key(dir, path); |
805 | 176 | ImportCacheValue *cached_value = cachedImports[key]; |
806 | 176 | if (cached_value != nullptr) |
807 | 0 | return cached_value; |
808 | | |
809 | 176 | char *found_here_cptr; |
810 | 176 | char *buf = NULL; |
811 | 176 | size_t buflen = 0; |
812 | 176 | int result = importCallback(importCallbackContext, |
813 | 176 | dir.c_str(), |
814 | 176 | encode_utf8(path).c_str(), |
815 | 176 | &found_here_cptr, |
816 | 176 | &buf, |
817 | 176 | &buflen); |
818 | | |
819 | 176 | std::string input(buf, buflen); |
820 | 176 | ::free(buf); |
821 | | |
822 | 176 | if (result == 1) { // failure |
823 | 176 | std::string epath = encode_utf8(jsonnet_string_escape(path, false)); |
824 | 176 | std::string msg = "couldn't open import \"" + epath + "\": "; |
825 | 176 | msg += input; |
826 | 176 | throw makeError(loc, msg); |
827 | 176 | } |
828 | | |
829 | 0 | auto *input_ptr = new ImportCacheValue(); |
830 | 0 | input_ptr->foundHere = found_here_cptr; |
831 | 0 | input_ptr->content = input; |
832 | 0 | input_ptr->thunk = nullptr; // May be filled in later by import(). |
833 | 0 | ::free(found_here_cptr); |
834 | 0 | cachedImports[key] = input_ptr; |
835 | 0 | return input_ptr; |
836 | 176 | } |
837 | | |
838 | | /** Capture the required variables from the environment. */ |
839 | | BindingFrame capture(const std::vector<const Identifier *> &free_vars) |
840 | 98.9M | { |
841 | 98.9M | BindingFrame env; |
842 | 1.25G | for (auto fv : free_vars) { |
843 | 1.25G | auto *th = stack.lookUpVar(fv); |
844 | 1.25G | env[fv] = th; |
845 | 1.25G | } |
846 | 98.9M | return env; |
847 | 98.9M | } |
848 | | |
849 | | /** Count the number of leaves in the tree. |
850 | | * |
851 | | * \param obj The root of the tree. |
852 | | * \returns The number of leaves. |
853 | | */ |
854 | | unsigned countLeaves(HeapObject *obj) |
855 | 11.5G | { |
856 | 11.5G | if (auto *ext = dynamic_cast<HeapExtendedObject *>(obj)) { |
857 | 5.76G | return countLeaves(ext->left) + countLeaves(ext->right); |
858 | 5.77G | } else { |
859 | | // Must be a HeapLeafObject. |
860 | 5.77G | return 1; |
861 | 5.77G | } |
862 | 11.5G | } |
863 | | |
864 | 8.23k | void prepareSourceValThunks() { |
865 | 1.25M | for (const auto &field : stdlibAST->fields) { |
866 | 1.25M | AST *nameAST = field.name; |
867 | 1.25M | if (nameAST->type != AST_LITERAL_STRING) { |
868 | | // Skip any fields without a known name. |
869 | 0 | continue; |
870 | 0 | } |
871 | 1.25M | UString name = dynamic_cast<LiteralString *>(nameAST)->value; |
872 | | |
873 | 1.25M | sourceFuncIds.emplace_back(new Identifier(name)); |
874 | 1.25M | auto *th = makeHeap<HeapThunk>(sourceFuncIds.back().get(), stdObject, 0, field.body); |
875 | 1.25M | sourceVals[encode_utf8(name)] = th; |
876 | 1.25M | } |
877 | 8.23k | } |
878 | | |
879 | | public: |
880 | | /** Create a new interpreter. |
881 | | * |
882 | | * \param loc The location range of the file to be executed. |
883 | | */ |
884 | | Interpreter(Allocator *alloc, const ExtMap &ext_vars, unsigned max_stack, double gc_min_objects, |
885 | | double gc_growth_trigger, const VmNativeCallbackMap &native_callbacks, |
886 | | JsonnetImportCallback *import_callback, void *import_callback_context) |
887 | | |
888 | | : heap(gc_min_objects, gc_growth_trigger), |
889 | | stack(max_stack), |
890 | | alloc(alloc), |
891 | | idImport(alloc->makeIdentifier(U"import")), |
892 | | idArrayElement(alloc->makeIdentifier(U"array_element")), |
893 | | idInvariant(alloc->makeIdentifier(U"object_assert")), |
894 | | idInternal(alloc->makeIdentifier(U"__internal__")), |
895 | | idJsonObjVar(alloc->makeIdentifier(U"_")), |
896 | | idEmpty(alloc->makeIdentifier(U"")), |
897 | | jsonObjVar(alloc->make<Var>(LocationRange(), Fodder{}, idJsonObjVar)), |
898 | | externalVars(ext_vars), |
899 | | nativeCallbacks(native_callbacks), |
900 | | importCallback(import_callback), |
901 | | importCallbackContext(import_callback_context) |
902 | 8.23k | { |
903 | 8.23k | scratch = makeNull(); |
904 | 8.23k | builtins["makeArray"] = &Interpreter::builtinMakeArray; |
905 | 8.23k | builtins["pow"] = &Interpreter::builtinPow; |
906 | 8.23k | builtins["floor"] = &Interpreter::builtinFloor; |
907 | 8.23k | builtins["ceil"] = &Interpreter::builtinCeil; |
908 | 8.23k | builtins["sqrt"] = &Interpreter::builtinSqrt; |
909 | 8.23k | builtins["sin"] = &Interpreter::builtinSin; |
910 | 8.23k | builtins["cos"] = &Interpreter::builtinCos; |
911 | 8.23k | builtins["tan"] = &Interpreter::builtinTan; |
912 | 8.23k | builtins["asin"] = &Interpreter::builtinAsin; |
913 | 8.23k | builtins["acos"] = &Interpreter::builtinAcos; |
914 | 8.23k | builtins["atan"] = &Interpreter::builtinAtan; |
915 | 8.23k | builtins["type"] = &Interpreter::builtinType; |
916 | 8.23k | builtins["filter"] = &Interpreter::builtinFilter; |
917 | 8.23k | builtins["objectHasEx"] = &Interpreter::builtinObjectHasEx; |
918 | 8.23k | builtins["length"] = &Interpreter::builtinLength; |
919 | 8.23k | builtins["objectFieldsEx"] = &Interpreter::builtinObjectFieldsEx; |
920 | 8.23k | builtins["codepoint"] = &Interpreter::builtinCodepoint; |
921 | 8.23k | builtins["char"] = &Interpreter::builtinChar; |
922 | 8.23k | builtins["log"] = &Interpreter::builtinLog; |
923 | 8.23k | builtins["exp"] = &Interpreter::builtinExp; |
924 | 8.23k | builtins["mantissa"] = &Interpreter::builtinMantissa; |
925 | 8.23k | builtins["exponent"] = &Interpreter::builtinExponent; |
926 | 8.23k | builtins["modulo"] = &Interpreter::builtinModulo; |
927 | 8.23k | builtins["extVar"] = &Interpreter::builtinExtVar; |
928 | 8.23k | builtins["primitiveEquals"] = &Interpreter::builtinPrimitiveEquals; |
929 | 8.23k | builtins["native"] = &Interpreter::builtinNative; |
930 | 8.23k | builtins["md5"] = &Interpreter::builtinMd5; |
931 | 8.23k | builtins["trace"] = &Interpreter::builtinTrace; |
932 | 8.23k | builtins["splitLimit"] = &Interpreter::builtinSplitLimit; |
933 | 8.23k | builtins["substr"] = &Interpreter::builtinSubstr; |
934 | 8.23k | builtins["range"] = &Interpreter::builtinRange; |
935 | 8.23k | builtins["strReplace"] = &Interpreter::builtinStrReplace; |
936 | 8.23k | builtins["asciiLower"] = &Interpreter::builtinAsciiLower; |
937 | 8.23k | builtins["asciiUpper"] = &Interpreter::builtinAsciiUpper; |
938 | 8.23k | builtins["join"] = &Interpreter::builtinJoin; |
939 | 8.23k | builtins["parseJson"] = &Interpreter::builtinParseJson; |
940 | 8.23k | builtins["parseYaml"] = &Interpreter::builtinParseYaml; |
941 | 8.23k | builtins["encodeUTF8"] = &Interpreter::builtinEncodeUTF8; |
942 | 8.23k | builtins["decodeUTF8"] = &Interpreter::builtinDecodeUTF8; |
943 | | |
944 | 8.23k | DesugaredObject *stdlib = makeStdlibAST(alloc, "__internal__"); |
945 | 8.23k | jsonnet_static_analysis(stdlib); |
946 | 8.23k | stdlibAST = stdlib; // stdlibAST is const, so we need to do analysis before this assignment |
947 | 8.23k | auto stdThunk = makeHeap<HeapThunk>(nullptr, nullptr, 0, static_cast<const AST*>(stdlibAST)); |
948 | 8.23k | stack.newCall(stdThunk->body->location, stdThunk, stdThunk->self, stdThunk->offset, stdThunk->upValues); |
949 | 8.23k | evaluate(stdThunk->body, 0); |
950 | 8.23k | stdObject = dynamic_cast<HeapObject*>(scratch.v.h); |
951 | 8.23k | prepareSourceValThunks(); |
952 | 8.23k | } |
953 | | |
954 | | |
955 | | /** Clean up the heap, stack, stash, and builtin function ASTs. */ |
956 | | ~Interpreter() |
957 | 8.23k | { |
958 | 8.23k | for (const auto &pair : cachedImports) { |
959 | 176 | delete pair.second; |
960 | 176 | } |
961 | 8.23k | } |
962 | | |
963 | | const Value &getScratchRegister(void) |
964 | 0 | { |
965 | 0 | return scratch; |
966 | 0 | } |
967 | | |
968 | | void setScratchRegister(const Value &v) |
969 | 0 | { |
970 | 0 | scratch = v; |
971 | 0 | } |
972 | | |
973 | | /** Raise an error if the arguments aren't the expected types. */ |
974 | | void validateBuiltinArgs(const LocationRange &loc, const std::string &name, |
975 | | const std::vector<Value> &args, const std::vector<Value::Type> params) |
976 | 405k | { |
977 | 405k | if (args.size() == params.size()) { |
978 | 1.15M | for (std::size_t i = 0; i < args.size(); ++i) { |
979 | 754k | if (args[i].t != params[i]) |
980 | 0 | goto bad; |
981 | 754k | } |
982 | 405k | return; |
983 | 405k | } |
984 | 0 | bad:; |
985 | 0 | std::stringstream ss; |
986 | 0 | ss << "Builtin function " + name + " expected ("; |
987 | 0 | const char *prefix = ""; |
988 | 0 | for (auto p : params) { |
989 | 0 | ss << prefix << type_str(p); |
990 | 0 | prefix = ", "; |
991 | 0 | } |
992 | 0 | ss << ") but got ("; |
993 | 0 | prefix = ""; |
994 | 0 | for (auto a : args) { |
995 | 0 | ss << prefix << type_str(a); |
996 | 0 | prefix = ", "; |
997 | 0 | } |
998 | 0 | ss << ")"; |
999 | 0 | throw makeError(loc, ss.str()); |
1000 | 405k | } |
1001 | | |
1002 | | const AST *builtinMakeArray(const LocationRange &loc, const std::vector<Value> &args) |
1003 | 0 | { |
1004 | 0 | Frame &f = stack.top(); |
1005 | 0 | validateBuiltinArgs(loc, "makeArray", args, {Value::NUMBER, Value::FUNCTION}); |
1006 | 0 | long sz = long(args[0].v.d); |
1007 | 0 | if (sz < 0) { |
1008 | 0 | std::stringstream ss; |
1009 | 0 | ss << "makeArray requires size >= 0, got " << sz; |
1010 | 0 | throw makeError(loc, ss.str()); |
1011 | 0 | } |
1012 | 0 | auto *func = static_cast<const HeapClosure *>(args[1].v.h); |
1013 | 0 | std::vector<HeapThunk *> elements; |
1014 | 0 | if (func->params.size() != 1) { |
1015 | 0 | std::stringstream ss; |
1016 | 0 | ss << "makeArray function must take 1 param, got: " << func->params.size(); |
1017 | 0 | throw makeError(loc, ss.str()); |
1018 | 0 | } |
1019 | 0 | elements.resize(sz); |
1020 | 0 | for (long i = 0; i < sz; ++i) { |
1021 | 0 | auto *th = makeHeap<HeapThunk>(idArrayElement, func->self, func->offset, func->body); |
1022 | | // The next line stops the new thunks from being GCed. |
1023 | 0 | f.thunks.push_back(th); |
1024 | 0 | th->upValues = func->upValues; |
1025 | |
|
1026 | 0 | auto *el = makeHeap<HeapThunk>(func->params[0].id, nullptr, 0, nullptr); |
1027 | 0 | el->fill(makeNumber(i)); // i guaranteed not to be inf/NaN |
1028 | 0 | th->upValues[func->params[0].id] = el; |
1029 | 0 | elements[i] = th; |
1030 | 0 | } |
1031 | 0 | scratch = makeArray(elements); |
1032 | 0 | return nullptr; |
1033 | 0 | } |
1034 | | |
1035 | | const AST *builtinPow(const LocationRange &loc, const std::vector<Value> &args) |
1036 | 5.45k | { |
1037 | 5.45k | validateBuiltinArgs(loc, "pow", args, {Value::NUMBER, Value::NUMBER}); |
1038 | 5.45k | scratch = makeNumberCheck(loc, std::pow(args[0].v.d, args[1].v.d)); |
1039 | 5.45k | return nullptr; |
1040 | 5.45k | } |
1041 | | |
1042 | | const AST *builtinFloor(const LocationRange &loc, const std::vector<Value> &args) |
1043 | 49.9k | { |
1044 | 49.9k | validateBuiltinArgs(loc, "floor", args, {Value::NUMBER}); |
1045 | 49.9k | scratch = makeNumberCheck(loc, std::floor(args[0].v.d)); |
1046 | 49.9k | return nullptr; |
1047 | 49.9k | } |
1048 | | |
1049 | | const AST *builtinCeil(const LocationRange &loc, const std::vector<Value> &args) |
1050 | 0 | { |
1051 | 0 | validateBuiltinArgs(loc, "ceil", args, {Value::NUMBER}); |
1052 | 0 | scratch = makeNumberCheck(loc, std::ceil(args[0].v.d)); |
1053 | 0 | return nullptr; |
1054 | 0 | } |
1055 | | |
1056 | | const AST *builtinSqrt(const LocationRange &loc, const std::vector<Value> &args) |
1057 | 0 | { |
1058 | 0 | validateBuiltinArgs(loc, "sqrt", args, {Value::NUMBER}); |
1059 | 0 | scratch = makeNumberCheck(loc, std::sqrt(args[0].v.d)); |
1060 | 0 | return nullptr; |
1061 | 0 | } |
1062 | | |
1063 | | const AST *builtinSin(const LocationRange &loc, const std::vector<Value> &args) |
1064 | 0 | { |
1065 | 0 | validateBuiltinArgs(loc, "sin", args, {Value::NUMBER}); |
1066 | 0 | scratch = makeNumberCheck(loc, std::sin(args[0].v.d)); |
1067 | 0 | return nullptr; |
1068 | 0 | } |
1069 | | |
1070 | | const AST *builtinCos(const LocationRange &loc, const std::vector<Value> &args) |
1071 | 0 | { |
1072 | 0 | validateBuiltinArgs(loc, "cos", args, {Value::NUMBER}); |
1073 | 0 | scratch = makeNumberCheck(loc, std::cos(args[0].v.d)); |
1074 | 0 | return nullptr; |
1075 | 0 | } |
1076 | | |
1077 | | const AST *builtinTan(const LocationRange &loc, const std::vector<Value> &args) |
1078 | 0 | { |
1079 | 0 | validateBuiltinArgs(loc, "tan", args, {Value::NUMBER}); |
1080 | 0 | scratch = makeNumberCheck(loc, std::tan(args[0].v.d)); |
1081 | 0 | return nullptr; |
1082 | 0 | } |
1083 | | |
1084 | | const AST *builtinAsin(const LocationRange &loc, const std::vector<Value> &args) |
1085 | 0 | { |
1086 | 0 | validateBuiltinArgs(loc, "asin", args, {Value::NUMBER}); |
1087 | 0 | scratch = makeNumberCheck(loc, std::asin(args[0].v.d)); |
1088 | 0 | return nullptr; |
1089 | 0 | } |
1090 | | |
1091 | | const AST *builtinAcos(const LocationRange &loc, const std::vector<Value> &args) |
1092 | 0 | { |
1093 | 0 | validateBuiltinArgs(loc, "acos", args, {Value::NUMBER}); |
1094 | 0 | scratch = makeNumberCheck(loc, std::acos(args[0].v.d)); |
1095 | 0 | return nullptr; |
1096 | 0 | } |
1097 | | |
1098 | | const AST *builtinAtan(const LocationRange &loc, const std::vector<Value> &args) |
1099 | 0 | { |
1100 | 0 | validateBuiltinArgs(loc, "atan", args, {Value::NUMBER}); |
1101 | 0 | scratch = makeNumberCheck(loc, std::atan(args[0].v.d)); |
1102 | 0 | return nullptr; |
1103 | 0 | } |
1104 | | |
1105 | | const AST *builtinType(const LocationRange &, const std::vector<Value> &args) |
1106 | 5.89M | { |
1107 | 5.89M | switch (args[0].t) { |
1108 | 304k | case Value::NULL_TYPE: scratch = makeString(U"null"); return nullptr; |
1109 | | |
1110 | 296k | case Value::BOOLEAN: scratch = makeString(U"boolean"); return nullptr; |
1111 | | |
1112 | 647k | case Value::NUMBER: scratch = makeString(U"number"); return nullptr; |
1113 | | |
1114 | 231k | case Value::ARRAY: scratch = makeString(U"array"); return nullptr; |
1115 | | |
1116 | 1.92k | case Value::FUNCTION: scratch = makeString(U"function"); return nullptr; |
1117 | | |
1118 | 420k | case Value::OBJECT: scratch = makeString(U"object"); return nullptr; |
1119 | | |
1120 | 3.99M | case Value::STRING: scratch = makeString(U"string"); return nullptr; |
1121 | 5.89M | } |
1122 | 0 | return nullptr; // Quiet, compiler. |
1123 | 5.89M | } |
1124 | | |
1125 | | const AST *builtinFilter(const LocationRange &loc, const std::vector<Value> &args) |
1126 | 0 | { |
1127 | 0 | Frame &f = stack.top(); |
1128 | 0 | validateBuiltinArgs(loc, "filter", args, {Value::FUNCTION, Value::ARRAY}); |
1129 | 0 | auto *func = static_cast<HeapClosure *>(args[0].v.h); |
1130 | 0 | auto *arr = static_cast<HeapArray *>(args[1].v.h); |
1131 | 0 | if (func->params.size() != 1) { |
1132 | 0 | throw makeError(loc, "filter function takes 1 parameter."); |
1133 | 0 | } |
1134 | 0 | if (arr->elements.size() == 0) { |
1135 | 0 | scratch = makeArray({}); |
1136 | 0 | } else { |
1137 | 0 | f.kind = FRAME_BUILTIN_FILTER; |
1138 | 0 | f.val = args[0]; |
1139 | 0 | f.val2 = args[1]; |
1140 | 0 | f.thunks.clear(); |
1141 | 0 | f.elementId = 0; |
1142 | |
|
1143 | 0 | auto *thunk = arr->elements[f.elementId]; |
1144 | 0 | BindingFrame bindings = func->upValues; |
1145 | 0 | bindings[func->params[0].id] = thunk; |
1146 | 0 | stack.newCall(loc, func, func->self, func->offset, bindings); |
1147 | 0 | return func->body; |
1148 | 0 | } |
1149 | 0 | return nullptr; |
1150 | 0 | } |
1151 | | |
1152 | | const AST *builtinObjectHasEx(const LocationRange &loc, const std::vector<Value> &args) |
1153 | 439 | { |
1154 | 439 | validateBuiltinArgs( |
1155 | 439 | loc, "objectHasEx", args, {Value::OBJECT, Value::STRING, Value::BOOLEAN}); |
1156 | 439 | const auto *obj = static_cast<const HeapObject *>(args[0].v.h); |
1157 | 439 | const auto *str = static_cast<const HeapString *>(args[1].v.h); |
1158 | 439 | bool include_hidden = args[2].v.b; |
1159 | 439 | bool found = false; |
1160 | 1.29k | for (const auto &field : objectFields(obj, !include_hidden)) { |
1161 | 1.29k | if (field->name == str->value) { |
1162 | 409 | found = true; |
1163 | 409 | break; |
1164 | 409 | } |
1165 | 1.29k | } |
1166 | 439 | scratch = makeBoolean(found); |
1167 | 439 | return nullptr; |
1168 | 439 | } |
1169 | | |
1170 | | const AST *builtinLength(const LocationRange &loc, const std::vector<Value> &args) |
1171 | 1.17M | { |
1172 | 1.17M | if (args.size() != 1) { |
1173 | 0 | throw makeError(loc, "length takes 1 parameter."); |
1174 | 0 | } |
1175 | 1.17M | HeapEntity *e = args[0].v.h; |
1176 | 1.17M | switch (args[0].t) { |
1177 | 131 | case Value::OBJECT: { |
1178 | 131 | auto fields = objectFields(static_cast<HeapObject *>(e), true); |
1179 | 131 | scratch = makeNumber(fields.size()); |
1180 | 131 | } break; |
1181 | | |
1182 | 413k | case Value::ARRAY: |
1183 | 413k | scratch = makeNumber(static_cast<HeapArray *>(e)->elements.size()); |
1184 | 413k | break; |
1185 | | |
1186 | 758k | case Value::STRING: |
1187 | 758k | scratch = makeNumber(static_cast<HeapString *>(e)->value.length()); |
1188 | 758k | break; |
1189 | | |
1190 | 0 | case Value::FUNCTION: |
1191 | 0 | scratch = makeNumber(static_cast<HeapClosure *>(e)->params.size()); |
1192 | 0 | break; |
1193 | | |
1194 | 49 | default: |
1195 | 49 | throw makeError(loc, |
1196 | 49 | "length operates on strings, objects, " |
1197 | 49 | "and arrays, got " + |
1198 | 49 | type_str(args[0])); |
1199 | 1.17M | } |
1200 | 1.17M | return nullptr; |
1201 | 1.17M | } |
1202 | | |
1203 | | const AST *builtinObjectFieldsEx(const LocationRange &loc, const std::vector<Value> &args) |
1204 | 208k | { |
1205 | 208k | validateBuiltinArgs(loc, "objectFieldsEx", args, {Value::OBJECT, Value::BOOLEAN}); |
1206 | 208k | const auto *obj = static_cast<HeapObject *>(args[0].v.h); |
1207 | 208k | bool include_hidden = args[1].v.b; |
1208 | | // Stash in a set first to sort them. |
1209 | 208k | std::set<UString> fields; |
1210 | 365k | for (const auto &field : objectFields(obj, !include_hidden)) { |
1211 | 365k | fields.insert(field->name); |
1212 | 365k | } |
1213 | 208k | scratch = makeArray({}); |
1214 | 208k | auto &elements = static_cast<HeapArray *>(scratch.v.h)->elements; |
1215 | 365k | for (const auto &field : fields) { |
1216 | 365k | auto *th = makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr); |
1217 | 365k | elements.push_back(th); |
1218 | 365k | th->fill(makeString(field)); |
1219 | 365k | } |
1220 | 208k | return nullptr; |
1221 | 208k | } |
1222 | | |
1223 | | const AST *builtinCodepoint(const LocationRange &loc, const std::vector<Value> &args) |
1224 | 0 | { |
1225 | 0 | validateBuiltinArgs(loc, "codepoint", args, {Value::STRING}); |
1226 | 0 | const UString &str = static_cast<HeapString *>(args[0].v.h)->value; |
1227 | 0 | if (str.length() != 1) { |
1228 | 0 | std::stringstream ss; |
1229 | 0 | ss << "codepoint takes a string of length 1, got length " << str.length(); |
1230 | 0 | throw makeError(loc, ss.str()); |
1231 | 0 | } |
1232 | 0 | char32_t c = static_cast<HeapString *>(args[0].v.h)->value[0]; |
1233 | 0 | scratch = makeNumber((unsigned long)(c)); |
1234 | 0 | return nullptr; |
1235 | 0 | } |
1236 | | |
1237 | | const AST *builtinChar(const LocationRange &loc, const std::vector<Value> &args) |
1238 | 1.91k | { |
1239 | 1.91k | validateBuiltinArgs(loc, "char", args, {Value::NUMBER}); |
1240 | 1.91k | long l = long(args[0].v.d); |
1241 | 1.91k | if (l < 0) { |
1242 | 137 | std::stringstream ss; |
1243 | 137 | ss << "codepoints must be >= 0, got " << l; |
1244 | 137 | throw makeError(loc, ss.str()); |
1245 | 137 | } |
1246 | 1.78k | if (l >= JSONNET_CODEPOINT_MAX) { |
1247 | 86 | std::stringstream ss; |
1248 | 86 | ss << "invalid unicode codepoint, got " << l; |
1249 | 86 | throw makeError(loc, ss.str()); |
1250 | 86 | } |
1251 | 1.69k | char32_t c = l; |
1252 | 1.69k | scratch = makeString(UString(&c, 1)); |
1253 | 1.69k | return nullptr; |
1254 | 1.78k | } |
1255 | | |
1256 | | const AST *builtinLog(const LocationRange &loc, const std::vector<Value> &args) |
1257 | 6.40k | { |
1258 | 6.40k | validateBuiltinArgs(loc, "log", args, {Value::NUMBER}); |
1259 | 6.40k | scratch = makeNumberCheck(loc, std::log(args[0].v.d)); |
1260 | 6.40k | return nullptr; |
1261 | 6.40k | } |
1262 | | |
1263 | | const AST *builtinExp(const LocationRange &loc, const std::vector<Value> &args) |
1264 | 0 | { |
1265 | 0 | validateBuiltinArgs(loc, "exp", args, {Value::NUMBER}); |
1266 | 0 | scratch = makeNumberCheck(loc, std::exp(args[0].v.d)); |
1267 | 0 | return nullptr; |
1268 | 0 | } |
1269 | | |
1270 | | const AST *builtinMantissa(const LocationRange &loc, const std::vector<Value> &args) |
1271 | 0 | { |
1272 | 0 | validateBuiltinArgs(loc, "mantissa", args, {Value::NUMBER}); |
1273 | 0 | int exp; |
1274 | 0 | double m = std::frexp(args[0].v.d, &exp); |
1275 | 0 | scratch = makeNumberCheck(loc, m); |
1276 | 0 | return nullptr; |
1277 | 0 | } |
1278 | | |
1279 | | const AST *builtinExponent(const LocationRange &loc, const std::vector<Value> &args) |
1280 | 0 | { |
1281 | 0 | validateBuiltinArgs(loc, "exponent", args, {Value::NUMBER}); |
1282 | 0 | int exp; |
1283 | 0 | std::frexp(args[0].v.d, &exp); |
1284 | 0 | scratch = makeNumberCheck(loc, exp); |
1285 | 0 | return nullptr; |
1286 | 0 | } |
1287 | | |
1288 | | const AST *builtinModulo(const LocationRange &loc, const std::vector<Value> &args) |
1289 | 131k | { |
1290 | 131k | validateBuiltinArgs(loc, "modulo", args, {Value::NUMBER, Value::NUMBER}); |
1291 | 131k | double a = args[0].v.d; |
1292 | 131k | double b = args[1].v.d; |
1293 | 131k | if (b == 0) |
1294 | 29 | throw makeError(loc, "division by zero."); |
1295 | 131k | scratch = makeNumberCheck(loc, std::fmod(a, b)); |
1296 | 131k | return nullptr; |
1297 | 131k | } |
1298 | | |
1299 | | const AST *builtinExtVar(const LocationRange &loc, const std::vector<Value> &args) |
1300 | 0 | { |
1301 | 0 | validateBuiltinArgs(loc, "extVar", args, {Value::STRING}); |
1302 | 0 | const UString &var = static_cast<HeapString *>(args[0].v.h)->value; |
1303 | 0 | std::string var8 = encode_utf8(var); |
1304 | 0 | auto it = externalVars.find(var8); |
1305 | 0 | if (it == externalVars.end()) { |
1306 | 0 | std::string msg = "undefined external variable: " + var8; |
1307 | 0 | throw makeError(loc, msg); |
1308 | 0 | } |
1309 | 0 | const VmExt &ext = it->second; |
1310 | 0 | if (ext.isCode) { |
1311 | 0 | std::string filename = "<extvar:" + var8 + ">"; |
1312 | 0 | Tokens tokens = jsonnet_lex(filename, ext.data.c_str()); |
1313 | 0 | AST *expr = jsonnet_parse(alloc, tokens); |
1314 | 0 | jsonnet_desugar(alloc, expr, nullptr); |
1315 | 0 | jsonnet_static_analysis(expr); |
1316 | 0 | stack.pop(); |
1317 | 0 | return expr; |
1318 | 0 | } else { |
1319 | 0 | scratch = makeString(decode_utf8(ext.data)); |
1320 | 0 | return nullptr; |
1321 | 0 | } |
1322 | 0 | } |
1323 | | |
1324 | | const AST *builtinPrimitiveEquals(const LocationRange &loc, const std::vector<Value> &args) |
1325 | 9.30M | { |
1326 | 9.30M | if (args.size() != 2) { |
1327 | 0 | std::stringstream ss; |
1328 | 0 | ss << "primitiveEquals takes 2 parameters, got " << args.size(); |
1329 | 0 | throw makeError(loc, ss.str()); |
1330 | 0 | } |
1331 | 9.30M | if (args[0].t != args[1].t) { |
1332 | 0 | scratch = makeBoolean(false); |
1333 | 0 | return nullptr; |
1334 | 0 | } |
1335 | 9.30M | bool r; |
1336 | 9.30M | switch (args[0].t) { |
1337 | 55.3k | case Value::BOOLEAN: r = args[0].v.b == args[1].v.b; break; |
1338 | | |
1339 | 180k | case Value::NUMBER: r = args[0].v.d == args[1].v.d; break; |
1340 | | |
1341 | 8.96M | case Value::STRING: |
1342 | 8.96M | r = static_cast<HeapString *>(args[0].v.h)->value == |
1343 | 8.96M | static_cast<HeapString *>(args[1].v.h)->value; |
1344 | 8.96M | break; |
1345 | | |
1346 | 97.2k | case Value::NULL_TYPE: r = true; break; |
1347 | | |
1348 | 8 | case Value::FUNCTION: throw makeError(loc, "cannot test equality of functions"); break; |
1349 | | |
1350 | 0 | default: |
1351 | 0 | throw makeError(loc, |
1352 | 0 | "primitiveEquals operates on primitive " |
1353 | 0 | "types, got " + |
1354 | 0 | type_str(args[0])); |
1355 | 9.30M | } |
1356 | 9.30M | scratch = makeBoolean(r); |
1357 | 9.30M | return nullptr; |
1358 | 9.30M | } |
1359 | | |
1360 | | const AST *builtinNative(const LocationRange &loc, const std::vector<Value> &args) |
1361 | 0 | { |
1362 | 0 | validateBuiltinArgs(loc, "native", args, {Value::STRING}); |
1363 | |
|
1364 | 0 | std::string builtin_name = encode_utf8(static_cast<HeapString *>(args[0].v.h)->value); |
1365 | |
|
1366 | 0 | VmNativeCallbackMap::const_iterator nit = nativeCallbacks.find(builtin_name); |
1367 | 0 | if (nit == nativeCallbacks.end()) { |
1368 | 0 | scratch = makeNull(); |
1369 | 0 | } else { |
1370 | 0 | const VmNativeCallback &cb = nit->second; |
1371 | 0 | scratch = makeNativeBuiltin(builtin_name, cb.params); |
1372 | 0 | } |
1373 | 0 | return nullptr; |
1374 | 0 | } |
1375 | | |
1376 | | const AST *builtinMd5(const LocationRange &loc, const std::vector<Value> &args) |
1377 | 0 | { |
1378 | 0 | validateBuiltinArgs(loc, "md5", args, {Value::STRING}); |
1379 | |
|
1380 | 0 | std::string value = encode_utf8(static_cast<HeapString *>(args[0].v.h)->value); |
1381 | |
|
1382 | 0 | scratch = makeString(decode_utf8(md5(value))); |
1383 | 0 | return nullptr; |
1384 | 0 | } |
1385 | | |
1386 | | const AST *builtinEncodeUTF8(const LocationRange &loc, const std::vector<Value> &args) |
1387 | 0 | { |
1388 | 0 | validateBuiltinArgs(loc, "encodeUTF8", args, {Value::STRING}); |
1389 | |
|
1390 | 0 | std::string byteString = encode_utf8(static_cast<HeapString *>(args[0].v.h)->value); |
1391 | |
|
1392 | 0 | scratch = makeArray({}); |
1393 | 0 | auto &elements = static_cast<HeapArray *>(scratch.v.h)->elements; |
1394 | 0 | for (const auto c : byteString) { |
1395 | 0 | auto *th = makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr); |
1396 | 0 | elements.push_back(th); |
1397 | 0 | th->fill(makeNumber(uint8_t(c))); |
1398 | 0 | } |
1399 | 0 | return nullptr; |
1400 | 0 | } |
1401 | | |
1402 | | const AST *decodeUTF8(void) |
1403 | 0 | { |
1404 | 0 | Frame &f = stack.top(); |
1405 | 0 | const auto& elements = static_cast<HeapArray*>(f.val.v.h)->elements; |
1406 | 0 | while (f.elementId < elements.size()) { |
1407 | 0 | auto *th = elements[f.elementId]; |
1408 | 0 | if (th->filled) { |
1409 | 0 | auto b = th->content; |
1410 | 0 | if (b.t != Value::NUMBER) { |
1411 | 0 | std::stringstream ss; |
1412 | 0 | ss << "Element " << f.elementId << " of the provided array was not a number"; |
1413 | 0 | throw makeError(stack.top().location, ss.str()); |
1414 | 0 | } else { |
1415 | 0 | double d = b.v.d; |
1416 | 0 | if (d < 0 || d > 255 || d != int(d)) { |
1417 | 0 | std::stringstream ss; |
1418 | 0 | ss << "Element " << f.elementId << " of the provided array was not an integer in range [0,255]"; |
1419 | 0 | throw makeError(stack.top().location, ss.str()); |
1420 | 0 | } |
1421 | 0 | f.bytes.push_back(uint8_t(d)); |
1422 | 0 | } |
1423 | 0 | f.elementId++; |
1424 | 0 | } else { |
1425 | 0 | stack.newCall(f.location, th, th->self, th->offset, th->upValues); |
1426 | 0 | return th->body; |
1427 | 0 | } |
1428 | 0 | } |
1429 | 0 | scratch = makeString(decode_utf8(f.bytes)); |
1430 | 0 | return nullptr; |
1431 | 0 | } |
1432 | | |
1433 | | const AST *builtinDecodeUTF8(const LocationRange &loc, const std::vector<Value> &args) |
1434 | 0 | { |
1435 | 0 | validateBuiltinArgs(loc, "decodeUTF8", args, {Value::ARRAY}); |
1436 | |
|
1437 | 0 | Frame &f = stack.top(); |
1438 | 0 | f.kind = FRAME_BUILTIN_DECODE_UTF8; |
1439 | 0 | f.val = args[0]; // arr |
1440 | 0 | f.bytes.clear(); |
1441 | 0 | f.elementId = 0; |
1442 | 0 | return decodeUTF8(); |
1443 | 0 | } |
1444 | | |
1445 | | const AST *builtinTrace(const LocationRange &loc, const std::vector<Value> &args) |
1446 | 0 | { |
1447 | 0 | if(args[0].t != Value::STRING) { |
1448 | 0 | std::stringstream ss; |
1449 | 0 | ss << "Builtin function trace expected string as first parameter but " |
1450 | 0 | << "got " << type_str(args[0].t); |
1451 | 0 | throw makeError(loc, ss.str()); |
1452 | 0 | } |
1453 | | |
1454 | 0 | std::string str = encode_utf8(static_cast<HeapString *>(args[0].v.h)->value); |
1455 | 0 | std::cerr << "TRACE: " << loc.file << ":" << loc.begin.line << " " << str |
1456 | 0 | << std::endl; |
1457 | |
|
1458 | 0 | scratch = args[1]; |
1459 | 0 | return nullptr; |
1460 | 0 | } |
1461 | | |
1462 | | const AST *builtinSplitLimit(const LocationRange &loc, const std::vector<Value> &args) |
1463 | 0 | { |
1464 | 0 | validateBuiltinArgs(loc, "splitLimit", args, {Value::STRING, Value::STRING, Value::NUMBER}); |
1465 | 0 | const auto *str = static_cast<const HeapString *>(args[0].v.h); |
1466 | 0 | const auto *c = static_cast<const HeapString *>(args[1].v.h); |
1467 | 0 | long maxsplits = long(args[2].v.d); |
1468 | 0 | unsigned start = 0; |
1469 | 0 | unsigned test = 0; |
1470 | 0 | scratch = makeArray({}); |
1471 | 0 | auto &elements = static_cast<HeapArray *>(scratch.v.h)->elements; |
1472 | 0 | while (test < str->value.size() && (maxsplits == -1 || |
1473 | 0 | size_t(maxsplits) > elements.size())) { |
1474 | 0 | if (c->value == str->value.substr(test, c->value.size())) { |
1475 | 0 | auto *th = makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr); |
1476 | 0 | elements.push_back(th); |
1477 | 0 | th->fill(makeString(str->value.substr(start, test - start))); |
1478 | 0 | start = test + c->value.size(); |
1479 | 0 | test = start; |
1480 | 0 | } else { |
1481 | 0 | ++test; |
1482 | 0 | } |
1483 | 0 | } |
1484 | 0 | auto *th = makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr); |
1485 | 0 | elements.push_back(th); |
1486 | 0 | th->fill(makeString(str->value.substr(start))); |
1487 | |
|
1488 | 0 | return nullptr; |
1489 | 0 | } |
1490 | | |
1491 | | const AST *builtinSubstr(const LocationRange &loc, const std::vector<Value> &args) |
1492 | 1.33k | { |
1493 | 1.33k | validateBuiltinArgs(loc, "substr", args, {Value::STRING, Value::NUMBER, Value::NUMBER}); |
1494 | 1.33k | const auto *str = static_cast<const HeapString *>(args[0].v.h); |
1495 | 1.33k | long from = long(args[1].v.d); |
1496 | 1.33k | long len = long(args[2].v.d); |
1497 | 1.33k | if (from < 0) { |
1498 | 0 | std::stringstream ss; |
1499 | 0 | ss << "substr second parameter should be greater than zero, got " << from; |
1500 | 0 | throw makeError(loc, ss.str()); |
1501 | 0 | } |
1502 | 1.33k | if (len < 0) { |
1503 | 0 | std::stringstream ss; |
1504 | 0 | ss << "substr third parameter should be greater than zero, got " << len; |
1505 | 0 | throw makeError(loc, ss.str()); |
1506 | 0 | } |
1507 | 1.33k | if (static_cast<unsigned long>(from) > str->value.size()) { |
1508 | 0 | scratch = makeString(UString()); |
1509 | 0 | return nullptr; |
1510 | 0 | } |
1511 | 1.33k | if (size_t(len + from) > str->value.size()) { |
1512 | 0 | len = str->value.size() - from; |
1513 | 0 | } |
1514 | 1.33k | scratch = makeString(str->value.substr(from, len)); |
1515 | 1.33k | return nullptr; |
1516 | 1.33k | } |
1517 | | |
1518 | | const AST *builtinRange(const LocationRange &loc, const std::vector<Value> &args) |
1519 | 0 | { |
1520 | 0 | validateBuiltinArgs(loc, "range", args, {Value::NUMBER, Value::NUMBER}); |
1521 | 0 | long from = long(args[0].v.d); |
1522 | 0 | long to = long(args[1].v.d); |
1523 | 0 | long len = to - from + 1; |
1524 | 0 | scratch = makeArray({}); |
1525 | 0 | if (len > 0) { |
1526 | 0 | auto &elements = static_cast<HeapArray *>(scratch.v.h)->elements; |
1527 | 0 | for (int i = 0; i < len; ++i) { |
1528 | 0 | auto *th = makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr); |
1529 | 0 | elements.push_back(th); |
1530 | 0 | th->fill(makeNumber(from + i)); |
1531 | 0 | } |
1532 | 0 | } |
1533 | 0 | return nullptr; |
1534 | 0 | } |
1535 | | |
1536 | | const AST *builtinStrReplace(const LocationRange &loc, const std::vector<Value> &args) |
1537 | 0 | { |
1538 | 0 | validateBuiltinArgs(loc, "strReplace", args, {Value::STRING, Value::STRING, Value::STRING}); |
1539 | 0 | const auto *str = static_cast<const HeapString *>(args[0].v.h); |
1540 | 0 | const auto *from = static_cast<const HeapString *>(args[1].v.h); |
1541 | 0 | const auto *to = static_cast<const HeapString *>(args[2].v.h); |
1542 | 0 | if (from->value.empty()) { |
1543 | 0 | throw makeError(loc, "'from' string must not be zero length."); |
1544 | 0 | } |
1545 | 0 | UString new_str(str->value); |
1546 | 0 | UString::size_type pos = 0; |
1547 | 0 | while (pos < new_str.size()) { |
1548 | 0 | auto index = new_str.find(from->value, pos); |
1549 | 0 | if (index == new_str.npos) { |
1550 | 0 | break; |
1551 | 0 | } |
1552 | 0 | new_str.replace(index, from->value.size(), to->value); |
1553 | 0 | pos = index + to->value.size(); |
1554 | 0 | } |
1555 | 0 | scratch = makeString(new_str); |
1556 | 0 | return nullptr; |
1557 | 0 | } |
1558 | | |
1559 | | const AST *builtinAsciiLower(const LocationRange &loc, const std::vector<Value> &args) |
1560 | 0 | { |
1561 | 0 | validateBuiltinArgs(loc, "asciiLower", args, {Value::STRING}); |
1562 | 0 | const auto *str = static_cast<const HeapString *>(args[0].v.h); |
1563 | 0 | UString new_str(str->value); |
1564 | 0 | for (size_t i = 0; i < new_str.size(); ++i) { |
1565 | 0 | if (new_str[i] >= 'A' && new_str[i] <= 'Z') { |
1566 | 0 | new_str[i] = new_str[i] - 'A' + 'a'; |
1567 | 0 | } |
1568 | 0 | } |
1569 | 0 | scratch = makeString(new_str); |
1570 | 0 | return nullptr; |
1571 | 0 | } |
1572 | | |
1573 | | const AST *builtinAsciiUpper(const LocationRange &loc, const std::vector<Value> &args) |
1574 | 0 | { |
1575 | 0 | validateBuiltinArgs(loc, "asciiUpper", args, {Value::STRING}); |
1576 | 0 | const auto *str = static_cast<const HeapString *>(args[0].v.h); |
1577 | 0 | UString new_str(str->value); |
1578 | 0 | for (size_t i = 0; i < new_str.size(); ++i) { |
1579 | 0 | if (new_str[i] >= 'a' && new_str[i] <= 'z') { |
1580 | 0 | new_str[i] = new_str[i] - 'a' + 'A'; |
1581 | 0 | } |
1582 | 0 | } |
1583 | 0 | scratch = makeString(new_str); |
1584 | 0 | return nullptr; |
1585 | 0 | } |
1586 | | |
1587 | | const AST *builtinParseJson(const LocationRange &loc, const std::vector<Value> &args) |
1588 | 0 | { |
1589 | 0 | validateBuiltinArgs(loc, "parseJson", args, {Value::STRING}); |
1590 | |
|
1591 | 0 | std::string value = encode_utf8(static_cast<HeapString *>(args[0].v.h)->value); |
1592 | |
|
1593 | 0 | try { |
1594 | 0 | auto j = json::parse(value); |
1595 | |
|
1596 | 0 | bool filled; |
1597 | 0 | otherJsonToHeap(j, filled, scratch); |
1598 | 0 | } catch (const json::parse_error &e) { |
1599 | 0 | throw makeError(loc, e.what()); |
1600 | 0 | } |
1601 | | |
1602 | 0 | return nullptr; |
1603 | 0 | } |
1604 | | |
1605 | | const AST *builtinParseYaml(const LocationRange &loc, const std::vector<Value> &args) |
1606 | 0 | { |
1607 | 0 | validateBuiltinArgs(loc, "parseYaml", args, {Value::STRING}); |
1608 | |
|
1609 | 0 | std::string value = encode_utf8(static_cast<HeapString *>(args[0].v.h)->value); |
1610 | |
|
1611 | 0 | ryml::Tree tree = treeFromString(value); |
1612 | |
|
1613 | 0 | json j; |
1614 | 0 | if (tree.is_stream(tree.root_id())) { |
1615 | | // Split into individual yaml documents |
1616 | 0 | std::stringstream ss; |
1617 | 0 | ss << tree; |
1618 | 0 | std::vector<std::string> v = split(ss.str(), "---\n"); |
1619 | | |
1620 | | // Convert yaml to json and push onto json array |
1621 | 0 | ryml::Tree doc; |
1622 | 0 | for (std::size_t i = 0; i < v.size(); ++i) { |
1623 | 0 | if (!v[i].empty()) { |
1624 | 0 | doc = treeFromString(v[i]); |
1625 | 0 | j.push_back(yamlTreeToJson(doc)); |
1626 | 0 | } |
1627 | 0 | } |
1628 | 0 | } else { |
1629 | 0 | j = yamlTreeToJson(tree); |
1630 | 0 | } |
1631 | |
|
1632 | 0 | bool filled; |
1633 | |
|
1634 | 0 | otherJsonToHeap(j, filled, scratch); |
1635 | |
|
1636 | 0 | return nullptr; |
1637 | 0 | } |
1638 | | |
1639 | 0 | const ryml::Tree treeFromString(const std::string& s) { |
1640 | 0 | return ryml::parse(c4::to_csubstr(s)); |
1641 | 0 | } |
1642 | | |
1643 | 0 | const std::vector<std::string> split(const std::string& s, const std::string& delimiter) { |
1644 | 0 | size_t pos_start = 0, pos_end, delim_len = delimiter.length(); |
1645 | 0 | std::string token; |
1646 | 0 | std::vector<std::string> res; |
1647 | |
|
1648 | 0 | while ((pos_end = s.find(delimiter, pos_start)) != std::string::npos) { |
1649 | 0 | token = s.substr(pos_start, pos_end - pos_start); |
1650 | 0 | pos_start = pos_end + delim_len; |
1651 | 0 | res.push_back(token); |
1652 | 0 | } |
1653 | |
|
1654 | 0 | res.push_back(s.substr(pos_start)); |
1655 | 0 | return res; |
1656 | 0 | } |
1657 | | |
1658 | 0 | const json yamlTreeToJson(const ryml::Tree& tree) { |
1659 | 0 | std::ostringstream jsonStream; |
1660 | 0 | jsonStream << ryml::as_json(tree); |
1661 | 0 | return json::parse(jsonStream.str()); |
1662 | 0 | } |
1663 | | |
1664 | 0 | void otherJsonToHeap(const json &v, bool &filled, Value &attach) { |
1665 | | // In order to not anger the garbage collector, assign to attach immediately after |
1666 | | // making the heap object. |
1667 | 0 | switch (v.type()) { |
1668 | 0 | case json::value_t::string: |
1669 | 0 | attach = makeString(decode_utf8(v.get<std::string>())); |
1670 | 0 | filled = true; |
1671 | 0 | break; |
1672 | | |
1673 | 0 | case json::value_t::boolean: |
1674 | 0 | attach = makeBoolean(v.get<bool>()); |
1675 | 0 | filled = true; |
1676 | 0 | break; |
1677 | | |
1678 | 0 | case json::value_t::number_integer: |
1679 | 0 | case json::value_t::number_unsigned: |
1680 | 0 | case json::value_t::number_float: |
1681 | 0 | attach = makeNumber(v.get<double>()); |
1682 | 0 | filled = true; |
1683 | 0 | break; |
1684 | | |
1685 | 0 | case json::value_t::null: |
1686 | 0 | attach = makeNull(); |
1687 | 0 | filled = true; |
1688 | 0 | break; |
1689 | | |
1690 | 0 | case json::value_t::array:{ |
1691 | 0 | attach = makeArray(std::vector<HeapThunk *>{}); |
1692 | 0 | filled = true; |
1693 | 0 | auto *arr = static_cast<HeapArray *>(attach.v.h); |
1694 | 0 | for (size_t i = 0; i < v.size(); ++i) { |
1695 | 0 | arr->elements.push_back(makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr)); |
1696 | 0 | otherJsonToHeap(v[i], arr->elements[i]->filled, arr->elements[i]->content); |
1697 | 0 | } |
1698 | 0 | } break; |
1699 | | |
1700 | 0 | case json::value_t::object: { |
1701 | 0 | attach = makeObject<HeapComprehensionObject>( |
1702 | 0 | BindingFrame{}, jsonObjVar, idJsonObjVar, BindingFrame{}); |
1703 | 0 | filled = true; |
1704 | 0 | auto *obj = static_cast<HeapComprehensionObject *>(attach.v.h); |
1705 | 0 | for (auto it = v.begin(); it != v.end(); ++it) { |
1706 | 0 | auto *thunk = makeHeap<HeapThunk>(idJsonObjVar, nullptr, 0, nullptr); |
1707 | 0 | obj->compValues[alloc->makeIdentifier(decode_utf8(it.key()))] = thunk; |
1708 | 0 | otherJsonToHeap(it.value(), thunk->filled, thunk->content); |
1709 | 0 | } |
1710 | 0 | } break; |
1711 | | |
1712 | 0 | case json::value_t::discarded: { |
1713 | 0 | abort(); |
1714 | 0 | } |
1715 | 0 | } |
1716 | 0 | } |
1717 | | |
1718 | | void joinString(bool &first, UString &running, const Value &sep, unsigned idx, const Value &elt) |
1719 | 0 | { |
1720 | 0 | if (elt.t == Value::NULL_TYPE) { |
1721 | 0 | return; |
1722 | 0 | } |
1723 | 0 | if (elt.t != Value::STRING) { |
1724 | 0 | std::stringstream ss; |
1725 | 0 | ss << "expected string but arr[" << idx << "] was " << type_str(elt); |
1726 | 0 | throw makeError(stack.top().location, ss.str()); |
1727 | 0 | } |
1728 | 0 | if (!first) { |
1729 | 0 | running.append(static_cast<HeapString *>(sep.v.h)->value); |
1730 | 0 | } |
1731 | 0 | first = false; |
1732 | 0 | running.append(static_cast<HeapString *>(elt.v.h)->value); |
1733 | 0 | } |
1734 | | |
1735 | | const AST *joinStrings(void) |
1736 | 0 | { |
1737 | 0 | Frame &f = stack.top(); |
1738 | 0 | const auto& elements = static_cast<HeapArray*>(f.val2.v.h)->elements; |
1739 | 0 | while (f.elementId < elements.size()) { |
1740 | 0 | auto *th = elements[f.elementId]; |
1741 | 0 | if (th->filled) { |
1742 | 0 | joinString(f.first, f.str, f.val, f.elementId, th->content); |
1743 | 0 | f.elementId++; |
1744 | 0 | } else { |
1745 | 0 | stack.newCall(f.location, th, th->self, th->offset, th->upValues); |
1746 | 0 | return th->body; |
1747 | 0 | } |
1748 | 0 | } |
1749 | 0 | scratch = makeString(f.str); |
1750 | 0 | return nullptr; |
1751 | 0 | } |
1752 | | |
1753 | | void joinArray(bool &first, std::vector<HeapThunk*> &running, const Value &sep, unsigned idx, |
1754 | | const Value &elt) |
1755 | 0 | { |
1756 | 0 | if (elt.t == Value::NULL_TYPE) { |
1757 | 0 | return; |
1758 | 0 | } |
1759 | 0 | if (elt.t != Value::ARRAY) { |
1760 | 0 | std::stringstream ss; |
1761 | 0 | ss << "expected array but arr[" << idx << "] was " << type_str(elt); |
1762 | 0 | throw makeError(stack.top().location, ss.str()); |
1763 | 0 | } |
1764 | 0 | if (!first) { |
1765 | 0 | auto& elts = static_cast<HeapArray *>(sep.v.h)->elements; |
1766 | 0 | running.insert(running.end(), elts.begin(), elts.end()); |
1767 | 0 | } |
1768 | 0 | first = false; |
1769 | 0 | auto& elts = static_cast<HeapArray *>(elt.v.h)->elements; |
1770 | 0 | running.insert(running.end(), elts.begin(), elts.end()); |
1771 | 0 | } |
1772 | | |
1773 | | const AST *joinArrays(void) |
1774 | 0 | { |
1775 | 0 | Frame &f = stack.top(); |
1776 | 0 | const auto& elements = static_cast<HeapArray*>(f.val2.v.h)->elements; |
1777 | 0 | while (f.elementId < elements.size()) { |
1778 | 0 | auto *th = elements[f.elementId]; |
1779 | 0 | if (th->filled) { |
1780 | 0 | joinArray(f.first, f.thunks, f.val, f.elementId, th->content); |
1781 | 0 | f.elementId++; |
1782 | 0 | } else { |
1783 | 0 | stack.newCall(f.location, th, th->self, th->offset, th->upValues); |
1784 | 0 | return th->body; |
1785 | 0 | } |
1786 | 0 | } |
1787 | 0 | scratch = makeArray(f.thunks); |
1788 | 0 | return nullptr; |
1789 | 0 | } |
1790 | | |
1791 | | const AST *builtinJoin(const LocationRange &loc, const std::vector<Value> &args) |
1792 | 0 | { |
1793 | 0 | if (args[0].t != Value::ARRAY && args[0].t != Value::STRING) { |
1794 | 0 | std::stringstream ss; |
1795 | 0 | ss << "join first parameter should be string or array, got " << type_str(args[0]); |
1796 | 0 | throw makeError(loc, ss.str()); |
1797 | 0 | } |
1798 | 0 | if (args[1].t != Value::ARRAY) { |
1799 | 0 | std::stringstream ss; |
1800 | 0 | ss << "join second parameter should be array, got " << type_str(args[1]); |
1801 | 0 | throw makeError(loc, ss.str()); |
1802 | 0 | } |
1803 | 0 | Frame &f = stack.top(); |
1804 | 0 | if (args[0].t == Value::STRING) { |
1805 | 0 | f.kind = FRAME_BUILTIN_JOIN_STRINGS; |
1806 | 0 | f.val = args[0]; // sep |
1807 | 0 | f.val2 = args[1]; // arr |
1808 | 0 | f.str.clear(); |
1809 | 0 | f.first = true; |
1810 | 0 | f.elementId = 0; |
1811 | 0 | return joinStrings(); |
1812 | 0 | } else { |
1813 | 0 | f.kind = FRAME_BUILTIN_JOIN_ARRAYS; |
1814 | 0 | f.val = args[0]; // sep |
1815 | 0 | f.val2 = args[1]; // arr |
1816 | 0 | f.thunks.clear(); |
1817 | 0 | f.first = true; |
1818 | 0 | f.elementId = 0; |
1819 | 0 | return joinArrays(); |
1820 | 0 | } |
1821 | 0 | } |
1822 | | |
1823 | | void jsonToHeap(const std::unique_ptr<JsonnetJsonValue> &v, bool &filled, Value &attach) |
1824 | 0 | { |
1825 | | // In order to not anger the garbage collector, assign to attach immediately after |
1826 | | // making the heap object. |
1827 | 0 | switch (v->kind) { |
1828 | 0 | case JsonnetJsonValue::STRING: |
1829 | 0 | attach = makeString(decode_utf8(v->string)); |
1830 | 0 | filled = true; |
1831 | 0 | break; |
1832 | | |
1833 | 0 | case JsonnetJsonValue::BOOL: |
1834 | 0 | attach = makeBoolean(v->number != 0.0); |
1835 | 0 | filled = true; |
1836 | 0 | break; |
1837 | | |
1838 | 0 | case JsonnetJsonValue::NUMBER: |
1839 | 0 | attach = makeNumber(v->number); |
1840 | 0 | filled = true; |
1841 | 0 | break; |
1842 | | |
1843 | 0 | case JsonnetJsonValue::NULL_KIND: |
1844 | 0 | attach = makeNull(); |
1845 | 0 | filled = true; |
1846 | 0 | break; |
1847 | | |
1848 | 0 | case JsonnetJsonValue::ARRAY: { |
1849 | 0 | attach = makeArray(std::vector<HeapThunk *>{}); |
1850 | 0 | filled = true; |
1851 | 0 | auto *arr = static_cast<HeapArray *>(attach.v.h); |
1852 | 0 | for (size_t i = 0; i < v->elements.size(); ++i) { |
1853 | 0 | arr->elements.push_back( |
1854 | 0 | makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr)); |
1855 | 0 | jsonToHeap(v->elements[i], arr->elements[i]->filled, arr->elements[i]->content); |
1856 | 0 | } |
1857 | 0 | } break; |
1858 | | |
1859 | 0 | case JsonnetJsonValue::OBJECT: { |
1860 | 0 | attach = makeObject<HeapComprehensionObject>( |
1861 | 0 | BindingFrame{}, jsonObjVar, idJsonObjVar, BindingFrame{}); |
1862 | 0 | filled = true; |
1863 | 0 | auto *obj = static_cast<HeapComprehensionObject *>(attach.v.h); |
1864 | 0 | for (const auto &pair : v->fields) { |
1865 | 0 | auto *thunk = makeHeap<HeapThunk>(idJsonObjVar, nullptr, 0, nullptr); |
1866 | 0 | obj->compValues[alloc->makeIdentifier(decode_utf8(pair.first))] = thunk; |
1867 | 0 | jsonToHeap(pair.second, thunk->filled, thunk->content); |
1868 | 0 | } |
1869 | 0 | } break; |
1870 | 0 | } |
1871 | 0 | } |
1872 | | |
1873 | | UString toString(const LocationRange &loc) |
1874 | 408k | { |
1875 | 408k | return manifestJson(loc, false, U""); |
1876 | 408k | } |
1877 | | |
1878 | | /** Recursively collect an object's invariants. |
1879 | | * |
1880 | | * \param curr |
1881 | | * \param self |
1882 | | * \param offset |
1883 | | * \param thunks |
1884 | | */ |
1885 | | void objectInvariants(HeapObject *curr, HeapObject *self, unsigned &counter, |
1886 | | std::vector<HeapThunk *> &thunks) |
1887 | 82.4M | { |
1888 | 82.4M | if (auto *ext = dynamic_cast<HeapExtendedObject *>(curr)) { |
1889 | 30.1M | objectInvariants(ext->right, self, counter, thunks); |
1890 | 30.1M | objectInvariants(ext->left, self, counter, thunks); |
1891 | 52.2M | } else { |
1892 | 52.2M | if (auto *simp = dynamic_cast<HeapSimpleObject *>(curr)) { |
1893 | 52.2M | for (AST *assert : simp->asserts) { |
1894 | 1.65M | auto *el_th = makeHeap<HeapThunk>(idInvariant, self, counter, assert); |
1895 | 1.65M | el_th->upValues = simp->upValues; |
1896 | 1.65M | thunks.push_back(el_th); |
1897 | 1.65M | } |
1898 | 52.2M | } |
1899 | 52.2M | counter++; |
1900 | 52.2M | } |
1901 | 82.4M | } |
1902 | | |
1903 | | /** Index an object's field. |
1904 | | * |
1905 | | * \param loc Location where the e.f occurred. |
1906 | | * \param obj The target |
1907 | | * \param f The field |
1908 | | */ |
1909 | | const AST *objectIndex(const LocationRange &loc, HeapObject *obj, const Identifier *f, |
1910 | | unsigned offset) |
1911 | 28.0M | { |
1912 | 28.0M | unsigned found_at = 0; |
1913 | 28.0M | HeapObject *self = obj; |
1914 | 28.0M | HeapLeafObject *found = findObject(f, obj, offset, found_at); |
1915 | 28.0M | if (found == nullptr) { |
1916 | 25 | throw makeError(loc, "field does not exist: " + encode_utf8(f->name)); |
1917 | 25 | } |
1918 | 28.0M | if (auto *simp = dynamic_cast<HeapSimpleObject *>(found)) { |
1919 | 28.0M | auto it = simp->fields.find(f); |
1920 | 28.0M | const AST *body = it->second.body; |
1921 | | |
1922 | 28.0M | stack.newCall(loc, simp, self, found_at, simp->upValues); |
1923 | 28.0M | return body; |
1924 | 28.0M | } else { |
1925 | | // If a HeapLeafObject is not HeapSimpleObject, it must be HeapComprehensionObject. |
1926 | 0 | auto *comp = static_cast<HeapComprehensionObject *>(found); |
1927 | 0 | auto it = comp->compValues.find(f); |
1928 | 0 | auto *th = it->second; |
1929 | 0 | BindingFrame binds = comp->upValues; |
1930 | 0 | binds[comp->id] = th; |
1931 | 0 | stack.newCall(loc, comp, self, found_at, binds); |
1932 | 0 | return comp->value; |
1933 | 0 | } |
1934 | 28.0M | } |
1935 | | |
1936 | | void runInvariants(const LocationRange &loc, HeapObject *self) |
1937 | 927k | { |
1938 | 927k | if (stack.alreadyExecutingInvariants(self)) |
1939 | 32.8k | return; |
1940 | | |
1941 | 895k | unsigned counter = 0; |
1942 | 895k | unsigned initial_stack_size = stack.size(); |
1943 | 895k | stack.newFrame(FRAME_INVARIANTS, loc); |
1944 | 895k | std::vector<HeapThunk *> &thunks = stack.top().thunks; |
1945 | 895k | objectInvariants(self, self, counter, thunks); |
1946 | 895k | if (thunks.size() == 0) { |
1947 | 858k | stack.pop(); |
1948 | 858k | return; |
1949 | 858k | } |
1950 | 36.7k | HeapThunk *thunk = thunks[0]; |
1951 | 36.7k | stack.top().elementId = 1; |
1952 | 36.7k | stack.top().self = self; |
1953 | 36.7k | stack.newCall(loc, thunk, thunk->self, thunk->offset, thunk->upValues); |
1954 | 36.7k | evaluate(thunk->body, initial_stack_size); |
1955 | 36.7k | } |
1956 | | |
1957 | | /** Call a sourceVal function with given arguments. |
1958 | | * |
1959 | | * This function requires all arguments to be positional. It also does not |
1960 | | * support default arguments. This is intended to be used internally so, |
1961 | | * error checking is also skipped. |
1962 | | */ |
1963 | 12.8k | const AST *callSourceVal(const AST *ast, HeapThunk *sourceVal, std::vector<HeapThunk*> args) { |
1964 | 12.8k | assert(sourceVal != nullptr); |
1965 | 0 | assert(sourceVal->filled); |
1966 | 0 | assert(sourceVal->content.t == Value::FUNCTION); |
1967 | 0 | auto *func = static_cast<HeapClosure *>(sourceVal->content.v.h); |
1968 | 12.8k | BindingFrame up_values = func->upValues; |
1969 | 38.6k | for (size_t i = 0; i < args.size(); ++i) { |
1970 | 25.7k | up_values.insert({func->params[i].id, args[i]}); |
1971 | 25.7k | } |
1972 | 12.8k | stack.newCall(ast->location, func, func->self, func->offset, up_values); |
1973 | 12.8k | return func->body; |
1974 | 12.8k | } |
1975 | | |
1976 | | /** Evaluate the given AST to a value. |
1977 | | * |
1978 | | * Rather than call itself recursively, this function maintains a separate stack of |
1979 | | * partially-evaluated constructs. First, the AST is handled depending on its type. If |
1980 | | * this cannot be completed without evaluating another AST (e.g. a sub expression) then a |
1981 | | * frame is pushed onto the stack containing the partial state, and the code jumps back to |
1982 | | * the beginning of this function. Once there are no more ASTs to evaluate, the code |
1983 | | * executes the second part of the function to unwind the stack. If the stack cannot be |
1984 | | * completely unwound without evaluating an AST then it jumps back to the beginning of the |
1985 | | * function again. The process terminates when the AST has been processed and the stack is |
1986 | | * the same size it was at the beginning of the call to evaluate. |
1987 | | */ |
1988 | | void evaluate(const AST *ast_, unsigned initial_stack_size) |
1989 | 7.79M | { |
1990 | 275M | recurse: |
1991 | | |
1992 | 275M | switch (ast_->type) { |
1993 | 22.6M | case AST_APPLY: { |
1994 | 22.6M | const auto &ast = *static_cast<const Apply *>(ast_); |
1995 | 22.6M | stack.newFrame(FRAME_APPLY_TARGET, ast_); |
1996 | 22.6M | ast_ = ast.target; |
1997 | 22.6M | goto recurse; |
1998 | 0 | } break; |
1999 | | |
2000 | 1.91M | case AST_ARRAY: { |
2001 | 1.91M | const auto &ast = *static_cast<const Array *>(ast_); |
2002 | 1.91M | HeapObject *self; |
2003 | 1.91M | unsigned offset; |
2004 | 1.91M | stack.getSelfBinding(self, offset); |
2005 | 1.91M | scratch = makeArray({}); |
2006 | 1.91M | auto &elements = static_cast<HeapArray *>(scratch.v.h)->elements; |
2007 | 14.5M | for (const auto &el : ast.elements) { |
2008 | 14.5M | auto *el_th = makeHeap<HeapThunk>(idArrayElement, self, offset, el.expr); |
2009 | 14.5M | el_th->upValues = capture(el.expr->freeVariables); |
2010 | 14.5M | elements.push_back(el_th); |
2011 | 14.5M | } |
2012 | 1.91M | } break; |
2013 | | |
2014 | 14.4M | case AST_BINARY: { |
2015 | 14.4M | const auto &ast = *static_cast<const Binary *>(ast_); |
2016 | 14.4M | stack.newFrame(FRAME_BINARY_LEFT, ast_); |
2017 | 14.4M | ast_ = ast.left; |
2018 | 14.4M | goto recurse; |
2019 | 0 | } break; |
2020 | | |
2021 | 16.7M | case AST_BUILTIN_FUNCTION: { |
2022 | 16.7M | const auto &ast = *static_cast<const BuiltinFunction *>(ast_); |
2023 | 16.7M | HeapClosure::Params params; |
2024 | 16.7M | params.reserve(ast.params.size()); |
2025 | 26.4M | for (const auto &p : ast.params) { |
2026 | | // None of the builtins have default args. |
2027 | 26.4M | params.emplace_back(p, nullptr); |
2028 | 26.4M | } |
2029 | 16.7M | scratch = makeBuiltin(ast.name, params); |
2030 | 16.7M | } break; |
2031 | | |
2032 | 17.6M | case AST_CONDITIONAL: { |
2033 | 17.6M | const auto &ast = *static_cast<const Conditional *>(ast_); |
2034 | 17.6M | stack.newFrame(FRAME_IF, ast_); |
2035 | 17.6M | ast_ = ast.cond; |
2036 | 17.6M | goto recurse; |
2037 | 0 | } break; |
2038 | | |
2039 | 13.0k | case AST_ERROR: { |
2040 | 13.0k | const auto &ast = *static_cast<const Error *>(ast_); |
2041 | 13.0k | stack.newFrame(FRAME_ERROR, ast_); |
2042 | 13.0k | ast_ = ast.expr; |
2043 | 13.0k | goto recurse; |
2044 | 0 | } break; |
2045 | | |
2046 | 3.89M | case AST_FUNCTION: { |
2047 | 3.89M | const auto &ast = *static_cast<const Function *>(ast_); |
2048 | 3.89M | auto env = capture(ast.freeVariables); |
2049 | 3.89M | HeapObject *self; |
2050 | 3.89M | unsigned offset; |
2051 | 3.89M | stack.getSelfBinding(self, offset); |
2052 | 3.89M | HeapClosure::Params params; |
2053 | 3.89M | params.reserve(ast.params.size()); |
2054 | 7.48M | for (const auto &p : ast.params) { |
2055 | 7.48M | params.emplace_back(p.id, p.expr); |
2056 | 7.48M | } |
2057 | 3.89M | scratch = makeClosure(env, self, offset, params, ast.body); |
2058 | 3.89M | } break; |
2059 | | |
2060 | 150 | case AST_IMPORT: { |
2061 | 150 | const auto &ast = *static_cast<const Import *>(ast_); |
2062 | 150 | HeapThunk *thunk = import(ast.location, ast.file); |
2063 | 150 | if (thunk->filled) { |
2064 | 0 | scratch = thunk->content; |
2065 | 150 | } else { |
2066 | 150 | stack.newCall(ast.location, thunk, thunk->self, thunk->offset, thunk->upValues); |
2067 | 150 | ast_ = thunk->body; |
2068 | 150 | goto recurse; |
2069 | 150 | } |
2070 | 150 | } break; |
2071 | | |
2072 | 9 | case AST_IMPORTSTR: { |
2073 | 9 | const auto &ast = *static_cast<const Importstr *>(ast_); |
2074 | 9 | const ImportCacheValue *value = importData(ast.location, ast.file); |
2075 | 9 | scratch = makeString(decode_utf8(value->content)); |
2076 | 9 | } break; |
2077 | | |
2078 | 17 | case AST_IMPORTBIN: { |
2079 | 17 | const auto &ast = *static_cast<const Importbin *>(ast_); |
2080 | 17 | const ImportCacheValue *value = importData(ast.location, ast.file); |
2081 | 17 | scratch = makeArray({}); |
2082 | 17 | auto &elements = static_cast<HeapArray *>(scratch.v.h)->elements; |
2083 | 17 | elements.reserve(value->content.size()); |
2084 | 17 | for (const auto c : value->content) { |
2085 | 0 | auto *th = makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr); |
2086 | 0 | elements.push_back(th); |
2087 | 0 | th->fill(makeNumber(uint8_t(c))); |
2088 | 0 | } |
2089 | 17 | } break; |
2090 | | |
2091 | 5.93M | case AST_IN_SUPER: { |
2092 | 5.93M | const auto &ast = *static_cast<const InSuper *>(ast_); |
2093 | 5.93M | stack.newFrame(FRAME_IN_SUPER_ELEMENT, ast_); |
2094 | 5.93M | ast_ = ast.element; |
2095 | 5.93M | goto recurse; |
2096 | 150 | } break; |
2097 | | |
2098 | 22.1M | case AST_INDEX: { |
2099 | 22.1M | const auto &ast = *static_cast<const Index *>(ast_); |
2100 | 22.1M | stack.newFrame(FRAME_INDEX_TARGET, ast_); |
2101 | 22.1M | ast_ = ast.target; |
2102 | 22.1M | goto recurse; |
2103 | 150 | } break; |
2104 | | |
2105 | 11.3M | case AST_LOCAL: { |
2106 | 11.3M | const auto &ast = *static_cast<const Local *>(ast_); |
2107 | 11.3M | stack.newFrame(FRAME_LOCAL, ast_); |
2108 | 11.3M | Frame &f = stack.top(); |
2109 | | // First build all the thunks and bind them. |
2110 | 11.3M | HeapObject *self; |
2111 | 11.3M | unsigned offset; |
2112 | 11.3M | stack.getSelfBinding(self, offset); |
2113 | 36.1M | for (const auto &bind : ast.binds) { |
2114 | | // Note that these 2 lines must remain separate to avoid the GC running |
2115 | | // when bindings has a nullptr for key bind.first. |
2116 | 36.1M | auto *th = makeHeap<HeapThunk>(bind.var, self, offset, bind.body); |
2117 | 36.1M | f.bindings[bind.var] = th; |
2118 | 36.1M | } |
2119 | | // Now capture the environment (including the new thunks, to make cycles). |
2120 | 36.1M | for (const auto &bind : ast.binds) { |
2121 | 36.1M | auto *thunk = f.bindings[bind.var]; |
2122 | 36.1M | thunk->upValues = capture(bind.body->freeVariables); |
2123 | 36.1M | } |
2124 | 11.3M | ast_ = ast.body; |
2125 | 11.3M | goto recurse; |
2126 | 150 | } break; |
2127 | | |
2128 | 981k | case AST_LITERAL_BOOLEAN: { |
2129 | 981k | const auto &ast = *static_cast<const LiteralBoolean *>(ast_); |
2130 | 981k | scratch = makeBoolean(ast.value); |
2131 | 981k | } break; |
2132 | | |
2133 | 10.8M | case AST_LITERAL_NUMBER: { |
2134 | 10.8M | const auto &ast = *static_cast<const LiteralNumber *>(ast_); |
2135 | 10.8M | scratch = makeNumberCheck(ast_->location, ast.value); |
2136 | 10.8M | } break; |
2137 | | |
2138 | 68.6M | case AST_LITERAL_STRING: { |
2139 | 68.6M | const auto &ast = *static_cast<const LiteralString *>(ast_); |
2140 | 68.6M | scratch = makeString(ast.value); |
2141 | 68.6M | } break; |
2142 | | |
2143 | 220k | case AST_LITERAL_NULL: { |
2144 | 220k | scratch = makeNull(); |
2145 | 220k | } break; |
2146 | | |
2147 | 3.51M | case AST_DESUGARED_OBJECT: { |
2148 | 3.51M | const auto &ast = *static_cast<const DesugaredObject *>(ast_); |
2149 | 3.51M | if (ast.fields.empty()) { |
2150 | 1.60M | auto env = capture(ast.freeVariables); |
2151 | 1.60M | std::map<const Identifier *, HeapSimpleObject::Field> fields; |
2152 | 1.60M | scratch = makeObject<HeapSimpleObject>(env, fields, ast.asserts); |
2153 | 1.91M | } else { |
2154 | 1.91M | auto env = capture(ast.freeVariables); |
2155 | 1.91M | stack.newFrame(FRAME_OBJECT, ast_); |
2156 | 1.91M | auto fit = ast.fields.begin(); |
2157 | 1.91M | stack.top().fit = fit; |
2158 | 1.91M | ast_ = fit->name; |
2159 | 1.91M | goto recurse; |
2160 | 1.91M | } |
2161 | 3.51M | } break; |
2162 | | |
2163 | 1.60M | case AST_OBJECT_COMPREHENSION_SIMPLE: { |
2164 | 56 | const auto &ast = *static_cast<const ObjectComprehensionSimple *>(ast_); |
2165 | 56 | stack.newFrame(FRAME_OBJECT_COMP_ARRAY, ast_); |
2166 | 56 | ast_ = ast.array; |
2167 | 56 | goto recurse; |
2168 | 3.51M | } break; |
2169 | | |
2170 | 3.83M | case AST_SELF: { |
2171 | 3.83M | scratch.t = Value::OBJECT; |
2172 | 3.83M | HeapObject *self; |
2173 | 3.83M | unsigned offset; |
2174 | 3.83M | stack.getSelfBinding(self, offset); |
2175 | 3.83M | scratch.v.h = self; |
2176 | 3.83M | } break; |
2177 | | |
2178 | 5.62M | case AST_SUPER_INDEX: { |
2179 | 5.62M | const auto &ast = *static_cast<const SuperIndex *>(ast_); |
2180 | 5.62M | stack.newFrame(FRAME_SUPER_INDEX, ast_); |
2181 | 5.62M | ast_ = ast.index; |
2182 | 5.62M | goto recurse; |
2183 | 3.51M | } break; |
2184 | | |
2185 | 5.15M | case AST_UNARY: { |
2186 | 5.15M | const auto &ast = *static_cast<const Unary *>(ast_); |
2187 | 5.15M | stack.newFrame(FRAME_UNARY, ast_); |
2188 | 5.15M | ast_ = ast.expr; |
2189 | 5.15M | goto recurse; |
2190 | 3.51M | } break; |
2191 | | |
2192 | 59.5M | case AST_VAR: { |
2193 | 59.5M | const auto &ast = *static_cast<const Var *>(ast_); |
2194 | 59.5M | auto *thunk = stack.lookUpVar(ast.id); |
2195 | 59.5M | if (thunk == nullptr) { |
2196 | 0 | std::cerr << "INTERNAL ERROR: Could not bind variable: " |
2197 | 0 | << encode_utf8(ast.id->name) << " at " |
2198 | 0 | << ast.location << std::endl; |
2199 | 0 | std::abort(); |
2200 | 0 | } |
2201 | 59.5M | if (thunk->filled) { |
2202 | 44.0M | scratch = thunk->content; |
2203 | 44.0M | } else { |
2204 | 15.4M | stack.newCall(ast.location, thunk, thunk->self, thunk->offset, thunk->upValues); |
2205 | 15.4M | ast_ = thunk->body; |
2206 | 15.4M | goto recurse; |
2207 | 15.4M | } |
2208 | 59.5M | } break; |
2209 | | |
2210 | 44.0M | default: |
2211 | 0 | std::cerr << "INTERNAL ERROR: Unknown AST: " << ast_->type << std::endl; |
2212 | 0 | std::abort(); |
2213 | 275M | } |
2214 | | |
2215 | | // To evaluate another AST, set ast to it, then goto recurse. |
2216 | | // To pop, exit the switch or goto popframe |
2217 | | // To change the frame and re-enter the switch, goto replaceframe |
2218 | 312M | while (stack.size() > initial_stack_size) { |
2219 | 304M | Frame &f = stack.top(); |
2220 | 304M | switch (f.kind) { |
2221 | 22.6M | case FRAME_APPLY_TARGET: { |
2222 | 22.6M | const auto &ast = *static_cast<const Apply *>(f.ast); |
2223 | 22.6M | if (scratch.t != Value::FUNCTION) { |
2224 | 25 | throw makeError(ast.location, |
2225 | 25 | "only functions can be called, got " + type_str(scratch)); |
2226 | 25 | } |
2227 | 22.6M | auto *func = static_cast<HeapClosure *>(scratch.v.h); |
2228 | | |
2229 | 22.6M | std::set<const Identifier *> params_needed; |
2230 | 38.8M | for (const auto ¶m : func->params) { |
2231 | 38.8M | params_needed.insert(param.id); |
2232 | 38.8M | } |
2233 | | |
2234 | | // Create thunks for arguments. |
2235 | 22.6M | std::vector<HeapThunk *> positional_args; |
2236 | 22.6M | BindingFrame args; |
2237 | 22.6M | bool got_named = false; |
2238 | 61.5M | for (std::size_t i = 0; i < ast.args.size(); ++i) { |
2239 | 38.8M | const auto &arg = ast.args[i]; |
2240 | | |
2241 | 38.8M | const Identifier *name; |
2242 | 38.8M | if (arg.id != nullptr) { |
2243 | 0 | got_named = true; |
2244 | 0 | name = arg.id; |
2245 | 38.8M | } else { |
2246 | 38.8M | if (got_named) { |
2247 | 0 | std::stringstream ss; |
2248 | 0 | ss << "internal error: got positional param after named at index " |
2249 | 0 | << i; |
2250 | 0 | throw makeError(ast.location, ss.str()); |
2251 | 0 | } |
2252 | 38.8M | if (i >= func->params.size()) { |
2253 | 1 | std::stringstream ss; |
2254 | 1 | ss << "too many args, function has " << func->params.size() |
2255 | 1 | << " parameter(s)"; |
2256 | 1 | throw makeError(ast.location, ss.str()); |
2257 | 1 | } |
2258 | 38.8M | name = func->params[i].id; |
2259 | 38.8M | } |
2260 | | // Special case for builtin functions -- leave identifier blank for |
2261 | | // them in the thunk. This removes the thunk frame from the stacktrace. |
2262 | 38.8M | const Identifier *name_ = func->body == nullptr ? nullptr : name; |
2263 | 38.8M | HeapObject *self; |
2264 | 38.8M | unsigned offset; |
2265 | 38.8M | stack.getSelfBinding(self, offset); |
2266 | 38.8M | auto *thunk = makeHeap<HeapThunk>(name_, self, offset, arg.expr); |
2267 | 38.8M | thunk->upValues = capture(arg.expr->freeVariables); |
2268 | | // While making the thunks, keep them in a frame to avoid premature garbage |
2269 | | // collection. |
2270 | 38.8M | f.thunks.push_back(thunk); |
2271 | 38.8M | if (args.find(name) != args.end()) { |
2272 | 0 | std::stringstream ss; |
2273 | 0 | ss << "binding parameter a second time: " << encode_utf8(name->name); |
2274 | 0 | throw makeError(ast.location, ss.str()); |
2275 | 0 | } |
2276 | 38.8M | args[name] = thunk; |
2277 | 38.8M | if (params_needed.find(name) == params_needed.end()) { |
2278 | 0 | std::stringstream ss; |
2279 | 0 | ss << "function has no parameter " << encode_utf8(name->name); |
2280 | 0 | throw makeError(ast.location, ss.str()); |
2281 | 0 | } |
2282 | 38.8M | } |
2283 | | |
2284 | | // For any func params for which there was no arg, create a thunk for those and |
2285 | | // bind the default argument. Allow default thunks to see other params. If no |
2286 | | // default argument than raise an error. |
2287 | | |
2288 | | // Raise errors for unbound params, create thunks (but don't fill in upvalues). |
2289 | | // This is a subset of f.thunks, so will not get garbage collected. |
2290 | 22.6M | std::vector<HeapThunk *> def_arg_thunks; |
2291 | 38.8M | for (const auto ¶m : func->params) { |
2292 | 38.8M | if (args.find(param.id) != args.end()) |
2293 | 38.8M | continue; |
2294 | 262 | if (param.def == nullptr) { |
2295 | 27 | std::stringstream ss; |
2296 | 27 | ss << "function parameter " << encode_utf8(param.id->name) |
2297 | 27 | << " not bound in call."; |
2298 | 27 | throw makeError(ast.location, ss.str()); |
2299 | 27 | } |
2300 | | |
2301 | | // Special case for builtin functions -- leave identifier blank for |
2302 | | // them in the thunk. This removes the thunk frame from the stacktrace. |
2303 | 235 | const Identifier *name_ = func->body == nullptr ? nullptr : param.id; |
2304 | 235 | auto *thunk = |
2305 | 235 | makeHeap<HeapThunk>(name_, func->self, func->offset, param.def); |
2306 | 235 | f.thunks.push_back(thunk); |
2307 | 235 | def_arg_thunks.push_back(thunk); |
2308 | 235 | args[param.id] = thunk; |
2309 | 235 | } |
2310 | | |
2311 | 22.6M | BindingFrame up_values = func->upValues; |
2312 | 22.6M | up_values.insert(args.begin(), args.end()); |
2313 | | |
2314 | | // Fill in upvalues |
2315 | 22.6M | for (HeapThunk *thunk : def_arg_thunks) { |
2316 | 51 | thunk->upValues = up_values; |
2317 | 51 | } |
2318 | | |
2319 | | // Cache these, because pop will invalidate them. |
2320 | 22.6M | std::vector<HeapThunk *> thunks_copy = f.thunks; |
2321 | | |
2322 | 22.6M | const AST *f_ast = f.ast; |
2323 | 22.6M | stack.pop(); |
2324 | | |
2325 | 22.6M | if (func->body == nullptr) { |
2326 | | // Built-in function. |
2327 | | // Give nullptr for self because no one looking at this frame will |
2328 | | // attempt to bind to self (it's native code). |
2329 | 16.7M | stack.newFrame(FRAME_BUILTIN_FORCE_THUNKS, f_ast); |
2330 | 16.7M | stack.top().thunks = thunks_copy; |
2331 | 16.7M | stack.top().val = scratch; |
2332 | 16.7M | goto replaceframe; |
2333 | 16.7M | } else { |
2334 | | // User defined function. |
2335 | 5.84M | stack.newCall(ast.location, func, func->self, func->offset, up_values); |
2336 | 5.84M | if (ast.tailstrict) { |
2337 | 3.24M | stack.top().tailCall = true; |
2338 | 3.24M | if (thunks_copy.size() == 0) { |
2339 | | // No need to force thunks, proceed straight to body. |
2340 | 0 | ast_ = func->body; |
2341 | 0 | goto recurse; |
2342 | 3.24M | } else { |
2343 | | // The check for args.size() > 0 |
2344 | 3.24M | stack.top().thunks = thunks_copy; |
2345 | 3.24M | stack.top().val = scratch; |
2346 | 3.24M | goto replaceframe; |
2347 | 3.24M | } |
2348 | 3.24M | } else { |
2349 | 2.59M | ast_ = func->body; |
2350 | 2.59M | goto recurse; |
2351 | 2.59M | } |
2352 | 5.84M | } |
2353 | 22.6M | } break; |
2354 | | |
2355 | 14.1M | case FRAME_BINARY_LEFT: { |
2356 | 14.1M | const auto &ast = *static_cast<const Binary *>(f.ast); |
2357 | 14.1M | const Value &lhs = scratch; |
2358 | 14.1M | if (lhs.t == Value::BOOLEAN) { |
2359 | | // Handle short-cut semantics |
2360 | 395k | switch (ast.op) { |
2361 | 182k | case BOP_AND: { |
2362 | 182k | if (!lhs.v.b) { |
2363 | 27.9k | scratch = makeBoolean(false); |
2364 | 27.9k | goto popframe; |
2365 | 27.9k | } |
2366 | 182k | } break; |
2367 | | |
2368 | 209k | case BOP_OR: { |
2369 | 209k | if (lhs.v.b) { |
2370 | 4.67k | scratch = makeBoolean(true); |
2371 | 4.67k | goto popframe; |
2372 | 4.67k | } |
2373 | 209k | } break; |
2374 | | |
2375 | 204k | default:; |
2376 | 395k | } |
2377 | 395k | } |
2378 | 14.1M | stack.top().kind = FRAME_BINARY_RIGHT; |
2379 | 14.1M | stack.top().val = lhs; |
2380 | 14.1M | ast_ = ast.right; |
2381 | 14.1M | goto recurse; |
2382 | 14.1M | } break; |
2383 | | |
2384 | 14.0M | case FRAME_BINARY_RIGHT: { |
2385 | 14.0M | stack.top().val2 = scratch; |
2386 | 14.0M | stack.top().kind = FRAME_BINARY_OP; |
2387 | 14.0M | } |
2388 | | // Falls through. |
2389 | 14.0M | case FRAME_BINARY_OP: { |
2390 | 14.0M | const auto &ast = *static_cast<const Binary *>(f.ast); |
2391 | 14.0M | const Value &lhs = stack.top().val; |
2392 | 14.0M | const Value &rhs = stack.top().val2; |
2393 | | |
2394 | | // Handle cases where the LHS and RHS are not the same type. |
2395 | 14.0M | if (lhs.t == Value::STRING || rhs.t == Value::STRING) { |
2396 | 4.72M | if (ast.op == BOP_PLUS) { |
2397 | | // Handle co-ercions for string processing. |
2398 | 4.61M | stack.top().kind = FRAME_STRING_CONCAT; |
2399 | 4.61M | stack.top().val2 = rhs; |
2400 | 4.61M | goto replaceframe; |
2401 | 4.61M | } |
2402 | 4.72M | } |
2403 | 9.48M | switch (ast.op) { |
2404 | | // Equality can be used when the types don't match. |
2405 | 0 | case BOP_MANIFEST_EQUAL: |
2406 | 0 | std::cerr << "INTERNAL ERROR: Equals not desugared" << std::endl; |
2407 | 0 | abort(); |
2408 | | |
2409 | | // Equality can be used when the types don't match. |
2410 | 0 | case BOP_MANIFEST_UNEQUAL: |
2411 | 0 | std::cerr << "INTERNAL ERROR: Notequals not desugared" << std::endl; |
2412 | 0 | abort(); |
2413 | | |
2414 | | // e in e |
2415 | 5.98k | case BOP_IN: { |
2416 | 5.98k | if (lhs.t != Value::STRING) { |
2417 | 27 | throw makeError(ast.location, |
2418 | 27 | "the left hand side of the 'in' operator should be " |
2419 | 27 | "a string, got " + |
2420 | 27 | type_str(lhs)); |
2421 | 27 | } |
2422 | 5.95k | auto *field = static_cast<HeapString *>(lhs.v.h); |
2423 | 5.95k | switch (rhs.t) { |
2424 | 5.95k | case Value::OBJECT: { |
2425 | 5.95k | auto *obj = static_cast<HeapObject *>(rhs.v.h); |
2426 | 5.95k | auto *fid = alloc->makeIdentifier(field->value); |
2427 | 5.95k | unsigned unused_found_at = 0; |
2428 | 5.95k | bool in = findObject(fid, obj, 0, unused_found_at); |
2429 | 5.95k | scratch = makeBoolean(in); |
2430 | 5.95k | } break; |
2431 | | |
2432 | 3 | default: |
2433 | 3 | throw makeError( |
2434 | 3 | ast.location, |
2435 | 3 | "the right hand side of the 'in' operator should be" |
2436 | 3 | " an object, got " + |
2437 | 3 | type_str(rhs)); |
2438 | 5.95k | } |
2439 | 5.95k | goto popframe; |
2440 | 5.95k | } |
2441 | | |
2442 | 9.47M | default:; |
2443 | 9.48M | } |
2444 | | // Everything else requires matching types. |
2445 | 9.47M | if (lhs.t != rhs.t) { |
2446 | 241 | throw makeError(ast.location, |
2447 | 241 | "binary operator " + bop_string(ast.op) + |
2448 | 241 | " requires " |
2449 | 241 | "matching types, got " + |
2450 | 241 | type_str(lhs) + " and " + type_str(rhs) + "."); |
2451 | 241 | } |
2452 | 9.47M | switch (lhs.t) { |
2453 | 1.61M | case Value::ARRAY: |
2454 | 1.61M | if (ast.op == BOP_PLUS) { |
2455 | 1.60M | auto *arr_l = static_cast<HeapArray *>(lhs.v.h); |
2456 | 1.60M | auto *arr_r = static_cast<HeapArray *>(rhs.v.h); |
2457 | 1.60M | std::vector<HeapThunk *> elements; |
2458 | 1.60M | for (auto *el : arr_l->elements) |
2459 | 145M | elements.push_back(el); |
2460 | 1.60M | for (auto *el : arr_r->elements) |
2461 | 1.81M | elements.push_back(el); |
2462 | 1.60M | scratch = makeArray(elements); |
2463 | 1.60M | } else if (ast.op == BOP_LESS || ast.op == BOP_LESS_EQ || ast.op == BOP_GREATER || ast.op == BOP_GREATER_EQ) { |
2464 | 13.0k | HeapThunk *func; |
2465 | 13.0k | switch(ast.op) { |
2466 | 8.92k | case BOP_LESS: |
2467 | 8.92k | func = sourceVals["__array_less"]; |
2468 | 8.92k | break; |
2469 | 1.12k | case BOP_LESS_EQ: |
2470 | 1.12k | func = sourceVals["__array_less_or_equal"]; |
2471 | 1.12k | break; |
2472 | 828 | case BOP_GREATER: |
2473 | 828 | func = sourceVals["__array_greater"]; |
2474 | 828 | break; |
2475 | 2.17k | case BOP_GREATER_EQ: |
2476 | 2.17k | func = sourceVals["__array_greater_or_equal"]; |
2477 | 2.17k | break; |
2478 | 0 | default: |
2479 | 0 | JSONNET_UNREACHABLE(); |
2480 | 13.0k | } |
2481 | 13.0k | if (!func->filled) { |
2482 | 166 | stack.newCall(ast.location, func, func->self, func->offset, func->upValues); |
2483 | 166 | ast_ = func->body; |
2484 | 166 | goto recurse; |
2485 | 166 | } |
2486 | 12.8k | auto *lhs_th = makeHeap<HeapThunk>(idInternal, f.self, f.offset, ast.left); |
2487 | 12.8k | lhs_th->fill(lhs); |
2488 | 12.8k | f.thunks.push_back(lhs_th); |
2489 | 12.8k | auto *rhs_th = makeHeap<HeapThunk>(idInternal, f.self, f.offset, ast.right); |
2490 | 12.8k | rhs_th->fill(rhs); |
2491 | 12.8k | f.thunks.push_back(rhs_th); |
2492 | 12.8k | const AST *orig_ast = ast_; |
2493 | 12.8k | stack.pop(); |
2494 | 12.8k | ast_ = callSourceVal(orig_ast, func, {lhs_th, rhs_th}); |
2495 | 12.8k | goto recurse; |
2496 | 13.0k | } else { |
2497 | 10 | throw makeError(ast.location, |
2498 | 10 | "binary operator " + bop_string(ast.op) + |
2499 | 10 | " does not operate on arrays."); |
2500 | 10 | } |
2501 | 1.60M | break; |
2502 | | |
2503 | 1.60M | case Value::BOOLEAN: |
2504 | 356k | switch (ast.op) { |
2505 | 152k | case BOP_AND: scratch = makeBoolean(lhs.v.b && rhs.v.b); break; |
2506 | | |
2507 | 204k | case BOP_OR: scratch = makeBoolean(lhs.v.b || rhs.v.b); break; |
2508 | | |
2509 | 49 | default: |
2510 | 49 | throw makeError(ast.location, |
2511 | 49 | "binary operator " + bop_string(ast.op) + |
2512 | 49 | " does not operate on booleans."); |
2513 | 356k | } |
2514 | 356k | break; |
2515 | | |
2516 | 5.01M | case Value::NUMBER: |
2517 | 5.01M | switch (ast.op) { |
2518 | 695k | case BOP_PLUS: |
2519 | 695k | scratch = makeNumberCheck(ast.location, lhs.v.d + rhs.v.d); |
2520 | 695k | break; |
2521 | | |
2522 | 1.47M | case BOP_MINUS: |
2523 | 1.47M | scratch = makeNumberCheck(ast.location, lhs.v.d - rhs.v.d); |
2524 | 1.47M | break; |
2525 | | |
2526 | 60.4k | case BOP_MULT: |
2527 | 60.4k | scratch = makeNumberCheck(ast.location, lhs.v.d * rhs.v.d); |
2528 | 60.4k | break; |
2529 | | |
2530 | 47.8k | case BOP_DIV: |
2531 | 47.8k | if (rhs.v.d == 0) |
2532 | 9 | throw makeError(ast.location, "division by zero."); |
2533 | 47.8k | scratch = makeNumberCheck(ast.location, lhs.v.d / rhs.v.d); |
2534 | 47.8k | break; |
2535 | | |
2536 | | // No need to check doubles made from longs |
2537 | | |
2538 | 385 | case BOP_SHIFT_L: { |
2539 | 385 | if (rhs.v.d < 0) |
2540 | 2 | throw makeError(ast.location, "shift by negative exponent."); |
2541 | 383 | int64_t long_l = lhs.v.d; |
2542 | 383 | int64_t long_r = rhs.v.d; |
2543 | 383 | long_r = long_r % 64; |
2544 | 383 | scratch = makeNumber(long_l << long_r); |
2545 | 383 | } break; |
2546 | | |
2547 | 96.2k | case BOP_SHIFT_R: { |
2548 | 96.2k | if (rhs.v.d < 0) |
2549 | 3 | throw makeError(ast.location, "shift by negative exponent."); |
2550 | 96.2k | int64_t long_l = lhs.v.d; |
2551 | 96.2k | int64_t long_r = rhs.v.d; |
2552 | 96.2k | long_r = long_r % 64; |
2553 | 96.2k | scratch = makeNumber(long_l >> long_r); |
2554 | 96.2k | } break; |
2555 | | |
2556 | 191k | case BOP_BITWISE_AND: { |
2557 | 191k | int64_t long_l = lhs.v.d; |
2558 | 191k | int64_t long_r = rhs.v.d; |
2559 | 191k | scratch = makeNumber(long_l & long_r); |
2560 | 191k | } break; |
2561 | | |
2562 | 63.6k | case BOP_BITWISE_XOR: { |
2563 | 63.6k | int64_t long_l = lhs.v.d; |
2564 | 63.6k | int64_t long_r = rhs.v.d; |
2565 | 63.6k | scratch = makeNumber(long_l ^ long_r); |
2566 | 63.6k | } break; |
2567 | | |
2568 | 1.37k | case BOP_BITWISE_OR: { |
2569 | 1.37k | int64_t long_l = lhs.v.d; |
2570 | 1.37k | int64_t long_r = rhs.v.d; |
2571 | 1.37k | scratch = makeNumber(long_l | long_r); |
2572 | 1.37k | } break; |
2573 | | |
2574 | 1.23M | case BOP_LESS_EQ: scratch = makeBoolean(lhs.v.d <= rhs.v.d); break; |
2575 | | |
2576 | 871k | case BOP_GREATER_EQ: |
2577 | 871k | scratch = makeBoolean(lhs.v.d >= rhs.v.d); |
2578 | 871k | break; |
2579 | | |
2580 | 237k | case BOP_LESS: scratch = makeBoolean(lhs.v.d < rhs.v.d); break; |
2581 | | |
2582 | 42.3k | case BOP_GREATER: scratch = makeBoolean(lhs.v.d > rhs.v.d); break; |
2583 | | |
2584 | 16 | default: |
2585 | 16 | throw makeError(ast.location, |
2586 | 16 | "binary operator " + bop_string(ast.op) + |
2587 | 16 | " does not operate on numbers."); |
2588 | 5.01M | } |
2589 | 5.01M | break; |
2590 | | |
2591 | 5.01M | case Value::FUNCTION: |
2592 | 1 | throw makeError(ast.location, |
2593 | 1 | "binary operator " + bop_string(ast.op) + |
2594 | 1 | " does not operate on functions."); |
2595 | | |
2596 | 3 | case Value::NULL_TYPE: |
2597 | 3 | throw makeError(ast.location, |
2598 | 3 | "binary operator " + bop_string(ast.op) + |
2599 | 3 | " does not operate on null."); |
2600 | | |
2601 | 2.38M | case Value::OBJECT: { |
2602 | 2.38M | if (ast.op != BOP_PLUS) { |
2603 | 22 | throw makeError(ast.location, |
2604 | 22 | "binary operator " + bop_string(ast.op) + |
2605 | 22 | " does not operate on objects."); |
2606 | 22 | } |
2607 | 2.38M | auto *lhs_obj = static_cast<HeapObject *>(lhs.v.h); |
2608 | 2.38M | auto *rhs_obj = static_cast<HeapObject *>(rhs.v.h); |
2609 | 2.38M | scratch = makeObject<HeapExtendedObject>(lhs_obj, rhs_obj); |
2610 | 2.38M | } break; |
2611 | | |
2612 | 100k | case Value::STRING: { |
2613 | 100k | const UString &lhs_str = static_cast<HeapString *>(lhs.v.h)->value; |
2614 | 100k | const UString &rhs_str = static_cast<HeapString *>(rhs.v.h)->value; |
2615 | 100k | switch (ast.op) { |
2616 | 0 | case BOP_PLUS: scratch = makeString(lhs_str + rhs_str); break; |
2617 | | |
2618 | 7.41k | case BOP_LESS_EQ: scratch = makeBoolean(lhs_str <= rhs_str); break; |
2619 | | |
2620 | 49.6k | case BOP_GREATER_EQ: |
2621 | 49.6k | scratch = makeBoolean(lhs_str >= rhs_str); |
2622 | 49.6k | break; |
2623 | | |
2624 | 9.63k | case BOP_LESS: scratch = makeBoolean(lhs_str < rhs_str); break; |
2625 | | |
2626 | 34.0k | case BOP_GREATER: scratch = makeBoolean(lhs_str > rhs_str); break; |
2627 | | |
2628 | 27 | default: |
2629 | 27 | throw makeError(ast.location, |
2630 | 27 | "binary operator " + bop_string(ast.op) + |
2631 | 27 | " does not operate on strings."); |
2632 | 100k | } |
2633 | 100k | } break; |
2634 | 9.47M | } |
2635 | 9.47M | } break; |
2636 | | |
2637 | 9.46M | case FRAME_BUILTIN_FILTER: { |
2638 | 0 | const auto &ast = *static_cast<const Apply *>(f.ast); |
2639 | 0 | auto *func = static_cast<HeapClosure *>(f.val.v.h); |
2640 | 0 | auto *arr = static_cast<HeapArray *>(f.val2.v.h); |
2641 | 0 | if (scratch.t != Value::BOOLEAN) { |
2642 | 0 | throw makeError( |
2643 | 0 | ast.location, |
2644 | 0 | "filter function must return boolean, got: " + type_str(scratch)); |
2645 | 0 | } |
2646 | 0 | if (scratch.v.b) |
2647 | 0 | f.thunks.push_back(arr->elements[f.elementId]); |
2648 | 0 | f.elementId++; |
2649 | | // Iterate through arr, calling the function on each. |
2650 | 0 | if (f.elementId == arr->elements.size()) { |
2651 | 0 | scratch = makeArray(f.thunks); |
2652 | 0 | } else { |
2653 | 0 | auto *thunk = arr->elements[f.elementId]; |
2654 | 0 | BindingFrame bindings = func->upValues; |
2655 | 0 | bindings[func->params[0].id] = thunk; |
2656 | 0 | stack.newCall(ast.location, func, func->self, func->offset, bindings); |
2657 | 0 | ast_ = func->body; |
2658 | 0 | goto recurse; |
2659 | 0 | } |
2660 | 0 | } break; |
2661 | | |
2662 | 43.2M | case FRAME_BUILTIN_FORCE_THUNKS: { |
2663 | 43.2M | const auto &ast = *static_cast<const Apply *>(f.ast); |
2664 | 43.2M | auto *func = static_cast<HeapClosure *>(f.val.v.h); |
2665 | 43.2M | if (f.elementId == f.thunks.size()) { |
2666 | | // All thunks forced, now the builtin implementations. |
2667 | 16.7M | const LocationRange &loc = ast.location; |
2668 | 16.7M | const std::string &builtin_name = func->builtinName; |
2669 | 16.7M | std::vector<Value> args; |
2670 | 26.4M | for (auto *th : f.thunks) { |
2671 | 26.4M | args.push_back(th->content); |
2672 | 26.4M | } |
2673 | 16.7M | BuiltinMap::const_iterator bit = builtins.find(builtin_name); |
2674 | 16.7M | if (bit != builtins.end()) { |
2675 | 16.7M | const AST *new_ast = (this->*bit->second)(loc, args); |
2676 | 16.7M | if (new_ast != nullptr) { |
2677 | 0 | ast_ = new_ast; |
2678 | 0 | goto recurse; |
2679 | 0 | } |
2680 | 16.7M | break; |
2681 | 16.7M | } |
2682 | 0 | VmNativeCallbackMap::const_iterator nit = |
2683 | 0 | nativeCallbacks.find(builtin_name); |
2684 | | // TODO(dcunnin): Support arrays. |
2685 | | // TODO(dcunnin): Support objects. |
2686 | 0 | std::vector<JsonnetJsonValue> args2; |
2687 | 0 | for (const Value &arg : args) { |
2688 | 0 | switch (arg.t) { |
2689 | 0 | case Value::STRING: |
2690 | 0 | args2.emplace_back( |
2691 | 0 | JsonnetJsonValue::STRING, |
2692 | 0 | encode_utf8(static_cast<HeapString *>(arg.v.h)->value), |
2693 | 0 | 0); |
2694 | 0 | break; |
2695 | | |
2696 | 0 | case Value::BOOLEAN: |
2697 | 0 | args2.emplace_back( |
2698 | 0 | JsonnetJsonValue::BOOL, "", arg.v.b ? 1.0 : 0.0); |
2699 | 0 | break; |
2700 | | |
2701 | 0 | case Value::NUMBER: |
2702 | 0 | args2.emplace_back(JsonnetJsonValue::NUMBER, "", arg.v.d); |
2703 | 0 | break; |
2704 | | |
2705 | 0 | case Value::NULL_TYPE: |
2706 | 0 | args2.emplace_back(JsonnetJsonValue::NULL_KIND, "", 0); |
2707 | 0 | break; |
2708 | | |
2709 | 0 | default: |
2710 | 0 | throw makeError(ast.location, |
2711 | 0 | "native extensions can only take primitives."); |
2712 | 0 | } |
2713 | 0 | } |
2714 | 0 | std::vector<const JsonnetJsonValue *> args3; |
2715 | 0 | for (size_t i = 0; i < args2.size(); ++i) { |
2716 | 0 | args3.push_back(&args2[i]); |
2717 | 0 | } |
2718 | 0 | if (nit == nativeCallbacks.end()) { |
2719 | 0 | throw makeError(ast.location, |
2720 | 0 | "unrecognized builtin name: " + builtin_name); |
2721 | 0 | } |
2722 | 0 | const VmNativeCallback &cb = nit->second; |
2723 | |
|
2724 | 0 | int succ; |
2725 | 0 | std::unique_ptr<JsonnetJsonValue> r(cb.cb(cb.ctx, args3.data(), &succ)); |
2726 | |
|
2727 | 0 | if (succ) { |
2728 | 0 | bool unused; |
2729 | 0 | jsonToHeap(r, unused, scratch); |
2730 | 0 | } else { |
2731 | 0 | if (r->kind != JsonnetJsonValue::STRING) { |
2732 | 0 | throw makeError( |
2733 | 0 | ast.location, |
2734 | 0 | "native extension returned an error that was not a string."); |
2735 | 0 | } |
2736 | 0 | std::string rs = r->string; |
2737 | 0 | throw makeError(ast.location, rs); |
2738 | 0 | } |
2739 | |
|
2740 | 26.4M | } else { |
2741 | | // Not all arguments forced yet. |
2742 | 26.4M | HeapThunk *th = f.thunks[f.elementId++]; |
2743 | 26.4M | if (!th->filled) { |
2744 | 26.4M | stack.newCall(ast.location, th, th->self, th->offset, th->upValues); |
2745 | 26.4M | ast_ = th->body; |
2746 | 26.4M | goto recurse; |
2747 | 26.4M | } |
2748 | 26.4M | } |
2749 | 43.2M | } break; |
2750 | | |
2751 | 91.8M | case FRAME_CALL: { |
2752 | 91.8M | if (auto *thunk = dynamic_cast<HeapThunk *>(f.context)) { |
2753 | | // If we called a thunk, cache result. |
2754 | 49.4M | thunk->fill(scratch); |
2755 | 49.4M | } else if (auto *closure = dynamic_cast<HeapClosure *>(f.context)) { |
2756 | 15.7M | if (f.elementId < f.thunks.size()) { |
2757 | | // If tailstrict, force thunks |
2758 | 7.44M | HeapThunk *th = f.thunks[f.elementId++]; |
2759 | 7.44M | if (!th->filled) { |
2760 | 7.44M | stack.newCall(f.location, th, th->self, th->offset, th->upValues); |
2761 | 7.44M | ast_ = th->body; |
2762 | 7.44M | goto recurse; |
2763 | 7.44M | } |
2764 | 8.28M | } else if (f.thunks.size() == 0) { |
2765 | | // Body has now been executed |
2766 | 5.04M | } else { |
2767 | | // Execute the body |
2768 | 3.23M | f.thunks.clear(); |
2769 | 3.23M | f.elementId = 0; |
2770 | 3.23M | ast_ = closure->body; |
2771 | 3.23M | goto recurse; |
2772 | 3.23M | } |
2773 | 15.7M | } |
2774 | | // Result of call is in scratch, just pop. |
2775 | 91.8M | } break; |
2776 | | |
2777 | 81.2M | case FRAME_ERROR: { |
2778 | 3.48k | const auto &ast = *static_cast<const Error *>(f.ast); |
2779 | 3.48k | UString msg; |
2780 | 3.48k | if (scratch.t == Value::STRING) { |
2781 | 876 | msg = static_cast<HeapString *>(scratch.v.h)->value; |
2782 | 2.61k | } else { |
2783 | 2.61k | msg = toString(ast.location); |
2784 | 2.61k | } |
2785 | 3.48k | throw makeError(ast.location, encode_utf8(msg)); |
2786 | 91.8M | } break; |
2787 | | |
2788 | 17.6M | case FRAME_IF: { |
2789 | 17.6M | const auto &ast = *static_cast<const Conditional *>(f.ast); |
2790 | 17.6M | if (scratch.t != Value::BOOLEAN) { |
2791 | 80 | throw makeError( |
2792 | 80 | ast.location, |
2793 | 80 | "condition must be boolean, got " + type_str(scratch) + "."); |
2794 | 80 | } |
2795 | 17.6M | ast_ = scratch.v.b ? ast.branchTrue : ast.branchFalse; |
2796 | 17.6M | stack.pop(); |
2797 | 17.6M | goto recurse; |
2798 | 17.6M | } break; |
2799 | | |
2800 | 5.62M | case FRAME_SUPER_INDEX: { |
2801 | 5.62M | const auto &ast = *static_cast<const SuperIndex *>(f.ast); |
2802 | 5.62M | HeapObject *self; |
2803 | 5.62M | unsigned offset; |
2804 | 5.62M | stack.getSelfBinding(self, offset); |
2805 | 5.62M | offset++; |
2806 | 5.62M | if (offset >= countLeaves(self)) { |
2807 | 3 | throw makeError(ast.location, |
2808 | 3 | "attempt to use super when there is no super class."); |
2809 | 3 | } |
2810 | 5.62M | if (scratch.t != Value::STRING) { |
2811 | 0 | throw makeError( |
2812 | 0 | ast.location, |
2813 | 0 | "super index must be string, got " + type_str(scratch) + "."); |
2814 | 0 | } |
2815 | | |
2816 | 5.62M | const UString &index_name = static_cast<HeapString *>(scratch.v.h)->value; |
2817 | 5.62M | auto *fid = alloc->makeIdentifier(index_name); |
2818 | 5.62M | stack.pop(); |
2819 | 5.62M | ast_ = objectIndex(ast.location, self, fid, offset); |
2820 | 5.62M | goto recurse; |
2821 | 5.62M | } break; |
2822 | | |
2823 | 5.93M | case FRAME_IN_SUPER_ELEMENT: { |
2824 | 5.93M | const auto &ast = *static_cast<const InSuper *>(f.ast); |
2825 | 5.93M | HeapObject *self; |
2826 | 5.93M | unsigned offset; |
2827 | 5.93M | stack.getSelfBinding(self, offset); |
2828 | 5.93M | offset++; |
2829 | 5.93M | if (scratch.t != Value::STRING) { |
2830 | 2 | throw makeError(ast.location, |
2831 | 2 | "left hand side of e in super must be string, got " + |
2832 | 2 | type_str(scratch) + "."); |
2833 | 2 | } |
2834 | 5.93M | if (offset >= countLeaves(self)) { |
2835 | | // There is no super object. |
2836 | 134k | scratch = makeBoolean(false); |
2837 | 5.80M | } else { |
2838 | 5.80M | const UString &element_name = static_cast<HeapString *>(scratch.v.h)->value; |
2839 | 5.80M | auto *fid = alloc->makeIdentifier(element_name); |
2840 | 5.80M | unsigned unused_found_at = 0; |
2841 | 5.80M | bool in = findObject(fid, self, offset, unused_found_at); |
2842 | 5.80M | scratch = makeBoolean(in); |
2843 | 5.80M | } |
2844 | 5.93M | } break; |
2845 | | |
2846 | 22.0M | case FRAME_INDEX_INDEX: { |
2847 | 22.0M | const auto &ast = *static_cast<const Index *>(f.ast); |
2848 | 22.0M | const Value &target = f.val; |
2849 | 22.0M | if (target.t == Value::ARRAY) { |
2850 | 184k | const auto *array = static_cast<HeapArray *>(target.v.h); |
2851 | 184k | if (scratch.t == Value::STRING) { |
2852 | 81 | const UString &str = static_cast<HeapString *>(scratch.v.h)->value; |
2853 | 81 | throw makeError( |
2854 | 81 | ast.location, |
2855 | 81 | "attempted index an array with string \"" |
2856 | 81 | + encode_utf8(jsonnet_string_escape(str, false)) + "\"."); |
2857 | 81 | } |
2858 | 184k | if (scratch.t != Value::NUMBER) { |
2859 | 14 | throw makeError( |
2860 | 14 | ast.location, |
2861 | 14 | "array index must be number, got " + type_str(scratch) + "."); |
2862 | 14 | } |
2863 | 184k | double index = ::floor(scratch.v.d); |
2864 | 184k | long sz = array->elements.size(); |
2865 | 184k | if (index < 0 || index >= sz) { |
2866 | 11 | std::stringstream ss; |
2867 | 11 | ss << "array bounds error: " << index << " not within [0, " << sz |
2868 | 11 | << ")"; |
2869 | 11 | throw makeError(ast.location, ss.str()); |
2870 | 11 | } |
2871 | 184k | if (scratch.v.d != index) { |
2872 | 19 | std::stringstream ss; |
2873 | 19 | ss << "array index was not integer: " << scratch.v.d; |
2874 | 19 | throw makeError(ast.location, ss.str()); |
2875 | 19 | } |
2876 | | // index < sz <= SIZE_T_MAX |
2877 | 184k | auto *thunk = array->elements[size_t(index)]; |
2878 | 184k | if (thunk->filled) { |
2879 | 129k | scratch = thunk->content; |
2880 | 129k | } else { |
2881 | 54.7k | stack.pop(); |
2882 | 54.7k | stack.newCall( |
2883 | 54.7k | ast.location, thunk, thunk->self, thunk->offset, thunk->upValues); |
2884 | 54.7k | ast_ = thunk->body; |
2885 | 54.7k | goto recurse; |
2886 | 54.7k | } |
2887 | 21.9M | } else if (target.t == Value::OBJECT) { |
2888 | 21.2M | auto *obj = static_cast<HeapObject *>(target.v.h); |
2889 | 21.2M | assert(obj != nullptr); |
2890 | 21.2M | if (scratch.t != Value::STRING) { |
2891 | 13 | throw makeError( |
2892 | 13 | ast.location, |
2893 | 13 | "object index must be string, got " + type_str(scratch) + "."); |
2894 | 13 | } |
2895 | 21.2M | const UString &index_name = static_cast<HeapString *>(scratch.v.h)->value; |
2896 | 21.2M | auto *fid = alloc->makeIdentifier(index_name); |
2897 | 21.2M | stack.pop(); |
2898 | 21.2M | ast_ = objectIndex(ast.location, obj, fid, 0); |
2899 | 21.2M | goto recurse; |
2900 | 21.2M | } else if (target.t == Value::STRING) { |
2901 | 637k | auto *obj = static_cast<HeapString *>(target.v.h); |
2902 | 637k | assert(obj != nullptr); |
2903 | 637k | if (scratch.t != Value::NUMBER) { |
2904 | 10 | throw makeError( |
2905 | 10 | ast.location, |
2906 | 10 | "string index must be a number, got " + type_str(scratch) + "."); |
2907 | 10 | } |
2908 | 637k | long sz = obj->value.length(); |
2909 | 637k | long i = (long)scratch.v.d; |
2910 | 637k | if (i < 0 || i >= sz) { |
2911 | 251 | std::stringstream ss; |
2912 | 251 | ss << "string bounds error: " << i << " not within [0, " << sz << ")"; |
2913 | 251 | throw makeError(ast.location, ss.str()); |
2914 | 251 | } |
2915 | 637k | char32_t ch[] = {obj->value[i], U'\0'}; |
2916 | 637k | scratch = makeString(ch); |
2917 | 637k | } else { |
2918 | 0 | std::cerr << "INTERNAL ERROR: not object / array / string." << std::endl; |
2919 | 0 | abort(); |
2920 | 0 | } |
2921 | 22.0M | } break; |
2922 | | |
2923 | 22.0M | case FRAME_INDEX_TARGET: { |
2924 | 22.0M | const auto &ast = *static_cast<const Index *>(f.ast); |
2925 | 22.0M | if (scratch.t != Value::ARRAY && scratch.t != Value::OBJECT && |
2926 | 22.0M | scratch.t != Value::STRING) { |
2927 | 22 | throw makeError(ast.location, |
2928 | 22 | "can only index objects, strings, and arrays, got " + |
2929 | 22 | type_str(scratch) + "."); |
2930 | 22 | } |
2931 | 22.0M | f.val = scratch; |
2932 | 22.0M | f.kind = FRAME_INDEX_INDEX; |
2933 | 22.0M | if (scratch.t == Value::OBJECT) { |
2934 | 21.2M | auto *self = static_cast<HeapObject *>(scratch.v.h); |
2935 | 21.2M | if (!stack.alreadyExecutingInvariants(self)) { |
2936 | 21.2M | stack.newFrame(FRAME_INVARIANTS, ast.location); |
2937 | 21.2M | Frame &f2 = stack.top(); |
2938 | 21.2M | f2.self = self; |
2939 | 21.2M | unsigned counter = 0; |
2940 | 21.2M | objectInvariants(self, self, counter, f2.thunks); |
2941 | 21.2M | if (f2.thunks.size() > 0) { |
2942 | 2.92k | auto *thunk = f2.thunks[0]; |
2943 | 2.92k | f2.elementId = 1; |
2944 | 2.92k | stack.newCall(ast.location, |
2945 | 2.92k | thunk, |
2946 | 2.92k | thunk->self, |
2947 | 2.92k | thunk->offset, |
2948 | 2.92k | thunk->upValues); |
2949 | 2.92k | ast_ = thunk->body; |
2950 | 2.92k | goto recurse; |
2951 | 2.92k | } |
2952 | 21.2M | } |
2953 | 21.2M | } |
2954 | 22.0M | ast_ = ast.index; |
2955 | 22.0M | goto recurse; |
2956 | 22.0M | } break; |
2957 | | |
2958 | 21.4M | case FRAME_INVARIANTS: { |
2959 | 21.4M | if (f.elementId >= f.thunks.size()) { |
2960 | 21.3M | if (stack.size() == initial_stack_size + 1) { |
2961 | | // Just pop, evaluate was invoked by runInvariants. |
2962 | 31.6k | break; |
2963 | 31.6k | } |
2964 | 21.2M | stack.pop(); |
2965 | 21.2M | Frame &f2 = stack.top(); |
2966 | 21.2M | const auto &ast = *static_cast<const Index *>(f2.ast); |
2967 | 21.2M | ast_ = ast.index; |
2968 | 21.2M | goto recurse; |
2969 | 21.3M | } |
2970 | 106k | auto *thunk = f.thunks[f.elementId++]; |
2971 | 106k | stack.newCall(f.location, thunk, thunk->self, thunk->offset, thunk->upValues); |
2972 | 106k | ast_ = thunk->body; |
2973 | 106k | goto recurse; |
2974 | 21.4M | } break; |
2975 | | |
2976 | 10.0M | case FRAME_LOCAL: { |
2977 | | // Result of execution is in scratch already. |
2978 | 10.0M | } break; |
2979 | | |
2980 | 5.00M | case FRAME_OBJECT: { |
2981 | 5.00M | const auto &ast = *static_cast<const DesugaredObject *>(f.ast); |
2982 | 5.00M | if (scratch.t != Value::NULL_TYPE) { |
2983 | 5.00M | if (scratch.t != Value::STRING) { |
2984 | 17 | throw makeError(ast.location, "field name was not a string."); |
2985 | 17 | } |
2986 | 5.00M | const auto &fname = static_cast<const HeapString *>(scratch.v.h)->value; |
2987 | 5.00M | const Identifier *fid = alloc->makeIdentifier(fname); |
2988 | 5.00M | if (f.objectFields.find(fid) != f.objectFields.end()) { |
2989 | 16 | std::string msg = |
2990 | 16 | "duplicate field name: \"" + encode_utf8(fname) + "\""; |
2991 | 16 | throw makeError(ast.location, msg); |
2992 | 16 | } |
2993 | 5.00M | f.objectFields[fid].hide = f.fit->hide; |
2994 | 5.00M | f.objectFields[fid].body = f.fit->body; |
2995 | 5.00M | } |
2996 | 5.00M | f.fit++; |
2997 | 5.00M | if (f.fit != ast.fields.end()) { |
2998 | 3.09M | ast_ = f.fit->name; |
2999 | 3.09M | goto recurse; |
3000 | 3.09M | } else { |
3001 | 1.91M | auto env = capture(ast.freeVariables); |
3002 | 1.91M | scratch = makeObject<HeapSimpleObject>(env, f.objectFields, ast.asserts); |
3003 | 1.91M | } |
3004 | 5.00M | } break; |
3005 | | |
3006 | 1.91M | case FRAME_OBJECT_COMP_ARRAY: { |
3007 | 0 | const auto &ast = *static_cast<const ObjectComprehensionSimple *>(f.ast); |
3008 | 0 | const Value &arr_v = scratch; |
3009 | 0 | if (scratch.t != Value::ARRAY) { |
3010 | 0 | throw makeError(ast.location, |
3011 | 0 | "object comprehension needs array, got " + type_str(arr_v)); |
3012 | 0 | } |
3013 | 0 | const auto *arr = static_cast<const HeapArray *>(arr_v.v.h); |
3014 | 0 | if (arr->elements.size() == 0) { |
3015 | | // Degenerate case. Just create the object now. |
3016 | 0 | scratch = makeObject<HeapComprehensionObject>( |
3017 | 0 | BindingFrame{}, ast.value, ast.id, BindingFrame{}); |
3018 | 0 | } else { |
3019 | 0 | f.kind = FRAME_OBJECT_COMP_ELEMENT; |
3020 | 0 | f.val = scratch; |
3021 | 0 | f.bindings[ast.id] = arr->elements[0]; |
3022 | 0 | f.elementId = 0; |
3023 | 0 | ast_ = ast.field; |
3024 | 0 | goto recurse; |
3025 | 0 | } |
3026 | 0 | } break; |
3027 | | |
3028 | 0 | case FRAME_OBJECT_COMP_ELEMENT: { |
3029 | 0 | const auto &ast = *static_cast<const ObjectComprehensionSimple *>(f.ast); |
3030 | 0 | const auto *arr = static_cast<const HeapArray *>(f.val.v.h); |
3031 | 0 | if (scratch.t != Value::NULL_TYPE) { |
3032 | 0 | if (scratch.t != Value::STRING) { |
3033 | 0 | std::stringstream ss; |
3034 | 0 | ss << "field must be string, got: " << type_str(scratch); |
3035 | 0 | throw makeError(ast.location, ss.str()); |
3036 | 0 | } |
3037 | 0 | const auto &fname = static_cast<const HeapString *>(scratch.v.h)->value; |
3038 | 0 | const Identifier *fid = alloc->makeIdentifier(fname); |
3039 | 0 | if (f.elements.find(fid) != f.elements.end()) { |
3040 | 0 | throw makeError(ast.location, |
3041 | 0 | "duplicate field name: \"" + encode_utf8(fname) + "\""); |
3042 | 0 | } |
3043 | 0 | f.elements[fid] = arr->elements[f.elementId]; |
3044 | 0 | } |
3045 | 0 | f.elementId++; |
3046 | |
|
3047 | 0 | if (f.elementId == arr->elements.size()) { |
3048 | 0 | auto env = capture(ast.freeVariables); |
3049 | 0 | scratch = |
3050 | 0 | makeObject<HeapComprehensionObject>(env, ast.value, ast.id, f.elements); |
3051 | 0 | } else { |
3052 | 0 | f.bindings[ast.id] = arr->elements[f.elementId]; |
3053 | 0 | ast_ = ast.field; |
3054 | 0 | goto recurse; |
3055 | 0 | } |
3056 | 0 | } break; |
3057 | | |
3058 | 4.61M | case FRAME_STRING_CONCAT: { |
3059 | 4.61M | const auto &ast = *static_cast<const Binary *>(f.ast); |
3060 | 4.61M | const Value &lhs = stack.top().val; |
3061 | 4.61M | UString output; |
3062 | 4.61M | if (lhs.t == Value::STRING) { |
3063 | 4.46M | output.append(static_cast<const HeapString *>(lhs.v.h)->value); |
3064 | 4.46M | } else { |
3065 | 146k | scratch = lhs; |
3066 | 146k | output.append(toString(ast.left->location)); |
3067 | 146k | } |
3068 | 4.61M | const Value &rhs = stack.top().val2; |
3069 | 4.61M | if (rhs.t == Value::STRING) { |
3070 | 4.34M | output.append(static_cast<const HeapString *>(rhs.v.h)->value); |
3071 | 4.34M | } else { |
3072 | 265k | scratch = rhs; |
3073 | 265k | output.append(toString(ast.right->location)); |
3074 | 265k | } |
3075 | 4.61M | scratch = makeString(output); |
3076 | 4.61M | } break; |
3077 | | |
3078 | 4.09M | case FRAME_UNARY: { |
3079 | 4.09M | const auto &ast = *static_cast<const Unary *>(f.ast); |
3080 | 4.09M | switch (scratch.t) { |
3081 | 3.25M | case Value::BOOLEAN: |
3082 | 3.25M | if (ast.op == UOP_NOT) { |
3083 | 3.25M | scratch = makeBoolean(!scratch.v.b); |
3084 | 3.25M | } else { |
3085 | 3 | throw makeError(ast.location, |
3086 | 3 | "unary operator " + uop_string(ast.op) + |
3087 | 3 | " does not operate on booleans."); |
3088 | 3 | } |
3089 | 3.25M | break; |
3090 | | |
3091 | 3.25M | case Value::NUMBER: |
3092 | 839k | switch (ast.op) { |
3093 | 10.2k | case UOP_PLUS: break; |
3094 | | |
3095 | 666k | case UOP_MINUS: scratch = makeNumber(-scratch.v.d); break; |
3096 | | |
3097 | 163k | case UOP_BITWISE_NOT: |
3098 | 163k | scratch = makeNumber(~(long)(scratch.v.d)); |
3099 | 163k | break; |
3100 | | |
3101 | 47 | default: |
3102 | 47 | throw makeError(ast.location, |
3103 | 47 | "unary operator " + uop_string(ast.op) + |
3104 | 47 | " does not operate on numbers."); |
3105 | 839k | } |
3106 | 839k | break; |
3107 | | |
3108 | 839k | default: |
3109 | 139 | throw makeError(ast.location, |
3110 | 139 | "unary operator " + uop_string(ast.op) + |
3111 | 139 | " does not operate on type " + type_str(scratch)); |
3112 | 4.09M | } |
3113 | 4.09M | } break; |
3114 | | |
3115 | 4.09M | case FRAME_BUILTIN_JOIN_STRINGS: { |
3116 | 0 | joinString(f.first, f.str, f.val, f.elementId, scratch); |
3117 | 0 | f.elementId++; |
3118 | 0 | auto *ast = joinStrings(); |
3119 | 0 | if (ast != nullptr) { |
3120 | 0 | ast_ = ast; |
3121 | 0 | goto recurse; |
3122 | 0 | } |
3123 | 0 | } break; |
3124 | | |
3125 | 0 | case FRAME_BUILTIN_JOIN_ARRAYS: { |
3126 | 0 | joinArray(f.first, f.thunks, f.val, f.elementId, scratch); |
3127 | 0 | f.elementId++; |
3128 | 0 | auto *ast = joinArrays(); |
3129 | 0 | if (ast != nullptr) { |
3130 | 0 | ast_ = ast; |
3131 | 0 | goto recurse; |
3132 | 0 | } |
3133 | 0 | } break; |
3134 | | |
3135 | 0 | case FRAME_BUILTIN_DECODE_UTF8: { |
3136 | 0 | auto *ast = decodeUTF8(); |
3137 | 0 | if (ast != nullptr) { |
3138 | 0 | ast_ = ast; |
3139 | 0 | goto recurse; |
3140 | 0 | } |
3141 | 0 | } break; |
3142 | | |
3143 | 0 | default: |
3144 | 0 | std::cerr << "INTERNAL ERROR: Unknown FrameKind: " << f.kind << std::endl; |
3145 | 0 | std::abort(); |
3146 | 304M | } |
3147 | | |
3148 | 134M | popframe:; |
3149 | | |
3150 | 134M | stack.pop(); |
3151 | | |
3152 | 159M | replaceframe:; |
3153 | 159M | } |
3154 | 152M | } |
3155 | | |
3156 | | /** Manifest the scratch value by evaluating any remaining fields, and then convert to JSON. |
3157 | | * |
3158 | | * This can trigger a garbage collection cycle. Be sure to stash any objects that aren't |
3159 | | * reachable via the stack or heap. |
3160 | | * |
3161 | | * \param multiline If true, will print objects and arrays in an indented fashion. |
3162 | | */ |
3163 | | UString manifestJson(const LocationRange &loc, bool multiline, const UString &indent) |
3164 | 8.12M | { |
3165 | | // Printing fields means evaluating and binding them, which can trigger |
3166 | | // garbage collection. |
3167 | | |
3168 | 8.12M | UStringStream ss; |
3169 | 8.12M | switch (scratch.t) { |
3170 | 258k | case Value::ARRAY: { |
3171 | 258k | HeapArray *arr = static_cast<HeapArray *>(scratch.v.h); |
3172 | 258k | if (arr->elements.size() == 0) { |
3173 | 65.7k | ss << U"[ ]"; |
3174 | 192k | } else { |
3175 | 192k | const char32_t *prefix = multiline ? U"[\n" : U"["; |
3176 | 192k | UString indent2 = multiline ? indent + U" " : indent; |
3177 | 5.98M | for (auto *thunk : arr->elements) { |
3178 | 5.98M | LocationRange tloc = thunk->body == nullptr ? loc : thunk->body->location; |
3179 | 5.98M | if (thunk->filled) { |
3180 | 0 | stack.newCall(loc, thunk, nullptr, 0, BindingFrame{}); |
3181 | | // Keep arr alive when scratch is overwritten |
3182 | 0 | stack.top().val = scratch; |
3183 | 0 | scratch = thunk->content; |
3184 | 5.98M | } else { |
3185 | 5.98M | stack.newCall(loc, thunk, thunk->self, thunk->offset, thunk->upValues); |
3186 | | // Keep arr alive when scratch is overwritten |
3187 | 5.98M | stack.top().val = scratch; |
3188 | 5.98M | evaluate(thunk->body, stack.size()); |
3189 | 5.98M | } |
3190 | 5.98M | auto element = manifestJson(tloc, multiline, indent2); |
3191 | | // Restore scratch |
3192 | 5.98M | scratch = stack.top().val; |
3193 | 5.98M | stack.pop(); |
3194 | 5.98M | ss << prefix << indent2 << element; |
3195 | 5.98M | prefix = multiline ? U",\n" : U", "; |
3196 | 5.98M | } |
3197 | 192k | ss << (multiline ? U"\n" : U"") << indent << U"]"; |
3198 | 192k | } |
3199 | 258k | } break; |
3200 | | |
3201 | 113k | case Value::BOOLEAN: ss << (scratch.v.b ? U"true" : U"false"); break; |
3202 | | |
3203 | 6.66M | case Value::NUMBER: ss << decode_utf8(jsonnet_unparse_number(scratch.v.d)); break; |
3204 | | |
3205 | 36 | case Value::FUNCTION: |
3206 | 36 | throw makeError(loc, "couldn't manifest function in JSON output."); |
3207 | | |
3208 | 3.22k | case Value::NULL_TYPE: ss << U"null"; break; |
3209 | | |
3210 | 927k | case Value::OBJECT: { |
3211 | 927k | auto *obj = static_cast<HeapObject *>(scratch.v.h); |
3212 | 927k | runInvariants(loc, obj); |
3213 | | // Using std::map has the useful side-effect of ordering the fields |
3214 | | // alphabetically. |
3215 | 927k | std::map<UString, const Identifier *> fields; |
3216 | 1.31M | for (const auto &f : objectFields(obj, true)) { |
3217 | 1.31M | fields[f->name] = f; |
3218 | 1.31M | } |
3219 | 927k | if (fields.size() == 0) { |
3220 | 316k | ss << U"{ }"; |
3221 | 611k | } else { |
3222 | 611k | UString indent2 = multiline ? indent + U" " : indent; |
3223 | 611k | const char32_t *prefix = multiline ? U"{\n" : U"{"; |
3224 | 1.13M | for (const auto &f : fields) { |
3225 | | // pushes FRAME_CALL |
3226 | 1.13M | const AST *body = objectIndex(loc, obj, f.second, 0); |
3227 | 1.13M | stack.top().val = scratch; |
3228 | 1.13M | evaluate(body, stack.size()); |
3229 | 1.13M | auto vstr = manifestJson(body->location, multiline, indent2); |
3230 | | // Reset scratch so that the object we're manifesting doesn't |
3231 | | // get GC'd. |
3232 | 1.13M | scratch = stack.top().val; |
3233 | 1.13M | stack.pop(); |
3234 | 1.13M | ss << prefix << indent2 << jsonnet_string_unparse(f.first, false) << U": " |
3235 | 1.13M | << vstr; |
3236 | 1.13M | prefix = multiline ? U",\n" : U", "; |
3237 | 1.13M | } |
3238 | 611k | ss << (multiline ? U"\n" : U"") << indent << U"}"; |
3239 | 611k | } |
3240 | 927k | } break; |
3241 | | |
3242 | 160k | case Value::STRING: { |
3243 | 160k | const UString &str = static_cast<HeapString *>(scratch.v.h)->value; |
3244 | 160k | ss << jsonnet_string_unparse(str, false); |
3245 | 160k | } break; |
3246 | 8.12M | } |
3247 | 7.77M | return ss.str(); |
3248 | 8.12M | } |
3249 | | |
3250 | | UString manifestString(const LocationRange &loc) |
3251 | 0 | { |
3252 | 0 | if (scratch.t != Value::STRING) { |
3253 | 0 | std::stringstream ss; |
3254 | 0 | ss << "expected string result, got: " << type_str(scratch.t); |
3255 | 0 | throw makeError(loc, ss.str()); |
3256 | 0 | } |
3257 | 0 | return static_cast<HeapString *>(scratch.v.h)->value; |
3258 | 0 | } |
3259 | | |
3260 | | StrMap manifestMulti(bool string) |
3261 | 171 | { |
3262 | 171 | StrMap r; |
3263 | 171 | LocationRange loc("During manifestation"); |
3264 | 171 | if (scratch.t != Value::OBJECT) { |
3265 | 54 | std::stringstream ss; |
3266 | 54 | ss << "multi mode: top-level object was a " << type_str(scratch.t) << ", " |
3267 | 54 | << "should be an object whose keys are filenames and values hold " |
3268 | 54 | << "the JSON for that file."; |
3269 | 54 | throw makeError(loc, ss.str()); |
3270 | 54 | } |
3271 | 117 | auto *obj = static_cast<HeapObject *>(scratch.v.h); |
3272 | 117 | runInvariants(loc, obj); |
3273 | 117 | std::map<UString, const Identifier *> fields; |
3274 | 610 | for (const auto &f : objectFields(obj, true)) { |
3275 | 610 | fields[f->name] = f; |
3276 | 610 | } |
3277 | 581 | for (const auto &f : fields) { |
3278 | | // pushes FRAME_CALL |
3279 | 581 | const AST *body = objectIndex(loc, obj, f.second, 0); |
3280 | 581 | stack.top().val = scratch; |
3281 | 581 | evaluate(body, stack.size()); |
3282 | 581 | auto vstr = |
3283 | 581 | string ? manifestString(body->location) : manifestJson(body->location, true, U""); |
3284 | | // Reset scratch so that the object we're manifesting doesn't |
3285 | | // get GC'd. |
3286 | 581 | scratch = stack.top().val; |
3287 | 581 | stack.pop(); |
3288 | 581 | r[encode_utf8(f.first)] = encode_utf8(vstr); |
3289 | 581 | } |
3290 | 117 | return r; |
3291 | 171 | } |
3292 | | |
3293 | | std::vector<std::string> manifestStream(bool string) |
3294 | 2.32k | { |
3295 | 2.32k | std::vector<std::string> r; |
3296 | 2.32k | LocationRange loc("During manifestation"); |
3297 | 2.32k | if (scratch.t != Value::ARRAY) { |
3298 | 1.47k | std::stringstream ss; |
3299 | 1.47k | ss << "stream mode: top-level object was a " << type_str(scratch.t) << ", " |
3300 | 1.47k | << "should be an array whose elements hold " |
3301 | 1.47k | << "the JSON for each document in the stream."; |
3302 | 1.47k | throw makeError(loc, ss.str()); |
3303 | 1.47k | } |
3304 | 842 | auto *arr = static_cast<HeapArray *>(scratch.v.h); |
3305 | 620k | for (auto *thunk : arr->elements) { |
3306 | 620k | LocationRange tloc = thunk->body == nullptr ? loc : thunk->body->location; |
3307 | 620k | if (thunk->filled) { |
3308 | 0 | stack.newCall(loc, thunk, nullptr, 0, BindingFrame{}); |
3309 | | // Keep arr alive when scratch is overwritten |
3310 | 0 | stack.top().val = scratch; |
3311 | 0 | scratch = thunk->content; |
3312 | 620k | } else { |
3313 | 620k | stack.newCall(loc, thunk, thunk->self, thunk->offset, thunk->upValues); |
3314 | | // Keep arr alive when scratch is overwritten |
3315 | 620k | stack.top().val = scratch; |
3316 | 620k | evaluate(thunk->body, stack.size()); |
3317 | 620k | } |
3318 | 620k | UString element = string ? manifestString(tloc) : manifestJson(tloc, true, U""); |
3319 | 620k | scratch = stack.top().val; |
3320 | 620k | stack.pop(); |
3321 | 620k | r.push_back(encode_utf8(element)); |
3322 | 620k | } |
3323 | 842 | return r; |
3324 | 2.32k | } |
3325 | | }; |
3326 | | |
3327 | | } // namespace |
3328 | | |
3329 | | std::string jsonnet_vm_execute(Allocator *alloc, const AST *ast, const ExtMap &ext_vars, |
3330 | | unsigned max_stack, double gc_min_objects, double gc_growth_trigger, |
3331 | | const VmNativeCallbackMap &natives, |
3332 | | JsonnetImportCallback *import_callback, void *ctx, |
3333 | | bool string_output) |
3334 | 3.81k | { |
3335 | 3.81k | Interpreter vm(alloc, |
3336 | 3.81k | ext_vars, |
3337 | 3.81k | max_stack, |
3338 | 3.81k | gc_min_objects, |
3339 | 3.81k | gc_growth_trigger, |
3340 | 3.81k | natives, |
3341 | 3.81k | import_callback, |
3342 | 3.81k | ctx); |
3343 | 3.81k | vm.evaluate(ast, 0); |
3344 | 3.81k | if (string_output) { |
3345 | 0 | return encode_utf8(vm.manifestString(LocationRange("During manifestation"))); |
3346 | 3.81k | } else { |
3347 | 3.81k | return encode_utf8(vm.manifestJson(LocationRange("During manifestation"), true, U"")); |
3348 | 3.81k | } |
3349 | 3.81k | } |
3350 | | |
3351 | | StrMap jsonnet_vm_execute_multi(Allocator *alloc, const AST *ast, const ExtMap &ext_vars, |
3352 | | unsigned max_stack, double gc_min_objects, double gc_growth_trigger, |
3353 | | const VmNativeCallbackMap &natives, |
3354 | | JsonnetImportCallback *import_callback, void *ctx, |
3355 | | bool string_output) |
3356 | 252 | { |
3357 | 252 | Interpreter vm(alloc, |
3358 | 252 | ext_vars, |
3359 | 252 | max_stack, |
3360 | 252 | gc_min_objects, |
3361 | 252 | gc_growth_trigger, |
3362 | 252 | natives, |
3363 | 252 | import_callback, |
3364 | 252 | ctx); |
3365 | 252 | vm.evaluate(ast, 0); |
3366 | 252 | return vm.manifestMulti(string_output); |
3367 | 252 | } |
3368 | | |
3369 | | std::vector<std::string> jsonnet_vm_execute_stream(Allocator *alloc, const AST *ast, |
3370 | | const ExtMap &ext_vars, unsigned max_stack, |
3371 | | double gc_min_objects, double gc_growth_trigger, |
3372 | | const VmNativeCallbackMap &natives, |
3373 | | JsonnetImportCallback *import_callback, |
3374 | | void *ctx, bool string_output) |
3375 | 4.16k | { |
3376 | 4.16k | Interpreter vm(alloc, |
3377 | 4.16k | ext_vars, |
3378 | 4.16k | max_stack, |
3379 | 4.16k | gc_min_objects, |
3380 | 4.16k | gc_growth_trigger, |
3381 | 4.16k | natives, |
3382 | 4.16k | import_callback, |
3383 | 4.16k | ctx); |
3384 | 4.16k | vm.evaluate(ast, 0); |
3385 | 4.16k | return vm.manifestStream(string_output); |
3386 | 4.16k | } |
3387 | | |
3388 | | } // namespace jsonnet::internal |