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