porffor 0.2.0-e562242 → 0.2.0-e69a2a2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +256 -0
- package/LICENSE +20 -20
- package/README.md +150 -89
- package/asur/README.md +2 -0
- package/asur/index.js +1262 -0
- package/byg/index.js +237 -0
- package/compiler/2c.js +317 -72
- package/compiler/{sections.js → assemble.js} +63 -15
- package/compiler/builtins/annexb_string.js +72 -0
- package/compiler/builtins/annexb_string.ts +18 -0
- package/compiler/builtins/array.ts +145 -0
- package/compiler/builtins/base64.ts +76 -0
- package/compiler/builtins/boolean.ts +6 -0
- package/compiler/builtins/crypto.ts +120 -0
- package/compiler/builtins/date.ts +2070 -0
- package/compiler/builtins/escape.ts +139 -0
- package/compiler/builtins/int.ts +147 -0
- package/compiler/builtins/number.ts +534 -0
- package/compiler/builtins/porffor.d.ts +59 -0
- package/compiler/builtins/string.ts +1070 -0
- package/compiler/builtins/tostring.ts +45 -0
- package/compiler/builtins.js +580 -272
- package/compiler/{codeGen.js → codegen.js} +1210 -446
- package/compiler/decompile.js +3 -4
- package/compiler/embedding.js +22 -22
- package/compiler/encoding.js +108 -10
- package/compiler/generated_builtins.js +1517 -0
- package/compiler/index.js +36 -34
- package/compiler/log.js +6 -3
- package/compiler/opt.js +51 -36
- package/compiler/parse.js +33 -23
- package/compiler/precompile.js +128 -0
- package/compiler/prefs.js +27 -0
- package/compiler/prototype.js +182 -42
- package/compiler/types.js +37 -0
- package/compiler/wasmSpec.js +30 -7
- package/compiler/wrap.js +141 -43
- package/package.json +9 -5
- package/porf +4 -0
- package/rhemyn/compile.js +46 -27
- package/rhemyn/parse.js +322 -320
- package/rhemyn/test/parse.js +58 -58
- package/runner/compare.js +34 -34
- package/runner/debug.js +122 -0
- package/runner/index.js +91 -11
- package/runner/profiler.js +102 -0
- package/runner/repl.js +42 -9
- package/runner/sizes.js +37 -37
- package/compiler/builtins/base64.js +0 -92
- package/node_trace.1.log +0 -1
- package/runner/info.js +0 -89
- package/runner/profile.js +0 -46
- package/runner/results.json +0 -1
- package/runner/transform.js +0 -15
- package/util/enum.js +0 -20
@@ -7,6 +7,8 @@ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enfor
|
|
7
7
|
import { log } from "./log.js";
|
8
8
|
import parse from "./parse.js";
|
9
9
|
import * as Rhemyn from "../rhemyn/compile.js";
|
10
|
+
import Prefs from './prefs.js';
|
11
|
+
import { TYPES, TYPE_NAMES } from './types.js';
|
10
12
|
|
11
13
|
let globals = {};
|
12
14
|
let globalInd = 0;
|
@@ -23,39 +25,45 @@ const debug = str => {
|
|
23
25
|
const logChar = n => {
|
24
26
|
code.push(...number(n));
|
25
27
|
|
26
|
-
code.push(Opcodes.call);
|
27
|
-
code.push(...unsignedLEB128(0));
|
28
|
+
code.push([ Opcodes.call, 0 ]);
|
28
29
|
};
|
29
30
|
|
30
31
|
for (let i = 0; i < str.length; i++) {
|
31
32
|
logChar(str.charCodeAt(i));
|
32
33
|
}
|
33
34
|
|
34
|
-
logChar(
|
35
|
+
logChar(10); // new line
|
35
36
|
|
36
37
|
return code;
|
37
38
|
};
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
this.name = 'TodoError';
|
44
|
-
}
|
40
|
+
class TodoError extends Error {
|
41
|
+
constructor(message) {
|
42
|
+
super(message);
|
43
|
+
this.name = 'TodoError';
|
45
44
|
}
|
45
|
+
}
|
46
|
+
const todo = (scope, msg, expectsValue = undefined) => {
|
47
|
+
switch (Prefs.todoTime ?? 'runtime') {
|
48
|
+
case 'compile':
|
49
|
+
throw new TodoError(msg);
|
46
50
|
|
47
|
-
|
48
|
-
|
49
|
-
const code = [];
|
50
|
-
|
51
|
-
code.push(...debug(`todo! ` + msg));
|
52
|
-
code.push(Opcodes.unreachable);
|
51
|
+
case 'runtime':
|
52
|
+
return internalThrow(scope, 'TodoError', msg, expectsValue);
|
53
53
|
|
54
|
-
|
54
|
+
// return [
|
55
|
+
// ...debug(`todo! ${msg}`),
|
56
|
+
// [ Opcodes.unreachable ]
|
57
|
+
// ];
|
58
|
+
}
|
55
59
|
};
|
56
60
|
|
57
61
|
const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
|
58
|
-
const
|
62
|
+
const hasFuncWithName = name => {
|
63
|
+
const func = funcs.find(x => x.name === name);
|
64
|
+
return !!(func || builtinFuncs[name] || importedFuncs[name] || internalConstrs[name]);
|
65
|
+
};
|
66
|
+
const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
|
59
67
|
switch (decl.type) {
|
60
68
|
case 'BinaryExpression':
|
61
69
|
return generateBinaryExp(scope, decl, global, name);
|
@@ -68,7 +76,12 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
68
76
|
|
69
77
|
case 'ArrowFunctionExpression':
|
70
78
|
case 'FunctionDeclaration':
|
71
|
-
generateFunc(scope, decl);
|
79
|
+
const func = generateFunc(scope, decl);
|
80
|
+
|
81
|
+
if (decl.type.endsWith('Expression')) {
|
82
|
+
return number(func.index);
|
83
|
+
}
|
84
|
+
|
72
85
|
return [];
|
73
86
|
|
74
87
|
case 'BlockStatement':
|
@@ -81,7 +94,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
81
94
|
return generateExp(scope, decl);
|
82
95
|
|
83
96
|
case 'CallExpression':
|
84
|
-
return generateCall(scope, decl, global, name);
|
97
|
+
return generateCall(scope, decl, global, name, valueUnused);
|
85
98
|
|
86
99
|
case 'NewExpression':
|
87
100
|
return generateNew(scope, decl, global, name);
|
@@ -99,7 +112,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
99
112
|
return generateUnary(scope, decl);
|
100
113
|
|
101
114
|
case 'UpdateExpression':
|
102
|
-
return generateUpdate(scope, decl);
|
115
|
+
return generateUpdate(scope, decl, global, name, valueUnused);
|
103
116
|
|
104
117
|
case 'IfStatement':
|
105
118
|
return generateIf(scope, decl);
|
@@ -110,6 +123,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
110
123
|
case 'WhileStatement':
|
111
124
|
return generateWhile(scope, decl);
|
112
125
|
|
126
|
+
case 'DoWhileStatement':
|
127
|
+
return generateDoWhile(scope, decl);
|
128
|
+
|
113
129
|
case 'ForOfStatement':
|
114
130
|
return generateForOf(scope, decl);
|
115
131
|
|
@@ -119,6 +135,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
119
135
|
case 'ContinueStatement':
|
120
136
|
return generateContinue(scope, decl);
|
121
137
|
|
138
|
+
case 'LabeledStatement':
|
139
|
+
return generateLabel(scope, decl);
|
140
|
+
|
122
141
|
case 'EmptyStatement':
|
123
142
|
return generateEmpty(scope, decl);
|
124
143
|
|
@@ -132,7 +151,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
132
151
|
return generateTry(scope, decl);
|
133
152
|
|
134
153
|
case 'DebuggerStatement':
|
135
|
-
// todo:
|
154
|
+
// todo: hook into terminal debugger
|
136
155
|
return [];
|
137
156
|
|
138
157
|
case 'ArrayExpression':
|
@@ -146,16 +165,22 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
146
165
|
const funcsBefore = funcs.length;
|
147
166
|
generate(scope, decl.declaration);
|
148
167
|
|
149
|
-
if (funcsBefore
|
168
|
+
if (funcsBefore !== funcs.length) {
|
169
|
+
// new func added
|
170
|
+
const newFunc = funcs[funcs.length - 1];
|
171
|
+
newFunc.export = true;
|
172
|
+
}
|
173
|
+
|
174
|
+
// if (funcsBefore === funcs.length) throw new Error('no new func added in export');
|
150
175
|
|
151
|
-
const newFunc = funcs[funcs.length - 1];
|
152
|
-
newFunc.export = true;
|
176
|
+
// const newFunc = funcs[funcs.length - 1];
|
177
|
+
// newFunc.export = true;
|
153
178
|
|
154
179
|
return [];
|
155
180
|
|
156
181
|
case 'TaggedTemplateExpression': {
|
157
182
|
const funcs = {
|
158
|
-
|
183
|
+
__Porffor_wasm: str => {
|
159
184
|
let out = [];
|
160
185
|
|
161
186
|
for (const line of str.split('\n')) {
|
@@ -163,8 +188,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
163
188
|
if (asm[0] === '') continue; // blank
|
164
189
|
|
165
190
|
if (asm[0] === 'local') {
|
166
|
-
const [ name,
|
167
|
-
scope.locals[name] = { idx:
|
191
|
+
const [ name, type ] = asm.slice(1);
|
192
|
+
scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
|
168
193
|
continue;
|
169
194
|
}
|
170
195
|
|
@@ -174,52 +199,74 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
174
199
|
}
|
175
200
|
|
176
201
|
if (asm[0] === 'memory') {
|
177
|
-
allocPage('asm instrinsic');
|
202
|
+
allocPage(scope, 'asm instrinsic');
|
178
203
|
// todo: add to store/load offset insts
|
179
204
|
continue;
|
180
205
|
}
|
181
206
|
|
182
207
|
let inst = Opcodes[asm[0].replace('.', '_')];
|
183
|
-
if (
|
208
|
+
if (inst == null) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
184
209
|
|
185
210
|
if (!Array.isArray(inst)) inst = [ inst ];
|
186
|
-
const immediates = asm.slice(1).map(x =>
|
211
|
+
const immediates = asm.slice(1).map(x => {
|
212
|
+
const int = parseInt(x);
|
213
|
+
if (Number.isNaN(int)) return scope.locals[x]?.idx;
|
214
|
+
return int;
|
215
|
+
});
|
187
216
|
|
188
|
-
out.push([ ...inst, ...immediates ]);
|
217
|
+
out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
|
189
218
|
}
|
190
219
|
|
191
220
|
return out;
|
192
221
|
},
|
193
222
|
|
194
|
-
|
195
|
-
|
223
|
+
__Porffor_bs: str => [
|
224
|
+
...makeString(scope, str, global, name, true),
|
196
225
|
|
197
|
-
|
198
|
-
...number(
|
199
|
-
|
226
|
+
...(name ? setType(scope, name, TYPES.bytestring) : [
|
227
|
+
...number(TYPES.bytestring, Valtype.i32),
|
228
|
+
...setLastType(scope)
|
229
|
+
])
|
230
|
+
],
|
231
|
+
__Porffor_s: str => [
|
232
|
+
...makeString(scope, str, global, name, false),
|
200
233
|
|
201
|
-
|
202
|
-
...number(
|
203
|
-
|
204
|
-
]
|
205
|
-
|
206
|
-
}
|
234
|
+
...(name ? setType(scope, name, TYPES.string) : [
|
235
|
+
...number(TYPES.string, Valtype.i32),
|
236
|
+
...setLastType(scope)
|
237
|
+
])
|
238
|
+
],
|
239
|
+
};
|
207
240
|
|
208
|
-
const
|
241
|
+
const func = decl.tag.name;
|
209
242
|
// hack for inline asm
|
210
|
-
if (!funcs[
|
243
|
+
if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
|
244
|
+
|
245
|
+
const { quasis, expressions } = decl.quasi;
|
246
|
+
let str = quasis[0].value.raw;
|
211
247
|
|
212
|
-
|
213
|
-
|
248
|
+
for (let i = 0; i < expressions.length; i++) {
|
249
|
+
const e = expressions[i];
|
250
|
+
if (!e.name) {
|
251
|
+
if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
|
252
|
+
str += lookupName(scope, e.left.name)[0].idx + e.right.value;
|
253
|
+
} else todo(scope, 'unsupported expression in intrinsic');
|
254
|
+
} else str += lookupName(scope, e.name)[0].idx;
|
255
|
+
|
256
|
+
str += quasis[i + 1].value.raw;
|
257
|
+
}
|
258
|
+
|
259
|
+
return funcs[func](str);
|
214
260
|
}
|
215
261
|
|
216
262
|
default:
|
217
|
-
|
218
|
-
|
263
|
+
// ignore typescript nodes
|
264
|
+
if (decl.type.startsWith('TS') ||
|
265
|
+
decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
|
219
266
|
return [];
|
220
267
|
}
|
221
268
|
|
222
|
-
return todo(`no generation for ${decl.type}!`);
|
269
|
+
return todo(scope, `no generation for ${decl.type}!`);
|
223
270
|
}
|
224
271
|
};
|
225
272
|
|
@@ -247,7 +294,7 @@ const lookupName = (scope, _name) => {
|
|
247
294
|
return [ undefined, undefined ];
|
248
295
|
};
|
249
296
|
|
250
|
-
const internalThrow = (scope, constructor, message, expectsValue =
|
297
|
+
const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
|
251
298
|
...generateThrow(scope, {
|
252
299
|
argument: {
|
253
300
|
type: 'NewExpression',
|
@@ -269,25 +316,33 @@ const generateIdent = (scope, decl) => {
|
|
269
316
|
const name = mapName(rawName);
|
270
317
|
let local = scope.locals[rawName];
|
271
318
|
|
272
|
-
if (builtinVars
|
319
|
+
if (Object.hasOwn(builtinVars, name)) {
|
273
320
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
274
|
-
|
321
|
+
|
322
|
+
let wasm = builtinVars[name];
|
323
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
|
324
|
+
return wasm.slice();
|
325
|
+
}
|
326
|
+
|
327
|
+
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
328
|
+
// todo: return an actual something
|
329
|
+
return number(1);
|
275
330
|
}
|
276
331
|
|
277
|
-
if (
|
332
|
+
if (isExistingProtoFunc(name)) {
|
278
333
|
// todo: return an actual something
|
279
334
|
return number(1);
|
280
335
|
}
|
281
336
|
|
282
|
-
if (local === undefined) {
|
337
|
+
if (local?.idx === undefined) {
|
283
338
|
// no local var with name
|
284
|
-
if (
|
285
|
-
if (funcIndex
|
339
|
+
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
340
|
+
if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
|
286
341
|
|
287
|
-
if (globals
|
342
|
+
if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
|
288
343
|
}
|
289
344
|
|
290
|
-
if (local === undefined && rawName.startsWith('__')) {
|
345
|
+
if (local?.idx === undefined && rawName.startsWith('__')) {
|
291
346
|
// return undefined if unknown key in already known var
|
292
347
|
let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
|
293
348
|
if (parent.includes('_')) parent = '__' + parent;
|
@@ -296,7 +351,7 @@ const generateIdent = (scope, decl) => {
|
|
296
351
|
if (!parentLookup[1]) return number(UNDEFINED);
|
297
352
|
}
|
298
353
|
|
299
|
-
if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
354
|
+
if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
300
355
|
|
301
356
|
return [ [ Opcodes.local_get, local.idx ] ];
|
302
357
|
};
|
@@ -309,14 +364,18 @@ const generateReturn = (scope, decl) => {
|
|
309
364
|
// just bare "return"
|
310
365
|
return [
|
311
366
|
...number(UNDEFINED), // "undefined" if func returns
|
312
|
-
...
|
367
|
+
...(scope.returnType != null ? [] : [
|
368
|
+
...number(TYPES.undefined, Valtype.i32) // type undefined
|
369
|
+
]),
|
313
370
|
[ Opcodes.return ]
|
314
371
|
];
|
315
372
|
}
|
316
373
|
|
317
374
|
return [
|
318
375
|
...generate(scope, decl.argument),
|
319
|
-
...
|
376
|
+
...(scope.returnType != null ? [] : [
|
377
|
+
...getNodeType(scope, decl.argument)
|
378
|
+
]),
|
320
379
|
[ Opcodes.return ]
|
321
380
|
];
|
322
381
|
};
|
@@ -330,7 +389,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
330
389
|
return idx;
|
331
390
|
};
|
332
391
|
|
333
|
-
const isIntOp = op => op && (op[0] >=
|
392
|
+
const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
|
393
|
+
const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
|
334
394
|
|
335
395
|
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
336
396
|
const checks = {
|
@@ -339,7 +399,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
339
399
|
'??': nullish
|
340
400
|
};
|
341
401
|
|
342
|
-
if (!checks[op]) return todo(`logic operator ${op} not implemented yet
|
402
|
+
if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
|
343
403
|
|
344
404
|
// generic structure for {a} OP {b}
|
345
405
|
// -->
|
@@ -347,8 +407,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
347
407
|
|
348
408
|
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
349
409
|
// (like if we are in an if condition - very common)
|
350
|
-
const leftIsInt =
|
351
|
-
const rightIsInt =
|
410
|
+
const leftIsInt = isFloatToIntOp(left[left.length - 1]);
|
411
|
+
const rightIsInt = isFloatToIntOp(right[right.length - 1]);
|
352
412
|
|
353
413
|
const canInt = leftIsInt && rightIsInt;
|
354
414
|
|
@@ -365,12 +425,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
365
425
|
...right,
|
366
426
|
// note type
|
367
427
|
...rightType,
|
368
|
-
setLastType(scope),
|
428
|
+
...setLastType(scope),
|
369
429
|
[ Opcodes.else ],
|
370
430
|
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
371
431
|
// note type
|
372
432
|
...leftType,
|
373
|
-
setLastType(scope),
|
433
|
+
...setLastType(scope),
|
374
434
|
[ Opcodes.end ],
|
375
435
|
Opcodes.i32_from
|
376
436
|
];
|
@@ -384,17 +444,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
384
444
|
...right,
|
385
445
|
// note type
|
386
446
|
...rightType,
|
387
|
-
setLastType(scope),
|
447
|
+
...setLastType(scope),
|
388
448
|
[ Opcodes.else ],
|
389
449
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
390
450
|
// note type
|
391
451
|
...leftType,
|
392
|
-
setLastType(scope),
|
452
|
+
...setLastType(scope),
|
393
453
|
[ Opcodes.end ]
|
394
454
|
];
|
395
455
|
};
|
396
456
|
|
397
|
-
const concatStrings = (scope, left, right, global, name, assign) => {
|
457
|
+
const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
|
398
458
|
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
399
459
|
// todo: convert left and right to strings if not
|
400
460
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -404,11 +464,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
404
464
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
405
465
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
406
466
|
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
if (assign) {
|
411
|
-
const pointer = arrays.get(name ?? '$undeclared');
|
467
|
+
if (assign && Prefs.aotPointerOpt) {
|
468
|
+
const pointer = scope.arrays?.get(name ?? '$undeclared');
|
412
469
|
|
413
470
|
return [
|
414
471
|
// setup right
|
@@ -433,11 +490,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
433
490
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
|
434
491
|
|
435
492
|
// copy right
|
436
|
-
// dst = out pointer + length size + current length *
|
493
|
+
// dst = out pointer + length size + current length * sizeof valtype
|
437
494
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
438
495
|
|
439
496
|
[ Opcodes.local_get, leftLength ],
|
440
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
497
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
441
498
|
[ Opcodes.i32_mul ],
|
442
499
|
[ Opcodes.i32_add ],
|
443
500
|
|
@@ -446,9 +503,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
446
503
|
...number(ValtypeSize.i32, Valtype.i32),
|
447
504
|
[ Opcodes.i32_add ],
|
448
505
|
|
449
|
-
// size = right length *
|
506
|
+
// size = right length * sizeof valtype
|
450
507
|
[ Opcodes.local_get, rightLength ],
|
451
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
508
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
452
509
|
[ Opcodes.i32_mul ],
|
453
510
|
|
454
511
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -506,11 +563,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
506
563
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
507
564
|
|
508
565
|
// copy right
|
509
|
-
// dst = out pointer + length size + left length *
|
566
|
+
// dst = out pointer + length size + left length * sizeof valtype
|
510
567
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
511
568
|
|
512
569
|
[ Opcodes.local_get, leftLength ],
|
513
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
570
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
514
571
|
[ Opcodes.i32_mul ],
|
515
572
|
[ Opcodes.i32_add ],
|
516
573
|
|
@@ -519,9 +576,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
519
576
|
...number(ValtypeSize.i32, Valtype.i32),
|
520
577
|
[ Opcodes.i32_add ],
|
521
578
|
|
522
|
-
// size = right length *
|
579
|
+
// size = right length * sizeof valtype
|
523
580
|
[ Opcodes.local_get, rightLength ],
|
524
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
581
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
525
582
|
[ Opcodes.i32_mul ],
|
526
583
|
|
527
584
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -531,7 +588,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
531
588
|
];
|
532
589
|
};
|
533
590
|
|
534
|
-
const compareStrings = (scope, left, right) => {
|
591
|
+
const compareStrings = (scope, left, right, bytestrings = false) => {
|
535
592
|
// todo: this should be rewritten into a func
|
536
593
|
// todo: convert left and right to strings if not
|
537
594
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -540,7 +597,6 @@ const compareStrings = (scope, left, right) => {
|
|
540
597
|
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
541
598
|
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
542
599
|
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
543
|
-
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
544
600
|
|
545
601
|
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
546
602
|
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
@@ -568,7 +624,6 @@ const compareStrings = (scope, left, right) => {
|
|
568
624
|
|
569
625
|
[ Opcodes.local_get, rightPointer ],
|
570
626
|
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
571
|
-
[ Opcodes.local_tee, rightLength ],
|
572
627
|
|
573
628
|
// fast path: check leftLength != rightLength
|
574
629
|
[ Opcodes.i32_ne ],
|
@@ -583,11 +638,13 @@ const compareStrings = (scope, left, right) => {
|
|
583
638
|
...number(0, Valtype.i32),
|
584
639
|
[ Opcodes.local_set, index ],
|
585
640
|
|
586
|
-
// setup index end as length * sizeof
|
641
|
+
// setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
|
587
642
|
// we do this instead of having to do mul/div each iter for perf™
|
588
643
|
[ Opcodes.local_get, leftLength ],
|
589
|
-
...
|
590
|
-
|
644
|
+
...(bytestrings ? [] : [
|
645
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
646
|
+
[ Opcodes.i32_mul ],
|
647
|
+
]),
|
591
648
|
[ Opcodes.local_set, indexEnd ],
|
592
649
|
|
593
650
|
// iterate over each char and check if eq
|
@@ -597,13 +654,17 @@ const compareStrings = (scope, left, right) => {
|
|
597
654
|
[ Opcodes.local_get, index ],
|
598
655
|
[ Opcodes.local_get, leftPointer ],
|
599
656
|
[ Opcodes.i32_add ],
|
600
|
-
|
657
|
+
bytestrings ?
|
658
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
659
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
601
660
|
|
602
661
|
// fetch right
|
603
662
|
[ Opcodes.local_get, index ],
|
604
663
|
[ Opcodes.local_get, rightPointer ],
|
605
664
|
[ Opcodes.i32_add ],
|
606
|
-
|
665
|
+
bytestrings ?
|
666
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
667
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
607
668
|
|
608
669
|
// not equal, "return" false
|
609
670
|
[ Opcodes.i32_ne ],
|
@@ -612,13 +673,13 @@ const compareStrings = (scope, left, right) => {
|
|
612
673
|
[ Opcodes.br, 2 ],
|
613
674
|
[ Opcodes.end ],
|
614
675
|
|
615
|
-
// index += sizeof
|
676
|
+
// index += sizeof valtype (1 for bytestring, 2 for string)
|
616
677
|
[ Opcodes.local_get, index ],
|
617
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
678
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
618
679
|
[ Opcodes.i32_add ],
|
619
680
|
[ Opcodes.local_tee, index ],
|
620
681
|
|
621
|
-
// if index != index end (length * sizeof
|
682
|
+
// if index != index end (length * sizeof valtype), loop
|
622
683
|
[ Opcodes.local_get, indexEnd ],
|
623
684
|
[ Opcodes.i32_ne ],
|
624
685
|
[ Opcodes.br_if, 0 ],
|
@@ -639,16 +700,18 @@ const compareStrings = (scope, left, right) => {
|
|
639
700
|
};
|
640
701
|
|
641
702
|
const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
642
|
-
if (
|
703
|
+
if (isFloatToIntOp(wasm[wasm.length - 1])) return [
|
643
704
|
...wasm,
|
644
705
|
...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
|
645
706
|
];
|
707
|
+
// if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
|
646
708
|
|
647
|
-
const
|
709
|
+
const useTmp = knownType(scope, type) == null;
|
710
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
648
711
|
|
649
712
|
const def = [
|
650
713
|
// if value != 0
|
651
|
-
[ Opcodes.local_get, tmp ],
|
714
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
652
715
|
|
653
716
|
// ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
654
717
|
...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
|
@@ -660,16 +723,16 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
660
723
|
|
661
724
|
return [
|
662
725
|
...wasm,
|
663
|
-
[ Opcodes.local_set, tmp ],
|
726
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
664
727
|
|
665
728
|
...typeSwitch(scope, type, {
|
666
729
|
// [TYPES.number]: def,
|
667
|
-
[TYPES.
|
730
|
+
[TYPES.array]: [
|
668
731
|
// arrays are always truthy
|
669
732
|
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
670
733
|
],
|
671
734
|
[TYPES.string]: [
|
672
|
-
[ Opcodes.local_get, tmp ],
|
735
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
673
736
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
674
737
|
|
675
738
|
// get length
|
@@ -680,24 +743,46 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
680
743
|
[ Opcodes.i32_eqz ], */
|
681
744
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
682
745
|
],
|
746
|
+
[TYPES.bytestring]: [ // duplicate of string
|
747
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
748
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
749
|
+
|
750
|
+
// get length
|
751
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
752
|
+
|
753
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
754
|
+
],
|
683
755
|
default: def
|
684
756
|
}, intOut ? Valtype.i32 : valtypeBinary)
|
685
757
|
];
|
686
758
|
};
|
687
759
|
|
688
760
|
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
689
|
-
const
|
761
|
+
const useTmp = knownType(scope, type) == null;
|
762
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
763
|
+
|
690
764
|
return [
|
691
765
|
...wasm,
|
692
|
-
[ Opcodes.local_set, tmp ],
|
766
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
693
767
|
|
694
768
|
...typeSwitch(scope, type, {
|
695
|
-
[TYPES.
|
769
|
+
[TYPES.array]: [
|
696
770
|
// arrays are always truthy
|
697
771
|
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
698
772
|
],
|
699
773
|
[TYPES.string]: [
|
700
|
-
[ Opcodes.local_get, tmp ],
|
774
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
775
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
776
|
+
|
777
|
+
// get length
|
778
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
779
|
+
|
780
|
+
// if length == 0
|
781
|
+
[ Opcodes.i32_eqz ],
|
782
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
783
|
+
],
|
784
|
+
[TYPES.bytestring]: [ // duplicate of string
|
785
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
701
786
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
702
787
|
|
703
788
|
// get length
|
@@ -709,7 +794,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
709
794
|
],
|
710
795
|
default: [
|
711
796
|
// if value == 0
|
712
|
-
[ Opcodes.local_get, tmp ],
|
797
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
713
798
|
|
714
799
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
715
800
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -719,10 +804,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
719
804
|
};
|
720
805
|
|
721
806
|
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
722
|
-
const
|
807
|
+
const useTmp = knownType(scope, type) == null;
|
808
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
809
|
+
|
723
810
|
return [
|
724
811
|
...wasm,
|
725
|
-
[ Opcodes.local_set, tmp ],
|
812
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
726
813
|
|
727
814
|
...typeSwitch(scope, type, {
|
728
815
|
[TYPES.undefined]: [
|
@@ -731,7 +818,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
731
818
|
],
|
732
819
|
[TYPES.object]: [
|
733
820
|
// object, null if == 0
|
734
|
-
[ Opcodes.local_get, tmp ],
|
821
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
735
822
|
|
736
823
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
737
824
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -760,11 +847,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
760
847
|
return performLogicOp(scope, op, left, right, leftType, rightType);
|
761
848
|
}
|
762
849
|
|
850
|
+
const knownLeft = knownType(scope, leftType);
|
851
|
+
const knownRight = knownType(scope, rightType);
|
852
|
+
|
763
853
|
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
764
854
|
const strictOp = op === '===' || op === '!==';
|
765
855
|
|
766
856
|
const startOut = [], endOut = [];
|
767
|
-
const
|
857
|
+
const finalize = out => startOut.concat(out, endOut);
|
768
858
|
|
769
859
|
// if strict (in)equal check types match
|
770
860
|
if (strictOp) {
|
@@ -809,31 +899,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
809
899
|
// todo: if equality op and an operand is undefined, return false
|
810
900
|
// todo: niche null hell with 0
|
811
901
|
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
902
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) {
|
903
|
+
if (op === '+') {
|
904
|
+
// todo: this should be dynamic too but for now only static
|
905
|
+
// string concat (a + b)
|
906
|
+
return concatStrings(scope, left, right, _global, _name, assign, false);
|
907
|
+
}
|
908
|
+
|
909
|
+
// not an equality op, NaN
|
910
|
+
if (!eqOp) return number(NaN);
|
911
|
+
|
912
|
+
// else leave bool ops
|
913
|
+
// todo: convert string to number if string and number/bool
|
914
|
+
// todo: string (>|>=|<|<=) string
|
915
|
+
|
916
|
+
// string comparison
|
917
|
+
if (op === '===' || op === '==') {
|
918
|
+
return compareStrings(scope, left, right);
|
919
|
+
}
|
920
|
+
|
921
|
+
if (op === '!==' || op === '!=') {
|
922
|
+
return [
|
923
|
+
...compareStrings(scope, left, right),
|
924
|
+
[ Opcodes.i32_eqz ]
|
925
|
+
];
|
926
|
+
}
|
927
|
+
}
|
928
|
+
|
929
|
+
if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) {
|
930
|
+
if (op === '+') {
|
931
|
+
// todo: this should be dynamic too but for now only static
|
932
|
+
// string concat (a + b)
|
933
|
+
return concatStrings(scope, left, right, _global, _name, assign, true);
|
934
|
+
}
|
935
|
+
|
936
|
+
// not an equality op, NaN
|
937
|
+
if (!eqOp) return number(NaN);
|
938
|
+
|
939
|
+
// else leave bool ops
|
940
|
+
// todo: convert string to number if string and number/bool
|
941
|
+
// todo: string (>|>=|<|<=) string
|
942
|
+
|
943
|
+
// string comparison
|
944
|
+
if (op === '===' || op === '==') {
|
945
|
+
return compareStrings(scope, left, right, true);
|
946
|
+
}
|
947
|
+
|
948
|
+
if (op === '!==' || op === '!=') {
|
949
|
+
return [
|
950
|
+
...compareStrings(scope, left, right, true),
|
951
|
+
[ Opcodes.i32_eqz ]
|
952
|
+
];
|
953
|
+
}
|
954
|
+
}
|
837
955
|
|
838
956
|
let ops = operatorOpcode[valtype][op];
|
839
957
|
|
@@ -843,33 +961,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
843
961
|
includeBuiltin(scope, builtinName);
|
844
962
|
const idx = funcIndex[builtinName];
|
845
963
|
|
846
|
-
return
|
964
|
+
return finalize([
|
847
965
|
...left,
|
848
966
|
...right,
|
849
967
|
[ Opcodes.call, idx ]
|
850
968
|
]);
|
851
969
|
}
|
852
970
|
|
853
|
-
if (!ops) return todo(`operator ${op} not implemented yet
|
971
|
+
if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
|
854
972
|
|
855
973
|
if (!Array.isArray(ops)) ops = [ ops ];
|
856
974
|
ops = [ ops ];
|
857
975
|
|
858
976
|
let tmpLeft, tmpRight;
|
859
977
|
// if equal op, check if strings for compareStrings
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
// todo: intelligent partial skip later
|
865
|
-
// if neither known are string, stop this madness
|
866
|
-
if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
|
867
|
-
return;
|
868
|
-
}
|
978
|
+
// todo: intelligent partial skip later
|
979
|
+
// if neither known are string, stop this madness
|
980
|
+
// we already do known checks earlier, so don't need to recheck
|
869
981
|
|
982
|
+
if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
|
870
983
|
tmpLeft = localTmp(scope, '__tmpop_left');
|
871
984
|
tmpRight = localTmp(scope, '__tmpop_right');
|
872
985
|
|
986
|
+
// returns false for one string, one not - but more ops/slower
|
987
|
+
// ops.unshift(...stringOnly([
|
988
|
+
// // if left is string
|
989
|
+
// ...leftType,
|
990
|
+
// ...number(TYPES.string, Valtype.i32),
|
991
|
+
// [ Opcodes.i32_eq ],
|
992
|
+
|
993
|
+
// // if right is string
|
994
|
+
// ...rightType,
|
995
|
+
// ...number(TYPES.string, Valtype.i32),
|
996
|
+
// [ Opcodes.i32_eq ],
|
997
|
+
|
998
|
+
// // if either are true
|
999
|
+
// [ Opcodes.i32_or ],
|
1000
|
+
// [ Opcodes.if, Blocktype.void ],
|
1001
|
+
|
1002
|
+
// // todo: convert non-strings to strings, for now fail immediately if one is not
|
1003
|
+
// // if left is not string
|
1004
|
+
// ...leftType,
|
1005
|
+
// ...number(TYPES.string, Valtype.i32),
|
1006
|
+
// [ Opcodes.i32_ne ],
|
1007
|
+
|
1008
|
+
// // if right is not string
|
1009
|
+
// ...rightType,
|
1010
|
+
// ...number(TYPES.string, Valtype.i32),
|
1011
|
+
// [ Opcodes.i32_ne ],
|
1012
|
+
|
1013
|
+
// // if either are true
|
1014
|
+
// [ Opcodes.i32_or ],
|
1015
|
+
// [ Opcodes.if, Blocktype.void ],
|
1016
|
+
// ...number(0, Valtype.i32),
|
1017
|
+
// [ Opcodes.br, 2 ],
|
1018
|
+
// [ Opcodes.end ],
|
1019
|
+
|
1020
|
+
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1021
|
+
// ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
1022
|
+
// [ Opcodes.br, 1 ],
|
1023
|
+
// [ Opcodes.end ],
|
1024
|
+
// ]));
|
1025
|
+
|
1026
|
+
// does not handle one string, one not (such cases go past)
|
873
1027
|
ops.unshift(...stringOnly([
|
874
1028
|
// if left is string
|
875
1029
|
...leftType,
|
@@ -881,30 +1035,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
881
1035
|
...number(TYPES.string, Valtype.i32),
|
882
1036
|
[ Opcodes.i32_eq ],
|
883
1037
|
|
884
|
-
// if
|
885
|
-
[ Opcodes.
|
1038
|
+
// if both are true
|
1039
|
+
[ Opcodes.i32_and ],
|
886
1040
|
[ Opcodes.if, Blocktype.void ],
|
1041
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1042
|
+
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
1043
|
+
[ Opcodes.br, 1 ],
|
1044
|
+
[ Opcodes.end ],
|
887
1045
|
|
888
|
-
//
|
889
|
-
// if left is not string
|
1046
|
+
// if left is bytestring
|
890
1047
|
...leftType,
|
891
|
-
...number(TYPES.
|
892
|
-
[ Opcodes.
|
1048
|
+
...number(TYPES.bytestring, Valtype.i32),
|
1049
|
+
[ Opcodes.i32_eq ],
|
893
1050
|
|
894
|
-
// if right is
|
1051
|
+
// if right is bytestring
|
895
1052
|
...rightType,
|
896
|
-
...number(TYPES.
|
897
|
-
[ Opcodes.
|
1053
|
+
...number(TYPES.bytestring, Valtype.i32),
|
1054
|
+
[ Opcodes.i32_eq ],
|
898
1055
|
|
899
|
-
// if
|
900
|
-
[ Opcodes.
|
1056
|
+
// if both are true
|
1057
|
+
[ Opcodes.i32_and ],
|
901
1058
|
[ Opcodes.if, Blocktype.void ],
|
902
|
-
...
|
903
|
-
[ Opcodes.br, 1 ],
|
904
|
-
[ Opcodes.end ],
|
905
|
-
|
906
|
-
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
907
|
-
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1059
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
|
908
1060
|
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
909
1061
|
[ Opcodes.br, 1 ],
|
910
1062
|
[ Opcodes.end ],
|
@@ -916,9 +1068,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
916
1068
|
// endOut.push(stringOnly([ Opcodes.end ]));
|
917
1069
|
endOut.unshift(stringOnly([ Opcodes.end ]));
|
918
1070
|
// }
|
919
|
-
}
|
1071
|
+
}
|
920
1072
|
|
921
|
-
return
|
1073
|
+
return finalize([
|
922
1074
|
...left,
|
923
1075
|
...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
|
924
1076
|
...right,
|
@@ -935,7 +1087,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
935
1087
|
return out;
|
936
1088
|
};
|
937
1089
|
|
938
|
-
const
|
1090
|
+
const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
|
1091
|
+
return func({ name, params, locals, returns, localInd }, {
|
1092
|
+
TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
|
1093
|
+
builtin: name => {
|
1094
|
+
let idx = funcIndex[name] ?? importedFuncs[name];
|
1095
|
+
if (idx === undefined && builtinFuncs[name]) {
|
1096
|
+
includeBuiltin(null, name);
|
1097
|
+
idx = funcIndex[name];
|
1098
|
+
}
|
1099
|
+
|
1100
|
+
return idx;
|
1101
|
+
}
|
1102
|
+
});
|
1103
|
+
};
|
1104
|
+
|
1105
|
+
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
|
939
1106
|
const existing = funcs.find(x => x.name === name);
|
940
1107
|
if (existing) return existing;
|
941
1108
|
|
@@ -947,6 +1114,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
947
1114
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
948
1115
|
}
|
949
1116
|
|
1117
|
+
for (const x of _data) {
|
1118
|
+
const copy = { ...x };
|
1119
|
+
copy.offset += pages.size * pageSize;
|
1120
|
+
data.push(copy);
|
1121
|
+
}
|
1122
|
+
|
1123
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
|
1124
|
+
|
950
1125
|
let baseGlobalIdx, i = 0;
|
951
1126
|
for (const type of globalTypes) {
|
952
1127
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -969,7 +1144,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
969
1144
|
params,
|
970
1145
|
locals,
|
971
1146
|
returns,
|
972
|
-
returnType:
|
1147
|
+
returnType: returnType ?? TYPES.number,
|
973
1148
|
wasm,
|
974
1149
|
internal: true,
|
975
1150
|
index: currentFuncIndex++
|
@@ -992,6 +1167,7 @@ const generateLogicExp = (scope, decl) => {
|
|
992
1167
|
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
993
1168
|
};
|
994
1169
|
|
1170
|
+
// potential future ideas for nan boxing (unused):
|
995
1171
|
// T = JS type, V = value/pointer
|
996
1172
|
// 0bTTT
|
997
1173
|
// qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
|
@@ -1005,7 +1181,6 @@ const generateLogicExp = (scope, decl) => {
|
|
1005
1181
|
// js type: 4 bits
|
1006
1182
|
// internal type: ? bits
|
1007
1183
|
// pointer: 32 bits
|
1008
|
-
|
1009
1184
|
// generic
|
1010
1185
|
// 1 23 4 5
|
1011
1186
|
// 0 11111111111 11TTTTIIII??????????PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
|
@@ -1015,47 +1190,29 @@ const generateLogicExp = (scope, decl) => {
|
|
1015
1190
|
// 4: internal type
|
1016
1191
|
// 5: pointer
|
1017
1192
|
|
1018
|
-
const
|
1019
|
-
|
1020
|
-
|
1021
|
-
string: 0x02,
|
1022
|
-
undefined: 0x03,
|
1023
|
-
object: 0x04,
|
1024
|
-
function: 0x05,
|
1025
|
-
symbol: 0x06,
|
1026
|
-
bigint: 0x07,
|
1193
|
+
const isExistingProtoFunc = name => {
|
1194
|
+
if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES.array][name.slice(18)];
|
1195
|
+
if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
|
1027
1196
|
|
1028
|
-
|
1029
|
-
_array: 0x10,
|
1030
|
-
_regexp: 0x11
|
1031
|
-
};
|
1032
|
-
|
1033
|
-
const TYPE_NAMES = {
|
1034
|
-
[TYPES.number]: 'Number',
|
1035
|
-
[TYPES.boolean]: 'Boolean',
|
1036
|
-
[TYPES.string]: 'String',
|
1037
|
-
[TYPES.undefined]: 'undefined',
|
1038
|
-
[TYPES.object]: 'Object',
|
1039
|
-
[TYPES.function]: 'Function',
|
1040
|
-
[TYPES.symbol]: 'Symbol',
|
1041
|
-
[TYPES.bigint]: 'BigInt',
|
1042
|
-
|
1043
|
-
[TYPES._array]: 'Array',
|
1044
|
-
[TYPES._regexp]: 'RegExp'
|
1197
|
+
return false;
|
1045
1198
|
};
|
1046
1199
|
|
1047
1200
|
const getType = (scope, _name) => {
|
1048
1201
|
const name = mapName(_name);
|
1049
1202
|
|
1203
|
+
// if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
|
1204
|
+
|
1205
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
|
1050
1206
|
if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
|
1207
|
+
|
1208
|
+
if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
|
1051
1209
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1052
1210
|
|
1053
1211
|
let type = TYPES.undefined;
|
1054
|
-
if (builtinVars[name]) type =
|
1212
|
+
if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
|
1055
1213
|
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
|
1056
1214
|
|
1057
|
-
if (name
|
1058
|
-
name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
|
1215
|
+
if (isExistingProtoFunc(name)) type = TYPES.function;
|
1059
1216
|
|
1060
1217
|
return number(type, Valtype.i32);
|
1061
1218
|
};
|
@@ -1078,21 +1235,24 @@ const setType = (scope, _name, type) => {
|
|
1078
1235
|
];
|
1079
1236
|
|
1080
1237
|
// throw new Error('could not find var');
|
1238
|
+
return [];
|
1081
1239
|
};
|
1082
1240
|
|
1083
1241
|
const getLastType = scope => {
|
1084
1242
|
scope.gotLastType = true;
|
1085
|
-
return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
|
1243
|
+
return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1086
1244
|
};
|
1087
1245
|
|
1088
1246
|
const setLastType = scope => {
|
1089
|
-
return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
|
1247
|
+
return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1090
1248
|
};
|
1091
1249
|
|
1092
1250
|
const getNodeType = (scope, node) => {
|
1093
1251
|
const inner = () => {
|
1094
1252
|
if (node.type === 'Literal') {
|
1095
|
-
if (node.regex) return TYPES.
|
1253
|
+
if (node.regex) return TYPES.regexp;
|
1254
|
+
|
1255
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES.bytestring;
|
1096
1256
|
|
1097
1257
|
return TYPES[typeof node.value];
|
1098
1258
|
}
|
@@ -1107,6 +1267,27 @@ const getNodeType = (scope, node) => {
|
|
1107
1267
|
|
1108
1268
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1109
1269
|
const name = node.callee.name;
|
1270
|
+
if (!name) {
|
1271
|
+
// iife
|
1272
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1273
|
+
|
1274
|
+
// presume
|
1275
|
+
// todo: warn here?
|
1276
|
+
return TYPES.number;
|
1277
|
+
}
|
1278
|
+
|
1279
|
+
if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
|
1280
|
+
if (builtinFuncs[name + '$constructor'].typedReturns) {
|
1281
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1282
|
+
|
1283
|
+
// presume
|
1284
|
+
// todo: warn here?
|
1285
|
+
return TYPES.number;
|
1286
|
+
}
|
1287
|
+
|
1288
|
+
return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
|
1289
|
+
}
|
1290
|
+
|
1110
1291
|
const func = funcs.find(x => x.name === name);
|
1111
1292
|
|
1112
1293
|
if (func) {
|
@@ -1114,7 +1295,7 @@ const getNodeType = (scope, node) => {
|
|
1114
1295
|
if (func.returnType) return func.returnType;
|
1115
1296
|
}
|
1116
1297
|
|
1117
|
-
if (builtinFuncs[name]) return
|
1298
|
+
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
1118
1299
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1119
1300
|
|
1120
1301
|
// check if this is a prototype function
|
@@ -1125,11 +1306,16 @@ const getNodeType = (scope, node) => {
|
|
1125
1306
|
const spl = name.slice(2).split('_');
|
1126
1307
|
|
1127
1308
|
const func = spl[spl.length - 1];
|
1128
|
-
const protoFuncs = Object.
|
1309
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
|
1129
1310
|
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1130
1311
|
}
|
1131
1312
|
|
1132
|
-
if (
|
1313
|
+
if (name.startsWith('__Porffor_wasm_')) {
|
1314
|
+
// todo: return undefined for non-returning ops
|
1315
|
+
return TYPES.number;
|
1316
|
+
}
|
1317
|
+
|
1318
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1133
1319
|
|
1134
1320
|
// presume
|
1135
1321
|
// todo: warn here?
|
@@ -1172,11 +1358,20 @@ const getNodeType = (scope, node) => {
|
|
1172
1358
|
}
|
1173
1359
|
|
1174
1360
|
if (node.type === 'ArrayExpression') {
|
1175
|
-
return TYPES.
|
1361
|
+
return TYPES.array;
|
1176
1362
|
}
|
1177
1363
|
|
1178
1364
|
if (node.type === 'BinaryExpression') {
|
1179
1365
|
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
1366
|
+
if (node.operator !== '+') return TYPES.number;
|
1367
|
+
|
1368
|
+
const knownLeft = knownType(scope, getNodeType(scope, node.left));
|
1369
|
+
const knownRight = knownType(scope, getNodeType(scope, node.right));
|
1370
|
+
|
1371
|
+
// todo: this should be dynamic but for now only static
|
1372
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
|
1373
|
+
if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) return TYPES.bytestring;
|
1374
|
+
|
1180
1375
|
return TYPES.number;
|
1181
1376
|
|
1182
1377
|
// todo: string concat types
|
@@ -1201,20 +1396,41 @@ const getNodeType = (scope, node) => {
|
|
1201
1396
|
if (node.operator === '!') return TYPES.boolean;
|
1202
1397
|
if (node.operator === 'void') return TYPES.undefined;
|
1203
1398
|
if (node.operator === 'delete') return TYPES.boolean;
|
1204
|
-
if (node.operator === 'typeof') return TYPES.string;
|
1399
|
+
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES.bytestring : TYPES.string;
|
1205
1400
|
|
1206
1401
|
return TYPES.number;
|
1207
1402
|
}
|
1208
1403
|
|
1209
1404
|
if (node.type === 'MemberExpression') {
|
1405
|
+
// hack: if something.name, string type
|
1406
|
+
if (node.property.name === 'name') {
|
1407
|
+
if (hasFuncWithName(node.object.name)) {
|
1408
|
+
return TYPES.bytestring;
|
1409
|
+
} else {
|
1410
|
+
return TYPES.undefined;
|
1411
|
+
}
|
1412
|
+
}
|
1413
|
+
|
1210
1414
|
// hack: if something.length, number type
|
1211
1415
|
if (node.property.name === 'length') return TYPES.number;
|
1212
1416
|
|
1213
|
-
//
|
1417
|
+
// ts hack
|
1418
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
|
1419
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.bytestring) return TYPES.bytestring;
|
1420
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.array) return TYPES.number;
|
1421
|
+
|
1422
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1423
|
+
|
1424
|
+
// presume
|
1214
1425
|
return TYPES.number;
|
1215
1426
|
}
|
1216
1427
|
|
1217
|
-
if (
|
1428
|
+
if (node.type === 'TaggedTemplateExpression') {
|
1429
|
+
// hack
|
1430
|
+
if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
|
1431
|
+
}
|
1432
|
+
|
1433
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1218
1434
|
|
1219
1435
|
// presume
|
1220
1436
|
// todo: warn here?
|
@@ -1227,28 +1443,11 @@ const getNodeType = (scope, node) => {
|
|
1227
1443
|
return ret;
|
1228
1444
|
};
|
1229
1445
|
|
1230
|
-
const toString = (scope, wasm, type) => {
|
1231
|
-
const tmp = localTmp(scope, '#tostring_tmp');
|
1232
|
-
return [
|
1233
|
-
...wasm,
|
1234
|
-
[ Opcodes.local_set, tmp ],
|
1235
|
-
|
1236
|
-
...typeSwitch(scope, type, {
|
1237
|
-
[TYPES.string]: [
|
1238
|
-
[ Opcodes.local_get, tmp ]
|
1239
|
-
],
|
1240
|
-
[TYPES.undefined]: [
|
1241
|
-
// [ Opcodes.]
|
1242
|
-
]
|
1243
|
-
})
|
1244
|
-
]
|
1245
|
-
};
|
1246
|
-
|
1247
1446
|
const generateLiteral = (scope, decl, global, name) => {
|
1248
1447
|
if (decl.value === null) return number(NULL);
|
1249
1448
|
|
1449
|
+
// hack: just return 1 for regex literals
|
1250
1450
|
if (decl.regex) {
|
1251
|
-
scope.regex[name] = decl.regex;
|
1252
1451
|
return number(1);
|
1253
1452
|
}
|
1254
1453
|
|
@@ -1261,19 +1460,10 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1261
1460
|
return number(decl.value ? 1 : 0);
|
1262
1461
|
|
1263
1462
|
case 'string':
|
1264
|
-
|
1265
|
-
const rawElements = new Array(str.length);
|
1266
|
-
let j = 0;
|
1267
|
-
for (let i = 0; i < str.length; i++) {
|
1268
|
-
rawElements[i] = str.charCodeAt(i);
|
1269
|
-
}
|
1270
|
-
|
1271
|
-
return makeArray(scope, {
|
1272
|
-
rawElements
|
1273
|
-
}, global, name, false, 'i16')[0];
|
1463
|
+
return makeString(scope, decl.value, global, name);
|
1274
1464
|
|
1275
1465
|
default:
|
1276
|
-
return todo(`cannot generate literal of type ${typeof decl.value}
|
1466
|
+
return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
|
1277
1467
|
}
|
1278
1468
|
};
|
1279
1469
|
|
@@ -1282,6 +1472,8 @@ const countLeftover = wasm => {
|
|
1282
1472
|
|
1283
1473
|
for (let i = 0; i < wasm.length; i++) {
|
1284
1474
|
const inst = wasm[i];
|
1475
|
+
if (inst[0] == null) continue;
|
1476
|
+
|
1285
1477
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
1286
1478
|
if (inst[0] === Opcodes.if) count--;
|
1287
1479
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1290,18 +1482,25 @@ const countLeftover = wasm => {
|
|
1290
1482
|
if (inst[0] === Opcodes.end) depth--;
|
1291
1483
|
|
1292
1484
|
if (depth === 0)
|
1293
|
-
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1294
|
-
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
1295
|
-
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1296
|
-
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
|
1485
|
+
if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1486
|
+
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
1487
|
+
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const, Opcodes.memory_size].includes(inst[0])) count++;
|
1488
|
+
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1297
1489
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1298
1490
|
else if (inst[0] === Opcodes.return) count = 0;
|
1299
1491
|
else if (inst[0] === Opcodes.call) {
|
1300
1492
|
let func = funcs.find(x => x.index === inst[1]);
|
1301
|
-
if (
|
1302
|
-
count
|
1303
|
-
} else
|
1304
|
-
|
1493
|
+
if (inst[1] === -1) {
|
1494
|
+
// todo: count for calling self
|
1495
|
+
} else if (!func && inst[1] < importedFuncs.length) {
|
1496
|
+
count -= importedFuncs[inst[1]].params;
|
1497
|
+
count += importedFuncs[inst[1]].returns;
|
1498
|
+
} else {
|
1499
|
+
if (func) {
|
1500
|
+
count -= func.params.length;
|
1501
|
+
} else count--;
|
1502
|
+
if (func) count += func.returns.length;
|
1503
|
+
}
|
1305
1504
|
} else count--;
|
1306
1505
|
|
1307
1506
|
// console.log(count, decompile([ inst ]).slice(0, -1));
|
@@ -1319,7 +1518,7 @@ const disposeLeftover = wasm => {
|
|
1319
1518
|
const generateExp = (scope, decl) => {
|
1320
1519
|
const expression = decl.expression;
|
1321
1520
|
|
1322
|
-
const out = generate(scope, expression);
|
1521
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1323
1522
|
disposeLeftover(out);
|
1324
1523
|
|
1325
1524
|
return out;
|
@@ -1377,7 +1576,7 @@ const RTArrayUtil = {
|
|
1377
1576
|
]
|
1378
1577
|
};
|
1379
1578
|
|
1380
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1579
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1381
1580
|
/* const callee = decl.callee;
|
1382
1581
|
const args = decl.arguments;
|
1383
1582
|
|
@@ -1393,10 +1592,21 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1393
1592
|
name = func.name;
|
1394
1593
|
}
|
1395
1594
|
|
1396
|
-
if (name === 'eval' && decl.arguments[0]
|
1595
|
+
if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
|
1397
1596
|
// literal eval hack
|
1398
|
-
const code = decl.arguments[0]
|
1399
|
-
|
1597
|
+
const code = decl.arguments[0]?.value ?? '';
|
1598
|
+
|
1599
|
+
let parsed;
|
1600
|
+
try {
|
1601
|
+
parsed = parse(code, []);
|
1602
|
+
} catch (e) {
|
1603
|
+
if (e.name === 'SyntaxError') {
|
1604
|
+
// throw syntax errors of evals at runtime instead
|
1605
|
+
return internalThrow(scope, 'SyntaxError', e.message, true);
|
1606
|
+
}
|
1607
|
+
|
1608
|
+
throw e;
|
1609
|
+
}
|
1400
1610
|
|
1401
1611
|
const out = generate(scope, {
|
1402
1612
|
type: 'BlockStatement',
|
@@ -1410,13 +1620,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1410
1620
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1411
1621
|
out.push(
|
1412
1622
|
...getNodeType(scope, finalStatement),
|
1413
|
-
setLastType(scope)
|
1623
|
+
...setLastType(scope)
|
1414
1624
|
);
|
1415
1625
|
} else if (countLeftover(out) === 0) {
|
1416
1626
|
out.push(...number(UNDEFINED));
|
1417
1627
|
out.push(
|
1418
1628
|
...number(TYPES.undefined, Valtype.i32),
|
1419
|
-
setLastType(scope)
|
1629
|
+
...setLastType(scope)
|
1420
1630
|
);
|
1421
1631
|
}
|
1422
1632
|
|
@@ -1438,29 +1648,39 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1438
1648
|
|
1439
1649
|
target = { ...decl.callee };
|
1440
1650
|
target.name = spl.slice(0, -1).join('_');
|
1651
|
+
|
1652
|
+
// failed to lookup name, abort
|
1653
|
+
if (!lookupName(scope, target.name)[0]) protoName = null;
|
1441
1654
|
}
|
1442
1655
|
|
1443
1656
|
// literal.func()
|
1444
1657
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1445
1658
|
// megahack for /regex/.func()
|
1446
|
-
|
1447
|
-
|
1448
|
-
const
|
1659
|
+
const funcName = decl.callee.property.name;
|
1660
|
+
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1661
|
+
const regex = decl.callee.object.regex.pattern;
|
1662
|
+
const rhemynName = `regex_${funcName}_${regex}`;
|
1449
1663
|
|
1450
|
-
funcIndex[
|
1451
|
-
|
1664
|
+
if (!funcIndex[rhemynName]) {
|
1665
|
+
const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
|
1452
1666
|
|
1667
|
+
funcIndex[func.name] = func.index;
|
1668
|
+
funcs.push(func);
|
1669
|
+
}
|
1670
|
+
|
1671
|
+
const idx = funcIndex[rhemynName];
|
1453
1672
|
return [
|
1454
1673
|
// make string arg
|
1455
1674
|
...generate(scope, decl.arguments[0]),
|
1675
|
+
Opcodes.i32_to_u,
|
1676
|
+
...getNodeType(scope, decl.arguments[0]),
|
1456
1677
|
|
1457
1678
|
// call regex func
|
1458
|
-
Opcodes.
|
1459
|
-
[ Opcodes.call, func.index ],
|
1679
|
+
[ Opcodes.call, idx ],
|
1460
1680
|
Opcodes.i32_from_u,
|
1461
1681
|
|
1462
1682
|
...number(TYPES.boolean, Valtype.i32),
|
1463
|
-
setLastType(scope)
|
1683
|
+
...setLastType(scope)
|
1464
1684
|
];
|
1465
1685
|
}
|
1466
1686
|
|
@@ -1485,23 +1705,48 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1485
1705
|
// }
|
1486
1706
|
|
1487
1707
|
if (protoName) {
|
1708
|
+
const protoBC = {};
|
1709
|
+
|
1710
|
+
const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
|
1711
|
+
|
1712
|
+
if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
|
1713
|
+
for (const x of builtinProtoCands) {
|
1714
|
+
const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
|
1715
|
+
if (type == null) continue;
|
1716
|
+
|
1717
|
+
protoBC[type] = generateCall(scope, {
|
1718
|
+
callee: {
|
1719
|
+
type: 'Identifier',
|
1720
|
+
name: x
|
1721
|
+
},
|
1722
|
+
arguments: [ target, ...decl.arguments ],
|
1723
|
+
_protoInternalCall: true
|
1724
|
+
});
|
1725
|
+
}
|
1726
|
+
}
|
1727
|
+
|
1488
1728
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1489
|
-
|
1490
|
-
if (f) acc[x] = f;
|
1729
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1491
1730
|
return acc;
|
1492
1731
|
}, {});
|
1493
1732
|
|
1494
|
-
// no prototype function candidates, ignore
|
1495
1733
|
if (Object.keys(protoCands).length > 0) {
|
1496
1734
|
// use local for cached i32 length as commonly used
|
1497
1735
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1498
1736
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1499
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1500
1737
|
|
1501
1738
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1502
1739
|
|
1740
|
+
const rawPointer = [
|
1741
|
+
...generate(scope, target),
|
1742
|
+
Opcodes.i32_to_u
|
1743
|
+
];
|
1744
|
+
|
1745
|
+
const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
|
1746
|
+
const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
|
1747
|
+
|
1748
|
+
let allOptUnused = true;
|
1503
1749
|
let lengthI32CacheUsed = false;
|
1504
|
-
const protoBC = {};
|
1505
1750
|
for (const x in protoCands) {
|
1506
1751
|
const protoFunc = protoCands[x];
|
1507
1752
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
@@ -1509,7 +1754,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1509
1754
|
...RTArrayUtil.getLength(getPointer),
|
1510
1755
|
|
1511
1756
|
...number(TYPES.number, Valtype.i32),
|
1512
|
-
setLastType(scope)
|
1757
|
+
...setLastType(scope)
|
1513
1758
|
];
|
1514
1759
|
continue;
|
1515
1760
|
}
|
@@ -1519,6 +1764,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1519
1764
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1520
1765
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1521
1766
|
|
1767
|
+
let optUnused = false;
|
1522
1768
|
const protoOut = protoFunc(getPointer, {
|
1523
1769
|
getCachedI32: () => {
|
1524
1770
|
lengthI32CacheUsed = true;
|
@@ -1533,23 +1779,30 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1533
1779
|
return makeArray(scope, {
|
1534
1780
|
rawElements: new Array(length)
|
1535
1781
|
}, _global, _name, true, itemType);
|
1782
|
+
}, () => {
|
1783
|
+
optUnused = true;
|
1784
|
+
return unusedValue;
|
1536
1785
|
});
|
1537
1786
|
|
1787
|
+
if (!optUnused) allOptUnused = false;
|
1788
|
+
|
1538
1789
|
protoBC[x] = [
|
1539
|
-
[ Opcodes.block, valtypeBinary ],
|
1790
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1540
1791
|
...protoOut,
|
1541
1792
|
|
1542
1793
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1543
|
-
setLastType(scope),
|
1794
|
+
...setLastType(scope),
|
1544
1795
|
[ Opcodes.end ]
|
1545
1796
|
];
|
1546
1797
|
}
|
1547
1798
|
|
1548
|
-
|
1549
|
-
...generate(scope, target),
|
1799
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1550
1800
|
|
1551
|
-
|
1552
|
-
|
1801
|
+
return [
|
1802
|
+
...(usePointerCache ? [
|
1803
|
+
...rawPointer,
|
1804
|
+
[ Opcodes.local_set, pointerLocal ],
|
1805
|
+
] : []),
|
1553
1806
|
|
1554
1807
|
...(!lengthI32CacheUsed ? [] : [
|
1555
1808
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1561,13 +1814,22 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1561
1814
|
|
1562
1815
|
// TODO: error better
|
1563
1816
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1564
|
-
}, valtypeBinary),
|
1817
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1565
1818
|
];
|
1566
1819
|
}
|
1820
|
+
|
1821
|
+
if (Object.keys(protoBC).length > 0) {
|
1822
|
+
return typeSwitch(scope, getNodeType(scope, target), {
|
1823
|
+
...protoBC,
|
1824
|
+
|
1825
|
+
// TODO: error better
|
1826
|
+
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1827
|
+
}, valtypeBinary);
|
1828
|
+
}
|
1567
1829
|
}
|
1568
1830
|
|
1569
1831
|
// TODO: only allows callee as literal
|
1570
|
-
if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
|
1832
|
+
if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
|
1571
1833
|
|
1572
1834
|
let idx = funcIndex[name] ?? importedFuncs[name];
|
1573
1835
|
if (idx === undefined && builtinFuncs[name]) {
|
@@ -1577,20 +1839,20 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1577
1839
|
idx = funcIndex[name];
|
1578
1840
|
|
1579
1841
|
// infer arguments types from builtins params
|
1580
|
-
const func = funcs.find(x => x.name === name);
|
1581
|
-
for (let i = 0; i < decl.arguments.length; i++) {
|
1582
|
-
|
1583
|
-
|
1584
|
-
|
1585
|
-
|
1586
|
-
|
1587
|
-
|
1588
|
-
|
1589
|
-
|
1590
|
-
|
1591
|
-
|
1592
|
-
|
1593
|
-
}
|
1842
|
+
// const func = funcs.find(x => x.name === name);
|
1843
|
+
// for (let i = 0; i < decl.arguments.length; i++) {
|
1844
|
+
// const arg = decl.arguments[i];
|
1845
|
+
// if (!arg.name) continue;
|
1846
|
+
|
1847
|
+
// const local = scope.locals[arg.name];
|
1848
|
+
// if (!local) continue;
|
1849
|
+
|
1850
|
+
// local.type = func.params[i];
|
1851
|
+
// if (local.type === Valtype.v128) {
|
1852
|
+
// // specify vec subtype inferred from last vec type in function name
|
1853
|
+
// local.vecType = name.split('_').reverse().find(x => x.includes('x'));
|
1854
|
+
// }
|
1855
|
+
// }
|
1594
1856
|
}
|
1595
1857
|
|
1596
1858
|
if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
@@ -1600,15 +1862,63 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1600
1862
|
idx = -1;
|
1601
1863
|
}
|
1602
1864
|
|
1865
|
+
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1866
|
+
const wasmOps = {
|
1867
|
+
// pointer, align, offset
|
1868
|
+
i32_load: { imms: 2, args: [ true ], returns: 1 },
|
1869
|
+
// pointer, value, align, offset
|
1870
|
+
i32_store: { imms: 2, args: [ true, true ], returns: 0 },
|
1871
|
+
// pointer, align, offset
|
1872
|
+
i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
|
1873
|
+
// pointer, value, align, offset
|
1874
|
+
i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
|
1875
|
+
// pointer, align, offset
|
1876
|
+
i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
|
1877
|
+
// pointer, value, align, offset
|
1878
|
+
i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
|
1879
|
+
|
1880
|
+
// pointer, align, offset
|
1881
|
+
f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
|
1882
|
+
// pointer, value, align, offset
|
1883
|
+
f64_store: { imms: 2, args: [ true, false ], returns: 0 },
|
1884
|
+
|
1885
|
+
// value
|
1886
|
+
i32_const: { imms: 1, args: [], returns: 1 },
|
1887
|
+
};
|
1888
|
+
|
1889
|
+
const opName = name.slice('__Porffor_wasm_'.length);
|
1890
|
+
|
1891
|
+
if (wasmOps[opName]) {
|
1892
|
+
const op = wasmOps[opName];
|
1893
|
+
|
1894
|
+
const argOut = [];
|
1895
|
+
for (let i = 0; i < op.args.length; i++) argOut.push(
|
1896
|
+
...generate(scope, decl.arguments[i]),
|
1897
|
+
...(op.args[i] ? [ Opcodes.i32_to ] : [])
|
1898
|
+
);
|
1899
|
+
|
1900
|
+
// literals only
|
1901
|
+
const imms = decl.arguments.slice(op.args.length).map(x => x.value);
|
1902
|
+
|
1903
|
+
return [
|
1904
|
+
...argOut,
|
1905
|
+
[ Opcodes[opName], ...imms ],
|
1906
|
+
...(new Array(op.returns).fill(Opcodes.i32_from))
|
1907
|
+
];
|
1908
|
+
}
|
1909
|
+
}
|
1910
|
+
|
1603
1911
|
if (idx === undefined) {
|
1604
|
-
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function
|
1605
|
-
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined
|
1912
|
+
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1913
|
+
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
1606
1914
|
}
|
1607
1915
|
|
1608
1916
|
const func = funcs.find(x => x.index === idx);
|
1609
1917
|
|
1610
1918
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1611
|
-
const
|
1919
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1920
|
+
const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
|
1921
|
+
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1612
1922
|
|
1613
1923
|
let args = decl.arguments;
|
1614
1924
|
if (func && args.length < paramCount) {
|
@@ -1624,14 +1934,24 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1624
1934
|
if (func && func.throws) scope.throws = true;
|
1625
1935
|
|
1626
1936
|
let out = [];
|
1627
|
-
for (
|
1937
|
+
for (let i = 0; i < args.length; i++) {
|
1938
|
+
const arg = args[i];
|
1628
1939
|
out = out.concat(generate(scope, arg));
|
1629
|
-
|
1940
|
+
|
1941
|
+
if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1942
|
+
out.push(Opcodes.i32_to);
|
1943
|
+
}
|
1944
|
+
|
1945
|
+
if (importedFuncs[name] && name.startsWith('profile')) {
|
1946
|
+
out.push(Opcodes.i32_to);
|
1947
|
+
}
|
1948
|
+
|
1949
|
+
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1630
1950
|
}
|
1631
1951
|
|
1632
1952
|
out.push([ Opcodes.call, idx ]);
|
1633
1953
|
|
1634
|
-
if (!
|
1954
|
+
if (!typedReturns) {
|
1635
1955
|
// let type;
|
1636
1956
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1637
1957
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1641,7 +1961,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1641
1961
|
// ...number(type, Valtype.i32),
|
1642
1962
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1643
1963
|
// );
|
1644
|
-
} else out.push(setLastType(scope));
|
1964
|
+
} else out.push(...setLastType(scope));
|
1965
|
+
|
1966
|
+
if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1967
|
+
out.push(Opcodes.i32_from);
|
1968
|
+
}
|
1645
1969
|
|
1646
1970
|
return out;
|
1647
1971
|
};
|
@@ -1649,8 +1973,21 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1649
1973
|
const generateNew = (scope, decl, _global, _name) => {
|
1650
1974
|
// hack: basically treat this as a normal call for builtins for now
|
1651
1975
|
const name = mapName(decl.callee.name);
|
1976
|
+
|
1652
1977
|
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1653
|
-
|
1978
|
+
|
1979
|
+
if (builtinFuncs[name + '$constructor']) {
|
1980
|
+
// custom ...$constructor override builtin func
|
1981
|
+
return generateCall(scope, {
|
1982
|
+
...decl,
|
1983
|
+
callee: {
|
1984
|
+
type: 'Identifier',
|
1985
|
+
name: name + '$constructor'
|
1986
|
+
}
|
1987
|
+
}, _global, _name);
|
1988
|
+
}
|
1989
|
+
|
1990
|
+
if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
|
1654
1991
|
|
1655
1992
|
return generateCall(scope, decl, _global, _name);
|
1656
1993
|
};
|
@@ -1767,12 +2104,14 @@ const brTable = (input, bc, returns) => {
|
|
1767
2104
|
};
|
1768
2105
|
|
1769
2106
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
2107
|
+
if (!Prefs.bytestring) delete bc[TYPES.bytestring];
|
2108
|
+
|
1770
2109
|
const known = knownType(scope, type);
|
1771
2110
|
if (known != null) {
|
1772
2111
|
return bc[known] ?? bc.default;
|
1773
2112
|
}
|
1774
2113
|
|
1775
|
-
if (
|
2114
|
+
if (Prefs.typeswitchUseBrtable)
|
1776
2115
|
return brTable(type, bc, returns);
|
1777
2116
|
|
1778
2117
|
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
@@ -1782,8 +2121,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1782
2121
|
[ Opcodes.block, returns ]
|
1783
2122
|
];
|
1784
2123
|
|
1785
|
-
// todo: use br_table?
|
1786
|
-
|
1787
2124
|
for (const x in bc) {
|
1788
2125
|
if (x === 'default') continue;
|
1789
2126
|
|
@@ -1807,7 +2144,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1807
2144
|
return out;
|
1808
2145
|
};
|
1809
2146
|
|
1810
|
-
const allocVar = (scope, name, global = false) => {
|
2147
|
+
const allocVar = (scope, name, global = false, type = true) => {
|
1811
2148
|
const target = global ? globals : scope.locals;
|
1812
2149
|
|
1813
2150
|
// already declared
|
@@ -1821,8 +2158,10 @@ const allocVar = (scope, name, global = false) => {
|
|
1821
2158
|
let idx = global ? globalInd++ : scope.localInd++;
|
1822
2159
|
target[name] = { idx, type: valtypeBinary };
|
1823
2160
|
|
1824
|
-
|
1825
|
-
|
2161
|
+
if (type) {
|
2162
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
2163
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
2164
|
+
}
|
1826
2165
|
|
1827
2166
|
return idx;
|
1828
2167
|
};
|
@@ -1837,11 +2176,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
|
1837
2176
|
};
|
1838
2177
|
|
1839
2178
|
const typeAnnoToPorfType = x => {
|
1840
|
-
if (
|
1841
|
-
if (TYPES[
|
2179
|
+
if (!x) return null;
|
2180
|
+
if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
|
2181
|
+
if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
|
1842
2182
|
|
1843
2183
|
switch (x) {
|
1844
2184
|
case 'i32':
|
2185
|
+
case 'i64':
|
2186
|
+
case 'f64':
|
1845
2187
|
return TYPES.number;
|
1846
2188
|
}
|
1847
2189
|
|
@@ -1852,7 +2194,7 @@ const extractTypeAnnotation = decl => {
|
|
1852
2194
|
let a = decl;
|
1853
2195
|
while (a.typeAnnotation) a = a.typeAnnotation;
|
1854
2196
|
|
1855
|
-
let type, elementType;
|
2197
|
+
let type = null, elementType = null;
|
1856
2198
|
if (a.typeName) {
|
1857
2199
|
type = a.typeName.name;
|
1858
2200
|
} else if (a.type.endsWith('Keyword')) {
|
@@ -1865,6 +2207,8 @@ const extractTypeAnnotation = decl => {
|
|
1865
2207
|
const typeName = type;
|
1866
2208
|
type = typeAnnoToPorfType(type);
|
1867
2209
|
|
2210
|
+
if (type === TYPES.bytestring && !Prefs.bytestring) type = TYPES.string;
|
2211
|
+
|
1868
2212
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1869
2213
|
|
1870
2214
|
return { type, typeName, elementType };
|
@@ -1877,10 +2221,13 @@ const generateVar = (scope, decl) => {
|
|
1877
2221
|
|
1878
2222
|
// global variable if in top scope (main) and var ..., or if wanted
|
1879
2223
|
const global = topLevel || decl._bare; // decl.kind === 'var';
|
2224
|
+
const target = global ? globals : scope.locals;
|
1880
2225
|
|
1881
2226
|
for (const x of decl.declarations) {
|
1882
2227
|
const name = mapName(x.id.name);
|
1883
2228
|
|
2229
|
+
if (!name) return todo(scope, 'destructuring is not supported yet');
|
2230
|
+
|
1884
2231
|
if (x.init && isFuncType(x.init.type)) {
|
1885
2232
|
// hack for let a = function () { ... }
|
1886
2233
|
x.init.id = { name };
|
@@ -1896,16 +2243,29 @@ const generateVar = (scope, decl) => {
|
|
1896
2243
|
continue; // always ignore
|
1897
2244
|
}
|
1898
2245
|
|
1899
|
-
|
2246
|
+
// // generate init before allocating var
|
2247
|
+
// let generated;
|
2248
|
+
// if (x.init) generated = generate(scope, x.init, global, name);
|
2249
|
+
|
2250
|
+
const typed = typedInput && x.id.typeAnnotation;
|
2251
|
+
let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
|
1900
2252
|
|
1901
|
-
if (
|
2253
|
+
if (typed) {
|
1902
2254
|
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1903
2255
|
}
|
1904
2256
|
|
1905
2257
|
if (x.init) {
|
1906
|
-
|
1907
|
-
|
1908
|
-
|
2258
|
+
const generated = generate(scope, x.init, global, name);
|
2259
|
+
if (scope.arrays?.get(name) != null) {
|
2260
|
+
// hack to set local as pointer before
|
2261
|
+
out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2262
|
+
if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
|
2263
|
+
generated.pop();
|
2264
|
+
out = out.concat(generated);
|
2265
|
+
} else {
|
2266
|
+
out = out.concat(generated);
|
2267
|
+
out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2268
|
+
}
|
1909
2269
|
out.push(...setType(scope, name, getNodeType(scope, x.init)));
|
1910
2270
|
}
|
1911
2271
|
|
@@ -1916,7 +2276,8 @@ const generateVar = (scope, decl) => {
|
|
1916
2276
|
return out;
|
1917
2277
|
};
|
1918
2278
|
|
1919
|
-
|
2279
|
+
// todo: optimize this func for valueUnused
|
2280
|
+
const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
1920
2281
|
const { type, name } = decl.left;
|
1921
2282
|
|
1922
2283
|
if (type === 'ObjectPattern') {
|
@@ -1931,22 +2292,30 @@ const generateAssign = (scope, decl) => {
|
|
1931
2292
|
return [];
|
1932
2293
|
}
|
1933
2294
|
|
2295
|
+
const op = decl.operator.slice(0, -1) || '=';
|
2296
|
+
|
1934
2297
|
// hack: .length setter
|
1935
2298
|
if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
|
1936
2299
|
const name = decl.left.object.name;
|
1937
|
-
const pointer = arrays
|
2300
|
+
const pointer = scope.arrays?.get(name);
|
1938
2301
|
|
1939
|
-
const aotPointer = pointer != null;
|
2302
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1940
2303
|
|
1941
2304
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
2305
|
+
const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1942
2306
|
|
1943
2307
|
return [
|
1944
2308
|
...(aotPointer ? number(0, Valtype.i32) : [
|
1945
2309
|
...generate(scope, decl.left.object),
|
1946
2310
|
Opcodes.i32_to_u
|
1947
2311
|
]),
|
2312
|
+
...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
1948
2313
|
|
1949
|
-
...generate(scope, decl.right),
|
2314
|
+
...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
|
2315
|
+
[ Opcodes.local_get, pointerTmp ],
|
2316
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
2317
|
+
Opcodes.i32_from_u
|
2318
|
+
], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
|
1950
2319
|
[ Opcodes.local_tee, newValueTmp ],
|
1951
2320
|
|
1952
2321
|
Opcodes.i32_to_u,
|
@@ -1956,21 +2325,19 @@ const generateAssign = (scope, decl) => {
|
|
1956
2325
|
];
|
1957
2326
|
}
|
1958
2327
|
|
1959
|
-
const op = decl.operator.slice(0, -1) || '=';
|
1960
|
-
|
1961
2328
|
// arr[i]
|
1962
2329
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
1963
2330
|
const name = decl.left.object.name;
|
1964
|
-
const pointer = arrays
|
2331
|
+
const pointer = scope.arrays?.get(name);
|
1965
2332
|
|
1966
|
-
const aotPointer = pointer != null;
|
2333
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1967
2334
|
|
1968
2335
|
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
1969
2336
|
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1970
2337
|
|
1971
2338
|
return [
|
1972
2339
|
...typeSwitch(scope, getNodeType(scope, decl.left.object), {
|
1973
|
-
[TYPES.
|
2340
|
+
[TYPES.array]: [
|
1974
2341
|
...(aotPointer ? [] : [
|
1975
2342
|
...generate(scope, decl.left.object),
|
1976
2343
|
Opcodes.i32_to_u
|
@@ -2019,6 +2386,8 @@ const generateAssign = (scope, decl) => {
|
|
2019
2386
|
];
|
2020
2387
|
}
|
2021
2388
|
|
2389
|
+
if (!name) return todo(scope, 'destructuring is not supported yet', true);
|
2390
|
+
|
2022
2391
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2023
2392
|
|
2024
2393
|
if (local === undefined) {
|
@@ -2065,9 +2434,7 @@ const generateAssign = (scope, decl) => {
|
|
2065
2434
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
2066
2435
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
2067
2436
|
|
2068
|
-
getLastType(scope)
|
2069
|
-
// hack: type is idx+1
|
2070
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2437
|
+
...setType(scope, name, getLastType(scope))
|
2071
2438
|
];
|
2072
2439
|
}
|
2073
2440
|
|
@@ -2078,9 +2445,7 @@ const generateAssign = (scope, decl) => {
|
|
2078
2445
|
|
2079
2446
|
// todo: string concat types
|
2080
2447
|
|
2081
|
-
|
2082
|
-
...number(TYPES.number, Valtype.i32),
|
2083
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2448
|
+
...setType(scope, name, TYPES.number)
|
2084
2449
|
];
|
2085
2450
|
};
|
2086
2451
|
|
@@ -2126,7 +2491,7 @@ const generateUnary = (scope, decl) => {
|
|
2126
2491
|
return out;
|
2127
2492
|
}
|
2128
2493
|
|
2129
|
-
case 'delete':
|
2494
|
+
case 'delete': {
|
2130
2495
|
let toReturn = true, toGenerate = true;
|
2131
2496
|
|
2132
2497
|
if (decl.argument.type === 'Identifier') {
|
@@ -2148,38 +2513,60 @@ const generateUnary = (scope, decl) => {
|
|
2148
2513
|
|
2149
2514
|
out.push(...number(toReturn ? 1 : 0));
|
2150
2515
|
return out;
|
2516
|
+
}
|
2517
|
+
|
2518
|
+
case 'typeof': {
|
2519
|
+
let overrideType, toGenerate = true;
|
2520
|
+
|
2521
|
+
if (decl.argument.type === 'Identifier') {
|
2522
|
+
const out = generateIdent(scope, decl.argument);
|
2523
|
+
|
2524
|
+
// if ReferenceError (undeclared var), ignore and return undefined
|
2525
|
+
if (out[1]) {
|
2526
|
+
// does not exist (2 ops from throw)
|
2527
|
+
overrideType = number(TYPES.undefined, Valtype.i32);
|
2528
|
+
toGenerate = false;
|
2529
|
+
}
|
2530
|
+
}
|
2151
2531
|
|
2152
|
-
|
2153
|
-
|
2532
|
+
const out = toGenerate ? generate(scope, decl.argument) : [];
|
2533
|
+
disposeLeftover(out);
|
2534
|
+
|
2535
|
+
out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
|
2154
2536
|
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
2155
2537
|
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
2156
2538
|
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
2157
2539
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
2158
2540
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
2159
2541
|
|
2542
|
+
[TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2543
|
+
|
2160
2544
|
// object and internal types
|
2161
2545
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2162
|
-
});
|
2546
|
+
}));
|
2547
|
+
|
2548
|
+
return out;
|
2549
|
+
}
|
2163
2550
|
|
2164
2551
|
default:
|
2165
|
-
return todo(`unary operator ${decl.operator} not implemented yet
|
2552
|
+
return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
|
2166
2553
|
}
|
2167
2554
|
};
|
2168
2555
|
|
2169
|
-
const generateUpdate = (scope, decl) => {
|
2556
|
+
const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
|
2170
2557
|
const { name } = decl.argument;
|
2171
2558
|
|
2172
2559
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2173
2560
|
|
2174
2561
|
if (local === undefined) {
|
2175
|
-
return todo(`update expression with undefined variable
|
2562
|
+
return todo(scope, `update expression with undefined variable`, true);
|
2176
2563
|
}
|
2177
2564
|
|
2178
2565
|
const idx = local.idx;
|
2179
2566
|
const out = [];
|
2180
2567
|
|
2181
2568
|
out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2182
|
-
if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2569
|
+
if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2183
2570
|
|
2184
2571
|
switch (decl.operator) {
|
2185
2572
|
case '++':
|
@@ -2192,7 +2579,7 @@ const generateUpdate = (scope, decl) => {
|
|
2192
2579
|
}
|
2193
2580
|
|
2194
2581
|
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2195
|
-
if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2582
|
+
if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2196
2583
|
|
2197
2584
|
return out;
|
2198
2585
|
};
|
@@ -2232,7 +2619,7 @@ const generateConditional = (scope, decl) => {
|
|
2232
2619
|
// note type
|
2233
2620
|
out.push(
|
2234
2621
|
...getNodeType(scope, decl.consequent),
|
2235
|
-
setLastType(scope)
|
2622
|
+
...setLastType(scope)
|
2236
2623
|
);
|
2237
2624
|
|
2238
2625
|
out.push([ Opcodes.else ]);
|
@@ -2241,7 +2628,7 @@ const generateConditional = (scope, decl) => {
|
|
2241
2628
|
// note type
|
2242
2629
|
out.push(
|
2243
2630
|
...getNodeType(scope, decl.alternate),
|
2244
|
-
setLastType(scope)
|
2631
|
+
...setLastType(scope)
|
2245
2632
|
);
|
2246
2633
|
|
2247
2634
|
out.push([ Opcodes.end ]);
|
@@ -2255,15 +2642,17 @@ const generateFor = (scope, decl) => {
|
|
2255
2642
|
const out = [];
|
2256
2643
|
|
2257
2644
|
if (decl.init) {
|
2258
|
-
out.push(...generate(scope, decl.init));
|
2645
|
+
out.push(...generate(scope, decl.init, false, undefined, true));
|
2259
2646
|
disposeLeftover(out);
|
2260
2647
|
}
|
2261
2648
|
|
2262
2649
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2263
2650
|
depth.push('for');
|
2264
2651
|
|
2265
|
-
out.push(...generate(scope, decl.test));
|
2266
|
-
|
2652
|
+
if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2653
|
+
else out.push(...number(1, Valtype.i32));
|
2654
|
+
|
2655
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
2267
2656
|
depth.push('if');
|
2268
2657
|
|
2269
2658
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2271,8 +2660,7 @@ const generateFor = (scope, decl) => {
|
|
2271
2660
|
out.push(...generate(scope, decl.body));
|
2272
2661
|
out.push([ Opcodes.end ]);
|
2273
2662
|
|
2274
|
-
out.push(...generate(scope, decl.update));
|
2275
|
-
depth.pop();
|
2663
|
+
if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
|
2276
2664
|
|
2277
2665
|
out.push([ Opcodes.br, 1 ]);
|
2278
2666
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2300,6 +2688,36 @@ const generateWhile = (scope, decl) => {
|
|
2300
2688
|
return out;
|
2301
2689
|
};
|
2302
2690
|
|
2691
|
+
const generateDoWhile = (scope, decl) => {
|
2692
|
+
const out = [];
|
2693
|
+
|
2694
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
2695
|
+
depth.push('dowhile');
|
2696
|
+
|
2697
|
+
// block for break (includes all)
|
2698
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2699
|
+
depth.push('block');
|
2700
|
+
|
2701
|
+
// block for continue
|
2702
|
+
// includes body but not test+loop so we can exit body at anytime
|
2703
|
+
// and still test+loop after
|
2704
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2705
|
+
depth.push('block');
|
2706
|
+
|
2707
|
+
out.push(...generate(scope, decl.body));
|
2708
|
+
|
2709
|
+
out.push([ Opcodes.end ]);
|
2710
|
+
depth.pop();
|
2711
|
+
|
2712
|
+
out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2713
|
+
out.push([ Opcodes.br_if, 1 ]);
|
2714
|
+
|
2715
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
2716
|
+
depth.pop(); depth.pop();
|
2717
|
+
|
2718
|
+
return out;
|
2719
|
+
};
|
2720
|
+
|
2303
2721
|
const generateForOf = (scope, decl) => {
|
2304
2722
|
const out = [];
|
2305
2723
|
|
@@ -2329,8 +2747,17 @@ const generateForOf = (scope, decl) => {
|
|
2329
2747
|
// setup local for left
|
2330
2748
|
generate(scope, decl.left);
|
2331
2749
|
|
2332
|
-
|
2750
|
+
let leftName = decl.left.declarations?.[0]?.id?.name;
|
2751
|
+
if (!leftName && decl.left.name) {
|
2752
|
+
leftName = decl.left.name;
|
2753
|
+
|
2754
|
+
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2755
|
+
}
|
2756
|
+
|
2757
|
+
// if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
|
2758
|
+
|
2333
2759
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2760
|
+
if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
|
2334
2761
|
|
2335
2762
|
depth.push('block');
|
2336
2763
|
depth.push('block');
|
@@ -2338,15 +2765,17 @@ const generateForOf = (scope, decl) => {
|
|
2338
2765
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2339
2766
|
// hack: this is naughty and will break things!
|
2340
2767
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2341
|
-
if (pages.
|
2768
|
+
if (pages.hasAnyString) {
|
2769
|
+
// todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
|
2342
2770
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2343
2771
|
rawElements: new Array(1)
|
2344
2772
|
}, isGlobal, leftName, true, 'i16');
|
2345
2773
|
}
|
2346
2774
|
|
2347
2775
|
// set type for local
|
2776
|
+
// todo: optimize away counter and use end pointer
|
2348
2777
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2349
|
-
[TYPES.
|
2778
|
+
[TYPES.array]: [
|
2350
2779
|
...setType(scope, leftName, TYPES.number),
|
2351
2780
|
|
2352
2781
|
[ Opcodes.loop, Blocktype.void ],
|
@@ -2429,6 +2858,56 @@ const generateForOf = (scope, decl) => {
|
|
2429
2858
|
[ Opcodes.end ],
|
2430
2859
|
[ Opcodes.end ]
|
2431
2860
|
],
|
2861
|
+
[TYPES.bytestring]: [
|
2862
|
+
...setType(scope, leftName, TYPES.bytestring),
|
2863
|
+
|
2864
|
+
[ Opcodes.loop, Blocktype.void ],
|
2865
|
+
|
2866
|
+
// setup new/out array
|
2867
|
+
...newOut,
|
2868
|
+
[ Opcodes.drop ],
|
2869
|
+
|
2870
|
+
...number(0, Valtype.i32), // base 0 for store after
|
2871
|
+
|
2872
|
+
// load current string ind {arg}
|
2873
|
+
[ Opcodes.local_get, pointer ],
|
2874
|
+
[ Opcodes.local_get, counter ],
|
2875
|
+
[ Opcodes.i32_add ],
|
2876
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
2877
|
+
|
2878
|
+
// store to new string ind 0
|
2879
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2880
|
+
|
2881
|
+
// return new string (page)
|
2882
|
+
...number(newPointer),
|
2883
|
+
|
2884
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2885
|
+
|
2886
|
+
[ Opcodes.block, Blocktype.void ],
|
2887
|
+
[ Opcodes.block, Blocktype.void ],
|
2888
|
+
...generate(scope, decl.body),
|
2889
|
+
[ Opcodes.end ],
|
2890
|
+
|
2891
|
+
// increment iter pointer
|
2892
|
+
// [ Opcodes.local_get, pointer ],
|
2893
|
+
// ...number(1, Valtype.i32),
|
2894
|
+
// [ Opcodes.i32_add ],
|
2895
|
+
// [ Opcodes.local_set, pointer ],
|
2896
|
+
|
2897
|
+
// increment counter by 1
|
2898
|
+
[ Opcodes.local_get, counter ],
|
2899
|
+
...number(1, Valtype.i32),
|
2900
|
+
[ Opcodes.i32_add ],
|
2901
|
+
[ Opcodes.local_tee, counter ],
|
2902
|
+
|
2903
|
+
// loop if counter != length
|
2904
|
+
[ Opcodes.local_get, length ],
|
2905
|
+
[ Opcodes.i32_ne ],
|
2906
|
+
[ Opcodes.br_if, 1 ],
|
2907
|
+
|
2908
|
+
[ Opcodes.end ],
|
2909
|
+
[ Opcodes.end ]
|
2910
|
+
],
|
2432
2911
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2433
2912
|
}, Blocktype.void));
|
2434
2913
|
|
@@ -2439,28 +2918,65 @@ const generateForOf = (scope, decl) => {
|
|
2439
2918
|
return out;
|
2440
2919
|
};
|
2441
2920
|
|
2921
|
+
// find the nearest loop in depth map by type
|
2442
2922
|
const getNearestLoop = () => {
|
2443
2923
|
for (let i = depth.length - 1; i >= 0; i--) {
|
2444
|
-
if (
|
2924
|
+
if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
|
2445
2925
|
}
|
2446
2926
|
|
2447
2927
|
return -1;
|
2448
2928
|
};
|
2449
2929
|
|
2450
2930
|
const generateBreak = (scope, decl) => {
|
2451
|
-
const
|
2931
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2932
|
+
const type = depth[target];
|
2933
|
+
|
2934
|
+
// different loop types have different branch offsets
|
2935
|
+
// as they have different wasm block/loop/if structures
|
2936
|
+
// we need to use the right offset by type to branch to the one we want
|
2937
|
+
// for a break: exit the loop without executing anything else inside it
|
2938
|
+
const offset = ({
|
2939
|
+
for: 2, // loop > if (wanted branch) > block (we are here)
|
2940
|
+
while: 2, // loop > if (wanted branch) (we are here)
|
2941
|
+
dowhile: 2, // loop > block (wanted branch) > block (we are here)
|
2942
|
+
forof: 2, // loop > block (wanted branch) > block (we are here)
|
2943
|
+
if: 1 // break inside if, branch 0 to skip the rest of the if
|
2944
|
+
})[type];
|
2945
|
+
|
2452
2946
|
return [
|
2453
|
-
[ Opcodes.br, ...signedLEB128(
|
2947
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2454
2948
|
];
|
2455
2949
|
};
|
2456
2950
|
|
2457
2951
|
const generateContinue = (scope, decl) => {
|
2458
|
-
const
|
2952
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2953
|
+
const type = depth[target];
|
2954
|
+
|
2955
|
+
// different loop types have different branch offsets
|
2956
|
+
// as they have different wasm block/loop/if structures
|
2957
|
+
// we need to use the right offset by type to branch to the one we want
|
2958
|
+
// for a continue: do test for the loop, and then loop depending on that success
|
2959
|
+
const offset = ({
|
2960
|
+
for: 3, // loop (wanted branch) > if > block (we are here)
|
2961
|
+
while: 1, // loop (wanted branch) > if (we are here)
|
2962
|
+
dowhile: 3, // loop > block > block (wanted branch) (we are here)
|
2963
|
+
forof: 3 // loop > block > block (wanted branch) (we are here)
|
2964
|
+
})[type];
|
2965
|
+
|
2459
2966
|
return [
|
2460
|
-
[ Opcodes.br, ...signedLEB128(
|
2967
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2461
2968
|
];
|
2462
2969
|
};
|
2463
2970
|
|
2971
|
+
const generateLabel = (scope, decl) => {
|
2972
|
+
scope.labels ??= new Map();
|
2973
|
+
|
2974
|
+
const name = decl.label.name;
|
2975
|
+
scope.labels.set(name, depth.length);
|
2976
|
+
|
2977
|
+
return generate(scope, decl.body);
|
2978
|
+
};
|
2979
|
+
|
2464
2980
|
const generateThrow = (scope, decl) => {
|
2465
2981
|
scope.throws = true;
|
2466
2982
|
|
@@ -2469,7 +2985,7 @@ const generateThrow = (scope, decl) => {
|
|
2469
2985
|
// hack: throw new X("...") -> throw "..."
|
2470
2986
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2471
2987
|
constructor = decl.argument.callee.name;
|
2472
|
-
message = decl.argument.arguments[0]
|
2988
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2473
2989
|
}
|
2474
2990
|
|
2475
2991
|
if (tags.length === 0) tags.push({
|
@@ -2481,6 +2997,9 @@ const generateThrow = (scope, decl) => {
|
|
2481
2997
|
let exceptId = exceptions.push({ constructor, message }) - 1;
|
2482
2998
|
let tagIdx = tags[0].idx;
|
2483
2999
|
|
3000
|
+
scope.exceptions ??= [];
|
3001
|
+
scope.exceptions.push(exceptId);
|
3002
|
+
|
2484
3003
|
// todo: write a description of how this works lol
|
2485
3004
|
|
2486
3005
|
return [
|
@@ -2490,7 +3009,7 @@ const generateThrow = (scope, decl) => {
|
|
2490
3009
|
};
|
2491
3010
|
|
2492
3011
|
const generateTry = (scope, decl) => {
|
2493
|
-
if (decl.finalizer) return todo('try finally not implemented yet');
|
3012
|
+
if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
|
2494
3013
|
|
2495
3014
|
const out = [];
|
2496
3015
|
|
@@ -2521,29 +3040,35 @@ const generateAssignPat = (scope, decl) => {
|
|
2521
3040
|
// TODO
|
2522
3041
|
// if identifier declared, use that
|
2523
3042
|
// else, use default (right)
|
2524
|
-
return todo('assignment pattern (optional arg)');
|
3043
|
+
return todo(scope, 'assignment pattern (optional arg)');
|
2525
3044
|
};
|
2526
3045
|
|
2527
3046
|
let pages = new Map();
|
2528
|
-
const allocPage = (reason, type) => {
|
3047
|
+
const allocPage = (scope, reason, type) => {
|
2529
3048
|
if (pages.has(reason)) return pages.get(reason).ind;
|
2530
3049
|
|
2531
3050
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2532
3051
|
if (reason.startsWith('string:')) pages.hasString = true;
|
3052
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
3053
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2533
3054
|
|
2534
3055
|
const ind = pages.size;
|
2535
3056
|
pages.set(reason, { ind, type });
|
2536
3057
|
|
2537
|
-
|
3058
|
+
scope.pages ??= new Map();
|
3059
|
+
scope.pages.set(reason, { ind, type });
|
3060
|
+
|
3061
|
+
if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2538
3062
|
|
2539
3063
|
return ind;
|
2540
3064
|
};
|
2541
3065
|
|
3066
|
+
// todo: add scope.pages
|
2542
3067
|
const freePage = reason => {
|
2543
3068
|
const { ind } = pages.get(reason);
|
2544
3069
|
pages.delete(reason);
|
2545
3070
|
|
2546
|
-
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
3071
|
+
if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2547
3072
|
|
2548
3073
|
return ind;
|
2549
3074
|
};
|
@@ -2563,38 +3088,53 @@ const StoreOps = {
|
|
2563
3088
|
f64: Opcodes.f64_store,
|
2564
3089
|
|
2565
3090
|
// expects i32 input!
|
2566
|
-
|
3091
|
+
i8: Opcodes.i32_store8,
|
3092
|
+
i16: Opcodes.i32_store16,
|
2567
3093
|
};
|
2568
3094
|
|
2569
3095
|
let data = [];
|
2570
3096
|
|
2571
|
-
const compileBytes = (val, itemType
|
3097
|
+
const compileBytes = (val, itemType) => {
|
2572
3098
|
// todo: this is a mess and needs confirming / ????
|
2573
3099
|
switch (itemType) {
|
2574
3100
|
case 'i8': return [ val % 256 ];
|
2575
|
-
case 'i16': return [ val % 256,
|
2576
|
-
|
2577
|
-
case 'i32':
|
2578
|
-
|
2579
|
-
return enforceFourBytes(signedLEB128(val));
|
3101
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3102
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3103
|
+
case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
|
3104
|
+
// todo: i64
|
2580
3105
|
|
2581
3106
|
case 'f64': return ieee754_binary64(val);
|
2582
3107
|
}
|
2583
3108
|
};
|
2584
3109
|
|
3110
|
+
const getAllocType = itemType => {
|
3111
|
+
switch (itemType) {
|
3112
|
+
case 'i8': return 'bytestring';
|
3113
|
+
case 'i16': return 'string';
|
3114
|
+
|
3115
|
+
default: return 'array';
|
3116
|
+
}
|
3117
|
+
};
|
3118
|
+
|
2585
3119
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2586
3120
|
const out = [];
|
2587
3121
|
|
3122
|
+
scope.arrays ??= new Map();
|
3123
|
+
|
2588
3124
|
let firstAssign = false;
|
2589
|
-
if (!arrays.has(name) || name === '$undeclared') {
|
3125
|
+
if (!scope.arrays.has(name) || name === '$undeclared') {
|
2590
3126
|
firstAssign = true;
|
2591
3127
|
|
2592
3128
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2593
3129
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2594
|
-
|
3130
|
+
|
3131
|
+
if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
|
3132
|
+
else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2595
3133
|
}
|
2596
3134
|
|
2597
|
-
const pointer = arrays.get(name);
|
3135
|
+
const pointer = scope.arrays.get(name);
|
3136
|
+
|
3137
|
+
const local = global ? globals[name] : scope.locals[name];
|
2598
3138
|
|
2599
3139
|
const useRawElements = !!decl.rawElements;
|
2600
3140
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
@@ -2602,19 +3142,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2602
3142
|
const valtype = itemTypeToValtype[itemType];
|
2603
3143
|
const length = elements.length;
|
2604
3144
|
|
2605
|
-
if (firstAssign && useRawElements) {
|
2606
|
-
|
3145
|
+
if (firstAssign && useRawElements && !Prefs.noData) {
|
3146
|
+
// if length is 0 memory/data will just be 0000... anyway
|
3147
|
+
if (length !== 0) {
|
3148
|
+
let bytes = compileBytes(length, 'i32');
|
2607
3149
|
|
2608
|
-
|
2609
|
-
|
3150
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
3151
|
+
if (elements[i] == null) continue;
|
2610
3152
|
|
2611
|
-
|
2612
|
-
|
3153
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
3154
|
+
}
|
2613
3155
|
|
2614
|
-
|
2615
|
-
|
2616
|
-
|
2617
|
-
|
3156
|
+
const ind = data.push({
|
3157
|
+
offset: pointer,
|
3158
|
+
bytes
|
3159
|
+
}) - 1;
|
3160
|
+
|
3161
|
+
scope.data ??= [];
|
3162
|
+
scope.data.push(ind);
|
3163
|
+
}
|
2618
3164
|
|
2619
3165
|
// local value as pointer
|
2620
3166
|
out.push(...number(pointer));
|
@@ -2622,11 +3168,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2622
3168
|
return [ out, pointer ];
|
2623
3169
|
}
|
2624
3170
|
|
3171
|
+
const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
|
3172
|
+
if (pointerTmp != null) {
|
3173
|
+
out.push(
|
3174
|
+
[ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
3175
|
+
Opcodes.i32_to_u,
|
3176
|
+
[ Opcodes.local_set, pointerTmp ]
|
3177
|
+
);
|
3178
|
+
}
|
3179
|
+
|
3180
|
+
const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
|
3181
|
+
|
2625
3182
|
// store length as 0th array
|
2626
3183
|
out.push(
|
2627
|
-
...
|
3184
|
+
...pointerWasm,
|
2628
3185
|
...number(length, Valtype.i32),
|
2629
|
-
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1,
|
3186
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
|
2630
3187
|
);
|
2631
3188
|
|
2632
3189
|
const storeOp = StoreOps[itemType];
|
@@ -2635,43 +3192,92 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2635
3192
|
if (elements[i] == null) continue;
|
2636
3193
|
|
2637
3194
|
out.push(
|
2638
|
-
...
|
3195
|
+
...pointerWasm,
|
2639
3196
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2640
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(
|
3197
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2641
3198
|
);
|
2642
3199
|
}
|
2643
3200
|
|
2644
3201
|
// local value as pointer
|
2645
|
-
out.push(...
|
3202
|
+
out.push(...pointerWasm, Opcodes.i32_from_u);
|
2646
3203
|
|
2647
3204
|
return [ out, pointer ];
|
2648
3205
|
};
|
2649
3206
|
|
2650
|
-
const
|
3207
|
+
const byteStringable = str => {
|
3208
|
+
if (!Prefs.bytestring) return false;
|
3209
|
+
|
3210
|
+
for (let i = 0; i < str.length; i++) {
|
3211
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
3212
|
+
}
|
3213
|
+
|
3214
|
+
return true;
|
3215
|
+
};
|
3216
|
+
|
3217
|
+
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2651
3218
|
const rawElements = new Array(str.length);
|
3219
|
+
let byteStringable = Prefs.bytestring;
|
2652
3220
|
for (let i = 0; i < str.length; i++) {
|
2653
|
-
|
3221
|
+
const c = str.charCodeAt(i);
|
3222
|
+
rawElements[i] = c;
|
3223
|
+
|
3224
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2654
3225
|
}
|
2655
3226
|
|
3227
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
3228
|
+
|
2656
3229
|
return makeArray(scope, {
|
2657
3230
|
rawElements
|
2658
|
-
}, global, name, false, 'i16')[0];
|
3231
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2659
3232
|
};
|
2660
3233
|
|
2661
|
-
let arrays = new Map();
|
2662
3234
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
2663
3235
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
2664
3236
|
};
|
2665
3237
|
|
2666
3238
|
export const generateMember = (scope, decl, _global, _name) => {
|
2667
3239
|
const name = decl.object.name;
|
2668
|
-
const pointer = arrays
|
3240
|
+
const pointer = scope.arrays?.get(name);
|
2669
3241
|
|
2670
|
-
const aotPointer = pointer != null;
|
3242
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
3243
|
+
|
3244
|
+
// hack: .name
|
3245
|
+
if (decl.property.name === 'name') {
|
3246
|
+
if (hasFuncWithName(name)) {
|
3247
|
+
let nameProp = name;
|
3248
|
+
|
3249
|
+
// eg: __String_prototype_toLowerCase -> toLowerCase
|
3250
|
+
if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
|
3251
|
+
|
3252
|
+
return makeString(scope, name, _global, _name, true);
|
3253
|
+
} else {
|
3254
|
+
return generate(scope, DEFAULT_VALUE);
|
3255
|
+
}
|
3256
|
+
}
|
2671
3257
|
|
2672
3258
|
// hack: .length
|
2673
3259
|
if (decl.property.name === 'length') {
|
2674
|
-
|
3260
|
+
const func = funcs.find(x => x.name === name);
|
3261
|
+
if (func) {
|
3262
|
+
const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
|
3263
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
3264
|
+
return number(typedParams ? func.params.length / 2 : func.params.length);
|
3265
|
+
}
|
3266
|
+
|
3267
|
+
if (builtinFuncs[name + '$constructor']) {
|
3268
|
+
const regularFunc = builtinFuncs[name];
|
3269
|
+
const regularParams = regularFunc.typedParams ? (regularFunc.params.length / 2) : regularFunc.params.length;
|
3270
|
+
|
3271
|
+
const constructorFunc = builtinFuncs[name + '$constructor'];
|
3272
|
+
const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
|
3273
|
+
|
3274
|
+
return number(Math.max(regularParams, constructorParams));
|
3275
|
+
}
|
3276
|
+
|
3277
|
+
if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
|
3278
|
+
if (importedFuncs[name]) return number(importedFuncs[name].params);
|
3279
|
+
if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
|
3280
|
+
|
2675
3281
|
return [
|
2676
3282
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2677
3283
|
...generate(scope, decl.object),
|
@@ -2683,19 +3289,22 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2683
3289
|
];
|
2684
3290
|
}
|
2685
3291
|
|
3292
|
+
const object = generate(scope, decl.object);
|
3293
|
+
const property = generate(scope, decl.property);
|
3294
|
+
|
2686
3295
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2687
3296
|
// hack: this is naughty and will break things!
|
2688
3297
|
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2689
|
-
if (pages.
|
3298
|
+
if (pages.hasAnyString) {
|
2690
3299
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2691
3300
|
rawElements: new Array(1)
|
2692
3301
|
}, _global, _name, true, 'i16');
|
2693
3302
|
}
|
2694
3303
|
|
2695
3304
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2696
|
-
[TYPES.
|
3305
|
+
[TYPES.array]: [
|
2697
3306
|
// get index as valtype
|
2698
|
-
...
|
3307
|
+
...property,
|
2699
3308
|
|
2700
3309
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2701
3310
|
Opcodes.i32_to_u,
|
@@ -2703,7 +3312,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2703
3312
|
[ Opcodes.i32_mul ],
|
2704
3313
|
|
2705
3314
|
...(aotPointer ? [] : [
|
2706
|
-
...
|
3315
|
+
...object,
|
2707
3316
|
Opcodes.i32_to_u,
|
2708
3317
|
[ Opcodes.i32_add ]
|
2709
3318
|
]),
|
@@ -2712,7 +3321,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2712
3321
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2713
3322
|
|
2714
3323
|
...number(TYPES.number, Valtype.i32),
|
2715
|
-
setLastType(scope)
|
3324
|
+
...setLastType(scope)
|
2716
3325
|
],
|
2717
3326
|
|
2718
3327
|
[TYPES.string]: [
|
@@ -2722,14 +3331,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2722
3331
|
|
2723
3332
|
...number(0, Valtype.i32), // base 0 for store later
|
2724
3333
|
|
2725
|
-
...
|
2726
|
-
|
3334
|
+
...property,
|
2727
3335
|
Opcodes.i32_to_u,
|
3336
|
+
|
2728
3337
|
...number(ValtypeSize.i16, Valtype.i32),
|
2729
3338
|
[ Opcodes.i32_mul ],
|
2730
3339
|
|
2731
3340
|
...(aotPointer ? [] : [
|
2732
|
-
...
|
3341
|
+
...object,
|
2733
3342
|
Opcodes.i32_to_u,
|
2734
3343
|
[ Opcodes.i32_add ]
|
2735
3344
|
]),
|
@@ -2744,10 +3353,38 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2744
3353
|
...number(newPointer),
|
2745
3354
|
|
2746
3355
|
...number(TYPES.string, Valtype.i32),
|
2747
|
-
setLastType(scope)
|
3356
|
+
...setLastType(scope)
|
3357
|
+
],
|
3358
|
+
[TYPES.bytestring]: [
|
3359
|
+
// setup new/out array
|
3360
|
+
...newOut,
|
3361
|
+
[ Opcodes.drop ],
|
3362
|
+
|
3363
|
+
...number(0, Valtype.i32), // base 0 for store later
|
3364
|
+
|
3365
|
+
...property,
|
3366
|
+
Opcodes.i32_to_u,
|
3367
|
+
|
3368
|
+
...(aotPointer ? [] : [
|
3369
|
+
...object,
|
3370
|
+
Opcodes.i32_to_u,
|
3371
|
+
[ Opcodes.i32_add ]
|
3372
|
+
]),
|
3373
|
+
|
3374
|
+
// load current string ind {arg}
|
3375
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
3376
|
+
|
3377
|
+
// store to new string ind 0
|
3378
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
3379
|
+
|
3380
|
+
// return new string (page)
|
3381
|
+
...number(newPointer),
|
3382
|
+
|
3383
|
+
...number(TYPES.bytestring, Valtype.i32),
|
3384
|
+
...setLastType(scope)
|
2748
3385
|
],
|
2749
3386
|
|
2750
|
-
default:
|
3387
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
|
2751
3388
|
});
|
2752
3389
|
};
|
2753
3390
|
|
@@ -2757,25 +3394,36 @@ const objectHack = node => {
|
|
2757
3394
|
if (!node) return node;
|
2758
3395
|
|
2759
3396
|
if (node.type === 'MemberExpression') {
|
2760
|
-
|
3397
|
+
const out = (() => {
|
3398
|
+
if (node.computed || node.optional) return;
|
3399
|
+
|
3400
|
+
let objectName = node.object.name;
|
2761
3401
|
|
2762
|
-
|
3402
|
+
// if object is not identifier or another member exp, give up
|
3403
|
+
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
|
3404
|
+
if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
|
2763
3405
|
|
2764
|
-
|
2765
|
-
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
3406
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2766
3407
|
|
2767
|
-
|
3408
|
+
// if .name or .length, give up (hack within a hack!)
|
3409
|
+
if (['name', 'length'].includes(node.property.name)) {
|
3410
|
+
node.object = objectHack(node.object);
|
3411
|
+
return;
|
3412
|
+
}
|
2768
3413
|
|
2769
|
-
|
2770
|
-
|
3414
|
+
// no object name, give up
|
3415
|
+
if (!objectName) return;
|
2771
3416
|
|
2772
|
-
|
2773
|
-
|
3417
|
+
const name = '__' + objectName + '_' + node.property.name;
|
3418
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2774
3419
|
|
2775
|
-
|
2776
|
-
|
2777
|
-
|
2778
|
-
|
3420
|
+
return {
|
3421
|
+
type: 'Identifier',
|
3422
|
+
name
|
3423
|
+
};
|
3424
|
+
})();
|
3425
|
+
|
3426
|
+
if (out) return out;
|
2779
3427
|
}
|
2780
3428
|
|
2781
3429
|
for (const x in node) {
|
@@ -2789,8 +3437,8 @@ const objectHack = node => {
|
|
2789
3437
|
};
|
2790
3438
|
|
2791
3439
|
const generateFunc = (scope, decl) => {
|
2792
|
-
if (decl.async) return todo('async functions are not supported');
|
2793
|
-
if (decl.generator) return todo('generator functions are not supported');
|
3440
|
+
if (decl.async) return todo(scope, 'async functions are not supported');
|
3441
|
+
if (decl.generator) return todo(scope, 'generator functions are not supported');
|
2794
3442
|
|
2795
3443
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2796
3444
|
const params = decl.params ?? [];
|
@@ -2806,6 +3454,11 @@ const generateFunc = (scope, decl) => {
|
|
2806
3454
|
name
|
2807
3455
|
};
|
2808
3456
|
|
3457
|
+
if (typedInput && decl.returnType) {
|
3458
|
+
innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
|
3459
|
+
innerScope.returns = [ valtypeBinary ];
|
3460
|
+
}
|
3461
|
+
|
2809
3462
|
for (let i = 0; i < params.length; i++) {
|
2810
3463
|
allocVar(innerScope, params[i].name, false);
|
2811
3464
|
|
@@ -2827,13 +3480,13 @@ const generateFunc = (scope, decl) => {
|
|
2827
3480
|
const func = {
|
2828
3481
|
name,
|
2829
3482
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2830
|
-
|
2831
|
-
|
2832
|
-
throws: innerScope.throws,
|
2833
|
-
index: currentFuncIndex++
|
3483
|
+
index: currentFuncIndex++,
|
3484
|
+
...innerScope
|
2834
3485
|
};
|
2835
3486
|
funcIndex[name] = func.index;
|
2836
3487
|
|
3488
|
+
if (name === 'main') func.gotLastType = true;
|
3489
|
+
|
2837
3490
|
// quick hack fixes
|
2838
3491
|
for (const inst of wasm) {
|
2839
3492
|
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
@@ -2885,7 +3538,7 @@ const internalConstrs = {
|
|
2885
3538
|
|
2886
3539
|
// todo: check in wasm instead of here
|
2887
3540
|
const literalValue = arg.value ?? 0;
|
2888
|
-
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
|
3541
|
+
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
|
2889
3542
|
|
2890
3543
|
return [
|
2891
3544
|
...number(0, Valtype.i32),
|
@@ -2896,7 +3549,8 @@ const internalConstrs = {
|
|
2896
3549
|
...number(pointer)
|
2897
3550
|
];
|
2898
3551
|
},
|
2899
|
-
type: TYPES.
|
3552
|
+
type: TYPES.array,
|
3553
|
+
length: 1
|
2900
3554
|
},
|
2901
3555
|
|
2902
3556
|
__Array_of: {
|
@@ -2907,27 +3561,134 @@ const internalConstrs = {
|
|
2907
3561
|
elements: decl.arguments
|
2908
3562
|
}, global, name);
|
2909
3563
|
},
|
2910
|
-
type: TYPES.
|
3564
|
+
type: TYPES.array,
|
3565
|
+
notConstr: true,
|
3566
|
+
length: 0
|
3567
|
+
},
|
3568
|
+
|
3569
|
+
__Porffor_fastOr: {
|
3570
|
+
generate: (scope, decl) => {
|
3571
|
+
const out = [];
|
3572
|
+
|
3573
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3574
|
+
out.push(
|
3575
|
+
...generate(scope, decl.arguments[i]),
|
3576
|
+
Opcodes.i32_to_u,
|
3577
|
+
...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
|
3578
|
+
);
|
3579
|
+
}
|
3580
|
+
|
3581
|
+
out.push(Opcodes.i32_from_u);
|
3582
|
+
|
3583
|
+
return out;
|
3584
|
+
},
|
3585
|
+
type: TYPES.boolean,
|
2911
3586
|
notConstr: true
|
2912
|
-
}
|
2913
|
-
|
3587
|
+
},
|
3588
|
+
|
3589
|
+
__Porffor_fastAnd: {
|
3590
|
+
generate: (scope, decl) => {
|
3591
|
+
const out = [];
|
2914
3592
|
|
2915
|
-
|
2916
|
-
|
2917
|
-
|
2918
|
-
|
2919
|
-
|
2920
|
-
|
2921
|
-
|
2922
|
-
|
2923
|
-
|
2924
|
-
|
2925
|
-
|
2926
|
-
|
2927
|
-
|
3593
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3594
|
+
out.push(
|
3595
|
+
...generate(scope, decl.arguments[i]),
|
3596
|
+
Opcodes.i32_to_u,
|
3597
|
+
...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
|
3598
|
+
);
|
3599
|
+
}
|
3600
|
+
|
3601
|
+
out.push(Opcodes.i32_from_u);
|
3602
|
+
|
3603
|
+
return out;
|
3604
|
+
},
|
3605
|
+
type: TYPES.boolean,
|
3606
|
+
notConstr: true
|
3607
|
+
},
|
2928
3608
|
|
2929
|
-
|
2930
|
-
|
3609
|
+
Boolean: {
|
3610
|
+
generate: (scope, decl) => {
|
3611
|
+
// todo: boolean object when used as constructor
|
3612
|
+
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
3613
|
+
return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
|
3614
|
+
},
|
3615
|
+
type: TYPES.boolean,
|
3616
|
+
length: 1
|
3617
|
+
},
|
3618
|
+
|
3619
|
+
__Math_max: {
|
3620
|
+
generate: (scope, decl) => {
|
3621
|
+
const out = [
|
3622
|
+
...number(-Infinity)
|
3623
|
+
];
|
3624
|
+
|
3625
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3626
|
+
out.push(
|
3627
|
+
...generate(scope, decl.arguments[i]),
|
3628
|
+
[ Opcodes.f64_max ]
|
3629
|
+
);
|
3630
|
+
}
|
3631
|
+
|
3632
|
+
return out;
|
3633
|
+
},
|
3634
|
+
type: TYPES.number,
|
3635
|
+
notConstr: true,
|
3636
|
+
length: 2
|
3637
|
+
},
|
3638
|
+
|
3639
|
+
__Math_min: {
|
3640
|
+
generate: (scope, decl) => {
|
3641
|
+
const out = [
|
3642
|
+
...number(Infinity)
|
3643
|
+
];
|
3644
|
+
|
3645
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3646
|
+
out.push(
|
3647
|
+
...generate(scope, decl.arguments[i]),
|
3648
|
+
[ Opcodes.f64_min ]
|
3649
|
+
);
|
3650
|
+
}
|
3651
|
+
|
3652
|
+
return out;
|
3653
|
+
},
|
3654
|
+
type: TYPES.number,
|
3655
|
+
notConstr: true,
|
3656
|
+
length: 2
|
3657
|
+
},
|
3658
|
+
|
3659
|
+
__console_log: {
|
3660
|
+
generate: (scope, decl) => {
|
3661
|
+
const out = [];
|
3662
|
+
|
3663
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3664
|
+
out.push(
|
3665
|
+
...generateCall(scope, {
|
3666
|
+
callee: {
|
3667
|
+
type: 'Identifier',
|
3668
|
+
name: '__Porffor_print'
|
3669
|
+
},
|
3670
|
+
arguments: [ decl.arguments[i] ]
|
3671
|
+
}),
|
3672
|
+
|
3673
|
+
// print space
|
3674
|
+
...number(32),
|
3675
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3676
|
+
);
|
3677
|
+
}
|
3678
|
+
|
3679
|
+
// print newline
|
3680
|
+
out.push(
|
3681
|
+
...number(10),
|
3682
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3683
|
+
);
|
3684
|
+
|
3685
|
+
return out;
|
3686
|
+
},
|
3687
|
+
type: TYPES.undefined,
|
3688
|
+
notConstr: true,
|
3689
|
+
length: 0
|
3690
|
+
}
|
3691
|
+
};
|
2931
3692
|
|
2932
3693
|
export default program => {
|
2933
3694
|
globals = {};
|
@@ -2937,20 +3698,23 @@ export default program => {
|
|
2937
3698
|
funcs = [];
|
2938
3699
|
funcIndex = {};
|
2939
3700
|
depth = [];
|
2940
|
-
arrays = new Map();
|
2941
3701
|
pages = new Map();
|
2942
3702
|
data = [];
|
2943
3703
|
currentFuncIndex = importedFuncs.length;
|
2944
3704
|
|
2945
3705
|
globalThis.valtype = 'f64';
|
2946
3706
|
|
2947
|
-
const valtypeOpt = process.argv.find(x => x.startsWith('
|
3707
|
+
const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
|
2948
3708
|
if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
|
2949
3709
|
|
2950
3710
|
globalThis.valtypeBinary = Valtype[valtype];
|
2951
3711
|
|
2952
3712
|
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
2953
3713
|
|
3714
|
+
globalThis.pageSize = PageSize;
|
3715
|
+
const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
|
3716
|
+
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3717
|
+
|
2954
3718
|
// set generic opcodes for current valtype
|
2955
3719
|
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
2956
3720
|
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
@@ -2959,10 +3723,10 @@ export default program => {
|
|
2959
3723
|
Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
|
2960
3724
|
Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
|
2961
3725
|
|
2962
|
-
Opcodes.i32_to = [ [
|
2963
|
-
Opcodes.i32_to_u = [ [
|
2964
|
-
Opcodes.i32_from = [ [
|
2965
|
-
Opcodes.i32_from_u = [ [
|
3726
|
+
Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
|
3727
|
+
Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
|
3728
|
+
Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
|
3729
|
+
Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
|
2966
3730
|
|
2967
3731
|
Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
|
2968
3732
|
Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
|
@@ -2975,10 +3739,6 @@ export default program => {
|
|
2975
3739
|
|
2976
3740
|
program.id = { name: 'main' };
|
2977
3741
|
|
2978
|
-
globalThis.pageSize = PageSize;
|
2979
|
-
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
2980
|
-
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
2981
|
-
|
2982
3742
|
const scope = {
|
2983
3743
|
locals: {},
|
2984
3744
|
localInd: 0
|
@@ -2989,7 +3749,7 @@ export default program => {
|
|
2989
3749
|
body: program.body
|
2990
3750
|
};
|
2991
3751
|
|
2992
|
-
if (
|
3752
|
+
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
2993
3753
|
|
2994
3754
|
generateFunc(scope, program);
|
2995
3755
|
|
@@ -3006,7 +3766,11 @@ export default program => {
|
|
3006
3766
|
}
|
3007
3767
|
|
3008
3768
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
3009
|
-
|
3769
|
+
if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
|
3770
|
+
main.wasm.splice(main.wasm.length - 1, 1);
|
3771
|
+
} else {
|
3772
|
+
main.returns = [];
|
3773
|
+
}
|
3010
3774
|
}
|
3011
3775
|
|
3012
3776
|
if (lastInst[0] === Opcodes.call) {
|