blob: 15d4f6e61635a669318713a9e0766f490d40f115 [file] [log] [blame]
Ethan Nicholas26a9aad2018-03-27 14:10:52 -04001/*
2 * Copyright 2018 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#ifndef SKSL_STANDALONE
9
Ethan Nicholasae9633b2019-05-24 12:46:34 -040010#include "include/core/SkPoint3.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050011#include "src/core/SkRasterPipeline.h"
Brian Osman07c117b2019-05-23 12:51:06 -070012#include "src/sksl/SkSLByteCodeGenerator.h"
Ethan Nicholas91164d12019-05-15 15:29:54 -040013#include "src/sksl/SkSLExternalValue.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050014#include "src/sksl/SkSLInterpreter.h"
15#include "src/sksl/ir/SkSLBinaryExpression.h"
16#include "src/sksl/ir/SkSLExpressionStatement.h"
17#include "src/sksl/ir/SkSLForStatement.h"
18#include "src/sksl/ir/SkSLFunctionCall.h"
19#include "src/sksl/ir/SkSLFunctionReference.h"
20#include "src/sksl/ir/SkSLIfStatement.h"
21#include "src/sksl/ir/SkSLIndexExpression.h"
22#include "src/sksl/ir/SkSLPostfixExpression.h"
23#include "src/sksl/ir/SkSLPrefixExpression.h"
24#include "src/sksl/ir/SkSLProgram.h"
25#include "src/sksl/ir/SkSLStatement.h"
26#include "src/sksl/ir/SkSLTernaryExpression.h"
27#include "src/sksl/ir/SkSLVarDeclarations.h"
28#include "src/sksl/ir/SkSLVarDeclarationsStatement.h"
29#include "src/sksl/ir/SkSLVariableReference.h"
Ethan Nicholas26a9aad2018-03-27 14:10:52 -040030
31namespace SkSL {
32
Ethan Nicholas0e9401d2019-03-21 11:05:37 -040033static constexpr int UNINITIALIZED = 0xDEADBEEF;
34
Mike Kleine7007382019-05-21 08:36:32 -050035Interpreter::Interpreter(std::unique_ptr<Program> program,
36 std::unique_ptr<ByteCode> byteCode,
Ethan Nicholasdfcad062019-05-07 12:53:34 -040037 Interpreter::Value inputs[])
38 : fProgram(std::move(program))
Mike Kleine7007382019-05-21 08:36:32 -050039 , fByteCode(std::move(byteCode))
40 , fGlobals(fByteCode->fGlobalCount, UNINITIALIZED) {
Brian Osmand369a5e2019-05-09 13:13:25 -040041 this->setInputs(inputs);
42}
43
44void Interpreter::setInputs(Interpreter::Value inputs[]) {
Mike Kleine7007382019-05-21 08:36:32 -050045 for (uint8_t slot : fByteCode->fInputSlots) {
46 fGlobals[slot] = *inputs++;
Ethan Nicholasdfcad062019-05-07 12:53:34 -040047 }
48}
49
50void Interpreter::run(const ByteCodeFunction& f, Interpreter::Value args[],
51 Interpreter::Value* outReturn) {
Ethan Nicholas0e9401d2019-03-21 11:05:37 -040052#ifdef TRACE
53 this->disassemble(f);
54#endif
Ethan Nicholasdfcad062019-05-07 12:53:34 -040055 Value smallStack[128];
56 std::unique_ptr<Value[]> largeStack;
57 Value* stack = smallStack;
Brian Osman226668a2019-05-14 16:47:30 -040058 if ((int) SK_ARRAY_COUNT(smallStack) < f.fStackCount) {
59 largeStack.reset(new Value[f.fStackCount]);
Ethan Nicholasdfcad062019-05-07 12:53:34 -040060 stack = largeStack.get();
Ethan Nicholas0e9401d2019-03-21 11:05:37 -040061 }
Mike Kleine7007382019-05-21 08:36:32 -050062
63 if (f.fParameterCount) {
64 memcpy(stack, args, f.fParameterCount * sizeof(Value));
65 }
66 this->innerRun(f, stack, outReturn);
67
68 for (const Variable* p : f.fDeclaration.fParameters) {
Brian Osman07c117b2019-05-23 12:51:06 -070069 const int nvalues = ByteCodeGenerator::SlotCount(p->fType);
Ethan Nicholas0e9401d2019-03-21 11:05:37 -040070 if (p->fModifiers.fFlags & Modifiers::kOut_Flag) {
Mike Kleine7007382019-05-21 08:36:32 -050071 memcpy(args, stack, nvalues * sizeof(Value));
Ethan Nicholas26a9aad2018-03-27 14:10:52 -040072 }
Mike Kleine7007382019-05-21 08:36:32 -050073 args += nvalues;
74 stack += nvalues;
Ethan Nicholas26a9aad2018-03-27 14:10:52 -040075 }
Ethan Nicholas26a9aad2018-03-27 14:10:52 -040076}
77
Mike Klein76346ac2019-05-17 11:57:10 -050078template <typename T>
79static T unaligned_load(const void* ptr) {
80 T val;
81 memcpy(&val, ptr, sizeof(val));
82 return val;
83}
Ethan Nicholas0e9401d2019-03-21 11:05:37 -040084
Mike Kleine7007382019-05-21 08:36:32 -050085#define READ8() (*(ip++))
Mike Klein76346ac2019-05-17 11:57:10 -050086#define READ16() (ip += 2, unaligned_load<uint16_t>(ip - 2))
87#define READ32() (ip += 4, unaligned_load<uint32_t>(ip - 4))
Ethan Nicholas0e9401d2019-03-21 11:05:37 -040088
Brian Osman3e833e12019-05-23 13:23:24 -070089#define VECTOR_DISASSEMBLE(op, text) \
Ethan Nicholas48a75aa2019-05-16 17:15:56 -040090 case ByteCodeInstruction::op: printf(text); break; \
91 case ByteCodeInstruction::op##2: printf(text "2"); break; \
92 case ByteCodeInstruction::op##3: printf(text "3"); break; \
93 case ByteCodeInstruction::op##4: printf(text "4"); break;
94
Brian Osman3e833e12019-05-23 13:23:24 -070095static const uint8_t* disassemble_instruction(const uint8_t* ip) {
96 switch ((ByteCodeInstruction) READ16()) {
97 VECTOR_DISASSEMBLE(kAddF, "addf")
98 VECTOR_DISASSEMBLE(kAddI, "addi")
Brian Osman16e6fd52019-05-29 11:19:00 -040099 VECTOR_DISASSEMBLE(kAndB, "andb")
100 VECTOR_DISASSEMBLE(kAndI, "andb")
Brian Osman3e833e12019-05-23 13:23:24 -0700101 case ByteCodeInstruction::kBranch: printf("branch %d", READ16()); break;
102 case ByteCodeInstruction::kCall: printf("call %d", READ8()); break;
103 case ByteCodeInstruction::kCallExternal: {
104 int argumentCount = READ8();
105 int returnCount = READ8();
106 int externalValue = READ8();
107 printf("callexternal %d, %d, %d", argumentCount, returnCount, externalValue);
108 break;
109 }
110 VECTOR_DISASSEMBLE(kCompareIEQ, "compareieq")
111 VECTOR_DISASSEMBLE(kCompareINEQ, "compareineq")
112 VECTOR_DISASSEMBLE(kCompareFEQ, "comparefeq")
113 VECTOR_DISASSEMBLE(kCompareFNEQ, "comparefneq")
114 VECTOR_DISASSEMBLE(kCompareFGT, "comparefgt")
115 VECTOR_DISASSEMBLE(kCompareFGTEQ, "comparefgteq")
116 VECTOR_DISASSEMBLE(kCompareFLT, "compareflt")
117 VECTOR_DISASSEMBLE(kCompareFLTEQ, "compareflteq")
118 VECTOR_DISASSEMBLE(kCompareSGT, "comparesgt")
119 VECTOR_DISASSEMBLE(kCompareSGTEQ, "comparesgteq")
120 VECTOR_DISASSEMBLE(kCompareSLT, "compareslt")
121 VECTOR_DISASSEMBLE(kCompareSLTEQ, "compareslteq")
122 VECTOR_DISASSEMBLE(kCompareUGT, "compareugt")
123 VECTOR_DISASSEMBLE(kCompareUGTEQ, "compareugteq")
124 VECTOR_DISASSEMBLE(kCompareULT, "compareult")
125 VECTOR_DISASSEMBLE(kCompareULTEQ, "compareulteq")
126 case ByteCodeInstruction::kConditionalBranch:
127 printf("conditionalbranch %d", READ16());
128 break;
129 VECTOR_DISASSEMBLE(kConvertFtoI, "convertftoi")
130 VECTOR_DISASSEMBLE(kConvertStoF, "convertstof")
131 VECTOR_DISASSEMBLE(kConvertUtoF, "convertutof")
132 VECTOR_DISASSEMBLE(kCos, "cos")
133 case ByteCodeInstruction::kDebugPrint: printf("debugprint"); break;
134 VECTOR_DISASSEMBLE(kDivideF, "dividef")
135 VECTOR_DISASSEMBLE(kDivideS, "divideS")
136 VECTOR_DISASSEMBLE(kDivideU, "divideu")
137 VECTOR_DISASSEMBLE(kDup, "dup")
138 case ByteCodeInstruction::kDupN: printf("dupN %d", READ8()); break;
139 case ByteCodeInstruction::kLoad: printf("load %d", READ8()); break;
140 case ByteCodeInstruction::kLoad2: printf("load2 %d", READ8()); break;
141 case ByteCodeInstruction::kLoad3: printf("load3 %d", READ8()); break;
142 case ByteCodeInstruction::kLoad4: printf("load4 %d", READ8()); break;
143 case ByteCodeInstruction::kLoadGlobal: printf("loadglobal %d", READ8()); break;
144 case ByteCodeInstruction::kLoadGlobal2: printf("loadglobal2 %d", READ8()); break;
145 case ByteCodeInstruction::kLoadGlobal3: printf("loadglobal3 %d", READ8()); break;
146 case ByteCodeInstruction::kLoadGlobal4: printf("loadglobal4 %d", READ8()); break;
147 case ByteCodeInstruction::kLoadSwizzle: {
148 int target = READ8();
149 int count = READ8();
150 printf("loadswizzle %d %d", target, count);
151 for (int i = 0; i < count; ++i) {
152 printf(", %d", READ8());
153 }
154 break;
155 }
156 case ByteCodeInstruction::kLoadSwizzleGlobal: {
157 int target = READ8();
158 int count = READ8();
159 printf("loadswizzleglobal %d %d", target, count);
160 for (int i = 0; i < count; ++i) {
161 printf(", %d", READ8());
162 }
163 break;
164 }
165 case ByteCodeInstruction::kLoadExtended: printf("loadextended %d", READ8()); break;
166 case ByteCodeInstruction::kLoadExtendedGlobal: printf("loadextendedglobal %d", READ8());
167 break;
Brian Osman29e013d2019-05-28 17:16:03 -0400168 case ByteCodeInstruction::kMatrixToMatrix: {
169 int srcCols = READ8();
170 int srcRows = READ8();
171 int dstCols = READ8();
172 int dstRows = READ8();
173 printf("matrixtomatrix %dx%d %dx%d", srcCols, srcRows, dstCols, dstRows);
174 break;
175 }
Ethan Nicholasae9633b2019-05-24 12:46:34 -0400176 VECTOR_DISASSEMBLE(kMix, "mix")
Brian Osman3e833e12019-05-23 13:23:24 -0700177 VECTOR_DISASSEMBLE(kMultiplyF, "multiplyf")
178 VECTOR_DISASSEMBLE(kMultiplyI, "multiplyi")
179 VECTOR_DISASSEMBLE(kNegateF, "negatef")
180 VECTOR_DISASSEMBLE(kNegateI, "negatei")
181 VECTOR_DISASSEMBLE(kNot, "not")
182 VECTOR_DISASSEMBLE(kOrB, "orb")
183 VECTOR_DISASSEMBLE(kOrI, "ori")
184 VECTOR_DISASSEMBLE(kPop, "pop")
185 case ByteCodeInstruction::kPopN: printf("popN %d", READ8()); break;
186 case ByteCodeInstruction::kPushImmediate: {
187 uint32_t v = READ32();
188 union { uint32_t u; float f; } pun = { v };
189 printf("pushimmediate %s", (to_string(v) + "(" + to_string(pun.f) + ")").c_str());
190 break;
191 }
192 case ByteCodeInstruction::kReadExternal: printf("readexternal %d", READ8()); break;
193 case ByteCodeInstruction::kReadExternal2: printf("readexternal2 %d", READ8()); break;
194 case ByteCodeInstruction::kReadExternal3: printf("readexternal3 %d", READ8()); break;
195 case ByteCodeInstruction::kReadExternal4: printf("readexternal4 %d", READ8()); break;
196 VECTOR_DISASSEMBLE(kRemainderF, "remainderf")
197 VECTOR_DISASSEMBLE(kRemainderS, "remainders")
198 VECTOR_DISASSEMBLE(kRemainderU, "remainderu")
199 case ByteCodeInstruction::kReturn: printf("return %d", READ8()); break;
Brian Osman29e013d2019-05-28 17:16:03 -0400200 case ByteCodeInstruction::kScalarToMatrix: {
201 int cols = READ8();
202 int rows = READ8();
203 printf("scalartomatrix %dx%d", cols, rows);
204 break;
205 }
Brian Osman3e833e12019-05-23 13:23:24 -0700206 VECTOR_DISASSEMBLE(kSin, "sin")
207 VECTOR_DISASSEMBLE(kSqrt, "sqrt")
208 case ByteCodeInstruction::kStore: printf("store %d", READ8()); break;
209 case ByteCodeInstruction::kStore2: printf("store2 %d", READ8()); break;
210 case ByteCodeInstruction::kStore3: printf("store3 %d", READ8()); break;
211 case ByteCodeInstruction::kStore4: printf("store4 %d", READ8()); break;
212 case ByteCodeInstruction::kStoreGlobal: printf("storeglobal %d", READ8()); break;
213 case ByteCodeInstruction::kStoreGlobal2: printf("storeglobal2 %d", READ8()); break;
214 case ByteCodeInstruction::kStoreGlobal3: printf("storeglobal3 %d", READ8()); break;
215 case ByteCodeInstruction::kStoreGlobal4: printf("storeglobal4 %d", READ8()); break;
216 case ByteCodeInstruction::kStoreSwizzle: {
217 int target = READ8();
218 int count = READ8();
219 printf("storeswizzle %d %d", target, count);
220 for (int i = 0; i < count; ++i) {
221 printf(", %d", READ8());
222 }
223 break;
224 }
225 case ByteCodeInstruction::kStoreSwizzleGlobal: {
226 int target = READ8();
227 int count = READ8();
228 printf("storeswizzleglobal %d %d", target, count);
229 for (int i = 0; i < count; ++i) {
230 printf(", %d", READ8());
231 }
232 break;
233 }
234 case ByteCodeInstruction::kStoreSwizzleIndirect: {
235 int count = READ8();
236 printf("storeswizzleindirect %d", count);
237 for (int i = 0; i < count; ++i) {
238 printf(", %d", READ8());
239 }
240 break;
241 }
242 case ByteCodeInstruction::kStoreSwizzleIndirectGlobal: {
243 int count = READ8();
244 printf("storeswizzleindirectglobal %d", count);
245 for (int i = 0; i < count; ++i) {
246 printf(", %d", READ8());
247 }
248 break;
249 }
250 case ByteCodeInstruction::kStoreExtended: printf("storeextended %d", READ8()); break;
251 case ByteCodeInstruction::kStoreExtendedGlobal: printf("storeextendedglobal %d", READ8());
252 break;
253 VECTOR_DISASSEMBLE(kSubtractF, "subtractf")
254 VECTOR_DISASSEMBLE(kSubtractI, "subtracti")
255 case ByteCodeInstruction::kSwizzle: {
256 printf("swizzle %d, ", READ8());
257 int count = READ8();
258 printf("%d", count);
259 for (int i = 0; i < count; ++i) {
260 printf(", %d", READ8());
261 }
262 break;
263 }
264 VECTOR_DISASSEMBLE(kTan, "tan")
265 case ByteCodeInstruction::kWriteExternal: printf("writeexternal %d", READ8()); break;
266 case ByteCodeInstruction::kWriteExternal2: printf("writeexternal2 %d", READ8()); break;
267 case ByteCodeInstruction::kWriteExternal3: printf("writeexternal3 %d", READ8()); break;
268 case ByteCodeInstruction::kWriteExternal4: printf("writeexternal4 %d", READ8()); break;
269 default: printf("unknown(%d)\n", *(ip - 1)); SkASSERT(false);
270 }
271 return ip;
272}
273
Ethan Nicholas0e9401d2019-03-21 11:05:37 -0400274void Interpreter::disassemble(const ByteCodeFunction& f) {
Ethan Nicholasdfcad062019-05-07 12:53:34 -0400275 const uint8_t* ip = f.fCode.data();
276 while (ip < f.fCode.data() + f.fCode.size()) {
277 printf("%d: ", (int) (ip - f.fCode.data()));
Brian Osman3e833e12019-05-23 13:23:24 -0700278 ip = disassemble_instruction(ip);
Ethan Nicholas0e9401d2019-03-21 11:05:37 -0400279 printf("\n");
Ethan Nicholas26a9aad2018-03-27 14:10:52 -0400280 }
281}
282
Mike Klein459aed12019-05-21 15:46:36 -0500283#define VECTOR_BINARY_OP(base, field, op) \
Mike Kleine7007382019-05-21 08:36:32 -0500284 case ByteCodeInstruction::base ## 4: \
Mike Klein459aed12019-05-21 15:46:36 -0500285 sp[-4] = sp[-4].field op sp[0].field; \
Mike Kleine7007382019-05-21 08:36:32 -0500286 POP(); \
287 /* fall through */ \
288 case ByteCodeInstruction::base ## 3: { \
289 int count = (int) ByteCodeInstruction::base - (int) inst - 1; \
Mike Klein459aed12019-05-21 15:46:36 -0500290 sp[count] = sp[count].field op sp[0].field; \
Mike Kleine7007382019-05-21 08:36:32 -0500291 POP(); \
292 } /* fall through */ \
293 case ByteCodeInstruction::base ## 2: { \
294 int count = (int) ByteCodeInstruction::base - (int) inst - 1; \
Mike Klein459aed12019-05-21 15:46:36 -0500295 sp[count] = sp[count].field op sp[0].field; \
Mike Kleine7007382019-05-21 08:36:32 -0500296 POP(); \
297 } /* fall through */ \
298 case ByteCodeInstruction::base: { \
299 int count = (int) ByteCodeInstruction::base - (int) inst - 1; \
Mike Klein459aed12019-05-21 15:46:36 -0500300 sp[count] = sp[count].field op sp[0].field; \
Mike Kleine7007382019-05-21 08:36:32 -0500301 POP(); \
302 break; \
303 }
Ethan Nicholasaeb71ce2019-05-20 09:55:44 -0400304
Ethan Nicholasae9633b2019-05-24 12:46:34 -0400305#define VECTOR_BINARY_FN(base, field, fn) \
306 case ByteCodeInstruction::base ## 4: \
307 sp[-4] = fn(sp[-4].field, sp[0].field); \
308 POP(); \
309 /* fall through */ \
310 case ByteCodeInstruction::base ## 3: { \
311 int target = (int) ByteCodeInstruction::base - (int) inst - 1; \
312 sp[target] = fn(sp[target].field, sp[0].field); \
313 POP(); \
314 } /* fall through */ \
315 case ByteCodeInstruction::base ## 2: { \
316 int target = (int) ByteCodeInstruction::base - (int) inst - 1; \
317 sp[target] = fn(sp[target].field, sp[0].field); \
318 POP(); \
319 } /* fall through */ \
320 case ByteCodeInstruction::base: { \
321 int target = (int) ByteCodeInstruction::base - (int) inst - 1; \
322 sp[target] = fn(sp[target].field, sp[0].field); \
323 POP(); \
324 break; \
Mike Kleine7007382019-05-21 08:36:32 -0500325 }
Ethan Nicholas0e9401d2019-03-21 11:05:37 -0400326
Mike Klein459aed12019-05-21 15:46:36 -0500327#define VECTOR_UNARY_FN(base, fn, field) \
328 case ByteCodeInstruction::base ## 4: sp[-3] = fn(sp[-3].field); \
329 case ByteCodeInstruction::base ## 3: sp[-2] = fn(sp[-2].field); \
330 case ByteCodeInstruction::base ## 2: sp[-1] = fn(sp[-1].field); \
331 case ByteCodeInstruction::base: sp[ 0] = fn(sp[ 0].field); \
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400332 break;
333
Brian Osman226668a2019-05-14 16:47:30 -0400334struct StackFrame {
335 const uint8_t* fCode;
336 const uint8_t* fIP;
337 Interpreter::Value* fStack;
338};
339
Ethan Nicholasae9633b2019-05-24 12:46:34 -0400340static float mix(float start, float end, float t) {
341 return start * (1 - t) + end * t;
342}
343
Mike Kleine7007382019-05-21 08:36:32 -0500344void Interpreter::innerRun(const ByteCodeFunction& f, Value* stack, Value* outReturn) {
345 Value* sp = stack + f.fParameterCount + f.fLocalCount - 1;
346
347 auto POP = [&] { SkASSERT(sp >= stack); return *(sp--); };
348 auto PUSH = [&](Value v) { SkASSERT(sp + 1 >= stack); *(++sp) = v; };
349
Brian Osman226668a2019-05-14 16:47:30 -0400350 const uint8_t* code = f.fCode.data();
Ethan Nicholasdfcad062019-05-07 12:53:34 -0400351 const uint8_t* ip = code;
Brian Osman226668a2019-05-14 16:47:30 -0400352 std::vector<StackFrame> frames;
353
Ethan Nicholas7e603db2019-05-03 12:57:47 -0400354 for (;;) {
Ethan Nicholasdfcad062019-05-07 12:53:34 -0400355#ifdef TRACE
Brian Osman3e833e12019-05-23 13:23:24 -0700356 printf("at %3d ", (int) (ip - code));
357 disassemble_instruction(ip);
358 printf("\n");
Ethan Nicholasdfcad062019-05-07 12:53:34 -0400359#endif
Brian Osmane85b6a52019-05-22 14:50:59 -0700360 ByteCodeInstruction inst = (ByteCodeInstruction) READ16();
Ethan Nicholas9764ebd2019-05-01 14:43:54 -0400361 switch (inst) {
Mike Kleinc1999982019-05-21 13:03:49 -0500362 VECTOR_BINARY_OP(kAddI, fSigned, +)
363 VECTOR_BINARY_OP(kAddF, fFloat, +)
Brian Osman16e6fd52019-05-29 11:19:00 -0400364 VECTOR_BINARY_OP(kAndB, fBool, &&)
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400365
Ethan Nicholas48a75aa2019-05-16 17:15:56 -0400366 case ByteCodeInstruction::kBranch:
Ethan Nicholasdfcad062019-05-07 12:53:34 -0400367 ip = code + READ16();
Ethan Nicholas9764ebd2019-05-01 14:43:54 -0400368 break;
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400369
Brian Osman226668a2019-05-14 16:47:30 -0400370 case ByteCodeInstruction::kCall: {
371 // Precursor code has pushed all parameters to the stack. Update our bottom of
372 // stack to point at the first parameter, and our sp to point past those parameters
373 // (plus space for locals).
Mike Kleine7007382019-05-21 08:36:32 -0500374 int target = READ8();
Brian Osman226668a2019-05-14 16:47:30 -0400375 const ByteCodeFunction* fun = fByteCode->fFunctions[target].get();
376 frames.push_back({ code, ip, stack });
377 ip = code = fun->fCode.data();
378 stack = sp - fun->fParameterCount + 1;
379 sp = stack + fun->fParameterCount + fun->fLocalCount - 1;
380 break;
381 }
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400382
Ethan Nicholas9e6a3932019-05-17 16:31:21 -0400383 case ByteCodeInstruction::kCallExternal: {
384 int argumentCount = READ8();
385 int returnCount = READ8();
Mike Kleine7007382019-05-21 08:36:32 -0500386 int target = READ8();
Ethan Nicholas9e6a3932019-05-17 16:31:21 -0400387 ExternalValue* v = fByteCode->fExternalValues[target];
388 sp -= argumentCount - 1;
Mike Kleine7007382019-05-21 08:36:32 -0500389
390 Value tmp[4];
391 SkASSERT(returnCount <= (int)SK_ARRAY_COUNT(tmp));
Ethan Nicholas9e6a3932019-05-17 16:31:21 -0400392 v->call(sp, tmp);
393 memcpy(sp, tmp, returnCount * sizeof(Value));
394 sp += returnCount - 1;
395 break;
396 }
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400397
Mike Kleinc1999982019-05-21 13:03:49 -0500398 VECTOR_BINARY_OP(kCompareIEQ, fSigned, ==)
399 VECTOR_BINARY_OP(kCompareFEQ, fFloat, ==)
400 VECTOR_BINARY_OP(kCompareINEQ, fSigned, !=)
401 VECTOR_BINARY_OP(kCompareFNEQ, fFloat, !=)
402 VECTOR_BINARY_OP(kCompareSGT, fSigned, >)
403 VECTOR_BINARY_OP(kCompareUGT, fUnsigned, >)
404 VECTOR_BINARY_OP(kCompareFGT, fFloat, >)
405 VECTOR_BINARY_OP(kCompareSGTEQ, fSigned, >=)
406 VECTOR_BINARY_OP(kCompareUGTEQ, fUnsigned, >=)
407 VECTOR_BINARY_OP(kCompareFGTEQ, fFloat, >=)
408 VECTOR_BINARY_OP(kCompareSLT, fSigned, <)
409 VECTOR_BINARY_OP(kCompareULT, fUnsigned, <)
410 VECTOR_BINARY_OP(kCompareFLT, fFloat, <)
411 VECTOR_BINARY_OP(kCompareSLTEQ, fSigned, <=)
412 VECTOR_BINARY_OP(kCompareULTEQ, fUnsigned, <=)
413 VECTOR_BINARY_OP(kCompareFLTEQ, fFloat, <=)
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400414
Mike Kleine7007382019-05-21 08:36:32 -0500415 case ByteCodeInstruction::kConditionalBranch: {
416 int target = READ16();
Ethan Nicholasdfcad062019-05-07 12:53:34 -0400417 if (POP().fBool) {
Mike Kleine7007382019-05-21 08:36:32 -0500418 ip = code + target;
Ethan Nicholas9764ebd2019-05-01 14:43:54 -0400419 }
420 break;
Mike Kleine7007382019-05-21 08:36:32 -0500421 }
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400422
423 case ByteCodeInstruction::kConvertFtoI4: sp[-3].fSigned = (int)sp[-3].fFloat;
424 case ByteCodeInstruction::kConvertFtoI3: sp[-2].fSigned = (int)sp[-2].fFloat;
425 case ByteCodeInstruction::kConvertFtoI2: sp[-1].fSigned = (int)sp[-1].fFloat;
426 case ByteCodeInstruction::kConvertFtoI: sp[ 0].fSigned = (int)sp[ 0].fFloat;
427 break;
428
429 case ByteCodeInstruction::kConvertStoF4: sp[-3].fFloat = sp[-3].fSigned;
430 case ByteCodeInstruction::kConvertStoF3: sp[-2].fFloat = sp[-2].fSigned;
431 case ByteCodeInstruction::kConvertStoF2: sp[-1].fFloat = sp[-1].fSigned;
432 case ByteCodeInstruction::kConvertStoF : sp[ 0].fFloat = sp[ 0].fSigned;
433 break;
434
435 case ByteCodeInstruction::kConvertUtoF4: sp[-3].fFloat = sp[-3].fUnsigned;
436 case ByteCodeInstruction::kConvertUtoF3: sp[-2].fFloat = sp[-2].fUnsigned;
437 case ByteCodeInstruction::kConvertUtoF2: sp[-1].fFloat = sp[-1].fUnsigned;
438 case ByteCodeInstruction::kConvertUtoF : sp[ 0].fFloat = sp[ 0].fUnsigned;
439 break;
440
Mike Klein459aed12019-05-21 15:46:36 -0500441 VECTOR_UNARY_FN(kCos, cosf, fFloat)
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400442
Ethan Nicholasae9633b2019-05-24 12:46:34 -0400443 case ByteCodeInstruction::kCross: {
444 SkPoint3 cross = SkPoint3::CrossProduct(SkPoint3::Make(sp[-5].fFloat,
445 sp[-4].fFloat,
446 sp[-3].fFloat),
447 SkPoint3::Make(sp[-2].fFloat,
448 sp[-1].fFloat,
449 sp[ 0].fFloat));
450 sp -= 3;
451 sp[-2] = cross.fX;
452 sp[-1] = cross.fY;
453 sp[ 0] = cross.fZ;
454 break;
455 }
456
Mike Kleine7007382019-05-21 08:36:32 -0500457 case ByteCodeInstruction::kDebugPrint: {
458 Value v = POP();
459 printf("Debug: %d(int), %d(uint), %f(float)\n", v.fSigned, v.fUnsigned, v.fFloat);
Ethan Nicholas9764ebd2019-05-01 14:43:54 -0400460 break;
Mike Kleine7007382019-05-21 08:36:32 -0500461 }
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400462
Mike Kleinc1999982019-05-21 13:03:49 -0500463 VECTOR_BINARY_OP(kDivideS, fSigned, /)
464 VECTOR_BINARY_OP(kDivideU, fUnsigned, /)
465 VECTOR_BINARY_OP(kDivideF, fFloat, /)
Mike Kleine7007382019-05-21 08:36:32 -0500466
467 case ByteCodeInstruction::kDup4: PUSH(sp[(int)ByteCodeInstruction::kDup - (int)inst]);
468 case ByteCodeInstruction::kDup3: PUSH(sp[(int)ByteCodeInstruction::kDup - (int)inst]);
469 case ByteCodeInstruction::kDup2: PUSH(sp[(int)ByteCodeInstruction::kDup - (int)inst]);
470 case ByteCodeInstruction::kDup : PUSH(sp[(int)ByteCodeInstruction::kDup - (int)inst]);
471 break;
472
Brian Osman07c117b2019-05-23 12:51:06 -0700473 case ByteCodeInstruction::kDupN: {
474 int count = READ8();
475 memcpy(sp + 1, sp - count + 1, count * sizeof(Value));
476 sp += count;
477 break;
478 }
479
Mike Kleine7007382019-05-21 08:36:32 -0500480 case ByteCodeInstruction::kLoad4: sp[4] = stack[*ip + 3];
481 case ByteCodeInstruction::kLoad3: sp[3] = stack[*ip + 2];
482 case ByteCodeInstruction::kLoad2: sp[2] = stack[*ip + 1];
483 case ByteCodeInstruction::kLoad : sp[1] = stack[*ip + 0];
484 ++ip;
485 sp += (int)inst - (int)ByteCodeInstruction::kLoad + 1;
486 break;
487
488 case ByteCodeInstruction::kLoadGlobal4: sp[4] = fGlobals[*ip + 3];
489 case ByteCodeInstruction::kLoadGlobal3: sp[3] = fGlobals[*ip + 2];
490 case ByteCodeInstruction::kLoadGlobal2: sp[2] = fGlobals[*ip + 1];
491 case ByteCodeInstruction::kLoadGlobal : sp[1] = fGlobals[*ip + 0];
492 ++ip;
Brian Osman07c117b2019-05-23 12:51:06 -0700493 sp += (int)inst -
494 (int)ByteCodeInstruction::kLoadGlobal + 1;
Mike Kleine7007382019-05-21 08:36:32 -0500495 break;
496
Brian Osman07c117b2019-05-23 12:51:06 -0700497 case ByteCodeInstruction::kLoadExtended: {
498 int count = READ8();
499 int src = POP().fSigned;
500 memcpy(sp + 1, &stack[src], count * sizeof(Value));
501 sp += count;
502 break;
503 }
504
505 case ByteCodeInstruction::kLoadExtendedGlobal: {
506 int count = READ8();
507 int src = POP().fSigned;
508 SkASSERT(src + count <= (int) fGlobals.size());
509 memcpy(sp + 1, &fGlobals[src], count * sizeof(Value));
510 sp += count;
511 break;
512 }
513
Mike Kleine7007382019-05-21 08:36:32 -0500514 case ByteCodeInstruction::kLoadSwizzle: {
515 int src = READ8();
516 int count = READ8();
Ethan Nicholas9764ebd2019-05-01 14:43:54 -0400517 for (int i = 0; i < count; ++i) {
Ethan Nicholas48a75aa2019-05-16 17:15:56 -0400518 PUSH(stack[src + *(ip + i)]);
Ethan Nicholas9764ebd2019-05-01 14:43:54 -0400519 }
Ethan Nicholas7e603db2019-05-03 12:57:47 -0400520 ip += count;
Ethan Nicholas9764ebd2019-05-01 14:43:54 -0400521 break;
Mike Kleine7007382019-05-21 08:36:32 -0500522 }
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400523
Mike Kleine7007382019-05-21 08:36:32 -0500524 case ByteCodeInstruction::kLoadSwizzleGlobal: {
525 int src = READ8();
Ethan Nicholas48a75aa2019-05-16 17:15:56 -0400526 SkASSERT(src < (int) fGlobals.size());
Mike Kleine7007382019-05-21 08:36:32 -0500527 int count = READ8();
Brian Osmanb7451292019-05-15 13:02:13 -0400528 for (int i = 0; i < count; ++i) {
Ethan Nicholas48a75aa2019-05-16 17:15:56 -0400529 PUSH(fGlobals[src + *(ip + i)]);
Brian Osmanb7451292019-05-15 13:02:13 -0400530 }
531 ip += count;
532 break;
Mike Kleine7007382019-05-21 08:36:32 -0500533 }
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400534
Brian Osman29e013d2019-05-28 17:16:03 -0400535 case ByteCodeInstruction::kMatrixToMatrix: {
536 int srcCols = READ8();
537 int srcRows = READ8();
538 int dstCols = READ8();
539 int dstRows = READ8();
540 SkASSERT(srcCols >= 2 && srcCols <= 4);
541 SkASSERT(srcRows >= 2 && srcRows <= 4);
542 SkASSERT(dstCols >= 2 && dstCols <= 4);
543 SkASSERT(dstRows >= 2 && dstRows <= 4);
544 SkMatrix44 m;
545 for (int c = srcCols - 1; c >= 0; --c) {
546 for (int r = srcRows - 1; r >= 0; --r) {
547 m.set(r, c, POP().fFloat);
548 }
549 }
550 for (int c = 0; c < dstCols; ++c) {
551 for (int r = 0; r < dstRows; ++r) {
552 PUSH(m.get(r, c));
553 }
554 }
555 break;
556 }
557
Ethan Nicholasae9633b2019-05-24 12:46:34 -0400558 // stack looks like: X1 Y1 Z1 W1 X2 Y2 Z2 W2 T
559 case ByteCodeInstruction::kMix4:
560 sp[-5] = mix(sp[-5].fFloat, sp[-1].fFloat, sp[0].fFloat);
561 // fall through
562 case ByteCodeInstruction::kMix3: {
563 int count = (int) inst - (int) ByteCodeInstruction::kMix + 1;
564 int target = 2 - count * 2;
565 sp[target] = mix(sp[target].fFloat, sp[2 - count].fFloat, sp[0].fFloat);
566 // fall through
567 }
568 case ByteCodeInstruction::kMix2: {
569 int count = (int) inst - (int) ByteCodeInstruction::kMix + 1;
570 int target = 1 - count * 2;
571 sp[target] = mix(sp[target].fFloat, sp[1 - count].fFloat, sp[0].fFloat);
572 // fall through
573 }
574 case ByteCodeInstruction::kMix: {
575 int count = (int) inst - (int) ByteCodeInstruction::kMix + 1;
576 int target = -count * 2;
577 sp[target] = mix(sp[target].fFloat, sp[-count].fFloat, sp[0].fFloat);
578 sp -= 1 + count;
579 break;
580 }
581
Mike Kleinc1999982019-05-21 13:03:49 -0500582 VECTOR_BINARY_OP(kMultiplyI, fSigned, *)
583 VECTOR_BINARY_OP(kMultiplyF, fFloat, *)
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400584
Ethan Nicholas48a75aa2019-05-16 17:15:56 -0400585 case ByteCodeInstruction::kNot:
Mike Kleine7007382019-05-21 08:36:32 -0500586 sp[0].fBool = !sp[0].fBool;
Ethan Nicholas9764ebd2019-05-01 14:43:54 -0400587 break;
Mike Kleine7007382019-05-21 08:36:32 -0500588
Mike Kleinc1999982019-05-21 13:03:49 -0500589 case ByteCodeInstruction::kNegateF4: sp[-3] = -sp[-3].fFloat;
590 case ByteCodeInstruction::kNegateF3: sp[-2] = -sp[-2].fFloat;
591 case ByteCodeInstruction::kNegateF2: sp[-1] = -sp[-1].fFloat;
592 case ByteCodeInstruction::kNegateF : sp[ 0] = -sp[ 0].fFloat;
Mike Kleine7007382019-05-21 08:36:32 -0500593 break;
594
Mike Kleinc1999982019-05-21 13:03:49 -0500595 case ByteCodeInstruction::kNegateI4: sp[-3] = -sp[-3].fSigned;
596 case ByteCodeInstruction::kNegateI3: sp[-2] = -sp[-2].fSigned;
597 case ByteCodeInstruction::kNegateI2: sp[-1] = -sp[-1].fSigned;
598 case ByteCodeInstruction::kNegateI : sp[ 0] = -sp [0].fSigned;
Mike Kleine7007382019-05-21 08:36:32 -0500599 break;
600
Brian Osman16e6fd52019-05-29 11:19:00 -0400601 VECTOR_BINARY_OP(kOrB, fBool, ||)
602
Mike Kleine7007382019-05-21 08:36:32 -0500603 case ByteCodeInstruction::kPop4: POP();
604 case ByteCodeInstruction::kPop3: POP();
605 case ByteCodeInstruction::kPop2: POP();
606 case ByteCodeInstruction::kPop : POP();
607 break;
608
Brian Osman07c117b2019-05-23 12:51:06 -0700609 case ByteCodeInstruction::kPopN:
610 sp -= READ8();
611 break;
612
Ethan Nicholas9764ebd2019-05-01 14:43:54 -0400613 case ByteCodeInstruction::kPushImmediate:
Mike Kleine7007382019-05-21 08:36:32 -0500614 PUSH(READ32());
Ethan Nicholas9764ebd2019-05-01 14:43:54 -0400615 break;
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400616
Ethan Nicholas48a75aa2019-05-16 17:15:56 -0400617 case ByteCodeInstruction::kReadExternal: // fall through
618 case ByteCodeInstruction::kReadExternal2: // fall through
619 case ByteCodeInstruction::kReadExternal3: // fall through
Mike Kleine7007382019-05-21 08:36:32 -0500620 case ByteCodeInstruction::kReadExternal4: {
621 int src = READ8();
Ethan Nicholas48a75aa2019-05-16 17:15:56 -0400622 fByteCode->fExternalValues[src]->read(sp + 1);
Mike Kleine7007382019-05-21 08:36:32 -0500623 sp += (int) inst - (int) ByteCodeInstruction::kReadExternal + 1;
Ethan Nicholas91164d12019-05-15 15:29:54 -0400624 break;
Mike Kleine7007382019-05-21 08:36:32 -0500625 }
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400626
Mike Kleinc1999982019-05-21 13:03:49 -0500627 VECTOR_BINARY_FN(kRemainderF, fFloat, fmodf)
628 VECTOR_BINARY_OP(kRemainderS, fSigned, %)
629 VECTOR_BINARY_OP(kRemainderU, fUnsigned, %)
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400630
Mike Kleine7007382019-05-21 08:36:32 -0500631 case ByteCodeInstruction::kReturn: {
632 int count = READ8();
Brian Osman226668a2019-05-14 16:47:30 -0400633 if (frames.empty()) {
634 if (outReturn) {
635 memcpy(outReturn, sp - count + 1, count * sizeof(Value));
636 }
637 return;
638 } else {
639 // When we were called, 'stack' was positioned at the old top-of-stack (where
640 // our parameters were placed). So copy our return values to that same spot.
641 memmove(stack, sp - count + 1, count * sizeof(Value));
642
643 // Now move the stack pointer to the end of the just-pushed return values,
644 // and restore everything else.
645 const StackFrame& frame(frames.back());
646 sp = stack + count - 1;
647 stack = frame.fStack;
648 code = frame.fCode;
649 ip = frame.fIP;
650 frames.pop_back();
651 break;
Ethan Nicholas9764ebd2019-05-01 14:43:54 -0400652 }
Mike Kleine7007382019-05-21 08:36:32 -0500653 }
654
Brian Osman29e013d2019-05-28 17:16:03 -0400655 case ByteCodeInstruction::kScalarToMatrix: {
656 int cols = READ8();
657 int rows = READ8();
658 Value v = POP();
659 for (int c = 0; c < cols; ++c) {
660 for (int r = 0; r < rows; ++r) {
661 PUSH(c == r ? v : 0.0f);
662 }
663 }
664 break;
665 }
666
Mike Klein459aed12019-05-21 15:46:36 -0500667 VECTOR_UNARY_FN(kSin, sinf, fFloat)
668 VECTOR_UNARY_FN(kSqrt, sqrtf, fFloat)
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400669
Mike Kleine7007382019-05-21 08:36:32 -0500670 case ByteCodeInstruction::kStore4: stack[*ip + 3] = POP();
671 case ByteCodeInstruction::kStore3: stack[*ip + 2] = POP();
672 case ByteCodeInstruction::kStore2: stack[*ip + 1] = POP();
673 case ByteCodeInstruction::kStore : stack[*ip + 0] = POP();
674 ++ip;
675 break;
676
677 case ByteCodeInstruction::kStoreGlobal4: fGlobals[*ip + 3] = POP();
678 case ByteCodeInstruction::kStoreGlobal3: fGlobals[*ip + 2] = POP();
679 case ByteCodeInstruction::kStoreGlobal2: fGlobals[*ip + 1] = POP();
680 case ByteCodeInstruction::kStoreGlobal : fGlobals[*ip + 0] = POP();
681 ++ip;
682 break;
683
Brian Osman07c117b2019-05-23 12:51:06 -0700684 case ByteCodeInstruction::kStoreExtended: {
685 int count = READ8();
686 int target = POP().fSigned;
687 memcpy(&stack[target], sp - count + 1, count * sizeof(Value));
688 sp -= count;
689 break;
690 }
691 case ByteCodeInstruction::kStoreExtendedGlobal: {
692 int count = READ8();
693 int target = POP().fSigned;
694 SkASSERT(target + count <= (int) fGlobals.size());
695 memcpy(&fGlobals[target], sp - count + 1, count * sizeof(Value));
696 sp -= count;
697 break;
698 }
699
Mike Kleine7007382019-05-21 08:36:32 -0500700 case ByteCodeInstruction::kStoreSwizzle: {
701 int target = READ8();
702 int count = READ8();
Ethan Nicholas9764ebd2019-05-01 14:43:54 -0400703 for (int i = count - 1; i >= 0; --i) {
Ethan Nicholasdfcad062019-05-07 12:53:34 -0400704 stack[target + *(ip + i)] = POP();
Ethan Nicholas9764ebd2019-05-01 14:43:54 -0400705 }
Brian Osman1091f022019-05-16 09:42:16 -0400706 ip += count;
707 break;
Mike Kleine7007382019-05-21 08:36:32 -0500708 }
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400709
Mike Kleine7007382019-05-21 08:36:32 -0500710 case ByteCodeInstruction::kStoreSwizzleGlobal: {
711 int target = READ8();
712 int count = READ8();
Brian Osman1091f022019-05-16 09:42:16 -0400713 for (int i = count - 1; i >= 0; --i) {
714 fGlobals[target + *(ip + i)] = POP();
715 }
Ethan Nicholas7e603db2019-05-03 12:57:47 -0400716 ip += count;
Ethan Nicholas9764ebd2019-05-01 14:43:54 -0400717 break;
Mike Kleine7007382019-05-21 08:36:32 -0500718 }
Brian Osman07c117b2019-05-23 12:51:06 -0700719 case ByteCodeInstruction::kStoreSwizzleIndirect: {
720 int target = POP().fSigned;
721 int count = READ8();
722 for (int i = count - 1; i >= 0; --i) {
723 stack[target + *(ip + i)] = POP();
724 }
725 ip += count;
726 break;
727 }
728 case ByteCodeInstruction::kStoreSwizzleIndirectGlobal: {
729 int target = POP().fSigned;
730 int count = READ8();
731 for (int i = count - 1; i >= 0; --i) {
732 fGlobals[target + *(ip + i)] = POP();
733 }
734 ip += count;
735 break;
736 }
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400737
Mike Kleinc1999982019-05-21 13:03:49 -0500738 VECTOR_BINARY_OP(kSubtractI, fSigned, -)
739 VECTOR_BINARY_OP(kSubtractF, fFloat, -)
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400740
Mike Kleine7007382019-05-21 08:36:32 -0500741 case ByteCodeInstruction::kSwizzle: {
742 Value tmp[4];
Ethan Nicholas7e603db2019-05-03 12:57:47 -0400743 for (int i = READ8() - 1; i >= 0; --i) {
Ethan Nicholas48a75aa2019-05-16 17:15:56 -0400744 tmp[i] = POP();
Ethan Nicholas9764ebd2019-05-01 14:43:54 -0400745 }
Ethan Nicholas7e603db2019-05-03 12:57:47 -0400746 for (int i = READ8() - 1; i >= 0; --i) {
Ethan Nicholas48a75aa2019-05-16 17:15:56 -0400747 PUSH(tmp[READ8()]);
Ethan Nicholas9764ebd2019-05-01 14:43:54 -0400748 }
749 break;
Mike Kleine7007382019-05-21 08:36:32 -0500750 }
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400751
Mike Klein459aed12019-05-21 15:46:36 -0500752 VECTOR_UNARY_FN(kTan, tanf, fFloat)
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400753
Ethan Nicholas48a75aa2019-05-16 17:15:56 -0400754 case ByteCodeInstruction::kWriteExternal: // fall through
755 case ByteCodeInstruction::kWriteExternal2: // fall through
756 case ByteCodeInstruction::kWriteExternal3: // fall through
Mike Kleine7007382019-05-21 08:36:32 -0500757 case ByteCodeInstruction::kWriteExternal4: {
758 int count = (int) inst - (int) ByteCodeInstruction::kWriteExternal + 1;
759 int target = READ8();
Ethan Nicholas48a75aa2019-05-16 17:15:56 -0400760 fByteCode->fExternalValues[target]->write(sp - count + 1);
761 sp -= count;
Ethan Nicholas9764ebd2019-05-01 14:43:54 -0400762 break;
Mike Kleine7007382019-05-21 08:36:32 -0500763 }
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400764
Ethan Nicholas9764ebd2019-05-01 14:43:54 -0400765 default:
Mike Kleine7007382019-05-21 08:36:32 -0500766 SkDEBUGFAILF("unsupported instruction %d\n", (int) inst);
Ethan Nicholas9764ebd2019-05-01 14:43:54 -0400767 }
768#ifdef TRACE
Mike Kleine7007382019-05-21 08:36:32 -0500769 int stackSize = (int) (sp - stack + 1);
770 printf("STACK(%d):", stackSize);
Ethan Nicholas82162ee2019-05-21 16:05:08 -0400771 for (int i = 0; i < stackSize; ++i) {
Brian Osmane85b6a52019-05-22 14:50:59 -0700772 printf(" %d(%g)", stack[i].fSigned, stack[i].fFloat);
Ethan Nicholasdfcad062019-05-07 12:53:34 -0400773 }
774 printf("\n");
Ethan Nicholas9764ebd2019-05-01 14:43:54 -0400775#endif
Ethan Nicholas0e9401d2019-03-21 11:05:37 -0400776 }
Ethan Nicholas26a9aad2018-03-27 14:10:52 -0400777}
778
779} // namespace
780
781#endif