porffor 0.2.0-30ecc4a → 0.2.0-371da1e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -20
- package/README.md +121 -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} +63 -15
- package/compiler/builtins/annexb_string.js +72 -0
- package/compiler/builtins/annexb_string.ts +19 -0
- package/compiler/builtins/array.ts +145 -0
- package/compiler/builtins/base64.ts +151 -0
- package/compiler/builtins/crypto.ts +120 -0
- package/compiler/builtins/date.ts +9 -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 +468 -264
- package/compiler/{codeGen.js → codegen.js} +920 -372
- package/compiler/embedding.js +22 -22
- package/compiler/encoding.js +108 -10
- package/compiler/generated_builtins.js +722 -0
- package/compiler/index.js +36 -34
- package/compiler/log.js +6 -3
- package/compiler/opt.js +50 -36
- package/compiler/parse.js +35 -27
- package/compiler/precompile.js +123 -0
- package/compiler/prefs.js +26 -0
- package/compiler/prototype.js +13 -28
- package/compiler/types.js +37 -0
- package/compiler/wasmSpec.js +27 -8
- package/compiler/wrap.js +49 -44
- 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/compiler/builtins/base64.js +0 -92
- package/runner/info.js +0 -89
- package/runner/profile.js +0 -46
- package/runner/results.json +0 -1
- package/runner/transform.js +0 -15
- package/util/enum.js +0 -20
@@ -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,35 +25,37 @@ 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';
|
@@ -104,7 +108,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
104
108
|
return generateUnary(scope, decl);
|
105
109
|
|
106
110
|
case 'UpdateExpression':
|
107
|
-
return generateUpdate(scope, decl);
|
111
|
+
return generateUpdate(scope, decl, global, name, valueUnused);
|
108
112
|
|
109
113
|
case 'IfStatement':
|
110
114
|
return generateIf(scope, decl);
|
@@ -115,6 +119,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
115
119
|
case 'WhileStatement':
|
116
120
|
return generateWhile(scope, decl);
|
117
121
|
|
122
|
+
case 'DoWhileStatement':
|
123
|
+
return generateDoWhile(scope, decl);
|
124
|
+
|
118
125
|
case 'ForOfStatement':
|
119
126
|
return generateForOf(scope, decl);
|
120
127
|
|
@@ -124,6 +131,9 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
124
131
|
case 'ContinueStatement':
|
125
132
|
return generateContinue(scope, decl);
|
126
133
|
|
134
|
+
case 'LabeledStatement':
|
135
|
+
return generateLabel(scope, decl);
|
136
|
+
|
127
137
|
case 'EmptyStatement':
|
128
138
|
return generateEmpty(scope, decl);
|
129
139
|
|
@@ -137,7 +147,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
137
147
|
return generateTry(scope, decl);
|
138
148
|
|
139
149
|
case 'DebuggerStatement':
|
140
|
-
// todo:
|
150
|
+
// todo: hook into terminal debugger
|
141
151
|
return [];
|
142
152
|
|
143
153
|
case 'ArrayExpression':
|
@@ -151,16 +161,22 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
151
161
|
const funcsBefore = funcs.length;
|
152
162
|
generate(scope, decl.declaration);
|
153
163
|
|
154
|
-
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');
|
155
171
|
|
156
|
-
const newFunc = funcs[funcs.length - 1];
|
157
|
-
newFunc.export = true;
|
172
|
+
// const newFunc = funcs[funcs.length - 1];
|
173
|
+
// newFunc.export = true;
|
158
174
|
|
159
175
|
return [];
|
160
176
|
|
161
177
|
case 'TaggedTemplateExpression': {
|
162
178
|
const funcs = {
|
163
|
-
|
179
|
+
__Porffor_wasm: str => {
|
164
180
|
let out = [];
|
165
181
|
|
166
182
|
for (const line of str.split('\n')) {
|
@@ -168,8 +184,8 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
168
184
|
if (asm[0] === '') continue; // blank
|
169
185
|
|
170
186
|
if (asm[0] === 'local') {
|
171
|
-
const [ name,
|
172
|
-
scope.locals[name] = { idx:
|
187
|
+
const [ name, type ] = asm.slice(1);
|
188
|
+
scope.locals[name] = { idx: scope.localInd++, type: Valtype[type] };
|
173
189
|
continue;
|
174
190
|
}
|
175
191
|
|
@@ -179,7 +195,7 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
179
195
|
}
|
180
196
|
|
181
197
|
if (asm[0] === 'memory') {
|
182
|
-
allocPage('asm instrinsic');
|
198
|
+
allocPage(scope, 'asm instrinsic');
|
183
199
|
// todo: add to store/load offset insts
|
184
200
|
continue;
|
185
201
|
}
|
@@ -188,7 +204,11 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
188
204
|
if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
|
189
205
|
|
190
206
|
if (!Array.isArray(inst)) inst = [ inst ];
|
191
|
-
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
|
+
});
|
192
212
|
|
193
213
|
out.push([ ...inst, ...immediates ]);
|
194
214
|
}
|
@@ -196,35 +216,53 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
196
216
|
return out;
|
197
217
|
},
|
198
218
|
|
199
|
-
|
200
|
-
|
219
|
+
__Porffor_bs: str => [
|
220
|
+
...makeString(scope, str, global, name, true),
|
201
221
|
|
202
|
-
|
203
|
-
...number(
|
204
|
-
|
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),
|
205
229
|
|
206
|
-
|
207
|
-
...number(
|
208
|
-
|
209
|
-
]
|
210
|
-
|
211
|
-
}
|
230
|
+
...(name ? setType(scope, name, TYPES.string) : [
|
231
|
+
...number(TYPES.string, Valtype.i32),
|
232
|
+
...setLastType(scope)
|
233
|
+
])
|
234
|
+
],
|
235
|
+
};
|
212
236
|
|
213
|
-
const
|
237
|
+
const func = decl.tag.name;
|
214
238
|
// hack for inline asm
|
215
|
-
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;
|
216
243
|
|
217
|
-
|
218
|
-
|
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);
|
219
256
|
}
|
220
257
|
|
221
258
|
default:
|
222
|
-
|
223
|
-
|
259
|
+
// ignore typescript nodes
|
260
|
+
if (decl.type.startsWith('TS') ||
|
261
|
+
decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
|
224
262
|
return [];
|
225
263
|
}
|
226
264
|
|
227
|
-
return todo(`no generation for ${decl.type}!`);
|
265
|
+
return todo(scope, `no generation for ${decl.type}!`);
|
228
266
|
}
|
229
267
|
};
|
230
268
|
|
@@ -252,7 +290,7 @@ const lookupName = (scope, _name) => {
|
|
252
290
|
return [ undefined, undefined ];
|
253
291
|
};
|
254
292
|
|
255
|
-
const internalThrow = (scope, constructor, message, expectsValue =
|
293
|
+
const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
|
256
294
|
...generateThrow(scope, {
|
257
295
|
argument: {
|
258
296
|
type: 'NewExpression',
|
@@ -274,25 +312,33 @@ const generateIdent = (scope, decl) => {
|
|
274
312
|
const name = mapName(rawName);
|
275
313
|
let local = scope.locals[rawName];
|
276
314
|
|
277
|
-
if (builtinVars
|
315
|
+
if (Object.hasOwn(builtinVars, name)) {
|
278
316
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
279
|
-
|
317
|
+
|
318
|
+
let wasm = builtinVars[name];
|
319
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
|
320
|
+
return wasm.slice();
|
280
321
|
}
|
281
322
|
|
282
|
-
if (builtinFuncs
|
323
|
+
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
283
324
|
// todo: return an actual something
|
284
325
|
return number(1);
|
285
326
|
}
|
286
327
|
|
287
|
-
if (
|
328
|
+
if (isExistingProtoFunc(name)) {
|
329
|
+
// todo: return an actual something
|
330
|
+
return number(1);
|
331
|
+
}
|
332
|
+
|
333
|
+
if (local?.idx === undefined) {
|
288
334
|
// no local var with name
|
289
|
-
if (
|
290
|
-
if (funcIndex
|
335
|
+
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
336
|
+
if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
|
291
337
|
|
292
|
-
if (globals
|
338
|
+
if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
|
293
339
|
}
|
294
340
|
|
295
|
-
if (local === undefined && rawName.startsWith('__')) {
|
341
|
+
if (local?.idx === undefined && rawName.startsWith('__')) {
|
296
342
|
// return undefined if unknown key in already known var
|
297
343
|
let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
|
298
344
|
if (parent.includes('_')) parent = '__' + parent;
|
@@ -301,7 +347,7 @@ const generateIdent = (scope, decl) => {
|
|
301
347
|
if (!parentLookup[1]) return number(UNDEFINED);
|
302
348
|
}
|
303
349
|
|
304
|
-
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);
|
305
351
|
|
306
352
|
return [ [ Opcodes.local_get, local.idx ] ];
|
307
353
|
};
|
@@ -314,14 +360,18 @@ const generateReturn = (scope, decl) => {
|
|
314
360
|
// just bare "return"
|
315
361
|
return [
|
316
362
|
...number(UNDEFINED), // "undefined" if func returns
|
317
|
-
...
|
363
|
+
...(scope.returnType != null ? [] : [
|
364
|
+
...number(TYPES.undefined, Valtype.i32) // type undefined
|
365
|
+
]),
|
318
366
|
[ Opcodes.return ]
|
319
367
|
];
|
320
368
|
}
|
321
369
|
|
322
370
|
return [
|
323
371
|
...generate(scope, decl.argument),
|
324
|
-
...
|
372
|
+
...(scope.returnType != null ? [] : [
|
373
|
+
...getNodeType(scope, decl.argument)
|
374
|
+
]),
|
325
375
|
[ Opcodes.return ]
|
326
376
|
];
|
327
377
|
};
|
@@ -335,7 +385,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
335
385
|
return idx;
|
336
386
|
};
|
337
387
|
|
338
|
-
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);
|
339
390
|
|
340
391
|
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
341
392
|
const checks = {
|
@@ -344,7 +395,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
344
395
|
'??': nullish
|
345
396
|
};
|
346
397
|
|
347
|
-
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);
|
348
399
|
|
349
400
|
// generic structure for {a} OP {b}
|
350
401
|
// -->
|
@@ -352,8 +403,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
352
403
|
|
353
404
|
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
354
405
|
// (like if we are in an if condition - very common)
|
355
|
-
const leftIsInt =
|
356
|
-
const rightIsInt =
|
406
|
+
const leftIsInt = isFloatToIntOp(left[left.length - 1]);
|
407
|
+
const rightIsInt = isFloatToIntOp(right[right.length - 1]);
|
357
408
|
|
358
409
|
const canInt = leftIsInt && rightIsInt;
|
359
410
|
|
@@ -370,12 +421,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
370
421
|
...right,
|
371
422
|
// note type
|
372
423
|
...rightType,
|
373
|
-
setLastType(scope),
|
424
|
+
...setLastType(scope),
|
374
425
|
[ Opcodes.else ],
|
375
426
|
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
376
427
|
// note type
|
377
428
|
...leftType,
|
378
|
-
setLastType(scope),
|
429
|
+
...setLastType(scope),
|
379
430
|
[ Opcodes.end ],
|
380
431
|
Opcodes.i32_from
|
381
432
|
];
|
@@ -389,17 +440,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
389
440
|
...right,
|
390
441
|
// note type
|
391
442
|
...rightType,
|
392
|
-
setLastType(scope),
|
443
|
+
...setLastType(scope),
|
393
444
|
[ Opcodes.else ],
|
394
445
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
395
446
|
// note type
|
396
447
|
...leftType,
|
397
|
-
setLastType(scope),
|
448
|
+
...setLastType(scope),
|
398
449
|
[ Opcodes.end ]
|
399
450
|
];
|
400
451
|
};
|
401
452
|
|
402
|
-
const concatStrings = (scope, left, right, global, name, assign) => {
|
453
|
+
const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
|
403
454
|
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
404
455
|
// todo: convert left and right to strings if not
|
405
456
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -409,11 +460,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
409
460
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
410
461
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
411
462
|
|
412
|
-
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
413
|
-
if (aotWFA) addVarMeta(name, { wellFormed: undefined });
|
414
|
-
|
415
463
|
if (assign) {
|
416
|
-
const pointer = arrays
|
464
|
+
const pointer = scope.arrays?.get(name ?? '$undeclared');
|
417
465
|
|
418
466
|
return [
|
419
467
|
// setup right
|
@@ -438,11 +486,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
438
486
|
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
|
439
487
|
|
440
488
|
// copy right
|
441
|
-
// dst = out pointer + length size + current length *
|
489
|
+
// dst = out pointer + length size + current length * sizeof valtype
|
442
490
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
443
491
|
|
444
492
|
[ Opcodes.local_get, leftLength ],
|
445
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
493
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
446
494
|
[ Opcodes.i32_mul ],
|
447
495
|
[ Opcodes.i32_add ],
|
448
496
|
|
@@ -451,9 +499,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
451
499
|
...number(ValtypeSize.i32, Valtype.i32),
|
452
500
|
[ Opcodes.i32_add ],
|
453
501
|
|
454
|
-
// size = right length *
|
502
|
+
// size = right length * sizeof valtype
|
455
503
|
[ Opcodes.local_get, rightLength ],
|
456
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
504
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
457
505
|
[ Opcodes.i32_mul ],
|
458
506
|
|
459
507
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -511,11 +559,11 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
511
559
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
512
560
|
|
513
561
|
// copy right
|
514
|
-
// dst = out pointer + length size + left length *
|
562
|
+
// dst = out pointer + length size + left length * sizeof valtype
|
515
563
|
...number(pointer + ValtypeSize.i32, Valtype.i32),
|
516
564
|
|
517
565
|
[ Opcodes.local_get, leftLength ],
|
518
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
566
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
519
567
|
[ Opcodes.i32_mul ],
|
520
568
|
[ Opcodes.i32_add ],
|
521
569
|
|
@@ -524,9 +572,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
524
572
|
...number(ValtypeSize.i32, Valtype.i32),
|
525
573
|
[ Opcodes.i32_add ],
|
526
574
|
|
527
|
-
// size = right length *
|
575
|
+
// size = right length * sizeof valtype
|
528
576
|
[ Opcodes.local_get, rightLength ],
|
529
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
577
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
530
578
|
[ Opcodes.i32_mul ],
|
531
579
|
|
532
580
|
[ ...Opcodes.memory_copy, 0x00, 0x00 ],
|
@@ -536,7 +584,7 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
536
584
|
];
|
537
585
|
};
|
538
586
|
|
539
|
-
const compareStrings = (scope, left, right) => {
|
587
|
+
const compareStrings = (scope, left, right, bytestrings = false) => {
|
540
588
|
// todo: this should be rewritten into a func
|
541
589
|
// todo: convert left and right to strings if not
|
542
590
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -545,7 +593,6 @@ const compareStrings = (scope, left, right) => {
|
|
545
593
|
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
546
594
|
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
547
595
|
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
548
|
-
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
549
596
|
|
550
597
|
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
551
598
|
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
@@ -573,7 +620,6 @@ const compareStrings = (scope, left, right) => {
|
|
573
620
|
|
574
621
|
[ Opcodes.local_get, rightPointer ],
|
575
622
|
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
576
|
-
[ Opcodes.local_tee, rightLength ],
|
577
623
|
|
578
624
|
// fast path: check leftLength != rightLength
|
579
625
|
[ Opcodes.i32_ne ],
|
@@ -588,11 +634,13 @@ const compareStrings = (scope, left, right) => {
|
|
588
634
|
...number(0, Valtype.i32),
|
589
635
|
[ Opcodes.local_set, index ],
|
590
636
|
|
591
|
-
// setup index end as length * sizeof
|
637
|
+
// setup index end as length * sizeof valtype (1 for bytestring, 2 for string)
|
592
638
|
// we do this instead of having to do mul/div each iter for perf™
|
593
639
|
[ Opcodes.local_get, leftLength ],
|
594
|
-
...
|
595
|
-
|
640
|
+
...(bytestrings ? [] : [
|
641
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
642
|
+
[ Opcodes.i32_mul ],
|
643
|
+
]),
|
596
644
|
[ Opcodes.local_set, indexEnd ],
|
597
645
|
|
598
646
|
// iterate over each char and check if eq
|
@@ -602,13 +650,17 @@ const compareStrings = (scope, left, right) => {
|
|
602
650
|
[ Opcodes.local_get, index ],
|
603
651
|
[ Opcodes.local_get, leftPointer ],
|
604
652
|
[ Opcodes.i32_add ],
|
605
|
-
|
653
|
+
bytestrings ?
|
654
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
655
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
606
656
|
|
607
657
|
// fetch right
|
608
658
|
[ Opcodes.local_get, index ],
|
609
659
|
[ Opcodes.local_get, rightPointer ],
|
610
660
|
[ Opcodes.i32_add ],
|
611
|
-
|
661
|
+
bytestrings ?
|
662
|
+
[ Opcodes.i32_load8_u, 0, ValtypeSize.i32 ] :
|
663
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ValtypeSize.i32 ],
|
612
664
|
|
613
665
|
// not equal, "return" false
|
614
666
|
[ Opcodes.i32_ne ],
|
@@ -617,13 +669,13 @@ const compareStrings = (scope, left, right) => {
|
|
617
669
|
[ Opcodes.br, 2 ],
|
618
670
|
[ Opcodes.end ],
|
619
671
|
|
620
|
-
// index += sizeof
|
672
|
+
// index += sizeof valtype (1 for bytestring, 2 for string)
|
621
673
|
[ Opcodes.local_get, index ],
|
622
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
674
|
+
...number(bytestrings ? ValtypeSize.i8 : ValtypeSize.i16, Valtype.i32),
|
623
675
|
[ Opcodes.i32_add ],
|
624
676
|
[ Opcodes.local_tee, index ],
|
625
677
|
|
626
|
-
// if index != index end (length * sizeof
|
678
|
+
// if index != index end (length * sizeof valtype), loop
|
627
679
|
[ Opcodes.local_get, indexEnd ],
|
628
680
|
[ Opcodes.i32_ne ],
|
629
681
|
[ Opcodes.br_if, 0 ],
|
@@ -644,16 +696,18 @@ const compareStrings = (scope, left, right) => {
|
|
644
696
|
};
|
645
697
|
|
646
698
|
const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
647
|
-
if (
|
699
|
+
if (isFloatToIntOp(wasm[wasm.length - 1])) return [
|
648
700
|
...wasm,
|
649
701
|
...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
|
650
702
|
];
|
703
|
+
// if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
|
651
704
|
|
652
|
-
const
|
705
|
+
const useTmp = knownType(scope, type) == null;
|
706
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
653
707
|
|
654
708
|
const def = [
|
655
709
|
// if value != 0
|
656
|
-
[ Opcodes.local_get, tmp ],
|
710
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
657
711
|
|
658
712
|
// ...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
659
713
|
...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
|
@@ -665,7 +719,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
665
719
|
|
666
720
|
return [
|
667
721
|
...wasm,
|
668
|
-
[ Opcodes.local_set, tmp ],
|
722
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
669
723
|
|
670
724
|
...typeSwitch(scope, type, {
|
671
725
|
// [TYPES.number]: def,
|
@@ -674,7 +728,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
674
728
|
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
675
729
|
],
|
676
730
|
[TYPES.string]: [
|
677
|
-
[ Opcodes.local_get, tmp ],
|
731
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
678
732
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
679
733
|
|
680
734
|
// get length
|
@@ -686,7 +740,7 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
686
740
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
687
741
|
],
|
688
742
|
[TYPES._bytestring]: [ // duplicate of string
|
689
|
-
|
743
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
690
744
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
691
745
|
|
692
746
|
// get length
|
@@ -700,10 +754,12 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
700
754
|
};
|
701
755
|
|
702
756
|
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
703
|
-
const
|
757
|
+
const useTmp = knownType(scope, type) == null;
|
758
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
759
|
+
|
704
760
|
return [
|
705
761
|
...wasm,
|
706
|
-
[ Opcodes.local_set, tmp ],
|
762
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
707
763
|
|
708
764
|
...typeSwitch(scope, type, {
|
709
765
|
[TYPES._array]: [
|
@@ -711,7 +767,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
711
767
|
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
712
768
|
],
|
713
769
|
[TYPES.string]: [
|
714
|
-
[ Opcodes.local_get, tmp ],
|
770
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
715
771
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
716
772
|
|
717
773
|
// get length
|
@@ -722,7 +778,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
722
778
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
723
779
|
],
|
724
780
|
[TYPES._bytestring]: [ // duplicate of string
|
725
|
-
[ Opcodes.local_get, tmp ],
|
781
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
726
782
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
727
783
|
|
728
784
|
// get length
|
@@ -734,7 +790,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
734
790
|
],
|
735
791
|
default: [
|
736
792
|
// if value == 0
|
737
|
-
[ Opcodes.local_get, tmp ],
|
793
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
738
794
|
|
739
795
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
740
796
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -744,10 +800,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
744
800
|
};
|
745
801
|
|
746
802
|
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
747
|
-
const
|
803
|
+
const useTmp = knownType(scope, type) == null;
|
804
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
805
|
+
|
748
806
|
return [
|
749
807
|
...wasm,
|
750
|
-
[ Opcodes.local_set, tmp ],
|
808
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
751
809
|
|
752
810
|
...typeSwitch(scope, type, {
|
753
811
|
[TYPES.undefined]: [
|
@@ -756,7 +814,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
756
814
|
],
|
757
815
|
[TYPES.object]: [
|
758
816
|
// object, null if == 0
|
759
|
-
[ Opcodes.local_get, tmp ],
|
817
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
760
818
|
|
761
819
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
762
820
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -785,11 +843,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
785
843
|
return performLogicOp(scope, op, left, right, leftType, rightType);
|
786
844
|
}
|
787
845
|
|
846
|
+
const knownLeft = knownType(scope, leftType);
|
847
|
+
const knownRight = knownType(scope, rightType);
|
848
|
+
|
788
849
|
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
789
850
|
const strictOp = op === '===' || op === '!==';
|
790
851
|
|
791
852
|
const startOut = [], endOut = [];
|
792
|
-
const
|
853
|
+
const finalize = out => startOut.concat(out, endOut);
|
793
854
|
|
794
855
|
// if strict (in)equal check types match
|
795
856
|
if (strictOp) {
|
@@ -834,31 +895,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
834
895
|
// todo: if equality op and an operand is undefined, return false
|
835
896
|
// todo: niche null hell with 0
|
836
897
|
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
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
|
+
}
|
862
951
|
|
863
952
|
let ops = operatorOpcode[valtype][op];
|
864
953
|
|
@@ -868,33 +957,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
868
957
|
includeBuiltin(scope, builtinName);
|
869
958
|
const idx = funcIndex[builtinName];
|
870
959
|
|
871
|
-
return
|
960
|
+
return finalize([
|
872
961
|
...left,
|
873
962
|
...right,
|
874
963
|
[ Opcodes.call, idx ]
|
875
964
|
]);
|
876
965
|
}
|
877
966
|
|
878
|
-
if (!ops) return todo(`operator ${op} not implemented yet
|
967
|
+
if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
|
879
968
|
|
880
969
|
if (!Array.isArray(ops)) ops = [ ops ];
|
881
970
|
ops = [ ops ];
|
882
971
|
|
883
972
|
let tmpLeft, tmpRight;
|
884
973
|
// if equal op, check if strings for compareStrings
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
// todo: intelligent partial skip later
|
890
|
-
// if neither known are string, stop this madness
|
891
|
-
if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
|
892
|
-
return;
|
893
|
-
}
|
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
|
894
977
|
|
978
|
+
if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
|
895
979
|
tmpLeft = localTmp(scope, '__tmpop_left');
|
896
980
|
tmpRight = localTmp(scope, '__tmpop_right');
|
897
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)
|
898
1023
|
ops.unshift(...stringOnly([
|
899
1024
|
// if left is string
|
900
1025
|
...leftType,
|
@@ -906,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
906
1031
|
...number(TYPES.string, Valtype.i32),
|
907
1032
|
[ Opcodes.i32_eq ],
|
908
1033
|
|
909
|
-
// if
|
910
|
-
[ Opcodes.
|
1034
|
+
// if both are true
|
1035
|
+
[ Opcodes.i32_and ],
|
911
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 ],
|
912
1041
|
|
913
|
-
//
|
914
|
-
// if left is not string
|
1042
|
+
// if left is bytestring
|
915
1043
|
...leftType,
|
916
|
-
...number(TYPES.
|
917
|
-
[ Opcodes.
|
1044
|
+
...number(TYPES._bytestring, Valtype.i32),
|
1045
|
+
[ Opcodes.i32_eq ],
|
918
1046
|
|
919
|
-
// if right is
|
1047
|
+
// if right is bytestring
|
920
1048
|
...rightType,
|
921
|
-
...number(TYPES.
|
922
|
-
[ Opcodes.
|
1049
|
+
...number(TYPES._bytestring, Valtype.i32),
|
1050
|
+
[ Opcodes.i32_eq ],
|
923
1051
|
|
924
|
-
// if
|
925
|
-
[ Opcodes.
|
1052
|
+
// if both are true
|
1053
|
+
[ Opcodes.i32_and ],
|
926
1054
|
[ Opcodes.if, Blocktype.void ],
|
927
|
-
...
|
928
|
-
[ Opcodes.br, 1 ],
|
929
|
-
[ Opcodes.end ],
|
930
|
-
|
931
|
-
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
932
|
-
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1055
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
|
933
1056
|
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
934
1057
|
[ Opcodes.br, 1 ],
|
935
1058
|
[ Opcodes.end ],
|
@@ -941,9 +1064,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
941
1064
|
// endOut.push(stringOnly([ Opcodes.end ]));
|
942
1065
|
endOut.unshift(stringOnly([ Opcodes.end ]));
|
943
1066
|
// }
|
944
|
-
}
|
1067
|
+
}
|
945
1068
|
|
946
|
-
return
|
1069
|
+
return finalize([
|
947
1070
|
...left,
|
948
1071
|
...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
|
949
1072
|
...right,
|
@@ -960,7 +1083,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
960
1083
|
return out;
|
961
1084
|
};
|
962
1085
|
|
963
|
-
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 = [] }) => {
|
964
1102
|
const existing = funcs.find(x => x.name === name);
|
965
1103
|
if (existing) return existing;
|
966
1104
|
|
@@ -972,18 +1110,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
972
1110
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
973
1111
|
}
|
974
1112
|
|
975
|
-
|
976
|
-
const
|
977
|
-
|
978
|
-
|
979
|
-
locals,
|
980
|
-
returns,
|
981
|
-
localInd: allLocals.length,
|
982
|
-
};
|
983
|
-
|
984
|
-
wasm = wasm(scope, { TYPES, typeSwitch, makeArray });
|
1113
|
+
for (const x of _data) {
|
1114
|
+
const copy = { ...x };
|
1115
|
+
copy.offset += pages.size * pageSize;
|
1116
|
+
data.push(copy);
|
985
1117
|
}
|
986
1118
|
|
1119
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
|
1120
|
+
|
987
1121
|
let baseGlobalIdx, i = 0;
|
988
1122
|
for (const type of globalTypes) {
|
989
1123
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -1006,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
1006
1140
|
params,
|
1007
1141
|
locals,
|
1008
1142
|
returns,
|
1009
|
-
returnType:
|
1143
|
+
returnType: returnType ?? TYPES.number,
|
1010
1144
|
wasm,
|
1011
1145
|
internal: true,
|
1012
1146
|
index: currentFuncIndex++
|
@@ -1029,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
|
|
1029
1163
|
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
1030
1164
|
};
|
1031
1165
|
|
1166
|
+
// potential future ideas for nan boxing (unused):
|
1032
1167
|
// T = JS type, V = value/pointer
|
1033
1168
|
// 0bTTT
|
1034
1169
|
// qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
|
@@ -1052,49 +1187,29 @@ const generateLogicExp = (scope, decl) => {
|
|
1052
1187
|
// 4: internal type
|
1053
1188
|
// 5: pointer
|
1054
1189
|
|
1055
|
-
const
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
object: 0x04,
|
1061
|
-
function: 0x05,
|
1062
|
-
symbol: 0x06,
|
1063
|
-
bigint: 0x07,
|
1064
|
-
|
1065
|
-
// these are not "typeof" types but tracked internally
|
1066
|
-
_array: 0x10,
|
1067
|
-
_regexp: 0x11,
|
1068
|
-
_bytestring: 0x12
|
1069
|
-
};
|
1070
|
-
|
1071
|
-
const TYPE_NAMES = {
|
1072
|
-
[TYPES.number]: 'Number',
|
1073
|
-
[TYPES.boolean]: 'Boolean',
|
1074
|
-
[TYPES.string]: 'String',
|
1075
|
-
[TYPES.undefined]: 'undefined',
|
1076
|
-
[TYPES.object]: 'Object',
|
1077
|
-
[TYPES.function]: 'Function',
|
1078
|
-
[TYPES.symbol]: 'Symbol',
|
1079
|
-
[TYPES.bigint]: 'BigInt',
|
1080
|
-
|
1081
|
-
[TYPES._array]: 'Array',
|
1082
|
-
[TYPES._regexp]: 'RegExp',
|
1083
|
-
[TYPES._bytestring]: 'ByteString'
|
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)];
|
1193
|
+
|
1194
|
+
return false;
|
1084
1195
|
};
|
1085
1196
|
|
1086
1197
|
const getType = (scope, _name) => {
|
1087
1198
|
const name = mapName(_name);
|
1088
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);
|
1089
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);
|
1090
1206
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1091
1207
|
|
1092
1208
|
let type = TYPES.undefined;
|
1093
|
-
if (builtinVars[name]) type =
|
1209
|
+
if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
|
1094
1210
|
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
|
1095
1211
|
|
1096
|
-
if (name
|
1097
|
-
name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
|
1212
|
+
if (isExistingProtoFunc(name)) type = TYPES.function;
|
1098
1213
|
|
1099
1214
|
return number(type, Valtype.i32);
|
1100
1215
|
};
|
@@ -1117,15 +1232,16 @@ const setType = (scope, _name, type) => {
|
|
1117
1232
|
];
|
1118
1233
|
|
1119
1234
|
// throw new Error('could not find var');
|
1235
|
+
return [];
|
1120
1236
|
};
|
1121
1237
|
|
1122
1238
|
const getLastType = scope => {
|
1123
1239
|
scope.gotLastType = true;
|
1124
|
-
return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
|
1240
|
+
return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1125
1241
|
};
|
1126
1242
|
|
1127
1243
|
const setLastType = scope => {
|
1128
|
-
return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
|
1244
|
+
return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1129
1245
|
};
|
1130
1246
|
|
1131
1247
|
const getNodeType = (scope, node) => {
|
@@ -1150,13 +1266,25 @@ const getNodeType = (scope, node) => {
|
|
1150
1266
|
const name = node.callee.name;
|
1151
1267
|
if (!name) {
|
1152
1268
|
// iife
|
1153
|
-
if (scope.locals['#last_type']) return
|
1269
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1154
1270
|
|
1155
1271
|
// presume
|
1156
1272
|
// todo: warn here?
|
1157
1273
|
return TYPES.number;
|
1158
1274
|
}
|
1159
1275
|
|
1276
|
+
if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
|
1277
|
+
if (builtinFuncs[name + '$constructor'].typedReturns) {
|
1278
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1279
|
+
|
1280
|
+
// presume
|
1281
|
+
// todo: warn here?
|
1282
|
+
return TYPES.number;
|
1283
|
+
}
|
1284
|
+
|
1285
|
+
return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
|
1286
|
+
}
|
1287
|
+
|
1160
1288
|
const func = funcs.find(x => x.name === name);
|
1161
1289
|
|
1162
1290
|
if (func) {
|
@@ -1164,7 +1292,7 @@ const getNodeType = (scope, node) => {
|
|
1164
1292
|
if (func.returnType) return func.returnType;
|
1165
1293
|
}
|
1166
1294
|
|
1167
|
-
if (builtinFuncs[name]) return
|
1295
|
+
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
1168
1296
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1169
1297
|
|
1170
1298
|
// check if this is a prototype function
|
@@ -1175,11 +1303,16 @@ const getNodeType = (scope, node) => {
|
|
1175
1303
|
const spl = name.slice(2).split('_');
|
1176
1304
|
|
1177
1305
|
const func = spl[spl.length - 1];
|
1178
|
-
const protoFuncs = Object.
|
1306
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
|
1179
1307
|
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1180
1308
|
}
|
1181
1309
|
|
1182
|
-
if (
|
1310
|
+
if (name.startsWith('__Porffor_wasm_')) {
|
1311
|
+
// todo: return undefined for non-returning ops
|
1312
|
+
return TYPES.number;
|
1313
|
+
}
|
1314
|
+
|
1315
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1183
1316
|
|
1184
1317
|
// presume
|
1185
1318
|
// todo: warn here?
|
@@ -1227,6 +1360,15 @@ const getNodeType = (scope, node) => {
|
|
1227
1360
|
|
1228
1361
|
if (node.type === 'BinaryExpression') {
|
1229
1362
|
if (['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(node.operator)) return TYPES.boolean;
|
1363
|
+
if (node.operator !== '+') return TYPES.number;
|
1364
|
+
|
1365
|
+
const knownLeft = knownType(scope, getNodeType(scope, node.left));
|
1366
|
+
const knownRight = knownType(scope, getNodeType(scope, node.right));
|
1367
|
+
|
1368
|
+
// todo: this should be dynamic but for now only static
|
1369
|
+
if (knownLeft === TYPES.string || knownRight === TYPES.string) return TYPES.string;
|
1370
|
+
if (knownLeft === TYPES._bytestring || knownRight === TYPES._bytestring) return TYPES._bytestring;
|
1371
|
+
|
1230
1372
|
return TYPES.number;
|
1231
1373
|
|
1232
1374
|
// todo: string concat types
|
@@ -1251,7 +1393,7 @@ const getNodeType = (scope, node) => {
|
|
1251
1393
|
if (node.operator === '!') return TYPES.boolean;
|
1252
1394
|
if (node.operator === 'void') return TYPES.undefined;
|
1253
1395
|
if (node.operator === 'delete') return TYPES.boolean;
|
1254
|
-
if (node.operator === 'typeof') return
|
1396
|
+
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
|
1255
1397
|
|
1256
1398
|
return TYPES.number;
|
1257
1399
|
}
|
@@ -1260,13 +1402,23 @@ const getNodeType = (scope, node) => {
|
|
1260
1402
|
// hack: if something.length, number type
|
1261
1403
|
if (node.property.name === 'length') return TYPES.number;
|
1262
1404
|
|
1263
|
-
|
1405
|
+
// ts hack
|
1406
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
|
1407
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES._bytestring) return TYPES._bytestring;
|
1408
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
|
1409
|
+
|
1410
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1264
1411
|
|
1265
1412
|
// presume
|
1266
1413
|
return TYPES.number;
|
1267
1414
|
}
|
1268
1415
|
|
1269
|
-
if (
|
1416
|
+
if (node.type === 'TaggedTemplateExpression') {
|
1417
|
+
// hack
|
1418
|
+
if (node.tag.name.startsWith('__Porffor_')) return TYPES.number;
|
1419
|
+
}
|
1420
|
+
|
1421
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1270
1422
|
|
1271
1423
|
// presume
|
1272
1424
|
// todo: warn here?
|
@@ -1279,28 +1431,11 @@ const getNodeType = (scope, node) => {
|
|
1279
1431
|
return ret;
|
1280
1432
|
};
|
1281
1433
|
|
1282
|
-
const toString = (scope, wasm, type) => {
|
1283
|
-
const tmp = localTmp(scope, '#tostring_tmp');
|
1284
|
-
return [
|
1285
|
-
...wasm,
|
1286
|
-
[ Opcodes.local_set, tmp ],
|
1287
|
-
|
1288
|
-
...typeSwitch(scope, type, {
|
1289
|
-
[TYPES.string]: [
|
1290
|
-
[ Opcodes.local_get, tmp ]
|
1291
|
-
],
|
1292
|
-
[TYPES.undefined]: [
|
1293
|
-
// [ Opcodes.]
|
1294
|
-
]
|
1295
|
-
})
|
1296
|
-
]
|
1297
|
-
};
|
1298
|
-
|
1299
1434
|
const generateLiteral = (scope, decl, global, name) => {
|
1300
1435
|
if (decl.value === null) return number(NULL);
|
1301
1436
|
|
1437
|
+
// hack: just return 1 for regex literals
|
1302
1438
|
if (decl.regex) {
|
1303
|
-
scope.regex[name] = decl.regex;
|
1304
1439
|
return number(1);
|
1305
1440
|
}
|
1306
1441
|
|
@@ -1316,7 +1451,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1316
1451
|
return makeString(scope, decl.value, global, name);
|
1317
1452
|
|
1318
1453
|
default:
|
1319
|
-
return todo(`cannot generate literal of type ${typeof decl.value}
|
1454
|
+
return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
|
1320
1455
|
}
|
1321
1456
|
};
|
1322
1457
|
|
@@ -1325,6 +1460,8 @@ const countLeftover = wasm => {
|
|
1325
1460
|
|
1326
1461
|
for (let i = 0; i < wasm.length; i++) {
|
1327
1462
|
const inst = wasm[i];
|
1463
|
+
if (inst[0] == null) continue;
|
1464
|
+
|
1328
1465
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
1329
1466
|
if (inst[0] === Opcodes.if) count--;
|
1330
1467
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1333,7 +1470,7 @@ const countLeftover = wasm => {
|
|
1333
1470
|
if (inst[0] === Opcodes.end) depth--;
|
1334
1471
|
|
1335
1472
|
if (depth === 0)
|
1336
|
-
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1473
|
+
if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1337
1474
|
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
1338
1475
|
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1339
1476
|
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
@@ -1341,10 +1478,17 @@ const countLeftover = wasm => {
|
|
1341
1478
|
else if (inst[0] === Opcodes.return) count = 0;
|
1342
1479
|
else if (inst[0] === Opcodes.call) {
|
1343
1480
|
let func = funcs.find(x => x.index === inst[1]);
|
1344
|
-
if (
|
1345
|
-
count
|
1346
|
-
} else
|
1347
|
-
|
1481
|
+
if (inst[1] === -1) {
|
1482
|
+
// todo: count for calling self
|
1483
|
+
} else if (!func && inst[1] < importedFuncs.length) {
|
1484
|
+
count -= importedFuncs[inst[1]].params;
|
1485
|
+
count += importedFuncs[inst[1]].returns;
|
1486
|
+
} else {
|
1487
|
+
if (func) {
|
1488
|
+
count -= func.params.length;
|
1489
|
+
} else count--;
|
1490
|
+
if (func) count += func.returns.length;
|
1491
|
+
}
|
1348
1492
|
} else count--;
|
1349
1493
|
|
1350
1494
|
// console.log(count, decompile([ inst ]).slice(0, -1));
|
@@ -1436,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1436
1580
|
name = func.name;
|
1437
1581
|
}
|
1438
1582
|
|
1439
|
-
if (name === 'eval' && decl.arguments[0]
|
1583
|
+
if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
|
1440
1584
|
// literal eval hack
|
1441
|
-
const code = decl.arguments[0]
|
1442
|
-
|
1585
|
+
const code = decl.arguments[0]?.value ?? '';
|
1586
|
+
|
1587
|
+
let parsed;
|
1588
|
+
try {
|
1589
|
+
parsed = parse(code, []);
|
1590
|
+
} catch (e) {
|
1591
|
+
if (e.name === 'SyntaxError') {
|
1592
|
+
// throw syntax errors of evals at runtime instead
|
1593
|
+
return internalThrow(scope, 'SyntaxError', e.message, true);
|
1594
|
+
}
|
1595
|
+
|
1596
|
+
throw e;
|
1597
|
+
}
|
1443
1598
|
|
1444
1599
|
const out = generate(scope, {
|
1445
1600
|
type: 'BlockStatement',
|
@@ -1453,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1453
1608
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1454
1609
|
out.push(
|
1455
1610
|
...getNodeType(scope, finalStatement),
|
1456
|
-
setLastType(scope)
|
1611
|
+
...setLastType(scope)
|
1457
1612
|
);
|
1458
1613
|
} else if (countLeftover(out) === 0) {
|
1459
1614
|
out.push(...number(UNDEFINED));
|
1460
1615
|
out.push(
|
1461
1616
|
...number(TYPES.undefined, Valtype.i32),
|
1462
|
-
setLastType(scope)
|
1617
|
+
...setLastType(scope)
|
1463
1618
|
);
|
1464
1619
|
}
|
1465
1620
|
|
@@ -1481,13 +1636,16 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1481
1636
|
|
1482
1637
|
target = { ...decl.callee };
|
1483
1638
|
target.name = spl.slice(0, -1).join('_');
|
1639
|
+
|
1640
|
+
// failed to lookup name, abort
|
1641
|
+
if (!lookupName(scope, target.name)[0]) protoName = null;
|
1484
1642
|
}
|
1485
1643
|
|
1486
1644
|
// literal.func()
|
1487
1645
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1488
1646
|
// megahack for /regex/.func()
|
1489
|
-
|
1490
|
-
|
1647
|
+
const funcName = decl.callee.property.name;
|
1648
|
+
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1491
1649
|
const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
|
1492
1650
|
|
1493
1651
|
funcIndex[func.name] = func.index;
|
@@ -1503,7 +1661,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1503
1661
|
Opcodes.i32_from_u,
|
1504
1662
|
|
1505
1663
|
...number(TYPES.boolean, Valtype.i32),
|
1506
|
-
setLastType(scope)
|
1664
|
+
...setLastType(scope)
|
1507
1665
|
];
|
1508
1666
|
}
|
1509
1667
|
|
@@ -1528,13 +1686,30 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1528
1686
|
// }
|
1529
1687
|
|
1530
1688
|
if (protoName) {
|
1689
|
+
const protoBC = {};
|
1690
|
+
|
1691
|
+
const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
|
1692
|
+
|
1693
|
+
if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
|
1694
|
+
for (const x of builtinProtoCands) {
|
1695
|
+
const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
|
1696
|
+
if (type == null) continue;
|
1697
|
+
|
1698
|
+
protoBC[type] = generateCall(scope, {
|
1699
|
+
callee: {
|
1700
|
+
name: x
|
1701
|
+
},
|
1702
|
+
arguments: [ target, ...decl.arguments ],
|
1703
|
+
_protoInternalCall: true
|
1704
|
+
});
|
1705
|
+
}
|
1706
|
+
}
|
1707
|
+
|
1531
1708
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1532
|
-
|
1533
|
-
if (f) acc[x] = f;
|
1709
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1534
1710
|
return acc;
|
1535
1711
|
}, {});
|
1536
1712
|
|
1537
|
-
// no prototype function candidates, ignore
|
1538
1713
|
if (Object.keys(protoCands).length > 0) {
|
1539
1714
|
// use local for cached i32 length as commonly used
|
1540
1715
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
@@ -1552,7 +1727,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1552
1727
|
|
1553
1728
|
let allOptUnused = true;
|
1554
1729
|
let lengthI32CacheUsed = false;
|
1555
|
-
const protoBC = {};
|
1556
1730
|
for (const x in protoCands) {
|
1557
1731
|
const protoFunc = protoCands[x];
|
1558
1732
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
@@ -1560,7 +1734,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1560
1734
|
...RTArrayUtil.getLength(getPointer),
|
1561
1735
|
|
1562
1736
|
...number(TYPES.number, Valtype.i32),
|
1563
|
-
setLastType(scope)
|
1737
|
+
...setLastType(scope)
|
1564
1738
|
];
|
1565
1739
|
continue;
|
1566
1740
|
}
|
@@ -1597,7 +1771,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1597
1771
|
...protoOut,
|
1598
1772
|
|
1599
1773
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1600
|
-
setLastType(scope),
|
1774
|
+
...setLastType(scope),
|
1601
1775
|
[ Opcodes.end ]
|
1602
1776
|
];
|
1603
1777
|
}
|
@@ -1623,10 +1797,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1623
1797
|
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1624
1798
|
];
|
1625
1799
|
}
|
1800
|
+
|
1801
|
+
if (Object.keys(protoBC).length > 0) {
|
1802
|
+
return typeSwitch(scope, getNodeType(scope, target), {
|
1803
|
+
...protoBC,
|
1804
|
+
|
1805
|
+
// TODO: error better
|
1806
|
+
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1807
|
+
}, valtypeBinary);
|
1808
|
+
}
|
1626
1809
|
}
|
1627
1810
|
|
1628
1811
|
// TODO: only allows callee as literal
|
1629
|
-
if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
|
1812
|
+
if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
|
1630
1813
|
|
1631
1814
|
let idx = funcIndex[name] ?? importedFuncs[name];
|
1632
1815
|
if (idx === undefined && builtinFuncs[name]) {
|
@@ -1659,16 +1842,65 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1659
1842
|
idx = -1;
|
1660
1843
|
}
|
1661
1844
|
|
1845
|
+
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1846
|
+
const wasmOps = {
|
1847
|
+
// pointer, align, offset
|
1848
|
+
i32_load: { imms: 2, args: 1, returns: 1 },
|
1849
|
+
// pointer, value, align, offset
|
1850
|
+
i32_store: { imms: 2, args: 2, returns: 0 },
|
1851
|
+
// pointer, align, offset
|
1852
|
+
i32_load8_u: { imms: 2, args: 1, returns: 1 },
|
1853
|
+
// pointer, value, align, offset
|
1854
|
+
i32_store8: { imms: 2, args: 2, returns: 0 },
|
1855
|
+
// pointer, align, offset
|
1856
|
+
i32_load16_u: { imms: 2, args: 1, returns: 1 },
|
1857
|
+
// pointer, value, align, offset
|
1858
|
+
i32_store16: { imms: 2, args: 2, returns: 0 },
|
1859
|
+
|
1860
|
+
// pointer, align, offset
|
1861
|
+
f64_load: { imms: 2, args: 1, returns: 1 },
|
1862
|
+
// pointer, value, align, offset
|
1863
|
+
f64_store: { imms: 2, args: 2, returns: 0 },
|
1864
|
+
|
1865
|
+
// value
|
1866
|
+
i32_const: { imms: 1, args: 0, returns: 1 },
|
1867
|
+
|
1868
|
+
// a, b
|
1869
|
+
i32_or: { imms: 0, args: 2, returns: 1 },
|
1870
|
+
};
|
1871
|
+
|
1872
|
+
const opName = name.slice('__Porffor_wasm_'.length);
|
1873
|
+
|
1874
|
+
if (wasmOps[opName]) {
|
1875
|
+
const op = wasmOps[opName];
|
1876
|
+
|
1877
|
+
const argOut = [];
|
1878
|
+
for (let i = 0; i < op.args; i++) argOut.push(
|
1879
|
+
...generate(scope, decl.arguments[i]),
|
1880
|
+
Opcodes.i32_to
|
1881
|
+
);
|
1882
|
+
|
1883
|
+
// literals only
|
1884
|
+
const imms = decl.arguments.slice(op.args).map(x => x.value);
|
1885
|
+
|
1886
|
+
return [
|
1887
|
+
...argOut,
|
1888
|
+
[ Opcodes[opName], ...imms ],
|
1889
|
+
...(new Array(op.returns).fill(Opcodes.i32_from))
|
1890
|
+
];
|
1891
|
+
}
|
1892
|
+
}
|
1893
|
+
|
1662
1894
|
if (idx === undefined) {
|
1663
|
-
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function
|
1664
|
-
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined
|
1895
|
+
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1896
|
+
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
1665
1897
|
}
|
1666
1898
|
|
1667
1899
|
const func = funcs.find(x => x.index === idx);
|
1668
1900
|
|
1669
1901
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1670
1902
|
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1671
|
-
const
|
1903
|
+
const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
|
1672
1904
|
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1673
1905
|
|
1674
1906
|
let args = decl.arguments;
|
@@ -1685,14 +1917,24 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1685
1917
|
if (func && func.throws) scope.throws = true;
|
1686
1918
|
|
1687
1919
|
let out = [];
|
1688
|
-
for (
|
1920
|
+
for (let i = 0; i < args.length; i++) {
|
1921
|
+
const arg = args[i];
|
1689
1922
|
out = out.concat(generate(scope, arg));
|
1923
|
+
|
1924
|
+
if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1925
|
+
out.push(Opcodes.i32_to);
|
1926
|
+
}
|
1927
|
+
|
1928
|
+
if (importedFuncs[name] && name.startsWith('profile')) {
|
1929
|
+
out.push(Opcodes.i32_to);
|
1930
|
+
}
|
1931
|
+
|
1690
1932
|
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1691
1933
|
}
|
1692
1934
|
|
1693
1935
|
out.push([ Opcodes.call, idx ]);
|
1694
1936
|
|
1695
|
-
if (!
|
1937
|
+
if (!typedReturns) {
|
1696
1938
|
// let type;
|
1697
1939
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1698
1940
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1702,7 +1944,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1702
1944
|
// ...number(type, Valtype.i32),
|
1703
1945
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1704
1946
|
// );
|
1705
|
-
} else out.push(setLastType(scope));
|
1947
|
+
} else out.push(...setLastType(scope));
|
1948
|
+
|
1949
|
+
if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1950
|
+
out.push(Opcodes.i32_from);
|
1951
|
+
}
|
1706
1952
|
|
1707
1953
|
return out;
|
1708
1954
|
};
|
@@ -1710,8 +1956,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1710
1956
|
const generateNew = (scope, decl, _global, _name) => {
|
1711
1957
|
// hack: basically treat this as a normal call for builtins for now
|
1712
1958
|
const name = mapName(decl.callee.name);
|
1959
|
+
|
1713
1960
|
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1714
|
-
|
1961
|
+
|
1962
|
+
if (builtinFuncs[name + '$constructor']) {
|
1963
|
+
// custom ...$constructor override builtin func
|
1964
|
+
return generateCall(scope, {
|
1965
|
+
...decl,
|
1966
|
+
callee: {
|
1967
|
+
type: 'Identifier',
|
1968
|
+
name: name + '$constructor'
|
1969
|
+
}
|
1970
|
+
}, _global, _name);
|
1971
|
+
}
|
1972
|
+
|
1973
|
+
if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
|
1715
1974
|
|
1716
1975
|
return generateCall(scope, decl, _global, _name);
|
1717
1976
|
};
|
@@ -1828,14 +2087,14 @@ const brTable = (input, bc, returns) => {
|
|
1828
2087
|
};
|
1829
2088
|
|
1830
2089
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1831
|
-
if (!
|
2090
|
+
if (!Prefs.bytestring) delete bc[TYPES._bytestring];
|
1832
2091
|
|
1833
2092
|
const known = knownType(scope, type);
|
1834
2093
|
if (known != null) {
|
1835
2094
|
return bc[known] ?? bc.default;
|
1836
2095
|
}
|
1837
2096
|
|
1838
|
-
if (
|
2097
|
+
if (Prefs.typeswitchUseBrtable)
|
1839
2098
|
return brTable(type, bc, returns);
|
1840
2099
|
|
1841
2100
|
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
@@ -1845,8 +2104,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1845
2104
|
[ Opcodes.block, returns ]
|
1846
2105
|
];
|
1847
2106
|
|
1848
|
-
// todo: use br_table?
|
1849
|
-
|
1850
2107
|
for (const x in bc) {
|
1851
2108
|
if (x === 'default') continue;
|
1852
2109
|
|
@@ -1870,7 +2127,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1870
2127
|
return out;
|
1871
2128
|
};
|
1872
2129
|
|
1873
|
-
const allocVar = (scope, name, global = false) => {
|
2130
|
+
const allocVar = (scope, name, global = false, type = true) => {
|
1874
2131
|
const target = global ? globals : scope.locals;
|
1875
2132
|
|
1876
2133
|
// already declared
|
@@ -1884,8 +2141,10 @@ const allocVar = (scope, name, global = false) => {
|
|
1884
2141
|
let idx = global ? globalInd++ : scope.localInd++;
|
1885
2142
|
target[name] = { idx, type: valtypeBinary };
|
1886
2143
|
|
1887
|
-
|
1888
|
-
|
2144
|
+
if (type) {
|
2145
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
2146
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
2147
|
+
}
|
1889
2148
|
|
1890
2149
|
return idx;
|
1891
2150
|
};
|
@@ -1900,11 +2159,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
|
1900
2159
|
};
|
1901
2160
|
|
1902
2161
|
const typeAnnoToPorfType = x => {
|
1903
|
-
if (
|
1904
|
-
if (TYPES[
|
2162
|
+
if (!x) return null;
|
2163
|
+
if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
|
2164
|
+
if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
|
1905
2165
|
|
1906
2166
|
switch (x) {
|
1907
2167
|
case 'i32':
|
2168
|
+
case 'i64':
|
2169
|
+
case 'f64':
|
1908
2170
|
return TYPES.number;
|
1909
2171
|
}
|
1910
2172
|
|
@@ -1915,7 +2177,7 @@ const extractTypeAnnotation = decl => {
|
|
1915
2177
|
let a = decl;
|
1916
2178
|
while (a.typeAnnotation) a = a.typeAnnotation;
|
1917
2179
|
|
1918
|
-
let type, elementType;
|
2180
|
+
let type = null, elementType = null;
|
1919
2181
|
if (a.typeName) {
|
1920
2182
|
type = a.typeName.name;
|
1921
2183
|
} else if (a.type.endsWith('Keyword')) {
|
@@ -1928,6 +2190,8 @@ const extractTypeAnnotation = decl => {
|
|
1928
2190
|
const typeName = type;
|
1929
2191
|
type = typeAnnoToPorfType(type);
|
1930
2192
|
|
2193
|
+
if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
|
2194
|
+
|
1931
2195
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1932
2196
|
|
1933
2197
|
return { type, typeName, elementType };
|
@@ -1944,6 +2208,8 @@ const generateVar = (scope, decl) => {
|
|
1944
2208
|
for (const x of decl.declarations) {
|
1945
2209
|
const name = mapName(x.id.name);
|
1946
2210
|
|
2211
|
+
if (!name) return todo(scope, 'destructuring is not supported yet');
|
2212
|
+
|
1947
2213
|
if (x.init && isFuncType(x.init.type)) {
|
1948
2214
|
// hack for let a = function () { ... }
|
1949
2215
|
x.init.id = { name };
|
@@ -1959,9 +2225,10 @@ const generateVar = (scope, decl) => {
|
|
1959
2225
|
continue; // always ignore
|
1960
2226
|
}
|
1961
2227
|
|
1962
|
-
|
2228
|
+
const typed = typedInput && x.id.typeAnnotation;
|
2229
|
+
let idx = allocVar(scope, name, global, !typed);
|
1963
2230
|
|
1964
|
-
if (
|
2231
|
+
if (typed) {
|
1965
2232
|
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1966
2233
|
}
|
1967
2234
|
|
@@ -1979,7 +2246,8 @@ const generateVar = (scope, decl) => {
|
|
1979
2246
|
return out;
|
1980
2247
|
};
|
1981
2248
|
|
1982
|
-
|
2249
|
+
// todo: optimize this func for valueUnused
|
2250
|
+
const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
1983
2251
|
const { type, name } = decl.left;
|
1984
2252
|
|
1985
2253
|
if (type === 'ObjectPattern') {
|
@@ -1997,9 +2265,9 @@ const generateAssign = (scope, decl) => {
|
|
1997
2265
|
// hack: .length setter
|
1998
2266
|
if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
|
1999
2267
|
const name = decl.left.object.name;
|
2000
|
-
const pointer = arrays
|
2268
|
+
const pointer = scope.arrays?.get(name);
|
2001
2269
|
|
2002
|
-
const aotPointer = pointer != null;
|
2270
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2003
2271
|
|
2004
2272
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
2005
2273
|
|
@@ -2024,9 +2292,9 @@ const generateAssign = (scope, decl) => {
|
|
2024
2292
|
// arr[i]
|
2025
2293
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
2026
2294
|
const name = decl.left.object.name;
|
2027
|
-
const pointer = arrays
|
2295
|
+
const pointer = scope.arrays?.get(name);
|
2028
2296
|
|
2029
|
-
const aotPointer = pointer != null;
|
2297
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2030
2298
|
|
2031
2299
|
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
2032
2300
|
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
@@ -2082,6 +2350,8 @@ const generateAssign = (scope, decl) => {
|
|
2082
2350
|
];
|
2083
2351
|
}
|
2084
2352
|
|
2353
|
+
if (!name) return todo(scope, 'destructuring is not supported yet', true);
|
2354
|
+
|
2085
2355
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2086
2356
|
|
2087
2357
|
if (local === undefined) {
|
@@ -2128,9 +2398,7 @@ const generateAssign = (scope, decl) => {
|
|
2128
2398
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
2129
2399
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
2130
2400
|
|
2131
|
-
getLastType(scope)
|
2132
|
-
// hack: type is idx+1
|
2133
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2401
|
+
...setType(scope, name, getLastType(scope))
|
2134
2402
|
];
|
2135
2403
|
}
|
2136
2404
|
|
@@ -2141,9 +2409,7 @@ const generateAssign = (scope, decl) => {
|
|
2141
2409
|
|
2142
2410
|
// todo: string concat types
|
2143
2411
|
|
2144
|
-
|
2145
|
-
...number(TYPES.number, Valtype.i32),
|
2146
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2412
|
+
...setType(scope, name, TYPES.number)
|
2147
2413
|
];
|
2148
2414
|
};
|
2149
2415
|
|
@@ -2189,7 +2455,7 @@ const generateUnary = (scope, decl) => {
|
|
2189
2455
|
return out;
|
2190
2456
|
}
|
2191
2457
|
|
2192
|
-
case 'delete':
|
2458
|
+
case 'delete': {
|
2193
2459
|
let toReturn = true, toGenerate = true;
|
2194
2460
|
|
2195
2461
|
if (decl.argument.type === 'Identifier') {
|
@@ -2211,9 +2477,26 @@ const generateUnary = (scope, decl) => {
|
|
2211
2477
|
|
2212
2478
|
out.push(...number(toReturn ? 1 : 0));
|
2213
2479
|
return out;
|
2480
|
+
}
|
2481
|
+
|
2482
|
+
case 'typeof': {
|
2483
|
+
let overrideType, toGenerate = true;
|
2484
|
+
|
2485
|
+
if (decl.argument.type === 'Identifier') {
|
2486
|
+
const out = generateIdent(scope, decl.argument);
|
2214
2487
|
|
2215
|
-
|
2216
|
-
|
2488
|
+
// if ReferenceError (undeclared var), ignore and return undefined
|
2489
|
+
if (out[1]) {
|
2490
|
+
// does not exist (2 ops from throw)
|
2491
|
+
overrideType = number(TYPES.undefined, Valtype.i32);
|
2492
|
+
toGenerate = false;
|
2493
|
+
}
|
2494
|
+
}
|
2495
|
+
|
2496
|
+
const out = toGenerate ? generate(scope, decl.argument) : [];
|
2497
|
+
disposeLeftover(out);
|
2498
|
+
|
2499
|
+
out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
|
2217
2500
|
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
2218
2501
|
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
2219
2502
|
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
@@ -2224,27 +2507,30 @@ const generateUnary = (scope, decl) => {
|
|
2224
2507
|
|
2225
2508
|
// object and internal types
|
2226
2509
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2227
|
-
});
|
2510
|
+
}));
|
2511
|
+
|
2512
|
+
return out;
|
2513
|
+
}
|
2228
2514
|
|
2229
2515
|
default:
|
2230
|
-
return todo(`unary operator ${decl.operator} not implemented yet
|
2516
|
+
return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
|
2231
2517
|
}
|
2232
2518
|
};
|
2233
2519
|
|
2234
|
-
const generateUpdate = (scope, decl) => {
|
2520
|
+
const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
|
2235
2521
|
const { name } = decl.argument;
|
2236
2522
|
|
2237
2523
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2238
2524
|
|
2239
2525
|
if (local === undefined) {
|
2240
|
-
return todo(`update expression with undefined variable
|
2526
|
+
return todo(scope, `update expression with undefined variable`, true);
|
2241
2527
|
}
|
2242
2528
|
|
2243
2529
|
const idx = local.idx;
|
2244
2530
|
const out = [];
|
2245
2531
|
|
2246
2532
|
out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2247
|
-
if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2533
|
+
if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2248
2534
|
|
2249
2535
|
switch (decl.operator) {
|
2250
2536
|
case '++':
|
@@ -2257,7 +2543,7 @@ const generateUpdate = (scope, decl) => {
|
|
2257
2543
|
}
|
2258
2544
|
|
2259
2545
|
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2260
|
-
if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2546
|
+
if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2261
2547
|
|
2262
2548
|
return out;
|
2263
2549
|
};
|
@@ -2297,7 +2583,7 @@ const generateConditional = (scope, decl) => {
|
|
2297
2583
|
// note type
|
2298
2584
|
out.push(
|
2299
2585
|
...getNodeType(scope, decl.consequent),
|
2300
|
-
setLastType(scope)
|
2586
|
+
...setLastType(scope)
|
2301
2587
|
);
|
2302
2588
|
|
2303
2589
|
out.push([ Opcodes.else ]);
|
@@ -2306,7 +2592,7 @@ const generateConditional = (scope, decl) => {
|
|
2306
2592
|
// note type
|
2307
2593
|
out.push(
|
2308
2594
|
...getNodeType(scope, decl.alternate),
|
2309
|
-
setLastType(scope)
|
2595
|
+
...setLastType(scope)
|
2310
2596
|
);
|
2311
2597
|
|
2312
2598
|
out.push([ Opcodes.end ]);
|
@@ -2320,15 +2606,17 @@ const generateFor = (scope, decl) => {
|
|
2320
2606
|
const out = [];
|
2321
2607
|
|
2322
2608
|
if (decl.init) {
|
2323
|
-
out.push(...generate(scope, decl.init));
|
2609
|
+
out.push(...generate(scope, decl.init, false, undefined, true));
|
2324
2610
|
disposeLeftover(out);
|
2325
2611
|
}
|
2326
2612
|
|
2327
2613
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2328
2614
|
depth.push('for');
|
2329
2615
|
|
2330
|
-
out.push(...generate(scope, decl.test));
|
2331
|
-
|
2616
|
+
if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2617
|
+
else out.push(...number(1, Valtype.i32));
|
2618
|
+
|
2619
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
2332
2620
|
depth.push('if');
|
2333
2621
|
|
2334
2622
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2336,8 +2624,7 @@ const generateFor = (scope, decl) => {
|
|
2336
2624
|
out.push(...generate(scope, decl.body));
|
2337
2625
|
out.push([ Opcodes.end ]);
|
2338
2626
|
|
2339
|
-
out.push(...generate(scope, decl.update));
|
2340
|
-
depth.pop();
|
2627
|
+
if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
|
2341
2628
|
|
2342
2629
|
out.push([ Opcodes.br, 1 ]);
|
2343
2630
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2365,6 +2652,36 @@ const generateWhile = (scope, decl) => {
|
|
2365
2652
|
return out;
|
2366
2653
|
};
|
2367
2654
|
|
2655
|
+
const generateDoWhile = (scope, decl) => {
|
2656
|
+
const out = [];
|
2657
|
+
|
2658
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
2659
|
+
depth.push('dowhile');
|
2660
|
+
|
2661
|
+
// block for break (includes all)
|
2662
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2663
|
+
depth.push('block');
|
2664
|
+
|
2665
|
+
// block for continue
|
2666
|
+
// includes body but not test+loop so we can exit body at anytime
|
2667
|
+
// and still test+loop after
|
2668
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2669
|
+
depth.push('block');
|
2670
|
+
|
2671
|
+
out.push(...generate(scope, decl.body));
|
2672
|
+
|
2673
|
+
out.push([ Opcodes.end ]);
|
2674
|
+
depth.pop();
|
2675
|
+
|
2676
|
+
out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2677
|
+
out.push([ Opcodes.br_if, 1 ]);
|
2678
|
+
|
2679
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
2680
|
+
depth.pop(); depth.pop();
|
2681
|
+
|
2682
|
+
return out;
|
2683
|
+
};
|
2684
|
+
|
2368
2685
|
const generateForOf = (scope, decl) => {
|
2369
2686
|
const out = [];
|
2370
2687
|
|
@@ -2394,8 +2711,17 @@ const generateForOf = (scope, decl) => {
|
|
2394
2711
|
// setup local for left
|
2395
2712
|
generate(scope, decl.left);
|
2396
2713
|
|
2397
|
-
|
2714
|
+
let leftName = decl.left.declarations?.[0]?.id?.name;
|
2715
|
+
if (!leftName && decl.left.name) {
|
2716
|
+
leftName = decl.left.name;
|
2717
|
+
|
2718
|
+
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2719
|
+
}
|
2720
|
+
|
2721
|
+
// if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
|
2722
|
+
|
2398
2723
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2724
|
+
if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
|
2399
2725
|
|
2400
2726
|
depth.push('block');
|
2401
2727
|
depth.push('block');
|
@@ -2404,6 +2730,7 @@ const generateForOf = (scope, decl) => {
|
|
2404
2730
|
// hack: this is naughty and will break things!
|
2405
2731
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2406
2732
|
if (pages.hasAnyString) {
|
2733
|
+
// todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
|
2407
2734
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2408
2735
|
rawElements: new Array(1)
|
2409
2736
|
}, isGlobal, leftName, true, 'i16');
|
@@ -2495,6 +2822,56 @@ const generateForOf = (scope, decl) => {
|
|
2495
2822
|
[ Opcodes.end ],
|
2496
2823
|
[ Opcodes.end ]
|
2497
2824
|
],
|
2825
|
+
[TYPES._bytestring]: [
|
2826
|
+
...setType(scope, leftName, TYPES._bytestring),
|
2827
|
+
|
2828
|
+
[ Opcodes.loop, Blocktype.void ],
|
2829
|
+
|
2830
|
+
// setup new/out array
|
2831
|
+
...newOut,
|
2832
|
+
[ Opcodes.drop ],
|
2833
|
+
|
2834
|
+
...number(0, Valtype.i32), // base 0 for store after
|
2835
|
+
|
2836
|
+
// load current string ind {arg}
|
2837
|
+
[ Opcodes.local_get, pointer ],
|
2838
|
+
[ Opcodes.local_get, counter ],
|
2839
|
+
[ Opcodes.i32_add ],
|
2840
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
2841
|
+
|
2842
|
+
// store to new string ind 0
|
2843
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2844
|
+
|
2845
|
+
// return new string (page)
|
2846
|
+
...number(newPointer),
|
2847
|
+
|
2848
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2849
|
+
|
2850
|
+
[ Opcodes.block, Blocktype.void ],
|
2851
|
+
[ Opcodes.block, Blocktype.void ],
|
2852
|
+
...generate(scope, decl.body),
|
2853
|
+
[ Opcodes.end ],
|
2854
|
+
|
2855
|
+
// increment iter pointer
|
2856
|
+
// [ Opcodes.local_get, pointer ],
|
2857
|
+
// ...number(1, Valtype.i32),
|
2858
|
+
// [ Opcodes.i32_add ],
|
2859
|
+
// [ Opcodes.local_set, pointer ],
|
2860
|
+
|
2861
|
+
// increment counter by 1
|
2862
|
+
[ Opcodes.local_get, counter ],
|
2863
|
+
...number(1, Valtype.i32),
|
2864
|
+
[ Opcodes.i32_add ],
|
2865
|
+
[ Opcodes.local_tee, counter ],
|
2866
|
+
|
2867
|
+
// loop if counter != length
|
2868
|
+
[ Opcodes.local_get, length ],
|
2869
|
+
[ Opcodes.i32_ne ],
|
2870
|
+
[ Opcodes.br_if, 1 ],
|
2871
|
+
|
2872
|
+
[ Opcodes.end ],
|
2873
|
+
[ Opcodes.end ]
|
2874
|
+
],
|
2498
2875
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2499
2876
|
}, Blocktype.void));
|
2500
2877
|
|
@@ -2505,28 +2882,65 @@ const generateForOf = (scope, decl) => {
|
|
2505
2882
|
return out;
|
2506
2883
|
};
|
2507
2884
|
|
2885
|
+
// find the nearest loop in depth map by type
|
2508
2886
|
const getNearestLoop = () => {
|
2509
2887
|
for (let i = depth.length - 1; i >= 0; i--) {
|
2510
|
-
if (
|
2888
|
+
if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
|
2511
2889
|
}
|
2512
2890
|
|
2513
2891
|
return -1;
|
2514
2892
|
};
|
2515
2893
|
|
2516
2894
|
const generateBreak = (scope, decl) => {
|
2517
|
-
const
|
2895
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2896
|
+
const type = depth[target];
|
2897
|
+
|
2898
|
+
// different loop types have different branch offsets
|
2899
|
+
// as they have different wasm block/loop/if structures
|
2900
|
+
// we need to use the right offset by type to branch to the one we want
|
2901
|
+
// for a break: exit the loop without executing anything else inside it
|
2902
|
+
const offset = ({
|
2903
|
+
for: 2, // loop > if (wanted branch) > block (we are here)
|
2904
|
+
while: 2, // loop > if (wanted branch) (we are here)
|
2905
|
+
dowhile: 2, // loop > block (wanted branch) > block (we are here)
|
2906
|
+
forof: 2, // loop > block (wanted branch) > block (we are here)
|
2907
|
+
if: 1 // break inside if, branch 0 to skip the rest of the if
|
2908
|
+
})[type];
|
2909
|
+
|
2518
2910
|
return [
|
2519
|
-
[ Opcodes.br, ...signedLEB128(
|
2911
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2520
2912
|
];
|
2521
2913
|
};
|
2522
2914
|
|
2523
2915
|
const generateContinue = (scope, decl) => {
|
2524
|
-
const
|
2916
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2917
|
+
const type = depth[target];
|
2918
|
+
|
2919
|
+
// different loop types have different branch offsets
|
2920
|
+
// as they have different wasm block/loop/if structures
|
2921
|
+
// we need to use the right offset by type to branch to the one we want
|
2922
|
+
// for a continue: do test for the loop, and then loop depending on that success
|
2923
|
+
const offset = ({
|
2924
|
+
for: 3, // loop (wanted branch) > if > block (we are here)
|
2925
|
+
while: 1, // loop (wanted branch) > if (we are here)
|
2926
|
+
dowhile: 3, // loop > block > block (wanted branch) (we are here)
|
2927
|
+
forof: 3 // loop > block > block (wanted branch) (we are here)
|
2928
|
+
})[type];
|
2929
|
+
|
2525
2930
|
return [
|
2526
|
-
[ Opcodes.br, ...signedLEB128(
|
2931
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2527
2932
|
];
|
2528
2933
|
};
|
2529
2934
|
|
2935
|
+
const generateLabel = (scope, decl) => {
|
2936
|
+
scope.labels ??= new Map();
|
2937
|
+
|
2938
|
+
const name = decl.label.name;
|
2939
|
+
scope.labels.set(name, depth.length);
|
2940
|
+
|
2941
|
+
return generate(scope, decl.body);
|
2942
|
+
};
|
2943
|
+
|
2530
2944
|
const generateThrow = (scope, decl) => {
|
2531
2945
|
scope.throws = true;
|
2532
2946
|
|
@@ -2535,7 +2949,7 @@ const generateThrow = (scope, decl) => {
|
|
2535
2949
|
// hack: throw new X("...") -> throw "..."
|
2536
2950
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2537
2951
|
constructor = decl.argument.callee.name;
|
2538
|
-
message = decl.argument.arguments[0]
|
2952
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2539
2953
|
}
|
2540
2954
|
|
2541
2955
|
if (tags.length === 0) tags.push({
|
@@ -2547,6 +2961,9 @@ const generateThrow = (scope, decl) => {
|
|
2547
2961
|
let exceptId = exceptions.push({ constructor, message }) - 1;
|
2548
2962
|
let tagIdx = tags[0].idx;
|
2549
2963
|
|
2964
|
+
scope.exceptions ??= [];
|
2965
|
+
scope.exceptions.push(exceptId);
|
2966
|
+
|
2550
2967
|
// todo: write a description of how this works lol
|
2551
2968
|
|
2552
2969
|
return [
|
@@ -2556,7 +2973,7 @@ const generateThrow = (scope, decl) => {
|
|
2556
2973
|
};
|
2557
2974
|
|
2558
2975
|
const generateTry = (scope, decl) => {
|
2559
|
-
if (decl.finalizer) return todo('try finally not implemented yet');
|
2976
|
+
if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
|
2560
2977
|
|
2561
2978
|
const out = [];
|
2562
2979
|
|
@@ -2587,11 +3004,11 @@ const generateAssignPat = (scope, decl) => {
|
|
2587
3004
|
// TODO
|
2588
3005
|
// if identifier declared, use that
|
2589
3006
|
// else, use default (right)
|
2590
|
-
return todo('assignment pattern (optional arg)');
|
3007
|
+
return todo(scope, 'assignment pattern (optional arg)');
|
2591
3008
|
};
|
2592
3009
|
|
2593
3010
|
let pages = new Map();
|
2594
|
-
const allocPage = (reason, type) => {
|
3011
|
+
const allocPage = (scope, reason, type) => {
|
2595
3012
|
if (pages.has(reason)) return pages.get(reason).ind;
|
2596
3013
|
|
2597
3014
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
@@ -2602,16 +3019,20 @@ const allocPage = (reason, type) => {
|
|
2602
3019
|
const ind = pages.size;
|
2603
3020
|
pages.set(reason, { ind, type });
|
2604
3021
|
|
2605
|
-
|
3022
|
+
scope.pages ??= new Map();
|
3023
|
+
scope.pages.set(reason, { ind, type });
|
3024
|
+
|
3025
|
+
if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2606
3026
|
|
2607
3027
|
return ind;
|
2608
3028
|
};
|
2609
3029
|
|
3030
|
+
// todo: add scope.pages
|
2610
3031
|
const freePage = reason => {
|
2611
3032
|
const { ind } = pages.get(reason);
|
2612
3033
|
pages.delete(reason);
|
2613
3034
|
|
2614
|
-
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
3035
|
+
if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2615
3036
|
|
2616
3037
|
return ind;
|
2617
3038
|
};
|
@@ -2637,15 +3058,14 @@ const StoreOps = {
|
|
2637
3058
|
|
2638
3059
|
let data = [];
|
2639
3060
|
|
2640
|
-
const compileBytes = (val, itemType
|
3061
|
+
const compileBytes = (val, itemType) => {
|
2641
3062
|
// todo: this is a mess and needs confirming / ????
|
2642
3063
|
switch (itemType) {
|
2643
3064
|
case 'i8': return [ val % 256 ];
|
2644
|
-
case 'i16': return [ val % 256,
|
2645
|
-
|
2646
|
-
case 'i32':
|
2647
|
-
|
2648
|
-
return enforceFourBytes(signedLEB128(val));
|
3065
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3066
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3067
|
+
case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
|
3068
|
+
// todo: i64
|
2649
3069
|
|
2650
3070
|
case 'f64': return ieee754_binary64(val);
|
2651
3071
|
}
|
@@ -2663,16 +3083,20 @@ const getAllocType = itemType => {
|
|
2663
3083
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2664
3084
|
const out = [];
|
2665
3085
|
|
3086
|
+
scope.arrays ??= new Map();
|
3087
|
+
|
2666
3088
|
let firstAssign = false;
|
2667
|
-
if (!arrays.has(name) || name === '$undeclared') {
|
3089
|
+
if (!scope.arrays.has(name) || name === '$undeclared') {
|
2668
3090
|
firstAssign = true;
|
2669
3091
|
|
2670
3092
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2671
3093
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2672
|
-
|
3094
|
+
|
3095
|
+
if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${scope.name} | ${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
3096
|
+
else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2673
3097
|
}
|
2674
3098
|
|
2675
|
-
const pointer = arrays.get(name);
|
3099
|
+
const pointer = scope.arrays.get(name);
|
2676
3100
|
|
2677
3101
|
const useRawElements = !!decl.rawElements;
|
2678
3102
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
@@ -2680,19 +3104,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2680
3104
|
const valtype = itemTypeToValtype[itemType];
|
2681
3105
|
const length = elements.length;
|
2682
3106
|
|
2683
|
-
if (firstAssign && useRawElements) {
|
2684
|
-
|
3107
|
+
if (firstAssign && useRawElements && !Prefs.noData) {
|
3108
|
+
// if length is 0 memory/data will just be 0000... anyway
|
3109
|
+
if (length !== 0) {
|
3110
|
+
let bytes = compileBytes(length, 'i32');
|
2685
3111
|
|
2686
|
-
|
2687
|
-
|
3112
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
3113
|
+
if (elements[i] == null) continue;
|
2688
3114
|
|
2689
|
-
|
2690
|
-
|
3115
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
3116
|
+
}
|
2691
3117
|
|
2692
|
-
|
2693
|
-
|
2694
|
-
|
2695
|
-
|
3118
|
+
const ind = data.push({
|
3119
|
+
offset: pointer,
|
3120
|
+
bytes
|
3121
|
+
}) - 1;
|
3122
|
+
|
3123
|
+
scope.data ??= [];
|
3124
|
+
scope.data.push(ind);
|
3125
|
+
}
|
2696
3126
|
|
2697
3127
|
// local value as pointer
|
2698
3128
|
out.push(...number(pointer));
|
@@ -2726,7 +3156,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2726
3156
|
};
|
2727
3157
|
|
2728
3158
|
const byteStringable = str => {
|
2729
|
-
if (!
|
3159
|
+
if (!Prefs.bytestring) return false;
|
2730
3160
|
|
2731
3161
|
for (let i = 0; i < str.length; i++) {
|
2732
3162
|
if (str.charCodeAt(i) > 0xFF) return false;
|
@@ -2735,9 +3165,9 @@ const byteStringable = str => {
|
|
2735
3165
|
return true;
|
2736
3166
|
};
|
2737
3167
|
|
2738
|
-
const makeString = (scope, str, global = false, name = '$undeclared') => {
|
3168
|
+
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2739
3169
|
const rawElements = new Array(str.length);
|
2740
|
-
let byteStringable =
|
3170
|
+
let byteStringable = Prefs.bytestring;
|
2741
3171
|
for (let i = 0; i < str.length; i++) {
|
2742
3172
|
const c = str.charCodeAt(i);
|
2743
3173
|
rawElements[i] = c;
|
@@ -2745,25 +3175,36 @@ const makeString = (scope, str, global = false, name = '$undeclared') => {
|
|
2745
3175
|
if (byteStringable && c > 0xFF) byteStringable = false;
|
2746
3176
|
}
|
2747
3177
|
|
3178
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
3179
|
+
|
2748
3180
|
return makeArray(scope, {
|
2749
3181
|
rawElements
|
2750
3182
|
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2751
3183
|
};
|
2752
3184
|
|
2753
|
-
let arrays = new Map();
|
2754
3185
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
2755
3186
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
2756
3187
|
};
|
2757
3188
|
|
2758
3189
|
export const generateMember = (scope, decl, _global, _name) => {
|
2759
3190
|
const name = decl.object.name;
|
2760
|
-
const pointer = arrays
|
3191
|
+
const pointer = scope.arrays?.get(name);
|
2761
3192
|
|
2762
|
-
const aotPointer = pointer != null;
|
3193
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2763
3194
|
|
2764
3195
|
// hack: .length
|
2765
3196
|
if (decl.property.name === 'length') {
|
2766
|
-
|
3197
|
+
const func = funcs.find(x => x.name === name);
|
3198
|
+
if (func) {
|
3199
|
+
const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
|
3200
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
3201
|
+
return number(typedParams ? func.params.length / 2 : func.params.length);
|
3202
|
+
}
|
3203
|
+
|
3204
|
+
if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
|
3205
|
+
if (importedFuncs[name]) return number(importedFuncs[name].params);
|
3206
|
+
if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
|
3207
|
+
|
2767
3208
|
return [
|
2768
3209
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2769
3210
|
...generate(scope, decl.object),
|
@@ -2807,7 +3248,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2807
3248
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2808
3249
|
|
2809
3250
|
...number(TYPES.number, Valtype.i32),
|
2810
|
-
setLastType(scope)
|
3251
|
+
...setLastType(scope)
|
2811
3252
|
],
|
2812
3253
|
|
2813
3254
|
[TYPES.string]: [
|
@@ -2839,7 +3280,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2839
3280
|
...number(newPointer),
|
2840
3281
|
|
2841
3282
|
...number(TYPES.string, Valtype.i32),
|
2842
|
-
setLastType(scope)
|
3283
|
+
...setLastType(scope)
|
2843
3284
|
],
|
2844
3285
|
[TYPES._bytestring]: [
|
2845
3286
|
// setup new/out array
|
@@ -2858,19 +3299,19 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2858
3299
|
]),
|
2859
3300
|
|
2860
3301
|
// load current string ind {arg}
|
2861
|
-
[ Opcodes.i32_load8_u,
|
3302
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2862
3303
|
|
2863
3304
|
// store to new string ind 0
|
2864
|
-
[ Opcodes.i32_store8,
|
3305
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2865
3306
|
|
2866
3307
|
// return new string (page)
|
2867
3308
|
...number(newPointer),
|
2868
3309
|
|
2869
3310
|
...number(TYPES._bytestring, Valtype.i32),
|
2870
|
-
setLastType(scope)
|
3311
|
+
...setLastType(scope)
|
2871
3312
|
],
|
2872
3313
|
|
2873
|
-
default:
|
3314
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
|
2874
3315
|
});
|
2875
3316
|
};
|
2876
3317
|
|
@@ -2880,25 +3321,36 @@ const objectHack = node => {
|
|
2880
3321
|
if (!node) return node;
|
2881
3322
|
|
2882
3323
|
if (node.type === 'MemberExpression') {
|
2883
|
-
|
3324
|
+
const out = (() => {
|
3325
|
+
if (node.computed || node.optional) return;
|
3326
|
+
|
3327
|
+
let objectName = node.object.name;
|
2884
3328
|
|
2885
|
-
|
3329
|
+
// if object is not identifier or another member exp, give up
|
3330
|
+
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
|
3331
|
+
if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
|
2886
3332
|
|
2887
|
-
|
2888
|
-
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
3333
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2889
3334
|
|
2890
|
-
|
3335
|
+
// if .length, give up (hack within a hack!)
|
3336
|
+
if (node.property.name === 'length') {
|
3337
|
+
node.object = objectHack(node.object);
|
3338
|
+
return;
|
3339
|
+
}
|
2891
3340
|
|
2892
|
-
|
2893
|
-
|
3341
|
+
// no object name, give up
|
3342
|
+
if (!objectName) return;
|
2894
3343
|
|
2895
|
-
|
2896
|
-
|
3344
|
+
const name = '__' + objectName + '_' + node.property.name;
|
3345
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2897
3346
|
|
2898
|
-
|
2899
|
-
|
2900
|
-
|
2901
|
-
|
3347
|
+
return {
|
3348
|
+
type: 'Identifier',
|
3349
|
+
name
|
3350
|
+
};
|
3351
|
+
})();
|
3352
|
+
|
3353
|
+
if (out) return out;
|
2902
3354
|
}
|
2903
3355
|
|
2904
3356
|
for (const x in node) {
|
@@ -2912,8 +3364,8 @@ const objectHack = node => {
|
|
2912
3364
|
};
|
2913
3365
|
|
2914
3366
|
const generateFunc = (scope, decl) => {
|
2915
|
-
if (decl.async) return todo('async functions are not supported');
|
2916
|
-
if (decl.generator) return todo('generator functions are not supported');
|
3367
|
+
if (decl.async) return todo(scope, 'async functions are not supported');
|
3368
|
+
if (decl.generator) return todo(scope, 'generator functions are not supported');
|
2917
3369
|
|
2918
3370
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2919
3371
|
const params = decl.params ?? [];
|
@@ -2929,6 +3381,11 @@ const generateFunc = (scope, decl) => {
|
|
2929
3381
|
name
|
2930
3382
|
};
|
2931
3383
|
|
3384
|
+
if (typedInput && decl.returnType) {
|
3385
|
+
innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
|
3386
|
+
innerScope.returns = [ valtypeBinary ];
|
3387
|
+
}
|
3388
|
+
|
2932
3389
|
for (let i = 0; i < params.length; i++) {
|
2933
3390
|
allocVar(innerScope, params[i].name, false);
|
2934
3391
|
|
@@ -2950,13 +3407,13 @@ const generateFunc = (scope, decl) => {
|
|
2950
3407
|
const func = {
|
2951
3408
|
name,
|
2952
3409
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2953
|
-
|
2954
|
-
|
2955
|
-
throws: innerScope.throws,
|
2956
|
-
index: currentFuncIndex++
|
3410
|
+
index: currentFuncIndex++,
|
3411
|
+
...innerScope
|
2957
3412
|
};
|
2958
3413
|
funcIndex[name] = func.index;
|
2959
3414
|
|
3415
|
+
if (name === 'main') func.gotLastType = true;
|
3416
|
+
|
2960
3417
|
// quick hack fixes
|
2961
3418
|
for (const inst of wasm) {
|
2962
3419
|
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
@@ -3008,7 +3465,7 @@ const internalConstrs = {
|
|
3008
3465
|
|
3009
3466
|
// todo: check in wasm instead of here
|
3010
3467
|
const literalValue = arg.value ?? 0;
|
3011
|
-
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
|
3468
|
+
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
|
3012
3469
|
|
3013
3470
|
return [
|
3014
3471
|
...number(0, Valtype.i32),
|
@@ -3019,7 +3476,8 @@ const internalConstrs = {
|
|
3019
3476
|
...number(pointer)
|
3020
3477
|
];
|
3021
3478
|
},
|
3022
|
-
type: TYPES._array
|
3479
|
+
type: TYPES._array,
|
3480
|
+
length: 1
|
3023
3481
|
},
|
3024
3482
|
|
3025
3483
|
__Array_of: {
|
@@ -3031,7 +3489,94 @@ const internalConstrs = {
|
|
3031
3489
|
}, global, name);
|
3032
3490
|
},
|
3033
3491
|
type: TYPES._array,
|
3492
|
+
notConstr: true,
|
3493
|
+
length: 0
|
3494
|
+
},
|
3495
|
+
|
3496
|
+
__Porffor_fastOr: {
|
3497
|
+
generate: (scope, decl) => {
|
3498
|
+
const out = [];
|
3499
|
+
|
3500
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3501
|
+
out.push(
|
3502
|
+
...generate(scope, decl.arguments[i]),
|
3503
|
+
Opcodes.i32_to_u,
|
3504
|
+
...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
|
3505
|
+
);
|
3506
|
+
}
|
3507
|
+
|
3508
|
+
return out;
|
3509
|
+
},
|
3510
|
+
type: TYPES.boolean,
|
3511
|
+
notConstr: true
|
3512
|
+
},
|
3513
|
+
|
3514
|
+
__Porffor_fastAnd: {
|
3515
|
+
generate: (scope, decl) => {
|
3516
|
+
const out = [];
|
3517
|
+
|
3518
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3519
|
+
out.push(
|
3520
|
+
...generate(scope, decl.arguments[i]),
|
3521
|
+
Opcodes.i32_to_u,
|
3522
|
+
...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
|
3523
|
+
);
|
3524
|
+
}
|
3525
|
+
|
3526
|
+
return out;
|
3527
|
+
},
|
3528
|
+
type: TYPES.boolean,
|
3034
3529
|
notConstr: true
|
3530
|
+
},
|
3531
|
+
|
3532
|
+
Boolean: {
|
3533
|
+
generate: (scope, decl) => {
|
3534
|
+
// todo: boolean object when used as constructor
|
3535
|
+
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
3536
|
+
return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
|
3537
|
+
},
|
3538
|
+
type: TYPES.boolean,
|
3539
|
+
length: 1
|
3540
|
+
},
|
3541
|
+
|
3542
|
+
__Math_max: {
|
3543
|
+
generate: (scope, decl) => {
|
3544
|
+
const out = [
|
3545
|
+
...number(-Infinity)
|
3546
|
+
];
|
3547
|
+
|
3548
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3549
|
+
out.push(
|
3550
|
+
...generate(scope, decl.arguments[i]),
|
3551
|
+
[ Opcodes.f64_max ]
|
3552
|
+
);
|
3553
|
+
}
|
3554
|
+
|
3555
|
+
return out;
|
3556
|
+
},
|
3557
|
+
type: TYPES.number,
|
3558
|
+
notConstr: true,
|
3559
|
+
length: 2
|
3560
|
+
},
|
3561
|
+
|
3562
|
+
__Math_min: {
|
3563
|
+
generate: (scope, decl) => {
|
3564
|
+
const out = [
|
3565
|
+
...number(Infinity)
|
3566
|
+
];
|
3567
|
+
|
3568
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3569
|
+
out.push(
|
3570
|
+
...generate(scope, decl.arguments[i]),
|
3571
|
+
[ Opcodes.f64_min ]
|
3572
|
+
);
|
3573
|
+
}
|
3574
|
+
|
3575
|
+
return out;
|
3576
|
+
},
|
3577
|
+
type: TYPES.number,
|
3578
|
+
notConstr: true,
|
3579
|
+
length: 2
|
3035
3580
|
}
|
3036
3581
|
};
|
3037
3582
|
|
@@ -3060,7 +3605,6 @@ export default program => {
|
|
3060
3605
|
funcs = [];
|
3061
3606
|
funcIndex = {};
|
3062
3607
|
depth = [];
|
3063
|
-
arrays = new Map();
|
3064
3608
|
pages = new Map();
|
3065
3609
|
data = [];
|
3066
3610
|
currentFuncIndex = importedFuncs.length;
|
@@ -3074,6 +3618,10 @@ export default program => {
|
|
3074
3618
|
|
3075
3619
|
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
3076
3620
|
|
3621
|
+
globalThis.pageSize = PageSize;
|
3622
|
+
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
3623
|
+
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3624
|
+
|
3077
3625
|
// set generic opcodes for current valtype
|
3078
3626
|
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
3079
3627
|
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
@@ -3082,10 +3630,10 @@ export default program => {
|
|
3082
3630
|
Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
|
3083
3631
|
Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
|
3084
3632
|
|
3085
|
-
Opcodes.i32_to = [ [
|
3086
|
-
Opcodes.i32_to_u = [ [
|
3087
|
-
Opcodes.i32_from = [ [
|
3088
|
-
Opcodes.i32_from_u = [ [
|
3633
|
+
Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
|
3634
|
+
Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
|
3635
|
+
Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
|
3636
|
+
Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
|
3089
3637
|
|
3090
3638
|
Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
|
3091
3639
|
Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
|
@@ -3098,10 +3646,6 @@ export default program => {
|
|
3098
3646
|
|
3099
3647
|
program.id = { name: 'main' };
|
3100
3648
|
|
3101
|
-
globalThis.pageSize = PageSize;
|
3102
|
-
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
3103
|
-
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3104
|
-
|
3105
3649
|
const scope = {
|
3106
3650
|
locals: {},
|
3107
3651
|
localInd: 0
|
@@ -3112,7 +3656,7 @@ export default program => {
|
|
3112
3656
|
body: program.body
|
3113
3657
|
};
|
3114
3658
|
|
3115
|
-
if (
|
3659
|
+
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
3116
3660
|
|
3117
3661
|
generateFunc(scope, program);
|
3118
3662
|
|
@@ -3129,7 +3673,11 @@ export default program => {
|
|
3129
3673
|
}
|
3130
3674
|
|
3131
3675
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
3132
|
-
|
3676
|
+
if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
|
3677
|
+
main.wasm.splice(main.wasm.length - 1, 1);
|
3678
|
+
} else {
|
3679
|
+
main.returns = [];
|
3680
|
+
}
|
3133
3681
|
}
|
3134
3682
|
|
3135
3683
|
if (lastInst[0] === Opcodes.call) {
|