porffor 0.2.0-6aff0fa → 0.2.0-6bc63ef
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 +115 -82
- package/asur/index.js +624 -340
- package/byg/index.js +216 -0
- package/compiler/2c.js +2 -53
- package/compiler/{sections.js → assemble.js} +60 -14
- 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 +7 -84
- package/compiler/builtins/boolean.ts +18 -0
- package/compiler/builtins/crypto.ts +120 -0
- package/compiler/builtins/date.ts +2067 -0
- package/compiler/builtins/escape.ts +141 -0
- package/compiler/builtins/function.ts +5 -0
- package/compiler/builtins/int.ts +145 -0
- package/compiler/builtins/number.ts +529 -0
- package/compiler/builtins/object.ts +4 -0
- package/compiler/builtins/porffor.d.ts +44 -7
- package/compiler/builtins/set.ts +187 -0
- package/compiler/builtins/string.ts +1080 -0
- package/compiler/builtins.js +400 -120
- package/compiler/{codeGen.js → codegen.js} +850 -402
- package/compiler/decompile.js +2 -3
- package/compiler/embedding.js +22 -22
- package/compiler/encoding.js +94 -10
- package/compiler/expression.js +1 -1
- package/compiler/generated_builtins.js +1613 -3
- package/compiler/index.js +16 -16
- package/compiler/log.js +2 -2
- package/compiler/opt.js +28 -27
- package/compiler/parse.js +36 -30
- package/compiler/precompile.js +37 -46
- package/compiler/prefs.js +7 -6
- package/compiler/prototype.js +20 -36
- package/compiler/types.js +38 -0
- package/compiler/wasmSpec.js +14 -1
- package/compiler/wrap.js +79 -69
- package/package.json +9 -5
- package/porf +2 -0
- package/rhemyn/compile.js +44 -26
- package/rhemyn/parse.js +322 -320
- package/rhemyn/test/parse.js +58 -58
- package/runner/compare.js +33 -34
- package/runner/debug.js +117 -0
- package/runner/index.js +69 -12
- package/runner/profiler.js +22 -30
- package/runner/repl.js +40 -13
- package/runner/sizes.js +37 -37
- package/runner/version.js +3 -3
- package/runner/info.js +0 -89
- package/runner/transform.js +0 -15
- package/util/enum.js +0 -20
@@ -1,12 +1,13 @@
|
|
1
|
-
import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from
|
2
|
-
import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from
|
3
|
-
import { operatorOpcode } from
|
4
|
-
import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from
|
5
|
-
import { PrototypeFuncs } from
|
6
|
-
import { number
|
7
|
-
import {
|
8
|
-
import
|
9
|
-
import
|
1
|
+
import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from './wasmSpec.js';
|
2
|
+
import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from './encoding.js';
|
3
|
+
import { operatorOpcode } from './expression.js';
|
4
|
+
import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from './builtins.js';
|
5
|
+
import { PrototypeFuncs } from './prototype.js';
|
6
|
+
import { number } from './embedding.js';
|
7
|
+
import { TYPES, TYPE_NAMES } from './types.js';
|
8
|
+
import * as Rhemyn from '../rhemyn/compile.js';
|
9
|
+
import parse from './parse.js';
|
10
|
+
import { log } from './log.js';
|
10
11
|
import Prefs from './prefs.js';
|
11
12
|
|
12
13
|
let globals = {};
|
@@ -18,44 +19,32 @@ let funcIndex = {};
|
|
18
19
|
let currentFuncIndex = importedFuncs.length;
|
19
20
|
let builtinFuncs = {}, builtinVars = {}, prototypeFuncs = {};
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
code.push(...number(n));
|
26
|
-
|
27
|
-
code.push(Opcodes.call);
|
28
|
-
code.push(...unsignedLEB128(0));
|
29
|
-
};
|
30
|
-
|
31
|
-
for (let i = 0; i < str.length; i++) {
|
32
|
-
logChar(str.charCodeAt(i));
|
22
|
+
class TodoError extends Error {
|
23
|
+
constructor(message) {
|
24
|
+
super(message);
|
25
|
+
this.name = 'TodoError';
|
33
26
|
}
|
27
|
+
}
|
28
|
+
const todo = (scope, msg, expectsValue = undefined) => {
|
29
|
+
switch (Prefs.todoTime ?? 'runtime') {
|
30
|
+
case 'compile':
|
31
|
+
throw new TodoError(msg);
|
34
32
|
|
35
|
-
|
36
|
-
|
37
|
-
return code;
|
38
|
-
};
|
33
|
+
case 'runtime':
|
34
|
+
return internalThrow(scope, 'TodoError', msg, expectsValue);
|
39
35
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
this.name = 'TodoError';
|
45
|
-
}
|
36
|
+
// return [
|
37
|
+
// ...debug(`todo! ${msg}`),
|
38
|
+
// [ Opcodes.unreachable ]
|
39
|
+
// ];
|
46
40
|
}
|
47
|
-
|
48
|
-
throw new TodoError(`todo: ${msg}`);
|
49
|
-
|
50
|
-
const code = [];
|
51
|
-
|
52
|
-
code.push(...debug(`todo! ` + msg));
|
53
|
-
code.push(Opcodes.unreachable);
|
54
|
-
|
55
|
-
return code;
|
56
41
|
};
|
57
42
|
|
58
43
|
const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
|
44
|
+
const hasFuncWithName = name => {
|
45
|
+
const func = funcs.find(x => x.name === name);
|
46
|
+
return !!(func || builtinFuncs[name] || importedFuncs[name] || internalConstrs[name]);
|
47
|
+
};
|
59
48
|
const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
|
60
49
|
switch (decl.type) {
|
61
50
|
case 'BinaryExpression':
|
@@ -105,7 +94,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
105
94
|
return generateUnary(scope, decl);
|
106
95
|
|
107
96
|
case 'UpdateExpression':
|
108
|
-
return generateUpdate(scope, decl);
|
97
|
+
return generateUpdate(scope, decl, global, name, valueUnused);
|
109
98
|
|
110
99
|
case 'IfStatement':
|
111
100
|
return generateIf(scope, decl);
|
@@ -116,6 +105,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
116
105
|
case 'WhileStatement':
|
117
106
|
return generateWhile(scope, decl);
|
118
107
|
|
108
|
+
case 'DoWhileStatement':
|
109
|
+
return generateDoWhile(scope, decl);
|
110
|
+
|
119
111
|
case 'ForOfStatement':
|
120
112
|
return generateForOf(scope, decl);
|
121
113
|
|
@@ -125,6 +117,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
125
117
|
case 'ContinueStatement':
|
126
118
|
return generateContinue(scope, decl);
|
127
119
|
|
120
|
+
case 'LabeledStatement':
|
121
|
+
return generateLabel(scope, decl);
|
122
|
+
|
128
123
|
case 'EmptyStatement':
|
129
124
|
return generateEmpty(scope, decl);
|
130
125
|
|
@@ -138,7 +133,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
138
133
|
return generateTry(scope, decl);
|
139
134
|
|
140
135
|
case 'DebuggerStatement':
|
141
|
-
// todo:
|
136
|
+
// todo: hook into terminal debugger
|
142
137
|
return [];
|
143
138
|
|
144
139
|
case 'ArrayExpression':
|
@@ -152,10 +147,11 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
152
147
|
const funcsBefore = funcs.length;
|
153
148
|
generate(scope, decl.declaration);
|
154
149
|
|
155
|
-
if (funcsBefore
|
156
|
-
|
157
|
-
|
158
|
-
|
150
|
+
if (funcsBefore !== funcs.length) {
|
151
|
+
// new func added
|
152
|
+
const newFunc = funcs[funcs.length - 1];
|
153
|
+
newFunc.export = true;
|
154
|
+
}
|
159
155
|
|
160
156
|
return [];
|
161
157
|
|
@@ -186,7 +182,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
186
182
|
}
|
187
183
|
|
188
184
|
let inst = Opcodes[asm[0].replace('.', '_')];
|
189
|
-
if (
|
185
|
+
if (inst == null) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
190
186
|
|
191
187
|
if (!Array.isArray(inst)) inst = [ inst ];
|
192
188
|
const immediates = asm.slice(1).map(x => {
|
@@ -195,40 +191,49 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
195
191
|
return int;
|
196
192
|
});
|
197
193
|
|
198
|
-
out.push([ ...inst, ...immediates ]);
|
194
|
+
out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
|
199
195
|
}
|
200
196
|
|
201
197
|
return out;
|
202
198
|
},
|
203
199
|
|
204
200
|
__Porffor_bs: str => [
|
205
|
-
...makeString(scope, str,
|
201
|
+
...makeString(scope, str, global, name, true),
|
206
202
|
|
207
|
-
...
|
208
|
-
|
203
|
+
...(name ? setType(scope, name, TYPES.bytestring) : [
|
204
|
+
...number(TYPES.bytestring, Valtype.i32),
|
205
|
+
...setLastType(scope)
|
206
|
+
])
|
209
207
|
],
|
210
208
|
__Porffor_s: str => [
|
211
|
-
...makeString(scope, str,
|
209
|
+
...makeString(scope, str, global, name, false),
|
212
210
|
|
213
|
-
...
|
214
|
-
|
211
|
+
...(name ? setType(scope, name, TYPES.string) : [
|
212
|
+
...number(TYPES.string, Valtype.i32),
|
213
|
+
...setLastType(scope)
|
214
|
+
])
|
215
215
|
],
|
216
216
|
};
|
217
217
|
|
218
|
-
const
|
218
|
+
const func = decl.tag.name;
|
219
219
|
// hack for inline asm
|
220
|
-
if (!funcs[
|
220
|
+
if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
|
221
221
|
|
222
222
|
const { quasis, expressions } = decl.quasi;
|
223
223
|
let str = quasis[0].value.raw;
|
224
224
|
|
225
225
|
for (let i = 0; i < expressions.length; i++) {
|
226
226
|
const e = expressions[i];
|
227
|
-
|
227
|
+
if (!e.name) {
|
228
|
+
if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
|
229
|
+
str += lookupName(scope, e.left.name)[0].idx + e.right.value;
|
230
|
+
} else todo(scope, 'unsupported expression in intrinsic');
|
231
|
+
} else str += lookupName(scope, e.name)[0].idx;
|
232
|
+
|
228
233
|
str += quasis[i + 1].value.raw;
|
229
234
|
}
|
230
235
|
|
231
|
-
return funcs[
|
236
|
+
return funcs[func](str);
|
232
237
|
}
|
233
238
|
|
234
239
|
default:
|
@@ -238,7 +243,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
238
243
|
return [];
|
239
244
|
}
|
240
245
|
|
241
|
-
return todo(`no generation for ${decl.type}!`);
|
246
|
+
return todo(scope, `no generation for ${decl.type}!`);
|
242
247
|
}
|
243
248
|
};
|
244
249
|
|
@@ -266,7 +271,7 @@ const lookupName = (scope, _name) => {
|
|
266
271
|
return [ undefined, undefined ];
|
267
272
|
};
|
268
273
|
|
269
|
-
const internalThrow = (scope, constructor, message, expectsValue =
|
274
|
+
const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
|
270
275
|
...generateThrow(scope, {
|
271
276
|
argument: {
|
272
277
|
type: 'NewExpression',
|
@@ -293,7 +298,7 @@ const generateIdent = (scope, decl) => {
|
|
293
298
|
|
294
299
|
let wasm = builtinVars[name];
|
295
300
|
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
|
296
|
-
return wasm;
|
301
|
+
return wasm.slice();
|
297
302
|
}
|
298
303
|
|
299
304
|
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
@@ -301,6 +306,11 @@ const generateIdent = (scope, decl) => {
|
|
301
306
|
return number(1);
|
302
307
|
}
|
303
308
|
|
309
|
+
if (isExistingProtoFunc(name)) {
|
310
|
+
// todo: return an actual something
|
311
|
+
return number(1);
|
312
|
+
}
|
313
|
+
|
304
314
|
if (local?.idx === undefined) {
|
305
315
|
// no local var with name
|
306
316
|
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
@@ -331,14 +341,18 @@ const generateReturn = (scope, decl) => {
|
|
331
341
|
// just bare "return"
|
332
342
|
return [
|
333
343
|
...number(UNDEFINED), // "undefined" if func returns
|
334
|
-
...
|
344
|
+
...(scope.returnType != null ? [] : [
|
345
|
+
...number(TYPES.undefined, Valtype.i32) // type undefined
|
346
|
+
]),
|
335
347
|
[ Opcodes.return ]
|
336
348
|
];
|
337
349
|
}
|
338
350
|
|
339
351
|
return [
|
340
352
|
...generate(scope, decl.argument),
|
341
|
-
...
|
353
|
+
...(scope.returnType != null ? [] : [
|
354
|
+
...getNodeType(scope, decl.argument)
|
355
|
+
]),
|
342
356
|
[ Opcodes.return ]
|
343
357
|
];
|
344
358
|
};
|
@@ -352,7 +366,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
352
366
|
return idx;
|
353
367
|
};
|
354
368
|
|
355
|
-
const isIntOp = op => op && (op[0] >=
|
369
|
+
const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
|
370
|
+
const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
|
356
371
|
|
357
372
|
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
358
373
|
const checks = {
|
@@ -361,7 +376,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
361
376
|
'??': nullish
|
362
377
|
};
|
363
378
|
|
364
|
-
if (!checks[op]) return todo(`logic operator ${op} not implemented yet
|
379
|
+
if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
|
365
380
|
|
366
381
|
// generic structure for {a} OP {b}
|
367
382
|
// -->
|
@@ -369,8 +384,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
369
384
|
|
370
385
|
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
371
386
|
// (like if we are in an if condition - very common)
|
372
|
-
const leftIsInt =
|
373
|
-
const rightIsInt =
|
387
|
+
const leftIsInt = isFloatToIntOp(left[left.length - 1]);
|
388
|
+
const rightIsInt = isFloatToIntOp(right[right.length - 1]);
|
374
389
|
|
375
390
|
const canInt = leftIsInt && rightIsInt;
|
376
391
|
|
@@ -387,12 +402,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
387
402
|
...right,
|
388
403
|
// note type
|
389
404
|
...rightType,
|
390
|
-
setLastType(scope),
|
405
|
+
...setLastType(scope),
|
391
406
|
[ Opcodes.else ],
|
392
407
|
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
393
408
|
// note type
|
394
409
|
...leftType,
|
395
|
-
setLastType(scope),
|
410
|
+
...setLastType(scope),
|
396
411
|
[ Opcodes.end ],
|
397
412
|
Opcodes.i32_from
|
398
413
|
];
|
@@ -406,17 +421,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
406
421
|
...right,
|
407
422
|
// note type
|
408
423
|
...rightType,
|
409
|
-
setLastType(scope),
|
424
|
+
...setLastType(scope),
|
410
425
|
[ Opcodes.else ],
|
411
426
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
412
427
|
// note type
|
413
428
|
...leftType,
|
414
|
-
setLastType(scope),
|
429
|
+
...setLastType(scope),
|
415
430
|
[ Opcodes.end ]
|
416
431
|
];
|
417
432
|
};
|
418
433
|
|
419
|
-
const concatStrings = (scope, left, right, global, name, assign) => {
|
434
|
+
const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
|
420
435
|
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
421
436
|
// todo: convert left and right to strings if not
|
422
437
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -426,8 +441,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
426
441
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
427
442
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
428
443
|
|
429
|
-
if (assign) {
|
430
|
-
const pointer = arrays
|
444
|
+
if (assign && Prefs.aotPointerOpt) {
|
445
|
+
const pointer = scope.arrays?.get(name ?? '$undeclared');
|
431
446
|
|
432
447
|
return [
|
433
448
|
// setup right
|
@@ -452,11 +467,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
452
467
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
|
453
468
|
|
454
469
|
// copy right
|
455
|
-
// dst = out pointer + length size + current length *
|
470
|
+
// dst = out pointer + length size + current length * sizeof valtype
|
456
471
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
457
472
|
|
458
473
|
[ Opcodes.local_get, leftLength ],
|
459
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
474
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
460
475
|
[ Opcodes.i32_mul ],
|
461
476
|
[ Opcodes.i32_add ],
|
462
477
|
|
@@ -465,9 +480,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
465
480
|
...number(ValtypeSize.i32, Valtype.i32),
|
466
481
|
[ Opcodes.i32_add ],
|
467
482
|
|
468
|
-
// size = right length *
|
483
|
+
// size = right length * sizeof valtype
|
469
484
|
[ Opcodes.local_get, rightLength ],
|
470
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
485
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
471
486
|
[ Opcodes.i32_mul ],
|
472
487
|
|
473
488
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -525,11 +540,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
525
540
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
526
541
|
|
527
542
|
// copy right
|
528
|
-
// dst = out pointer + length size + left length *
|
543
|
+
// dst = out pointer + length size + left length * sizeof valtype
|
529
544
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
530
545
|
|
531
546
|
[ Opcodes.local_get, leftLength ],
|
532
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
547
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
533
548
|
[ Opcodes.i32_mul ],
|
534
549
|
[ Opcodes.i32_add ],
|
535
550
|
|
@@ -538,9 +553,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
538
553
|
...number(ValtypeSize.i32, Valtype.i32),
|
539
554
|
[ Opcodes.i32_add ],
|
540
555
|
|
541
|
-
// size = right length *
|
556
|
+
// size = right length * sizeof valtype
|
542
557
|
[ Opcodes.local_get, rightLength ],
|
543
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
558
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
544
559
|
[ Opcodes.i32_mul ],
|
545
560
|
|
546
561
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -550,7 +565,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
550
565
|
];
|
551
566
|
};
|
552
567
|
|
553
|
-
const compareStrings = (scope, left, right) => {
|
568
|
+
const compareStrings = (scope, left, right, bytestrings = false) => {
|
554
569
|
// todo: this should be rewritten into a func
|
555
570
|
// todo: convert left and right to strings if not
|
556
571
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -559,7 +574,6 @@ const compareStrings = (scope, left, right) => {
|
|
559
574
|
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
560
575
|
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
561
576
|
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
562
|
-
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
563
577
|
|
564
578
|
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
565
579
|
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
@@ -587,7 +601,6 @@ const compareStrings = (scope, left, right) => {
|
|
587
601
|
|
588
602
|
[ Opcodes.local_get, rightPointer ],
|
589
603
|
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
590
|
-
[ Opcodes.local_tee, rightLength ],
|
591
604
|
|
592
605
|
// fast path: check leftLength != rightLength
|
593
606
|
[ Opcodes.i32_ne ],
|
@@ -602,11 +615,13 @@ const compareStrings = (scope, left, right) => {
|
|
602
615
|
...number(0, Valtype.i32),
|
603
616
|
[ Opcodes.local_set, index ],
|
604
617
|
|
605
|
-
// setup index end as length * sizeof
|
618
|
+
// setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
|
606
619
|
// we do this instead of having to do mul/div each iter for perf™
|
607
620
|
[ Opcodes.local_get, leftLength ],
|
608
|
-
...
|
609
|
-
|
621
|
+
...(bytestrings ? [] : [
|
622
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
623
|
+
[ Opcodes.i32_mul ],
|
624
|
+
]),
|
610
625
|
[ Opcodes.local_set, indexEnd ],
|
611
626
|
|
612
627
|
// iterate over each char and check if eq
|
@@ -616,13 +631,17 @@ const compareStrings = (scope, left, right) => {
|
|
616
631
|
[ Opcodes.local_get, index ],
|
617
632
|
[ Opcodes.local_get, leftPointer ],
|
618
633
|
[ Opcodes.i32_add ],
|
619
|
-
|
634
|
+
bytestrings ?
|
635
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
636
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
620
637
|
|
621
638
|
// fetch right
|
622
639
|
[ Opcodes.local_get, index ],
|
623
640
|
[ Opcodes.local_get, rightPointer ],
|
624
641
|
[ Opcodes.i32_add ],
|
625
|
-
|
642
|
+
bytestrings ?
|
643
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
644
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
626
645
|
|
627
646
|
// not equal, "return" false
|
628
647
|
[ Opcodes.i32_ne ],
|
@@ -631,13 +650,13 @@ const compareStrings = (scope, left, right) => {
|
|
631
650
|
[ Opcodes.br, 2 ],
|
632
651
|
[ Opcodes.end ],
|
633
652
|
|
634
|
-
// index += sizeof
|
653
|
+
// index += sizeof valtype (1 for bytestring, 2 for string)
|
635
654
|
[ Opcodes.local_get, index ],
|
636
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
655
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
637
656
|
[ Opcodes.i32_add ],
|
638
657
|
[ Opcodes.local_tee, index ],
|
639
658
|
|
640
|
-
// if index != index end (length * sizeof
|
659
|
+
// if index != index end (length * sizeof valtype), loop
|
641
660
|
[ Opcodes.local_get, indexEnd ],
|
642
661
|
[ Opcodes.i32_ne ],
|
643
662
|
[ Opcodes.br_if, 0 ],
|
@@ -658,13 +677,14 @@ const compareStrings = (scope, left, right) => {
|
|
658
677
|
};
|
659
678
|
|
660
679
|
const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
661
|
-
if (
|
680
|
+
if (isFloatToIntOp(wasm[wasm.length - 1])) return [
|
662
681
|
...wasm,
|
663
682
|
...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
|
664
683
|
];
|
684
|
+
// if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
|
665
685
|
|
666
686
|
const useTmp = knownType(scope, type) == null;
|
667
|
-
const tmp = useTmp && localTmp(scope,
|
687
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
668
688
|
|
669
689
|
const def = [
|
670
690
|
// if value != 0
|
@@ -684,7 +704,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
684
704
|
|
685
705
|
...typeSwitch(scope, type, {
|
686
706
|
// [TYPES.number]: def,
|
687
|
-
[TYPES.
|
707
|
+
[TYPES.array]: [
|
688
708
|
// arrays are always truthy
|
689
709
|
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
690
710
|
],
|
@@ -700,7 +720,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
700
720
|
[ Opcodes.i32_eqz ], */
|
701
721
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
702
722
|
],
|
703
|
-
[TYPES.
|
723
|
+
[TYPES.bytestring]: [ // duplicate of string
|
704
724
|
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
705
725
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
706
726
|
|
@@ -716,14 +736,14 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
716
736
|
|
717
737
|
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
718
738
|
const useTmp = knownType(scope, type) == null;
|
719
|
-
const tmp = useTmp && localTmp(scope,
|
739
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
720
740
|
|
721
741
|
return [
|
722
742
|
...wasm,
|
723
743
|
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
724
744
|
|
725
745
|
...typeSwitch(scope, type, {
|
726
|
-
[TYPES.
|
746
|
+
[TYPES.array]: [
|
727
747
|
// arrays are always truthy
|
728
748
|
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
729
749
|
],
|
@@ -738,7 +758,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
738
758
|
[ Opcodes.i32_eqz ],
|
739
759
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
740
760
|
],
|
741
|
-
[TYPES.
|
761
|
+
[TYPES.bytestring]: [ // duplicate of string
|
742
762
|
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
743
763
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
744
764
|
|
@@ -762,7 +782,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
762
782
|
|
763
783
|
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
764
784
|
const useTmp = knownType(scope, type) == null;
|
765
|
-
const tmp = useTmp && localTmp(scope,
|
785
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
766
786
|
|
767
787
|
return [
|
768
788
|
...wasm,
|
@@ -815,31 +835,6 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
815
835
|
|
816
836
|
// if strict (in)equal check types match
|
817
837
|
if (strictOp) {
|
818
|
-
// startOut.push(
|
819
|
-
// ...leftType,
|
820
|
-
// ...rightType,
|
821
|
-
// [ Opcodes.i32_eq ]
|
822
|
-
// );
|
823
|
-
|
824
|
-
// endOut.push(
|
825
|
-
// [ Opcodes.i32_and ]
|
826
|
-
// );
|
827
|
-
|
828
|
-
// startOut.push(
|
829
|
-
// [ Opcodes.block, Valtype.i32 ],
|
830
|
-
// ...leftType,
|
831
|
-
// ...rightType,
|
832
|
-
// [ Opcodes.i32_ne ],
|
833
|
-
// [ Opcodes.if, Blocktype.void ],
|
834
|
-
// ...number(op === '===' ? 0 : 1, Valtype.i32),
|
835
|
-
// [ Opcodes.br, 1 ],
|
836
|
-
// [ Opcodes.end ]
|
837
|
-
// );
|
838
|
-
|
839
|
-
// endOut.push(
|
840
|
-
// [ Opcodes.end ]
|
841
|
-
// );
|
842
|
-
|
843
838
|
endOut.push(
|
844
839
|
...leftType,
|
845
840
|
...rightType,
|
@@ -856,11 +851,11 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
856
851
|
// todo: if equality op and an operand is undefined, return false
|
857
852
|
// todo: niche null hell with 0
|
858
853
|
|
859
|
-
// todo: this should be dynamic but for now only static
|
860
854
|
if (knownLeft === TYPES.string || knownRight === TYPES.string) {
|
861
855
|
if (op === '+') {
|
856
|
+
// todo: this should be dynamic too but for now only static
|
862
857
|
// string concat (a + b)
|
863
|
-
return concatStrings(scope, left, right, _global, _name, assign);
|
858
|
+
return concatStrings(scope, left, right, _global, _name, assign, false);
|
864
859
|
}
|
865
860
|
|
866
861
|
// not an equality op, NaN
|
@@ -883,6 +878,33 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
883
878
|
}
|
884
879
|
}
|
885
880
|
|
881
|
+
if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) {
|
882
|
+
if (op === '+') {
|
883
|
+
// todo: this should be dynamic too but for now only static
|
884
|
+
// string concat (a + b)
|
885
|
+
return concatStrings(scope, left, right, _global, _name, assign, true);
|
886
|
+
}
|
887
|
+
|
888
|
+
// not an equality op, NaN
|
889
|
+
if (!eqOp) return number(NaN);
|
890
|
+
|
891
|
+
// else leave bool ops
|
892
|
+
// todo: convert string to number if string and number/bool
|
893
|
+
// todo: string (>|>=|<|<=) string
|
894
|
+
|
895
|
+
// string comparison
|
896
|
+
if (op === '===' || op === '==') {
|
897
|
+
return compareStrings(scope, left, right, true);
|
898
|
+
}
|
899
|
+
|
900
|
+
if (op === '!==' || op === '!=') {
|
901
|
+
return [
|
902
|
+
...compareStrings(scope, left, right, true),
|
903
|
+
[ Opcodes.i32_eqz ]
|
904
|
+
];
|
905
|
+
}
|
906
|
+
}
|
907
|
+
|
886
908
|
let ops = operatorOpcode[valtype][op];
|
887
909
|
|
888
910
|
// some complex ops are implemented as builtin funcs
|
@@ -898,23 +920,62 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
898
920
|
]);
|
899
921
|
}
|
900
922
|
|
901
|
-
if (!ops) return todo(`operator ${op} not implemented yet
|
923
|
+
if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
|
902
924
|
|
903
925
|
if (!Array.isArray(ops)) ops = [ ops ];
|
904
926
|
ops = [ ops ];
|
905
927
|
|
906
928
|
let tmpLeft, tmpRight;
|
907
929
|
// if equal op, check if strings for compareStrings
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
|
912
|
-
return;
|
913
|
-
}
|
930
|
+
// todo: intelligent partial skip later
|
931
|
+
// if neither known are string, stop this madness
|
932
|
+
// we already do known checks earlier, so don't need to recheck
|
914
933
|
|
934
|
+
if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
|
915
935
|
tmpLeft = localTmp(scope, '__tmpop_left');
|
916
936
|
tmpRight = localTmp(scope, '__tmpop_right');
|
917
937
|
|
938
|
+
// returns false for one string, one not - but more ops/slower
|
939
|
+
// ops.unshift(...stringOnly([
|
940
|
+
// // if left is string
|
941
|
+
// ...leftType,
|
942
|
+
// ...number(TYPES.string, Valtype.i32),
|
943
|
+
// [ Opcodes.i32_eq ],
|
944
|
+
|
945
|
+
// // if right is string
|
946
|
+
// ...rightType,
|
947
|
+
// ...number(TYPES.string, Valtype.i32),
|
948
|
+
// [ Opcodes.i32_eq ],
|
949
|
+
|
950
|
+
// // if either are true
|
951
|
+
// [ Opcodes.i32_or ],
|
952
|
+
// [ Opcodes.if, Blocktype.void ],
|
953
|
+
|
954
|
+
// // todo: convert non-strings to strings, for now fail immediately if one is not
|
955
|
+
// // if left is not string
|
956
|
+
// ...leftType,
|
957
|
+
// ...number(TYPES.string, Valtype.i32),
|
958
|
+
// [ Opcodes.i32_ne ],
|
959
|
+
|
960
|
+
// // if right is not string
|
961
|
+
// ...rightType,
|
962
|
+
// ...number(TYPES.string, Valtype.i32),
|
963
|
+
// [ Opcodes.i32_ne ],
|
964
|
+
|
965
|
+
// // if either are true
|
966
|
+
// [ Opcodes.i32_or ],
|
967
|
+
// [ Opcodes.if, Blocktype.void ],
|
968
|
+
// ...number(0, Valtype.i32),
|
969
|
+
// [ Opcodes.br, 2 ],
|
970
|
+
// [ Opcodes.end ],
|
971
|
+
|
972
|
+
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
973
|
+
// ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
974
|
+
// [ Opcodes.br, 1 ],
|
975
|
+
// [ Opcodes.end ],
|
976
|
+
// ]));
|
977
|
+
|
978
|
+
// does not handle one string, one not (such cases go past)
|
918
979
|
ops.unshift(...stringOnly([
|
919
980
|
// if left is string
|
920
981
|
...leftType,
|
@@ -926,30 +987,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
926
987
|
...number(TYPES.string, Valtype.i32),
|
927
988
|
[ Opcodes.i32_eq ],
|
928
989
|
|
929
|
-
// if
|
930
|
-
[ Opcodes.
|
990
|
+
// if both are true
|
991
|
+
[ Opcodes.i32_and ],
|
931
992
|
[ Opcodes.if, Blocktype.void ],
|
993
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
994
|
+
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
995
|
+
[ Opcodes.br, 1 ],
|
996
|
+
[ Opcodes.end ],
|
932
997
|
|
933
|
-
//
|
934
|
-
// if left is not string
|
998
|
+
// if left is bytestring
|
935
999
|
...leftType,
|
936
|
-
...number(TYPES.
|
937
|
-
[ Opcodes.
|
1000
|
+
...number(TYPES.bytestring, Valtype.i32),
|
1001
|
+
[ Opcodes.i32_eq ],
|
938
1002
|
|
939
|
-
// if right is
|
1003
|
+
// if right is bytestring
|
940
1004
|
...rightType,
|
941
|
-
...number(TYPES.
|
942
|
-
[ Opcodes.
|
1005
|
+
...number(TYPES.bytestring, Valtype.i32),
|
1006
|
+
[ Opcodes.i32_eq ],
|
943
1007
|
|
944
|
-
// if
|
945
|
-
[ Opcodes.
|
1008
|
+
// if both are true
|
1009
|
+
[ Opcodes.i32_and ],
|
946
1010
|
[ Opcodes.if, Blocktype.void ],
|
947
|
-
...
|
948
|
-
[ Opcodes.br, 2 ],
|
949
|
-
[ Opcodes.end ],
|
950
|
-
|
951
|
-
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
952
|
-
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1011
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
|
953
1012
|
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
954
1013
|
[ Opcodes.br, 1 ],
|
955
1014
|
[ Opcodes.end ],
|
@@ -961,7 +1020,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
961
1020
|
// endOut.push(stringOnly([ Opcodes.end ]));
|
962
1021
|
endOut.unshift(stringOnly([ Opcodes.end ]));
|
963
1022
|
// }
|
964
|
-
}
|
1023
|
+
}
|
965
1024
|
|
966
1025
|
return finalize([
|
967
1026
|
...left,
|
@@ -982,7 +1041,7 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
982
1041
|
|
983
1042
|
const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
|
984
1043
|
return func({ name, params, locals, returns, localInd }, {
|
985
|
-
TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
|
1044
|
+
TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
|
986
1045
|
builtin: name => {
|
987
1046
|
let idx = funcIndex[name] ?? importedFuncs[name];
|
988
1047
|
if (idx === undefined && builtinFuncs[name]) {
|
@@ -1037,7 +1096,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
1037
1096
|
params,
|
1038
1097
|
locals,
|
1039
1098
|
returns,
|
1040
|
-
returnType:
|
1099
|
+
returnType: returnType ?? TYPES.number,
|
1041
1100
|
wasm,
|
1042
1101
|
internal: true,
|
1043
1102
|
index: currentFuncIndex++
|
@@ -1060,6 +1119,7 @@ const generateLogicExp = (scope, decl) => {
|
|
1060
1119
|
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
1061
1120
|
};
|
1062
1121
|
|
1122
|
+
// potential future ideas for nan boxing (unused):
|
1063
1123
|
// T = JS type, V = value/pointer
|
1064
1124
|
// 0bTTT
|
1065
1125
|
// qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
|
@@ -1073,7 +1133,6 @@ const generateLogicExp = (scope, decl) => {
|
|
1073
1133
|
// js type: 4 bits
|
1074
1134
|
// internal type: ? bits
|
1075
1135
|
// pointer: 32 bits
|
1076
|
-
|
1077
1136
|
// generic
|
1078
1137
|
// 1 23 4 5
|
1079
1138
|
// 0 11111111111 11TTTTIIII??????????PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
|
@@ -1083,40 +1142,18 @@ const generateLogicExp = (scope, decl) => {
|
|
1083
1142
|
// 4: internal type
|
1084
1143
|
// 5: pointer
|
1085
1144
|
|
1086
|
-
const
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
object: 0x04,
|
1092
|
-
function: 0x05,
|
1093
|
-
symbol: 0x06,
|
1094
|
-
bigint: 0x07,
|
1095
|
-
|
1096
|
-
// these are not "typeof" types but tracked internally
|
1097
|
-
_array: 0x10,
|
1098
|
-
_regexp: 0x11,
|
1099
|
-
_bytestring: 0x12
|
1100
|
-
};
|
1101
|
-
|
1102
|
-
const TYPE_NAMES = {
|
1103
|
-
[TYPES.number]: 'Number',
|
1104
|
-
[TYPES.boolean]: 'Boolean',
|
1105
|
-
[TYPES.string]: 'String',
|
1106
|
-
[TYPES.undefined]: 'undefined',
|
1107
|
-
[TYPES.object]: 'Object',
|
1108
|
-
[TYPES.function]: 'Function',
|
1109
|
-
[TYPES.symbol]: 'Symbol',
|
1110
|
-
[TYPES.bigint]: 'BigInt',
|
1111
|
-
|
1112
|
-
[TYPES._array]: 'Array',
|
1113
|
-
[TYPES._regexp]: 'RegExp',
|
1114
|
-
[TYPES._bytestring]: 'ByteString'
|
1145
|
+
const isExistingProtoFunc = name => {
|
1146
|
+
if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES.array][name.slice(18)];
|
1147
|
+
if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
|
1148
|
+
|
1149
|
+
return false;
|
1115
1150
|
};
|
1116
1151
|
|
1117
1152
|
const getType = (scope, _name) => {
|
1118
1153
|
const name = mapName(_name);
|
1119
1154
|
|
1155
|
+
// if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
|
1156
|
+
|
1120
1157
|
if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
|
1121
1158
|
if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
|
1122
1159
|
|
@@ -1124,11 +1161,10 @@ const getType = (scope, _name) => {
|
|
1124
1161
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1125
1162
|
|
1126
1163
|
let type = TYPES.undefined;
|
1127
|
-
if (builtinVars[name]) type =
|
1164
|
+
if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
|
1128
1165
|
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
|
1129
1166
|
|
1130
|
-
if (name
|
1131
|
-
name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
|
1167
|
+
if (isExistingProtoFunc(name)) type = TYPES.function;
|
1132
1168
|
|
1133
1169
|
return number(type, Valtype.i32);
|
1134
1170
|
};
|
@@ -1151,23 +1187,24 @@ const setType = (scope, _name, type) => {
|
|
1151
1187
|
];
|
1152
1188
|
|
1153
1189
|
// throw new Error('could not find var');
|
1190
|
+
return [];
|
1154
1191
|
};
|
1155
1192
|
|
1156
1193
|
const getLastType = scope => {
|
1157
1194
|
scope.gotLastType = true;
|
1158
|
-
return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
|
1195
|
+
return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1159
1196
|
};
|
1160
1197
|
|
1161
1198
|
const setLastType = scope => {
|
1162
|
-
return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
|
1199
|
+
return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1163
1200
|
};
|
1164
1201
|
|
1165
1202
|
const getNodeType = (scope, node) => {
|
1166
|
-
const
|
1203
|
+
const ret = (() => {
|
1167
1204
|
if (node.type === 'Literal') {
|
1168
|
-
if (node.regex) return TYPES.
|
1205
|
+
if (node.regex) return TYPES.regexp;
|
1169
1206
|
|
1170
|
-
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES.
|
1207
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES.bytestring;
|
1171
1208
|
|
1172
1209
|
return TYPES[typeof node.value];
|
1173
1210
|
}
|
@@ -1184,21 +1221,32 @@ const getNodeType = (scope, node) => {
|
|
1184
1221
|
const name = node.callee.name;
|
1185
1222
|
if (!name) {
|
1186
1223
|
// iife
|
1187
|
-
if (scope.locals['#last_type']) return
|
1224
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1188
1225
|
|
1189
1226
|
// presume
|
1190
1227
|
// todo: warn here?
|
1191
1228
|
return TYPES.number;
|
1192
1229
|
}
|
1193
1230
|
|
1231
|
+
if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
|
1232
|
+
if (builtinFuncs[name + '$constructor'].typedReturns) {
|
1233
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1234
|
+
|
1235
|
+
// presume
|
1236
|
+
// todo: warn here?
|
1237
|
+
return TYPES.number;
|
1238
|
+
}
|
1239
|
+
|
1240
|
+
return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
|
1241
|
+
}
|
1242
|
+
|
1194
1243
|
const func = funcs.find(x => x.name === name);
|
1195
1244
|
|
1196
1245
|
if (func) {
|
1197
|
-
// console.log(scope, func, func.returnType);
|
1198
1246
|
if (func.returnType) return func.returnType;
|
1199
1247
|
}
|
1200
1248
|
|
1201
|
-
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return
|
1249
|
+
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
1202
1250
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1203
1251
|
|
1204
1252
|
// check if this is a prototype function
|
@@ -1209,7 +1257,7 @@ const getNodeType = (scope, node) => {
|
|
1209
1257
|
const spl = name.slice(2).split('_');
|
1210
1258
|
|
1211
1259
|
const func = spl[spl.length - 1];
|
1212
|
-
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.
|
1260
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
|
1213
1261
|
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1214
1262
|
}
|
1215
1263
|
|
@@ -1218,7 +1266,7 @@ const getNodeType = (scope, node) => {
|
|
1218
1266
|
return TYPES.number;
|
1219
1267
|
}
|
1220
1268
|
|
1221
|
-
if (scope.locals['#last_type']) return
|
1269
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1222
1270
|
|
1223
1271
|
// presume
|
1224
1272
|
// todo: warn here?
|
@@ -1261,7 +1309,7 @@ const getNodeType = (scope, node) => {
|
|
1261
1309
|
}
|
1262
1310
|
|
1263
1311
|
if (node.type === 'ArrayExpression') {
|
1264
|
-
return TYPES.
|
1312
|
+
return TYPES.array;
|
1265
1313
|
}
|
1266
1314
|
|
1267
1315
|
if (node.type === 'BinaryExpression') {
|
@@ -1273,6 +1321,7 @@ const getNodeType = (scope, node) => {
|
|
1273
1321
|
|
1274
1322
|
// todo: this should be dynamic but for now only static
|
1275
1323
|
if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
|
1324
|
+
if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) return TYPES.bytestring;
|
1276
1325
|
|
1277
1326
|
return TYPES.number;
|
1278
1327
|
|
@@ -1298,34 +1347,47 @@ const getNodeType = (scope, node) => {
|
|
1298
1347
|
if (node.operator === '!') return TYPES.boolean;
|
1299
1348
|
if (node.operator === 'void') return TYPES.undefined;
|
1300
1349
|
if (node.operator === 'delete') return TYPES.boolean;
|
1301
|
-
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES.
|
1350
|
+
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES.bytestring : TYPES.string;
|
1302
1351
|
|
1303
1352
|
return TYPES.number;
|
1304
1353
|
}
|
1305
1354
|
|
1306
1355
|
if (node.type === 'MemberExpression') {
|
1356
|
+
// hack: if something.name, string type
|
1357
|
+
if (node.property.name === 'name') {
|
1358
|
+
if (hasFuncWithName(node.object.name)) {
|
1359
|
+
return TYPES.bytestring;
|
1360
|
+
} else {
|
1361
|
+
return TYPES.undefined;
|
1362
|
+
}
|
1363
|
+
}
|
1364
|
+
|
1307
1365
|
// hack: if something.length, number type
|
1308
1366
|
if (node.property.name === 'length') return TYPES.number;
|
1309
1367
|
|
1310
1368
|
// ts hack
|
1311
1369
|
if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
|
1312
|
-
if (scope.locals[node.object.name]?.metadata?.type === TYPES.
|
1370
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.bytestring) return TYPES.bytestring;
|
1371
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.array) return TYPES.number;
|
1313
1372
|
|
1314
|
-
if (scope.locals['#last_type']) return
|
1373
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1315
1374
|
|
1316
1375
|
// presume
|
1317
1376
|
return TYPES.number;
|
1318
1377
|
}
|
1319
1378
|
|
1320
|
-
if (
|
1379
|
+
if (node.type === 'TaggedTemplateExpression') {
|
1380
|
+
// hack
|
1381
|
+
if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
|
1382
|
+
}
|
1383
|
+
|
1384
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1321
1385
|
|
1322
1386
|
// presume
|
1323
1387
|
// todo: warn here?
|
1324
1388
|
return TYPES.number;
|
1325
|
-
};
|
1389
|
+
})();
|
1326
1390
|
|
1327
|
-
const ret = inner();
|
1328
|
-
// console.trace(node, ret);
|
1329
1391
|
if (typeof ret === 'number') return number(ret, Valtype.i32);
|
1330
1392
|
return ret;
|
1331
1393
|
};
|
@@ -1350,7 +1412,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1350
1412
|
return makeString(scope, decl.value, global, name);
|
1351
1413
|
|
1352
1414
|
default:
|
1353
|
-
return todo(`cannot generate literal of type ${typeof decl.value}
|
1415
|
+
return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
|
1354
1416
|
}
|
1355
1417
|
};
|
1356
1418
|
|
@@ -1359,6 +1421,8 @@ const countLeftover = wasm => {
|
|
1359
1421
|
|
1360
1422
|
for (let i = 0; i < wasm.length; i++) {
|
1361
1423
|
const inst = wasm[i];
|
1424
|
+
if (inst[0] == null) continue;
|
1425
|
+
|
1362
1426
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
1363
1427
|
if (inst[0] === Opcodes.if) count--;
|
1364
1428
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1369,16 +1433,23 @@ const countLeftover = wasm => {
|
|
1369
1433
|
if (depth === 0)
|
1370
1434
|
if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1371
1435
|
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)) {}
|
1372
|
-
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1436
|
+
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++;
|
1373
1437
|
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1374
1438
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1375
1439
|
else if (inst[0] === Opcodes.return) count = 0;
|
1376
1440
|
else if (inst[0] === Opcodes.call) {
|
1377
1441
|
let func = funcs.find(x => x.index === inst[1]);
|
1378
|
-
if (
|
1379
|
-
count
|
1380
|
-
} else
|
1381
|
-
|
1442
|
+
if (inst[1] === -1) {
|
1443
|
+
// todo: count for calling self
|
1444
|
+
} else if (!func && inst[1] < importedFuncs.length) {
|
1445
|
+
count -= importedFuncs[inst[1]].params;
|
1446
|
+
count += importedFuncs[inst[1]].returns;
|
1447
|
+
} else {
|
1448
|
+
if (func) {
|
1449
|
+
count -= func.params.length;
|
1450
|
+
} else count--;
|
1451
|
+
if (func) count += func.returns.length;
|
1452
|
+
}
|
1382
1453
|
} else count--;
|
1383
1454
|
|
1384
1455
|
// console.log(count, decompile([ inst ]).slice(0, -1));
|
@@ -1455,25 +1526,27 @@ const RTArrayUtil = {
|
|
1455
1526
|
};
|
1456
1527
|
|
1457
1528
|
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1458
|
-
/* const callee = decl.callee;
|
1459
|
-
const args = decl.arguments;
|
1460
|
-
|
1461
|
-
return [
|
1462
|
-
...generate(args),
|
1463
|
-
...generate(callee),
|
1464
|
-
Opcodes.call_indirect,
|
1465
|
-
]; */
|
1466
|
-
|
1467
1529
|
let name = mapName(decl.callee.name);
|
1468
1530
|
if (isFuncType(decl.callee.type)) { // iife
|
1469
1531
|
const func = generateFunc(scope, decl.callee);
|
1470
1532
|
name = func.name;
|
1471
1533
|
}
|
1472
1534
|
|
1473
|
-
if (name === 'eval' && decl.arguments[0]
|
1535
|
+
if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
|
1474
1536
|
// literal eval hack
|
1475
|
-
const code = decl.arguments[0]
|
1476
|
-
|
1537
|
+
const code = decl.arguments[0]?.value ?? '';
|
1538
|
+
|
1539
|
+
let parsed;
|
1540
|
+
try {
|
1541
|
+
parsed = parse(code, []);
|
1542
|
+
} catch (e) {
|
1543
|
+
if (e.name === 'SyntaxError') {
|
1544
|
+
// throw syntax errors of evals at runtime instead
|
1545
|
+
return internalThrow(scope, 'SyntaxError', e.message, true);
|
1546
|
+
}
|
1547
|
+
|
1548
|
+
throw e;
|
1549
|
+
}
|
1477
1550
|
|
1478
1551
|
const out = generate(scope, {
|
1479
1552
|
type: 'BlockStatement',
|
@@ -1487,13 +1560,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1487
1560
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1488
1561
|
out.push(
|
1489
1562
|
...getNodeType(scope, finalStatement),
|
1490
|
-
setLastType(scope)
|
1563
|
+
...setLastType(scope)
|
1491
1564
|
);
|
1492
1565
|
} else if (countLeftover(out) === 0) {
|
1493
1566
|
out.push(...number(UNDEFINED));
|
1494
1567
|
out.push(
|
1495
1568
|
...number(TYPES.undefined, Valtype.i32),
|
1496
|
-
setLastType(scope)
|
1569
|
+
...setLastType(scope)
|
1497
1570
|
);
|
1498
1571
|
}
|
1499
1572
|
|
@@ -1515,6 +1588,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1515
1588
|
|
1516
1589
|
target = { ...decl.callee };
|
1517
1590
|
target.name = spl.slice(0, -1).join('_');
|
1591
|
+
|
1592
|
+
// failed to lookup name, abort
|
1593
|
+
if (!lookupName(scope, target.name)[0]) protoName = null;
|
1518
1594
|
}
|
1519
1595
|
|
1520
1596
|
// literal.func()
|
@@ -1522,22 +1598,29 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1522
1598
|
// megahack for /regex/.func()
|
1523
1599
|
const funcName = decl.callee.property.name;
|
1524
1600
|
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1525
|
-
const
|
1601
|
+
const regex = decl.callee.object.regex.pattern;
|
1602
|
+
const rhemynName = `regex_${funcName}_${regex}`;
|
1526
1603
|
|
1527
|
-
funcIndex[
|
1528
|
-
|
1604
|
+
if (!funcIndex[rhemynName]) {
|
1605
|
+
const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
|
1529
1606
|
|
1607
|
+
funcIndex[func.name] = func.index;
|
1608
|
+
funcs.push(func);
|
1609
|
+
}
|
1610
|
+
|
1611
|
+
const idx = funcIndex[rhemynName];
|
1530
1612
|
return [
|
1531
1613
|
// make string arg
|
1532
1614
|
...generate(scope, decl.arguments[0]),
|
1615
|
+
Opcodes.i32_to_u,
|
1616
|
+
...getNodeType(scope, decl.arguments[0]),
|
1533
1617
|
|
1534
1618
|
// call regex func
|
1535
|
-
Opcodes.
|
1536
|
-
[ Opcodes.call, func.index ],
|
1619
|
+
[ Opcodes.call, idx ],
|
1537
1620
|
Opcodes.i32_from_u,
|
1538
1621
|
|
1539
1622
|
...number(TYPES.boolean, Valtype.i32),
|
1540
|
-
setLastType(scope)
|
1623
|
+
...setLastType(scope)
|
1541
1624
|
];
|
1542
1625
|
}
|
1543
1626
|
|
@@ -1562,12 +1645,31 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1562
1645
|
// }
|
1563
1646
|
|
1564
1647
|
if (protoName) {
|
1648
|
+
const protoBC = {};
|
1649
|
+
|
1650
|
+
const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
|
1651
|
+
|
1652
|
+
if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
|
1653
|
+
for (const x of builtinProtoCands) {
|
1654
|
+
const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
|
1655
|
+
if (type == null) continue;
|
1656
|
+
|
1657
|
+
protoBC[type] = generateCall(scope, {
|
1658
|
+
callee: {
|
1659
|
+
type: 'Identifier',
|
1660
|
+
name: x
|
1661
|
+
},
|
1662
|
+
arguments: [ target, ...decl.arguments ],
|
1663
|
+
_protoInternalCall: true
|
1664
|
+
});
|
1665
|
+
}
|
1666
|
+
}
|
1667
|
+
|
1565
1668
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1566
1669
|
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1567
1670
|
return acc;
|
1568
1671
|
}, {});
|
1569
1672
|
|
1570
|
-
// no prototype function candidates, ignore
|
1571
1673
|
if (Object.keys(protoCands).length > 0) {
|
1572
1674
|
// use local for cached i32 length as commonly used
|
1573
1675
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
@@ -1585,7 +1687,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1585
1687
|
|
1586
1688
|
let allOptUnused = true;
|
1587
1689
|
let lengthI32CacheUsed = false;
|
1588
|
-
const protoBC = {};
|
1589
1690
|
for (const x in protoCands) {
|
1590
1691
|
const protoFunc = protoCands[x];
|
1591
1692
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
@@ -1593,13 +1694,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1593
1694
|
...RTArrayUtil.getLength(getPointer),
|
1594
1695
|
|
1595
1696
|
...number(TYPES.number, Valtype.i32),
|
1596
|
-
setLastType(scope)
|
1697
|
+
...setLastType(scope)
|
1597
1698
|
];
|
1598
1699
|
continue;
|
1599
1700
|
}
|
1600
1701
|
|
1601
|
-
// const protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp`, protoFunc.local) : -1;
|
1602
|
-
// const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp2`, protoFunc.local2) : -1;
|
1603
1702
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1604
1703
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1605
1704
|
|
@@ -1630,7 +1729,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1630
1729
|
...protoOut,
|
1631
1730
|
|
1632
1731
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1633
|
-
setLastType(scope),
|
1732
|
+
...setLastType(scope),
|
1634
1733
|
[ Opcodes.end ]
|
1635
1734
|
];
|
1636
1735
|
}
|
@@ -1656,10 +1755,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1656
1755
|
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1657
1756
|
];
|
1658
1757
|
}
|
1758
|
+
|
1759
|
+
if (Object.keys(protoBC).length > 0) {
|
1760
|
+
return typeSwitch(scope, getNodeType(scope, target), {
|
1761
|
+
...protoBC,
|
1762
|
+
|
1763
|
+
// TODO: error better
|
1764
|
+
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1765
|
+
}, valtypeBinary);
|
1766
|
+
}
|
1659
1767
|
}
|
1660
1768
|
|
1661
1769
|
// TODO: only allows callee as literal
|
1662
|
-
if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
|
1770
|
+
if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
|
1663
1771
|
|
1664
1772
|
let idx = funcIndex[name] ?? importedFuncs[name];
|
1665
1773
|
if (idx === undefined && builtinFuncs[name]) {
|
@@ -1667,22 +1775,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1667
1775
|
|
1668
1776
|
includeBuiltin(scope, name);
|
1669
1777
|
idx = funcIndex[name];
|
1670
|
-
|
1671
|
-
// infer arguments types from builtins params
|
1672
|
-
const func = funcs.find(x => x.name === name);
|
1673
|
-
for (let i = 0; i < decl.arguments.length; i++) {
|
1674
|
-
const arg = decl.arguments[i];
|
1675
|
-
if (!arg.name) continue;
|
1676
|
-
|
1677
|
-
const local = scope.locals[arg.name];
|
1678
|
-
if (!local) continue;
|
1679
|
-
|
1680
|
-
local.type = func.params[i];
|
1681
|
-
if (local.type === Valtype.v128) {
|
1682
|
-
// specify vec subtype inferred from last vec type in function name
|
1683
|
-
local.vecType = name.split('_').reverse().find(x => x.includes('x'));
|
1684
|
-
}
|
1685
|
-
}
|
1686
1778
|
}
|
1687
1779
|
|
1688
1780
|
if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
@@ -1695,9 +1787,25 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1695
1787
|
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1696
1788
|
const wasmOps = {
|
1697
1789
|
// pointer, align, offset
|
1698
|
-
|
1790
|
+
i32_load: { imms: 2, args: [ true ], returns: 1 },
|
1699
1791
|
// pointer, value, align, offset
|
1700
|
-
|
1792
|
+
i32_store: { imms: 2, args: [ true, true ], returns: 0 },
|
1793
|
+
// pointer, align, offset
|
1794
|
+
i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
|
1795
|
+
// pointer, value, align, offset
|
1796
|
+
i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
|
1797
|
+
// pointer, align, offset
|
1798
|
+
i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
|
1799
|
+
// pointer, value, align, offset
|
1800
|
+
i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
|
1801
|
+
|
1802
|
+
// pointer, align, offset
|
1803
|
+
f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
|
1804
|
+
// pointer, value, align, offset
|
1805
|
+
f64_store: { imms: 2, args: [ true, false ], returns: 0 },
|
1806
|
+
|
1807
|
+
// value
|
1808
|
+
i32_const: { imms: 1, args: [], returns: 1 },
|
1701
1809
|
};
|
1702
1810
|
|
1703
1811
|
const opName = name.slice('__Porffor_wasm_'.length);
|
@@ -1706,28 +1814,32 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1706
1814
|
const op = wasmOps[opName];
|
1707
1815
|
|
1708
1816
|
const argOut = [];
|
1709
|
-
for (let i = 0; i < op.args; i++) argOut.push(
|
1817
|
+
for (let i = 0; i < op.args.length; i++) argOut.push(
|
1818
|
+
...generate(scope, decl.arguments[i]),
|
1819
|
+
...(op.args[i] ? [ Opcodes.i32_to ] : [])
|
1820
|
+
);
|
1710
1821
|
|
1711
1822
|
// literals only
|
1712
|
-
const imms = decl.arguments.slice(op.args).map(x => x.value);
|
1823
|
+
const imms = decl.arguments.slice(op.args.length).map(x => x.value);
|
1713
1824
|
|
1714
1825
|
return [
|
1715
1826
|
...argOut,
|
1716
|
-
[ Opcodes[opName], ...imms ]
|
1827
|
+
[ Opcodes[opName], ...imms ],
|
1828
|
+
...(new Array(op.returns).fill(Opcodes.i32_from))
|
1717
1829
|
];
|
1718
1830
|
}
|
1719
1831
|
}
|
1720
1832
|
|
1721
1833
|
if (idx === undefined) {
|
1722
|
-
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function
|
1723
|
-
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined
|
1834
|
+
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1835
|
+
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
1724
1836
|
}
|
1725
1837
|
|
1726
1838
|
const func = funcs.find(x => x.index === idx);
|
1727
1839
|
|
1728
1840
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1729
1841
|
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1730
|
-
const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
|
1842
|
+
const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
|
1731
1843
|
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1732
1844
|
|
1733
1845
|
let args = decl.arguments;
|
@@ -1748,7 +1860,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1748
1860
|
const arg = args[i];
|
1749
1861
|
out = out.concat(generate(scope, arg));
|
1750
1862
|
|
1751
|
-
if (builtinFuncs[name] && builtinFuncs[name].params[i] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1863
|
+
if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1864
|
+
out.push(Opcodes.i32_to);
|
1865
|
+
}
|
1866
|
+
|
1867
|
+
if (importedFuncs[name] && name.startsWith('profile')) {
|
1752
1868
|
out.push(Opcodes.i32_to);
|
1753
1869
|
}
|
1754
1870
|
|
@@ -1767,9 +1883,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1767
1883
|
// ...number(type, Valtype.i32),
|
1768
1884
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1769
1885
|
// );
|
1770
|
-
} else out.push(setLastType(scope));
|
1886
|
+
} else out.push(...setLastType(scope));
|
1771
1887
|
|
1772
|
-
if (builtinFuncs[name] && builtinFuncs[name].returns[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1888
|
+
if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1773
1889
|
out.push(Opcodes.i32_from);
|
1774
1890
|
}
|
1775
1891
|
|
@@ -1779,8 +1895,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1779
1895
|
const generateNew = (scope, decl, _global, _name) => {
|
1780
1896
|
// hack: basically treat this as a normal call for builtins for now
|
1781
1897
|
const name = mapName(decl.callee.name);
|
1898
|
+
|
1782
1899
|
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1783
|
-
|
1900
|
+
|
1901
|
+
if (builtinFuncs[name + '$constructor']) {
|
1902
|
+
// custom ...$constructor override builtin func
|
1903
|
+
return generateCall(scope, {
|
1904
|
+
...decl,
|
1905
|
+
callee: {
|
1906
|
+
type: 'Identifier',
|
1907
|
+
name: name + '$constructor'
|
1908
|
+
}
|
1909
|
+
}, _global, _name);
|
1910
|
+
}
|
1911
|
+
|
1912
|
+
if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
|
1784
1913
|
|
1785
1914
|
return generateCall(scope, decl, _global, _name);
|
1786
1915
|
};
|
@@ -1897,7 +2026,7 @@ const brTable = (input, bc, returns) => {
|
|
1897
2026
|
};
|
1898
2027
|
|
1899
2028
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1900
|
-
if (!Prefs.bytestring) delete bc[TYPES.
|
2029
|
+
if (!Prefs.bytestring) delete bc[TYPES.bytestring];
|
1901
2030
|
|
1902
2031
|
const known = knownType(scope, type);
|
1903
2032
|
if (known != null) {
|
@@ -1914,8 +2043,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1914
2043
|
[ Opcodes.block, returns ]
|
1915
2044
|
];
|
1916
2045
|
|
1917
|
-
// todo: use br_table?
|
1918
|
-
|
1919
2046
|
for (const x in bc) {
|
1920
2047
|
if (x === 'default') continue;
|
1921
2048
|
|
@@ -1971,12 +2098,13 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
|
1971
2098
|
};
|
1972
2099
|
|
1973
2100
|
const typeAnnoToPorfType = x => {
|
1974
|
-
if (
|
1975
|
-
if (TYPES[
|
2101
|
+
if (!x) return null;
|
2102
|
+
if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
|
1976
2103
|
|
1977
2104
|
switch (x) {
|
1978
2105
|
case 'i32':
|
1979
2106
|
case 'i64':
|
2107
|
+
case 'f64':
|
1980
2108
|
return TYPES.number;
|
1981
2109
|
}
|
1982
2110
|
|
@@ -1987,7 +2115,7 @@ const extractTypeAnnotation = decl => {
|
|
1987
2115
|
let a = decl;
|
1988
2116
|
while (a.typeAnnotation) a = a.typeAnnotation;
|
1989
2117
|
|
1990
|
-
let type, elementType;
|
2118
|
+
let type = null, elementType = null;
|
1991
2119
|
if (a.typeName) {
|
1992
2120
|
type = a.typeName.name;
|
1993
2121
|
} else if (a.type.endsWith('Keyword')) {
|
@@ -2000,7 +2128,7 @@ const extractTypeAnnotation = decl => {
|
|
2000
2128
|
const typeName = type;
|
2001
2129
|
type = typeAnnoToPorfType(type);
|
2002
2130
|
|
2003
|
-
if (type === TYPES.
|
2131
|
+
if (type === TYPES.bytestring && !Prefs.bytestring) type = TYPES.string;
|
2004
2132
|
|
2005
2133
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
2006
2134
|
|
@@ -2012,13 +2140,13 @@ const generateVar = (scope, decl) => {
|
|
2012
2140
|
|
2013
2141
|
const topLevel = scope.name === 'main';
|
2014
2142
|
|
2015
|
-
// global variable if in top scope (main)
|
2016
|
-
const global = topLevel || decl._bare;
|
2143
|
+
// global variable if in top scope (main) or if internally wanted
|
2144
|
+
const global = topLevel || decl._bare;
|
2017
2145
|
|
2018
2146
|
for (const x of decl.declarations) {
|
2019
2147
|
const name = mapName(x.id.name);
|
2020
2148
|
|
2021
|
-
if (!name) return todo('destructuring is not supported yet');
|
2149
|
+
if (!name) return todo(scope, 'destructuring is not supported yet');
|
2022
2150
|
|
2023
2151
|
if (x.init && isFuncType(x.init.type)) {
|
2024
2152
|
// hack for let a = function () { ... }
|
@@ -2027,7 +2155,6 @@ const generateVar = (scope, decl) => {
|
|
2027
2155
|
continue;
|
2028
2156
|
}
|
2029
2157
|
|
2030
|
-
// console.log(name);
|
2031
2158
|
if (topLevel && builtinVars[name]) {
|
2032
2159
|
// cannot redeclare
|
2033
2160
|
if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
|
@@ -2035,16 +2162,29 @@ const generateVar = (scope, decl) => {
|
|
2035
2162
|
continue; // always ignore
|
2036
2163
|
}
|
2037
2164
|
|
2038
|
-
|
2165
|
+
// // generate init before allocating var
|
2166
|
+
// let generated;
|
2167
|
+
// if (x.init) generated = generate(scope, x.init, global, name);
|
2168
|
+
|
2169
|
+
const typed = typedInput && x.id.typeAnnotation;
|
2170
|
+
let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
|
2039
2171
|
|
2040
|
-
if (
|
2172
|
+
if (typed) {
|
2041
2173
|
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
2042
2174
|
}
|
2043
2175
|
|
2044
2176
|
if (x.init) {
|
2045
|
-
|
2046
|
-
|
2047
|
-
|
2177
|
+
const generated = generate(scope, x.init, global, name);
|
2178
|
+
if (scope.arrays?.get(name) != null) {
|
2179
|
+
// hack to set local as pointer before
|
2180
|
+
out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2181
|
+
if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
|
2182
|
+
generated.pop();
|
2183
|
+
out = out.concat(generated);
|
2184
|
+
} else {
|
2185
|
+
out = out.concat(generated);
|
2186
|
+
out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2187
|
+
}
|
2048
2188
|
out.push(...setType(scope, name, getNodeType(scope, x.init)));
|
2049
2189
|
}
|
2050
2190
|
|
@@ -2055,7 +2195,8 @@ const generateVar = (scope, decl) => {
|
|
2055
2195
|
return out;
|
2056
2196
|
};
|
2057
2197
|
|
2058
|
-
|
2198
|
+
// todo: optimize this func for valueUnused
|
2199
|
+
const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
2059
2200
|
const { type, name } = decl.left;
|
2060
2201
|
|
2061
2202
|
if (type === 'ObjectPattern') {
|
@@ -2070,22 +2211,30 @@ const generateAssign = (scope, decl) => {
|
|
2070
2211
|
return [];
|
2071
2212
|
}
|
2072
2213
|
|
2214
|
+
const op = decl.operator.slice(0, -1) || '=';
|
2215
|
+
|
2073
2216
|
// hack: .length setter
|
2074
2217
|
if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
|
2075
2218
|
const name = decl.left.object.name;
|
2076
|
-
const pointer = arrays
|
2219
|
+
const pointer = scope.arrays?.get(name);
|
2077
2220
|
|
2078
|
-
const aotPointer = pointer != null;
|
2221
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2079
2222
|
|
2080
2223
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
2224
|
+
const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
2081
2225
|
|
2082
2226
|
return [
|
2083
2227
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2084
2228
|
...generate(scope, decl.left.object),
|
2085
2229
|
Opcodes.i32_to_u
|
2086
2230
|
]),
|
2231
|
+
...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
2087
2232
|
|
2088
|
-
...generate(scope, decl.right),
|
2233
|
+
...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
|
2234
|
+
[ Opcodes.local_get, pointerTmp ],
|
2235
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
2236
|
+
Opcodes.i32_from_u
|
2237
|
+
], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
|
2089
2238
|
[ Opcodes.local_tee, newValueTmp ],
|
2090
2239
|
|
2091
2240
|
Opcodes.i32_to_u,
|
@@ -2095,21 +2244,19 @@ const generateAssign = (scope, decl) => {
|
|
2095
2244
|
];
|
2096
2245
|
}
|
2097
2246
|
|
2098
|
-
const op = decl.operator.slice(0, -1) || '=';
|
2099
|
-
|
2100
2247
|
// arr[i]
|
2101
2248
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
2102
2249
|
const name = decl.left.object.name;
|
2103
|
-
const pointer = arrays
|
2250
|
+
const pointer = scope.arrays?.get(name);
|
2104
2251
|
|
2105
|
-
const aotPointer = pointer != null;
|
2252
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2106
2253
|
|
2107
2254
|
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
2108
2255
|
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
2109
2256
|
|
2110
2257
|
return [
|
2111
2258
|
...typeSwitch(scope, getNodeType(scope, decl.left.object), {
|
2112
|
-
[TYPES.
|
2259
|
+
[TYPES.array]: [
|
2113
2260
|
...(aotPointer ? [] : [
|
2114
2261
|
...generate(scope, decl.left.object),
|
2115
2262
|
Opcodes.i32_to_u
|
@@ -2158,7 +2305,7 @@ const generateAssign = (scope, decl) => {
|
|
2158
2305
|
];
|
2159
2306
|
}
|
2160
2307
|
|
2161
|
-
if (!name) return todo('destructuring is not supported yet');
|
2308
|
+
if (!name) return todo(scope, 'destructuring is not supported yet', true);
|
2162
2309
|
|
2163
2310
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2164
2311
|
|
@@ -2263,7 +2410,7 @@ const generateUnary = (scope, decl) => {
|
|
2263
2410
|
return out;
|
2264
2411
|
}
|
2265
2412
|
|
2266
|
-
case 'delete':
|
2413
|
+
case 'delete': {
|
2267
2414
|
let toReturn = true, toGenerate = true;
|
2268
2415
|
|
2269
2416
|
if (decl.argument.type === 'Identifier') {
|
@@ -2285,40 +2432,60 @@ const generateUnary = (scope, decl) => {
|
|
2285
2432
|
|
2286
2433
|
out.push(...number(toReturn ? 1 : 0));
|
2287
2434
|
return out;
|
2435
|
+
}
|
2288
2436
|
|
2289
|
-
case 'typeof':
|
2290
|
-
|
2437
|
+
case 'typeof': {
|
2438
|
+
let overrideType, toGenerate = true;
|
2439
|
+
|
2440
|
+
if (decl.argument.type === 'Identifier') {
|
2441
|
+
const out = generateIdent(scope, decl.argument);
|
2442
|
+
|
2443
|
+
// if ReferenceError (undeclared var), ignore and return undefined
|
2444
|
+
if (out[1]) {
|
2445
|
+
// does not exist (2 ops from throw)
|
2446
|
+
overrideType = number(TYPES.undefined, Valtype.i32);
|
2447
|
+
toGenerate = false;
|
2448
|
+
}
|
2449
|
+
}
|
2450
|
+
|
2451
|
+
const out = toGenerate ? generate(scope, decl.argument) : [];
|
2452
|
+
disposeLeftover(out);
|
2453
|
+
|
2454
|
+
out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
|
2291
2455
|
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
2292
2456
|
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
2293
2457
|
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
2294
2458
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
2295
2459
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
2296
2460
|
|
2297
|
-
[TYPES.
|
2461
|
+
[TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2298
2462
|
|
2299
2463
|
// object and internal types
|
2300
2464
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2301
|
-
});
|
2465
|
+
}));
|
2466
|
+
|
2467
|
+
return out;
|
2468
|
+
}
|
2302
2469
|
|
2303
2470
|
default:
|
2304
|
-
return todo(`unary operator ${decl.operator} not implemented yet
|
2471
|
+
return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
|
2305
2472
|
}
|
2306
2473
|
};
|
2307
2474
|
|
2308
|
-
const generateUpdate = (scope, decl) => {
|
2475
|
+
const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
|
2309
2476
|
const { name } = decl.argument;
|
2310
2477
|
|
2311
2478
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2312
2479
|
|
2313
2480
|
if (local === undefined) {
|
2314
|
-
return todo(`update expression with undefined variable
|
2481
|
+
return todo(scope, `update expression with undefined variable`, true);
|
2315
2482
|
}
|
2316
2483
|
|
2317
2484
|
const idx = local.idx;
|
2318
2485
|
const out = [];
|
2319
2486
|
|
2320
2487
|
out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2321
|
-
if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2488
|
+
if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2322
2489
|
|
2323
2490
|
switch (decl.operator) {
|
2324
2491
|
case '++':
|
@@ -2331,7 +2498,7 @@ const generateUpdate = (scope, decl) => {
|
|
2331
2498
|
}
|
2332
2499
|
|
2333
2500
|
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2334
|
-
if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2501
|
+
if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2335
2502
|
|
2336
2503
|
return out;
|
2337
2504
|
};
|
@@ -2371,7 +2538,7 @@ const generateConditional = (scope, decl) => {
|
|
2371
2538
|
// note type
|
2372
2539
|
out.push(
|
2373
2540
|
...getNodeType(scope, decl.consequent),
|
2374
|
-
setLastType(scope)
|
2541
|
+
...setLastType(scope)
|
2375
2542
|
);
|
2376
2543
|
|
2377
2544
|
out.push([ Opcodes.else ]);
|
@@ -2380,7 +2547,7 @@ const generateConditional = (scope, decl) => {
|
|
2380
2547
|
// note type
|
2381
2548
|
out.push(
|
2382
2549
|
...getNodeType(scope, decl.alternate),
|
2383
|
-
setLastType(scope)
|
2550
|
+
...setLastType(scope)
|
2384
2551
|
);
|
2385
2552
|
|
2386
2553
|
out.push([ Opcodes.end ]);
|
@@ -2394,7 +2561,7 @@ const generateFor = (scope, decl) => {
|
|
2394
2561
|
const out = [];
|
2395
2562
|
|
2396
2563
|
if (decl.init) {
|
2397
|
-
out.push(...generate(scope, decl.init));
|
2564
|
+
out.push(...generate(scope, decl.init, false, undefined, true));
|
2398
2565
|
disposeLeftover(out);
|
2399
2566
|
}
|
2400
2567
|
|
@@ -2412,7 +2579,7 @@ const generateFor = (scope, decl) => {
|
|
2412
2579
|
out.push(...generate(scope, decl.body));
|
2413
2580
|
out.push([ Opcodes.end ]);
|
2414
2581
|
|
2415
|
-
if (decl.update) out.push(...generate(scope, decl.update));
|
2582
|
+
if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
|
2416
2583
|
|
2417
2584
|
out.push([ Opcodes.br, 1 ]);
|
2418
2585
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2440,6 +2607,36 @@ const generateWhile = (scope, decl) => {
|
|
2440
2607
|
return out;
|
2441
2608
|
};
|
2442
2609
|
|
2610
|
+
const generateDoWhile = (scope, decl) => {
|
2611
|
+
const out = [];
|
2612
|
+
|
2613
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
2614
|
+
depth.push('dowhile');
|
2615
|
+
|
2616
|
+
// block for break (includes all)
|
2617
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2618
|
+
depth.push('block');
|
2619
|
+
|
2620
|
+
// block for continue
|
2621
|
+
// includes body but not test+loop so we can exit body at anytime
|
2622
|
+
// and still test+loop after
|
2623
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2624
|
+
depth.push('block');
|
2625
|
+
|
2626
|
+
out.push(...generate(scope, decl.body));
|
2627
|
+
|
2628
|
+
out.push([ Opcodes.end ]);
|
2629
|
+
depth.pop();
|
2630
|
+
|
2631
|
+
out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2632
|
+
out.push([ Opcodes.br_if, 1 ]);
|
2633
|
+
|
2634
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
2635
|
+
depth.pop(); depth.pop();
|
2636
|
+
|
2637
|
+
return out;
|
2638
|
+
};
|
2639
|
+
|
2443
2640
|
const generateForOf = (scope, decl) => {
|
2444
2641
|
const out = [];
|
2445
2642
|
|
@@ -2476,7 +2673,10 @@ const generateForOf = (scope, decl) => {
|
|
2476
2673
|
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2477
2674
|
}
|
2478
2675
|
|
2676
|
+
// if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
|
2677
|
+
|
2479
2678
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2679
|
+
if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
|
2480
2680
|
|
2481
2681
|
depth.push('block');
|
2482
2682
|
depth.push('block');
|
@@ -2485,6 +2685,7 @@ const generateForOf = (scope, decl) => {
|
|
2485
2685
|
// hack: this is naughty and will break things!
|
2486
2686
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2487
2687
|
if (pages.hasAnyString) {
|
2688
|
+
// todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
|
2488
2689
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2489
2690
|
rawElements: new Array(1)
|
2490
2691
|
}, isGlobal, leftName, true, 'i16');
|
@@ -2493,7 +2694,7 @@ const generateForOf = (scope, decl) => {
|
|
2493
2694
|
// set type for local
|
2494
2695
|
// todo: optimize away counter and use end pointer
|
2495
2696
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2496
|
-
[TYPES.
|
2697
|
+
[TYPES.array]: [
|
2497
2698
|
...setType(scope, leftName, TYPES.number),
|
2498
2699
|
|
2499
2700
|
[ Opcodes.loop, Blocktype.void ],
|
@@ -2576,6 +2777,56 @@ const generateForOf = (scope, decl) => {
|
|
2576
2777
|
[ Opcodes.end ],
|
2577
2778
|
[ Opcodes.end ]
|
2578
2779
|
],
|
2780
|
+
[TYPES.bytestring]: [
|
2781
|
+
...setType(scope, leftName, TYPES.bytestring),
|
2782
|
+
|
2783
|
+
[ Opcodes.loop, Blocktype.void ],
|
2784
|
+
|
2785
|
+
// setup new/out array
|
2786
|
+
...newOut,
|
2787
|
+
[ Opcodes.drop ],
|
2788
|
+
|
2789
|
+
...number(0, Valtype.i32), // base 0 for store after
|
2790
|
+
|
2791
|
+
// load current string ind {arg}
|
2792
|
+
[ Opcodes.local_get, pointer ],
|
2793
|
+
[ Opcodes.local_get, counter ],
|
2794
|
+
[ Opcodes.i32_add ],
|
2795
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
2796
|
+
|
2797
|
+
// store to new string ind 0
|
2798
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2799
|
+
|
2800
|
+
// return new string (page)
|
2801
|
+
...number(newPointer),
|
2802
|
+
|
2803
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2804
|
+
|
2805
|
+
[ Opcodes.block, Blocktype.void ],
|
2806
|
+
[ Opcodes.block, Blocktype.void ],
|
2807
|
+
...generate(scope, decl.body),
|
2808
|
+
[ Opcodes.end ],
|
2809
|
+
|
2810
|
+
// increment iter pointer
|
2811
|
+
// [ Opcodes.local_get, pointer ],
|
2812
|
+
// ...number(1, Valtype.i32),
|
2813
|
+
// [ Opcodes.i32_add ],
|
2814
|
+
// [ Opcodes.local_set, pointer ],
|
2815
|
+
|
2816
|
+
// increment counter by 1
|
2817
|
+
[ Opcodes.local_get, counter ],
|
2818
|
+
...number(1, Valtype.i32),
|
2819
|
+
[ Opcodes.i32_add ],
|
2820
|
+
[ Opcodes.local_tee, counter ],
|
2821
|
+
|
2822
|
+
// loop if counter != length
|
2823
|
+
[ Opcodes.local_get, length ],
|
2824
|
+
[ Opcodes.i32_ne ],
|
2825
|
+
[ Opcodes.br_if, 1 ],
|
2826
|
+
|
2827
|
+
[ Opcodes.end ],
|
2828
|
+
[ Opcodes.end ]
|
2829
|
+
],
|
2579
2830
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2580
2831
|
}, Blocktype.void));
|
2581
2832
|
|
@@ -2586,28 +2837,65 @@ const generateForOf = (scope, decl) => {
|
|
2586
2837
|
return out;
|
2587
2838
|
};
|
2588
2839
|
|
2840
|
+
// find the nearest loop in depth map by type
|
2589
2841
|
const getNearestLoop = () => {
|
2590
2842
|
for (let i = depth.length - 1; i >= 0; i--) {
|
2591
|
-
if (
|
2843
|
+
if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
|
2592
2844
|
}
|
2593
2845
|
|
2594
2846
|
return -1;
|
2595
2847
|
};
|
2596
2848
|
|
2597
2849
|
const generateBreak = (scope, decl) => {
|
2598
|
-
const
|
2850
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2851
|
+
const type = depth[target];
|
2852
|
+
|
2853
|
+
// different loop types have different branch offsets
|
2854
|
+
// as they have different wasm block/loop/if structures
|
2855
|
+
// we need to use the right offset by type to branch to the one we want
|
2856
|
+
// for a break: exit the loop without executing anything else inside it
|
2857
|
+
const offset = ({
|
2858
|
+
for: 2, // loop > if (wanted branch) > block (we are here)
|
2859
|
+
while: 2, // loop > if (wanted branch) (we are here)
|
2860
|
+
dowhile: 2, // loop > block (wanted branch) > block (we are here)
|
2861
|
+
forof: 2, // loop > block (wanted branch) > block (we are here)
|
2862
|
+
if: 1 // break inside if, branch 0 to skip the rest of the if
|
2863
|
+
})[type];
|
2864
|
+
|
2599
2865
|
return [
|
2600
|
-
[ Opcodes.br, ...signedLEB128(
|
2866
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2601
2867
|
];
|
2602
2868
|
};
|
2603
2869
|
|
2604
2870
|
const generateContinue = (scope, decl) => {
|
2605
|
-
const
|
2871
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2872
|
+
const type = depth[target];
|
2873
|
+
|
2874
|
+
// different loop types have different branch offsets
|
2875
|
+
// as they have different wasm block/loop/if structures
|
2876
|
+
// we need to use the right offset by type to branch to the one we want
|
2877
|
+
// for a continue: do test for the loop, and then loop depending on that success
|
2878
|
+
const offset = ({
|
2879
|
+
for: 3, // loop (wanted branch) > if > block (we are here)
|
2880
|
+
while: 1, // loop (wanted branch) > if (we are here)
|
2881
|
+
dowhile: 3, // loop > block > block (wanted branch) (we are here)
|
2882
|
+
forof: 3 // loop > block > block (wanted branch) (we are here)
|
2883
|
+
})[type];
|
2884
|
+
|
2606
2885
|
return [
|
2607
|
-
[ Opcodes.br, ...signedLEB128(
|
2886
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2608
2887
|
];
|
2609
2888
|
};
|
2610
2889
|
|
2890
|
+
const generateLabel = (scope, decl) => {
|
2891
|
+
scope.labels ??= new Map();
|
2892
|
+
|
2893
|
+
const name = decl.label.name;
|
2894
|
+
scope.labels.set(name, depth.length);
|
2895
|
+
|
2896
|
+
return generate(scope, decl.body);
|
2897
|
+
};
|
2898
|
+
|
2611
2899
|
const generateThrow = (scope, decl) => {
|
2612
2900
|
scope.throws = true;
|
2613
2901
|
|
@@ -2640,7 +2928,7 @@ const generateThrow = (scope, decl) => {
|
|
2640
2928
|
};
|
2641
2929
|
|
2642
2930
|
const generateTry = (scope, decl) => {
|
2643
|
-
if (decl.finalizer) return todo('try finally not implemented yet');
|
2931
|
+
if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
|
2644
2932
|
|
2645
2933
|
const out = [];
|
2646
2934
|
|
@@ -2667,13 +2955,6 @@ const generateEmpty = (scope, decl) => {
|
|
2667
2955
|
return [];
|
2668
2956
|
};
|
2669
2957
|
|
2670
|
-
const generateAssignPat = (scope, decl) => {
|
2671
|
-
// TODO
|
2672
|
-
// if identifier declared, use that
|
2673
|
-
// else, use default (right)
|
2674
|
-
return todo('assignment pattern (optional arg)');
|
2675
|
-
};
|
2676
|
-
|
2677
2958
|
let pages = new Map();
|
2678
2959
|
const allocPage = (scope, reason, type) => {
|
2679
2960
|
if (pages.has(reason)) return pages.get(reason).ind;
|
@@ -2750,16 +3031,22 @@ const getAllocType = itemType => {
|
|
2750
3031
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2751
3032
|
const out = [];
|
2752
3033
|
|
3034
|
+
scope.arrays ??= new Map();
|
3035
|
+
|
2753
3036
|
let firstAssign = false;
|
2754
|
-
if (!arrays.has(name) || name === '$undeclared') {
|
3037
|
+
if (!scope.arrays.has(name) || name === '$undeclared') {
|
2755
3038
|
firstAssign = true;
|
2756
3039
|
|
2757
3040
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2758
3041
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2759
|
-
|
3042
|
+
|
3043
|
+
if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
|
3044
|
+
else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2760
3045
|
}
|
2761
3046
|
|
2762
|
-
const pointer = arrays.get(name);
|
3047
|
+
const pointer = scope.arrays.get(name);
|
3048
|
+
|
3049
|
+
const local = global ? globals[name] : scope.locals[name];
|
2763
3050
|
|
2764
3051
|
const useRawElements = !!decl.rawElements;
|
2765
3052
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
@@ -2793,11 +3080,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2793
3080
|
return [ out, pointer ];
|
2794
3081
|
}
|
2795
3082
|
|
3083
|
+
const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
|
3084
|
+
if (pointerTmp != null) {
|
3085
|
+
out.push(
|
3086
|
+
[ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
3087
|
+
Opcodes.i32_to_u,
|
3088
|
+
[ Opcodes.local_set, pointerTmp ]
|
3089
|
+
);
|
3090
|
+
}
|
3091
|
+
|
3092
|
+
const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
|
3093
|
+
|
2796
3094
|
// store length as 0th array
|
2797
3095
|
out.push(
|
2798
|
-
...
|
3096
|
+
...pointerWasm,
|
2799
3097
|
...number(length, Valtype.i32),
|
2800
|
-
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1,
|
3098
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
|
2801
3099
|
);
|
2802
3100
|
|
2803
3101
|
const storeOp = StoreOps[itemType];
|
@@ -2806,14 +3104,14 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2806
3104
|
if (elements[i] == null) continue;
|
2807
3105
|
|
2808
3106
|
out.push(
|
2809
|
-
...
|
3107
|
+
...pointerWasm,
|
2810
3108
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2811
|
-
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(
|
3109
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2812
3110
|
);
|
2813
3111
|
}
|
2814
3112
|
|
2815
3113
|
// local value as pointer
|
2816
|
-
out.push(...
|
3114
|
+
out.push(...pointerWasm, Opcodes.i32_from_u);
|
2817
3115
|
|
2818
3116
|
return [ out, pointer ];
|
2819
3117
|
};
|
@@ -2845,20 +3143,53 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
|
|
2845
3143
|
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2846
3144
|
};
|
2847
3145
|
|
2848
|
-
let arrays = new Map();
|
2849
3146
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
2850
3147
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
2851
3148
|
};
|
2852
3149
|
|
2853
3150
|
export const generateMember = (scope, decl, _global, _name) => {
|
2854
3151
|
const name = decl.object.name;
|
2855
|
-
const pointer = arrays
|
3152
|
+
const pointer = scope.arrays?.get(name);
|
2856
3153
|
|
2857
|
-
const aotPointer = pointer != null;
|
3154
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
3155
|
+
|
3156
|
+
// hack: .name
|
3157
|
+
if (decl.property.name === 'name') {
|
3158
|
+
if (hasFuncWithName(name)) {
|
3159
|
+
let nameProp = name;
|
3160
|
+
|
3161
|
+
// eg: __String_prototype_toLowerCase -> toLowerCase
|
3162
|
+
if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
|
3163
|
+
|
3164
|
+
return makeString(scope, nameProp, _global, _name, true);
|
3165
|
+
} else {
|
3166
|
+
return generate(scope, DEFAULT_VALUE);
|
3167
|
+
}
|
3168
|
+
}
|
2858
3169
|
|
2859
3170
|
// hack: .length
|
2860
3171
|
if (decl.property.name === 'length') {
|
2861
|
-
|
3172
|
+
const func = funcs.find(x => x.name === name);
|
3173
|
+
if (func) {
|
3174
|
+
const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
|
3175
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
3176
|
+
return number(typedParams ? func.params.length / 2 : func.params.length);
|
3177
|
+
}
|
3178
|
+
|
3179
|
+
if (builtinFuncs[name + '$constructor']) {
|
3180
|
+
const regularFunc = builtinFuncs[name];
|
3181
|
+
const regularParams = regularFunc.typedParams ? (regularFunc.params.length / 2) : regularFunc.params.length;
|
3182
|
+
|
3183
|
+
const constructorFunc = builtinFuncs[name + '$constructor'];
|
3184
|
+
const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
|
3185
|
+
|
3186
|
+
return number(Math.max(regularParams, constructorParams));
|
3187
|
+
}
|
3188
|
+
|
3189
|
+
if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
|
3190
|
+
if (importedFuncs[name]) return number(importedFuncs[name].params);
|
3191
|
+
if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
|
3192
|
+
|
2862
3193
|
return [
|
2863
3194
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2864
3195
|
...generate(scope, decl.object),
|
@@ -2883,7 +3214,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2883
3214
|
}
|
2884
3215
|
|
2885
3216
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2886
|
-
[TYPES.
|
3217
|
+
[TYPES.array]: [
|
2887
3218
|
// get index as valtype
|
2888
3219
|
...property,
|
2889
3220
|
|
@@ -2902,7 +3233,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2902
3233
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2903
3234
|
|
2904
3235
|
...number(TYPES.number, Valtype.i32),
|
2905
|
-
setLastType(scope)
|
3236
|
+
...setLastType(scope)
|
2906
3237
|
],
|
2907
3238
|
|
2908
3239
|
[TYPES.string]: [
|
@@ -2934,9 +3265,9 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2934
3265
|
...number(newPointer),
|
2935
3266
|
|
2936
3267
|
...number(TYPES.string, Valtype.i32),
|
2937
|
-
setLastType(scope)
|
3268
|
+
...setLastType(scope)
|
2938
3269
|
],
|
2939
|
-
[TYPES.
|
3270
|
+
[TYPES.bytestring]: [
|
2940
3271
|
// setup new/out array
|
2941
3272
|
...newOut,
|
2942
3273
|
[ Opcodes.drop ],
|
@@ -2953,19 +3284,19 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2953
3284
|
]),
|
2954
3285
|
|
2955
3286
|
// load current string ind {arg}
|
2956
|
-
[ Opcodes.i32_load8_u,
|
3287
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2957
3288
|
|
2958
3289
|
// store to new string ind 0
|
2959
|
-
[ Opcodes.i32_store8,
|
3290
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2960
3291
|
|
2961
3292
|
// return new string (page)
|
2962
3293
|
...number(newPointer),
|
2963
3294
|
|
2964
|
-
...number(TYPES.
|
2965
|
-
setLastType(scope)
|
3295
|
+
...number(TYPES.bytestring, Valtype.i32),
|
3296
|
+
...setLastType(scope)
|
2966
3297
|
],
|
2967
3298
|
|
2968
|
-
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
|
3299
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
|
2969
3300
|
});
|
2970
3301
|
};
|
2971
3302
|
|
@@ -2975,28 +3306,36 @@ const objectHack = node => {
|
|
2975
3306
|
if (!node) return node;
|
2976
3307
|
|
2977
3308
|
if (node.type === 'MemberExpression') {
|
2978
|
-
|
3309
|
+
const out = (() => {
|
3310
|
+
if (node.computed || node.optional) return;
|
2979
3311
|
|
2980
|
-
|
3312
|
+
let objectName = node.object.name;
|
2981
3313
|
|
2982
|
-
|
2983
|
-
|
3314
|
+
// if object is not identifier or another member exp, give up
|
3315
|
+
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
|
3316
|
+
if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
|
2984
3317
|
|
2985
|
-
|
3318
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2986
3319
|
|
2987
|
-
|
2988
|
-
|
3320
|
+
// if .name or .length, give up (hack within a hack!)
|
3321
|
+
if (['name', 'length'].includes(node.property.name)) {
|
3322
|
+
node.object = objectHack(node.object);
|
3323
|
+
return;
|
3324
|
+
}
|
2989
3325
|
|
2990
|
-
|
2991
|
-
|
3326
|
+
// no object name, give up
|
3327
|
+
if (!objectName) return;
|
2992
3328
|
|
2993
|
-
|
2994
|
-
|
3329
|
+
const name = '__' + objectName + '_' + node.property.name;
|
3330
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2995
3331
|
|
2996
|
-
|
2997
|
-
|
2998
|
-
|
2999
|
-
|
3332
|
+
return {
|
3333
|
+
type: 'Identifier',
|
3334
|
+
name
|
3335
|
+
};
|
3336
|
+
})();
|
3337
|
+
|
3338
|
+
if (out) return out;
|
3000
3339
|
}
|
3001
3340
|
|
3002
3341
|
for (const x in node) {
|
@@ -3010,8 +3349,8 @@ const objectHack = node => {
|
|
3010
3349
|
};
|
3011
3350
|
|
3012
3351
|
const generateFunc = (scope, decl) => {
|
3013
|
-
if (decl.async) return todo('async functions are not supported');
|
3014
|
-
if (decl.generator) return todo('generator functions are not supported');
|
3352
|
+
if (decl.async) return todo(scope, 'async functions are not supported');
|
3353
|
+
if (decl.generator) return todo(scope, 'generator functions are not supported');
|
3015
3354
|
|
3016
3355
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
3017
3356
|
const params = decl.params ?? [];
|
@@ -3027,6 +3366,14 @@ const generateFunc = (scope, decl) => {
|
|
3027
3366
|
name
|
3028
3367
|
};
|
3029
3368
|
|
3369
|
+
if (typedInput && decl.returnType) {
|
3370
|
+
const { type } = extractTypeAnnotation(decl.returnType);
|
3371
|
+
if (type != null) {
|
3372
|
+
innerScope.returnType = type;
|
3373
|
+
innerScope.returns = [ valtypeBinary ];
|
3374
|
+
}
|
3375
|
+
}
|
3376
|
+
|
3030
3377
|
for (let i = 0; i < params.length; i++) {
|
3031
3378
|
allocVar(innerScope, params[i].name, false);
|
3032
3379
|
|
@@ -3066,7 +3413,7 @@ const generateFunc = (scope, decl) => {
|
|
3066
3413
|
if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
|
3067
3414
|
wasm.push(
|
3068
3415
|
...number(0),
|
3069
|
-
...number(TYPES.undefined, Valtype.i32),
|
3416
|
+
...(innerScope.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
|
3070
3417
|
[ Opcodes.return ]
|
3071
3418
|
);
|
3072
3419
|
}
|
@@ -3089,16 +3436,6 @@ const generateCode = (scope, decl) => {
|
|
3089
3436
|
};
|
3090
3437
|
|
3091
3438
|
const internalConstrs = {
|
3092
|
-
Boolean: {
|
3093
|
-
generate: (scope, decl) => {
|
3094
|
-
if (decl.arguments.length === 0) return number(0);
|
3095
|
-
|
3096
|
-
// should generate/run all args
|
3097
|
-
return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
|
3098
|
-
},
|
3099
|
-
type: TYPES.boolean
|
3100
|
-
},
|
3101
|
-
|
3102
3439
|
Array: {
|
3103
3440
|
generate: (scope, decl, global, name) => {
|
3104
3441
|
// new Array(i0, i1, ...)
|
@@ -3116,7 +3453,7 @@ const internalConstrs = {
|
|
3116
3453
|
|
3117
3454
|
// todo: check in wasm instead of here
|
3118
3455
|
const literalValue = arg.value ?? 0;
|
3119
|
-
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
|
3456
|
+
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
|
3120
3457
|
|
3121
3458
|
return [
|
3122
3459
|
...number(0, Valtype.i32),
|
@@ -3127,7 +3464,8 @@ const internalConstrs = {
|
|
3127
3464
|
...number(pointer)
|
3128
3465
|
];
|
3129
3466
|
},
|
3130
|
-
type: TYPES.
|
3467
|
+
type: TYPES.array,
|
3468
|
+
length: 1
|
3131
3469
|
},
|
3132
3470
|
|
3133
3471
|
__Array_of: {
|
@@ -3138,27 +3476,134 @@ const internalConstrs = {
|
|
3138
3476
|
elements: decl.arguments
|
3139
3477
|
}, global, name);
|
3140
3478
|
},
|
3141
|
-
type: TYPES.
|
3479
|
+
type: TYPES.array,
|
3480
|
+
notConstr: true,
|
3481
|
+
length: 0
|
3482
|
+
},
|
3483
|
+
|
3484
|
+
__Porffor_fastOr: {
|
3485
|
+
generate: (scope, decl) => {
|
3486
|
+
const out = [];
|
3487
|
+
|
3488
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3489
|
+
out.push(
|
3490
|
+
...generate(scope, decl.arguments[i]),
|
3491
|
+
Opcodes.i32_to_u,
|
3492
|
+
...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
|
3493
|
+
);
|
3494
|
+
}
|
3495
|
+
|
3496
|
+
out.push(Opcodes.i32_from_u);
|
3497
|
+
|
3498
|
+
return out;
|
3499
|
+
},
|
3500
|
+
type: TYPES.boolean,
|
3142
3501
|
notConstr: true
|
3143
|
-
}
|
3144
|
-
|
3502
|
+
},
|
3503
|
+
|
3504
|
+
__Porffor_fastAnd: {
|
3505
|
+
generate: (scope, decl) => {
|
3506
|
+
const out = [];
|
3507
|
+
|
3508
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3509
|
+
out.push(
|
3510
|
+
...generate(scope, decl.arguments[i]),
|
3511
|
+
Opcodes.i32_to_u,
|
3512
|
+
...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
|
3513
|
+
);
|
3514
|
+
}
|
3515
|
+
|
3516
|
+
out.push(Opcodes.i32_from_u);
|
3145
3517
|
|
3146
|
-
|
3147
|
-
|
3148
|
-
|
3149
|
-
|
3150
|
-
|
3151
|
-
|
3152
|
-
|
3153
|
-
|
3154
|
-
//
|
3155
|
-
|
3156
|
-
|
3157
|
-
|
3158
|
-
|
3518
|
+
return out;
|
3519
|
+
},
|
3520
|
+
type: TYPES.boolean,
|
3521
|
+
notConstr: true
|
3522
|
+
},
|
3523
|
+
|
3524
|
+
Boolean: {
|
3525
|
+
generate: (scope, decl) => {
|
3526
|
+
// todo: boolean object when used as constructor
|
3527
|
+
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
3528
|
+
return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
|
3529
|
+
},
|
3530
|
+
type: TYPES.boolean,
|
3531
|
+
length: 1
|
3532
|
+
},
|
3159
3533
|
|
3160
|
-
|
3161
|
-
|
3534
|
+
__Math_max: {
|
3535
|
+
generate: (scope, decl) => {
|
3536
|
+
const out = [
|
3537
|
+
...number(-Infinity)
|
3538
|
+
];
|
3539
|
+
|
3540
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3541
|
+
out.push(
|
3542
|
+
...generate(scope, decl.arguments[i]),
|
3543
|
+
[ Opcodes.f64_max ]
|
3544
|
+
);
|
3545
|
+
}
|
3546
|
+
|
3547
|
+
return out;
|
3548
|
+
},
|
3549
|
+
type: TYPES.number,
|
3550
|
+
notConstr: true,
|
3551
|
+
length: 2
|
3552
|
+
},
|
3553
|
+
|
3554
|
+
__Math_min: {
|
3555
|
+
generate: (scope, decl) => {
|
3556
|
+
const out = [
|
3557
|
+
...number(Infinity)
|
3558
|
+
];
|
3559
|
+
|
3560
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3561
|
+
out.push(
|
3562
|
+
...generate(scope, decl.arguments[i]),
|
3563
|
+
[ Opcodes.f64_min ]
|
3564
|
+
);
|
3565
|
+
}
|
3566
|
+
|
3567
|
+
return out;
|
3568
|
+
},
|
3569
|
+
type: TYPES.number,
|
3570
|
+
notConstr: true,
|
3571
|
+
length: 2
|
3572
|
+
},
|
3573
|
+
|
3574
|
+
__console_log: {
|
3575
|
+
generate: (scope, decl) => {
|
3576
|
+
const out = [];
|
3577
|
+
|
3578
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3579
|
+
out.push(
|
3580
|
+
...generateCall(scope, {
|
3581
|
+
callee: {
|
3582
|
+
type: 'Identifier',
|
3583
|
+
name: '__Porffor_print'
|
3584
|
+
},
|
3585
|
+
arguments: [ decl.arguments[i] ]
|
3586
|
+
}),
|
3587
|
+
|
3588
|
+
// print space
|
3589
|
+
...number(32),
|
3590
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3591
|
+
);
|
3592
|
+
}
|
3593
|
+
|
3594
|
+
// print newline
|
3595
|
+
out.push(
|
3596
|
+
...number(10),
|
3597
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3598
|
+
);
|
3599
|
+
|
3600
|
+
return out;
|
3601
|
+
},
|
3602
|
+
type: TYPES.undefined,
|
3603
|
+
notConstr: true,
|
3604
|
+
length: 0
|
3605
|
+
}
|
3606
|
+
};
|
3162
3607
|
|
3163
3608
|
export default program => {
|
3164
3609
|
globals = {};
|
@@ -3168,20 +3613,23 @@ export default program => {
|
|
3168
3613
|
funcs = [];
|
3169
3614
|
funcIndex = {};
|
3170
3615
|
depth = [];
|
3171
|
-
arrays = new Map();
|
3172
3616
|
pages = new Map();
|
3173
3617
|
data = [];
|
3174
3618
|
currentFuncIndex = importedFuncs.length;
|
3175
3619
|
|
3176
3620
|
globalThis.valtype = 'f64';
|
3177
3621
|
|
3178
|
-
const valtypeOpt = process.argv.find(x => x.startsWith('
|
3622
|
+
const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
|
3179
3623
|
if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
|
3180
3624
|
|
3181
3625
|
globalThis.valtypeBinary = Valtype[valtype];
|
3182
3626
|
|
3183
3627
|
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
3184
3628
|
|
3629
|
+
globalThis.pageSize = PageSize;
|
3630
|
+
const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
|
3631
|
+
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3632
|
+
|
3185
3633
|
// set generic opcodes for current valtype
|
3186
3634
|
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
3187
3635
|
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
@@ -3190,10 +3638,10 @@ export default program => {
|
|
3190
3638
|
Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
|
3191
3639
|
Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
|
3192
3640
|
|
3193
|
-
Opcodes.i32_to = [ [
|
3194
|
-
Opcodes.i32_to_u = [ [
|
3195
|
-
Opcodes.i32_from = [ [
|
3196
|
-
Opcodes.i32_from_u = [ [
|
3641
|
+
Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
|
3642
|
+
Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
|
3643
|
+
Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
|
3644
|
+
Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
|
3197
3645
|
|
3198
3646
|
Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
|
3199
3647
|
Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
|
@@ -3206,10 +3654,6 @@ export default program => {
|
|
3206
3654
|
|
3207
3655
|
program.id = { name: 'main' };
|
3208
3656
|
|
3209
|
-
globalThis.pageSize = PageSize;
|
3210
|
-
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
3211
|
-
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3212
|
-
|
3213
3657
|
const scope = {
|
3214
3658
|
locals: {},
|
3215
3659
|
localInd: 0
|
@@ -3220,7 +3664,7 @@ export default program => {
|
|
3220
3664
|
body: program.body
|
3221
3665
|
};
|
3222
3666
|
|
3223
|
-
if (Prefs.astLog) console.log(program.body.body);
|
3667
|
+
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
3224
3668
|
|
3225
3669
|
generateFunc(scope, program);
|
3226
3670
|
|
@@ -3237,7 +3681,11 @@ export default program => {
|
|
3237
3681
|
}
|
3238
3682
|
|
3239
3683
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
3240
|
-
|
3684
|
+
if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
|
3685
|
+
main.wasm.splice(main.wasm.length - 1, 1);
|
3686
|
+
} else {
|
3687
|
+
main.returns = [];
|
3688
|
+
}
|
3241
3689
|
}
|
3242
3690
|
|
3243
3691
|
if (lastInst[0] === Opcodes.call) {
|