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