porffor 0.2.0-fde989a → 0.14.0-0d97d1e6a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +256 -0
- package/LICENSE +20 -20
- package/README.md +131 -86
- package/asur/README.md +2 -0
- package/asur/index.js +1262 -0
- package/byg/index.js +216 -0
- package/compiler/2c.js +2 -53
- package/compiler/{sections.js → assemble.js} +95 -21
- package/compiler/builtins/annexb_string.js +72 -0
- package/compiler/builtins/annexb_string.ts +18 -0
- package/compiler/builtins/array.ts +145 -0
- package/compiler/builtins/base64.ts +76 -0
- package/compiler/builtins/boolean.ts +18 -0
- package/compiler/builtins/crypto.ts +120 -0
- package/compiler/builtins/date.ts +2067 -0
- package/compiler/builtins/escape.ts +141 -0
- package/compiler/builtins/function.ts +5 -0
- package/compiler/builtins/int.ts +145 -0
- package/compiler/builtins/number.ts +529 -0
- package/compiler/builtins/object.ts +4 -0
- package/compiler/builtins/porffor.d.ts +60 -0
- package/compiler/builtins/set.ts +187 -0
- package/compiler/builtins/string.ts +1080 -0
- package/compiler/builtins/symbol.ts +61 -0
- package/compiler/builtins.js +440 -285
- package/compiler/{codeGen.js → codegen.js} +1116 -489
- 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 +1670 -0
- package/compiler/index.js +27 -43
- package/compiler/log.js +6 -3
- package/compiler/opt.js +55 -41
- package/compiler/parse.js +38 -30
- package/compiler/precompile.js +120 -0
- package/compiler/prefs.js +31 -0
- package/compiler/prototype.js +31 -46
- package/compiler/types.js +38 -0
- package/compiler/wasmSpec.js +33 -8
- package/compiler/wrap.js +107 -71
- package/package.json +9 -5
- package/porf +2 -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 +78 -11
- package/runner/profiler.js +75 -0
- package/runner/repl.js +40 -13
- 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
43
|
const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
|
44
|
+
const hasFuncWithName = name => {
|
45
|
+
const func = funcs.find(x => x.name === name);
|
46
|
+
return !!(func || builtinFuncs[name] || importedFuncs[name] || internalConstrs[name]);
|
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,7 +134,7 @@ 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':
|
@@ -151,16 +148,17 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
151
148
|
const funcsBefore = funcs.length;
|
152
149
|
generate(scope, decl.declaration);
|
153
150
|
|
154
|
-
if (funcsBefore
|
155
|
-
|
156
|
-
|
157
|
-
|
151
|
+
if (funcsBefore !== funcs.length) {
|
152
|
+
// new func added
|
153
|
+
const newFunc = funcs[funcs.length - 1];
|
154
|
+
newFunc.export = true;
|
155
|
+
}
|
158
156
|
|
159
157
|
return [];
|
160
158
|
|
161
159
|
case 'TaggedTemplateExpression': {
|
162
160
|
const funcs = {
|
163
|
-
|
161
|
+
__Porffor_wasm: str => {
|
164
162
|
let out = [];
|
165
163
|
|
166
164
|
for (const line of str.split('\n')) {
|
@@ -168,8 +166,8 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
168
166
|
if (asm[0] === '') continue; // blank
|
169
167
|
|
170
168
|
if (asm[0] === 'local') {
|
171
|
-
const [ name,
|
172
|
-
scope.locals[name] = { idx:
|
169
|
+
const [ name, type ] = asm.slice(1);
|
170
|
+
scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
|
173
171
|
continue;
|
174
172
|
}
|
175
173
|
|
@@ -179,52 +177,74 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
179
177
|
}
|
180
178
|
|
181
179
|
if (asm[0] === 'memory') {
|
182
|
-
allocPage('asm instrinsic');
|
180
|
+
allocPage(scope, 'asm instrinsic');
|
183
181
|
// todo: add to store/load offset insts
|
184
182
|
continue;
|
185
183
|
}
|
186
184
|
|
187
185
|
let inst = Opcodes[asm[0].replace('.', '_')];
|
188
|
-
if (
|
186
|
+
if (inst == null) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
189
187
|
|
190
188
|
if (!Array.isArray(inst)) inst = [ inst ];
|
191
|
-
const immediates = asm.slice(1).map(x =>
|
189
|
+
const immediates = asm.slice(1).map(x => {
|
190
|
+
const int = parseInt(x);
|
191
|
+
if (Number.isNaN(int)) return scope.locals[x]?.idx ?? globals[x].idx;
|
192
|
+
return int;
|
193
|
+
});
|
192
194
|
|
193
|
-
out.push([ ...inst, ...immediates ]);
|
195
|
+
out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
|
194
196
|
}
|
195
197
|
|
196
198
|
return out;
|
197
199
|
},
|
198
200
|
|
199
201
|
__Porffor_bs: str => [
|
200
|
-
...makeString(scope, str,
|
202
|
+
...makeString(scope, str, global, name, true),
|
201
203
|
|
202
|
-
...
|
203
|
-
|
204
|
+
...(name ? setType(scope, name, TYPES.bytestring) : [
|
205
|
+
...number(TYPES.bytestring, Valtype.i32),
|
206
|
+
...setLastType(scope)
|
207
|
+
])
|
204
208
|
],
|
205
209
|
__Porffor_s: str => [
|
206
|
-
...makeString(scope, str,
|
210
|
+
...makeString(scope, str, global, name, false),
|
207
211
|
|
208
|
-
...
|
209
|
-
|
212
|
+
...(name ? setType(scope, name, TYPES.string) : [
|
213
|
+
...number(TYPES.string, Valtype.i32),
|
214
|
+
...setLastType(scope)
|
215
|
+
])
|
210
216
|
],
|
211
217
|
};
|
212
218
|
|
213
|
-
const
|
219
|
+
const func = decl.tag.name;
|
214
220
|
// hack for inline asm
|
215
|
-
if (!funcs[
|
221
|
+
if (!funcs[func]) return todo(scope, 'tagged template expressions not implemented', true);
|
222
|
+
|
223
|
+
const { quasis, expressions } = decl.quasi;
|
224
|
+
let str = quasis[0].value.raw;
|
225
|
+
|
226
|
+
for (let i = 0; i < expressions.length; i++) {
|
227
|
+
const e = expressions[i];
|
228
|
+
if (!e.name) {
|
229
|
+
if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
|
230
|
+
str += lookupName(scope, e.left.name)[0].idx + e.right.value;
|
231
|
+
} else todo(scope, 'unsupported expression in intrinsic');
|
232
|
+
} else str += lookupName(scope, e.name)[0].idx;
|
233
|
+
|
234
|
+
str += quasis[i + 1].value.raw;
|
235
|
+
}
|
216
236
|
|
217
|
-
|
218
|
-
return funcs[name](str);
|
237
|
+
return funcs[func](str);
|
219
238
|
}
|
220
239
|
|
221
240
|
default:
|
222
|
-
|
223
|
-
|
241
|
+
// ignore typescript nodes
|
242
|
+
if (decl.type.startsWith('TS') ||
|
243
|
+
decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
|
224
244
|
return [];
|
225
245
|
}
|
226
246
|
|
227
|
-
return todo(`no generation for ${decl.type}!`);
|
247
|
+
return todo(scope, `no generation for ${decl.type}!`);
|
228
248
|
}
|
229
249
|
};
|
230
250
|
|
@@ -252,7 +272,7 @@ const lookupName = (scope, _name) => {
|
|
252
272
|
return [ undefined, undefined ];
|
253
273
|
};
|
254
274
|
|
255
|
-
const internalThrow = (scope, constructor, message, expectsValue =
|
275
|
+
const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
|
256
276
|
...generateThrow(scope, {
|
257
277
|
argument: {
|
258
278
|
type: 'NewExpression',
|
@@ -276,7 +296,10 @@ const generateIdent = (scope, decl) => {
|
|
276
296
|
|
277
297
|
if (Object.hasOwn(builtinVars, name)) {
|
278
298
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
279
|
-
|
299
|
+
|
300
|
+
let wasm = builtinVars[name];
|
301
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
|
302
|
+
return wasm.slice();
|
280
303
|
}
|
281
304
|
|
282
305
|
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
@@ -284,12 +307,17 @@ const generateIdent = (scope, decl) => {
|
|
284
307
|
return number(1);
|
285
308
|
}
|
286
309
|
|
310
|
+
if (isExistingProtoFunc(name)) {
|
311
|
+
// todo: return an actual something
|
312
|
+
return number(1);
|
313
|
+
}
|
314
|
+
|
287
315
|
if (local?.idx === undefined) {
|
288
316
|
// 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
317
|
if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
|
318
|
+
|
319
|
+
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name] - importedFuncs.length);
|
320
|
+
if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name] - importedFuncs.length);
|
293
321
|
}
|
294
322
|
|
295
323
|
if (local?.idx === undefined && rawName.startsWith('__')) {
|
@@ -314,14 +342,16 @@ const generateReturn = (scope, decl) => {
|
|
314
342
|
// just bare "return"
|
315
343
|
return [
|
316
344
|
...number(UNDEFINED), // "undefined" if func returns
|
317
|
-
...
|
345
|
+
...(scope.returnType != null ? [] : [
|
346
|
+
...number(TYPES.undefined, Valtype.i32) // type undefined
|
347
|
+
]),
|
318
348
|
[ Opcodes.return ]
|
319
349
|
];
|
320
350
|
}
|
321
351
|
|
322
352
|
return [
|
323
353
|
...generate(scope, decl.argument),
|
324
|
-
...getNodeType(scope, decl.argument),
|
354
|
+
...(scope.returnType != null ? [] : getNodeType(scope, decl.argument)),
|
325
355
|
[ Opcodes.return ]
|
326
356
|
];
|
327
357
|
};
|
@@ -335,7 +365,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
335
365
|
return idx;
|
336
366
|
};
|
337
367
|
|
338
|
-
const isIntOp = op => op && (op[0] >=
|
368
|
+
const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
|
369
|
+
const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
|
339
370
|
|
340
371
|
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
341
372
|
const checks = {
|
@@ -344,7 +375,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
344
375
|
'??': nullish
|
345
376
|
};
|
346
377
|
|
347
|
-
if (!checks[op]) return todo(`logic operator ${op} not implemented yet
|
378
|
+
if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
|
348
379
|
|
349
380
|
// generic structure for {a} OP {b}
|
350
381
|
// -->
|
@@ -352,8 +383,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
352
383
|
|
353
384
|
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
354
385
|
// (like if we are in an if condition - very common)
|
355
|
-
const leftIsInt =
|
356
|
-
const rightIsInt =
|
386
|
+
const leftIsInt = isFloatToIntOp(left[left.length - 1]);
|
387
|
+
const rightIsInt = isFloatToIntOp(right[right.length - 1]);
|
357
388
|
|
358
389
|
const canInt = leftIsInt && rightIsInt;
|
359
390
|
|
@@ -370,12 +401,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
370
401
|
...right,
|
371
402
|
// note type
|
372
403
|
...rightType,
|
373
|
-
setLastType(scope),
|
404
|
+
...setLastType(scope),
|
374
405
|
[ Opcodes.else ],
|
375
406
|
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
376
407
|
// note type
|
377
408
|
...leftType,
|
378
|
-
setLastType(scope),
|
409
|
+
...setLastType(scope),
|
379
410
|
[ Opcodes.end ],
|
380
411
|
Opcodes.i32_from
|
381
412
|
];
|
@@ -389,17 +420,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
389
420
|
...right,
|
390
421
|
// note type
|
391
422
|
...rightType,
|
392
|
-
setLastType(scope),
|
423
|
+
...setLastType(scope),
|
393
424
|
[ Opcodes.else ],
|
394
425
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
395
426
|
// note type
|
396
427
|
...leftType,
|
397
|
-
setLastType(scope),
|
428
|
+
...setLastType(scope),
|
398
429
|
[ Opcodes.end ]
|
399
430
|
];
|
400
431
|
};
|
401
432
|
|
402
|
-
const concatStrings = (scope, left, right, global, name, assign) => {
|
433
|
+
const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
|
403
434
|
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
404
435
|
// todo: convert left and right to strings if not
|
405
436
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -409,11 +440,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
409
440
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
410
441
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
411
442
|
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
if (assign) {
|
416
|
-
const pointer = arrays.get(name ?? '$undeclared');
|
443
|
+
if (assign && Prefs.aotPointerOpt) {
|
444
|
+
const pointer = scope.arrays?.get(name ?? '$undeclared');
|
417
445
|
|
418
446
|
return [
|
419
447
|
// setup right
|
@@ -438,11 +466,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
438
466
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
|
439
467
|
|
440
468
|
// copy right
|
441
|
-
// dst = out pointer + length size + current length *
|
469
|
+
// dst = out pointer + length size + current length * sizeof valtype
|
442
470
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
443
471
|
|
444
472
|
[ Opcodes.local_get, leftLength ],
|
445
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
473
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
446
474
|
[ Opcodes.i32_mul ],
|
447
475
|
[ Opcodes.i32_add ],
|
448
476
|
|
@@ -451,9 +479,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
451
479
|
...number(ValtypeSize.i32, Valtype.i32),
|
452
480
|
[ Opcodes.i32_add ],
|
453
481
|
|
454
|
-
// size = right length *
|
482
|
+
// size = right length * sizeof valtype
|
455
483
|
[ Opcodes.local_get, rightLength ],
|
456
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
484
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
457
485
|
[ Opcodes.i32_mul ],
|
458
486
|
|
459
487
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -511,11 +539,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
511
539
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
512
540
|
|
513
541
|
// copy right
|
514
|
-
// dst = out pointer + length size + left length *
|
542
|
+
// dst = out pointer + length size + left length * sizeof valtype
|
515
543
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
516
544
|
|
517
545
|
[ Opcodes.local_get, leftLength ],
|
518
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
546
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
519
547
|
[ Opcodes.i32_mul ],
|
520
548
|
[ Opcodes.i32_add ],
|
521
549
|
|
@@ -524,9 +552,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
524
552
|
...number(ValtypeSize.i32, Valtype.i32),
|
525
553
|
[ Opcodes.i32_add ],
|
526
554
|
|
527
|
-
// size = right length *
|
555
|
+
// size = right length * sizeof valtype
|
528
556
|
[ Opcodes.local_get, rightLength ],
|
529
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
557
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
530
558
|
[ Opcodes.i32_mul ],
|
531
559
|
|
532
560
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -536,7 +564,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
536
564
|
];
|
537
565
|
};
|
538
566
|
|
539
|
-
const compareStrings = (scope, left, right) => {
|
567
|
+
const compareStrings = (scope, left, right, bytestrings = false) => {
|
540
568
|
// todo: this should be rewritten into a func
|
541
569
|
// todo: convert left and right to strings if not
|
542
570
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -545,7 +573,6 @@ const compareStrings = (scope, left, right) => {
|
|
545
573
|
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
546
574
|
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
547
575
|
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
548
|
-
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
549
576
|
|
550
577
|
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
551
578
|
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
@@ -573,7 +600,6 @@ const compareStrings = (scope, left, right) => {
|
|
573
600
|
|
574
601
|
[ Opcodes.local_get, rightPointer ],
|
575
602
|
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
576
|
-
[ Opcodes.local_tee, rightLength ],
|
577
603
|
|
578
604
|
// fast path: check leftLength != rightLength
|
579
605
|
[ Opcodes.i32_ne ],
|
@@ -588,11 +614,13 @@ const compareStrings = (scope, left, right) => {
|
|
588
614
|
...number(0, Valtype.i32),
|
589
615
|
[ Opcodes.local_set, index ],
|
590
616
|
|
591
|
-
// setup index end as length * sizeof
|
617
|
+
// setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
|
592
618
|
// we do this instead of having to do mul/div each iter for perf™
|
593
619
|
[ Opcodes.local_get, leftLength ],
|
594
|
-
...
|
595
|
-
|
620
|
+
...(bytestrings ? [] : [
|
621
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
622
|
+
[ Opcodes.i32_mul ],
|
623
|
+
]),
|
596
624
|
[ Opcodes.local_set, indexEnd ],
|
597
625
|
|
598
626
|
// iterate over each char and check if eq
|
@@ -602,13 +630,17 @@ const compareStrings = (scope, left, right) => {
|
|
602
630
|
[ Opcodes.local_get, index ],
|
603
631
|
[ Opcodes.local_get, leftPointer ],
|
604
632
|
[ Opcodes.i32_add ],
|
605
|
-
|
633
|
+
bytestrings ?
|
634
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
635
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
606
636
|
|
607
637
|
// fetch right
|
608
638
|
[ Opcodes.local_get, index ],
|
609
639
|
[ Opcodes.local_get, rightPointer ],
|
610
640
|
[ Opcodes.i32_add ],
|
611
|
-
|
641
|
+
bytestrings ?
|
642
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
643
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
612
644
|
|
613
645
|
// not equal, "return" false
|
614
646
|
[ Opcodes.i32_ne ],
|
@@ -617,13 +649,13 @@ const compareStrings = (scope, left, right) => {
|
|
617
649
|
[ Opcodes.br, 2 ],
|
618
650
|
[ Opcodes.end ],
|
619
651
|
|
620
|
-
// index += sizeof
|
652
|
+
// index += sizeof valtype (1 for bytestring, 2 for string)
|
621
653
|
[ Opcodes.local_get, index ],
|
622
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
654
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
623
655
|
[ Opcodes.i32_add ],
|
624
656
|
[ Opcodes.local_tee, index ],
|
625
657
|
|
626
|
-
// if index != index end (length * sizeof
|
658
|
+
// if index != index end (length * sizeof valtype), loop
|
627
659
|
[ Opcodes.local_get, indexEnd ],
|
628
660
|
[ Opcodes.i32_ne ],
|
629
661
|
[ Opcodes.br_if, 0 ],
|
@@ -644,16 +676,18 @@ const compareStrings = (scope, left, right) => {
|
|
644
676
|
};
|
645
677
|
|
646
678
|
const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
647
|
-
if (
|
679
|
+
if (isFloatToIntOp(wasm[wasm.length - 1])) return [
|
648
680
|
...wasm,
|
649
681
|
...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
|
650
682
|
];
|
683
|
+
// if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
|
651
684
|
|
652
|
-
const
|
685
|
+
const useTmp = knownType(scope, type) == null;
|
686
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
653
687
|
|
654
688
|
const def = [
|
655
689
|
// if value != 0
|
656
|
-
[ Opcodes.local_get, tmp ],
|
690
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
657
691
|
|
658
692
|
// ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
659
693
|
...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
|
@@ -665,16 +699,16 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
665
699
|
|
666
700
|
return [
|
667
701
|
...wasm,
|
668
|
-
[ Opcodes.local_set, tmp ],
|
702
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
669
703
|
|
670
704
|
...typeSwitch(scope, type, {
|
671
705
|
// [TYPES.number]: def,
|
672
|
-
[TYPES.
|
706
|
+
[TYPES.array]: [
|
673
707
|
// arrays are always truthy
|
674
708
|
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
675
709
|
],
|
676
710
|
[TYPES.string]: [
|
677
|
-
[ Opcodes.local_get, tmp ],
|
711
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
678
712
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
679
713
|
|
680
714
|
// get length
|
@@ -685,8 +719,8 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
685
719
|
[ Opcodes.i32_eqz ], */
|
686
720
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
687
721
|
],
|
688
|
-
[TYPES.
|
689
|
-
|
722
|
+
[TYPES.bytestring]: [ // duplicate of string
|
723
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
690
724
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
691
725
|
|
692
726
|
// get length
|
@@ -700,18 +734,20 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
700
734
|
};
|
701
735
|
|
702
736
|
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
703
|
-
const
|
737
|
+
const useTmp = knownType(scope, type) == null;
|
738
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
739
|
+
|
704
740
|
return [
|
705
741
|
...wasm,
|
706
|
-
[ Opcodes.local_set, tmp ],
|
742
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
707
743
|
|
708
744
|
...typeSwitch(scope, type, {
|
709
|
-
[TYPES.
|
745
|
+
[TYPES.array]: [
|
710
746
|
// arrays are always truthy
|
711
747
|
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
712
748
|
],
|
713
749
|
[TYPES.string]: [
|
714
|
-
[ Opcodes.local_get, tmp ],
|
750
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
715
751
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
716
752
|
|
717
753
|
// get length
|
@@ -721,8 +757,8 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
721
757
|
[ Opcodes.i32_eqz ],
|
722
758
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
723
759
|
],
|
724
|
-
[TYPES.
|
725
|
-
[ Opcodes.local_get, tmp ],
|
760
|
+
[TYPES.bytestring]: [ // duplicate of string
|
761
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
726
762
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
727
763
|
|
728
764
|
// get length
|
@@ -734,7 +770,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
734
770
|
],
|
735
771
|
default: [
|
736
772
|
// if value == 0
|
737
|
-
[ Opcodes.local_get, tmp ],
|
773
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
738
774
|
|
739
775
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
740
776
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -744,10 +780,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
744
780
|
};
|
745
781
|
|
746
782
|
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
747
|
-
const
|
783
|
+
const useTmp = knownType(scope, type) == null;
|
784
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
785
|
+
|
748
786
|
return [
|
749
787
|
...wasm,
|
750
|
-
[ Opcodes.local_set, tmp ],
|
788
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
751
789
|
|
752
790
|
...typeSwitch(scope, type, {
|
753
791
|
[TYPES.undefined]: [
|
@@ -756,7 +794,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
756
794
|
],
|
757
795
|
[TYPES.object]: [
|
758
796
|
// object, null if == 0
|
759
|
-
[ Opcodes.local_get, tmp ],
|
797
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
760
798
|
|
761
799
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
762
800
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -785,39 +823,17 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
785
823
|
return performLogicOp(scope, op, left, right, leftType, rightType);
|
786
824
|
}
|
787
825
|
|
826
|
+
const knownLeft = knownType(scope, leftType);
|
827
|
+
const knownRight = knownType(scope, rightType);
|
828
|
+
|
788
829
|
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
789
830
|
const strictOp = op === '===' || op === '!==';
|
790
831
|
|
791
832
|
const startOut = [], endOut = [];
|
792
|
-
const
|
833
|
+
const finalize = out => startOut.concat(out, endOut);
|
793
834
|
|
794
835
|
// if strict (in)equal check types match
|
795
836
|
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
837
|
endOut.push(
|
822
838
|
...leftType,
|
823
839
|
...rightType,
|
@@ -834,31 +850,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
834
850
|
// todo: if equality op and an operand is undefined, return false
|
835
851
|
// todo: niche null hell with 0
|
836
852
|
|
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
|
-
|
853
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) {
|
854
|
+
if (op === '+') {
|
855
|
+
// todo: this should be dynamic too but for now only static
|
856
|
+
// string concat (a + b)
|
857
|
+
return concatStrings(scope, left, right, _global, _name, assign, false);
|
858
|
+
}
|
859
|
+
|
860
|
+
// not an equality op, NaN
|
861
|
+
if (!eqOp) return number(NaN);
|
862
|
+
|
863
|
+
// else leave bool ops
|
864
|
+
// todo: convert string to number if string and number/bool
|
865
|
+
// todo: string (>|>=|<|<=) string
|
866
|
+
|
867
|
+
// string comparison
|
868
|
+
if (op === '===' || op === '==') {
|
869
|
+
return compareStrings(scope, left, right);
|
870
|
+
}
|
871
|
+
|
872
|
+
if (op === '!==' || op === '!=') {
|
873
|
+
return [
|
874
|
+
...compareStrings(scope, left, right),
|
875
|
+
[ Opcodes.i32_eqz ]
|
876
|
+
];
|
877
|
+
}
|
878
|
+
}
|
879
|
+
|
880
|
+
if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) {
|
881
|
+
if (op === '+') {
|
882
|
+
// todo: this should be dynamic too but for now only static
|
883
|
+
// string concat (a + b)
|
884
|
+
return concatStrings(scope, left, right, _global, _name, assign, true);
|
885
|
+
}
|
886
|
+
|
887
|
+
// not an equality op, NaN
|
888
|
+
if (!eqOp) return number(NaN);
|
889
|
+
|
890
|
+
// else leave bool ops
|
891
|
+
// todo: convert string to number if string and number/bool
|
892
|
+
// todo: string (>|>=|<|<=) string
|
893
|
+
|
894
|
+
// string comparison
|
895
|
+
if (op === '===' || op === '==') {
|
896
|
+
return compareStrings(scope, left, right, true);
|
897
|
+
}
|
898
|
+
|
899
|
+
if (op === '!==' || op === '!=') {
|
900
|
+
return [
|
901
|
+
...compareStrings(scope, left, right, true),
|
902
|
+
[ Opcodes.i32_eqz ]
|
903
|
+
];
|
904
|
+
}
|
905
|
+
}
|
862
906
|
|
863
907
|
let ops = operatorOpcode[valtype][op];
|
864
908
|
|
@@ -868,33 +912,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
868
912
|
includeBuiltin(scope, builtinName);
|
869
913
|
const idx = funcIndex[builtinName];
|
870
914
|
|
871
|
-
return
|
915
|
+
return finalize([
|
872
916
|
...left,
|
873
917
|
...right,
|
874
918
|
[ Opcodes.call, idx ]
|
875
919
|
]);
|
876
920
|
}
|
877
921
|
|
878
|
-
if (!ops) return todo(`operator ${op} not implemented yet
|
922
|
+
if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
|
879
923
|
|
880
924
|
if (!Array.isArray(ops)) ops = [ ops ];
|
881
925
|
ops = [ ops ];
|
882
926
|
|
883
927
|
let tmpLeft, tmpRight;
|
884
928
|
// 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
|
-
}
|
929
|
+
// todo: intelligent partial skip later
|
930
|
+
// if neither known are string, stop this madness
|
931
|
+
// we already do known checks earlier, so don't need to recheck
|
894
932
|
|
933
|
+
if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
|
895
934
|
tmpLeft = localTmp(scope, '__tmpop_left');
|
896
935
|
tmpRight = localTmp(scope, '__tmpop_right');
|
897
936
|
|
937
|
+
// returns false for one string, one not - but more ops/slower
|
938
|
+
// ops.unshift(...stringOnly([
|
939
|
+
// // if left is string
|
940
|
+
// ...leftType,
|
941
|
+
// ...number(TYPES.string, Valtype.i32),
|
942
|
+
// [ Opcodes.i32_eq ],
|
943
|
+
|
944
|
+
// // if right is string
|
945
|
+
// ...rightType,
|
946
|
+
// ...number(TYPES.string, Valtype.i32),
|
947
|
+
// [ Opcodes.i32_eq ],
|
948
|
+
|
949
|
+
// // if either are true
|
950
|
+
// [ Opcodes.i32_or ],
|
951
|
+
// [ Opcodes.if, Blocktype.void ],
|
952
|
+
|
953
|
+
// // todo: convert non-strings to strings, for now fail immediately if one is not
|
954
|
+
// // if left is not string
|
955
|
+
// ...leftType,
|
956
|
+
// ...number(TYPES.string, Valtype.i32),
|
957
|
+
// [ Opcodes.i32_ne ],
|
958
|
+
|
959
|
+
// // if right is not string
|
960
|
+
// ...rightType,
|
961
|
+
// ...number(TYPES.string, Valtype.i32),
|
962
|
+
// [ Opcodes.i32_ne ],
|
963
|
+
|
964
|
+
// // if either are true
|
965
|
+
// [ Opcodes.i32_or ],
|
966
|
+
// [ Opcodes.if, Blocktype.void ],
|
967
|
+
// ...number(0, Valtype.i32),
|
968
|
+
// [ Opcodes.br, 2 ],
|
969
|
+
// [ Opcodes.end ],
|
970
|
+
|
971
|
+
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
972
|
+
// ...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
973
|
+
// [ Opcodes.br, 1 ],
|
974
|
+
// [ Opcodes.end ],
|
975
|
+
// ]));
|
976
|
+
|
977
|
+
// does not handle one string, one not (such cases go past)
|
898
978
|
ops.unshift(...stringOnly([
|
899
979
|
// if left is string
|
900
980
|
...leftType,
|
@@ -906,30 +986,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
906
986
|
...number(TYPES.string, Valtype.i32),
|
907
987
|
[ Opcodes.i32_eq ],
|
908
988
|
|
909
|
-
// if
|
910
|
-
[ Opcodes.
|
989
|
+
// if both are true
|
990
|
+
[ Opcodes.i32_and ],
|
911
991
|
[ Opcodes.if, Blocktype.void ],
|
992
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
993
|
+
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
994
|
+
[ Opcodes.br, 1 ],
|
995
|
+
[ Opcodes.end ],
|
912
996
|
|
913
|
-
//
|
914
|
-
// if left is not string
|
997
|
+
// if left is bytestring
|
915
998
|
...leftType,
|
916
|
-
...number(TYPES.
|
917
|
-
[ Opcodes.
|
999
|
+
...number(TYPES.bytestring, Valtype.i32),
|
1000
|
+
[ Opcodes.i32_eq ],
|
918
1001
|
|
919
|
-
// if right is
|
1002
|
+
// if right is bytestring
|
920
1003
|
...rightType,
|
921
|
-
...number(TYPES.
|
922
|
-
[ Opcodes.
|
1004
|
+
...number(TYPES.bytestring, Valtype.i32),
|
1005
|
+
[ Opcodes.i32_eq ],
|
923
1006
|
|
924
|
-
// if
|
925
|
-
[ Opcodes.
|
1007
|
+
// if both are true
|
1008
|
+
[ Opcodes.i32_and ],
|
926
1009
|
[ 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 ] ]),
|
1010
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
|
933
1011
|
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
934
1012
|
[ Opcodes.br, 1 ],
|
935
1013
|
[ Opcodes.end ],
|
@@ -941,9 +1019,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
941
1019
|
// endOut.push(stringOnly([ Opcodes.end ]));
|
942
1020
|
endOut.unshift(stringOnly([ Opcodes.end ]));
|
943
1021
|
// }
|
944
|
-
}
|
1022
|
+
}
|
945
1023
|
|
946
|
-
return
|
1024
|
+
return finalize([
|
947
1025
|
...left,
|
948
1026
|
...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
|
949
1027
|
...right,
|
@@ -960,7 +1038,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
960
1038
|
return out;
|
961
1039
|
};
|
962
1040
|
|
963
|
-
const
|
1041
|
+
const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals = [], returns = [], localInd = 0 }) => {
|
1042
|
+
return func({ name, params, locals, returns, localInd }, {
|
1043
|
+
TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
|
1044
|
+
builtin: name => {
|
1045
|
+
let idx = funcIndex[name] ?? importedFuncs[name];
|
1046
|
+
if (idx === undefined && builtinFuncs[name]) {
|
1047
|
+
includeBuiltin(null, name);
|
1048
|
+
idx = funcIndex[name];
|
1049
|
+
}
|
1050
|
+
|
1051
|
+
return idx;
|
1052
|
+
}
|
1053
|
+
});
|
1054
|
+
};
|
1055
|
+
|
1056
|
+
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
|
964
1057
|
const existing = funcs.find(x => x.name === name);
|
965
1058
|
if (existing) return existing;
|
966
1059
|
|
@@ -972,18 +1065,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
972
1065
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
973
1066
|
}
|
974
1067
|
|
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 });
|
1068
|
+
for (const x of _data) {
|
1069
|
+
const copy = { ...x };
|
1070
|
+
copy.offset += pages.size * pageSize;
|
1071
|
+
data.push(copy);
|
985
1072
|
}
|
986
1073
|
|
1074
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
|
1075
|
+
|
987
1076
|
let baseGlobalIdx, i = 0;
|
988
1077
|
for (const type of globalTypes) {
|
989
1078
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -1006,7 +1095,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
1006
1095
|
params,
|
1007
1096
|
locals,
|
1008
1097
|
returns,
|
1009
|
-
returnType:
|
1098
|
+
returnType: returnType ?? TYPES.number,
|
1010
1099
|
wasm,
|
1011
1100
|
internal: true,
|
1012
1101
|
index: currentFuncIndex++
|
@@ -1029,6 +1118,7 @@ const generateLogicExp = (scope, decl) => {
|
|
1029
1118
|
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
1030
1119
|
};
|
1031
1120
|
|
1121
|
+
// potential future ideas for nan boxing (unused):
|
1032
1122
|
// T = JS type, V = value/pointer
|
1033
1123
|
// 0bTTT
|
1034
1124
|
// qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
|
@@ -1042,7 +1132,6 @@ const generateLogicExp = (scope, decl) => {
|
|
1042
1132
|
// js type: 4 bits
|
1043
1133
|
// internal type: ? bits
|
1044
1134
|
// pointer: 32 bits
|
1045
|
-
|
1046
1135
|
// generic
|
1047
1136
|
// 1 23 4 5
|
1048
1137
|
// 0 11111111111 11TTTTIIII??????????PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP
|
@@ -1052,49 +1141,29 @@ const generateLogicExp = (scope, decl) => {
|
|
1052
1141
|
// 4: internal type
|
1053
1142
|
// 5: pointer
|
1054
1143
|
|
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'
|
1144
|
+
const isExistingProtoFunc = name => {
|
1145
|
+
if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES.array][name.slice(18)];
|
1146
|
+
if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
|
1147
|
+
|
1148
|
+
return false;
|
1084
1149
|
};
|
1085
1150
|
|
1086
1151
|
const getType = (scope, _name) => {
|
1087
1152
|
const name = mapName(_name);
|
1088
1153
|
|
1154
|
+
// if (scope.locals[name] && !scope.locals[name + '#type']) console.log(name);
|
1155
|
+
|
1156
|
+
if (typedInput && scope.locals[name]?.metadata?.type != null) return number(scope.locals[name].metadata.type, Valtype.i32);
|
1089
1157
|
if (scope.locals[name]) return [ [ Opcodes.local_get, scope.locals[name + '#type'].idx ] ];
|
1158
|
+
|
1159
|
+
if (typedInput && globals[name]?.metadata?.type != null) return number(globals[name].metadata.type, Valtype.i32);
|
1090
1160
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1091
1161
|
|
1092
1162
|
let type = TYPES.undefined;
|
1093
|
-
if (builtinVars[name]) type =
|
1163
|
+
if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
|
1094
1164
|
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
|
1095
1165
|
|
1096
|
-
if (name
|
1097
|
-
name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
|
1166
|
+
if (isExistingProtoFunc(name)) type = TYPES.function;
|
1098
1167
|
|
1099
1168
|
return number(type, Valtype.i32);
|
1100
1169
|
};
|
@@ -1117,23 +1186,24 @@ const setType = (scope, _name, type) => {
|
|
1117
1186
|
];
|
1118
1187
|
|
1119
1188
|
// throw new Error('could not find var');
|
1189
|
+
return [];
|
1120
1190
|
};
|
1121
1191
|
|
1122
1192
|
const getLastType = scope => {
|
1123
1193
|
scope.gotLastType = true;
|
1124
|
-
return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
|
1194
|
+
return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1125
1195
|
};
|
1126
1196
|
|
1127
1197
|
const setLastType = scope => {
|
1128
|
-
return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
|
1198
|
+
return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1129
1199
|
};
|
1130
1200
|
|
1131
1201
|
const getNodeType = (scope, node) => {
|
1132
|
-
const
|
1202
|
+
const ret = (() => {
|
1133
1203
|
if (node.type === 'Literal') {
|
1134
|
-
if (node.regex) return TYPES.
|
1204
|
+
if (node.regex) return TYPES.regexp;
|
1135
1205
|
|
1136
|
-
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES.
|
1206
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES.bytestring;
|
1137
1207
|
|
1138
1208
|
return TYPES[typeof node.value];
|
1139
1209
|
}
|
@@ -1150,21 +1220,32 @@ const getNodeType = (scope, node) => {
|
|
1150
1220
|
const name = node.callee.name;
|
1151
1221
|
if (!name) {
|
1152
1222
|
// iife
|
1153
|
-
if (scope.locals['#last_type']) return
|
1223
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1154
1224
|
|
1155
1225
|
// presume
|
1156
1226
|
// todo: warn here?
|
1157
1227
|
return TYPES.number;
|
1158
1228
|
}
|
1159
1229
|
|
1230
|
+
if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
|
1231
|
+
if (builtinFuncs[name + '$constructor'].typedReturns) {
|
1232
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1233
|
+
|
1234
|
+
// presume
|
1235
|
+
// todo: warn here?
|
1236
|
+
return TYPES.number;
|
1237
|
+
}
|
1238
|
+
|
1239
|
+
return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
|
1240
|
+
}
|
1241
|
+
|
1160
1242
|
const func = funcs.find(x => x.name === name);
|
1161
1243
|
|
1162
1244
|
if (func) {
|
1163
|
-
// console.log(scope, func, func.returnType);
|
1164
1245
|
if (func.returnType) return func.returnType;
|
1165
1246
|
}
|
1166
1247
|
|
1167
|
-
if (builtinFuncs[name]) return
|
1248
|
+
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
1168
1249
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1169
1250
|
|
1170
1251
|
// check if this is a prototype function
|
@@ -1175,11 +1256,16 @@ const getNodeType = (scope, node) => {
|
|
1175
1256
|
const spl = name.slice(2).split('_');
|
1176
1257
|
|
1177
1258
|
const func = spl[spl.length - 1];
|
1178
|
-
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.
|
1259
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
|
1179
1260
|
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1180
1261
|
}
|
1181
1262
|
|
1182
|
-
if (
|
1263
|
+
if (name.startsWith('__Porffor_wasm_')) {
|
1264
|
+
// todo: return undefined for non-returning ops
|
1265
|
+
return TYPES.number;
|
1266
|
+
}
|
1267
|
+
|
1268
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1183
1269
|
|
1184
1270
|
// presume
|
1185
1271
|
// todo: warn here?
|
@@ -1222,11 +1308,20 @@ const getNodeType = (scope, node) => {
|
|
1222
1308
|
}
|
1223
1309
|
|
1224
1310
|
if (node.type === 'ArrayExpression') {
|
1225
|
-
return TYPES.
|
1311
|
+
return TYPES.array;
|
1226
1312
|
}
|
1227
1313
|
|
1228
1314
|
if (node.type === 'BinaryExpression') {
|
1229
1315
|
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
1316
|
+
if (node.operator !== '+') return TYPES.number;
|
1317
|
+
|
1318
|
+
const knownLeft = knownType(scope, getNodeType(scope, node.left));
|
1319
|
+
const knownRight = knownType(scope, getNodeType(scope, node.right));
|
1320
|
+
|
1321
|
+
// todo: this should be dynamic but for now only static
|
1322
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
|
1323
|
+
if (knownLeft === TYPES.bytestring || knownRight === TYPES.bytestring) return TYPES.bytestring;
|
1324
|
+
|
1230
1325
|
return TYPES.number;
|
1231
1326
|
|
1232
1327
|
// todo: string concat types
|
@@ -1251,34 +1346,47 @@ const getNodeType = (scope, node) => {
|
|
1251
1346
|
if (node.operator === '!') return TYPES.boolean;
|
1252
1347
|
if (node.operator === 'void') return TYPES.undefined;
|
1253
1348
|
if (node.operator === 'delete') return TYPES.boolean;
|
1254
|
-
if (node.operator === 'typeof') return
|
1349
|
+
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES.bytestring : TYPES.string;
|
1255
1350
|
|
1256
1351
|
return TYPES.number;
|
1257
1352
|
}
|
1258
1353
|
|
1259
1354
|
if (node.type === 'MemberExpression') {
|
1355
|
+
// hack: if something.name, string type
|
1356
|
+
if (node.property.name === 'name') {
|
1357
|
+
if (hasFuncWithName(node.object.name)) {
|
1358
|
+
return TYPES.bytestring;
|
1359
|
+
} else {
|
1360
|
+
return TYPES.undefined;
|
1361
|
+
}
|
1362
|
+
}
|
1363
|
+
|
1260
1364
|
// hack: if something.length, number type
|
1261
1365
|
if (node.property.name === 'length') return TYPES.number;
|
1262
1366
|
|
1263
1367
|
// ts hack
|
1264
1368
|
if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
|
1265
|
-
if (scope.locals[node.object.name]?.metadata?.type === TYPES.
|
1369
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.bytestring) return TYPES.bytestring;
|
1370
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.array) return TYPES.number;
|
1266
1371
|
|
1267
|
-
if (scope.locals['#last_type']) return
|
1372
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1268
1373
|
|
1269
1374
|
// presume
|
1270
1375
|
return TYPES.number;
|
1271
1376
|
}
|
1272
1377
|
|
1273
|
-
if (
|
1378
|
+
if (node.type === 'TaggedTemplateExpression') {
|
1379
|
+
// hack
|
1380
|
+
if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
|
1381
|
+
}
|
1382
|
+
|
1383
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1274
1384
|
|
1275
1385
|
// presume
|
1276
1386
|
// todo: warn here?
|
1277
1387
|
return TYPES.number;
|
1278
|
-
};
|
1388
|
+
})();
|
1279
1389
|
|
1280
|
-
const ret = inner();
|
1281
|
-
// console.trace(node, ret);
|
1282
1390
|
if (typeof ret === 'number') return number(ret, Valtype.i32);
|
1283
1391
|
return ret;
|
1284
1392
|
};
|
@@ -1303,7 +1411,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1303
1411
|
return makeString(scope, decl.value, global, name);
|
1304
1412
|
|
1305
1413
|
default:
|
1306
|
-
return todo(`cannot generate literal of type ${typeof decl.value}
|
1414
|
+
return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
|
1307
1415
|
}
|
1308
1416
|
};
|
1309
1417
|
|
@@ -1312,6 +1420,8 @@ const countLeftover = wasm => {
|
|
1312
1420
|
|
1313
1421
|
for (let i = 0; i < wasm.length; i++) {
|
1314
1422
|
const inst = wasm[i];
|
1423
|
+
if (inst[0] == null) continue;
|
1424
|
+
|
1315
1425
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
1316
1426
|
if (inst[0] === Opcodes.if) count--;
|
1317
1427
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1320,18 +1430,29 @@ const countLeftover = wasm => {
|
|
1320
1430
|
if (inst[0] === Opcodes.end) depth--;
|
1321
1431
|
|
1322
1432
|
if (depth === 0)
|
1323
|
-
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1433
|
+
if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1324
1434
|
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++;
|
1435
|
+
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
1436
|
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1327
1437
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1328
1438
|
else if (inst[0] === Opcodes.return) count = 0;
|
1329
1439
|
else if (inst[0] === Opcodes.call) {
|
1330
1440
|
let func = funcs.find(x => x.index === inst[1]);
|
1331
|
-
if (
|
1332
|
-
count
|
1333
|
-
} else
|
1334
|
-
|
1441
|
+
if (inst[1] === -1) {
|
1442
|
+
// todo: count for calling self
|
1443
|
+
} else if (!func && inst[1] < importedFuncs.length) {
|
1444
|
+
count -= importedFuncs[inst[1]].params;
|
1445
|
+
count += importedFuncs[inst[1]].returns;
|
1446
|
+
} else {
|
1447
|
+
if (func) {
|
1448
|
+
count -= func.params.length;
|
1449
|
+
} else count--;
|
1450
|
+
if (func) count += func.returns.length;
|
1451
|
+
}
|
1452
|
+
} else if (inst[0] === Opcodes.call_indirect) {
|
1453
|
+
count--; // funcidx
|
1454
|
+
count -= inst[1] * 2; // params * 2 (typed)
|
1455
|
+
count += 2; // fixed return (value, type)
|
1335
1456
|
} else count--;
|
1336
1457
|
|
1337
1458
|
// console.log(count, decompile([ inst ]).slice(0, -1));
|
@@ -1408,25 +1529,27 @@ const RTArrayUtil = {
|
|
1408
1529
|
};
|
1409
1530
|
|
1410
1531
|
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
1532
|
let name = mapName(decl.callee.name);
|
1421
1533
|
if (isFuncType(decl.callee.type)) { // iife
|
1422
1534
|
const func = generateFunc(scope, decl.callee);
|
1423
1535
|
name = func.name;
|
1424
1536
|
}
|
1425
1537
|
|
1426
|
-
if (name === 'eval' && decl.arguments[0]
|
1538
|
+
if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
|
1427
1539
|
// literal eval hack
|
1428
|
-
const code = decl.arguments[0]
|
1429
|
-
|
1540
|
+
const code = decl.arguments[0]?.value ?? '';
|
1541
|
+
|
1542
|
+
let parsed;
|
1543
|
+
try {
|
1544
|
+
parsed = parse(code, []);
|
1545
|
+
} catch (e) {
|
1546
|
+
if (e.name === 'SyntaxError') {
|
1547
|
+
// throw syntax errors of evals at runtime instead
|
1548
|
+
return internalThrow(scope, 'SyntaxError', e.message, true);
|
1549
|
+
}
|
1550
|
+
|
1551
|
+
throw e;
|
1552
|
+
}
|
1430
1553
|
|
1431
1554
|
const out = generate(scope, {
|
1432
1555
|
type: 'BlockStatement',
|
@@ -1440,13 +1563,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1440
1563
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1441
1564
|
out.push(
|
1442
1565
|
...getNodeType(scope, finalStatement),
|
1443
|
-
setLastType(scope)
|
1566
|
+
...setLastType(scope)
|
1444
1567
|
);
|
1445
1568
|
} else if (countLeftover(out) === 0) {
|
1446
1569
|
out.push(...number(UNDEFINED));
|
1447
1570
|
out.push(
|
1448
1571
|
...number(TYPES.undefined, Valtype.i32),
|
1449
|
-
setLastType(scope)
|
1572
|
+
...setLastType(scope)
|
1450
1573
|
);
|
1451
1574
|
}
|
1452
1575
|
|
@@ -1468,6 +1591,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1468
1591
|
|
1469
1592
|
target = { ...decl.callee };
|
1470
1593
|
target.name = spl.slice(0, -1).join('_');
|
1594
|
+
|
1595
|
+
// failed to lookup name, abort
|
1596
|
+
if (!lookupName(scope, target.name)[0]) protoName = null;
|
1471
1597
|
}
|
1472
1598
|
|
1473
1599
|
// literal.func()
|
@@ -1475,22 +1601,29 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1475
1601
|
// megahack for /regex/.func()
|
1476
1602
|
const funcName = decl.callee.property.name;
|
1477
1603
|
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1478
|
-
const
|
1604
|
+
const regex = decl.callee.object.regex.pattern;
|
1605
|
+
const rhemynName = `regex_${funcName}_${regex}`;
|
1606
|
+
|
1607
|
+
if (!funcIndex[rhemynName]) {
|
1608
|
+
const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
|
1479
1609
|
|
1480
|
-
|
1481
|
-
|
1610
|
+
funcIndex[func.name] = func.index;
|
1611
|
+
funcs.push(func);
|
1612
|
+
}
|
1482
1613
|
|
1614
|
+
const idx = funcIndex[rhemynName];
|
1483
1615
|
return [
|
1484
1616
|
// make string arg
|
1485
1617
|
...generate(scope, decl.arguments[0]),
|
1618
|
+
Opcodes.i32_to_u,
|
1619
|
+
...getNodeType(scope, decl.arguments[0]),
|
1486
1620
|
|
1487
1621
|
// call regex func
|
1488
|
-
Opcodes.
|
1489
|
-
[ Opcodes.call, func.index ],
|
1622
|
+
[ Opcodes.call, idx ],
|
1490
1623
|
Opcodes.i32_from_u,
|
1491
1624
|
|
1492
1625
|
...number(TYPES.boolean, Valtype.i32),
|
1493
|
-
setLastType(scope)
|
1626
|
+
...setLastType(scope)
|
1494
1627
|
];
|
1495
1628
|
}
|
1496
1629
|
|
@@ -1515,12 +1648,31 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1515
1648
|
// }
|
1516
1649
|
|
1517
1650
|
if (protoName) {
|
1651
|
+
const protoBC = {};
|
1652
|
+
|
1653
|
+
const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
|
1654
|
+
|
1655
|
+
if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
|
1656
|
+
for (const x of builtinProtoCands) {
|
1657
|
+
const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
|
1658
|
+
if (type == null) continue;
|
1659
|
+
|
1660
|
+
protoBC[type] = generateCall(scope, {
|
1661
|
+
callee: {
|
1662
|
+
type: 'Identifier',
|
1663
|
+
name: x
|
1664
|
+
},
|
1665
|
+
arguments: [ target, ...decl.arguments ],
|
1666
|
+
_protoInternalCall: true
|
1667
|
+
});
|
1668
|
+
}
|
1669
|
+
}
|
1670
|
+
|
1518
1671
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1519
1672
|
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1520
1673
|
return acc;
|
1521
1674
|
}, {});
|
1522
1675
|
|
1523
|
-
// no prototype function candidates, ignore
|
1524
1676
|
if (Object.keys(protoCands).length > 0) {
|
1525
1677
|
// use local for cached i32 length as commonly used
|
1526
1678
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
@@ -1538,7 +1690,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1538
1690
|
|
1539
1691
|
let allOptUnused = true;
|
1540
1692
|
let lengthI32CacheUsed = false;
|
1541
|
-
const protoBC = {};
|
1542
1693
|
for (const x in protoCands) {
|
1543
1694
|
const protoFunc = protoCands[x];
|
1544
1695
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
@@ -1546,13 +1697,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1546
1697
|
...RTArrayUtil.getLength(getPointer),
|
1547
1698
|
|
1548
1699
|
...number(TYPES.number, Valtype.i32),
|
1549
|
-
setLastType(scope)
|
1700
|
+
...setLastType(scope)
|
1550
1701
|
];
|
1551
1702
|
continue;
|
1552
1703
|
}
|
1553
1704
|
|
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
1705
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1557
1706
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1558
1707
|
|
@@ -1583,7 +1732,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1583
1732
|
...protoOut,
|
1584
1733
|
|
1585
1734
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1586
|
-
setLastType(scope),
|
1735
|
+
...setLastType(scope),
|
1587
1736
|
[ Opcodes.end ]
|
1588
1737
|
];
|
1589
1738
|
}
|
@@ -1609,10 +1758,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1609
1758
|
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1610
1759
|
];
|
1611
1760
|
}
|
1761
|
+
|
1762
|
+
if (Object.keys(protoBC).length > 0) {
|
1763
|
+
return typeSwitch(scope, getNodeType(scope, target), {
|
1764
|
+
...protoBC,
|
1765
|
+
|
1766
|
+
// TODO: error better
|
1767
|
+
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1768
|
+
}, valtypeBinary);
|
1769
|
+
}
|
1612
1770
|
}
|
1613
1771
|
|
1614
1772
|
// TODO: only allows callee as literal
|
1615
|
-
if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
|
1773
|
+
if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
|
1616
1774
|
|
1617
1775
|
let idx = funcIndex[name] ?? importedFuncs[name];
|
1618
1776
|
if (idx === undefined && builtinFuncs[name]) {
|
@@ -1620,22 +1778,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1620
1778
|
|
1621
1779
|
includeBuiltin(scope, name);
|
1622
1780
|
idx = funcIndex[name];
|
1623
|
-
|
1624
|
-
// infer arguments types from builtins params
|
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;
|
1629
|
-
|
1630
|
-
const local = scope.locals[arg.name];
|
1631
|
-
if (!local) continue;
|
1632
|
-
|
1633
|
-
local.type = func.params[i];
|
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
1781
|
}
|
1640
1782
|
|
1641
1783
|
if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
@@ -1645,16 +1787,100 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1645
1787
|
idx = -1;
|
1646
1788
|
}
|
1647
1789
|
|
1790
|
+
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1791
|
+
const wasmOps = {
|
1792
|
+
// pointer, align, offset
|
1793
|
+
i32_load: { imms: 2, args: [ true ], returns: 1 },
|
1794
|
+
// pointer, value, align, offset
|
1795
|
+
i32_store: { imms: 2, args: [ true, true ], returns: 0 },
|
1796
|
+
// pointer, align, offset
|
1797
|
+
i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
|
1798
|
+
// pointer, value, align, offset
|
1799
|
+
i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
|
1800
|
+
// pointer, align, offset
|
1801
|
+
i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
|
1802
|
+
// pointer, value, align, offset
|
1803
|
+
i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
|
1804
|
+
|
1805
|
+
// pointer, align, offset
|
1806
|
+
f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
|
1807
|
+
// pointer, value, align, offset
|
1808
|
+
f64_store: { imms: 2, args: [ true, false ], returns: 0 },
|
1809
|
+
|
1810
|
+
// value
|
1811
|
+
i32_const: { imms: 1, args: [], returns: 1 },
|
1812
|
+
};
|
1813
|
+
|
1814
|
+
const opName = name.slice('__Porffor_wasm_'.length);
|
1815
|
+
|
1816
|
+
if (wasmOps[opName]) {
|
1817
|
+
const op = wasmOps[opName];
|
1818
|
+
|
1819
|
+
const argOut = [];
|
1820
|
+
for (let i = 0; i < op.args.length; i++) argOut.push(
|
1821
|
+
...generate(scope, decl.arguments[i]),
|
1822
|
+
...(op.args[i] ? [ Opcodes.i32_to ] : [])
|
1823
|
+
);
|
1824
|
+
|
1825
|
+
// literals only
|
1826
|
+
const imms = decl.arguments.slice(op.args.length).map(x => x.value);
|
1827
|
+
|
1828
|
+
return [
|
1829
|
+
...argOut,
|
1830
|
+
[ Opcodes[opName], ...imms ],
|
1831
|
+
...(new Array(op.returns).fill(Opcodes.i32_from))
|
1832
|
+
];
|
1833
|
+
}
|
1834
|
+
}
|
1835
|
+
|
1648
1836
|
if (idx === undefined) {
|
1649
|
-
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined)
|
1650
|
-
|
1837
|
+
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) {
|
1838
|
+
const [ local, global ] = lookupName(scope, name);
|
1839
|
+
if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1840
|
+
|
1841
|
+
// todo: only works when:
|
1842
|
+
// 1. arg count matches arg count of function
|
1843
|
+
// 2. function uses typedParams and typedReturns
|
1844
|
+
|
1845
|
+
funcs.table = true;
|
1846
|
+
|
1847
|
+
let args = decl.arguments;
|
1848
|
+
let argWasm = [];
|
1849
|
+
|
1850
|
+
for (let i = 0; i < args.length; i++) {
|
1851
|
+
const arg = args[i];
|
1852
|
+
argWasm = argWasm.concat(generate(scope, arg));
|
1853
|
+
|
1854
|
+
if (valtypeBinary !== Valtype.i32 && (
|
1855
|
+
(builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
|
1856
|
+
(importedFuncs[name] && name.startsWith('profile'))
|
1857
|
+
)) {
|
1858
|
+
argWasm.push(Opcodes.i32_to);
|
1859
|
+
}
|
1860
|
+
|
1861
|
+
argWasm = argWasm.concat(getNodeType(scope, arg));
|
1862
|
+
}
|
1863
|
+
|
1864
|
+
return typeSwitch(scope, getNodeType(scope, decl.callee), {
|
1865
|
+
[TYPES.function]: [
|
1866
|
+
...argWasm,
|
1867
|
+
[ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1868
|
+
Opcodes.i32_to_u,
|
1869
|
+
[ Opcodes.call_indirect, args.length, 0 ],
|
1870
|
+
...setLastType(scope)
|
1871
|
+
],
|
1872
|
+
default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
|
1873
|
+
});
|
1874
|
+
}
|
1875
|
+
|
1876
|
+
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
1651
1877
|
}
|
1652
1878
|
|
1653
1879
|
const func = funcs.find(x => x.index === idx);
|
1654
1880
|
|
1655
1881
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1656
1882
|
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1657
|
-
const
|
1883
|
+
const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
|
1658
1884
|
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1659
1885
|
|
1660
1886
|
let args = decl.arguments;
|
@@ -1671,14 +1897,29 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1671
1897
|
if (func && func.throws) scope.throws = true;
|
1672
1898
|
|
1673
1899
|
let out = [];
|
1674
|
-
for (
|
1900
|
+
for (let i = 0; i < args.length; i++) {
|
1901
|
+
const arg = args[i];
|
1675
1902
|
out = out.concat(generate(scope, arg));
|
1903
|
+
|
1904
|
+
if (i >= paramCount) {
|
1905
|
+
// over param count of func, drop arg
|
1906
|
+
out.push([ Opcodes.drop ]);
|
1907
|
+
continue;
|
1908
|
+
}
|
1909
|
+
|
1910
|
+
if (valtypeBinary !== Valtype.i32 && (
|
1911
|
+
(builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
|
1912
|
+
(importedFuncs[name] && name.startsWith('profile'))
|
1913
|
+
)) {
|
1914
|
+
out.push(Opcodes.i32_to);
|
1915
|
+
}
|
1916
|
+
|
1676
1917
|
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1677
1918
|
}
|
1678
1919
|
|
1679
1920
|
out.push([ Opcodes.call, idx ]);
|
1680
1921
|
|
1681
|
-
if (!
|
1922
|
+
if (!typedReturns) {
|
1682
1923
|
// let type;
|
1683
1924
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1684
1925
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1688,7 +1929,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1688
1929
|
// ...number(type, Valtype.i32),
|
1689
1930
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1690
1931
|
// );
|
1691
|
-
} else out.push(setLastType(scope));
|
1932
|
+
} else out.push(...setLastType(scope));
|
1933
|
+
|
1934
|
+
if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1935
|
+
out.push(Opcodes.i32_from);
|
1936
|
+
}
|
1692
1937
|
|
1693
1938
|
return out;
|
1694
1939
|
};
|
@@ -1696,8 +1941,26 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1696
1941
|
const generateNew = (scope, decl, _global, _name) => {
|
1697
1942
|
// hack: basically treat this as a normal call for builtins for now
|
1698
1943
|
const name = mapName(decl.callee.name);
|
1944
|
+
|
1699
1945
|
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1700
|
-
|
1946
|
+
|
1947
|
+
if (builtinFuncs[name + '$constructor']) {
|
1948
|
+
// custom ...$constructor override builtin func
|
1949
|
+
return generateCall(scope, {
|
1950
|
+
...decl,
|
1951
|
+
callee: {
|
1952
|
+
type: 'Identifier',
|
1953
|
+
name: name + '$constructor'
|
1954
|
+
}
|
1955
|
+
}, _global, _name);
|
1956
|
+
}
|
1957
|
+
|
1958
|
+
if (
|
1959
|
+
(builtinFuncs[name] && !builtinFuncs[name].constr) ||
|
1960
|
+
(internalConstrs[name] && builtinFuncs[name].notConstr)
|
1961
|
+
) return internalThrow(scope, 'TypeError', `${name} is not a constructor`);
|
1962
|
+
|
1963
|
+
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
1964
|
|
1702
1965
|
return generateCall(scope, decl, _global, _name);
|
1703
1966
|
};
|
@@ -1814,14 +2077,14 @@ const brTable = (input, bc, returns) => {
|
|
1814
2077
|
};
|
1815
2078
|
|
1816
2079
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1817
|
-
if (!
|
2080
|
+
if (!Prefs.bytestring) delete bc[TYPES.bytestring];
|
1818
2081
|
|
1819
2082
|
const known = knownType(scope, type);
|
1820
2083
|
if (known != null) {
|
1821
2084
|
return bc[known] ?? bc.default;
|
1822
2085
|
}
|
1823
2086
|
|
1824
|
-
if (
|
2087
|
+
if (Prefs.typeswitchUseBrtable)
|
1825
2088
|
return brTable(type, bc, returns);
|
1826
2089
|
|
1827
2090
|
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
@@ -1831,8 +2094,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1831
2094
|
[ Opcodes.block, returns ]
|
1832
2095
|
];
|
1833
2096
|
|
1834
|
-
// todo: use br_table?
|
1835
|
-
|
1836
2097
|
for (const x in bc) {
|
1837
2098
|
if (x === 'default') continue;
|
1838
2099
|
|
@@ -1856,7 +2117,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1856
2117
|
return out;
|
1857
2118
|
};
|
1858
2119
|
|
1859
|
-
const allocVar = (scope, name, global = false) => {
|
2120
|
+
const allocVar = (scope, name, global = false, type = true) => {
|
1860
2121
|
const target = global ? globals : scope.locals;
|
1861
2122
|
|
1862
2123
|
// already declared
|
@@ -1870,8 +2131,10 @@ const allocVar = (scope, name, global = false) => {
|
|
1870
2131
|
let idx = global ? globalInd++ : scope.localInd++;
|
1871
2132
|
target[name] = { idx, type: valtypeBinary };
|
1872
2133
|
|
1873
|
-
|
1874
|
-
|
2134
|
+
if (type) {
|
2135
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
2136
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
2137
|
+
}
|
1875
2138
|
|
1876
2139
|
return idx;
|
1877
2140
|
};
|
@@ -1886,11 +2149,13 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
|
1886
2149
|
};
|
1887
2150
|
|
1888
2151
|
const typeAnnoToPorfType = x => {
|
1889
|
-
if (
|
1890
|
-
if (TYPES[
|
2152
|
+
if (!x) return null;
|
2153
|
+
if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
|
1891
2154
|
|
1892
2155
|
switch (x) {
|
1893
2156
|
case 'i32':
|
2157
|
+
case 'i64':
|
2158
|
+
case 'f64':
|
1894
2159
|
return TYPES.number;
|
1895
2160
|
}
|
1896
2161
|
|
@@ -1901,7 +2166,7 @@ const extractTypeAnnotation = decl => {
|
|
1901
2166
|
let a = decl;
|
1902
2167
|
while (a.typeAnnotation) a = a.typeAnnotation;
|
1903
2168
|
|
1904
|
-
let type, elementType;
|
2169
|
+
let type = null, elementType = null;
|
1905
2170
|
if (a.typeName) {
|
1906
2171
|
type = a.typeName.name;
|
1907
2172
|
} else if (a.type.endsWith('Keyword')) {
|
@@ -1914,7 +2179,7 @@ const extractTypeAnnotation = decl => {
|
|
1914
2179
|
const typeName = type;
|
1915
2180
|
type = typeAnnoToPorfType(type);
|
1916
2181
|
|
1917
|
-
if (type === TYPES.
|
2182
|
+
if (type === TYPES.bytestring && !Prefs.bytestring) type = TYPES.string;
|
1918
2183
|
|
1919
2184
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1920
2185
|
|
@@ -1926,13 +2191,13 @@ const generateVar = (scope, decl) => {
|
|
1926
2191
|
|
1927
2192
|
const topLevel = scope.name === 'main';
|
1928
2193
|
|
1929
|
-
// global variable if in top scope (main)
|
1930
|
-
const global = topLevel || decl._bare;
|
2194
|
+
// global variable if in top scope (main) or if internally wanted
|
2195
|
+
const global = topLevel || decl._bare;
|
1931
2196
|
|
1932
2197
|
for (const x of decl.declarations) {
|
1933
2198
|
const name = mapName(x.id.name);
|
1934
2199
|
|
1935
|
-
if (!name) return todo('destructuring is not supported yet');
|
2200
|
+
if (!name) return todo(scope, 'destructuring is not supported yet');
|
1936
2201
|
|
1937
2202
|
if (x.init && isFuncType(x.init.type)) {
|
1938
2203
|
// hack for let a = function () { ... }
|
@@ -1941,7 +2206,6 @@ const generateVar = (scope, decl) => {
|
|
1941
2206
|
continue;
|
1942
2207
|
}
|
1943
2208
|
|
1944
|
-
// console.log(name);
|
1945
2209
|
if (topLevel && builtinVars[name]) {
|
1946
2210
|
// cannot redeclare
|
1947
2211
|
if (decl.kind !== 'var') return internalThrow(scope, 'SyntaxError', `Identifier '${unhackName(name)}' has already been declared`);
|
@@ -1949,16 +2213,46 @@ const generateVar = (scope, decl) => {
|
|
1949
2213
|
continue; // always ignore
|
1950
2214
|
}
|
1951
2215
|
|
1952
|
-
|
2216
|
+
// // generate init before allocating var
|
2217
|
+
// let generated;
|
2218
|
+
// if (x.init) generated = generate(scope, x.init, global, name);
|
1953
2219
|
|
1954
|
-
|
2220
|
+
const typed = typedInput && x.id.typeAnnotation;
|
2221
|
+
let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
|
2222
|
+
|
2223
|
+
if (typed) {
|
1955
2224
|
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1956
2225
|
}
|
1957
2226
|
|
1958
2227
|
if (x.init) {
|
1959
|
-
|
2228
|
+
// if (isFuncType(x.init.type)) {
|
2229
|
+
// // let a = function () { ... }
|
2230
|
+
// x.init.id = { name };
|
2231
|
+
|
2232
|
+
// const func = generateFunc(scope, x.init);
|
2233
|
+
|
2234
|
+
// out.push(
|
2235
|
+
// ...number(func.index - importedFuncs.length),
|
2236
|
+
// [ global ? Opcodes.global_set : Opcodes.local_set, idx ],
|
2237
|
+
|
2238
|
+
// ...setType(scope, name, TYPES.function)
|
2239
|
+
// );
|
2240
|
+
|
2241
|
+
// continue;
|
2242
|
+
// }
|
2243
|
+
|
2244
|
+
const generated = generate(scope, x.init, global, name);
|
2245
|
+
if (scope.arrays?.get(name) != null) {
|
2246
|
+
// hack to set local as pointer before
|
2247
|
+
out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2248
|
+
if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
|
2249
|
+
generated.pop();
|
2250
|
+
out = out.concat(generated);
|
2251
|
+
} else {
|
2252
|
+
out = out.concat(generated);
|
2253
|
+
out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2254
|
+
}
|
1960
2255
|
|
1961
|
-
out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
1962
2256
|
out.push(...setType(scope, name, getNodeType(scope, x.init)));
|
1963
2257
|
}
|
1964
2258
|
|
@@ -1969,8 +2263,10 @@ const generateVar = (scope, decl) => {
|
|
1969
2263
|
return out;
|
1970
2264
|
};
|
1971
2265
|
|
1972
|
-
|
2266
|
+
// todo: optimize this func for valueUnused
|
2267
|
+
const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
1973
2268
|
const { type, name } = decl.left;
|
2269
|
+
const [ local, isGlobal ] = lookupName(scope, name);
|
1974
2270
|
|
1975
2271
|
if (type === 'ObjectPattern') {
|
1976
2272
|
// hack: ignore object parts of `var a = {} = 2`
|
@@ -1980,26 +2276,44 @@ const generateAssign = (scope, decl) => {
|
|
1980
2276
|
if (isFuncType(decl.right.type)) {
|
1981
2277
|
// hack for a = function () { ... }
|
1982
2278
|
decl.right.id = { name };
|
1983
|
-
|
1984
|
-
|
2279
|
+
|
2280
|
+
const func = generateFunc(scope, decl.right);
|
2281
|
+
|
2282
|
+
return [
|
2283
|
+
...number(func.index - importedFuncs.length),
|
2284
|
+
...(local != null ? [
|
2285
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2286
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
2287
|
+
|
2288
|
+
...setType(scope, name, TYPES.function)
|
2289
|
+
] : [])
|
2290
|
+
];
|
1985
2291
|
}
|
1986
2292
|
|
2293
|
+
const op = decl.operator.slice(0, -1) || '=';
|
2294
|
+
|
1987
2295
|
// hack: .length setter
|
1988
2296
|
if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
|
1989
2297
|
const name = decl.left.object.name;
|
1990
|
-
const pointer = arrays
|
2298
|
+
const pointer = scope.arrays?.get(name);
|
1991
2299
|
|
1992
|
-
const aotPointer = pointer != null;
|
2300
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1993
2301
|
|
1994
2302
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
2303
|
+
const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1995
2304
|
|
1996
2305
|
return [
|
1997
2306
|
...(aotPointer ? number(0, Valtype.i32) : [
|
1998
2307
|
...generate(scope, decl.left.object),
|
1999
2308
|
Opcodes.i32_to_u
|
2000
2309
|
]),
|
2310
|
+
...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
2001
2311
|
|
2002
|
-
...generate(scope, decl.right),
|
2312
|
+
...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
|
2313
|
+
[ Opcodes.local_get, pointerTmp ],
|
2314
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
2315
|
+
Opcodes.i32_from_u
|
2316
|
+
], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
|
2003
2317
|
[ Opcodes.local_tee, newValueTmp ],
|
2004
2318
|
|
2005
2319
|
Opcodes.i32_to_u,
|
@@ -2009,21 +2323,19 @@ const generateAssign = (scope, decl) => {
|
|
2009
2323
|
];
|
2010
2324
|
}
|
2011
2325
|
|
2012
|
-
const op = decl.operator.slice(0, -1) || '=';
|
2013
|
-
|
2014
2326
|
// arr[i]
|
2015
2327
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
2016
2328
|
const name = decl.left.object.name;
|
2017
|
-
const pointer = arrays
|
2329
|
+
const pointer = scope.arrays?.get(name);
|
2018
2330
|
|
2019
|
-
const aotPointer = pointer != null;
|
2331
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2020
2332
|
|
2021
2333
|
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
2022
2334
|
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
2023
2335
|
|
2024
2336
|
return [
|
2025
2337
|
...typeSwitch(scope, getNodeType(scope, decl.left.object), {
|
2026
|
-
[TYPES.
|
2338
|
+
[TYPES.array]: [
|
2027
2339
|
...(aotPointer ? [] : [
|
2028
2340
|
...generate(scope, decl.left.object),
|
2029
2341
|
Opcodes.i32_to_u
|
@@ -2072,9 +2384,7 @@ const generateAssign = (scope, decl) => {
|
|
2072
2384
|
];
|
2073
2385
|
}
|
2074
2386
|
|
2075
|
-
if (!name) return todo('destructuring is not supported yet');
|
2076
|
-
|
2077
|
-
const [ local, isGlobal ] = lookupName(scope, name);
|
2387
|
+
if (!name) return todo(scope, 'destructuring is not supported yet', true);
|
2078
2388
|
|
2079
2389
|
if (local === undefined) {
|
2080
2390
|
// todo: this should be a sloppy mode only thing
|
@@ -2120,9 +2430,7 @@ const generateAssign = (scope, decl) => {
|
|
2120
2430
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
2121
2431
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
2122
2432
|
|
2123
|
-
getLastType(scope)
|
2124
|
-
// hack: type is idx+1
|
2125
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2433
|
+
...setType(scope, name, getLastType(scope))
|
2126
2434
|
];
|
2127
2435
|
}
|
2128
2436
|
|
@@ -2133,9 +2441,7 @@ const generateAssign = (scope, decl) => {
|
|
2133
2441
|
|
2134
2442
|
// todo: string concat types
|
2135
2443
|
|
2136
|
-
|
2137
|
-
...number(TYPES.number, Valtype.i32),
|
2138
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2444
|
+
...setType(scope, name, TYPES.number)
|
2139
2445
|
];
|
2140
2446
|
};
|
2141
2447
|
|
@@ -2181,7 +2487,7 @@ const generateUnary = (scope, decl) => {
|
|
2181
2487
|
return out;
|
2182
2488
|
}
|
2183
2489
|
|
2184
|
-
case 'delete':
|
2490
|
+
case 'delete': {
|
2185
2491
|
let toReturn = true, toGenerate = true;
|
2186
2492
|
|
2187
2493
|
if (decl.argument.type === 'Identifier') {
|
@@ -2203,40 +2509,61 @@ const generateUnary = (scope, decl) => {
|
|
2203
2509
|
|
2204
2510
|
out.push(...number(toReturn ? 1 : 0));
|
2205
2511
|
return out;
|
2512
|
+
}
|
2513
|
+
|
2514
|
+
case 'typeof': {
|
2515
|
+
let overrideType, toGenerate = true;
|
2206
2516
|
|
2207
|
-
|
2208
|
-
|
2517
|
+
if (decl.argument.type === 'Identifier') {
|
2518
|
+
const out = generateIdent(scope, decl.argument);
|
2519
|
+
|
2520
|
+
// if ReferenceError (undeclared var), ignore and return undefined
|
2521
|
+
if (out[1]) {
|
2522
|
+
// does not exist (2 ops from throw)
|
2523
|
+
overrideType = number(TYPES.undefined, Valtype.i32);
|
2524
|
+
toGenerate = false;
|
2525
|
+
}
|
2526
|
+
}
|
2527
|
+
|
2528
|
+
const out = toGenerate ? generate(scope, decl.argument) : [];
|
2529
|
+
disposeLeftover(out);
|
2530
|
+
|
2531
|
+
out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
|
2209
2532
|
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
2210
2533
|
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
2211
2534
|
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
2212
2535
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
2213
2536
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
2537
|
+
[TYPES.symbol]: makeString(scope, 'symbol', false, '#typeof_result'),
|
2214
2538
|
|
2215
|
-
[TYPES.
|
2539
|
+
[TYPES.bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2216
2540
|
|
2217
2541
|
// object and internal types
|
2218
2542
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2219
|
-
});
|
2543
|
+
}));
|
2544
|
+
|
2545
|
+
return out;
|
2546
|
+
}
|
2220
2547
|
|
2221
2548
|
default:
|
2222
|
-
return todo(`unary operator ${decl.operator} not implemented yet
|
2549
|
+
return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
|
2223
2550
|
}
|
2224
2551
|
};
|
2225
2552
|
|
2226
|
-
const generateUpdate = (scope, decl) => {
|
2553
|
+
const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
|
2227
2554
|
const { name } = decl.argument;
|
2228
2555
|
|
2229
2556
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2230
2557
|
|
2231
2558
|
if (local === undefined) {
|
2232
|
-
return todo(`update expression with undefined variable
|
2559
|
+
return todo(scope, `update expression with undefined variable`, true);
|
2233
2560
|
}
|
2234
2561
|
|
2235
2562
|
const idx = local.idx;
|
2236
2563
|
const out = [];
|
2237
2564
|
|
2238
2565
|
out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2239
|
-
if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2566
|
+
if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2240
2567
|
|
2241
2568
|
switch (decl.operator) {
|
2242
2569
|
case '++':
|
@@ -2249,7 +2576,7 @@ const generateUpdate = (scope, decl) => {
|
|
2249
2576
|
}
|
2250
2577
|
|
2251
2578
|
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2252
|
-
if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2579
|
+
if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2253
2580
|
|
2254
2581
|
return out;
|
2255
2582
|
};
|
@@ -2289,7 +2616,7 @@ const generateConditional = (scope, decl) => {
|
|
2289
2616
|
// note type
|
2290
2617
|
out.push(
|
2291
2618
|
...getNodeType(scope, decl.consequent),
|
2292
|
-
setLastType(scope)
|
2619
|
+
...setLastType(scope)
|
2293
2620
|
);
|
2294
2621
|
|
2295
2622
|
out.push([ Opcodes.else ]);
|
@@ -2298,7 +2625,7 @@ const generateConditional = (scope, decl) => {
|
|
2298
2625
|
// note type
|
2299
2626
|
out.push(
|
2300
2627
|
...getNodeType(scope, decl.alternate),
|
2301
|
-
setLastType(scope)
|
2628
|
+
...setLastType(scope)
|
2302
2629
|
);
|
2303
2630
|
|
2304
2631
|
out.push([ Opcodes.end ]);
|
@@ -2312,7 +2639,7 @@ const generateFor = (scope, decl) => {
|
|
2312
2639
|
const out = [];
|
2313
2640
|
|
2314
2641
|
if (decl.init) {
|
2315
|
-
out.push(...generate(scope, decl.init));
|
2642
|
+
out.push(...generate(scope, decl.init, false, undefined, true));
|
2316
2643
|
disposeLeftover(out);
|
2317
2644
|
}
|
2318
2645
|
|
@@ -2330,7 +2657,7 @@ const generateFor = (scope, decl) => {
|
|
2330
2657
|
out.push(...generate(scope, decl.body));
|
2331
2658
|
out.push([ Opcodes.end ]);
|
2332
2659
|
|
2333
|
-
if (decl.update) out.push(...generate(scope, decl.update));
|
2660
|
+
if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
|
2334
2661
|
|
2335
2662
|
out.push([ Opcodes.br, 1 ]);
|
2336
2663
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2358,6 +2685,36 @@ const generateWhile = (scope, decl) => {
|
|
2358
2685
|
return out;
|
2359
2686
|
};
|
2360
2687
|
|
2688
|
+
const generateDoWhile = (scope, decl) => {
|
2689
|
+
const out = [];
|
2690
|
+
|
2691
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
2692
|
+
depth.push('dowhile');
|
2693
|
+
|
2694
|
+
// block for break (includes all)
|
2695
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2696
|
+
depth.push('block');
|
2697
|
+
|
2698
|
+
// block for continue
|
2699
|
+
// includes body but not test+loop so we can exit body at anytime
|
2700
|
+
// and still test+loop after
|
2701
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2702
|
+
depth.push('block');
|
2703
|
+
|
2704
|
+
out.push(...generate(scope, decl.body));
|
2705
|
+
|
2706
|
+
out.push([ Opcodes.end ]);
|
2707
|
+
depth.pop();
|
2708
|
+
|
2709
|
+
out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2710
|
+
out.push([ Opcodes.br_if, 1 ]);
|
2711
|
+
|
2712
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
2713
|
+
depth.pop(); depth.pop();
|
2714
|
+
|
2715
|
+
return out;
|
2716
|
+
};
|
2717
|
+
|
2361
2718
|
const generateForOf = (scope, decl) => {
|
2362
2719
|
const out = [];
|
2363
2720
|
|
@@ -2394,7 +2751,10 @@ const generateForOf = (scope, decl) => {
|
|
2394
2751
|
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2395
2752
|
}
|
2396
2753
|
|
2754
|
+
// if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
|
2755
|
+
|
2397
2756
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2757
|
+
if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
|
2398
2758
|
|
2399
2759
|
depth.push('block');
|
2400
2760
|
depth.push('block');
|
@@ -2403,6 +2763,7 @@ const generateForOf = (scope, decl) => {
|
|
2403
2763
|
// hack: this is naughty and will break things!
|
2404
2764
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2405
2765
|
if (pages.hasAnyString) {
|
2766
|
+
// todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
|
2406
2767
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2407
2768
|
rawElements: new Array(1)
|
2408
2769
|
}, isGlobal, leftName, true, 'i16');
|
@@ -2411,7 +2772,7 @@ const generateForOf = (scope, decl) => {
|
|
2411
2772
|
// set type for local
|
2412
2773
|
// todo: optimize away counter and use end pointer
|
2413
2774
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2414
|
-
[TYPES.
|
2775
|
+
[TYPES.array]: [
|
2415
2776
|
...setType(scope, leftName, TYPES.number),
|
2416
2777
|
|
2417
2778
|
[ Opcodes.loop, Blocktype.void ],
|
@@ -2494,6 +2855,56 @@ const generateForOf = (scope, decl) => {
|
|
2494
2855
|
[ Opcodes.end ],
|
2495
2856
|
[ Opcodes.end ]
|
2496
2857
|
],
|
2858
|
+
[TYPES.bytestring]: [
|
2859
|
+
...setType(scope, leftName, TYPES.bytestring),
|
2860
|
+
|
2861
|
+
[ Opcodes.loop, Blocktype.void ],
|
2862
|
+
|
2863
|
+
// setup new/out array
|
2864
|
+
...newOut,
|
2865
|
+
[ Opcodes.drop ],
|
2866
|
+
|
2867
|
+
...number(0, Valtype.i32), // base 0 for store after
|
2868
|
+
|
2869
|
+
// load current string ind {arg}
|
2870
|
+
[ Opcodes.local_get, pointer ],
|
2871
|
+
[ Opcodes.local_get, counter ],
|
2872
|
+
[ Opcodes.i32_add ],
|
2873
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
2874
|
+
|
2875
|
+
// store to new string ind 0
|
2876
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2877
|
+
|
2878
|
+
// return new string (page)
|
2879
|
+
...number(newPointer),
|
2880
|
+
|
2881
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2882
|
+
|
2883
|
+
[ Opcodes.block, Blocktype.void ],
|
2884
|
+
[ Opcodes.block, Blocktype.void ],
|
2885
|
+
...generate(scope, decl.body),
|
2886
|
+
[ Opcodes.end ],
|
2887
|
+
|
2888
|
+
// increment iter pointer
|
2889
|
+
// [ Opcodes.local_get, pointer ],
|
2890
|
+
// ...number(1, Valtype.i32),
|
2891
|
+
// [ Opcodes.i32_add ],
|
2892
|
+
// [ Opcodes.local_set, pointer ],
|
2893
|
+
|
2894
|
+
// increment counter by 1
|
2895
|
+
[ Opcodes.local_get, counter ],
|
2896
|
+
...number(1, Valtype.i32),
|
2897
|
+
[ Opcodes.i32_add ],
|
2898
|
+
[ Opcodes.local_tee, counter ],
|
2899
|
+
|
2900
|
+
// loop if counter != length
|
2901
|
+
[ Opcodes.local_get, length ],
|
2902
|
+
[ Opcodes.i32_ne ],
|
2903
|
+
[ Opcodes.br_if, 1 ],
|
2904
|
+
|
2905
|
+
[ Opcodes.end ],
|
2906
|
+
[ Opcodes.end ]
|
2907
|
+
],
|
2497
2908
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2498
2909
|
}, Blocktype.void));
|
2499
2910
|
|
@@ -2504,28 +2915,65 @@ const generateForOf = (scope, decl) => {
|
|
2504
2915
|
return out;
|
2505
2916
|
};
|
2506
2917
|
|
2918
|
+
// find the nearest loop in depth map by type
|
2507
2919
|
const getNearestLoop = () => {
|
2508
2920
|
for (let i = depth.length - 1; i >= 0; i--) {
|
2509
|
-
if (
|
2921
|
+
if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
|
2510
2922
|
}
|
2511
2923
|
|
2512
2924
|
return -1;
|
2513
2925
|
};
|
2514
2926
|
|
2515
2927
|
const generateBreak = (scope, decl) => {
|
2516
|
-
const
|
2928
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2929
|
+
const type = depth[target];
|
2930
|
+
|
2931
|
+
// different loop types have different branch offsets
|
2932
|
+
// as they have different wasm block/loop/if structures
|
2933
|
+
// we need to use the right offset by type to branch to the one we want
|
2934
|
+
// for a break: exit the loop without executing anything else inside it
|
2935
|
+
const offset = ({
|
2936
|
+
for: 2, // loop > if (wanted branch) > block (we are here)
|
2937
|
+
while: 2, // loop > if (wanted branch) (we are here)
|
2938
|
+
dowhile: 2, // loop > block (wanted branch) > block (we are here)
|
2939
|
+
forof: 2, // loop > block (wanted branch) > block (we are here)
|
2940
|
+
if: 1 // break inside if, branch 0 to skip the rest of the if
|
2941
|
+
})[type];
|
2942
|
+
|
2517
2943
|
return [
|
2518
|
-
[ Opcodes.br, ...signedLEB128(
|
2944
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2519
2945
|
];
|
2520
2946
|
};
|
2521
2947
|
|
2522
2948
|
const generateContinue = (scope, decl) => {
|
2523
|
-
const
|
2949
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2950
|
+
const type = depth[target];
|
2951
|
+
|
2952
|
+
// different loop types have different branch offsets
|
2953
|
+
// as they have different wasm block/loop/if structures
|
2954
|
+
// we need to use the right offset by type to branch to the one we want
|
2955
|
+
// for a continue: do test for the loop, and then loop depending on that success
|
2956
|
+
const offset = ({
|
2957
|
+
for: 3, // loop (wanted branch) > if > block (we are here)
|
2958
|
+
while: 1, // loop (wanted branch) > if (we are here)
|
2959
|
+
dowhile: 3, // loop > block > block (wanted branch) (we are here)
|
2960
|
+
forof: 3 // loop > block > block (wanted branch) (we are here)
|
2961
|
+
})[type];
|
2962
|
+
|
2524
2963
|
return [
|
2525
|
-
[ Opcodes.br, ...signedLEB128(
|
2964
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2526
2965
|
];
|
2527
2966
|
};
|
2528
2967
|
|
2968
|
+
const generateLabel = (scope, decl) => {
|
2969
|
+
scope.labels ??= new Map();
|
2970
|
+
|
2971
|
+
const name = decl.label.name;
|
2972
|
+
scope.labels.set(name, depth.length);
|
2973
|
+
|
2974
|
+
return generate(scope, decl.body);
|
2975
|
+
};
|
2976
|
+
|
2529
2977
|
const generateThrow = (scope, decl) => {
|
2530
2978
|
scope.throws = true;
|
2531
2979
|
|
@@ -2546,6 +2994,9 @@ const generateThrow = (scope, decl) => {
|
|
2546
2994
|
let exceptId = exceptions.push({ constructor, message }) - 1;
|
2547
2995
|
let tagIdx = tags[0].idx;
|
2548
2996
|
|
2997
|
+
scope.exceptions ??= [];
|
2998
|
+
scope.exceptions.push(exceptId);
|
2999
|
+
|
2549
3000
|
// todo: write a description of how this works lol
|
2550
3001
|
|
2551
3002
|
return [
|
@@ -2555,7 +3006,7 @@ const generateThrow = (scope, decl) => {
|
|
2555
3006
|
};
|
2556
3007
|
|
2557
3008
|
const generateTry = (scope, decl) => {
|
2558
|
-
if (decl.finalizer) return todo('try finally not implemented yet');
|
3009
|
+
if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
|
2559
3010
|
|
2560
3011
|
const out = [];
|
2561
3012
|
|
@@ -2582,15 +3033,8 @@ const generateEmpty = (scope, decl) => {
|
|
2582
3033
|
return [];
|
2583
3034
|
};
|
2584
3035
|
|
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
3036
|
let pages = new Map();
|
2593
|
-
const allocPage = (reason, type) => {
|
3037
|
+
const allocPage = (scope, reason, type) => {
|
2594
3038
|
if (pages.has(reason)) return pages.get(reason).ind;
|
2595
3039
|
|
2596
3040
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
@@ -2601,16 +3045,20 @@ const allocPage = (reason, type) => {
|
|
2601
3045
|
const ind = pages.size;
|
2602
3046
|
pages.set(reason, { ind, type });
|
2603
3047
|
|
2604
|
-
|
3048
|
+
scope.pages ??= new Map();
|
3049
|
+
scope.pages.set(reason, { ind, type });
|
3050
|
+
|
3051
|
+
if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2605
3052
|
|
2606
3053
|
return ind;
|
2607
3054
|
};
|
2608
3055
|
|
3056
|
+
// todo: add scope.pages
|
2609
3057
|
const freePage = reason => {
|
2610
3058
|
const { ind } = pages.get(reason);
|
2611
3059
|
pages.delete(reason);
|
2612
3060
|
|
2613
|
-
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
3061
|
+
if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2614
3062
|
|
2615
3063
|
return ind;
|
2616
3064
|
};
|
@@ -2661,16 +3109,22 @@ const getAllocType = itemType => {
|
|
2661
3109
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2662
3110
|
const out = [];
|
2663
3111
|
|
3112
|
+
scope.arrays ??= new Map();
|
3113
|
+
|
2664
3114
|
let firstAssign = false;
|
2665
|
-
if (!arrays.has(name) || name === '$undeclared') {
|
3115
|
+
if (!scope.arrays.has(name) || name === '$undeclared') {
|
2666
3116
|
firstAssign = true;
|
2667
3117
|
|
2668
3118
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2669
3119
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2670
|
-
|
3120
|
+
|
3121
|
+
if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
|
3122
|
+
else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2671
3123
|
}
|
2672
3124
|
|
2673
|
-
const pointer = arrays.get(name);
|
3125
|
+
const pointer = scope.arrays.get(name);
|
3126
|
+
|
3127
|
+
const local = global ? globals[name] : scope.locals[name];
|
2674
3128
|
|
2675
3129
|
const useRawElements = !!decl.rawElements;
|
2676
3130
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
@@ -2678,19 +3132,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2678
3132
|
const valtype = itemTypeToValtype[itemType];
|
2679
3133
|
const length = elements.length;
|
2680
3134
|
|
2681
|
-
if (firstAssign && useRawElements) {
|
2682
|
-
|
3135
|
+
if (firstAssign && useRawElements && !Prefs.noData) {
|
3136
|
+
// if length is 0 memory/data will just be 0000... anyway
|
3137
|
+
if (length !== 0) {
|
3138
|
+
let bytes = compileBytes(length, 'i32');
|
2683
3139
|
|
2684
|
-
|
2685
|
-
|
3140
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
3141
|
+
if (elements[i] == null) continue;
|
2686
3142
|
|
2687
|
-
|
2688
|
-
|
3143
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
3144
|
+
}
|
2689
3145
|
|
2690
|
-
|
2691
|
-
|
2692
|
-
|
2693
|
-
|
3146
|
+
const ind = data.push({
|
3147
|
+
offset: pointer,
|
3148
|
+
bytes
|
3149
|
+
}) - 1;
|
3150
|
+
|
3151
|
+
scope.data ??= [];
|
3152
|
+
scope.data.push(ind);
|
3153
|
+
}
|
2694
3154
|
|
2695
3155
|
// local value as pointer
|
2696
3156
|
out.push(...number(pointer));
|
@@ -2698,11 +3158,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2698
3158
|
return [ out, pointer ];
|
2699
3159
|
}
|
2700
3160
|
|
3161
|
+
const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
|
3162
|
+
if (pointerTmp != null) {
|
3163
|
+
out.push(
|
3164
|
+
[ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
3165
|
+
Opcodes.i32_to_u,
|
3166
|
+
[ Opcodes.local_set, pointerTmp ]
|
3167
|
+
);
|
3168
|
+
}
|
3169
|
+
|
3170
|
+
const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
|
3171
|
+
|
2701
3172
|
// store length as 0th array
|
2702
3173
|
out.push(
|
2703
|
-
...
|
3174
|
+
...pointerWasm,
|
2704
3175
|
...number(length, Valtype.i32),
|
2705
|
-
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1,
|
3176
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
|
2706
3177
|
);
|
2707
3178
|
|
2708
3179
|
const storeOp = StoreOps[itemType];
|
@@ -2711,20 +3182,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2711
3182
|
if (elements[i] == null) continue;
|
2712
3183
|
|
2713
3184
|
out.push(
|
2714
|
-
...
|
3185
|
+
...pointerWasm,
|
2715
3186
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2716
|
-
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(
|
3187
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2717
3188
|
);
|
2718
3189
|
}
|
2719
3190
|
|
2720
3191
|
// local value as pointer
|
2721
|
-
out.push(...
|
3192
|
+
out.push(...pointerWasm, Opcodes.i32_from_u);
|
2722
3193
|
|
2723
3194
|
return [ out, pointer ];
|
2724
3195
|
};
|
2725
3196
|
|
2726
3197
|
const byteStringable = str => {
|
2727
|
-
if (!
|
3198
|
+
if (!Prefs.bytestring) return false;
|
2728
3199
|
|
2729
3200
|
for (let i = 0; i < str.length; i++) {
|
2730
3201
|
if (str.charCodeAt(i) > 0xFF) return false;
|
@@ -2735,7 +3206,7 @@ const byteStringable = str => {
|
|
2735
3206
|
|
2736
3207
|
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2737
3208
|
const rawElements = new Array(str.length);
|
2738
|
-
let byteStringable =
|
3209
|
+
let byteStringable = Prefs.bytestring;
|
2739
3210
|
for (let i = 0; i < str.length; i++) {
|
2740
3211
|
const c = str.charCodeAt(i);
|
2741
3212
|
rawElements[i] = c;
|
@@ -2750,20 +3221,53 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
|
|
2750
3221
|
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2751
3222
|
};
|
2752
3223
|
|
2753
|
-
let arrays = new Map();
|
2754
3224
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
2755
3225
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
2756
3226
|
};
|
2757
3227
|
|
2758
3228
|
export const generateMember = (scope, decl, _global, _name) => {
|
2759
3229
|
const name = decl.object.name;
|
2760
|
-
const pointer = arrays
|
3230
|
+
const pointer = scope.arrays?.get(name);
|
3231
|
+
|
3232
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2761
3233
|
|
2762
|
-
|
3234
|
+
// hack: .name
|
3235
|
+
if (decl.property.name === 'name') {
|
3236
|
+
if (hasFuncWithName(name)) {
|
3237
|
+
let nameProp = name;
|
3238
|
+
|
3239
|
+
// eg: __String_prototype_toLowerCase -> toLowerCase
|
3240
|
+
if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
|
3241
|
+
|
3242
|
+
return makeString(scope, nameProp, _global, _name, true);
|
3243
|
+
} else {
|
3244
|
+
return generate(scope, DEFAULT_VALUE);
|
3245
|
+
}
|
3246
|
+
}
|
2763
3247
|
|
2764
3248
|
// hack: .length
|
2765
3249
|
if (decl.property.name === 'length') {
|
2766
|
-
|
3250
|
+
const func = funcs.find(x => x.name === name);
|
3251
|
+
if (func) {
|
3252
|
+
const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
|
3253
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
3254
|
+
return number(typedParams ? func.params.length / 2 : func.params.length);
|
3255
|
+
}
|
3256
|
+
|
3257
|
+
if (builtinFuncs[name + '$constructor']) {
|
3258
|
+
const regularFunc = builtinFuncs[name];
|
3259
|
+
const regularParams = regularFunc.typedParams ? (regularFunc.params.length / 2) : regularFunc.params.length;
|
3260
|
+
|
3261
|
+
const constructorFunc = builtinFuncs[name + '$constructor'];
|
3262
|
+
const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
|
3263
|
+
|
3264
|
+
return number(Math.max(regularParams, constructorParams));
|
3265
|
+
}
|
3266
|
+
|
3267
|
+
if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
|
3268
|
+
if (importedFuncs[name]) return number(importedFuncs[name].params);
|
3269
|
+
if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
|
3270
|
+
|
2767
3271
|
return [
|
2768
3272
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2769
3273
|
...generate(scope, decl.object),
|
@@ -2788,7 +3292,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2788
3292
|
}
|
2789
3293
|
|
2790
3294
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2791
|
-
[TYPES.
|
3295
|
+
[TYPES.array]: [
|
2792
3296
|
// get index as valtype
|
2793
3297
|
...property,
|
2794
3298
|
|
@@ -2807,7 +3311,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2807
3311
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2808
3312
|
|
2809
3313
|
...number(TYPES.number, Valtype.i32),
|
2810
|
-
setLastType(scope)
|
3314
|
+
...setLastType(scope)
|
2811
3315
|
],
|
2812
3316
|
|
2813
3317
|
[TYPES.string]: [
|
@@ -2839,9 +3343,9 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2839
3343
|
...number(newPointer),
|
2840
3344
|
|
2841
3345
|
...number(TYPES.string, Valtype.i32),
|
2842
|
-
setLastType(scope)
|
3346
|
+
...setLastType(scope)
|
2843
3347
|
],
|
2844
|
-
[TYPES.
|
3348
|
+
[TYPES.bytestring]: [
|
2845
3349
|
// setup new/out array
|
2846
3350
|
...newOut,
|
2847
3351
|
[ Opcodes.drop ],
|
@@ -2858,19 +3362,19 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2858
3362
|
]),
|
2859
3363
|
|
2860
3364
|
// load current string ind {arg}
|
2861
|
-
[ Opcodes.i32_load8_u,
|
3365
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2862
3366
|
|
2863
3367
|
// store to new string ind 0
|
2864
|
-
[ Opcodes.i32_store8,
|
3368
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2865
3369
|
|
2866
3370
|
// return new string (page)
|
2867
3371
|
...number(newPointer),
|
2868
3372
|
|
2869
|
-
...number(TYPES.
|
2870
|
-
setLastType(scope)
|
3373
|
+
...number(TYPES.bytestring, Valtype.i32),
|
3374
|
+
...setLastType(scope)
|
2871
3375
|
],
|
2872
3376
|
|
2873
|
-
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet')
|
3377
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
|
2874
3378
|
});
|
2875
3379
|
};
|
2876
3380
|
|
@@ -2880,28 +3384,36 @@ const objectHack = node => {
|
|
2880
3384
|
if (!node) return node;
|
2881
3385
|
|
2882
3386
|
if (node.type === 'MemberExpression') {
|
2883
|
-
|
3387
|
+
const out = (() => {
|
3388
|
+
if (node.computed || node.optional) return;
|
2884
3389
|
|
2885
|
-
|
3390
|
+
let objectName = node.object.name;
|
2886
3391
|
|
2887
|
-
|
2888
|
-
|
3392
|
+
// if object is not identifier or another member exp, give up
|
3393
|
+
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
|
3394
|
+
if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
|
2889
3395
|
|
2890
|
-
|
3396
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2891
3397
|
|
2892
|
-
|
2893
|
-
|
3398
|
+
// if .name or .length, give up (hack within a hack!)
|
3399
|
+
if (['name', 'length'].includes(node.property.name)) {
|
3400
|
+
node.object = objectHack(node.object);
|
3401
|
+
return;
|
3402
|
+
}
|
2894
3403
|
|
2895
|
-
|
2896
|
-
|
3404
|
+
// no object name, give up
|
3405
|
+
if (!objectName) return;
|
2897
3406
|
|
2898
|
-
|
2899
|
-
|
3407
|
+
const name = '__' + objectName + '_' + node.property.name;
|
3408
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2900
3409
|
|
2901
|
-
|
2902
|
-
|
2903
|
-
|
2904
|
-
|
3410
|
+
return {
|
3411
|
+
type: 'Identifier',
|
3412
|
+
name
|
3413
|
+
};
|
3414
|
+
})();
|
3415
|
+
|
3416
|
+
if (out) return out;
|
2905
3417
|
}
|
2906
3418
|
|
2907
3419
|
for (const x in node) {
|
@@ -2915,8 +3427,8 @@ const objectHack = node => {
|
|
2915
3427
|
};
|
2916
3428
|
|
2917
3429
|
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');
|
3430
|
+
if (decl.async) return todo(scope, 'async functions are not supported');
|
3431
|
+
if (decl.generator) return todo(scope, 'generator functions are not supported');
|
2920
3432
|
|
2921
3433
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2922
3434
|
const params = decl.params ?? [];
|
@@ -2932,6 +3444,14 @@ const generateFunc = (scope, decl) => {
|
|
2932
3444
|
name
|
2933
3445
|
};
|
2934
3446
|
|
3447
|
+
if (typedInput && decl.returnType) {
|
3448
|
+
const { type } = extractTypeAnnotation(decl.returnType);
|
3449
|
+
if (type != null && !Prefs.indirectCalls) {
|
3450
|
+
innerScope.returnType = type;
|
3451
|
+
innerScope.returns = [ valtypeBinary ];
|
3452
|
+
}
|
3453
|
+
}
|
3454
|
+
|
2935
3455
|
for (let i = 0; i < params.length; i++) {
|
2936
3456
|
allocVar(innerScope, params[i].name, false);
|
2937
3457
|
|
@@ -2958,6 +3478,8 @@ const generateFunc = (scope, decl) => {
|
|
2958
3478
|
};
|
2959
3479
|
funcIndex[name] = func.index;
|
2960
3480
|
|
3481
|
+
if (name === 'main') func.gotLastType = true;
|
3482
|
+
|
2961
3483
|
// quick hack fixes
|
2962
3484
|
for (const inst of wasm) {
|
2963
3485
|
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
@@ -2969,7 +3491,7 @@ const generateFunc = (scope, decl) => {
|
|
2969
3491
|
if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
|
2970
3492
|
wasm.push(
|
2971
3493
|
...number(0),
|
2972
|
-
...number(TYPES.undefined, Valtype.i32),
|
3494
|
+
...(innerScope.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
|
2973
3495
|
[ Opcodes.return ]
|
2974
3496
|
);
|
2975
3497
|
}
|
@@ -2992,16 +3514,6 @@ const generateCode = (scope, decl) => {
|
|
2992
3514
|
};
|
2993
3515
|
|
2994
3516
|
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
3517
|
Array: {
|
3006
3518
|
generate: (scope, decl, global, name) => {
|
3007
3519
|
// new Array(i0, i1, ...)
|
@@ -3019,7 +3531,7 @@ const internalConstrs = {
|
|
3019
3531
|
|
3020
3532
|
// todo: check in wasm instead of here
|
3021
3533
|
const literalValue = arg.value ?? 0;
|
3022
|
-
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
|
3534
|
+
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
|
3023
3535
|
|
3024
3536
|
return [
|
3025
3537
|
...number(0, Valtype.i32),
|
@@ -3030,7 +3542,8 @@ const internalConstrs = {
|
|
3030
3542
|
...number(pointer)
|
3031
3543
|
];
|
3032
3544
|
},
|
3033
|
-
type: TYPES.
|
3545
|
+
type: TYPES.array,
|
3546
|
+
length: 1
|
3034
3547
|
},
|
3035
3548
|
|
3036
3549
|
__Array_of: {
|
@@ -3041,27 +3554,138 @@ const internalConstrs = {
|
|
3041
3554
|
elements: decl.arguments
|
3042
3555
|
}, global, name);
|
3043
3556
|
},
|
3044
|
-
type: TYPES.
|
3557
|
+
type: TYPES.array,
|
3558
|
+
notConstr: true,
|
3559
|
+
length: 0
|
3560
|
+
},
|
3561
|
+
|
3562
|
+
__Porffor_fastOr: {
|
3563
|
+
generate: (scope, decl) => {
|
3564
|
+
const out = [];
|
3565
|
+
|
3566
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3567
|
+
out.push(
|
3568
|
+
...generate(scope, decl.arguments[i]),
|
3569
|
+
Opcodes.i32_to_u,
|
3570
|
+
...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
|
3571
|
+
);
|
3572
|
+
}
|
3573
|
+
|
3574
|
+
out.push(Opcodes.i32_from_u);
|
3575
|
+
|
3576
|
+
return out;
|
3577
|
+
},
|
3578
|
+
type: TYPES.boolean,
|
3045
3579
|
notConstr: true
|
3046
|
-
}
|
3047
|
-
|
3580
|
+
},
|
3581
|
+
|
3582
|
+
__Porffor_fastAnd: {
|
3583
|
+
generate: (scope, decl) => {
|
3584
|
+
const out = [];
|
3585
|
+
|
3586
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3587
|
+
out.push(
|
3588
|
+
...generate(scope, decl.arguments[i]),
|
3589
|
+
Opcodes.i32_to_u,
|
3590
|
+
...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
|
3591
|
+
);
|
3592
|
+
}
|
3593
|
+
|
3594
|
+
out.push(Opcodes.i32_from_u);
|
3595
|
+
|
3596
|
+
return out;
|
3597
|
+
},
|
3598
|
+
type: TYPES.boolean,
|
3599
|
+
notConstr: true
|
3600
|
+
},
|
3601
|
+
|
3602
|
+
Boolean: {
|
3603
|
+
generate: (scope, decl) => {
|
3604
|
+
// todo: boolean object when used as constructor
|
3605
|
+
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
3606
|
+
return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
|
3607
|
+
},
|
3608
|
+
type: TYPES.boolean,
|
3609
|
+
length: 1
|
3610
|
+
},
|
3611
|
+
|
3612
|
+
__Math_max: {
|
3613
|
+
generate: (scope, decl) => {
|
3614
|
+
const out = [
|
3615
|
+
...number(-Infinity)
|
3616
|
+
];
|
3048
3617
|
|
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);
|
3618
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3619
|
+
out.push(
|
3620
|
+
...generate(scope, decl.arguments[i]),
|
3621
|
+
[ Opcodes.f64_max ]
|
3622
|
+
);
|
3623
|
+
}
|
3062
3624
|
|
3063
|
-
|
3064
|
-
|
3625
|
+
return out;
|
3626
|
+
},
|
3627
|
+
type: TYPES.number,
|
3628
|
+
notConstr: true,
|
3629
|
+
length: 2
|
3630
|
+
},
|
3631
|
+
|
3632
|
+
__Math_min: {
|
3633
|
+
generate: (scope, decl) => {
|
3634
|
+
const out = [
|
3635
|
+
...number(Infinity)
|
3636
|
+
];
|
3637
|
+
|
3638
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3639
|
+
out.push(
|
3640
|
+
...generate(scope, decl.arguments[i]),
|
3641
|
+
[ Opcodes.f64_min ]
|
3642
|
+
);
|
3643
|
+
}
|
3644
|
+
|
3645
|
+
return out;
|
3646
|
+
},
|
3647
|
+
type: TYPES.number,
|
3648
|
+
notConstr: true,
|
3649
|
+
length: 2
|
3650
|
+
},
|
3651
|
+
|
3652
|
+
__console_log: {
|
3653
|
+
generate: (scope, decl) => {
|
3654
|
+
const out = [];
|
3655
|
+
|
3656
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3657
|
+
out.push(
|
3658
|
+
...generateCall(scope, {
|
3659
|
+
callee: {
|
3660
|
+
type: 'Identifier',
|
3661
|
+
name: '__Porffor_print'
|
3662
|
+
},
|
3663
|
+
arguments: [ decl.arguments[i] ]
|
3664
|
+
}),
|
3665
|
+
|
3666
|
+
// print space
|
3667
|
+
...(i !== decl.arguments.length - 1 ? [
|
3668
|
+
...number(32),
|
3669
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3670
|
+
] : [])
|
3671
|
+
);
|
3672
|
+
}
|
3673
|
+
|
3674
|
+
// print newline
|
3675
|
+
out.push(
|
3676
|
+
...number(10),
|
3677
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3678
|
+
);
|
3679
|
+
|
3680
|
+
out.push(...number(UNDEFINED));
|
3681
|
+
|
3682
|
+
return out;
|
3683
|
+
},
|
3684
|
+
type: TYPES.undefined,
|
3685
|
+
notConstr: true,
|
3686
|
+
length: 0
|
3687
|
+
}
|
3688
|
+
};
|
3065
3689
|
|
3066
3690
|
export default program => {
|
3067
3691
|
globals = {};
|
@@ -3071,20 +3695,23 @@ export default program => {
|
|
3071
3695
|
funcs = [];
|
3072
3696
|
funcIndex = {};
|
3073
3697
|
depth = [];
|
3074
|
-
arrays = new Map();
|
3075
3698
|
pages = new Map();
|
3076
3699
|
data = [];
|
3077
3700
|
currentFuncIndex = importedFuncs.length;
|
3078
3701
|
|
3079
3702
|
globalThis.valtype = 'f64';
|
3080
3703
|
|
3081
|
-
const valtypeOpt = process.argv.find(x => x.startsWith('
|
3704
|
+
const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
|
3082
3705
|
if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
|
3083
3706
|
|
3084
3707
|
globalThis.valtypeBinary = Valtype[valtype];
|
3085
3708
|
|
3086
3709
|
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
3087
3710
|
|
3711
|
+
globalThis.pageSize = PageSize;
|
3712
|
+
const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
|
3713
|
+
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3714
|
+
|
3088
3715
|
// set generic opcodes for current valtype
|
3089
3716
|
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
3090
3717
|
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
@@ -3093,10 +3720,10 @@ export default program => {
|
|
3093
3720
|
Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
|
3094
3721
|
Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
|
3095
3722
|
|
3096
|
-
Opcodes.i32_to = [ [
|
3097
|
-
Opcodes.i32_to_u = [ [
|
3098
|
-
Opcodes.i32_from = [ [
|
3099
|
-
Opcodes.i32_from_u = [ [
|
3723
|
+
Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
|
3724
|
+
Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
|
3725
|
+
Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
|
3726
|
+
Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
|
3100
3727
|
|
3101
3728
|
Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
|
3102
3729
|
Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
|
@@ -3109,10 +3736,6 @@ export default program => {
|
|
3109
3736
|
|
3110
3737
|
program.id = { name: 'main' };
|
3111
3738
|
|
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
3739
|
const scope = {
|
3117
3740
|
locals: {},
|
3118
3741
|
localInd: 0
|
@@ -3123,7 +3746,7 @@ export default program => {
|
|
3123
3746
|
body: program.body
|
3124
3747
|
};
|
3125
3748
|
|
3126
|
-
if (
|
3749
|
+
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
3127
3750
|
|
3128
3751
|
generateFunc(scope, program);
|
3129
3752
|
|
@@ -3140,7 +3763,11 @@ export default program => {
|
|
3140
3763
|
}
|
3141
3764
|
|
3142
3765
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
3143
|
-
|
3766
|
+
if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
|
3767
|
+
main.wasm.splice(main.wasm.length - 1, 1);
|
3768
|
+
} else {
|
3769
|
+
main.returns = [];
|
3770
|
+
}
|
3144
3771
|
}
|
3145
3772
|
|
3146
3773
|
if (lastInst[0] === Opcodes.call) {
|