porffor 0.2.0-9ca9aed → 0.2.0-a6c01f5
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 +169 -76
- 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} +64 -16
- 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 +601 -272
- package/compiler/{codeGen.js → codegen.js} +1282 -484
- package/compiler/decompile.js +3 -3
- 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 +65 -29
- package/compiler/parse.js +42 -35
- package/compiler/precompile.js +123 -0
- package/compiler/prefs.js +26 -0
- package/compiler/prototype.js +177 -37
- package/compiler/types.js +37 -0
- package/compiler/wasmSpec.js +31 -7
- package/compiler/wrap.js +141 -43
- package/fib.js +7 -0
- 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 +74 -10
- package/runner/profiler.js +102 -0
- package/runner/repl.js +42 -9
- package/runner/sizes.js +37 -37
- package/compiler/builtins/base64.js +0 -92
- package/runner/info.js +0 -89
- package/runner/profile.js +0 -46
- package/runner/results.json +0 -1
- package/runner/transform.js +0 -15
- package/tmp.c +0 -71
- package/util/enum.js +0 -20
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
|
2
|
-
import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
|
2
|
+
import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from "./encoding.js";
|
3
3
|
import { operatorOpcode } from "./expression.js";
|
4
4
|
import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
|
5
5
|
import { PrototypeFuncs } from "./prototype.js";
|
@@ -7,6 +7,8 @@ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enfor
|
|
7
7
|
import { log } from "./log.js";
|
8
8
|
import parse from "./parse.js";
|
9
9
|
import * as Rhemyn from "../rhemyn/compile.js";
|
10
|
+
import Prefs from './prefs.js';
|
11
|
+
import { TYPES, TYPE_NAMES } from './types.js';
|
10
12
|
|
11
13
|
let globals = {};
|
12
14
|
let globalInd = 0;
|
@@ -23,39 +25,41 @@ const debug = str => {
|
|
23
25
|
const logChar = n => {
|
24
26
|
code.push(...number(n));
|
25
27
|
|
26
|
-
code.push(Opcodes.call);
|
27
|
-
code.push(...unsignedLEB128(0));
|
28
|
+
code.push([ Opcodes.call, 0 ]);
|
28
29
|
};
|
29
30
|
|
30
31
|
for (let i = 0; i < str.length; i++) {
|
31
32
|
logChar(str.charCodeAt(i));
|
32
33
|
}
|
33
34
|
|
34
|
-
logChar(
|
35
|
+
logChar(10); // new line
|
35
36
|
|
36
37
|
return code;
|
37
38
|
};
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
this.name = 'TodoError';
|
44
|
-
}
|
40
|
+
class TodoError extends Error {
|
41
|
+
constructor(message) {
|
42
|
+
super(message);
|
43
|
+
this.name = 'TodoError';
|
45
44
|
}
|
45
|
+
}
|
46
|
+
const todo = (scope, msg, expectsValue = undefined) => {
|
47
|
+
switch (Prefs.todoTime ?? 'runtime') {
|
48
|
+
case 'compile':
|
49
|
+
throw new TodoError(msg);
|
46
50
|
|
47
|
-
|
48
|
-
|
49
|
-
const code = [];
|
50
|
-
|
51
|
-
code.push(...debug(`todo! ` + msg));
|
52
|
-
code.push(Opcodes.unreachable);
|
51
|
+
case 'runtime':
|
52
|
+
return internalThrow(scope, 'TodoError', msg, expectsValue);
|
53
53
|
|
54
|
-
|
54
|
+
// return [
|
55
|
+
// ...debug(`todo! ${msg}`),
|
56
|
+
// [ Opcodes.unreachable ]
|
57
|
+
// ];
|
58
|
+
}
|
55
59
|
};
|
56
60
|
|
57
61
|
const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
|
58
|
-
const generate = (scope, decl, global = false, name = undefined) => {
|
62
|
+
const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
|
59
63
|
switch (decl.type) {
|
60
64
|
case 'BinaryExpression':
|
61
65
|
return generateBinaryExp(scope, decl, global, name);
|
@@ -68,7 +72,12 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
68
72
|
|
69
73
|
case 'ArrowFunctionExpression':
|
70
74
|
case 'FunctionDeclaration':
|
71
|
-
generateFunc(scope, decl);
|
75
|
+
const func = generateFunc(scope, decl);
|
76
|
+
|
77
|
+
if (decl.type.endsWith('Expression')) {
|
78
|
+
return number(func.index);
|
79
|
+
}
|
80
|
+
|
72
81
|
return [];
|
73
82
|
|
74
83
|
case 'BlockStatement':
|
@@ -81,7 +90,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
81
90
|
return generateExp(scope, decl);
|
82
91
|
|
83
92
|
case 'CallExpression':
|
84
|
-
return generateCall(scope, decl, global, name);
|
93
|
+
return generateCall(scope, decl, global, name, valueUnused);
|
85
94
|
|
86
95
|
case 'NewExpression':
|
87
96
|
return generateNew(scope, decl, global, name);
|
@@ -99,7 +108,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
99
108
|
return generateUnary(scope, decl);
|
100
109
|
|
101
110
|
case 'UpdateExpression':
|
102
|
-
return generateUpdate(scope, decl);
|
111
|
+
return generateUpdate(scope, decl, global, name, valueUnused);
|
103
112
|
|
104
113
|
case 'IfStatement':
|
105
114
|
return generateIf(scope, decl);
|
@@ -110,6 +119,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
110
119
|
case 'WhileStatement':
|
111
120
|
return generateWhile(scope, decl);
|
112
121
|
|
122
|
+
case 'DoWhileStatement':
|
123
|
+
return generateDoWhile(scope, decl);
|
124
|
+
|
113
125
|
case 'ForOfStatement':
|
114
126
|
return generateForOf(scope, decl);
|
115
127
|
|
@@ -119,6 +131,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
119
131
|
case 'ContinueStatement':
|
120
132
|
return generateContinue(scope, decl);
|
121
133
|
|
134
|
+
case 'LabeledStatement':
|
135
|
+
return generateLabel(scope, decl);
|
136
|
+
|
122
137
|
case 'EmptyStatement':
|
123
138
|
return generateEmpty(scope, decl);
|
124
139
|
|
@@ -132,7 +147,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
132
147
|
return generateTry(scope, decl);
|
133
148
|
|
134
149
|
case 'DebuggerStatement':
|
135
|
-
// todo:
|
150
|
+
// todo: hook into terminal debugger
|
136
151
|
return [];
|
137
152
|
|
138
153
|
case 'ArrayExpression':
|
@@ -146,16 +161,22 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
146
161
|
const funcsBefore = funcs.length;
|
147
162
|
generate(scope, decl.declaration);
|
148
163
|
|
149
|
-
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');
|
150
171
|
|
151
|
-
const newFunc = funcs[funcs.length - 1];
|
152
|
-
newFunc.export = true;
|
172
|
+
// const newFunc = funcs[funcs.length - 1];
|
173
|
+
// newFunc.export = true;
|
153
174
|
|
154
175
|
return [];
|
155
176
|
|
156
177
|
case 'TaggedTemplateExpression': {
|
157
178
|
const funcs = {
|
158
|
-
|
179
|
+
__Porffor_wasm: str => {
|
159
180
|
let out = [];
|
160
181
|
|
161
182
|
for (const line of str.split('\n')) {
|
@@ -163,8 +184,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
163
184
|
if (asm[0] === '') continue; // blank
|
164
185
|
|
165
186
|
if (asm[0] === 'local') {
|
166
|
-
const [ name,
|
167
|
-
scope.locals[name] = { idx:
|
187
|
+
const [ name, type ] = asm.slice(1);
|
188
|
+
scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
|
168
189
|
continue;
|
169
190
|
}
|
170
191
|
|
@@ -174,7 +195,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
174
195
|
}
|
175
196
|
|
176
197
|
if (asm[0] === 'memory') {
|
177
|
-
allocPage('asm instrinsic');
|
198
|
+
allocPage(scope, 'asm instrinsic');
|
178
199
|
// todo: add to store/load offset insts
|
179
200
|
continue;
|
180
201
|
}
|
@@ -183,38 +204,65 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
183
204
|
if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
184
205
|
|
185
206
|
if (!Array.isArray(inst)) inst = [ inst ];
|
186
|
-
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
|
+
});
|
187
212
|
|
188
|
-
out.push([ ...inst, ...immediates ]);
|
213
|
+
out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
|
189
214
|
}
|
190
215
|
|
191
216
|
return out;
|
192
217
|
},
|
193
218
|
|
194
|
-
|
195
|
-
|
219
|
+
__Porffor_bs: str => [
|
220
|
+
...makeString(scope, str, global, name, true),
|
196
221
|
|
197
|
-
|
198
|
-
...number(
|
199
|
-
|
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),
|
200
229
|
|
201
|
-
|
202
|
-
...number(
|
203
|
-
|
204
|
-
]
|
205
|
-
|
206
|
-
}
|
230
|
+
...(name ? setType(scope, name, TYPES.string) : [
|
231
|
+
...number(TYPES.string, Valtype.i32),
|
232
|
+
...setLastType(scope)
|
233
|
+
])
|
234
|
+
],
|
235
|
+
};
|
207
236
|
|
208
|
-
const
|
237
|
+
const func = decl.tag.name;
|
209
238
|
// hack for inline asm
|
210
|
-
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;
|
211
243
|
|
212
|
-
|
213
|
-
|
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
|
+
}
|
254
|
+
|
255
|
+
return funcs[func](str);
|
214
256
|
}
|
215
257
|
|
216
258
|
default:
|
217
|
-
|
259
|
+
// ignore typescript nodes
|
260
|
+
if (decl.type.startsWith('TS') ||
|
261
|
+
decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
|
262
|
+
return [];
|
263
|
+
}
|
264
|
+
|
265
|
+
return todo(scope, `no generation for ${decl.type}!`);
|
218
266
|
}
|
219
267
|
};
|
220
268
|
|
@@ -242,7 +290,7 @@ const lookupName = (scope, _name) => {
|
|
242
290
|
return [ undefined, undefined ];
|
243
291
|
};
|
244
292
|
|
245
|
-
const internalThrow = (scope, constructor, message, expectsValue =
|
293
|
+
const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
|
246
294
|
...generateThrow(scope, {
|
247
295
|
argument: {
|
248
296
|
type: 'NewExpression',
|
@@ -264,25 +312,33 @@ const generateIdent = (scope, decl) => {
|
|
264
312
|
const name = mapName(rawName);
|
265
313
|
let local = scope.locals[rawName];
|
266
314
|
|
267
|
-
if (builtinVars
|
315
|
+
if (Object.hasOwn(builtinVars, name)) {
|
268
316
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
269
|
-
|
317
|
+
|
318
|
+
let wasm = builtinVars[name];
|
319
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
|
320
|
+
return wasm.slice();
|
321
|
+
}
|
322
|
+
|
323
|
+
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
324
|
+
// todo: return an actual something
|
325
|
+
return number(1);
|
270
326
|
}
|
271
327
|
|
272
|
-
if (
|
328
|
+
if (isExistingProtoFunc(name)) {
|
273
329
|
// todo: return an actual something
|
274
330
|
return number(1);
|
275
331
|
}
|
276
332
|
|
277
|
-
if (local === undefined) {
|
333
|
+
if (local?.idx === undefined) {
|
278
334
|
// no local var with name
|
279
|
-
if (
|
280
|
-
if (funcIndex
|
335
|
+
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
336
|
+
if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
|
281
337
|
|
282
|
-
if (globals
|
338
|
+
if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
|
283
339
|
}
|
284
340
|
|
285
|
-
if (local === undefined && rawName.startsWith('__')) {
|
341
|
+
if (local?.idx === undefined && rawName.startsWith('__')) {
|
286
342
|
// return undefined if unknown key in already known var
|
287
343
|
let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
|
288
344
|
if (parent.includes('_')) parent = '__' + parent;
|
@@ -291,7 +347,7 @@ const generateIdent = (scope, decl) => {
|
|
291
347
|
if (!parentLookup[1]) return number(UNDEFINED);
|
292
348
|
}
|
293
349
|
|
294
|
-
if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
350
|
+
if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
295
351
|
|
296
352
|
return [ [ Opcodes.local_get, local.idx ] ];
|
297
353
|
};
|
@@ -304,14 +360,18 @@ const generateReturn = (scope, decl) => {
|
|
304
360
|
// just bare "return"
|
305
361
|
return [
|
306
362
|
...number(UNDEFINED), // "undefined" if func returns
|
307
|
-
...
|
363
|
+
...(scope.returnType != null ? [] : [
|
364
|
+
...number(TYPES.undefined, Valtype.i32) // type undefined
|
365
|
+
]),
|
308
366
|
[ Opcodes.return ]
|
309
367
|
];
|
310
368
|
}
|
311
369
|
|
312
370
|
return [
|
313
371
|
...generate(scope, decl.argument),
|
314
|
-
...
|
372
|
+
...(scope.returnType != null ? [] : [
|
373
|
+
...getNodeType(scope, decl.argument)
|
374
|
+
]),
|
315
375
|
[ Opcodes.return ]
|
316
376
|
];
|
317
377
|
};
|
@@ -325,7 +385,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
325
385
|
return idx;
|
326
386
|
};
|
327
387
|
|
328
|
-
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);
|
329
390
|
|
330
391
|
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
331
392
|
const checks = {
|
@@ -334,7 +395,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
334
395
|
'??': nullish
|
335
396
|
};
|
336
397
|
|
337
|
-
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);
|
338
399
|
|
339
400
|
// generic structure for {a} OP {b}
|
340
401
|
// -->
|
@@ -342,8 +403,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
342
403
|
|
343
404
|
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
344
405
|
// (like if we are in an if condition - very common)
|
345
|
-
const leftIsInt =
|
346
|
-
const rightIsInt =
|
406
|
+
const leftIsInt = isFloatToIntOp(left[left.length - 1]);
|
407
|
+
const rightIsInt = isFloatToIntOp(right[right.length - 1]);
|
347
408
|
|
348
409
|
const canInt = leftIsInt && rightIsInt;
|
349
410
|
|
@@ -360,12 +421,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
360
421
|
...right,
|
361
422
|
// note type
|
362
423
|
...rightType,
|
363
|
-
|
424
|
+
...setLastType(scope),
|
364
425
|
[ Opcodes.else ],
|
365
426
|
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
366
427
|
// note type
|
367
428
|
...leftType,
|
368
|
-
|
429
|
+
...setLastType(scope),
|
369
430
|
[ Opcodes.end ],
|
370
431
|
Opcodes.i32_from
|
371
432
|
];
|
@@ -379,17 +440,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
379
440
|
...right,
|
380
441
|
// note type
|
381
442
|
...rightType,
|
382
|
-
|
443
|
+
...setLastType(scope),
|
383
444
|
[ Opcodes.else ],
|
384
445
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
385
446
|
// note type
|
386
447
|
...leftType,
|
387
|
-
|
448
|
+
...setLastType(scope),
|
388
449
|
[ Opcodes.end ]
|
389
450
|
];
|
390
451
|
};
|
391
452
|
|
392
|
-
const concatStrings = (scope, left, right, global, name, assign) => {
|
453
|
+
const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
|
393
454
|
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
394
455
|
// todo: convert left and right to strings if not
|
395
456
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -399,11 +460,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
399
460
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
400
461
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
401
462
|
|
402
|
-
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
403
|
-
if (aotWFA) addVarMeta(name, { wellFormed: undefined });
|
404
|
-
|
405
463
|
if (assign) {
|
406
|
-
const pointer = arrays
|
464
|
+
const pointer = scope.arrays?.get(name ?? '$undeclared');
|
407
465
|
|
408
466
|
return [
|
409
467
|
// setup right
|
@@ -428,11 +486,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
428
486
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
|
429
487
|
|
430
488
|
// copy right
|
431
|
-
// dst = out pointer + length size + current length *
|
489
|
+
// dst = out pointer + length size + current length * sizeof valtype
|
432
490
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
433
491
|
|
434
492
|
[ Opcodes.local_get, leftLength ],
|
435
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
493
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
436
494
|
[ Opcodes.i32_mul ],
|
437
495
|
[ Opcodes.i32_add ],
|
438
496
|
|
@@ -441,9 +499,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
441
499
|
...number(ValtypeSize.i32, Valtype.i32),
|
442
500
|
[ Opcodes.i32_add ],
|
443
501
|
|
444
|
-
// size = right length *
|
502
|
+
// size = right length * sizeof valtype
|
445
503
|
[ Opcodes.local_get, rightLength ],
|
446
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
504
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
447
505
|
[ Opcodes.i32_mul ],
|
448
506
|
|
449
507
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -501,11 +559,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
501
559
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
502
560
|
|
503
561
|
// copy right
|
504
|
-
// dst = out pointer + length size + left length *
|
562
|
+
// dst = out pointer + length size + left length * sizeof valtype
|
505
563
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
506
564
|
|
507
565
|
[ Opcodes.local_get, leftLength ],
|
508
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
566
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
509
567
|
[ Opcodes.i32_mul ],
|
510
568
|
[ Opcodes.i32_add ],
|
511
569
|
|
@@ -514,9 +572,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
514
572
|
...number(ValtypeSize.i32, Valtype.i32),
|
515
573
|
[ Opcodes.i32_add ],
|
516
574
|
|
517
|
-
// size = right length *
|
575
|
+
// size = right length * sizeof valtype
|
518
576
|
[ Opcodes.local_get, rightLength ],
|
519
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
577
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
520
578
|
[ Opcodes.i32_mul ],
|
521
579
|
|
522
580
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -526,7 +584,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
526
584
|
];
|
527
585
|
};
|
528
586
|
|
529
|
-
const compareStrings = (scope, left, right) => {
|
587
|
+
const compareStrings = (scope, left, right, bytestrings = false) => {
|
530
588
|
// todo: this should be rewritten into a func
|
531
589
|
// todo: convert left and right to strings if not
|
532
590
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -535,7 +593,6 @@ const compareStrings = (scope, left, right) => {
|
|
535
593
|
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
536
594
|
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
537
595
|
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
538
|
-
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
539
596
|
|
540
597
|
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
541
598
|
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
@@ -563,7 +620,6 @@ const compareStrings = (scope, left, right) => {
|
|
563
620
|
|
564
621
|
[ Opcodes.local_get, rightPointer ],
|
565
622
|
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
566
|
-
[ Opcodes.local_tee, rightLength ],
|
567
623
|
|
568
624
|
// fast path: check leftLength != rightLength
|
569
625
|
[ Opcodes.i32_ne ],
|
@@ -578,11 +634,13 @@ const compareStrings = (scope, left, right) => {
|
|
578
634
|
...number(0, Valtype.i32),
|
579
635
|
[ Opcodes.local_set, index ],
|
580
636
|
|
581
|
-
// setup index end as length * sizeof
|
637
|
+
// setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
|
582
638
|
// we do this instead of having to do mul/div each iter for perf™
|
583
639
|
[ Opcodes.local_get, leftLength ],
|
584
|
-
...
|
585
|
-
|
640
|
+
...(bytestrings ? [] : [
|
641
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
642
|
+
[ Opcodes.i32_mul ],
|
643
|
+
]),
|
586
644
|
[ Opcodes.local_set, indexEnd ],
|
587
645
|
|
588
646
|
// iterate over each char and check if eq
|
@@ -592,13 +650,17 @@ const compareStrings = (scope, left, right) => {
|
|
592
650
|
[ Opcodes.local_get, index ],
|
593
651
|
[ Opcodes.local_get, leftPointer ],
|
594
652
|
[ Opcodes.i32_add ],
|
595
|
-
|
653
|
+
bytestrings ?
|
654
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
655
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
596
656
|
|
597
657
|
// fetch right
|
598
658
|
[ Opcodes.local_get, index ],
|
599
659
|
[ Opcodes.local_get, rightPointer ],
|
600
660
|
[ Opcodes.i32_add ],
|
601
|
-
|
661
|
+
bytestrings ?
|
662
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
663
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
602
664
|
|
603
665
|
// not equal, "return" false
|
604
666
|
[ Opcodes.i32_ne ],
|
@@ -607,13 +669,13 @@ const compareStrings = (scope, left, right) => {
|
|
607
669
|
[ Opcodes.br, 2 ],
|
608
670
|
[ Opcodes.end ],
|
609
671
|
|
610
|
-
// index += sizeof
|
672
|
+
// index += sizeof valtype (1 for bytestring, 2 for string)
|
611
673
|
[ Opcodes.local_get, index ],
|
612
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
674
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
613
675
|
[ Opcodes.i32_add ],
|
614
676
|
[ Opcodes.local_tee, index ],
|
615
677
|
|
616
|
-
// if index != index end (length * sizeof
|
678
|
+
// if index != index end (length * sizeof valtype), loop
|
617
679
|
[ Opcodes.local_get, indexEnd ],
|
618
680
|
[ Opcodes.i32_ne ],
|
619
681
|
[ Opcodes.br_if, 0 ],
|
@@ -634,16 +696,18 @@ const compareStrings = (scope, left, right) => {
|
|
634
696
|
};
|
635
697
|
|
636
698
|
const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
637
|
-
if (
|
699
|
+
if (isFloatToIntOp(wasm[wasm.length - 1])) return [
|
638
700
|
...wasm,
|
639
701
|
...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
|
640
702
|
];
|
703
|
+
// if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
|
641
704
|
|
642
|
-
const
|
705
|
+
const useTmp = knownType(scope, type) == null;
|
706
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
643
707
|
|
644
708
|
const def = [
|
645
709
|
// if value != 0
|
646
|
-
[ Opcodes.local_get, tmp ],
|
710
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
647
711
|
|
648
712
|
// ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
649
713
|
...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
|
@@ -655,7 +719,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
655
719
|
|
656
720
|
return [
|
657
721
|
...wasm,
|
658
|
-
[ Opcodes.local_set, tmp ],
|
722
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
659
723
|
|
660
724
|
...typeSwitch(scope, type, {
|
661
725
|
// [TYPES.number]: def,
|
@@ -664,7 +728,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
664
728
|
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
665
729
|
],
|
666
730
|
[TYPES.string]: [
|
667
|
-
[ Opcodes.local_get, tmp ],
|
731
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
668
732
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
669
733
|
|
670
734
|
// get length
|
@@ -675,16 +739,27 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
675
739
|
[ Opcodes.i32_eqz ], */
|
676
740
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
677
741
|
],
|
742
|
+
[TYPES._bytestring]: [ // duplicate of string
|
743
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
744
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
745
|
+
|
746
|
+
// get length
|
747
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
748
|
+
|
749
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
750
|
+
],
|
678
751
|
default: def
|
679
752
|
}, intOut ? Valtype.i32 : valtypeBinary)
|
680
753
|
];
|
681
754
|
};
|
682
755
|
|
683
756
|
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
684
|
-
const
|
757
|
+
const useTmp = knownType(scope, type) == null;
|
758
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
759
|
+
|
685
760
|
return [
|
686
761
|
...wasm,
|
687
|
-
[ Opcodes.local_set, tmp ],
|
762
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
688
763
|
|
689
764
|
...typeSwitch(scope, type, {
|
690
765
|
[TYPES._array]: [
|
@@ -692,7 +767,18 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
692
767
|
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
693
768
|
],
|
694
769
|
[TYPES.string]: [
|
695
|
-
[ Opcodes.local_get, tmp ],
|
770
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
771
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
772
|
+
|
773
|
+
// get length
|
774
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
775
|
+
|
776
|
+
// if length == 0
|
777
|
+
[ Opcodes.i32_eqz ],
|
778
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
779
|
+
],
|
780
|
+
[TYPES._bytestring]: [ // duplicate of string
|
781
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
696
782
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
697
783
|
|
698
784
|
// get length
|
@@ -704,7 +790,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
704
790
|
],
|
705
791
|
default: [
|
706
792
|
// if value == 0
|
707
|
-
[ Opcodes.local_get, tmp ],
|
793
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
708
794
|
|
709
795
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
710
796
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -714,10 +800,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
714
800
|
};
|
715
801
|
|
716
802
|
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
717
|
-
const
|
803
|
+
const useTmp = knownType(scope, type) == null;
|
804
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
805
|
+
|
718
806
|
return [
|
719
807
|
...wasm,
|
720
|
-
[ Opcodes.local_set, tmp ],
|
808
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
721
809
|
|
722
810
|
...typeSwitch(scope, type, {
|
723
811
|
[TYPES.undefined]: [
|
@@ -726,7 +814,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
726
814
|
],
|
727
815
|
[TYPES.object]: [
|
728
816
|
// object, null if == 0
|
729
|
-
[ Opcodes.local_get, tmp ],
|
817
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
730
818
|
|
731
819
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
732
820
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -755,11 +843,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
755
843
|
return performLogicOp(scope, op, left, right, leftType, rightType);
|
756
844
|
}
|
757
845
|
|
846
|
+
const knownLeft = knownType(scope, leftType);
|
847
|
+
const knownRight = knownType(scope, rightType);
|
848
|
+
|
758
849
|
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
759
850
|
const strictOp = op === '===' || op === '!==';
|
760
851
|
|
761
852
|
const startOut = [], endOut = [];
|
762
|
-
const
|
853
|
+
const finalize = out => startOut.concat(out, endOut);
|
763
854
|
|
764
855
|
// if strict (in)equal check types match
|
765
856
|
if (strictOp) {
|
@@ -804,31 +895,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
804
895
|
// todo: if equality op and an operand is undefined, return false
|
805
896
|
// todo: niche null hell with 0
|
806
897
|
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
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
|
+
}
|
832
951
|
|
833
952
|
let ops = operatorOpcode[valtype][op];
|
834
953
|
|
@@ -838,24 +957,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
838
957
|
includeBuiltin(scope, builtinName);
|
839
958
|
const idx = funcIndex[builtinName];
|
840
959
|
|
841
|
-
return
|
960
|
+
return finalize([
|
842
961
|
...left,
|
843
962
|
...right,
|
844
963
|
[ Opcodes.call, idx ]
|
845
964
|
]);
|
846
965
|
}
|
847
966
|
|
848
|
-
if (!ops) return todo(`operator ${op} not implemented yet
|
967
|
+
if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
|
849
968
|
|
850
969
|
if (!Array.isArray(ops)) ops = [ ops ];
|
851
970
|
ops = [ ops ];
|
852
971
|
|
853
972
|
let tmpLeft, tmpRight;
|
854
973
|
// if equal op, check if strings for compareStrings
|
855
|
-
|
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
|
977
|
+
|
978
|
+
if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
|
856
979
|
tmpLeft = localTmp(scope, '__tmpop_left');
|
857
980
|
tmpRight = localTmp(scope, '__tmpop_right');
|
858
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)
|
859
1023
|
ops.unshift(...stringOnly([
|
860
1024
|
// if left is string
|
861
1025
|
...leftType,
|
@@ -867,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
867
1031
|
...number(TYPES.string, Valtype.i32),
|
868
1032
|
[ Opcodes.i32_eq ],
|
869
1033
|
|
870
|
-
// if
|
871
|
-
[ Opcodes.
|
1034
|
+
// if both are true
|
1035
|
+
[ Opcodes.i32_and ],
|
872
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 ],
|
873
1041
|
|
874
|
-
//
|
875
|
-
// if left is not string
|
1042
|
+
// if left is bytestring
|
876
1043
|
...leftType,
|
877
|
-
...number(TYPES.
|
878
|
-
[ Opcodes.
|
1044
|
+
...number(TYPES._bytestring, Valtype.i32),
|
1045
|
+
[ Opcodes.i32_eq ],
|
879
1046
|
|
880
|
-
// if right is
|
1047
|
+
// if right is bytestring
|
881
1048
|
...rightType,
|
882
|
-
...number(TYPES.
|
883
|
-
[ Opcodes.
|
1049
|
+
...number(TYPES._bytestring, Valtype.i32),
|
1050
|
+
[ Opcodes.i32_eq ],
|
884
1051
|
|
885
|
-
// if
|
886
|
-
[ Opcodes.
|
1052
|
+
// if both are true
|
1053
|
+
[ Opcodes.i32_and ],
|
887
1054
|
[ Opcodes.if, Blocktype.void ],
|
888
|
-
...
|
889
|
-
[ Opcodes.br, 1 ],
|
890
|
-
[ Opcodes.end ],
|
891
|
-
|
892
|
-
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
893
|
-
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1055
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
|
894
1056
|
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
895
1057
|
[ Opcodes.br, 1 ],
|
896
1058
|
[ Opcodes.end ],
|
@@ -904,7 +1066,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
904
1066
|
// }
|
905
1067
|
}
|
906
1068
|
|
907
|
-
return
|
1069
|
+
return finalize([
|
908
1070
|
...left,
|
909
1071
|
...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
|
910
1072
|
...right,
|
@@ -921,7 +1083,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
921
1083
|
return out;
|
922
1084
|
};
|
923
1085
|
|
924
|
-
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 = [] }) => {
|
925
1102
|
const existing = funcs.find(x => x.name === name);
|
926
1103
|
if (existing) return existing;
|
927
1104
|
|
@@ -933,6 +1110,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
933
1110
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
934
1111
|
}
|
935
1112
|
|
1113
|
+
for (const x of _data) {
|
1114
|
+
const copy = { ...x };
|
1115
|
+
copy.offset += pages.size * pageSize;
|
1116
|
+
data.push(copy);
|
1117
|
+
}
|
1118
|
+
|
1119
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
|
1120
|
+
|
936
1121
|
let baseGlobalIdx, i = 0;
|
937
1122
|
for (const type of globalTypes) {
|
938
1123
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -955,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
955
1140
|
params,
|
956
1141
|
locals,
|
957
1142
|
returns,
|
958
|
-
returnType:
|
1143
|
+
returnType: returnType ?? TYPES.number,
|
959
1144
|
wasm,
|
960
1145
|
internal: true,
|
961
1146
|
index: currentFuncIndex++
|
@@ -978,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
|
|
978
1163
|
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
979
1164
|
};
|
980
1165
|
|
1166
|
+
// potential future ideas for nan boxing (unused):
|
981
1167
|
// T = JS type, V = value/pointer
|
982
1168
|
// 0bTTT
|
983
1169
|
// qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
|
@@ -1001,58 +1187,29 @@ const generateLogicExp = (scope, decl) => {
|
|
1001
1187
|
// 4: internal type
|
1002
1188
|
// 5: pointer
|
1003
1189
|
|
1004
|
-
const
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
object: 0x04,
|
1010
|
-
function: 0x05,
|
1011
|
-
symbol: 0x06,
|
1012
|
-
bigint: 0x07,
|
1013
|
-
|
1014
|
-
// these are not "typeof" types but tracked internally
|
1015
|
-
_array: 0x10,
|
1016
|
-
_regexp: 0x11,
|
1017
|
-
|
1018
|
-
// typed arrays
|
1019
|
-
_int8array: 0x20,
|
1020
|
-
_uint8array: 0x21,
|
1021
|
-
_uint8clampedarray: 0x22,
|
1022
|
-
_int16array: 0x23,
|
1023
|
-
_uint16array: 0x24,
|
1024
|
-
_int32array: 0x25,
|
1025
|
-
_uint32array: 0x26,
|
1026
|
-
_float32array: 0x27,
|
1027
|
-
_float64array: 0x28,
|
1028
|
-
};
|
1029
|
-
|
1030
|
-
const TYPE_NAMES = {
|
1031
|
-
[TYPES.number]: 'Number',
|
1032
|
-
[TYPES.boolean]: 'Boolean',
|
1033
|
-
[TYPES.string]: 'String',
|
1034
|
-
[TYPES.undefined]: 'undefined',
|
1035
|
-
[TYPES.object]: 'Object',
|
1036
|
-
[TYPES.function]: 'Function',
|
1037
|
-
[TYPES.symbol]: 'Symbol',
|
1038
|
-
[TYPES.bigint]: 'BigInt',
|
1039
|
-
|
1040
|
-
[TYPES._array]: 'Array',
|
1041
|
-
[TYPES._regexp]: 'RegExp'
|
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;
|
1042
1195
|
};
|
1043
1196
|
|
1044
1197
|
const getType = (scope, _name) => {
|
1045
1198
|
const name = mapName(_name);
|
1046
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);
|
1047
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);
|
1048
1206
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1049
1207
|
|
1050
1208
|
let type = TYPES.undefined;
|
1051
|
-
if (builtinVars[name]) type =
|
1209
|
+
if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
|
1052
1210
|
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
|
1053
1211
|
|
1054
|
-
if (name
|
1055
|
-
name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
|
1212
|
+
if (isExistingProtoFunc(name)) type = TYPES.function;
|
1056
1213
|
|
1057
1214
|
return number(type, Valtype.i32);
|
1058
1215
|
};
|
@@ -1062,17 +1219,29 @@ const setType = (scope, _name, type) => {
|
|
1062
1219
|
|
1063
1220
|
const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
|
1064
1221
|
|
1222
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return [];
|
1065
1223
|
if (scope.locals[name]) return [
|
1066
1224
|
...out,
|
1067
1225
|
[ Opcodes.local_set, scope.locals[name + '#type'].idx ]
|
1068
1226
|
];
|
1069
1227
|
|
1228
|
+
if (typedInput && globals[name]?.metadata?.type != null) return [];
|
1070
1229
|
if (globals[name]) return [
|
1071
1230
|
...out,
|
1072
1231
|
[ Opcodes.global_set, globals[name + '#type'].idx ]
|
1073
1232
|
];
|
1074
1233
|
|
1075
1234
|
// throw new Error('could not find var');
|
1235
|
+
return [];
|
1236
|
+
};
|
1237
|
+
|
1238
|
+
const getLastType = scope => {
|
1239
|
+
scope.gotLastType = true;
|
1240
|
+
return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1241
|
+
};
|
1242
|
+
|
1243
|
+
const setLastType = scope => {
|
1244
|
+
return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1076
1245
|
};
|
1077
1246
|
|
1078
1247
|
const getNodeType = (scope, node) => {
|
@@ -1080,6 +1249,8 @@ const getNodeType = (scope, node) => {
|
|
1080
1249
|
if (node.type === 'Literal') {
|
1081
1250
|
if (node.regex) return TYPES._regexp;
|
1082
1251
|
|
1252
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
|
1253
|
+
|
1083
1254
|
return TYPES[typeof node.value];
|
1084
1255
|
}
|
1085
1256
|
|
@@ -1093,6 +1264,27 @@ const getNodeType = (scope, node) => {
|
|
1093
1264
|
|
1094
1265
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1095
1266
|
const name = node.callee.name;
|
1267
|
+
if (!name) {
|
1268
|
+
// iife
|
1269
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1270
|
+
|
1271
|
+
// presume
|
1272
|
+
// todo: warn here?
|
1273
|
+
return TYPES.number;
|
1274
|
+
}
|
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
|
+
|
1096
1288
|
const func = funcs.find(x => x.name === name);
|
1097
1289
|
|
1098
1290
|
if (func) {
|
@@ -1100,10 +1292,27 @@ const getNodeType = (scope, node) => {
|
|
1100
1292
|
if (func.returnType) return func.returnType;
|
1101
1293
|
}
|
1102
1294
|
|
1103
|
-
if (builtinFuncs[name]) return
|
1295
|
+
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
1104
1296
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1105
1297
|
|
1106
|
-
|
1298
|
+
// check if this is a prototype function
|
1299
|
+
// if so and there is only one impl (eg charCodeAt)
|
1300
|
+
// use that return type as that is the only possibility
|
1301
|
+
// (if non-matching type it would error out)
|
1302
|
+
if (name.startsWith('__')) {
|
1303
|
+
const spl = name.slice(2).split('_');
|
1304
|
+
|
1305
|
+
const func = spl[spl.length - 1];
|
1306
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
|
1307
|
+
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1308
|
+
}
|
1309
|
+
|
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);
|
1107
1316
|
|
1108
1317
|
// presume
|
1109
1318
|
// todo: warn here?
|
@@ -1151,6 +1360,15 @@ const getNodeType = (scope, node) => {
|
|
1151
1360
|
|
1152
1361
|
if (node.type === 'BinaryExpression') {
|
1153
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
|
+
|
1154
1372
|
return TYPES.number;
|
1155
1373
|
|
1156
1374
|
// todo: string concat types
|
@@ -1175,7 +1393,7 @@ const getNodeType = (scope, node) => {
|
|
1175
1393
|
if (node.operator === '!') return TYPES.boolean;
|
1176
1394
|
if (node.operator === 'void') return TYPES.undefined;
|
1177
1395
|
if (node.operator === 'delete') return TYPES.boolean;
|
1178
|
-
if (node.operator === 'typeof') return TYPES.string;
|
1396
|
+
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
|
1179
1397
|
|
1180
1398
|
return TYPES.number;
|
1181
1399
|
}
|
@@ -1184,11 +1402,23 @@ const getNodeType = (scope, node) => {
|
|
1184
1402
|
// hack: if something.length, number type
|
1185
1403
|
if (node.property.name === 'length') return TYPES.number;
|
1186
1404
|
|
1187
|
-
//
|
1405
|
+
// ts hack
|
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;
|
1408
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
|
1409
|
+
|
1410
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1411
|
+
|
1412
|
+
// presume
|
1188
1413
|
return TYPES.number;
|
1189
1414
|
}
|
1190
1415
|
|
1191
|
-
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);
|
1192
1422
|
|
1193
1423
|
// presume
|
1194
1424
|
// todo: warn here?
|
@@ -1204,8 +1434,8 @@ const getNodeType = (scope, node) => {
|
|
1204
1434
|
const generateLiteral = (scope, decl, global, name) => {
|
1205
1435
|
if (decl.value === null) return number(NULL);
|
1206
1436
|
|
1437
|
+
// hack: just return 1 for regex literals
|
1207
1438
|
if (decl.regex) {
|
1208
|
-
scope.regex[name] = decl.regex;
|
1209
1439
|
return number(1);
|
1210
1440
|
}
|
1211
1441
|
|
@@ -1218,19 +1448,10 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1218
1448
|
return number(decl.value ? 1 : 0);
|
1219
1449
|
|
1220
1450
|
case 'string':
|
1221
|
-
|
1222
|
-
const rawElements = new Array(str.length);
|
1223
|
-
let j = 0;
|
1224
|
-
for (let i = 0; i < str.length; i++) {
|
1225
|
-
rawElements[i] = str.charCodeAt(i);
|
1226
|
-
}
|
1227
|
-
|
1228
|
-
return makeArray(scope, {
|
1229
|
-
rawElements
|
1230
|
-
}, global, name, false, 'i16')[0];
|
1451
|
+
return makeString(scope, decl.value, global, name);
|
1231
1452
|
|
1232
1453
|
default:
|
1233
|
-
return todo(`cannot generate literal of type ${typeof decl.value}
|
1454
|
+
return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
|
1234
1455
|
}
|
1235
1456
|
};
|
1236
1457
|
|
@@ -1239,6 +1460,8 @@ const countLeftover = wasm => {
|
|
1239
1460
|
|
1240
1461
|
for (let i = 0; i < wasm.length; i++) {
|
1241
1462
|
const inst = wasm[i];
|
1463
|
+
if (inst[0] == null) continue;
|
1464
|
+
|
1242
1465
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
1243
1466
|
if (inst[0] === Opcodes.if) count--;
|
1244
1467
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1247,18 +1470,25 @@ const countLeftover = wasm => {
|
|
1247
1470
|
if (inst[0] === Opcodes.end) depth--;
|
1248
1471
|
|
1249
1472
|
if (depth === 0)
|
1250
|
-
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1251
|
-
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
1252
|
-
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1253
|
-
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
|
1473
|
+
if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
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)) {}
|
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++;
|
1476
|
+
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1254
1477
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1255
1478
|
else if (inst[0] === Opcodes.return) count = 0;
|
1256
1479
|
else if (inst[0] === Opcodes.call) {
|
1257
1480
|
let func = funcs.find(x => x.index === inst[1]);
|
1258
|
-
if (
|
1259
|
-
count
|
1260
|
-
} else
|
1261
|
-
|
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
|
+
}
|
1262
1492
|
} else count--;
|
1263
1493
|
|
1264
1494
|
// console.log(count, decompile([ inst ]).slice(0, -1));
|
@@ -1276,7 +1506,7 @@ const disposeLeftover = wasm => {
|
|
1276
1506
|
const generateExp = (scope, decl) => {
|
1277
1507
|
const expression = decl.expression;
|
1278
1508
|
|
1279
|
-
const out = generate(scope, expression);
|
1509
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1280
1510
|
disposeLeftover(out);
|
1281
1511
|
|
1282
1512
|
return out;
|
@@ -1334,7 +1564,7 @@ const RTArrayUtil = {
|
|
1334
1564
|
]
|
1335
1565
|
};
|
1336
1566
|
|
1337
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1567
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1338
1568
|
/* const callee = decl.callee;
|
1339
1569
|
const args = decl.arguments;
|
1340
1570
|
|
@@ -1350,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1350
1580
|
name = func.name;
|
1351
1581
|
}
|
1352
1582
|
|
1353
|
-
if (name === 'eval' && decl.arguments[0]
|
1583
|
+
if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
|
1354
1584
|
// literal eval hack
|
1355
|
-
const code = decl.arguments[0]
|
1356
|
-
|
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
|
+
}
|
1357
1598
|
|
1358
1599
|
const out = generate(scope, {
|
1359
1600
|
type: 'BlockStatement',
|
@@ -1367,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1367
1608
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1368
1609
|
out.push(
|
1369
1610
|
...getNodeType(scope, finalStatement),
|
1370
|
-
|
1611
|
+
...setLastType(scope)
|
1371
1612
|
);
|
1372
1613
|
} else if (countLeftover(out) === 0) {
|
1373
1614
|
out.push(...number(UNDEFINED));
|
1374
1615
|
out.push(
|
1375
1616
|
...number(TYPES.undefined, Valtype.i32),
|
1376
|
-
|
1617
|
+
...setLastType(scope)
|
1377
1618
|
);
|
1378
1619
|
}
|
1379
1620
|
|
@@ -1391,18 +1632,20 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1391
1632
|
if (name && name.startsWith('__')) {
|
1392
1633
|
const spl = name.slice(2).split('_');
|
1393
1634
|
|
1394
|
-
|
1395
|
-
protoName = func;
|
1635
|
+
protoName = spl[spl.length - 1];
|
1396
1636
|
|
1397
1637
|
target = { ...decl.callee };
|
1398
1638
|
target.name = spl.slice(0, -1).join('_');
|
1639
|
+
|
1640
|
+
// failed to lookup name, abort
|
1641
|
+
if (!lookupName(scope, target.name)[0]) protoName = null;
|
1399
1642
|
}
|
1400
1643
|
|
1401
1644
|
// literal.func()
|
1402
1645
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1403
1646
|
// megahack for /regex/.func()
|
1404
|
-
|
1405
|
-
|
1647
|
+
const funcName = decl.callee.property.name;
|
1648
|
+
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1406
1649
|
const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
|
1407
1650
|
|
1408
1651
|
funcIndex[func.name] = func.index;
|
@@ -1418,12 +1661,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1418
1661
|
Opcodes.i32_from_u,
|
1419
1662
|
|
1420
1663
|
...number(TYPES.boolean, Valtype.i32),
|
1421
|
-
|
1664
|
+
...setLastType(scope)
|
1422
1665
|
];
|
1423
1666
|
}
|
1424
1667
|
|
1425
|
-
|
1426
|
-
protoName = func;
|
1668
|
+
protoName = decl.callee.property.name;
|
1427
1669
|
|
1428
1670
|
target = decl.callee.object;
|
1429
1671
|
}
|
@@ -1444,23 +1686,48 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1444
1686
|
// }
|
1445
1687
|
|
1446
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
|
+
|
1447
1709
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1448
|
-
|
1449
|
-
if (f) acc[x] = f;
|
1710
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1450
1711
|
return acc;
|
1451
1712
|
}, {});
|
1452
1713
|
|
1453
|
-
// no prototype function candidates, ignore
|
1454
1714
|
if (Object.keys(protoCands).length > 0) {
|
1455
1715
|
// use local for cached i32 length as commonly used
|
1456
1716
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1457
1717
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1458
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1459
1718
|
|
1460
1719
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1461
1720
|
|
1721
|
+
const rawPointer = [
|
1722
|
+
...generate(scope, target),
|
1723
|
+
Opcodes.i32_to_u
|
1724
|
+
];
|
1725
|
+
|
1726
|
+
const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
|
1727
|
+
const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
|
1728
|
+
|
1729
|
+
let allOptUnused = true;
|
1462
1730
|
let lengthI32CacheUsed = false;
|
1463
|
-
const protoBC = {};
|
1464
1731
|
for (const x in protoCands) {
|
1465
1732
|
const protoFunc = protoCands[x];
|
1466
1733
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
@@ -1468,7 +1735,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1468
1735
|
...RTArrayUtil.getLength(getPointer),
|
1469
1736
|
|
1470
1737
|
...number(TYPES.number, Valtype.i32),
|
1471
|
-
|
1738
|
+
...setLastType(scope)
|
1472
1739
|
];
|
1473
1740
|
continue;
|
1474
1741
|
}
|
@@ -1478,6 +1745,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1478
1745
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1479
1746
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1480
1747
|
|
1748
|
+
let optUnused = false;
|
1481
1749
|
const protoOut = protoFunc(getPointer, {
|
1482
1750
|
getCachedI32: () => {
|
1483
1751
|
lengthI32CacheUsed = true;
|
@@ -1492,23 +1760,30 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1492
1760
|
return makeArray(scope, {
|
1493
1761
|
rawElements: new Array(length)
|
1494
1762
|
}, _global, _name, true, itemType);
|
1763
|
+
}, () => {
|
1764
|
+
optUnused = true;
|
1765
|
+
return unusedValue;
|
1495
1766
|
});
|
1496
1767
|
|
1768
|
+
if (!optUnused) allOptUnused = false;
|
1769
|
+
|
1497
1770
|
protoBC[x] = [
|
1498
|
-
[ Opcodes.block, valtypeBinary ],
|
1771
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1499
1772
|
...protoOut,
|
1500
1773
|
|
1501
1774
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1502
|
-
|
1775
|
+
...setLastType(scope),
|
1503
1776
|
[ Opcodes.end ]
|
1504
1777
|
];
|
1505
1778
|
}
|
1506
1779
|
|
1507
|
-
|
1508
|
-
...generate(scope, target),
|
1780
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1509
1781
|
|
1510
|
-
|
1511
|
-
|
1782
|
+
return [
|
1783
|
+
...(usePointerCache ? [
|
1784
|
+
...rawPointer,
|
1785
|
+
[ Opcodes.local_set, pointerLocal ],
|
1786
|
+
] : []),
|
1512
1787
|
|
1513
1788
|
...(!lengthI32CacheUsed ? [] : [
|
1514
1789
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1520,13 +1795,22 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1520
1795
|
|
1521
1796
|
// TODO: error better
|
1522
1797
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1523
|
-
}, valtypeBinary),
|
1798
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1524
1799
|
];
|
1525
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
|
+
}
|
1526
1810
|
}
|
1527
1811
|
|
1528
1812
|
// TODO: only allows callee as literal
|
1529
|
-
if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
|
1813
|
+
if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
|
1530
1814
|
|
1531
1815
|
let idx = funcIndex[name] ?? importedFuncs[name];
|
1532
1816
|
if (idx === undefined && builtinFuncs[name]) {
|
@@ -1559,15 +1843,66 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1559
1843
|
idx = -1;
|
1560
1844
|
}
|
1561
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
|
+
|
1562
1895
|
if (idx === undefined) {
|
1563
|
-
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function
|
1564
|
-
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);
|
1565
1898
|
}
|
1566
1899
|
|
1567
1900
|
const func = funcs.find(x => x.index === idx);
|
1568
1901
|
|
1569
1902
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1570
|
-
const
|
1903
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1904
|
+
const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
|
1905
|
+
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1571
1906
|
|
1572
1907
|
let args = decl.arguments;
|
1573
1908
|
if (func && args.length < paramCount) {
|
@@ -1583,14 +1918,24 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1583
1918
|
if (func && func.throws) scope.throws = true;
|
1584
1919
|
|
1585
1920
|
let out = [];
|
1586
|
-
for (
|
1921
|
+
for (let i = 0; i < args.length; i++) {
|
1922
|
+
const arg = args[i];
|
1587
1923
|
out = out.concat(generate(scope, arg));
|
1588
|
-
|
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
|
+
|
1933
|
+
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1589
1934
|
}
|
1590
1935
|
|
1591
1936
|
out.push([ Opcodes.call, idx ]);
|
1592
1937
|
|
1593
|
-
if (!
|
1938
|
+
if (!typedReturns) {
|
1594
1939
|
// let type;
|
1595
1940
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1596
1941
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1600,7 +1945,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1600
1945
|
// ...number(type, Valtype.i32),
|
1601
1946
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1602
1947
|
// );
|
1603
|
-
} else out.push(
|
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
|
+
}
|
1604
1953
|
|
1605
1954
|
return out;
|
1606
1955
|
};
|
@@ -1608,8 +1957,21 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1608
1957
|
const generateNew = (scope, decl, _global, _name) => {
|
1609
1958
|
// hack: basically treat this as a normal call for builtins for now
|
1610
1959
|
const name = mapName(decl.callee.name);
|
1960
|
+
|
1611
1961
|
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1612
|
-
|
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)})`);
|
1613
1975
|
|
1614
1976
|
return generateCall(scope, decl, _global, _name);
|
1615
1977
|
};
|
@@ -1625,17 +1987,124 @@ const unhackName = name => {
|
|
1625
1987
|
return name;
|
1626
1988
|
};
|
1627
1989
|
|
1990
|
+
const knownType = (scope, type) => {
|
1991
|
+
if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
|
1992
|
+
return type[0][1];
|
1993
|
+
}
|
1994
|
+
|
1995
|
+
if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
|
1996
|
+
const idx = type[0][1];
|
1997
|
+
|
1998
|
+
// type idx = var idx + 1
|
1999
|
+
const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
|
2000
|
+
if (v.metadata?.type != null) return v.metadata.type;
|
2001
|
+
}
|
2002
|
+
|
2003
|
+
return null;
|
2004
|
+
};
|
2005
|
+
|
2006
|
+
const brTable = (input, bc, returns) => {
|
2007
|
+
const out = [];
|
2008
|
+
const keys = Object.keys(bc);
|
2009
|
+
const count = keys.length;
|
2010
|
+
|
2011
|
+
if (count === 1) {
|
2012
|
+
// return [
|
2013
|
+
// ...input,
|
2014
|
+
// ...bc[keys[0]]
|
2015
|
+
// ];
|
2016
|
+
return bc[keys[0]];
|
2017
|
+
}
|
2018
|
+
|
2019
|
+
if (count === 2) {
|
2020
|
+
// just use if else
|
2021
|
+
const other = keys.find(x => x !== 'default');
|
2022
|
+
return [
|
2023
|
+
...input,
|
2024
|
+
...number(other, Valtype.i32),
|
2025
|
+
[ Opcodes.i32_eq ],
|
2026
|
+
[ Opcodes.if, returns ],
|
2027
|
+
...bc[other],
|
2028
|
+
[ Opcodes.else ],
|
2029
|
+
...bc.default,
|
2030
|
+
[ Opcodes.end ]
|
2031
|
+
];
|
2032
|
+
}
|
2033
|
+
|
2034
|
+
for (let i = 0; i < count; i++) {
|
2035
|
+
if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
|
2036
|
+
else out.push([ Opcodes.block, Blocktype.void ]);
|
2037
|
+
}
|
2038
|
+
|
2039
|
+
const nums = keys.filter(x => +x);
|
2040
|
+
const offset = Math.min(...nums);
|
2041
|
+
const max = Math.max(...nums);
|
2042
|
+
|
2043
|
+
const table = [];
|
2044
|
+
let br = 1;
|
2045
|
+
|
2046
|
+
for (let i = offset; i <= max; i++) {
|
2047
|
+
// if branch for this num, go to that block
|
2048
|
+
if (bc[i]) {
|
2049
|
+
table.push(br);
|
2050
|
+
br++;
|
2051
|
+
continue;
|
2052
|
+
}
|
2053
|
+
|
2054
|
+
// else default
|
2055
|
+
table.push(0);
|
2056
|
+
}
|
2057
|
+
|
2058
|
+
out.push(
|
2059
|
+
[ Opcodes.block, Blocktype.void ],
|
2060
|
+
...input,
|
2061
|
+
...(offset > 0 ? [
|
2062
|
+
...number(offset, Valtype.i32),
|
2063
|
+
[ Opcodes.i32_sub ]
|
2064
|
+
] : []),
|
2065
|
+
[ Opcodes.br_table, ...encodeVector(table), 0 ]
|
2066
|
+
);
|
2067
|
+
|
2068
|
+
// if you can guess why we sort the wrong way and then reverse
|
2069
|
+
// (instead of just sorting the correct way)
|
2070
|
+
// dm me and if you are correct and the first person
|
2071
|
+
// I will somehow shout you out or something
|
2072
|
+
const orderedBc = keys.sort((a, b) => b - a).reverse();
|
2073
|
+
|
2074
|
+
br = count - 1;
|
2075
|
+
for (const x of orderedBc) {
|
2076
|
+
out.push(
|
2077
|
+
[ Opcodes.end ],
|
2078
|
+
...bc[x],
|
2079
|
+
...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
|
2080
|
+
);
|
2081
|
+
br--;
|
2082
|
+
}
|
2083
|
+
|
2084
|
+
return [
|
2085
|
+
...out,
|
2086
|
+
[ Opcodes.end, 'br table end' ]
|
2087
|
+
];
|
2088
|
+
};
|
2089
|
+
|
1628
2090
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1629
|
-
|
2091
|
+
if (!Prefs.bytestring) delete bc[TYPES._bytestring];
|
2092
|
+
|
2093
|
+
const known = knownType(scope, type);
|
2094
|
+
if (known != null) {
|
2095
|
+
return bc[known] ?? bc.default;
|
2096
|
+
}
|
1630
2097
|
|
2098
|
+
if (Prefs.typeswitchUseBrtable)
|
2099
|
+
return brTable(type, bc, returns);
|
2100
|
+
|
2101
|
+
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
1631
2102
|
const out = [
|
1632
2103
|
...type,
|
1633
2104
|
[ Opcodes.local_set, tmp ],
|
1634
2105
|
[ Opcodes.block, returns ]
|
1635
2106
|
];
|
1636
2107
|
|
1637
|
-
// todo: use br_table?
|
1638
|
-
|
1639
2108
|
for (const x in bc) {
|
1640
2109
|
if (x === 'default') continue;
|
1641
2110
|
|
@@ -1659,7 +2128,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1659
2128
|
return out;
|
1660
2129
|
};
|
1661
2130
|
|
1662
|
-
const allocVar = (scope, name, global = false) => {
|
2131
|
+
const allocVar = (scope, name, global = false, type = true) => {
|
1663
2132
|
const target = global ? globals : scope.locals;
|
1664
2133
|
|
1665
2134
|
// already declared
|
@@ -1673,12 +2142,62 @@ const allocVar = (scope, name, global = false) => {
|
|
1673
2142
|
let idx = global ? globalInd++ : scope.localInd++;
|
1674
2143
|
target[name] = { idx, type: valtypeBinary };
|
1675
2144
|
|
1676
|
-
|
1677
|
-
|
2145
|
+
if (type) {
|
2146
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
2147
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
2148
|
+
}
|
1678
2149
|
|
1679
2150
|
return idx;
|
1680
2151
|
};
|
1681
2152
|
|
2153
|
+
const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
2154
|
+
const target = global ? globals : scope.locals;
|
2155
|
+
|
2156
|
+
target[name].metadata ??= {};
|
2157
|
+
for (const x in metadata) {
|
2158
|
+
if (metadata[x] != null) target[name].metadata[x] = metadata[x];
|
2159
|
+
}
|
2160
|
+
};
|
2161
|
+
|
2162
|
+
const typeAnnoToPorfType = x => {
|
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()];
|
2166
|
+
|
2167
|
+
switch (x) {
|
2168
|
+
case 'i32':
|
2169
|
+
case 'i64':
|
2170
|
+
case 'f64':
|
2171
|
+
return TYPES.number;
|
2172
|
+
}
|
2173
|
+
|
2174
|
+
return null;
|
2175
|
+
};
|
2176
|
+
|
2177
|
+
const extractTypeAnnotation = decl => {
|
2178
|
+
let a = decl;
|
2179
|
+
while (a.typeAnnotation) a = a.typeAnnotation;
|
2180
|
+
|
2181
|
+
let type = null, elementType = null;
|
2182
|
+
if (a.typeName) {
|
2183
|
+
type = a.typeName.name;
|
2184
|
+
} else if (a.type.endsWith('Keyword')) {
|
2185
|
+
type = a.type.slice(2, -7).toLowerCase();
|
2186
|
+
} else if (a.type === 'TSArrayType') {
|
2187
|
+
type = 'array';
|
2188
|
+
elementType = extractTypeAnnotation(a.elementType).type;
|
2189
|
+
}
|
2190
|
+
|
2191
|
+
const typeName = type;
|
2192
|
+
type = typeAnnoToPorfType(type);
|
2193
|
+
|
2194
|
+
if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
|
2195
|
+
|
2196
|
+
// if (decl.name) console.log(decl.name, { type, elementType });
|
2197
|
+
|
2198
|
+
return { type, typeName, elementType };
|
2199
|
+
};
|
2200
|
+
|
1682
2201
|
const generateVar = (scope, decl) => {
|
1683
2202
|
let out = [];
|
1684
2203
|
|
@@ -1690,6 +2209,8 @@ const generateVar = (scope, decl) => {
|
|
1690
2209
|
for (const x of decl.declarations) {
|
1691
2210
|
const name = mapName(x.id.name);
|
1692
2211
|
|
2212
|
+
if (!name) return todo(scope, 'destructuring is not supported yet');
|
2213
|
+
|
1693
2214
|
if (x.init && isFuncType(x.init.type)) {
|
1694
2215
|
// hack for let a = function () { ... }
|
1695
2216
|
x.init.id = { name };
|
@@ -1705,7 +2226,13 @@ const generateVar = (scope, decl) => {
|
|
1705
2226
|
continue; // always ignore
|
1706
2227
|
}
|
1707
2228
|
|
1708
|
-
|
2229
|
+
const typed = typedInput && x.id.typeAnnotation;
|
2230
|
+
let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
|
2231
|
+
|
2232
|
+
if (typed) {
|
2233
|
+
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
2234
|
+
}
|
2235
|
+
|
1709
2236
|
if (x.init) {
|
1710
2237
|
out = out.concat(generate(scope, x.init, global, name));
|
1711
2238
|
|
@@ -1720,7 +2247,8 @@ const generateVar = (scope, decl) => {
|
|
1720
2247
|
return out;
|
1721
2248
|
};
|
1722
2249
|
|
1723
|
-
|
2250
|
+
// todo: optimize this func for valueUnused
|
2251
|
+
const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
1724
2252
|
const { type, name } = decl.left;
|
1725
2253
|
|
1726
2254
|
if (type === 'ObjectPattern') {
|
@@ -1738,9 +2266,9 @@ const generateAssign = (scope, decl) => {
|
|
1738
2266
|
// hack: .length setter
|
1739
2267
|
if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
|
1740
2268
|
const name = decl.left.object.name;
|
1741
|
-
const pointer = arrays
|
2269
|
+
const pointer = scope.arrays?.get(name);
|
1742
2270
|
|
1743
|
-
const aotPointer = pointer != null;
|
2271
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1744
2272
|
|
1745
2273
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
1746
2274
|
|
@@ -1765,9 +2293,9 @@ const generateAssign = (scope, decl) => {
|
|
1765
2293
|
// arr[i]
|
1766
2294
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
1767
2295
|
const name = decl.left.object.name;
|
1768
|
-
const pointer = arrays
|
2296
|
+
const pointer = scope.arrays?.get(name);
|
1769
2297
|
|
1770
|
-
const aotPointer = pointer != null;
|
2298
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1771
2299
|
|
1772
2300
|
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
1773
2301
|
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
@@ -1823,6 +2351,8 @@ const generateAssign = (scope, decl) => {
|
|
1823
2351
|
];
|
1824
2352
|
}
|
1825
2353
|
|
2354
|
+
if (!name) return todo(scope, 'destructuring is not supported yet', true);
|
2355
|
+
|
1826
2356
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1827
2357
|
|
1828
2358
|
if (local === undefined) {
|
@@ -1869,9 +2399,7 @@ const generateAssign = (scope, decl) => {
|
|
1869
2399
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
1870
2400
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1871
2401
|
|
1872
|
-
|
1873
|
-
// hack: type is idx+1
|
1874
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2402
|
+
...setType(scope, name, getLastType(scope))
|
1875
2403
|
];
|
1876
2404
|
}
|
1877
2405
|
|
@@ -1882,9 +2410,7 @@ const generateAssign = (scope, decl) => {
|
|
1882
2410
|
|
1883
2411
|
// todo: string concat types
|
1884
2412
|
|
1885
|
-
|
1886
|
-
...number(TYPES.number, Valtype.i32),
|
1887
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2413
|
+
...setType(scope, name, TYPES.number)
|
1888
2414
|
];
|
1889
2415
|
};
|
1890
2416
|
|
@@ -1930,7 +2456,7 @@ const generateUnary = (scope, decl) => {
|
|
1930
2456
|
return out;
|
1931
2457
|
}
|
1932
2458
|
|
1933
|
-
case 'delete':
|
2459
|
+
case 'delete': {
|
1934
2460
|
let toReturn = true, toGenerate = true;
|
1935
2461
|
|
1936
2462
|
if (decl.argument.type === 'Identifier') {
|
@@ -1952,38 +2478,60 @@ const generateUnary = (scope, decl) => {
|
|
1952
2478
|
|
1953
2479
|
out.push(...number(toReturn ? 1 : 0));
|
1954
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
|
+
}
|
1955
2496
|
|
1956
|
-
|
1957
|
-
|
2497
|
+
const out = toGenerate ? generate(scope, decl.argument) : [];
|
2498
|
+
disposeLeftover(out);
|
2499
|
+
|
2500
|
+
out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
|
1958
2501
|
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
1959
2502
|
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
1960
2503
|
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
1961
2504
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
1962
2505
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
1963
2506
|
|
2507
|
+
[TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2508
|
+
|
1964
2509
|
// object and internal types
|
1965
2510
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
1966
|
-
});
|
2511
|
+
}));
|
2512
|
+
|
2513
|
+
return out;
|
2514
|
+
}
|
1967
2515
|
|
1968
2516
|
default:
|
1969
|
-
return todo(`unary operator ${decl.operator} not implemented yet
|
2517
|
+
return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
|
1970
2518
|
}
|
1971
2519
|
};
|
1972
2520
|
|
1973
|
-
const generateUpdate = (scope, decl) => {
|
2521
|
+
const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
|
1974
2522
|
const { name } = decl.argument;
|
1975
2523
|
|
1976
2524
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1977
2525
|
|
1978
2526
|
if (local === undefined) {
|
1979
|
-
return todo(`update expression with undefined variable
|
2527
|
+
return todo(scope, `update expression with undefined variable`, true);
|
1980
2528
|
}
|
1981
2529
|
|
1982
2530
|
const idx = local.idx;
|
1983
2531
|
const out = [];
|
1984
2532
|
|
1985
2533
|
out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
1986
|
-
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 ]);
|
1987
2535
|
|
1988
2536
|
switch (decl.operator) {
|
1989
2537
|
case '++':
|
@@ -1996,7 +2544,7 @@ const generateUpdate = (scope, decl) => {
|
|
1996
2544
|
}
|
1997
2545
|
|
1998
2546
|
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
1999
|
-
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 ]);
|
2000
2548
|
|
2001
2549
|
return out;
|
2002
2550
|
};
|
@@ -2036,7 +2584,7 @@ const generateConditional = (scope, decl) => {
|
|
2036
2584
|
// note type
|
2037
2585
|
out.push(
|
2038
2586
|
...getNodeType(scope, decl.consequent),
|
2039
|
-
|
2587
|
+
...setLastType(scope)
|
2040
2588
|
);
|
2041
2589
|
|
2042
2590
|
out.push([ Opcodes.else ]);
|
@@ -2045,7 +2593,7 @@ const generateConditional = (scope, decl) => {
|
|
2045
2593
|
// note type
|
2046
2594
|
out.push(
|
2047
2595
|
...getNodeType(scope, decl.alternate),
|
2048
|
-
|
2596
|
+
...setLastType(scope)
|
2049
2597
|
);
|
2050
2598
|
|
2051
2599
|
out.push([ Opcodes.end ]);
|
@@ -2059,15 +2607,17 @@ const generateFor = (scope, decl) => {
|
|
2059
2607
|
const out = [];
|
2060
2608
|
|
2061
2609
|
if (decl.init) {
|
2062
|
-
out.push(...generate(scope, decl.init));
|
2610
|
+
out.push(...generate(scope, decl.init, false, undefined, true));
|
2063
2611
|
disposeLeftover(out);
|
2064
2612
|
}
|
2065
2613
|
|
2066
2614
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2067
2615
|
depth.push('for');
|
2068
2616
|
|
2069
|
-
out.push(...generate(scope, decl.test));
|
2070
|
-
|
2617
|
+
if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2618
|
+
else out.push(...number(1, Valtype.i32));
|
2619
|
+
|
2620
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
2071
2621
|
depth.push('if');
|
2072
2622
|
|
2073
2623
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2075,8 +2625,7 @@ const generateFor = (scope, decl) => {
|
|
2075
2625
|
out.push(...generate(scope, decl.body));
|
2076
2626
|
out.push([ Opcodes.end ]);
|
2077
2627
|
|
2078
|
-
out.push(...generate(scope, decl.update));
|
2079
|
-
depth.pop();
|
2628
|
+
if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
|
2080
2629
|
|
2081
2630
|
out.push([ Opcodes.br, 1 ]);
|
2082
2631
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2104,6 +2653,36 @@ const generateWhile = (scope, decl) => {
|
|
2104
2653
|
return out;
|
2105
2654
|
};
|
2106
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
|
+
|
2107
2686
|
const generateForOf = (scope, decl) => {
|
2108
2687
|
const out = [];
|
2109
2688
|
|
@@ -2133,8 +2712,17 @@ const generateForOf = (scope, decl) => {
|
|
2133
2712
|
// setup local for left
|
2134
2713
|
generate(scope, decl.left);
|
2135
2714
|
|
2136
|
-
|
2715
|
+
let leftName = decl.left.declarations?.[0]?.id?.name;
|
2716
|
+
if (!leftName && decl.left.name) {
|
2717
|
+
leftName = decl.left.name;
|
2718
|
+
|
2719
|
+
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2720
|
+
}
|
2721
|
+
|
2722
|
+
// if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
|
2723
|
+
|
2137
2724
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2725
|
+
if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
|
2138
2726
|
|
2139
2727
|
depth.push('block');
|
2140
2728
|
depth.push('block');
|
@@ -2142,13 +2730,15 @@ const generateForOf = (scope, decl) => {
|
|
2142
2730
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2143
2731
|
// hack: this is naughty and will break things!
|
2144
2732
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2145
|
-
if (pages.
|
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?
|
2146
2735
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2147
2736
|
rawElements: new Array(1)
|
2148
2737
|
}, isGlobal, leftName, true, 'i16');
|
2149
2738
|
}
|
2150
2739
|
|
2151
2740
|
// set type for local
|
2741
|
+
// todo: optimize away counter and use end pointer
|
2152
2742
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2153
2743
|
[TYPES._array]: [
|
2154
2744
|
...setType(scope, leftName, TYPES.number),
|
@@ -2233,6 +2823,56 @@ const generateForOf = (scope, decl) => {
|
|
2233
2823
|
[ Opcodes.end ],
|
2234
2824
|
[ Opcodes.end ]
|
2235
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
|
+
],
|
2236
2876
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2237
2877
|
}, Blocktype.void));
|
2238
2878
|
|
@@ -2243,28 +2883,65 @@ const generateForOf = (scope, decl) => {
|
|
2243
2883
|
return out;
|
2244
2884
|
};
|
2245
2885
|
|
2886
|
+
// find the nearest loop in depth map by type
|
2246
2887
|
const getNearestLoop = () => {
|
2247
2888
|
for (let i = depth.length - 1; i >= 0; i--) {
|
2248
|
-
if (
|
2889
|
+
if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
|
2249
2890
|
}
|
2250
2891
|
|
2251
2892
|
return -1;
|
2252
2893
|
};
|
2253
2894
|
|
2254
2895
|
const generateBreak = (scope, decl) => {
|
2255
|
-
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
|
+
|
2256
2911
|
return [
|
2257
|
-
[ Opcodes.br, ...signedLEB128(
|
2912
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2258
2913
|
];
|
2259
2914
|
};
|
2260
2915
|
|
2261
2916
|
const generateContinue = (scope, decl) => {
|
2262
|
-
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
|
+
|
2263
2931
|
return [
|
2264
|
-
[ Opcodes.br, ...signedLEB128(
|
2932
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2265
2933
|
];
|
2266
2934
|
};
|
2267
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
|
+
|
2268
2945
|
const generateThrow = (scope, decl) => {
|
2269
2946
|
scope.throws = true;
|
2270
2947
|
|
@@ -2273,7 +2950,7 @@ const generateThrow = (scope, decl) => {
|
|
2273
2950
|
// hack: throw new X("...") -> throw "..."
|
2274
2951
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2275
2952
|
constructor = decl.argument.callee.name;
|
2276
|
-
message = decl.argument.arguments[0]
|
2953
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2277
2954
|
}
|
2278
2955
|
|
2279
2956
|
if (tags.length === 0) tags.push({
|
@@ -2285,6 +2962,9 @@ const generateThrow = (scope, decl) => {
|
|
2285
2962
|
let exceptId = exceptions.push({ constructor, message }) - 1;
|
2286
2963
|
let tagIdx = tags[0].idx;
|
2287
2964
|
|
2965
|
+
scope.exceptions ??= [];
|
2966
|
+
scope.exceptions.push(exceptId);
|
2967
|
+
|
2288
2968
|
// todo: write a description of how this works lol
|
2289
2969
|
|
2290
2970
|
return [
|
@@ -2294,7 +2974,7 @@ const generateThrow = (scope, decl) => {
|
|
2294
2974
|
};
|
2295
2975
|
|
2296
2976
|
const generateTry = (scope, decl) => {
|
2297
|
-
if (decl.finalizer) return todo('try finally not implemented yet');
|
2977
|
+
if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
|
2298
2978
|
|
2299
2979
|
const out = [];
|
2300
2980
|
|
@@ -2325,29 +3005,35 @@ const generateAssignPat = (scope, decl) => {
|
|
2325
3005
|
// TODO
|
2326
3006
|
// if identifier declared, use that
|
2327
3007
|
// else, use default (right)
|
2328
|
-
return todo('assignment pattern (optional arg)');
|
3008
|
+
return todo(scope, 'assignment pattern (optional arg)');
|
2329
3009
|
};
|
2330
3010
|
|
2331
3011
|
let pages = new Map();
|
2332
|
-
const allocPage = (reason, type) => {
|
3012
|
+
const allocPage = (scope, reason, type) => {
|
2333
3013
|
if (pages.has(reason)) return pages.get(reason).ind;
|
2334
3014
|
|
2335
3015
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2336
3016
|
if (reason.startsWith('string:')) pages.hasString = true;
|
3017
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
3018
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2337
3019
|
|
2338
3020
|
const ind = pages.size;
|
2339
3021
|
pages.set(reason, { ind, type });
|
2340
3022
|
|
2341
|
-
|
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})`);
|
2342
3027
|
|
2343
3028
|
return ind;
|
2344
3029
|
};
|
2345
3030
|
|
3031
|
+
// todo: add scope.pages
|
2346
3032
|
const freePage = reason => {
|
2347
3033
|
const { ind } = pages.get(reason);
|
2348
3034
|
pages.delete(reason);
|
2349
3035
|
|
2350
|
-
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
3036
|
+
if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2351
3037
|
|
2352
3038
|
return ind;
|
2353
3039
|
};
|
@@ -2367,38 +3053,51 @@ const StoreOps = {
|
|
2367
3053
|
f64: Opcodes.f64_store,
|
2368
3054
|
|
2369
3055
|
// expects i32 input!
|
2370
|
-
|
3056
|
+
i8: Opcodes.i32_store8,
|
3057
|
+
i16: Opcodes.i32_store16,
|
2371
3058
|
};
|
2372
3059
|
|
2373
3060
|
let data = [];
|
2374
3061
|
|
2375
|
-
const compileBytes = (val, itemType
|
3062
|
+
const compileBytes = (val, itemType) => {
|
2376
3063
|
// todo: this is a mess and needs confirming / ????
|
2377
3064
|
switch (itemType) {
|
2378
3065
|
case 'i8': return [ val % 256 ];
|
2379
|
-
case 'i16': return [ val % 256,
|
2380
|
-
|
2381
|
-
case 'i32':
|
2382
|
-
|
2383
|
-
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
|
2384
3070
|
|
2385
3071
|
case 'f64': return ieee754_binary64(val);
|
2386
3072
|
}
|
2387
3073
|
};
|
2388
3074
|
|
3075
|
+
const getAllocType = itemType => {
|
3076
|
+
switch (itemType) {
|
3077
|
+
case 'i8': return 'bytestring';
|
3078
|
+
case 'i16': return 'string';
|
3079
|
+
|
3080
|
+
default: return 'array';
|
3081
|
+
}
|
3082
|
+
};
|
3083
|
+
|
2389
3084
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2390
3085
|
const out = [];
|
2391
3086
|
|
3087
|
+
scope.arrays ??= new Map();
|
3088
|
+
|
2392
3089
|
let firstAssign = false;
|
2393
|
-
if (!arrays.has(name) || name === '$undeclared') {
|
3090
|
+
if (!scope.arrays.has(name) || name === '$undeclared') {
|
2394
3091
|
firstAssign = true;
|
2395
3092
|
|
2396
3093
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2397
3094
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2398
|
-
|
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);
|
2399
3098
|
}
|
2400
3099
|
|
2401
|
-
const pointer = arrays.get(name);
|
3100
|
+
const pointer = scope.arrays.get(name);
|
2402
3101
|
|
2403
3102
|
const useRawElements = !!decl.rawElements;
|
2404
3103
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
@@ -2406,19 +3105,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2406
3105
|
const valtype = itemTypeToValtype[itemType];
|
2407
3106
|
const length = elements.length;
|
2408
3107
|
|
2409
|
-
if (firstAssign && useRawElements) {
|
2410
|
-
|
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');
|
2411
3112
|
|
2412
|
-
|
2413
|
-
|
3113
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
3114
|
+
if (elements[i] == null) continue;
|
2414
3115
|
|
2415
|
-
|
2416
|
-
|
3116
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
3117
|
+
}
|
2417
3118
|
|
2418
|
-
|
2419
|
-
|
2420
|
-
|
2421
|
-
|
3119
|
+
const ind = data.push({
|
3120
|
+
offset: pointer,
|
3121
|
+
bytes
|
3122
|
+
}) - 1;
|
3123
|
+
|
3124
|
+
scope.data ??= [];
|
3125
|
+
scope.data.push(ind);
|
3126
|
+
}
|
2422
3127
|
|
2423
3128
|
// local value as pointer
|
2424
3129
|
out.push(...number(pointer));
|
@@ -2441,7 +3146,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2441
3146
|
out.push(
|
2442
3147
|
...number(0, Valtype.i32),
|
2443
3148
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2444
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
3149
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2445
3150
|
);
|
2446
3151
|
}
|
2447
3152
|
|
@@ -2451,31 +3156,56 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2451
3156
|
return [ out, pointer ];
|
2452
3157
|
};
|
2453
3158
|
|
2454
|
-
const
|
3159
|
+
const byteStringable = str => {
|
3160
|
+
if (!Prefs.bytestring) return false;
|
3161
|
+
|
3162
|
+
for (let i = 0; i < str.length; i++) {
|
3163
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
3164
|
+
}
|
3165
|
+
|
3166
|
+
return true;
|
3167
|
+
};
|
3168
|
+
|
3169
|
+
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2455
3170
|
const rawElements = new Array(str.length);
|
3171
|
+
let byteStringable = Prefs.bytestring;
|
2456
3172
|
for (let i = 0; i < str.length; i++) {
|
2457
|
-
|
3173
|
+
const c = str.charCodeAt(i);
|
3174
|
+
rawElements[i] = c;
|
3175
|
+
|
3176
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2458
3177
|
}
|
2459
3178
|
|
3179
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
3180
|
+
|
2460
3181
|
return makeArray(scope, {
|
2461
3182
|
rawElements
|
2462
|
-
}, global, name, false, 'i16')[0];
|
3183
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2463
3184
|
};
|
2464
3185
|
|
2465
|
-
let arrays = new Map();
|
2466
3186
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
2467
3187
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
2468
3188
|
};
|
2469
3189
|
|
2470
3190
|
export const generateMember = (scope, decl, _global, _name) => {
|
2471
3191
|
const name = decl.object.name;
|
2472
|
-
const pointer = arrays
|
3192
|
+
const pointer = scope.arrays?.get(name);
|
2473
3193
|
|
2474
|
-
const aotPointer = pointer != null;
|
3194
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2475
3195
|
|
2476
3196
|
// hack: .length
|
2477
3197
|
if (decl.property.name === 'length') {
|
2478
|
-
|
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
|
+
|
2479
3209
|
return [
|
2480
3210
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2481
3211
|
...generate(scope, decl.object),
|
@@ -2487,10 +3217,13 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2487
3217
|
];
|
2488
3218
|
}
|
2489
3219
|
|
3220
|
+
const object = generate(scope, decl.object);
|
3221
|
+
const property = generate(scope, decl.property);
|
3222
|
+
|
2490
3223
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2491
3224
|
// hack: this is naughty and will break things!
|
2492
|
-
let newOut = number(0,
|
2493
|
-
if (pages.
|
3225
|
+
let newOut = number(0, valtypeBinary), newPointer = -1;
|
3226
|
+
if (pages.hasAnyString) {
|
2494
3227
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2495
3228
|
rawElements: new Array(1)
|
2496
3229
|
}, _global, _name, true, 'i16');
|
@@ -2499,7 +3232,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2499
3232
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2500
3233
|
[TYPES._array]: [
|
2501
3234
|
// get index as valtype
|
2502
|
-
...
|
3235
|
+
...property,
|
2503
3236
|
|
2504
3237
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2505
3238
|
Opcodes.i32_to_u,
|
@@ -2507,7 +3240,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2507
3240
|
[ Opcodes.i32_mul ],
|
2508
3241
|
|
2509
3242
|
...(aotPointer ? [] : [
|
2510
|
-
...
|
3243
|
+
...object,
|
2511
3244
|
Opcodes.i32_to_u,
|
2512
3245
|
[ Opcodes.i32_add ]
|
2513
3246
|
]),
|
@@ -2516,7 +3249,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2516
3249
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2517
3250
|
|
2518
3251
|
...number(TYPES.number, Valtype.i32),
|
2519
|
-
|
3252
|
+
...setLastType(scope)
|
2520
3253
|
],
|
2521
3254
|
|
2522
3255
|
[TYPES.string]: [
|
@@ -2526,14 +3259,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2526
3259
|
|
2527
3260
|
...number(0, Valtype.i32), // base 0 for store later
|
2528
3261
|
|
2529
|
-
...
|
2530
|
-
|
3262
|
+
...property,
|
2531
3263
|
Opcodes.i32_to_u,
|
3264
|
+
|
2532
3265
|
...number(ValtypeSize.i16, Valtype.i32),
|
2533
3266
|
[ Opcodes.i32_mul ],
|
2534
3267
|
|
2535
3268
|
...(aotPointer ? [] : [
|
2536
|
-
...
|
3269
|
+
...object,
|
2537
3270
|
Opcodes.i32_to_u,
|
2538
3271
|
[ Opcodes.i32_add ]
|
2539
3272
|
]),
|
@@ -2548,10 +3281,38 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2548
3281
|
...number(newPointer),
|
2549
3282
|
|
2550
3283
|
...number(TYPES.string, Valtype.i32),
|
2551
|
-
|
3284
|
+
...setLastType(scope)
|
3285
|
+
],
|
3286
|
+
[TYPES._bytestring]: [
|
3287
|
+
// setup new/out array
|
3288
|
+
...newOut,
|
3289
|
+
[ Opcodes.drop ],
|
3290
|
+
|
3291
|
+
...number(0, Valtype.i32), // base 0 for store later
|
3292
|
+
|
3293
|
+
...property,
|
3294
|
+
Opcodes.i32_to_u,
|
3295
|
+
|
3296
|
+
...(aotPointer ? [] : [
|
3297
|
+
...object,
|
3298
|
+
Opcodes.i32_to_u,
|
3299
|
+
[ Opcodes.i32_add ]
|
3300
|
+
]),
|
3301
|
+
|
3302
|
+
// load current string ind {arg}
|
3303
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
3304
|
+
|
3305
|
+
// store to new string ind 0
|
3306
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
3307
|
+
|
3308
|
+
// return new string (page)
|
3309
|
+
...number(newPointer),
|
3310
|
+
|
3311
|
+
...number(TYPES._bytestring, Valtype.i32),
|
3312
|
+
...setLastType(scope)
|
2552
3313
|
],
|
2553
3314
|
|
2554
|
-
default:
|
3315
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
|
2555
3316
|
});
|
2556
3317
|
};
|
2557
3318
|
|
@@ -2561,25 +3322,36 @@ const objectHack = node => {
|
|
2561
3322
|
if (!node) return node;
|
2562
3323
|
|
2563
3324
|
if (node.type === 'MemberExpression') {
|
2564
|
-
|
3325
|
+
const out = (() => {
|
3326
|
+
if (node.computed || node.optional) return;
|
2565
3327
|
|
2566
|
-
|
3328
|
+
let objectName = node.object.name;
|
2567
3329
|
|
2568
|
-
|
2569
|
-
|
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;
|
2570
3333
|
|
2571
|
-
|
3334
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2572
3335
|
|
2573
|
-
|
2574
|
-
|
3336
|
+
// if .length, give up (hack within a hack!)
|
3337
|
+
if (node.property.name === 'length') {
|
3338
|
+
node.object = objectHack(node.object);
|
3339
|
+
return;
|
3340
|
+
}
|
2575
3341
|
|
2576
|
-
|
2577
|
-
|
3342
|
+
// no object name, give up
|
3343
|
+
if (!objectName) return;
|
2578
3344
|
|
2579
|
-
|
2580
|
-
|
2581
|
-
|
2582
|
-
|
3345
|
+
const name = '__' + objectName + '_' + node.property.name;
|
3346
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
3347
|
+
|
3348
|
+
return {
|
3349
|
+
type: 'Identifier',
|
3350
|
+
name
|
3351
|
+
};
|
3352
|
+
})();
|
3353
|
+
|
3354
|
+
if (out) return out;
|
2583
3355
|
}
|
2584
3356
|
|
2585
3357
|
for (const x in node) {
|
@@ -2593,11 +3365,11 @@ const objectHack = node => {
|
|
2593
3365
|
};
|
2594
3366
|
|
2595
3367
|
const generateFunc = (scope, decl) => {
|
2596
|
-
if (decl.async) return todo('async functions are not supported');
|
2597
|
-
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');
|
2598
3370
|
|
2599
3371
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2600
|
-
const params = decl.params
|
3372
|
+
const params = decl.params ?? [];
|
2601
3373
|
|
2602
3374
|
// const innerScope = { ...scope };
|
2603
3375
|
// TODO: share scope/locals between !!!
|
@@ -2610,8 +3382,17 @@ const generateFunc = (scope, decl) => {
|
|
2610
3382
|
name
|
2611
3383
|
};
|
2612
3384
|
|
3385
|
+
if (typedInput && decl.returnType) {
|
3386
|
+
innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
|
3387
|
+
innerScope.returns = [ valtypeBinary ];
|
3388
|
+
}
|
3389
|
+
|
2613
3390
|
for (let i = 0; i < params.length; i++) {
|
2614
|
-
allocVar(innerScope, params[i], false);
|
3391
|
+
allocVar(innerScope, params[i].name, false);
|
3392
|
+
|
3393
|
+
if (typedInput && params[i].typeAnnotation) {
|
3394
|
+
addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
|
3395
|
+
}
|
2615
3396
|
}
|
2616
3397
|
|
2617
3398
|
let body = objectHack(decl.body);
|
@@ -2627,13 +3408,13 @@ const generateFunc = (scope, decl) => {
|
|
2627
3408
|
const func = {
|
2628
3409
|
name,
|
2629
3410
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2630
|
-
|
2631
|
-
|
2632
|
-
throws: innerScope.throws,
|
2633
|
-
index: currentFuncIndex++
|
3411
|
+
index: currentFuncIndex++,
|
3412
|
+
...innerScope
|
2634
3413
|
};
|
2635
3414
|
funcIndex[name] = func.index;
|
2636
3415
|
|
3416
|
+
if (name === 'main') func.gotLastType = true;
|
3417
|
+
|
2637
3418
|
// quick hack fixes
|
2638
3419
|
for (const inst of wasm) {
|
2639
3420
|
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
@@ -2650,117 +3431,6 @@ const generateFunc = (scope, decl) => {
|
|
2650
3431
|
);
|
2651
3432
|
}
|
2652
3433
|
|
2653
|
-
// change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
|
2654
|
-
let offset = 0, vecParams = 0;
|
2655
|
-
for (let i = 0; i < params.length; i++) {
|
2656
|
-
const name = params[i];
|
2657
|
-
const local = func.locals[name];
|
2658
|
-
if (local.type === Valtype.v128) {
|
2659
|
-
vecParams++;
|
2660
|
-
|
2661
|
-
/* wasm.unshift( // add v128 load for param
|
2662
|
-
[ Opcodes.i32_const, 0 ],
|
2663
|
-
[ ...Opcodes.v128_load, 0, i * 16 ],
|
2664
|
-
[ Opcodes.local_set, local.idx ]
|
2665
|
-
); */
|
2666
|
-
|
2667
|
-
// using params and replace_lane is noticably faster than just loading from memory (above) somehow
|
2668
|
-
|
2669
|
-
// extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
|
2670
|
-
const { vecType } = local;
|
2671
|
-
let [ type, lanes ] = vecType.split('x');
|
2672
|
-
if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
|
2673
|
-
|
2674
|
-
lanes = parseInt(lanes);
|
2675
|
-
type = Valtype[type];
|
2676
|
-
|
2677
|
-
const name = params[i]; // get original param name
|
2678
|
-
|
2679
|
-
func.params.splice(offset, 1, ...new Array(lanes).fill(type)); // add new params of {type}, {lanes} times
|
2680
|
-
|
2681
|
-
// update index of original local
|
2682
|
-
// delete func.locals[name];
|
2683
|
-
|
2684
|
-
// add new locals for params
|
2685
|
-
for (let j = 0; j < lanes; j++) {
|
2686
|
-
func.locals[name + j] = { idx: offset + j, type, vecParamAutogen: true };
|
2687
|
-
}
|
2688
|
-
|
2689
|
-
// prepend wasm to generate expected v128 locals
|
2690
|
-
wasm.splice(i * 2 + offset * 2, 0,
|
2691
|
-
...i32x4(0, 0, 0, 0),
|
2692
|
-
...new Array(lanes).fill(0).flatMap((_, j) => [
|
2693
|
-
[ Opcodes.local_get, offset + j ],
|
2694
|
-
[ ...Opcodes[vecType + '_replace_lane'], j ]
|
2695
|
-
]),
|
2696
|
-
[ Opcodes.local_set, i ]
|
2697
|
-
);
|
2698
|
-
|
2699
|
-
offset += lanes;
|
2700
|
-
|
2701
|
-
// note: wrapping is disabled for now due to perf/dx concerns (so this will never run)
|
2702
|
-
/* if (!func.name.startsWith('#')) func.name = '##' + func.name;
|
2703
|
-
|
2704
|
-
// add vec type index to hash name prefix for wrapper to know how to wrap
|
2705
|
-
const vecTypeIdx = [ 'i8x16', 'i16x8', 'i32x4', 'i64x2', 'f32x4', 'f64x2' ].indexOf(local.vecType);
|
2706
|
-
const secondHash = func.name.slice(1).indexOf('#');
|
2707
|
-
func.name = '#' + func.name.slice(1, secondHash) + vecTypeIdx + func.name.slice(secondHash); */
|
2708
|
-
}
|
2709
|
-
}
|
2710
|
-
|
2711
|
-
if (offset !== 0) {
|
2712
|
-
// bump local indexes for all other locals after
|
2713
|
-
for (const x in func.locals) {
|
2714
|
-
const local = func.locals[x];
|
2715
|
-
if (!local.vecParamAutogen) local.idx += offset;
|
2716
|
-
}
|
2717
|
-
|
2718
|
-
// bump local indexes in wasm local.get/set
|
2719
|
-
for (let j = 0; j < wasm.length; j++) {
|
2720
|
-
const inst = wasm[j];
|
2721
|
-
if (j < offset * 2 + vecParams * 2) {
|
2722
|
-
if (inst[0] === Opcodes.local_set) inst[1] += offset;
|
2723
|
-
continue;
|
2724
|
-
}
|
2725
|
-
|
2726
|
-
if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set) inst[1] += offset;
|
2727
|
-
}
|
2728
|
-
}
|
2729
|
-
|
2730
|
-
// change v128 return into many <type> instead as unsupported return valtype
|
2731
|
-
const lastReturnLocal = wasm.length > 2 && wasm[wasm.length - 1][0] === Opcodes.return && Object.values(func.locals).find(x => x.idx === wasm[wasm.length - 2][1]);
|
2732
|
-
if (lastReturnLocal && lastReturnLocal.type === Valtype.v128) {
|
2733
|
-
const name = Object.keys(func.locals)[Object.values(func.locals).indexOf(lastReturnLocal)];
|
2734
|
-
// extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
|
2735
|
-
const { vecType } = lastReturnLocal;
|
2736
|
-
let [ type, lanes ] = vecType.split('x');
|
2737
|
-
if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
|
2738
|
-
|
2739
|
-
lanes = parseInt(lanes);
|
2740
|
-
type = Valtype[type];
|
2741
|
-
|
2742
|
-
const vecIdx = lastReturnLocal.idx;
|
2743
|
-
|
2744
|
-
const lastIdx = Math.max(0, ...Object.values(func.locals).map(x => x.idx));
|
2745
|
-
const tmpIdx = [];
|
2746
|
-
for (let i = 0; i < lanes; i++) {
|
2747
|
-
const idx = lastIdx + i + 1;
|
2748
|
-
tmpIdx.push(idx);
|
2749
|
-
func.locals[name + i] = { idx, type, vecReturnAutogen: true };
|
2750
|
-
}
|
2751
|
-
|
2752
|
-
wasm.splice(wasm.length - 1, 1,
|
2753
|
-
...new Array(lanes).fill(0).flatMap((_, i) => [
|
2754
|
-
i === 0 ? null : [ Opcodes.local_get, vecIdx ],
|
2755
|
-
[ ...Opcodes[vecType + '_extract_lane'], i ],
|
2756
|
-
[ Opcodes.local_set, tmpIdx[i] ],
|
2757
|
-
].filter(x => x !== null)),
|
2758
|
-
...new Array(lanes).fill(0).map((_, i) => [ Opcodes.local_get, tmpIdx[i]])
|
2759
|
-
);
|
2760
|
-
|
2761
|
-
func.returns = new Array(lanes).fill(type);
|
2762
|
-
}
|
2763
|
-
|
2764
3434
|
func.wasm = wasm;
|
2765
3435
|
|
2766
3436
|
funcs.push(func);
|
@@ -2796,7 +3466,7 @@ const internalConstrs = {
|
|
2796
3466
|
|
2797
3467
|
// todo: check in wasm instead of here
|
2798
3468
|
const literalValue = arg.value ?? 0;
|
2799
|
-
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);
|
2800
3470
|
|
2801
3471
|
return [
|
2802
3472
|
...number(0, Valtype.i32),
|
@@ -2807,7 +3477,8 @@ const internalConstrs = {
|
|
2807
3477
|
...number(pointer)
|
2808
3478
|
];
|
2809
3479
|
},
|
2810
|
-
type: TYPES._array
|
3480
|
+
type: TYPES._array,
|
3481
|
+
length: 1
|
2811
3482
|
},
|
2812
3483
|
|
2813
3484
|
__Array_of: {
|
@@ -2819,7 +3490,131 @@ const internalConstrs = {
|
|
2819
3490
|
}, global, name);
|
2820
3491
|
},
|
2821
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,
|
2822
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,
|
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
|
2823
3618
|
}
|
2824
3619
|
};
|
2825
3620
|
|
@@ -2848,7 +3643,6 @@ export default program => {
|
|
2848
3643
|
funcs = [];
|
2849
3644
|
funcIndex = {};
|
2850
3645
|
depth = [];
|
2851
|
-
arrays = new Map();
|
2852
3646
|
pages = new Map();
|
2853
3647
|
data = [];
|
2854
3648
|
currentFuncIndex = importedFuncs.length;
|
@@ -2862,6 +3656,10 @@ export default program => {
|
|
2862
3656
|
|
2863
3657
|
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
2864
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
|
+
|
2865
3663
|
// set generic opcodes for current valtype
|
2866
3664
|
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
2867
3665
|
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
@@ -2870,10 +3668,10 @@ export default program => {
|
|
2870
3668
|
Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
|
2871
3669
|
Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
|
2872
3670
|
|
2873
|
-
Opcodes.i32_to = [ [
|
2874
|
-
Opcodes.i32_to_u = [ [
|
2875
|
-
Opcodes.i32_from = [ [
|
2876
|
-
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];
|
2877
3675
|
|
2878
3676
|
Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
|
2879
3677
|
Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
|
@@ -2886,10 +3684,6 @@ export default program => {
|
|
2886
3684
|
|
2887
3685
|
program.id = { name: 'main' };
|
2888
3686
|
|
2889
|
-
globalThis.pageSize = PageSize;
|
2890
|
-
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
2891
|
-
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
2892
|
-
|
2893
3687
|
const scope = {
|
2894
3688
|
locals: {},
|
2895
3689
|
localInd: 0
|
@@ -2900,7 +3694,7 @@ export default program => {
|
|
2900
3694
|
body: program.body
|
2901
3695
|
};
|
2902
3696
|
|
2903
|
-
if (
|
3697
|
+
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
2904
3698
|
|
2905
3699
|
generateFunc(scope, program);
|
2906
3700
|
|
@@ -2917,7 +3711,11 @@ export default program => {
|
|
2917
3711
|
}
|
2918
3712
|
|
2919
3713
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
2920
|
-
|
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
|
+
}
|
2921
3719
|
}
|
2922
3720
|
|
2923
3721
|
if (lastInst[0] === Opcodes.call) {
|