porffor 0.2.0-5e33105 → 0.2.0-69d30a8
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/.vscode/launch.json +18 -0
- package/LICENSE +20 -20
- package/README.md +124 -83
- 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} +62 -14
- 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/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 +591 -269
- package/compiler/{codeGen.js → codegen.js} +1021 -382
- package/compiler/decompile.js +3 -3
- package/compiler/embedding.js +22 -22
- package/compiler/encoding.js +108 -10
- package/compiler/generated_builtins.js +695 -0
- package/compiler/index.js +36 -34
- package/compiler/log.js +6 -3
- package/compiler/opt.js +51 -36
- 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 +29 -7
- package/compiler/wrap.js +137 -42
- 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 +49 -10
- package/runner/profiler.js +102 -0
- package/runner/repl.js +40 -7
- package/runner/sizes.js +37 -37
- package/test262_changes_from_1afe9b87d2_to_04-09.md +270 -0
- package/compiler/builtins/base64.js +0 -92
- package/node_trace.1.log +0 -1
- 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
@@ -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,7 +204,11 @@ 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
213
|
out.push([ ...inst, ...immediates ]);
|
189
214
|
}
|
@@ -191,35 +216,53 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
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;
|
211
243
|
|
212
|
-
|
213
|
-
|
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
|
+
}
|
254
|
+
|
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,15 @@ 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
|
+
|
1110
1276
|
const func = funcs.find(x => x.name === name);
|
1111
1277
|
|
1112
1278
|
if (func) {
|
@@ -1114,7 +1280,7 @@ const getNodeType = (scope, node) => {
|
|
1114
1280
|
if (func.returnType) return func.returnType;
|
1115
1281
|
}
|
1116
1282
|
|
1117
|
-
if (builtinFuncs[name]) return
|
1283
|
+
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
1118
1284
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1119
1285
|
|
1120
1286
|
// check if this is a prototype function
|
@@ -1125,11 +1291,16 @@ const getNodeType = (scope, node) => {
|
|
1125
1291
|
const spl = name.slice(2).split('_');
|
1126
1292
|
|
1127
1293
|
const func = spl[spl.length - 1];
|
1128
|
-
const protoFuncs = Object.
|
1294
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
|
1129
1295
|
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1130
1296
|
}
|
1131
1297
|
|
1132
|
-
if (
|
1298
|
+
if (name.startsWith('__Porffor_wasm_')) {
|
1299
|
+
// todo: return undefined for non-returning ops
|
1300
|
+
return TYPES.number;
|
1301
|
+
}
|
1302
|
+
|
1303
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1133
1304
|
|
1134
1305
|
// presume
|
1135
1306
|
// todo: warn here?
|
@@ -1177,6 +1348,15 @@ const getNodeType = (scope, node) => {
|
|
1177
1348
|
|
1178
1349
|
if (node.type === 'BinaryExpression') {
|
1179
1350
|
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
1351
|
+
if (node.operator !== '+') return TYPES.number;
|
1352
|
+
|
1353
|
+
const knownLeft = knownType(scope, getNodeType(scope, node.left));
|
1354
|
+
const knownRight = knownType(scope, getNodeType(scope, node.right));
|
1355
|
+
|
1356
|
+
// todo: this should be dynamic but for now only static
|
1357
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
|
1358
|
+
if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) return TYPES._bytestring;
|
1359
|
+
|
1180
1360
|
return TYPES.number;
|
1181
1361
|
|
1182
1362
|
// todo: string concat types
|
@@ -1201,7 +1381,7 @@ const getNodeType = (scope, node) => {
|
|
1201
1381
|
if (node.operator === '!') return TYPES.boolean;
|
1202
1382
|
if (node.operator === 'void') return TYPES.undefined;
|
1203
1383
|
if (node.operator === 'delete') return TYPES.boolean;
|
1204
|
-
if (node.operator === 'typeof') return TYPES.string;
|
1384
|
+
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
|
1205
1385
|
|
1206
1386
|
return TYPES.number;
|
1207
1387
|
}
|
@@ -1210,11 +1390,23 @@ const getNodeType = (scope, node) => {
|
|
1210
1390
|
// hack: if something.length, number type
|
1211
1391
|
if (node.property.name === 'length') return TYPES.number;
|
1212
1392
|
|
1213
|
-
//
|
1393
|
+
// ts hack
|
1394
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
|
1395
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES._bytestring) return TYPES._bytestring;
|
1396
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
|
1397
|
+
|
1398
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1399
|
+
|
1400
|
+
// presume
|
1214
1401
|
return TYPES.number;
|
1215
1402
|
}
|
1216
1403
|
|
1217
|
-
if (
|
1404
|
+
if (node.type === 'TaggedTemplateExpression') {
|
1405
|
+
// hack
|
1406
|
+
if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
|
1407
|
+
}
|
1408
|
+
|
1409
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1218
1410
|
|
1219
1411
|
// presume
|
1220
1412
|
// todo: warn here?
|
@@ -1227,28 +1419,11 @@ const getNodeType = (scope, node) => {
|
|
1227
1419
|
return ret;
|
1228
1420
|
};
|
1229
1421
|
|
1230
|
-
const toString = (scope, wasm, type) => {
|
1231
|
-
const tmp = localTmp(scope, '#tostring_tmp');
|
1232
|
-
return [
|
1233
|
-
...wasm,
|
1234
|
-
[ Opcodes.local_set, tmp ],
|
1235
|
-
|
1236
|
-
...typeSwitch(scope, type, {
|
1237
|
-
[TYPES.string]: [
|
1238
|
-
[ Opcodes.local_get, tmp ]
|
1239
|
-
],
|
1240
|
-
[TYPES.undefined]: [
|
1241
|
-
// [ Opcodes.]
|
1242
|
-
]
|
1243
|
-
})
|
1244
|
-
]
|
1245
|
-
};
|
1246
|
-
|
1247
1422
|
const generateLiteral = (scope, decl, global, name) => {
|
1248
1423
|
if (decl.value === null) return number(NULL);
|
1249
1424
|
|
1425
|
+
// hack: just return 1 for regex literals
|
1250
1426
|
if (decl.regex) {
|
1251
|
-
scope.regex[name] = decl.regex;
|
1252
1427
|
return number(1);
|
1253
1428
|
}
|
1254
1429
|
|
@@ -1261,19 +1436,10 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1261
1436
|
return number(decl.value ? 1 : 0);
|
1262
1437
|
|
1263
1438
|
case 'string':
|
1264
|
-
|
1265
|
-
const rawElements = new Array(str.length);
|
1266
|
-
let j = 0;
|
1267
|
-
for (let i = 0; i < str.length; i++) {
|
1268
|
-
rawElements[i] = str.charCodeAt(i);
|
1269
|
-
}
|
1270
|
-
|
1271
|
-
return makeArray(scope, {
|
1272
|
-
rawElements
|
1273
|
-
}, global, name, false, 'i16')[0];
|
1439
|
+
return makeString(scope, decl.value, global, name);
|
1274
1440
|
|
1275
1441
|
default:
|
1276
|
-
return todo(`cannot generate literal of type ${typeof decl.value}
|
1442
|
+
return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
|
1277
1443
|
}
|
1278
1444
|
};
|
1279
1445
|
|
@@ -1282,6 +1448,8 @@ const countLeftover = wasm => {
|
|
1282
1448
|
|
1283
1449
|
for (let i = 0; i < wasm.length; i++) {
|
1284
1450
|
const inst = wasm[i];
|
1451
|
+
if (inst[0] == null) continue;
|
1452
|
+
|
1285
1453
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
1286
1454
|
if (inst[0] === Opcodes.if) count--;
|
1287
1455
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1290,10 +1458,10 @@ const countLeftover = wasm => {
|
|
1290
1458
|
if (inst[0] === Opcodes.end) depth--;
|
1291
1459
|
|
1292
1460
|
if (depth === 0)
|
1293
|
-
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1294
|
-
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)) {}
|
1461
|
+
if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1462
|
+
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)) {}
|
1295
1463
|
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1296
|
-
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
|
1464
|
+
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1297
1465
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1298
1466
|
else if (inst[0] === Opcodes.return) count = 0;
|
1299
1467
|
else if (inst[0] === Opcodes.call) {
|
@@ -1319,7 +1487,7 @@ const disposeLeftover = wasm => {
|
|
1319
1487
|
const generateExp = (scope, decl) => {
|
1320
1488
|
const expression = decl.expression;
|
1321
1489
|
|
1322
|
-
const out = generate(scope, expression);
|
1490
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1323
1491
|
disposeLeftover(out);
|
1324
1492
|
|
1325
1493
|
return out;
|
@@ -1377,7 +1545,7 @@ const RTArrayUtil = {
|
|
1377
1545
|
]
|
1378
1546
|
};
|
1379
1547
|
|
1380
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1548
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1381
1549
|
/* const callee = decl.callee;
|
1382
1550
|
const args = decl.arguments;
|
1383
1551
|
|
@@ -1393,10 +1561,21 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1393
1561
|
name = func.name;
|
1394
1562
|
}
|
1395
1563
|
|
1396
|
-
if (name === 'eval' && decl.arguments[0]
|
1564
|
+
if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
|
1397
1565
|
// literal eval hack
|
1398
|
-
const code = decl.arguments[0]
|
1399
|
-
|
1566
|
+
const code = decl.arguments[0]?.value ?? '';
|
1567
|
+
|
1568
|
+
let parsed;
|
1569
|
+
try {
|
1570
|
+
parsed = parse(code, []);
|
1571
|
+
} catch (e) {
|
1572
|
+
if (e.name === 'SyntaxError') {
|
1573
|
+
// throw syntax errors of evals at runtime instead
|
1574
|
+
return internalThrow(scope, 'SyntaxError', e.message, true);
|
1575
|
+
}
|
1576
|
+
|
1577
|
+
throw e;
|
1578
|
+
}
|
1400
1579
|
|
1401
1580
|
const out = generate(scope, {
|
1402
1581
|
type: 'BlockStatement',
|
@@ -1410,13 +1589,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1410
1589
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1411
1590
|
out.push(
|
1412
1591
|
...getNodeType(scope, finalStatement),
|
1413
|
-
setLastType(scope)
|
1592
|
+
...setLastType(scope)
|
1414
1593
|
);
|
1415
1594
|
} else if (countLeftover(out) === 0) {
|
1416
1595
|
out.push(...number(UNDEFINED));
|
1417
1596
|
out.push(
|
1418
1597
|
...number(TYPES.undefined, Valtype.i32),
|
1419
|
-
setLastType(scope)
|
1598
|
+
...setLastType(scope)
|
1420
1599
|
);
|
1421
1600
|
}
|
1422
1601
|
|
@@ -1438,13 +1617,16 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1438
1617
|
|
1439
1618
|
target = { ...decl.callee };
|
1440
1619
|
target.name = spl.slice(0, -1).join('_');
|
1620
|
+
|
1621
|
+
// failed to lookup name, abort
|
1622
|
+
if (!lookupName(scope, target.name)[0]) protoName = null;
|
1441
1623
|
}
|
1442
1624
|
|
1443
1625
|
// literal.func()
|
1444
1626
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1445
1627
|
// megahack for /regex/.func()
|
1446
|
-
|
1447
|
-
|
1628
|
+
const funcName = decl.callee.property.name;
|
1629
|
+
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1448
1630
|
const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
|
1449
1631
|
|
1450
1632
|
funcIndex[func.name] = func.index;
|
@@ -1460,7 +1642,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1460
1642
|
Opcodes.i32_from_u,
|
1461
1643
|
|
1462
1644
|
...number(TYPES.boolean, Valtype.i32),
|
1463
|
-
setLastType(scope)
|
1645
|
+
...setLastType(scope)
|
1464
1646
|
];
|
1465
1647
|
}
|
1466
1648
|
|
@@ -1485,23 +1667,47 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1485
1667
|
// }
|
1486
1668
|
|
1487
1669
|
if (protoName) {
|
1670
|
+
const protoBC = {};
|
1671
|
+
|
1672
|
+
const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
|
1673
|
+
|
1674
|
+
if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
|
1675
|
+
for (const x of builtinProtoCands) {
|
1676
|
+
const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
|
1677
|
+
if (type == null) continue;
|
1678
|
+
|
1679
|
+
protoBC[type] = generateCall(scope, {
|
1680
|
+
callee: {
|
1681
|
+
name: x
|
1682
|
+
},
|
1683
|
+
arguments: [ target, ...decl.arguments ],
|
1684
|
+
_protoInternalCall: true
|
1685
|
+
});
|
1686
|
+
}
|
1687
|
+
}
|
1688
|
+
|
1488
1689
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1489
|
-
|
1490
|
-
if (f) acc[x] = f;
|
1690
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1491
1691
|
return acc;
|
1492
1692
|
}, {});
|
1493
1693
|
|
1494
|
-
// no prototype function candidates, ignore
|
1495
1694
|
if (Object.keys(protoCands).length > 0) {
|
1496
1695
|
// use local for cached i32 length as commonly used
|
1497
1696
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1498
1697
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1499
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1500
1698
|
|
1501
1699
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1502
1700
|
|
1701
|
+
const rawPointer = [
|
1702
|
+
...generate(scope, target),
|
1703
|
+
Opcodes.i32_to_u
|
1704
|
+
];
|
1705
|
+
|
1706
|
+
const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
|
1707
|
+
const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
|
1708
|
+
|
1709
|
+
let allOptUnused = true;
|
1503
1710
|
let lengthI32CacheUsed = false;
|
1504
|
-
const protoBC = {};
|
1505
1711
|
for (const x in protoCands) {
|
1506
1712
|
const protoFunc = protoCands[x];
|
1507
1713
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
@@ -1509,7 +1715,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1509
1715
|
...RTArrayUtil.getLength(getPointer),
|
1510
1716
|
|
1511
1717
|
...number(TYPES.number, Valtype.i32),
|
1512
|
-
setLastType(scope)
|
1718
|
+
...setLastType(scope)
|
1513
1719
|
];
|
1514
1720
|
continue;
|
1515
1721
|
}
|
@@ -1519,6 +1725,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1519
1725
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1520
1726
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1521
1727
|
|
1728
|
+
let optUnused = false;
|
1522
1729
|
const protoOut = protoFunc(getPointer, {
|
1523
1730
|
getCachedI32: () => {
|
1524
1731
|
lengthI32CacheUsed = true;
|
@@ -1533,23 +1740,30 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1533
1740
|
return makeArray(scope, {
|
1534
1741
|
rawElements: new Array(length)
|
1535
1742
|
}, _global, _name, true, itemType);
|
1743
|
+
}, () => {
|
1744
|
+
optUnused = true;
|
1745
|
+
return unusedValue;
|
1536
1746
|
});
|
1537
1747
|
|
1748
|
+
if (!optUnused) allOptUnused = false;
|
1749
|
+
|
1538
1750
|
protoBC[x] = [
|
1539
|
-
[ Opcodes.block, valtypeBinary ],
|
1751
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1540
1752
|
...protoOut,
|
1541
1753
|
|
1542
1754
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1543
|
-
setLastType(scope),
|
1755
|
+
...setLastType(scope),
|
1544
1756
|
[ Opcodes.end ]
|
1545
1757
|
];
|
1546
1758
|
}
|
1547
1759
|
|
1548
|
-
|
1549
|
-
...generate(scope, target),
|
1760
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1550
1761
|
|
1551
|
-
|
1552
|
-
|
1762
|
+
return [
|
1763
|
+
...(usePointerCache ? [
|
1764
|
+
...rawPointer,
|
1765
|
+
[ Opcodes.local_set, pointerLocal ],
|
1766
|
+
] : []),
|
1553
1767
|
|
1554
1768
|
...(!lengthI32CacheUsed ? [] : [
|
1555
1769
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1561,13 +1775,22 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1561
1775
|
|
1562
1776
|
// TODO: error better
|
1563
1777
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1564
|
-
}, valtypeBinary),
|
1778
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1565
1779
|
];
|
1566
1780
|
}
|
1781
|
+
|
1782
|
+
if (Object.keys(protoBC).length > 0) {
|
1783
|
+
return typeSwitch(scope, getNodeType(scope, target), {
|
1784
|
+
...protoBC,
|
1785
|
+
|
1786
|
+
// TODO: error better
|
1787
|
+
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1788
|
+
}, valtypeBinary);
|
1789
|
+
}
|
1567
1790
|
}
|
1568
1791
|
|
1569
1792
|
// TODO: only allows callee as literal
|
1570
|
-
if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
|
1793
|
+
if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
|
1571
1794
|
|
1572
1795
|
let idx = funcIndex[name] ?? importedFuncs[name];
|
1573
1796
|
if (idx === undefined && builtinFuncs[name]) {
|
@@ -1600,15 +1823,66 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1600
1823
|
idx = -1;
|
1601
1824
|
}
|
1602
1825
|
|
1826
|
+
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1827
|
+
const wasmOps = {
|
1828
|
+
// pointer, align, offset
|
1829
|
+
i32_load: { imms: 2, args: 1, returns: 1 },
|
1830
|
+
// pointer, value, align, offset
|
1831
|
+
i32_store: { imms: 2, args: 2, returns: 0 },
|
1832
|
+
// pointer, align, offset
|
1833
|
+
i32_load8_u: { imms: 2, args: 1, returns: 1 },
|
1834
|
+
// pointer, value, align, offset
|
1835
|
+
i32_store8: { imms: 2, args: 2, returns: 0 },
|
1836
|
+
// pointer, align, offset
|
1837
|
+
i32_load16_u: { imms: 2, args: 1, returns: 1 },
|
1838
|
+
// pointer, value, align, offset
|
1839
|
+
i32_store16: { imms: 2, args: 2, returns: 0 },
|
1840
|
+
|
1841
|
+
// pointer, align, offset
|
1842
|
+
f64_load: { imms: 2, args: 1, returns: 1 },
|
1843
|
+
// pointer, value, align, offset
|
1844
|
+
f64_store: { imms: 2, args: 2, returns: 0 },
|
1845
|
+
|
1846
|
+
// value
|
1847
|
+
i32_const: { imms: 1, args: 0, returns: 1 },
|
1848
|
+
|
1849
|
+
// a, b
|
1850
|
+
i32_or: { imms: 0, args: 2, returns: 1 },
|
1851
|
+
};
|
1852
|
+
|
1853
|
+
const opName = name.slice('__Porffor_wasm_'.length);
|
1854
|
+
|
1855
|
+
if (wasmOps[opName]) {
|
1856
|
+
const op = wasmOps[opName];
|
1857
|
+
|
1858
|
+
const argOut = [];
|
1859
|
+
for (let i = 0; i < op.args; i++) argOut.push(
|
1860
|
+
...generate(scope, decl.arguments[i]),
|
1861
|
+
Opcodes.i32_to
|
1862
|
+
);
|
1863
|
+
|
1864
|
+
// literals only
|
1865
|
+
const imms = decl.arguments.slice(op.args).map(x => x.value);
|
1866
|
+
|
1867
|
+
return [
|
1868
|
+
...argOut,
|
1869
|
+
[ Opcodes[opName], ...imms ],
|
1870
|
+
...(new Array(op.returns).fill(Opcodes.i32_from))
|
1871
|
+
];
|
1872
|
+
}
|
1873
|
+
}
|
1874
|
+
|
1603
1875
|
if (idx === undefined) {
|
1604
|
-
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function
|
1605
|
-
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined
|
1876
|
+
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1877
|
+
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
1606
1878
|
}
|
1607
1879
|
|
1608
1880
|
const func = funcs.find(x => x.index === idx);
|
1609
1881
|
|
1610
1882
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1611
|
-
const
|
1883
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1884
|
+
const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
|
1885
|
+
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1612
1886
|
|
1613
1887
|
let args = decl.arguments;
|
1614
1888
|
if (func && args.length < paramCount) {
|
@@ -1624,14 +1898,24 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1624
1898
|
if (func && func.throws) scope.throws = true;
|
1625
1899
|
|
1626
1900
|
let out = [];
|
1627
|
-
for (
|
1901
|
+
for (let i = 0; i < args.length; i++) {
|
1902
|
+
const arg = args[i];
|
1628
1903
|
out = out.concat(generate(scope, arg));
|
1629
|
-
|
1904
|
+
|
1905
|
+
if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1906
|
+
out.push(Opcodes.i32_to);
|
1907
|
+
}
|
1908
|
+
|
1909
|
+
if (importedFuncs[name] && name.startsWith('profile')) {
|
1910
|
+
out.push(Opcodes.i32_to);
|
1911
|
+
}
|
1912
|
+
|
1913
|
+
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1630
1914
|
}
|
1631
1915
|
|
1632
1916
|
out.push([ Opcodes.call, idx ]);
|
1633
1917
|
|
1634
|
-
if (!
|
1918
|
+
if (!typedReturns) {
|
1635
1919
|
// let type;
|
1636
1920
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1637
1921
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1641,7 +1925,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1641
1925
|
// ...number(type, Valtype.i32),
|
1642
1926
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1643
1927
|
// );
|
1644
|
-
} else out.push(setLastType(scope));
|
1928
|
+
} else out.push(...setLastType(scope));
|
1929
|
+
|
1930
|
+
if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1931
|
+
out.push(Opcodes.i32_from);
|
1932
|
+
}
|
1645
1933
|
|
1646
1934
|
return out;
|
1647
1935
|
};
|
@@ -1650,7 +1938,7 @@ const generateNew = (scope, decl, _global, _name) => {
|
|
1650
1938
|
// hack: basically treat this as a normal call for builtins for now
|
1651
1939
|
const name = mapName(decl.callee.name);
|
1652
1940
|
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1653
|
-
if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
|
1941
|
+
if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
|
1654
1942
|
|
1655
1943
|
return generateCall(scope, decl, _global, _name);
|
1656
1944
|
};
|
@@ -1767,12 +2055,14 @@ const brTable = (input, bc, returns) => {
|
|
1767
2055
|
};
|
1768
2056
|
|
1769
2057
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
2058
|
+
if (!Prefs.bytestring) delete bc[TYPES._bytestring];
|
2059
|
+
|
1770
2060
|
const known = knownType(scope, type);
|
1771
2061
|
if (known != null) {
|
1772
2062
|
return bc[known] ?? bc.default;
|
1773
2063
|
}
|
1774
2064
|
|
1775
|
-
if (
|
2065
|
+
if (Prefs.typeswitchUseBrtable)
|
1776
2066
|
return brTable(type, bc, returns);
|
1777
2067
|
|
1778
2068
|
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
@@ -1782,8 +2072,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1782
2072
|
[ Opcodes.block, returns ]
|
1783
2073
|
];
|
1784
2074
|
|
1785
|
-
// todo: use br_table?
|
1786
|
-
|
1787
2075
|
for (const x in bc) {
|
1788
2076
|
if (x === 'default') continue;
|
1789
2077
|
|
@@ -1807,7 +2095,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1807
2095
|
return out;
|
1808
2096
|
};
|
1809
2097
|
|
1810
|
-
const allocVar = (scope, name, global = false) => {
|
2098
|
+
const allocVar = (scope, name, global = false, type = true) => {
|
1811
2099
|
const target = global ? globals : scope.locals;
|
1812
2100
|
|
1813
2101
|
// already declared
|
@@ -1821,8 +2109,10 @@ const allocVar = (scope, name, global = false) => {
|
|
1821
2109
|
let idx = global ? globalInd++ : scope.localInd++;
|
1822
2110
|
target[name] = { idx, type: valtypeBinary };
|
1823
2111
|
|
1824
|
-
|
1825
|
-
|
2112
|
+
if (type) {
|
2113
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
2114
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
2115
|
+
}
|
1826
2116
|
|
1827
2117
|
return idx;
|
1828
2118
|
};
|
@@ -1837,11 +2127,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
|
1837
2127
|
};
|
1838
2128
|
|
1839
2129
|
const typeAnnoToPorfType = x => {
|
1840
|
-
if (
|
1841
|
-
if (TYPES[
|
2130
|
+
if (!x) return null;
|
2131
|
+
if (TYPES[x] != null) return TYPES[x];
|
2132
|
+
if (TYPES['_' + x] != null) return TYPES['_' + x];
|
1842
2133
|
|
1843
2134
|
switch (x) {
|
1844
2135
|
case 'i32':
|
2136
|
+
case 'i64':
|
2137
|
+
case 'f64':
|
1845
2138
|
return TYPES.number;
|
1846
2139
|
}
|
1847
2140
|
|
@@ -1852,7 +2145,7 @@ const extractTypeAnnotation = decl => {
|
|
1852
2145
|
let a = decl;
|
1853
2146
|
while (a.typeAnnotation) a = a.typeAnnotation;
|
1854
2147
|
|
1855
|
-
let type, elementType;
|
2148
|
+
let type = null, elementType = null;
|
1856
2149
|
if (a.typeName) {
|
1857
2150
|
type = a.typeName.name;
|
1858
2151
|
} else if (a.type.endsWith('Keyword')) {
|
@@ -1865,6 +2158,8 @@ const extractTypeAnnotation = decl => {
|
|
1865
2158
|
const typeName = type;
|
1866
2159
|
type = typeAnnoToPorfType(type);
|
1867
2160
|
|
2161
|
+
if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
|
2162
|
+
|
1868
2163
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1869
2164
|
|
1870
2165
|
return { type, typeName, elementType };
|
@@ -1881,6 +2176,8 @@ const generateVar = (scope, decl) => {
|
|
1881
2176
|
for (const x of decl.declarations) {
|
1882
2177
|
const name = mapName(x.id.name);
|
1883
2178
|
|
2179
|
+
if (!name) return todo(scope, 'destructuring is not supported yet');
|
2180
|
+
|
1884
2181
|
if (x.init && isFuncType(x.init.type)) {
|
1885
2182
|
// hack for let a = function () { ... }
|
1886
2183
|
x.init.id = { name };
|
@@ -1896,9 +2193,10 @@ const generateVar = (scope, decl) => {
|
|
1896
2193
|
continue; // always ignore
|
1897
2194
|
}
|
1898
2195
|
|
1899
|
-
|
2196
|
+
const typed = typedInput && x.id.typeAnnotation;
|
2197
|
+
let idx = allocVar(scope, name, global, !typed);
|
1900
2198
|
|
1901
|
-
if (
|
2199
|
+
if (typed) {
|
1902
2200
|
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1903
2201
|
}
|
1904
2202
|
|
@@ -1916,7 +2214,8 @@ const generateVar = (scope, decl) => {
|
|
1916
2214
|
return out;
|
1917
2215
|
};
|
1918
2216
|
|
1919
|
-
|
2217
|
+
// todo: optimize this func for valueUnused
|
2218
|
+
const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
1920
2219
|
const { type, name } = decl.left;
|
1921
2220
|
|
1922
2221
|
if (type === 'ObjectPattern') {
|
@@ -1934,9 +2233,9 @@ const generateAssign = (scope, decl) => {
|
|
1934
2233
|
// hack: .length setter
|
1935
2234
|
if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
|
1936
2235
|
const name = decl.left.object.name;
|
1937
|
-
const pointer = arrays
|
2236
|
+
const pointer = scope.arrays?.get(name);
|
1938
2237
|
|
1939
|
-
const aotPointer = pointer != null;
|
2238
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1940
2239
|
|
1941
2240
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
1942
2241
|
|
@@ -1961,9 +2260,9 @@ const generateAssign = (scope, decl) => {
|
|
1961
2260
|
// arr[i]
|
1962
2261
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
1963
2262
|
const name = decl.left.object.name;
|
1964
|
-
const pointer = arrays
|
2263
|
+
const pointer = scope.arrays?.get(name);
|
1965
2264
|
|
1966
|
-
const aotPointer = pointer != null;
|
2265
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1967
2266
|
|
1968
2267
|
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
1969
2268
|
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
@@ -2019,6 +2318,8 @@ const generateAssign = (scope, decl) => {
|
|
2019
2318
|
];
|
2020
2319
|
}
|
2021
2320
|
|
2321
|
+
if (!name) return todo(scope, 'destructuring is not supported yet', true);
|
2322
|
+
|
2022
2323
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2023
2324
|
|
2024
2325
|
if (local === undefined) {
|
@@ -2065,9 +2366,7 @@ const generateAssign = (scope, decl) => {
|
|
2065
2366
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
2066
2367
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
2067
2368
|
|
2068
|
-
getLastType(scope)
|
2069
|
-
// hack: type is idx+1
|
2070
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2369
|
+
...setType(scope, name, getLastType(scope))
|
2071
2370
|
];
|
2072
2371
|
}
|
2073
2372
|
|
@@ -2078,9 +2377,7 @@ const generateAssign = (scope, decl) => {
|
|
2078
2377
|
|
2079
2378
|
// todo: string concat types
|
2080
2379
|
|
2081
|
-
|
2082
|
-
...number(TYPES.number, Valtype.i32),
|
2083
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2380
|
+
...setType(scope, name, TYPES.number)
|
2084
2381
|
];
|
2085
2382
|
};
|
2086
2383
|
|
@@ -2126,7 +2423,7 @@ const generateUnary = (scope, decl) => {
|
|
2126
2423
|
return out;
|
2127
2424
|
}
|
2128
2425
|
|
2129
|
-
case 'delete':
|
2426
|
+
case 'delete': {
|
2130
2427
|
let toReturn = true, toGenerate = true;
|
2131
2428
|
|
2132
2429
|
if (decl.argument.type === 'Identifier') {
|
@@ -2148,38 +2445,60 @@ const generateUnary = (scope, decl) => {
|
|
2148
2445
|
|
2149
2446
|
out.push(...number(toReturn ? 1 : 0));
|
2150
2447
|
return out;
|
2448
|
+
}
|
2449
|
+
|
2450
|
+
case 'typeof': {
|
2451
|
+
let overrideType, toGenerate = true;
|
2452
|
+
|
2453
|
+
if (decl.argument.type === 'Identifier') {
|
2454
|
+
const out = generateIdent(scope, decl.argument);
|
2455
|
+
|
2456
|
+
// if ReferenceError (undeclared var), ignore and return undefined
|
2457
|
+
if (out[1]) {
|
2458
|
+
// does not exist (2 ops from throw)
|
2459
|
+
overrideType = number(TYPES.undefined, Valtype.i32);
|
2460
|
+
toGenerate = false;
|
2461
|
+
}
|
2462
|
+
}
|
2463
|
+
|
2464
|
+
const out = toGenerate ? generate(scope, decl.argument) : [];
|
2465
|
+
disposeLeftover(out);
|
2151
2466
|
|
2152
|
-
|
2153
|
-
return typeSwitch(scope, getNodeType(scope, decl.argument), {
|
2467
|
+
out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
|
2154
2468
|
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
2155
2469
|
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
2156
2470
|
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
2157
2471
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
2158
2472
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
2159
2473
|
|
2474
|
+
[TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2475
|
+
|
2160
2476
|
// object and internal types
|
2161
2477
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2162
|
-
});
|
2478
|
+
}));
|
2479
|
+
|
2480
|
+
return out;
|
2481
|
+
}
|
2163
2482
|
|
2164
2483
|
default:
|
2165
|
-
return todo(`unary operator ${decl.operator} not implemented yet
|
2484
|
+
return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
|
2166
2485
|
}
|
2167
2486
|
};
|
2168
2487
|
|
2169
|
-
const generateUpdate = (scope, decl) => {
|
2488
|
+
const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
|
2170
2489
|
const { name } = decl.argument;
|
2171
2490
|
|
2172
2491
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2173
2492
|
|
2174
2493
|
if (local === undefined) {
|
2175
|
-
return todo(`update expression with undefined variable
|
2494
|
+
return todo(scope, `update expression with undefined variable`, true);
|
2176
2495
|
}
|
2177
2496
|
|
2178
2497
|
const idx = local.idx;
|
2179
2498
|
const out = [];
|
2180
2499
|
|
2181
2500
|
out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2182
|
-
if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2501
|
+
if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2183
2502
|
|
2184
2503
|
switch (decl.operator) {
|
2185
2504
|
case '++':
|
@@ -2192,7 +2511,7 @@ const generateUpdate = (scope, decl) => {
|
|
2192
2511
|
}
|
2193
2512
|
|
2194
2513
|
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2195
|
-
if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2514
|
+
if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2196
2515
|
|
2197
2516
|
return out;
|
2198
2517
|
};
|
@@ -2232,7 +2551,7 @@ const generateConditional = (scope, decl) => {
|
|
2232
2551
|
// note type
|
2233
2552
|
out.push(
|
2234
2553
|
...getNodeType(scope, decl.consequent),
|
2235
|
-
setLastType(scope)
|
2554
|
+
...setLastType(scope)
|
2236
2555
|
);
|
2237
2556
|
|
2238
2557
|
out.push([ Opcodes.else ]);
|
@@ -2241,7 +2560,7 @@ const generateConditional = (scope, decl) => {
|
|
2241
2560
|
// note type
|
2242
2561
|
out.push(
|
2243
2562
|
...getNodeType(scope, decl.alternate),
|
2244
|
-
setLastType(scope)
|
2563
|
+
...setLastType(scope)
|
2245
2564
|
);
|
2246
2565
|
|
2247
2566
|
out.push([ Opcodes.end ]);
|
@@ -2255,15 +2574,17 @@ const generateFor = (scope, decl) => {
|
|
2255
2574
|
const out = [];
|
2256
2575
|
|
2257
2576
|
if (decl.init) {
|
2258
|
-
out.push(...generate(scope, decl.init));
|
2577
|
+
out.push(...generate(scope, decl.init, false, undefined, true));
|
2259
2578
|
disposeLeftover(out);
|
2260
2579
|
}
|
2261
2580
|
|
2262
2581
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2263
2582
|
depth.push('for');
|
2264
2583
|
|
2265
|
-
out.push(...generate(scope, decl.test));
|
2266
|
-
|
2584
|
+
if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2585
|
+
else out.push(...number(1, Valtype.i32));
|
2586
|
+
|
2587
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
2267
2588
|
depth.push('if');
|
2268
2589
|
|
2269
2590
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2271,8 +2592,7 @@ const generateFor = (scope, decl) => {
|
|
2271
2592
|
out.push(...generate(scope, decl.body));
|
2272
2593
|
out.push([ Opcodes.end ]);
|
2273
2594
|
|
2274
|
-
out.push(...generate(scope, decl.update));
|
2275
|
-
depth.pop();
|
2595
|
+
if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
|
2276
2596
|
|
2277
2597
|
out.push([ Opcodes.br, 1 ]);
|
2278
2598
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2300,6 +2620,36 @@ const generateWhile = (scope, decl) => {
|
|
2300
2620
|
return out;
|
2301
2621
|
};
|
2302
2622
|
|
2623
|
+
const generateDoWhile = (scope, decl) => {
|
2624
|
+
const out = [];
|
2625
|
+
|
2626
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
2627
|
+
depth.push('dowhile');
|
2628
|
+
|
2629
|
+
// block for break (includes all)
|
2630
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2631
|
+
depth.push('block');
|
2632
|
+
|
2633
|
+
// block for continue
|
2634
|
+
// includes body but not test+loop so we can exit body at anytime
|
2635
|
+
// and still test+loop after
|
2636
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2637
|
+
depth.push('block');
|
2638
|
+
|
2639
|
+
out.push(...generate(scope, decl.body));
|
2640
|
+
|
2641
|
+
out.push([ Opcodes.end ]);
|
2642
|
+
depth.pop();
|
2643
|
+
|
2644
|
+
out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2645
|
+
out.push([ Opcodes.br_if, 1 ]);
|
2646
|
+
|
2647
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
2648
|
+
depth.pop(); depth.pop();
|
2649
|
+
|
2650
|
+
return out;
|
2651
|
+
};
|
2652
|
+
|
2303
2653
|
const generateForOf = (scope, decl) => {
|
2304
2654
|
const out = [];
|
2305
2655
|
|
@@ -2329,8 +2679,17 @@ const generateForOf = (scope, decl) => {
|
|
2329
2679
|
// setup local for left
|
2330
2680
|
generate(scope, decl.left);
|
2331
2681
|
|
2332
|
-
|
2682
|
+
let leftName = decl.left.declarations?.[0]?.id?.name;
|
2683
|
+
if (!leftName && decl.left.name) {
|
2684
|
+
leftName = decl.left.name;
|
2685
|
+
|
2686
|
+
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2687
|
+
}
|
2688
|
+
|
2689
|
+
// if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
|
2690
|
+
|
2333
2691
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2692
|
+
if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
|
2334
2693
|
|
2335
2694
|
depth.push('block');
|
2336
2695
|
depth.push('block');
|
@@ -2338,13 +2697,15 @@ const generateForOf = (scope, decl) => {
|
|
2338
2697
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2339
2698
|
// hack: this is naughty and will break things!
|
2340
2699
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2341
|
-
if (pages.
|
2700
|
+
if (pages.hasAnyString) {
|
2701
|
+
// todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
|
2342
2702
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2343
2703
|
rawElements: new Array(1)
|
2344
2704
|
}, isGlobal, leftName, true, 'i16');
|
2345
2705
|
}
|
2346
2706
|
|
2347
2707
|
// set type for local
|
2708
|
+
// todo: optimize away counter and use end pointer
|
2348
2709
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2349
2710
|
[TYPES._array]: [
|
2350
2711
|
...setType(scope, leftName, TYPES.number),
|
@@ -2429,6 +2790,56 @@ const generateForOf = (scope, decl) => {
|
|
2429
2790
|
[ Opcodes.end ],
|
2430
2791
|
[ Opcodes.end ]
|
2431
2792
|
],
|
2793
|
+
[TYPES._bytestring]: [
|
2794
|
+
...setType(scope, leftName, TYPES._bytestring),
|
2795
|
+
|
2796
|
+
[ Opcodes.loop, Blocktype.void ],
|
2797
|
+
|
2798
|
+
// setup new/out array
|
2799
|
+
...newOut,
|
2800
|
+
[ Opcodes.drop ],
|
2801
|
+
|
2802
|
+
...number(0, Valtype.i32), // base 0 for store after
|
2803
|
+
|
2804
|
+
// load current string ind {arg}
|
2805
|
+
[ Opcodes.local_get, pointer ],
|
2806
|
+
[ Opcodes.local_get, counter ],
|
2807
|
+
[ Opcodes.i32_add ],
|
2808
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
2809
|
+
|
2810
|
+
// store to new string ind 0
|
2811
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2812
|
+
|
2813
|
+
// return new string (page)
|
2814
|
+
...number(newPointer),
|
2815
|
+
|
2816
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2817
|
+
|
2818
|
+
[ Opcodes.block, Blocktype.void ],
|
2819
|
+
[ Opcodes.block, Blocktype.void ],
|
2820
|
+
...generate(scope, decl.body),
|
2821
|
+
[ Opcodes.end ],
|
2822
|
+
|
2823
|
+
// increment iter pointer
|
2824
|
+
// [ Opcodes.local_get, pointer ],
|
2825
|
+
// ...number(1, Valtype.i32),
|
2826
|
+
// [ Opcodes.i32_add ],
|
2827
|
+
// [ Opcodes.local_set, pointer ],
|
2828
|
+
|
2829
|
+
// increment counter by 1
|
2830
|
+
[ Opcodes.local_get, counter ],
|
2831
|
+
...number(1, Valtype.i32),
|
2832
|
+
[ Opcodes.i32_add ],
|
2833
|
+
[ Opcodes.local_tee, counter ],
|
2834
|
+
|
2835
|
+
// loop if counter != length
|
2836
|
+
[ Opcodes.local_get, length ],
|
2837
|
+
[ Opcodes.i32_ne ],
|
2838
|
+
[ Opcodes.br_if, 1 ],
|
2839
|
+
|
2840
|
+
[ Opcodes.end ],
|
2841
|
+
[ Opcodes.end ]
|
2842
|
+
],
|
2432
2843
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2433
2844
|
}, Blocktype.void));
|
2434
2845
|
|
@@ -2439,28 +2850,65 @@ const generateForOf = (scope, decl) => {
|
|
2439
2850
|
return out;
|
2440
2851
|
};
|
2441
2852
|
|
2853
|
+
// find the nearest loop in depth map by type
|
2442
2854
|
const getNearestLoop = () => {
|
2443
2855
|
for (let i = depth.length - 1; i >= 0; i--) {
|
2444
|
-
if (
|
2856
|
+
if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
|
2445
2857
|
}
|
2446
2858
|
|
2447
2859
|
return -1;
|
2448
2860
|
};
|
2449
2861
|
|
2450
2862
|
const generateBreak = (scope, decl) => {
|
2451
|
-
const
|
2863
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2864
|
+
const type = depth[target];
|
2865
|
+
|
2866
|
+
// different loop types have different branch offsets
|
2867
|
+
// as they have different wasm block/loop/if structures
|
2868
|
+
// we need to use the right offset by type to branch to the one we want
|
2869
|
+
// for a break: exit the loop without executing anything else inside it
|
2870
|
+
const offset = ({
|
2871
|
+
for: 2, // loop > if (wanted branch) > block (we are here)
|
2872
|
+
while: 2, // loop > if (wanted branch) (we are here)
|
2873
|
+
dowhile: 2, // loop > block (wanted branch) > block (we are here)
|
2874
|
+
forof: 2, // loop > block (wanted branch) > block (we are here)
|
2875
|
+
if: 1 // break inside if, branch 0 to skip the rest of the if
|
2876
|
+
})[type];
|
2877
|
+
|
2452
2878
|
return [
|
2453
|
-
[ Opcodes.br, ...signedLEB128(
|
2879
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2454
2880
|
];
|
2455
2881
|
};
|
2456
2882
|
|
2457
2883
|
const generateContinue = (scope, decl) => {
|
2458
|
-
const
|
2884
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2885
|
+
const type = depth[target];
|
2886
|
+
|
2887
|
+
// different loop types have different branch offsets
|
2888
|
+
// as they have different wasm block/loop/if structures
|
2889
|
+
// we need to use the right offset by type to branch to the one we want
|
2890
|
+
// for a continue: do test for the loop, and then loop depending on that success
|
2891
|
+
const offset = ({
|
2892
|
+
for: 3, // loop (wanted branch) > if > block (we are here)
|
2893
|
+
while: 1, // loop (wanted branch) > if (we are here)
|
2894
|
+
dowhile: 3, // loop > block > block (wanted branch) (we are here)
|
2895
|
+
forof: 3 // loop > block > block (wanted branch) (we are here)
|
2896
|
+
})[type];
|
2897
|
+
|
2459
2898
|
return [
|
2460
|
-
[ Opcodes.br, ...signedLEB128(
|
2899
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2461
2900
|
];
|
2462
2901
|
};
|
2463
2902
|
|
2903
|
+
const generateLabel = (scope, decl) => {
|
2904
|
+
scope.labels ??= new Map();
|
2905
|
+
|
2906
|
+
const name = decl.label.name;
|
2907
|
+
scope.labels.set(name, depth.length);
|
2908
|
+
|
2909
|
+
return generate(scope, decl.body);
|
2910
|
+
};
|
2911
|
+
|
2464
2912
|
const generateThrow = (scope, decl) => {
|
2465
2913
|
scope.throws = true;
|
2466
2914
|
|
@@ -2469,7 +2917,7 @@ const generateThrow = (scope, decl) => {
|
|
2469
2917
|
// hack: throw new X("...") -> throw "..."
|
2470
2918
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2471
2919
|
constructor = decl.argument.callee.name;
|
2472
|
-
message = decl.argument.arguments[0]
|
2920
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2473
2921
|
}
|
2474
2922
|
|
2475
2923
|
if (tags.length === 0) tags.push({
|
@@ -2481,6 +2929,9 @@ const generateThrow = (scope, decl) => {
|
|
2481
2929
|
let exceptId = exceptions.push({ constructor, message }) - 1;
|
2482
2930
|
let tagIdx = tags[0].idx;
|
2483
2931
|
|
2932
|
+
scope.exceptions ??= [];
|
2933
|
+
scope.exceptions.push(exceptId);
|
2934
|
+
|
2484
2935
|
// todo: write a description of how this works lol
|
2485
2936
|
|
2486
2937
|
return [
|
@@ -2490,7 +2941,7 @@ const generateThrow = (scope, decl) => {
|
|
2490
2941
|
};
|
2491
2942
|
|
2492
2943
|
const generateTry = (scope, decl) => {
|
2493
|
-
if (decl.finalizer) return todo('try finally not implemented yet');
|
2944
|
+
if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
|
2494
2945
|
|
2495
2946
|
const out = [];
|
2496
2947
|
|
@@ -2521,29 +2972,35 @@ const generateAssignPat = (scope, decl) => {
|
|
2521
2972
|
// TODO
|
2522
2973
|
// if identifier declared, use that
|
2523
2974
|
// else, use default (right)
|
2524
|
-
return todo('assignment pattern (optional arg)');
|
2975
|
+
return todo(scope, 'assignment pattern (optional arg)');
|
2525
2976
|
};
|
2526
2977
|
|
2527
2978
|
let pages = new Map();
|
2528
|
-
const allocPage = (reason, type) => {
|
2979
|
+
const allocPage = (scope, reason, type) => {
|
2529
2980
|
if (pages.has(reason)) return pages.get(reason).ind;
|
2530
2981
|
|
2531
2982
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2532
2983
|
if (reason.startsWith('string:')) pages.hasString = true;
|
2984
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
2985
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2533
2986
|
|
2534
2987
|
const ind = pages.size;
|
2535
2988
|
pages.set(reason, { ind, type });
|
2536
2989
|
|
2537
|
-
|
2990
|
+
scope.pages ??= new Map();
|
2991
|
+
scope.pages.set(reason, { ind, type });
|
2992
|
+
|
2993
|
+
if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2538
2994
|
|
2539
2995
|
return ind;
|
2540
2996
|
};
|
2541
2997
|
|
2998
|
+
// todo: add scope.pages
|
2542
2999
|
const freePage = reason => {
|
2543
3000
|
const { ind } = pages.get(reason);
|
2544
3001
|
pages.delete(reason);
|
2545
3002
|
|
2546
|
-
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
3003
|
+
if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2547
3004
|
|
2548
3005
|
return ind;
|
2549
3006
|
};
|
@@ -2563,38 +3020,51 @@ const StoreOps = {
|
|
2563
3020
|
f64: Opcodes.f64_store,
|
2564
3021
|
|
2565
3022
|
// expects i32 input!
|
2566
|
-
|
3023
|
+
i8: Opcodes.i32_store8,
|
3024
|
+
i16: Opcodes.i32_store16,
|
2567
3025
|
};
|
2568
3026
|
|
2569
3027
|
let data = [];
|
2570
3028
|
|
2571
|
-
const compileBytes = (val, itemType
|
3029
|
+
const compileBytes = (val, itemType) => {
|
2572
3030
|
// todo: this is a mess and needs confirming / ????
|
2573
3031
|
switch (itemType) {
|
2574
3032
|
case 'i8': return [ val % 256 ];
|
2575
|
-
case 'i16': return [ val % 256,
|
2576
|
-
|
2577
|
-
case 'i32':
|
2578
|
-
|
2579
|
-
return enforceFourBytes(signedLEB128(val));
|
3033
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3034
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3035
|
+
case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
|
3036
|
+
// todo: i64
|
2580
3037
|
|
2581
3038
|
case 'f64': return ieee754_binary64(val);
|
2582
3039
|
}
|
2583
3040
|
};
|
2584
3041
|
|
3042
|
+
const getAllocType = itemType => {
|
3043
|
+
switch (itemType) {
|
3044
|
+
case 'i8': return 'bytestring';
|
3045
|
+
case 'i16': return 'string';
|
3046
|
+
|
3047
|
+
default: return 'array';
|
3048
|
+
}
|
3049
|
+
};
|
3050
|
+
|
2585
3051
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2586
3052
|
const out = [];
|
2587
3053
|
|
3054
|
+
scope.arrays ??= new Map();
|
3055
|
+
|
2588
3056
|
let firstAssign = false;
|
2589
|
-
if (!arrays.has(name) || name === '$undeclared') {
|
3057
|
+
if (!scope.arrays.has(name) || name === '$undeclared') {
|
2590
3058
|
firstAssign = true;
|
2591
3059
|
|
2592
3060
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2593
3061
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2594
|
-
|
3062
|
+
|
3063
|
+
if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${scope.name} | ${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
3064
|
+
else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2595
3065
|
}
|
2596
3066
|
|
2597
|
-
const pointer = arrays.get(name);
|
3067
|
+
const pointer = scope.arrays.get(name);
|
2598
3068
|
|
2599
3069
|
const useRawElements = !!decl.rawElements;
|
2600
3070
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
@@ -2602,19 +3072,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2602
3072
|
const valtype = itemTypeToValtype[itemType];
|
2603
3073
|
const length = elements.length;
|
2604
3074
|
|
2605
|
-
if (firstAssign && useRawElements) {
|
2606
|
-
|
3075
|
+
if (firstAssign && useRawElements && !Prefs.noData) {
|
3076
|
+
// if length is 0 memory/data will just be 0000... anyway
|
3077
|
+
if (length !== 0) {
|
3078
|
+
let bytes = compileBytes(length, 'i32');
|
2607
3079
|
|
2608
|
-
|
2609
|
-
|
3080
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
3081
|
+
if (elements[i] == null) continue;
|
2610
3082
|
|
2611
|
-
|
2612
|
-
|
3083
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
3084
|
+
}
|
2613
3085
|
|
2614
|
-
|
2615
|
-
|
2616
|
-
|
2617
|
-
|
3086
|
+
const ind = data.push({
|
3087
|
+
offset: pointer,
|
3088
|
+
bytes
|
3089
|
+
}) - 1;
|
3090
|
+
|
3091
|
+
scope.data ??= [];
|
3092
|
+
scope.data.push(ind);
|
3093
|
+
}
|
2618
3094
|
|
2619
3095
|
// local value as pointer
|
2620
3096
|
out.push(...number(pointer));
|
@@ -2637,7 +3113,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2637
3113
|
out.push(
|
2638
3114
|
...number(0, Valtype.i32),
|
2639
3115
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2640
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
3116
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2641
3117
|
);
|
2642
3118
|
}
|
2643
3119
|
|
@@ -2647,31 +3123,56 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2647
3123
|
return [ out, pointer ];
|
2648
3124
|
};
|
2649
3125
|
|
2650
|
-
const
|
3126
|
+
const byteStringable = str => {
|
3127
|
+
if (!Prefs.bytestring) return false;
|
3128
|
+
|
3129
|
+
for (let i = 0; i < str.length; i++) {
|
3130
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
3131
|
+
}
|
3132
|
+
|
3133
|
+
return true;
|
3134
|
+
};
|
3135
|
+
|
3136
|
+
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2651
3137
|
const rawElements = new Array(str.length);
|
3138
|
+
let byteStringable = Prefs.bytestring;
|
2652
3139
|
for (let i = 0; i < str.length; i++) {
|
2653
|
-
|
3140
|
+
const c = str.charCodeAt(i);
|
3141
|
+
rawElements[i] = c;
|
3142
|
+
|
3143
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2654
3144
|
}
|
2655
3145
|
|
3146
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
3147
|
+
|
2656
3148
|
return makeArray(scope, {
|
2657
3149
|
rawElements
|
2658
|
-
}, global, name, false, 'i16')[0];
|
3150
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2659
3151
|
};
|
2660
3152
|
|
2661
|
-
let arrays = new Map();
|
2662
3153
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
2663
3154
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
2664
3155
|
};
|
2665
3156
|
|
2666
3157
|
export const generateMember = (scope, decl, _global, _name) => {
|
2667
3158
|
const name = decl.object.name;
|
2668
|
-
const pointer = arrays
|
3159
|
+
const pointer = scope.arrays?.get(name);
|
2669
3160
|
|
2670
|
-
const aotPointer = pointer != null;
|
3161
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2671
3162
|
|
2672
3163
|
// hack: .length
|
2673
3164
|
if (decl.property.name === 'length') {
|
2674
|
-
|
3165
|
+
const func = funcs.find(x => x.name === name);
|
3166
|
+
if (func) {
|
3167
|
+
const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
|
3168
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
3169
|
+
return number(typedParams ? func.params.length / 2 : func.params.length);
|
3170
|
+
}
|
3171
|
+
|
3172
|
+
if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
|
3173
|
+
if (importedFuncs[name]) return number(importedFuncs[name].params);
|
3174
|
+
if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
|
3175
|
+
|
2675
3176
|
return [
|
2676
3177
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2677
3178
|
...generate(scope, decl.object),
|
@@ -2683,10 +3184,13 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2683
3184
|
];
|
2684
3185
|
}
|
2685
3186
|
|
3187
|
+
const object = generate(scope, decl.object);
|
3188
|
+
const property = generate(scope, decl.property);
|
3189
|
+
|
2686
3190
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2687
3191
|
// hack: this is naughty and will break things!
|
2688
3192
|
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2689
|
-
if (pages.
|
3193
|
+
if (pages.hasAnyString) {
|
2690
3194
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2691
3195
|
rawElements: new Array(1)
|
2692
3196
|
}, _global, _name, true, 'i16');
|
@@ -2695,7 +3199,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2695
3199
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2696
3200
|
[TYPES._array]: [
|
2697
3201
|
// get index as valtype
|
2698
|
-
...
|
3202
|
+
...property,
|
2699
3203
|
|
2700
3204
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2701
3205
|
Opcodes.i32_to_u,
|
@@ -2703,7 +3207,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2703
3207
|
[ Opcodes.i32_mul ],
|
2704
3208
|
|
2705
3209
|
...(aotPointer ? [] : [
|
2706
|
-
...
|
3210
|
+
...object,
|
2707
3211
|
Opcodes.i32_to_u,
|
2708
3212
|
[ Opcodes.i32_add ]
|
2709
3213
|
]),
|
@@ -2712,7 +3216,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2712
3216
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2713
3217
|
|
2714
3218
|
...number(TYPES.number, Valtype.i32),
|
2715
|
-
setLastType(scope)
|
3219
|
+
...setLastType(scope)
|
2716
3220
|
],
|
2717
3221
|
|
2718
3222
|
[TYPES.string]: [
|
@@ -2722,14 +3226,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2722
3226
|
|
2723
3227
|
...number(0, Valtype.i32), // base 0 for store later
|
2724
3228
|
|
2725
|
-
...
|
2726
|
-
|
3229
|
+
...property,
|
2727
3230
|
Opcodes.i32_to_u,
|
3231
|
+
|
2728
3232
|
...number(ValtypeSize.i16, Valtype.i32),
|
2729
3233
|
[ Opcodes.i32_mul ],
|
2730
3234
|
|
2731
3235
|
...(aotPointer ? [] : [
|
2732
|
-
...
|
3236
|
+
...object,
|
2733
3237
|
Opcodes.i32_to_u,
|
2734
3238
|
[ Opcodes.i32_add ]
|
2735
3239
|
]),
|
@@ -2744,10 +3248,38 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2744
3248
|
...number(newPointer),
|
2745
3249
|
|
2746
3250
|
...number(TYPES.string, Valtype.i32),
|
2747
|
-
setLastType(scope)
|
3251
|
+
...setLastType(scope)
|
3252
|
+
],
|
3253
|
+
[TYPES._bytestring]: [
|
3254
|
+
// setup new/out array
|
3255
|
+
...newOut,
|
3256
|
+
[ Opcodes.drop ],
|
3257
|
+
|
3258
|
+
...number(0, Valtype.i32), // base 0 for store later
|
3259
|
+
|
3260
|
+
...property,
|
3261
|
+
Opcodes.i32_to_u,
|
3262
|
+
|
3263
|
+
...(aotPointer ? [] : [
|
3264
|
+
...object,
|
3265
|
+
Opcodes.i32_to_u,
|
3266
|
+
[ Opcodes.i32_add ]
|
3267
|
+
]),
|
3268
|
+
|
3269
|
+
// load current string ind {arg}
|
3270
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
3271
|
+
|
3272
|
+
// store to new string ind 0
|
3273
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
3274
|
+
|
3275
|
+
// return new string (page)
|
3276
|
+
...number(newPointer),
|
3277
|
+
|
3278
|
+
...number(TYPES._bytestring, Valtype.i32),
|
3279
|
+
...setLastType(scope)
|
2748
3280
|
],
|
2749
3281
|
|
2750
|
-
default:
|
3282
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
|
2751
3283
|
});
|
2752
3284
|
};
|
2753
3285
|
|
@@ -2757,25 +3289,36 @@ const objectHack = node => {
|
|
2757
3289
|
if (!node) return node;
|
2758
3290
|
|
2759
3291
|
if (node.type === 'MemberExpression') {
|
2760
|
-
|
3292
|
+
const out = (() => {
|
3293
|
+
if (node.computed || node.optional) return;
|
3294
|
+
|
3295
|
+
let objectName = node.object.name;
|
2761
3296
|
|
2762
|
-
|
3297
|
+
// if object is not identifier or another member exp, give up
|
3298
|
+
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
|
3299
|
+
if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
|
2763
3300
|
|
2764
|
-
|
2765
|
-
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
3301
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2766
3302
|
|
2767
|
-
|
3303
|
+
// if .length, give up (hack within a hack!)
|
3304
|
+
if (node.property.name === 'length') {
|
3305
|
+
node.object = objectHack(node.object);
|
3306
|
+
return;
|
3307
|
+
}
|
2768
3308
|
|
2769
|
-
|
2770
|
-
|
3309
|
+
// no object name, give up
|
3310
|
+
if (!objectName) return;
|
2771
3311
|
|
2772
|
-
|
2773
|
-
|
3312
|
+
const name = '__' + objectName + '_' + node.property.name;
|
3313
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2774
3314
|
|
2775
|
-
|
2776
|
-
|
2777
|
-
|
2778
|
-
|
3315
|
+
return {
|
3316
|
+
type: 'Identifier',
|
3317
|
+
name
|
3318
|
+
};
|
3319
|
+
})();
|
3320
|
+
|
3321
|
+
if (out) return out;
|
2779
3322
|
}
|
2780
3323
|
|
2781
3324
|
for (const x in node) {
|
@@ -2789,8 +3332,8 @@ const objectHack = node => {
|
|
2789
3332
|
};
|
2790
3333
|
|
2791
3334
|
const generateFunc = (scope, decl) => {
|
2792
|
-
if (decl.async) return todo('async functions are not supported');
|
2793
|
-
if (decl.generator) return todo('generator functions are not supported');
|
3335
|
+
if (decl.async) return todo(scope, 'async functions are not supported');
|
3336
|
+
if (decl.generator) return todo(scope, 'generator functions are not supported');
|
2794
3337
|
|
2795
3338
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2796
3339
|
const params = decl.params ?? [];
|
@@ -2806,6 +3349,11 @@ const generateFunc = (scope, decl) => {
|
|
2806
3349
|
name
|
2807
3350
|
};
|
2808
3351
|
|
3352
|
+
if (typedInput && decl.returnType) {
|
3353
|
+
innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
|
3354
|
+
innerScope.returns = [ valtypeBinary ];
|
3355
|
+
}
|
3356
|
+
|
2809
3357
|
for (let i = 0; i < params.length; i++) {
|
2810
3358
|
allocVar(innerScope, params[i].name, false);
|
2811
3359
|
|
@@ -2827,13 +3375,13 @@ const generateFunc = (scope, decl) => {
|
|
2827
3375
|
const func = {
|
2828
3376
|
name,
|
2829
3377
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2830
|
-
|
2831
|
-
|
2832
|
-
throws: innerScope.throws,
|
2833
|
-
index: currentFuncIndex++
|
3378
|
+
index: currentFuncIndex++,
|
3379
|
+
...innerScope
|
2834
3380
|
};
|
2835
3381
|
funcIndex[name] = func.index;
|
2836
3382
|
|
3383
|
+
if (name === 'main') func.gotLastType = true;
|
3384
|
+
|
2837
3385
|
// quick hack fixes
|
2838
3386
|
for (const inst of wasm) {
|
2839
3387
|
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
@@ -2885,7 +3433,7 @@ const internalConstrs = {
|
|
2885
3433
|
|
2886
3434
|
// todo: check in wasm instead of here
|
2887
3435
|
const literalValue = arg.value ?? 0;
|
2888
|
-
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
|
3436
|
+
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
|
2889
3437
|
|
2890
3438
|
return [
|
2891
3439
|
...number(0, Valtype.i32),
|
@@ -2896,7 +3444,8 @@ const internalConstrs = {
|
|
2896
3444
|
...number(pointer)
|
2897
3445
|
];
|
2898
3446
|
},
|
2899
|
-
type: TYPES._array
|
3447
|
+
type: TYPES._array,
|
3448
|
+
length: 1
|
2900
3449
|
},
|
2901
3450
|
|
2902
3451
|
__Array_of: {
|
@@ -2908,7 +3457,94 @@ const internalConstrs = {
|
|
2908
3457
|
}, global, name);
|
2909
3458
|
},
|
2910
3459
|
type: TYPES._array,
|
3460
|
+
notConstr: true,
|
3461
|
+
length: 0
|
3462
|
+
},
|
3463
|
+
|
3464
|
+
__Porffor_fastOr: {
|
3465
|
+
generate: (scope, decl) => {
|
3466
|
+
const out = [];
|
3467
|
+
|
3468
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3469
|
+
out.push(
|
3470
|
+
...generate(scope, decl.arguments[i]),
|
3471
|
+
Opcodes.i32_to_u,
|
3472
|
+
...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
|
3473
|
+
);
|
3474
|
+
}
|
3475
|
+
|
3476
|
+
return out;
|
3477
|
+
},
|
3478
|
+
type: TYPES.boolean,
|
2911
3479
|
notConstr: true
|
3480
|
+
},
|
3481
|
+
|
3482
|
+
__Porffor_fastAnd: {
|
3483
|
+
generate: (scope, decl) => {
|
3484
|
+
const out = [];
|
3485
|
+
|
3486
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3487
|
+
out.push(
|
3488
|
+
...generate(scope, decl.arguments[i]),
|
3489
|
+
Opcodes.i32_to_u,
|
3490
|
+
...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
|
3491
|
+
);
|
3492
|
+
}
|
3493
|
+
|
3494
|
+
return out;
|
3495
|
+
},
|
3496
|
+
type: TYPES.boolean,
|
3497
|
+
notConstr: true
|
3498
|
+
},
|
3499
|
+
|
3500
|
+
Boolean: {
|
3501
|
+
generate: (scope, decl) => {
|
3502
|
+
// todo: boolean object when used as constructor
|
3503
|
+
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
3504
|
+
return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
|
3505
|
+
},
|
3506
|
+
type: TYPES.boolean,
|
3507
|
+
length: 1
|
3508
|
+
},
|
3509
|
+
|
3510
|
+
__Math_max: {
|
3511
|
+
generate: (scope, decl) => {
|
3512
|
+
const out = [
|
3513
|
+
...number(-Infinity)
|
3514
|
+
];
|
3515
|
+
|
3516
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3517
|
+
out.push(
|
3518
|
+
...generate(scope, decl.arguments[i]),
|
3519
|
+
[ Opcodes.f64_max ]
|
3520
|
+
);
|
3521
|
+
}
|
3522
|
+
|
3523
|
+
return out;
|
3524
|
+
},
|
3525
|
+
type: TYPES.number,
|
3526
|
+
notConstr: true,
|
3527
|
+
length: 2
|
3528
|
+
},
|
3529
|
+
|
3530
|
+
__Math_min: {
|
3531
|
+
generate: (scope, decl) => {
|
3532
|
+
const out = [
|
3533
|
+
...number(Infinity)
|
3534
|
+
];
|
3535
|
+
|
3536
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3537
|
+
out.push(
|
3538
|
+
...generate(scope, decl.arguments[i]),
|
3539
|
+
[ Opcodes.f64_min ]
|
3540
|
+
);
|
3541
|
+
}
|
3542
|
+
|
3543
|
+
return out;
|
3544
|
+
},
|
3545
|
+
type: TYPES.number,
|
3546
|
+
notConstr: true,
|
3547
|
+
length: 2
|
2912
3548
|
}
|
2913
3549
|
};
|
2914
3550
|
|
@@ -2937,7 +3573,6 @@ export default program => {
|
|
2937
3573
|
funcs = [];
|
2938
3574
|
funcIndex = {};
|
2939
3575
|
depth = [];
|
2940
|
-
arrays = new Map();
|
2941
3576
|
pages = new Map();
|
2942
3577
|
data = [];
|
2943
3578
|
currentFuncIndex = importedFuncs.length;
|
@@ -2951,6 +3586,10 @@ export default program => {
|
|
2951
3586
|
|
2952
3587
|
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
2953
3588
|
|
3589
|
+
globalThis.pageSize = PageSize;
|
3590
|
+
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
3591
|
+
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3592
|
+
|
2954
3593
|
// set generic opcodes for current valtype
|
2955
3594
|
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
2956
3595
|
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
@@ -2959,10 +3598,10 @@ export default program => {
|
|
2959
3598
|
Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
|
2960
3599
|
Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
|
2961
3600
|
|
2962
|
-
Opcodes.i32_to = [ [
|
2963
|
-
Opcodes.i32_to_u = [ [
|
2964
|
-
Opcodes.i32_from = [ [
|
2965
|
-
Opcodes.i32_from_u = [ [
|
3601
|
+
Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
|
3602
|
+
Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
|
3603
|
+
Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
|
3604
|
+
Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
|
2966
3605
|
|
2967
3606
|
Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
|
2968
3607
|
Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
|
@@ -2975,10 +3614,6 @@ export default program => {
|
|
2975
3614
|
|
2976
3615
|
program.id = { name: 'main' };
|
2977
3616
|
|
2978
|
-
globalThis.pageSize = PageSize;
|
2979
|
-
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
2980
|
-
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
2981
|
-
|
2982
3617
|
const scope = {
|
2983
3618
|
locals: {},
|
2984
3619
|
localInd: 0
|
@@ -2989,7 +3624,7 @@ export default program => {
|
|
2989
3624
|
body: program.body
|
2990
3625
|
};
|
2991
3626
|
|
2992
|
-
if (
|
3627
|
+
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
2993
3628
|
|
2994
3629
|
generateFunc(scope, program);
|
2995
3630
|
|
@@ -3006,7 +3641,11 @@ export default program => {
|
|
3006
3641
|
}
|
3007
3642
|
|
3008
3643
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
3009
|
-
|
3644
|
+
if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
|
3645
|
+
main.wasm.splice(main.wasm.length - 1, 1);
|
3646
|
+
} else {
|
3647
|
+
main.returns = [];
|
3648
|
+
}
|
3010
3649
|
}
|
3011
3650
|
|
3012
3651
|
if (lastInst[0] === Opcodes.call) {
|