porffor 0.2.0-fde989a → 0.14.0-032e4ad08
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 +262 -0
- package/LICENSE +20 -20
- package/README.md +135 -94
- package/asur/README.md +2 -0
- package/asur/index.js +1262 -0
- package/byg/index.js +216 -0
- package/compiler/2c.js +66 -54
- package/compiler/{sections.js → assemble.js} +109 -21
- package/compiler/builtins/annexb_string.js +72 -0
- package/compiler/builtins/annexb_string.ts +19 -0
- package/compiler/builtins/array.ts +225 -0
- package/compiler/builtins/base64.ts +77 -0
- package/compiler/builtins/boolean.ts +20 -0
- package/compiler/builtins/crypto.ts +121 -0
- package/compiler/builtins/date.ts +2069 -0
- package/compiler/builtins/error.js +22 -0
- package/compiler/builtins/escape.ts +140 -0
- package/compiler/builtins/function.ts +7 -0
- package/compiler/builtins/int.ts +147 -0
- package/compiler/builtins/math.ts +410 -0
- package/compiler/builtins/number.ts +531 -0
- package/compiler/builtins/object.ts +6 -0
- package/compiler/builtins/porffor.d.ts +60 -0
- package/compiler/builtins/set.ts +199 -0
- package/compiler/builtins/string.ts +1081 -0
- package/compiler/builtins/symbol.ts +62 -0
- package/compiler/builtins.js +466 -284
- package/compiler/{codeGen.js → codegen.js} +1573 -656
- package/compiler/decompile.js +3 -4
- package/compiler/embedding.js +22 -22
- package/compiler/encoding.js +94 -10
- package/compiler/expression.js +1 -1
- package/compiler/generated_builtins.js +2110 -0
- package/compiler/index.js +29 -44
- package/compiler/log.js +6 -3
- package/compiler/opt.js +55 -41
- package/compiler/parse.js +38 -30
- package/compiler/precompile.js +121 -0
- package/compiler/prefs.js +31 -0
- package/compiler/prototype.js +209 -201
- package/compiler/types.js +38 -0
- package/compiler/wasmSpec.js +33 -8
- package/compiler/wrap.js +154 -89
- package/package.json +9 -5
- package/porf +2 -0
- package/porffor_tmp.c +202 -0
- package/rhemyn/compile.js +46 -27
- package/rhemyn/parse.js +322 -320
- package/rhemyn/test/parse.js +58 -58
- package/runner/compare.js +33 -34
- package/runner/debug.js +117 -0
- package/runner/index.js +80 -12
- package/runner/profiler.js +75 -0
- package/runner/repl.js +58 -15
- package/runner/sizes.js +37 -37
- package/runner/version.js +10 -8
- package/compiler/builtins/base64.js +0 -92
- package/filesize.cmd +0 -2
- 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 -661
- package/util/enum.js +0 -20
@@ -1,12 +1,14 @@
|
|
1
|
-
import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from
|
2
|
-
import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from
|
3
|
-
import { operatorOpcode } from
|
4
|
-
import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from
|
5
|
-
import { PrototypeFuncs } from
|
6
|
-
import { number
|
7
|
-
import {
|
8
|
-
import
|
9
|
-
import
|
1
|
+
import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from './wasmSpec.js';
|
2
|
+
import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from './encoding.js';
|
3
|
+
import { operatorOpcode } from './expression.js';
|
4
|
+
import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from './builtins.js';
|
5
|
+
import { PrototypeFuncs } from './prototype.js';
|
6
|
+
import { number } from './embedding.js';
|
7
|
+
import { TYPES, TYPE_NAMES } from './types.js';
|
8
|
+
import * as Rhemyn from '../rhemyn/compile.js';
|
9
|
+
import parse from './parse.js';
|
10
|
+
import { log } from './log.js';
|
11
|
+
import Prefs from './prefs.js';
|
10
12
|
|
11
13
|
let globals = {};
|
12
14
|
let globalInd = 0;
|
@@ -17,44 +19,32 @@ let funcIndex = {};
|
|
17
19
|
let currentFuncIndex = importedFuncs.length;
|
18
20
|
let builtinFuncs = {}, builtinVars = {}, prototypeFuncs = {};
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
code.push(...number(n));
|
25
|
-
|
26
|
-
code.push(Opcodes.call);
|
27
|
-
code.push(...unsignedLEB128(0));
|
28
|
-
};
|
29
|
-
|
30
|
-
for (let i = 0; i < str.length; i++) {
|
31
|
-
logChar(str.charCodeAt(i));
|
22
|
+
class TodoError extends Error {
|
23
|
+
constructor(message) {
|
24
|
+
super(message);
|
25
|
+
this.name = 'TodoError';
|
32
26
|
}
|
27
|
+
}
|
28
|
+
const todo = (scope, msg, expectsValue = undefined) => {
|
29
|
+
switch (Prefs.todoTime ?? 'runtime') {
|
30
|
+
case 'compile':
|
31
|
+
throw new TodoError(msg);
|
33
32
|
|
34
|
-
|
35
|
-
|
36
|
-
return code;
|
37
|
-
};
|
33
|
+
case 'runtime':
|
34
|
+
return internalThrow(scope, 'TodoError', msg, expectsValue);
|
38
35
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
this.name = 'TodoError';
|
44
|
-
}
|
36
|
+
// return [
|
37
|
+
// ...debug(`todo! ${msg}`),
|
38
|
+
// [ Opcodes.unreachable ]
|
39
|
+
// ];
|
45
40
|
}
|
46
|
-
|
47
|
-
throw new TodoError(`todo: ${msg}`);
|
48
|
-
|
49
|
-
const code = [];
|
50
|
-
|
51
|
-
code.push(...debug(`todo! ` + msg));
|
52
|
-
code.push(Opcodes.unreachable);
|
53
|
-
|
54
|
-
return code;
|
55
41
|
};
|
56
42
|
|
57
|
-
const isFuncType = type =>
|
43
|
+
const isFuncType = type =>
|
44
|
+
type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
|
45
|
+
const hasFuncWithName = name =>
|
46
|
+
funcIndex[name] != null || builtinFuncs[name] != null || importedFuncs[name] != null || internalConstrs[name] != null;
|
47
|
+
|
58
48
|
const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
|
59
49
|
switch (decl.type) {
|
60
50
|
case 'BinaryExpression':
|
@@ -68,10 +58,11 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
68
58
|
|
69
59
|
case 'ArrowFunctionExpression':
|
70
60
|
case 'FunctionDeclaration':
|
61
|
+
case 'FunctionExpression':
|
71
62
|
const func = generateFunc(scope, decl);
|
72
63
|
|
73
64
|
if (decl.type.endsWith('Expression')) {
|
74
|
-
return number(func.index);
|
65
|
+
return number(func.index - importedFuncs.length);
|
75
66
|
}
|
76
67
|
|
77
68
|
return [];
|
@@ -104,7 +95,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
104
95
|
return generateUnary(scope, decl);
|
105
96
|
|
106
97
|
case 'UpdateExpression':
|
107
|
-
return generateUpdate(scope, decl);
|
98
|
+
return generateUpdate(scope, decl, global, name, valueUnused);
|
108
99
|
|
109
100
|
case 'IfStatement':
|
110
101
|
return generateIf(scope, decl);
|
@@ -115,6 +106,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
115
106
|
case 'WhileStatement':
|
116
107
|
return generateWhile(scope, decl);
|
117
108
|
|
109
|
+
case 'DoWhileStatement':
|
110
|
+
return generateDoWhile(scope, decl);
|
111
|
+
|
118
112
|
case 'ForOfStatement':
|
119
113
|
return generateForOf(scope, decl);
|
120
114
|
|
@@ -124,6 +118,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
124
118
|
case 'ContinueStatement':
|
125
119
|
return generateContinue(scope, decl);
|
126
120
|
|
121
|
+
case 'LabeledStatement':
|
122
|
+
return generateLabel(scope, decl);
|
123
|
+
|
127
124
|
case 'EmptyStatement':
|
128
125
|
return generateEmpty(scope, decl);
|
129
126
|
|
@@ -137,30 +134,36 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
137
134
|
return generateTry(scope, decl);
|
138
135
|
|
139
136
|
case 'DebuggerStatement':
|
140
|
-
// todo:
|
137
|
+
// todo: hook into terminal debugger
|
141
138
|
return [];
|
142
139
|
|
143
140
|
case 'ArrayExpression':
|
144
141
|
return generateArray(scope, decl, global, name);
|
145
142
|
|
143
|
+
case 'ObjectExpression':
|
144
|
+
return generateObject(scope, decl, global, name);
|
145
|
+
|
146
146
|
case 'MemberExpression':
|
147
147
|
return generateMember(scope, decl, global, name);
|
148
148
|
|
149
149
|
case 'ExportNamedDeclaration':
|
150
|
-
|
151
|
-
const funcsBefore = funcs.length;
|
150
|
+
const funcsBefore = funcs.map(x => x.name);
|
152
151
|
generate(scope, decl.declaration);
|
153
152
|
|
154
|
-
|
153
|
+
// set new funcs as exported
|
154
|
+
if (funcsBefore.length !== funcs.length) {
|
155
|
+
const newFuncs = funcs.filter(x => !funcsBefore.includes(x.name)).filter(x => !x.internal);
|
155
156
|
|
156
|
-
|
157
|
-
|
157
|
+
for (const x of newFuncs) {
|
158
|
+
x.export = true;
|
159
|
+
}
|
160
|
+
}
|
158
161
|
|
159
162
|
return [];
|
160
163
|
|
161
164
|
case 'TaggedTemplateExpression': {
|
162
165
|
const funcs = {
|
163
|
-
|
166
|
+
__Porffor_wasm: str => {
|
164
167
|
let out = [];
|
165
168
|
|
166
169
|
for (const line of str.split('\n')) {
|
@@ -168,8 +171,8 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
168
171
|
if (asm[0] === '') continue; // blank
|
169
172
|
|
170
173
|
if (asm[0] === 'local') {
|
171
|
-
const [ name,
|
172
|
-
scope.locals[name] = { idx:
|
174
|
+
const [ name, type ] = asm.slice(1);
|
175
|
+
scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
|
173
176
|
continue;
|
174
177
|
}
|
175
178
|
|
@@ -179,52 +182,66 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
179
182
|
}
|
180
183
|
|
181
184
|
if (asm[0] === 'memory') {
|
182
|
-
allocPage('asm instrinsic');
|
185
|
+
allocPage(scope, 'asm instrinsic');
|
183
186
|
// todo: add to store/load offset insts
|
184
187
|
continue;
|
185
188
|
}
|
186
189
|
|
187
190
|
let inst = Opcodes[asm[0].replace('.', '_')];
|
188
|
-
if (
|
191
|
+
if (inst == null) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
189
192
|
|
190
193
|
if (!Array.isArray(inst)) inst = [ inst ];
|
191
|
-
const immediates = asm.slice(1).map(x =>
|
194
|
+
const immediates = asm.slice(1).map(x => {
|
195
|
+
const int = parseInt(x);
|
196
|
+
if (Number.isNaN(int)) return scope.locals[x]?.idx ?? globals[x].idx;
|
197
|
+
return int;
|
198
|
+
});
|
192
199
|
|
193
|
-
out.push([ ...inst, ...immediates ]);
|
200
|
+
out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
|
194
201
|
}
|
195
202
|
|
196
203
|
return out;
|
197
204
|
},
|
198
205
|
|
199
206
|
__Porffor_bs: str => [
|
200
|
-
...makeString(scope, str,
|
201
|
-
|
202
|
-
...number(TYPES._bytestring, Valtype.i32),
|
203
|
-
setLastType(scope)
|
207
|
+
...makeString(scope, str, global, name, true),
|
208
|
+
...(name ? setType(scope, name, TYPES.bytestring) : setLastType(scope, TYPES.bytestring))
|
204
209
|
],
|
205
210
|
__Porffor_s: str => [
|
206
|
-
...makeString(scope, str,
|
207
|
-
|
208
|
-
...number(TYPES.string, Valtype.i32),
|
209
|
-
setLastType(scope)
|
211
|
+
...makeString(scope, str, global, name, false),
|
212
|
+
...(name ? setType(scope, name, TYPES.string) : setLastType(scope, TYPES.string))
|
210
213
|
],
|
211
214
|
};
|
212
215
|
|
213
|
-
const
|
216
|
+
const func = decl.tag.name;
|
214
217
|
// hack for inline asm
|
215
|
-
if (!funcs[
|
218
|
+
if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
|
219
|
+
|
220
|
+
const { quasis, expressions } = decl.quasi;
|
221
|
+
let str = quasis[0].value.raw;
|
222
|
+
|
223
|
+
for (let i = 0; i < expressions.length; i++) {
|
224
|
+
const e = expressions[i];
|
225
|
+
if (!e.name) {
|
226
|
+
if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
|
227
|
+
str += lookupName(scope, e.left.name)[0].idx + e.right.value;
|
228
|
+
} else todo(scope, 'unsupported expression in intrinsic');
|
229
|
+
} else str += lookupName(scope, e.name)[0].idx;
|
230
|
+
|
231
|
+
str += quasis[i + 1].value.raw;
|
232
|
+
}
|
216
233
|
|
217
|
-
|
218
|
-
return funcs[name](str);
|
234
|
+
return funcs[func](str);
|
219
235
|
}
|
220
236
|
|
221
237
|
default:
|
222
|
-
|
223
|
-
|
238
|
+
// ignore typescript nodes
|
239
|
+
if (decl.type.startsWith('TS') ||
|
240
|
+
decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
|
224
241
|
return [];
|
225
242
|
}
|
226
243
|
|
227
|
-
return todo(`no generation for ${decl.type}!`);
|
244
|
+
return todo(scope, `no generation for ${decl.type}!`);
|
228
245
|
}
|
229
246
|
};
|
230
247
|
|
@@ -252,7 +269,7 @@ const lookupName = (scope, _name) => {
|
|
252
269
|
return [ undefined, undefined ];
|
253
270
|
};
|
254
271
|
|
255
|
-
const internalThrow = (scope, constructor, message, expectsValue =
|
272
|
+
const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
|
256
273
|
...generateThrow(scope, {
|
257
274
|
argument: {
|
258
275
|
type: 'NewExpression',
|
@@ -276,7 +293,10 @@ const generateIdent = (scope, decl) => {
|
|
276
293
|
|
277
294
|
if (Object.hasOwn(builtinVars, name)) {
|
278
295
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
279
|
-
|
296
|
+
|
297
|
+
let wasm = builtinVars[name];
|
298
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
|
299
|
+
return wasm.slice();
|
280
300
|
}
|
281
301
|
|
282
302
|
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
@@ -284,12 +304,17 @@ const generateIdent = (scope, decl) => {
|
|
284
304
|
return number(1);
|
285
305
|
}
|
286
306
|
|
307
|
+
if (isExistingProtoFunc(name)) {
|
308
|
+
// todo: return an actual something
|
309
|
+
return number(1);
|
310
|
+
}
|
311
|
+
|
287
312
|
if (local?.idx === undefined) {
|
288
313
|
// no local var with name
|
289
|
-
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
290
|
-
if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
|
291
|
-
|
292
314
|
if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
|
315
|
+
|
316
|
+
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name] - importedFuncs.length);
|
317
|
+
if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name] - importedFuncs.length);
|
293
318
|
}
|
294
319
|
|
295
320
|
if (local?.idx === undefined && rawName.startsWith('__')) {
|
@@ -314,14 +339,16 @@ const generateReturn = (scope, decl) => {
|
|
314
339
|
// just bare "return"
|
315
340
|
return [
|
316
341
|
...number(UNDEFINED), // "undefined" if func returns
|
317
|
-
...
|
342
|
+
...(scope.returnType != null ? [] : [
|
343
|
+
...number(TYPES.undefined, Valtype.i32) // type undefined
|
344
|
+
]),
|
318
345
|
[ Opcodes.return ]
|
319
346
|
];
|
320
347
|
}
|
321
348
|
|
322
349
|
return [
|
323
350
|
...generate(scope, decl.argument),
|
324
|
-
...getNodeType(scope, decl.argument),
|
351
|
+
...(scope.returnType != null ? [] : getNodeType(scope, decl.argument)),
|
325
352
|
[ Opcodes.return ]
|
326
353
|
];
|
327
354
|
};
|
@@ -335,7 +362,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
335
362
|
return idx;
|
336
363
|
};
|
337
364
|
|
338
|
-
const isIntOp = op => op && (op[0] >=
|
365
|
+
const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
|
366
|
+
const isIntToFloatOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
|
339
367
|
|
340
368
|
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
341
369
|
const checks = {
|
@@ -344,7 +372,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
344
372
|
'??': nullish
|
345
373
|
};
|
346
374
|
|
347
|
-
if (!checks[op]) return todo(`logic operator ${op} not implemented yet
|
375
|
+
if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
|
348
376
|
|
349
377
|
// generic structure for {a} OP {b}
|
350
378
|
// -->
|
@@ -352,10 +380,10 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
352
380
|
|
353
381
|
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
354
382
|
// (like if we are in an if condition - very common)
|
355
|
-
const
|
356
|
-
const
|
383
|
+
const leftWasInt = isIntToFloatOp(left[left.length - 1]);
|
384
|
+
const rightWasInt = isIntToFloatOp(right[right.length - 1]);
|
357
385
|
|
358
|
-
const canInt =
|
386
|
+
const canInt = leftWasInt && rightWasInt;
|
359
387
|
|
360
388
|
if (canInt) {
|
361
389
|
// remove int -> float conversions from left and right
|
@@ -369,13 +397,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
369
397
|
[ Opcodes.if, Valtype.i32 ],
|
370
398
|
...right,
|
371
399
|
// note type
|
372
|
-
...rightType,
|
373
|
-
setLastType(scope),
|
400
|
+
...setLastType(scope, rightType),
|
374
401
|
[ Opcodes.else ],
|
375
402
|
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
376
403
|
// note type
|
377
|
-
...leftType,
|
378
|
-
setLastType(scope),
|
404
|
+
...setLastType(scope, leftType),
|
379
405
|
[ Opcodes.end ],
|
380
406
|
Opcodes.i32_from
|
381
407
|
];
|
@@ -388,18 +414,16 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
388
414
|
[ Opcodes.if, valtypeBinary ],
|
389
415
|
...right,
|
390
416
|
// note type
|
391
|
-
...rightType,
|
392
|
-
setLastType(scope),
|
417
|
+
...setLastType(scope, rightType),
|
393
418
|
[ Opcodes.else ],
|
394
419
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
395
420
|
// note type
|
396
|
-
...leftType,
|
397
|
-
setLastType(scope),
|
421
|
+
...setLastType(scope, leftType),
|
398
422
|
[ Opcodes.end ]
|
399
423
|
];
|
400
424
|
};
|
401
425
|
|
402
|
-
const concatStrings = (scope, left, right, global, name, assign) => {
|
426
|
+
const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
|
403
427
|
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
404
428
|
// todo: convert left and right to strings if not
|
405
429
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -409,11 +433,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
409
433
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
410
434
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
411
435
|
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
if (assign) {
|
416
|
-
const pointer = arrays.get(name ?? '$undeclared');
|
436
|
+
if (assign && Prefs.aotPointerOpt) {
|
437
|
+
const pointer = scope.arrays?.get(name ?? '$undeclared');
|
417
438
|
|
418
439
|
return [
|
419
440
|
// setup right
|
@@ -425,11 +446,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
425
446
|
...number(0, Valtype.i32), // base 0 for store later
|
426
447
|
|
427
448
|
...number(pointer, Valtype.i32),
|
428
|
-
[ Opcodes.i32_load,
|
449
|
+
[ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
|
429
450
|
[ Opcodes.local_tee, leftLength ],
|
430
451
|
|
431
452
|
[ Opcodes.local_get, rightPointer ],
|
432
|
-
[ Opcodes.i32_load,
|
453
|
+
[ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
|
433
454
|
[ Opcodes.local_tee, rightLength ],
|
434
455
|
|
435
456
|
[ Opcodes.i32_add ],
|
@@ -438,11 +459,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
438
459
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
|
439
460
|
|
440
461
|
// copy right
|
441
|
-
// dst = out pointer + length size + current length *
|
462
|
+
// dst = out pointer + length size + current length * sizeof valtype
|
442
463
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
443
464
|
|
444
465
|
[ Opcodes.local_get, leftLength ],
|
445
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
466
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
446
467
|
[ Opcodes.i32_mul ],
|
447
468
|
[ Opcodes.i32_add ],
|
448
469
|
|
@@ -451,9 +472,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
451
472
|
...number(ValtypeSize.i32, Valtype.i32),
|
452
473
|
[ Opcodes.i32_add ],
|
453
474
|
|
454
|
-
// size = right length *
|
475
|
+
// size = right length * sizeof valtype
|
455
476
|
[ Opcodes.local_get, rightLength ],
|
456
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
477
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
457
478
|
[ Opcodes.i32_mul ],
|
458
479
|
|
459
480
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -485,11 +506,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
485
506
|
...number(0, Valtype.i32), // base 0 for store later
|
486
507
|
|
487
508
|
[ Opcodes.local_get, leftPointer ],
|
488
|
-
[ Opcodes.i32_load,
|
509
|
+
[ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
|
489
510
|
[ Opcodes.local_tee, leftLength ],
|
490
511
|
|
491
512
|
[ Opcodes.local_get, rightPointer ],
|
492
|
-
[ Opcodes.i32_load,
|
513
|
+
[ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
|
493
514
|
[ Opcodes.local_tee, rightLength ],
|
494
515
|
|
495
516
|
[ Opcodes.i32_add ],
|
@@ -511,11 +532,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
511
532
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
512
533
|
|
513
534
|
// copy right
|
514
|
-
// dst = out pointer + length size + left length *
|
535
|
+
// dst = out pointer + length size + left length * sizeof valtype
|
515
536
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
516
537
|
|
517
538
|
[ Opcodes.local_get, leftLength ],
|
518
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
539
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
519
540
|
[ Opcodes.i32_mul ],
|
520
541
|
[ Opcodes.i32_add ],
|
521
542
|
|
@@ -524,9 +545,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
524
545
|
...number(ValtypeSize.i32, Valtype.i32),
|
525
546
|
[ Opcodes.i32_add ],
|
526
547
|
|
527
|
-
// size = right length *
|
548
|
+
// size = right length * sizeof valtype
|
528
549
|
[ Opcodes.local_get, rightLength ],
|
529
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
550
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
530
551
|
[ Opcodes.i32_mul ],
|
531
552
|
|
532
553
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -536,7 +557,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
536
557
|
];
|
537
558
|
};
|
538
559
|
|
539
|
-
const compareStrings = (scope, left, right) => {
|
560
|
+
const compareStrings = (scope, left, right, bytestrings = false) => {
|
540
561
|
// todo: this should be rewritten into a func
|
541
562
|
// todo: convert left and right to strings if not
|
542
563
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -545,7 +566,6 @@ const compareStrings = (scope, left, right) => {
|
|
545
566
|
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
546
567
|
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
547
568
|
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
548
|
-
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
549
569
|
|
550
570
|
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
551
571
|
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
@@ -568,12 +588,11 @@ const compareStrings = (scope, left, right) => {
|
|
568
588
|
|
569
589
|
// get lengths
|
570
590
|
[ Opcodes.local_get, leftPointer ],
|
571
|
-
[ Opcodes.i32_load,
|
591
|
+
[ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
|
572
592
|
[ Opcodes.local_tee, leftLength ],
|
573
593
|
|
574
594
|
[ Opcodes.local_get, rightPointer ],
|
575
|
-
[ Opcodes.i32_load,
|
576
|
-
[ Opcodes.local_tee, rightLength ],
|
595
|
+
[ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
|
577
596
|
|
578
597
|
// fast path: check leftLength != rightLength
|
579
598
|
[ Opcodes.i32_ne ],
|
@@ -588,11 +607,13 @@ const compareStrings = (scope, left, right) => {
|
|
588
607
|
...number(0, Valtype.i32),
|
589
608
|
[ Opcodes.local_set, index ],
|
590
609
|
|
591
|
-
// setup index end as length * sizeof
|
610
|
+
// setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
|
592
611
|
// we do this instead of having to do mul/div each iter for perf™
|
593
612
|
[ Opcodes.local_get, leftLength ],
|
594
|
-
...
|
595
|
-
|
613
|
+
...(bytestrings ? [] : [
|
614
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
615
|
+
[ Opcodes.i32_mul ],
|
616
|
+
]),
|
596
617
|
[ Opcodes.local_set, indexEnd ],
|
597
618
|
|
598
619
|
// iterate over each char and check if eq
|
@@ -602,13 +623,17 @@ const compareStrings = (scope, left, right) => {
|
|
602
623
|
[ Opcodes.local_get, index ],
|
603
624
|
[ Opcodes.local_get, leftPointer ],
|
604
625
|
[ Opcodes.i32_add ],
|
605
|
-
|
626
|
+
bytestrings ?
|
627
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
628
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
606
629
|
|
607
630
|
// fetch right
|
608
631
|
[ Opcodes.local_get, index ],
|
609
632
|
[ Opcodes.local_get, rightPointer ],
|
610
633
|
[ Opcodes.i32_add ],
|
611
|
-
|
634
|
+
bytestrings ?
|
635
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
636
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
612
637
|
|
613
638
|
// not equal, "return" false
|
614
639
|
[ Opcodes.i32_ne ],
|
@@ -617,15 +642,15 @@ const compareStrings = (scope, left, right) => {
|
|
617
642
|
[ Opcodes.br, 2 ],
|
618
643
|
[ Opcodes.end ],
|
619
644
|
|
620
|
-
// index += sizeof
|
645
|
+
// index += sizeof valtype (1 for bytestring, 2 for string)
|
621
646
|
[ Opcodes.local_get, index ],
|
622
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
647
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
623
648
|
[ Opcodes.i32_add ],
|
624
649
|
[ Opcodes.local_tee, index ],
|
625
650
|
|
626
|
-
// if index
|
651
|
+
// if index < index end (length * sizeof valtype), loop
|
627
652
|
[ Opcodes.local_get, indexEnd ],
|
628
|
-
[ Opcodes.
|
653
|
+
[ Opcodes.i32_lt_s ],
|
629
654
|
[ Opcodes.br_if, 0 ],
|
630
655
|
[ Opcodes.end ],
|
631
656
|
|
@@ -643,38 +668,52 @@ const compareStrings = (scope, left, right) => {
|
|
643
668
|
];
|
644
669
|
};
|
645
670
|
|
646
|
-
const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
647
|
-
if (
|
671
|
+
const truthy = (scope, wasm, type, intIn = false, intOut = false, forceTruthyMode = undefined) => {
|
672
|
+
if (isIntToFloatOp(wasm[wasm.length - 1])) return [
|
648
673
|
...wasm,
|
649
674
|
...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
|
650
675
|
];
|
676
|
+
// if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
|
651
677
|
|
652
|
-
|
678
|
+
// todo/perf: use knownType and custom bytecode here instead of typeSwitch
|
653
679
|
|
654
|
-
const
|
655
|
-
|
656
|
-
[ Opcodes.local_get, tmp ],
|
680
|
+
const useTmp = knownType(scope, type) == null;
|
681
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
657
682
|
|
658
|
-
|
659
|
-
|
683
|
+
const def = (truthyMode => {
|
684
|
+
if (truthyMode === 'full') return [
|
685
|
+
// if value != 0 or NaN
|
686
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
687
|
+
...(intIn ? [ ] : [ Opcodes.i32_to ]),
|
660
688
|
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
689
|
+
[ Opcodes.i32_eqz ],
|
690
|
+
[ Opcodes.i32_eqz ],
|
691
|
+
|
692
|
+
...(intOut ? [] : [ Opcodes.i32_from ]),
|
693
|
+
];
|
694
|
+
|
695
|
+
if (truthyMode === 'no_negative') return [
|
696
|
+
// if value != 0 or NaN, non-binary output. negative numbers not truthy :/
|
697
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
698
|
+
...(intIn ? [] : [ Opcodes.i32_to ]),
|
699
|
+
...(intOut ? [] : [ Opcodes.i32_from ])
|
700
|
+
];
|
701
|
+
|
702
|
+
if (truthyMode === 'no_nan_negative') return [
|
703
|
+
// simpler and faster but makes NaN truthy and negative numbers not truthy,
|
704
|
+
// plus non-binary output
|
705
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
706
|
+
...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ])
|
707
|
+
];
|
708
|
+
})(forceTruthyMode ?? Prefs.truthy ?? 'full');
|
665
709
|
|
666
710
|
return [
|
667
711
|
...wasm,
|
668
|
-
[ Opcodes.local_set, tmp ],
|
712
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
669
713
|
|
670
714
|
...typeSwitch(scope, type, {
|
671
|
-
// [TYPES.number]: def,
|
672
|
-
[TYPES._array]: [
|
673
|
-
// arrays are always truthy
|
674
|
-
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
675
|
-
],
|
676
715
|
[TYPES.string]: [
|
677
|
-
[ Opcodes.local_get, tmp ],
|
716
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
678
717
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
679
718
|
|
680
719
|
// get length
|
@@ -685,8 +724,8 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
685
724
|
[ Opcodes.i32_eqz ], */
|
686
725
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
687
726
|
],
|
688
|
-
[TYPES.
|
689
|
-
|
727
|
+
[TYPES.bytestring]: [ // duplicate of string
|
728
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
690
729
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
691
730
|
|
692
731
|
// get length
|
@@ -700,18 +739,16 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
700
739
|
};
|
701
740
|
|
702
741
|
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
703
|
-
const
|
742
|
+
const useTmp = knownType(scope, type) == null;
|
743
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
744
|
+
|
704
745
|
return [
|
705
746
|
...wasm,
|
706
|
-
[ Opcodes.local_set, tmp ],
|
747
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
707
748
|
|
708
749
|
...typeSwitch(scope, type, {
|
709
|
-
[TYPES._array]: [
|
710
|
-
// arrays are always truthy
|
711
|
-
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
712
|
-
],
|
713
750
|
[TYPES.string]: [
|
714
|
-
[ Opcodes.local_get, tmp ],
|
751
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
715
752
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
716
753
|
|
717
754
|
// get length
|
@@ -721,8 +758,8 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
721
758
|
[ Opcodes.i32_eqz ],
|
722
759
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
723
760
|
],
|
724
|
-
[TYPES.
|
725
|
-
[ Opcodes.local_get, tmp ],
|
761
|
+
[TYPES.bytestring]: [ // duplicate of string
|
762
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
726
763
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
727
764
|
|
728
765
|
// get length
|
@@ -734,7 +771,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
734
771
|
],
|
735
772
|
default: [
|
736
773
|
// if value == 0
|
737
|
-
[ Opcodes.local_get, tmp ],
|
774
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
738
775
|
|
739
776
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
740
777
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -744,10 +781,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
744
781
|
};
|
745
782
|
|
746
783
|
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
747
|
-
const
|
784
|
+
const useTmp = knownType(scope, type) == null;
|
785
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
786
|
+
|
748
787
|
return [
|
749
788
|
...wasm,
|
750
|
-
[ Opcodes.local_set, tmp ],
|
789
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
751
790
|
|
752
791
|
...typeSwitch(scope, type, {
|
753
792
|
[TYPES.undefined]: [
|
@@ -756,7 +795,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
756
795
|
],
|
757
796
|
[TYPES.object]: [
|
758
797
|
// object, null if == 0
|
759
|
-
[ Opcodes.local_get, tmp ],
|
798
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
760
799
|
|
761
800
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
762
801
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -785,39 +824,17 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
785
824
|
return performLogicOp(scope, op, left, right, leftType, rightType);
|
786
825
|
}
|
787
826
|
|
827
|
+
const knownLeft = knownType(scope, leftType);
|
828
|
+
const knownRight = knownType(scope, rightType);
|
829
|
+
|
788
830
|
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
789
831
|
const strictOp = op === '===' || op === '!==';
|
790
832
|
|
791
833
|
const startOut = [], endOut = [];
|
792
|
-
const
|
834
|
+
const finalize = out => startOut.concat(out, endOut);
|
793
835
|
|
794
836
|
// if strict (in)equal check types match
|
795
837
|
if (strictOp) {
|
796
|
-
// startOut.push(
|
797
|
-
// ...leftType,
|
798
|
-
// ...rightType,
|
799
|
-
// [ Opcodes.i32_eq ]
|
800
|
-
// );
|
801
|
-
|
802
|
-
// endOut.push(
|
803
|
-
// [ Opcodes.i32_and ]
|
804
|
-
// );
|
805
|
-
|
806
|
-
// startOut.push(
|
807
|
-
// [ Opcodes.block, Valtype.i32 ],
|
808
|
-
// ...leftType,
|
809
|
-
// ...rightType,
|
810
|
-
// [ Opcodes.i32_ne ],
|
811
|
-
// [ Opcodes.if, Blocktype.void ],
|
812
|
-
// ...number(op === '===' ? 0 : 1, Valtype.i32),
|
813
|
-
// [ Opcodes.br, 1 ],
|
814
|
-
// [ Opcodes.end ]
|
815
|
-
// );
|
816
|
-
|
817
|
-
// endOut.push(
|
818
|
-
// [ Opcodes.end ]
|
819
|
-
// );
|
820
|
-
|
821
838
|
endOut.push(
|
822
839
|
...leftType,
|
823
840
|
...rightType,
|
@@ -834,31 +851,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
834
851
|
// todo: if equality op and an operand is undefined, return false
|
835
852
|
// todo: niche null hell with 0
|
836
853
|
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
854
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) {
|
855
|
+
if (op === '+') {
|
856
|
+
// todo: this should be dynamic too but for now only static
|
857
|
+
// string concat (a + b)
|
858
|
+
return concatStrings(scope, left, right, _global, _name, assign, false);
|
859
|
+
}
|
860
|
+
|
861
|
+
// not an equality op, NaN
|
862
|
+
if (!eqOp) return number(NaN);
|
863
|
+
|
864
|
+
// else leave bool ops
|
865
|
+
// todo: convert string to number if string and number/bool
|
866
|
+
// todo: string (>|>=|<|<=) string
|
867
|
+
|
868
|
+
// string comparison
|
869
|
+
if (op === '===' || op === '==') {
|
870
|
+
return compareStrings(scope, left, right);
|
871
|
+
}
|
872
|
+
|
873
|
+
if (op === '!==' || op === '!=') {
|
874
|
+
return [
|
875
|
+
...compareStrings(scope, left, right),
|
876
|
+
[ Opcodes.i32_eqz ]
|
877
|
+
];
|
878
|
+
}
|
879
|
+
}
|
880
|
+
|
881
|
+
if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) {
|
882
|
+
if (op === '+') {
|
883
|
+
// todo: this should be dynamic too but for now only static
|
884
|
+
// string concat (a + b)
|
885
|
+
return concatStrings(scope, left, right, _global, _name, assign, true);
|
886
|
+
}
|
887
|
+
|
888
|
+
// not an equality op, NaN
|
889
|
+
if (!eqOp) return number(NaN);
|
890
|
+
|
891
|
+
// else leave bool ops
|
892
|
+
// todo: convert string to number if string and number/bool
|
893
|
+
// todo: string (>|>=|<|<=) string
|
894
|
+
|
895
|
+
// string comparison
|
896
|
+
if (op === '===' || op === '==') {
|
897
|
+
return compareStrings(scope, left, right, true);
|
898
|
+
}
|
899
|
+
|
900
|
+
if (op === '!==' || op === '!=') {
|
901
|
+
return [
|
902
|
+
...compareStrings(scope, left, right, true),
|
903
|
+
[ Opcodes.i32_eqz ]
|
904
|
+
];
|
905
|
+
}
|
906
|
+
}
|
862
907
|
|
863
908
|
let ops = operatorOpcode[valtype][op];
|
864
909
|
|
@@ -868,33 +913,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
868
913
|
includeBuiltin(scope, builtinName);
|
869
914
|
const idx = funcIndex[builtinName];
|
870
915
|
|
871
|
-
return
|
916
|
+
return finalize([
|
872
917
|
...left,
|
873
918
|
...right,
|
874
919
|
[ Opcodes.call, idx ]
|
875
920
|
]);
|
876
921
|
}
|
877
922
|
|
878
|
-
if (!ops) return todo(`operator ${op} not implemented yet
|
923
|
+
if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
|
879
924
|
|
880
925
|
if (!Array.isArray(ops)) ops = [ ops ];
|
881
926
|
ops = [ ops ];
|
882
927
|
|
883
928
|
let tmpLeft, tmpRight;
|
884
929
|
// if equal op, check if strings for compareStrings
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
// todo: intelligent partial skip later
|
890
|
-
// if neither known are string, stop this madness
|
891
|
-
if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
|
892
|
-
return;
|
893
|
-
}
|
930
|
+
// todo: intelligent partial skip later
|
931
|
+
// if neither known are string, stop this madness
|
932
|
+
// we already do known checks earlier, so don't need to recheck
|
894
933
|
|
934
|
+
if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
|
895
935
|
tmpLeft = localTmp(scope, '__tmpop_left');
|
896
936
|
tmpRight = localTmp(scope, '__tmpop_right');
|
897
937
|
|
938
|
+
// returns false for one string, one not - but more ops/slower
|
939
|
+
// ops.unshift(...stringOnly([
|
940
|
+
// // if left is string
|
941
|
+
// ...leftType,
|
942
|
+
// ...number(TYPES.string, Valtype.i32),
|
943
|
+
// [ Opcodes.i32_eq ],
|
944
|
+
|
945
|
+
// // if right is string
|
946
|
+
// ...rightType,
|
947
|
+
// ...number(TYPES.string, Valtype.i32),
|
948
|
+
// [ Opcodes.i32_eq ],
|
949
|
+
|
950
|
+
// // if either are true
|
951
|
+
// [ Opcodes.i32_or ],
|
952
|
+
// [ Opcodes.if, Blocktype.void ],
|
953
|
+
|
954
|
+
// // todo: convert non-strings to strings, for now fail immediately if one is not
|
955
|
+
// // if left is not string
|
956
|
+
// ...leftType,
|
957
|
+
// ...number(TYPES.string, Valtype.i32),
|
958
|
+
// [ Opcodes.i32_ne ],
|
959
|
+
|
960
|
+
// // if right is not string
|
961
|
+
// ...rightType,
|
962
|
+
// ...number(TYPES.string, Valtype.i32),
|
963
|
+
// [ Opcodes.i32_ne ],
|
964
|
+
|
965
|
+
// // if either are true
|
966
|
+
// [ Opcodes.i32_or ],
|
967
|
+
// [ Opcodes.if, Blocktype.void ],
|
968
|
+
// ...number(0, Valtype.i32),
|
969
|
+
// [ Opcodes.br, 2 ],
|
970
|
+
// [ Opcodes.end ],
|
971
|
+
|
972
|
+
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
973
|
+
// ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
974
|
+
// [ Opcodes.br, 1 ],
|
975
|
+
// [ Opcodes.end ],
|
976
|
+
// ]));
|
977
|
+
|
978
|
+
// does not handle one string, one not (such cases go past)
|
898
979
|
ops.unshift(...stringOnly([
|
899
980
|
// if left is string
|
900
981
|
...leftType,
|
@@ -906,30 +987,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
906
987
|
...number(TYPES.string, Valtype.i32),
|
907
988
|
[ Opcodes.i32_eq ],
|
908
989
|
|
909
|
-
// if
|
910
|
-
[ Opcodes.
|
990
|
+
// if both are true
|
991
|
+
[ Opcodes.i32_and ],
|
911
992
|
[ Opcodes.if, Blocktype.void ],
|
993
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], false),
|
994
|
+
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
995
|
+
[ Opcodes.br, 1 ],
|
996
|
+
[ Opcodes.end ],
|
912
997
|
|
913
|
-
//
|
914
|
-
// if left is not string
|
998
|
+
// if left is bytestring
|
915
999
|
...leftType,
|
916
|
-
...number(TYPES.
|
917
|
-
[ Opcodes.
|
1000
|
+
...number(TYPES.bytestring, Valtype.i32),
|
1001
|
+
[ Opcodes.i32_eq ],
|
918
1002
|
|
919
|
-
// if right is
|
1003
|
+
// if right is bytestring
|
920
1004
|
...rightType,
|
921
|
-
...number(TYPES.
|
922
|
-
[ Opcodes.
|
1005
|
+
...number(TYPES.bytestring, Valtype.i32),
|
1006
|
+
[ Opcodes.i32_eq ],
|
923
1007
|
|
924
|
-
// if
|
925
|
-
[ Opcodes.
|
1008
|
+
// if both are true
|
1009
|
+
[ Opcodes.i32_and ],
|
926
1010
|
[ Opcodes.if, Blocktype.void ],
|
927
|
-
...
|
928
|
-
[ Opcodes.br, 2 ],
|
929
|
-
[ Opcodes.end ],
|
930
|
-
|
931
|
-
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
932
|
-
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1011
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
|
933
1012
|
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
934
1013
|
[ Opcodes.br, 1 ],
|
935
1014
|
[ Opcodes.end ],
|
@@ -941,9 +1020,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
941
1020
|
// endOut.push(stringOnly([ Opcodes.end ]));
|
942
1021
|
endOut.unshift(stringOnly([ Opcodes.end ]));
|
943
1022
|
// }
|
944
|
-
}
|
1023
|
+
}
|
945
1024
|
|
946
|
-
return
|
1025
|
+
return finalize([
|
947
1026
|
...left,
|
948
1027
|
...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
|
949
1028
|
...right,
|
@@ -960,7 +1039,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
960
1039
|
return out;
|
961
1040
|
};
|
962
1041
|
|
963
|
-
const
|
1042
|
+
const asmFuncToAsm = (func, scope) => {
|
1043
|
+
return func(scope, {
|
1044
|
+
TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
|
1045
|
+
builtin: n => {
|
1046
|
+
let idx = funcIndex[n] ?? importedFuncs[n];
|
1047
|
+
if (idx == null && builtinFuncs[n]) {
|
1048
|
+
includeBuiltin(null, n);
|
1049
|
+
idx = funcIndex[n];
|
1050
|
+
}
|
1051
|
+
|
1052
|
+
return idx;
|
1053
|
+
}
|
1054
|
+
});
|
1055
|
+
};
|
1056
|
+
|
1057
|
+
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false }) => {
|
964
1058
|
const existing = funcs.find(x => x.name === name);
|
965
1059
|
if (existing) return existing;
|
966
1060
|
|
@@ -972,18 +1066,29 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
972
1066
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
973
1067
|
}
|
974
1068
|
|
975
|
-
|
976
|
-
const
|
977
|
-
|
978
|
-
|
979
|
-
locals,
|
980
|
-
returns,
|
981
|
-
localInd: allLocals.length,
|
982
|
-
};
|
983
|
-
|
984
|
-
wasm = wasm(scope, { TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString });
|
1069
|
+
for (const x of _data) {
|
1070
|
+
const copy = { ...x };
|
1071
|
+
copy.offset += pages.size * pageSize;
|
1072
|
+
data.push(copy);
|
985
1073
|
}
|
986
1074
|
|
1075
|
+
const func = {
|
1076
|
+
name,
|
1077
|
+
params,
|
1078
|
+
locals,
|
1079
|
+
localInd: allLocals.length,
|
1080
|
+
returns,
|
1081
|
+
returnType: returnType ?? TYPES.number,
|
1082
|
+
internal: true,
|
1083
|
+
index: currentFuncIndex++,
|
1084
|
+
table
|
1085
|
+
};
|
1086
|
+
|
1087
|
+
funcs.push(func);
|
1088
|
+
funcIndex[name] = func.index;
|
1089
|
+
|
1090
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, func);
|
1091
|
+
|
987
1092
|
let baseGlobalIdx, i = 0;
|
988
1093
|
for (const type of globalTypes) {
|
989
1094
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -1001,19 +1106,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
1001
1106
|
}
|
1002
1107
|
}
|
1003
1108
|
|
1004
|
-
const
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
wasm,
|
1011
|
-
internal: true,
|
1012
|
-
index: currentFuncIndex++
|
1013
|
-
};
|
1109
|
+
if (table) for (const inst of wasm) {
|
1110
|
+
if (inst[0] === Opcodes.i32_load16_u && inst.at(-1) === 'read_argc') {
|
1111
|
+
inst.splice(2, 99);
|
1112
|
+
inst.push(...unsignedLEB128(allocPage({}, 'func argc lut') * pageSize));
|
1113
|
+
}
|
1114
|
+
}
|
1014
1115
|
|
1015
|
-
|
1016
|
-
funcIndex[name] = func.index;
|
1116
|
+
func.wasm = wasm;
|
1017
1117
|
|
1018
1118
|
return func;
|
1019
1119
|
};
|
@@ -1029,6 +1129,7 @@ const generateLogicExp = (scope, decl) => {
|
|
1029
1129
|
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
1030
1130
|
};
|
1031
1131
|
|
1132
|
+
// potential future ideas for nan boxing (unused):
|
1032
1133
|
// T = JS type, V = value/pointer
|
1033
1134
|
// 0bTTT
|
1034
1135
|
// qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
|
@@ -1042,7 +1143,6 @@ const generateLogicExp = (scope, decl) => {
|
|
1042
1143
|
// js type: 4 bits
|
1043
1144
|
// internal type: ? bits
|
1044
1145
|
// pointer: 32 bits
|
1045
|
-
|
1046
1146
|
// generic
|
1047
1147
|
// 1 23 4 5
|
1048
1148
|
// 0 11111111111 11TTTTIIII??????????PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
|
@@ -1052,49 +1152,29 @@ const generateLogicExp = (scope, decl) => {
|
|
1052
1152
|
// 4: internal type
|
1053
1153
|
// 5: pointer
|
1054
1154
|
|
1055
|
-
const
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
object: 0x04,
|
1061
|
-
function: 0x05,
|
1062
|
-
symbol: 0x06,
|
1063
|
-
bigint: 0x07,
|
1064
|
-
|
1065
|
-
// these are not "typeof" types but tracked internally
|
1066
|
-
_array: 0x10,
|
1067
|
-
_regexp: 0x11,
|
1068
|
-
_bytestring: 0x12
|
1069
|
-
};
|
1070
|
-
|
1071
|
-
const TYPE_NAMES = {
|
1072
|
-
[TYPES.number]: 'Number',
|
1073
|
-
[TYPES.boolean]: 'Boolean',
|
1074
|
-
[TYPES.string]: 'String',
|
1075
|
-
[TYPES.undefined]: 'undefined',
|
1076
|
-
[TYPES.object]: 'Object',
|
1077
|
-
[TYPES.function]: 'Function',
|
1078
|
-
[TYPES.symbol]: 'Symbol',
|
1079
|
-
[TYPES.bigint]: 'BigInt',
|
1080
|
-
|
1081
|
-
[TYPES._array]: 'Array',
|
1082
|
-
[TYPES._regexp]: 'RegExp',
|
1083
|
-
[TYPES._bytestring]: 'ByteString'
|
1155
|
+
const isExistingProtoFunc = name => {
|
1156
|
+
if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES.array][name.slice(18)];
|
1157
|
+
if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
|
1158
|
+
|
1159
|
+
return false;
|
1084
1160
|
};
|
1085
1161
|
|
1086
1162
|
const getType = (scope, _name) => {
|
1087
1163
|
const name = mapName(_name);
|
1088
1164
|
|
1165
|
+
// if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
|
1166
|
+
|
1167
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
|
1089
1168
|
if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
|
1169
|
+
|
1170
|
+
if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
|
1090
1171
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1091
1172
|
|
1092
1173
|
let type = TYPES.undefined;
|
1093
|
-
if (builtinVars[name]) type =
|
1174
|
+
if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
|
1094
1175
|
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
|
1095
1176
|
|
1096
|
-
if (name
|
1097
|
-
name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
|
1177
|
+
if (isExistingProtoFunc(name)) type = TYPES.function;
|
1098
1178
|
|
1099
1179
|
return number(type, Valtype.i32);
|
1100
1180
|
};
|
@@ -1117,23 +1197,25 @@ const setType = (scope, _name, type) => {
|
|
1117
1197
|
];
|
1118
1198
|
|
1119
1199
|
// throw new Error('could not find var');
|
1200
|
+
return [];
|
1120
1201
|
};
|
1121
1202
|
|
1122
1203
|
const getLastType = scope => {
|
1123
1204
|
scope.gotLastType = true;
|
1124
|
-
return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
|
1205
|
+
return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1125
1206
|
};
|
1126
1207
|
|
1127
|
-
const setLastType = scope =>
|
1128
|
-
|
1129
|
-
|
1208
|
+
const setLastType = (scope, type = []) => [
|
1209
|
+
...(typeof type === 'number' ? number(type, Valtype.i32) : type),
|
1210
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1211
|
+
];
|
1130
1212
|
|
1131
1213
|
const getNodeType = (scope, node) => {
|
1132
|
-
const
|
1214
|
+
const ret = (() => {
|
1133
1215
|
if (node.type === 'Literal') {
|
1134
|
-
if (node.regex) return TYPES.
|
1216
|
+
if (node.regex) return TYPES.regexp;
|
1135
1217
|
|
1136
|
-
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES.
|
1218
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES.bytestring;
|
1137
1219
|
|
1138
1220
|
return TYPES[typeof node.value];
|
1139
1221
|
}
|
@@ -1150,21 +1232,32 @@ const getNodeType = (scope, node) => {
|
|
1150
1232
|
const name = node.callee.name;
|
1151
1233
|
if (!name) {
|
1152
1234
|
// iife
|
1153
|
-
if (scope.locals['#last_type']) return
|
1235
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1154
1236
|
|
1155
1237
|
// presume
|
1156
1238
|
// todo: warn here?
|
1157
1239
|
return TYPES.number;
|
1158
1240
|
}
|
1159
1241
|
|
1242
|
+
if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
|
1243
|
+
if (builtinFuncs[name + '$constructor'].typedReturns) {
|
1244
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1245
|
+
|
1246
|
+
// presume
|
1247
|
+
// todo: warn here?
|
1248
|
+
return TYPES.number;
|
1249
|
+
}
|
1250
|
+
|
1251
|
+
return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
|
1252
|
+
}
|
1253
|
+
|
1160
1254
|
const func = funcs.find(x => x.name === name);
|
1161
1255
|
|
1162
1256
|
if (func) {
|
1163
|
-
// console.log(scope, func, func.returnType);
|
1164
1257
|
if (func.returnType) return func.returnType;
|
1165
1258
|
}
|
1166
1259
|
|
1167
|
-
if (builtinFuncs[name]) return
|
1260
|
+
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
1168
1261
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1169
1262
|
|
1170
1263
|
// check if this is a prototype function
|
@@ -1175,11 +1268,26 @@ const getNodeType = (scope, node) => {
|
|
1175
1268
|
const spl = name.slice(2).split('_');
|
1176
1269
|
|
1177
1270
|
const func = spl[spl.length - 1];
|
1178
|
-
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.
|
1179
|
-
if (protoFuncs.length === 1)
|
1271
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
|
1272
|
+
if (protoFuncs.length === 1) {
|
1273
|
+
if (protoFuncs[0].returnType) return protoFuncs[0].returnType;
|
1274
|
+
}
|
1275
|
+
|
1276
|
+
if (protoFuncs.length > 0) {
|
1277
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1278
|
+
|
1279
|
+
// presume
|
1280
|
+
// todo: warn here?
|
1281
|
+
return TYPES.number;
|
1282
|
+
}
|
1283
|
+
}
|
1284
|
+
|
1285
|
+
if (name.startsWith('__Porffor_wasm_')) {
|
1286
|
+
// todo: return undefined for non-returning ops
|
1287
|
+
return TYPES.number;
|
1180
1288
|
}
|
1181
1289
|
|
1182
|
-
if (scope.locals['#last_type']) return
|
1290
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1183
1291
|
|
1184
1292
|
// presume
|
1185
1293
|
// todo: warn here?
|
@@ -1222,11 +1330,20 @@ const getNodeType = (scope, node) => {
|
|
1222
1330
|
}
|
1223
1331
|
|
1224
1332
|
if (node.type === 'ArrayExpression') {
|
1225
|
-
return TYPES.
|
1333
|
+
return TYPES.array;
|
1226
1334
|
}
|
1227
1335
|
|
1228
1336
|
if (node.type === 'BinaryExpression') {
|
1229
1337
|
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
1338
|
+
if (node.operator !== '+') return TYPES.number;
|
1339
|
+
|
1340
|
+
const knownLeft = knownType(scope, getNodeType(scope, node.left));
|
1341
|
+
const knownRight = knownType(scope, getNodeType(scope, node.right));
|
1342
|
+
|
1343
|
+
// todo: this should be dynamic but for now only static
|
1344
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
|
1345
|
+
if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) return TYPES.bytestring;
|
1346
|
+
|
1230
1347
|
return TYPES.number;
|
1231
1348
|
|
1232
1349
|
// todo: string concat types
|
@@ -1251,34 +1368,52 @@ const getNodeType = (scope, node) => {
|
|
1251
1368
|
if (node.operator === '!') return TYPES.boolean;
|
1252
1369
|
if (node.operator === 'void') return TYPES.undefined;
|
1253
1370
|
if (node.operator === 'delete') return TYPES.boolean;
|
1254
|
-
if (node.operator === 'typeof') return
|
1371
|
+
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES.bytestring : TYPES.string;
|
1255
1372
|
|
1256
1373
|
return TYPES.number;
|
1257
1374
|
}
|
1258
1375
|
|
1259
1376
|
if (node.type === 'MemberExpression') {
|
1260
|
-
|
1261
|
-
|
1377
|
+
const name = node.property.name;
|
1378
|
+
|
1379
|
+
if (name === 'length') {
|
1380
|
+
if (hasFuncWithName(node.object.name)) return TYPES.number;
|
1381
|
+
if (Prefs.fastLength) return TYPES.number;
|
1382
|
+
}
|
1383
|
+
|
1384
|
+
|
1385
|
+
const objectKnownType = knownType(scope, getNodeType(scope, node.object));
|
1386
|
+
if (objectKnownType != null) {
|
1387
|
+
if (name === 'length') {
|
1388
|
+
if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(objectKnownType)) return TYPES.number;
|
1389
|
+
else return TYPES.undefined;
|
1390
|
+
}
|
1262
1391
|
|
1263
|
-
|
1264
|
-
|
1265
|
-
|
1392
|
+
if (node.computed) {
|
1393
|
+
if (objectKnownType === TYPES.string) return TYPES.string;
|
1394
|
+
if (objectKnownType === TYPES.bytestring) return TYPES.bytestring;
|
1395
|
+
if (objectKnownType === TYPES.array) return TYPES.number;
|
1396
|
+
}
|
1397
|
+
}
|
1266
1398
|
|
1267
|
-
if (scope.locals['#last_type']) return
|
1399
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1268
1400
|
|
1269
1401
|
// presume
|
1270
1402
|
return TYPES.number;
|
1271
1403
|
}
|
1272
1404
|
|
1273
|
-
if (
|
1405
|
+
if (node.type === 'TaggedTemplateExpression') {
|
1406
|
+
// hack
|
1407
|
+
if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
|
1408
|
+
}
|
1409
|
+
|
1410
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1274
1411
|
|
1275
1412
|
// presume
|
1276
1413
|
// todo: warn here?
|
1277
1414
|
return TYPES.number;
|
1278
|
-
};
|
1415
|
+
})();
|
1279
1416
|
|
1280
|
-
const ret = inner();
|
1281
|
-
// console.trace(node, ret);
|
1282
1417
|
if (typeof ret === 'number') return number(ret, Valtype.i32);
|
1283
1418
|
return ret;
|
1284
1419
|
};
|
@@ -1303,7 +1438,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1303
1438
|
return makeString(scope, decl.value, global, name);
|
1304
1439
|
|
1305
1440
|
default:
|
1306
|
-
return todo(`cannot generate literal of type ${typeof decl.value}
|
1441
|
+
return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
|
1307
1442
|
}
|
1308
1443
|
};
|
1309
1444
|
|
@@ -1312,6 +1447,8 @@ const countLeftover = wasm => {
|
|
1312
1447
|
|
1313
1448
|
for (let i = 0; i < wasm.length; i++) {
|
1314
1449
|
const inst = wasm[i];
|
1450
|
+
if (inst[0] == null) continue;
|
1451
|
+
|
1315
1452
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
1316
1453
|
if (inst[0] === Opcodes.if) count--;
|
1317
1454
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1320,18 +1457,24 @@ const countLeftover = wasm => {
|
|
1320
1457
|
if (inst[0] === Opcodes.end) depth--;
|
1321
1458
|
|
1322
1459
|
if (depth === 0)
|
1323
|
-
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1460
|
+
if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1324
1461
|
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)) {}
|
1325
|
-
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1462
|
+
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++;
|
1326
1463
|
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1327
1464
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1328
1465
|
else if (inst[0] === Opcodes.return) count = 0;
|
1329
1466
|
else if (inst[0] === Opcodes.call) {
|
1330
1467
|
let func = funcs.find(x => x.index === inst[1]);
|
1331
|
-
if (
|
1332
|
-
|
1333
|
-
|
1334
|
-
|
1468
|
+
if (inst[1] < importedFuncs.length) {
|
1469
|
+
func = importedFuncs[inst[1]];
|
1470
|
+
count = count - func.params + func.returns;
|
1471
|
+
} else {
|
1472
|
+
count = count - func.params.length + func.returns.length;
|
1473
|
+
}
|
1474
|
+
} else if (inst[0] === Opcodes.call_indirect) {
|
1475
|
+
count--; // funcidx
|
1476
|
+
count -= inst[1] * 2; // params * 2 (typed)
|
1477
|
+
count += 2; // fixed return (value, type)
|
1335
1478
|
} else count--;
|
1336
1479
|
|
1337
1480
|
// console.log(count, decompile([ inst ]).slice(0, -1));
|
@@ -1349,7 +1492,7 @@ const disposeLeftover = wasm => {
|
|
1349
1492
|
const generateExp = (scope, decl) => {
|
1350
1493
|
const expression = decl.expression;
|
1351
1494
|
|
1352
|
-
const out = generate(scope, expression, undefined, undefined,
|
1495
|
+
const out = generate(scope, expression, undefined, undefined, Prefs.optUnused);
|
1353
1496
|
disposeLeftover(out);
|
1354
1497
|
|
1355
1498
|
return out;
|
@@ -1408,25 +1551,27 @@ const RTArrayUtil = {
|
|
1408
1551
|
};
|
1409
1552
|
|
1410
1553
|
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1411
|
-
/* const callee = decl.callee;
|
1412
|
-
const args = decl.arguments;
|
1413
|
-
|
1414
|
-
return [
|
1415
|
-
...generate(args),
|
1416
|
-
...generate(callee),
|
1417
|
-
Opcodes.call_indirect,
|
1418
|
-
]; */
|
1419
|
-
|
1420
1554
|
let name = mapName(decl.callee.name);
|
1421
1555
|
if (isFuncType(decl.callee.type)) { // iife
|
1422
1556
|
const func = generateFunc(scope, decl.callee);
|
1423
1557
|
name = func.name;
|
1424
1558
|
}
|
1425
1559
|
|
1426
|
-
if (name === 'eval' && decl.arguments[0]
|
1560
|
+
if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
|
1427
1561
|
// literal eval hack
|
1428
|
-
const code = decl.arguments[0]
|
1429
|
-
|
1562
|
+
const code = decl.arguments[0]?.value ?? '';
|
1563
|
+
|
1564
|
+
let parsed;
|
1565
|
+
try {
|
1566
|
+
parsed = parse(code, []);
|
1567
|
+
} catch (e) {
|
1568
|
+
if (e.name === 'SyntaxError') {
|
1569
|
+
// throw syntax errors of evals at runtime instead
|
1570
|
+
return internalThrow(scope, 'SyntaxError', e.message, true);
|
1571
|
+
}
|
1572
|
+
|
1573
|
+
throw e;
|
1574
|
+
}
|
1430
1575
|
|
1431
1576
|
const out = generate(scope, {
|
1432
1577
|
type: 'BlockStatement',
|
@@ -1438,16 +1583,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1438
1583
|
out.splice(out.length - 1, 1);
|
1439
1584
|
|
1440
1585
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1441
|
-
out.push(
|
1442
|
-
...getNodeType(scope, finalStatement),
|
1443
|
-
setLastType(scope)
|
1444
|
-
);
|
1586
|
+
out.push(...setLastType(scope, getNodeType(scope, finalStatement)));
|
1445
1587
|
} else if (countLeftover(out) === 0) {
|
1446
1588
|
out.push(...number(UNDEFINED));
|
1447
|
-
out.push(
|
1448
|
-
...number(TYPES.undefined, Valtype.i32),
|
1449
|
-
setLastType(scope)
|
1450
|
-
);
|
1589
|
+
out.push(...setLastType(scope, TYPES.undefined));
|
1451
1590
|
}
|
1452
1591
|
|
1453
1592
|
// if (lastInst && lastInst[0] === Opcodes.drop) {
|
@@ -1468,6 +1607,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1468
1607
|
|
1469
1608
|
target = { ...decl.callee };
|
1470
1609
|
target.name = spl.slice(0, -1).join('_');
|
1610
|
+
|
1611
|
+
// failed to lookup name, abort
|
1612
|
+
if (!lookupName(scope, target.name)[0]) protoName = null;
|
1471
1613
|
}
|
1472
1614
|
|
1473
1615
|
// literal.func()
|
@@ -1475,22 +1617,29 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1475
1617
|
// megahack for /regex/.func()
|
1476
1618
|
const funcName = decl.callee.property.name;
|
1477
1619
|
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1478
|
-
const
|
1620
|
+
const regex = decl.callee.object.regex.pattern;
|
1621
|
+
const rhemynName = `regex_${funcName}_${regex}`;
|
1622
|
+
|
1623
|
+
if (!funcIndex[rhemynName]) {
|
1624
|
+
const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
|
1625
|
+
func.internal = true;
|
1479
1626
|
|
1480
|
-
|
1481
|
-
|
1627
|
+
funcIndex[func.name] = func.index;
|
1628
|
+
funcs.push(func);
|
1629
|
+
}
|
1482
1630
|
|
1631
|
+
const idx = funcIndex[rhemynName];
|
1483
1632
|
return [
|
1484
1633
|
// make string arg
|
1485
1634
|
...generate(scope, decl.arguments[0]),
|
1635
|
+
Opcodes.i32_to_u,
|
1636
|
+
...getNodeType(scope, decl.arguments[0]),
|
1486
1637
|
|
1487
1638
|
// call regex func
|
1488
|
-
Opcodes.
|
1489
|
-
[ Opcodes.call, func.index ],
|
1639
|
+
[ Opcodes.call, idx ],
|
1490
1640
|
Opcodes.i32_from_u,
|
1491
1641
|
|
1492
|
-
...
|
1493
|
-
setLastType(scope)
|
1642
|
+
...setLastType(scope, TYPES.boolean)
|
1494
1643
|
];
|
1495
1644
|
}
|
1496
1645
|
|
@@ -1515,12 +1664,31 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1515
1664
|
// }
|
1516
1665
|
|
1517
1666
|
if (protoName) {
|
1667
|
+
const protoBC = {};
|
1668
|
+
|
1669
|
+
const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
|
1670
|
+
|
1671
|
+
if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
|
1672
|
+
for (const x of builtinProtoCands) {
|
1673
|
+
const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
|
1674
|
+
if (type == null) continue;
|
1675
|
+
|
1676
|
+
protoBC[type] = generateCall(scope, {
|
1677
|
+
callee: {
|
1678
|
+
type: 'Identifier',
|
1679
|
+
name: x
|
1680
|
+
},
|
1681
|
+
arguments: [ target, ...decl.arguments ],
|
1682
|
+
_protoInternalCall: true
|
1683
|
+
});
|
1684
|
+
}
|
1685
|
+
}
|
1686
|
+
|
1518
1687
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1519
1688
|
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1520
1689
|
return acc;
|
1521
1690
|
}, {});
|
1522
1691
|
|
1523
|
-
// no prototype function candidates, ignore
|
1524
1692
|
if (Object.keys(protoCands).length > 0) {
|
1525
1693
|
// use local for cached i32 length as commonly used
|
1526
1694
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
@@ -1538,21 +1706,16 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1538
1706
|
|
1539
1707
|
let allOptUnused = true;
|
1540
1708
|
let lengthI32CacheUsed = false;
|
1541
|
-
const protoBC = {};
|
1542
1709
|
for (const x in protoCands) {
|
1543
1710
|
const protoFunc = protoCands[x];
|
1544
1711
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
1545
1712
|
protoBC[x] = [
|
1546
1713
|
...RTArrayUtil.getLength(getPointer),
|
1547
|
-
|
1548
|
-
...number(TYPES.number, Valtype.i32),
|
1549
|
-
setLastType(scope)
|
1714
|
+
...setLastType(scope, TYPES.number)
|
1550
1715
|
];
|
1551
1716
|
continue;
|
1552
1717
|
}
|
1553
1718
|
|
1554
|
-
// const protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp`, protoFunc.local) : -1;
|
1555
|
-
// const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${TYPE_NAMES[x]}_${protoName}_tmp2`, protoFunc.local2) : -1;
|
1556
1719
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1557
1720
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1558
1721
|
|
@@ -1567,7 +1730,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1567
1730
|
getI32: () => RTArrayUtil.getLengthI32(getPointer),
|
1568
1731
|
set: value => RTArrayUtil.setLength(getPointer, value),
|
1569
1732
|
setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
|
1570
|
-
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
|
1733
|
+
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), getNodeType(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
|
1571
1734
|
return makeArray(scope, {
|
1572
1735
|
rawElements: new Array(length)
|
1573
1736
|
}, _global, _name, true, itemType);
|
@@ -1581,9 +1744,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1581
1744
|
protoBC[x] = [
|
1582
1745
|
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1583
1746
|
...protoOut,
|
1584
|
-
|
1585
|
-
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1586
|
-
setLastType(scope),
|
1747
|
+
...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
|
1587
1748
|
[ Opcodes.end ]
|
1588
1749
|
];
|
1589
1750
|
}
|
@@ -1609,10 +1770,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1609
1770
|
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1610
1771
|
];
|
1611
1772
|
}
|
1773
|
+
|
1774
|
+
if (Object.keys(protoBC).length > 0) {
|
1775
|
+
return typeSwitch(scope, getNodeType(scope, target), {
|
1776
|
+
...protoBC,
|
1777
|
+
|
1778
|
+
// TODO: error better
|
1779
|
+
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1780
|
+
}, valtypeBinary);
|
1781
|
+
}
|
1612
1782
|
}
|
1613
1783
|
|
1614
1784
|
// TODO: only allows callee as literal
|
1615
|
-
if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
|
1785
|
+
if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
|
1616
1786
|
|
1617
1787
|
let idx = funcIndex[name] ?? importedFuncs[name];
|
1618
1788
|
if (idx === undefined && builtinFuncs[name]) {
|
@@ -1620,65 +1790,235 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1620
1790
|
|
1621
1791
|
includeBuiltin(scope, name);
|
1622
1792
|
idx = funcIndex[name];
|
1793
|
+
}
|
1623
1794
|
|
1624
|
-
|
1625
|
-
const func = funcs.find(x => x.name === name);
|
1626
|
-
for (let i = 0; i < decl.arguments.length; i++) {
|
1627
|
-
const arg = decl.arguments[i];
|
1628
|
-
if (!arg.name) continue;
|
1795
|
+
if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1629
1796
|
|
1630
|
-
|
1631
|
-
|
1797
|
+
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1798
|
+
const wasmOps = {
|
1799
|
+
// pointer, align, offset
|
1800
|
+
i32_load: { imms: 2, args: [ true ], returns: 1 },
|
1801
|
+
// pointer, value, align, offset
|
1802
|
+
i32_store: { imms: 2, args: [ true, true ], returns: 0 },
|
1803
|
+
// pointer, align, offset
|
1804
|
+
i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
|
1805
|
+
// pointer, value, align, offset
|
1806
|
+
i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
|
1807
|
+
// pointer, align, offset
|
1808
|
+
i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
|
1809
|
+
// pointer, value, align, offset
|
1810
|
+
i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
|
1811
|
+
|
1812
|
+
// pointer, align, offset
|
1813
|
+
f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
|
1814
|
+
// pointer, value, align, offset
|
1815
|
+
f64_store: { imms: 2, args: [ true, false ], returns: 0 },
|
1816
|
+
|
1817
|
+
// value
|
1818
|
+
i32_const: { imms: 1, args: [], returns: 1 },
|
1819
|
+
};
|
1632
1820
|
|
1633
|
-
|
1634
|
-
if (local.type === Valtype.v128) {
|
1635
|
-
// specify vec subtype inferred from last vec type in function name
|
1636
|
-
local.vecType = name.split('_').reverse().find(x => x.includes('x'));
|
1637
|
-
}
|
1638
|
-
}
|
1639
|
-
}
|
1821
|
+
const opName = name.slice('__Porffor_wasm_'.length);
|
1640
1822
|
|
1641
|
-
|
1823
|
+
if (wasmOps[opName]) {
|
1824
|
+
const op = wasmOps[opName];
|
1642
1825
|
|
1643
|
-
|
1644
|
-
|
1645
|
-
|
1826
|
+
const argOut = [];
|
1827
|
+
for (let i = 0; i < op.args.length; i++) argOut.push(
|
1828
|
+
...generate(scope, decl.arguments[i]),
|
1829
|
+
...(op.args[i] ? [ Opcodes.i32_to ] : [])
|
1830
|
+
);
|
1831
|
+
|
1832
|
+
// literals only
|
1833
|
+
const imms = decl.arguments.slice(op.args.length).map(x => x.value);
|
1834
|
+
|
1835
|
+
return [
|
1836
|
+
...argOut,
|
1837
|
+
[ Opcodes[opName], ...imms ],
|
1838
|
+
...(new Array(op.returns).fill(Opcodes.i32_from))
|
1839
|
+
];
|
1840
|
+
}
|
1646
1841
|
}
|
1647
1842
|
|
1648
1843
|
if (idx === undefined) {
|
1649
|
-
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined)
|
1650
|
-
|
1651
|
-
|
1844
|
+
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) {
|
1845
|
+
const [ local, global ] = lookupName(scope, name);
|
1846
|
+
if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1652
1847
|
|
1653
|
-
|
1848
|
+
// todo: only works when function uses typedParams and typedReturns
|
1654
1849
|
|
1655
|
-
|
1656
|
-
|
1657
|
-
|
1658
|
-
|
1850
|
+
const indirectMode = Prefs.indirectCallMode ?? 'vararg';
|
1851
|
+
// options: vararg, strict
|
1852
|
+
// - strict: simpler, smaller size usage, no func argc lut needed.
|
1853
|
+
// ONLY works when arg count of call == arg count of function being called
|
1854
|
+
// - vararg: large size usage, cursed.
|
1855
|
+
// works when arg count of call != arg count of function being called*
|
1856
|
+
// * most of the time, some edgecases
|
1659
1857
|
|
1660
|
-
|
1661
|
-
|
1662
|
-
// too little args, push undefineds
|
1663
|
-
args = args.concat(new Array(paramCount - args.length).fill(DEFAULT_VALUE));
|
1664
|
-
}
|
1858
|
+
funcs.table = true;
|
1859
|
+
scope.table = true;
|
1665
1860
|
|
1666
|
-
|
1667
|
-
|
1668
|
-
args = args.slice(0, paramCount);
|
1669
|
-
}
|
1861
|
+
let args = decl.arguments;
|
1862
|
+
let out = [];
|
1670
1863
|
|
1671
|
-
|
1864
|
+
let locals = [];
|
1672
1865
|
|
1673
|
-
|
1674
|
-
|
1675
|
-
out = out.concat(generate(scope, arg));
|
1676
|
-
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1677
|
-
}
|
1866
|
+
if (indirectMode === 'vararg') {
|
1867
|
+
const minArgc = Prefs.indirectCallMinArgc ?? 3;
|
1678
1868
|
|
1679
|
-
|
1869
|
+
if (args.length < minArgc) {
|
1870
|
+
args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
|
1871
|
+
}
|
1872
|
+
}
|
1873
|
+
|
1874
|
+
for (let i = 0; i < args.length; i++) {
|
1875
|
+
const arg = args[i];
|
1876
|
+
out = out.concat(generate(scope, arg));
|
1877
|
+
|
1878
|
+
if (valtypeBinary !== Valtype.i32 && (
|
1879
|
+
(builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
|
1880
|
+
(importedFuncs[name] && name.startsWith('profile'))
|
1881
|
+
)) {
|
1882
|
+
out.push(Opcodes.i32_to);
|
1883
|
+
}
|
1884
|
+
|
1885
|
+
out = out.concat(getNodeType(scope, arg));
|
1886
|
+
|
1887
|
+
if (indirectMode === 'vararg') {
|
1888
|
+
const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
|
1889
|
+
const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
|
1890
|
+
|
1891
|
+
locals.push([valLocal, typeLocal]);
|
1892
|
+
|
1893
|
+
out.push(
|
1894
|
+
[ Opcodes.local_set, typeLocal ],
|
1895
|
+
[ Opcodes.local_set, valLocal ]
|
1896
|
+
);
|
1897
|
+
}
|
1898
|
+
}
|
1899
|
+
|
1900
|
+
if (indirectMode === 'strict') {
|
1901
|
+
return typeSwitch(scope, getNodeType(scope, decl.callee), {
|
1902
|
+
[TYPES.function]: [
|
1903
|
+
...argWasm,
|
1904
|
+
[ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1905
|
+
Opcodes.i32_to_u,
|
1906
|
+
[ Opcodes.call_indirect, args.length, 0 ],
|
1907
|
+
...setLastType(scope)
|
1908
|
+
],
|
1909
|
+
default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
|
1910
|
+
});
|
1911
|
+
}
|
1912
|
+
|
1913
|
+
// hi, I will now explain how vararg mode works:
|
1914
|
+
// wasm's indirect_call instruction requires you know the func type at compile-time
|
1915
|
+
// since we have varargs (variable argument count), we do not know it.
|
1916
|
+
// we could just store args in memory and not use wasm func args,
|
1917
|
+
// but that is slow (probably) and breaks js exports.
|
1918
|
+
// instead, we generate every* possibility of argc and use different indirect_call
|
1919
|
+
// ops for each one, with type depending on argc for that branch.
|
1920
|
+
// then we load the argc for the wanted function from a memory lut,
|
1921
|
+
// and call the branch with the matching argc we require.
|
1922
|
+
// sorry, yes it is very cursed (and size inefficient), but indirect calls
|
1923
|
+
// are kind of rare anyway (mostly callbacks) so I am not concerned atm.
|
1924
|
+
// *for argc 0-3, in future (todo:) the max number should be
|
1925
|
+
// dynamically changed to the max argc of any func in the js file.
|
1926
|
+
|
1927
|
+
const funcLocal = localTmp(scope, `#indirect_func`, Valtype.i32);
|
1928
|
+
|
1929
|
+
const gen = argc => {
|
1930
|
+
const out = [];
|
1931
|
+
for (let i = 0; i < argc; i++) {
|
1932
|
+
out.push(
|
1933
|
+
[ Opcodes.local_get, locals[i][0] ],
|
1934
|
+
[ Opcodes.local_get, locals[i][1] ]
|
1935
|
+
);
|
1936
|
+
}
|
1937
|
+
|
1938
|
+
out.push(
|
1939
|
+
[ Opcodes.local_get, funcLocal ],
|
1940
|
+
[ Opcodes.call_indirect, argc, 0 ],
|
1941
|
+
...setLastType(scope)
|
1942
|
+
)
|
1943
|
+
|
1944
|
+
return out;
|
1945
|
+
};
|
1946
|
+
|
1947
|
+
const tableBc = {};
|
1948
|
+
for (let i = 0; i <= args.length; i++) {
|
1949
|
+
tableBc[i] = gen(i);
|
1950
|
+
}
|
1951
|
+
|
1952
|
+
// todo/perf: check if we should use br_table here or just generate our own big if..elses
|
1953
|
+
|
1954
|
+
return typeSwitch(scope, getNodeType(scope, decl.callee), {
|
1955
|
+
[TYPES.function]: [
|
1956
|
+
...out,
|
1957
|
+
|
1958
|
+
[ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1959
|
+
Opcodes.i32_to_u,
|
1960
|
+
[ Opcodes.local_set, funcLocal ],
|
1961
|
+
|
1962
|
+
...brTable([
|
1963
|
+
// get argc of func we are calling
|
1964
|
+
[ Opcodes.local_get, funcLocal ],
|
1965
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
1966
|
+
[ Opcodes.i32_mul ],
|
1967
|
+
|
1968
|
+
[ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func argc lut') * pageSize), 'read_argc' ]
|
1969
|
+
], tableBc, valtypeBinary)
|
1970
|
+
],
|
1971
|
+
default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
|
1972
|
+
});
|
1973
|
+
}
|
1974
|
+
|
1975
|
+
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
1976
|
+
}
|
1977
|
+
|
1978
|
+
const func = funcs[idx - importedFuncs.length]; // idx === scope.index ? scope : funcs.find(x => x.index === idx);
|
1979
|
+
const userFunc = func && !func.internal;
|
1980
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1981
|
+
const typedReturns = (func && func.returnType == null) || builtinFuncs[name]?.typedReturns;
|
1982
|
+
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1983
|
+
|
1984
|
+
let args = decl.arguments;
|
1985
|
+
if (func && args.length < paramCount) {
|
1986
|
+
// too little args, push undefineds
|
1987
|
+
args = args.concat(new Array(paramCount - args.length).fill(DEFAULT_VALUE));
|
1988
|
+
}
|
1989
|
+
|
1990
|
+
if (func && args.length > paramCount) {
|
1991
|
+
// too many args, slice extras off
|
1992
|
+
args = args.slice(0, paramCount);
|
1993
|
+
}
|
1994
|
+
|
1995
|
+
if (func && func.throws) scope.throws = true;
|
1996
|
+
|
1997
|
+
let out = [];
|
1998
|
+
for (let i = 0; i < args.length; i++) {
|
1999
|
+
const arg = args[i];
|
2000
|
+
out = out.concat(generate(scope, arg));
|
2001
|
+
|
2002
|
+
// todo: this should be used instead of the too many args thing above (by removing that)
|
2003
|
+
if (i >= paramCount) {
|
2004
|
+
// over param count of func, drop arg
|
2005
|
+
out.push([ Opcodes.drop ]);
|
2006
|
+
continue;
|
2007
|
+
}
|
2008
|
+
|
2009
|
+
if (valtypeBinary !== Valtype.i32 && (
|
2010
|
+
(builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
|
2011
|
+
(importedFuncs[name] && name.startsWith('profile'))
|
2012
|
+
)) {
|
2013
|
+
out.push(Opcodes.i32_to);
|
2014
|
+
}
|
1680
2015
|
|
1681
|
-
|
2016
|
+
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
2017
|
+
}
|
2018
|
+
|
2019
|
+
out.push([ Opcodes.call, idx ]);
|
2020
|
+
|
2021
|
+
if (!typedReturns) {
|
1682
2022
|
// let type;
|
1683
2023
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1684
2024
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1688,7 +2028,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1688
2028
|
// ...number(type, Valtype.i32),
|
1689
2029
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1690
2030
|
// );
|
1691
|
-
} else out.push(setLastType(scope));
|
2031
|
+
} else out.push(...setLastType(scope));
|
2032
|
+
|
2033
|
+
if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
2034
|
+
out.push(Opcodes.i32_from);
|
2035
|
+
}
|
1692
2036
|
|
1693
2037
|
return out;
|
1694
2038
|
};
|
@@ -1696,8 +2040,26 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1696
2040
|
const generateNew = (scope, decl, _global, _name) => {
|
1697
2041
|
// hack: basically treat this as a normal call for builtins for now
|
1698
2042
|
const name = mapName(decl.callee.name);
|
2043
|
+
|
1699
2044
|
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1700
|
-
|
2045
|
+
|
2046
|
+
if (builtinFuncs[name + '$constructor']) {
|
2047
|
+
// custom ...$constructor override builtin func
|
2048
|
+
return generateCall(scope, {
|
2049
|
+
...decl,
|
2050
|
+
callee: {
|
2051
|
+
type: 'Identifier',
|
2052
|
+
name: name + '$constructor'
|
2053
|
+
}
|
2054
|
+
}, _global, _name);
|
2055
|
+
}
|
2056
|
+
|
2057
|
+
if (
|
2058
|
+
(builtinFuncs[name] && !builtinFuncs[name].constr) ||
|
2059
|
+
(internalConstrs[name] && builtinFuncs[name].notConstr)
|
2060
|
+
) return internalThrow(scope, 'TypeError', `${name} is not a constructor`);
|
2061
|
+
|
2062
|
+
if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
|
1701
2063
|
|
1702
2064
|
return generateCall(scope, decl, _global, _name);
|
1703
2065
|
};
|
@@ -1722,8 +2084,11 @@ const knownType = (scope, type) => {
|
|
1722
2084
|
const idx = type[0][1];
|
1723
2085
|
|
1724
2086
|
// type idx = var idx + 1
|
1725
|
-
const
|
1726
|
-
if (
|
2087
|
+
const name = Object.values(scope.locals).find(x => x.idx === idx)?.name;
|
2088
|
+
if (name) {
|
2089
|
+
const local = scope.locals[name];
|
2090
|
+
if (local.metadata?.type != null) return v.metadata.type;
|
2091
|
+
}
|
1727
2092
|
}
|
1728
2093
|
|
1729
2094
|
return null;
|
@@ -1758,16 +2123,17 @@ const brTable = (input, bc, returns) => {
|
|
1758
2123
|
}
|
1759
2124
|
|
1760
2125
|
for (let i = 0; i < count; i++) {
|
1761
|
-
if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
|
2126
|
+
// if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
|
2127
|
+
if (i === 0) out.push([ Opcodes.block, returns ]);
|
1762
2128
|
else out.push([ Opcodes.block, Blocktype.void ]);
|
1763
2129
|
}
|
1764
2130
|
|
1765
|
-
const nums = keys.filter(x => +x);
|
2131
|
+
const nums = keys.filter(x => +x >= 0);
|
1766
2132
|
const offset = Math.min(...nums);
|
1767
2133
|
const max = Math.max(...nums);
|
1768
2134
|
|
1769
2135
|
const table = [];
|
1770
|
-
let br =
|
2136
|
+
let br = 0;
|
1771
2137
|
|
1772
2138
|
for (let i = offset; i <= max; i++) {
|
1773
2139
|
// if branch for this num, go to that block
|
@@ -1807,21 +2173,20 @@ const brTable = (input, bc, returns) => {
|
|
1807
2173
|
br--;
|
1808
2174
|
}
|
1809
2175
|
|
1810
|
-
|
1811
|
-
|
1812
|
-
|
1813
|
-
];
|
2176
|
+
out.push([ Opcodes.end ]);
|
2177
|
+
|
2178
|
+
return out;
|
1814
2179
|
};
|
1815
2180
|
|
1816
2181
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1817
|
-
if (!
|
2182
|
+
if (!Prefs.bytestring) delete bc[TYPES.bytestring];
|
1818
2183
|
|
1819
2184
|
const known = knownType(scope, type);
|
1820
2185
|
if (known != null) {
|
1821
2186
|
return bc[known] ?? bc.default;
|
1822
2187
|
}
|
1823
2188
|
|
1824
|
-
if (
|
2189
|
+
if (Prefs.typeswitchUseBrtable)
|
1825
2190
|
return brTable(type, bc, returns);
|
1826
2191
|
|
1827
2192
|
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
@@ -1831,8 +2196,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1831
2196
|
[ Opcodes.block, returns ]
|
1832
2197
|
];
|
1833
2198
|
|
1834
|
-
// todo: use br_table?
|
1835
|
-
|
1836
2199
|
for (const x in bc) {
|
1837
2200
|
if (x === 'default') continue;
|
1838
2201
|
|
@@ -1856,7 +2219,18 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1856
2219
|
return out;
|
1857
2220
|
};
|
1858
2221
|
|
1859
|
-
const
|
2222
|
+
const typeIsOneOf = (type, types, valtype = Valtype.i32) => {
|
2223
|
+
const out = [];
|
2224
|
+
|
2225
|
+
for (let i = 0; i < types.length; i++) {
|
2226
|
+
out.push(...type, ...number(types[i], valtype), valtype === Valtype.f64 ? [ Opcodes.f64_eq ] : [ Opcodes.i32_eq ]);
|
2227
|
+
if (i !== 0) out.push([ Opcodes.i32_or ]);
|
2228
|
+
}
|
2229
|
+
|
2230
|
+
return out;
|
2231
|
+
};
|
2232
|
+
|
2233
|
+
const allocVar = (scope, name, global = false, type = true) => {
|
1860
2234
|
const target = global ? globals : scope.locals;
|
1861
2235
|
|
1862
2236
|
// already declared
|
@@ -1870,8 +2244,10 @@ const allocVar = (scope, name, global = false) => {
|
|
1870
2244
|
let idx = global ? globalInd++ : scope.localInd++;
|
1871
2245
|
target[name] = { idx, type: valtypeBinary };
|
1872
2246
|
|
1873
|
-
|
1874
|
-
|
2247
|
+
if (type) {
|
2248
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
2249
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32, name };
|
2250
|
+
}
|
1875
2251
|
|
1876
2252
|
return idx;
|
1877
2253
|
};
|
@@ -1886,11 +2262,13 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
|
1886
2262
|
};
|
1887
2263
|
|
1888
2264
|
const typeAnnoToPorfType = x => {
|
1889
|
-
if (
|
1890
|
-
if (TYPES[
|
2265
|
+
if (!x) return null;
|
2266
|
+
if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
|
1891
2267
|
|
1892
2268
|
switch (x) {
|
1893
2269
|
case 'i32':
|
2270
|
+
case 'i64':
|
2271
|
+
case 'f64':
|
1894
2272
|
return TYPES.number;
|
1895
2273
|
}
|
1896
2274
|
|
@@ -1901,7 +2279,7 @@ const extractTypeAnnotation = decl => {
|
|
1901
2279
|
let a = decl;
|
1902
2280
|
while (a.typeAnnotation) a = a.typeAnnotation;
|
1903
2281
|
|
1904
|
-
let type, elementType;
|
2282
|
+
let type = null, elementType = null;
|
1905
2283
|
if (a.typeName) {
|
1906
2284
|
type = a.typeName.name;
|
1907
2285
|
} else if (a.type.endsWith('Keyword')) {
|
@@ -1914,7 +2292,7 @@ const extractTypeAnnotation = decl => {
|
|
1914
2292
|
const typeName = type;
|
1915
2293
|
type = typeAnnoToPorfType(type);
|
1916
2294
|
|
1917
|
-
if (type === TYPES.
|
2295
|
+
if (type === TYPES.bytestring && !Prefs.bytestring) type = TYPES.string;
|
1918
2296
|
|
1919
2297
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1920
2298
|
|
@@ -1926,13 +2304,13 @@ const generateVar = (scope, decl) => {
|
|
1926
2304
|
|
1927
2305
|
const topLevel = scope.name === 'main';
|
1928
2306
|
|
1929
|
-
// global variable if in top scope (main)
|
1930
|
-
const global = topLevel || decl._bare;
|
2307
|
+
// global variable if in top scope (main) or if internally wanted
|
2308
|
+
const global = topLevel || decl._bare;
|
1931
2309
|
|
1932
2310
|
for (const x of decl.declarations) {
|
1933
2311
|
const name = mapName(x.id.name);
|
1934
2312
|
|
1935
|
-
if (!name) return todo('destructuring is not supported yet');
|
2313
|
+
if (!name) return todo(scope, 'destructuring is not supported yet');
|
1936
2314
|
|
1937
2315
|
if (x.init && isFuncType(x.init.type)) {
|
1938
2316
|
// hack for let a = function () { ... }
|
@@ -1941,7 +2319,6 @@ const generateVar = (scope, decl) => {
|
|
1941
2319
|
continue;
|
1942
2320
|
}
|
1943
2321
|
|
1944
|
-
// console.log(name);
|
1945
2322
|
if (topLevel && builtinVars[name]) {
|
1946
2323
|
// cannot redeclare
|
1947
2324
|
if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
|
@@ -1949,16 +2326,46 @@ const generateVar = (scope, decl) => {
|
|
1949
2326
|
continue; // always ignore
|
1950
2327
|
}
|
1951
2328
|
|
1952
|
-
|
2329
|
+
// // generate init before allocating var
|
2330
|
+
// let generated;
|
2331
|
+
// if (x.init) generated = generate(scope, x.init, global, name);
|
1953
2332
|
|
1954
|
-
|
2333
|
+
const typed = typedInput && x.id.typeAnnotation;
|
2334
|
+
let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
|
2335
|
+
|
2336
|
+
if (typed) {
|
1955
2337
|
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1956
2338
|
}
|
1957
2339
|
|
1958
2340
|
if (x.init) {
|
1959
|
-
|
2341
|
+
// if (isFuncType(x.init.type)) {
|
2342
|
+
// // let a = function () { ... }
|
2343
|
+
// x.init.id = { name };
|
2344
|
+
|
2345
|
+
// const func = generateFunc(scope, x.init);
|
2346
|
+
|
2347
|
+
// out.push(
|
2348
|
+
// ...number(func.index - importedFuncs.length),
|
2349
|
+
// [ global ? Opcodes.global_set : Opcodes.local_set, idx ],
|
2350
|
+
|
2351
|
+
// ...setType(scope, name, TYPES.function)
|
2352
|
+
// );
|
2353
|
+
|
2354
|
+
// continue;
|
2355
|
+
// }
|
2356
|
+
|
2357
|
+
const generated = generate(scope, x.init, global, name);
|
2358
|
+
if (scope.arrays?.get(name) != null) {
|
2359
|
+
// hack to set local as pointer before
|
2360
|
+
out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2361
|
+
if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
|
2362
|
+
generated.pop();
|
2363
|
+
out = out.concat(generated);
|
2364
|
+
} else {
|
2365
|
+
out = out.concat(generated);
|
2366
|
+
out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2367
|
+
}
|
1960
2368
|
|
1961
|
-
out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
1962
2369
|
out.push(...setType(scope, name, getNodeType(scope, x.init)));
|
1963
2370
|
}
|
1964
2371
|
|
@@ -1969,8 +2376,10 @@ const generateVar = (scope, decl) => {
|
|
1969
2376
|
return out;
|
1970
2377
|
};
|
1971
2378
|
|
1972
|
-
|
2379
|
+
// todo: optimize this func for valueUnused
|
2380
|
+
const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
1973
2381
|
const { type, name } = decl.left;
|
2382
|
+
const [ local, isGlobal ] = lookupName(scope, name);
|
1974
2383
|
|
1975
2384
|
if (type === 'ObjectPattern') {
|
1976
2385
|
// hack: ignore object parts of `var a = {} = 2`
|
@@ -1980,26 +2389,44 @@ const generateAssign = (scope, decl) => {
|
|
1980
2389
|
if (isFuncType(decl.right.type)) {
|
1981
2390
|
// hack for a = function () { ... }
|
1982
2391
|
decl.right.id = { name };
|
1983
|
-
|
1984
|
-
|
2392
|
+
|
2393
|
+
const func = generateFunc(scope, decl.right);
|
2394
|
+
|
2395
|
+
return [
|
2396
|
+
...number(func.index - importedFuncs.length),
|
2397
|
+
...(local != null ? [
|
2398
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2399
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
2400
|
+
|
2401
|
+
...setType(scope, name, TYPES.function)
|
2402
|
+
] : [])
|
2403
|
+
];
|
1985
2404
|
}
|
1986
2405
|
|
2406
|
+
const op = decl.operator.slice(0, -1) || '=';
|
2407
|
+
|
1987
2408
|
// hack: .length setter
|
1988
2409
|
if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
|
1989
2410
|
const name = decl.left.object.name;
|
1990
|
-
const pointer = arrays
|
2411
|
+
const pointer = scope.arrays?.get(name);
|
1991
2412
|
|
1992
|
-
const aotPointer = pointer != null;
|
2413
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1993
2414
|
|
1994
2415
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
2416
|
+
const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1995
2417
|
|
1996
2418
|
return [
|
1997
2419
|
...(aotPointer ? number(0, Valtype.i32) : [
|
1998
2420
|
...generate(scope, decl.left.object),
|
1999
2421
|
Opcodes.i32_to_u
|
2000
2422
|
]),
|
2423
|
+
...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
2001
2424
|
|
2002
|
-
...generate(scope, decl.right),
|
2425
|
+
...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
|
2426
|
+
[ Opcodes.local_get, pointerTmp ],
|
2427
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
2428
|
+
Opcodes.i32_from_u
|
2429
|
+
], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
|
2003
2430
|
[ Opcodes.local_tee, newValueTmp ],
|
2004
2431
|
|
2005
2432
|
Opcodes.i32_to_u,
|
@@ -2009,21 +2436,19 @@ const generateAssign = (scope, decl) => {
|
|
2009
2436
|
];
|
2010
2437
|
}
|
2011
2438
|
|
2012
|
-
const op = decl.operator.slice(0, -1) || '=';
|
2013
|
-
|
2014
2439
|
// arr[i]
|
2015
2440
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
2016
2441
|
const name = decl.left.object.name;
|
2017
|
-
const pointer = arrays
|
2442
|
+
const pointer = scope.arrays?.get(name);
|
2018
2443
|
|
2019
|
-
const aotPointer = pointer != null;
|
2444
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2020
2445
|
|
2021
2446
|
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
2022
2447
|
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
2023
2448
|
|
2024
2449
|
return [
|
2025
2450
|
...typeSwitch(scope, getNodeType(scope, decl.left.object), {
|
2026
|
-
[TYPES.
|
2451
|
+
[TYPES.array]: [
|
2027
2452
|
...(aotPointer ? [] : [
|
2028
2453
|
...generate(scope, decl.left.object),
|
2029
2454
|
Opcodes.i32_to_u
|
@@ -2034,18 +2459,21 @@ const generateAssign = (scope, decl) => {
|
|
2034
2459
|
Opcodes.i32_to_u,
|
2035
2460
|
|
2036
2461
|
// turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2037
|
-
...number(ValtypeSize[valtype], Valtype.i32),
|
2462
|
+
...number(ValtypeSize[valtype] + 1, Valtype.i32),
|
2038
2463
|
[ Opcodes.i32_mul ],
|
2039
2464
|
...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
|
2040
2465
|
...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
2041
2466
|
|
2042
2467
|
...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
|
2043
2468
|
[ Opcodes.local_get, pointerTmp ],
|
2044
|
-
[ Opcodes.load,
|
2045
|
-
], generate(scope, decl.right),
|
2469
|
+
[ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
2470
|
+
], generate(scope, decl.right), [
|
2471
|
+
[ Opcodes.local_get, pointerTmp ],
|
2472
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
|
2473
|
+
], getNodeType(scope, decl.right), false, name, true)),
|
2046
2474
|
[ Opcodes.local_tee, newValueTmp ],
|
2047
2475
|
|
2048
|
-
[ Opcodes.store,
|
2476
|
+
[ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
2049
2477
|
],
|
2050
2478
|
|
2051
2479
|
default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
|
@@ -2072,9 +2500,7 @@ const generateAssign = (scope, decl) => {
|
|
2072
2500
|
];
|
2073
2501
|
}
|
2074
2502
|
|
2075
|
-
if (!name) return todo('destructuring is not supported yet');
|
2076
|
-
|
2077
|
-
const [ local, isGlobal ] = lookupName(scope, name);
|
2503
|
+
if (!name) return todo(scope, 'destructuring is not supported yet', true);
|
2078
2504
|
|
2079
2505
|
if (local === undefined) {
|
2080
2506
|
// todo: this should be a sloppy mode only thing
|
@@ -2120,9 +2546,7 @@ const generateAssign = (scope, decl) => {
|
|
2120
2546
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
2121
2547
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
2122
2548
|
|
2123
|
-
getLastType(scope)
|
2124
|
-
// hack: type is idx+1
|
2125
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2549
|
+
...setType(scope, name, getLastType(scope))
|
2126
2550
|
];
|
2127
2551
|
}
|
2128
2552
|
|
@@ -2133,9 +2557,7 @@ const generateAssign = (scope, decl) => {
|
|
2133
2557
|
|
2134
2558
|
// todo: string concat types
|
2135
2559
|
|
2136
|
-
|
2137
|
-
...number(TYPES.number, Valtype.i32),
|
2138
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2560
|
+
...setType(scope, name, TYPES.number)
|
2139
2561
|
];
|
2140
2562
|
};
|
2141
2563
|
|
@@ -2159,6 +2581,11 @@ const generateUnary = (scope, decl) => {
|
|
2159
2581
|
];
|
2160
2582
|
|
2161
2583
|
case '!':
|
2584
|
+
const arg = decl.argument;
|
2585
|
+
if (arg.type === "UnaryExpression" && arg.operator === "!") {
|
2586
|
+
// !!x -> is x truthy
|
2587
|
+
return truthy(scope, generate(scope, arg.argument), getNodeType(scope, arg.argument), false, false);
|
2588
|
+
}
|
2162
2589
|
// !=
|
2163
2590
|
return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
|
2164
2591
|
|
@@ -2181,7 +2608,7 @@ const generateUnary = (scope, decl) => {
|
|
2181
2608
|
return out;
|
2182
2609
|
}
|
2183
2610
|
|
2184
|
-
case 'delete':
|
2611
|
+
case 'delete': {
|
2185
2612
|
let toReturn = true, toGenerate = true;
|
2186
2613
|
|
2187
2614
|
if (decl.argument.type === 'Identifier') {
|
@@ -2203,40 +2630,61 @@ const generateUnary = (scope, decl) => {
|
|
2203
2630
|
|
2204
2631
|
out.push(...number(toReturn ? 1 : 0));
|
2205
2632
|
return out;
|
2633
|
+
}
|
2634
|
+
|
2635
|
+
case 'typeof': {
|
2636
|
+
let overrideType, toGenerate = true;
|
2206
2637
|
|
2207
|
-
|
2208
|
-
|
2638
|
+
if (decl.argument.type === 'Identifier') {
|
2639
|
+
const out = generateIdent(scope, decl.argument);
|
2640
|
+
|
2641
|
+
// if ReferenceError (undeclared var), ignore and return undefined
|
2642
|
+
if (out[1]) {
|
2643
|
+
// does not exist (2 ops from throw)
|
2644
|
+
overrideType = number(TYPES.undefined, Valtype.i32);
|
2645
|
+
toGenerate = false;
|
2646
|
+
}
|
2647
|
+
}
|
2648
|
+
|
2649
|
+
const out = toGenerate ? generate(scope, decl.argument) : [];
|
2650
|
+
disposeLeftover(out);
|
2651
|
+
|
2652
|
+
out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
|
2209
2653
|
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
2210
2654
|
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
2211
2655
|
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
2212
2656
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
2213
2657
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
2658
|
+
[TYPES.symbol]: makeString(scope, 'symbol', false, '#typeof_result'),
|
2214
2659
|
|
2215
|
-
[TYPES.
|
2660
|
+
[TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2216
2661
|
|
2217
2662
|
// object and internal types
|
2218
2663
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2219
|
-
});
|
2664
|
+
}));
|
2665
|
+
|
2666
|
+
return out;
|
2667
|
+
}
|
2220
2668
|
|
2221
2669
|
default:
|
2222
|
-
return todo(`unary operator ${decl.operator} not implemented yet
|
2670
|
+
return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
|
2223
2671
|
}
|
2224
2672
|
};
|
2225
2673
|
|
2226
|
-
const generateUpdate = (scope, decl) => {
|
2674
|
+
const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
|
2227
2675
|
const { name } = decl.argument;
|
2228
2676
|
|
2229
2677
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2230
2678
|
|
2231
2679
|
if (local === undefined) {
|
2232
|
-
return todo(`update expression with undefined variable
|
2680
|
+
return todo(scope, `update expression with undefined variable`, true);
|
2233
2681
|
}
|
2234
2682
|
|
2235
2683
|
const idx = local.idx;
|
2236
2684
|
const out = [];
|
2237
2685
|
|
2238
2686
|
out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2239
|
-
if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2687
|
+
if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2240
2688
|
|
2241
2689
|
switch (decl.operator) {
|
2242
2690
|
case '++':
|
@@ -2249,7 +2697,7 @@ const generateUpdate = (scope, decl) => {
|
|
2249
2697
|
}
|
2250
2698
|
|
2251
2699
|
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2252
|
-
if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2700
|
+
if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2253
2701
|
|
2254
2702
|
return out;
|
2255
2703
|
};
|
@@ -2284,21 +2732,16 @@ const generateConditional = (scope, decl) => {
|
|
2284
2732
|
out.push([ Opcodes.if, valtypeBinary ]);
|
2285
2733
|
depth.push('if');
|
2286
2734
|
|
2287
|
-
out.push(...generate(scope, decl.consequent));
|
2288
|
-
|
2289
|
-
// note type
|
2290
2735
|
out.push(
|
2291
|
-
...
|
2292
|
-
setLastType(scope)
|
2736
|
+
...generate(scope, decl.consequent),
|
2737
|
+
...setLastType(scope, getNodeType(scope, decl.consequent))
|
2293
2738
|
);
|
2294
2739
|
|
2295
2740
|
out.push([ Opcodes.else ]);
|
2296
|
-
out.push(...generate(scope, decl.alternate));
|
2297
2741
|
|
2298
|
-
// note type
|
2299
2742
|
out.push(
|
2300
|
-
...
|
2301
|
-
setLastType(scope)
|
2743
|
+
...generate(scope, decl.alternate),
|
2744
|
+
...setLastType(scope, getNodeType(scope, decl.alternate))
|
2302
2745
|
);
|
2303
2746
|
|
2304
2747
|
out.push([ Opcodes.end ]);
|
@@ -2312,7 +2755,7 @@ const generateFor = (scope, decl) => {
|
|
2312
2755
|
const out = [];
|
2313
2756
|
|
2314
2757
|
if (decl.init) {
|
2315
|
-
out.push(...generate(scope, decl.init));
|
2758
|
+
out.push(...generate(scope, decl.init, false, undefined, true));
|
2316
2759
|
disposeLeftover(out);
|
2317
2760
|
}
|
2318
2761
|
|
@@ -2330,7 +2773,7 @@ const generateFor = (scope, decl) => {
|
|
2330
2773
|
out.push(...generate(scope, decl.body));
|
2331
2774
|
out.push([ Opcodes.end ]);
|
2332
2775
|
|
2333
|
-
if (decl.update) out.push(...generate(scope, decl.update));
|
2776
|
+
if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
|
2334
2777
|
|
2335
2778
|
out.push([ Opcodes.br, 1 ]);
|
2336
2779
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2358,6 +2801,36 @@ const generateWhile = (scope, decl) => {
|
|
2358
2801
|
return out;
|
2359
2802
|
};
|
2360
2803
|
|
2804
|
+
const generateDoWhile = (scope, decl) => {
|
2805
|
+
const out = [];
|
2806
|
+
|
2807
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
2808
|
+
depth.push('dowhile');
|
2809
|
+
|
2810
|
+
// block for break (includes all)
|
2811
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2812
|
+
depth.push('block');
|
2813
|
+
|
2814
|
+
// block for continue
|
2815
|
+
// includes body but not test+loop so we can exit body at anytime
|
2816
|
+
// and still test+loop after
|
2817
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2818
|
+
depth.push('block');
|
2819
|
+
|
2820
|
+
out.push(...generate(scope, decl.body));
|
2821
|
+
|
2822
|
+
out.push([ Opcodes.end ]);
|
2823
|
+
depth.pop();
|
2824
|
+
|
2825
|
+
out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2826
|
+
out.push([ Opcodes.br_if, 1 ]);
|
2827
|
+
|
2828
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
2829
|
+
depth.pop(); depth.pop();
|
2830
|
+
|
2831
|
+
return out;
|
2832
|
+
};
|
2833
|
+
|
2361
2834
|
const generateForOf = (scope, decl) => {
|
2362
2835
|
const out = [];
|
2363
2836
|
|
@@ -2394,7 +2867,10 @@ const generateForOf = (scope, decl) => {
|
|
2394
2867
|
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2395
2868
|
}
|
2396
2869
|
|
2870
|
+
// if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
|
2871
|
+
|
2397
2872
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2873
|
+
if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
|
2398
2874
|
|
2399
2875
|
depth.push('block');
|
2400
2876
|
depth.push('block');
|
@@ -2403,6 +2879,7 @@ const generateForOf = (scope, decl) => {
|
|
2403
2879
|
// hack: this is naughty and will break things!
|
2404
2880
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2405
2881
|
if (pages.hasAnyString) {
|
2882
|
+
// todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
|
2406
2883
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2407
2884
|
rawElements: new Array(1)
|
2408
2885
|
}, isGlobal, leftName, true, 'i16');
|
@@ -2411,13 +2888,16 @@ const generateForOf = (scope, decl) => {
|
|
2411
2888
|
// set type for local
|
2412
2889
|
// todo: optimize away counter and use end pointer
|
2413
2890
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2414
|
-
[TYPES.
|
2415
|
-
...setType(scope, leftName, TYPES.number),
|
2416
|
-
|
2891
|
+
[TYPES.array]: [
|
2417
2892
|
[ Opcodes.loop, Blocktype.void ],
|
2418
2893
|
|
2419
2894
|
[ Opcodes.local_get, pointer ],
|
2420
|
-
[ Opcodes.load,
|
2895
|
+
[ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
2896
|
+
|
2897
|
+
...setType(scope, leftName, [
|
2898
|
+
[ Opcodes.local_get, pointer ],
|
2899
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
|
2900
|
+
]),
|
2421
2901
|
|
2422
2902
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2423
2903
|
|
@@ -2426,9 +2906,9 @@ const generateForOf = (scope, decl) => {
|
|
2426
2906
|
...generate(scope, decl.body),
|
2427
2907
|
[ Opcodes.end ],
|
2428
2908
|
|
2429
|
-
// increment iter pointer by valtype size
|
2909
|
+
// increment iter pointer by valtype size + 1
|
2430
2910
|
[ Opcodes.local_get, pointer ],
|
2431
|
-
...number(ValtypeSize[valtype], Valtype.i32),
|
2911
|
+
...number(ValtypeSize[valtype] + 1, Valtype.i32),
|
2432
2912
|
[ Opcodes.i32_add ],
|
2433
2913
|
[ Opcodes.local_set, pointer ],
|
2434
2914
|
|
@@ -2494,6 +2974,94 @@ const generateForOf = (scope, decl) => {
|
|
2494
2974
|
[ Opcodes.end ],
|
2495
2975
|
[ Opcodes.end ]
|
2496
2976
|
],
|
2977
|
+
[TYPES.bytestring]: [
|
2978
|
+
...setType(scope, leftName, TYPES.bytestring),
|
2979
|
+
|
2980
|
+
[ Opcodes.loop, Blocktype.void ],
|
2981
|
+
|
2982
|
+
// setup new/out array
|
2983
|
+
...newOut,
|
2984
|
+
[ Opcodes.drop ],
|
2985
|
+
|
2986
|
+
...number(0, Valtype.i32), // base 0 for store after
|
2987
|
+
|
2988
|
+
// load current string ind {arg}
|
2989
|
+
[ Opcodes.local_get, pointer ],
|
2990
|
+
[ Opcodes.local_get, counter ],
|
2991
|
+
[ Opcodes.i32_add ],
|
2992
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
2993
|
+
|
2994
|
+
// store to new string ind 0
|
2995
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2996
|
+
|
2997
|
+
// return new string (page)
|
2998
|
+
...number(newPointer),
|
2999
|
+
|
3000
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
3001
|
+
|
3002
|
+
[ Opcodes.block, Blocktype.void ],
|
3003
|
+
[ Opcodes.block, Blocktype.void ],
|
3004
|
+
...generate(scope, decl.body),
|
3005
|
+
[ Opcodes.end ],
|
3006
|
+
|
3007
|
+
// increment iter pointer
|
3008
|
+
// [ Opcodes.local_get, pointer ],
|
3009
|
+
// ...number(1, Valtype.i32),
|
3010
|
+
// [ Opcodes.i32_add ],
|
3011
|
+
// [ Opcodes.local_set, pointer ],
|
3012
|
+
|
3013
|
+
// increment counter by 1
|
3014
|
+
[ Opcodes.local_get, counter ],
|
3015
|
+
...number(1, Valtype.i32),
|
3016
|
+
[ Opcodes.i32_add ],
|
3017
|
+
[ Opcodes.local_tee, counter ],
|
3018
|
+
|
3019
|
+
// loop if counter != length
|
3020
|
+
[ Opcodes.local_get, length ],
|
3021
|
+
[ Opcodes.i32_ne ],
|
3022
|
+
[ Opcodes.br_if, 1 ],
|
3023
|
+
|
3024
|
+
[ Opcodes.end ],
|
3025
|
+
[ Opcodes.end ]
|
3026
|
+
],
|
3027
|
+
[TYPES.set]: [
|
3028
|
+
[ Opcodes.loop, Blocktype.void ],
|
3029
|
+
|
3030
|
+
[ Opcodes.local_get, pointer ],
|
3031
|
+
[ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
3032
|
+
|
3033
|
+
...setType(scope, leftName, [
|
3034
|
+
[ Opcodes.local_get, pointer ],
|
3035
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
|
3036
|
+
]),
|
3037
|
+
|
3038
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
3039
|
+
|
3040
|
+
[ Opcodes.block, Blocktype.void ],
|
3041
|
+
[ Opcodes.block, Blocktype.void ],
|
3042
|
+
...generate(scope, decl.body),
|
3043
|
+
[ Opcodes.end ],
|
3044
|
+
|
3045
|
+
// increment iter pointer by valtype size + 1
|
3046
|
+
[ Opcodes.local_get, pointer ],
|
3047
|
+
...number(ValtypeSize[valtype] + 1, Valtype.i32),
|
3048
|
+
[ Opcodes.i32_add ],
|
3049
|
+
[ Opcodes.local_set, pointer ],
|
3050
|
+
|
3051
|
+
// increment counter by 1
|
3052
|
+
[ Opcodes.local_get, counter ],
|
3053
|
+
...number(1, Valtype.i32),
|
3054
|
+
[ Opcodes.i32_add ],
|
3055
|
+
[ Opcodes.local_tee, counter ],
|
3056
|
+
|
3057
|
+
// loop if counter != length
|
3058
|
+
[ Opcodes.local_get, length ],
|
3059
|
+
[ Opcodes.i32_ne ],
|
3060
|
+
[ Opcodes.br_if, 1 ],
|
3061
|
+
|
3062
|
+
[ Opcodes.end ],
|
3063
|
+
[ Opcodes.end ]
|
3064
|
+
],
|
2497
3065
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2498
3066
|
}, Blocktype.void));
|
2499
3067
|
|
@@ -2504,28 +3072,65 @@ const generateForOf = (scope, decl) => {
|
|
2504
3072
|
return out;
|
2505
3073
|
};
|
2506
3074
|
|
3075
|
+
// find the nearest loop in depth map by type
|
2507
3076
|
const getNearestLoop = () => {
|
2508
3077
|
for (let i = depth.length - 1; i >= 0; i--) {
|
2509
|
-
if (
|
3078
|
+
if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
|
2510
3079
|
}
|
2511
3080
|
|
2512
3081
|
return -1;
|
2513
3082
|
};
|
2514
3083
|
|
2515
3084
|
const generateBreak = (scope, decl) => {
|
2516
|
-
const
|
3085
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
3086
|
+
const type = depth[target];
|
3087
|
+
|
3088
|
+
// different loop types have different branch offsets
|
3089
|
+
// as they have different wasm block/loop/if structures
|
3090
|
+
// we need to use the right offset by type to branch to the one we want
|
3091
|
+
// for a break: exit the loop without executing anything else inside it
|
3092
|
+
const offset = ({
|
3093
|
+
for: 2, // loop > if (wanted branch) > block (we are here)
|
3094
|
+
while: 2, // loop > if (wanted branch) (we are here)
|
3095
|
+
dowhile: 2, // loop > block (wanted branch) > block (we are here)
|
3096
|
+
forof: 2, // loop > block (wanted branch) > block (we are here)
|
3097
|
+
if: 1 // break inside if, branch 0 to skip the rest of the if
|
3098
|
+
})[type];
|
3099
|
+
|
2517
3100
|
return [
|
2518
|
-
[ Opcodes.br, ...signedLEB128(
|
3101
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2519
3102
|
];
|
2520
3103
|
};
|
2521
3104
|
|
2522
3105
|
const generateContinue = (scope, decl) => {
|
2523
|
-
const
|
3106
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
3107
|
+
const type = depth[target];
|
3108
|
+
|
3109
|
+
// different loop types have different branch offsets
|
3110
|
+
// as they have different wasm block/loop/if structures
|
3111
|
+
// we need to use the right offset by type to branch to the one we want
|
3112
|
+
// for a continue: do test for the loop, and then loop depending on that success
|
3113
|
+
const offset = ({
|
3114
|
+
for: 3, // loop (wanted branch) > if > block (we are here)
|
3115
|
+
while: 1, // loop (wanted branch) > if (we are here)
|
3116
|
+
dowhile: 3, // loop > block > block (wanted branch) (we are here)
|
3117
|
+
forof: 3 // loop > block > block (wanted branch) (we are here)
|
3118
|
+
})[type];
|
3119
|
+
|
2524
3120
|
return [
|
2525
|
-
[ Opcodes.br, ...signedLEB128(
|
3121
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2526
3122
|
];
|
2527
3123
|
};
|
2528
3124
|
|
3125
|
+
const generateLabel = (scope, decl) => {
|
3126
|
+
scope.labels ??= new Map();
|
3127
|
+
|
3128
|
+
const name = decl.label.name;
|
3129
|
+
scope.labels.set(name, depth.length);
|
3130
|
+
|
3131
|
+
return generate(scope, decl.body);
|
3132
|
+
};
|
3133
|
+
|
2529
3134
|
const generateThrow = (scope, decl) => {
|
2530
3135
|
scope.throws = true;
|
2531
3136
|
|
@@ -2546,6 +3151,9 @@ const generateThrow = (scope, decl) => {
|
|
2546
3151
|
let exceptId = exceptions.push({ constructor, message }) - 1;
|
2547
3152
|
let tagIdx = tags[0].idx;
|
2548
3153
|
|
3154
|
+
scope.exceptions ??= [];
|
3155
|
+
scope.exceptions.push(exceptId);
|
3156
|
+
|
2549
3157
|
// todo: write a description of how this works lol
|
2550
3158
|
|
2551
3159
|
return [
|
@@ -2555,14 +3163,18 @@ const generateThrow = (scope, decl) => {
|
|
2555
3163
|
};
|
2556
3164
|
|
2557
3165
|
const generateTry = (scope, decl) => {
|
2558
|
-
|
3166
|
+
// todo: handle control-flow pre-exit for finally
|
3167
|
+
// "Immediately before a control-flow statement (return, throw, break, continue) is executed in the try block or catch block."
|
2559
3168
|
|
2560
3169
|
const out = [];
|
2561
3170
|
|
3171
|
+
const finalizer = decl.finalizer ? generate(scope, decl.finalizer) : [];
|
3172
|
+
|
2562
3173
|
out.push([ Opcodes.try, Blocktype.void ]);
|
2563
3174
|
depth.push('try');
|
2564
3175
|
|
2565
3176
|
out.push(...generate(scope, decl.block));
|
3177
|
+
out.push(...finalizer);
|
2566
3178
|
|
2567
3179
|
if (decl.handler) {
|
2568
3180
|
depth.pop();
|
@@ -2570,6 +3182,7 @@ const generateTry = (scope, decl) => {
|
|
2570
3182
|
|
2571
3183
|
out.push([ Opcodes.catch_all ]);
|
2572
3184
|
out.push(...generate(scope, decl.handler.body));
|
3185
|
+
out.push(...finalizer);
|
2573
3186
|
}
|
2574
3187
|
|
2575
3188
|
out.push([ Opcodes.end ]);
|
@@ -2582,15 +3195,8 @@ const generateEmpty = (scope, decl) => {
|
|
2582
3195
|
return [];
|
2583
3196
|
};
|
2584
3197
|
|
2585
|
-
const generateAssignPat = (scope, decl) => {
|
2586
|
-
// TODO
|
2587
|
-
// if identifier declared, use that
|
2588
|
-
// else, use default (right)
|
2589
|
-
return todo('assignment pattern (optional arg)');
|
2590
|
-
};
|
2591
|
-
|
2592
3198
|
let pages = new Map();
|
2593
|
-
const allocPage = (reason, type) => {
|
3199
|
+
const allocPage = (scope, reason, type) => {
|
2594
3200
|
if (pages.has(reason)) return pages.get(reason).ind;
|
2595
3201
|
|
2596
3202
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
@@ -2601,16 +3207,20 @@ const allocPage = (reason, type) => {
|
|
2601
3207
|
const ind = pages.size;
|
2602
3208
|
pages.set(reason, { ind, type });
|
2603
3209
|
|
2604
|
-
|
3210
|
+
scope.pages ??= new Map();
|
3211
|
+
scope.pages.set(reason, { ind, type });
|
3212
|
+
|
3213
|
+
if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2605
3214
|
|
2606
3215
|
return ind;
|
2607
3216
|
};
|
2608
3217
|
|
3218
|
+
// todo: add scope.pages
|
2609
3219
|
const freePage = reason => {
|
2610
3220
|
const { ind } = pages.get(reason);
|
2611
3221
|
pages.delete(reason);
|
2612
3222
|
|
2613
|
-
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
3223
|
+
if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2614
3224
|
|
2615
3225
|
return ind;
|
2616
3226
|
};
|
@@ -2658,19 +3268,30 @@ const getAllocType = itemType => {
|
|
2658
3268
|
}
|
2659
3269
|
};
|
2660
3270
|
|
2661
|
-
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
3271
|
+
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype, typed = false) => {
|
2662
3272
|
const out = [];
|
2663
3273
|
|
3274
|
+
scope.arrays ??= new Map();
|
3275
|
+
|
2664
3276
|
let firstAssign = false;
|
2665
|
-
if (!arrays.has(name) || name === '$undeclared') {
|
3277
|
+
if (!scope.arrays.has(name) || name === '$undeclared') {
|
2666
3278
|
firstAssign = true;
|
2667
3279
|
|
2668
3280
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2669
3281
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2670
|
-
|
3282
|
+
|
3283
|
+
let page;
|
3284
|
+
if (Prefs.scopedPageNames) page = allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType);
|
3285
|
+
else page = allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType);
|
3286
|
+
|
3287
|
+
// hack: use 1 for page 0 pointer for fast truthiness
|
3288
|
+
const ptr = page === 0 ? 1 : (page * pageSize);
|
3289
|
+
scope.arrays.set(name, ptr);
|
2671
3290
|
}
|
2672
3291
|
|
2673
|
-
const pointer = arrays.get(name);
|
3292
|
+
const pointer = scope.arrays.get(name);
|
3293
|
+
|
3294
|
+
const local = global ? globals[name] : scope.locals[name];
|
2674
3295
|
|
2675
3296
|
const useRawElements = !!decl.rawElements;
|
2676
3297
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
@@ -2678,19 +3299,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2678
3299
|
const valtype = itemTypeToValtype[itemType];
|
2679
3300
|
const length = elements.length;
|
2680
3301
|
|
2681
|
-
if (firstAssign && useRawElements) {
|
2682
|
-
|
3302
|
+
if (firstAssign && useRawElements && !Prefs.noData) {
|
3303
|
+
// if length is 0 memory/data will just be 0000... anyway
|
3304
|
+
if (length !== 0) {
|
3305
|
+
let bytes = compileBytes(length, 'i32');
|
2683
3306
|
|
2684
|
-
|
2685
|
-
|
3307
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
3308
|
+
if (elements[i] == null) continue;
|
2686
3309
|
|
2687
|
-
|
2688
|
-
|
3310
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
3311
|
+
}
|
2689
3312
|
|
2690
|
-
|
2691
|
-
|
2692
|
-
|
2693
|
-
|
3313
|
+
const ind = data.push({
|
3314
|
+
offset: pointer,
|
3315
|
+
bytes
|
3316
|
+
}) - 1;
|
3317
|
+
|
3318
|
+
scope.data ??= [];
|
3319
|
+
scope.data.push(ind);
|
3320
|
+
}
|
2694
3321
|
|
2695
3322
|
// local value as pointer
|
2696
3323
|
out.push(...number(pointer));
|
@@ -2698,33 +3325,109 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2698
3325
|
return [ out, pointer ];
|
2699
3326
|
}
|
2700
3327
|
|
2701
|
-
|
3328
|
+
const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
|
3329
|
+
if (pointerTmp != null) {
|
3330
|
+
out.push(
|
3331
|
+
[ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
3332
|
+
Opcodes.i32_to_u,
|
3333
|
+
[ Opcodes.local_set, pointerTmp ]
|
3334
|
+
);
|
3335
|
+
}
|
3336
|
+
|
3337
|
+
const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
|
3338
|
+
|
3339
|
+
// store length
|
2702
3340
|
out.push(
|
2703
|
-
...
|
3341
|
+
...pointerWasm,
|
2704
3342
|
...number(length, Valtype.i32),
|
2705
|
-
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1,
|
3343
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
|
2706
3344
|
);
|
2707
3345
|
|
2708
3346
|
const storeOp = StoreOps[itemType];
|
2709
|
-
|
3347
|
+
const sizePerEl = ValtypeSize[itemType] + (typed ? 1 : 0);
|
2710
3348
|
if (!initEmpty) for (let i = 0; i < length; i++) {
|
2711
3349
|
if (elements[i] == null) continue;
|
2712
3350
|
|
3351
|
+
const offset = ValtypeSize.i32 + i * sizePerEl;
|
2713
3352
|
out.push(
|
2714
|
-
...
|
3353
|
+
...pointerWasm,
|
2715
3354
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2716
|
-
[ storeOp,
|
3355
|
+
[ storeOp, 0, ...unsignedLEB128(offset) ],
|
3356
|
+
...(!typed ? [] : [ // typed presumes !useRawElements
|
3357
|
+
...pointerWasm,
|
3358
|
+
...getNodeType(scope, elements[i]),
|
3359
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(offset + ValtypeSize[itemType]) ]
|
3360
|
+
])
|
2717
3361
|
);
|
2718
3362
|
}
|
2719
3363
|
|
2720
3364
|
// local value as pointer
|
2721
|
-
out.push(...
|
3365
|
+
out.push(...pointerWasm, Opcodes.i32_from_u);
|
2722
3366
|
|
2723
3367
|
return [ out, pointer ];
|
2724
3368
|
};
|
2725
3369
|
|
3370
|
+
const storeArray = (scope, array, index, element, aotPointer = null) => {
|
3371
|
+
if (!Array.isArray(element)) element = generate(scope, element);
|
3372
|
+
if (typeof index === 'number') index = number(index);
|
3373
|
+
|
3374
|
+
const offset = localTmp(scope, '#storeArray_offset', Valtype.i32);
|
3375
|
+
|
3376
|
+
return [
|
3377
|
+
// calculate offset
|
3378
|
+
...index,
|
3379
|
+
Opcodes.i32_to_u,
|
3380
|
+
...number(ValtypeSize[valtype] + 1, Valtype.i32),
|
3381
|
+
[ Opcodes.i32_mul ],
|
3382
|
+
...(aotPointer ? [] : [
|
3383
|
+
...array,
|
3384
|
+
Opcodes.i32_to_u,
|
3385
|
+
[ Opcodes.i32_add ],
|
3386
|
+
]),
|
3387
|
+
[ Opcodes.local_set, offset ],
|
3388
|
+
|
3389
|
+
// store value
|
3390
|
+
[ Opcodes.local_get, offset ],
|
3391
|
+
...generate(scope, element),
|
3392
|
+
[ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
3393
|
+
|
3394
|
+
// store type
|
3395
|
+
[ Opcodes.local_get, offset ],
|
3396
|
+
...getNodeType(scope, element),
|
3397
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
|
3398
|
+
];
|
3399
|
+
};
|
3400
|
+
|
3401
|
+
const loadArray = (scope, array, index, aotPointer = null) => {
|
3402
|
+
if (typeof index === 'number') index = number(index);
|
3403
|
+
|
3404
|
+
const offset = localTmp(scope, '#loadArray_offset', Valtype.i32);
|
3405
|
+
|
3406
|
+
return [
|
3407
|
+
// calculate offset
|
3408
|
+
...index,
|
3409
|
+
Opcodes.i32_to_u,
|
3410
|
+
...number(ValtypeSize[valtype] + 1, Valtype.i32),
|
3411
|
+
[ Opcodes.i32_mul ],
|
3412
|
+
...(aotPointer ? [] : [
|
3413
|
+
...array,
|
3414
|
+
Opcodes.i32_to_u,
|
3415
|
+
[ Opcodes.i32_add ],
|
3416
|
+
]),
|
3417
|
+
[ Opcodes.local_set, offset ],
|
3418
|
+
|
3419
|
+
// load value
|
3420
|
+
[ Opcodes.local_get, offset ],
|
3421
|
+
[ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
3422
|
+
|
3423
|
+
// load type
|
3424
|
+
[ Opcodes.local_get, offset ],
|
3425
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
|
3426
|
+
];
|
3427
|
+
};
|
3428
|
+
|
2726
3429
|
const byteStringable = str => {
|
2727
|
-
if (!
|
3430
|
+
if (!Prefs.bytestring) return false;
|
2728
3431
|
|
2729
3432
|
for (let i = 0; i < str.length; i++) {
|
2730
3433
|
if (str.charCodeAt(i) > 0xFF) return false;
|
@@ -2735,7 +3438,7 @@ const byteStringable = str => {
|
|
2735
3438
|
|
2736
3439
|
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2737
3440
|
const rawElements = new Array(str.length);
|
2738
|
-
let byteStringable =
|
3441
|
+
let byteStringable = Prefs.bytestring;
|
2739
3442
|
for (let i = 0; i < str.length; i++) {
|
2740
3443
|
const c = str.charCodeAt(i);
|
2741
3444
|
rawElements[i] = c;
|
@@ -2750,31 +3453,152 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
|
|
2750
3453
|
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2751
3454
|
};
|
2752
3455
|
|
2753
|
-
let arrays = new Map();
|
2754
3456
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
2755
|
-
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
3457
|
+
return makeArray(scope, decl, global, name, initEmpty, valtype, true)[0];
|
2756
3458
|
};
|
2757
3459
|
|
2758
|
-
|
3460
|
+
const generateObject = (scope, decl, global = false, name = '$undeclared') => {
|
3461
|
+
if (decl.properties.length > 0) return todo(scope, 'objects are not supported yet', true);
|
3462
|
+
|
3463
|
+
return [
|
3464
|
+
...number(1),
|
3465
|
+
...setLastType(scope, TYPES.object)
|
3466
|
+
];
|
3467
|
+
};
|
3468
|
+
|
3469
|
+
const withType = (scope, wasm, type) => [
|
3470
|
+
...wasm,
|
3471
|
+
...setLastType(scope, type)
|
3472
|
+
];
|
3473
|
+
|
3474
|
+
const generateMember = (scope, decl, _global, _name) => {
|
2759
3475
|
const name = decl.object.name;
|
2760
|
-
const pointer = arrays.get(name);
|
2761
3476
|
|
2762
|
-
|
3477
|
+
// hack: process.argv[n]
|
3478
|
+
if (name === '__process_argv') {
|
3479
|
+
const setPointer = scope.arrays?.get(_name);
|
3480
|
+
|
3481
|
+
return [
|
3482
|
+
...number(decl.property.value - 1),
|
3483
|
+
...(setPointer ? number(setPointer) : makeArray(scope, { elements: [] }, undefined, undefined, true, 'i8')[0]),
|
3484
|
+
[ Opcodes.call, importedFuncs.__Porffor_readArgv ]
|
3485
|
+
];
|
3486
|
+
}
|
3487
|
+
|
3488
|
+
const pointer = scope.arrays?.get(name);
|
3489
|
+
const aotPointer = Prefs.aotPointerOpt && pointer;
|
3490
|
+
|
3491
|
+
// hack: .name
|
3492
|
+
if (decl.property.name === 'name') {
|
3493
|
+
if (hasFuncWithName(name)) {
|
3494
|
+
let nameProp = name;
|
3495
|
+
|
3496
|
+
// eg: __String_prototype_toLowerCase -> toLowerCase
|
3497
|
+
if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
|
3498
|
+
|
3499
|
+
return withType(scope, makeString(scope, nameProp, _global, _name, true), TYPES.bytestring);
|
3500
|
+
} else {
|
3501
|
+
return withType(scope, number(0), TYPES.undefined);
|
3502
|
+
}
|
3503
|
+
}
|
2763
3504
|
|
2764
3505
|
// hack: .length
|
2765
3506
|
if (decl.property.name === 'length') {
|
2766
|
-
|
3507
|
+
const func = funcs.find(x => x.name === name);
|
3508
|
+
if (func) {
|
3509
|
+
const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
|
3510
|
+
return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
|
3511
|
+
}
|
3512
|
+
|
3513
|
+
if (builtinFuncs[name + '$constructor']) {
|
3514
|
+
const regularFunc = builtinFuncs[name];
|
3515
|
+
const regularParams = regularFunc.typedParams ? (regularFunc.params.length / 2) : regularFunc.params.length;
|
3516
|
+
|
3517
|
+
const constructorFunc = builtinFuncs[name + '$constructor'];
|
3518
|
+
const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
|
3519
|
+
|
3520
|
+
return withType(scope, number(Math.max(regularParams, constructorParams)), TYPES.number);
|
3521
|
+
}
|
3522
|
+
|
3523
|
+
if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
|
3524
|
+
if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params), TYPES.number);
|
3525
|
+
if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
|
3526
|
+
|
3527
|
+
if (Prefs.fastLength) {
|
3528
|
+
// presume valid length object
|
3529
|
+
return [
|
3530
|
+
...(aotPointer ? number(0, Valtype.i32) : [
|
3531
|
+
...generate(scope, decl.object),
|
3532
|
+
Opcodes.i32_to_u
|
3533
|
+
]),
|
3534
|
+
|
3535
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
|
3536
|
+
Opcodes.i32_from_u
|
3537
|
+
];
|
3538
|
+
}
|
3539
|
+
|
3540
|
+
const type = getNodeType(scope, decl.object);
|
3541
|
+
const known = knownType(scope, type);
|
3542
|
+
if (known != null) {
|
3543
|
+
if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(known)) return [
|
3544
|
+
...(aotPointer ? number(0, Valtype.i32) : [
|
3545
|
+
...generate(scope, decl.object),
|
3546
|
+
Opcodes.i32_to_u
|
3547
|
+
]),
|
3548
|
+
|
3549
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
|
3550
|
+
Opcodes.i32_from_u
|
3551
|
+
];
|
3552
|
+
|
3553
|
+
return number(0);
|
3554
|
+
}
|
3555
|
+
|
2767
3556
|
return [
|
2768
|
-
...(
|
2769
|
-
|
2770
|
-
|
2771
|
-
|
3557
|
+
...typeIsOneOf(getNodeType(scope, decl.object), [ TYPES.string, TYPES.bytestring, TYPES.array ]),
|
3558
|
+
[ Opcodes.if, valtypeBinary ],
|
3559
|
+
...(aotPointer ? number(0, Valtype.i32) : [
|
3560
|
+
...generate(scope, decl.object),
|
3561
|
+
Opcodes.i32_to_u
|
3562
|
+
]),
|
2772
3563
|
|
2773
|
-
|
2774
|
-
|
3564
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
|
3565
|
+
Opcodes.i32_from_u,
|
3566
|
+
|
3567
|
+
...setLastType(scope, TYPES.number),
|
3568
|
+
[ Opcodes.else ],
|
3569
|
+
...number(0),
|
3570
|
+
...setLastType(scope, TYPES.undefined),
|
3571
|
+
[ Opcodes.end ]
|
2775
3572
|
];
|
2776
3573
|
}
|
2777
3574
|
|
3575
|
+
// todo: generate this array procedurally during builtinFuncs creation
|
3576
|
+
if (['size', 'description'].includes(decl.property.name)) {
|
3577
|
+
const bc = {};
|
3578
|
+
const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
|
3579
|
+
|
3580
|
+
if (cands.length > 0) {
|
3581
|
+
for (const x of cands) {
|
3582
|
+
const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
|
3583
|
+
if (type == null) continue;
|
3584
|
+
|
3585
|
+
bc[type] = generateCall(scope, {
|
3586
|
+
callee: {
|
3587
|
+
type: 'Identifier',
|
3588
|
+
name: x
|
3589
|
+
},
|
3590
|
+
arguments: [ decl.object ],
|
3591
|
+
_protoInternalCall: true
|
3592
|
+
});
|
3593
|
+
}
|
3594
|
+
}
|
3595
|
+
|
3596
|
+
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
3597
|
+
...bc,
|
3598
|
+
default: withType(scope, number(0), TYPES.undefined)
|
3599
|
+
}, valtypeBinary);
|
3600
|
+
}
|
3601
|
+
|
2778
3602
|
const object = generate(scope, decl.object);
|
2779
3603
|
const property = generate(scope, decl.property);
|
2780
3604
|
|
@@ -2788,26 +3612,9 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2788
3612
|
}
|
2789
3613
|
|
2790
3614
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2791
|
-
[TYPES.
|
2792
|
-
|
2793
|
-
...
|
2794
|
-
|
2795
|
-
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2796
|
-
Opcodes.i32_to_u,
|
2797
|
-
...number(ValtypeSize[valtype], Valtype.i32),
|
2798
|
-
[ Opcodes.i32_mul ],
|
2799
|
-
|
2800
|
-
...(aotPointer ? [] : [
|
2801
|
-
...object,
|
2802
|
-
Opcodes.i32_to_u,
|
2803
|
-
[ Opcodes.i32_add ]
|
2804
|
-
]),
|
2805
|
-
|
2806
|
-
// read from memory
|
2807
|
-
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2808
|
-
|
2809
|
-
...number(TYPES.number, Valtype.i32),
|
2810
|
-
setLastType(scope)
|
3615
|
+
[TYPES.array]: [
|
3616
|
+
...loadArray(scope, object, property, aotPointer),
|
3617
|
+
...setLastType(scope)
|
2811
3618
|
],
|
2812
3619
|
|
2813
3620
|
[TYPES.string]: [
|
@@ -2837,11 +3644,9 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2837
3644
|
|
2838
3645
|
// return new string (page)
|
2839
3646
|
...number(newPointer),
|
2840
|
-
|
2841
|
-
...number(TYPES.string, Valtype.i32),
|
2842
|
-
setLastType(scope)
|
3647
|
+
...setLastType(scope, TYPES.string)
|
2843
3648
|
],
|
2844
|
-
[TYPES.
|
3649
|
+
[TYPES.bytestring]: [
|
2845
3650
|
// setup new/out array
|
2846
3651
|
...newOut,
|
2847
3652
|
[ Opcodes.drop ],
|
@@ -2858,19 +3663,17 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2858
3663
|
]),
|
2859
3664
|
|
2860
3665
|
// load current string ind {arg}
|
2861
|
-
[ Opcodes.i32_load8_u,
|
3666
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2862
3667
|
|
2863
3668
|
// store to new string ind 0
|
2864
|
-
[ Opcodes.i32_store8,
|
3669
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2865
3670
|
|
2866
3671
|
// return new string (page)
|
2867
3672
|
...number(newPointer),
|
2868
|
-
|
2869
|
-
...number(TYPES._bytestring, Valtype.i32),
|
2870
|
-
setLastType(scope)
|
3673
|
+
...setLastType(scope, TYPES.bytestring)
|
2871
3674
|
],
|
2872
3675
|
|
2873
|
-
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
|
3676
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
|
2874
3677
|
});
|
2875
3678
|
};
|
2876
3679
|
|
@@ -2880,28 +3683,36 @@ const objectHack = node => {
|
|
2880
3683
|
if (!node) return node;
|
2881
3684
|
|
2882
3685
|
if (node.type === 'MemberExpression') {
|
2883
|
-
|
3686
|
+
const out = (() => {
|
3687
|
+
if (node.computed || node.optional) return;
|
2884
3688
|
|
2885
|
-
|
3689
|
+
let objectName = node.object.name;
|
2886
3690
|
|
2887
|
-
|
2888
|
-
|
3691
|
+
// if object is not identifier or another member exp, give up
|
3692
|
+
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
|
3693
|
+
if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
|
2889
3694
|
|
2890
|
-
|
3695
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2891
3696
|
|
2892
|
-
|
2893
|
-
|
3697
|
+
// if .name or .length, give up (hack within a hack!)
|
3698
|
+
if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
|
3699
|
+
node.object = objectHack(node.object);
|
3700
|
+
return;
|
3701
|
+
}
|
2894
3702
|
|
2895
|
-
|
2896
|
-
|
3703
|
+
// no object name, give up
|
3704
|
+
if (!objectName) return;
|
2897
3705
|
|
2898
|
-
|
2899
|
-
|
3706
|
+
const name = '__' + objectName + '_' + node.property.name;
|
3707
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2900
3708
|
|
2901
|
-
|
2902
|
-
|
2903
|
-
|
2904
|
-
|
3709
|
+
return {
|
3710
|
+
type: 'Identifier',
|
3711
|
+
name
|
3712
|
+
};
|
3713
|
+
})();
|
3714
|
+
|
3715
|
+
if (out) return out;
|
2905
3716
|
}
|
2906
3717
|
|
2907
3718
|
for (const x in node) {
|
@@ -2915,31 +3726,45 @@ const objectHack = node => {
|
|
2915
3726
|
};
|
2916
3727
|
|
2917
3728
|
const generateFunc = (scope, decl) => {
|
2918
|
-
if (decl.async) return todo('async functions are not supported');
|
2919
|
-
if (decl.generator) return todo('generator functions are not supported');
|
3729
|
+
if (decl.async) return todo(scope, 'async functions are not supported');
|
3730
|
+
if (decl.generator) return todo(scope, 'generator functions are not supported');
|
2920
3731
|
|
2921
3732
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2922
3733
|
const params = decl.params ?? [];
|
2923
3734
|
|
2924
|
-
// const innerScope = { ...scope };
|
2925
3735
|
// TODO: share scope/locals between !!!
|
2926
|
-
const
|
3736
|
+
const func = {
|
2927
3737
|
locals: {},
|
2928
3738
|
localInd: 0,
|
2929
3739
|
// value, type
|
2930
3740
|
returns: [ valtypeBinary, Valtype.i32 ],
|
2931
3741
|
throws: false,
|
2932
|
-
name
|
3742
|
+
name,
|
3743
|
+
index: currentFuncIndex++
|
2933
3744
|
};
|
2934
3745
|
|
3746
|
+
if (typedInput && decl.returnType) {
|
3747
|
+
const { type } = extractTypeAnnotation(decl.returnType);
|
3748
|
+
// if (type != null && !Prefs.indirectCalls) {
|
3749
|
+
if (type != null) {
|
3750
|
+
func.returnType = type;
|
3751
|
+
func.returns = [ valtypeBinary ];
|
3752
|
+
}
|
3753
|
+
}
|
3754
|
+
|
2935
3755
|
for (let i = 0; i < params.length; i++) {
|
2936
|
-
|
3756
|
+
const name = params[i].name;
|
3757
|
+
// if (name == null) return todo('non-identifier args are not supported');
|
3758
|
+
|
3759
|
+
allocVar(func, name, false);
|
2937
3760
|
|
2938
3761
|
if (typedInput && params[i].typeAnnotation) {
|
2939
|
-
addVarMetadata(
|
3762
|
+
addVarMetadata(func, name, false, extractTypeAnnotation(params[i]));
|
2940
3763
|
}
|
2941
3764
|
}
|
2942
3765
|
|
3766
|
+
func.params = Object.values(func.locals).map(x => x.type);
|
3767
|
+
|
2943
3768
|
let body = objectHack(decl.body);
|
2944
3769
|
if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
|
2945
3770
|
// hack: () => 0 -> () => return 0
|
@@ -2949,35 +3774,23 @@ const generateFunc = (scope, decl) => {
|
|
2949
3774
|
};
|
2950
3775
|
}
|
2951
3776
|
|
2952
|
-
const wasm = generate(innerScope, body);
|
2953
|
-
const func = {
|
2954
|
-
name,
|
2955
|
-
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2956
|
-
index: currentFuncIndex++,
|
2957
|
-
...innerScope
|
2958
|
-
};
|
2959
3777
|
funcIndex[name] = func.index;
|
3778
|
+
funcs.push(func);
|
2960
3779
|
|
2961
|
-
|
2962
|
-
|
2963
|
-
|
2964
|
-
|
2965
|
-
}
|
2966
|
-
}
|
3780
|
+
const wasm = generate(func, body);
|
3781
|
+
func.wasm = wasm;
|
3782
|
+
|
3783
|
+
if (name === 'main') func.gotLastType = true;
|
2967
3784
|
|
2968
3785
|
// add end return if not found
|
2969
3786
|
if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
|
2970
3787
|
wasm.push(
|
2971
3788
|
...number(0),
|
2972
|
-
...number(TYPES.undefined, Valtype.i32),
|
3789
|
+
...(func.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
|
2973
3790
|
[ Opcodes.return ]
|
2974
3791
|
);
|
2975
3792
|
}
|
2976
3793
|
|
2977
|
-
func.wasm = wasm;
|
2978
|
-
|
2979
|
-
funcs.push(func);
|
2980
|
-
|
2981
3794
|
return func;
|
2982
3795
|
};
|
2983
3796
|
|
@@ -2992,16 +3805,6 @@ const generateCode = (scope, decl) => {
|
|
2992
3805
|
};
|
2993
3806
|
|
2994
3807
|
const internalConstrs = {
|
2995
|
-
Boolean: {
|
2996
|
-
generate: (scope, decl) => {
|
2997
|
-
if (decl.arguments.length === 0) return number(0);
|
2998
|
-
|
2999
|
-
// should generate/run all args
|
3000
|
-
return truthy(scope, generate(scope, decl.arguments[0]), getNodeType(scope, decl.arguments[0]), false, false);
|
3001
|
-
},
|
3002
|
-
type: TYPES.boolean
|
3003
|
-
},
|
3004
|
-
|
3005
3808
|
Array: {
|
3006
3809
|
generate: (scope, decl, global, name) => {
|
3007
3810
|
// new Array(i0, i1, ...)
|
@@ -3019,7 +3822,7 @@ const internalConstrs = {
|
|
3019
3822
|
|
3020
3823
|
// todo: check in wasm instead of here
|
3021
3824
|
const literalValue = arg.value ?? 0;
|
3022
|
-
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
|
3825
|
+
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
|
3023
3826
|
|
3024
3827
|
return [
|
3025
3828
|
...number(0, Valtype.i32),
|
@@ -3030,7 +3833,8 @@ const internalConstrs = {
|
|
3030
3833
|
...number(pointer)
|
3031
3834
|
];
|
3032
3835
|
},
|
3033
|
-
type: TYPES.
|
3836
|
+
type: TYPES.array,
|
3837
|
+
length: 1
|
3034
3838
|
},
|
3035
3839
|
|
3036
3840
|
__Array_of: {
|
@@ -3041,27 +3845,138 @@ const internalConstrs = {
|
|
3041
3845
|
elements: decl.arguments
|
3042
3846
|
}, global, name);
|
3043
3847
|
},
|
3044
|
-
type: TYPES.
|
3848
|
+
type: TYPES.array,
|
3849
|
+
notConstr: true,
|
3850
|
+
length: 0
|
3851
|
+
},
|
3852
|
+
|
3853
|
+
__Porffor_fastOr: {
|
3854
|
+
generate: (scope, decl) => {
|
3855
|
+
const out = [];
|
3856
|
+
|
3857
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3858
|
+
out.push(
|
3859
|
+
...generate(scope, decl.arguments[i]),
|
3860
|
+
Opcodes.i32_to_u,
|
3861
|
+
...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
|
3862
|
+
);
|
3863
|
+
}
|
3864
|
+
|
3865
|
+
out.push(Opcodes.i32_from_u);
|
3866
|
+
|
3867
|
+
return out;
|
3868
|
+
},
|
3869
|
+
type: TYPES.boolean,
|
3045
3870
|
notConstr: true
|
3046
|
-
}
|
3047
|
-
|
3871
|
+
},
|
3872
|
+
|
3873
|
+
__Porffor_fastAnd: {
|
3874
|
+
generate: (scope, decl) => {
|
3875
|
+
const out = [];
|
3876
|
+
|
3877
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3878
|
+
out.push(
|
3879
|
+
...generate(scope, decl.arguments[i]),
|
3880
|
+
Opcodes.i32_to_u,
|
3881
|
+
...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
|
3882
|
+
);
|
3883
|
+
}
|
3884
|
+
|
3885
|
+
out.push(Opcodes.i32_from_u);
|
3886
|
+
|
3887
|
+
return out;
|
3888
|
+
},
|
3889
|
+
type: TYPES.boolean,
|
3890
|
+
notConstr: true
|
3891
|
+
},
|
3892
|
+
|
3893
|
+
Boolean: {
|
3894
|
+
generate: (scope, decl) => {
|
3895
|
+
// todo: boolean object when used as constructor
|
3896
|
+
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
3897
|
+
return truthy(scope, generate(scope, arg), getNodeType(scope, arg), false, false, 'full');
|
3898
|
+
},
|
3899
|
+
type: TYPES.boolean,
|
3900
|
+
length: 1
|
3901
|
+
},
|
3902
|
+
|
3903
|
+
__Math_max: {
|
3904
|
+
generate: (scope, decl) => {
|
3905
|
+
const out = [
|
3906
|
+
...number(-Infinity)
|
3907
|
+
];
|
3048
3908
|
|
3049
|
-
|
3050
|
-
|
3051
|
-
|
3052
|
-
|
3053
|
-
|
3054
|
-
|
3055
|
-
// process.exit();
|
3056
|
-
// }
|
3057
|
-
// if (Array.isArray(x)) check(x);
|
3058
|
-
// }
|
3059
|
-
// };
|
3060
|
-
// if (Array.isArray(a) && !new Error().stack.includes('node:')) check(a);
|
3061
|
-
// // if (Array.isArray(a)) check(a);
|
3909
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3910
|
+
out.push(
|
3911
|
+
...generate(scope, decl.arguments[i]),
|
3912
|
+
[ Opcodes.f64_max ]
|
3913
|
+
);
|
3914
|
+
}
|
3062
3915
|
|
3063
|
-
|
3064
|
-
|
3916
|
+
return out;
|
3917
|
+
},
|
3918
|
+
type: TYPES.number,
|
3919
|
+
notConstr: true,
|
3920
|
+
length: 2
|
3921
|
+
},
|
3922
|
+
|
3923
|
+
__Math_min: {
|
3924
|
+
generate: (scope, decl) => {
|
3925
|
+
const out = [
|
3926
|
+
...number(Infinity)
|
3927
|
+
];
|
3928
|
+
|
3929
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3930
|
+
out.push(
|
3931
|
+
...generate(scope, decl.arguments[i]),
|
3932
|
+
[ Opcodes.f64_min ]
|
3933
|
+
);
|
3934
|
+
}
|
3935
|
+
|
3936
|
+
return out;
|
3937
|
+
},
|
3938
|
+
type: TYPES.number,
|
3939
|
+
notConstr: true,
|
3940
|
+
length: 2
|
3941
|
+
},
|
3942
|
+
|
3943
|
+
__console_log: {
|
3944
|
+
generate: (scope, decl) => {
|
3945
|
+
const out = [];
|
3946
|
+
|
3947
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3948
|
+
out.push(
|
3949
|
+
...generateCall(scope, {
|
3950
|
+
callee: {
|
3951
|
+
type: 'Identifier',
|
3952
|
+
name: '__Porffor_print'
|
3953
|
+
},
|
3954
|
+
arguments: [ decl.arguments[i] ]
|
3955
|
+
}),
|
3956
|
+
|
3957
|
+
// print space
|
3958
|
+
...(i !== decl.arguments.length - 1 ? [
|
3959
|
+
...number(32),
|
3960
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3961
|
+
] : [])
|
3962
|
+
);
|
3963
|
+
}
|
3964
|
+
|
3965
|
+
// print newline
|
3966
|
+
out.push(
|
3967
|
+
...number(10),
|
3968
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3969
|
+
);
|
3970
|
+
|
3971
|
+
out.push(...number(UNDEFINED));
|
3972
|
+
|
3973
|
+
return out;
|
3974
|
+
},
|
3975
|
+
type: TYPES.undefined,
|
3976
|
+
notConstr: true,
|
3977
|
+
length: 0
|
3978
|
+
}
|
3979
|
+
};
|
3065
3980
|
|
3066
3981
|
export default program => {
|
3067
3982
|
globals = {};
|
@@ -3071,20 +3986,23 @@ export default program => {
|
|
3071
3986
|
funcs = [];
|
3072
3987
|
funcIndex = {};
|
3073
3988
|
depth = [];
|
3074
|
-
arrays = new Map();
|
3075
3989
|
pages = new Map();
|
3076
3990
|
data = [];
|
3077
3991
|
currentFuncIndex = importedFuncs.length;
|
3078
3992
|
|
3079
3993
|
globalThis.valtype = 'f64';
|
3080
3994
|
|
3081
|
-
const valtypeOpt = process.argv.find(x => x.startsWith('
|
3995
|
+
const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
|
3082
3996
|
if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
|
3083
3997
|
|
3084
3998
|
globalThis.valtypeBinary = Valtype[valtype];
|
3085
3999
|
|
3086
4000
|
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
3087
4001
|
|
4002
|
+
globalThis.pageSize = PageSize;
|
4003
|
+
const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
|
4004
|
+
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
4005
|
+
|
3088
4006
|
// set generic opcodes for current valtype
|
3089
4007
|
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
3090
4008
|
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
@@ -3093,10 +4011,10 @@ export default program => {
|
|
3093
4011
|
Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
|
3094
4012
|
Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
|
3095
4013
|
|
3096
|
-
Opcodes.i32_to = [ [
|
3097
|
-
Opcodes.i32_to_u = [ [
|
3098
|
-
Opcodes.i32_from = [ [
|
3099
|
-
Opcodes.i32_from_u = [ [
|
4014
|
+
Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
|
4015
|
+
Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
|
4016
|
+
Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
|
4017
|
+
Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
|
3100
4018
|
|
3101
4019
|
Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
|
3102
4020
|
Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
|
@@ -3109,10 +4027,6 @@ export default program => {
|
|
3109
4027
|
|
3110
4028
|
program.id = { name: 'main' };
|
3111
4029
|
|
3112
|
-
globalThis.pageSize = PageSize;
|
3113
|
-
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
3114
|
-
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3115
|
-
|
3116
4030
|
const scope = {
|
3117
4031
|
locals: {},
|
3118
4032
|
localInd: 0
|
@@ -3123,11 +4037,10 @@ export default program => {
|
|
3123
4037
|
body: program.body
|
3124
4038
|
};
|
3125
4039
|
|
3126
|
-
if (
|
4040
|
+
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
3127
4041
|
|
3128
|
-
generateFunc(scope, program);
|
4042
|
+
const main = generateFunc(scope, program);
|
3129
4043
|
|
3130
|
-
const main = funcs[funcs.length - 1];
|
3131
4044
|
main.export = true;
|
3132
4045
|
main.returns = [ valtypeBinary, Valtype.i32 ];
|
3133
4046
|
|
@@ -3140,7 +4053,11 @@ export default program => {
|
|
3140
4053
|
}
|
3141
4054
|
|
3142
4055
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
3143
|
-
|
4056
|
+
if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
|
4057
|
+
main.wasm.splice(main.wasm.length - 1, 1);
|
4058
|
+
} else {
|
4059
|
+
main.returns = [];
|
4060
|
+
}
|
3144
4061
|
}
|
3145
4062
|
|
3146
4063
|
if (lastInst[0] === Opcodes.call) {
|
@@ -3150,7 +4067,7 @@ export default program => {
|
|
3150
4067
|
}
|
3151
4068
|
|
3152
4069
|
// if blank main func and other exports, remove it
|
3153
|
-
if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(
|
4070
|
+
if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(main.index - importedFuncs.length, 1);
|
3154
4071
|
|
3155
4072
|
return { funcs, globals, tags, exceptions, pages, data };
|
3156
4073
|
};
|