porffor 0.2.0-5ac7ea0 → 0.2.0-5ad562e
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 +255 -0
- package/LICENSE +20 -20
- package/README.md +115 -82
- package/asur/index.js +624 -340
- package/byg/index.js +237 -0
- package/compiler/2c.js +1 -1
- package/compiler/{sections.js → assemble.js} +59 -12
- 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/crypto.ts +29 -41
- package/compiler/builtins/date.ts +2071 -0
- package/compiler/builtins/escape.ts +141 -0
- package/compiler/builtins/int.ts +147 -0
- package/compiler/builtins/number.ts +527 -0
- package/compiler/builtins/porffor.d.ts +42 -9
- package/compiler/builtins/string.ts +1055 -0
- package/compiler/builtins/tostring.ts +45 -0
- package/compiler/builtins.js +58 -85
- package/compiler/{codeGen.js → codegen.js} +792 -279
- package/compiler/decompile.js +0 -1
- package/compiler/embedding.js +22 -22
- package/compiler/encoding.js +108 -10
- package/compiler/generated_builtins.js +1463 -7
- package/compiler/index.js +16 -14
- package/compiler/log.js +2 -2
- package/compiler/opt.js +23 -22
- package/compiler/parse.js +30 -22
- package/compiler/precompile.js +25 -26
- package/compiler/prefs.js +7 -6
- package/compiler/prototype.js +2 -18
- package/compiler/types.js +37 -0
- package/compiler/wasmSpec.js +11 -1
- package/compiler/wrap.js +41 -44
- 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 +34 -34
- package/runner/debug.js +122 -0
- package/runner/index.js +69 -12
- package/runner/profiler.js +45 -26
- package/runner/repl.js +42 -9
- package/runner/sizes.js +37 -37
- package/runner/info.js +0 -89
- package/runner/transform.js +0 -15
- package/util/enum.js +0 -20
@@ -8,6 +8,7 @@ import { log } from "./log.js";
|
|
8
8
|
import parse from "./parse.js";
|
9
9
|
import * as Rhemyn from "../rhemyn/compile.js";
|
10
10
|
import Prefs from './prefs.js';
|
11
|
+
import { TYPES, TYPE_NAMES } from './types.js';
|
11
12
|
|
12
13
|
let globals = {};
|
13
14
|
let globalInd = 0;
|
@@ -24,35 +25,37 @@ const debug = str => {
|
|
24
25
|
const logChar = n => {
|
25
26
|
code.push(...number(n));
|
26
27
|
|
27
|
-
code.push(Opcodes.call);
|
28
|
-
code.push(...unsignedLEB128(0));
|
28
|
+
code.push([ Opcodes.call, 0 ]);
|
29
29
|
};
|
30
30
|
|
31
31
|
for (let i = 0; i < str.length; i++) {
|
32
32
|
logChar(str.charCodeAt(i));
|
33
33
|
}
|
34
34
|
|
35
|
-
logChar(
|
35
|
+
logChar(10); // new line
|
36
36
|
|
37
37
|
return code;
|
38
38
|
};
|
39
39
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
this.name = 'TodoError';
|
45
|
-
}
|
40
|
+
class TodoError extends Error {
|
41
|
+
constructor(message) {
|
42
|
+
super(message);
|
43
|
+
this.name = 'TodoError';
|
46
44
|
}
|
45
|
+
}
|
46
|
+
const todo = (scope, msg, expectsValue = undefined) => {
|
47
|
+
switch (Prefs.todoTime ?? 'runtime') {
|
48
|
+
case 'compile':
|
49
|
+
throw new TodoError(msg);
|
47
50
|
|
48
|
-
|
49
|
-
|
50
|
-
const code = [];
|
51
|
-
|
52
|
-
code.push(...debug(`todo! ` + msg));
|
53
|
-
code.push(Opcodes.unreachable);
|
51
|
+
case 'runtime':
|
52
|
+
return internalThrow(scope, 'TodoError', msg, expectsValue);
|
54
53
|
|
55
|
-
|
54
|
+
// return [
|
55
|
+
// ...debug(`todo! ${msg}`),
|
56
|
+
// [ Opcodes.unreachable ]
|
57
|
+
// ];
|
58
|
+
}
|
56
59
|
};
|
57
60
|
|
58
61
|
const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
|
@@ -116,6 +119,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
116
119
|
case 'WhileStatement':
|
117
120
|
return generateWhile(scope, decl);
|
118
121
|
|
122
|
+
case 'DoWhileStatement':
|
123
|
+
return generateDoWhile(scope, decl);
|
124
|
+
|
119
125
|
case 'ForOfStatement':
|
120
126
|
return generateForOf(scope, decl);
|
121
127
|
|
@@ -125,6 +131,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
125
131
|
case 'ContinueStatement':
|
126
132
|
return generateContinue(scope, decl);
|
127
133
|
|
134
|
+
case 'LabeledStatement':
|
135
|
+
return generateLabel(scope, decl);
|
136
|
+
|
128
137
|
case 'EmptyStatement':
|
129
138
|
return generateEmpty(scope, decl);
|
130
139
|
|
@@ -138,7 +147,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
138
147
|
return generateTry(scope, decl);
|
139
148
|
|
140
149
|
case 'DebuggerStatement':
|
141
|
-
// todo:
|
150
|
+
// todo: hook into terminal debugger
|
142
151
|
return [];
|
143
152
|
|
144
153
|
case 'ArrayExpression':
|
@@ -152,10 +161,16 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
152
161
|
const funcsBefore = funcs.length;
|
153
162
|
generate(scope, decl.declaration);
|
154
163
|
|
155
|
-
if (funcsBefore
|
164
|
+
if (funcsBefore !== funcs.length) {
|
165
|
+
// new func added
|
166
|
+
const newFunc = funcs[funcs.length - 1];
|
167
|
+
newFunc.export = true;
|
168
|
+
}
|
169
|
+
|
170
|
+
// if (funcsBefore === funcs.length) throw new Error('no new func added in export');
|
156
171
|
|
157
|
-
const newFunc = funcs[funcs.length - 1];
|
158
|
-
newFunc.export = true;
|
172
|
+
// const newFunc = funcs[funcs.length - 1];
|
173
|
+
// newFunc.export = true;
|
159
174
|
|
160
175
|
return [];
|
161
176
|
|
@@ -186,7 +201,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
186
201
|
}
|
187
202
|
|
188
203
|
let inst = Opcodes[asm[0].replace('.', '_')];
|
189
|
-
if (
|
204
|
+
if (inst == null) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
190
205
|
|
191
206
|
if (!Array.isArray(inst)) inst = [ inst ];
|
192
207
|
const immediates = asm.slice(1).map(x => {
|
@@ -195,40 +210,49 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
195
210
|
return int;
|
196
211
|
});
|
197
212
|
|
198
|
-
out.push([ ...inst, ...immediates ]);
|
213
|
+
out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
|
199
214
|
}
|
200
215
|
|
201
216
|
return out;
|
202
217
|
},
|
203
218
|
|
204
219
|
__Porffor_bs: str => [
|
205
|
-
...makeString(scope, str,
|
220
|
+
...makeString(scope, str, global, name, true),
|
206
221
|
|
207
|
-
...
|
208
|
-
|
222
|
+
...(name ? setType(scope, name, TYPES._bytestring) : [
|
223
|
+
...number(TYPES._bytestring, Valtype.i32),
|
224
|
+
...setLastType(scope)
|
225
|
+
])
|
209
226
|
],
|
210
227
|
__Porffor_s: str => [
|
211
|
-
...makeString(scope, str,
|
228
|
+
...makeString(scope, str, global, name, false),
|
212
229
|
|
213
|
-
...
|
214
|
-
|
230
|
+
...(name ? setType(scope, name, TYPES.string) : [
|
231
|
+
...number(TYPES.string, Valtype.i32),
|
232
|
+
...setLastType(scope)
|
233
|
+
])
|
215
234
|
],
|
216
235
|
};
|
217
236
|
|
218
|
-
const
|
237
|
+
const func = decl.tag.name;
|
219
238
|
// hack for inline asm
|
220
|
-
if (!funcs[
|
239
|
+
if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
|
221
240
|
|
222
241
|
const { quasis, expressions } = decl.quasi;
|
223
242
|
let str = quasis[0].value.raw;
|
224
243
|
|
225
244
|
for (let i = 0; i < expressions.length; i++) {
|
226
245
|
const e = expressions[i];
|
227
|
-
|
246
|
+
if (!e.name) {
|
247
|
+
if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
|
248
|
+
str += lookupName(scope, e.left.name)[0].idx + e.right.value;
|
249
|
+
} else todo(scope, 'unsupported expression in intrinsic');
|
250
|
+
} else str += lookupName(scope, e.name)[0].idx;
|
251
|
+
|
228
252
|
str += quasis[i + 1].value.raw;
|
229
253
|
}
|
230
254
|
|
231
|
-
return funcs[
|
255
|
+
return funcs[func](str);
|
232
256
|
}
|
233
257
|
|
234
258
|
default:
|
@@ -238,7 +262,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
238
262
|
return [];
|
239
263
|
}
|
240
264
|
|
241
|
-
return todo(`no generation for ${decl.type}!`);
|
265
|
+
return todo(scope, `no generation for ${decl.type}!`);
|
242
266
|
}
|
243
267
|
};
|
244
268
|
|
@@ -266,7 +290,7 @@ const lookupName = (scope, _name) => {
|
|
266
290
|
return [ undefined, undefined ];
|
267
291
|
};
|
268
292
|
|
269
|
-
const internalThrow = (scope, constructor, message, expectsValue =
|
293
|
+
const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
|
270
294
|
...generateThrow(scope, {
|
271
295
|
argument: {
|
272
296
|
type: 'NewExpression',
|
@@ -293,7 +317,7 @@ const generateIdent = (scope, decl) => {
|
|
293
317
|
|
294
318
|
let wasm = builtinVars[name];
|
295
319
|
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
|
296
|
-
return wasm;
|
320
|
+
return wasm.slice();
|
297
321
|
}
|
298
322
|
|
299
323
|
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
@@ -301,6 +325,11 @@ const generateIdent = (scope, decl) => {
|
|
301
325
|
return number(1);
|
302
326
|
}
|
303
327
|
|
328
|
+
if (isExistingProtoFunc(name)) {
|
329
|
+
// todo: return an actual something
|
330
|
+
return number(1);
|
331
|
+
}
|
332
|
+
|
304
333
|
if (local?.idx === undefined) {
|
305
334
|
// no local var with name
|
306
335
|
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
@@ -331,14 +360,18 @@ const generateReturn = (scope, decl) => {
|
|
331
360
|
// just bare "return"
|
332
361
|
return [
|
333
362
|
...number(UNDEFINED), // "undefined" if func returns
|
334
|
-
...
|
363
|
+
...(scope.returnType != null ? [] : [
|
364
|
+
...number(TYPES.undefined, Valtype.i32) // type undefined
|
365
|
+
]),
|
335
366
|
[ Opcodes.return ]
|
336
367
|
];
|
337
368
|
}
|
338
369
|
|
339
370
|
return [
|
340
371
|
...generate(scope, decl.argument),
|
341
|
-
...
|
372
|
+
...(scope.returnType != null ? [] : [
|
373
|
+
...getNodeType(scope, decl.argument)
|
374
|
+
]),
|
342
375
|
[ Opcodes.return ]
|
343
376
|
];
|
344
377
|
};
|
@@ -352,7 +385,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
352
385
|
return idx;
|
353
386
|
};
|
354
387
|
|
355
|
-
const isIntOp = op => op && (op[0] >=
|
388
|
+
const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
|
389
|
+
const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
|
356
390
|
|
357
391
|
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
358
392
|
const checks = {
|
@@ -361,7 +395,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
361
395
|
'??': nullish
|
362
396
|
};
|
363
397
|
|
364
|
-
if (!checks[op]) return todo(`logic operator ${op} not implemented yet
|
398
|
+
if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
|
365
399
|
|
366
400
|
// generic structure for {a} OP {b}
|
367
401
|
// -->
|
@@ -369,8 +403,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
369
403
|
|
370
404
|
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
371
405
|
// (like if we are in an if condition - very common)
|
372
|
-
const leftIsInt =
|
373
|
-
const rightIsInt =
|
406
|
+
const leftIsInt = isFloatToIntOp(left[left.length - 1]);
|
407
|
+
const rightIsInt = isFloatToIntOp(right[right.length - 1]);
|
374
408
|
|
375
409
|
const canInt = leftIsInt && rightIsInt;
|
376
410
|
|
@@ -387,12 +421,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
387
421
|
...right,
|
388
422
|
// note type
|
389
423
|
...rightType,
|
390
|
-
setLastType(scope),
|
424
|
+
...setLastType(scope),
|
391
425
|
[ Opcodes.else ],
|
392
426
|
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
393
427
|
// note type
|
394
428
|
...leftType,
|
395
|
-
setLastType(scope),
|
429
|
+
...setLastType(scope),
|
396
430
|
[ Opcodes.end ],
|
397
431
|
Opcodes.i32_from
|
398
432
|
];
|
@@ -406,17 +440,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
406
440
|
...right,
|
407
441
|
// note type
|
408
442
|
...rightType,
|
409
|
-
setLastType(scope),
|
443
|
+
...setLastType(scope),
|
410
444
|
[ Opcodes.else ],
|
411
445
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
412
446
|
// note type
|
413
447
|
...leftType,
|
414
|
-
setLastType(scope),
|
448
|
+
...setLastType(scope),
|
415
449
|
[ Opcodes.end ]
|
416
450
|
];
|
417
451
|
};
|
418
452
|
|
419
|
-
const concatStrings = (scope, left, right, global, name, assign) => {
|
453
|
+
const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
|
420
454
|
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
421
455
|
// todo: convert left and right to strings if not
|
422
456
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -426,8 +460,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
426
460
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
427
461
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
428
462
|
|
429
|
-
if (assign) {
|
430
|
-
const pointer = arrays
|
463
|
+
if (assign && Prefs.aotPointerOpt) {
|
464
|
+
const pointer = scope.arrays?.get(name ?? '$undeclared');
|
431
465
|
|
432
466
|
return [
|
433
467
|
// setup right
|
@@ -452,11 +486,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
452
486
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
|
453
487
|
|
454
488
|
// copy right
|
455
|
-
// dst = out pointer + length size + current length *
|
489
|
+
// dst = out pointer + length size + current length * sizeof valtype
|
456
490
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
457
491
|
|
458
492
|
[ Opcodes.local_get, leftLength ],
|
459
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
493
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
460
494
|
[ Opcodes.i32_mul ],
|
461
495
|
[ Opcodes.i32_add ],
|
462
496
|
|
@@ -465,9 +499,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
465
499
|
...number(ValtypeSize.i32, Valtype.i32),
|
466
500
|
[ Opcodes.i32_add ],
|
467
501
|
|
468
|
-
// size = right length *
|
502
|
+
// size = right length * sizeof valtype
|
469
503
|
[ Opcodes.local_get, rightLength ],
|
470
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
504
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
471
505
|
[ Opcodes.i32_mul ],
|
472
506
|
|
473
507
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -525,11 +559,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
525
559
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
526
560
|
|
527
561
|
// copy right
|
528
|
-
// dst = out pointer + length size + left length *
|
562
|
+
// dst = out pointer + length size + left length * sizeof valtype
|
529
563
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
530
564
|
|
531
565
|
[ Opcodes.local_get, leftLength ],
|
532
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
566
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
533
567
|
[ Opcodes.i32_mul ],
|
534
568
|
[ Opcodes.i32_add ],
|
535
569
|
|
@@ -538,9 +572,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
538
572
|
...number(ValtypeSize.i32, Valtype.i32),
|
539
573
|
[ Opcodes.i32_add ],
|
540
574
|
|
541
|
-
// size = right length *
|
575
|
+
// size = right length * sizeof valtype
|
542
576
|
[ Opcodes.local_get, rightLength ],
|
543
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
577
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
544
578
|
[ Opcodes.i32_mul ],
|
545
579
|
|
546
580
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -550,7 +584,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
550
584
|
];
|
551
585
|
};
|
552
586
|
|
553
|
-
const compareStrings = (scope, left, right) => {
|
587
|
+
const compareStrings = (scope, left, right, bytestrings = false) => {
|
554
588
|
// todo: this should be rewritten into a func
|
555
589
|
// todo: convert left and right to strings if not
|
556
590
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -559,7 +593,6 @@ const compareStrings = (scope, left, right) => {
|
|
559
593
|
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
560
594
|
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
561
595
|
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
562
|
-
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
563
596
|
|
564
597
|
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
565
598
|
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
@@ -587,7 +620,6 @@ const compareStrings = (scope, left, right) => {
|
|
587
620
|
|
588
621
|
[ Opcodes.local_get, rightPointer ],
|
589
622
|
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
590
|
-
[ Opcodes.local_tee, rightLength ],
|
591
623
|
|
592
624
|
// fast path: check leftLength != rightLength
|
593
625
|
[ Opcodes.i32_ne ],
|
@@ -602,11 +634,13 @@ const compareStrings = (scope, left, right) => {
|
|
602
634
|
...number(0, Valtype.i32),
|
603
635
|
[ Opcodes.local_set, index ],
|
604
636
|
|
605
|
-
// setup index end as length * sizeof
|
637
|
+
// setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
|
606
638
|
// we do this instead of having to do mul/div each iter for perf™
|
607
639
|
[ Opcodes.local_get, leftLength ],
|
608
|
-
...
|
609
|
-
|
640
|
+
...(bytestrings ? [] : [
|
641
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
642
|
+
[ Opcodes.i32_mul ],
|
643
|
+
]),
|
610
644
|
[ Opcodes.local_set, indexEnd ],
|
611
645
|
|
612
646
|
// iterate over each char and check if eq
|
@@ -616,13 +650,17 @@ const compareStrings = (scope, left, right) => {
|
|
616
650
|
[ Opcodes.local_get, index ],
|
617
651
|
[ Opcodes.local_get, leftPointer ],
|
618
652
|
[ Opcodes.i32_add ],
|
619
|
-
|
653
|
+
bytestrings ?
|
654
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
655
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
620
656
|
|
621
657
|
// fetch right
|
622
658
|
[ Opcodes.local_get, index ],
|
623
659
|
[ Opcodes.local_get, rightPointer ],
|
624
660
|
[ Opcodes.i32_add ],
|
625
|
-
|
661
|
+
bytestrings ?
|
662
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
663
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
626
664
|
|
627
665
|
// not equal, "return" false
|
628
666
|
[ Opcodes.i32_ne ],
|
@@ -631,13 +669,13 @@ const compareStrings = (scope, left, right) => {
|
|
631
669
|
[ Opcodes.br, 2 ],
|
632
670
|
[ Opcodes.end ],
|
633
671
|
|
634
|
-
// index += sizeof
|
672
|
+
// index += sizeof valtype (1 for bytestring, 2 for string)
|
635
673
|
[ Opcodes.local_get, index ],
|
636
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
674
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
637
675
|
[ Opcodes.i32_add ],
|
638
676
|
[ Opcodes.local_tee, index ],
|
639
677
|
|
640
|
-
// if index != index end (length * sizeof
|
678
|
+
// if index != index end (length * sizeof valtype), loop
|
641
679
|
[ Opcodes.local_get, indexEnd ],
|
642
680
|
[ Opcodes.i32_ne ],
|
643
681
|
[ Opcodes.br_if, 0 ],
|
@@ -658,13 +696,14 @@ const compareStrings = (scope, left, right) => {
|
|
658
696
|
};
|
659
697
|
|
660
698
|
const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
661
|
-
if (
|
699
|
+
if (isFloatToIntOp(wasm[wasm.length - 1])) return [
|
662
700
|
...wasm,
|
663
701
|
...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
|
664
702
|
];
|
703
|
+
// if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
|
665
704
|
|
666
705
|
const useTmp = knownType(scope, type) == null;
|
667
|
-
const tmp = useTmp && localTmp(scope,
|
706
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
668
707
|
|
669
708
|
const def = [
|
670
709
|
// if value != 0
|
@@ -716,7 +755,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
716
755
|
|
717
756
|
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
718
757
|
const useTmp = knownType(scope, type) == null;
|
719
|
-
const tmp = useTmp && localTmp(scope,
|
758
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
720
759
|
|
721
760
|
return [
|
722
761
|
...wasm,
|
@@ -762,7 +801,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
762
801
|
|
763
802
|
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
764
803
|
const useTmp = knownType(scope, type) == null;
|
765
|
-
const tmp = useTmp && localTmp(scope,
|
804
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
766
805
|
|
767
806
|
return [
|
768
807
|
...wasm,
|
@@ -856,11 +895,11 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
856
895
|
// todo: if equality op and an operand is undefined, return false
|
857
896
|
// todo: niche null hell with 0
|
858
897
|
|
859
|
-
// todo: this should be dynamic but for now only static
|
860
898
|
if (knownLeft === TYPES.string || knownRight === TYPES.string) {
|
861
899
|
if (op === '+') {
|
900
|
+
// todo: this should be dynamic too but for now only static
|
862
901
|
// string concat (a + b)
|
863
|
-
return concatStrings(scope, left, right, _global, _name, assign);
|
902
|
+
return concatStrings(scope, left, right, _global, _name, assign, false);
|
864
903
|
}
|
865
904
|
|
866
905
|
// not an equality op, NaN
|
@@ -883,6 +922,33 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
883
922
|
}
|
884
923
|
}
|
885
924
|
|
925
|
+
if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) {
|
926
|
+
if (op === '+') {
|
927
|
+
// todo: this should be dynamic too but for now only static
|
928
|
+
// string concat (a + b)
|
929
|
+
return concatStrings(scope, left, right, _global, _name, assign, true);
|
930
|
+
}
|
931
|
+
|
932
|
+
// not an equality op, NaN
|
933
|
+
if (!eqOp) return number(NaN);
|
934
|
+
|
935
|
+
// else leave bool ops
|
936
|
+
// todo: convert string to number if string and number/bool
|
937
|
+
// todo: string (>|>=|<|<=) string
|
938
|
+
|
939
|
+
// string comparison
|
940
|
+
if (op === '===' || op === '==') {
|
941
|
+
return compareStrings(scope, left, right, true);
|
942
|
+
}
|
943
|
+
|
944
|
+
if (op === '!==' || op === '!=') {
|
945
|
+
return [
|
946
|
+
...compareStrings(scope, left, right, true),
|
947
|
+
[ Opcodes.i32_eqz ]
|
948
|
+
];
|
949
|
+
}
|
950
|
+
}
|
951
|
+
|
886
952
|
let ops = operatorOpcode[valtype][op];
|
887
953
|
|
888
954
|
// some complex ops are implemented as builtin funcs
|
@@ -898,23 +964,62 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
898
964
|
]);
|
899
965
|
}
|
900
966
|
|
901
|
-
if (!ops) return todo(`operator ${op} not implemented yet
|
967
|
+
if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
|
902
968
|
|
903
969
|
if (!Array.isArray(ops)) ops = [ ops ];
|
904
970
|
ops = [ ops ];
|
905
971
|
|
906
972
|
let tmpLeft, tmpRight;
|
907
973
|
// 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
|
-
}
|
974
|
+
// todo: intelligent partial skip later
|
975
|
+
// if neither known are string, stop this madness
|
976
|
+
// we already do known checks earlier, so don't need to recheck
|
914
977
|
|
978
|
+
if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
|
915
979
|
tmpLeft = localTmp(scope, '__tmpop_left');
|
916
980
|
tmpRight = localTmp(scope, '__tmpop_right');
|
917
981
|
|
982
|
+
// returns false for one string, one not - but more ops/slower
|
983
|
+
// ops.unshift(...stringOnly([
|
984
|
+
// // if left is string
|
985
|
+
// ...leftType,
|
986
|
+
// ...number(TYPES.string, Valtype.i32),
|
987
|
+
// [ Opcodes.i32_eq ],
|
988
|
+
|
989
|
+
// // if right is string
|
990
|
+
// ...rightType,
|
991
|
+
// ...number(TYPES.string, Valtype.i32),
|
992
|
+
// [ Opcodes.i32_eq ],
|
993
|
+
|
994
|
+
// // if either are true
|
995
|
+
// [ Opcodes.i32_or ],
|
996
|
+
// [ Opcodes.if, Blocktype.void ],
|
997
|
+
|
998
|
+
// // todo: convert non-strings to strings, for now fail immediately if one is not
|
999
|
+
// // if left is not string
|
1000
|
+
// ...leftType,
|
1001
|
+
// ...number(TYPES.string, Valtype.i32),
|
1002
|
+
// [ Opcodes.i32_ne ],
|
1003
|
+
|
1004
|
+
// // if right is not string
|
1005
|
+
// ...rightType,
|
1006
|
+
// ...number(TYPES.string, Valtype.i32),
|
1007
|
+
// [ Opcodes.i32_ne ],
|
1008
|
+
|
1009
|
+
// // if either are true
|
1010
|
+
// [ Opcodes.i32_or ],
|
1011
|
+
// [ Opcodes.if, Blocktype.void ],
|
1012
|
+
// ...number(0, Valtype.i32),
|
1013
|
+
// [ Opcodes.br, 2 ],
|
1014
|
+
// [ Opcodes.end ],
|
1015
|
+
|
1016
|
+
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1017
|
+
// ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
1018
|
+
// [ Opcodes.br, 1 ],
|
1019
|
+
// [ Opcodes.end ],
|
1020
|
+
// ]));
|
1021
|
+
|
1022
|
+
// does not handle one string, one not (such cases go past)
|
918
1023
|
ops.unshift(...stringOnly([
|
919
1024
|
// if left is string
|
920
1025
|
...leftType,
|
@@ -926,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
926
1031
|
...number(TYPES.string, Valtype.i32),
|
927
1032
|
[ Opcodes.i32_eq ],
|
928
1033
|
|
929
|
-
// if
|
930
|
-
[ Opcodes.
|
1034
|
+
// if both are true
|
1035
|
+
[ Opcodes.i32_and ],
|
931
1036
|
[ Opcodes.if, Blocktype.void ],
|
1037
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1038
|
+
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
1039
|
+
[ Opcodes.br, 1 ],
|
1040
|
+
[ Opcodes.end ],
|
932
1041
|
|
933
|
-
//
|
934
|
-
// if left is not string
|
1042
|
+
// if left is bytestring
|
935
1043
|
...leftType,
|
936
|
-
...number(TYPES.
|
937
|
-
[ Opcodes.
|
1044
|
+
...number(TYPES._bytestring, Valtype.i32),
|
1045
|
+
[ Opcodes.i32_eq ],
|
938
1046
|
|
939
|
-
// if right is
|
1047
|
+
// if right is bytestring
|
940
1048
|
...rightType,
|
941
|
-
...number(TYPES.
|
942
|
-
[ Opcodes.
|
1049
|
+
...number(TYPES._bytestring, Valtype.i32),
|
1050
|
+
[ Opcodes.i32_eq ],
|
943
1051
|
|
944
|
-
// if
|
945
|
-
[ Opcodes.
|
1052
|
+
// if both are true
|
1053
|
+
[ Opcodes.i32_and ],
|
946
1054
|
[ 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 ] ]),
|
1055
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
|
953
1056
|
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
954
1057
|
[ Opcodes.br, 1 ],
|
955
1058
|
[ Opcodes.end ],
|
@@ -961,7 +1064,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
961
1064
|
// endOut.push(stringOnly([ Opcodes.end ]));
|
962
1065
|
endOut.unshift(stringOnly([ Opcodes.end ]));
|
963
1066
|
// }
|
964
|
-
}
|
1067
|
+
}
|
965
1068
|
|
966
1069
|
return finalize([
|
967
1070
|
...left,
|
@@ -1037,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
1037
1140
|
params,
|
1038
1141
|
locals,
|
1039
1142
|
returns,
|
1040
|
-
returnType:
|
1143
|
+
returnType: returnType ?? TYPES.number,
|
1041
1144
|
wasm,
|
1042
1145
|
internal: true,
|
1043
1146
|
index: currentFuncIndex++
|
@@ -1060,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
|
|
1060
1163
|
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
1061
1164
|
};
|
1062
1165
|
|
1166
|
+
// potential future ideas for nan boxing (unused):
|
1063
1167
|
// T = JS type, V = value/pointer
|
1064
1168
|
// 0bTTT
|
1065
1169
|
// qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
|
@@ -1083,40 +1187,18 @@ const generateLogicExp = (scope, decl) => {
|
|
1083
1187
|
// 4: internal type
|
1084
1188
|
// 5: pointer
|
1085
1189
|
|
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'
|
1190
|
+
const isExistingProtoFunc = name => {
|
1191
|
+
if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES._array][name.slice(18)];
|
1192
|
+
if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
|
1193
|
+
|
1194
|
+
return false;
|
1115
1195
|
};
|
1116
1196
|
|
1117
1197
|
const getType = (scope, _name) => {
|
1118
1198
|
const name = mapName(_name);
|
1119
1199
|
|
1200
|
+
// if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
|
1201
|
+
|
1120
1202
|
if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
|
1121
1203
|
if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
|
1122
1204
|
|
@@ -1124,11 +1206,10 @@ const getType = (scope, _name) => {
|
|
1124
1206
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1125
1207
|
|
1126
1208
|
let type = TYPES.undefined;
|
1127
|
-
if (builtinVars[name]) type =
|
1209
|
+
if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
|
1128
1210
|
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
|
1129
1211
|
|
1130
|
-
if (name
|
1131
|
-
name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
|
1212
|
+
if (isExistingProtoFunc(name)) type = TYPES.function;
|
1132
1213
|
|
1133
1214
|
return number(type, Valtype.i32);
|
1134
1215
|
};
|
@@ -1151,15 +1232,16 @@ const setType = (scope, _name, type) => {
|
|
1151
1232
|
];
|
1152
1233
|
|
1153
1234
|
// throw new Error('could not find var');
|
1235
|
+
return [];
|
1154
1236
|
};
|
1155
1237
|
|
1156
1238
|
const getLastType = scope => {
|
1157
1239
|
scope.gotLastType = true;
|
1158
|
-
return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
|
1240
|
+
return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1159
1241
|
};
|
1160
1242
|
|
1161
1243
|
const setLastType = scope => {
|
1162
|
-
return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
|
1244
|
+
return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1163
1245
|
};
|
1164
1246
|
|
1165
1247
|
const getNodeType = (scope, node) => {
|
@@ -1184,13 +1266,25 @@ const getNodeType = (scope, node) => {
|
|
1184
1266
|
const name = node.callee.name;
|
1185
1267
|
if (!name) {
|
1186
1268
|
// iife
|
1187
|
-
if (scope.locals['#last_type']) return
|
1269
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1188
1270
|
|
1189
1271
|
// presume
|
1190
1272
|
// todo: warn here?
|
1191
1273
|
return TYPES.number;
|
1192
1274
|
}
|
1193
1275
|
|
1276
|
+
if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
|
1277
|
+
if (builtinFuncs[name + '$constructor'].typedReturns) {
|
1278
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1279
|
+
|
1280
|
+
// presume
|
1281
|
+
// todo: warn here?
|
1282
|
+
return TYPES.number;
|
1283
|
+
}
|
1284
|
+
|
1285
|
+
return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
|
1286
|
+
}
|
1287
|
+
|
1194
1288
|
const func = funcs.find(x => x.name === name);
|
1195
1289
|
|
1196
1290
|
if (func) {
|
@@ -1198,7 +1292,7 @@ const getNodeType = (scope, node) => {
|
|
1198
1292
|
if (func.returnType) return func.returnType;
|
1199
1293
|
}
|
1200
1294
|
|
1201
|
-
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return
|
1295
|
+
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
1202
1296
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1203
1297
|
|
1204
1298
|
// check if this is a prototype function
|
@@ -1218,7 +1312,7 @@ const getNodeType = (scope, node) => {
|
|
1218
1312
|
return TYPES.number;
|
1219
1313
|
}
|
1220
1314
|
|
1221
|
-
if (scope.locals['#last_type']) return
|
1315
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1222
1316
|
|
1223
1317
|
// presume
|
1224
1318
|
// todo: warn here?
|
@@ -1273,6 +1367,7 @@ const getNodeType = (scope, node) => {
|
|
1273
1367
|
|
1274
1368
|
// todo: this should be dynamic but for now only static
|
1275
1369
|
if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
|
1370
|
+
if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) return TYPES._bytestring;
|
1276
1371
|
|
1277
1372
|
return TYPES.number;
|
1278
1373
|
|
@@ -1309,15 +1404,21 @@ const getNodeType = (scope, node) => {
|
|
1309
1404
|
|
1310
1405
|
// ts hack
|
1311
1406
|
if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
|
1407
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES._bytestring) return TYPES._bytestring;
|
1312
1408
|
if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
|
1313
1409
|
|
1314
|
-
if (scope.locals['#last_type']) return
|
1410
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1315
1411
|
|
1316
1412
|
// presume
|
1317
1413
|
return TYPES.number;
|
1318
1414
|
}
|
1319
1415
|
|
1320
|
-
if (
|
1416
|
+
if (node.type === 'TaggedTemplateExpression') {
|
1417
|
+
// hack
|
1418
|
+
if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
|
1419
|
+
}
|
1420
|
+
|
1421
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1321
1422
|
|
1322
1423
|
// presume
|
1323
1424
|
// todo: warn here?
|
@@ -1350,7 +1451,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1350
1451
|
return makeString(scope, decl.value, global, name);
|
1351
1452
|
|
1352
1453
|
default:
|
1353
|
-
return todo(`cannot generate literal of type ${typeof decl.value}
|
1454
|
+
return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
|
1354
1455
|
}
|
1355
1456
|
};
|
1356
1457
|
|
@@ -1359,6 +1460,8 @@ const countLeftover = wasm => {
|
|
1359
1460
|
|
1360
1461
|
for (let i = 0; i < wasm.length; i++) {
|
1361
1462
|
const inst = wasm[i];
|
1463
|
+
if (inst[0] == null) continue;
|
1464
|
+
|
1362
1465
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
1363
1466
|
if (inst[0] === Opcodes.if) count--;
|
1364
1467
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1369,16 +1472,23 @@ const countLeftover = wasm => {
|
|
1369
1472
|
if (depth === 0)
|
1370
1473
|
if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1371
1474
|
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++;
|
1475
|
+
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
1476
|
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1374
1477
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1375
1478
|
else if (inst[0] === Opcodes.return) count = 0;
|
1376
1479
|
else if (inst[0] === Opcodes.call) {
|
1377
1480
|
let func = funcs.find(x => x.index === inst[1]);
|
1378
|
-
if (
|
1379
|
-
count
|
1380
|
-
} else
|
1381
|
-
|
1481
|
+
if (inst[1] === -1) {
|
1482
|
+
// todo: count for calling self
|
1483
|
+
} else if (!func && inst[1] < importedFuncs.length) {
|
1484
|
+
count -= importedFuncs[inst[1]].params;
|
1485
|
+
count += importedFuncs[inst[1]].returns;
|
1486
|
+
} else {
|
1487
|
+
if (func) {
|
1488
|
+
count -= func.params.length;
|
1489
|
+
} else count--;
|
1490
|
+
if (func) count += func.returns.length;
|
1491
|
+
}
|
1382
1492
|
} else count--;
|
1383
1493
|
|
1384
1494
|
// console.log(count, decompile([ inst ]).slice(0, -1));
|
@@ -1470,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1470
1580
|
name = func.name;
|
1471
1581
|
}
|
1472
1582
|
|
1473
|
-
if (name === 'eval' && decl.arguments[0]
|
1583
|
+
if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
|
1474
1584
|
// literal eval hack
|
1475
|
-
const code = decl.arguments[0]
|
1476
|
-
|
1585
|
+
const code = decl.arguments[0]?.value ?? '';
|
1586
|
+
|
1587
|
+
let parsed;
|
1588
|
+
try {
|
1589
|
+
parsed = parse(code, []);
|
1590
|
+
} catch (e) {
|
1591
|
+
if (e.name === 'SyntaxError') {
|
1592
|
+
// throw syntax errors of evals at runtime instead
|
1593
|
+
return internalThrow(scope, 'SyntaxError', e.message, true);
|
1594
|
+
}
|
1595
|
+
|
1596
|
+
throw e;
|
1597
|
+
}
|
1477
1598
|
|
1478
1599
|
const out = generate(scope, {
|
1479
1600
|
type: 'BlockStatement',
|
@@ -1487,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1487
1608
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1488
1609
|
out.push(
|
1489
1610
|
...getNodeType(scope, finalStatement),
|
1490
|
-
setLastType(scope)
|
1611
|
+
...setLastType(scope)
|
1491
1612
|
);
|
1492
1613
|
} else if (countLeftover(out) === 0) {
|
1493
1614
|
out.push(...number(UNDEFINED));
|
1494
1615
|
out.push(
|
1495
1616
|
...number(TYPES.undefined, Valtype.i32),
|
1496
|
-
setLastType(scope)
|
1617
|
+
...setLastType(scope)
|
1497
1618
|
);
|
1498
1619
|
}
|
1499
1620
|
|
@@ -1515,6 +1636,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1515
1636
|
|
1516
1637
|
target = { ...decl.callee };
|
1517
1638
|
target.name = spl.slice(0, -1).join('_');
|
1639
|
+
|
1640
|
+
// failed to lookup name, abort
|
1641
|
+
if (!lookupName(scope, target.name)[0]) protoName = null;
|
1518
1642
|
}
|
1519
1643
|
|
1520
1644
|
// literal.func()
|
@@ -1522,22 +1646,29 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1522
1646
|
// megahack for /regex/.func()
|
1523
1647
|
const funcName = decl.callee.property.name;
|
1524
1648
|
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1525
|
-
const
|
1649
|
+
const regex = decl.callee.object.regex.pattern;
|
1650
|
+
const rhemynName = `regex_${funcName}_${regex}`;
|
1526
1651
|
|
1527
|
-
funcIndex[
|
1528
|
-
|
1652
|
+
if (!funcIndex[rhemynName]) {
|
1653
|
+
const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
|
1654
|
+
|
1655
|
+
funcIndex[func.name] = func.index;
|
1656
|
+
funcs.push(func);
|
1657
|
+
}
|
1529
1658
|
|
1659
|
+
const idx = funcIndex[rhemynName];
|
1530
1660
|
return [
|
1531
1661
|
// make string arg
|
1532
1662
|
...generate(scope, decl.arguments[0]),
|
1663
|
+
Opcodes.i32_to_u,
|
1664
|
+
...getNodeType(scope, decl.arguments[0]),
|
1533
1665
|
|
1534
1666
|
// call regex func
|
1535
|
-
Opcodes.
|
1536
|
-
[ Opcodes.call, func.index ],
|
1667
|
+
[ Opcodes.call, idx ],
|
1537
1668
|
Opcodes.i32_from_u,
|
1538
1669
|
|
1539
1670
|
...number(TYPES.boolean, Valtype.i32),
|
1540
|
-
setLastType(scope)
|
1671
|
+
...setLastType(scope)
|
1541
1672
|
];
|
1542
1673
|
}
|
1543
1674
|
|
@@ -1562,12 +1693,31 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1562
1693
|
// }
|
1563
1694
|
|
1564
1695
|
if (protoName) {
|
1696
|
+
const protoBC = {};
|
1697
|
+
|
1698
|
+
const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
|
1699
|
+
|
1700
|
+
if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
|
1701
|
+
for (const x of builtinProtoCands) {
|
1702
|
+
const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
|
1703
|
+
if (type == null) continue;
|
1704
|
+
|
1705
|
+
protoBC[type] = generateCall(scope, {
|
1706
|
+
callee: {
|
1707
|
+
type: 'Identifier',
|
1708
|
+
name: x
|
1709
|
+
},
|
1710
|
+
arguments: [ target, ...decl.arguments ],
|
1711
|
+
_protoInternalCall: true
|
1712
|
+
});
|
1713
|
+
}
|
1714
|
+
}
|
1715
|
+
|
1565
1716
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1566
1717
|
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1567
1718
|
return acc;
|
1568
1719
|
}, {});
|
1569
1720
|
|
1570
|
-
// no prototype function candidates, ignore
|
1571
1721
|
if (Object.keys(protoCands).length > 0) {
|
1572
1722
|
// use local for cached i32 length as commonly used
|
1573
1723
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
@@ -1585,7 +1735,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1585
1735
|
|
1586
1736
|
let allOptUnused = true;
|
1587
1737
|
let lengthI32CacheUsed = false;
|
1588
|
-
const protoBC = {};
|
1589
1738
|
for (const x in protoCands) {
|
1590
1739
|
const protoFunc = protoCands[x];
|
1591
1740
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
@@ -1593,7 +1742,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1593
1742
|
...RTArrayUtil.getLength(getPointer),
|
1594
1743
|
|
1595
1744
|
...number(TYPES.number, Valtype.i32),
|
1596
|
-
setLastType(scope)
|
1745
|
+
...setLastType(scope)
|
1597
1746
|
];
|
1598
1747
|
continue;
|
1599
1748
|
}
|
@@ -1630,7 +1779,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1630
1779
|
...protoOut,
|
1631
1780
|
|
1632
1781
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1633
|
-
setLastType(scope),
|
1782
|
+
...setLastType(scope),
|
1634
1783
|
[ Opcodes.end ]
|
1635
1784
|
];
|
1636
1785
|
}
|
@@ -1656,10 +1805,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1656
1805
|
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1657
1806
|
];
|
1658
1807
|
}
|
1808
|
+
|
1809
|
+
if (Object.keys(protoBC).length > 0) {
|
1810
|
+
return typeSwitch(scope, getNodeType(scope, target), {
|
1811
|
+
...protoBC,
|
1812
|
+
|
1813
|
+
// TODO: error better
|
1814
|
+
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1815
|
+
}, valtypeBinary);
|
1816
|
+
}
|
1659
1817
|
}
|
1660
1818
|
|
1661
1819
|
// TODO: only allows callee as literal
|
1662
|
-
if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
|
1820
|
+
if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
|
1663
1821
|
|
1664
1822
|
let idx = funcIndex[name] ?? importedFuncs[name];
|
1665
1823
|
if (idx === undefined && builtinFuncs[name]) {
|
@@ -1669,20 +1827,20 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1669
1827
|
idx = funcIndex[name];
|
1670
1828
|
|
1671
1829
|
// 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
|
-
|
1675
|
-
|
1676
|
-
|
1677
|
-
|
1678
|
-
|
1679
|
-
|
1680
|
-
|
1681
|
-
|
1682
|
-
|
1683
|
-
|
1684
|
-
|
1685
|
-
}
|
1830
|
+
// const func = funcs.find(x => x.name === name);
|
1831
|
+
// for (let i = 0; i < decl.arguments.length; i++) {
|
1832
|
+
// const arg = decl.arguments[i];
|
1833
|
+
// if (!arg.name) continue;
|
1834
|
+
|
1835
|
+
// const local = scope.locals[arg.name];
|
1836
|
+
// if (!local) continue;
|
1837
|
+
|
1838
|
+
// local.type = func.params[i];
|
1839
|
+
// if (local.type === Valtype.v128) {
|
1840
|
+
// // specify vec subtype inferred from last vec type in function name
|
1841
|
+
// local.vecType = name.split('_').reverse().find(x => x.includes('x'));
|
1842
|
+
// }
|
1843
|
+
// }
|
1686
1844
|
}
|
1687
1845
|
|
1688
1846
|
if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
@@ -1695,9 +1853,25 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1695
1853
|
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1696
1854
|
const wasmOps = {
|
1697
1855
|
// pointer, align, offset
|
1698
|
-
|
1856
|
+
i32_load: { imms: 2, args: [ true ], returns: 1 },
|
1857
|
+
// pointer, value, align, offset
|
1858
|
+
i32_store: { imms: 2, args: [ true, true ], returns: 0 },
|
1859
|
+
// pointer, align, offset
|
1860
|
+
i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
|
1861
|
+
// pointer, value, align, offset
|
1862
|
+
i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
|
1863
|
+
// pointer, align, offset
|
1864
|
+
i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
|
1865
|
+
// pointer, value, align, offset
|
1866
|
+
i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
|
1867
|
+
|
1868
|
+
// pointer, align, offset
|
1869
|
+
f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
|
1699
1870
|
// pointer, value, align, offset
|
1700
|
-
|
1871
|
+
f64_store: { imms: 2, args: [ true, false ], returns: 0 },
|
1872
|
+
|
1873
|
+
// value
|
1874
|
+
i32_const: { imms: 1, args: [], returns: 1 },
|
1701
1875
|
};
|
1702
1876
|
|
1703
1877
|
const opName = name.slice('__Porffor_wasm_'.length);
|
@@ -1706,28 +1880,32 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1706
1880
|
const op = wasmOps[opName];
|
1707
1881
|
|
1708
1882
|
const argOut = [];
|
1709
|
-
for (let i = 0; i < op.args; i++) argOut.push(
|
1883
|
+
for (let i = 0; i < op.args.length; i++) argOut.push(
|
1884
|
+
...generate(scope, decl.arguments[i]),
|
1885
|
+
...(op.args[i] ? [ Opcodes.i32_to ] : [])
|
1886
|
+
);
|
1710
1887
|
|
1711
1888
|
// literals only
|
1712
|
-
const imms = decl.arguments.slice(op.args).map(x => x.value);
|
1889
|
+
const imms = decl.arguments.slice(op.args.length).map(x => x.value);
|
1713
1890
|
|
1714
1891
|
return [
|
1715
1892
|
...argOut,
|
1716
|
-
[ Opcodes[opName], ...imms ]
|
1893
|
+
[ Opcodes[opName], ...imms ],
|
1894
|
+
...(new Array(op.returns).fill(Opcodes.i32_from))
|
1717
1895
|
];
|
1718
1896
|
}
|
1719
1897
|
}
|
1720
1898
|
|
1721
1899
|
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
|
1900
|
+
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1901
|
+
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
1724
1902
|
}
|
1725
1903
|
|
1726
1904
|
const func = funcs.find(x => x.index === idx);
|
1727
1905
|
|
1728
1906
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1729
1907
|
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1730
|
-
const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
|
1908
|
+
const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
|
1731
1909
|
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1732
1910
|
|
1733
1911
|
let args = decl.arguments;
|
@@ -1748,7 +1926,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1748
1926
|
const arg = args[i];
|
1749
1927
|
out = out.concat(generate(scope, arg));
|
1750
1928
|
|
1751
|
-
if (builtinFuncs[name] && builtinFuncs[name].params[i] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1929
|
+
if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1930
|
+
out.push(Opcodes.i32_to);
|
1931
|
+
}
|
1932
|
+
|
1933
|
+
if (importedFuncs[name] && name.startsWith('profile')) {
|
1752
1934
|
out.push(Opcodes.i32_to);
|
1753
1935
|
}
|
1754
1936
|
|
@@ -1767,9 +1949,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1767
1949
|
// ...number(type, Valtype.i32),
|
1768
1950
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1769
1951
|
// );
|
1770
|
-
} else out.push(setLastType(scope));
|
1952
|
+
} else out.push(...setLastType(scope));
|
1771
1953
|
|
1772
|
-
if (builtinFuncs[name] && builtinFuncs[name].returns[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1954
|
+
if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1773
1955
|
out.push(Opcodes.i32_from);
|
1774
1956
|
}
|
1775
1957
|
|
@@ -1779,8 +1961,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1779
1961
|
const generateNew = (scope, decl, _global, _name) => {
|
1780
1962
|
// hack: basically treat this as a normal call for builtins for now
|
1781
1963
|
const name = mapName(decl.callee.name);
|
1964
|
+
|
1782
1965
|
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1783
|
-
|
1966
|
+
|
1967
|
+
if (builtinFuncs[name + '$constructor']) {
|
1968
|
+
// custom ...$constructor override builtin func
|
1969
|
+
return generateCall(scope, {
|
1970
|
+
...decl,
|
1971
|
+
callee: {
|
1972
|
+
type: 'Identifier',
|
1973
|
+
name: name + '$constructor'
|
1974
|
+
}
|
1975
|
+
}, _global, _name);
|
1976
|
+
}
|
1977
|
+
|
1978
|
+
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
1979
|
|
1785
1980
|
return generateCall(scope, decl, _global, _name);
|
1786
1981
|
};
|
@@ -1914,8 +2109,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1914
2109
|
[ Opcodes.block, returns ]
|
1915
2110
|
];
|
1916
2111
|
|
1917
|
-
// todo: use br_table?
|
1918
|
-
|
1919
2112
|
for (const x in bc) {
|
1920
2113
|
if (x === 'default') continue;
|
1921
2114
|
|
@@ -1971,12 +2164,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
|
1971
2164
|
};
|
1972
2165
|
|
1973
2166
|
const typeAnnoToPorfType = x => {
|
1974
|
-
if (
|
1975
|
-
if (TYPES[
|
2167
|
+
if (!x) return null;
|
2168
|
+
if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
|
2169
|
+
if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
|
1976
2170
|
|
1977
2171
|
switch (x) {
|
1978
2172
|
case 'i32':
|
1979
2173
|
case 'i64':
|
2174
|
+
case 'f64':
|
1980
2175
|
return TYPES.number;
|
1981
2176
|
}
|
1982
2177
|
|
@@ -1987,7 +2182,7 @@ const extractTypeAnnotation = decl => {
|
|
1987
2182
|
let a = decl;
|
1988
2183
|
while (a.typeAnnotation) a = a.typeAnnotation;
|
1989
2184
|
|
1990
|
-
let type, elementType;
|
2185
|
+
let type = null, elementType = null;
|
1991
2186
|
if (a.typeName) {
|
1992
2187
|
type = a.typeName.name;
|
1993
2188
|
} else if (a.type.endsWith('Keyword')) {
|
@@ -2014,11 +2209,12 @@ const generateVar = (scope, decl) => {
|
|
2014
2209
|
|
2015
2210
|
// global variable if in top scope (main) and var ..., or if wanted
|
2016
2211
|
const global = topLevel || decl._bare; // decl.kind === 'var';
|
2212
|
+
const target = global ? globals : scope.locals;
|
2017
2213
|
|
2018
2214
|
for (const x of decl.declarations) {
|
2019
2215
|
const name = mapName(x.id.name);
|
2020
2216
|
|
2021
|
-
if (!name) return todo('destructuring is not supported yet');
|
2217
|
+
if (!name) return todo(scope, 'destructuring is not supported yet');
|
2022
2218
|
|
2023
2219
|
if (x.init && isFuncType(x.init.type)) {
|
2024
2220
|
// hack for let a = function () { ... }
|
@@ -2035,16 +2231,29 @@ const generateVar = (scope, decl) => {
|
|
2035
2231
|
continue; // always ignore
|
2036
2232
|
}
|
2037
2233
|
|
2038
|
-
|
2234
|
+
// // generate init before allocating var
|
2235
|
+
// let generated;
|
2236
|
+
// if (x.init) generated = generate(scope, x.init, global, name);
|
2237
|
+
|
2238
|
+
const typed = typedInput && x.id.typeAnnotation;
|
2239
|
+
let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
|
2039
2240
|
|
2040
|
-
if (
|
2241
|
+
if (typed) {
|
2041
2242
|
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
2042
2243
|
}
|
2043
2244
|
|
2044
2245
|
if (x.init) {
|
2045
|
-
|
2046
|
-
|
2047
|
-
|
2246
|
+
const generated = generate(scope, x.init, global, name);
|
2247
|
+
if (scope.arrays?.get(name) != null) {
|
2248
|
+
// hack to set local as pointer before
|
2249
|
+
out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2250
|
+
if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
|
2251
|
+
generated.pop();
|
2252
|
+
out = out.concat(generated);
|
2253
|
+
} else {
|
2254
|
+
out = out.concat(generated);
|
2255
|
+
out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2256
|
+
}
|
2048
2257
|
out.push(...setType(scope, name, getNodeType(scope, x.init)));
|
2049
2258
|
}
|
2050
2259
|
|
@@ -2071,22 +2280,30 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
|
2071
2280
|
return [];
|
2072
2281
|
}
|
2073
2282
|
|
2283
|
+
const op = decl.operator.slice(0, -1) || '=';
|
2284
|
+
|
2074
2285
|
// hack: .length setter
|
2075
2286
|
if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
|
2076
2287
|
const name = decl.left.object.name;
|
2077
|
-
const pointer = arrays
|
2288
|
+
const pointer = scope.arrays?.get(name);
|
2078
2289
|
|
2079
|
-
const aotPointer = pointer != null;
|
2290
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2080
2291
|
|
2081
2292
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
2293
|
+
const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
2082
2294
|
|
2083
2295
|
return [
|
2084
2296
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2085
2297
|
...generate(scope, decl.left.object),
|
2086
2298
|
Opcodes.i32_to_u
|
2087
2299
|
]),
|
2300
|
+
...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
2088
2301
|
|
2089
|
-
...generate(scope, decl.right),
|
2302
|
+
...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
|
2303
|
+
[ Opcodes.local_get, pointerTmp ],
|
2304
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
2305
|
+
Opcodes.i32_from_u
|
2306
|
+
], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
|
2090
2307
|
[ Opcodes.local_tee, newValueTmp ],
|
2091
2308
|
|
2092
2309
|
Opcodes.i32_to_u,
|
@@ -2096,14 +2313,12 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
|
2096
2313
|
];
|
2097
2314
|
}
|
2098
2315
|
|
2099
|
-
const op = decl.operator.slice(0, -1) || '=';
|
2100
|
-
|
2101
2316
|
// arr[i]
|
2102
2317
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
2103
2318
|
const name = decl.left.object.name;
|
2104
|
-
const pointer = arrays
|
2319
|
+
const pointer = scope.arrays?.get(name);
|
2105
2320
|
|
2106
|
-
const aotPointer = pointer != null;
|
2321
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2107
2322
|
|
2108
2323
|
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
2109
2324
|
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
@@ -2159,7 +2374,7 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
|
2159
2374
|
];
|
2160
2375
|
}
|
2161
2376
|
|
2162
|
-
if (!name) return todo('destructuring is not supported yet');
|
2377
|
+
if (!name) return todo(scope, 'destructuring is not supported yet', true);
|
2163
2378
|
|
2164
2379
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2165
2380
|
|
@@ -2264,7 +2479,7 @@ const generateUnary = (scope, decl) => {
|
|
2264
2479
|
return out;
|
2265
2480
|
}
|
2266
2481
|
|
2267
|
-
case 'delete':
|
2482
|
+
case 'delete': {
|
2268
2483
|
let toReturn = true, toGenerate = true;
|
2269
2484
|
|
2270
2485
|
if (decl.argument.type === 'Identifier') {
|
@@ -2286,9 +2501,26 @@ const generateUnary = (scope, decl) => {
|
|
2286
2501
|
|
2287
2502
|
out.push(...number(toReturn ? 1 : 0));
|
2288
2503
|
return out;
|
2504
|
+
}
|
2505
|
+
|
2506
|
+
case 'typeof': {
|
2507
|
+
let overrideType, toGenerate = true;
|
2508
|
+
|
2509
|
+
if (decl.argument.type === 'Identifier') {
|
2510
|
+
const out = generateIdent(scope, decl.argument);
|
2511
|
+
|
2512
|
+
// if ReferenceError (undeclared var), ignore and return undefined
|
2513
|
+
if (out[1]) {
|
2514
|
+
// does not exist (2 ops from throw)
|
2515
|
+
overrideType = number(TYPES.undefined, Valtype.i32);
|
2516
|
+
toGenerate = false;
|
2517
|
+
}
|
2518
|
+
}
|
2519
|
+
|
2520
|
+
const out = toGenerate ? generate(scope, decl.argument) : [];
|
2521
|
+
disposeLeftover(out);
|
2289
2522
|
|
2290
|
-
|
2291
|
-
return typeSwitch(scope, getNodeType(scope, decl.argument), {
|
2523
|
+
out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
|
2292
2524
|
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
2293
2525
|
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
2294
2526
|
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
@@ -2299,10 +2531,13 @@ const generateUnary = (scope, decl) => {
|
|
2299
2531
|
|
2300
2532
|
// object and internal types
|
2301
2533
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2302
|
-
});
|
2534
|
+
}));
|
2535
|
+
|
2536
|
+
return out;
|
2537
|
+
}
|
2303
2538
|
|
2304
2539
|
default:
|
2305
|
-
return todo(`unary operator ${decl.operator} not implemented yet
|
2540
|
+
return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
|
2306
2541
|
}
|
2307
2542
|
};
|
2308
2543
|
|
@@ -2312,7 +2547,7 @@ const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
|
|
2312
2547
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2313
2548
|
|
2314
2549
|
if (local === undefined) {
|
2315
|
-
return todo(`update expression with undefined variable
|
2550
|
+
return todo(scope, `update expression with undefined variable`, true);
|
2316
2551
|
}
|
2317
2552
|
|
2318
2553
|
const idx = local.idx;
|
@@ -2372,7 +2607,7 @@ const generateConditional = (scope, decl) => {
|
|
2372
2607
|
// note type
|
2373
2608
|
out.push(
|
2374
2609
|
...getNodeType(scope, decl.consequent),
|
2375
|
-
setLastType(scope)
|
2610
|
+
...setLastType(scope)
|
2376
2611
|
);
|
2377
2612
|
|
2378
2613
|
out.push([ Opcodes.else ]);
|
@@ -2381,7 +2616,7 @@ const generateConditional = (scope, decl) => {
|
|
2381
2616
|
// note type
|
2382
2617
|
out.push(
|
2383
2618
|
...getNodeType(scope, decl.alternate),
|
2384
|
-
setLastType(scope)
|
2619
|
+
...setLastType(scope)
|
2385
2620
|
);
|
2386
2621
|
|
2387
2622
|
out.push([ Opcodes.end ]);
|
@@ -2441,6 +2676,36 @@ const generateWhile = (scope, decl) => {
|
|
2441
2676
|
return out;
|
2442
2677
|
};
|
2443
2678
|
|
2679
|
+
const generateDoWhile = (scope, decl) => {
|
2680
|
+
const out = [];
|
2681
|
+
|
2682
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
2683
|
+
depth.push('dowhile');
|
2684
|
+
|
2685
|
+
// block for break (includes all)
|
2686
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2687
|
+
depth.push('block');
|
2688
|
+
|
2689
|
+
// block for continue
|
2690
|
+
// includes body but not test+loop so we can exit body at anytime
|
2691
|
+
// and still test+loop after
|
2692
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2693
|
+
depth.push('block');
|
2694
|
+
|
2695
|
+
out.push(...generate(scope, decl.body));
|
2696
|
+
|
2697
|
+
out.push([ Opcodes.end ]);
|
2698
|
+
depth.pop();
|
2699
|
+
|
2700
|
+
out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2701
|
+
out.push([ Opcodes.br_if, 1 ]);
|
2702
|
+
|
2703
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
2704
|
+
depth.pop(); depth.pop();
|
2705
|
+
|
2706
|
+
return out;
|
2707
|
+
};
|
2708
|
+
|
2444
2709
|
const generateForOf = (scope, decl) => {
|
2445
2710
|
const out = [];
|
2446
2711
|
|
@@ -2477,7 +2742,10 @@ const generateForOf = (scope, decl) => {
|
|
2477
2742
|
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2478
2743
|
}
|
2479
2744
|
|
2745
|
+
// if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
|
2746
|
+
|
2480
2747
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2748
|
+
if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
|
2481
2749
|
|
2482
2750
|
depth.push('block');
|
2483
2751
|
depth.push('block');
|
@@ -2486,6 +2754,7 @@ const generateForOf = (scope, decl) => {
|
|
2486
2754
|
// hack: this is naughty and will break things!
|
2487
2755
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2488
2756
|
if (pages.hasAnyString) {
|
2757
|
+
// todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
|
2489
2758
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2490
2759
|
rawElements: new Array(1)
|
2491
2760
|
}, isGlobal, leftName, true, 'i16');
|
@@ -2577,6 +2846,56 @@ const generateForOf = (scope, decl) => {
|
|
2577
2846
|
[ Opcodes.end ],
|
2578
2847
|
[ Opcodes.end ]
|
2579
2848
|
],
|
2849
|
+
[TYPES._bytestring]: [
|
2850
|
+
...setType(scope, leftName, TYPES._bytestring),
|
2851
|
+
|
2852
|
+
[ Opcodes.loop, Blocktype.void ],
|
2853
|
+
|
2854
|
+
// setup new/out array
|
2855
|
+
...newOut,
|
2856
|
+
[ Opcodes.drop ],
|
2857
|
+
|
2858
|
+
...number(0, Valtype.i32), // base 0 for store after
|
2859
|
+
|
2860
|
+
// load current string ind {arg}
|
2861
|
+
[ Opcodes.local_get, pointer ],
|
2862
|
+
[ Opcodes.local_get, counter ],
|
2863
|
+
[ Opcodes.i32_add ],
|
2864
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
2865
|
+
|
2866
|
+
// store to new string ind 0
|
2867
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2868
|
+
|
2869
|
+
// return new string (page)
|
2870
|
+
...number(newPointer),
|
2871
|
+
|
2872
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2873
|
+
|
2874
|
+
[ Opcodes.block, Blocktype.void ],
|
2875
|
+
[ Opcodes.block, Blocktype.void ],
|
2876
|
+
...generate(scope, decl.body),
|
2877
|
+
[ Opcodes.end ],
|
2878
|
+
|
2879
|
+
// increment iter pointer
|
2880
|
+
// [ Opcodes.local_get, pointer ],
|
2881
|
+
// ...number(1, Valtype.i32),
|
2882
|
+
// [ Opcodes.i32_add ],
|
2883
|
+
// [ Opcodes.local_set, pointer ],
|
2884
|
+
|
2885
|
+
// increment counter by 1
|
2886
|
+
[ Opcodes.local_get, counter ],
|
2887
|
+
...number(1, Valtype.i32),
|
2888
|
+
[ Opcodes.i32_add ],
|
2889
|
+
[ Opcodes.local_tee, counter ],
|
2890
|
+
|
2891
|
+
// loop if counter != length
|
2892
|
+
[ Opcodes.local_get, length ],
|
2893
|
+
[ Opcodes.i32_ne ],
|
2894
|
+
[ Opcodes.br_if, 1 ],
|
2895
|
+
|
2896
|
+
[ Opcodes.end ],
|
2897
|
+
[ Opcodes.end ]
|
2898
|
+
],
|
2580
2899
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2581
2900
|
}, Blocktype.void));
|
2582
2901
|
|
@@ -2587,28 +2906,65 @@ const generateForOf = (scope, decl) => {
|
|
2587
2906
|
return out;
|
2588
2907
|
};
|
2589
2908
|
|
2909
|
+
// find the nearest loop in depth map by type
|
2590
2910
|
const getNearestLoop = () => {
|
2591
2911
|
for (let i = depth.length - 1; i >= 0; i--) {
|
2592
|
-
if (
|
2912
|
+
if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
|
2593
2913
|
}
|
2594
2914
|
|
2595
2915
|
return -1;
|
2596
2916
|
};
|
2597
2917
|
|
2598
2918
|
const generateBreak = (scope, decl) => {
|
2599
|
-
const
|
2919
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2920
|
+
const type = depth[target];
|
2921
|
+
|
2922
|
+
// different loop types have different branch offsets
|
2923
|
+
// as they have different wasm block/loop/if structures
|
2924
|
+
// we need to use the right offset by type to branch to the one we want
|
2925
|
+
// for a break: exit the loop without executing anything else inside it
|
2926
|
+
const offset = ({
|
2927
|
+
for: 2, // loop > if (wanted branch) > block (we are here)
|
2928
|
+
while: 2, // loop > if (wanted branch) (we are here)
|
2929
|
+
dowhile: 2, // loop > block (wanted branch) > block (we are here)
|
2930
|
+
forof: 2, // loop > block (wanted branch) > block (we are here)
|
2931
|
+
if: 1 // break inside if, branch 0 to skip the rest of the if
|
2932
|
+
})[type];
|
2933
|
+
|
2600
2934
|
return [
|
2601
|
-
[ Opcodes.br, ...signedLEB128(
|
2935
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2602
2936
|
];
|
2603
2937
|
};
|
2604
2938
|
|
2605
2939
|
const generateContinue = (scope, decl) => {
|
2606
|
-
const
|
2940
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2941
|
+
const type = depth[target];
|
2942
|
+
|
2943
|
+
// different loop types have different branch offsets
|
2944
|
+
// as they have different wasm block/loop/if structures
|
2945
|
+
// we need to use the right offset by type to branch to the one we want
|
2946
|
+
// for a continue: do test for the loop, and then loop depending on that success
|
2947
|
+
const offset = ({
|
2948
|
+
for: 3, // loop (wanted branch) > if > block (we are here)
|
2949
|
+
while: 1, // loop (wanted branch) > if (we are here)
|
2950
|
+
dowhile: 3, // loop > block > block (wanted branch) (we are here)
|
2951
|
+
forof: 3 // loop > block > block (wanted branch) (we are here)
|
2952
|
+
})[type];
|
2953
|
+
|
2607
2954
|
return [
|
2608
|
-
[ Opcodes.br, ...signedLEB128(
|
2955
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2609
2956
|
];
|
2610
2957
|
};
|
2611
2958
|
|
2959
|
+
const generateLabel = (scope, decl) => {
|
2960
|
+
scope.labels ??= new Map();
|
2961
|
+
|
2962
|
+
const name = decl.label.name;
|
2963
|
+
scope.labels.set(name, depth.length);
|
2964
|
+
|
2965
|
+
return generate(scope, decl.body);
|
2966
|
+
};
|
2967
|
+
|
2612
2968
|
const generateThrow = (scope, decl) => {
|
2613
2969
|
scope.throws = true;
|
2614
2970
|
|
@@ -2641,7 +2997,7 @@ const generateThrow = (scope, decl) => {
|
|
2641
2997
|
};
|
2642
2998
|
|
2643
2999
|
const generateTry = (scope, decl) => {
|
2644
|
-
if (decl.finalizer) return todo('try finally not implemented yet');
|
3000
|
+
if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
|
2645
3001
|
|
2646
3002
|
const out = [];
|
2647
3003
|
|
@@ -2672,7 +3028,7 @@ const generateAssignPat = (scope, decl) => {
|
|
2672
3028
|
// TODO
|
2673
3029
|
// if identifier declared, use that
|
2674
3030
|
// else, use default (right)
|
2675
|
-
return todo('assignment pattern (optional arg)');
|
3031
|
+
return todo(scope, 'assignment pattern (optional arg)');
|
2676
3032
|
};
|
2677
3033
|
|
2678
3034
|
let pages = new Map();
|
@@ -2751,16 +3107,22 @@ const getAllocType = itemType => {
|
|
2751
3107
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2752
3108
|
const out = [];
|
2753
3109
|
|
3110
|
+
scope.arrays ??= new Map();
|
3111
|
+
|
2754
3112
|
let firstAssign = false;
|
2755
|
-
if (!arrays.has(name) || name === '$undeclared') {
|
3113
|
+
if (!scope.arrays.has(name) || name === '$undeclared') {
|
2756
3114
|
firstAssign = true;
|
2757
3115
|
|
2758
3116
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2759
3117
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2760
|
-
|
3118
|
+
|
3119
|
+
if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
|
3120
|
+
else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2761
3121
|
}
|
2762
3122
|
|
2763
|
-
const pointer = arrays.get(name);
|
3123
|
+
const pointer = scope.arrays.get(name);
|
3124
|
+
|
3125
|
+
const local = global ? globals[name] : scope.locals[name];
|
2764
3126
|
|
2765
3127
|
const useRawElements = !!decl.rawElements;
|
2766
3128
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
@@ -2794,11 +3156,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2794
3156
|
return [ out, pointer ];
|
2795
3157
|
}
|
2796
3158
|
|
3159
|
+
const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
|
3160
|
+
if (pointerTmp != null) {
|
3161
|
+
out.push(
|
3162
|
+
[ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
3163
|
+
Opcodes.i32_to_u,
|
3164
|
+
[ Opcodes.local_set, pointerTmp ]
|
3165
|
+
);
|
3166
|
+
}
|
3167
|
+
|
3168
|
+
const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
|
3169
|
+
|
2797
3170
|
// store length as 0th array
|
2798
3171
|
out.push(
|
2799
|
-
...
|
3172
|
+
...pointerWasm,
|
2800
3173
|
...number(length, Valtype.i32),
|
2801
|
-
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1,
|
3174
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
|
2802
3175
|
);
|
2803
3176
|
|
2804
3177
|
const storeOp = StoreOps[itemType];
|
@@ -2807,14 +3180,14 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2807
3180
|
if (elements[i] == null) continue;
|
2808
3181
|
|
2809
3182
|
out.push(
|
2810
|
-
...
|
3183
|
+
...pointerWasm,
|
2811
3184
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2812
|
-
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(
|
3185
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2813
3186
|
);
|
2814
3187
|
}
|
2815
3188
|
|
2816
3189
|
// local value as pointer
|
2817
|
-
out.push(...
|
3190
|
+
out.push(...pointerWasm, Opcodes.i32_from_u);
|
2818
3191
|
|
2819
3192
|
return [ out, pointer ];
|
2820
3193
|
};
|
@@ -2846,20 +3219,29 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
|
|
2846
3219
|
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2847
3220
|
};
|
2848
3221
|
|
2849
|
-
let arrays = new Map();
|
2850
3222
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
2851
3223
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
2852
3224
|
};
|
2853
3225
|
|
2854
3226
|
export const generateMember = (scope, decl, _global, _name) => {
|
2855
3227
|
const name = decl.object.name;
|
2856
|
-
const pointer = arrays
|
3228
|
+
const pointer = scope.arrays?.get(name);
|
2857
3229
|
|
2858
|
-
const aotPointer = pointer != null;
|
3230
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2859
3231
|
|
2860
3232
|
// hack: .length
|
2861
3233
|
if (decl.property.name === 'length') {
|
2862
|
-
|
3234
|
+
const func = funcs.find(x => x.name === name);
|
3235
|
+
if (func) {
|
3236
|
+
const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
|
3237
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
3238
|
+
return number(typedParams ? func.params.length / 2 : func.params.length);
|
3239
|
+
}
|
3240
|
+
|
3241
|
+
if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
|
3242
|
+
if (importedFuncs[name]) return number(importedFuncs[name].params);
|
3243
|
+
if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
|
3244
|
+
|
2863
3245
|
return [
|
2864
3246
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2865
3247
|
...generate(scope, decl.object),
|
@@ -2903,7 +3285,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2903
3285
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2904
3286
|
|
2905
3287
|
...number(TYPES.number, Valtype.i32),
|
2906
|
-
setLastType(scope)
|
3288
|
+
...setLastType(scope)
|
2907
3289
|
],
|
2908
3290
|
|
2909
3291
|
[TYPES.string]: [
|
@@ -2935,7 +3317,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2935
3317
|
...number(newPointer),
|
2936
3318
|
|
2937
3319
|
...number(TYPES.string, Valtype.i32),
|
2938
|
-
setLastType(scope)
|
3320
|
+
...setLastType(scope)
|
2939
3321
|
],
|
2940
3322
|
[TYPES._bytestring]: [
|
2941
3323
|
// setup new/out array
|
@@ -2954,19 +3336,19 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2954
3336
|
]),
|
2955
3337
|
|
2956
3338
|
// load current string ind {arg}
|
2957
|
-
[ Opcodes.i32_load8_u,
|
3339
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2958
3340
|
|
2959
3341
|
// store to new string ind 0
|
2960
|
-
[ Opcodes.i32_store8,
|
3342
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2961
3343
|
|
2962
3344
|
// return new string (page)
|
2963
3345
|
...number(newPointer),
|
2964
3346
|
|
2965
3347
|
...number(TYPES._bytestring, Valtype.i32),
|
2966
|
-
setLastType(scope)
|
3348
|
+
...setLastType(scope)
|
2967
3349
|
],
|
2968
3350
|
|
2969
|
-
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
|
3351
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
|
2970
3352
|
});
|
2971
3353
|
};
|
2972
3354
|
|
@@ -2976,28 +3358,36 @@ const objectHack = node => {
|
|
2976
3358
|
if (!node) return node;
|
2977
3359
|
|
2978
3360
|
if (node.type === 'MemberExpression') {
|
2979
|
-
|
3361
|
+
const out = (() => {
|
3362
|
+
if (node.computed || node.optional) return;
|
2980
3363
|
|
2981
|
-
|
3364
|
+
let objectName = node.object.name;
|
2982
3365
|
|
2983
|
-
|
2984
|
-
|
3366
|
+
// if object is not identifier or another member exp, give up
|
3367
|
+
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
|
3368
|
+
if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
|
2985
3369
|
|
2986
|
-
|
3370
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2987
3371
|
|
2988
|
-
|
2989
|
-
|
3372
|
+
// if .length, give up (hack within a hack!)
|
3373
|
+
if (node.property.name === 'length') {
|
3374
|
+
node.object = objectHack(node.object);
|
3375
|
+
return;
|
3376
|
+
}
|
2990
3377
|
|
2991
|
-
|
2992
|
-
|
3378
|
+
// no object name, give up
|
3379
|
+
if (!objectName) return;
|
2993
3380
|
|
2994
|
-
|
2995
|
-
|
3381
|
+
const name = '__' + objectName + '_' + node.property.name;
|
3382
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2996
3383
|
|
2997
|
-
|
2998
|
-
|
2999
|
-
|
3000
|
-
|
3384
|
+
return {
|
3385
|
+
type: 'Identifier',
|
3386
|
+
name
|
3387
|
+
};
|
3388
|
+
})();
|
3389
|
+
|
3390
|
+
if (out) return out;
|
3001
3391
|
}
|
3002
3392
|
|
3003
3393
|
for (const x in node) {
|
@@ -3011,8 +3401,8 @@ const objectHack = node => {
|
|
3011
3401
|
};
|
3012
3402
|
|
3013
3403
|
const generateFunc = (scope, decl) => {
|
3014
|
-
if (decl.async) return todo('async functions are not supported');
|
3015
|
-
if (decl.generator) return todo('generator functions are not supported');
|
3404
|
+
if (decl.async) return todo(scope, 'async functions are not supported');
|
3405
|
+
if (decl.generator) return todo(scope, 'generator functions are not supported');
|
3016
3406
|
|
3017
3407
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
3018
3408
|
const params = decl.params ?? [];
|
@@ -3028,6 +3418,11 @@ const generateFunc = (scope, decl) => {
|
|
3028
3418
|
name
|
3029
3419
|
};
|
3030
3420
|
|
3421
|
+
if (typedInput && decl.returnType) {
|
3422
|
+
innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
|
3423
|
+
innerScope.returns = [ valtypeBinary ];
|
3424
|
+
}
|
3425
|
+
|
3031
3426
|
for (let i = 0; i < params.length; i++) {
|
3032
3427
|
allocVar(innerScope, params[i].name, false);
|
3033
3428
|
|
@@ -3090,16 +3485,6 @@ const generateCode = (scope, decl) => {
|
|
3090
3485
|
};
|
3091
3486
|
|
3092
3487
|
const internalConstrs = {
|
3093
|
-
Boolean: {
|
3094
|
-
generate: (scope, decl) => {
|
3095
|
-
if (decl.arguments.length === 0) return number(0);
|
3096
|
-
|
3097
|
-
// should generate/run all args
|
3098
|
-
return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
|
3099
|
-
},
|
3100
|
-
type: TYPES.boolean
|
3101
|
-
},
|
3102
|
-
|
3103
3488
|
Array: {
|
3104
3489
|
generate: (scope, decl, global, name) => {
|
3105
3490
|
// new Array(i0, i1, ...)
|
@@ -3117,7 +3502,7 @@ const internalConstrs = {
|
|
3117
3502
|
|
3118
3503
|
// todo: check in wasm instead of here
|
3119
3504
|
const literalValue = arg.value ?? 0;
|
3120
|
-
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
|
3505
|
+
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
|
3121
3506
|
|
3122
3507
|
return [
|
3123
3508
|
...number(0, Valtype.i32),
|
@@ -3128,7 +3513,8 @@ const internalConstrs = {
|
|
3128
3513
|
...number(pointer)
|
3129
3514
|
];
|
3130
3515
|
},
|
3131
|
-
type: TYPES._array
|
3516
|
+
type: TYPES._array,
|
3517
|
+
length: 1
|
3132
3518
|
},
|
3133
3519
|
|
3134
3520
|
__Array_of: {
|
@@ -3140,7 +3526,131 @@ const internalConstrs = {
|
|
3140
3526
|
}, global, name);
|
3141
3527
|
},
|
3142
3528
|
type: TYPES._array,
|
3529
|
+
notConstr: true,
|
3530
|
+
length: 0
|
3531
|
+
},
|
3532
|
+
|
3533
|
+
__Porffor_fastOr: {
|
3534
|
+
generate: (scope, decl) => {
|
3535
|
+
const out = [];
|
3536
|
+
|
3537
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3538
|
+
out.push(
|
3539
|
+
...generate(scope, decl.arguments[i]),
|
3540
|
+
Opcodes.i32_to_u,
|
3541
|
+
...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
|
3542
|
+
);
|
3543
|
+
}
|
3544
|
+
|
3545
|
+
out.push(Opcodes.i32_from_u);
|
3546
|
+
|
3547
|
+
return out;
|
3548
|
+
},
|
3549
|
+
type: TYPES.boolean,
|
3550
|
+
notConstr: true
|
3551
|
+
},
|
3552
|
+
|
3553
|
+
__Porffor_fastAnd: {
|
3554
|
+
generate: (scope, decl) => {
|
3555
|
+
const out = [];
|
3556
|
+
|
3557
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3558
|
+
out.push(
|
3559
|
+
...generate(scope, decl.arguments[i]),
|
3560
|
+
Opcodes.i32_to_u,
|
3561
|
+
...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
|
3562
|
+
);
|
3563
|
+
}
|
3564
|
+
|
3565
|
+
out.push(Opcodes.i32_from_u);
|
3566
|
+
|
3567
|
+
return out;
|
3568
|
+
},
|
3569
|
+
type: TYPES.boolean,
|
3143
3570
|
notConstr: true
|
3571
|
+
},
|
3572
|
+
|
3573
|
+
Boolean: {
|
3574
|
+
generate: (scope, decl) => {
|
3575
|
+
// todo: boolean object when used as constructor
|
3576
|
+
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
3577
|
+
return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
|
3578
|
+
},
|
3579
|
+
type: TYPES.boolean,
|
3580
|
+
length: 1
|
3581
|
+
},
|
3582
|
+
|
3583
|
+
__Math_max: {
|
3584
|
+
generate: (scope, decl) => {
|
3585
|
+
const out = [
|
3586
|
+
...number(-Infinity)
|
3587
|
+
];
|
3588
|
+
|
3589
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3590
|
+
out.push(
|
3591
|
+
...generate(scope, decl.arguments[i]),
|
3592
|
+
[ Opcodes.f64_max ]
|
3593
|
+
);
|
3594
|
+
}
|
3595
|
+
|
3596
|
+
return out;
|
3597
|
+
},
|
3598
|
+
type: TYPES.number,
|
3599
|
+
notConstr: true,
|
3600
|
+
length: 2
|
3601
|
+
},
|
3602
|
+
|
3603
|
+
__Math_min: {
|
3604
|
+
generate: (scope, decl) => {
|
3605
|
+
const out = [
|
3606
|
+
...number(Infinity)
|
3607
|
+
];
|
3608
|
+
|
3609
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3610
|
+
out.push(
|
3611
|
+
...generate(scope, decl.arguments[i]),
|
3612
|
+
[ Opcodes.f64_min ]
|
3613
|
+
);
|
3614
|
+
}
|
3615
|
+
|
3616
|
+
return out;
|
3617
|
+
},
|
3618
|
+
type: TYPES.number,
|
3619
|
+
notConstr: true,
|
3620
|
+
length: 2
|
3621
|
+
},
|
3622
|
+
|
3623
|
+
__console_log: {
|
3624
|
+
generate: (scope, decl) => {
|
3625
|
+
const out = [];
|
3626
|
+
|
3627
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3628
|
+
out.push(
|
3629
|
+
...generateCall(scope, {
|
3630
|
+
callee: {
|
3631
|
+
type: 'Identifier',
|
3632
|
+
name: '__Porffor_print'
|
3633
|
+
},
|
3634
|
+
arguments: [ decl.arguments[i] ]
|
3635
|
+
}),
|
3636
|
+
|
3637
|
+
// print space
|
3638
|
+
...number(32),
|
3639
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3640
|
+
);
|
3641
|
+
}
|
3642
|
+
|
3643
|
+
// print newline
|
3644
|
+
out.push(
|
3645
|
+
...number(10),
|
3646
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3647
|
+
);
|
3648
|
+
|
3649
|
+
return out;
|
3650
|
+
},
|
3651
|
+
type: TYPES.undefined,
|
3652
|
+
notConstr: true,
|
3653
|
+
length: 0
|
3144
3654
|
}
|
3145
3655
|
};
|
3146
3656
|
|
@@ -3169,20 +3679,23 @@ export default program => {
|
|
3169
3679
|
funcs = [];
|
3170
3680
|
funcIndex = {};
|
3171
3681
|
depth = [];
|
3172
|
-
arrays = new Map();
|
3173
3682
|
pages = new Map();
|
3174
3683
|
data = [];
|
3175
3684
|
currentFuncIndex = importedFuncs.length;
|
3176
3685
|
|
3177
3686
|
globalThis.valtype = 'f64';
|
3178
3687
|
|
3179
|
-
const valtypeOpt = process.argv.find(x => x.startsWith('
|
3688
|
+
const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
|
3180
3689
|
if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
|
3181
3690
|
|
3182
3691
|
globalThis.valtypeBinary = Valtype[valtype];
|
3183
3692
|
|
3184
3693
|
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
3185
3694
|
|
3695
|
+
globalThis.pageSize = PageSize;
|
3696
|
+
const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
|
3697
|
+
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3698
|
+
|
3186
3699
|
// set generic opcodes for current valtype
|
3187
3700
|
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
3188
3701
|
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
@@ -3191,10 +3704,10 @@ export default program => {
|
|
3191
3704
|
Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
|
3192
3705
|
Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
|
3193
3706
|
|
3194
|
-
Opcodes.i32_to = [ [
|
3195
|
-
Opcodes.i32_to_u = [ [
|
3196
|
-
Opcodes.i32_from = [ [
|
3197
|
-
Opcodes.i32_from_u = [ [
|
3707
|
+
Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
|
3708
|
+
Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
|
3709
|
+
Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
|
3710
|
+
Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
|
3198
3711
|
|
3199
3712
|
Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
|
3200
3713
|
Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
|
@@ -3207,10 +3720,6 @@ export default program => {
|
|
3207
3720
|
|
3208
3721
|
program.id = { name: 'main' };
|
3209
3722
|
|
3210
|
-
globalThis.pageSize = PageSize;
|
3211
|
-
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
3212
|
-
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3213
|
-
|
3214
3723
|
const scope = {
|
3215
3724
|
locals: {},
|
3216
3725
|
localInd: 0
|
@@ -3221,7 +3730,7 @@ export default program => {
|
|
3221
3730
|
body: program.body
|
3222
3731
|
};
|
3223
3732
|
|
3224
|
-
if (Prefs.astLog) console.log(program.body.body);
|
3733
|
+
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
3225
3734
|
|
3226
3735
|
generateFunc(scope, program);
|
3227
3736
|
|
@@ -3238,7 +3747,11 @@ export default program => {
|
|
3238
3747
|
}
|
3239
3748
|
|
3240
3749
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
3241
|
-
|
3750
|
+
if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
|
3751
|
+
main.wasm.splice(main.wasm.length - 1, 1);
|
3752
|
+
} else {
|
3753
|
+
main.returns = [];
|
3754
|
+
}
|
3242
3755
|
}
|
3243
3756
|
|
3244
3757
|
if (lastInst[0] === Opcodes.call) {
|