porffor 0.2.0-6fd3f05 → 0.2.0-75bc012
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 +322 -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 +1080 -0
- package/compiler/builtins/tostring.ts +25 -0
- package/compiler/builtins.js +580 -272
- package/compiler/{codeGen.js → codegen.js} +1413 -549
- 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 -69
- package/util/enum.js +0 -20
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
|
2
|
-
import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
|
2
|
+
import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from "./encoding.js";
|
3
3
|
import { operatorOpcode } from "./expression.js";
|
4
4
|
import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
|
5
5
|
import { PrototypeFuncs } from "./prototype.js";
|
@@ -7,6 +7,8 @@ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enfor
|
|
7
7
|
import { log } from "./log.js";
|
8
8
|
import parse from "./parse.js";
|
9
9
|
import * as Rhemyn from "../rhemyn/compile.js";
|
10
|
+
import Prefs from './prefs.js';
|
11
|
+
import { TYPES, TYPE_NAMES } from './types.js';
|
10
12
|
|
11
13
|
let globals = {};
|
12
14
|
let globalInd = 0;
|
@@ -23,39 +25,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,47 +1190,29 @@ const generateLogicExp = (scope, decl) => {
|
|
1001
1190
|
// 4: internal type
|
1002
1191
|
// 5: pointer
|
1003
1192
|
|
1004
|
-
const
|
1005
|
-
|
1006
|
-
|
1007
|
-
string: 0x02,
|
1008
|
-
undefined: 0x03,
|
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
|
-
|
1019
|
-
const TYPE_NAMES = {
|
1020
|
-
[TYPES.number]: 'Number',
|
1021
|
-
[TYPES.boolean]: 'Boolean',
|
1022
|
-
[TYPES.string]: 'String',
|
1023
|
-
[TYPES.undefined]: 'undefined',
|
1024
|
-
[TYPES.object]: 'Object',
|
1025
|
-
[TYPES.function]: 'Function',
|
1026
|
-
[TYPES.symbol]: 'Symbol',
|
1027
|
-
[TYPES.bigint]: 'BigInt',
|
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)];
|
1028
1196
|
|
1029
|
-
|
1030
|
-
[TYPES._regexp]: 'RegExp'
|
1197
|
+
return false;
|
1031
1198
|
};
|
1032
1199
|
|
1033
1200
|
const getType = (scope, _name) => {
|
1034
1201
|
const name = mapName(_name);
|
1035
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);
|
1036
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);
|
1037
1209
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1038
1210
|
|
1039
1211
|
let type = TYPES.undefined;
|
1040
|
-
if (builtinVars[name]) type =
|
1212
|
+
if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
|
1041
1213
|
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
|
1042
1214
|
|
1043
|
-
if (name
|
1044
|
-
name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
|
1215
|
+
if (isExistingProtoFunc(name)) type = TYPES.function;
|
1045
1216
|
|
1046
1217
|
return number(type, Valtype.i32);
|
1047
1218
|
};
|
@@ -1051,23 +1222,37 @@ const setType = (scope, _name, type) => {
|
|
1051
1222
|
|
1052
1223
|
const out = typeof type === 'number' ? number(type, Valtype.i32) : type;
|
1053
1224
|
|
1225
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return [];
|
1054
1226
|
if (scope.locals[name]) return [
|
1055
1227
|
...out,
|
1056
1228
|
[ Opcodes.local_set, scope.locals[name + '#type'].idx ]
|
1057
1229
|
];
|
1058
1230
|
|
1231
|
+
if (typedInput && globals[name]?.metadata?.type != null) return [];
|
1059
1232
|
if (globals[name]) return [
|
1060
1233
|
...out,
|
1061
1234
|
[ Opcodes.global_set, globals[name + '#type'].idx ]
|
1062
1235
|
];
|
1063
1236
|
|
1064
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) ] ];
|
1065
1248
|
};
|
1066
1249
|
|
1067
1250
|
const getNodeType = (scope, node) => {
|
1068
1251
|
const inner = () => {
|
1069
1252
|
if (node.type === 'Literal') {
|
1070
|
-
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;
|
1071
1256
|
|
1072
1257
|
return TYPES[typeof node.value];
|
1073
1258
|
}
|
@@ -1082,6 +1267,27 @@ const getNodeType = (scope, node) => {
|
|
1082
1267
|
|
1083
1268
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1084
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
|
+
|
1085
1291
|
const func = funcs.find(x => x.name === name);
|
1086
1292
|
|
1087
1293
|
if (func) {
|
@@ -1089,10 +1295,27 @@ const getNodeType = (scope, node) => {
|
|
1089
1295
|
if (func.returnType) return func.returnType;
|
1090
1296
|
}
|
1091
1297
|
|
1092
|
-
if (builtinFuncs[name]) return
|
1298
|
+
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
1093
1299
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1094
1300
|
|
1095
|
-
|
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);
|
1096
1319
|
|
1097
1320
|
// presume
|
1098
1321
|
// todo: warn here?
|
@@ -1135,11 +1358,20 @@ const getNodeType = (scope, node) => {
|
|
1135
1358
|
}
|
1136
1359
|
|
1137
1360
|
if (node.type === 'ArrayExpression') {
|
1138
|
-
return TYPES.
|
1361
|
+
return TYPES.array;
|
1139
1362
|
}
|
1140
1363
|
|
1141
1364
|
if (node.type === 'BinaryExpression') {
|
1142
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
|
+
|
1143
1375
|
return TYPES.number;
|
1144
1376
|
|
1145
1377
|
// todo: string concat types
|
@@ -1164,20 +1396,41 @@ const getNodeType = (scope, node) => {
|
|
1164
1396
|
if (node.operator === '!') return TYPES.boolean;
|
1165
1397
|
if (node.operator === 'void') return TYPES.undefined;
|
1166
1398
|
if (node.operator === 'delete') return TYPES.boolean;
|
1167
|
-
if (node.operator === 'typeof') return TYPES.string;
|
1399
|
+
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES.bytestring : TYPES.string;
|
1168
1400
|
|
1169
1401
|
return TYPES.number;
|
1170
1402
|
}
|
1171
1403
|
|
1172
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
|
+
|
1173
1414
|
// hack: if something.length, number type
|
1174
1415
|
if (node.property.name === 'length') return TYPES.number;
|
1175
1416
|
|
1176
|
-
//
|
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
|
1177
1425
|
return TYPES.number;
|
1178
1426
|
}
|
1179
1427
|
|
1180
|
-
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);
|
1181
1434
|
|
1182
1435
|
// presume
|
1183
1436
|
// todo: warn here?
|
@@ -1193,8 +1446,8 @@ const getNodeType = (scope, node) => {
|
|
1193
1446
|
const generateLiteral = (scope, decl, global, name) => {
|
1194
1447
|
if (decl.value === null) return number(NULL);
|
1195
1448
|
|
1449
|
+
// hack: just return 1 for regex literals
|
1196
1450
|
if (decl.regex) {
|
1197
|
-
scope.regex[name] = decl.regex;
|
1198
1451
|
return number(1);
|
1199
1452
|
}
|
1200
1453
|
|
@@ -1207,19 +1460,10 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1207
1460
|
return number(decl.value ? 1 : 0);
|
1208
1461
|
|
1209
1462
|
case 'string':
|
1210
|
-
|
1211
|
-
const rawElements = new Array(str.length);
|
1212
|
-
let j = 0;
|
1213
|
-
for (let i = 0; i < str.length; i++) {
|
1214
|
-
rawElements[i] = str.charCodeAt(i);
|
1215
|
-
}
|
1216
|
-
|
1217
|
-
return makeArray(scope, {
|
1218
|
-
rawElements
|
1219
|
-
}, global, name, false, 'i16')[0];
|
1463
|
+
return makeString(scope, decl.value, global, name);
|
1220
1464
|
|
1221
1465
|
default:
|
1222
|
-
return todo(`cannot generate literal of type ${typeof decl.value}
|
1466
|
+
return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
|
1223
1467
|
}
|
1224
1468
|
};
|
1225
1469
|
|
@@ -1228,6 +1472,8 @@ const countLeftover = wasm => {
|
|
1228
1472
|
|
1229
1473
|
for (let i = 0; i < wasm.length; i++) {
|
1230
1474
|
const inst = wasm[i];
|
1475
|
+
if (inst[0] == null) continue;
|
1476
|
+
|
1231
1477
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
1232
1478
|
if (inst[0] === Opcodes.if) count--;
|
1233
1479
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1236,18 +1482,25 @@ const countLeftover = wasm => {
|
|
1236
1482
|
if (inst[0] === Opcodes.end) depth--;
|
1237
1483
|
|
1238
1484
|
if (depth === 0)
|
1239
|
-
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1240
|
-
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
1241
|
-
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1242
|
-
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
|
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;
|
1243
1489
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1244
1490
|
else if (inst[0] === Opcodes.return) count = 0;
|
1245
1491
|
else if (inst[0] === Opcodes.call) {
|
1246
1492
|
let func = funcs.find(x => x.index === inst[1]);
|
1247
|
-
if (
|
1248
|
-
count
|
1249
|
-
} else
|
1250
|
-
|
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
|
+
}
|
1251
1504
|
} else count--;
|
1252
1505
|
|
1253
1506
|
// console.log(count, decompile([ inst ]).slice(0, -1));
|
@@ -1265,7 +1518,7 @@ const disposeLeftover = wasm => {
|
|
1265
1518
|
const generateExp = (scope, decl) => {
|
1266
1519
|
const expression = decl.expression;
|
1267
1520
|
|
1268
|
-
const out = generate(scope, expression);
|
1521
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1269
1522
|
disposeLeftover(out);
|
1270
1523
|
|
1271
1524
|
return out;
|
@@ -1323,7 +1576,7 @@ const RTArrayUtil = {
|
|
1323
1576
|
]
|
1324
1577
|
};
|
1325
1578
|
|
1326
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1579
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1327
1580
|
/* const callee = decl.callee;
|
1328
1581
|
const args = decl.arguments;
|
1329
1582
|
|
@@ -1339,10 +1592,21 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1339
1592
|
name = func.name;
|
1340
1593
|
}
|
1341
1594
|
|
1342
|
-
if (name === 'eval' && decl.arguments[0]
|
1595
|
+
if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
|
1343
1596
|
// literal eval hack
|
1344
|
-
const code = decl.arguments[0]
|
1345
|
-
|
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
|
+
}
|
1346
1610
|
|
1347
1611
|
const out = generate(scope, {
|
1348
1612
|
type: 'BlockStatement',
|
@@ -1356,13 +1620,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1356
1620
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1357
1621
|
out.push(
|
1358
1622
|
...getNodeType(scope, finalStatement),
|
1359
|
-
|
1623
|
+
...setLastType(scope)
|
1360
1624
|
);
|
1361
1625
|
} else if (countLeftover(out) === 0) {
|
1362
1626
|
out.push(...number(UNDEFINED));
|
1363
1627
|
out.push(
|
1364
1628
|
...number(TYPES.undefined, Valtype.i32),
|
1365
|
-
|
1629
|
+
...setLastType(scope)
|
1366
1630
|
);
|
1367
1631
|
}
|
1368
1632
|
|
@@ -1380,39 +1644,47 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1380
1644
|
if (name && name.startsWith('__')) {
|
1381
1645
|
const spl = name.slice(2).split('_');
|
1382
1646
|
|
1383
|
-
|
1384
|
-
protoName = func;
|
1647
|
+
protoName = spl[spl.length - 1];
|
1385
1648
|
|
1386
1649
|
target = { ...decl.callee };
|
1387
1650
|
target.name = spl.slice(0, -1).join('_');
|
1651
|
+
|
1652
|
+
// failed to lookup name, abort
|
1653
|
+
if (!lookupName(scope, target.name)[0]) protoName = null;
|
1388
1654
|
}
|
1389
1655
|
|
1390
1656
|
// literal.func()
|
1391
1657
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1392
1658
|
// megahack for /regex/.func()
|
1393
|
-
|
1394
|
-
|
1395
|
-
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);
|
1396
1666
|
|
1397
|
-
|
1398
|
-
|
1667
|
+
funcIndex[func.name] = func.index;
|
1668
|
+
funcs.push(func);
|
1669
|
+
}
|
1399
1670
|
|
1671
|
+
const idx = funcIndex[rhemynName];
|
1400
1672
|
return [
|
1401
1673
|
// make string arg
|
1402
1674
|
...generate(scope, decl.arguments[0]),
|
1675
|
+
Opcodes.i32_to_u,
|
1676
|
+
...getNodeType(scope, decl.arguments[0]),
|
1403
1677
|
|
1404
1678
|
// call regex func
|
1405
|
-
Opcodes.
|
1406
|
-
[ Opcodes.call, func.index ],
|
1679
|
+
[ Opcodes.call, idx ],
|
1407
1680
|
Opcodes.i32_from_u,
|
1408
1681
|
|
1409
1682
|
...number(TYPES.boolean, Valtype.i32),
|
1410
|
-
|
1683
|
+
...setLastType(scope)
|
1411
1684
|
];
|
1412
1685
|
}
|
1413
1686
|
|
1414
|
-
|
1415
|
-
protoName = func;
|
1687
|
+
protoName = decl.callee.property.name;
|
1416
1688
|
|
1417
1689
|
target = decl.callee.object;
|
1418
1690
|
}
|
@@ -1433,23 +1705,48 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1433
1705
|
// }
|
1434
1706
|
|
1435
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
|
+
|
1436
1728
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1437
|
-
|
1438
|
-
if (f) acc[x] = f;
|
1729
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1439
1730
|
return acc;
|
1440
1731
|
}, {});
|
1441
1732
|
|
1442
|
-
// no prototype function candidates, ignore
|
1443
1733
|
if (Object.keys(protoCands).length > 0) {
|
1444
1734
|
// use local for cached i32 length as commonly used
|
1445
1735
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1446
1736
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1447
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1448
1737
|
|
1449
1738
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1450
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;
|
1451
1749
|
let lengthI32CacheUsed = false;
|
1452
|
-
const protoBC = {};
|
1453
1750
|
for (const x in protoCands) {
|
1454
1751
|
const protoFunc = protoCands[x];
|
1455
1752
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
@@ -1457,7 +1754,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1457
1754
|
...RTArrayUtil.getLength(getPointer),
|
1458
1755
|
|
1459
1756
|
...number(TYPES.number, Valtype.i32),
|
1460
|
-
|
1757
|
+
...setLastType(scope)
|
1461
1758
|
];
|
1462
1759
|
continue;
|
1463
1760
|
}
|
@@ -1467,6 +1764,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1467
1764
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1468
1765
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1469
1766
|
|
1767
|
+
let optUnused = false;
|
1470
1768
|
const protoOut = protoFunc(getPointer, {
|
1471
1769
|
getCachedI32: () => {
|
1472
1770
|
lengthI32CacheUsed = true;
|
@@ -1481,23 +1779,30 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1481
1779
|
return makeArray(scope, {
|
1482
1780
|
rawElements: new Array(length)
|
1483
1781
|
}, _global, _name, true, itemType);
|
1782
|
+
}, () => {
|
1783
|
+
optUnused = true;
|
1784
|
+
return unusedValue;
|
1484
1785
|
});
|
1485
1786
|
|
1787
|
+
if (!optUnused) allOptUnused = false;
|
1788
|
+
|
1486
1789
|
protoBC[x] = [
|
1487
|
-
[ Opcodes.block, valtypeBinary ],
|
1790
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1488
1791
|
...protoOut,
|
1489
1792
|
|
1490
1793
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1491
|
-
|
1794
|
+
...setLastType(scope),
|
1492
1795
|
[ Opcodes.end ]
|
1493
1796
|
];
|
1494
1797
|
}
|
1495
1798
|
|
1496
|
-
|
1497
|
-
...generate(scope, target),
|
1799
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1498
1800
|
|
1499
|
-
|
1500
|
-
|
1801
|
+
return [
|
1802
|
+
...(usePointerCache ? [
|
1803
|
+
...rawPointer,
|
1804
|
+
[ Opcodes.local_set, pointerLocal ],
|
1805
|
+
] : []),
|
1501
1806
|
|
1502
1807
|
...(!lengthI32CacheUsed ? [] : [
|
1503
1808
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1509,13 +1814,22 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1509
1814
|
|
1510
1815
|
// TODO: error better
|
1511
1816
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1512
|
-
}, valtypeBinary),
|
1817
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1513
1818
|
];
|
1514
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
|
+
}
|
1515
1829
|
}
|
1516
1830
|
|
1517
1831
|
// TODO: only allows callee as literal
|
1518
|
-
if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
|
1832
|
+
if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
|
1519
1833
|
|
1520
1834
|
let idx = funcIndex[name] ?? importedFuncs[name];
|
1521
1835
|
if (idx === undefined && builtinFuncs[name]) {
|
@@ -1525,20 +1839,20 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1525
1839
|
idx = funcIndex[name];
|
1526
1840
|
|
1527
1841
|
// infer arguments types from builtins params
|
1528
|
-
const func = funcs.find(x => x.name === name);
|
1529
|
-
for (let i = 0; i < decl.arguments.length; i++) {
|
1530
|
-
|
1531
|
-
|
1532
|
-
|
1533
|
-
|
1534
|
-
|
1535
|
-
|
1536
|
-
|
1537
|
-
|
1538
|
-
|
1539
|
-
|
1540
|
-
|
1541
|
-
}
|
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
|
+
// }
|
1542
1856
|
}
|
1543
1857
|
|
1544
1858
|
if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
@@ -1548,15 +1862,63 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1548
1862
|
idx = -1;
|
1549
1863
|
}
|
1550
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
|
+
|
1551
1911
|
if (idx === undefined) {
|
1552
|
-
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function
|
1553
|
-
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined
|
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);
|
1554
1914
|
}
|
1555
1915
|
|
1556
1916
|
const func = funcs.find(x => x.index === idx);
|
1557
1917
|
|
1558
1918
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1559
|
-
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);
|
1560
1922
|
|
1561
1923
|
let args = decl.arguments;
|
1562
1924
|
if (func && args.length < paramCount) {
|
@@ -1572,14 +1934,24 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1572
1934
|
if (func && func.throws) scope.throws = true;
|
1573
1935
|
|
1574
1936
|
let out = [];
|
1575
|
-
for (
|
1937
|
+
for (let i = 0; i < args.length; i++) {
|
1938
|
+
const arg = args[i];
|
1576
1939
|
out = out.concat(generate(scope, arg));
|
1577
|
-
|
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));
|
1578
1950
|
}
|
1579
1951
|
|
1580
1952
|
out.push([ Opcodes.call, idx ]);
|
1581
1953
|
|
1582
|
-
if (!
|
1954
|
+
if (!typedReturns) {
|
1583
1955
|
// let type;
|
1584
1956
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1585
1957
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1589,7 +1961,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1589
1961
|
// ...number(type, Valtype.i32),
|
1590
1962
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1591
1963
|
// );
|
1592
|
-
} 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
|
+
}
|
1593
1969
|
|
1594
1970
|
return out;
|
1595
1971
|
};
|
@@ -1597,8 +1973,21 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1597
1973
|
const generateNew = (scope, decl, _global, _name) => {
|
1598
1974
|
// hack: basically treat this as a normal call for builtins for now
|
1599
1975
|
const name = mapName(decl.callee.name);
|
1976
|
+
|
1600
1977
|
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1601
|
-
|
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)})`);
|
1602
1991
|
|
1603
1992
|
return generateCall(scope, decl, _global, _name);
|
1604
1993
|
};
|
@@ -1614,41 +2003,148 @@ const unhackName = name => {
|
|
1614
2003
|
return name;
|
1615
2004
|
};
|
1616
2005
|
|
1617
|
-
const
|
1618
|
-
|
2006
|
+
const knownType = (scope, type) => {
|
2007
|
+
if (type.length === 1 && type[0][0] === Opcodes.i32_const) {
|
2008
|
+
return type[0][1];
|
2009
|
+
}
|
1619
2010
|
|
1620
|
-
|
1621
|
-
|
1622
|
-
[ Opcodes.local_set, tmp ],
|
1623
|
-
[ Opcodes.block, returns ]
|
1624
|
-
];
|
2011
|
+
if (typedInput && type.length === 1 && type[0][0] === Opcodes.local_get) {
|
2012
|
+
const idx = type[0][1];
|
1625
2013
|
|
1626
|
-
|
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
|
+
}
|
1627
2018
|
|
1628
|
-
|
1629
|
-
|
2019
|
+
return null;
|
2020
|
+
};
|
1630
2021
|
|
1631
|
-
|
1632
|
-
|
1633
|
-
|
1634
|
-
|
2022
|
+
const brTable = (input, bc, returns) => {
|
2023
|
+
const out = [];
|
2024
|
+
const keys = Object.keys(bc);
|
2025
|
+
const count = keys.length;
|
1635
2026
|
|
1636
|
-
|
1637
|
-
|
1638
|
-
|
1639
|
-
|
2027
|
+
if (count === 1) {
|
2028
|
+
// return [
|
2029
|
+
// ...input,
|
2030
|
+
// ...bc[keys[0]]
|
2031
|
+
// ];
|
2032
|
+
return bc[keys[0]];
|
1640
2033
|
}
|
1641
2034
|
|
1642
|
-
|
1643
|
-
|
1644
|
-
|
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
|
+
}
|
1645
2049
|
|
1646
|
-
|
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
|
+
}
|
2054
|
+
|
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
|
+
];
|
2104
|
+
};
|
2105
|
+
|
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' ]);
|
1647
2143
|
|
1648
2144
|
return out;
|
1649
2145
|
};
|
1650
2146
|
|
1651
|
-
const allocVar = (scope, name, global = false) => {
|
2147
|
+
const allocVar = (scope, name, global = false, type = true) => {
|
1652
2148
|
const target = global ? globals : scope.locals;
|
1653
2149
|
|
1654
2150
|
// already declared
|
@@ -1662,12 +2158,62 @@ const allocVar = (scope, name, global = false) => {
|
|
1662
2158
|
let idx = global ? globalInd++ : scope.localInd++;
|
1663
2159
|
target[name] = { idx, type: valtypeBinary };
|
1664
2160
|
|
1665
|
-
|
1666
|
-
|
2161
|
+
if (type) {
|
2162
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
2163
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
2164
|
+
}
|
1667
2165
|
|
1668
2166
|
return idx;
|
1669
2167
|
};
|
1670
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
|
+
|
1671
2217
|
const generateVar = (scope, decl) => {
|
1672
2218
|
let out = [];
|
1673
2219
|
|
@@ -1675,10 +2221,13 @@ const generateVar = (scope, decl) => {
|
|
1675
2221
|
|
1676
2222
|
// global variable if in top scope (main) and var ..., or if wanted
|
1677
2223
|
const global = topLevel || decl._bare; // decl.kind === 'var';
|
2224
|
+
const target = global ? globals : scope.locals;
|
1678
2225
|
|
1679
2226
|
for (const x of decl.declarations) {
|
1680
2227
|
const name = mapName(x.id.name);
|
1681
2228
|
|
2229
|
+
if (!name) return todo(scope, 'destructuring is not supported yet');
|
2230
|
+
|
1682
2231
|
if (x.init && isFuncType(x.init.type)) {
|
1683
2232
|
// hack for let a = function () { ... }
|
1684
2233
|
x.init.id = { name };
|
@@ -1694,11 +2243,29 @@ const generateVar = (scope, decl) => {
|
|
1694
2243
|
continue; // always ignore
|
1695
2244
|
}
|
1696
2245
|
|
1697
|
-
|
1698
|
-
|
1699
|
-
|
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));
|
1700
2252
|
|
1701
|
-
|
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
|
+
}
|
1702
2269
|
out.push(...setType(scope, name, getNodeType(scope, x.init)));
|
1703
2270
|
}
|
1704
2271
|
|
@@ -1709,7 +2276,8 @@ const generateVar = (scope, decl) => {
|
|
1709
2276
|
return out;
|
1710
2277
|
};
|
1711
2278
|
|
1712
|
-
|
2279
|
+
// todo: optimize this func for valueUnused
|
2280
|
+
const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
1713
2281
|
const { type, name } = decl.left;
|
1714
2282
|
|
1715
2283
|
if (type === 'ObjectPattern') {
|
@@ -1724,22 +2292,30 @@ const generateAssign = (scope, decl) => {
|
|
1724
2292
|
return [];
|
1725
2293
|
}
|
1726
2294
|
|
2295
|
+
const op = decl.operator.slice(0, -1) || '=';
|
2296
|
+
|
1727
2297
|
// hack: .length setter
|
1728
2298
|
if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
|
1729
2299
|
const name = decl.left.object.name;
|
1730
|
-
const pointer = arrays
|
2300
|
+
const pointer = scope.arrays?.get(name);
|
1731
2301
|
|
1732
|
-
const aotPointer = pointer != null;
|
2302
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1733
2303
|
|
1734
2304
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
2305
|
+
const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1735
2306
|
|
1736
2307
|
return [
|
1737
2308
|
...(aotPointer ? number(0, Valtype.i32) : [
|
1738
2309
|
...generate(scope, decl.left.object),
|
1739
2310
|
Opcodes.i32_to_u
|
1740
2311
|
]),
|
2312
|
+
...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
1741
2313
|
|
1742
|
-
...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))),
|
1743
2319
|
[ Opcodes.local_tee, newValueTmp ],
|
1744
2320
|
|
1745
2321
|
Opcodes.i32_to_u,
|
@@ -1749,21 +2325,19 @@ const generateAssign = (scope, decl) => {
|
|
1749
2325
|
];
|
1750
2326
|
}
|
1751
2327
|
|
1752
|
-
const op = decl.operator.slice(0, -1) || '=';
|
1753
|
-
|
1754
2328
|
// arr[i]
|
1755
2329
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
1756
2330
|
const name = decl.left.object.name;
|
1757
|
-
const pointer = arrays
|
2331
|
+
const pointer = scope.arrays?.get(name);
|
1758
2332
|
|
1759
|
-
const aotPointer = pointer != null;
|
2333
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1760
2334
|
|
1761
2335
|
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
1762
2336
|
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1763
2337
|
|
1764
2338
|
return [
|
1765
2339
|
...typeSwitch(scope, getNodeType(scope, decl.left.object), {
|
1766
|
-
[TYPES.
|
2340
|
+
[TYPES.array]: [
|
1767
2341
|
...(aotPointer ? [] : [
|
1768
2342
|
...generate(scope, decl.left.object),
|
1769
2343
|
Opcodes.i32_to_u
|
@@ -1812,6 +2386,8 @@ const generateAssign = (scope, decl) => {
|
|
1812
2386
|
];
|
1813
2387
|
}
|
1814
2388
|
|
2389
|
+
if (!name) return todo(scope, 'destructuring is not supported yet', true);
|
2390
|
+
|
1815
2391
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1816
2392
|
|
1817
2393
|
if (local === undefined) {
|
@@ -1858,9 +2434,7 @@ const generateAssign = (scope, decl) => {
|
|
1858
2434
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
1859
2435
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1860
2436
|
|
1861
|
-
|
1862
|
-
// hack: type is idx+1
|
1863
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2437
|
+
...setType(scope, name, getLastType(scope))
|
1864
2438
|
];
|
1865
2439
|
}
|
1866
2440
|
|
@@ -1871,9 +2445,7 @@ const generateAssign = (scope, decl) => {
|
|
1871
2445
|
|
1872
2446
|
// todo: string concat types
|
1873
2447
|
|
1874
|
-
|
1875
|
-
...number(TYPES.number, Valtype.i32),
|
1876
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2448
|
+
...setType(scope, name, TYPES.number)
|
1877
2449
|
];
|
1878
2450
|
};
|
1879
2451
|
|
@@ -1919,7 +2491,7 @@ const generateUnary = (scope, decl) => {
|
|
1919
2491
|
return out;
|
1920
2492
|
}
|
1921
2493
|
|
1922
|
-
case 'delete':
|
2494
|
+
case 'delete': {
|
1923
2495
|
let toReturn = true, toGenerate = true;
|
1924
2496
|
|
1925
2497
|
if (decl.argument.type === 'Identifier') {
|
@@ -1941,38 +2513,60 @@ const generateUnary = (scope, decl) => {
|
|
1941
2513
|
|
1942
2514
|
out.push(...number(toReturn ? 1 : 0));
|
1943
2515
|
return out;
|
2516
|
+
}
|
1944
2517
|
|
1945
|
-
case 'typeof':
|
1946
|
-
|
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), {
|
1947
2536
|
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
1948
2537
|
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
1949
2538
|
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
1950
2539
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
1951
2540
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
1952
2541
|
|
2542
|
+
[TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2543
|
+
|
1953
2544
|
// object and internal types
|
1954
2545
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
1955
|
-
});
|
2546
|
+
}));
|
2547
|
+
|
2548
|
+
return out;
|
2549
|
+
}
|
1956
2550
|
|
1957
2551
|
default:
|
1958
|
-
return todo(`unary operator ${decl.operator} not implemented yet
|
2552
|
+
return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
|
1959
2553
|
}
|
1960
2554
|
};
|
1961
2555
|
|
1962
|
-
const generateUpdate = (scope, decl) => {
|
2556
|
+
const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
|
1963
2557
|
const { name } = decl.argument;
|
1964
2558
|
|
1965
2559
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1966
2560
|
|
1967
2561
|
if (local === undefined) {
|
1968
|
-
return todo(`update expression with undefined variable
|
2562
|
+
return todo(scope, `update expression with undefined variable`, true);
|
1969
2563
|
}
|
1970
2564
|
|
1971
2565
|
const idx = local.idx;
|
1972
2566
|
const out = [];
|
1973
2567
|
|
1974
2568
|
out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
1975
|
-
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 ]);
|
1976
2570
|
|
1977
2571
|
switch (decl.operator) {
|
1978
2572
|
case '++':
|
@@ -1985,7 +2579,7 @@ const generateUpdate = (scope, decl) => {
|
|
1985
2579
|
}
|
1986
2580
|
|
1987
2581
|
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
1988
|
-
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 ]);
|
1989
2583
|
|
1990
2584
|
return out;
|
1991
2585
|
};
|
@@ -2025,7 +2619,7 @@ const generateConditional = (scope, decl) => {
|
|
2025
2619
|
// note type
|
2026
2620
|
out.push(
|
2027
2621
|
...getNodeType(scope, decl.consequent),
|
2028
|
-
|
2622
|
+
...setLastType(scope)
|
2029
2623
|
);
|
2030
2624
|
|
2031
2625
|
out.push([ Opcodes.else ]);
|
@@ -2034,7 +2628,7 @@ const generateConditional = (scope, decl) => {
|
|
2034
2628
|
// note type
|
2035
2629
|
out.push(
|
2036
2630
|
...getNodeType(scope, decl.alternate),
|
2037
|
-
|
2631
|
+
...setLastType(scope)
|
2038
2632
|
);
|
2039
2633
|
|
2040
2634
|
out.push([ Opcodes.end ]);
|
@@ -2048,15 +2642,17 @@ const generateFor = (scope, decl) => {
|
|
2048
2642
|
const out = [];
|
2049
2643
|
|
2050
2644
|
if (decl.init) {
|
2051
|
-
out.push(...generate(scope, decl.init));
|
2645
|
+
out.push(...generate(scope, decl.init, false, undefined, true));
|
2052
2646
|
disposeLeftover(out);
|
2053
2647
|
}
|
2054
2648
|
|
2055
2649
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2056
2650
|
depth.push('for');
|
2057
2651
|
|
2058
|
-
out.push(...generate(scope, decl.test));
|
2059
|
-
|
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 ]);
|
2060
2656
|
depth.push('if');
|
2061
2657
|
|
2062
2658
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2064,8 +2660,7 @@ const generateFor = (scope, decl) => {
|
|
2064
2660
|
out.push(...generate(scope, decl.body));
|
2065
2661
|
out.push([ Opcodes.end ]);
|
2066
2662
|
|
2067
|
-
out.push(...generate(scope, decl.update));
|
2068
|
-
depth.pop();
|
2663
|
+
if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
|
2069
2664
|
|
2070
2665
|
out.push([ Opcodes.br, 1 ]);
|
2071
2666
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2093,6 +2688,36 @@ const generateWhile = (scope, decl) => {
|
|
2093
2688
|
return out;
|
2094
2689
|
};
|
2095
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
|
+
|
2096
2721
|
const generateForOf = (scope, decl) => {
|
2097
2722
|
const out = [];
|
2098
2723
|
|
@@ -2122,8 +2747,17 @@ const generateForOf = (scope, decl) => {
|
|
2122
2747
|
// setup local for left
|
2123
2748
|
generate(scope, decl.left);
|
2124
2749
|
|
2125
|
-
|
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
|
+
|
2126
2759
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2760
|
+
if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
|
2127
2761
|
|
2128
2762
|
depth.push('block');
|
2129
2763
|
depth.push('block');
|
@@ -2131,15 +2765,17 @@ const generateForOf = (scope, decl) => {
|
|
2131
2765
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2132
2766
|
// hack: this is naughty and will break things!
|
2133
2767
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2134
|
-
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?
|
2135
2770
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2136
2771
|
rawElements: new Array(1)
|
2137
2772
|
}, isGlobal, leftName, true, 'i16');
|
2138
2773
|
}
|
2139
2774
|
|
2140
2775
|
// set type for local
|
2776
|
+
// todo: optimize away counter and use end pointer
|
2141
2777
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2142
|
-
[TYPES.
|
2778
|
+
[TYPES.array]: [
|
2143
2779
|
...setType(scope, leftName, TYPES.number),
|
2144
2780
|
|
2145
2781
|
[ Opcodes.loop, Blocktype.void ],
|
@@ -2222,6 +2858,56 @@ const generateForOf = (scope, decl) => {
|
|
2222
2858
|
[ Opcodes.end ],
|
2223
2859
|
[ Opcodes.end ]
|
2224
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
|
+
],
|
2225
2911
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2226
2912
|
}, Blocktype.void));
|
2227
2913
|
|
@@ -2232,28 +2918,65 @@ const generateForOf = (scope, decl) => {
|
|
2232
2918
|
return out;
|
2233
2919
|
};
|
2234
2920
|
|
2921
|
+
// find the nearest loop in depth map by type
|
2235
2922
|
const getNearestLoop = () => {
|
2236
2923
|
for (let i = depth.length - 1; i >= 0; i--) {
|
2237
|
-
if (
|
2924
|
+
if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
|
2238
2925
|
}
|
2239
2926
|
|
2240
2927
|
return -1;
|
2241
2928
|
};
|
2242
2929
|
|
2243
2930
|
const generateBreak = (scope, decl) => {
|
2244
|
-
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
|
+
|
2245
2946
|
return [
|
2246
|
-
[ Opcodes.br, ...signedLEB128(
|
2947
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2247
2948
|
];
|
2248
2949
|
};
|
2249
2950
|
|
2250
2951
|
const generateContinue = (scope, decl) => {
|
2251
|
-
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
|
+
|
2252
2966
|
return [
|
2253
|
-
[ Opcodes.br, ...signedLEB128(
|
2967
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2254
2968
|
];
|
2255
2969
|
};
|
2256
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
|
+
|
2257
2980
|
const generateThrow = (scope, decl) => {
|
2258
2981
|
scope.throws = true;
|
2259
2982
|
|
@@ -2262,7 +2985,7 @@ const generateThrow = (scope, decl) => {
|
|
2262
2985
|
// hack: throw new X("...") -> throw "..."
|
2263
2986
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2264
2987
|
constructor = decl.argument.callee.name;
|
2265
|
-
message = decl.argument.arguments[0]
|
2988
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2266
2989
|
}
|
2267
2990
|
|
2268
2991
|
if (tags.length === 0) tags.push({
|
@@ -2274,6 +2997,9 @@ const generateThrow = (scope, decl) => {
|
|
2274
2997
|
let exceptId = exceptions.push({ constructor, message }) - 1;
|
2275
2998
|
let tagIdx = tags[0].idx;
|
2276
2999
|
|
3000
|
+
scope.exceptions ??= [];
|
3001
|
+
scope.exceptions.push(exceptId);
|
3002
|
+
|
2277
3003
|
// todo: write a description of how this works lol
|
2278
3004
|
|
2279
3005
|
return [
|
@@ -2283,7 +3009,7 @@ const generateThrow = (scope, decl) => {
|
|
2283
3009
|
};
|
2284
3010
|
|
2285
3011
|
const generateTry = (scope, decl) => {
|
2286
|
-
if (decl.finalizer) return todo('try finally not implemented yet');
|
3012
|
+
if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
|
2287
3013
|
|
2288
3014
|
const out = [];
|
2289
3015
|
|
@@ -2314,29 +3040,35 @@ const generateAssignPat = (scope, decl) => {
|
|
2314
3040
|
// TODO
|
2315
3041
|
// if identifier declared, use that
|
2316
3042
|
// else, use default (right)
|
2317
|
-
return todo('assignment pattern (optional arg)');
|
3043
|
+
return todo(scope, 'assignment pattern (optional arg)');
|
2318
3044
|
};
|
2319
3045
|
|
2320
3046
|
let pages = new Map();
|
2321
|
-
const allocPage = (reason, type) => {
|
3047
|
+
const allocPage = (scope, reason, type) => {
|
2322
3048
|
if (pages.has(reason)) return pages.get(reason).ind;
|
2323
3049
|
|
2324
3050
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2325
3051
|
if (reason.startsWith('string:')) pages.hasString = true;
|
3052
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
3053
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2326
3054
|
|
2327
3055
|
const ind = pages.size;
|
2328
3056
|
pages.set(reason, { ind, type });
|
2329
3057
|
|
2330
|
-
|
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})`);
|
2331
3062
|
|
2332
3063
|
return ind;
|
2333
3064
|
};
|
2334
3065
|
|
3066
|
+
// todo: add scope.pages
|
2335
3067
|
const freePage = reason => {
|
2336
3068
|
const { ind } = pages.get(reason);
|
2337
3069
|
pages.delete(reason);
|
2338
3070
|
|
2339
|
-
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
3071
|
+
if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2340
3072
|
|
2341
3073
|
return ind;
|
2342
3074
|
};
|
@@ -2356,38 +3088,53 @@ const StoreOps = {
|
|
2356
3088
|
f64: Opcodes.f64_store,
|
2357
3089
|
|
2358
3090
|
// expects i32 input!
|
2359
|
-
|
3091
|
+
i8: Opcodes.i32_store8,
|
3092
|
+
i16: Opcodes.i32_store16,
|
2360
3093
|
};
|
2361
3094
|
|
2362
3095
|
let data = [];
|
2363
3096
|
|
2364
|
-
const compileBytes = (val, itemType
|
3097
|
+
const compileBytes = (val, itemType) => {
|
2365
3098
|
// todo: this is a mess and needs confirming / ????
|
2366
3099
|
switch (itemType) {
|
2367
3100
|
case 'i8': return [ val % 256 ];
|
2368
|
-
case 'i16': return [ val % 256,
|
2369
|
-
|
2370
|
-
case 'i32':
|
2371
|
-
|
2372
|
-
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
|
2373
3105
|
|
2374
3106
|
case 'f64': return ieee754_binary64(val);
|
2375
3107
|
}
|
2376
3108
|
};
|
2377
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
|
+
|
2378
3119
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2379
3120
|
const out = [];
|
2380
3121
|
|
3122
|
+
scope.arrays ??= new Map();
|
3123
|
+
|
2381
3124
|
let firstAssign = false;
|
2382
|
-
if (!arrays.has(name) || name === '$undeclared') {
|
3125
|
+
if (!scope.arrays.has(name) || name === '$undeclared') {
|
2383
3126
|
firstAssign = true;
|
2384
3127
|
|
2385
3128
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2386
3129
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2387
|
-
|
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);
|
2388
3133
|
}
|
2389
3134
|
|
2390
|
-
const pointer = arrays.get(name);
|
3135
|
+
const pointer = scope.arrays.get(name);
|
3136
|
+
|
3137
|
+
const local = global ? globals[name] : scope.locals[name];
|
2391
3138
|
|
2392
3139
|
const useRawElements = !!decl.rawElements;
|
2393
3140
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
@@ -2395,19 +3142,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2395
3142
|
const valtype = itemTypeToValtype[itemType];
|
2396
3143
|
const length = elements.length;
|
2397
3144
|
|
2398
|
-
if (firstAssign && useRawElements) {
|
2399
|
-
|
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');
|
2400
3149
|
|
2401
|
-
|
2402
|
-
|
3150
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
3151
|
+
if (elements[i] == null) continue;
|
2403
3152
|
|
2404
|
-
|
2405
|
-
|
3153
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
3154
|
+
}
|
2406
3155
|
|
2407
|
-
|
2408
|
-
|
2409
|
-
|
2410
|
-
|
3156
|
+
const ind = data.push({
|
3157
|
+
offset: pointer,
|
3158
|
+
bytes
|
3159
|
+
}) - 1;
|
3160
|
+
|
3161
|
+
scope.data ??= [];
|
3162
|
+
scope.data.push(ind);
|
3163
|
+
}
|
2411
3164
|
|
2412
3165
|
// local value as pointer
|
2413
3166
|
out.push(...number(pointer));
|
@@ -2415,11 +3168,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2415
3168
|
return [ out, pointer ];
|
2416
3169
|
}
|
2417
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
|
+
|
2418
3182
|
// store length as 0th array
|
2419
3183
|
out.push(
|
2420
|
-
...
|
3184
|
+
...pointerWasm,
|
2421
3185
|
...number(length, Valtype.i32),
|
2422
|
-
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1,
|
3186
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
|
2423
3187
|
);
|
2424
3188
|
|
2425
3189
|
const storeOp = StoreOps[itemType];
|
@@ -2428,43 +3192,92 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2428
3192
|
if (elements[i] == null) continue;
|
2429
3193
|
|
2430
3194
|
out.push(
|
2431
|
-
...
|
3195
|
+
...pointerWasm,
|
2432
3196
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2433
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(
|
3197
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2434
3198
|
);
|
2435
3199
|
}
|
2436
3200
|
|
2437
3201
|
// local value as pointer
|
2438
|
-
out.push(...
|
3202
|
+
out.push(...pointerWasm, Opcodes.i32_from_u);
|
2439
3203
|
|
2440
3204
|
return [ out, pointer ];
|
2441
3205
|
};
|
2442
3206
|
|
2443
|
-
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) => {
|
2444
3218
|
const rawElements = new Array(str.length);
|
3219
|
+
let byteStringable = Prefs.bytestring;
|
2445
3220
|
for (let i = 0; i < str.length; i++) {
|
2446
|
-
|
3221
|
+
const c = str.charCodeAt(i);
|
3222
|
+
rawElements[i] = c;
|
3223
|
+
|
3224
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2447
3225
|
}
|
2448
3226
|
|
3227
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
3228
|
+
|
2449
3229
|
return makeArray(scope, {
|
2450
3230
|
rawElements
|
2451
|
-
}, global, name, false, 'i16')[0];
|
3231
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2452
3232
|
};
|
2453
3233
|
|
2454
|
-
let arrays = new Map();
|
2455
3234
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
2456
3235
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
2457
3236
|
};
|
2458
3237
|
|
2459
3238
|
export const generateMember = (scope, decl, _global, _name) => {
|
2460
3239
|
const name = decl.object.name;
|
2461
|
-
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;
|
2462
3248
|
|
2463
|
-
|
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
|
+
}
|
2464
3257
|
|
2465
3258
|
// hack: .length
|
2466
3259
|
if (decl.property.name === 'length') {
|
2467
|
-
|
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
|
+
|
2468
3281
|
return [
|
2469
3282
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2470
3283
|
...generate(scope, decl.object),
|
@@ -2476,19 +3289,22 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2476
3289
|
];
|
2477
3290
|
}
|
2478
3291
|
|
3292
|
+
const object = generate(scope, decl.object);
|
3293
|
+
const property = generate(scope, decl.property);
|
3294
|
+
|
2479
3295
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2480
3296
|
// hack: this is naughty and will break things!
|
2481
|
-
let newOut = number(0,
|
2482
|
-
if (pages.
|
3297
|
+
let newOut = number(0, valtypeBinary), newPointer = -1;
|
3298
|
+
if (pages.hasAnyString) {
|
2483
3299
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2484
3300
|
rawElements: new Array(1)
|
2485
3301
|
}, _global, _name, true, 'i16');
|
2486
3302
|
}
|
2487
3303
|
|
2488
3304
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2489
|
-
[TYPES.
|
3305
|
+
[TYPES.array]: [
|
2490
3306
|
// get index as valtype
|
2491
|
-
...
|
3307
|
+
...property,
|
2492
3308
|
|
2493
3309
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2494
3310
|
Opcodes.i32_to_u,
|
@@ -2496,7 +3312,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2496
3312
|
[ Opcodes.i32_mul ],
|
2497
3313
|
|
2498
3314
|
...(aotPointer ? [] : [
|
2499
|
-
...
|
3315
|
+
...object,
|
2500
3316
|
Opcodes.i32_to_u,
|
2501
3317
|
[ Opcodes.i32_add ]
|
2502
3318
|
]),
|
@@ -2505,7 +3321,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2505
3321
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2506
3322
|
|
2507
3323
|
...number(TYPES.number, Valtype.i32),
|
2508
|
-
|
3324
|
+
...setLastType(scope)
|
2509
3325
|
],
|
2510
3326
|
|
2511
3327
|
[TYPES.string]: [
|
@@ -2515,14 +3331,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2515
3331
|
|
2516
3332
|
...number(0, Valtype.i32), // base 0 for store later
|
2517
3333
|
|
2518
|
-
...
|
2519
|
-
|
3334
|
+
...property,
|
2520
3335
|
Opcodes.i32_to_u,
|
3336
|
+
|
2521
3337
|
...number(ValtypeSize.i16, Valtype.i32),
|
2522
3338
|
[ Opcodes.i32_mul ],
|
2523
3339
|
|
2524
3340
|
...(aotPointer ? [] : [
|
2525
|
-
...
|
3341
|
+
...object,
|
2526
3342
|
Opcodes.i32_to_u,
|
2527
3343
|
[ Opcodes.i32_add ]
|
2528
3344
|
]),
|
@@ -2537,10 +3353,38 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2537
3353
|
...number(newPointer),
|
2538
3354
|
|
2539
3355
|
...number(TYPES.string, Valtype.i32),
|
2540
|
-
|
3356
|
+
...setLastType(scope)
|
2541
3357
|
],
|
3358
|
+
[TYPES.bytestring]: [
|
3359
|
+
// setup new/out array
|
3360
|
+
...newOut,
|
3361
|
+
[ Opcodes.drop ],
|
2542
3362
|
|
2543
|
-
|
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)
|
2544
3388
|
});
|
2545
3389
|
};
|
2546
3390
|
|
@@ -2550,25 +3394,36 @@ const objectHack = node => {
|
|
2550
3394
|
if (!node) return node;
|
2551
3395
|
|
2552
3396
|
if (node.type === 'MemberExpression') {
|
2553
|
-
|
3397
|
+
const out = (() => {
|
3398
|
+
if (node.computed || node.optional) return;
|
3399
|
+
|
3400
|
+
let objectName = node.object.name;
|
2554
3401
|
|
2555
|
-
|
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;
|
2556
3405
|
|
2557
|
-
|
2558
|
-
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
3406
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2559
3407
|
|
2560
|
-
|
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
|
+
}
|
2561
3413
|
|
2562
|
-
|
2563
|
-
|
3414
|
+
// no object name, give up
|
3415
|
+
if (!objectName) return;
|
2564
3416
|
|
2565
|
-
|
2566
|
-
|
3417
|
+
const name = '__' + objectName + '_' + node.property.name;
|
3418
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2567
3419
|
|
2568
|
-
|
2569
|
-
|
2570
|
-
|
2571
|
-
|
3420
|
+
return {
|
3421
|
+
type: 'Identifier',
|
3422
|
+
name
|
3423
|
+
};
|
3424
|
+
})();
|
3425
|
+
|
3426
|
+
if (out) return out;
|
2572
3427
|
}
|
2573
3428
|
|
2574
3429
|
for (const x in node) {
|
@@ -2582,11 +3437,11 @@ const objectHack = node => {
|
|
2582
3437
|
};
|
2583
3438
|
|
2584
3439
|
const generateFunc = (scope, decl) => {
|
2585
|
-
if (decl.async) return todo('async functions are not supported');
|
2586
|
-
if (decl.generator) return todo('generator functions are not supported');
|
3440
|
+
if (decl.async) return todo(scope, 'async functions are not supported');
|
3441
|
+
if (decl.generator) return todo(scope, 'generator functions are not supported');
|
2587
3442
|
|
2588
3443
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2589
|
-
const params = decl.params
|
3444
|
+
const params = decl.params ?? [];
|
2590
3445
|
|
2591
3446
|
// const innerScope = { ...scope };
|
2592
3447
|
// TODO: share scope/locals between !!!
|
@@ -2599,8 +3454,17 @@ const generateFunc = (scope, decl) => {
|
|
2599
3454
|
name
|
2600
3455
|
};
|
2601
3456
|
|
3457
|
+
if (typedInput && decl.returnType) {
|
3458
|
+
innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
|
3459
|
+
innerScope.returns = [ valtypeBinary ];
|
3460
|
+
}
|
3461
|
+
|
2602
3462
|
for (let i = 0; i < params.length; i++) {
|
2603
|
-
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
|
+
}
|
2604
3468
|
}
|
2605
3469
|
|
2606
3470
|
let body = objectHack(decl.body);
|
@@ -2616,13 +3480,13 @@ const generateFunc = (scope, decl) => {
|
|
2616
3480
|
const func = {
|
2617
3481
|
name,
|
2618
3482
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2619
|
-
|
2620
|
-
|
2621
|
-
throws: innerScope.throws,
|
2622
|
-
index: currentFuncIndex++
|
3483
|
+
index: currentFuncIndex++,
|
3484
|
+
...innerScope
|
2623
3485
|
};
|
2624
3486
|
funcIndex[name] = func.index;
|
2625
3487
|
|
3488
|
+
if (name === 'main') func.gotLastType = true;
|
3489
|
+
|
2626
3490
|
// quick hack fixes
|
2627
3491
|
for (const inst of wasm) {
|
2628
3492
|
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
@@ -2639,117 +3503,6 @@ const generateFunc = (scope, decl) => {
|
|
2639
3503
|
);
|
2640
3504
|
}
|
2641
3505
|
|
2642
|
-
// change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
|
2643
|
-
let offset = 0, vecParams = 0;
|
2644
|
-
for (let i = 0; i < params.length; i++) {
|
2645
|
-
const name = params[i];
|
2646
|
-
const local = func.locals[name];
|
2647
|
-
if (local.type === Valtype.v128) {
|
2648
|
-
vecParams++;
|
2649
|
-
|
2650
|
-
/* wasm.unshift( // add v128 load for param
|
2651
|
-
[ Opcodes.i32_const, 0 ],
|
2652
|
-
[ ...Opcodes.v128_load, 0, i * 16 ],
|
2653
|
-
[ Opcodes.local_set, local.idx ]
|
2654
|
-
); */
|
2655
|
-
|
2656
|
-
// using params and replace_lane is noticably faster than just loading from memory (above) somehow
|
2657
|
-
|
2658
|
-
// extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
|
2659
|
-
const { vecType } = local;
|
2660
|
-
let [ type, lanes ] = vecType.split('x');
|
2661
|
-
if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
|
2662
|
-
|
2663
|
-
lanes = parseInt(lanes);
|
2664
|
-
type = Valtype[type];
|
2665
|
-
|
2666
|
-
const name = params[i]; // get original param name
|
2667
|
-
|
2668
|
-
func.params.splice(offset, 1, ...new Array(lanes).fill(type)); // add new params of {type}, {lanes} times
|
2669
|
-
|
2670
|
-
// update index of original local
|
2671
|
-
// delete func.locals[name];
|
2672
|
-
|
2673
|
-
// add new locals for params
|
2674
|
-
for (let j = 0; j < lanes; j++) {
|
2675
|
-
func.locals[name + j] = { idx: offset + j, type, vecParamAutogen: true };
|
2676
|
-
}
|
2677
|
-
|
2678
|
-
// prepend wasm to generate expected v128 locals
|
2679
|
-
wasm.splice(i * 2 + offset * 2, 0,
|
2680
|
-
...i32x4(0, 0, 0, 0),
|
2681
|
-
...new Array(lanes).fill(0).flatMap((_, j) => [
|
2682
|
-
[ Opcodes.local_get, offset + j ],
|
2683
|
-
[ ...Opcodes[vecType + '_replace_lane'], j ]
|
2684
|
-
]),
|
2685
|
-
[ Opcodes.local_set, i ]
|
2686
|
-
);
|
2687
|
-
|
2688
|
-
offset += lanes;
|
2689
|
-
|
2690
|
-
// note: wrapping is disabled for now due to perf/dx concerns (so this will never run)
|
2691
|
-
/* if (!func.name.startsWith('#')) func.name = '##' + func.name;
|
2692
|
-
|
2693
|
-
// add vec type index to hash name prefix for wrapper to know how to wrap
|
2694
|
-
const vecTypeIdx = [ 'i8x16', 'i16x8', 'i32x4', 'i64x2', 'f32x4', 'f64x2' ].indexOf(local.vecType);
|
2695
|
-
const secondHash = func.name.slice(1).indexOf('#');
|
2696
|
-
func.name = '#' + func.name.slice(1, secondHash) + vecTypeIdx + func.name.slice(secondHash); */
|
2697
|
-
}
|
2698
|
-
}
|
2699
|
-
|
2700
|
-
if (offset !== 0) {
|
2701
|
-
// bump local indexes for all other locals after
|
2702
|
-
for (const x in func.locals) {
|
2703
|
-
const local = func.locals[x];
|
2704
|
-
if (!local.vecParamAutogen) local.idx += offset;
|
2705
|
-
}
|
2706
|
-
|
2707
|
-
// bump local indexes in wasm local.get/set
|
2708
|
-
for (let j = 0; j < wasm.length; j++) {
|
2709
|
-
const inst = wasm[j];
|
2710
|
-
if (j < offset * 2 + vecParams * 2) {
|
2711
|
-
if (inst[0] === Opcodes.local_set) inst[1] += offset;
|
2712
|
-
continue;
|
2713
|
-
}
|
2714
|
-
|
2715
|
-
if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set) inst[1] += offset;
|
2716
|
-
}
|
2717
|
-
}
|
2718
|
-
|
2719
|
-
// change v128 return into many <type> instead as unsupported return valtype
|
2720
|
-
const lastReturnLocal = wasm.length > 2 && wasm[wasm.length - 1][0] === Opcodes.return && Object.values(func.locals).find(x => x.idx === wasm[wasm.length - 2][1]);
|
2721
|
-
if (lastReturnLocal && lastReturnLocal.type === Valtype.v128) {
|
2722
|
-
const name = Object.keys(func.locals)[Object.values(func.locals).indexOf(lastReturnLocal)];
|
2723
|
-
// extract valtype and lane count from vec type (i32x4 = i32 4, i8x16 = i8 16, etc)
|
2724
|
-
const { vecType } = lastReturnLocal;
|
2725
|
-
let [ type, lanes ] = vecType.split('x');
|
2726
|
-
if (!type || !lanes) throw new Error('bad metadata from vec params'); // sanity check
|
2727
|
-
|
2728
|
-
lanes = parseInt(lanes);
|
2729
|
-
type = Valtype[type];
|
2730
|
-
|
2731
|
-
const vecIdx = lastReturnLocal.idx;
|
2732
|
-
|
2733
|
-
const lastIdx = Math.max(0, ...Object.values(func.locals).map(x => x.idx));
|
2734
|
-
const tmpIdx = [];
|
2735
|
-
for (let i = 0; i < lanes; i++) {
|
2736
|
-
const idx = lastIdx + i + 1;
|
2737
|
-
tmpIdx.push(idx);
|
2738
|
-
func.locals[name + i] = { idx, type, vecReturnAutogen: true };
|
2739
|
-
}
|
2740
|
-
|
2741
|
-
wasm.splice(wasm.length - 1, 1,
|
2742
|
-
...new Array(lanes).fill(0).flatMap((_, i) => [
|
2743
|
-
i === 0 ? null : [ Opcodes.local_get, vecIdx ],
|
2744
|
-
[ ...Opcodes[vecType + '_extract_lane'], i ],
|
2745
|
-
[ Opcodes.local_set, tmpIdx[i] ],
|
2746
|
-
].filter(x => x !== null)),
|
2747
|
-
...new Array(lanes).fill(0).map((_, i) => [ Opcodes.local_get, tmpIdx[i]])
|
2748
|
-
);
|
2749
|
-
|
2750
|
-
func.returns = new Array(lanes).fill(type);
|
2751
|
-
}
|
2752
|
-
|
2753
3506
|
func.wasm = wasm;
|
2754
3507
|
|
2755
3508
|
funcs.push(func);
|
@@ -2785,7 +3538,7 @@ const internalConstrs = {
|
|
2785
3538
|
|
2786
3539
|
// todo: check in wasm instead of here
|
2787
3540
|
const literalValue = arg.value ?? 0;
|
2788
|
-
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);
|
2789
3542
|
|
2790
3543
|
return [
|
2791
3544
|
...number(0, Valtype.i32),
|
@@ -2796,7 +3549,8 @@ const internalConstrs = {
|
|
2796
3549
|
...number(pointer)
|
2797
3550
|
];
|
2798
3551
|
},
|
2799
|
-
type: TYPES.
|
3552
|
+
type: TYPES.array,
|
3553
|
+
length: 1
|
2800
3554
|
},
|
2801
3555
|
|
2802
3556
|
__Array_of: {
|
@@ -2807,27 +3561,134 @@ const internalConstrs = {
|
|
2807
3561
|
elements: decl.arguments
|
2808
3562
|
}, global, name);
|
2809
3563
|
},
|
2810
|
-
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,
|
2811
3586
|
notConstr: true
|
2812
|
-
}
|
2813
|
-
};
|
3587
|
+
},
|
2814
3588
|
|
2815
|
-
|
2816
|
-
|
2817
|
-
|
2818
|
-
// for (const x of arr) {
|
2819
|
-
// if (x === undefined) {
|
2820
|
-
// console.trace(arr);
|
2821
|
-
// process.exit();
|
2822
|
-
// }
|
2823
|
-
// if (Array.isArray(x)) check(x);
|
2824
|
-
// }
|
2825
|
-
// };
|
2826
|
-
// if (Array.isArray(a) && !new Error().stack.includes('node:')) check(a);
|
2827
|
-
// // if (Array.isArray(a)) check(a);
|
3589
|
+
__Porffor_fastAnd: {
|
3590
|
+
generate: (scope, decl) => {
|
3591
|
+
const out = [];
|
2828
3592
|
|
2829
|
-
|
2830
|
-
|
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
|
+
};
|
2831
3692
|
|
2832
3693
|
export default program => {
|
2833
3694
|
globals = {};
|
@@ -2837,20 +3698,23 @@ export default program => {
|
|
2837
3698
|
funcs = [];
|
2838
3699
|
funcIndex = {};
|
2839
3700
|
depth = [];
|
2840
|
-
arrays = new Map();
|
2841
3701
|
pages = new Map();
|
2842
3702
|
data = [];
|
2843
3703
|
currentFuncIndex = importedFuncs.length;
|
2844
3704
|
|
2845
3705
|
globalThis.valtype = 'f64';
|
2846
3706
|
|
2847
|
-
const valtypeOpt = process.argv.find(x => x.startsWith('
|
3707
|
+
const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
|
2848
3708
|
if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
|
2849
3709
|
|
2850
3710
|
globalThis.valtypeBinary = Valtype[valtype];
|
2851
3711
|
|
2852
3712
|
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
2853
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
|
+
|
2854
3718
|
// set generic opcodes for current valtype
|
2855
3719
|
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
2856
3720
|
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
@@ -2859,10 +3723,10 @@ export default program => {
|
|
2859
3723
|
Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
|
2860
3724
|
Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
|
2861
3725
|
|
2862
|
-
Opcodes.i32_to = [ [
|
2863
|
-
Opcodes.i32_to_u = [ [
|
2864
|
-
Opcodes.i32_from = [ [
|
2865
|
-
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];
|
2866
3730
|
|
2867
3731
|
Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
|
2868
3732
|
Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
|
@@ -2875,10 +3739,6 @@ export default program => {
|
|
2875
3739
|
|
2876
3740
|
program.id = { name: 'main' };
|
2877
3741
|
|
2878
|
-
globalThis.pageSize = PageSize;
|
2879
|
-
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
2880
|
-
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
2881
|
-
|
2882
3742
|
const scope = {
|
2883
3743
|
locals: {},
|
2884
3744
|
localInd: 0
|
@@ -2889,7 +3749,7 @@ export default program => {
|
|
2889
3749
|
body: program.body
|
2890
3750
|
};
|
2891
3751
|
|
2892
|
-
if (
|
3752
|
+
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
2893
3753
|
|
2894
3754
|
generateFunc(scope, program);
|
2895
3755
|
|
@@ -2906,7 +3766,11 @@ export default program => {
|
|
2906
3766
|
}
|
2907
3767
|
|
2908
3768
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
2909
|
-
|
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
|
+
}
|
2910
3774
|
}
|
2911
3775
|
|
2912
3776
|
if (lastInst[0] === Opcodes.call) {
|