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