porffor 0.2.0-c7b7423 → 0.2.0-c87ffeb
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/.vscode/launch.json +18 -0
- package/LICENSE +20 -20
- package/README.md +131 -71
- package/asur/README.md +2 -0
- package/asur/index.js +1262 -0
- package/byg/index.js +237 -0
- package/compiler/2c.js +322 -72
- package/compiler/{sections.js → assemble.js} +63 -15
- package/compiler/builtins/annexb_string.js +72 -0
- package/compiler/builtins/annexb_string.ts +19 -0
- package/compiler/builtins/array.ts +145 -0
- package/compiler/builtins/base64.ts +151 -0
- package/compiler/builtins/crypto.ts +120 -0
- package/compiler/builtins/date.ts +7 -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 -269
- package/compiler/{codeGen.js → codegen.js} +1231 -472
- package/compiler/decompile.js +3 -3
- package/compiler/embedding.js +22 -22
- package/compiler/encoding.js +98 -114
- package/compiler/generated_builtins.js +695 -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 +30 -7
- package/compiler/wrap.js +138 -42
- package/package.json +9 -5
- package/porf +4 -0
- package/rhemyn/compile.js +5 -3
- package/rhemyn/parse.js +323 -320
- package/rhemyn/test/parse.js +58 -58
- package/runner/compare.js +34 -34
- package/runner/debug.js +122 -0
- package/runner/index.js +49 -10
- package/runner/profiler.js +102 -0
- package/runner/repl.js +42 -9
- package/runner/sizes.js +37 -37
- package/test262_changes_from_1afe9b87d2_to_04-09.md +270 -0
- 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 -69
- 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,7 +204,11 @@ 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
213
|
out.push([ ...inst, ...immediates ]);
|
189
214
|
}
|
@@ -191,30 +216,53 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
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,47 +1187,29 @@ const generateLogicExp = (scope, decl) => {
|
|
1001
1187
|
// 4: internal type
|
1002
1188
|
// 5: pointer
|
1003
1189
|
|
1004
|
-
const
|
1005
|
-
|
1006
|
-
|
1007
|
-
string: 0x02,
|
1008
|
-
undefined: 0x03,
|
1009
|
-
object: 0x04,
|
1010
|
-
function: 0x05,
|
1011
|
-
symbol: 0x06,
|
1012
|
-
bigint: 0x07,
|
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)];
|
1013
1193
|
|
1014
|
-
|
1015
|
-
_array: 0x10,
|
1016
|
-
_regexp: 0x11
|
1017
|
-
};
|
1018
|
-
|
1019
|
-
const TYPE_NAMES = {
|
1020
|
-
[TYPES.number]: 'Number',
|
1021
|
-
[TYPES.boolean]: 'Boolean',
|
1022
|
-
[TYPES.string]: 'String',
|
1023
|
-
[TYPES.undefined]: 'undefined',
|
1024
|
-
[TYPES.object]: 'Object',
|
1025
|
-
[TYPES.function]: 'Function',
|
1026
|
-
[TYPES.symbol]: 'Symbol',
|
1027
|
-
[TYPES.bigint]: 'BigInt',
|
1028
|
-
|
1029
|
-
[TYPES._array]: 'Array',
|
1030
|
-
[TYPES._regexp]: 'RegExp'
|
1194
|
+
return false;
|
1031
1195
|
};
|
1032
1196
|
|
1033
1197
|
const getType = (scope, _name) => {
|
1034
1198
|
const name = mapName(_name);
|
1035
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);
|
1036
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);
|
1037
1206
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1038
1207
|
|
1039
1208
|
let type = TYPES.undefined;
|
1040
|
-
if (builtinVars[name]) type =
|
1209
|
+
if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
|
1041
1210
|
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
|
1042
1211
|
|
1043
|
-
if (name
|
1044
|
-
name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
|
1212
|
+
if (isExistingProtoFunc(name)) type = TYPES.function;
|
1045
1213
|
|
1046
1214
|
return number(type, Valtype.i32);
|
1047
1215
|
};
|
@@ -1051,17 +1219,29 @@ const setType = (scope, _name, type) => {
|
|
1051
1219
|
|
1052
1220
|
const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
|
1053
1221
|
|
1222
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return [];
|
1054
1223
|
if (scope.locals[name]) return [
|
1055
1224
|
...out,
|
1056
1225
|
[ Opcodes.local_set, scope.locals[name + '#type'].idx ]
|
1057
1226
|
];
|
1058
1227
|
|
1228
|
+
if (typedInput && globals[name]?.metadata?.type != null) return [];
|
1059
1229
|
if (globals[name]) return [
|
1060
1230
|
...out,
|
1061
1231
|
[ Opcodes.global_set, globals[name + '#type'].idx ]
|
1062
1232
|
];
|
1063
1233
|
|
1064
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) ] ];
|
1065
1245
|
};
|
1066
1246
|
|
1067
1247
|
const getNodeType = (scope, node) => {
|
@@ -1069,6 +1249,8 @@ const getNodeType = (scope, node) => {
|
|
1069
1249
|
if (node.type === 'Literal') {
|
1070
1250
|
if (node.regex) return TYPES._regexp;
|
1071
1251
|
|
1252
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
|
1253
|
+
|
1072
1254
|
return TYPES[typeof node.value];
|
1073
1255
|
}
|
1074
1256
|
|
@@ -1082,6 +1264,15 @@ const getNodeType = (scope, node) => {
|
|
1082
1264
|
|
1083
1265
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1084
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
|
+
|
1085
1276
|
const func = funcs.find(x => x.name === name);
|
1086
1277
|
|
1087
1278
|
if (func) {
|
@@ -1089,10 +1280,27 @@ const getNodeType = (scope, node) => {
|
|
1089
1280
|
if (func.returnType) return func.returnType;
|
1090
1281
|
}
|
1091
1282
|
|
1092
|
-
if (builtinFuncs[name]) return
|
1283
|
+
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
1093
1284
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1094
1285
|
|
1095
|
-
|
1286
|
+
// check if this is a prototype function
|
1287
|
+
// if so and there is only one impl (eg charCodeAt)
|
1288
|
+
// use that return type as that is the only possibility
|
1289
|
+
// (if non-matching type it would error out)
|
1290
|
+
if (name.startsWith('__')) {
|
1291
|
+
const spl = name.slice(2).split('_');
|
1292
|
+
|
1293
|
+
const func = spl[spl.length - 1];
|
1294
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
|
1295
|
+
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1296
|
+
}
|
1297
|
+
|
1298
|
+
if (name.startsWith('__Porffor_wasm_')) {
|
1299
|
+
// todo: return undefined for non-returning ops
|
1300
|
+
return TYPES.number;
|
1301
|
+
}
|
1302
|
+
|
1303
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1096
1304
|
|
1097
1305
|
// presume
|
1098
1306
|
// todo: warn here?
|
@@ -1140,6 +1348,15 @@ const getNodeType = (scope, node) => {
|
|
1140
1348
|
|
1141
1349
|
if (node.type === 'BinaryExpression') {
|
1142
1350
|
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
1351
|
+
if (node.operator !== '+') return TYPES.number;
|
1352
|
+
|
1353
|
+
const knownLeft = knownType(scope, getNodeType(scope, node.left));
|
1354
|
+
const knownRight = knownType(scope, getNodeType(scope, node.right));
|
1355
|
+
|
1356
|
+
// todo: this should be dynamic but for now only static
|
1357
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
|
1358
|
+
if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) return TYPES._bytestring;
|
1359
|
+
|
1143
1360
|
return TYPES.number;
|
1144
1361
|
|
1145
1362
|
// todo: string concat types
|
@@ -1164,7 +1381,7 @@ const getNodeType = (scope, node) => {
|
|
1164
1381
|
if (node.operator === '!') return TYPES.boolean;
|
1165
1382
|
if (node.operator === 'void') return TYPES.undefined;
|
1166
1383
|
if (node.operator === 'delete') return TYPES.boolean;
|
1167
|
-
if (node.operator === 'typeof') return TYPES.string;
|
1384
|
+
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
|
1168
1385
|
|
1169
1386
|
return TYPES.number;
|
1170
1387
|
}
|
@@ -1173,11 +1390,23 @@ const getNodeType = (scope, node) => {
|
|
1173
1390
|
// hack: if something.length, number type
|
1174
1391
|
if (node.property.name === 'length') return TYPES.number;
|
1175
1392
|
|
1176
|
-
//
|
1393
|
+
// ts hack
|
1394
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
|
1395
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES._bytestring) return TYPES._bytestring;
|
1396
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
|
1397
|
+
|
1398
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1399
|
+
|
1400
|
+
// presume
|
1177
1401
|
return TYPES.number;
|
1178
1402
|
}
|
1179
1403
|
|
1180
|
-
if (
|
1404
|
+
if (node.type === 'TaggedTemplateExpression') {
|
1405
|
+
// hack
|
1406
|
+
if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
|
1407
|
+
}
|
1408
|
+
|
1409
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1181
1410
|
|
1182
1411
|
// presume
|
1183
1412
|
// todo: warn here?
|
@@ -1193,8 +1422,8 @@ const getNodeType = (scope, node) => {
|
|
1193
1422
|
const generateLiteral = (scope, decl, global, name) => {
|
1194
1423
|
if (decl.value === null) return number(NULL);
|
1195
1424
|
|
1425
|
+
// hack: just return 1 for regex literals
|
1196
1426
|
if (decl.regex) {
|
1197
|
-
scope.regex[name] = decl.regex;
|
1198
1427
|
return number(1);
|
1199
1428
|
}
|
1200
1429
|
|
@@ -1207,19 +1436,10 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1207
1436
|
return number(decl.value ? 1 : 0);
|
1208
1437
|
|
1209
1438
|
case 'string':
|
1210
|
-
|
1211
|
-
const rawElements = new Array(str.length);
|
1212
|
-
let j = 0;
|
1213
|
-
for (let i = 0; i < str.length; i++) {
|
1214
|
-
rawElements[i] = str.charCodeAt(i);
|
1215
|
-
}
|
1216
|
-
|
1217
|
-
return makeArray(scope, {
|
1218
|
-
rawElements
|
1219
|
-
}, global, name, false, 'i16')[0];
|
1439
|
+
return makeString(scope, decl.value, global, name);
|
1220
1440
|
|
1221
1441
|
default:
|
1222
|
-
return todo(`cannot generate literal of type ${typeof decl.value}
|
1442
|
+
return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
|
1223
1443
|
}
|
1224
1444
|
};
|
1225
1445
|
|
@@ -1228,6 +1448,8 @@ const countLeftover = wasm => {
|
|
1228
1448
|
|
1229
1449
|
for (let i = 0; i < wasm.length; i++) {
|
1230
1450
|
const inst = wasm[i];
|
1451
|
+
if (inst[0] == null) continue;
|
1452
|
+
|
1231
1453
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
1232
1454
|
if (inst[0] === Opcodes.if) count--;
|
1233
1455
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1236,18 +1458,25 @@ const countLeftover = wasm => {
|
|
1236
1458
|
if (inst[0] === Opcodes.end) depth--;
|
1237
1459
|
|
1238
1460
|
if (depth === 0)
|
1239
|
-
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1240
|
-
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)) {}
|
1461
|
+
if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1462
|
+
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)) {}
|
1241
1463
|
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1242
|
-
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
|
1464
|
+
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1243
1465
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1244
1466
|
else if (inst[0] === Opcodes.return) count = 0;
|
1245
1467
|
else if (inst[0] === Opcodes.call) {
|
1246
1468
|
let func = funcs.find(x => x.index === inst[1]);
|
1247
|
-
if (
|
1248
|
-
count
|
1249
|
-
} else
|
1250
|
-
|
1469
|
+
if (inst[1] === -1) {
|
1470
|
+
// todo: count for calling self
|
1471
|
+
} else if (!func && inst[1] < importedFuncs.length) {
|
1472
|
+
count -= importedFuncs[inst[1]].params;
|
1473
|
+
count += importedFuncs[inst[1]].returns;
|
1474
|
+
} else {
|
1475
|
+
if (func) {
|
1476
|
+
count -= func.params.length;
|
1477
|
+
} else count--;
|
1478
|
+
if (func) count += func.returns.length;
|
1479
|
+
}
|
1251
1480
|
} else count--;
|
1252
1481
|
|
1253
1482
|
// console.log(count, decompile([ inst ]).slice(0, -1));
|
@@ -1265,7 +1494,7 @@ const disposeLeftover = wasm => {
|
|
1265
1494
|
const generateExp = (scope, decl) => {
|
1266
1495
|
const expression = decl.expression;
|
1267
1496
|
|
1268
|
-
const out = generate(scope, expression);
|
1497
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1269
1498
|
disposeLeftover(out);
|
1270
1499
|
|
1271
1500
|
return out;
|
@@ -1323,7 +1552,7 @@ const RTArrayUtil = {
|
|
1323
1552
|
]
|
1324
1553
|
};
|
1325
1554
|
|
1326
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1555
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1327
1556
|
/* const callee = decl.callee;
|
1328
1557
|
const args = decl.arguments;
|
1329
1558
|
|
@@ -1339,10 +1568,21 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1339
1568
|
name = func.name;
|
1340
1569
|
}
|
1341
1570
|
|
1342
|
-
if (name === 'eval' && decl.arguments[0]
|
1571
|
+
if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
|
1343
1572
|
// literal eval hack
|
1344
|
-
const code = decl.arguments[0]
|
1345
|
-
|
1573
|
+
const code = decl.arguments[0]?.value ?? '';
|
1574
|
+
|
1575
|
+
let parsed;
|
1576
|
+
try {
|
1577
|
+
parsed = parse(code, []);
|
1578
|
+
} catch (e) {
|
1579
|
+
if (e.name === 'SyntaxError') {
|
1580
|
+
// throw syntax errors of evals at runtime instead
|
1581
|
+
return internalThrow(scope, 'SyntaxError', e.message, true);
|
1582
|
+
}
|
1583
|
+
|
1584
|
+
throw e;
|
1585
|
+
}
|
1346
1586
|
|
1347
1587
|
const out = generate(scope, {
|
1348
1588
|
type: 'BlockStatement',
|
@@ -1356,13 +1596,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1356
1596
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1357
1597
|
out.push(
|
1358
1598
|
...getNodeType(scope, finalStatement),
|
1359
|
-
|
1599
|
+
...setLastType(scope)
|
1360
1600
|
);
|
1361
1601
|
} else if (countLeftover(out) === 0) {
|
1362
1602
|
out.push(...number(UNDEFINED));
|
1363
1603
|
out.push(
|
1364
1604
|
...number(TYPES.undefined, Valtype.i32),
|
1365
|
-
|
1605
|
+
...setLastType(scope)
|
1366
1606
|
);
|
1367
1607
|
}
|
1368
1608
|
|
@@ -1380,18 +1620,20 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1380
1620
|
if (name && name.startsWith('__')) {
|
1381
1621
|
const spl = name.slice(2).split('_');
|
1382
1622
|
|
1383
|
-
|
1384
|
-
protoName = func;
|
1623
|
+
protoName = spl[spl.length - 1];
|
1385
1624
|
|
1386
1625
|
target = { ...decl.callee };
|
1387
1626
|
target.name = spl.slice(0, -1).join('_');
|
1627
|
+
|
1628
|
+
// failed to lookup name, abort
|
1629
|
+
if (!lookupName(scope, target.name)[0]) protoName = null;
|
1388
1630
|
}
|
1389
1631
|
|
1390
1632
|
// literal.func()
|
1391
1633
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1392
1634
|
// megahack for /regex/.func()
|
1393
|
-
|
1394
|
-
|
1635
|
+
const funcName = decl.callee.property.name;
|
1636
|
+
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1395
1637
|
const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
|
1396
1638
|
|
1397
1639
|
funcIndex[func.name] = func.index;
|
@@ -1407,12 +1649,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1407
1649
|
Opcodes.i32_from_u,
|
1408
1650
|
|
1409
1651
|
...number(TYPES.boolean, Valtype.i32),
|
1410
|
-
|
1652
|
+
...setLastType(scope)
|
1411
1653
|
];
|
1412
1654
|
}
|
1413
1655
|
|
1414
|
-
|
1415
|
-
protoName = func;
|
1656
|
+
protoName = decl.callee.property.name;
|
1416
1657
|
|
1417
1658
|
target = decl.callee.object;
|
1418
1659
|
}
|
@@ -1433,23 +1674,47 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1433
1674
|
// }
|
1434
1675
|
|
1435
1676
|
if (protoName) {
|
1677
|
+
const protoBC = {};
|
1678
|
+
|
1679
|
+
const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
|
1680
|
+
|
1681
|
+
if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
|
1682
|
+
for (const x of builtinProtoCands) {
|
1683
|
+
const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
|
1684
|
+
if (type == null) continue;
|
1685
|
+
|
1686
|
+
protoBC[type] = generateCall(scope, {
|
1687
|
+
callee: {
|
1688
|
+
name: x
|
1689
|
+
},
|
1690
|
+
arguments: [ target, ...decl.arguments ],
|
1691
|
+
_protoInternalCall: true
|
1692
|
+
});
|
1693
|
+
}
|
1694
|
+
}
|
1695
|
+
|
1436
1696
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1437
|
-
|
1438
|
-
if (f) acc[x] = f;
|
1697
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1439
1698
|
return acc;
|
1440
1699
|
}, {});
|
1441
1700
|
|
1442
|
-
// no prototype function candidates, ignore
|
1443
1701
|
if (Object.keys(protoCands).length > 0) {
|
1444
1702
|
// use local for cached i32 length as commonly used
|
1445
1703
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1446
1704
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1447
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1448
1705
|
|
1449
1706
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1450
1707
|
|
1708
|
+
const rawPointer = [
|
1709
|
+
...generate(scope, target),
|
1710
|
+
Opcodes.i32_to_u
|
1711
|
+
];
|
1712
|
+
|
1713
|
+
const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
|
1714
|
+
const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
|
1715
|
+
|
1716
|
+
let allOptUnused = true;
|
1451
1717
|
let lengthI32CacheUsed = false;
|
1452
|
-
const protoBC = {};
|
1453
1718
|
for (const x in protoCands) {
|
1454
1719
|
const protoFunc = protoCands[x];
|
1455
1720
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
@@ -1457,7 +1722,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1457
1722
|
...RTArrayUtil.getLength(getPointer),
|
1458
1723
|
|
1459
1724
|
...number(TYPES.number, Valtype.i32),
|
1460
|
-
|
1725
|
+
...setLastType(scope)
|
1461
1726
|
];
|
1462
1727
|
continue;
|
1463
1728
|
}
|
@@ -1467,6 +1732,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1467
1732
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1468
1733
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1469
1734
|
|
1735
|
+
let optUnused = false;
|
1470
1736
|
const protoOut = protoFunc(getPointer, {
|
1471
1737
|
getCachedI32: () => {
|
1472
1738
|
lengthI32CacheUsed = true;
|
@@ -1481,23 +1747,30 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1481
1747
|
return makeArray(scope, {
|
1482
1748
|
rawElements: new Array(length)
|
1483
1749
|
}, _global, _name, true, itemType);
|
1750
|
+
}, () => {
|
1751
|
+
optUnused = true;
|
1752
|
+
return unusedValue;
|
1484
1753
|
});
|
1485
1754
|
|
1755
|
+
if (!optUnused) allOptUnused = false;
|
1756
|
+
|
1486
1757
|
protoBC[x] = [
|
1487
|
-
[ Opcodes.block, valtypeBinary ],
|
1758
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1488
1759
|
...protoOut,
|
1489
1760
|
|
1490
1761
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1491
|
-
|
1762
|
+
...setLastType(scope),
|
1492
1763
|
[ Opcodes.end ]
|
1493
1764
|
];
|
1494
1765
|
}
|
1495
1766
|
|
1496
|
-
|
1497
|
-
...generate(scope, target),
|
1767
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1498
1768
|
|
1499
|
-
|
1500
|
-
|
1769
|
+
return [
|
1770
|
+
...(usePointerCache ? [
|
1771
|
+
...rawPointer,
|
1772
|
+
[ Opcodes.local_set, pointerLocal ],
|
1773
|
+
] : []),
|
1501
1774
|
|
1502
1775
|
...(!lengthI32CacheUsed ? [] : [
|
1503
1776
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1509,13 +1782,22 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1509
1782
|
|
1510
1783
|
// TODO: error better
|
1511
1784
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1512
|
-
}, valtypeBinary),
|
1785
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1513
1786
|
];
|
1514
1787
|
}
|
1788
|
+
|
1789
|
+
if (Object.keys(protoBC).length > 0) {
|
1790
|
+
return typeSwitch(scope, getNodeType(scope, target), {
|
1791
|
+
...protoBC,
|
1792
|
+
|
1793
|
+
// TODO: error better
|
1794
|
+
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1795
|
+
}, valtypeBinary);
|
1796
|
+
}
|
1515
1797
|
}
|
1516
1798
|
|
1517
1799
|
// TODO: only allows callee as literal
|
1518
|
-
if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
|
1800
|
+
if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
|
1519
1801
|
|
1520
1802
|
let idx = funcIndex[name] ?? importedFuncs[name];
|
1521
1803
|
if (idx === undefined && builtinFuncs[name]) {
|
@@ -1548,15 +1830,66 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1548
1830
|
idx = -1;
|
1549
1831
|
}
|
1550
1832
|
|
1833
|
+
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1834
|
+
const wasmOps = {
|
1835
|
+
// pointer, align, offset
|
1836
|
+
i32_load: { imms: 2, args: 1, returns: 1 },
|
1837
|
+
// pointer, value, align, offset
|
1838
|
+
i32_store: { imms: 2, args: 2, returns: 0 },
|
1839
|
+
// pointer, align, offset
|
1840
|
+
i32_load8_u: { imms: 2, args: 1, returns: 1 },
|
1841
|
+
// pointer, value, align, offset
|
1842
|
+
i32_store8: { imms: 2, args: 2, returns: 0 },
|
1843
|
+
// pointer, align, offset
|
1844
|
+
i32_load16_u: { imms: 2, args: 1, returns: 1 },
|
1845
|
+
// pointer, value, align, offset
|
1846
|
+
i32_store16: { imms: 2, args: 2, returns: 0 },
|
1847
|
+
|
1848
|
+
// pointer, align, offset
|
1849
|
+
f64_load: { imms: 2, args: 1, returns: 1 },
|
1850
|
+
// pointer, value, align, offset
|
1851
|
+
f64_store: { imms: 2, args: 2, returns: 0 },
|
1852
|
+
|
1853
|
+
// value
|
1854
|
+
i32_const: { imms: 1, args: 0, returns: 1 },
|
1855
|
+
|
1856
|
+
// a, b
|
1857
|
+
i32_or: { imms: 0, args: 2, returns: 1 },
|
1858
|
+
};
|
1859
|
+
|
1860
|
+
const opName = name.slice('__Porffor_wasm_'.length);
|
1861
|
+
|
1862
|
+
if (wasmOps[opName]) {
|
1863
|
+
const op = wasmOps[opName];
|
1864
|
+
|
1865
|
+
const argOut = [];
|
1866
|
+
for (let i = 0; i < op.args; i++) argOut.push(
|
1867
|
+
...generate(scope, decl.arguments[i]),
|
1868
|
+
Opcodes.i32_to
|
1869
|
+
);
|
1870
|
+
|
1871
|
+
// literals only
|
1872
|
+
const imms = decl.arguments.slice(op.args).map(x => x.value);
|
1873
|
+
|
1874
|
+
return [
|
1875
|
+
...argOut,
|
1876
|
+
[ Opcodes[opName], ...imms ],
|
1877
|
+
...(new Array(op.returns).fill(Opcodes.i32_from))
|
1878
|
+
];
|
1879
|
+
}
|
1880
|
+
}
|
1881
|
+
|
1551
1882
|
if (idx === undefined) {
|
1552
|
-
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function
|
1553
|
-
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined
|
1883
|
+
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1884
|
+
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
1554
1885
|
}
|
1555
1886
|
|
1556
1887
|
const func = funcs.find(x => x.index === idx);
|
1557
1888
|
|
1558
1889
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1559
|
-
const
|
1890
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1891
|
+
const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
|
1892
|
+
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1560
1893
|
|
1561
1894
|
let args = decl.arguments;
|
1562
1895
|
if (func && args.length < paramCount) {
|
@@ -1572,14 +1905,24 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1572
1905
|
if (func && func.throws) scope.throws = true;
|
1573
1906
|
|
1574
1907
|
let out = [];
|
1575
|
-
for (
|
1908
|
+
for (let i = 0; i < args.length; i++) {
|
1909
|
+
const arg = args[i];
|
1576
1910
|
out = out.concat(generate(scope, arg));
|
1577
|
-
|
1911
|
+
|
1912
|
+
if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1913
|
+
out.push(Opcodes.i32_to);
|
1914
|
+
}
|
1915
|
+
|
1916
|
+
if (importedFuncs[name] && name.startsWith('profile')) {
|
1917
|
+
out.push(Opcodes.i32_to);
|
1918
|
+
}
|
1919
|
+
|
1920
|
+
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1578
1921
|
}
|
1579
1922
|
|
1580
1923
|
out.push([ Opcodes.call, idx ]);
|
1581
1924
|
|
1582
|
-
if (!
|
1925
|
+
if (!typedReturns) {
|
1583
1926
|
// let type;
|
1584
1927
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1585
1928
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1589,7 +1932,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1589
1932
|
// ...number(type, Valtype.i32),
|
1590
1933
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1591
1934
|
// );
|
1592
|
-
} else out.push(
|
1935
|
+
} else out.push(...setLastType(scope));
|
1936
|
+
|
1937
|
+
if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1938
|
+
out.push(Opcodes.i32_from);
|
1939
|
+
}
|
1593
1940
|
|
1594
1941
|
return out;
|
1595
1942
|
};
|
@@ -1597,8 +1944,21 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1597
1944
|
const generateNew = (scope, decl, _global, _name) => {
|
1598
1945
|
// hack: basically treat this as a normal call for builtins for now
|
1599
1946
|
const name = mapName(decl.callee.name);
|
1947
|
+
|
1600
1948
|
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1601
|
-
|
1949
|
+
|
1950
|
+
if (builtinFuncs[name + '$constructor']) {
|
1951
|
+
// custom ...$constructor override builtin func
|
1952
|
+
return generateCall(scope, {
|
1953
|
+
...decl,
|
1954
|
+
callee: {
|
1955
|
+
type: 'Identifier',
|
1956
|
+
name: name + '$constructor'
|
1957
|
+
}
|
1958
|
+
}, _global, _name);
|
1959
|
+
}
|
1960
|
+
|
1961
|
+
if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
|
1602
1962
|
|
1603
1963
|
return generateCall(scope, decl, _global, _name);
|
1604
1964
|
};
|
@@ -1614,17 +1974,124 @@ const unhackName = name => {
|
|
1614
1974
|
return name;
|
1615
1975
|
};
|
1616
1976
|
|
1977
|
+
const knownType = (scope, type) => {
|
1978
|
+
if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
|
1979
|
+
return type[0][1];
|
1980
|
+
}
|
1981
|
+
|
1982
|
+
if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
|
1983
|
+
const idx = type[0][1];
|
1984
|
+
|
1985
|
+
// type idx = var idx + 1
|
1986
|
+
const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
|
1987
|
+
if (v.metadata?.type != null) return v.metadata.type;
|
1988
|
+
}
|
1989
|
+
|
1990
|
+
return null;
|
1991
|
+
};
|
1992
|
+
|
1993
|
+
const brTable = (input, bc, returns) => {
|
1994
|
+
const out = [];
|
1995
|
+
const keys = Object.keys(bc);
|
1996
|
+
const count = keys.length;
|
1997
|
+
|
1998
|
+
if (count === 1) {
|
1999
|
+
// return [
|
2000
|
+
// ...input,
|
2001
|
+
// ...bc[keys[0]]
|
2002
|
+
// ];
|
2003
|
+
return bc[keys[0]];
|
2004
|
+
}
|
2005
|
+
|
2006
|
+
if (count === 2) {
|
2007
|
+
// just use if else
|
2008
|
+
const other = keys.find(x => x !== 'default');
|
2009
|
+
return [
|
2010
|
+
...input,
|
2011
|
+
...number(other, Valtype.i32),
|
2012
|
+
[ Opcodes.i32_eq ],
|
2013
|
+
[ Opcodes.if, returns ],
|
2014
|
+
...bc[other],
|
2015
|
+
[ Opcodes.else ],
|
2016
|
+
...bc.default,
|
2017
|
+
[ Opcodes.end ]
|
2018
|
+
];
|
2019
|
+
}
|
2020
|
+
|
2021
|
+
for (let i = 0; i < count; i++) {
|
2022
|
+
if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
|
2023
|
+
else out.push([ Opcodes.block, Blocktype.void ]);
|
2024
|
+
}
|
2025
|
+
|
2026
|
+
const nums = keys.filter(x => +x);
|
2027
|
+
const offset = Math.min(...nums);
|
2028
|
+
const max = Math.max(...nums);
|
2029
|
+
|
2030
|
+
const table = [];
|
2031
|
+
let br = 1;
|
2032
|
+
|
2033
|
+
for (let i = offset; i <= max; i++) {
|
2034
|
+
// if branch for this num, go to that block
|
2035
|
+
if (bc[i]) {
|
2036
|
+
table.push(br);
|
2037
|
+
br++;
|
2038
|
+
continue;
|
2039
|
+
}
|
2040
|
+
|
2041
|
+
// else default
|
2042
|
+
table.push(0);
|
2043
|
+
}
|
2044
|
+
|
2045
|
+
out.push(
|
2046
|
+
[ Opcodes.block, Blocktype.void ],
|
2047
|
+
...input,
|
2048
|
+
...(offset > 0 ? [
|
2049
|
+
...number(offset, Valtype.i32),
|
2050
|
+
[ Opcodes.i32_sub ]
|
2051
|
+
] : []),
|
2052
|
+
[ Opcodes.br_table, ...encodeVector(table), 0 ]
|
2053
|
+
);
|
2054
|
+
|
2055
|
+
// if you can guess why we sort the wrong way and then reverse
|
2056
|
+
// (instead of just sorting the correct way)
|
2057
|
+
// dm me and if you are correct and the first person
|
2058
|
+
// I will somehow shout you out or something
|
2059
|
+
const orderedBc = keys.sort((a, b) => b - a).reverse();
|
2060
|
+
|
2061
|
+
br = count - 1;
|
2062
|
+
for (const x of orderedBc) {
|
2063
|
+
out.push(
|
2064
|
+
[ Opcodes.end ],
|
2065
|
+
...bc[x],
|
2066
|
+
...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
|
2067
|
+
);
|
2068
|
+
br--;
|
2069
|
+
}
|
2070
|
+
|
2071
|
+
return [
|
2072
|
+
...out,
|
2073
|
+
[ Opcodes.end, 'br table end' ]
|
2074
|
+
];
|
2075
|
+
};
|
2076
|
+
|
1617
2077
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1618
|
-
|
2078
|
+
if (!Prefs.bytestring) delete bc[TYPES._bytestring];
|
2079
|
+
|
2080
|
+
const known = knownType(scope, type);
|
2081
|
+
if (known != null) {
|
2082
|
+
return bc[known] ?? bc.default;
|
2083
|
+
}
|
2084
|
+
|
2085
|
+
if (Prefs.typeswitchUseBrtable)
|
2086
|
+
return brTable(type, bc, returns);
|
1619
2087
|
|
2088
|
+
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
1620
2089
|
const out = [
|
1621
2090
|
...type,
|
1622
2091
|
[ Opcodes.local_set, tmp ],
|
1623
2092
|
[ Opcodes.block, returns ]
|
1624
2093
|
];
|
1625
2094
|
|
1626
|
-
// todo: use br_table?
|
1627
|
-
|
1628
2095
|
for (const x in bc) {
|
1629
2096
|
if (x === 'default') continue;
|
1630
2097
|
|
@@ -1648,7 +2115,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1648
2115
|
return out;
|
1649
2116
|
};
|
1650
2117
|
|
1651
|
-
const allocVar = (scope, name, global = false) => {
|
2118
|
+
const allocVar = (scope, name, global = false, type = true) => {
|
1652
2119
|
const target = global ? globals : scope.locals;
|
1653
2120
|
|
1654
2121
|
// already declared
|
@@ -1662,14 +2129,64 @@ const allocVar = (scope, name, global = false) => {
|
|
1662
2129
|
let idx = global ? globalInd++ : scope.localInd++;
|
1663
2130
|
target[name] = { idx, type: valtypeBinary };
|
1664
2131
|
|
1665
|
-
|
1666
|
-
|
2132
|
+
if (type) {
|
2133
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
2134
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
2135
|
+
}
|
1667
2136
|
|
1668
2137
|
return idx;
|
1669
2138
|
};
|
1670
2139
|
|
1671
|
-
const
|
1672
|
-
|
2140
|
+
const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
2141
|
+
const target = global ? globals : scope.locals;
|
2142
|
+
|
2143
|
+
target[name].metadata ??= {};
|
2144
|
+
for (const x in metadata) {
|
2145
|
+
if (metadata[x] != null) target[name].metadata[x] = metadata[x];
|
2146
|
+
}
|
2147
|
+
};
|
2148
|
+
|
2149
|
+
const typeAnnoToPorfType = x => {
|
2150
|
+
if (!x) return null;
|
2151
|
+
if (TYPES[x] != null) return TYPES[x];
|
2152
|
+
if (TYPES['_' + x] != null) return TYPES['_' + x];
|
2153
|
+
|
2154
|
+
switch (x) {
|
2155
|
+
case 'i32':
|
2156
|
+
case 'i64':
|
2157
|
+
case 'f64':
|
2158
|
+
return TYPES.number;
|
2159
|
+
}
|
2160
|
+
|
2161
|
+
return null;
|
2162
|
+
};
|
2163
|
+
|
2164
|
+
const extractTypeAnnotation = decl => {
|
2165
|
+
let a = decl;
|
2166
|
+
while (a.typeAnnotation) a = a.typeAnnotation;
|
2167
|
+
|
2168
|
+
let type = null, elementType = null;
|
2169
|
+
if (a.typeName) {
|
2170
|
+
type = a.typeName.name;
|
2171
|
+
} else if (a.type.endsWith('Keyword')) {
|
2172
|
+
type = a.type.slice(2, -7).toLowerCase();
|
2173
|
+
} else if (a.type === 'TSArrayType') {
|
2174
|
+
type = 'array';
|
2175
|
+
elementType = extractTypeAnnotation(a.elementType).type;
|
2176
|
+
}
|
2177
|
+
|
2178
|
+
const typeName = type;
|
2179
|
+
type = typeAnnoToPorfType(type);
|
2180
|
+
|
2181
|
+
if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
|
2182
|
+
|
2183
|
+
// if (decl.name) console.log(decl.name, { type, elementType });
|
2184
|
+
|
2185
|
+
return { type, typeName, elementType };
|
2186
|
+
};
|
2187
|
+
|
2188
|
+
const generateVar = (scope, decl) => {
|
2189
|
+
let out = [];
|
1673
2190
|
|
1674
2191
|
const topLevel = scope.name === 'main';
|
1675
2192
|
|
@@ -1679,6 +2196,8 @@ const generateVar = (scope, decl) => {
|
|
1679
2196
|
for (const x of decl.declarations) {
|
1680
2197
|
const name = mapName(x.id.name);
|
1681
2198
|
|
2199
|
+
if (!name) return todo(scope, 'destructuring is not supported yet');
|
2200
|
+
|
1682
2201
|
if (x.init && isFuncType(x.init.type)) {
|
1683
2202
|
// hack for let a = function () { ... }
|
1684
2203
|
x.init.id = { name };
|
@@ -1694,7 +2213,13 @@ const generateVar = (scope, decl) => {
|
|
1694
2213
|
continue; // always ignore
|
1695
2214
|
}
|
1696
2215
|
|
1697
|
-
|
2216
|
+
const typed = typedInput && x.id.typeAnnotation;
|
2217
|
+
let idx = allocVar(scope, name, global, !typed);
|
2218
|
+
|
2219
|
+
if (typed) {
|
2220
|
+
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
2221
|
+
}
|
2222
|
+
|
1698
2223
|
if (x.init) {
|
1699
2224
|
out = out.concat(generate(scope, x.init, global, name));
|
1700
2225
|
|
@@ -1709,7 +2234,8 @@ const generateVar = (scope, decl) => {
|
|
1709
2234
|
return out;
|
1710
2235
|
};
|
1711
2236
|
|
1712
|
-
|
2237
|
+
// todo: optimize this func for valueUnused
|
2238
|
+
const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
1713
2239
|
const { type, name } = decl.left;
|
1714
2240
|
|
1715
2241
|
if (type === 'ObjectPattern') {
|
@@ -1727,9 +2253,9 @@ const generateAssign = (scope, decl) => {
|
|
1727
2253
|
// hack: .length setter
|
1728
2254
|
if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
|
1729
2255
|
const name = decl.left.object.name;
|
1730
|
-
const pointer = arrays
|
2256
|
+
const pointer = scope.arrays?.get(name);
|
1731
2257
|
|
1732
|
-
const aotPointer = pointer != null;
|
2258
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1733
2259
|
|
1734
2260
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
1735
2261
|
|
@@ -1754,9 +2280,9 @@ const generateAssign = (scope, decl) => {
|
|
1754
2280
|
// arr[i]
|
1755
2281
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
1756
2282
|
const name = decl.left.object.name;
|
1757
|
-
const pointer = arrays
|
2283
|
+
const pointer = scope.arrays?.get(name);
|
1758
2284
|
|
1759
|
-
const aotPointer = pointer != null;
|
2285
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1760
2286
|
|
1761
2287
|
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
1762
2288
|
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
@@ -1812,6 +2338,8 @@ const generateAssign = (scope, decl) => {
|
|
1812
2338
|
];
|
1813
2339
|
}
|
1814
2340
|
|
2341
|
+
if (!name) return todo(scope, 'destructuring is not supported yet', true);
|
2342
|
+
|
1815
2343
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1816
2344
|
|
1817
2345
|
if (local === undefined) {
|
@@ -1858,9 +2386,7 @@ const generateAssign = (scope, decl) => {
|
|
1858
2386
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
1859
2387
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1860
2388
|
|
1861
|
-
|
1862
|
-
// hack: type is idx+1
|
1863
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2389
|
+
...setType(scope, name, getLastType(scope))
|
1864
2390
|
];
|
1865
2391
|
}
|
1866
2392
|
|
@@ -1871,9 +2397,7 @@ const generateAssign = (scope, decl) => {
|
|
1871
2397
|
|
1872
2398
|
// todo: string concat types
|
1873
2399
|
|
1874
|
-
|
1875
|
-
...number(TYPES.number, Valtype.i32),
|
1876
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2400
|
+
...setType(scope, name, TYPES.number)
|
1877
2401
|
];
|
1878
2402
|
};
|
1879
2403
|
|
@@ -1919,7 +2443,7 @@ const generateUnary = (scope, decl) => {
|
|
1919
2443
|
return out;
|
1920
2444
|
}
|
1921
2445
|
|
1922
|
-
case 'delete':
|
2446
|
+
case 'delete': {
|
1923
2447
|
let toReturn = true, toGenerate = true;
|
1924
2448
|
|
1925
2449
|
if (decl.argument.type === 'Identifier') {
|
@@ -1941,38 +2465,60 @@ const generateUnary = (scope, decl) => {
|
|
1941
2465
|
|
1942
2466
|
out.push(...number(toReturn ? 1 : 0));
|
1943
2467
|
return out;
|
2468
|
+
}
|
2469
|
+
|
2470
|
+
case 'typeof': {
|
2471
|
+
let overrideType, toGenerate = true;
|
2472
|
+
|
2473
|
+
if (decl.argument.type === 'Identifier') {
|
2474
|
+
const out = generateIdent(scope, decl.argument);
|
2475
|
+
|
2476
|
+
// if ReferenceError (undeclared var), ignore and return undefined
|
2477
|
+
if (out[1]) {
|
2478
|
+
// does not exist (2 ops from throw)
|
2479
|
+
overrideType = number(TYPES.undefined, Valtype.i32);
|
2480
|
+
toGenerate = false;
|
2481
|
+
}
|
2482
|
+
}
|
1944
2483
|
|
1945
|
-
|
1946
|
-
|
2484
|
+
const out = toGenerate ? generate(scope, decl.argument) : [];
|
2485
|
+
disposeLeftover(out);
|
2486
|
+
|
2487
|
+
out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
|
1947
2488
|
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
1948
2489
|
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
1949
2490
|
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
1950
2491
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
1951
2492
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
1952
2493
|
|
2494
|
+
[TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2495
|
+
|
1953
2496
|
// object and internal types
|
1954
2497
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
1955
|
-
});
|
2498
|
+
}));
|
2499
|
+
|
2500
|
+
return out;
|
2501
|
+
}
|
1956
2502
|
|
1957
2503
|
default:
|
1958
|
-
return todo(`unary operator ${decl.operator} not implemented yet
|
2504
|
+
return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
|
1959
2505
|
}
|
1960
2506
|
};
|
1961
2507
|
|
1962
|
-
const generateUpdate = (scope, decl) => {
|
2508
|
+
const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
|
1963
2509
|
const { name } = decl.argument;
|
1964
2510
|
|
1965
2511
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1966
2512
|
|
1967
2513
|
if (local === undefined) {
|
1968
|
-
return todo(`update expression with undefined variable
|
2514
|
+
return todo(scope, `update expression with undefined variable`, true);
|
1969
2515
|
}
|
1970
2516
|
|
1971
2517
|
const idx = local.idx;
|
1972
2518
|
const out = [];
|
1973
2519
|
|
1974
2520
|
out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
1975
|
-
if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2521
|
+
if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
1976
2522
|
|
1977
2523
|
switch (decl.operator) {
|
1978
2524
|
case '++':
|
@@ -1985,7 +2531,7 @@ const generateUpdate = (scope, decl) => {
|
|
1985
2531
|
}
|
1986
2532
|
|
1987
2533
|
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
1988
|
-
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 ]);
|
1989
2535
|
|
1990
2536
|
return out;
|
1991
2537
|
};
|
@@ -2025,7 +2571,7 @@ const generateConditional = (scope, decl) => {
|
|
2025
2571
|
// note type
|
2026
2572
|
out.push(
|
2027
2573
|
...getNodeType(scope, decl.consequent),
|
2028
|
-
|
2574
|
+
...setLastType(scope)
|
2029
2575
|
);
|
2030
2576
|
|
2031
2577
|
out.push([ Opcodes.else ]);
|
@@ -2034,7 +2580,7 @@ const generateConditional = (scope, decl) => {
|
|
2034
2580
|
// note type
|
2035
2581
|
out.push(
|
2036
2582
|
...getNodeType(scope, decl.alternate),
|
2037
|
-
|
2583
|
+
...setLastType(scope)
|
2038
2584
|
);
|
2039
2585
|
|
2040
2586
|
out.push([ Opcodes.end ]);
|
@@ -2048,15 +2594,17 @@ const generateFor = (scope, decl) => {
|
|
2048
2594
|
const out = [];
|
2049
2595
|
|
2050
2596
|
if (decl.init) {
|
2051
|
-
out.push(...generate(scope, decl.init));
|
2597
|
+
out.push(...generate(scope, decl.init, false, undefined, true));
|
2052
2598
|
disposeLeftover(out);
|
2053
2599
|
}
|
2054
2600
|
|
2055
2601
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2056
2602
|
depth.push('for');
|
2057
2603
|
|
2058
|
-
out.push(...generate(scope, decl.test));
|
2059
|
-
|
2604
|
+
if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2605
|
+
else out.push(...number(1, Valtype.i32));
|
2606
|
+
|
2607
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
2060
2608
|
depth.push('if');
|
2061
2609
|
|
2062
2610
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2064,8 +2612,7 @@ const generateFor = (scope, decl) => {
|
|
2064
2612
|
out.push(...generate(scope, decl.body));
|
2065
2613
|
out.push([ Opcodes.end ]);
|
2066
2614
|
|
2067
|
-
out.push(...generate(scope, decl.update));
|
2068
|
-
depth.pop();
|
2615
|
+
if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
|
2069
2616
|
|
2070
2617
|
out.push([ Opcodes.br, 1 ]);
|
2071
2618
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2093,6 +2640,36 @@ const generateWhile = (scope, decl) => {
|
|
2093
2640
|
return out;
|
2094
2641
|
};
|
2095
2642
|
|
2643
|
+
const generateDoWhile = (scope, decl) => {
|
2644
|
+
const out = [];
|
2645
|
+
|
2646
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
2647
|
+
depth.push('dowhile');
|
2648
|
+
|
2649
|
+
// block for break (includes all)
|
2650
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2651
|
+
depth.push('block');
|
2652
|
+
|
2653
|
+
// block for continue
|
2654
|
+
// includes body but not test+loop so we can exit body at anytime
|
2655
|
+
// and still test+loop after
|
2656
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2657
|
+
depth.push('block');
|
2658
|
+
|
2659
|
+
out.push(...generate(scope, decl.body));
|
2660
|
+
|
2661
|
+
out.push([ Opcodes.end ]);
|
2662
|
+
depth.pop();
|
2663
|
+
|
2664
|
+
out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2665
|
+
out.push([ Opcodes.br_if, 1 ]);
|
2666
|
+
|
2667
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
2668
|
+
depth.pop(); depth.pop();
|
2669
|
+
|
2670
|
+
return out;
|
2671
|
+
};
|
2672
|
+
|
2096
2673
|
const generateForOf = (scope, decl) => {
|
2097
2674
|
const out = [];
|
2098
2675
|
|
@@ -2122,8 +2699,17 @@ const generateForOf = (scope, decl) => {
|
|
2122
2699
|
// setup local for left
|
2123
2700
|
generate(scope, decl.left);
|
2124
2701
|
|
2125
|
-
|
2702
|
+
let leftName = decl.left.declarations?.[0]?.id?.name;
|
2703
|
+
if (!leftName && decl.left.name) {
|
2704
|
+
leftName = decl.left.name;
|
2705
|
+
|
2706
|
+
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2707
|
+
}
|
2708
|
+
|
2709
|
+
// if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
|
2710
|
+
|
2126
2711
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2712
|
+
if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
|
2127
2713
|
|
2128
2714
|
depth.push('block');
|
2129
2715
|
depth.push('block');
|
@@ -2131,13 +2717,15 @@ const generateForOf = (scope, decl) => {
|
|
2131
2717
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2132
2718
|
// hack: this is naughty and will break things!
|
2133
2719
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2134
|
-
if (pages.
|
2720
|
+
if (pages.hasAnyString) {
|
2721
|
+
// todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
|
2135
2722
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2136
2723
|
rawElements: new Array(1)
|
2137
2724
|
}, isGlobal, leftName, true, 'i16');
|
2138
2725
|
}
|
2139
2726
|
|
2140
2727
|
// set type for local
|
2728
|
+
// todo: optimize away counter and use end pointer
|
2141
2729
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2142
2730
|
[TYPES._array]: [
|
2143
2731
|
...setType(scope, leftName, TYPES.number),
|
@@ -2222,6 +2810,56 @@ const generateForOf = (scope, decl) => {
|
|
2222
2810
|
[ Opcodes.end ],
|
2223
2811
|
[ Opcodes.end ]
|
2224
2812
|
],
|
2813
|
+
[TYPES._bytestring]: [
|
2814
|
+
...setType(scope, leftName, TYPES._bytestring),
|
2815
|
+
|
2816
|
+
[ Opcodes.loop, Blocktype.void ],
|
2817
|
+
|
2818
|
+
// setup new/out array
|
2819
|
+
...newOut,
|
2820
|
+
[ Opcodes.drop ],
|
2821
|
+
|
2822
|
+
...number(0, Valtype.i32), // base 0 for store after
|
2823
|
+
|
2824
|
+
// load current string ind {arg}
|
2825
|
+
[ Opcodes.local_get, pointer ],
|
2826
|
+
[ Opcodes.local_get, counter ],
|
2827
|
+
[ Opcodes.i32_add ],
|
2828
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
2829
|
+
|
2830
|
+
// store to new string ind 0
|
2831
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2832
|
+
|
2833
|
+
// return new string (page)
|
2834
|
+
...number(newPointer),
|
2835
|
+
|
2836
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2837
|
+
|
2838
|
+
[ Opcodes.block, Blocktype.void ],
|
2839
|
+
[ Opcodes.block, Blocktype.void ],
|
2840
|
+
...generate(scope, decl.body),
|
2841
|
+
[ Opcodes.end ],
|
2842
|
+
|
2843
|
+
// increment iter pointer
|
2844
|
+
// [ Opcodes.local_get, pointer ],
|
2845
|
+
// ...number(1, Valtype.i32),
|
2846
|
+
// [ Opcodes.i32_add ],
|
2847
|
+
// [ Opcodes.local_set, pointer ],
|
2848
|
+
|
2849
|
+
// increment counter by 1
|
2850
|
+
[ Opcodes.local_get, counter ],
|
2851
|
+
...number(1, Valtype.i32),
|
2852
|
+
[ Opcodes.i32_add ],
|
2853
|
+
[ Opcodes.local_tee, counter ],
|
2854
|
+
|
2855
|
+
// loop if counter != length
|
2856
|
+
[ Opcodes.local_get, length ],
|
2857
|
+
[ Opcodes.i32_ne ],
|
2858
|
+
[ Opcodes.br_if, 1 ],
|
2859
|
+
|
2860
|
+
[ Opcodes.end ],
|
2861
|
+
[ Opcodes.end ]
|
2862
|
+
],
|
2225
2863
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2226
2864
|
}, Blocktype.void));
|
2227
2865
|
|
@@ -2232,28 +2870,65 @@ const generateForOf = (scope, decl) => {
|
|
2232
2870
|
return out;
|
2233
2871
|
};
|
2234
2872
|
|
2873
|
+
// find the nearest loop in depth map by type
|
2235
2874
|
const getNearestLoop = () => {
|
2236
2875
|
for (let i = depth.length - 1; i >= 0; i--) {
|
2237
|
-
if (
|
2876
|
+
if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
|
2238
2877
|
}
|
2239
2878
|
|
2240
2879
|
return -1;
|
2241
2880
|
};
|
2242
2881
|
|
2243
2882
|
const generateBreak = (scope, decl) => {
|
2244
|
-
const
|
2883
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2884
|
+
const type = depth[target];
|
2885
|
+
|
2886
|
+
// different loop types have different branch offsets
|
2887
|
+
// as they have different wasm block/loop/if structures
|
2888
|
+
// we need to use the right offset by type to branch to the one we want
|
2889
|
+
// for a break: exit the loop without executing anything else inside it
|
2890
|
+
const offset = ({
|
2891
|
+
for: 2, // loop > if (wanted branch) > block (we are here)
|
2892
|
+
while: 2, // loop > if (wanted branch) (we are here)
|
2893
|
+
dowhile: 2, // loop > block (wanted branch) > block (we are here)
|
2894
|
+
forof: 2, // loop > block (wanted branch) > block (we are here)
|
2895
|
+
if: 1 // break inside if, branch 0 to skip the rest of the if
|
2896
|
+
})[type];
|
2897
|
+
|
2245
2898
|
return [
|
2246
|
-
[ Opcodes.br, ...signedLEB128(
|
2899
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2247
2900
|
];
|
2248
2901
|
};
|
2249
2902
|
|
2250
2903
|
const generateContinue = (scope, decl) => {
|
2251
|
-
const
|
2904
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2905
|
+
const type = depth[target];
|
2906
|
+
|
2907
|
+
// different loop types have different branch offsets
|
2908
|
+
// as they have different wasm block/loop/if structures
|
2909
|
+
// we need to use the right offset by type to branch to the one we want
|
2910
|
+
// for a continue: do test for the loop, and then loop depending on that success
|
2911
|
+
const offset = ({
|
2912
|
+
for: 3, // loop (wanted branch) > if > block (we are here)
|
2913
|
+
while: 1, // loop (wanted branch) > if (we are here)
|
2914
|
+
dowhile: 3, // loop > block > block (wanted branch) (we are here)
|
2915
|
+
forof: 3 // loop > block > block (wanted branch) (we are here)
|
2916
|
+
})[type];
|
2917
|
+
|
2252
2918
|
return [
|
2253
|
-
[ Opcodes.br, ...signedLEB128(
|
2919
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2254
2920
|
];
|
2255
2921
|
};
|
2256
2922
|
|
2923
|
+
const generateLabel = (scope, decl) => {
|
2924
|
+
scope.labels ??= new Map();
|
2925
|
+
|
2926
|
+
const name = decl.label.name;
|
2927
|
+
scope.labels.set(name, depth.length);
|
2928
|
+
|
2929
|
+
return generate(scope, decl.body);
|
2930
|
+
};
|
2931
|
+
|
2257
2932
|
const generateThrow = (scope, decl) => {
|
2258
2933
|
scope.throws = true;
|
2259
2934
|
|
@@ -2262,7 +2937,7 @@ const generateThrow = (scope, decl) => {
|
|
2262
2937
|
// hack: throw new X("...") -> throw "..."
|
2263
2938
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2264
2939
|
constructor = decl.argument.callee.name;
|
2265
|
-
message = decl.argument.arguments[0]
|
2940
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2266
2941
|
}
|
2267
2942
|
|
2268
2943
|
if (tags.length === 0) tags.push({
|
@@ -2274,6 +2949,9 @@ const generateThrow = (scope, decl) => {
|
|
2274
2949
|
let exceptId = exceptions.push({ constructor, message }) - 1;
|
2275
2950
|
let tagIdx = tags[0].idx;
|
2276
2951
|
|
2952
|
+
scope.exceptions ??= [];
|
2953
|
+
scope.exceptions.push(exceptId);
|
2954
|
+
|
2277
2955
|
// todo: write a description of how this works lol
|
2278
2956
|
|
2279
2957
|
return [
|
@@ -2283,7 +2961,7 @@ const generateThrow = (scope, decl) => {
|
|
2283
2961
|
};
|
2284
2962
|
|
2285
2963
|
const generateTry = (scope, decl) => {
|
2286
|
-
if (decl.finalizer) return todo('try finally not implemented yet');
|
2964
|
+
if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
|
2287
2965
|
|
2288
2966
|
const out = [];
|
2289
2967
|
|
@@ -2314,29 +2992,35 @@ const generateAssignPat = (scope, decl) => {
|
|
2314
2992
|
// TODO
|
2315
2993
|
// if identifier declared, use that
|
2316
2994
|
// else, use default (right)
|
2317
|
-
return todo('assignment pattern (optional arg)');
|
2995
|
+
return todo(scope, 'assignment pattern (optional arg)');
|
2318
2996
|
};
|
2319
2997
|
|
2320
2998
|
let pages = new Map();
|
2321
|
-
const allocPage = (reason, type) => {
|
2999
|
+
const allocPage = (scope, reason, type) => {
|
2322
3000
|
if (pages.has(reason)) return pages.get(reason).ind;
|
2323
3001
|
|
2324
3002
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2325
3003
|
if (reason.startsWith('string:')) pages.hasString = true;
|
3004
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
3005
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2326
3006
|
|
2327
3007
|
const ind = pages.size;
|
2328
3008
|
pages.set(reason, { ind, type });
|
2329
3009
|
|
2330
|
-
|
3010
|
+
scope.pages ??= new Map();
|
3011
|
+
scope.pages.set(reason, { ind, type });
|
3012
|
+
|
3013
|
+
if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2331
3014
|
|
2332
3015
|
return ind;
|
2333
3016
|
};
|
2334
3017
|
|
3018
|
+
// todo: add scope.pages
|
2335
3019
|
const freePage = reason => {
|
2336
3020
|
const { ind } = pages.get(reason);
|
2337
3021
|
pages.delete(reason);
|
2338
3022
|
|
2339
|
-
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
3023
|
+
if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2340
3024
|
|
2341
3025
|
return ind;
|
2342
3026
|
};
|
@@ -2356,38 +3040,51 @@ const StoreOps = {
|
|
2356
3040
|
f64: Opcodes.f64_store,
|
2357
3041
|
|
2358
3042
|
// expects i32 input!
|
2359
|
-
|
3043
|
+
i8: Opcodes.i32_store8,
|
3044
|
+
i16: Opcodes.i32_store16,
|
2360
3045
|
};
|
2361
3046
|
|
2362
3047
|
let data = [];
|
2363
3048
|
|
2364
|
-
const compileBytes = (val, itemType
|
3049
|
+
const compileBytes = (val, itemType) => {
|
2365
3050
|
// todo: this is a mess and needs confirming / ????
|
2366
3051
|
switch (itemType) {
|
2367
3052
|
case 'i8': return [ val % 256 ];
|
2368
|
-
case 'i16': return [ val % 256,
|
2369
|
-
|
2370
|
-
case 'i32':
|
2371
|
-
|
2372
|
-
return enforceFourBytes(signedLEB128(val));
|
3053
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3054
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3055
|
+
case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
|
3056
|
+
// todo: i64
|
2373
3057
|
|
2374
3058
|
case 'f64': return ieee754_binary64(val);
|
2375
3059
|
}
|
2376
3060
|
};
|
2377
3061
|
|
3062
|
+
const getAllocType = itemType => {
|
3063
|
+
switch (itemType) {
|
3064
|
+
case 'i8': return 'bytestring';
|
3065
|
+
case 'i16': return 'string';
|
3066
|
+
|
3067
|
+
default: return 'array';
|
3068
|
+
}
|
3069
|
+
};
|
3070
|
+
|
2378
3071
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2379
3072
|
const out = [];
|
2380
3073
|
|
3074
|
+
scope.arrays ??= new Map();
|
3075
|
+
|
2381
3076
|
let firstAssign = false;
|
2382
|
-
if (!arrays.has(name) || name === '$undeclared') {
|
3077
|
+
if (!scope.arrays.has(name) || name === '$undeclared') {
|
2383
3078
|
firstAssign = true;
|
2384
3079
|
|
2385
3080
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2386
3081
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2387
|
-
|
3082
|
+
|
3083
|
+
if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${scope.name} | ${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
3084
|
+
else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2388
3085
|
}
|
2389
3086
|
|
2390
|
-
const pointer = arrays.get(name);
|
3087
|
+
const pointer = scope.arrays.get(name);
|
2391
3088
|
|
2392
3089
|
const useRawElements = !!decl.rawElements;
|
2393
3090
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
@@ -2395,19 +3092,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2395
3092
|
const valtype = itemTypeToValtype[itemType];
|
2396
3093
|
const length = elements.length;
|
2397
3094
|
|
2398
|
-
if (firstAssign && useRawElements) {
|
2399
|
-
|
3095
|
+
if (firstAssign && useRawElements && !Prefs.noData) {
|
3096
|
+
// if length is 0 memory/data will just be 0000... anyway
|
3097
|
+
if (length !== 0) {
|
3098
|
+
let bytes = compileBytes(length, 'i32');
|
2400
3099
|
|
2401
|
-
|
2402
|
-
|
3100
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
3101
|
+
if (elements[i] == null) continue;
|
2403
3102
|
|
2404
|
-
|
2405
|
-
|
3103
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
3104
|
+
}
|
2406
3105
|
|
2407
|
-
|
2408
|
-
|
2409
|
-
|
2410
|
-
|
3106
|
+
const ind = data.push({
|
3107
|
+
offset: pointer,
|
3108
|
+
bytes
|
3109
|
+
}) - 1;
|
3110
|
+
|
3111
|
+
scope.data ??= [];
|
3112
|
+
scope.data.push(ind);
|
3113
|
+
}
|
2411
3114
|
|
2412
3115
|
// local value as pointer
|
2413
3116
|
out.push(...number(pointer));
|
@@ -2430,7 +3133,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2430
3133
|
out.push(
|
2431
3134
|
...number(0, Valtype.i32),
|
2432
3135
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2433
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
3136
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2434
3137
|
);
|
2435
3138
|
}
|
2436
3139
|
|
@@ -2440,31 +3143,56 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2440
3143
|
return [ out, pointer ];
|
2441
3144
|
};
|
2442
3145
|
|
2443
|
-
const
|
3146
|
+
const byteStringable = str => {
|
3147
|
+
if (!Prefs.bytestring) return false;
|
3148
|
+
|
3149
|
+
for (let i = 0; i < str.length; i++) {
|
3150
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
3151
|
+
}
|
3152
|
+
|
3153
|
+
return true;
|
3154
|
+
};
|
3155
|
+
|
3156
|
+
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2444
3157
|
const rawElements = new Array(str.length);
|
3158
|
+
let byteStringable = Prefs.bytestring;
|
2445
3159
|
for (let i = 0; i < str.length; i++) {
|
2446
|
-
|
3160
|
+
const c = str.charCodeAt(i);
|
3161
|
+
rawElements[i] = c;
|
3162
|
+
|
3163
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2447
3164
|
}
|
2448
3165
|
|
3166
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
3167
|
+
|
2449
3168
|
return makeArray(scope, {
|
2450
3169
|
rawElements
|
2451
|
-
}, global, name, false, 'i16')[0];
|
3170
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2452
3171
|
};
|
2453
3172
|
|
2454
|
-
let arrays = new Map();
|
2455
3173
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
2456
3174
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
2457
3175
|
};
|
2458
3176
|
|
2459
3177
|
export const generateMember = (scope, decl, _global, _name) => {
|
2460
3178
|
const name = decl.object.name;
|
2461
|
-
const pointer = arrays
|
3179
|
+
const pointer = scope.arrays?.get(name);
|
2462
3180
|
|
2463
|
-
const aotPointer = pointer != null;
|
3181
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2464
3182
|
|
2465
3183
|
// hack: .length
|
2466
3184
|
if (decl.property.name === 'length') {
|
2467
|
-
|
3185
|
+
const func = funcs.find(x => x.name === name);
|
3186
|
+
if (func) {
|
3187
|
+
const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
|
3188
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
3189
|
+
return number(typedParams ? func.params.length / 2 : func.params.length);
|
3190
|
+
}
|
3191
|
+
|
3192
|
+
if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
|
3193
|
+
if (importedFuncs[name]) return number(importedFuncs[name].params);
|
3194
|
+
if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
|
3195
|
+
|
2468
3196
|
return [
|
2469
3197
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2470
3198
|
...generate(scope, decl.object),
|
@@ -2476,10 +3204,13 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2476
3204
|
];
|
2477
3205
|
}
|
2478
3206
|
|
3207
|
+
const object = generate(scope, decl.object);
|
3208
|
+
const property = generate(scope, decl.property);
|
3209
|
+
|
2479
3210
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2480
3211
|
// hack: this is naughty and will break things!
|
2481
|
-
let newOut = number(0,
|
2482
|
-
if (pages.
|
3212
|
+
let newOut = number(0, valtypeBinary), newPointer = -1;
|
3213
|
+
if (pages.hasAnyString) {
|
2483
3214
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2484
3215
|
rawElements: new Array(1)
|
2485
3216
|
}, _global, _name, true, 'i16');
|
@@ -2488,7 +3219,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2488
3219
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2489
3220
|
[TYPES._array]: [
|
2490
3221
|
// get index as valtype
|
2491
|
-
...
|
3222
|
+
...property,
|
2492
3223
|
|
2493
3224
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2494
3225
|
Opcodes.i32_to_u,
|
@@ -2496,7 +3227,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2496
3227
|
[ Opcodes.i32_mul ],
|
2497
3228
|
|
2498
3229
|
...(aotPointer ? [] : [
|
2499
|
-
...
|
3230
|
+
...object,
|
2500
3231
|
Opcodes.i32_to_u,
|
2501
3232
|
[ Opcodes.i32_add ]
|
2502
3233
|
]),
|
@@ -2505,7 +3236,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2505
3236
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2506
3237
|
|
2507
3238
|
...number(TYPES.number, Valtype.i32),
|
2508
|
-
|
3239
|
+
...setLastType(scope)
|
2509
3240
|
],
|
2510
3241
|
|
2511
3242
|
[TYPES.string]: [
|
@@ -2515,14 +3246,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2515
3246
|
|
2516
3247
|
...number(0, Valtype.i32), // base 0 for store later
|
2517
3248
|
|
2518
|
-
...
|
2519
|
-
|
3249
|
+
...property,
|
2520
3250
|
Opcodes.i32_to_u,
|
3251
|
+
|
2521
3252
|
...number(ValtypeSize.i16, Valtype.i32),
|
2522
3253
|
[ Opcodes.i32_mul ],
|
2523
3254
|
|
2524
3255
|
...(aotPointer ? [] : [
|
2525
|
-
...
|
3256
|
+
...object,
|
2526
3257
|
Opcodes.i32_to_u,
|
2527
3258
|
[ Opcodes.i32_add ]
|
2528
3259
|
]),
|
@@ -2537,10 +3268,38 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2537
3268
|
...number(newPointer),
|
2538
3269
|
|
2539
3270
|
...number(TYPES.string, Valtype.i32),
|
2540
|
-
|
3271
|
+
...setLastType(scope)
|
3272
|
+
],
|
3273
|
+
[TYPES._bytestring]: [
|
3274
|
+
// setup new/out array
|
3275
|
+
...newOut,
|
3276
|
+
[ Opcodes.drop ],
|
3277
|
+
|
3278
|
+
...number(0, Valtype.i32), // base 0 for store later
|
3279
|
+
|
3280
|
+
...property,
|
3281
|
+
Opcodes.i32_to_u,
|
3282
|
+
|
3283
|
+
...(aotPointer ? [] : [
|
3284
|
+
...object,
|
3285
|
+
Opcodes.i32_to_u,
|
3286
|
+
[ Opcodes.i32_add ]
|
3287
|
+
]),
|
3288
|
+
|
3289
|
+
// load current string ind {arg}
|
3290
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
3291
|
+
|
3292
|
+
// store to new string ind 0
|
3293
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
3294
|
+
|
3295
|
+
// return new string (page)
|
3296
|
+
...number(newPointer),
|
3297
|
+
|
3298
|
+
...number(TYPES._bytestring, Valtype.i32),
|
3299
|
+
...setLastType(scope)
|
2541
3300
|
],
|
2542
3301
|
|
2543
|
-
default:
|
3302
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
|
2544
3303
|
});
|
2545
3304
|
};
|
2546
3305
|
|
@@ -2550,25 +3309,36 @@ const objectHack = node => {
|
|
2550
3309
|
if (!node) return node;
|
2551
3310
|
|
2552
3311
|
if (node.type === 'MemberExpression') {
|
2553
|
-
|
3312
|
+
const out = (() => {
|
3313
|
+
if (node.computed || node.optional) return;
|
2554
3314
|
|
2555
|
-
|
3315
|
+
let objectName = node.object.name;
|
2556
3316
|
|
2557
|
-
|
2558
|
-
|
3317
|
+
// if object is not identifier or another member exp, give up
|
3318
|
+
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
|
3319
|
+
if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
|
2559
3320
|
|
2560
|
-
|
3321
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2561
3322
|
|
2562
|
-
|
2563
|
-
|
3323
|
+
// if .length, give up (hack within a hack!)
|
3324
|
+
if (node.property.name === 'length') {
|
3325
|
+
node.object = objectHack(node.object);
|
3326
|
+
return;
|
3327
|
+
}
|
2564
3328
|
|
2565
|
-
|
2566
|
-
|
3329
|
+
// no object name, give up
|
3330
|
+
if (!objectName) return;
|
2567
3331
|
|
2568
|
-
|
2569
|
-
|
2570
|
-
|
2571
|
-
|
3332
|
+
const name = '__' + objectName + '_' + node.property.name;
|
3333
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
3334
|
+
|
3335
|
+
return {
|
3336
|
+
type: 'Identifier',
|
3337
|
+
name
|
3338
|
+
};
|
3339
|
+
})();
|
3340
|
+
|
3341
|
+
if (out) return out;
|
2572
3342
|
}
|
2573
3343
|
|
2574
3344
|
for (const x in node) {
|
@@ -2582,11 +3352,11 @@ const objectHack = node => {
|
|
2582
3352
|
};
|
2583
3353
|
|
2584
3354
|
const generateFunc = (scope, decl) => {
|
2585
|
-
if (decl.async) return todo('async functions are not supported');
|
2586
|
-
if (decl.generator) return todo('generator functions are not supported');
|
3355
|
+
if (decl.async) return todo(scope, 'async functions are not supported');
|
3356
|
+
if (decl.generator) return todo(scope, 'generator functions are not supported');
|
2587
3357
|
|
2588
3358
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2589
|
-
const params = decl.params
|
3359
|
+
const params = decl.params ?? [];
|
2590
3360
|
|
2591
3361
|
// const innerScope = { ...scope };
|
2592
3362
|
// TODO: share scope/locals between !!!
|
@@ -2599,8 +3369,17 @@ const generateFunc = (scope, decl) => {
|
|
2599
3369
|
name
|
2600
3370
|
};
|
2601
3371
|
|
3372
|
+
if (typedInput && decl.returnType) {
|
3373
|
+
innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
|
3374
|
+
innerScope.returns = [ valtypeBinary ];
|
3375
|
+
}
|
3376
|
+
|
2602
3377
|
for (let i = 0; i < params.length; i++) {
|
2603
|
-
allocVar(innerScope, params[i], false);
|
3378
|
+
allocVar(innerScope, params[i].name, false);
|
3379
|
+
|
3380
|
+
if (typedInput && params[i].typeAnnotation) {
|
3381
|
+
addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
|
3382
|
+
}
|
2604
3383
|
}
|
2605
3384
|
|
2606
3385
|
let body = objectHack(decl.body);
|
@@ -2616,13 +3395,13 @@ const generateFunc = (scope, decl) => {
|
|
2616
3395
|
const func = {
|
2617
3396
|
name,
|
2618
3397
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2619
|
-
|
2620
|
-
|
2621
|
-
throws: innerScope.throws,
|
2622
|
-
index: currentFuncIndex++
|
3398
|
+
index: currentFuncIndex++,
|
3399
|
+
...innerScope
|
2623
3400
|
};
|
2624
3401
|
funcIndex[name] = func.index;
|
2625
3402
|
|
3403
|
+
if (name === 'main') func.gotLastType = true;
|
3404
|
+
|
2626
3405
|
// quick hack fixes
|
2627
3406
|
for (const inst of wasm) {
|
2628
3407
|
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
@@ -2639,117 +3418,6 @@ const generateFunc = (scope, decl) => {
|
|
2639
3418
|
);
|
2640
3419
|
}
|
2641
3420
|
|
2642
|
-
// change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
|
2643
|
-
let offset = 0, vecParams = 0;
|
2644
|
-
for (let i = 0; i < params.length; i++) {
|
2645
|
-
const name = params[i];
|
2646
|
-
const local = func.locals[name];
|
2647
|
-
if (local.type === Valtype.v128) {
|
2648
|
-
vecParams++;
|
2649
|
-
|
2650
|
-
/* wasm.unshift( // add v128 load for param
|
2651
|
-
[ Opcodes.i32_const, 0 ],
|
2652
|
-
[ ...Opcodes.v128_load, 0, i * 16 ],
|
2653
|
-
[ Opcodes.local_set, local.idx ]
|
2654
|
-
); */
|
2655
|
-
|
2656
|
-
// using params and replace_lane is noticably faster than just loading from memory (above) somehow
|
2657
|
-
|
2658
|
-
// extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
|
2659
|
-
const { vecType } = local;
|
2660
|
-
let [ type, lanes ] = vecType.split('x');
|
2661
|
-
if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
|
2662
|
-
|
2663
|
-
lanes = parseInt(lanes);
|
2664
|
-
type = Valtype[type];
|
2665
|
-
|
2666
|
-
const name = params[i]; // get original param name
|
2667
|
-
|
2668
|
-
func.params.splice(offset, 1, ...new Array(lanes).fill(type)); // add new params of {type}, {lanes} times
|
2669
|
-
|
2670
|
-
// update index of original local
|
2671
|
-
// delete func.locals[name];
|
2672
|
-
|
2673
|
-
// add new locals for params
|
2674
|
-
for (let j = 0; j < lanes; j++) {
|
2675
|
-
func.locals[name + j] = { idx: offset + j, type, vecParamAutogen: true };
|
2676
|
-
}
|
2677
|
-
|
2678
|
-
// prepend wasm to generate expected v128 locals
|
2679
|
-
wasm.splice(i * 2 + offset * 2, 0,
|
2680
|
-
...i32x4(0, 0, 0, 0),
|
2681
|
-
...new Array(lanes).fill(0).flatMap((_, j) => [
|
2682
|
-
[ Opcodes.local_get, offset + j ],
|
2683
|
-
[ ...Opcodes[vecType + '_replace_lane'], j ]
|
2684
|
-
]),
|
2685
|
-
[ Opcodes.local_set, i ]
|
2686
|
-
);
|
2687
|
-
|
2688
|
-
offset += lanes;
|
2689
|
-
|
2690
|
-
// note: wrapping is disabled for now due to perf/dx concerns (so this will never run)
|
2691
|
-
/* if (!func.name.startsWith('#')) func.name = '##' + func.name;
|
2692
|
-
|
2693
|
-
// add vec type index to hash name prefix for wrapper to know how to wrap
|
2694
|
-
const vecTypeIdx = [ 'i8x16', 'i16x8', 'i32x4', 'i64x2', 'f32x4', 'f64x2' ].indexOf(local.vecType);
|
2695
|
-
const secondHash = func.name.slice(1).indexOf('#');
|
2696
|
-
func.name = '#' + func.name.slice(1, secondHash) + vecTypeIdx + func.name.slice(secondHash); */
|
2697
|
-
}
|
2698
|
-
}
|
2699
|
-
|
2700
|
-
if (offset !== 0) {
|
2701
|
-
// bump local indexes for all other locals after
|
2702
|
-
for (const x in func.locals) {
|
2703
|
-
const local = func.locals[x];
|
2704
|
-
if (!local.vecParamAutogen) local.idx += offset;
|
2705
|
-
}
|
2706
|
-
|
2707
|
-
// bump local indexes in wasm local.get/set
|
2708
|
-
for (let j = 0; j < wasm.length; j++) {
|
2709
|
-
const inst = wasm[j];
|
2710
|
-
if (j < offset * 2 + vecParams * 2) {
|
2711
|
-
if (inst[0] === Opcodes.local_set) inst[1] += offset;
|
2712
|
-
continue;
|
2713
|
-
}
|
2714
|
-
|
2715
|
-
if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set) inst[1] += offset;
|
2716
|
-
}
|
2717
|
-
}
|
2718
|
-
|
2719
|
-
// change v128 return into many <type> instead as unsupported return valtype
|
2720
|
-
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]);
|
2721
|
-
if (lastReturnLocal && lastReturnLocal.type === Valtype.v128) {
|
2722
|
-
const name = Object.keys(func.locals)[Object.values(func.locals).indexOf(lastReturnLocal)];
|
2723
|
-
// extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
|
2724
|
-
const { vecType } = lastReturnLocal;
|
2725
|
-
let [ type, lanes ] = vecType.split('x');
|
2726
|
-
if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
|
2727
|
-
|
2728
|
-
lanes = parseInt(lanes);
|
2729
|
-
type = Valtype[type];
|
2730
|
-
|
2731
|
-
const vecIdx = lastReturnLocal.idx;
|
2732
|
-
|
2733
|
-
const lastIdx = Math.max(0, ...Object.values(func.locals).map(x => x.idx));
|
2734
|
-
const tmpIdx = [];
|
2735
|
-
for (let i = 0; i < lanes; i++) {
|
2736
|
-
const idx = lastIdx + i + 1;
|
2737
|
-
tmpIdx.push(idx);
|
2738
|
-
func.locals[name + i] = { idx, type, vecReturnAutogen: true };
|
2739
|
-
}
|
2740
|
-
|
2741
|
-
wasm.splice(wasm.length - 1, 1,
|
2742
|
-
...new Array(lanes).fill(0).flatMap((_, i) => [
|
2743
|
-
i === 0 ? null : [ Opcodes.local_get, vecIdx ],
|
2744
|
-
[ ...Opcodes[vecType + '_extract_lane'], i ],
|
2745
|
-
[ Opcodes.local_set, tmpIdx[i] ],
|
2746
|
-
].filter(x => x !== null)),
|
2747
|
-
...new Array(lanes).fill(0).map((_, i) => [ Opcodes.local_get, tmpIdx[i]])
|
2748
|
-
);
|
2749
|
-
|
2750
|
-
func.returns = new Array(lanes).fill(type);
|
2751
|
-
}
|
2752
|
-
|
2753
3421
|
func.wasm = wasm;
|
2754
3422
|
|
2755
3423
|
funcs.push(func);
|
@@ -2785,7 +3453,7 @@ const internalConstrs = {
|
|
2785
3453
|
|
2786
3454
|
// todo: check in wasm instead of here
|
2787
3455
|
const literalValue = arg.value ?? 0;
|
2788
|
-
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
|
3456
|
+
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
|
2789
3457
|
|
2790
3458
|
return [
|
2791
3459
|
...number(0, Valtype.i32),
|
@@ -2796,7 +3464,8 @@ const internalConstrs = {
|
|
2796
3464
|
...number(pointer)
|
2797
3465
|
];
|
2798
3466
|
},
|
2799
|
-
type: TYPES._array
|
3467
|
+
type: TYPES._array,
|
3468
|
+
length: 1
|
2800
3469
|
},
|
2801
3470
|
|
2802
3471
|
__Array_of: {
|
@@ -2808,7 +3477,94 @@ const internalConstrs = {
|
|
2808
3477
|
}, global, name);
|
2809
3478
|
},
|
2810
3479
|
type: TYPES._array,
|
3480
|
+
notConstr: true,
|
3481
|
+
length: 0
|
3482
|
+
},
|
3483
|
+
|
3484
|
+
__Porffor_fastOr: {
|
3485
|
+
generate: (scope, decl) => {
|
3486
|
+
const out = [];
|
3487
|
+
|
3488
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3489
|
+
out.push(
|
3490
|
+
...generate(scope, decl.arguments[i]),
|
3491
|
+
Opcodes.i32_to_u,
|
3492
|
+
...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
|
3493
|
+
);
|
3494
|
+
}
|
3495
|
+
|
3496
|
+
return out;
|
3497
|
+
},
|
3498
|
+
type: TYPES.boolean,
|
2811
3499
|
notConstr: true
|
3500
|
+
},
|
3501
|
+
|
3502
|
+
__Porffor_fastAnd: {
|
3503
|
+
generate: (scope, decl) => {
|
3504
|
+
const out = [];
|
3505
|
+
|
3506
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3507
|
+
out.push(
|
3508
|
+
...generate(scope, decl.arguments[i]),
|
3509
|
+
Opcodes.i32_to_u,
|
3510
|
+
...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
|
3511
|
+
);
|
3512
|
+
}
|
3513
|
+
|
3514
|
+
return out;
|
3515
|
+
},
|
3516
|
+
type: TYPES.boolean,
|
3517
|
+
notConstr: true
|
3518
|
+
},
|
3519
|
+
|
3520
|
+
Boolean: {
|
3521
|
+
generate: (scope, decl) => {
|
3522
|
+
// todo: boolean object when used as constructor
|
3523
|
+
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
3524
|
+
return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
|
3525
|
+
},
|
3526
|
+
type: TYPES.boolean,
|
3527
|
+
length: 1
|
3528
|
+
},
|
3529
|
+
|
3530
|
+
__Math_max: {
|
3531
|
+
generate: (scope, decl) => {
|
3532
|
+
const out = [
|
3533
|
+
...number(-Infinity)
|
3534
|
+
];
|
3535
|
+
|
3536
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3537
|
+
out.push(
|
3538
|
+
...generate(scope, decl.arguments[i]),
|
3539
|
+
[ Opcodes.f64_max ]
|
3540
|
+
);
|
3541
|
+
}
|
3542
|
+
|
3543
|
+
return out;
|
3544
|
+
},
|
3545
|
+
type: TYPES.number,
|
3546
|
+
notConstr: true,
|
3547
|
+
length: 2
|
3548
|
+
},
|
3549
|
+
|
3550
|
+
__Math_min: {
|
3551
|
+
generate: (scope, decl) => {
|
3552
|
+
const out = [
|
3553
|
+
...number(Infinity)
|
3554
|
+
];
|
3555
|
+
|
3556
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3557
|
+
out.push(
|
3558
|
+
...generate(scope, decl.arguments[i]),
|
3559
|
+
[ Opcodes.f64_min ]
|
3560
|
+
);
|
3561
|
+
}
|
3562
|
+
|
3563
|
+
return out;
|
3564
|
+
},
|
3565
|
+
type: TYPES.number,
|
3566
|
+
notConstr: true,
|
3567
|
+
length: 2
|
2812
3568
|
}
|
2813
3569
|
};
|
2814
3570
|
|
@@ -2837,7 +3593,6 @@ export default program => {
|
|
2837
3593
|
funcs = [];
|
2838
3594
|
funcIndex = {};
|
2839
3595
|
depth = [];
|
2840
|
-
arrays = new Map();
|
2841
3596
|
pages = new Map();
|
2842
3597
|
data = [];
|
2843
3598
|
currentFuncIndex = importedFuncs.length;
|
@@ -2851,6 +3606,10 @@ export default program => {
|
|
2851
3606
|
|
2852
3607
|
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
2853
3608
|
|
3609
|
+
globalThis.pageSize = PageSize;
|
3610
|
+
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
3611
|
+
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3612
|
+
|
2854
3613
|
// set generic opcodes for current valtype
|
2855
3614
|
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
2856
3615
|
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
@@ -2859,10 +3618,10 @@ export default program => {
|
|
2859
3618
|
Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
|
2860
3619
|
Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
|
2861
3620
|
|
2862
|
-
Opcodes.i32_to = [ [
|
2863
|
-
Opcodes.i32_to_u = [ [
|
2864
|
-
Opcodes.i32_from = [ [
|
2865
|
-
Opcodes.i32_from_u = [ [
|
3621
|
+
Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
|
3622
|
+
Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
|
3623
|
+
Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
|
3624
|
+
Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
|
2866
3625
|
|
2867
3626
|
Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
|
2868
3627
|
Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
|
@@ -2875,10 +3634,6 @@ export default program => {
|
|
2875
3634
|
|
2876
3635
|
program.id = { name: 'main' };
|
2877
3636
|
|
2878
|
-
globalThis.pageSize = PageSize;
|
2879
|
-
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
2880
|
-
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
2881
|
-
|
2882
3637
|
const scope = {
|
2883
3638
|
locals: {},
|
2884
3639
|
localInd: 0
|
@@ -2889,7 +3644,7 @@ export default program => {
|
|
2889
3644
|
body: program.body
|
2890
3645
|
};
|
2891
3646
|
|
2892
|
-
if (
|
3647
|
+
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
2893
3648
|
|
2894
3649
|
generateFunc(scope, program);
|
2895
3650
|
|
@@ -2906,7 +3661,11 @@ export default program => {
|
|
2906
3661
|
}
|
2907
3662
|
|
2908
3663
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
2909
|
-
|
3664
|
+
if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
|
3665
|
+
main.wasm.splice(main.wasm.length - 1, 1);
|
3666
|
+
} else {
|
3667
|
+
main.returns = [];
|
3668
|
+
}
|
2910
3669
|
}
|
2911
3670
|
|
2912
3671
|
if (lastInst[0] === Opcodes.call) {
|