porffor 0.2.0-dcc06c8 → 0.2.0-e04e26f
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/LICENSE +20 -20
- package/README.md +63 -44
- package/asur/README.md +2 -0
- package/asur/index.js +1262 -0
- package/byg/index.js +237 -0
- package/compiler/2c.js +1 -1
- package/compiler/{sections.js → assemble.js} +58 -11
- package/compiler/builtins/annexb_string.js +72 -0
- package/compiler/builtins/annexb_string.ts +19 -0
- package/compiler/builtins/array.ts +145 -0
- package/compiler/builtins/base64.ts +103 -40
- package/compiler/builtins/crypto.ts +120 -0
- package/compiler/builtins/date.ts +1128 -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 +33 -1
- package/compiler/builtins/string.ts +1055 -0
- package/compiler/builtins/tostring.ts +45 -0
- package/compiler/builtins.js +452 -238
- package/compiler/{codeGen.js → codegen.js} +799 -290
- package/compiler/embedding.js +22 -22
- package/compiler/encoding.js +108 -10
- package/compiler/generated_builtins.js +1133 -0
- package/compiler/index.js +16 -14
- package/compiler/log.js +6 -3
- package/compiler/opt.js +23 -22
- package/compiler/parse.js +31 -25
- package/compiler/precompile.js +66 -22
- package/compiler/prefs.js +5 -1
- package/compiler/prototype.js +4 -20
- package/compiler/types.js +37 -0
- package/compiler/wasmSpec.js +28 -8
- package/compiler/wrap.js +51 -47
- package/package.json +9 -5
- package/porf +2 -0
- package/rhemyn/compile.js +3 -2
- package/rhemyn/parse.js +323 -320
- package/rhemyn/test/parse.js +58 -58
- package/runner/compare.js +34 -34
- package/runner/debug.js +122 -0
- package/runner/index.js +31 -9
- package/runner/profiler.js +102 -0
- package/runner/repl.js +40 -7
- package/runner/sizes.js +37 -37
- package/demo.js +0 -3
- package/demo.ts +0 -1
- package/filesize.cmd +0 -2
- package/hello +0 -0
- package/runner/info.js +0 -89
- package/runner/profile.js +0 -46
- package/runner/results.json +0 -1
- package/runner/transform.js +0 -15
- package/tmp.c +0 -152
- 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';
|
@@ -105,7 +108,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
105
108
|
return generateUnary(scope, decl);
|
106
109
|
|
107
110
|
case 'UpdateExpression':
|
108
|
-
return generateUpdate(scope, decl);
|
111
|
+
return generateUpdate(scope, decl, global, name, valueUnused);
|
109
112
|
|
110
113
|
case 'IfStatement':
|
111
114
|
return generateIf(scope, decl);
|
@@ -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
|
|
@@ -169,8 +184,8 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
169
184
|
if (asm[0] === '') continue; // blank
|
170
185
|
|
171
186
|
if (asm[0] === 'local') {
|
172
|
-
const [ name,
|
173
|
-
scope.locals[name] = { idx:
|
187
|
+
const [ name, type ] = asm.slice(1);
|
188
|
+
scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
|
174
189
|
continue;
|
175
190
|
}
|
176
191
|
|
@@ -189,43 +204,55 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
189
204
|
if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
190
205
|
|
191
206
|
if (!Array.isArray(inst)) inst = [ inst ];
|
192
|
-
const immediates = asm.slice(1).map(x =>
|
207
|
+
const immediates = asm.slice(1).map(x => {
|
208
|
+
const int = parseInt(x);
|
209
|
+
if (Number.isNaN(int)) return scope.locals[x]?.idx;
|
210
|
+
return int;
|
211
|
+
});
|
193
212
|
|
194
|
-
out.push([ ...inst, ...immediates ]);
|
213
|
+
out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
|
195
214
|
}
|
196
215
|
|
197
216
|
return out;
|
198
217
|
},
|
199
218
|
|
200
219
|
__Porffor_bs: str => [
|
201
|
-
...makeString(scope, str,
|
220
|
+
...makeString(scope, str, global, name, true),
|
202
221
|
|
203
|
-
...
|
204
|
-
|
222
|
+
...(name ? setType(scope, name, TYPES._bytestring) : [
|
223
|
+
...number(TYPES._bytestring, Valtype.i32),
|
224
|
+
...setLastType(scope)
|
225
|
+
])
|
205
226
|
],
|
206
227
|
__Porffor_s: str => [
|
207
|
-
...makeString(scope, str,
|
228
|
+
...makeString(scope, str, global, name, false),
|
208
229
|
|
209
|
-
...
|
210
|
-
|
230
|
+
...(name ? setType(scope, name, TYPES.string) : [
|
231
|
+
...number(TYPES.string, Valtype.i32),
|
232
|
+
...setLastType(scope)
|
233
|
+
])
|
211
234
|
],
|
212
235
|
};
|
213
236
|
|
214
|
-
const
|
237
|
+
const func = decl.tag.name;
|
215
238
|
// hack for inline asm
|
216
|
-
if (!funcs[
|
239
|
+
if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
|
217
240
|
|
218
241
|
const { quasis, expressions } = decl.quasi;
|
219
242
|
let str = quasis[0].value.raw;
|
220
243
|
|
221
244
|
for (let i = 0; i < expressions.length; i++) {
|
222
245
|
const e = expressions[i];
|
223
|
-
|
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;
|
224
251
|
|
225
252
|
str += quasis[i + 1].value.raw;
|
226
253
|
}
|
227
254
|
|
228
|
-
return funcs[
|
255
|
+
return funcs[func](str);
|
229
256
|
}
|
230
257
|
|
231
258
|
default:
|
@@ -235,7 +262,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
235
262
|
return [];
|
236
263
|
}
|
237
264
|
|
238
|
-
return todo(`no generation for ${decl.type}!`);
|
265
|
+
return todo(scope, `no generation for ${decl.type}!`);
|
239
266
|
}
|
240
267
|
};
|
241
268
|
|
@@ -263,7 +290,7 @@ const lookupName = (scope, _name) => {
|
|
263
290
|
return [ undefined, undefined ];
|
264
291
|
};
|
265
292
|
|
266
|
-
const internalThrow = (scope, constructor, message, expectsValue =
|
293
|
+
const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
|
267
294
|
...generateThrow(scope, {
|
268
295
|
argument: {
|
269
296
|
type: 'NewExpression',
|
@@ -287,7 +314,10 @@ const generateIdent = (scope, decl) => {
|
|
287
314
|
|
288
315
|
if (Object.hasOwn(builtinVars, name)) {
|
289
316
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
290
|
-
|
317
|
+
|
318
|
+
let wasm = builtinVars[name];
|
319
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
|
320
|
+
return wasm.slice();
|
291
321
|
}
|
292
322
|
|
293
323
|
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
@@ -295,6 +325,11 @@ const generateIdent = (scope, decl) => {
|
|
295
325
|
return number(1);
|
296
326
|
}
|
297
327
|
|
328
|
+
if (isExistingProtoFunc(name)) {
|
329
|
+
// todo: return an actual something
|
330
|
+
return number(1);
|
331
|
+
}
|
332
|
+
|
298
333
|
if (local?.idx === undefined) {
|
299
334
|
// no local var with name
|
300
335
|
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
@@ -325,14 +360,18 @@ const generateReturn = (scope, decl) => {
|
|
325
360
|
// just bare "return"
|
326
361
|
return [
|
327
362
|
...number(UNDEFINED), // "undefined" if func returns
|
328
|
-
...
|
363
|
+
...(scope.returnType != null ? [] : [
|
364
|
+
...number(TYPES.undefined, Valtype.i32) // type undefined
|
365
|
+
]),
|
329
366
|
[ Opcodes.return ]
|
330
367
|
];
|
331
368
|
}
|
332
369
|
|
333
370
|
return [
|
334
371
|
...generate(scope, decl.argument),
|
335
|
-
...
|
372
|
+
...(scope.returnType != null ? [] : [
|
373
|
+
...getNodeType(scope, decl.argument)
|
374
|
+
]),
|
336
375
|
[ Opcodes.return ]
|
337
376
|
];
|
338
377
|
};
|
@@ -346,7 +385,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
346
385
|
return idx;
|
347
386
|
};
|
348
387
|
|
349
|
-
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);
|
350
390
|
|
351
391
|
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
352
392
|
const checks = {
|
@@ -355,7 +395,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
355
395
|
'??': nullish
|
356
396
|
};
|
357
397
|
|
358
|
-
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);
|
359
399
|
|
360
400
|
// generic structure for {a} OP {b}
|
361
401
|
// -->
|
@@ -363,8 +403,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
363
403
|
|
364
404
|
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
365
405
|
// (like if we are in an if condition - very common)
|
366
|
-
const leftIsInt =
|
367
|
-
const rightIsInt =
|
406
|
+
const leftIsInt = isFloatToIntOp(left[left.length - 1]);
|
407
|
+
const rightIsInt = isFloatToIntOp(right[right.length - 1]);
|
368
408
|
|
369
409
|
const canInt = leftIsInt && rightIsInt;
|
370
410
|
|
@@ -381,12 +421,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
381
421
|
...right,
|
382
422
|
// note type
|
383
423
|
...rightType,
|
384
|
-
setLastType(scope),
|
424
|
+
...setLastType(scope),
|
385
425
|
[ Opcodes.else ],
|
386
426
|
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
387
427
|
// note type
|
388
428
|
...leftType,
|
389
|
-
setLastType(scope),
|
429
|
+
...setLastType(scope),
|
390
430
|
[ Opcodes.end ],
|
391
431
|
Opcodes.i32_from
|
392
432
|
];
|
@@ -400,17 +440,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
400
440
|
...right,
|
401
441
|
// note type
|
402
442
|
...rightType,
|
403
|
-
setLastType(scope),
|
443
|
+
...setLastType(scope),
|
404
444
|
[ Opcodes.else ],
|
405
445
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
406
446
|
// note type
|
407
447
|
...leftType,
|
408
|
-
setLastType(scope),
|
448
|
+
...setLastType(scope),
|
409
449
|
[ Opcodes.end ]
|
410
450
|
];
|
411
451
|
};
|
412
452
|
|
413
|
-
const concatStrings = (scope, left, right, global, name, assign) => {
|
453
|
+
const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
|
414
454
|
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
415
455
|
// todo: convert left and right to strings if not
|
416
456
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -421,7 +461,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
421
461
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
422
462
|
|
423
463
|
if (assign) {
|
424
|
-
const pointer = arrays
|
464
|
+
const pointer = scope.arrays?.get(name ?? '$undeclared');
|
425
465
|
|
426
466
|
return [
|
427
467
|
// setup right
|
@@ -446,11 +486,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
446
486
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
|
447
487
|
|
448
488
|
// copy right
|
449
|
-
// dst = out pointer + length size + current length *
|
489
|
+
// dst = out pointer + length size + current length * sizeof valtype
|
450
490
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
451
491
|
|
452
492
|
[ Opcodes.local_get, leftLength ],
|
453
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
493
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
454
494
|
[ Opcodes.i32_mul ],
|
455
495
|
[ Opcodes.i32_add ],
|
456
496
|
|
@@ -459,9 +499,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
459
499
|
...number(ValtypeSize.i32, Valtype.i32),
|
460
500
|
[ Opcodes.i32_add ],
|
461
501
|
|
462
|
-
// size = right length *
|
502
|
+
// size = right length * sizeof valtype
|
463
503
|
[ Opcodes.local_get, rightLength ],
|
464
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
504
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
465
505
|
[ Opcodes.i32_mul ],
|
466
506
|
|
467
507
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -519,11 +559,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
519
559
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
520
560
|
|
521
561
|
// copy right
|
522
|
-
// dst = out pointer + length size + left length *
|
562
|
+
// dst = out pointer + length size + left length * sizeof valtype
|
523
563
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
524
564
|
|
525
565
|
[ Opcodes.local_get, leftLength ],
|
526
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
566
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
527
567
|
[ Opcodes.i32_mul ],
|
528
568
|
[ Opcodes.i32_add ],
|
529
569
|
|
@@ -532,9 +572,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
532
572
|
...number(ValtypeSize.i32, Valtype.i32),
|
533
573
|
[ Opcodes.i32_add ],
|
534
574
|
|
535
|
-
// size = right length *
|
575
|
+
// size = right length * sizeof valtype
|
536
576
|
[ Opcodes.local_get, rightLength ],
|
537
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
577
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
538
578
|
[ Opcodes.i32_mul ],
|
539
579
|
|
540
580
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -544,7 +584,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
544
584
|
];
|
545
585
|
};
|
546
586
|
|
547
|
-
const compareStrings = (scope, left, right) => {
|
587
|
+
const compareStrings = (scope, left, right, bytestrings = false) => {
|
548
588
|
// todo: this should be rewritten into a func
|
549
589
|
// todo: convert left and right to strings if not
|
550
590
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -553,7 +593,6 @@ const compareStrings = (scope, left, right) => {
|
|
553
593
|
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
554
594
|
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
555
595
|
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
556
|
-
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
557
596
|
|
558
597
|
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
559
598
|
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
@@ -581,7 +620,6 @@ const compareStrings = (scope, left, right) => {
|
|
581
620
|
|
582
621
|
[ Opcodes.local_get, rightPointer ],
|
583
622
|
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
584
|
-
[ Opcodes.local_tee, rightLength ],
|
585
623
|
|
586
624
|
// fast path: check leftLength != rightLength
|
587
625
|
[ Opcodes.i32_ne ],
|
@@ -596,11 +634,13 @@ const compareStrings = (scope, left, right) => {
|
|
596
634
|
...number(0, Valtype.i32),
|
597
635
|
[ Opcodes.local_set, index ],
|
598
636
|
|
599
|
-
// setup index end as length * sizeof
|
637
|
+
// setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
|
600
638
|
// we do this instead of having to do mul/div each iter for perf™
|
601
639
|
[ Opcodes.local_get, leftLength ],
|
602
|
-
...
|
603
|
-
|
640
|
+
...(bytestrings ? [] : [
|
641
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
642
|
+
[ Opcodes.i32_mul ],
|
643
|
+
]),
|
604
644
|
[ Opcodes.local_set, indexEnd ],
|
605
645
|
|
606
646
|
// iterate over each char and check if eq
|
@@ -610,13 +650,17 @@ const compareStrings = (scope, left, right) => {
|
|
610
650
|
[ Opcodes.local_get, index ],
|
611
651
|
[ Opcodes.local_get, leftPointer ],
|
612
652
|
[ Opcodes.i32_add ],
|
613
|
-
|
653
|
+
bytestrings ?
|
654
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
655
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
614
656
|
|
615
657
|
// fetch right
|
616
658
|
[ Opcodes.local_get, index ],
|
617
659
|
[ Opcodes.local_get, rightPointer ],
|
618
660
|
[ Opcodes.i32_add ],
|
619
|
-
|
661
|
+
bytestrings ?
|
662
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
663
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
620
664
|
|
621
665
|
// not equal, "return" false
|
622
666
|
[ Opcodes.i32_ne ],
|
@@ -625,13 +669,13 @@ const compareStrings = (scope, left, right) => {
|
|
625
669
|
[ Opcodes.br, 2 ],
|
626
670
|
[ Opcodes.end ],
|
627
671
|
|
628
|
-
// index += sizeof
|
672
|
+
// index += sizeof valtype (1 for bytestring, 2 for string)
|
629
673
|
[ Opcodes.local_get, index ],
|
630
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
674
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
631
675
|
[ Opcodes.i32_add ],
|
632
676
|
[ Opcodes.local_tee, index ],
|
633
677
|
|
634
|
-
// if index != index end (length * sizeof
|
678
|
+
// if index != index end (length * sizeof valtype), loop
|
635
679
|
[ Opcodes.local_get, indexEnd ],
|
636
680
|
[ Opcodes.i32_ne ],
|
637
681
|
[ Opcodes.br_if, 0 ],
|
@@ -652,16 +696,18 @@ const compareStrings = (scope, left, right) => {
|
|
652
696
|
};
|
653
697
|
|
654
698
|
const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
655
|
-
if (
|
699
|
+
if (isFloatToIntOp(wasm[wasm.length - 1])) return [
|
656
700
|
...wasm,
|
657
701
|
...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
|
658
702
|
];
|
703
|
+
// if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
|
659
704
|
|
660
|
-
const
|
705
|
+
const useTmp = knownType(scope, type) == null;
|
706
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
661
707
|
|
662
708
|
const def = [
|
663
709
|
// if value != 0
|
664
|
-
[ Opcodes.local_get, tmp ],
|
710
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
665
711
|
|
666
712
|
// ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
667
713
|
...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
|
@@ -673,7 +719,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
673
719
|
|
674
720
|
return [
|
675
721
|
...wasm,
|
676
|
-
[ Opcodes.local_set, tmp ],
|
722
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
677
723
|
|
678
724
|
...typeSwitch(scope, type, {
|
679
725
|
// [TYPES.number]: def,
|
@@ -682,7 +728,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
682
728
|
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
683
729
|
],
|
684
730
|
[TYPES.string]: [
|
685
|
-
[ Opcodes.local_get, tmp ],
|
731
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
686
732
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
687
733
|
|
688
734
|
// get length
|
@@ -694,7 +740,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
694
740
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
695
741
|
],
|
696
742
|
[TYPES._bytestring]: [ // duplicate of string
|
697
|
-
|
743
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
698
744
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
699
745
|
|
700
746
|
// get length
|
@@ -708,10 +754,12 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
708
754
|
};
|
709
755
|
|
710
756
|
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
711
|
-
const
|
757
|
+
const useTmp = knownType(scope, type) == null;
|
758
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
759
|
+
|
712
760
|
return [
|
713
761
|
...wasm,
|
714
|
-
[ Opcodes.local_set, tmp ],
|
762
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
715
763
|
|
716
764
|
...typeSwitch(scope, type, {
|
717
765
|
[TYPES._array]: [
|
@@ -719,7 +767,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
719
767
|
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
720
768
|
],
|
721
769
|
[TYPES.string]: [
|
722
|
-
[ Opcodes.local_get, tmp ],
|
770
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
723
771
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
724
772
|
|
725
773
|
// get length
|
@@ -730,7 +778,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
730
778
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
731
779
|
],
|
732
780
|
[TYPES._bytestring]: [ // duplicate of string
|
733
|
-
[ Opcodes.local_get, tmp ],
|
781
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
734
782
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
735
783
|
|
736
784
|
// get length
|
@@ -742,7 +790,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
742
790
|
],
|
743
791
|
default: [
|
744
792
|
// if value == 0
|
745
|
-
[ Opcodes.local_get, tmp ],
|
793
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
746
794
|
|
747
795
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
748
796
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -752,10 +800,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
752
800
|
};
|
753
801
|
|
754
802
|
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
755
|
-
const
|
803
|
+
const useTmp = knownType(scope, type) == null;
|
804
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
805
|
+
|
756
806
|
return [
|
757
807
|
...wasm,
|
758
|
-
[ Opcodes.local_set, tmp ],
|
808
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
759
809
|
|
760
810
|
...typeSwitch(scope, type, {
|
761
811
|
[TYPES.undefined]: [
|
@@ -764,7 +814,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
764
814
|
],
|
765
815
|
[TYPES.object]: [
|
766
816
|
// object, null if == 0
|
767
|
-
[ Opcodes.local_get, tmp ],
|
817
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
768
818
|
|
769
819
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
770
820
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -845,11 +895,11 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
845
895
|
// todo: if equality op and an operand is undefined, return false
|
846
896
|
// todo: niche null hell with 0
|
847
897
|
|
848
|
-
// todo: this should be dynamic but for now only static
|
849
898
|
if (knownLeft === TYPES.string || knownRight === TYPES.string) {
|
850
899
|
if (op === '+') {
|
900
|
+
// todo: this should be dynamic too but for now only static
|
851
901
|
// string concat (a + b)
|
852
|
-
return concatStrings(scope, left, right, _global, _name, assign);
|
902
|
+
return concatStrings(scope, left, right, _global, _name, assign, false);
|
853
903
|
}
|
854
904
|
|
855
905
|
// not an equality op, NaN
|
@@ -872,6 +922,33 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
872
922
|
}
|
873
923
|
}
|
874
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
|
+
|
875
952
|
let ops = operatorOpcode[valtype][op];
|
876
953
|
|
877
954
|
// some complex ops are implemented as builtin funcs
|
@@ -887,23 +964,62 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
887
964
|
]);
|
888
965
|
}
|
889
966
|
|
890
|
-
if (!ops) return todo(`operator ${op} not implemented yet
|
967
|
+
if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
|
891
968
|
|
892
969
|
if (!Array.isArray(ops)) ops = [ ops ];
|
893
970
|
ops = [ ops ];
|
894
971
|
|
895
972
|
let tmpLeft, tmpRight;
|
896
973
|
// if equal op, check if strings for compareStrings
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
|
901
|
-
return;
|
902
|
-
}
|
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
|
903
977
|
|
978
|
+
if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
|
904
979
|
tmpLeft = localTmp(scope, '__tmpop_left');
|
905
980
|
tmpRight = localTmp(scope, '__tmpop_right');
|
906
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)
|
907
1023
|
ops.unshift(...stringOnly([
|
908
1024
|
// if left is string
|
909
1025
|
...leftType,
|
@@ -915,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
915
1031
|
...number(TYPES.string, Valtype.i32),
|
916
1032
|
[ Opcodes.i32_eq ],
|
917
1033
|
|
918
|
-
// if
|
919
|
-
[ Opcodes.
|
1034
|
+
// if both are true
|
1035
|
+
[ Opcodes.i32_and ],
|
920
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 ],
|
921
1041
|
|
922
|
-
//
|
923
|
-
// if left is not string
|
1042
|
+
// if left is bytestring
|
924
1043
|
...leftType,
|
925
|
-
...number(TYPES.
|
926
|
-
[ Opcodes.
|
1044
|
+
...number(TYPES._bytestring, Valtype.i32),
|
1045
|
+
[ Opcodes.i32_eq ],
|
927
1046
|
|
928
|
-
// if right is
|
1047
|
+
// if right is bytestring
|
929
1048
|
...rightType,
|
930
|
-
...number(TYPES.
|
931
|
-
[ Opcodes.
|
1049
|
+
...number(TYPES._bytestring, Valtype.i32),
|
1050
|
+
[ Opcodes.i32_eq ],
|
932
1051
|
|
933
|
-
// if
|
934
|
-
[ Opcodes.
|
1052
|
+
// if both are true
|
1053
|
+
[ Opcodes.i32_and ],
|
935
1054
|
[ Opcodes.if, Blocktype.void ],
|
936
|
-
...
|
937
|
-
[ Opcodes.br, 2 ],
|
938
|
-
[ Opcodes.end ],
|
939
|
-
|
940
|
-
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
941
|
-
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1055
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
|
942
1056
|
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
943
1057
|
[ Opcodes.br, 1 ],
|
944
1058
|
[ Opcodes.end ],
|
@@ -950,7 +1064,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
950
1064
|
// endOut.push(stringOnly([ Opcodes.end ]));
|
951
1065
|
endOut.unshift(stringOnly([ Opcodes.end ]));
|
952
1066
|
// }
|
953
|
-
}
|
1067
|
+
}
|
954
1068
|
|
955
1069
|
return finalize([
|
956
1070
|
...left,
|
@@ -969,7 +1083,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
969
1083
|
return out;
|
970
1084
|
};
|
971
1085
|
|
972
|
-
const
|
1086
|
+
const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
|
1087
|
+
return func({ name, params, locals, returns, localInd }, {
|
1088
|
+
TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
|
1089
|
+
builtin: name => {
|
1090
|
+
let idx = funcIndex[name] ?? importedFuncs[name];
|
1091
|
+
if (idx === undefined && builtinFuncs[name]) {
|
1092
|
+
includeBuiltin(null, name);
|
1093
|
+
idx = funcIndex[name];
|
1094
|
+
}
|
1095
|
+
|
1096
|
+
return idx;
|
1097
|
+
}
|
1098
|
+
});
|
1099
|
+
};
|
1100
|
+
|
1101
|
+
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
|
973
1102
|
const existing = funcs.find(x => x.name === name);
|
974
1103
|
if (existing) return existing;
|
975
1104
|
|
@@ -981,18 +1110,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
981
1110
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
982
1111
|
}
|
983
1112
|
|
984
|
-
|
985
|
-
const
|
986
|
-
|
987
|
-
|
988
|
-
locals,
|
989
|
-
returns,
|
990
|
-
localInd: allLocals.length,
|
991
|
-
};
|
992
|
-
|
993
|
-
wasm = wasm(scope, { TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString });
|
1113
|
+
for (const x of _data) {
|
1114
|
+
const copy = { ...x };
|
1115
|
+
copy.offset += pages.size * pageSize;
|
1116
|
+
data.push(copy);
|
994
1117
|
}
|
995
1118
|
|
1119
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
|
1120
|
+
|
996
1121
|
let baseGlobalIdx, i = 0;
|
997
1122
|
for (const type of globalTypes) {
|
998
1123
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -1015,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
1015
1140
|
params,
|
1016
1141
|
locals,
|
1017
1142
|
returns,
|
1018
|
-
returnType:
|
1143
|
+
returnType: returnType ?? TYPES.number,
|
1019
1144
|
wasm,
|
1020
1145
|
internal: true,
|
1021
1146
|
index: currentFuncIndex++
|
@@ -1038,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
|
|
1038
1163
|
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
1039
1164
|
};
|
1040
1165
|
|
1166
|
+
// potential future ideas for nan boxing (unused):
|
1041
1167
|
// T = JS type, V = value/pointer
|
1042
1168
|
// 0bTTT
|
1043
1169
|
// qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
|
@@ -1061,40 +1187,18 @@ const generateLogicExp = (scope, decl) => {
|
|
1061
1187
|
// 4: internal type
|
1062
1188
|
// 5: pointer
|
1063
1189
|
|
1064
|
-
const
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
object: 0x04,
|
1070
|
-
function: 0x05,
|
1071
|
-
symbol: 0x06,
|
1072
|
-
bigint: 0x07,
|
1073
|
-
|
1074
|
-
// these are not "typeof" types but tracked internally
|
1075
|
-
_array: 0x10,
|
1076
|
-
_regexp: 0x11,
|
1077
|
-
_bytestring: 0x12
|
1078
|
-
};
|
1079
|
-
|
1080
|
-
const TYPE_NAMES = {
|
1081
|
-
[TYPES.number]: 'Number',
|
1082
|
-
[TYPES.boolean]: 'Boolean',
|
1083
|
-
[TYPES.string]: 'String',
|
1084
|
-
[TYPES.undefined]: 'undefined',
|
1085
|
-
[TYPES.object]: 'Object',
|
1086
|
-
[TYPES.function]: 'Function',
|
1087
|
-
[TYPES.symbol]: 'Symbol',
|
1088
|
-
[TYPES.bigint]: 'BigInt',
|
1089
|
-
|
1090
|
-
[TYPES._array]: 'Array',
|
1091
|
-
[TYPES._regexp]: 'RegExp',
|
1092
|
-
[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;
|
1093
1195
|
};
|
1094
1196
|
|
1095
1197
|
const getType = (scope, _name) => {
|
1096
1198
|
const name = mapName(_name);
|
1097
1199
|
|
1200
|
+
// if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
|
1201
|
+
|
1098
1202
|
if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
|
1099
1203
|
if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
|
1100
1204
|
|
@@ -1102,11 +1206,10 @@ const getType = (scope, _name) => {
|
|
1102
1206
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1103
1207
|
|
1104
1208
|
let type = TYPES.undefined;
|
1105
|
-
if (builtinVars[name]) type =
|
1209
|
+
if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
|
1106
1210
|
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
|
1107
1211
|
|
1108
|
-
if (name
|
1109
|
-
name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
|
1212
|
+
if (isExistingProtoFunc(name)) type = TYPES.function;
|
1110
1213
|
|
1111
1214
|
return number(type, Valtype.i32);
|
1112
1215
|
};
|
@@ -1129,15 +1232,16 @@ const setType = (scope, _name, type) => {
|
|
1129
1232
|
];
|
1130
1233
|
|
1131
1234
|
// throw new Error('could not find var');
|
1235
|
+
return [];
|
1132
1236
|
};
|
1133
1237
|
|
1134
1238
|
const getLastType = scope => {
|
1135
1239
|
scope.gotLastType = true;
|
1136
|
-
return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
|
1240
|
+
return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1137
1241
|
};
|
1138
1242
|
|
1139
1243
|
const setLastType = scope => {
|
1140
|
-
return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
|
1244
|
+
return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1141
1245
|
};
|
1142
1246
|
|
1143
1247
|
const getNodeType = (scope, node) => {
|
@@ -1162,13 +1266,25 @@ const getNodeType = (scope, node) => {
|
|
1162
1266
|
const name = node.callee.name;
|
1163
1267
|
if (!name) {
|
1164
1268
|
// iife
|
1165
|
-
if (scope.locals['#last_type']) return
|
1269
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1166
1270
|
|
1167
1271
|
// presume
|
1168
1272
|
// todo: warn here?
|
1169
1273
|
return TYPES.number;
|
1170
1274
|
}
|
1171
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
|
+
|
1172
1288
|
const func = funcs.find(x => x.name === name);
|
1173
1289
|
|
1174
1290
|
if (func) {
|
@@ -1176,7 +1292,7 @@ const getNodeType = (scope, node) => {
|
|
1176
1292
|
if (func.returnType) return func.returnType;
|
1177
1293
|
}
|
1178
1294
|
|
1179
|
-
if (builtinFuncs[name]) return
|
1295
|
+
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
1180
1296
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1181
1297
|
|
1182
1298
|
// check if this is a prototype function
|
@@ -1191,7 +1307,12 @@ const getNodeType = (scope, node) => {
|
|
1191
1307
|
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1192
1308
|
}
|
1193
1309
|
|
1194
|
-
if (
|
1310
|
+
if (name.startsWith('__Porffor_wasm_')) {
|
1311
|
+
// todo: return undefined for non-returning ops
|
1312
|
+
return TYPES.number;
|
1313
|
+
}
|
1314
|
+
|
1315
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1195
1316
|
|
1196
1317
|
// presume
|
1197
1318
|
// todo: warn here?
|
@@ -1246,6 +1367,7 @@ const getNodeType = (scope, node) => {
|
|
1246
1367
|
|
1247
1368
|
// todo: this should be dynamic but for now only static
|
1248
1369
|
if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
|
1370
|
+
if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) return TYPES._bytestring;
|
1249
1371
|
|
1250
1372
|
return TYPES.number;
|
1251
1373
|
|
@@ -1282,15 +1404,21 @@ const getNodeType = (scope, node) => {
|
|
1282
1404
|
|
1283
1405
|
// ts hack
|
1284
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;
|
1285
1408
|
if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
|
1286
1409
|
|
1287
|
-
if (scope.locals['#last_type']) return
|
1410
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1288
1411
|
|
1289
1412
|
// presume
|
1290
1413
|
return TYPES.number;
|
1291
1414
|
}
|
1292
1415
|
|
1293
|
-
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);
|
1294
1422
|
|
1295
1423
|
// presume
|
1296
1424
|
// todo: warn here?
|
@@ -1323,7 +1451,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1323
1451
|
return makeString(scope, decl.value, global, name);
|
1324
1452
|
|
1325
1453
|
default:
|
1326
|
-
return todo(`cannot generate literal of type ${typeof decl.value}
|
1454
|
+
return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
|
1327
1455
|
}
|
1328
1456
|
};
|
1329
1457
|
|
@@ -1332,6 +1460,8 @@ const countLeftover = wasm => {
|
|
1332
1460
|
|
1333
1461
|
for (let i = 0; i < wasm.length; i++) {
|
1334
1462
|
const inst = wasm[i];
|
1463
|
+
if (inst[0] == null) continue;
|
1464
|
+
|
1335
1465
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
1336
1466
|
if (inst[0] === Opcodes.if) count--;
|
1337
1467
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1340,18 +1470,25 @@ const countLeftover = wasm => {
|
|
1340
1470
|
if (inst[0] === Opcodes.end) depth--;
|
1341
1471
|
|
1342
1472
|
if (depth === 0)
|
1343
|
-
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1473
|
+
if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1344
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)) {}
|
1345
|
-
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++;
|
1346
1476
|
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1347
1477
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1348
1478
|
else if (inst[0] === Opcodes.return) count = 0;
|
1349
1479
|
else if (inst[0] === Opcodes.call) {
|
1350
1480
|
let func = funcs.find(x => x.index === inst[1]);
|
1351
|
-
if (
|
1352
|
-
count
|
1353
|
-
} else
|
1354
|
-
|
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
|
+
}
|
1355
1492
|
} else count--;
|
1356
1493
|
|
1357
1494
|
// console.log(count, decompile([ inst ]).slice(0, -1));
|
@@ -1443,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1443
1580
|
name = func.name;
|
1444
1581
|
}
|
1445
1582
|
|
1446
|
-
if (name === 'eval' && decl.arguments[0]
|
1583
|
+
if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
|
1447
1584
|
// literal eval hack
|
1448
|
-
const code = decl.arguments[0]
|
1449
|
-
|
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
|
+
}
|
1450
1598
|
|
1451
1599
|
const out = generate(scope, {
|
1452
1600
|
type: 'BlockStatement',
|
@@ -1460,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1460
1608
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1461
1609
|
out.push(
|
1462
1610
|
...getNodeType(scope, finalStatement),
|
1463
|
-
setLastType(scope)
|
1611
|
+
...setLastType(scope)
|
1464
1612
|
);
|
1465
1613
|
} else if (countLeftover(out) === 0) {
|
1466
1614
|
out.push(...number(UNDEFINED));
|
1467
1615
|
out.push(
|
1468
1616
|
...number(TYPES.undefined, Valtype.i32),
|
1469
|
-
setLastType(scope)
|
1617
|
+
...setLastType(scope)
|
1470
1618
|
);
|
1471
1619
|
}
|
1472
1620
|
|
@@ -1488,6 +1636,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1488
1636
|
|
1489
1637
|
target = { ...decl.callee };
|
1490
1638
|
target.name = spl.slice(0, -1).join('_');
|
1639
|
+
|
1640
|
+
// failed to lookup name, abort
|
1641
|
+
if (!lookupName(scope, target.name)[0]) protoName = null;
|
1491
1642
|
}
|
1492
1643
|
|
1493
1644
|
// literal.func()
|
@@ -1510,7 +1661,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1510
1661
|
Opcodes.i32_from_u,
|
1511
1662
|
|
1512
1663
|
...number(TYPES.boolean, Valtype.i32),
|
1513
|
-
setLastType(scope)
|
1664
|
+
...setLastType(scope)
|
1514
1665
|
];
|
1515
1666
|
}
|
1516
1667
|
|
@@ -1535,12 +1686,30 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1535
1686
|
// }
|
1536
1687
|
|
1537
1688
|
if (protoName) {
|
1689
|
+
const protoBC = {};
|
1690
|
+
|
1691
|
+
const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
|
1692
|
+
|
1693
|
+
if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
|
1694
|
+
for (const x of builtinProtoCands) {
|
1695
|
+
const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
|
1696
|
+
if (type == null) continue;
|
1697
|
+
|
1698
|
+
protoBC[type] = generateCall(scope, {
|
1699
|
+
callee: {
|
1700
|
+
name: x
|
1701
|
+
},
|
1702
|
+
arguments: [ target, ...decl.arguments ],
|
1703
|
+
_protoInternalCall: true
|
1704
|
+
});
|
1705
|
+
}
|
1706
|
+
}
|
1707
|
+
|
1538
1708
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1539
1709
|
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1540
1710
|
return acc;
|
1541
1711
|
}, {});
|
1542
1712
|
|
1543
|
-
// no prototype function candidates, ignore
|
1544
1713
|
if (Object.keys(protoCands).length > 0) {
|
1545
1714
|
// use local for cached i32 length as commonly used
|
1546
1715
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
@@ -1558,7 +1727,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1558
1727
|
|
1559
1728
|
let allOptUnused = true;
|
1560
1729
|
let lengthI32CacheUsed = false;
|
1561
|
-
const protoBC = {};
|
1562
1730
|
for (const x in protoCands) {
|
1563
1731
|
const protoFunc = protoCands[x];
|
1564
1732
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
@@ -1566,7 +1734,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1566
1734
|
...RTArrayUtil.getLength(getPointer),
|
1567
1735
|
|
1568
1736
|
...number(TYPES.number, Valtype.i32),
|
1569
|
-
setLastType(scope)
|
1737
|
+
...setLastType(scope)
|
1570
1738
|
];
|
1571
1739
|
continue;
|
1572
1740
|
}
|
@@ -1603,7 +1771,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1603
1771
|
...protoOut,
|
1604
1772
|
|
1605
1773
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1606
|
-
setLastType(scope),
|
1774
|
+
...setLastType(scope),
|
1607
1775
|
[ Opcodes.end ]
|
1608
1776
|
];
|
1609
1777
|
}
|
@@ -1629,10 +1797,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1629
1797
|
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1630
1798
|
];
|
1631
1799
|
}
|
1800
|
+
|
1801
|
+
if (Object.keys(protoBC).length > 0) {
|
1802
|
+
return typeSwitch(scope, getNodeType(scope, target), {
|
1803
|
+
...protoBC,
|
1804
|
+
|
1805
|
+
// TODO: error better
|
1806
|
+
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1807
|
+
}, valtypeBinary);
|
1808
|
+
}
|
1632
1809
|
}
|
1633
1810
|
|
1634
1811
|
// TODO: only allows callee as literal
|
1635
|
-
if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
|
1812
|
+
if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
|
1636
1813
|
|
1637
1814
|
let idx = funcIndex[name] ?? importedFuncs[name];
|
1638
1815
|
if (idx === undefined && builtinFuncs[name]) {
|
@@ -1665,16 +1842,65 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1665
1842
|
idx = -1;
|
1666
1843
|
}
|
1667
1844
|
|
1845
|
+
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1846
|
+
const wasmOps = {
|
1847
|
+
// pointer, align, offset
|
1848
|
+
i32_load: { imms: 2, args: [ true ], returns: 1 },
|
1849
|
+
// pointer, value, align, offset
|
1850
|
+
i32_store: { imms: 2, args: [ true, true ], returns: 0 },
|
1851
|
+
// pointer, align, offset
|
1852
|
+
i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
|
1853
|
+
// pointer, value, align, offset
|
1854
|
+
i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
|
1855
|
+
// pointer, align, offset
|
1856
|
+
i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
|
1857
|
+
// pointer, value, align, offset
|
1858
|
+
i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
|
1859
|
+
|
1860
|
+
// pointer, align, offset
|
1861
|
+
f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
|
1862
|
+
// pointer, value, align, offset
|
1863
|
+
f64_store: { imms: 2, args: [ true, false ], returns: 0 },
|
1864
|
+
|
1865
|
+
// value
|
1866
|
+
i32_const: { imms: 1, args: [], returns: 1 },
|
1867
|
+
|
1868
|
+
// a, b
|
1869
|
+
i32_or: { imms: 0, args: [ true, true ], returns: 1 },
|
1870
|
+
};
|
1871
|
+
|
1872
|
+
const opName = name.slice('__Porffor_wasm_'.length);
|
1873
|
+
|
1874
|
+
if (wasmOps[opName]) {
|
1875
|
+
const op = wasmOps[opName];
|
1876
|
+
|
1877
|
+
const argOut = [];
|
1878
|
+
for (let i = 0; i < op.args.length; i++) argOut.push(
|
1879
|
+
...generate(scope, decl.arguments[i]),
|
1880
|
+
...(op.args[i] ? [ Opcodes.i32_to ] : [])
|
1881
|
+
);
|
1882
|
+
|
1883
|
+
// literals only
|
1884
|
+
const imms = decl.arguments.slice(op.args.length).map(x => x.value);
|
1885
|
+
|
1886
|
+
return [
|
1887
|
+
...argOut,
|
1888
|
+
[ Opcodes[opName], ...imms ],
|
1889
|
+
...(new Array(op.returns).fill(Opcodes.i32_from))
|
1890
|
+
];
|
1891
|
+
}
|
1892
|
+
}
|
1893
|
+
|
1668
1894
|
if (idx === undefined) {
|
1669
|
-
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function
|
1670
|
-
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined
|
1895
|
+
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1896
|
+
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
1671
1897
|
}
|
1672
1898
|
|
1673
1899
|
const func = funcs.find(x => x.index === idx);
|
1674
1900
|
|
1675
1901
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1676
1902
|
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1677
|
-
const typedReturns = userFunc || builtinFuncs[name]?.typedReturns;
|
1903
|
+
const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
|
1678
1904
|
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1679
1905
|
|
1680
1906
|
let args = decl.arguments;
|
@@ -1691,8 +1917,18 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1691
1917
|
if (func && func.throws) scope.throws = true;
|
1692
1918
|
|
1693
1919
|
let out = [];
|
1694
|
-
for (
|
1920
|
+
for (let i = 0; i < args.length; i++) {
|
1921
|
+
const arg = args[i];
|
1695
1922
|
out = out.concat(generate(scope, arg));
|
1923
|
+
|
1924
|
+
if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1925
|
+
out.push(Opcodes.i32_to);
|
1926
|
+
}
|
1927
|
+
|
1928
|
+
if (importedFuncs[name] && name.startsWith('profile')) {
|
1929
|
+
out.push(Opcodes.i32_to);
|
1930
|
+
}
|
1931
|
+
|
1696
1932
|
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1697
1933
|
}
|
1698
1934
|
|
@@ -1708,7 +1944,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1708
1944
|
// ...number(type, Valtype.i32),
|
1709
1945
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1710
1946
|
// );
|
1711
|
-
} else out.push(setLastType(scope));
|
1947
|
+
} else out.push(...setLastType(scope));
|
1948
|
+
|
1949
|
+
if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1950
|
+
out.push(Opcodes.i32_from);
|
1951
|
+
}
|
1712
1952
|
|
1713
1953
|
return out;
|
1714
1954
|
};
|
@@ -1716,8 +1956,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1716
1956
|
const generateNew = (scope, decl, _global, _name) => {
|
1717
1957
|
// hack: basically treat this as a normal call for builtins for now
|
1718
1958
|
const name = mapName(decl.callee.name);
|
1959
|
+
|
1719
1960
|
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1720
|
-
|
1961
|
+
|
1962
|
+
if (builtinFuncs[name + '$constructor']) {
|
1963
|
+
// custom ...$constructor override builtin func
|
1964
|
+
return generateCall(scope, {
|
1965
|
+
...decl,
|
1966
|
+
callee: {
|
1967
|
+
type: 'Identifier',
|
1968
|
+
name: name + '$constructor'
|
1969
|
+
}
|
1970
|
+
}, _global, _name);
|
1971
|
+
}
|
1972
|
+
|
1973
|
+
if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
|
1721
1974
|
|
1722
1975
|
return generateCall(scope, decl, _global, _name);
|
1723
1976
|
};
|
@@ -1851,8 +2104,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1851
2104
|
[ Opcodes.block, returns ]
|
1852
2105
|
];
|
1853
2106
|
|
1854
|
-
// todo: use br_table?
|
1855
|
-
|
1856
2107
|
for (const x in bc) {
|
1857
2108
|
if (x === 'default') continue;
|
1858
2109
|
|
@@ -1908,11 +2159,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
|
1908
2159
|
};
|
1909
2160
|
|
1910
2161
|
const typeAnnoToPorfType = x => {
|
1911
|
-
if (
|
1912
|
-
if (TYPES[
|
2162
|
+
if (!x) return null;
|
2163
|
+
if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
|
2164
|
+
if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
|
1913
2165
|
|
1914
2166
|
switch (x) {
|
1915
2167
|
case 'i32':
|
2168
|
+
case 'i64':
|
2169
|
+
case 'f64':
|
1916
2170
|
return TYPES.number;
|
1917
2171
|
}
|
1918
2172
|
|
@@ -1923,7 +2177,7 @@ const extractTypeAnnotation = decl => {
|
|
1923
2177
|
let a = decl;
|
1924
2178
|
while (a.typeAnnotation) a = a.typeAnnotation;
|
1925
2179
|
|
1926
|
-
let type, elementType;
|
2180
|
+
let type = null, elementType = null;
|
1927
2181
|
if (a.typeName) {
|
1928
2182
|
type = a.typeName.name;
|
1929
2183
|
} else if (a.type.endsWith('Keyword')) {
|
@@ -1954,7 +2208,7 @@ const generateVar = (scope, decl) => {
|
|
1954
2208
|
for (const x of decl.declarations) {
|
1955
2209
|
const name = mapName(x.id.name);
|
1956
2210
|
|
1957
|
-
if (!name) return todo('destructuring is not supported yet');
|
2211
|
+
if (!name) return todo(scope, 'destructuring is not supported yet');
|
1958
2212
|
|
1959
2213
|
if (x.init && isFuncType(x.init.type)) {
|
1960
2214
|
// hack for let a = function () { ... }
|
@@ -1971,9 +2225,10 @@ const generateVar = (scope, decl) => {
|
|
1971
2225
|
continue; // always ignore
|
1972
2226
|
}
|
1973
2227
|
|
1974
|
-
|
2228
|
+
const typed = typedInput && x.id.typeAnnotation;
|
2229
|
+
let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
|
1975
2230
|
|
1976
|
-
if (
|
2231
|
+
if (typed) {
|
1977
2232
|
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1978
2233
|
}
|
1979
2234
|
|
@@ -1991,7 +2246,8 @@ const generateVar = (scope, decl) => {
|
|
1991
2246
|
return out;
|
1992
2247
|
};
|
1993
2248
|
|
1994
|
-
|
2249
|
+
// todo: optimize this func for valueUnused
|
2250
|
+
const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
1995
2251
|
const { type, name } = decl.left;
|
1996
2252
|
|
1997
2253
|
if (type === 'ObjectPattern') {
|
@@ -2009,9 +2265,9 @@ const generateAssign = (scope, decl) => {
|
|
2009
2265
|
// hack: .length setter
|
2010
2266
|
if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
|
2011
2267
|
const name = decl.left.object.name;
|
2012
|
-
const pointer = arrays
|
2268
|
+
const pointer = scope.arrays?.get(name);
|
2013
2269
|
|
2014
|
-
const aotPointer = pointer != null;
|
2270
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2015
2271
|
|
2016
2272
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
2017
2273
|
|
@@ -2036,9 +2292,9 @@ const generateAssign = (scope, decl) => {
|
|
2036
2292
|
// arr[i]
|
2037
2293
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
2038
2294
|
const name = decl.left.object.name;
|
2039
|
-
const pointer = arrays
|
2295
|
+
const pointer = scope.arrays?.get(name);
|
2040
2296
|
|
2041
|
-
const aotPointer = pointer != null;
|
2297
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2042
2298
|
|
2043
2299
|
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
2044
2300
|
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
@@ -2094,7 +2350,7 @@ const generateAssign = (scope, decl) => {
|
|
2094
2350
|
];
|
2095
2351
|
}
|
2096
2352
|
|
2097
|
-
if (!name) return todo('destructuring is not supported yet');
|
2353
|
+
if (!name) return todo(scope, 'destructuring is not supported yet', true);
|
2098
2354
|
|
2099
2355
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2100
2356
|
|
@@ -2142,9 +2398,7 @@ const generateAssign = (scope, decl) => {
|
|
2142
2398
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
2143
2399
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
2144
2400
|
|
2145
|
-
getLastType(scope)
|
2146
|
-
// hack: type is idx+1
|
2147
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2401
|
+
...setType(scope, name, getLastType(scope))
|
2148
2402
|
];
|
2149
2403
|
}
|
2150
2404
|
|
@@ -2155,9 +2409,7 @@ const generateAssign = (scope, decl) => {
|
|
2155
2409
|
|
2156
2410
|
// todo: string concat types
|
2157
2411
|
|
2158
|
-
|
2159
|
-
...number(TYPES.number, Valtype.i32),
|
2160
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2412
|
+
...setType(scope, name, TYPES.number)
|
2161
2413
|
];
|
2162
2414
|
};
|
2163
2415
|
|
@@ -2203,7 +2455,7 @@ const generateUnary = (scope, decl) => {
|
|
2203
2455
|
return out;
|
2204
2456
|
}
|
2205
2457
|
|
2206
|
-
case 'delete':
|
2458
|
+
case 'delete': {
|
2207
2459
|
let toReturn = true, toGenerate = true;
|
2208
2460
|
|
2209
2461
|
if (decl.argument.type === 'Identifier') {
|
@@ -2225,9 +2477,26 @@ const generateUnary = (scope, decl) => {
|
|
2225
2477
|
|
2226
2478
|
out.push(...number(toReturn ? 1 : 0));
|
2227
2479
|
return out;
|
2480
|
+
}
|
2481
|
+
|
2482
|
+
case 'typeof': {
|
2483
|
+
let overrideType, toGenerate = true;
|
2228
2484
|
|
2229
|
-
|
2230
|
-
|
2485
|
+
if (decl.argument.type === 'Identifier') {
|
2486
|
+
const out = generateIdent(scope, decl.argument);
|
2487
|
+
|
2488
|
+
// if ReferenceError (undeclared var), ignore and return undefined
|
2489
|
+
if (out[1]) {
|
2490
|
+
// does not exist (2 ops from throw)
|
2491
|
+
overrideType = number(TYPES.undefined, Valtype.i32);
|
2492
|
+
toGenerate = false;
|
2493
|
+
}
|
2494
|
+
}
|
2495
|
+
|
2496
|
+
const out = toGenerate ? generate(scope, decl.argument) : [];
|
2497
|
+
disposeLeftover(out);
|
2498
|
+
|
2499
|
+
out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
|
2231
2500
|
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
2232
2501
|
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
2233
2502
|
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
@@ -2238,27 +2507,30 @@ const generateUnary = (scope, decl) => {
|
|
2238
2507
|
|
2239
2508
|
// object and internal types
|
2240
2509
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2241
|
-
});
|
2510
|
+
}));
|
2511
|
+
|
2512
|
+
return out;
|
2513
|
+
}
|
2242
2514
|
|
2243
2515
|
default:
|
2244
|
-
return todo(`unary operator ${decl.operator} not implemented yet
|
2516
|
+
return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
|
2245
2517
|
}
|
2246
2518
|
};
|
2247
2519
|
|
2248
|
-
const generateUpdate = (scope, decl) => {
|
2520
|
+
const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
|
2249
2521
|
const { name } = decl.argument;
|
2250
2522
|
|
2251
2523
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2252
2524
|
|
2253
2525
|
if (local === undefined) {
|
2254
|
-
return todo(`update expression with undefined variable
|
2526
|
+
return todo(scope, `update expression with undefined variable`, true);
|
2255
2527
|
}
|
2256
2528
|
|
2257
2529
|
const idx = local.idx;
|
2258
2530
|
const out = [];
|
2259
2531
|
|
2260
2532
|
out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2261
|
-
if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2533
|
+
if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2262
2534
|
|
2263
2535
|
switch (decl.operator) {
|
2264
2536
|
case '++':
|
@@ -2271,7 +2543,7 @@ const generateUpdate = (scope, decl) => {
|
|
2271
2543
|
}
|
2272
2544
|
|
2273
2545
|
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2274
|
-
if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2546
|
+
if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2275
2547
|
|
2276
2548
|
return out;
|
2277
2549
|
};
|
@@ -2311,7 +2583,7 @@ const generateConditional = (scope, decl) => {
|
|
2311
2583
|
// note type
|
2312
2584
|
out.push(
|
2313
2585
|
...getNodeType(scope, decl.consequent),
|
2314
|
-
setLastType(scope)
|
2586
|
+
...setLastType(scope)
|
2315
2587
|
);
|
2316
2588
|
|
2317
2589
|
out.push([ Opcodes.else ]);
|
@@ -2320,7 +2592,7 @@ const generateConditional = (scope, decl) => {
|
|
2320
2592
|
// note type
|
2321
2593
|
out.push(
|
2322
2594
|
...getNodeType(scope, decl.alternate),
|
2323
|
-
setLastType(scope)
|
2595
|
+
...setLastType(scope)
|
2324
2596
|
);
|
2325
2597
|
|
2326
2598
|
out.push([ Opcodes.end ]);
|
@@ -2334,7 +2606,7 @@ const generateFor = (scope, decl) => {
|
|
2334
2606
|
const out = [];
|
2335
2607
|
|
2336
2608
|
if (decl.init) {
|
2337
|
-
out.push(...generate(scope, decl.init));
|
2609
|
+
out.push(...generate(scope, decl.init, false, undefined, true));
|
2338
2610
|
disposeLeftover(out);
|
2339
2611
|
}
|
2340
2612
|
|
@@ -2352,7 +2624,7 @@ const generateFor = (scope, decl) => {
|
|
2352
2624
|
out.push(...generate(scope, decl.body));
|
2353
2625
|
out.push([ Opcodes.end ]);
|
2354
2626
|
|
2355
|
-
if (decl.update) out.push(...generate(scope, decl.update));
|
2627
|
+
if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
|
2356
2628
|
|
2357
2629
|
out.push([ Opcodes.br, 1 ]);
|
2358
2630
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2380,6 +2652,36 @@ const generateWhile = (scope, decl) => {
|
|
2380
2652
|
return out;
|
2381
2653
|
};
|
2382
2654
|
|
2655
|
+
const generateDoWhile = (scope, decl) => {
|
2656
|
+
const out = [];
|
2657
|
+
|
2658
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
2659
|
+
depth.push('dowhile');
|
2660
|
+
|
2661
|
+
// block for break (includes all)
|
2662
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2663
|
+
depth.push('block');
|
2664
|
+
|
2665
|
+
// block for continue
|
2666
|
+
// includes body but not test+loop so we can exit body at anytime
|
2667
|
+
// and still test+loop after
|
2668
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2669
|
+
depth.push('block');
|
2670
|
+
|
2671
|
+
out.push(...generate(scope, decl.body));
|
2672
|
+
|
2673
|
+
out.push([ Opcodes.end ]);
|
2674
|
+
depth.pop();
|
2675
|
+
|
2676
|
+
out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2677
|
+
out.push([ Opcodes.br_if, 1 ]);
|
2678
|
+
|
2679
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
2680
|
+
depth.pop(); depth.pop();
|
2681
|
+
|
2682
|
+
return out;
|
2683
|
+
};
|
2684
|
+
|
2383
2685
|
const generateForOf = (scope, decl) => {
|
2384
2686
|
const out = [];
|
2385
2687
|
|
@@ -2416,7 +2718,10 @@ const generateForOf = (scope, decl) => {
|
|
2416
2718
|
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2417
2719
|
}
|
2418
2720
|
|
2721
|
+
// if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
|
2722
|
+
|
2419
2723
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2724
|
+
if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
|
2420
2725
|
|
2421
2726
|
depth.push('block');
|
2422
2727
|
depth.push('block');
|
@@ -2425,6 +2730,7 @@ const generateForOf = (scope, decl) => {
|
|
2425
2730
|
// hack: this is naughty and will break things!
|
2426
2731
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2427
2732
|
if (pages.hasAnyString) {
|
2733
|
+
// todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
|
2428
2734
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2429
2735
|
rawElements: new Array(1)
|
2430
2736
|
}, isGlobal, leftName, true, 'i16');
|
@@ -2516,6 +2822,56 @@ const generateForOf = (scope, decl) => {
|
|
2516
2822
|
[ Opcodes.end ],
|
2517
2823
|
[ Opcodes.end ]
|
2518
2824
|
],
|
2825
|
+
[TYPES._bytestring]: [
|
2826
|
+
...setType(scope, leftName, TYPES._bytestring),
|
2827
|
+
|
2828
|
+
[ Opcodes.loop, Blocktype.void ],
|
2829
|
+
|
2830
|
+
// setup new/out array
|
2831
|
+
...newOut,
|
2832
|
+
[ Opcodes.drop ],
|
2833
|
+
|
2834
|
+
...number(0, Valtype.i32), // base 0 for store after
|
2835
|
+
|
2836
|
+
// load current string ind {arg}
|
2837
|
+
[ Opcodes.local_get, pointer ],
|
2838
|
+
[ Opcodes.local_get, counter ],
|
2839
|
+
[ Opcodes.i32_add ],
|
2840
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
2841
|
+
|
2842
|
+
// store to new string ind 0
|
2843
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2844
|
+
|
2845
|
+
// return new string (page)
|
2846
|
+
...number(newPointer),
|
2847
|
+
|
2848
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2849
|
+
|
2850
|
+
[ Opcodes.block, Blocktype.void ],
|
2851
|
+
[ Opcodes.block, Blocktype.void ],
|
2852
|
+
...generate(scope, decl.body),
|
2853
|
+
[ Opcodes.end ],
|
2854
|
+
|
2855
|
+
// increment iter pointer
|
2856
|
+
// [ Opcodes.local_get, pointer ],
|
2857
|
+
// ...number(1, Valtype.i32),
|
2858
|
+
// [ Opcodes.i32_add ],
|
2859
|
+
// [ Opcodes.local_set, pointer ],
|
2860
|
+
|
2861
|
+
// increment counter by 1
|
2862
|
+
[ Opcodes.local_get, counter ],
|
2863
|
+
...number(1, Valtype.i32),
|
2864
|
+
[ Opcodes.i32_add ],
|
2865
|
+
[ Opcodes.local_tee, counter ],
|
2866
|
+
|
2867
|
+
// loop if counter != length
|
2868
|
+
[ Opcodes.local_get, length ],
|
2869
|
+
[ Opcodes.i32_ne ],
|
2870
|
+
[ Opcodes.br_if, 1 ],
|
2871
|
+
|
2872
|
+
[ Opcodes.end ],
|
2873
|
+
[ Opcodes.end ]
|
2874
|
+
],
|
2519
2875
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2520
2876
|
}, Blocktype.void));
|
2521
2877
|
|
@@ -2526,28 +2882,65 @@ const generateForOf = (scope, decl) => {
|
|
2526
2882
|
return out;
|
2527
2883
|
};
|
2528
2884
|
|
2885
|
+
// find the nearest loop in depth map by type
|
2529
2886
|
const getNearestLoop = () => {
|
2530
2887
|
for (let i = depth.length - 1; i >= 0; i--) {
|
2531
|
-
if (
|
2888
|
+
if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
|
2532
2889
|
}
|
2533
2890
|
|
2534
2891
|
return -1;
|
2535
2892
|
};
|
2536
2893
|
|
2537
2894
|
const generateBreak = (scope, decl) => {
|
2538
|
-
const
|
2895
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2896
|
+
const type = depth[target];
|
2897
|
+
|
2898
|
+
// different loop types have different branch offsets
|
2899
|
+
// as they have different wasm block/loop/if structures
|
2900
|
+
// we need to use the right offset by type to branch to the one we want
|
2901
|
+
// for a break: exit the loop without executing anything else inside it
|
2902
|
+
const offset = ({
|
2903
|
+
for: 2, // loop > if (wanted branch) > block (we are here)
|
2904
|
+
while: 2, // loop > if (wanted branch) (we are here)
|
2905
|
+
dowhile: 2, // loop > block (wanted branch) > block (we are here)
|
2906
|
+
forof: 2, // loop > block (wanted branch) > block (we are here)
|
2907
|
+
if: 1 // break inside if, branch 0 to skip the rest of the if
|
2908
|
+
})[type];
|
2909
|
+
|
2539
2910
|
return [
|
2540
|
-
[ Opcodes.br, ...signedLEB128(
|
2911
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2541
2912
|
];
|
2542
2913
|
};
|
2543
2914
|
|
2544
2915
|
const generateContinue = (scope, decl) => {
|
2545
|
-
const
|
2916
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2917
|
+
const type = depth[target];
|
2918
|
+
|
2919
|
+
// different loop types have different branch offsets
|
2920
|
+
// as they have different wasm block/loop/if structures
|
2921
|
+
// we need to use the right offset by type to branch to the one we want
|
2922
|
+
// for a continue: do test for the loop, and then loop depending on that success
|
2923
|
+
const offset = ({
|
2924
|
+
for: 3, // loop (wanted branch) > if > block (we are here)
|
2925
|
+
while: 1, // loop (wanted branch) > if (we are here)
|
2926
|
+
dowhile: 3, // loop > block > block (wanted branch) (we are here)
|
2927
|
+
forof: 3 // loop > block > block (wanted branch) (we are here)
|
2928
|
+
})[type];
|
2929
|
+
|
2546
2930
|
return [
|
2547
|
-
[ Opcodes.br, ...signedLEB128(
|
2931
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2548
2932
|
];
|
2549
2933
|
};
|
2550
2934
|
|
2935
|
+
const generateLabel = (scope, decl) => {
|
2936
|
+
scope.labels ??= new Map();
|
2937
|
+
|
2938
|
+
const name = decl.label.name;
|
2939
|
+
scope.labels.set(name, depth.length);
|
2940
|
+
|
2941
|
+
return generate(scope, decl.body);
|
2942
|
+
};
|
2943
|
+
|
2551
2944
|
const generateThrow = (scope, decl) => {
|
2552
2945
|
scope.throws = true;
|
2553
2946
|
|
@@ -2580,7 +2973,7 @@ const generateThrow = (scope, decl) => {
|
|
2580
2973
|
};
|
2581
2974
|
|
2582
2975
|
const generateTry = (scope, decl) => {
|
2583
|
-
if (decl.finalizer) return todo('try finally not implemented yet');
|
2976
|
+
if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
|
2584
2977
|
|
2585
2978
|
const out = [];
|
2586
2979
|
|
@@ -2611,7 +3004,7 @@ const generateAssignPat = (scope, decl) => {
|
|
2611
3004
|
// TODO
|
2612
3005
|
// if identifier declared, use that
|
2613
3006
|
// else, use default (right)
|
2614
|
-
return todo('assignment pattern (optional arg)');
|
3007
|
+
return todo(scope, 'assignment pattern (optional arg)');
|
2615
3008
|
};
|
2616
3009
|
|
2617
3010
|
let pages = new Map();
|
@@ -2690,16 +3083,20 @@ const getAllocType = itemType => {
|
|
2690
3083
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2691
3084
|
const out = [];
|
2692
3085
|
|
3086
|
+
scope.arrays ??= new Map();
|
3087
|
+
|
2693
3088
|
let firstAssign = false;
|
2694
|
-
if (!arrays.has(name) || name === '$undeclared') {
|
3089
|
+
if (!scope.arrays.has(name) || name === '$undeclared') {
|
2695
3090
|
firstAssign = true;
|
2696
3091
|
|
2697
3092
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2698
3093
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2699
|
-
|
3094
|
+
|
3095
|
+
if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${scope.name} | ${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
3096
|
+
else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2700
3097
|
}
|
2701
3098
|
|
2702
|
-
const pointer = arrays.get(name);
|
3099
|
+
const pointer = scope.arrays.get(name);
|
2703
3100
|
|
2704
3101
|
const useRawElements = !!decl.rawElements;
|
2705
3102
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
@@ -2707,22 +3104,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2707
3104
|
const valtype = itemTypeToValtype[itemType];
|
2708
3105
|
const length = elements.length;
|
2709
3106
|
|
2710
|
-
if (firstAssign && useRawElements) {
|
2711
|
-
|
3107
|
+
if (firstAssign && useRawElements && !Prefs.noData) {
|
3108
|
+
// if length is 0 memory/data will just be 0000... anyway
|
3109
|
+
if (length !== 0) {
|
3110
|
+
let bytes = compileBytes(length, 'i32');
|
2712
3111
|
|
2713
|
-
|
2714
|
-
|
3112
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
3113
|
+
if (elements[i] == null) continue;
|
2715
3114
|
|
2716
|
-
|
2717
|
-
|
3115
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
3116
|
+
}
|
2718
3117
|
|
2719
|
-
|
2720
|
-
|
2721
|
-
|
2722
|
-
|
3118
|
+
const ind = data.push({
|
3119
|
+
offset: pointer,
|
3120
|
+
bytes
|
3121
|
+
}) - 1;
|
2723
3122
|
|
2724
|
-
|
2725
|
-
|
3123
|
+
scope.data ??= [];
|
3124
|
+
scope.data.push(ind);
|
3125
|
+
}
|
2726
3126
|
|
2727
3127
|
// local value as pointer
|
2728
3128
|
out.push(...number(pointer));
|
@@ -2782,20 +3182,29 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
|
|
2782
3182
|
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2783
3183
|
};
|
2784
3184
|
|
2785
|
-
let arrays = new Map();
|
2786
3185
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
2787
3186
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
2788
3187
|
};
|
2789
3188
|
|
2790
3189
|
export const generateMember = (scope, decl, _global, _name) => {
|
2791
3190
|
const name = decl.object.name;
|
2792
|
-
const pointer = arrays
|
3191
|
+
const pointer = scope.arrays?.get(name);
|
2793
3192
|
|
2794
|
-
const aotPointer = pointer != null;
|
3193
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2795
3194
|
|
2796
3195
|
// hack: .length
|
2797
3196
|
if (decl.property.name === 'length') {
|
2798
|
-
|
3197
|
+
const func = funcs.find(x => x.name === name);
|
3198
|
+
if (func) {
|
3199
|
+
const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
|
3200
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
3201
|
+
return number(typedParams ? func.params.length / 2 : func.params.length);
|
3202
|
+
}
|
3203
|
+
|
3204
|
+
if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
|
3205
|
+
if (importedFuncs[name]) return number(importedFuncs[name].params);
|
3206
|
+
if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
|
3207
|
+
|
2799
3208
|
return [
|
2800
3209
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2801
3210
|
...generate(scope, decl.object),
|
@@ -2839,7 +3248,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2839
3248
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2840
3249
|
|
2841
3250
|
...number(TYPES.number, Valtype.i32),
|
2842
|
-
setLastType(scope)
|
3251
|
+
...setLastType(scope)
|
2843
3252
|
],
|
2844
3253
|
|
2845
3254
|
[TYPES.string]: [
|
@@ -2871,7 +3280,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2871
3280
|
...number(newPointer),
|
2872
3281
|
|
2873
3282
|
...number(TYPES.string, Valtype.i32),
|
2874
|
-
setLastType(scope)
|
3283
|
+
...setLastType(scope)
|
2875
3284
|
],
|
2876
3285
|
[TYPES._bytestring]: [
|
2877
3286
|
// setup new/out array
|
@@ -2890,19 +3299,19 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2890
3299
|
]),
|
2891
3300
|
|
2892
3301
|
// load current string ind {arg}
|
2893
|
-
[ Opcodes.i32_load8_u,
|
3302
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2894
3303
|
|
2895
3304
|
// store to new string ind 0
|
2896
|
-
[ Opcodes.i32_store8,
|
3305
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2897
3306
|
|
2898
3307
|
// return new string (page)
|
2899
3308
|
...number(newPointer),
|
2900
3309
|
|
2901
3310
|
...number(TYPES._bytestring, Valtype.i32),
|
2902
|
-
setLastType(scope)
|
3311
|
+
...setLastType(scope)
|
2903
3312
|
],
|
2904
3313
|
|
2905
|
-
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
|
3314
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
|
2906
3315
|
});
|
2907
3316
|
};
|
2908
3317
|
|
@@ -2912,28 +3321,36 @@ const objectHack = node => {
|
|
2912
3321
|
if (!node) return node;
|
2913
3322
|
|
2914
3323
|
if (node.type === 'MemberExpression') {
|
2915
|
-
|
3324
|
+
const out = (() => {
|
3325
|
+
if (node.computed || node.optional) return;
|
2916
3326
|
|
2917
|
-
|
3327
|
+
let objectName = node.object.name;
|
2918
3328
|
|
2919
|
-
|
2920
|
-
|
3329
|
+
// if object is not identifier or another member exp, give up
|
3330
|
+
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
|
3331
|
+
if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
|
2921
3332
|
|
2922
|
-
|
3333
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2923
3334
|
|
2924
|
-
|
2925
|
-
|
3335
|
+
// if .length, give up (hack within a hack!)
|
3336
|
+
if (node.property.name === 'length') {
|
3337
|
+
node.object = objectHack(node.object);
|
3338
|
+
return;
|
3339
|
+
}
|
2926
3340
|
|
2927
|
-
|
2928
|
-
|
3341
|
+
// no object name, give up
|
3342
|
+
if (!objectName) return;
|
2929
3343
|
|
2930
|
-
|
2931
|
-
|
3344
|
+
const name = '__' + objectName + '_' + node.property.name;
|
3345
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2932
3346
|
|
2933
|
-
|
2934
|
-
|
2935
|
-
|
2936
|
-
|
3347
|
+
return {
|
3348
|
+
type: 'Identifier',
|
3349
|
+
name
|
3350
|
+
};
|
3351
|
+
})();
|
3352
|
+
|
3353
|
+
if (out) return out;
|
2937
3354
|
}
|
2938
3355
|
|
2939
3356
|
for (const x in node) {
|
@@ -2947,8 +3364,8 @@ const objectHack = node => {
|
|
2947
3364
|
};
|
2948
3365
|
|
2949
3366
|
const generateFunc = (scope, decl) => {
|
2950
|
-
if (decl.async) return todo('async functions are not supported');
|
2951
|
-
if (decl.generator) return todo('generator functions are not supported');
|
3367
|
+
if (decl.async) return todo(scope, 'async functions are not supported');
|
3368
|
+
if (decl.generator) return todo(scope, 'generator functions are not supported');
|
2952
3369
|
|
2953
3370
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2954
3371
|
const params = decl.params ?? [];
|
@@ -2964,6 +3381,11 @@ const generateFunc = (scope, decl) => {
|
|
2964
3381
|
name
|
2965
3382
|
};
|
2966
3383
|
|
3384
|
+
if (typedInput && decl.returnType) {
|
3385
|
+
innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
|
3386
|
+
innerScope.returns = [ valtypeBinary ];
|
3387
|
+
}
|
3388
|
+
|
2967
3389
|
for (let i = 0; i < params.length; i++) {
|
2968
3390
|
allocVar(innerScope, params[i].name, false);
|
2969
3391
|
|
@@ -2990,6 +3412,8 @@ const generateFunc = (scope, decl) => {
|
|
2990
3412
|
};
|
2991
3413
|
funcIndex[name] = func.index;
|
2992
3414
|
|
3415
|
+
if (name === 'main') func.gotLastType = true;
|
3416
|
+
|
2993
3417
|
// quick hack fixes
|
2994
3418
|
for (const inst of wasm) {
|
2995
3419
|
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
@@ -3024,16 +3448,6 @@ const generateCode = (scope, decl) => {
|
|
3024
3448
|
};
|
3025
3449
|
|
3026
3450
|
const internalConstrs = {
|
3027
|
-
Boolean: {
|
3028
|
-
generate: (scope, decl) => {
|
3029
|
-
if (decl.arguments.length === 0) return number(0);
|
3030
|
-
|
3031
|
-
// should generate/run all args
|
3032
|
-
return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
|
3033
|
-
},
|
3034
|
-
type: TYPES.boolean
|
3035
|
-
},
|
3036
|
-
|
3037
3451
|
Array: {
|
3038
3452
|
generate: (scope, decl, global, name) => {
|
3039
3453
|
// new Array(i0, i1, ...)
|
@@ -3051,7 +3465,7 @@ const internalConstrs = {
|
|
3051
3465
|
|
3052
3466
|
// todo: check in wasm instead of here
|
3053
3467
|
const literalValue = arg.value ?? 0;
|
3054
|
-
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
|
3468
|
+
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
|
3055
3469
|
|
3056
3470
|
return [
|
3057
3471
|
...number(0, Valtype.i32),
|
@@ -3062,7 +3476,8 @@ const internalConstrs = {
|
|
3062
3476
|
...number(pointer)
|
3063
3477
|
];
|
3064
3478
|
},
|
3065
|
-
type: TYPES._array
|
3479
|
+
type: TYPES._array,
|
3480
|
+
length: 1
|
3066
3481
|
},
|
3067
3482
|
|
3068
3483
|
__Array_of: {
|
@@ -3074,7 +3489,98 @@ const internalConstrs = {
|
|
3074
3489
|
}, global, name);
|
3075
3490
|
},
|
3076
3491
|
type: TYPES._array,
|
3492
|
+
notConstr: true,
|
3493
|
+
length: 0
|
3494
|
+
},
|
3495
|
+
|
3496
|
+
__Porffor_fastOr: {
|
3497
|
+
generate: (scope, decl) => {
|
3498
|
+
const out = [];
|
3499
|
+
|
3500
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3501
|
+
out.push(
|
3502
|
+
...generate(scope, decl.arguments[i]),
|
3503
|
+
Opcodes.i32_to_u,
|
3504
|
+
...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
|
3505
|
+
);
|
3506
|
+
}
|
3507
|
+
|
3508
|
+
out.push(Opcodes.i32_from_u);
|
3509
|
+
|
3510
|
+
return out;
|
3511
|
+
},
|
3512
|
+
type: TYPES.boolean,
|
3513
|
+
notConstr: true
|
3514
|
+
},
|
3515
|
+
|
3516
|
+
__Porffor_fastAnd: {
|
3517
|
+
generate: (scope, decl) => {
|
3518
|
+
const out = [];
|
3519
|
+
|
3520
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3521
|
+
out.push(
|
3522
|
+
...generate(scope, decl.arguments[i]),
|
3523
|
+
Opcodes.i32_to_u,
|
3524
|
+
...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
|
3525
|
+
);
|
3526
|
+
}
|
3527
|
+
|
3528
|
+
out.push(Opcodes.i32_from_u);
|
3529
|
+
|
3530
|
+
return out;
|
3531
|
+
},
|
3532
|
+
type: TYPES.boolean,
|
3077
3533
|
notConstr: true
|
3534
|
+
},
|
3535
|
+
|
3536
|
+
Boolean: {
|
3537
|
+
generate: (scope, decl) => {
|
3538
|
+
// todo: boolean object when used as constructor
|
3539
|
+
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
3540
|
+
return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
|
3541
|
+
},
|
3542
|
+
type: TYPES.boolean,
|
3543
|
+
length: 1
|
3544
|
+
},
|
3545
|
+
|
3546
|
+
__Math_max: {
|
3547
|
+
generate: (scope, decl) => {
|
3548
|
+
const out = [
|
3549
|
+
...number(-Infinity)
|
3550
|
+
];
|
3551
|
+
|
3552
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3553
|
+
out.push(
|
3554
|
+
...generate(scope, decl.arguments[i]),
|
3555
|
+
[ Opcodes.f64_max ]
|
3556
|
+
);
|
3557
|
+
}
|
3558
|
+
|
3559
|
+
return out;
|
3560
|
+
},
|
3561
|
+
type: TYPES.number,
|
3562
|
+
notConstr: true,
|
3563
|
+
length: 2
|
3564
|
+
},
|
3565
|
+
|
3566
|
+
__Math_min: {
|
3567
|
+
generate: (scope, decl) => {
|
3568
|
+
const out = [
|
3569
|
+
...number(Infinity)
|
3570
|
+
];
|
3571
|
+
|
3572
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3573
|
+
out.push(
|
3574
|
+
...generate(scope, decl.arguments[i]),
|
3575
|
+
[ Opcodes.f64_min ]
|
3576
|
+
);
|
3577
|
+
}
|
3578
|
+
|
3579
|
+
return out;
|
3580
|
+
},
|
3581
|
+
type: TYPES.number,
|
3582
|
+
notConstr: true,
|
3583
|
+
length: 2
|
3078
3584
|
}
|
3079
3585
|
};
|
3080
3586
|
|
@@ -3103,7 +3609,6 @@ export default program => {
|
|
3103
3609
|
funcs = [];
|
3104
3610
|
funcIndex = {};
|
3105
3611
|
depth = [];
|
3106
|
-
arrays = new Map();
|
3107
3612
|
pages = new Map();
|
3108
3613
|
data = [];
|
3109
3614
|
currentFuncIndex = importedFuncs.length;
|
@@ -3117,6 +3622,10 @@ export default program => {
|
|
3117
3622
|
|
3118
3623
|
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
3119
3624
|
|
3625
|
+
globalThis.pageSize = PageSize;
|
3626
|
+
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
3627
|
+
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3628
|
+
|
3120
3629
|
// set generic opcodes for current valtype
|
3121
3630
|
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
3122
3631
|
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
@@ -3125,10 +3634,10 @@ export default program => {
|
|
3125
3634
|
Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
|
3126
3635
|
Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
|
3127
3636
|
|
3128
|
-
Opcodes.i32_to = [ [
|
3129
|
-
Opcodes.i32_to_u = [ [
|
3130
|
-
Opcodes.i32_from = [ [
|
3131
|
-
Opcodes.i32_from_u = [ [
|
3637
|
+
Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
|
3638
|
+
Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
|
3639
|
+
Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
|
3640
|
+
Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
|
3132
3641
|
|
3133
3642
|
Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
|
3134
3643
|
Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
|
@@ -3141,10 +3650,6 @@ export default program => {
|
|
3141
3650
|
|
3142
3651
|
program.id = { name: 'main' };
|
3143
3652
|
|
3144
|
-
globalThis.pageSize = PageSize;
|
3145
|
-
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
3146
|
-
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3147
|
-
|
3148
3653
|
const scope = {
|
3149
3654
|
locals: {},
|
3150
3655
|
localInd: 0
|
@@ -3155,7 +3660,7 @@ export default program => {
|
|
3155
3660
|
body: program.body
|
3156
3661
|
};
|
3157
3662
|
|
3158
|
-
if (Prefs.astLog) console.log(program.body.body);
|
3663
|
+
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
3159
3664
|
|
3160
3665
|
generateFunc(scope, program);
|
3161
3666
|
|
@@ -3172,7 +3677,11 @@ export default program => {
|
|
3172
3677
|
}
|
3173
3678
|
|
3174
3679
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
3175
|
-
|
3680
|
+
if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
|
3681
|
+
main.wasm.splice(main.wasm.length - 1, 1);
|
3682
|
+
} else {
|
3683
|
+
main.returns = [];
|
3684
|
+
}
|
3176
3685
|
}
|
3177
3686
|
|
3178
3687
|
if (lastInst[0] === Opcodes.call) {
|