UNPKG

10.4 kBJavaScriptView Raw
1const {Expr, Token, TrackPath, Get, Expression} = require('./lang');
2const _ = require('lodash');
3const SimpleCompiler = require('./simple-compiler');
4const {searchExpressions} = require('./expr-search');
5const {exprHash} = require('./expr-hash');
6
7const enums = require('../bytecode/bytecode-enums');
8
9// const {flatbuffers} = require('flatbuffers');
10// const {CarmiBytecode} = require('../flatbuffers/bytecode_generated');
11// const {ValueType} = CarmiBytecode;
12
13const maxInlineNumber = 32767;
14const minInlineNumber = 0;
15
16function embeddedVal(type, val) {
17 if (typeof type !== 'number' || typeof val !== 'number' || type < 0 || type > enums.nonVerbs) {
18 throw new Error(`illegal value, ${type}, ${val}`);
19 }
20 return (val << 5) + type;
21}
22
23function canInlineNumber(val) {
24 return val >= minInlineNumber && val < maxInlineNumber;
25}
26
27function setToMap(src) {
28 const res = new Map();
29 src.forEach(val => res.set(val, res.size));
30 return res;
31}
32
33function str2ab_array(str) {
34 if (str.length % 2 === 1) {
35 str += ' ';
36 }
37 const buf = new ArrayBuffer(str.length * 2);
38 const bufView = new Uint16Array(buf);
39 for (let i = 0; i < str.length; i++) {
40 bufView[i] = str.charCodeAt(i);
41 }
42 return bufView;
43}
44
45function concatBuffers(...buffers) {
46 // buffers = [buffers[0]]
47 const offsetsSize = 4 * (buffers.length + 1);
48 const totalSize = _.sum(buffers.map(buf => buf.byteLength)) + offsetsSize;
49 // console.log('bufferSizes', buffers.map(buf => buf.byteLength));
50 const out = new Buffer(totalSize);
51
52 let offset = 4;
53 let totalLength = offsetsSize;
54 out.writeUInt32LE(offsetsSize, 0);
55 for (let i = 0; i < buffers.length; i++) {
56 totalLength += buffers[i].byteLength;
57 out.writeUInt32LE(totalLength, offset);
58 offset += 4;
59 }
60 buffers.forEach(buf => {
61 for (let i = 0; i < buf.length; i++) {
62 if (buf instanceof Uint32Array) {
63 out.writeUInt32LE(buf[i], offset);
64 offset += 4;
65 } else {
66 out.writeUInt16LE(buf[i], offset);
67 offset += 2;
68 }
69 }
70 });
71 return out;
72}
73
74class BytecodeCompiler extends SimpleCompiler {
75 constructor(model, options) {
76 options = {...options, disableHelperFunctions: true};
77 super(model, options);
78 this.realGetters = [];
79 this.exprsFromHash = {};
80 this.tagToHash = {};
81 this.exprsHashToIndex = new Map();
82 }
83 extractConsts() {
84 const stringsSet = new Set();
85 stringsSet.add(''); // the zero constant string is the empty string
86 const numbersSet = new Set();
87 const addConst = t => {
88 if (typeof t === 'string') {
89 stringsSet.add(t);
90 } else if (typeof t === 'number' && !canInlineNumber(t)) {
91 numbersSet.add(t);
92 }
93 };
94 Object.keys(this.getters).forEach(t => {
95 if (this.options.debug || t[0] !== '$') {
96 stringsSet.add(t);
97 }
98 });
99 Object.keys(this.setters).forEach(t => stringsSet.add(t));
100 Object.values(this.setters).forEach(setter => setter.forEach(addConst));
101 searchExpressions(e => {
102 if (e[0].$type === 'get' && e[2] instanceof Token && e[2].$type === 'topLevel') {
103 e[1] = this.topLevelToIndex(e[1]);
104 }
105 e.forEach(addConst);
106 }, Object.values(this.getters));
107 Object.values(this.setters).forEach(s => s.forEach(addConst));
108 this.stringsMap = setToMap(stringsSet);
109 this.numbersMap = setToMap(numbersSet);
110 this.stringsSet = stringsSet;
111 this.numbersSet = numbersSet;
112 }
113 convertToEmbeddedValue(val) {
114 if (typeof val === 'string') {
115 return embeddedVal(enums.$stringRef, this.stringsMap.get(val));
116 } else if (typeof val === 'number') {
117 return canInlineNumber(val) ?
118 embeddedVal(enums.$numberInline, val) :
119 embeddedVal(enums.$numberRef, this.numbersMap.get(val));
120 } else if (typeof val === 'boolean') {
121 return embeddedVal(enums.$booleanInline, val ? 1 : 0);
122 } else if (val instanceof Token) {
123 return embeddedVal(enums[`$${val.$type}`], 0);
124 } else if (val instanceof Expression) {
125 if (val[0].$type === 'cond') {
126 return embeddedVal(enums.$condRef, this.expressionsOffsets[this.expressionsHashToIndex[val[1]]]);
127 }
128 return embeddedVal(enums.$expressionRef, this.expressionsOffsets[this.expressionsHashToIndex[exprHash(val)]]);
129 }
130 }
131 rewriteCondsToHash(expr) {
132 if (!(expr instanceof Expression)) {
133 return expr;
134 }
135 if (expr[0].$type === 'cond') {
136 expr[1] = this.tagToHash.hasOwnProperty(expr[1]) ? this.tagToHash[expr[1]] : expr[1];
137 return expr;
138 }
139 return Expr(...expr.map(t => this.rewriteCondsToHash(t)));
140 }
141 addExpressionsToHashes(exprs) {
142 searchExpressions(e => {
143 if (!(e instanceof Expression) || e[0].$type === 'cond') {
144 return;
145 }
146 const hash = exprHash(e);
147 e[0].$hash = hash;
148 this.exprsFromHash[hash] = this.exprsFromHash[hash] || e;
149 if (e[0].hasOwnProperty('$id')) {
150 this.tagToHash[e[0].$id] = hash;
151 }
152 // console.log(hash, JSON.stringify(e));
153 }, exprs);
154 }
155 compile() {
156 Object.keys(this.getters).forEach(name => {
157 const index = this.topLevelToIndex(name);
158 if (typeof index === 'number') {
159 this.realGetters[index] = name;
160 }
161 });
162 const countTopLevels = this.realGetters.length;
163
164 this.addExpressionsToHashes(Object.values(this.getters));
165
166 this.extractConsts();
167
168 const trackedPaths = {root: true, context: true, topLevel: true};
169
170 searchExpressions(e => {
171 if (!(e instanceof Expression)) {
172 return;
173 }
174 if (!e[0].$path) {
175 return;
176 }
177 const trackParts = Array.from(e[0].$path.entries())
178 .filter(([path, cond]) =>
179 // console.log(trackedPaths[path[0].$type], path[0].$type, JSON.stringify(path))
180 trackedPaths[path[0].$type] || path.length > 1 && path[0] instanceof Expression && path[0][0].$type === 'get' && path[0][2].$type === 'topLevel'
181 )
182 .map(([path, cond]) => {
183 const pathAsExpr = path.slice(1).reduce((acc, t) => Expr(Get, t, acc), path[0]);
184 const pathHash = exprHash(pathAsExpr);
185 // console.log('path', pathHash, this.exprsFromHash.hasOwnProperty(pathHash), pathAsExpr);
186 // console.log('cond', cond);
187 return [this.rewriteCondsToHash(cond), pathAsExpr];
188 });
189 e[0].$path = Expr(TrackPath, ...[].concat(...trackParts));
190 // console.log(JSON.stringify(e[0].$path))
191 this.addExpressionsToHashes([e[0].$path]);
192 if (e[0].$type === 'func') {
193 e.push(e[0].$path);
194 }
195 }, Object.values(this.getters));
196
197 Object.keys(this.exprsFromHash).forEach(hash => {
198 this.exprsHashToIndex.set(hash, this.exprsHashToIndex.size);
199 });
200
201 // console.log(this.exprsHashToIndex.size, stringsSet.size, numbersSet.size, Object.keys(this.getters).length);
202 this.expressionsHashToIndex = {};
203 Object.keys(this.exprsFromHash).forEach((hash, index) => this.expressionsHashToIndex[hash] = index);
204
205 const stringsAndNumbers = JSON.stringify({
206 $strings: Array.from(this.stringsSet),
207 $numbers: Array.from(this.numbersSet)
208 });
209 const constsBuffer = str2ab_array(stringsAndNumbers);
210 const countOfTopLevels = Object.keys(this.getters).length;
211 const countOfExpressions = Object.keys(this.exprsFromHash).length;
212 const lengthOfAllExpressions = _.sum(Object.values(this.exprsFromHash).map(e => e.length));
213 const header = new Uint32Array(1);
214 header[0] = countOfTopLevels;
215 const topLevelNames = new Uint32Array(countOfTopLevels);
216 const topLevelExpressions = new Uint32Array(countOfTopLevels);
217 const topLevelTracking = new Uint32Array(countOfTopLevels);
218 // console.log(this.exprsHashToIndex);
219 let exprOffset = 0;
220 this.expressionsOffsets = new Uint32Array(countOfExpressions);
221 Object.keys(this.exprsFromHash).forEach((hash, index) => {
222 const e = this.exprsFromHash[hash];
223 this.expressionsOffsets[index] = exprOffset;
224 exprOffset += e.length;
225 });
226 _.range(countTopLevels).forEach(i => {
227 let name = '';
228 if (this.options.debug || this.realGetters[i][0] !== '$') {
229 name = this.realGetters[i];
230 }
231 topLevelNames[i] = this.stringsMap.get(name);
232 const expr = this.getters[this.realGetters[i]];
233 topLevelExpressions[i] = this.expressionsOffsets[this.exprsHashToIndex.get(exprHash(expr))];
234 // console.log(expr[0].$path, exprHash(expr[0].$path), this.exprsHashToIndex.get(exprHash(expr[0].$path)));
235 topLevelTracking[i] = this.expressionsOffsets[this.exprsHashToIndex.get(exprHash(expr[0].$path))];
236 });
237 const expressions = new Uint32Array(lengthOfAllExpressions);
238 // console.log({countOfExpressions, lengthOfAllExpressions, countTopLevels})
239
240 Object.keys(this.exprsFromHash).forEach((hash, index) => {
241 exprOffset = this.expressionsOffsets[index];
242 const e = this.exprsFromHash[hash];
243 const verb = enums[`$${e[0].$type}`] << 16;
244 expressions[exprOffset] = verb + e.length;
245 // console.log(e[0].$type, expressions[exprOffset], JSON.stringify(e));
246 e.slice(1)
247 .map(t => this.convertToEmbeddedValue(t))
248 .forEach((val, indexInExpr) => {
249 expressions[exprOffset + 1 + indexInExpr] = val;
250 });
251 });
252
253 const settersSize = _.sum(Object.keys(this.setters).map(key => this.setters[key].length + 2));
254 const settersBuffer = new Uint32Array(settersSize);
255 let settersOffset = 0;
256 Object.keys(this.setters).forEach(key => {
257 const setter = this.setters[key];
258 const type = enums[`$${setter.setterType()}`] << 16;
259 settersBuffer[settersOffset++] = type + setter.length;
260 settersBuffer[settersOffset++] = this.stringsMap.get(key);
261 setter
262 .map(v => this.convertToEmbeddedValue(v))
263 .forEach(val => {
264 settersBuffer[settersOffset++] = val;
265 });
266 });
267
268 // console.log({
269 // header,
270 // topLevelExpressions,
271 // topLevelNames,
272 // expressionsOffsets,
273 // expressions,
274 // constsBuffer,
275 // lengthOfAllExpressions
276 // });
277 const outputArray = concatBuffers(
278 header,
279 topLevelExpressions,
280 topLevelNames,
281 topLevelTracking,
282 expressions,
283 settersBuffer,
284 constsBuffer
285 );
286 return outputArray;
287 }
288}
289
290module.exports = BytecodeCompiler;