porffor 0.2.0-9ca9aed → 0.2.0-9f58210
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +256 -0
- package/LICENSE +20 -20
- package/README.md +157 -77
- package/asur/README.md +2 -0
- package/asur/index.js +1262 -0
- package/byg/index.js +237 -0
- package/compiler/2c.js +317 -72
- package/compiler/{sections.js → assemble.js} +64 -16
- package/compiler/builtins/annexb_string.js +72 -0
- package/compiler/builtins/annexb_string.ts +18 -0
- package/compiler/builtins/array.ts +145 -0
- package/compiler/builtins/base64.ts +76 -0
- package/compiler/builtins/boolean.ts +20 -0
- package/compiler/builtins/crypto.ts +120 -0
- package/compiler/builtins/date.ts +2070 -0
- package/compiler/builtins/escape.ts +141 -0
- package/compiler/builtins/int.ts +147 -0
- package/compiler/builtins/number.ts +534 -0
- package/compiler/builtins/porffor.d.ts +59 -0
- package/compiler/builtins/string.ts +1070 -0
- package/compiler/builtins/tostring.ts +37 -0
- package/compiler/builtins.js +580 -272
- package/compiler/{codeGen.js → codegen.js} +1415 -562
- package/compiler/decompile.js +3 -4
- package/compiler/embedding.js +22 -22
- package/compiler/encoding.js +108 -10
- package/compiler/generated_builtins.js +1517 -0
- package/compiler/index.js +36 -34
- package/compiler/log.js +6 -3
- package/compiler/opt.js +65 -29
- package/compiler/parse.js +38 -29
- package/compiler/precompile.js +128 -0
- package/compiler/prefs.js +27 -0
- package/compiler/prototype.js +182 -42
- package/compiler/types.js +37 -0
- package/compiler/wasmSpec.js +31 -7
- package/compiler/wrap.js +141 -43
- package/package.json +9 -5
- package/porf +4 -0
- package/rhemyn/compile.js +46 -27
- package/rhemyn/parse.js +322 -320
- package/rhemyn/test/parse.js +58 -58
- package/runner/compare.js +34 -34
- package/runner/debug.js +122 -0
- package/runner/index.js +91 -11
- package/runner/profiler.js +102 -0
- package/runner/repl.js +44 -11
- package/runner/sizes.js +37 -37
- package/compiler/builtins/base64.js +0 -92
- package/runner/info.js +0 -89
- package/runner/profile.js +0 -46
- package/runner/results.json +0 -1
- package/runner/transform.js +0 -15
- package/tmp.c +0 -71
- package/util/enum.js +0 -20
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
|
2
|
-
import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
|
2
|
+
import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from "./encoding.js";
|
3
3
|
import { operatorOpcode } from "./expression.js";
|
4
4
|
import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
|
5
5
|
import { PrototypeFuncs } from "./prototype.js";
|
@@ -7,6 +7,8 @@ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enfor
|
|
7
7
|
import { log } from "./log.js";
|
8
8
|
import parse from "./parse.js";
|
9
9
|
import * as Rhemyn from "../rhemyn/compile.js";
|
10
|
+
import Prefs from './prefs.js';
|
11
|
+
import { TYPES, TYPE_NAMES } from './types.js';
|
10
12
|
|
11
13
|
let globals = {};
|
12
14
|
let globalInd = 0;
|
@@ -23,39 +25,45 @@ 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
|
62
|
+
const hasFuncWithName = name => {
|
63
|
+
const func = funcs.find(x => x.name === name);
|
64
|
+
return !!(func || builtinFuncs[name] || importedFuncs[name] || internalConstrs[name]);
|
65
|
+
};
|
66
|
+
const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
|
59
67
|
switch (decl.type) {
|
60
68
|
case 'BinaryExpression':
|
61
69
|
return generateBinaryExp(scope, decl, global, name);
|
@@ -68,7 +76,12 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
68
76
|
|
69
77
|
case 'ArrowFunctionExpression':
|
70
78
|
case 'FunctionDeclaration':
|
71
|
-
generateFunc(scope, decl);
|
79
|
+
const func = generateFunc(scope, decl);
|
80
|
+
|
81
|
+
if (decl.type.endsWith('Expression')) {
|
82
|
+
return number(func.index);
|
83
|
+
}
|
84
|
+
|
72
85
|
return [];
|
73
86
|
|
74
87
|
case 'BlockStatement':
|
@@ -81,7 +94,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
81
94
|
return generateExp(scope, decl);
|
82
95
|
|
83
96
|
case 'CallExpression':
|
84
|
-
return generateCall(scope, decl, global, name);
|
97
|
+
return generateCall(scope, decl, global, name, valueUnused);
|
85
98
|
|
86
99
|
case 'NewExpression':
|
87
100
|
return generateNew(scope, decl, global, name);
|
@@ -99,7 +112,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
99
112
|
return generateUnary(scope, decl);
|
100
113
|
|
101
114
|
case 'UpdateExpression':
|
102
|
-
return generateUpdate(scope, decl);
|
115
|
+
return generateUpdate(scope, decl, global, name, valueUnused);
|
103
116
|
|
104
117
|
case 'IfStatement':
|
105
118
|
return generateIf(scope, decl);
|
@@ -110,6 +123,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
110
123
|
case 'WhileStatement':
|
111
124
|
return generateWhile(scope, decl);
|
112
125
|
|
126
|
+
case 'DoWhileStatement':
|
127
|
+
return generateDoWhile(scope, decl);
|
128
|
+
|
113
129
|
case 'ForOfStatement':
|
114
130
|
return generateForOf(scope, decl);
|
115
131
|
|
@@ -119,6 +135,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
119
135
|
case 'ContinueStatement':
|
120
136
|
return generateContinue(scope, decl);
|
121
137
|
|
138
|
+
case 'LabeledStatement':
|
139
|
+
return generateLabel(scope, decl);
|
140
|
+
|
122
141
|
case 'EmptyStatement':
|
123
142
|
return generateEmpty(scope, decl);
|
124
143
|
|
@@ -132,7 +151,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
132
151
|
return generateTry(scope, decl);
|
133
152
|
|
134
153
|
case 'DebuggerStatement':
|
135
|
-
// todo:
|
154
|
+
// todo: hook into terminal debugger
|
136
155
|
return [];
|
137
156
|
|
138
157
|
case 'ArrayExpression':
|
@@ -146,16 +165,22 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
146
165
|
const funcsBefore = funcs.length;
|
147
166
|
generate(scope, decl.declaration);
|
148
167
|
|
149
|
-
if (funcsBefore
|
168
|
+
if (funcsBefore !== funcs.length) {
|
169
|
+
// new func added
|
170
|
+
const newFunc = funcs[funcs.length - 1];
|
171
|
+
newFunc.export = true;
|
172
|
+
}
|
173
|
+
|
174
|
+
// if (funcsBefore === funcs.length) throw new Error('no new func added in export');
|
150
175
|
|
151
|
-
const newFunc = funcs[funcs.length - 1];
|
152
|
-
newFunc.export = true;
|
176
|
+
// const newFunc = funcs[funcs.length - 1];
|
177
|
+
// newFunc.export = true;
|
153
178
|
|
154
179
|
return [];
|
155
180
|
|
156
181
|
case 'TaggedTemplateExpression': {
|
157
182
|
const funcs = {
|
158
|
-
|
183
|
+
__Porffor_wasm: str => {
|
159
184
|
let out = [];
|
160
185
|
|
161
186
|
for (const line of str.split('\n')) {
|
@@ -163,8 +188,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
163
188
|
if (asm[0] === '') continue; // blank
|
164
189
|
|
165
190
|
if (asm[0] === 'local') {
|
166
|
-
const [ name,
|
167
|
-
scope.locals[name] = { idx:
|
191
|
+
const [ name, type ] = asm.slice(1);
|
192
|
+
scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
|
168
193
|
continue;
|
169
194
|
}
|
170
195
|
|
@@ -174,47 +199,74 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
174
199
|
}
|
175
200
|
|
176
201
|
if (asm[0] === 'memory') {
|
177
|
-
allocPage('asm instrinsic');
|
202
|
+
allocPage(scope, 'asm instrinsic');
|
178
203
|
// todo: add to store/load offset insts
|
179
204
|
continue;
|
180
205
|
}
|
181
206
|
|
182
207
|
let inst = Opcodes[asm[0].replace('.', '_')];
|
183
|
-
if (
|
208
|
+
if (inst == null) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
184
209
|
|
185
210
|
if (!Array.isArray(inst)) inst = [ inst ];
|
186
|
-
const immediates = asm.slice(1).map(x =>
|
211
|
+
const immediates = asm.slice(1).map(x => {
|
212
|
+
const int = parseInt(x);
|
213
|
+
if (Number.isNaN(int)) return scope.locals[x]?.idx;
|
214
|
+
return int;
|
215
|
+
});
|
187
216
|
|
188
|
-
out.push([ ...inst, ...immediates ]);
|
217
|
+
out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
|
189
218
|
}
|
190
219
|
|
191
220
|
return out;
|
192
221
|
},
|
193
222
|
|
194
|
-
|
195
|
-
|
223
|
+
__Porffor_bs: str => [
|
224
|
+
...makeString(scope, str, global, name, true),
|
196
225
|
|
197
|
-
|
198
|
-
...number(
|
199
|
-
|
226
|
+
...(name ? setType(scope, name, TYPES.bytestring) : [
|
227
|
+
...number(TYPES.bytestring, Valtype.i32),
|
228
|
+
...setLastType(scope)
|
229
|
+
])
|
230
|
+
],
|
231
|
+
__Porffor_s: str => [
|
232
|
+
...makeString(scope, str, global, name, false),
|
200
233
|
|
201
|
-
|
202
|
-
...number(
|
203
|
-
|
204
|
-
]
|
205
|
-
|
206
|
-
}
|
234
|
+
...(name ? setType(scope, name, TYPES.string) : [
|
235
|
+
...number(TYPES.string, Valtype.i32),
|
236
|
+
...setLastType(scope)
|
237
|
+
])
|
238
|
+
],
|
239
|
+
};
|
207
240
|
|
208
|
-
const
|
241
|
+
const func = decl.tag.name;
|
209
242
|
// hack for inline asm
|
210
|
-
if (!funcs[
|
243
|
+
if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
|
244
|
+
|
245
|
+
const { quasis, expressions } = decl.quasi;
|
246
|
+
let str = quasis[0].value.raw;
|
247
|
+
|
248
|
+
for (let i = 0; i < expressions.length; i++) {
|
249
|
+
const e = expressions[i];
|
250
|
+
if (!e.name) {
|
251
|
+
if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
|
252
|
+
str += lookupName(scope, e.left.name)[0].idx + e.right.value;
|
253
|
+
} else todo(scope, 'unsupported expression in intrinsic');
|
254
|
+
} else str += lookupName(scope, e.name)[0].idx;
|
255
|
+
|
256
|
+
str += quasis[i + 1].value.raw;
|
257
|
+
}
|
211
258
|
|
212
|
-
|
213
|
-
return funcs[name](str);
|
259
|
+
return funcs[func](str);
|
214
260
|
}
|
215
261
|
|
216
262
|
default:
|
217
|
-
|
263
|
+
// ignore typescript nodes
|
264
|
+
if (decl.type.startsWith('TS') ||
|
265
|
+
decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
|
266
|
+
return [];
|
267
|
+
}
|
268
|
+
|
269
|
+
return todo(scope, `no generation for ${decl.type}!`);
|
218
270
|
}
|
219
271
|
};
|
220
272
|
|
@@ -242,7 +294,7 @@ const lookupName = (scope, _name) => {
|
|
242
294
|
return [ undefined, undefined ];
|
243
295
|
};
|
244
296
|
|
245
|
-
const internalThrow = (scope, constructor, message, expectsValue =
|
297
|
+
const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
|
246
298
|
...generateThrow(scope, {
|
247
299
|
argument: {
|
248
300
|
type: 'NewExpression',
|
@@ -264,25 +316,33 @@ const generateIdent = (scope, decl) => {
|
|
264
316
|
const name = mapName(rawName);
|
265
317
|
let local = scope.locals[rawName];
|
266
318
|
|
267
|
-
if (builtinVars
|
319
|
+
if (Object.hasOwn(builtinVars, name)) {
|
268
320
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
269
|
-
|
321
|
+
|
322
|
+
let wasm = builtinVars[name];
|
323
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
|
324
|
+
return wasm.slice();
|
325
|
+
}
|
326
|
+
|
327
|
+
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
328
|
+
// todo: return an actual something
|
329
|
+
return number(1);
|
270
330
|
}
|
271
331
|
|
272
|
-
if (
|
332
|
+
if (isExistingProtoFunc(name)) {
|
273
333
|
// todo: return an actual something
|
274
334
|
return number(1);
|
275
335
|
}
|
276
336
|
|
277
|
-
if (local === undefined) {
|
337
|
+
if (local?.idx === undefined) {
|
278
338
|
// no local var with name
|
279
|
-
if (
|
280
|
-
if (funcIndex
|
339
|
+
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
340
|
+
if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
|
281
341
|
|
282
|
-
if (globals
|
342
|
+
if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
|
283
343
|
}
|
284
344
|
|
285
|
-
if (local === undefined && rawName.startsWith('__')) {
|
345
|
+
if (local?.idx === undefined && rawName.startsWith('__')) {
|
286
346
|
// return undefined if unknown key in already known var
|
287
347
|
let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
|
288
348
|
if (parent.includes('_')) parent = '__' + parent;
|
@@ -291,7 +351,7 @@ const generateIdent = (scope, decl) => {
|
|
291
351
|
if (!parentLookup[1]) return number(UNDEFINED);
|
292
352
|
}
|
293
353
|
|
294
|
-
if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
354
|
+
if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
295
355
|
|
296
356
|
return [ [ Opcodes.local_get, local.idx ] ];
|
297
357
|
};
|
@@ -304,14 +364,18 @@ const generateReturn = (scope, decl) => {
|
|
304
364
|
// just bare "return"
|
305
365
|
return [
|
306
366
|
...number(UNDEFINED), // "undefined" if func returns
|
307
|
-
...
|
367
|
+
...(scope.returnType != null ? [] : [
|
368
|
+
...number(TYPES.undefined, Valtype.i32) // type undefined
|
369
|
+
]),
|
308
370
|
[ Opcodes.return ]
|
309
371
|
];
|
310
372
|
}
|
311
373
|
|
312
374
|
return [
|
313
375
|
...generate(scope, decl.argument),
|
314
|
-
...
|
376
|
+
...(scope.returnType != null ? [] : [
|
377
|
+
...getNodeType(scope, decl.argument)
|
378
|
+
]),
|
315
379
|
[ Opcodes.return ]
|
316
380
|
];
|
317
381
|
};
|
@@ -325,7 +389,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
325
389
|
return idx;
|
326
390
|
};
|
327
391
|
|
328
|
-
const isIntOp = op => op && (op[0] >=
|
392
|
+
const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
|
393
|
+
const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
|
329
394
|
|
330
395
|
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
331
396
|
const checks = {
|
@@ -334,7 +399,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
334
399
|
'??': nullish
|
335
400
|
};
|
336
401
|
|
337
|
-
if (!checks[op]) return todo(`logic operator ${op} not implemented yet
|
402
|
+
if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
|
338
403
|
|
339
404
|
// generic structure for {a} OP {b}
|
340
405
|
// -->
|
@@ -342,8 +407,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
342
407
|
|
343
408
|
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
344
409
|
// (like if we are in an if condition - very common)
|
345
|
-
const leftIsInt =
|
346
|
-
const rightIsInt =
|
410
|
+
const leftIsInt = isFloatToIntOp(left[left.length - 1]);
|
411
|
+
const rightIsInt = isFloatToIntOp(right[right.length - 1]);
|
347
412
|
|
348
413
|
const canInt = leftIsInt && rightIsInt;
|
349
414
|
|
@@ -360,12 +425,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
360
425
|
...right,
|
361
426
|
// note type
|
362
427
|
...rightType,
|
363
|
-
|
428
|
+
...setLastType(scope),
|
364
429
|
[ Opcodes.else ],
|
365
430
|
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
366
431
|
// note type
|
367
432
|
...leftType,
|
368
|
-
|
433
|
+
...setLastType(scope),
|
369
434
|
[ Opcodes.end ],
|
370
435
|
Opcodes.i32_from
|
371
436
|
];
|
@@ -379,17 +444,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
379
444
|
...right,
|
380
445
|
// note type
|
381
446
|
...rightType,
|
382
|
-
|
447
|
+
...setLastType(scope),
|
383
448
|
[ Opcodes.else ],
|
384
449
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
385
450
|
// note type
|
386
451
|
...leftType,
|
387
|
-
|
452
|
+
...setLastType(scope),
|
388
453
|
[ Opcodes.end ]
|
389
454
|
];
|
390
455
|
};
|
391
456
|
|
392
|
-
const concatStrings = (scope, left, right, global, name, assign) => {
|
457
|
+
const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
|
393
458
|
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
394
459
|
// todo: convert left and right to strings if not
|
395
460
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -399,11 +464,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
399
464
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
400
465
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
401
466
|
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
if (assign) {
|
406
|
-
const pointer = arrays.get(name ?? '$undeclared');
|
467
|
+
if (assign && Prefs.aotPointerOpt) {
|
468
|
+
const pointer = scope.arrays?.get(name ?? '$undeclared');
|
407
469
|
|
408
470
|
return [
|
409
471
|
// setup right
|
@@ -428,11 +490,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
428
490
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
|
429
491
|
|
430
492
|
// copy right
|
431
|
-
// dst = out pointer + length size + current length *
|
493
|
+
// dst = out pointer + length size + current length * sizeof valtype
|
432
494
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
433
495
|
|
434
496
|
[ Opcodes.local_get, leftLength ],
|
435
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
497
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
436
498
|
[ Opcodes.i32_mul ],
|
437
499
|
[ Opcodes.i32_add ],
|
438
500
|
|
@@ -441,9 +503,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
441
503
|
...number(ValtypeSize.i32, Valtype.i32),
|
442
504
|
[ Opcodes.i32_add ],
|
443
505
|
|
444
|
-
// size = right length *
|
506
|
+
// size = right length * sizeof valtype
|
445
507
|
[ Opcodes.local_get, rightLength ],
|
446
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
508
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
447
509
|
[ Opcodes.i32_mul ],
|
448
510
|
|
449
511
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -501,11 +563,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
501
563
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
502
564
|
|
503
565
|
// copy right
|
504
|
-
// dst = out pointer + length size + left length *
|
566
|
+
// dst = out pointer + length size + left length * sizeof valtype
|
505
567
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
506
568
|
|
507
569
|
[ Opcodes.local_get, leftLength ],
|
508
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
570
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
509
571
|
[ Opcodes.i32_mul ],
|
510
572
|
[ Opcodes.i32_add ],
|
511
573
|
|
@@ -514,9 +576,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
514
576
|
...number(ValtypeSize.i32, Valtype.i32),
|
515
577
|
[ Opcodes.i32_add ],
|
516
578
|
|
517
|
-
// size = right length *
|
579
|
+
// size = right length * sizeof valtype
|
518
580
|
[ Opcodes.local_get, rightLength ],
|
519
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
581
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
520
582
|
[ Opcodes.i32_mul ],
|
521
583
|
|
522
584
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -526,7 +588,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
526
588
|
];
|
527
589
|
};
|
528
590
|
|
529
|
-
const compareStrings = (scope, left, right) => {
|
591
|
+
const compareStrings = (scope, left, right, bytestrings = false) => {
|
530
592
|
// todo: this should be rewritten into a func
|
531
593
|
// todo: convert left and right to strings if not
|
532
594
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -535,7 +597,6 @@ const compareStrings = (scope, left, right) => {
|
|
535
597
|
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
536
598
|
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
537
599
|
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
538
|
-
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
539
600
|
|
540
601
|
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
541
602
|
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
@@ -563,7 +624,6 @@ const compareStrings = (scope, left, right) => {
|
|
563
624
|
|
564
625
|
[ Opcodes.local_get, rightPointer ],
|
565
626
|
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
566
|
-
[ Opcodes.local_tee, rightLength ],
|
567
627
|
|
568
628
|
// fast path: check leftLength != rightLength
|
569
629
|
[ Opcodes.i32_ne ],
|
@@ -578,11 +638,13 @@ const compareStrings = (scope, left, right) => {
|
|
578
638
|
...number(0, Valtype.i32),
|
579
639
|
[ Opcodes.local_set, index ],
|
580
640
|
|
581
|
-
// setup index end as length * sizeof
|
641
|
+
// setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
|
582
642
|
// we do this instead of having to do mul/div each iter for perf™
|
583
643
|
[ Opcodes.local_get, leftLength ],
|
584
|
-
...
|
585
|
-
|
644
|
+
...(bytestrings ? [] : [
|
645
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
646
|
+
[ Opcodes.i32_mul ],
|
647
|
+
]),
|
586
648
|
[ Opcodes.local_set, indexEnd ],
|
587
649
|
|
588
650
|
// iterate over each char and check if eq
|
@@ -592,13 +654,17 @@ const compareStrings = (scope, left, right) => {
|
|
592
654
|
[ Opcodes.local_get, index ],
|
593
655
|
[ Opcodes.local_get, leftPointer ],
|
594
656
|
[ Opcodes.i32_add ],
|
595
|
-
|
657
|
+
bytestrings ?
|
658
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
659
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
596
660
|
|
597
661
|
// fetch right
|
598
662
|
[ Opcodes.local_get, index ],
|
599
663
|
[ Opcodes.local_get, rightPointer ],
|
600
664
|
[ Opcodes.i32_add ],
|
601
|
-
|
665
|
+
bytestrings ?
|
666
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
667
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
602
668
|
|
603
669
|
// not equal, "return" false
|
604
670
|
[ Opcodes.i32_ne ],
|
@@ -607,13 +673,13 @@ const compareStrings = (scope, left, right) => {
|
|
607
673
|
[ Opcodes.br, 2 ],
|
608
674
|
[ Opcodes.end ],
|
609
675
|
|
610
|
-
// index += sizeof
|
676
|
+
// index += sizeof valtype (1 for bytestring, 2 for string)
|
611
677
|
[ Opcodes.local_get, index ],
|
612
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
678
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
613
679
|
[ Opcodes.i32_add ],
|
614
680
|
[ Opcodes.local_tee, index ],
|
615
681
|
|
616
|
-
// if index != index end (length * sizeof
|
682
|
+
// if index != index end (length * sizeof valtype), loop
|
617
683
|
[ Opcodes.local_get, indexEnd ],
|
618
684
|
[ Opcodes.i32_ne ],
|
619
685
|
[ Opcodes.br_if, 0 ],
|
@@ -634,16 +700,18 @@ const compareStrings = (scope, left, right) => {
|
|
634
700
|
};
|
635
701
|
|
636
702
|
const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
637
|
-
if (
|
703
|
+
if (isFloatToIntOp(wasm[wasm.length - 1])) return [
|
638
704
|
...wasm,
|
639
705
|
...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
|
640
706
|
];
|
707
|
+
// if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
|
641
708
|
|
642
|
-
const
|
709
|
+
const useTmp = knownType(scope, type) == null;
|
710
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
643
711
|
|
644
712
|
const def = [
|
645
713
|
// if value != 0
|
646
|
-
[ Opcodes.local_get, tmp ],
|
714
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
647
715
|
|
648
716
|
// ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
649
717
|
...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
|
@@ -655,16 +723,16 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
655
723
|
|
656
724
|
return [
|
657
725
|
...wasm,
|
658
|
-
[ Opcodes.local_set, tmp ],
|
726
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
659
727
|
|
660
728
|
...typeSwitch(scope, type, {
|
661
729
|
// [TYPES.number]: def,
|
662
|
-
[TYPES.
|
730
|
+
[TYPES.array]: [
|
663
731
|
// arrays are always truthy
|
664
732
|
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
665
733
|
],
|
666
734
|
[TYPES.string]: [
|
667
|
-
[ Opcodes.local_get, tmp ],
|
735
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
668
736
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
669
737
|
|
670
738
|
// get length
|
@@ -675,24 +743,46 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
675
743
|
[ Opcodes.i32_eqz ], */
|
676
744
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
677
745
|
],
|
746
|
+
[TYPES.bytestring]: [ // duplicate of string
|
747
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
748
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
749
|
+
|
750
|
+
// get length
|
751
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
752
|
+
|
753
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
754
|
+
],
|
678
755
|
default: def
|
679
756
|
}, intOut ? Valtype.i32 : valtypeBinary)
|
680
757
|
];
|
681
758
|
};
|
682
759
|
|
683
760
|
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
684
|
-
const
|
761
|
+
const useTmp = knownType(scope, type) == null;
|
762
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
763
|
+
|
685
764
|
return [
|
686
765
|
...wasm,
|
687
|
-
[ Opcodes.local_set, tmp ],
|
766
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
688
767
|
|
689
768
|
...typeSwitch(scope, type, {
|
690
|
-
[TYPES.
|
769
|
+
[TYPES.array]: [
|
691
770
|
// arrays are always truthy
|
692
771
|
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
693
772
|
],
|
694
773
|
[TYPES.string]: [
|
695
|
-
[ Opcodes.local_get, tmp ],
|
774
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
775
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
776
|
+
|
777
|
+
// get length
|
778
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
779
|
+
|
780
|
+
// if length == 0
|
781
|
+
[ Opcodes.i32_eqz ],
|
782
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
783
|
+
],
|
784
|
+
[TYPES.bytestring]: [ // duplicate of string
|
785
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
696
786
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
697
787
|
|
698
788
|
// get length
|
@@ -704,7 +794,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
704
794
|
],
|
705
795
|
default: [
|
706
796
|
// if value == 0
|
707
|
-
[ Opcodes.local_get, tmp ],
|
797
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
708
798
|
|
709
799
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
710
800
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -714,10 +804,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
714
804
|
};
|
715
805
|
|
716
806
|
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
717
|
-
const
|
807
|
+
const useTmp = knownType(scope, type) == null;
|
808
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
809
|
+
|
718
810
|
return [
|
719
811
|
...wasm,
|
720
|
-
[ Opcodes.local_set, tmp ],
|
812
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
721
813
|
|
722
814
|
...typeSwitch(scope, type, {
|
723
815
|
[TYPES.undefined]: [
|
@@ -726,7 +818,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
726
818
|
],
|
727
819
|
[TYPES.object]: [
|
728
820
|
// object, null if == 0
|
729
|
-
[ Opcodes.local_get, tmp ],
|
821
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
730
822
|
|
731
823
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
732
824
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -755,11 +847,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
755
847
|
return performLogicOp(scope, op, left, right, leftType, rightType);
|
756
848
|
}
|
757
849
|
|
850
|
+
const knownLeft = knownType(scope, leftType);
|
851
|
+
const knownRight = knownType(scope, rightType);
|
852
|
+
|
758
853
|
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
759
854
|
const strictOp = op === '===' || op === '!==';
|
760
855
|
|
761
856
|
const startOut = [], endOut = [];
|
762
|
-
const
|
857
|
+
const finalize = out => startOut.concat(out, endOut);
|
763
858
|
|
764
859
|
// if strict (in)equal check types match
|
765
860
|
if (strictOp) {
|
@@ -804,31 +899,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
804
899
|
// todo: if equality op and an operand is undefined, return false
|
805
900
|
// todo: niche null hell with 0
|
806
901
|
|
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
|
-
|
902
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) {
|
903
|
+
if (op === '+') {
|
904
|
+
// todo: this should be dynamic too but for now only static
|
905
|
+
// string concat (a + b)
|
906
|
+
return concatStrings(scope, left, right, _global, _name, assign, false);
|
907
|
+
}
|
908
|
+
|
909
|
+
// not an equality op, NaN
|
910
|
+
if (!eqOp) return number(NaN);
|
911
|
+
|
912
|
+
// else leave bool ops
|
913
|
+
// todo: convert string to number if string and number/bool
|
914
|
+
// todo: string (>|>=|<|<=) string
|
915
|
+
|
916
|
+
// string comparison
|
917
|
+
if (op === '===' || op === '==') {
|
918
|
+
return compareStrings(scope, left, right);
|
919
|
+
}
|
920
|
+
|
921
|
+
if (op === '!==' || op === '!=') {
|
922
|
+
return [
|
923
|
+
...compareStrings(scope, left, right),
|
924
|
+
[ Opcodes.i32_eqz ]
|
925
|
+
];
|
926
|
+
}
|
927
|
+
}
|
928
|
+
|
929
|
+
if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) {
|
930
|
+
if (op === '+') {
|
931
|
+
// todo: this should be dynamic too but for now only static
|
932
|
+
// string concat (a + b)
|
933
|
+
return concatStrings(scope, left, right, _global, _name, assign, true);
|
934
|
+
}
|
935
|
+
|
936
|
+
// not an equality op, NaN
|
937
|
+
if (!eqOp) return number(NaN);
|
938
|
+
|
939
|
+
// else leave bool ops
|
940
|
+
// todo: convert string to number if string and number/bool
|
941
|
+
// todo: string (>|>=|<|<=) string
|
942
|
+
|
943
|
+
// string comparison
|
944
|
+
if (op === '===' || op === '==') {
|
945
|
+
return compareStrings(scope, left, right, true);
|
946
|
+
}
|
947
|
+
|
948
|
+
if (op === '!==' || op === '!=') {
|
949
|
+
return [
|
950
|
+
...compareStrings(scope, left, right, true),
|
951
|
+
[ Opcodes.i32_eqz ]
|
952
|
+
];
|
953
|
+
}
|
954
|
+
}
|
832
955
|
|
833
956
|
let ops = operatorOpcode[valtype][op];
|
834
957
|
|
@@ -838,24 +961,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
838
961
|
includeBuiltin(scope, builtinName);
|
839
962
|
const idx = funcIndex[builtinName];
|
840
963
|
|
841
|
-
return
|
964
|
+
return finalize([
|
842
965
|
...left,
|
843
966
|
...right,
|
844
967
|
[ Opcodes.call, idx ]
|
845
968
|
]);
|
846
969
|
}
|
847
970
|
|
848
|
-
if (!ops) return todo(`operator ${op} not implemented yet
|
971
|
+
if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
|
849
972
|
|
850
973
|
if (!Array.isArray(ops)) ops = [ ops ];
|
851
974
|
ops = [ ops ];
|
852
975
|
|
853
976
|
let tmpLeft, tmpRight;
|
854
977
|
// if equal op, check if strings for compareStrings
|
855
|
-
|
978
|
+
// todo: intelligent partial skip later
|
979
|
+
// if neither known are string, stop this madness
|
980
|
+
// we already do known checks earlier, so don't need to recheck
|
981
|
+
|
982
|
+
if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
|
856
983
|
tmpLeft = localTmp(scope, '__tmpop_left');
|
857
984
|
tmpRight = localTmp(scope, '__tmpop_right');
|
858
985
|
|
986
|
+
// returns false for one string, one not - but more ops/slower
|
987
|
+
// ops.unshift(...stringOnly([
|
988
|
+
// // if left is string
|
989
|
+
// ...leftType,
|
990
|
+
// ...number(TYPES.string, Valtype.i32),
|
991
|
+
// [ Opcodes.i32_eq ],
|
992
|
+
|
993
|
+
// // if right is string
|
994
|
+
// ...rightType,
|
995
|
+
// ...number(TYPES.string, Valtype.i32),
|
996
|
+
// [ Opcodes.i32_eq ],
|
997
|
+
|
998
|
+
// // if either are true
|
999
|
+
// [ Opcodes.i32_or ],
|
1000
|
+
// [ Opcodes.if, Blocktype.void ],
|
1001
|
+
|
1002
|
+
// // todo: convert non-strings to strings, for now fail immediately if one is not
|
1003
|
+
// // if left is not string
|
1004
|
+
// ...leftType,
|
1005
|
+
// ...number(TYPES.string, Valtype.i32),
|
1006
|
+
// [ Opcodes.i32_ne ],
|
1007
|
+
|
1008
|
+
// // if right is not string
|
1009
|
+
// ...rightType,
|
1010
|
+
// ...number(TYPES.string, Valtype.i32),
|
1011
|
+
// [ Opcodes.i32_ne ],
|
1012
|
+
|
1013
|
+
// // if either are true
|
1014
|
+
// [ Opcodes.i32_or ],
|
1015
|
+
// [ Opcodes.if, Blocktype.void ],
|
1016
|
+
// ...number(0, Valtype.i32),
|
1017
|
+
// [ Opcodes.br, 2 ],
|
1018
|
+
// [ Opcodes.end ],
|
1019
|
+
|
1020
|
+
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1021
|
+
// ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
1022
|
+
// [ Opcodes.br, 1 ],
|
1023
|
+
// [ Opcodes.end ],
|
1024
|
+
// ]));
|
1025
|
+
|
1026
|
+
// does not handle one string, one not (such cases go past)
|
859
1027
|
ops.unshift(...stringOnly([
|
860
1028
|
// if left is string
|
861
1029
|
...leftType,
|
@@ -867,30 +1035,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
867
1035
|
...number(TYPES.string, Valtype.i32),
|
868
1036
|
[ Opcodes.i32_eq ],
|
869
1037
|
|
870
|
-
// if
|
871
|
-
[ Opcodes.
|
1038
|
+
// if both are true
|
1039
|
+
[ Opcodes.i32_and ],
|
872
1040
|
[ Opcodes.if, Blocktype.void ],
|
1041
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1042
|
+
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
1043
|
+
[ Opcodes.br, 1 ],
|
1044
|
+
[ Opcodes.end ],
|
873
1045
|
|
874
|
-
//
|
875
|
-
// if left is not string
|
1046
|
+
// if left is bytestring
|
876
1047
|
...leftType,
|
877
|
-
...number(TYPES.
|
878
|
-
[ Opcodes.
|
1048
|
+
...number(TYPES.bytestring, Valtype.i32),
|
1049
|
+
[ Opcodes.i32_eq ],
|
879
1050
|
|
880
|
-
// if right is
|
1051
|
+
// if right is bytestring
|
881
1052
|
...rightType,
|
882
|
-
...number(TYPES.
|
883
|
-
[ Opcodes.
|
1053
|
+
...number(TYPES.bytestring, Valtype.i32),
|
1054
|
+
[ Opcodes.i32_eq ],
|
884
1055
|
|
885
|
-
// if
|
886
|
-
[ Opcodes.
|
1056
|
+
// if both are true
|
1057
|
+
[ Opcodes.i32_and ],
|
887
1058
|
[ 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 ] ]),
|
1059
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
|
894
1060
|
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
895
1061
|
[ Opcodes.br, 1 ],
|
896
1062
|
[ Opcodes.end ],
|
@@ -904,7 +1070,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
904
1070
|
// }
|
905
1071
|
}
|
906
1072
|
|
907
|
-
return
|
1073
|
+
return finalize([
|
908
1074
|
...left,
|
909
1075
|
...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
|
910
1076
|
...right,
|
@@ -921,7 +1087,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
921
1087
|
return out;
|
922
1088
|
};
|
923
1089
|
|
924
|
-
const
|
1090
|
+
const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
|
1091
|
+
return func({ name, params, locals, returns, localInd }, {
|
1092
|
+
TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage,
|
1093
|
+
builtin: name => {
|
1094
|
+
let idx = funcIndex[name] ?? importedFuncs[name];
|
1095
|
+
if (idx === undefined && builtinFuncs[name]) {
|
1096
|
+
includeBuiltin(null, name);
|
1097
|
+
idx = funcIndex[name];
|
1098
|
+
}
|
1099
|
+
|
1100
|
+
return idx;
|
1101
|
+
}
|
1102
|
+
});
|
1103
|
+
};
|
1104
|
+
|
1105
|
+
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
|
925
1106
|
const existing = funcs.find(x => x.name === name);
|
926
1107
|
if (existing) return existing;
|
927
1108
|
|
@@ -933,6 +1114,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
933
1114
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
934
1115
|
}
|
935
1116
|
|
1117
|
+
for (const x of _data) {
|
1118
|
+
const copy = { ...x };
|
1119
|
+
copy.offset += pages.size * pageSize;
|
1120
|
+
data.push(copy);
|
1121
|
+
}
|
1122
|
+
|
1123
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
|
1124
|
+
|
936
1125
|
let baseGlobalIdx, i = 0;
|
937
1126
|
for (const type of globalTypes) {
|
938
1127
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -955,7 +1144,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
955
1144
|
params,
|
956
1145
|
locals,
|
957
1146
|
returns,
|
958
|
-
returnType:
|
1147
|
+
returnType: returnType ?? TYPES.number,
|
959
1148
|
wasm,
|
960
1149
|
internal: true,
|
961
1150
|
index: currentFuncIndex++
|
@@ -978,6 +1167,7 @@ const generateLogicExp = (scope, decl) => {
|
|
978
1167
|
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
979
1168
|
};
|
980
1169
|
|
1170
|
+
// potential future ideas for nan boxing (unused):
|
981
1171
|
// T = JS type, V = value/pointer
|
982
1172
|
// 0bTTT
|
983
1173
|
// qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
|
@@ -991,7 +1181,6 @@ const generateLogicExp = (scope, decl) => {
|
|
991
1181
|
// js type: 4 bits
|
992
1182
|
// internal type: ? bits
|
993
1183
|
// pointer: 32 bits
|
994
|
-
|
995
1184
|
// generic
|
996
1185
|
// 1 23 4 5
|
997
1186
|
// 0 11111111111 11TTTTIIII??????????PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
|
@@ -1001,58 +1190,29 @@ const generateLogicExp = (scope, decl) => {
|
|
1001
1190
|
// 4: internal type
|
1002
1191
|
// 5: pointer
|
1003
1192
|
|
1004
|
-
const
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
object: 0x04,
|
1010
|
-
function: 0x05,
|
1011
|
-
symbol: 0x06,
|
1012
|
-
bigint: 0x07,
|
1013
|
-
|
1014
|
-
// these are not "typeof" types but tracked internally
|
1015
|
-
_array: 0x10,
|
1016
|
-
_regexp: 0x11,
|
1017
|
-
|
1018
|
-
// typed arrays
|
1019
|
-
_int8array: 0x20,
|
1020
|
-
_uint8array: 0x21,
|
1021
|
-
_uint8clampedarray: 0x22,
|
1022
|
-
_int16array: 0x23,
|
1023
|
-
_uint16array: 0x24,
|
1024
|
-
_int32array: 0x25,
|
1025
|
-
_uint32array: 0x26,
|
1026
|
-
_float32array: 0x27,
|
1027
|
-
_float64array: 0x28,
|
1028
|
-
};
|
1029
|
-
|
1030
|
-
const TYPE_NAMES = {
|
1031
|
-
[TYPES.number]: 'Number',
|
1032
|
-
[TYPES.boolean]: 'Boolean',
|
1033
|
-
[TYPES.string]: 'String',
|
1034
|
-
[TYPES.undefined]: 'undefined',
|
1035
|
-
[TYPES.object]: 'Object',
|
1036
|
-
[TYPES.function]: 'Function',
|
1037
|
-
[TYPES.symbol]: 'Symbol',
|
1038
|
-
[TYPES.bigint]: 'BigInt',
|
1039
|
-
|
1040
|
-
[TYPES._array]: 'Array',
|
1041
|
-
[TYPES._regexp]: 'RegExp'
|
1193
|
+
const isExistingProtoFunc = name => {
|
1194
|
+
if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES.array][name.slice(18)];
|
1195
|
+
if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
|
1196
|
+
|
1197
|
+
return false;
|
1042
1198
|
};
|
1043
1199
|
|
1044
1200
|
const getType = (scope, _name) => {
|
1045
1201
|
const name = mapName(_name);
|
1046
1202
|
|
1203
|
+
// if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
|
1204
|
+
|
1205
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
|
1047
1206
|
if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
|
1207
|
+
|
1208
|
+
if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
|
1048
1209
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1049
1210
|
|
1050
1211
|
let type = TYPES.undefined;
|
1051
|
-
if (builtinVars[name]) type =
|
1212
|
+
if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
|
1052
1213
|
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
|
1053
1214
|
|
1054
|
-
if (name
|
1055
|
-
name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
|
1215
|
+
if (isExistingProtoFunc(name)) type = TYPES.function;
|
1056
1216
|
|
1057
1217
|
return number(type, Valtype.i32);
|
1058
1218
|
};
|
@@ -1062,23 +1222,37 @@ const setType = (scope, _name, type) => {
|
|
1062
1222
|
|
1063
1223
|
const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
|
1064
1224
|
|
1225
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return [];
|
1065
1226
|
if (scope.locals[name]) return [
|
1066
1227
|
...out,
|
1067
1228
|
[ Opcodes.local_set, scope.locals[name + '#type'].idx ]
|
1068
1229
|
];
|
1069
1230
|
|
1231
|
+
if (typedInput && globals[name]?.metadata?.type != null) return [];
|
1070
1232
|
if (globals[name]) return [
|
1071
1233
|
...out,
|
1072
1234
|
[ Opcodes.global_set, globals[name + '#type'].idx ]
|
1073
1235
|
];
|
1074
1236
|
|
1075
1237
|
// throw new Error('could not find var');
|
1238
|
+
return [];
|
1239
|
+
};
|
1240
|
+
|
1241
|
+
const getLastType = scope => {
|
1242
|
+
scope.gotLastType = true;
|
1243
|
+
return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1244
|
+
};
|
1245
|
+
|
1246
|
+
const setLastType = scope => {
|
1247
|
+
return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1076
1248
|
};
|
1077
1249
|
|
1078
1250
|
const getNodeType = (scope, node) => {
|
1079
1251
|
const inner = () => {
|
1080
1252
|
if (node.type === 'Literal') {
|
1081
|
-
if (node.regex) return TYPES.
|
1253
|
+
if (node.regex) return TYPES.regexp;
|
1254
|
+
|
1255
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES.bytestring;
|
1082
1256
|
|
1083
1257
|
return TYPES[typeof node.value];
|
1084
1258
|
}
|
@@ -1093,6 +1267,27 @@ const getNodeType = (scope, node) => {
|
|
1093
1267
|
|
1094
1268
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1095
1269
|
const name = node.callee.name;
|
1270
|
+
if (!name) {
|
1271
|
+
// iife
|
1272
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1273
|
+
|
1274
|
+
// presume
|
1275
|
+
// todo: warn here?
|
1276
|
+
return TYPES.number;
|
1277
|
+
}
|
1278
|
+
|
1279
|
+
if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
|
1280
|
+
if (builtinFuncs[name + '$constructor'].typedReturns) {
|
1281
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1282
|
+
|
1283
|
+
// presume
|
1284
|
+
// todo: warn here?
|
1285
|
+
return TYPES.number;
|
1286
|
+
}
|
1287
|
+
|
1288
|
+
return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
|
1289
|
+
}
|
1290
|
+
|
1096
1291
|
const func = funcs.find(x => x.name === name);
|
1097
1292
|
|
1098
1293
|
if (func) {
|
@@ -1100,10 +1295,27 @@ const getNodeType = (scope, node) => {
|
|
1100
1295
|
if (func.returnType) return func.returnType;
|
1101
1296
|
}
|
1102
1297
|
|
1103
|
-
if (builtinFuncs[name]) return
|
1298
|
+
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
1104
1299
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1105
1300
|
|
1106
|
-
|
1301
|
+
// check if this is a prototype function
|
1302
|
+
// if so and there is only one impl (eg charCodeAt)
|
1303
|
+
// use that return type as that is the only possibility
|
1304
|
+
// (if non-matching type it would error out)
|
1305
|
+
if (name.startsWith('__')) {
|
1306
|
+
const spl = name.slice(2).split('_');
|
1307
|
+
|
1308
|
+
const func = spl[spl.length - 1];
|
1309
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
|
1310
|
+
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1311
|
+
}
|
1312
|
+
|
1313
|
+
if (name.startsWith('__Porffor_wasm_')) {
|
1314
|
+
// todo: return undefined for non-returning ops
|
1315
|
+
return TYPES.number;
|
1316
|
+
}
|
1317
|
+
|
1318
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1107
1319
|
|
1108
1320
|
// presume
|
1109
1321
|
// todo: warn here?
|
@@ -1146,11 +1358,20 @@ const getNodeType = (scope, node) => {
|
|
1146
1358
|
}
|
1147
1359
|
|
1148
1360
|
if (node.type === 'ArrayExpression') {
|
1149
|
-
return TYPES.
|
1361
|
+
return TYPES.array;
|
1150
1362
|
}
|
1151
1363
|
|
1152
1364
|
if (node.type === 'BinaryExpression') {
|
1153
1365
|
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
1366
|
+
if (node.operator !== '+') return TYPES.number;
|
1367
|
+
|
1368
|
+
const knownLeft = knownType(scope, getNodeType(scope, node.left));
|
1369
|
+
const knownRight = knownType(scope, getNodeType(scope, node.right));
|
1370
|
+
|
1371
|
+
// todo: this should be dynamic but for now only static
|
1372
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
|
1373
|
+
if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) return TYPES.bytestring;
|
1374
|
+
|
1154
1375
|
return TYPES.number;
|
1155
1376
|
|
1156
1377
|
// todo: string concat types
|
@@ -1175,20 +1396,41 @@ const getNodeType = (scope, node) => {
|
|
1175
1396
|
if (node.operator === '!') return TYPES.boolean;
|
1176
1397
|
if (node.operator === 'void') return TYPES.undefined;
|
1177
1398
|
if (node.operator === 'delete') return TYPES.boolean;
|
1178
|
-
if (node.operator === 'typeof') return TYPES.string;
|
1399
|
+
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES.bytestring : TYPES.string;
|
1179
1400
|
|
1180
1401
|
return TYPES.number;
|
1181
1402
|
}
|
1182
1403
|
|
1183
1404
|
if (node.type === 'MemberExpression') {
|
1405
|
+
// hack: if something.name, string type
|
1406
|
+
if (node.property.name === 'name') {
|
1407
|
+
if (hasFuncWithName(node.object.name)) {
|
1408
|
+
return TYPES.bytestring;
|
1409
|
+
} else {
|
1410
|
+
return TYPES.undefined;
|
1411
|
+
}
|
1412
|
+
}
|
1413
|
+
|
1184
1414
|
// hack: if something.length, number type
|
1185
1415
|
if (node.property.name === 'length') return TYPES.number;
|
1186
1416
|
|
1187
|
-
//
|
1417
|
+
// ts hack
|
1418
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
|
1419
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.bytestring) return TYPES.bytestring;
|
1420
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.array) return TYPES.number;
|
1421
|
+
|
1422
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1423
|
+
|
1424
|
+
// presume
|
1188
1425
|
return TYPES.number;
|
1189
1426
|
}
|
1190
1427
|
|
1191
|
-
if (
|
1428
|
+
if (node.type === 'TaggedTemplateExpression') {
|
1429
|
+
// hack
|
1430
|
+
if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
|
1431
|
+
}
|
1432
|
+
|
1433
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1192
1434
|
|
1193
1435
|
// presume
|
1194
1436
|
// todo: warn here?
|
@@ -1204,8 +1446,8 @@ const getNodeType = (scope, node) => {
|
|
1204
1446
|
const generateLiteral = (scope, decl, global, name) => {
|
1205
1447
|
if (decl.value === null) return number(NULL);
|
1206
1448
|
|
1449
|
+
// hack: just return 1 for regex literals
|
1207
1450
|
if (decl.regex) {
|
1208
|
-
scope.regex[name] = decl.regex;
|
1209
1451
|
return number(1);
|
1210
1452
|
}
|
1211
1453
|
|
@@ -1218,19 +1460,10 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1218
1460
|
return number(decl.value ? 1 : 0);
|
1219
1461
|
|
1220
1462
|
case 'string':
|
1221
|
-
|
1222
|
-
const rawElements = new Array(str.length);
|
1223
|
-
let j = 0;
|
1224
|
-
for (let i = 0; i < str.length; i++) {
|
1225
|
-
rawElements[i] = str.charCodeAt(i);
|
1226
|
-
}
|
1227
|
-
|
1228
|
-
return makeArray(scope, {
|
1229
|
-
rawElements
|
1230
|
-
}, global, name, false, 'i16')[0];
|
1463
|
+
return makeString(scope, decl.value, global, name);
|
1231
1464
|
|
1232
1465
|
default:
|
1233
|
-
return todo(`cannot generate literal of type ${typeof decl.value}
|
1466
|
+
return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
|
1234
1467
|
}
|
1235
1468
|
};
|
1236
1469
|
|
@@ -1239,6 +1472,8 @@ const countLeftover = wasm => {
|
|
1239
1472
|
|
1240
1473
|
for (let i = 0; i < wasm.length; i++) {
|
1241
1474
|
const inst = wasm[i];
|
1475
|
+
if (inst[0] == null) continue;
|
1476
|
+
|
1242
1477
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
1243
1478
|
if (inst[0] === Opcodes.if) count--;
|
1244
1479
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1247,18 +1482,25 @@ const countLeftover = wasm => {
|
|
1247
1482
|
if (inst[0] === Opcodes.end) depth--;
|
1248
1483
|
|
1249
1484
|
if (depth === 0)
|
1250
|
-
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1251
|
-
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
1252
|
-
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1253
|
-
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
|
1485
|
+
if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1486
|
+
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)) {}
|
1487
|
+
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const, Opcodes.memory_size].includes(inst[0])) count++;
|
1488
|
+
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1254
1489
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1255
1490
|
else if (inst[0] === Opcodes.return) count = 0;
|
1256
1491
|
else if (inst[0] === Opcodes.call) {
|
1257
1492
|
let func = funcs.find(x => x.index === inst[1]);
|
1258
|
-
if (
|
1259
|
-
count
|
1260
|
-
} else
|
1261
|
-
|
1493
|
+
if (inst[1] === -1) {
|
1494
|
+
// todo: count for calling self
|
1495
|
+
} else if (!func && inst[1] < importedFuncs.length) {
|
1496
|
+
count -= importedFuncs[inst[1]].params;
|
1497
|
+
count += importedFuncs[inst[1]].returns;
|
1498
|
+
} else {
|
1499
|
+
if (func) {
|
1500
|
+
count -= func.params.length;
|
1501
|
+
} else count--;
|
1502
|
+
if (func) count += func.returns.length;
|
1503
|
+
}
|
1262
1504
|
} else count--;
|
1263
1505
|
|
1264
1506
|
// console.log(count, decompile([ inst ]).slice(0, -1));
|
@@ -1276,7 +1518,7 @@ const disposeLeftover = wasm => {
|
|
1276
1518
|
const generateExp = (scope, decl) => {
|
1277
1519
|
const expression = decl.expression;
|
1278
1520
|
|
1279
|
-
const out = generate(scope, expression);
|
1521
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1280
1522
|
disposeLeftover(out);
|
1281
1523
|
|
1282
1524
|
return out;
|
@@ -1334,7 +1576,7 @@ const RTArrayUtil = {
|
|
1334
1576
|
]
|
1335
1577
|
};
|
1336
1578
|
|
1337
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1579
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1338
1580
|
/* const callee = decl.callee;
|
1339
1581
|
const args = decl.arguments;
|
1340
1582
|
|
@@ -1350,10 +1592,21 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1350
1592
|
name = func.name;
|
1351
1593
|
}
|
1352
1594
|
|
1353
|
-
if (name === 'eval' && decl.arguments[0]
|
1595
|
+
if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
|
1354
1596
|
// literal eval hack
|
1355
|
-
const code = decl.arguments[0]
|
1356
|
-
|
1597
|
+
const code = decl.arguments[0]?.value ?? '';
|
1598
|
+
|
1599
|
+
let parsed;
|
1600
|
+
try {
|
1601
|
+
parsed = parse(code, []);
|
1602
|
+
} catch (e) {
|
1603
|
+
if (e.name === 'SyntaxError') {
|
1604
|
+
// throw syntax errors of evals at runtime instead
|
1605
|
+
return internalThrow(scope, 'SyntaxError', e.message, true);
|
1606
|
+
}
|
1607
|
+
|
1608
|
+
throw e;
|
1609
|
+
}
|
1357
1610
|
|
1358
1611
|
const out = generate(scope, {
|
1359
1612
|
type: 'BlockStatement',
|
@@ -1367,13 +1620,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1367
1620
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1368
1621
|
out.push(
|
1369
1622
|
...getNodeType(scope, finalStatement),
|
1370
|
-
|
1623
|
+
...setLastType(scope)
|
1371
1624
|
);
|
1372
1625
|
} else if (countLeftover(out) === 0) {
|
1373
1626
|
out.push(...number(UNDEFINED));
|
1374
1627
|
out.push(
|
1375
1628
|
...number(TYPES.undefined, Valtype.i32),
|
1376
|
-
|
1629
|
+
...setLastType(scope)
|
1377
1630
|
);
|
1378
1631
|
}
|
1379
1632
|
|
@@ -1391,39 +1644,47 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1391
1644
|
if (name && name.startsWith('__')) {
|
1392
1645
|
const spl = name.slice(2).split('_');
|
1393
1646
|
|
1394
|
-
|
1395
|
-
protoName = func;
|
1647
|
+
protoName = spl[spl.length - 1];
|
1396
1648
|
|
1397
1649
|
target = { ...decl.callee };
|
1398
1650
|
target.name = spl.slice(0, -1).join('_');
|
1651
|
+
|
1652
|
+
// failed to lookup name, abort
|
1653
|
+
if (!lookupName(scope, target.name)[0]) protoName = null;
|
1399
1654
|
}
|
1400
1655
|
|
1401
1656
|
// literal.func()
|
1402
1657
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1403
1658
|
// megahack for /regex/.func()
|
1404
|
-
|
1405
|
-
|
1406
|
-
const
|
1659
|
+
const funcName = decl.callee.property.name;
|
1660
|
+
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1661
|
+
const regex = decl.callee.object.regex.pattern;
|
1662
|
+
const rhemynName = `regex_${funcName}_${regex}`;
|
1663
|
+
|
1664
|
+
if (!funcIndex[rhemynName]) {
|
1665
|
+
const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
|
1407
1666
|
|
1408
|
-
|
1409
|
-
|
1667
|
+
funcIndex[func.name] = func.index;
|
1668
|
+
funcs.push(func);
|
1669
|
+
}
|
1410
1670
|
|
1671
|
+
const idx = funcIndex[rhemynName];
|
1411
1672
|
return [
|
1412
1673
|
// make string arg
|
1413
1674
|
...generate(scope, decl.arguments[0]),
|
1675
|
+
Opcodes.i32_to_u,
|
1676
|
+
...getNodeType(scope, decl.arguments[0]),
|
1414
1677
|
|
1415
1678
|
// call regex func
|
1416
|
-
Opcodes.
|
1417
|
-
[ Opcodes.call, func.index ],
|
1679
|
+
[ Opcodes.call, idx ],
|
1418
1680
|
Opcodes.i32_from_u,
|
1419
1681
|
|
1420
1682
|
...number(TYPES.boolean, Valtype.i32),
|
1421
|
-
|
1683
|
+
...setLastType(scope)
|
1422
1684
|
];
|
1423
1685
|
}
|
1424
1686
|
|
1425
|
-
|
1426
|
-
protoName = func;
|
1687
|
+
protoName = decl.callee.property.name;
|
1427
1688
|
|
1428
1689
|
target = decl.callee.object;
|
1429
1690
|
}
|
@@ -1444,23 +1705,48 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1444
1705
|
// }
|
1445
1706
|
|
1446
1707
|
if (protoName) {
|
1708
|
+
const protoBC = {};
|
1709
|
+
|
1710
|
+
const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
|
1711
|
+
|
1712
|
+
if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
|
1713
|
+
for (const x of builtinProtoCands) {
|
1714
|
+
const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
|
1715
|
+
if (type == null) continue;
|
1716
|
+
|
1717
|
+
protoBC[type] = generateCall(scope, {
|
1718
|
+
callee: {
|
1719
|
+
type: 'Identifier',
|
1720
|
+
name: x
|
1721
|
+
},
|
1722
|
+
arguments: [ target, ...decl.arguments ],
|
1723
|
+
_protoInternalCall: true
|
1724
|
+
});
|
1725
|
+
}
|
1726
|
+
}
|
1727
|
+
|
1447
1728
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1448
|
-
|
1449
|
-
if (f) acc[x] = f;
|
1729
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1450
1730
|
return acc;
|
1451
1731
|
}, {});
|
1452
1732
|
|
1453
|
-
// no prototype function candidates, ignore
|
1454
1733
|
if (Object.keys(protoCands).length > 0) {
|
1455
1734
|
// use local for cached i32 length as commonly used
|
1456
1735
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1457
1736
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1458
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1459
1737
|
|
1460
1738
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1461
1739
|
|
1740
|
+
const rawPointer = [
|
1741
|
+
...generate(scope, target),
|
1742
|
+
Opcodes.i32_to_u
|
1743
|
+
];
|
1744
|
+
|
1745
|
+
const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
|
1746
|
+
const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
|
1747
|
+
|
1748
|
+
let allOptUnused = true;
|
1462
1749
|
let lengthI32CacheUsed = false;
|
1463
|
-
const protoBC = {};
|
1464
1750
|
for (const x in protoCands) {
|
1465
1751
|
const protoFunc = protoCands[x];
|
1466
1752
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
@@ -1468,7 +1754,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1468
1754
|
...RTArrayUtil.getLength(getPointer),
|
1469
1755
|
|
1470
1756
|
...number(TYPES.number, Valtype.i32),
|
1471
|
-
|
1757
|
+
...setLastType(scope)
|
1472
1758
|
];
|
1473
1759
|
continue;
|
1474
1760
|
}
|
@@ -1478,6 +1764,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1478
1764
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1479
1765
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1480
1766
|
|
1767
|
+
let optUnused = false;
|
1481
1768
|
const protoOut = protoFunc(getPointer, {
|
1482
1769
|
getCachedI32: () => {
|
1483
1770
|
lengthI32CacheUsed = true;
|
@@ -1492,23 +1779,30 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1492
1779
|
return makeArray(scope, {
|
1493
1780
|
rawElements: new Array(length)
|
1494
1781
|
}, _global, _name, true, itemType);
|
1782
|
+
}, () => {
|
1783
|
+
optUnused = true;
|
1784
|
+
return unusedValue;
|
1495
1785
|
});
|
1496
1786
|
|
1787
|
+
if (!optUnused) allOptUnused = false;
|
1788
|
+
|
1497
1789
|
protoBC[x] = [
|
1498
|
-
[ Opcodes.block, valtypeBinary ],
|
1790
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1499
1791
|
...protoOut,
|
1500
1792
|
|
1501
1793
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1502
|
-
|
1794
|
+
...setLastType(scope),
|
1503
1795
|
[ Opcodes.end ]
|
1504
1796
|
];
|
1505
1797
|
}
|
1506
1798
|
|
1507
|
-
|
1508
|
-
...generate(scope, target),
|
1799
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1509
1800
|
|
1510
|
-
|
1511
|
-
|
1801
|
+
return [
|
1802
|
+
...(usePointerCache ? [
|
1803
|
+
...rawPointer,
|
1804
|
+
[ Opcodes.local_set, pointerLocal ],
|
1805
|
+
] : []),
|
1512
1806
|
|
1513
1807
|
...(!lengthI32CacheUsed ? [] : [
|
1514
1808
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1520,13 +1814,22 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1520
1814
|
|
1521
1815
|
// TODO: error better
|
1522
1816
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1523
|
-
}, valtypeBinary),
|
1817
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1524
1818
|
];
|
1525
1819
|
}
|
1820
|
+
|
1821
|
+
if (Object.keys(protoBC).length > 0) {
|
1822
|
+
return typeSwitch(scope, getNodeType(scope, target), {
|
1823
|
+
...protoBC,
|
1824
|
+
|
1825
|
+
// TODO: error better
|
1826
|
+
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1827
|
+
}, valtypeBinary);
|
1828
|
+
}
|
1526
1829
|
}
|
1527
1830
|
|
1528
1831
|
// TODO: only allows callee as literal
|
1529
|
-
if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
|
1832
|
+
if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
|
1530
1833
|
|
1531
1834
|
let idx = funcIndex[name] ?? importedFuncs[name];
|
1532
1835
|
if (idx === undefined && builtinFuncs[name]) {
|
@@ -1536,20 +1839,20 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1536
1839
|
idx = funcIndex[name];
|
1537
1840
|
|
1538
1841
|
// infer arguments types from builtins params
|
1539
|
-
const func = funcs.find(x => x.name === name);
|
1540
|
-
for (let i = 0; i < decl.arguments.length; i++) {
|
1541
|
-
|
1542
|
-
|
1543
|
-
|
1544
|
-
|
1545
|
-
|
1546
|
-
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1552
|
-
}
|
1842
|
+
// const func = funcs.find(x => x.name === name);
|
1843
|
+
// for (let i = 0; i < decl.arguments.length; i++) {
|
1844
|
+
// const arg = decl.arguments[i];
|
1845
|
+
// if (!arg.name) continue;
|
1846
|
+
|
1847
|
+
// const local = scope.locals[arg.name];
|
1848
|
+
// if (!local) continue;
|
1849
|
+
|
1850
|
+
// local.type = func.params[i];
|
1851
|
+
// if (local.type === Valtype.v128) {
|
1852
|
+
// // specify vec subtype inferred from last vec type in function name
|
1853
|
+
// local.vecType = name.split('_').reverse().find(x => x.includes('x'));
|
1854
|
+
// }
|
1855
|
+
// }
|
1553
1856
|
}
|
1554
1857
|
|
1555
1858
|
if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
@@ -1559,15 +1862,63 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1559
1862
|
idx = -1;
|
1560
1863
|
}
|
1561
1864
|
|
1865
|
+
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1866
|
+
const wasmOps = {
|
1867
|
+
// pointer, align, offset
|
1868
|
+
i32_load: { imms: 2, args: [ true ], returns: 1 },
|
1869
|
+
// pointer, value, align, offset
|
1870
|
+
i32_store: { imms: 2, args: [ true, true ], returns: 0 },
|
1871
|
+
// pointer, align, offset
|
1872
|
+
i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
|
1873
|
+
// pointer, value, align, offset
|
1874
|
+
i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
|
1875
|
+
// pointer, align, offset
|
1876
|
+
i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
|
1877
|
+
// pointer, value, align, offset
|
1878
|
+
i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
|
1879
|
+
|
1880
|
+
// pointer, align, offset
|
1881
|
+
f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
|
1882
|
+
// pointer, value, align, offset
|
1883
|
+
f64_store: { imms: 2, args: [ true, false ], returns: 0 },
|
1884
|
+
|
1885
|
+
// value
|
1886
|
+
i32_const: { imms: 1, args: [], returns: 1 },
|
1887
|
+
};
|
1888
|
+
|
1889
|
+
const opName = name.slice('__Porffor_wasm_'.length);
|
1890
|
+
|
1891
|
+
if (wasmOps[opName]) {
|
1892
|
+
const op = wasmOps[opName];
|
1893
|
+
|
1894
|
+
const argOut = [];
|
1895
|
+
for (let i = 0; i < op.args.length; i++) argOut.push(
|
1896
|
+
...generate(scope, decl.arguments[i]),
|
1897
|
+
...(op.args[i] ? [ Opcodes.i32_to ] : [])
|
1898
|
+
);
|
1899
|
+
|
1900
|
+
// literals only
|
1901
|
+
const imms = decl.arguments.slice(op.args.length).map(x => x.value);
|
1902
|
+
|
1903
|
+
return [
|
1904
|
+
...argOut,
|
1905
|
+
[ Opcodes[opName], ...imms ],
|
1906
|
+
...(new Array(op.returns).fill(Opcodes.i32_from))
|
1907
|
+
];
|
1908
|
+
}
|
1909
|
+
}
|
1910
|
+
|
1562
1911
|
if (idx === undefined) {
|
1563
|
-
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function
|
1564
|
-
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined
|
1912
|
+
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1913
|
+
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
1565
1914
|
}
|
1566
1915
|
|
1567
1916
|
const func = funcs.find(x => x.index === idx);
|
1568
1917
|
|
1569
1918
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1570
|
-
const
|
1919
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1920
|
+
const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
|
1921
|
+
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1571
1922
|
|
1572
1923
|
let args = decl.arguments;
|
1573
1924
|
if (func && args.length < paramCount) {
|
@@ -1583,14 +1934,24 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1583
1934
|
if (func && func.throws) scope.throws = true;
|
1584
1935
|
|
1585
1936
|
let out = [];
|
1586
|
-
for (
|
1937
|
+
for (let i = 0; i < args.length; i++) {
|
1938
|
+
const arg = args[i];
|
1587
1939
|
out = out.concat(generate(scope, arg));
|
1588
|
-
|
1940
|
+
|
1941
|
+
if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1942
|
+
out.push(Opcodes.i32_to);
|
1943
|
+
}
|
1944
|
+
|
1945
|
+
if (importedFuncs[name] && name.startsWith('profile')) {
|
1946
|
+
out.push(Opcodes.i32_to);
|
1947
|
+
}
|
1948
|
+
|
1949
|
+
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1589
1950
|
}
|
1590
1951
|
|
1591
1952
|
out.push([ Opcodes.call, idx ]);
|
1592
1953
|
|
1593
|
-
if (!
|
1954
|
+
if (!typedReturns) {
|
1594
1955
|
// let type;
|
1595
1956
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1596
1957
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1600,7 +1961,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1600
1961
|
// ...number(type, Valtype.i32),
|
1601
1962
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1602
1963
|
// );
|
1603
|
-
} else out.push(
|
1964
|
+
} else out.push(...setLastType(scope));
|
1965
|
+
|
1966
|
+
if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1967
|
+
out.push(Opcodes.i32_from);
|
1968
|
+
}
|
1604
1969
|
|
1605
1970
|
return out;
|
1606
1971
|
};
|
@@ -1608,8 +1973,21 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1608
1973
|
const generateNew = (scope, decl, _global, _name) => {
|
1609
1974
|
// hack: basically treat this as a normal call for builtins for now
|
1610
1975
|
const name = mapName(decl.callee.name);
|
1976
|
+
|
1611
1977
|
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1612
|
-
|
1978
|
+
|
1979
|
+
if (builtinFuncs[name + '$constructor']) {
|
1980
|
+
// custom ...$constructor override builtin func
|
1981
|
+
return generateCall(scope, {
|
1982
|
+
...decl,
|
1983
|
+
callee: {
|
1984
|
+
type: 'Identifier',
|
1985
|
+
name: name + '$constructor'
|
1986
|
+
}
|
1987
|
+
}, _global, _name);
|
1988
|
+
}
|
1989
|
+
|
1990
|
+
if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
|
1613
1991
|
|
1614
1992
|
return generateCall(scope, decl, _global, _name);
|
1615
1993
|
};
|
@@ -1625,41 +2003,148 @@ const unhackName = name => {
|
|
1625
2003
|
return name;
|
1626
2004
|
};
|
1627
2005
|
|
1628
|
-
const
|
1629
|
-
|
2006
|
+
const knownType = (scope, type) => {
|
2007
|
+
if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
|
2008
|
+
return type[0][1];
|
2009
|
+
}
|
1630
2010
|
|
1631
|
-
|
1632
|
-
|
1633
|
-
[ Opcodes.local_set, tmp ],
|
1634
|
-
[ Opcodes.block, returns ]
|
1635
|
-
];
|
2011
|
+
if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
|
2012
|
+
const idx = type[0][1];
|
1636
2013
|
|
1637
|
-
|
2014
|
+
// type idx = var idx + 1
|
2015
|
+
const v = Object.values(scope.locals).find(x => x.idx === idx - 1);
|
2016
|
+
if (v.metadata?.type != null) return v.metadata.type;
|
2017
|
+
}
|
1638
2018
|
|
1639
|
-
|
1640
|
-
|
2019
|
+
return null;
|
2020
|
+
};
|
1641
2021
|
|
1642
|
-
|
1643
|
-
|
1644
|
-
|
1645
|
-
|
2022
|
+
const brTable = (input, bc, returns) => {
|
2023
|
+
const out = [];
|
2024
|
+
const keys = Object.keys(bc);
|
2025
|
+
const count = keys.length;
|
1646
2026
|
|
1647
|
-
|
1648
|
-
|
1649
|
-
|
1650
|
-
|
2027
|
+
if (count === 1) {
|
2028
|
+
// return [
|
2029
|
+
// ...input,
|
2030
|
+
// ...bc[keys[0]]
|
2031
|
+
// ];
|
2032
|
+
return bc[keys[0]];
|
1651
2033
|
}
|
1652
2034
|
|
1653
|
-
|
1654
|
-
|
1655
|
-
|
2035
|
+
if (count === 2) {
|
2036
|
+
// just use if else
|
2037
|
+
const other = keys.find(x => x !== 'default');
|
2038
|
+
return [
|
2039
|
+
...input,
|
2040
|
+
...number(other, Valtype.i32),
|
2041
|
+
[ Opcodes.i32_eq ],
|
2042
|
+
[ Opcodes.if, returns ],
|
2043
|
+
...bc[other],
|
2044
|
+
[ Opcodes.else ],
|
2045
|
+
...bc.default,
|
2046
|
+
[ Opcodes.end ]
|
2047
|
+
];
|
2048
|
+
}
|
1656
2049
|
|
1657
|
-
|
2050
|
+
for (let i = 0; i < count; i++) {
|
2051
|
+
if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
|
2052
|
+
else out.push([ Opcodes.block, Blocktype.void ]);
|
2053
|
+
}
|
1658
2054
|
|
1659
|
-
|
2055
|
+
const nums = keys.filter(x => +x);
|
2056
|
+
const offset = Math.min(...nums);
|
2057
|
+
const max = Math.max(...nums);
|
2058
|
+
|
2059
|
+
const table = [];
|
2060
|
+
let br = 1;
|
2061
|
+
|
2062
|
+
for (let i = offset; i <= max; i++) {
|
2063
|
+
// if branch for this num, go to that block
|
2064
|
+
if (bc[i]) {
|
2065
|
+
table.push(br);
|
2066
|
+
br++;
|
2067
|
+
continue;
|
2068
|
+
}
|
2069
|
+
|
2070
|
+
// else default
|
2071
|
+
table.push(0);
|
2072
|
+
}
|
2073
|
+
|
2074
|
+
out.push(
|
2075
|
+
[ Opcodes.block, Blocktype.void ],
|
2076
|
+
...input,
|
2077
|
+
...(offset > 0 ? [
|
2078
|
+
...number(offset, Valtype.i32),
|
2079
|
+
[ Opcodes.i32_sub ]
|
2080
|
+
] : []),
|
2081
|
+
[ Opcodes.br_table, ...encodeVector(table), 0 ]
|
2082
|
+
);
|
2083
|
+
|
2084
|
+
// if you can guess why we sort the wrong way and then reverse
|
2085
|
+
// (instead of just sorting the correct way)
|
2086
|
+
// dm me and if you are correct and the first person
|
2087
|
+
// I will somehow shout you out or something
|
2088
|
+
const orderedBc = keys.sort((a, b) => b - a).reverse();
|
2089
|
+
|
2090
|
+
br = count - 1;
|
2091
|
+
for (const x of orderedBc) {
|
2092
|
+
out.push(
|
2093
|
+
[ Opcodes.end ],
|
2094
|
+
...bc[x],
|
2095
|
+
...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
|
2096
|
+
);
|
2097
|
+
br--;
|
2098
|
+
}
|
2099
|
+
|
2100
|
+
return [
|
2101
|
+
...out,
|
2102
|
+
[ Opcodes.end, 'br table end' ]
|
2103
|
+
];
|
1660
2104
|
};
|
1661
2105
|
|
1662
|
-
const
|
2106
|
+
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
2107
|
+
if (!Prefs.bytestring) delete bc[TYPES.bytestring];
|
2108
|
+
|
2109
|
+
const known = knownType(scope, type);
|
2110
|
+
if (known != null) {
|
2111
|
+
return bc[known] ?? bc.default;
|
2112
|
+
}
|
2113
|
+
|
2114
|
+
if (Prefs.typeswitchUseBrtable)
|
2115
|
+
return brTable(type, bc, returns);
|
2116
|
+
|
2117
|
+
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
2118
|
+
const out = [
|
2119
|
+
...type,
|
2120
|
+
[ Opcodes.local_set, tmp ],
|
2121
|
+
[ Opcodes.block, returns ]
|
2122
|
+
];
|
2123
|
+
|
2124
|
+
for (const x in bc) {
|
2125
|
+
if (x === 'default') continue;
|
2126
|
+
|
2127
|
+
// if type == x
|
2128
|
+
out.push([ Opcodes.local_get, tmp ]);
|
2129
|
+
out.push(...number(x, Valtype.i32));
|
2130
|
+
out.push([ Opcodes.i32_eq ]);
|
2131
|
+
|
2132
|
+
out.push([ Opcodes.if, Blocktype.void, `TYPESWITCH|${TYPE_NAMES[x]}` ]);
|
2133
|
+
out.push(...bc[x]);
|
2134
|
+
out.push([ Opcodes.br, 1 ]);
|
2135
|
+
out.push([ Opcodes.end ]);
|
2136
|
+
}
|
2137
|
+
|
2138
|
+
// default
|
2139
|
+
if (bc.default) out.push(...bc.default);
|
2140
|
+
else if (returns !== Blocktype.void) out.push(...number(0, returns));
|
2141
|
+
|
2142
|
+
out.push([ Opcodes.end, 'TYPESWITCH_end' ]);
|
2143
|
+
|
2144
|
+
return out;
|
2145
|
+
};
|
2146
|
+
|
2147
|
+
const allocVar = (scope, name, global = false, type = true) => {
|
1663
2148
|
const target = global ? globals : scope.locals;
|
1664
2149
|
|
1665
2150
|
// already declared
|
@@ -1673,12 +2158,62 @@ const allocVar = (scope, name, global = false) => {
|
|
1673
2158
|
let idx = global ? globalInd++ : scope.localInd++;
|
1674
2159
|
target[name] = { idx, type: valtypeBinary };
|
1675
2160
|
|
1676
|
-
|
1677
|
-
|
2161
|
+
if (type) {
|
2162
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
2163
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
2164
|
+
}
|
1678
2165
|
|
1679
2166
|
return idx;
|
1680
2167
|
};
|
1681
2168
|
|
2169
|
+
const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
2170
|
+
const target = global ? globals : scope.locals;
|
2171
|
+
|
2172
|
+
target[name].metadata ??= {};
|
2173
|
+
for (const x in metadata) {
|
2174
|
+
if (metadata[x] != null) target[name].metadata[x] = metadata[x];
|
2175
|
+
}
|
2176
|
+
};
|
2177
|
+
|
2178
|
+
const typeAnnoToPorfType = x => {
|
2179
|
+
if (!x) return null;
|
2180
|
+
if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
|
2181
|
+
if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
|
2182
|
+
|
2183
|
+
switch (x) {
|
2184
|
+
case 'i32':
|
2185
|
+
case 'i64':
|
2186
|
+
case 'f64':
|
2187
|
+
return TYPES.number;
|
2188
|
+
}
|
2189
|
+
|
2190
|
+
return null;
|
2191
|
+
};
|
2192
|
+
|
2193
|
+
const extractTypeAnnotation = decl => {
|
2194
|
+
let a = decl;
|
2195
|
+
while (a.typeAnnotation) a = a.typeAnnotation;
|
2196
|
+
|
2197
|
+
let type = null, elementType = null;
|
2198
|
+
if (a.typeName) {
|
2199
|
+
type = a.typeName.name;
|
2200
|
+
} else if (a.type.endsWith('Keyword')) {
|
2201
|
+
type = a.type.slice(2, -7).toLowerCase();
|
2202
|
+
} else if (a.type === 'TSArrayType') {
|
2203
|
+
type = 'array';
|
2204
|
+
elementType = extractTypeAnnotation(a.elementType).type;
|
2205
|
+
}
|
2206
|
+
|
2207
|
+
const typeName = type;
|
2208
|
+
type = typeAnnoToPorfType(type);
|
2209
|
+
|
2210
|
+
if (type === TYPES.bytestring && !Prefs.bytestring) type = TYPES.string;
|
2211
|
+
|
2212
|
+
// if (decl.name) console.log(decl.name, { type, elementType });
|
2213
|
+
|
2214
|
+
return { type, typeName, elementType };
|
2215
|
+
};
|
2216
|
+
|
1682
2217
|
const generateVar = (scope, decl) => {
|
1683
2218
|
let out = [];
|
1684
2219
|
|
@@ -1686,10 +2221,13 @@ const generateVar = (scope, decl) => {
|
|
1686
2221
|
|
1687
2222
|
// global variable if in top scope (main) and var ..., or if wanted
|
1688
2223
|
const global = topLevel || decl._bare; // decl.kind === 'var';
|
2224
|
+
const target = global ? globals : scope.locals;
|
1689
2225
|
|
1690
2226
|
for (const x of decl.declarations) {
|
1691
2227
|
const name = mapName(x.id.name);
|
1692
2228
|
|
2229
|
+
if (!name) return todo(scope, 'destructuring is not supported yet');
|
2230
|
+
|
1693
2231
|
if (x.init && isFuncType(x.init.type)) {
|
1694
2232
|
// hack for let a = function () { ... }
|
1695
2233
|
x.init.id = { name };
|
@@ -1705,11 +2243,29 @@ const generateVar = (scope, decl) => {
|
|
1705
2243
|
continue; // always ignore
|
1706
2244
|
}
|
1707
2245
|
|
1708
|
-
|
1709
|
-
|
1710
|
-
|
2246
|
+
// // generate init before allocating var
|
2247
|
+
// let generated;
|
2248
|
+
// if (x.init) generated = generate(scope, x.init, global, name);
|
2249
|
+
|
2250
|
+
const typed = typedInput && x.id.typeAnnotation;
|
2251
|
+
let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
|
1711
2252
|
|
1712
|
-
|
2253
|
+
if (typed) {
|
2254
|
+
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
2255
|
+
}
|
2256
|
+
|
2257
|
+
if (x.init) {
|
2258
|
+
const generated = generate(scope, x.init, global, name);
|
2259
|
+
if (scope.arrays?.get(name) != null) {
|
2260
|
+
// hack to set local as pointer before
|
2261
|
+
out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2262
|
+
if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
|
2263
|
+
generated.pop();
|
2264
|
+
out = out.concat(generated);
|
2265
|
+
} else {
|
2266
|
+
out = out.concat(generated);
|
2267
|
+
out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2268
|
+
}
|
1713
2269
|
out.push(...setType(scope, name, getNodeType(scope, x.init)));
|
1714
2270
|
}
|
1715
2271
|
|
@@ -1720,7 +2276,8 @@ const generateVar = (scope, decl) => {
|
|
1720
2276
|
return out;
|
1721
2277
|
};
|
1722
2278
|
|
1723
|
-
|
2279
|
+
// todo: optimize this func for valueUnused
|
2280
|
+
const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
1724
2281
|
const { type, name } = decl.left;
|
1725
2282
|
|
1726
2283
|
if (type === 'ObjectPattern') {
|
@@ -1735,22 +2292,30 @@ const generateAssign = (scope, decl) => {
|
|
1735
2292
|
return [];
|
1736
2293
|
}
|
1737
2294
|
|
2295
|
+
const op = decl.operator.slice(0, -1) || '=';
|
2296
|
+
|
1738
2297
|
// hack: .length setter
|
1739
2298
|
if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
|
1740
2299
|
const name = decl.left.object.name;
|
1741
|
-
const pointer = arrays
|
2300
|
+
const pointer = scope.arrays?.get(name);
|
1742
2301
|
|
1743
|
-
const aotPointer = pointer != null;
|
2302
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1744
2303
|
|
1745
2304
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
2305
|
+
const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1746
2306
|
|
1747
2307
|
return [
|
1748
2308
|
...(aotPointer ? number(0, Valtype.i32) : [
|
1749
2309
|
...generate(scope, decl.left.object),
|
1750
2310
|
Opcodes.i32_to_u
|
1751
2311
|
]),
|
2312
|
+
...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
1752
2313
|
|
1753
|
-
...generate(scope, decl.right),
|
2314
|
+
...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
|
2315
|
+
[ Opcodes.local_get, pointerTmp ],
|
2316
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
2317
|
+
Opcodes.i32_from_u
|
2318
|
+
], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
|
1754
2319
|
[ Opcodes.local_tee, newValueTmp ],
|
1755
2320
|
|
1756
2321
|
Opcodes.i32_to_u,
|
@@ -1760,21 +2325,19 @@ const generateAssign = (scope, decl) => {
|
|
1760
2325
|
];
|
1761
2326
|
}
|
1762
2327
|
|
1763
|
-
const op = decl.operator.slice(0, -1) || '=';
|
1764
|
-
|
1765
2328
|
// arr[i]
|
1766
2329
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
1767
2330
|
const name = decl.left.object.name;
|
1768
|
-
const pointer = arrays
|
2331
|
+
const pointer = scope.arrays?.get(name);
|
1769
2332
|
|
1770
|
-
const aotPointer = pointer != null;
|
2333
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1771
2334
|
|
1772
2335
|
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
1773
2336
|
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1774
2337
|
|
1775
2338
|
return [
|
1776
2339
|
...typeSwitch(scope, getNodeType(scope, decl.left.object), {
|
1777
|
-
[TYPES.
|
2340
|
+
[TYPES.array]: [
|
1778
2341
|
...(aotPointer ? [] : [
|
1779
2342
|
...generate(scope, decl.left.object),
|
1780
2343
|
Opcodes.i32_to_u
|
@@ -1823,6 +2386,8 @@ const generateAssign = (scope, decl) => {
|
|
1823
2386
|
];
|
1824
2387
|
}
|
1825
2388
|
|
2389
|
+
if (!name) return todo(scope, 'destructuring is not supported yet', true);
|
2390
|
+
|
1826
2391
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1827
2392
|
|
1828
2393
|
if (local === undefined) {
|
@@ -1869,9 +2434,7 @@ const generateAssign = (scope, decl) => {
|
|
1869
2434
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
1870
2435
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1871
2436
|
|
1872
|
-
|
1873
|
-
// hack: type is idx+1
|
1874
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2437
|
+
...setType(scope, name, getLastType(scope))
|
1875
2438
|
];
|
1876
2439
|
}
|
1877
2440
|
|
@@ -1882,9 +2445,7 @@ const generateAssign = (scope, decl) => {
|
|
1882
2445
|
|
1883
2446
|
// todo: string concat types
|
1884
2447
|
|
1885
|
-
|
1886
|
-
...number(TYPES.number, Valtype.i32),
|
1887
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2448
|
+
...setType(scope, name, TYPES.number)
|
1888
2449
|
];
|
1889
2450
|
};
|
1890
2451
|
|
@@ -1930,7 +2491,7 @@ const generateUnary = (scope, decl) => {
|
|
1930
2491
|
return out;
|
1931
2492
|
}
|
1932
2493
|
|
1933
|
-
case 'delete':
|
2494
|
+
case 'delete': {
|
1934
2495
|
let toReturn = true, toGenerate = true;
|
1935
2496
|
|
1936
2497
|
if (decl.argument.type === 'Identifier') {
|
@@ -1952,38 +2513,60 @@ const generateUnary = (scope, decl) => {
|
|
1952
2513
|
|
1953
2514
|
out.push(...number(toReturn ? 1 : 0));
|
1954
2515
|
return out;
|
2516
|
+
}
|
1955
2517
|
|
1956
|
-
case 'typeof':
|
1957
|
-
|
2518
|
+
case 'typeof': {
|
2519
|
+
let overrideType, toGenerate = true;
|
2520
|
+
|
2521
|
+
if (decl.argument.type === 'Identifier') {
|
2522
|
+
const out = generateIdent(scope, decl.argument);
|
2523
|
+
|
2524
|
+
// if ReferenceError (undeclared var), ignore and return undefined
|
2525
|
+
if (out[1]) {
|
2526
|
+
// does not exist (2 ops from throw)
|
2527
|
+
overrideType = number(TYPES.undefined, Valtype.i32);
|
2528
|
+
toGenerate = false;
|
2529
|
+
}
|
2530
|
+
}
|
2531
|
+
|
2532
|
+
const out = toGenerate ? generate(scope, decl.argument) : [];
|
2533
|
+
disposeLeftover(out);
|
2534
|
+
|
2535
|
+
out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
|
1958
2536
|
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
1959
2537
|
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
1960
2538
|
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
1961
2539
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
1962
2540
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
1963
2541
|
|
2542
|
+
[TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2543
|
+
|
1964
2544
|
// object and internal types
|
1965
2545
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
1966
|
-
});
|
2546
|
+
}));
|
2547
|
+
|
2548
|
+
return out;
|
2549
|
+
}
|
1967
2550
|
|
1968
2551
|
default:
|
1969
|
-
return todo(`unary operator ${decl.operator} not implemented yet
|
2552
|
+
return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
|
1970
2553
|
}
|
1971
2554
|
};
|
1972
2555
|
|
1973
|
-
const generateUpdate = (scope, decl) => {
|
2556
|
+
const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
|
1974
2557
|
const { name } = decl.argument;
|
1975
2558
|
|
1976
2559
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1977
2560
|
|
1978
2561
|
if (local === undefined) {
|
1979
|
-
return todo(`update expression with undefined variable
|
2562
|
+
return todo(scope, `update expression with undefined variable`, true);
|
1980
2563
|
}
|
1981
2564
|
|
1982
2565
|
const idx = local.idx;
|
1983
2566
|
const out = [];
|
1984
2567
|
|
1985
2568
|
out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
1986
|
-
if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2569
|
+
if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
1987
2570
|
|
1988
2571
|
switch (decl.operator) {
|
1989
2572
|
case '++':
|
@@ -1996,7 +2579,7 @@ const generateUpdate = (scope, decl) => {
|
|
1996
2579
|
}
|
1997
2580
|
|
1998
2581
|
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
1999
|
-
if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2582
|
+
if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2000
2583
|
|
2001
2584
|
return out;
|
2002
2585
|
};
|
@@ -2036,7 +2619,7 @@ const generateConditional = (scope, decl) => {
|
|
2036
2619
|
// note type
|
2037
2620
|
out.push(
|
2038
2621
|
...getNodeType(scope, decl.consequent),
|
2039
|
-
|
2622
|
+
...setLastType(scope)
|
2040
2623
|
);
|
2041
2624
|
|
2042
2625
|
out.push([ Opcodes.else ]);
|
@@ -2045,7 +2628,7 @@ const generateConditional = (scope, decl) => {
|
|
2045
2628
|
// note type
|
2046
2629
|
out.push(
|
2047
2630
|
...getNodeType(scope, decl.alternate),
|
2048
|
-
|
2631
|
+
...setLastType(scope)
|
2049
2632
|
);
|
2050
2633
|
|
2051
2634
|
out.push([ Opcodes.end ]);
|
@@ -2059,15 +2642,17 @@ const generateFor = (scope, decl) => {
|
|
2059
2642
|
const out = [];
|
2060
2643
|
|
2061
2644
|
if (decl.init) {
|
2062
|
-
out.push(...generate(scope, decl.init));
|
2645
|
+
out.push(...generate(scope, decl.init, false, undefined, true));
|
2063
2646
|
disposeLeftover(out);
|
2064
2647
|
}
|
2065
2648
|
|
2066
2649
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2067
2650
|
depth.push('for');
|
2068
2651
|
|
2069
|
-
out.push(...generate(scope, decl.test));
|
2070
|
-
|
2652
|
+
if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2653
|
+
else out.push(...number(1, Valtype.i32));
|
2654
|
+
|
2655
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
2071
2656
|
depth.push('if');
|
2072
2657
|
|
2073
2658
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2075,8 +2660,7 @@ const generateFor = (scope, decl) => {
|
|
2075
2660
|
out.push(...generate(scope, decl.body));
|
2076
2661
|
out.push([ Opcodes.end ]);
|
2077
2662
|
|
2078
|
-
out.push(...generate(scope, decl.update));
|
2079
|
-
depth.pop();
|
2663
|
+
if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
|
2080
2664
|
|
2081
2665
|
out.push([ Opcodes.br, 1 ]);
|
2082
2666
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2104,6 +2688,36 @@ const generateWhile = (scope, decl) => {
|
|
2104
2688
|
return out;
|
2105
2689
|
};
|
2106
2690
|
|
2691
|
+
const generateDoWhile = (scope, decl) => {
|
2692
|
+
const out = [];
|
2693
|
+
|
2694
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
2695
|
+
depth.push('dowhile');
|
2696
|
+
|
2697
|
+
// block for break (includes all)
|
2698
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2699
|
+
depth.push('block');
|
2700
|
+
|
2701
|
+
// block for continue
|
2702
|
+
// includes body but not test+loop so we can exit body at anytime
|
2703
|
+
// and still test+loop after
|
2704
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2705
|
+
depth.push('block');
|
2706
|
+
|
2707
|
+
out.push(...generate(scope, decl.body));
|
2708
|
+
|
2709
|
+
out.push([ Opcodes.end ]);
|
2710
|
+
depth.pop();
|
2711
|
+
|
2712
|
+
out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2713
|
+
out.push([ Opcodes.br_if, 1 ]);
|
2714
|
+
|
2715
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
2716
|
+
depth.pop(); depth.pop();
|
2717
|
+
|
2718
|
+
return out;
|
2719
|
+
};
|
2720
|
+
|
2107
2721
|
const generateForOf = (scope, decl) => {
|
2108
2722
|
const out = [];
|
2109
2723
|
|
@@ -2133,8 +2747,17 @@ const generateForOf = (scope, decl) => {
|
|
2133
2747
|
// setup local for left
|
2134
2748
|
generate(scope, decl.left);
|
2135
2749
|
|
2136
|
-
|
2750
|
+
let leftName = decl.left.declarations?.[0]?.id?.name;
|
2751
|
+
if (!leftName && decl.left.name) {
|
2752
|
+
leftName = decl.left.name;
|
2753
|
+
|
2754
|
+
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2755
|
+
}
|
2756
|
+
|
2757
|
+
// if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
|
2758
|
+
|
2137
2759
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2760
|
+
if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
|
2138
2761
|
|
2139
2762
|
depth.push('block');
|
2140
2763
|
depth.push('block');
|
@@ -2142,15 +2765,17 @@ const generateForOf = (scope, decl) => {
|
|
2142
2765
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2143
2766
|
// hack: this is naughty and will break things!
|
2144
2767
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2145
|
-
if (pages.
|
2768
|
+
if (pages.hasAnyString) {
|
2769
|
+
// todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
|
2146
2770
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2147
2771
|
rawElements: new Array(1)
|
2148
2772
|
}, isGlobal, leftName, true, 'i16');
|
2149
2773
|
}
|
2150
2774
|
|
2151
2775
|
// set type for local
|
2776
|
+
// todo: optimize away counter and use end pointer
|
2152
2777
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2153
|
-
[TYPES.
|
2778
|
+
[TYPES.array]: [
|
2154
2779
|
...setType(scope, leftName, TYPES.number),
|
2155
2780
|
|
2156
2781
|
[ Opcodes.loop, Blocktype.void ],
|
@@ -2233,6 +2858,56 @@ const generateForOf = (scope, decl) => {
|
|
2233
2858
|
[ Opcodes.end ],
|
2234
2859
|
[ Opcodes.end ]
|
2235
2860
|
],
|
2861
|
+
[TYPES.bytestring]: [
|
2862
|
+
...setType(scope, leftName, TYPES.bytestring),
|
2863
|
+
|
2864
|
+
[ Opcodes.loop, Blocktype.void ],
|
2865
|
+
|
2866
|
+
// setup new/out array
|
2867
|
+
...newOut,
|
2868
|
+
[ Opcodes.drop ],
|
2869
|
+
|
2870
|
+
...number(0, Valtype.i32), // base 0 for store after
|
2871
|
+
|
2872
|
+
// load current string ind {arg}
|
2873
|
+
[ Opcodes.local_get, pointer ],
|
2874
|
+
[ Opcodes.local_get, counter ],
|
2875
|
+
[ Opcodes.i32_add ],
|
2876
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
2877
|
+
|
2878
|
+
// store to new string ind 0
|
2879
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2880
|
+
|
2881
|
+
// return new string (page)
|
2882
|
+
...number(newPointer),
|
2883
|
+
|
2884
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2885
|
+
|
2886
|
+
[ Opcodes.block, Blocktype.void ],
|
2887
|
+
[ Opcodes.block, Blocktype.void ],
|
2888
|
+
...generate(scope, decl.body),
|
2889
|
+
[ Opcodes.end ],
|
2890
|
+
|
2891
|
+
// increment iter pointer
|
2892
|
+
// [ Opcodes.local_get, pointer ],
|
2893
|
+
// ...number(1, Valtype.i32),
|
2894
|
+
// [ Opcodes.i32_add ],
|
2895
|
+
// [ Opcodes.local_set, pointer ],
|
2896
|
+
|
2897
|
+
// increment counter by 1
|
2898
|
+
[ Opcodes.local_get, counter ],
|
2899
|
+
...number(1, Valtype.i32),
|
2900
|
+
[ Opcodes.i32_add ],
|
2901
|
+
[ Opcodes.local_tee, counter ],
|
2902
|
+
|
2903
|
+
// loop if counter != length
|
2904
|
+
[ Opcodes.local_get, length ],
|
2905
|
+
[ Opcodes.i32_ne ],
|
2906
|
+
[ Opcodes.br_if, 1 ],
|
2907
|
+
|
2908
|
+
[ Opcodes.end ],
|
2909
|
+
[ Opcodes.end ]
|
2910
|
+
],
|
2236
2911
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2237
2912
|
}, Blocktype.void));
|
2238
2913
|
|
@@ -2243,28 +2918,65 @@ const generateForOf = (scope, decl) => {
|
|
2243
2918
|
return out;
|
2244
2919
|
};
|
2245
2920
|
|
2921
|
+
// find the nearest loop in depth map by type
|
2246
2922
|
const getNearestLoop = () => {
|
2247
2923
|
for (let i = depth.length - 1; i >= 0; i--) {
|
2248
|
-
if (
|
2924
|
+
if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
|
2249
2925
|
}
|
2250
2926
|
|
2251
2927
|
return -1;
|
2252
2928
|
};
|
2253
2929
|
|
2254
2930
|
const generateBreak = (scope, decl) => {
|
2255
|
-
const
|
2931
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2932
|
+
const type = depth[target];
|
2933
|
+
|
2934
|
+
// different loop types have different branch offsets
|
2935
|
+
// as they have different wasm block/loop/if structures
|
2936
|
+
// we need to use the right offset by type to branch to the one we want
|
2937
|
+
// for a break: exit the loop without executing anything else inside it
|
2938
|
+
const offset = ({
|
2939
|
+
for: 2, // loop > if (wanted branch) > block (we are here)
|
2940
|
+
while: 2, // loop > if (wanted branch) (we are here)
|
2941
|
+
dowhile: 2, // loop > block (wanted branch) > block (we are here)
|
2942
|
+
forof: 2, // loop > block (wanted branch) > block (we are here)
|
2943
|
+
if: 1 // break inside if, branch 0 to skip the rest of the if
|
2944
|
+
})[type];
|
2945
|
+
|
2256
2946
|
return [
|
2257
|
-
[ Opcodes.br, ...signedLEB128(
|
2947
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2258
2948
|
];
|
2259
2949
|
};
|
2260
2950
|
|
2261
2951
|
const generateContinue = (scope, decl) => {
|
2262
|
-
const
|
2952
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2953
|
+
const type = depth[target];
|
2954
|
+
|
2955
|
+
// different loop types have different branch offsets
|
2956
|
+
// as they have different wasm block/loop/if structures
|
2957
|
+
// we need to use the right offset by type to branch to the one we want
|
2958
|
+
// for a continue: do test for the loop, and then loop depending on that success
|
2959
|
+
const offset = ({
|
2960
|
+
for: 3, // loop (wanted branch) > if > block (we are here)
|
2961
|
+
while: 1, // loop (wanted branch) > if (we are here)
|
2962
|
+
dowhile: 3, // loop > block > block (wanted branch) (we are here)
|
2963
|
+
forof: 3 // loop > block > block (wanted branch) (we are here)
|
2964
|
+
})[type];
|
2965
|
+
|
2263
2966
|
return [
|
2264
|
-
[ Opcodes.br, ...signedLEB128(
|
2967
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2265
2968
|
];
|
2266
2969
|
};
|
2267
2970
|
|
2971
|
+
const generateLabel = (scope, decl) => {
|
2972
|
+
scope.labels ??= new Map();
|
2973
|
+
|
2974
|
+
const name = decl.label.name;
|
2975
|
+
scope.labels.set(name, depth.length);
|
2976
|
+
|
2977
|
+
return generate(scope, decl.body);
|
2978
|
+
};
|
2979
|
+
|
2268
2980
|
const generateThrow = (scope, decl) => {
|
2269
2981
|
scope.throws = true;
|
2270
2982
|
|
@@ -2273,7 +2985,7 @@ const generateThrow = (scope, decl) => {
|
|
2273
2985
|
// hack: throw new X("...") -> throw "..."
|
2274
2986
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2275
2987
|
constructor = decl.argument.callee.name;
|
2276
|
-
message = decl.argument.arguments[0]
|
2988
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2277
2989
|
}
|
2278
2990
|
|
2279
2991
|
if (tags.length === 0) tags.push({
|
@@ -2285,6 +2997,9 @@ const generateThrow = (scope, decl) => {
|
|
2285
2997
|
let exceptId = exceptions.push({ constructor, message }) - 1;
|
2286
2998
|
let tagIdx = tags[0].idx;
|
2287
2999
|
|
3000
|
+
scope.exceptions ??= [];
|
3001
|
+
scope.exceptions.push(exceptId);
|
3002
|
+
|
2288
3003
|
// todo: write a description of how this works lol
|
2289
3004
|
|
2290
3005
|
return [
|
@@ -2294,7 +3009,7 @@ const generateThrow = (scope, decl) => {
|
|
2294
3009
|
};
|
2295
3010
|
|
2296
3011
|
const generateTry = (scope, decl) => {
|
2297
|
-
if (decl.finalizer) return todo('try finally not implemented yet');
|
3012
|
+
if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
|
2298
3013
|
|
2299
3014
|
const out = [];
|
2300
3015
|
|
@@ -2325,29 +3040,35 @@ const generateAssignPat = (scope, decl) => {
|
|
2325
3040
|
// TODO
|
2326
3041
|
// if identifier declared, use that
|
2327
3042
|
// else, use default (right)
|
2328
|
-
return todo('assignment pattern (optional arg)');
|
3043
|
+
return todo(scope, 'assignment pattern (optional arg)');
|
2329
3044
|
};
|
2330
3045
|
|
2331
3046
|
let pages = new Map();
|
2332
|
-
const allocPage = (reason, type) => {
|
3047
|
+
const allocPage = (scope, reason, type) => {
|
2333
3048
|
if (pages.has(reason)) return pages.get(reason).ind;
|
2334
3049
|
|
2335
3050
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2336
3051
|
if (reason.startsWith('string:')) pages.hasString = true;
|
3052
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
3053
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2337
3054
|
|
2338
3055
|
const ind = pages.size;
|
2339
3056
|
pages.set(reason, { ind, type });
|
2340
3057
|
|
2341
|
-
|
3058
|
+
scope.pages ??= new Map();
|
3059
|
+
scope.pages.set(reason, { ind, type });
|
3060
|
+
|
3061
|
+
if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2342
3062
|
|
2343
3063
|
return ind;
|
2344
3064
|
};
|
2345
3065
|
|
3066
|
+
// todo: add scope.pages
|
2346
3067
|
const freePage = reason => {
|
2347
3068
|
const { ind } = pages.get(reason);
|
2348
3069
|
pages.delete(reason);
|
2349
3070
|
|
2350
|
-
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
3071
|
+
if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2351
3072
|
|
2352
3073
|
return ind;
|
2353
3074
|
};
|
@@ -2367,38 +3088,53 @@ const StoreOps = {
|
|
2367
3088
|
f64: Opcodes.f64_store,
|
2368
3089
|
|
2369
3090
|
// expects i32 input!
|
2370
|
-
|
3091
|
+
i8: Opcodes.i32_store8,
|
3092
|
+
i16: Opcodes.i32_store16,
|
2371
3093
|
};
|
2372
3094
|
|
2373
3095
|
let data = [];
|
2374
3096
|
|
2375
|
-
const compileBytes = (val, itemType
|
3097
|
+
const compileBytes = (val, itemType) => {
|
2376
3098
|
// todo: this is a mess and needs confirming / ????
|
2377
3099
|
switch (itemType) {
|
2378
3100
|
case 'i8': return [ val % 256 ];
|
2379
|
-
case 'i16': return [ val % 256,
|
2380
|
-
|
2381
|
-
case 'i32':
|
2382
|
-
|
2383
|
-
return enforceFourBytes(signedLEB128(val));
|
3101
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3102
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3103
|
+
case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
|
3104
|
+
// todo: i64
|
2384
3105
|
|
2385
3106
|
case 'f64': return ieee754_binary64(val);
|
2386
3107
|
}
|
2387
3108
|
};
|
2388
3109
|
|
3110
|
+
const getAllocType = itemType => {
|
3111
|
+
switch (itemType) {
|
3112
|
+
case 'i8': return 'bytestring';
|
3113
|
+
case 'i16': return 'string';
|
3114
|
+
|
3115
|
+
default: return 'array';
|
3116
|
+
}
|
3117
|
+
};
|
3118
|
+
|
2389
3119
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2390
3120
|
const out = [];
|
2391
3121
|
|
3122
|
+
scope.arrays ??= new Map();
|
3123
|
+
|
2392
3124
|
let firstAssign = false;
|
2393
|
-
if (!arrays.has(name) || name === '$undeclared') {
|
3125
|
+
if (!scope.arrays.has(name) || name === '$undeclared') {
|
2394
3126
|
firstAssign = true;
|
2395
3127
|
|
2396
3128
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2397
3129
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2398
|
-
|
3130
|
+
|
3131
|
+
if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
|
3132
|
+
else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2399
3133
|
}
|
2400
3134
|
|
2401
|
-
const pointer = arrays.get(name);
|
3135
|
+
const pointer = scope.arrays.get(name);
|
3136
|
+
|
3137
|
+
const local = global ? globals[name] : scope.locals[name];
|
2402
3138
|
|
2403
3139
|
const useRawElements = !!decl.rawElements;
|
2404
3140
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
@@ -2406,19 +3142,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2406
3142
|
const valtype = itemTypeToValtype[itemType];
|
2407
3143
|
const length = elements.length;
|
2408
3144
|
|
2409
|
-
if (firstAssign && useRawElements) {
|
2410
|
-
|
3145
|
+
if (firstAssign && useRawElements && !Prefs.noData) {
|
3146
|
+
// if length is 0 memory/data will just be 0000... anyway
|
3147
|
+
if (length !== 0) {
|
3148
|
+
let bytes = compileBytes(length, 'i32');
|
2411
3149
|
|
2412
|
-
|
2413
|
-
|
3150
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
3151
|
+
if (elements[i] == null) continue;
|
2414
3152
|
|
2415
|
-
|
2416
|
-
|
3153
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
3154
|
+
}
|
2417
3155
|
|
2418
|
-
|
2419
|
-
|
2420
|
-
|
2421
|
-
|
3156
|
+
const ind = data.push({
|
3157
|
+
offset: pointer,
|
3158
|
+
bytes
|
3159
|
+
}) - 1;
|
3160
|
+
|
3161
|
+
scope.data ??= [];
|
3162
|
+
scope.data.push(ind);
|
3163
|
+
}
|
2422
3164
|
|
2423
3165
|
// local value as pointer
|
2424
3166
|
out.push(...number(pointer));
|
@@ -2426,11 +3168,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2426
3168
|
return [ out, pointer ];
|
2427
3169
|
}
|
2428
3170
|
|
3171
|
+
const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
|
3172
|
+
if (pointerTmp != null) {
|
3173
|
+
out.push(
|
3174
|
+
[ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
3175
|
+
Opcodes.i32_to_u,
|
3176
|
+
[ Opcodes.local_set, pointerTmp ]
|
3177
|
+
);
|
3178
|
+
}
|
3179
|
+
|
3180
|
+
const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
|
3181
|
+
|
2429
3182
|
// store length as 0th array
|
2430
3183
|
out.push(
|
2431
|
-
...
|
3184
|
+
...pointerWasm,
|
2432
3185
|
...number(length, Valtype.i32),
|
2433
|
-
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1,
|
3186
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
|
2434
3187
|
);
|
2435
3188
|
|
2436
3189
|
const storeOp = StoreOps[itemType];
|
@@ -2439,43 +3192,92 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2439
3192
|
if (elements[i] == null) continue;
|
2440
3193
|
|
2441
3194
|
out.push(
|
2442
|
-
...
|
3195
|
+
...pointerWasm,
|
2443
3196
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2444
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(
|
3197
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2445
3198
|
);
|
2446
3199
|
}
|
2447
3200
|
|
2448
3201
|
// local value as pointer
|
2449
|
-
out.push(...
|
3202
|
+
out.push(...pointerWasm, Opcodes.i32_from_u);
|
2450
3203
|
|
2451
3204
|
return [ out, pointer ];
|
2452
3205
|
};
|
2453
3206
|
|
2454
|
-
const
|
3207
|
+
const byteStringable = str => {
|
3208
|
+
if (!Prefs.bytestring) return false;
|
3209
|
+
|
3210
|
+
for (let i = 0; i < str.length; i++) {
|
3211
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
3212
|
+
}
|
3213
|
+
|
3214
|
+
return true;
|
3215
|
+
};
|
3216
|
+
|
3217
|
+
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2455
3218
|
const rawElements = new Array(str.length);
|
3219
|
+
let byteStringable = Prefs.bytestring;
|
2456
3220
|
for (let i = 0; i < str.length; i++) {
|
2457
|
-
|
3221
|
+
const c = str.charCodeAt(i);
|
3222
|
+
rawElements[i] = c;
|
3223
|
+
|
3224
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2458
3225
|
}
|
2459
3226
|
|
3227
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
3228
|
+
|
2460
3229
|
return makeArray(scope, {
|
2461
3230
|
rawElements
|
2462
|
-
}, global, name, false, 'i16')[0];
|
3231
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2463
3232
|
};
|
2464
3233
|
|
2465
|
-
let arrays = new Map();
|
2466
3234
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
2467
3235
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
2468
3236
|
};
|
2469
3237
|
|
2470
3238
|
export const generateMember = (scope, decl, _global, _name) => {
|
2471
3239
|
const name = decl.object.name;
|
2472
|
-
const pointer = arrays
|
3240
|
+
const pointer = scope.arrays?.get(name);
|
3241
|
+
|
3242
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
3243
|
+
|
3244
|
+
// hack: .name
|
3245
|
+
if (decl.property.name === 'name') {
|
3246
|
+
if (hasFuncWithName(name)) {
|
3247
|
+
let nameProp = name;
|
2473
3248
|
|
2474
|
-
|
3249
|
+
// eg: __String_prototype_toLowerCase -> toLowerCase
|
3250
|
+
if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
|
3251
|
+
|
3252
|
+
return makeString(scope, nameProp, _global, _name, true);
|
3253
|
+
} else {
|
3254
|
+
return generate(scope, DEFAULT_VALUE);
|
3255
|
+
}
|
3256
|
+
}
|
2475
3257
|
|
2476
3258
|
// hack: .length
|
2477
3259
|
if (decl.property.name === 'length') {
|
2478
|
-
|
3260
|
+
const func = funcs.find(x => x.name === name);
|
3261
|
+
if (func) {
|
3262
|
+
const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
|
3263
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
3264
|
+
return number(typedParams ? func.params.length / 2 : func.params.length);
|
3265
|
+
}
|
3266
|
+
|
3267
|
+
if (builtinFuncs[name + '$constructor']) {
|
3268
|
+
const regularFunc = builtinFuncs[name];
|
3269
|
+
const regularParams = regularFunc.typedParams ? (regularFunc.params.length / 2) : regularFunc.params.length;
|
3270
|
+
|
3271
|
+
const constructorFunc = builtinFuncs[name + '$constructor'];
|
3272
|
+
const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
|
3273
|
+
|
3274
|
+
return number(Math.max(regularParams, constructorParams));
|
3275
|
+
}
|
3276
|
+
|
3277
|
+
if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
|
3278
|
+
if (importedFuncs[name]) return number(importedFuncs[name].params);
|
3279
|
+
if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
|
3280
|
+
|
2479
3281
|
return [
|
2480
3282
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2481
3283
|
...generate(scope, decl.object),
|
@@ -2487,19 +3289,22 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2487
3289
|
];
|
2488
3290
|
}
|
2489
3291
|
|
3292
|
+
const object = generate(scope, decl.object);
|
3293
|
+
const property = generate(scope, decl.property);
|
3294
|
+
|
2490
3295
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2491
3296
|
// hack: this is naughty and will break things!
|
2492
|
-
let newOut = number(0,
|
2493
|
-
if (pages.
|
3297
|
+
let newOut = number(0, valtypeBinary), newPointer = -1;
|
3298
|
+
if (pages.hasAnyString) {
|
2494
3299
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2495
3300
|
rawElements: new Array(1)
|
2496
3301
|
}, _global, _name, true, 'i16');
|
2497
3302
|
}
|
2498
3303
|
|
2499
3304
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2500
|
-
[TYPES.
|
3305
|
+
[TYPES.array]: [
|
2501
3306
|
// get index as valtype
|
2502
|
-
...
|
3307
|
+
...property,
|
2503
3308
|
|
2504
3309
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2505
3310
|
Opcodes.i32_to_u,
|
@@ -2507,7 +3312,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2507
3312
|
[ Opcodes.i32_mul ],
|
2508
3313
|
|
2509
3314
|
...(aotPointer ? [] : [
|
2510
|
-
...
|
3315
|
+
...object,
|
2511
3316
|
Opcodes.i32_to_u,
|
2512
3317
|
[ Opcodes.i32_add ]
|
2513
3318
|
]),
|
@@ -2516,7 +3321,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2516
3321
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2517
3322
|
|
2518
3323
|
...number(TYPES.number, Valtype.i32),
|
2519
|
-
|
3324
|
+
...setLastType(scope)
|
2520
3325
|
],
|
2521
3326
|
|
2522
3327
|
[TYPES.string]: [
|
@@ -2526,14 +3331,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2526
3331
|
|
2527
3332
|
...number(0, Valtype.i32), // base 0 for store later
|
2528
3333
|
|
2529
|
-
...
|
2530
|
-
|
3334
|
+
...property,
|
2531
3335
|
Opcodes.i32_to_u,
|
3336
|
+
|
2532
3337
|
...number(ValtypeSize.i16, Valtype.i32),
|
2533
3338
|
[ Opcodes.i32_mul ],
|
2534
3339
|
|
2535
3340
|
...(aotPointer ? [] : [
|
2536
|
-
...
|
3341
|
+
...object,
|
2537
3342
|
Opcodes.i32_to_u,
|
2538
3343
|
[ Opcodes.i32_add ]
|
2539
3344
|
]),
|
@@ -2548,10 +3353,38 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2548
3353
|
...number(newPointer),
|
2549
3354
|
|
2550
3355
|
...number(TYPES.string, Valtype.i32),
|
2551
|
-
|
3356
|
+
...setLastType(scope)
|
2552
3357
|
],
|
3358
|
+
[TYPES.bytestring]: [
|
3359
|
+
// setup new/out array
|
3360
|
+
...newOut,
|
3361
|
+
[ Opcodes.drop ],
|
2553
3362
|
|
2554
|
-
|
3363
|
+
...number(0, Valtype.i32), // base 0 for store later
|
3364
|
+
|
3365
|
+
...property,
|
3366
|
+
Opcodes.i32_to_u,
|
3367
|
+
|
3368
|
+
...(aotPointer ? [] : [
|
3369
|
+
...object,
|
3370
|
+
Opcodes.i32_to_u,
|
3371
|
+
[ Opcodes.i32_add ]
|
3372
|
+
]),
|
3373
|
+
|
3374
|
+
// load current string ind {arg}
|
3375
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
3376
|
+
|
3377
|
+
// store to new string ind 0
|
3378
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
3379
|
+
|
3380
|
+
// return new string (page)
|
3381
|
+
...number(newPointer),
|
3382
|
+
|
3383
|
+
...number(TYPES.bytestring, Valtype.i32),
|
3384
|
+
...setLastType(scope)
|
3385
|
+
],
|
3386
|
+
|
3387
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
|
2555
3388
|
});
|
2556
3389
|
};
|
2557
3390
|
|
@@ -2561,25 +3394,36 @@ const objectHack = node => {
|
|
2561
3394
|
if (!node) return node;
|
2562
3395
|
|
2563
3396
|
if (node.type === 'MemberExpression') {
|
2564
|
-
|
3397
|
+
const out = (() => {
|
3398
|
+
if (node.computed || node.optional) return;
|
3399
|
+
|
3400
|
+
let objectName = node.object.name;
|
2565
3401
|
|
2566
|
-
|
3402
|
+
// if object is not identifier or another member exp, give up
|
3403
|
+
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
|
3404
|
+
if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
|
2567
3405
|
|
2568
|
-
|
2569
|
-
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
3406
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2570
3407
|
|
2571
|
-
|
3408
|
+
// if .name or .length, give up (hack within a hack!)
|
3409
|
+
if (['name', 'length'].includes(node.property.name)) {
|
3410
|
+
node.object = objectHack(node.object);
|
3411
|
+
return;
|
3412
|
+
}
|
2572
3413
|
|
2573
|
-
|
2574
|
-
|
3414
|
+
// no object name, give up
|
3415
|
+
if (!objectName) return;
|
2575
3416
|
|
2576
|
-
|
2577
|
-
|
3417
|
+
const name = '__' + objectName + '_' + node.property.name;
|
3418
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2578
3419
|
|
2579
|
-
|
2580
|
-
|
2581
|
-
|
2582
|
-
|
3420
|
+
return {
|
3421
|
+
type: 'Identifier',
|
3422
|
+
name
|
3423
|
+
};
|
3424
|
+
})();
|
3425
|
+
|
3426
|
+
if (out) return out;
|
2583
3427
|
}
|
2584
3428
|
|
2585
3429
|
for (const x in node) {
|
@@ -2593,11 +3437,11 @@ const objectHack = node => {
|
|
2593
3437
|
};
|
2594
3438
|
|
2595
3439
|
const generateFunc = (scope, decl) => {
|
2596
|
-
if (decl.async) return todo('async functions are not supported');
|
2597
|
-
if (decl.generator) return todo('generator functions are not supported');
|
3440
|
+
if (decl.async) return todo(scope, 'async functions are not supported');
|
3441
|
+
if (decl.generator) return todo(scope, 'generator functions are not supported');
|
2598
3442
|
|
2599
3443
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2600
|
-
const params = decl.params
|
3444
|
+
const params = decl.params ?? [];
|
2601
3445
|
|
2602
3446
|
// const innerScope = { ...scope };
|
2603
3447
|
// TODO: share scope/locals between !!!
|
@@ -2610,8 +3454,17 @@ const generateFunc = (scope, decl) => {
|
|
2610
3454
|
name
|
2611
3455
|
};
|
2612
3456
|
|
3457
|
+
if (typedInput && decl.returnType) {
|
3458
|
+
innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
|
3459
|
+
innerScope.returns = [ valtypeBinary ];
|
3460
|
+
}
|
3461
|
+
|
2613
3462
|
for (let i = 0; i < params.length; i++) {
|
2614
|
-
allocVar(innerScope, params[i], false);
|
3463
|
+
allocVar(innerScope, params[i].name, false);
|
3464
|
+
|
3465
|
+
if (typedInput && params[i].typeAnnotation) {
|
3466
|
+
addVarMetadata(innerScope, params[i].name, false, extractTypeAnnotation(params[i]));
|
3467
|
+
}
|
2615
3468
|
}
|
2616
3469
|
|
2617
3470
|
let body = objectHack(decl.body);
|
@@ -2627,13 +3480,13 @@ const generateFunc = (scope, decl) => {
|
|
2627
3480
|
const func = {
|
2628
3481
|
name,
|
2629
3482
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2630
|
-
|
2631
|
-
|
2632
|
-
throws: innerScope.throws,
|
2633
|
-
index: currentFuncIndex++
|
3483
|
+
index: currentFuncIndex++,
|
3484
|
+
...innerScope
|
2634
3485
|
};
|
2635
3486
|
funcIndex[name] = func.index;
|
2636
3487
|
|
3488
|
+
if (name === 'main') func.gotLastType = true;
|
3489
|
+
|
2637
3490
|
// quick hack fixes
|
2638
3491
|
for (const inst of wasm) {
|
2639
3492
|
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
@@ -2650,117 +3503,6 @@ const generateFunc = (scope, decl) => {
|
|
2650
3503
|
);
|
2651
3504
|
}
|
2652
3505
|
|
2653
|
-
// change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
|
2654
|
-
let offset = 0, vecParams = 0;
|
2655
|
-
for (let i = 0; i < params.length; i++) {
|
2656
|
-
const name = params[i];
|
2657
|
-
const local = func.locals[name];
|
2658
|
-
if (local.type === Valtype.v128) {
|
2659
|
-
vecParams++;
|
2660
|
-
|
2661
|
-
/* wasm.unshift( // add v128 load for param
|
2662
|
-
[ Opcodes.i32_const, 0 ],
|
2663
|
-
[ ...Opcodes.v128_load, 0, i * 16 ],
|
2664
|
-
[ Opcodes.local_set, local.idx ]
|
2665
|
-
); */
|
2666
|
-
|
2667
|
-
// using params and replace_lane is noticably faster than just loading from memory (above) somehow
|
2668
|
-
|
2669
|
-
// extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
|
2670
|
-
const { vecType } = local;
|
2671
|
-
let [ type, lanes ] = vecType.split('x');
|
2672
|
-
if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
|
2673
|
-
|
2674
|
-
lanes = parseInt(lanes);
|
2675
|
-
type = Valtype[type];
|
2676
|
-
|
2677
|
-
const name = params[i]; // get original param name
|
2678
|
-
|
2679
|
-
func.params.splice(offset, 1, ...new Array(lanes).fill(type)); // add new params of {type}, {lanes} times
|
2680
|
-
|
2681
|
-
// update index of original local
|
2682
|
-
// delete func.locals[name];
|
2683
|
-
|
2684
|
-
// add new locals for params
|
2685
|
-
for (let j = 0; j < lanes; j++) {
|
2686
|
-
func.locals[name + j] = { idx: offset + j, type, vecParamAutogen: true };
|
2687
|
-
}
|
2688
|
-
|
2689
|
-
// prepend wasm to generate expected v128 locals
|
2690
|
-
wasm.splice(i * 2 + offset * 2, 0,
|
2691
|
-
...i32x4(0, 0, 0, 0),
|
2692
|
-
...new Array(lanes).fill(0).flatMap((_, j) => [
|
2693
|
-
[ Opcodes.local_get, offset + j ],
|
2694
|
-
[ ...Opcodes[vecType + '_replace_lane'], j ]
|
2695
|
-
]),
|
2696
|
-
[ Opcodes.local_set, i ]
|
2697
|
-
);
|
2698
|
-
|
2699
|
-
offset += lanes;
|
2700
|
-
|
2701
|
-
// note: wrapping is disabled for now due to perf/dx concerns (so this will never run)
|
2702
|
-
/* if (!func.name.startsWith('#')) func.name = '##' + func.name;
|
2703
|
-
|
2704
|
-
// add vec type index to hash name prefix for wrapper to know how to wrap
|
2705
|
-
const vecTypeIdx = [ 'i8x16', 'i16x8', 'i32x4', 'i64x2', 'f32x4', 'f64x2' ].indexOf(local.vecType);
|
2706
|
-
const secondHash = func.name.slice(1).indexOf('#');
|
2707
|
-
func.name = '#' + func.name.slice(1, secondHash) + vecTypeIdx + func.name.slice(secondHash); */
|
2708
|
-
}
|
2709
|
-
}
|
2710
|
-
|
2711
|
-
if (offset !== 0) {
|
2712
|
-
// bump local indexes for all other locals after
|
2713
|
-
for (const x in func.locals) {
|
2714
|
-
const local = func.locals[x];
|
2715
|
-
if (!local.vecParamAutogen) local.idx += offset;
|
2716
|
-
}
|
2717
|
-
|
2718
|
-
// bump local indexes in wasm local.get/set
|
2719
|
-
for (let j = 0; j < wasm.length; j++) {
|
2720
|
-
const inst = wasm[j];
|
2721
|
-
if (j < offset * 2 + vecParams * 2) {
|
2722
|
-
if (inst[0] === Opcodes.local_set) inst[1] += offset;
|
2723
|
-
continue;
|
2724
|
-
}
|
2725
|
-
|
2726
|
-
if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set) inst[1] += offset;
|
2727
|
-
}
|
2728
|
-
}
|
2729
|
-
|
2730
|
-
// change v128 return into many <type> instead as unsupported return valtype
|
2731
|
-
const lastReturnLocal = wasm.length > 2 && wasm[wasm.length - 1][0] === Opcodes.return && Object.values(func.locals).find(x => x.idx === wasm[wasm.length - 2][1]);
|
2732
|
-
if (lastReturnLocal && lastReturnLocal.type === Valtype.v128) {
|
2733
|
-
const name = Object.keys(func.locals)[Object.values(func.locals).indexOf(lastReturnLocal)];
|
2734
|
-
// extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
|
2735
|
-
const { vecType } = lastReturnLocal;
|
2736
|
-
let [ type, lanes ] = vecType.split('x');
|
2737
|
-
if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
|
2738
|
-
|
2739
|
-
lanes = parseInt(lanes);
|
2740
|
-
type = Valtype[type];
|
2741
|
-
|
2742
|
-
const vecIdx = lastReturnLocal.idx;
|
2743
|
-
|
2744
|
-
const lastIdx = Math.max(0, ...Object.values(func.locals).map(x => x.idx));
|
2745
|
-
const tmpIdx = [];
|
2746
|
-
for (let i = 0; i < lanes; i++) {
|
2747
|
-
const idx = lastIdx + i + 1;
|
2748
|
-
tmpIdx.push(idx);
|
2749
|
-
func.locals[name + i] = { idx, type, vecReturnAutogen: true };
|
2750
|
-
}
|
2751
|
-
|
2752
|
-
wasm.splice(wasm.length - 1, 1,
|
2753
|
-
...new Array(lanes).fill(0).flatMap((_, i) => [
|
2754
|
-
i === 0 ? null : [ Opcodes.local_get, vecIdx ],
|
2755
|
-
[ ...Opcodes[vecType + '_extract_lane'], i ],
|
2756
|
-
[ Opcodes.local_set, tmpIdx[i] ],
|
2757
|
-
].filter(x => x !== null)),
|
2758
|
-
...new Array(lanes).fill(0).map((_, i) => [ Opcodes.local_get, tmpIdx[i]])
|
2759
|
-
);
|
2760
|
-
|
2761
|
-
func.returns = new Array(lanes).fill(type);
|
2762
|
-
}
|
2763
|
-
|
2764
3506
|
func.wasm = wasm;
|
2765
3507
|
|
2766
3508
|
funcs.push(func);
|
@@ -2796,7 +3538,7 @@ const internalConstrs = {
|
|
2796
3538
|
|
2797
3539
|
// todo: check in wasm instead of here
|
2798
3540
|
const literalValue = arg.value ?? 0;
|
2799
|
-
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
|
3541
|
+
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
|
2800
3542
|
|
2801
3543
|
return [
|
2802
3544
|
...number(0, Valtype.i32),
|
@@ -2807,7 +3549,8 @@ const internalConstrs = {
|
|
2807
3549
|
...number(pointer)
|
2808
3550
|
];
|
2809
3551
|
},
|
2810
|
-
type: TYPES.
|
3552
|
+
type: TYPES.array,
|
3553
|
+
length: 1
|
2811
3554
|
},
|
2812
3555
|
|
2813
3556
|
__Array_of: {
|
@@ -2818,27 +3561,134 @@ const internalConstrs = {
|
|
2818
3561
|
elements: decl.arguments
|
2819
3562
|
}, global, name);
|
2820
3563
|
},
|
2821
|
-
type: TYPES.
|
3564
|
+
type: TYPES.array,
|
3565
|
+
notConstr: true,
|
3566
|
+
length: 0
|
3567
|
+
},
|
3568
|
+
|
3569
|
+
__Porffor_fastOr: {
|
3570
|
+
generate: (scope, decl) => {
|
3571
|
+
const out = [];
|
3572
|
+
|
3573
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3574
|
+
out.push(
|
3575
|
+
...generate(scope, decl.arguments[i]),
|
3576
|
+
Opcodes.i32_to_u,
|
3577
|
+
...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
|
3578
|
+
);
|
3579
|
+
}
|
3580
|
+
|
3581
|
+
out.push(Opcodes.i32_from_u);
|
3582
|
+
|
3583
|
+
return out;
|
3584
|
+
},
|
3585
|
+
type: TYPES.boolean,
|
2822
3586
|
notConstr: true
|
2823
|
-
}
|
2824
|
-
};
|
3587
|
+
},
|
2825
3588
|
|
2826
|
-
|
2827
|
-
|
2828
|
-
|
2829
|
-
// for (const x of arr) {
|
2830
|
-
// if (x === undefined) {
|
2831
|
-
// console.trace(arr);
|
2832
|
-
// process.exit();
|
2833
|
-
// }
|
2834
|
-
// if (Array.isArray(x)) check(x);
|
2835
|
-
// }
|
2836
|
-
// };
|
2837
|
-
// if (Array.isArray(a) && !new Error().stack.includes('node:')) check(a);
|
2838
|
-
// // if (Array.isArray(a)) check(a);
|
3589
|
+
__Porffor_fastAnd: {
|
3590
|
+
generate: (scope, decl) => {
|
3591
|
+
const out = [];
|
2839
3592
|
|
2840
|
-
|
2841
|
-
|
3593
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3594
|
+
out.push(
|
3595
|
+
...generate(scope, decl.arguments[i]),
|
3596
|
+
Opcodes.i32_to_u,
|
3597
|
+
...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
|
3598
|
+
);
|
3599
|
+
}
|
3600
|
+
|
3601
|
+
out.push(Opcodes.i32_from_u);
|
3602
|
+
|
3603
|
+
return out;
|
3604
|
+
},
|
3605
|
+
type: TYPES.boolean,
|
3606
|
+
notConstr: true
|
3607
|
+
},
|
3608
|
+
|
3609
|
+
Boolean: {
|
3610
|
+
generate: (scope, decl) => {
|
3611
|
+
// todo: boolean object when used as constructor
|
3612
|
+
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
3613
|
+
return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
|
3614
|
+
},
|
3615
|
+
type: TYPES.boolean,
|
3616
|
+
length: 1
|
3617
|
+
},
|
3618
|
+
|
3619
|
+
__Math_max: {
|
3620
|
+
generate: (scope, decl) => {
|
3621
|
+
const out = [
|
3622
|
+
...number(-Infinity)
|
3623
|
+
];
|
3624
|
+
|
3625
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3626
|
+
out.push(
|
3627
|
+
...generate(scope, decl.arguments[i]),
|
3628
|
+
[ Opcodes.f64_max ]
|
3629
|
+
);
|
3630
|
+
}
|
3631
|
+
|
3632
|
+
return out;
|
3633
|
+
},
|
3634
|
+
type: TYPES.number,
|
3635
|
+
notConstr: true,
|
3636
|
+
length: 2
|
3637
|
+
},
|
3638
|
+
|
3639
|
+
__Math_min: {
|
3640
|
+
generate: (scope, decl) => {
|
3641
|
+
const out = [
|
3642
|
+
...number(Infinity)
|
3643
|
+
];
|
3644
|
+
|
3645
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3646
|
+
out.push(
|
3647
|
+
...generate(scope, decl.arguments[i]),
|
3648
|
+
[ Opcodes.f64_min ]
|
3649
|
+
);
|
3650
|
+
}
|
3651
|
+
|
3652
|
+
return out;
|
3653
|
+
},
|
3654
|
+
type: TYPES.number,
|
3655
|
+
notConstr: true,
|
3656
|
+
length: 2
|
3657
|
+
},
|
3658
|
+
|
3659
|
+
__console_log: {
|
3660
|
+
generate: (scope, decl) => {
|
3661
|
+
const out = [];
|
3662
|
+
|
3663
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3664
|
+
out.push(
|
3665
|
+
...generateCall(scope, {
|
3666
|
+
callee: {
|
3667
|
+
type: 'Identifier',
|
3668
|
+
name: '__Porffor_print'
|
3669
|
+
},
|
3670
|
+
arguments: [ decl.arguments[i] ]
|
3671
|
+
}),
|
3672
|
+
|
3673
|
+
// print space
|
3674
|
+
...number(32),
|
3675
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3676
|
+
);
|
3677
|
+
}
|
3678
|
+
|
3679
|
+
// print newline
|
3680
|
+
out.push(
|
3681
|
+
...number(10),
|
3682
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3683
|
+
);
|
3684
|
+
|
3685
|
+
return out;
|
3686
|
+
},
|
3687
|
+
type: TYPES.undefined,
|
3688
|
+
notConstr: true,
|
3689
|
+
length: 0
|
3690
|
+
}
|
3691
|
+
};
|
2842
3692
|
|
2843
3693
|
export default program => {
|
2844
3694
|
globals = {};
|
@@ -2848,20 +3698,23 @@ export default program => {
|
|
2848
3698
|
funcs = [];
|
2849
3699
|
funcIndex = {};
|
2850
3700
|
depth = [];
|
2851
|
-
arrays = new Map();
|
2852
3701
|
pages = new Map();
|
2853
3702
|
data = [];
|
2854
3703
|
currentFuncIndex = importedFuncs.length;
|
2855
3704
|
|
2856
3705
|
globalThis.valtype = 'f64';
|
2857
3706
|
|
2858
|
-
const valtypeOpt = process.argv.find(x => x.startsWith('
|
3707
|
+
const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
|
2859
3708
|
if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
|
2860
3709
|
|
2861
3710
|
globalThis.valtypeBinary = Valtype[valtype];
|
2862
3711
|
|
2863
3712
|
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
2864
3713
|
|
3714
|
+
globalThis.pageSize = PageSize;
|
3715
|
+
const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
|
3716
|
+
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3717
|
+
|
2865
3718
|
// set generic opcodes for current valtype
|
2866
3719
|
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
2867
3720
|
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
@@ -2870,10 +3723,10 @@ export default program => {
|
|
2870
3723
|
Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
|
2871
3724
|
Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
|
2872
3725
|
|
2873
|
-
Opcodes.i32_to = [ [
|
2874
|
-
Opcodes.i32_to_u = [ [
|
2875
|
-
Opcodes.i32_from = [ [
|
2876
|
-
Opcodes.i32_from_u = [ [
|
3726
|
+
Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
|
3727
|
+
Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
|
3728
|
+
Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
|
3729
|
+
Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
|
2877
3730
|
|
2878
3731
|
Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
|
2879
3732
|
Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
|
@@ -2886,10 +3739,6 @@ export default program => {
|
|
2886
3739
|
|
2887
3740
|
program.id = { name: 'main' };
|
2888
3741
|
|
2889
|
-
globalThis.pageSize = PageSize;
|
2890
|
-
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
2891
|
-
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
2892
|
-
|
2893
3742
|
const scope = {
|
2894
3743
|
locals: {},
|
2895
3744
|
localInd: 0
|
@@ -2900,7 +3749,7 @@ export default program => {
|
|
2900
3749
|
body: program.body
|
2901
3750
|
};
|
2902
3751
|
|
2903
|
-
if (
|
3752
|
+
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
2904
3753
|
|
2905
3754
|
generateFunc(scope, program);
|
2906
3755
|
|
@@ -2917,7 +3766,11 @@ export default program => {
|
|
2917
3766
|
}
|
2918
3767
|
|
2919
3768
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
2920
|
-
|
3769
|
+
if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
|
3770
|
+
main.wasm.splice(main.wasm.length - 1, 1);
|
3771
|
+
} else {
|
3772
|
+
main.returns = [];
|
3773
|
+
}
|
2921
3774
|
}
|
2922
3775
|
|
2923
3776
|
if (lastInst[0] === Opcodes.call) {
|