porffor 0.2.0-1afe9b8 → 0.2.0-219912f
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 +123 -84
- package/asur/README.md +2 -0
- package/asur/index.js +1262 -0
- package/byg/index.js +237 -0
- package/compiler/2c.js +317 -72
- package/compiler/{sections.js → assemble.js} +63 -15
- package/compiler/builtins/annexb_string.js +72 -0
- package/compiler/builtins/annexb_string.ts +19 -0
- package/compiler/builtins/array.ts +145 -0
- package/compiler/builtins/base64.ts +151 -0
- package/compiler/builtins/crypto.ts +120 -0
- package/compiler/builtins/date.ts +1370 -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 -0
- package/compiler/builtins/string.ts +1055 -0
- package/compiler/builtins/tostring.ts +45 -0
- package/compiler/builtins.js +457 -254
- package/compiler/{codeGen.js → codegen.js} +929 -340
- package/compiler/embedding.js +22 -22
- package/compiler/encoding.js +108 -10
- package/compiler/generated_builtins.js +1262 -0
- package/compiler/index.js +36 -34
- package/compiler/log.js +6 -3
- package/compiler/opt.js +50 -36
- package/compiler/parse.js +35 -27
- package/compiler/precompile.js +123 -0
- package/compiler/prefs.js +26 -0
- package/compiler/prototype.js +13 -28
- package/compiler/types.js +37 -0
- package/compiler/wasmSpec.js +28 -8
- package/compiler/wrap.js +51 -46
- package/package.json +9 -5
- package/porf +4 -0
- package/rhemyn/compile.js +5 -3
- 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 +49 -10
- package/runner/profiler.js +102 -0
- package/runner/repl.js +40 -7
- package/runner/sizes.js +37 -37
- package/compiler/builtins/base64.js +0 -92
- package/r.js +0 -4
- package/runner/info.js +0 -89
- package/runner/profile.js +0 -46
- package/runner/results.json +0 -1
- package/runner/transform.js +0 -15
- package/util/enum.js +0 -20
@@ -7,6 +7,8 @@ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enfor
|
|
7
7
|
import { log } from "./log.js";
|
8
8
|
import parse from "./parse.js";
|
9
9
|
import * as Rhemyn from "../rhemyn/compile.js";
|
10
|
+
import Prefs from './prefs.js';
|
11
|
+
import { TYPES, TYPE_NAMES } from './types.js';
|
10
12
|
|
11
13
|
let globals = {};
|
12
14
|
let globalInd = 0;
|
@@ -23,35 +25,37 @@ const debug = str => {
|
|
23
25
|
const logChar = n => {
|
24
26
|
code.push(...number(n));
|
25
27
|
|
26
|
-
code.push(Opcodes.call);
|
27
|
-
code.push(...unsignedLEB128(0));
|
28
|
+
code.push([ Opcodes.call, 0 ]);
|
28
29
|
};
|
29
30
|
|
30
31
|
for (let i = 0; i < str.length; i++) {
|
31
32
|
logChar(str.charCodeAt(i));
|
32
33
|
}
|
33
34
|
|
34
|
-
logChar(
|
35
|
+
logChar(10); // new line
|
35
36
|
|
36
37
|
return code;
|
37
38
|
};
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
this.name = 'TodoError';
|
44
|
-
}
|
40
|
+
class TodoError extends Error {
|
41
|
+
constructor(message) {
|
42
|
+
super(message);
|
43
|
+
this.name = 'TodoError';
|
45
44
|
}
|
45
|
+
}
|
46
|
+
const todo = (scope, msg, expectsValue = undefined) => {
|
47
|
+
switch (Prefs.todoTime ?? 'runtime') {
|
48
|
+
case 'compile':
|
49
|
+
throw new TodoError(msg);
|
46
50
|
|
47
|
-
|
48
|
-
|
49
|
-
const code = [];
|
50
|
-
|
51
|
-
code.push(...debug(`todo! ` + msg));
|
52
|
-
code.push(Opcodes.unreachable);
|
51
|
+
case 'runtime':
|
52
|
+
return internalThrow(scope, 'TodoError', msg, expectsValue);
|
53
53
|
|
54
|
-
|
54
|
+
// return [
|
55
|
+
// ...debug(`todo! ${msg}`),
|
56
|
+
// [ Opcodes.unreachable ]
|
57
|
+
// ];
|
58
|
+
}
|
55
59
|
};
|
56
60
|
|
57
61
|
const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
|
@@ -104,7 +108,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
104
108
|
return generateUnary(scope, decl);
|
105
109
|
|
106
110
|
case 'UpdateExpression':
|
107
|
-
return generateUpdate(scope, decl);
|
111
|
+
return generateUpdate(scope, decl, global, name, valueUnused);
|
108
112
|
|
109
113
|
case 'IfStatement':
|
110
114
|
return generateIf(scope, decl);
|
@@ -115,6 +119,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
115
119
|
case 'WhileStatement':
|
116
120
|
return generateWhile(scope, decl);
|
117
121
|
|
122
|
+
case 'DoWhileStatement':
|
123
|
+
return generateDoWhile(scope, decl);
|
124
|
+
|
118
125
|
case 'ForOfStatement':
|
119
126
|
return generateForOf(scope, decl);
|
120
127
|
|
@@ -124,6 +131,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
124
131
|
case 'ContinueStatement':
|
125
132
|
return generateContinue(scope, decl);
|
126
133
|
|
134
|
+
case 'LabeledStatement':
|
135
|
+
return generateLabel(scope, decl);
|
136
|
+
|
127
137
|
case 'EmptyStatement':
|
128
138
|
return generateEmpty(scope, decl);
|
129
139
|
|
@@ -137,7 +147,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
137
147
|
return generateTry(scope, decl);
|
138
148
|
|
139
149
|
case 'DebuggerStatement':
|
140
|
-
// todo:
|
150
|
+
// todo: hook into terminal debugger
|
141
151
|
return [];
|
142
152
|
|
143
153
|
case 'ArrayExpression':
|
@@ -151,16 +161,22 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
151
161
|
const funcsBefore = funcs.length;
|
152
162
|
generate(scope, decl.declaration);
|
153
163
|
|
154
|
-
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');
|
155
171
|
|
156
|
-
const newFunc = funcs[funcs.length - 1];
|
157
|
-
newFunc.export = true;
|
172
|
+
// const newFunc = funcs[funcs.length - 1];
|
173
|
+
// newFunc.export = true;
|
158
174
|
|
159
175
|
return [];
|
160
176
|
|
161
177
|
case 'TaggedTemplateExpression': {
|
162
178
|
const funcs = {
|
163
|
-
|
179
|
+
__Porffor_wasm: str => {
|
164
180
|
let out = [];
|
165
181
|
|
166
182
|
for (const line of str.split('\n')) {
|
@@ -168,8 +184,8 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
168
184
|
if (asm[0] === '') continue; // blank
|
169
185
|
|
170
186
|
if (asm[0] === 'local') {
|
171
|
-
const [ name,
|
172
|
-
scope.locals[name] = { idx:
|
187
|
+
const [ name, type ] = asm.slice(1);
|
188
|
+
scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
|
173
189
|
continue;
|
174
190
|
}
|
175
191
|
|
@@ -179,7 +195,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
179
195
|
}
|
180
196
|
|
181
197
|
if (asm[0] === 'memory') {
|
182
|
-
allocPage('asm instrinsic');
|
198
|
+
allocPage(scope, 'asm instrinsic');
|
183
199
|
// todo: add to store/load offset insts
|
184
200
|
continue;
|
185
201
|
}
|
@@ -188,30 +204,65 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
188
204
|
if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
189
205
|
|
190
206
|
if (!Array.isArray(inst)) inst = [ inst ];
|
191
|
-
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
|
+
});
|
192
212
|
|
193
|
-
out.push([ ...inst, ...immediates ]);
|
213
|
+
out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
|
194
214
|
}
|
195
215
|
|
196
216
|
return out;
|
197
|
-
}
|
198
|
-
|
217
|
+
},
|
218
|
+
|
219
|
+
__Porffor_bs: str => [
|
220
|
+
...makeString(scope, str, global, name, true),
|
199
221
|
|
200
|
-
|
222
|
+
...(name ? setType(scope, name, TYPES._bytestring) : [
|
223
|
+
...number(TYPES._bytestring, Valtype.i32),
|
224
|
+
...setLastType(scope)
|
225
|
+
])
|
226
|
+
],
|
227
|
+
__Porffor_s: str => [
|
228
|
+
...makeString(scope, str, global, name, false),
|
229
|
+
|
230
|
+
...(name ? setType(scope, name, TYPES.string) : [
|
231
|
+
...number(TYPES.string, Valtype.i32),
|
232
|
+
...setLastType(scope)
|
233
|
+
])
|
234
|
+
],
|
235
|
+
};
|
236
|
+
|
237
|
+
const func = decl.tag.name;
|
201
238
|
// hack for inline asm
|
202
|
-
if (!funcs[
|
239
|
+
if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
|
240
|
+
|
241
|
+
const { quasis, expressions } = decl.quasi;
|
242
|
+
let str = quasis[0].value.raw;
|
243
|
+
|
244
|
+
for (let i = 0; i < expressions.length; i++) {
|
245
|
+
const e = expressions[i];
|
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
|
+
|
252
|
+
str += quasis[i + 1].value.raw;
|
253
|
+
}
|
203
254
|
|
204
|
-
|
205
|
-
return funcs[name](str);
|
255
|
+
return funcs[func](str);
|
206
256
|
}
|
207
257
|
|
208
258
|
default:
|
209
|
-
|
210
|
-
|
259
|
+
// ignore typescript nodes
|
260
|
+
if (decl.type.startsWith('TS') ||
|
261
|
+
decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
|
211
262
|
return [];
|
212
263
|
}
|
213
264
|
|
214
|
-
return todo(`no generation for ${decl.type}!`);
|
265
|
+
return todo(scope, `no generation for ${decl.type}!`);
|
215
266
|
}
|
216
267
|
};
|
217
268
|
|
@@ -239,7 +290,7 @@ const lookupName = (scope, _name) => {
|
|
239
290
|
return [ undefined, undefined ];
|
240
291
|
};
|
241
292
|
|
242
|
-
const internalThrow = (scope, constructor, message, expectsValue =
|
293
|
+
const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
|
243
294
|
...generateThrow(scope, {
|
244
295
|
argument: {
|
245
296
|
type: 'NewExpression',
|
@@ -263,7 +314,10 @@ const generateIdent = (scope, decl) => {
|
|
263
314
|
|
264
315
|
if (Object.hasOwn(builtinVars, name)) {
|
265
316
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
266
|
-
|
317
|
+
|
318
|
+
let wasm = builtinVars[name];
|
319
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
|
320
|
+
return wasm.slice();
|
267
321
|
}
|
268
322
|
|
269
323
|
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
@@ -271,6 +325,11 @@ const generateIdent = (scope, decl) => {
|
|
271
325
|
return number(1);
|
272
326
|
}
|
273
327
|
|
328
|
+
if (isExistingProtoFunc(name)) {
|
329
|
+
// todo: return an actual something
|
330
|
+
return number(1);
|
331
|
+
}
|
332
|
+
|
274
333
|
if (local?.idx === undefined) {
|
275
334
|
// no local var with name
|
276
335
|
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
@@ -301,14 +360,18 @@ const generateReturn = (scope, decl) => {
|
|
301
360
|
// just bare "return"
|
302
361
|
return [
|
303
362
|
...number(UNDEFINED), // "undefined" if func returns
|
304
|
-
...
|
363
|
+
...(scope.returnType != null ? [] : [
|
364
|
+
...number(TYPES.undefined, Valtype.i32) // type undefined
|
365
|
+
]),
|
305
366
|
[ Opcodes.return ]
|
306
367
|
];
|
307
368
|
}
|
308
369
|
|
309
370
|
return [
|
310
371
|
...generate(scope, decl.argument),
|
311
|
-
...
|
372
|
+
...(scope.returnType != null ? [] : [
|
373
|
+
...getNodeType(scope, decl.argument)
|
374
|
+
]),
|
312
375
|
[ Opcodes.return ]
|
313
376
|
];
|
314
377
|
};
|
@@ -322,7 +385,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
322
385
|
return idx;
|
323
386
|
};
|
324
387
|
|
325
|
-
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);
|
326
390
|
|
327
391
|
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
328
392
|
const checks = {
|
@@ -331,7 +395,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
331
395
|
'??': nullish
|
332
396
|
};
|
333
397
|
|
334
|
-
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);
|
335
399
|
|
336
400
|
// generic structure for {a} OP {b}
|
337
401
|
// -->
|
@@ -339,8 +403,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
339
403
|
|
340
404
|
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
341
405
|
// (like if we are in an if condition - very common)
|
342
|
-
const leftIsInt =
|
343
|
-
const rightIsInt =
|
406
|
+
const leftIsInt = isFloatToIntOp(left[left.length - 1]);
|
407
|
+
const rightIsInt = isFloatToIntOp(right[right.length - 1]);
|
344
408
|
|
345
409
|
const canInt = leftIsInt && rightIsInt;
|
346
410
|
|
@@ -357,12 +421,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
357
421
|
...right,
|
358
422
|
// note type
|
359
423
|
...rightType,
|
360
|
-
setLastType(scope),
|
424
|
+
...setLastType(scope),
|
361
425
|
[ Opcodes.else ],
|
362
426
|
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
363
427
|
// note type
|
364
428
|
...leftType,
|
365
|
-
setLastType(scope),
|
429
|
+
...setLastType(scope),
|
366
430
|
[ Opcodes.end ],
|
367
431
|
Opcodes.i32_from
|
368
432
|
];
|
@@ -376,17 +440,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
376
440
|
...right,
|
377
441
|
// note type
|
378
442
|
...rightType,
|
379
|
-
setLastType(scope),
|
443
|
+
...setLastType(scope),
|
380
444
|
[ Opcodes.else ],
|
381
445
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
382
446
|
// note type
|
383
447
|
...leftType,
|
384
|
-
setLastType(scope),
|
448
|
+
...setLastType(scope),
|
385
449
|
[ Opcodes.end ]
|
386
450
|
];
|
387
451
|
};
|
388
452
|
|
389
|
-
const concatStrings = (scope, left, right, global, name, assign) => {
|
453
|
+
const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
|
390
454
|
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
391
455
|
// todo: convert left and right to strings if not
|
392
456
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -396,11 +460,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
396
460
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
397
461
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
398
462
|
|
399
|
-
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
400
|
-
if (aotWFA) addVarMeta(name, { wellFormed: undefined });
|
401
|
-
|
402
463
|
if (assign) {
|
403
|
-
const pointer = arrays
|
464
|
+
const pointer = scope.arrays?.get(name ?? '$undeclared');
|
404
465
|
|
405
466
|
return [
|
406
467
|
// setup right
|
@@ -425,11 +486,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
425
486
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
|
426
487
|
|
427
488
|
// copy right
|
428
|
-
// dst = out pointer + length size + current length *
|
489
|
+
// dst = out pointer + length size + current length * sizeof valtype
|
429
490
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
430
491
|
|
431
492
|
[ Opcodes.local_get, leftLength ],
|
432
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
493
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
433
494
|
[ Opcodes.i32_mul ],
|
434
495
|
[ Opcodes.i32_add ],
|
435
496
|
|
@@ -438,9 +499,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
438
499
|
...number(ValtypeSize.i32, Valtype.i32),
|
439
500
|
[ Opcodes.i32_add ],
|
440
501
|
|
441
|
-
// size = right length *
|
502
|
+
// size = right length * sizeof valtype
|
442
503
|
[ Opcodes.local_get, rightLength ],
|
443
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
504
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
444
505
|
[ Opcodes.i32_mul ],
|
445
506
|
|
446
507
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -498,11 +559,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
498
559
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
499
560
|
|
500
561
|
// copy right
|
501
|
-
// dst = out pointer + length size + left length *
|
562
|
+
// dst = out pointer + length size + left length * sizeof valtype
|
502
563
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
503
564
|
|
504
565
|
[ Opcodes.local_get, leftLength ],
|
505
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
566
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
506
567
|
[ Opcodes.i32_mul ],
|
507
568
|
[ Opcodes.i32_add ],
|
508
569
|
|
@@ -511,9 +572,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
511
572
|
...number(ValtypeSize.i32, Valtype.i32),
|
512
573
|
[ Opcodes.i32_add ],
|
513
574
|
|
514
|
-
// size = right length *
|
575
|
+
// size = right length * sizeof valtype
|
515
576
|
[ Opcodes.local_get, rightLength ],
|
516
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
577
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
517
578
|
[ Opcodes.i32_mul ],
|
518
579
|
|
519
580
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -523,7 +584,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
523
584
|
];
|
524
585
|
};
|
525
586
|
|
526
|
-
const compareStrings = (scope, left, right) => {
|
587
|
+
const compareStrings = (scope, left, right, bytestrings = false) => {
|
527
588
|
// todo: this should be rewritten into a func
|
528
589
|
// todo: convert left and right to strings if not
|
529
590
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -532,7 +593,6 @@ const compareStrings = (scope, left, right) => {
|
|
532
593
|
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
533
594
|
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
534
595
|
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
535
|
-
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
536
596
|
|
537
597
|
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
538
598
|
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
@@ -560,7 +620,6 @@ const compareStrings = (scope, left, right) => {
|
|
560
620
|
|
561
621
|
[ Opcodes.local_get, rightPointer ],
|
562
622
|
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
563
|
-
[ Opcodes.local_tee, rightLength ],
|
564
623
|
|
565
624
|
// fast path: check leftLength != rightLength
|
566
625
|
[ Opcodes.i32_ne ],
|
@@ -575,11 +634,13 @@ const compareStrings = (scope, left, right) => {
|
|
575
634
|
...number(0, Valtype.i32),
|
576
635
|
[ Opcodes.local_set, index ],
|
577
636
|
|
578
|
-
// setup index end as length * sizeof
|
637
|
+
// setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
|
579
638
|
// we do this instead of having to do mul/div each iter for perf™
|
580
639
|
[ Opcodes.local_get, leftLength ],
|
581
|
-
...
|
582
|
-
|
640
|
+
...(bytestrings ? [] : [
|
641
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
642
|
+
[ Opcodes.i32_mul ],
|
643
|
+
]),
|
583
644
|
[ Opcodes.local_set, indexEnd ],
|
584
645
|
|
585
646
|
// iterate over each char and check if eq
|
@@ -589,13 +650,17 @@ const compareStrings = (scope, left, right) => {
|
|
589
650
|
[ Opcodes.local_get, index ],
|
590
651
|
[ Opcodes.local_get, leftPointer ],
|
591
652
|
[ Opcodes.i32_add ],
|
592
|
-
|
653
|
+
bytestrings ?
|
654
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
655
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
593
656
|
|
594
657
|
// fetch right
|
595
658
|
[ Opcodes.local_get, index ],
|
596
659
|
[ Opcodes.local_get, rightPointer ],
|
597
660
|
[ Opcodes.i32_add ],
|
598
|
-
|
661
|
+
bytestrings ?
|
662
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
663
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
599
664
|
|
600
665
|
// not equal, "return" false
|
601
666
|
[ Opcodes.i32_ne ],
|
@@ -604,13 +669,13 @@ const compareStrings = (scope, left, right) => {
|
|
604
669
|
[ Opcodes.br, 2 ],
|
605
670
|
[ Opcodes.end ],
|
606
671
|
|
607
|
-
// index += sizeof
|
672
|
+
// index += sizeof valtype (1 for bytestring, 2 for string)
|
608
673
|
[ Opcodes.local_get, index ],
|
609
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
674
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
610
675
|
[ Opcodes.i32_add ],
|
611
676
|
[ Opcodes.local_tee, index ],
|
612
677
|
|
613
|
-
// if index != index end (length * sizeof
|
678
|
+
// if index != index end (length * sizeof valtype), loop
|
614
679
|
[ Opcodes.local_get, indexEnd ],
|
615
680
|
[ Opcodes.i32_ne ],
|
616
681
|
[ Opcodes.br_if, 0 ],
|
@@ -631,16 +696,18 @@ const compareStrings = (scope, left, right) => {
|
|
631
696
|
};
|
632
697
|
|
633
698
|
const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
634
|
-
if (
|
699
|
+
if (isFloatToIntOp(wasm[wasm.length - 1])) return [
|
635
700
|
...wasm,
|
636
701
|
...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
|
637
702
|
];
|
703
|
+
// if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
|
638
704
|
|
639
|
-
const
|
705
|
+
const useTmp = knownType(scope, type) == null;
|
706
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
640
707
|
|
641
708
|
const def = [
|
642
709
|
// if value != 0
|
643
|
-
[ Opcodes.local_get, tmp ],
|
710
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
644
711
|
|
645
712
|
// ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
646
713
|
...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
|
@@ -652,7 +719,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
652
719
|
|
653
720
|
return [
|
654
721
|
...wasm,
|
655
|
-
[ Opcodes.local_set, tmp ],
|
722
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
656
723
|
|
657
724
|
...typeSwitch(scope, type, {
|
658
725
|
// [TYPES.number]: def,
|
@@ -661,7 +728,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
661
728
|
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
662
729
|
],
|
663
730
|
[TYPES.string]: [
|
664
|
-
[ Opcodes.local_get, tmp ],
|
731
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
665
732
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
666
733
|
|
667
734
|
// get length
|
@@ -673,7 +740,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
673
740
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
674
741
|
],
|
675
742
|
[TYPES._bytestring]: [ // duplicate of string
|
676
|
-
|
743
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
677
744
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
678
745
|
|
679
746
|
// get length
|
@@ -687,10 +754,12 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
687
754
|
};
|
688
755
|
|
689
756
|
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
690
|
-
const
|
757
|
+
const useTmp = knownType(scope, type) == null;
|
758
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
759
|
+
|
691
760
|
return [
|
692
761
|
...wasm,
|
693
|
-
[ Opcodes.local_set, tmp ],
|
762
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
694
763
|
|
695
764
|
...typeSwitch(scope, type, {
|
696
765
|
[TYPES._array]: [
|
@@ -698,7 +767,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
698
767
|
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
699
768
|
],
|
700
769
|
[TYPES.string]: [
|
701
|
-
[ Opcodes.local_get, tmp ],
|
770
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
702
771
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
703
772
|
|
704
773
|
// get length
|
@@ -709,7 +778,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
709
778
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
710
779
|
],
|
711
780
|
[TYPES._bytestring]: [ // duplicate of string
|
712
|
-
[ Opcodes.local_get, tmp ],
|
781
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
713
782
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
714
783
|
|
715
784
|
// get length
|
@@ -721,7 +790,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
721
790
|
],
|
722
791
|
default: [
|
723
792
|
// if value == 0
|
724
|
-
[ Opcodes.local_get, tmp ],
|
793
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
725
794
|
|
726
795
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
727
796
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -731,10 +800,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
731
800
|
};
|
732
801
|
|
733
802
|
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
734
|
-
const
|
803
|
+
const useTmp = knownType(scope, type) == null;
|
804
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
805
|
+
|
735
806
|
return [
|
736
807
|
...wasm,
|
737
|
-
[ Opcodes.local_set, tmp ],
|
808
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
738
809
|
|
739
810
|
...typeSwitch(scope, type, {
|
740
811
|
[TYPES.undefined]: [
|
@@ -743,7 +814,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
743
814
|
],
|
744
815
|
[TYPES.object]: [
|
745
816
|
// object, null if == 0
|
746
|
-
[ Opcodes.local_get, tmp ],
|
817
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
747
818
|
|
748
819
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
749
820
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -772,11 +843,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
772
843
|
return performLogicOp(scope, op, left, right, leftType, rightType);
|
773
844
|
}
|
774
845
|
|
846
|
+
const knownLeft = knownType(scope, leftType);
|
847
|
+
const knownRight = knownType(scope, rightType);
|
848
|
+
|
775
849
|
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
776
850
|
const strictOp = op === '===' || op === '!==';
|
777
851
|
|
778
852
|
const startOut = [], endOut = [];
|
779
|
-
const
|
853
|
+
const finalize = out => startOut.concat(out, endOut);
|
780
854
|
|
781
855
|
// if strict (in)equal check types match
|
782
856
|
if (strictOp) {
|
@@ -821,31 +895,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
821
895
|
// todo: if equality op and an operand is undefined, return false
|
822
896
|
// todo: niche null hell with 0
|
823
897
|
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
898
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) {
|
899
|
+
if (op === '+') {
|
900
|
+
// todo: this should be dynamic too but for now only static
|
901
|
+
// string concat (a + b)
|
902
|
+
return concatStrings(scope, left, right, _global, _name, assign, false);
|
903
|
+
}
|
904
|
+
|
905
|
+
// not an equality op, NaN
|
906
|
+
if (!eqOp) return number(NaN);
|
907
|
+
|
908
|
+
// else leave bool ops
|
909
|
+
// todo: convert string to number if string and number/bool
|
910
|
+
// todo: string (>|>=|<|<=) string
|
911
|
+
|
912
|
+
// string comparison
|
913
|
+
if (op === '===' || op === '==') {
|
914
|
+
return compareStrings(scope, left, right);
|
915
|
+
}
|
916
|
+
|
917
|
+
if (op === '!==' || op === '!=') {
|
918
|
+
return [
|
919
|
+
...compareStrings(scope, left, right),
|
920
|
+
[ Opcodes.i32_eqz ]
|
921
|
+
];
|
922
|
+
}
|
923
|
+
}
|
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
|
+
}
|
849
951
|
|
850
952
|
let ops = operatorOpcode[valtype][op];
|
851
953
|
|
@@ -855,33 +957,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
855
957
|
includeBuiltin(scope, builtinName);
|
856
958
|
const idx = funcIndex[builtinName];
|
857
959
|
|
858
|
-
return
|
960
|
+
return finalize([
|
859
961
|
...left,
|
860
962
|
...right,
|
861
963
|
[ Opcodes.call, idx ]
|
862
964
|
]);
|
863
965
|
}
|
864
966
|
|
865
|
-
if (!ops) return todo(`operator ${op} not implemented yet
|
967
|
+
if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
|
866
968
|
|
867
969
|
if (!Array.isArray(ops)) ops = [ ops ];
|
868
970
|
ops = [ ops ];
|
869
971
|
|
870
972
|
let tmpLeft, tmpRight;
|
871
973
|
// if equal op, check if strings for compareStrings
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
// todo: intelligent partial skip later
|
877
|
-
// if neither known are string, stop this madness
|
878
|
-
if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
|
879
|
-
return;
|
880
|
-
}
|
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
|
881
977
|
|
978
|
+
if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
|
882
979
|
tmpLeft = localTmp(scope, '__tmpop_left');
|
883
980
|
tmpRight = localTmp(scope, '__tmpop_right');
|
884
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)
|
885
1023
|
ops.unshift(...stringOnly([
|
886
1024
|
// if left is string
|
887
1025
|
...leftType,
|
@@ -893,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
893
1031
|
...number(TYPES.string, Valtype.i32),
|
894
1032
|
[ Opcodes.i32_eq ],
|
895
1033
|
|
896
|
-
// if
|
897
|
-
[ Opcodes.
|
1034
|
+
// if both are true
|
1035
|
+
[ Opcodes.i32_and ],
|
898
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 ],
|
899
1041
|
|
900
|
-
//
|
901
|
-
// if left is not string
|
1042
|
+
// if left is bytestring
|
902
1043
|
...leftType,
|
903
|
-
...number(TYPES.
|
904
|
-
[ Opcodes.
|
1044
|
+
...number(TYPES._bytestring, Valtype.i32),
|
1045
|
+
[ Opcodes.i32_eq ],
|
905
1046
|
|
906
|
-
// if right is
|
1047
|
+
// if right is bytestring
|
907
1048
|
...rightType,
|
908
|
-
...number(TYPES.
|
909
|
-
[ Opcodes.
|
1049
|
+
...number(TYPES._bytestring, Valtype.i32),
|
1050
|
+
[ Opcodes.i32_eq ],
|
910
1051
|
|
911
|
-
// if
|
912
|
-
[ Opcodes.
|
1052
|
+
// if both are true
|
1053
|
+
[ Opcodes.i32_and ],
|
913
1054
|
[ Opcodes.if, Blocktype.void ],
|
914
|
-
...
|
915
|
-
[ Opcodes.br, 1 ],
|
916
|
-
[ Opcodes.end ],
|
917
|
-
|
918
|
-
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
919
|
-
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1055
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
|
920
1056
|
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
921
1057
|
[ Opcodes.br, 1 ],
|
922
1058
|
[ Opcodes.end ],
|
@@ -928,9 +1064,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
928
1064
|
// endOut.push(stringOnly([ Opcodes.end ]));
|
929
1065
|
endOut.unshift(stringOnly([ Opcodes.end ]));
|
930
1066
|
// }
|
931
|
-
}
|
1067
|
+
}
|
932
1068
|
|
933
|
-
return
|
1069
|
+
return finalize([
|
934
1070
|
...left,
|
935
1071
|
...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
|
936
1072
|
...right,
|
@@ -947,7 +1083,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
947
1083
|
return out;
|
948
1084
|
};
|
949
1085
|
|
950
|
-
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 = [] }) => {
|
951
1102
|
const existing = funcs.find(x => x.name === name);
|
952
1103
|
if (existing) return existing;
|
953
1104
|
|
@@ -959,18 +1110,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
959
1110
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
960
1111
|
}
|
961
1112
|
|
962
|
-
|
963
|
-
const
|
964
|
-
|
965
|
-
|
966
|
-
locals,
|
967
|
-
returns,
|
968
|
-
localInd: allLocals.length,
|
969
|
-
};
|
970
|
-
|
971
|
-
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);
|
972
1117
|
}
|
973
1118
|
|
1119
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
|
1120
|
+
|
974
1121
|
let baseGlobalIdx, i = 0;
|
975
1122
|
for (const type of globalTypes) {
|
976
1123
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -993,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
993
1140
|
params,
|
994
1141
|
locals,
|
995
1142
|
returns,
|
996
|
-
returnType:
|
1143
|
+
returnType: returnType ?? TYPES.number,
|
997
1144
|
wasm,
|
998
1145
|
internal: true,
|
999
1146
|
index: currentFuncIndex++
|
@@ -1016,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
|
|
1016
1163
|
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
1017
1164
|
};
|
1018
1165
|
|
1166
|
+
// potential future ideas for nan boxing (unused):
|
1019
1167
|
// T = JS type, V = value/pointer
|
1020
1168
|
// 0bTTT
|
1021
1169
|
// qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
|
@@ -1039,49 +1187,29 @@ const generateLogicExp = (scope, decl) => {
|
|
1039
1187
|
// 4: internal type
|
1040
1188
|
// 5: pointer
|
1041
1189
|
|
1042
|
-
const
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
object: 0x04,
|
1048
|
-
function: 0x05,
|
1049
|
-
symbol: 0x06,
|
1050
|
-
bigint: 0x07,
|
1051
|
-
|
1052
|
-
// these are not "typeof" types but tracked internally
|
1053
|
-
_array: 0x10,
|
1054
|
-
_regexp: 0x11,
|
1055
|
-
_bytestring: 0x12
|
1056
|
-
};
|
1057
|
-
|
1058
|
-
const TYPE_NAMES = {
|
1059
|
-
[TYPES.number]: 'Number',
|
1060
|
-
[TYPES.boolean]: 'Boolean',
|
1061
|
-
[TYPES.string]: 'String',
|
1062
|
-
[TYPES.undefined]: 'undefined',
|
1063
|
-
[TYPES.object]: 'Object',
|
1064
|
-
[TYPES.function]: 'Function',
|
1065
|
-
[TYPES.symbol]: 'Symbol',
|
1066
|
-
[TYPES.bigint]: 'BigInt',
|
1067
|
-
|
1068
|
-
[TYPES._array]: 'Array',
|
1069
|
-
[TYPES._regexp]: 'RegExp',
|
1070
|
-
[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;
|
1071
1195
|
};
|
1072
1196
|
|
1073
1197
|
const getType = (scope, _name) => {
|
1074
1198
|
const name = mapName(_name);
|
1075
1199
|
|
1200
|
+
// if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
|
1201
|
+
|
1202
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
|
1076
1203
|
if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
|
1204
|
+
|
1205
|
+
if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
|
1077
1206
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1078
1207
|
|
1079
1208
|
let type = TYPES.undefined;
|
1080
|
-
if (builtinVars[name]) type =
|
1209
|
+
if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
|
1081
1210
|
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
|
1082
1211
|
|
1083
|
-
if (name
|
1084
|
-
name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
|
1212
|
+
if (isExistingProtoFunc(name)) type = TYPES.function;
|
1085
1213
|
|
1086
1214
|
return number(type, Valtype.i32);
|
1087
1215
|
};
|
@@ -1104,15 +1232,16 @@ const setType = (scope, _name, type) => {
|
|
1104
1232
|
];
|
1105
1233
|
|
1106
1234
|
// throw new Error('could not find var');
|
1235
|
+
return [];
|
1107
1236
|
};
|
1108
1237
|
|
1109
1238
|
const getLastType = scope => {
|
1110
1239
|
scope.gotLastType = true;
|
1111
|
-
return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
|
1240
|
+
return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1112
1241
|
};
|
1113
1242
|
|
1114
1243
|
const setLastType = scope => {
|
1115
|
-
return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
|
1244
|
+
return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1116
1245
|
};
|
1117
1246
|
|
1118
1247
|
const getNodeType = (scope, node) => {
|
@@ -1137,13 +1266,25 @@ const getNodeType = (scope, node) => {
|
|
1137
1266
|
const name = node.callee.name;
|
1138
1267
|
if (!name) {
|
1139
1268
|
// iife
|
1140
|
-
if (scope.locals['#last_type']) return
|
1269
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1141
1270
|
|
1142
1271
|
// presume
|
1143
1272
|
// todo: warn here?
|
1144
1273
|
return TYPES.number;
|
1145
1274
|
}
|
1146
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
|
+
|
1147
1288
|
const func = funcs.find(x => x.name === name);
|
1148
1289
|
|
1149
1290
|
if (func) {
|
@@ -1151,7 +1292,7 @@ const getNodeType = (scope, node) => {
|
|
1151
1292
|
if (func.returnType) return func.returnType;
|
1152
1293
|
}
|
1153
1294
|
|
1154
|
-
if (builtinFuncs[name]) return
|
1295
|
+
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
1155
1296
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1156
1297
|
|
1157
1298
|
// check if this is a prototype function
|
@@ -1166,7 +1307,12 @@ const getNodeType = (scope, node) => {
|
|
1166
1307
|
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1167
1308
|
}
|
1168
1309
|
|
1169
|
-
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);
|
1170
1316
|
|
1171
1317
|
// presume
|
1172
1318
|
// todo: warn here?
|
@@ -1214,6 +1360,15 @@ const getNodeType = (scope, node) => {
|
|
1214
1360
|
|
1215
1361
|
if (node.type === 'BinaryExpression') {
|
1216
1362
|
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
1363
|
+
if (node.operator !== '+') return TYPES.number;
|
1364
|
+
|
1365
|
+
const knownLeft = knownType(scope, getNodeType(scope, node.left));
|
1366
|
+
const knownRight = knownType(scope, getNodeType(scope, node.right));
|
1367
|
+
|
1368
|
+
// todo: this should be dynamic but for now only static
|
1369
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
|
1370
|
+
if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) return TYPES._bytestring;
|
1371
|
+
|
1217
1372
|
return TYPES.number;
|
1218
1373
|
|
1219
1374
|
// todo: string concat types
|
@@ -1238,7 +1393,7 @@ const getNodeType = (scope, node) => {
|
|
1238
1393
|
if (node.operator === '!') return TYPES.boolean;
|
1239
1394
|
if (node.operator === 'void') return TYPES.undefined;
|
1240
1395
|
if (node.operator === 'delete') return TYPES.boolean;
|
1241
|
-
if (node.operator === 'typeof') return
|
1396
|
+
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
|
1242
1397
|
|
1243
1398
|
return TYPES.number;
|
1244
1399
|
}
|
@@ -1249,15 +1404,21 @@ const getNodeType = (scope, node) => {
|
|
1249
1404
|
|
1250
1405
|
// ts hack
|
1251
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;
|
1252
1408
|
if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
|
1253
1409
|
|
1254
|
-
if (scope.locals['#last_type']) return
|
1410
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1255
1411
|
|
1256
1412
|
// presume
|
1257
1413
|
return TYPES.number;
|
1258
1414
|
}
|
1259
1415
|
|
1260
|
-
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);
|
1261
1422
|
|
1262
1423
|
// presume
|
1263
1424
|
// todo: warn here?
|
@@ -1290,7 +1451,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1290
1451
|
return makeString(scope, decl.value, global, name);
|
1291
1452
|
|
1292
1453
|
default:
|
1293
|
-
return todo(`cannot generate literal of type ${typeof decl.value}
|
1454
|
+
return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
|
1294
1455
|
}
|
1295
1456
|
};
|
1296
1457
|
|
@@ -1299,6 +1460,8 @@ const countLeftover = wasm => {
|
|
1299
1460
|
|
1300
1461
|
for (let i = 0; i < wasm.length; i++) {
|
1301
1462
|
const inst = wasm[i];
|
1463
|
+
if (inst[0] == null) continue;
|
1464
|
+
|
1302
1465
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
1303
1466
|
if (inst[0] === Opcodes.if) count--;
|
1304
1467
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1307,18 +1470,25 @@ const countLeftover = wasm => {
|
|
1307
1470
|
if (inst[0] === Opcodes.end) depth--;
|
1308
1471
|
|
1309
1472
|
if (depth === 0)
|
1310
|
-
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--;
|
1311
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)) {}
|
1312
|
-
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++;
|
1313
1476
|
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1314
1477
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1315
1478
|
else if (inst[0] === Opcodes.return) count = 0;
|
1316
1479
|
else if (inst[0] === Opcodes.call) {
|
1317
1480
|
let func = funcs.find(x => x.index === inst[1]);
|
1318
|
-
if (
|
1319
|
-
count
|
1320
|
-
} else
|
1321
|
-
|
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
|
+
}
|
1322
1492
|
} else count--;
|
1323
1493
|
|
1324
1494
|
// console.log(count, decompile([ inst ]).slice(0, -1));
|
@@ -1410,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1410
1580
|
name = func.name;
|
1411
1581
|
}
|
1412
1582
|
|
1413
|
-
if (name === 'eval' && decl.arguments[0]
|
1583
|
+
if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
|
1414
1584
|
// literal eval hack
|
1415
|
-
const code = decl.arguments[0]
|
1416
|
-
|
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
|
+
}
|
1417
1598
|
|
1418
1599
|
const out = generate(scope, {
|
1419
1600
|
type: 'BlockStatement',
|
@@ -1427,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1427
1608
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1428
1609
|
out.push(
|
1429
1610
|
...getNodeType(scope, finalStatement),
|
1430
|
-
setLastType(scope)
|
1611
|
+
...setLastType(scope)
|
1431
1612
|
);
|
1432
1613
|
} else if (countLeftover(out) === 0) {
|
1433
1614
|
out.push(...number(UNDEFINED));
|
1434
1615
|
out.push(
|
1435
1616
|
...number(TYPES.undefined, Valtype.i32),
|
1436
|
-
setLastType(scope)
|
1617
|
+
...setLastType(scope)
|
1437
1618
|
);
|
1438
1619
|
}
|
1439
1620
|
|
@@ -1455,6 +1636,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1455
1636
|
|
1456
1637
|
target = { ...decl.callee };
|
1457
1638
|
target.name = spl.slice(0, -1).join('_');
|
1639
|
+
|
1640
|
+
// failed to lookup name, abort
|
1641
|
+
if (!lookupName(scope, target.name)[0]) protoName = null;
|
1458
1642
|
}
|
1459
1643
|
|
1460
1644
|
// literal.func()
|
@@ -1477,7 +1661,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1477
1661
|
Opcodes.i32_from_u,
|
1478
1662
|
|
1479
1663
|
...number(TYPES.boolean, Valtype.i32),
|
1480
|
-
setLastType(scope)
|
1664
|
+
...setLastType(scope)
|
1481
1665
|
];
|
1482
1666
|
}
|
1483
1667
|
|
@@ -1502,12 +1686,31 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1502
1686
|
// }
|
1503
1687
|
|
1504
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
|
+
type: 'Identifier',
|
1701
|
+
name: x
|
1702
|
+
},
|
1703
|
+
arguments: [ target, ...decl.arguments ],
|
1704
|
+
_protoInternalCall: true
|
1705
|
+
});
|
1706
|
+
}
|
1707
|
+
}
|
1708
|
+
|
1505
1709
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1506
1710
|
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1507
1711
|
return acc;
|
1508
1712
|
}, {});
|
1509
1713
|
|
1510
|
-
// no prototype function candidates, ignore
|
1511
1714
|
if (Object.keys(protoCands).length > 0) {
|
1512
1715
|
// use local for cached i32 length as commonly used
|
1513
1716
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
@@ -1525,7 +1728,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1525
1728
|
|
1526
1729
|
let allOptUnused = true;
|
1527
1730
|
let lengthI32CacheUsed = false;
|
1528
|
-
const protoBC = {};
|
1529
1731
|
for (const x in protoCands) {
|
1530
1732
|
const protoFunc = protoCands[x];
|
1531
1733
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
@@ -1533,7 +1735,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1533
1735
|
...RTArrayUtil.getLength(getPointer),
|
1534
1736
|
|
1535
1737
|
...number(TYPES.number, Valtype.i32),
|
1536
|
-
setLastType(scope)
|
1738
|
+
...setLastType(scope)
|
1537
1739
|
];
|
1538
1740
|
continue;
|
1539
1741
|
}
|
@@ -1570,7 +1772,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1570
1772
|
...protoOut,
|
1571
1773
|
|
1572
1774
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1573
|
-
setLastType(scope),
|
1775
|
+
...setLastType(scope),
|
1574
1776
|
[ Opcodes.end ]
|
1575
1777
|
];
|
1576
1778
|
}
|
@@ -1596,10 +1798,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1596
1798
|
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1597
1799
|
];
|
1598
1800
|
}
|
1801
|
+
|
1802
|
+
if (Object.keys(protoBC).length > 0) {
|
1803
|
+
return typeSwitch(scope, getNodeType(scope, target), {
|
1804
|
+
...protoBC,
|
1805
|
+
|
1806
|
+
// TODO: error better
|
1807
|
+
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1808
|
+
}, valtypeBinary);
|
1809
|
+
}
|
1599
1810
|
}
|
1600
1811
|
|
1601
1812
|
// TODO: only allows callee as literal
|
1602
|
-
if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
|
1813
|
+
if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
|
1603
1814
|
|
1604
1815
|
let idx = funcIndex[name] ?? importedFuncs[name];
|
1605
1816
|
if (idx === undefined && builtinFuncs[name]) {
|
@@ -1632,16 +1843,65 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1632
1843
|
idx = -1;
|
1633
1844
|
}
|
1634
1845
|
|
1846
|
+
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1847
|
+
const wasmOps = {
|
1848
|
+
// pointer, align, offset
|
1849
|
+
i32_load: { imms: 2, args: [ true ], returns: 1 },
|
1850
|
+
// pointer, value, align, offset
|
1851
|
+
i32_store: { imms: 2, args: [ true, true ], returns: 0 },
|
1852
|
+
// pointer, align, offset
|
1853
|
+
i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
|
1854
|
+
// pointer, value, align, offset
|
1855
|
+
i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
|
1856
|
+
// pointer, align, offset
|
1857
|
+
i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
|
1858
|
+
// pointer, value, align, offset
|
1859
|
+
i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
|
1860
|
+
|
1861
|
+
// pointer, align, offset
|
1862
|
+
f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
|
1863
|
+
// pointer, value, align, offset
|
1864
|
+
f64_store: { imms: 2, args: [ true, false ], returns: 0 },
|
1865
|
+
|
1866
|
+
// value
|
1867
|
+
i32_const: { imms: 1, args: [], returns: 1 },
|
1868
|
+
|
1869
|
+
// a, b
|
1870
|
+
i32_or: { imms: 0, args: [ true, true ], returns: 1 },
|
1871
|
+
};
|
1872
|
+
|
1873
|
+
const opName = name.slice('__Porffor_wasm_'.length);
|
1874
|
+
|
1875
|
+
if (wasmOps[opName]) {
|
1876
|
+
const op = wasmOps[opName];
|
1877
|
+
|
1878
|
+
const argOut = [];
|
1879
|
+
for (let i = 0; i < op.args.length; i++) argOut.push(
|
1880
|
+
...generate(scope, decl.arguments[i]),
|
1881
|
+
...(op.args[i] ? [ Opcodes.i32_to ] : [])
|
1882
|
+
);
|
1883
|
+
|
1884
|
+
// literals only
|
1885
|
+
const imms = decl.arguments.slice(op.args.length).map(x => x.value);
|
1886
|
+
|
1887
|
+
return [
|
1888
|
+
...argOut,
|
1889
|
+
[ Opcodes[opName], ...imms ],
|
1890
|
+
...(new Array(op.returns).fill(Opcodes.i32_from))
|
1891
|
+
];
|
1892
|
+
}
|
1893
|
+
}
|
1894
|
+
|
1635
1895
|
if (idx === undefined) {
|
1636
|
-
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function
|
1637
|
-
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined
|
1896
|
+
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1897
|
+
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
1638
1898
|
}
|
1639
1899
|
|
1640
1900
|
const func = funcs.find(x => x.index === idx);
|
1641
1901
|
|
1642
1902
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1643
1903
|
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1644
|
-
const
|
1904
|
+
const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
|
1645
1905
|
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1646
1906
|
|
1647
1907
|
let args = decl.arguments;
|
@@ -1658,14 +1918,24 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1658
1918
|
if (func && func.throws) scope.throws = true;
|
1659
1919
|
|
1660
1920
|
let out = [];
|
1661
|
-
for (
|
1921
|
+
for (let i = 0; i < args.length; i++) {
|
1922
|
+
const arg = args[i];
|
1662
1923
|
out = out.concat(generate(scope, arg));
|
1924
|
+
|
1925
|
+
if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1926
|
+
out.push(Opcodes.i32_to);
|
1927
|
+
}
|
1928
|
+
|
1929
|
+
if (importedFuncs[name] && name.startsWith('profile')) {
|
1930
|
+
out.push(Opcodes.i32_to);
|
1931
|
+
}
|
1932
|
+
|
1663
1933
|
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1664
1934
|
}
|
1665
1935
|
|
1666
1936
|
out.push([ Opcodes.call, idx ]);
|
1667
1937
|
|
1668
|
-
if (!
|
1938
|
+
if (!typedReturns) {
|
1669
1939
|
// let type;
|
1670
1940
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1671
1941
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1675,7 +1945,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1675
1945
|
// ...number(type, Valtype.i32),
|
1676
1946
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1677
1947
|
// );
|
1678
|
-
} else out.push(setLastType(scope));
|
1948
|
+
} else out.push(...setLastType(scope));
|
1949
|
+
|
1950
|
+
if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1951
|
+
out.push(Opcodes.i32_from);
|
1952
|
+
}
|
1679
1953
|
|
1680
1954
|
return out;
|
1681
1955
|
};
|
@@ -1683,8 +1957,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1683
1957
|
const generateNew = (scope, decl, _global, _name) => {
|
1684
1958
|
// hack: basically treat this as a normal call for builtins for now
|
1685
1959
|
const name = mapName(decl.callee.name);
|
1960
|
+
|
1686
1961
|
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1687
|
-
|
1962
|
+
|
1963
|
+
if (builtinFuncs[name + '$constructor']) {
|
1964
|
+
// custom ...$constructor override builtin func
|
1965
|
+
return generateCall(scope, {
|
1966
|
+
...decl,
|
1967
|
+
callee: {
|
1968
|
+
type: 'Identifier',
|
1969
|
+
name: name + '$constructor'
|
1970
|
+
}
|
1971
|
+
}, _global, _name);
|
1972
|
+
}
|
1973
|
+
|
1974
|
+
if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
|
1688
1975
|
|
1689
1976
|
return generateCall(scope, decl, _global, _name);
|
1690
1977
|
};
|
@@ -1801,14 +2088,14 @@ const brTable = (input, bc, returns) => {
|
|
1801
2088
|
};
|
1802
2089
|
|
1803
2090
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1804
|
-
if (!
|
2091
|
+
if (!Prefs.bytestring) delete bc[TYPES._bytestring];
|
1805
2092
|
|
1806
2093
|
const known = knownType(scope, type);
|
1807
2094
|
if (known != null) {
|
1808
2095
|
return bc[known] ?? bc.default;
|
1809
2096
|
}
|
1810
2097
|
|
1811
|
-
if (
|
2098
|
+
if (Prefs.typeswitchUseBrtable)
|
1812
2099
|
return brTable(type, bc, returns);
|
1813
2100
|
|
1814
2101
|
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
@@ -1818,8 +2105,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1818
2105
|
[ Opcodes.block, returns ]
|
1819
2106
|
];
|
1820
2107
|
|
1821
|
-
// todo: use br_table?
|
1822
|
-
|
1823
2108
|
for (const x in bc) {
|
1824
2109
|
if (x === 'default') continue;
|
1825
2110
|
|
@@ -1843,7 +2128,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1843
2128
|
return out;
|
1844
2129
|
};
|
1845
2130
|
|
1846
|
-
const allocVar = (scope, name, global = false) => {
|
2131
|
+
const allocVar = (scope, name, global = false, type = true) => {
|
1847
2132
|
const target = global ? globals : scope.locals;
|
1848
2133
|
|
1849
2134
|
// already declared
|
@@ -1857,8 +2142,10 @@ const allocVar = (scope, name, global = false) => {
|
|
1857
2142
|
let idx = global ? globalInd++ : scope.localInd++;
|
1858
2143
|
target[name] = { idx, type: valtypeBinary };
|
1859
2144
|
|
1860
|
-
|
1861
|
-
|
2145
|
+
if (type) {
|
2146
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
2147
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
2148
|
+
}
|
1862
2149
|
|
1863
2150
|
return idx;
|
1864
2151
|
};
|
@@ -1873,11 +2160,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
|
1873
2160
|
};
|
1874
2161
|
|
1875
2162
|
const typeAnnoToPorfType = x => {
|
1876
|
-
if (
|
1877
|
-
if (TYPES[
|
2163
|
+
if (!x) return null;
|
2164
|
+
if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
|
2165
|
+
if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
|
1878
2166
|
|
1879
2167
|
switch (x) {
|
1880
2168
|
case 'i32':
|
2169
|
+
case 'i64':
|
2170
|
+
case 'f64':
|
1881
2171
|
return TYPES.number;
|
1882
2172
|
}
|
1883
2173
|
|
@@ -1888,7 +2178,7 @@ const extractTypeAnnotation = decl => {
|
|
1888
2178
|
let a = decl;
|
1889
2179
|
while (a.typeAnnotation) a = a.typeAnnotation;
|
1890
2180
|
|
1891
|
-
let type, elementType;
|
2181
|
+
let type = null, elementType = null;
|
1892
2182
|
if (a.typeName) {
|
1893
2183
|
type = a.typeName.name;
|
1894
2184
|
} else if (a.type.endsWith('Keyword')) {
|
@@ -1901,7 +2191,7 @@ const extractTypeAnnotation = decl => {
|
|
1901
2191
|
const typeName = type;
|
1902
2192
|
type = typeAnnoToPorfType(type);
|
1903
2193
|
|
1904
|
-
if (type === TYPES._bytestring && !
|
2194
|
+
if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
|
1905
2195
|
|
1906
2196
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1907
2197
|
|
@@ -1919,7 +2209,7 @@ const generateVar = (scope, decl) => {
|
|
1919
2209
|
for (const x of decl.declarations) {
|
1920
2210
|
const name = mapName(x.id.name);
|
1921
2211
|
|
1922
|
-
if (!name) return todo('destructuring is not supported yet');
|
2212
|
+
if (!name) return todo(scope, 'destructuring is not supported yet');
|
1923
2213
|
|
1924
2214
|
if (x.init && isFuncType(x.init.type)) {
|
1925
2215
|
// hack for let a = function () { ... }
|
@@ -1936,9 +2226,10 @@ const generateVar = (scope, decl) => {
|
|
1936
2226
|
continue; // always ignore
|
1937
2227
|
}
|
1938
2228
|
|
1939
|
-
|
2229
|
+
const typed = typedInput && x.id.typeAnnotation;
|
2230
|
+
let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
|
1940
2231
|
|
1941
|
-
if (
|
2232
|
+
if (typed) {
|
1942
2233
|
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1943
2234
|
}
|
1944
2235
|
|
@@ -1956,7 +2247,8 @@ const generateVar = (scope, decl) => {
|
|
1956
2247
|
return out;
|
1957
2248
|
};
|
1958
2249
|
|
1959
|
-
|
2250
|
+
// todo: optimize this func for valueUnused
|
2251
|
+
const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
1960
2252
|
const { type, name } = decl.left;
|
1961
2253
|
|
1962
2254
|
if (type === 'ObjectPattern') {
|
@@ -1974,9 +2266,9 @@ const generateAssign = (scope, decl) => {
|
|
1974
2266
|
// hack: .length setter
|
1975
2267
|
if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
|
1976
2268
|
const name = decl.left.object.name;
|
1977
|
-
const pointer = arrays
|
2269
|
+
const pointer = scope.arrays?.get(name);
|
1978
2270
|
|
1979
|
-
const aotPointer = pointer != null;
|
2271
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1980
2272
|
|
1981
2273
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
1982
2274
|
|
@@ -2001,9 +2293,9 @@ const generateAssign = (scope, decl) => {
|
|
2001
2293
|
// arr[i]
|
2002
2294
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
2003
2295
|
const name = decl.left.object.name;
|
2004
|
-
const pointer = arrays
|
2296
|
+
const pointer = scope.arrays?.get(name);
|
2005
2297
|
|
2006
|
-
const aotPointer = pointer != null;
|
2298
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2007
2299
|
|
2008
2300
|
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
2009
2301
|
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
@@ -2059,7 +2351,7 @@ const generateAssign = (scope, decl) => {
|
|
2059
2351
|
];
|
2060
2352
|
}
|
2061
2353
|
|
2062
|
-
if (!name) return todo('destructuring is not supported yet');
|
2354
|
+
if (!name) return todo(scope, 'destructuring is not supported yet', true);
|
2063
2355
|
|
2064
2356
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2065
2357
|
|
@@ -2107,9 +2399,7 @@ const generateAssign = (scope, decl) => {
|
|
2107
2399
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
2108
2400
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
2109
2401
|
|
2110
|
-
getLastType(scope)
|
2111
|
-
// hack: type is idx+1
|
2112
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2402
|
+
...setType(scope, name, getLastType(scope))
|
2113
2403
|
];
|
2114
2404
|
}
|
2115
2405
|
|
@@ -2120,9 +2410,7 @@ const generateAssign = (scope, decl) => {
|
|
2120
2410
|
|
2121
2411
|
// todo: string concat types
|
2122
2412
|
|
2123
|
-
|
2124
|
-
...number(TYPES.number, Valtype.i32),
|
2125
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2413
|
+
...setType(scope, name, TYPES.number)
|
2126
2414
|
];
|
2127
2415
|
};
|
2128
2416
|
|
@@ -2168,7 +2456,7 @@ const generateUnary = (scope, decl) => {
|
|
2168
2456
|
return out;
|
2169
2457
|
}
|
2170
2458
|
|
2171
|
-
case 'delete':
|
2459
|
+
case 'delete': {
|
2172
2460
|
let toReturn = true, toGenerate = true;
|
2173
2461
|
|
2174
2462
|
if (decl.argument.type === 'Identifier') {
|
@@ -2190,9 +2478,26 @@ const generateUnary = (scope, decl) => {
|
|
2190
2478
|
|
2191
2479
|
out.push(...number(toReturn ? 1 : 0));
|
2192
2480
|
return out;
|
2481
|
+
}
|
2482
|
+
|
2483
|
+
case 'typeof': {
|
2484
|
+
let overrideType, toGenerate = true;
|
2485
|
+
|
2486
|
+
if (decl.argument.type === 'Identifier') {
|
2487
|
+
const out = generateIdent(scope, decl.argument);
|
2488
|
+
|
2489
|
+
// if ReferenceError (undeclared var), ignore and return undefined
|
2490
|
+
if (out[1]) {
|
2491
|
+
// does not exist (2 ops from throw)
|
2492
|
+
overrideType = number(TYPES.undefined, Valtype.i32);
|
2493
|
+
toGenerate = false;
|
2494
|
+
}
|
2495
|
+
}
|
2193
2496
|
|
2194
|
-
|
2195
|
-
|
2497
|
+
const out = toGenerate ? generate(scope, decl.argument) : [];
|
2498
|
+
disposeLeftover(out);
|
2499
|
+
|
2500
|
+
out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
|
2196
2501
|
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
2197
2502
|
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
2198
2503
|
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
@@ -2203,27 +2508,30 @@ const generateUnary = (scope, decl) => {
|
|
2203
2508
|
|
2204
2509
|
// object and internal types
|
2205
2510
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2206
|
-
});
|
2511
|
+
}));
|
2512
|
+
|
2513
|
+
return out;
|
2514
|
+
}
|
2207
2515
|
|
2208
2516
|
default:
|
2209
|
-
return todo(`unary operator ${decl.operator} not implemented yet
|
2517
|
+
return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
|
2210
2518
|
}
|
2211
2519
|
};
|
2212
2520
|
|
2213
|
-
const generateUpdate = (scope, decl) => {
|
2521
|
+
const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
|
2214
2522
|
const { name } = decl.argument;
|
2215
2523
|
|
2216
2524
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2217
2525
|
|
2218
2526
|
if (local === undefined) {
|
2219
|
-
return todo(`update expression with undefined variable
|
2527
|
+
return todo(scope, `update expression with undefined variable`, true);
|
2220
2528
|
}
|
2221
2529
|
|
2222
2530
|
const idx = local.idx;
|
2223
2531
|
const out = [];
|
2224
2532
|
|
2225
2533
|
out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2226
|
-
if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2534
|
+
if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2227
2535
|
|
2228
2536
|
switch (decl.operator) {
|
2229
2537
|
case '++':
|
@@ -2236,7 +2544,7 @@ const generateUpdate = (scope, decl) => {
|
|
2236
2544
|
}
|
2237
2545
|
|
2238
2546
|
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2239
|
-
if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2547
|
+
if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2240
2548
|
|
2241
2549
|
return out;
|
2242
2550
|
};
|
@@ -2276,7 +2584,7 @@ const generateConditional = (scope, decl) => {
|
|
2276
2584
|
// note type
|
2277
2585
|
out.push(
|
2278
2586
|
...getNodeType(scope, decl.consequent),
|
2279
|
-
setLastType(scope)
|
2587
|
+
...setLastType(scope)
|
2280
2588
|
);
|
2281
2589
|
|
2282
2590
|
out.push([ Opcodes.else ]);
|
@@ -2285,7 +2593,7 @@ const generateConditional = (scope, decl) => {
|
|
2285
2593
|
// note type
|
2286
2594
|
out.push(
|
2287
2595
|
...getNodeType(scope, decl.alternate),
|
2288
|
-
setLastType(scope)
|
2596
|
+
...setLastType(scope)
|
2289
2597
|
);
|
2290
2598
|
|
2291
2599
|
out.push([ Opcodes.end ]);
|
@@ -2299,7 +2607,7 @@ const generateFor = (scope, decl) => {
|
|
2299
2607
|
const out = [];
|
2300
2608
|
|
2301
2609
|
if (decl.init) {
|
2302
|
-
out.push(...generate(scope, decl.init));
|
2610
|
+
out.push(...generate(scope, decl.init, false, undefined, true));
|
2303
2611
|
disposeLeftover(out);
|
2304
2612
|
}
|
2305
2613
|
|
@@ -2317,7 +2625,7 @@ const generateFor = (scope, decl) => {
|
|
2317
2625
|
out.push(...generate(scope, decl.body));
|
2318
2626
|
out.push([ Opcodes.end ]);
|
2319
2627
|
|
2320
|
-
if (decl.update) out.push(...generate(scope, decl.update));
|
2628
|
+
if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
|
2321
2629
|
|
2322
2630
|
out.push([ Opcodes.br, 1 ]);
|
2323
2631
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2345,6 +2653,36 @@ const generateWhile = (scope, decl) => {
|
|
2345
2653
|
return out;
|
2346
2654
|
};
|
2347
2655
|
|
2656
|
+
const generateDoWhile = (scope, decl) => {
|
2657
|
+
const out = [];
|
2658
|
+
|
2659
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
2660
|
+
depth.push('dowhile');
|
2661
|
+
|
2662
|
+
// block for break (includes all)
|
2663
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2664
|
+
depth.push('block');
|
2665
|
+
|
2666
|
+
// block for continue
|
2667
|
+
// includes body but not test+loop so we can exit body at anytime
|
2668
|
+
// and still test+loop after
|
2669
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2670
|
+
depth.push('block');
|
2671
|
+
|
2672
|
+
out.push(...generate(scope, decl.body));
|
2673
|
+
|
2674
|
+
out.push([ Opcodes.end ]);
|
2675
|
+
depth.pop();
|
2676
|
+
|
2677
|
+
out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2678
|
+
out.push([ Opcodes.br_if, 1 ]);
|
2679
|
+
|
2680
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
2681
|
+
depth.pop(); depth.pop();
|
2682
|
+
|
2683
|
+
return out;
|
2684
|
+
};
|
2685
|
+
|
2348
2686
|
const generateForOf = (scope, decl) => {
|
2349
2687
|
const out = [];
|
2350
2688
|
|
@@ -2381,7 +2719,10 @@ const generateForOf = (scope, decl) => {
|
|
2381
2719
|
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2382
2720
|
}
|
2383
2721
|
|
2722
|
+
// if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
|
2723
|
+
|
2384
2724
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2725
|
+
if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
|
2385
2726
|
|
2386
2727
|
depth.push('block');
|
2387
2728
|
depth.push('block');
|
@@ -2390,6 +2731,7 @@ const generateForOf = (scope, decl) => {
|
|
2390
2731
|
// hack: this is naughty and will break things!
|
2391
2732
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2392
2733
|
if (pages.hasAnyString) {
|
2734
|
+
// todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
|
2393
2735
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2394
2736
|
rawElements: new Array(1)
|
2395
2737
|
}, isGlobal, leftName, true, 'i16');
|
@@ -2481,6 +2823,56 @@ const generateForOf = (scope, decl) => {
|
|
2481
2823
|
[ Opcodes.end ],
|
2482
2824
|
[ Opcodes.end ]
|
2483
2825
|
],
|
2826
|
+
[TYPES._bytestring]: [
|
2827
|
+
...setType(scope, leftName, TYPES._bytestring),
|
2828
|
+
|
2829
|
+
[ Opcodes.loop, Blocktype.void ],
|
2830
|
+
|
2831
|
+
// setup new/out array
|
2832
|
+
...newOut,
|
2833
|
+
[ Opcodes.drop ],
|
2834
|
+
|
2835
|
+
...number(0, Valtype.i32), // base 0 for store after
|
2836
|
+
|
2837
|
+
// load current string ind {arg}
|
2838
|
+
[ Opcodes.local_get, pointer ],
|
2839
|
+
[ Opcodes.local_get, counter ],
|
2840
|
+
[ Opcodes.i32_add ],
|
2841
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
2842
|
+
|
2843
|
+
// store to new string ind 0
|
2844
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2845
|
+
|
2846
|
+
// return new string (page)
|
2847
|
+
...number(newPointer),
|
2848
|
+
|
2849
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2850
|
+
|
2851
|
+
[ Opcodes.block, Blocktype.void ],
|
2852
|
+
[ Opcodes.block, Blocktype.void ],
|
2853
|
+
...generate(scope, decl.body),
|
2854
|
+
[ Opcodes.end ],
|
2855
|
+
|
2856
|
+
// increment iter pointer
|
2857
|
+
// [ Opcodes.local_get, pointer ],
|
2858
|
+
// ...number(1, Valtype.i32),
|
2859
|
+
// [ Opcodes.i32_add ],
|
2860
|
+
// [ Opcodes.local_set, pointer ],
|
2861
|
+
|
2862
|
+
// increment counter by 1
|
2863
|
+
[ Opcodes.local_get, counter ],
|
2864
|
+
...number(1, Valtype.i32),
|
2865
|
+
[ Opcodes.i32_add ],
|
2866
|
+
[ Opcodes.local_tee, counter ],
|
2867
|
+
|
2868
|
+
// loop if counter != length
|
2869
|
+
[ Opcodes.local_get, length ],
|
2870
|
+
[ Opcodes.i32_ne ],
|
2871
|
+
[ Opcodes.br_if, 1 ],
|
2872
|
+
|
2873
|
+
[ Opcodes.end ],
|
2874
|
+
[ Opcodes.end ]
|
2875
|
+
],
|
2484
2876
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2485
2877
|
}, Blocktype.void));
|
2486
2878
|
|
@@ -2491,28 +2883,65 @@ const generateForOf = (scope, decl) => {
|
|
2491
2883
|
return out;
|
2492
2884
|
};
|
2493
2885
|
|
2886
|
+
// find the nearest loop in depth map by type
|
2494
2887
|
const getNearestLoop = () => {
|
2495
2888
|
for (let i = depth.length - 1; i >= 0; i--) {
|
2496
|
-
if (
|
2889
|
+
if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
|
2497
2890
|
}
|
2498
2891
|
|
2499
2892
|
return -1;
|
2500
2893
|
};
|
2501
2894
|
|
2502
2895
|
const generateBreak = (scope, decl) => {
|
2503
|
-
const
|
2896
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2897
|
+
const type = depth[target];
|
2898
|
+
|
2899
|
+
// different loop types have different branch offsets
|
2900
|
+
// as they have different wasm block/loop/if structures
|
2901
|
+
// we need to use the right offset by type to branch to the one we want
|
2902
|
+
// for a break: exit the loop without executing anything else inside it
|
2903
|
+
const offset = ({
|
2904
|
+
for: 2, // loop > if (wanted branch) > block (we are here)
|
2905
|
+
while: 2, // loop > if (wanted branch) (we are here)
|
2906
|
+
dowhile: 2, // loop > block (wanted branch) > block (we are here)
|
2907
|
+
forof: 2, // loop > block (wanted branch) > block (we are here)
|
2908
|
+
if: 1 // break inside if, branch 0 to skip the rest of the if
|
2909
|
+
})[type];
|
2910
|
+
|
2504
2911
|
return [
|
2505
|
-
[ Opcodes.br, ...signedLEB128(
|
2912
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2506
2913
|
];
|
2507
2914
|
};
|
2508
2915
|
|
2509
2916
|
const generateContinue = (scope, decl) => {
|
2510
|
-
const
|
2917
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2918
|
+
const type = depth[target];
|
2919
|
+
|
2920
|
+
// different loop types have different branch offsets
|
2921
|
+
// as they have different wasm block/loop/if structures
|
2922
|
+
// we need to use the right offset by type to branch to the one we want
|
2923
|
+
// for a continue: do test for the loop, and then loop depending on that success
|
2924
|
+
const offset = ({
|
2925
|
+
for: 3, // loop (wanted branch) > if > block (we are here)
|
2926
|
+
while: 1, // loop (wanted branch) > if (we are here)
|
2927
|
+
dowhile: 3, // loop > block > block (wanted branch) (we are here)
|
2928
|
+
forof: 3 // loop > block > block (wanted branch) (we are here)
|
2929
|
+
})[type];
|
2930
|
+
|
2511
2931
|
return [
|
2512
|
-
[ Opcodes.br, ...signedLEB128(
|
2932
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2513
2933
|
];
|
2514
2934
|
};
|
2515
2935
|
|
2936
|
+
const generateLabel = (scope, decl) => {
|
2937
|
+
scope.labels ??= new Map();
|
2938
|
+
|
2939
|
+
const name = decl.label.name;
|
2940
|
+
scope.labels.set(name, depth.length);
|
2941
|
+
|
2942
|
+
return generate(scope, decl.body);
|
2943
|
+
};
|
2944
|
+
|
2516
2945
|
const generateThrow = (scope, decl) => {
|
2517
2946
|
scope.throws = true;
|
2518
2947
|
|
@@ -2533,6 +2962,9 @@ const generateThrow = (scope, decl) => {
|
|
2533
2962
|
let exceptId = exceptions.push({ constructor, message }) - 1;
|
2534
2963
|
let tagIdx = tags[0].idx;
|
2535
2964
|
|
2965
|
+
scope.exceptions ??= [];
|
2966
|
+
scope.exceptions.push(exceptId);
|
2967
|
+
|
2536
2968
|
// todo: write a description of how this works lol
|
2537
2969
|
|
2538
2970
|
return [
|
@@ -2542,7 +2974,7 @@ const generateThrow = (scope, decl) => {
|
|
2542
2974
|
};
|
2543
2975
|
|
2544
2976
|
const generateTry = (scope, decl) => {
|
2545
|
-
if (decl.finalizer) return todo('try finally not implemented yet');
|
2977
|
+
if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
|
2546
2978
|
|
2547
2979
|
const out = [];
|
2548
2980
|
|
@@ -2573,11 +3005,11 @@ const generateAssignPat = (scope, decl) => {
|
|
2573
3005
|
// TODO
|
2574
3006
|
// if identifier declared, use that
|
2575
3007
|
// else, use default (right)
|
2576
|
-
return todo('assignment pattern (optional arg)');
|
3008
|
+
return todo(scope, 'assignment pattern (optional arg)');
|
2577
3009
|
};
|
2578
3010
|
|
2579
3011
|
let pages = new Map();
|
2580
|
-
const allocPage = (reason, type) => {
|
3012
|
+
const allocPage = (scope, reason, type) => {
|
2581
3013
|
if (pages.has(reason)) return pages.get(reason).ind;
|
2582
3014
|
|
2583
3015
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
@@ -2588,16 +3020,20 @@ const allocPage = (reason, type) => {
|
|
2588
3020
|
const ind = pages.size;
|
2589
3021
|
pages.set(reason, { ind, type });
|
2590
3022
|
|
2591
|
-
|
3023
|
+
scope.pages ??= new Map();
|
3024
|
+
scope.pages.set(reason, { ind, type });
|
3025
|
+
|
3026
|
+
if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2592
3027
|
|
2593
3028
|
return ind;
|
2594
3029
|
};
|
2595
3030
|
|
3031
|
+
// todo: add scope.pages
|
2596
3032
|
const freePage = reason => {
|
2597
3033
|
const { ind } = pages.get(reason);
|
2598
3034
|
pages.delete(reason);
|
2599
3035
|
|
2600
|
-
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
3036
|
+
if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2601
3037
|
|
2602
3038
|
return ind;
|
2603
3039
|
};
|
@@ -2623,15 +3059,14 @@ const StoreOps = {
|
|
2623
3059
|
|
2624
3060
|
let data = [];
|
2625
3061
|
|
2626
|
-
const compileBytes = (val, itemType
|
3062
|
+
const compileBytes = (val, itemType) => {
|
2627
3063
|
// todo: this is a mess and needs confirming / ????
|
2628
3064
|
switch (itemType) {
|
2629
3065
|
case 'i8': return [ val % 256 ];
|
2630
|
-
case 'i16': return [ val % 256,
|
2631
|
-
|
2632
|
-
case 'i32':
|
2633
|
-
|
2634
|
-
return enforceFourBytes(signedLEB128(val));
|
3066
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3067
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3068
|
+
case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
|
3069
|
+
// todo: i64
|
2635
3070
|
|
2636
3071
|
case 'f64': return ieee754_binary64(val);
|
2637
3072
|
}
|
@@ -2649,16 +3084,20 @@ const getAllocType = itemType => {
|
|
2649
3084
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2650
3085
|
const out = [];
|
2651
3086
|
|
3087
|
+
scope.arrays ??= new Map();
|
3088
|
+
|
2652
3089
|
let firstAssign = false;
|
2653
|
-
if (!arrays.has(name) || name === '$undeclared') {
|
3090
|
+
if (!scope.arrays.has(name) || name === '$undeclared') {
|
2654
3091
|
firstAssign = true;
|
2655
3092
|
|
2656
3093
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2657
3094
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2658
|
-
|
3095
|
+
|
3096
|
+
if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${scope.name} | ${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
3097
|
+
else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2659
3098
|
}
|
2660
3099
|
|
2661
|
-
const pointer = arrays.get(name);
|
3100
|
+
const pointer = scope.arrays.get(name);
|
2662
3101
|
|
2663
3102
|
const useRawElements = !!decl.rawElements;
|
2664
3103
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
@@ -2666,19 +3105,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2666
3105
|
const valtype = itemTypeToValtype[itemType];
|
2667
3106
|
const length = elements.length;
|
2668
3107
|
|
2669
|
-
if (firstAssign && useRawElements) {
|
2670
|
-
|
3108
|
+
if (firstAssign && useRawElements && !Prefs.noData) {
|
3109
|
+
// if length is 0 memory/data will just be 0000... anyway
|
3110
|
+
if (length !== 0) {
|
3111
|
+
let bytes = compileBytes(length, 'i32');
|
2671
3112
|
|
2672
|
-
|
2673
|
-
|
3113
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
3114
|
+
if (elements[i] == null) continue;
|
2674
3115
|
|
2675
|
-
|
2676
|
-
|
3116
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
3117
|
+
}
|
2677
3118
|
|
2678
|
-
|
2679
|
-
|
2680
|
-
|
2681
|
-
|
3119
|
+
const ind = data.push({
|
3120
|
+
offset: pointer,
|
3121
|
+
bytes
|
3122
|
+
}) - 1;
|
3123
|
+
|
3124
|
+
scope.data ??= [];
|
3125
|
+
scope.data.push(ind);
|
3126
|
+
}
|
2682
3127
|
|
2683
3128
|
// local value as pointer
|
2684
3129
|
out.push(...number(pointer));
|
@@ -2712,7 +3157,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2712
3157
|
};
|
2713
3158
|
|
2714
3159
|
const byteStringable = str => {
|
2715
|
-
if (!
|
3160
|
+
if (!Prefs.bytestring) return false;
|
2716
3161
|
|
2717
3162
|
for (let i = 0; i < str.length; i++) {
|
2718
3163
|
if (str.charCodeAt(i) > 0xFF) return false;
|
@@ -2721,9 +3166,9 @@ const byteStringable = str => {
|
|
2721
3166
|
return true;
|
2722
3167
|
};
|
2723
3168
|
|
2724
|
-
const makeString = (scope, str, global = false, name = '$undeclared') => {
|
3169
|
+
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2725
3170
|
const rawElements = new Array(str.length);
|
2726
|
-
let byteStringable =
|
3171
|
+
let byteStringable = Prefs.bytestring;
|
2727
3172
|
for (let i = 0; i < str.length; i++) {
|
2728
3173
|
const c = str.charCodeAt(i);
|
2729
3174
|
rawElements[i] = c;
|
@@ -2731,25 +3176,36 @@ const makeString = (scope, str, global = false, name = '$undeclared') => {
|
|
2731
3176
|
if (byteStringable && c > 0xFF) byteStringable = false;
|
2732
3177
|
}
|
2733
3178
|
|
3179
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
3180
|
+
|
2734
3181
|
return makeArray(scope, {
|
2735
3182
|
rawElements
|
2736
3183
|
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2737
3184
|
};
|
2738
3185
|
|
2739
|
-
let arrays = new Map();
|
2740
3186
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
2741
3187
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
2742
3188
|
};
|
2743
3189
|
|
2744
3190
|
export const generateMember = (scope, decl, _global, _name) => {
|
2745
3191
|
const name = decl.object.name;
|
2746
|
-
const pointer = arrays
|
3192
|
+
const pointer = scope.arrays?.get(name);
|
2747
3193
|
|
2748
|
-
const aotPointer = pointer != null;
|
3194
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2749
3195
|
|
2750
3196
|
// hack: .length
|
2751
3197
|
if (decl.property.name === 'length') {
|
2752
|
-
|
3198
|
+
const func = funcs.find(x => x.name === name);
|
3199
|
+
if (func) {
|
3200
|
+
const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
|
3201
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
3202
|
+
return number(typedParams ? func.params.length / 2 : func.params.length);
|
3203
|
+
}
|
3204
|
+
|
3205
|
+
if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
|
3206
|
+
if (importedFuncs[name]) return number(importedFuncs[name].params);
|
3207
|
+
if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
|
3208
|
+
|
2753
3209
|
return [
|
2754
3210
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2755
3211
|
...generate(scope, decl.object),
|
@@ -2793,7 +3249,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2793
3249
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2794
3250
|
|
2795
3251
|
...number(TYPES.number, Valtype.i32),
|
2796
|
-
setLastType(scope)
|
3252
|
+
...setLastType(scope)
|
2797
3253
|
],
|
2798
3254
|
|
2799
3255
|
[TYPES.string]: [
|
@@ -2825,7 +3281,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2825
3281
|
...number(newPointer),
|
2826
3282
|
|
2827
3283
|
...number(TYPES.string, Valtype.i32),
|
2828
|
-
setLastType(scope)
|
3284
|
+
...setLastType(scope)
|
2829
3285
|
],
|
2830
3286
|
[TYPES._bytestring]: [
|
2831
3287
|
// setup new/out array
|
@@ -2844,19 +3300,19 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2844
3300
|
]),
|
2845
3301
|
|
2846
3302
|
// load current string ind {arg}
|
2847
|
-
[ Opcodes.i32_load8_u,
|
3303
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2848
3304
|
|
2849
3305
|
// store to new string ind 0
|
2850
|
-
[ Opcodes.i32_store8,
|
3306
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2851
3307
|
|
2852
3308
|
// return new string (page)
|
2853
3309
|
...number(newPointer),
|
2854
3310
|
|
2855
3311
|
...number(TYPES._bytestring, Valtype.i32),
|
2856
|
-
setLastType(scope)
|
3312
|
+
...setLastType(scope)
|
2857
3313
|
],
|
2858
3314
|
|
2859
|
-
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
|
3315
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
|
2860
3316
|
});
|
2861
3317
|
};
|
2862
3318
|
|
@@ -2866,28 +3322,36 @@ const objectHack = node => {
|
|
2866
3322
|
if (!node) return node;
|
2867
3323
|
|
2868
3324
|
if (node.type === 'MemberExpression') {
|
2869
|
-
|
3325
|
+
const out = (() => {
|
3326
|
+
if (node.computed || node.optional) return;
|
3327
|
+
|
3328
|
+
let objectName = node.object.name;
|
2870
3329
|
|
2871
|
-
|
3330
|
+
// if object is not identifier or another member exp, give up
|
3331
|
+
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
|
3332
|
+
if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
|
2872
3333
|
|
2873
|
-
|
2874
|
-
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
3334
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2875
3335
|
|
2876
|
-
|
3336
|
+
// if .length, give up (hack within a hack!)
|
3337
|
+
if (node.property.name === 'length') {
|
3338
|
+
node.object = objectHack(node.object);
|
3339
|
+
return;
|
3340
|
+
}
|
2877
3341
|
|
2878
|
-
|
2879
|
-
|
3342
|
+
// no object name, give up
|
3343
|
+
if (!objectName) return;
|
2880
3344
|
|
2881
|
-
|
2882
|
-
|
3345
|
+
const name = '__' + objectName + '_' + node.property.name;
|
3346
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2883
3347
|
|
2884
|
-
|
2885
|
-
|
3348
|
+
return {
|
3349
|
+
type: 'Identifier',
|
3350
|
+
name
|
3351
|
+
};
|
3352
|
+
})();
|
2886
3353
|
|
2887
|
-
return
|
2888
|
-
type: 'Identifier',
|
2889
|
-
name
|
2890
|
-
};
|
3354
|
+
if (out) return out;
|
2891
3355
|
}
|
2892
3356
|
|
2893
3357
|
for (const x in node) {
|
@@ -2901,8 +3365,8 @@ const objectHack = node => {
|
|
2901
3365
|
};
|
2902
3366
|
|
2903
3367
|
const generateFunc = (scope, decl) => {
|
2904
|
-
if (decl.async) return todo('async functions are not supported');
|
2905
|
-
if (decl.generator) return todo('generator functions are not supported');
|
3368
|
+
if (decl.async) return todo(scope, 'async functions are not supported');
|
3369
|
+
if (decl.generator) return todo(scope, 'generator functions are not supported');
|
2906
3370
|
|
2907
3371
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2908
3372
|
const params = decl.params ?? [];
|
@@ -2918,6 +3382,11 @@ const generateFunc = (scope, decl) => {
|
|
2918
3382
|
name
|
2919
3383
|
};
|
2920
3384
|
|
3385
|
+
if (typedInput && decl.returnType) {
|
3386
|
+
innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
|
3387
|
+
innerScope.returns = [ valtypeBinary ];
|
3388
|
+
}
|
3389
|
+
|
2921
3390
|
for (let i = 0; i < params.length; i++) {
|
2922
3391
|
allocVar(innerScope, params[i].name, false);
|
2923
3392
|
|
@@ -2944,6 +3413,8 @@ const generateFunc = (scope, decl) => {
|
|
2944
3413
|
};
|
2945
3414
|
funcIndex[name] = func.index;
|
2946
3415
|
|
3416
|
+
if (name === 'main') func.gotLastType = true;
|
3417
|
+
|
2947
3418
|
// quick hack fixes
|
2948
3419
|
for (const inst of wasm) {
|
2949
3420
|
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
@@ -2978,16 +3449,6 @@ const generateCode = (scope, decl) => {
|
|
2978
3449
|
};
|
2979
3450
|
|
2980
3451
|
const internalConstrs = {
|
2981
|
-
Boolean: {
|
2982
|
-
generate: (scope, decl) => {
|
2983
|
-
if (decl.arguments.length === 0) return number(0);
|
2984
|
-
|
2985
|
-
// should generate/run all args
|
2986
|
-
return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
|
2987
|
-
},
|
2988
|
-
type: TYPES.boolean
|
2989
|
-
},
|
2990
|
-
|
2991
3452
|
Array: {
|
2992
3453
|
generate: (scope, decl, global, name) => {
|
2993
3454
|
// new Array(i0, i1, ...)
|
@@ -3005,7 +3466,7 @@ const internalConstrs = {
|
|
3005
3466
|
|
3006
3467
|
// todo: check in wasm instead of here
|
3007
3468
|
const literalValue = arg.value ?? 0;
|
3008
|
-
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
|
3469
|
+
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
|
3009
3470
|
|
3010
3471
|
return [
|
3011
3472
|
...number(0, Valtype.i32),
|
@@ -3016,7 +3477,8 @@ const internalConstrs = {
|
|
3016
3477
|
...number(pointer)
|
3017
3478
|
];
|
3018
3479
|
},
|
3019
|
-
type: TYPES._array
|
3480
|
+
type: TYPES._array,
|
3481
|
+
length: 1
|
3020
3482
|
},
|
3021
3483
|
|
3022
3484
|
__Array_of: {
|
@@ -3028,7 +3490,131 @@ const internalConstrs = {
|
|
3028
3490
|
}, global, name);
|
3029
3491
|
},
|
3030
3492
|
type: TYPES._array,
|
3493
|
+
notConstr: true,
|
3494
|
+
length: 0
|
3495
|
+
},
|
3496
|
+
|
3497
|
+
__Porffor_fastOr: {
|
3498
|
+
generate: (scope, decl) => {
|
3499
|
+
const out = [];
|
3500
|
+
|
3501
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3502
|
+
out.push(
|
3503
|
+
...generate(scope, decl.arguments[i]),
|
3504
|
+
Opcodes.i32_to_u,
|
3505
|
+
...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
|
3506
|
+
);
|
3507
|
+
}
|
3508
|
+
|
3509
|
+
out.push(Opcodes.i32_from_u);
|
3510
|
+
|
3511
|
+
return out;
|
3512
|
+
},
|
3513
|
+
type: TYPES.boolean,
|
3514
|
+
notConstr: true
|
3515
|
+
},
|
3516
|
+
|
3517
|
+
__Porffor_fastAnd: {
|
3518
|
+
generate: (scope, decl) => {
|
3519
|
+
const out = [];
|
3520
|
+
|
3521
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3522
|
+
out.push(
|
3523
|
+
...generate(scope, decl.arguments[i]),
|
3524
|
+
Opcodes.i32_to_u,
|
3525
|
+
...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
|
3526
|
+
);
|
3527
|
+
}
|
3528
|
+
|
3529
|
+
out.push(Opcodes.i32_from_u);
|
3530
|
+
|
3531
|
+
return out;
|
3532
|
+
},
|
3533
|
+
type: TYPES.boolean,
|
3031
3534
|
notConstr: true
|
3535
|
+
},
|
3536
|
+
|
3537
|
+
Boolean: {
|
3538
|
+
generate: (scope, decl) => {
|
3539
|
+
// todo: boolean object when used as constructor
|
3540
|
+
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
3541
|
+
return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
|
3542
|
+
},
|
3543
|
+
type: TYPES.boolean,
|
3544
|
+
length: 1
|
3545
|
+
},
|
3546
|
+
|
3547
|
+
__Math_max: {
|
3548
|
+
generate: (scope, decl) => {
|
3549
|
+
const out = [
|
3550
|
+
...number(-Infinity)
|
3551
|
+
];
|
3552
|
+
|
3553
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3554
|
+
out.push(
|
3555
|
+
...generate(scope, decl.arguments[i]),
|
3556
|
+
[ Opcodes.f64_max ]
|
3557
|
+
);
|
3558
|
+
}
|
3559
|
+
|
3560
|
+
return out;
|
3561
|
+
},
|
3562
|
+
type: TYPES.number,
|
3563
|
+
notConstr: true,
|
3564
|
+
length: 2
|
3565
|
+
},
|
3566
|
+
|
3567
|
+
__Math_min: {
|
3568
|
+
generate: (scope, decl) => {
|
3569
|
+
const out = [
|
3570
|
+
...number(Infinity)
|
3571
|
+
];
|
3572
|
+
|
3573
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3574
|
+
out.push(
|
3575
|
+
...generate(scope, decl.arguments[i]),
|
3576
|
+
[ Opcodes.f64_min ]
|
3577
|
+
);
|
3578
|
+
}
|
3579
|
+
|
3580
|
+
return out;
|
3581
|
+
},
|
3582
|
+
type: TYPES.number,
|
3583
|
+
notConstr: true,
|
3584
|
+
length: 2
|
3585
|
+
},
|
3586
|
+
|
3587
|
+
__console_log: {
|
3588
|
+
generate: (scope, decl) => {
|
3589
|
+
const out = [];
|
3590
|
+
|
3591
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3592
|
+
out.push(
|
3593
|
+
...generateCall(scope, {
|
3594
|
+
callee: {
|
3595
|
+
type: 'Identifier',
|
3596
|
+
name: '__Porffor_print'
|
3597
|
+
},
|
3598
|
+
arguments: [ decl.arguments[i] ]
|
3599
|
+
}),
|
3600
|
+
|
3601
|
+
// print space
|
3602
|
+
...number(32),
|
3603
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3604
|
+
);
|
3605
|
+
}
|
3606
|
+
|
3607
|
+
// print newline
|
3608
|
+
out.push(
|
3609
|
+
...number(10),
|
3610
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3611
|
+
);
|
3612
|
+
|
3613
|
+
return out;
|
3614
|
+
},
|
3615
|
+
type: TYPES.undefined,
|
3616
|
+
notConstr: true,
|
3617
|
+
length: 0
|
3032
3618
|
}
|
3033
3619
|
};
|
3034
3620
|
|
@@ -3057,7 +3643,6 @@ export default program => {
|
|
3057
3643
|
funcs = [];
|
3058
3644
|
funcIndex = {};
|
3059
3645
|
depth = [];
|
3060
|
-
arrays = new Map();
|
3061
3646
|
pages = new Map();
|
3062
3647
|
data = [];
|
3063
3648
|
currentFuncIndex = importedFuncs.length;
|
@@ -3071,6 +3656,10 @@ export default program => {
|
|
3071
3656
|
|
3072
3657
|
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
3073
3658
|
|
3659
|
+
globalThis.pageSize = PageSize;
|
3660
|
+
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
3661
|
+
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3662
|
+
|
3074
3663
|
// set generic opcodes for current valtype
|
3075
3664
|
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
3076
3665
|
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
@@ -3079,10 +3668,10 @@ export default program => {
|
|
3079
3668
|
Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
|
3080
3669
|
Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
|
3081
3670
|
|
3082
|
-
Opcodes.i32_to = [ [
|
3083
|
-
Opcodes.i32_to_u = [ [
|
3084
|
-
Opcodes.i32_from = [ [
|
3085
|
-
Opcodes.i32_from_u = [ [
|
3671
|
+
Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
|
3672
|
+
Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
|
3673
|
+
Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
|
3674
|
+
Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
|
3086
3675
|
|
3087
3676
|
Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
|
3088
3677
|
Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
|
@@ -3095,10 +3684,6 @@ export default program => {
|
|
3095
3684
|
|
3096
3685
|
program.id = { name: 'main' };
|
3097
3686
|
|
3098
|
-
globalThis.pageSize = PageSize;
|
3099
|
-
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
3100
|
-
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3101
|
-
|
3102
3687
|
const scope = {
|
3103
3688
|
locals: {},
|
3104
3689
|
localInd: 0
|
@@ -3109,7 +3694,7 @@ export default program => {
|
|
3109
3694
|
body: program.body
|
3110
3695
|
};
|
3111
3696
|
|
3112
|
-
if (
|
3697
|
+
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
3113
3698
|
|
3114
3699
|
generateFunc(scope, program);
|
3115
3700
|
|
@@ -3126,7 +3711,11 @@ export default program => {
|
|
3126
3711
|
}
|
3127
3712
|
|
3128
3713
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
3129
|
-
|
3714
|
+
if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
|
3715
|
+
main.wasm.splice(main.wasm.length - 1, 1);
|
3716
|
+
} else {
|
3717
|
+
main.returns = [];
|
3718
|
+
}
|
3130
3719
|
}
|
3131
3720
|
|
3132
3721
|
if (lastInst[0] === Opcodes.call) {
|