porffor 0.2.0-9f8ffb2 → 0.2.0-a2c18e7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +239 -0
- package/LICENSE +20 -20
- package/README.md +154 -89
- 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 +1858 -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 +59 -0
- package/compiler/builtins/string.ts +1055 -0
- package/compiler/builtins/tostring.ts +45 -0
- package/compiler/builtins.js +449 -269
- package/compiler/{codeGen.js → codegen.js} +1009 -381
- package/compiler/decompile.js +0 -1
- package/compiler/embedding.js +22 -22
- package/compiler/encoding.js +108 -10
- package/compiler/generated_builtins.js +1454 -0
- package/compiler/index.js +36 -34
- package/compiler/log.js +6 -3
- package/compiler/opt.js +50 -36
- package/compiler/parse.js +33 -23
- package/compiler/precompile.js +128 -0
- package/compiler/prefs.js +27 -0
- package/compiler/prototype.js +13 -28
- package/compiler/types.js +37 -0
- package/compiler/wasmSpec.js +28 -8
- package/compiler/wrap.js +51 -46
- package/package.json +9 -5
- package/porf +4 -0
- package/rhemyn/compile.js +46 -27
- package/rhemyn/parse.js +322 -320
- package/rhemyn/test/parse.js +58 -58
- package/runner/compare.js +34 -34
- package/runner/debug.js +122 -0
- package/runner/index.js +91 -11
- package/runner/profiler.js +102 -0
- package/runner/repl.js +42 -9
- package/runner/sizes.js +37 -37
- package/compiler/builtins/base64.js +0 -92
- package/runner/info.js +0 -89
- package/runner/profile.js +0 -46
- package/runner/results.json +0 -1
- package/runner/transform.js +0 -15
- package/util/enum.js +0 -20
@@ -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,52 +195,74 @@ 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
|
}
|
186
202
|
|
187
203
|
let inst = Opcodes[asm[0].replace('.', '_')];
|
188
|
-
if (
|
204
|
+
if (inst == null) 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
|
-
out.push([ ...inst, ...immediates ]);
|
213
|
+
out.push([ ...inst, ...immediates.flatMap(x => signedLEB128(x)) ]);
|
194
214
|
}
|
195
215
|
|
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;
|
243
|
+
|
244
|
+
for (let i = 0; i < expressions.length; i++) {
|
245
|
+
const e = expressions[i];
|
246
|
+
if (!e.name) {
|
247
|
+
if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
|
248
|
+
str += lookupName(scope, e.left.name)[0].idx + e.right.value;
|
249
|
+
} else todo(scope, 'unsupported expression in intrinsic');
|
250
|
+
} else str += lookupName(scope, e.name)[0].idx;
|
216
251
|
|
217
|
-
|
218
|
-
|
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();
|
321
|
+
}
|
322
|
+
|
323
|
+
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
324
|
+
// todo: return an actual something
|
325
|
+
return number(1);
|
280
326
|
}
|
281
327
|
|
282
|
-
if (
|
328
|
+
if (isExistingProtoFunc(name)) {
|
283
329
|
// todo: return an actual something
|
284
330
|
return number(1);
|
285
331
|
}
|
286
332
|
|
287
|
-
if (local === undefined) {
|
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
|
-
|
413
|
-
|
414
|
-
|
415
|
-
if (assign) {
|
416
|
-
const pointer = arrays.get(name ?? '$undeclared');
|
463
|
+
if (assign && Prefs.aotPointerOpt) {
|
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
|
@@ -1179,7 +1307,12 @@ const getNodeType = (scope, node) => {
|
|
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
|
}
|
@@ -1262,15 +1404,21 @@ const getNodeType = (scope, node) => {
|
|
1262
1404
|
|
1263
1405
|
// ts hack
|
1264
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;
|
1265
1408
|
if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
|
1266
1409
|
|
1267
|
-
if (scope.locals['#last_type']) return
|
1410
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1268
1411
|
|
1269
1412
|
// presume
|
1270
1413
|
return TYPES.number;
|
1271
1414
|
}
|
1272
1415
|
|
1273
|
-
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);
|
1274
1422
|
|
1275
1423
|
// presume
|
1276
1424
|
// todo: warn here?
|
@@ -1303,7 +1451,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1303
1451
|
return makeString(scope, decl.value, global, name);
|
1304
1452
|
|
1305
1453
|
default:
|
1306
|
-
return todo(`cannot generate literal of type ${typeof decl.value}
|
1454
|
+
return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
|
1307
1455
|
}
|
1308
1456
|
};
|
1309
1457
|
|
@@ -1312,6 +1460,8 @@ const countLeftover = wasm => {
|
|
1312
1460
|
|
1313
1461
|
for (let i = 0; i < wasm.length; i++) {
|
1314
1462
|
const inst = wasm[i];
|
1463
|
+
if (inst[0] == null) continue;
|
1464
|
+
|
1315
1465
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
1316
1466
|
if (inst[0] === Opcodes.if) count--;
|
1317
1467
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1320,18 +1470,25 @@ const countLeftover = wasm => {
|
|
1320
1470
|
if (inst[0] === Opcodes.end) depth--;
|
1321
1471
|
|
1322
1472
|
if (depth === 0)
|
1323
|
-
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--;
|
1324
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)) {}
|
1325
|
-
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1475
|
+
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const, Opcodes.memory_size].includes(inst[0])) count++;
|
1326
1476
|
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1327
1477
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1328
1478
|
else if (inst[0] === Opcodes.return) count = 0;
|
1329
1479
|
else if (inst[0] === Opcodes.call) {
|
1330
1480
|
let func = funcs.find(x => x.index === inst[1]);
|
1331
|
-
if (
|
1332
|
-
count
|
1333
|
-
} else
|
1334
|
-
|
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
|
+
}
|
1335
1492
|
} else count--;
|
1336
1493
|
|
1337
1494
|
// console.log(count, decompile([ inst ]).slice(0, -1));
|
@@ -1423,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1423
1580
|
name = func.name;
|
1424
1581
|
}
|
1425
1582
|
|
1426
|
-
if (name === 'eval' && decl.arguments[0]
|
1583
|
+
if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
|
1427
1584
|
// literal eval hack
|
1428
|
-
const code = decl.arguments[0]
|
1429
|
-
|
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
|
+
}
|
1430
1598
|
|
1431
1599
|
const out = generate(scope, {
|
1432
1600
|
type: 'BlockStatement',
|
@@ -1440,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1440
1608
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1441
1609
|
out.push(
|
1442
1610
|
...getNodeType(scope, finalStatement),
|
1443
|
-
setLastType(scope)
|
1611
|
+
...setLastType(scope)
|
1444
1612
|
);
|
1445
1613
|
} else if (countLeftover(out) === 0) {
|
1446
1614
|
out.push(...number(UNDEFINED));
|
1447
1615
|
out.push(
|
1448
1616
|
...number(TYPES.undefined, Valtype.i32),
|
1449
|
-
setLastType(scope)
|
1617
|
+
...setLastType(scope)
|
1450
1618
|
);
|
1451
1619
|
}
|
1452
1620
|
|
@@ -1468,6 +1636,9 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1468
1636
|
|
1469
1637
|
target = { ...decl.callee };
|
1470
1638
|
target.name = spl.slice(0, -1).join('_');
|
1639
|
+
|
1640
|
+
// failed to lookup name, abort
|
1641
|
+
if (!lookupName(scope, target.name)[0]) protoName = null;
|
1471
1642
|
}
|
1472
1643
|
|
1473
1644
|
// literal.func()
|
@@ -1475,22 +1646,29 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1475
1646
|
// megahack for /regex/.func()
|
1476
1647
|
const funcName = decl.callee.property.name;
|
1477
1648
|
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1478
|
-
const
|
1649
|
+
const regex = decl.callee.object.regex.pattern;
|
1650
|
+
const rhemynName = `regex_${funcName}_${regex}`;
|
1651
|
+
|
1652
|
+
if (!funcIndex[rhemynName]) {
|
1653
|
+
const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
|
1479
1654
|
|
1480
|
-
|
1481
|
-
|
1655
|
+
funcIndex[func.name] = func.index;
|
1656
|
+
funcs.push(func);
|
1657
|
+
}
|
1482
1658
|
|
1659
|
+
const idx = funcIndex[rhemynName];
|
1483
1660
|
return [
|
1484
1661
|
// make string arg
|
1485
1662
|
...generate(scope, decl.arguments[0]),
|
1663
|
+
Opcodes.i32_to_u,
|
1664
|
+
...getNodeType(scope, decl.arguments[0]),
|
1486
1665
|
|
1487
1666
|
// call regex func
|
1488
|
-
Opcodes.
|
1489
|
-
[ Opcodes.call, func.index ],
|
1667
|
+
[ Opcodes.call, idx ],
|
1490
1668
|
Opcodes.i32_from_u,
|
1491
1669
|
|
1492
1670
|
...number(TYPES.boolean, Valtype.i32),
|
1493
|
-
setLastType(scope)
|
1671
|
+
...setLastType(scope)
|
1494
1672
|
];
|
1495
1673
|
}
|
1496
1674
|
|
@@ -1515,12 +1693,31 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1515
1693
|
// }
|
1516
1694
|
|
1517
1695
|
if (protoName) {
|
1696
|
+
const protoBC = {};
|
1697
|
+
|
1698
|
+
const builtinProtoCands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + protoName));
|
1699
|
+
|
1700
|
+
if (!decl._protoInternalCall && builtinProtoCands.length > 0) {
|
1701
|
+
for (const x of builtinProtoCands) {
|
1702
|
+
const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
|
1703
|
+
if (type == null) continue;
|
1704
|
+
|
1705
|
+
protoBC[type] = generateCall(scope, {
|
1706
|
+
callee: {
|
1707
|
+
type: 'Identifier',
|
1708
|
+
name: x
|
1709
|
+
},
|
1710
|
+
arguments: [ target, ...decl.arguments ],
|
1711
|
+
_protoInternalCall: true
|
1712
|
+
});
|
1713
|
+
}
|
1714
|
+
}
|
1715
|
+
|
1518
1716
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1519
1717
|
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1520
1718
|
return acc;
|
1521
1719
|
}, {});
|
1522
1720
|
|
1523
|
-
// no prototype function candidates, ignore
|
1524
1721
|
if (Object.keys(protoCands).length > 0) {
|
1525
1722
|
// use local for cached i32 length as commonly used
|
1526
1723
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
@@ -1538,7 +1735,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1538
1735
|
|
1539
1736
|
let allOptUnused = true;
|
1540
1737
|
let lengthI32CacheUsed = false;
|
1541
|
-
const protoBC = {};
|
1542
1738
|
for (const x in protoCands) {
|
1543
1739
|
const protoFunc = protoCands[x];
|
1544
1740
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
@@ -1546,7 +1742,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1546
1742
|
...RTArrayUtil.getLength(getPointer),
|
1547
1743
|
|
1548
1744
|
...number(TYPES.number, Valtype.i32),
|
1549
|
-
setLastType(scope)
|
1745
|
+
...setLastType(scope)
|
1550
1746
|
];
|
1551
1747
|
continue;
|
1552
1748
|
}
|
@@ -1583,7 +1779,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1583
1779
|
...protoOut,
|
1584
1780
|
|
1585
1781
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1586
|
-
setLastType(scope),
|
1782
|
+
...setLastType(scope),
|
1587
1783
|
[ Opcodes.end ]
|
1588
1784
|
];
|
1589
1785
|
}
|
@@ -1609,10 +1805,19 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1609
1805
|
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1610
1806
|
];
|
1611
1807
|
}
|
1808
|
+
|
1809
|
+
if (Object.keys(protoBC).length > 0) {
|
1810
|
+
return typeSwitch(scope, getNodeType(scope, target), {
|
1811
|
+
...protoBC,
|
1812
|
+
|
1813
|
+
// TODO: error better
|
1814
|
+
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1815
|
+
}, valtypeBinary);
|
1816
|
+
}
|
1612
1817
|
}
|
1613
1818
|
|
1614
1819
|
// TODO: only allows callee as literal
|
1615
|
-
if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
|
1820
|
+
if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
|
1616
1821
|
|
1617
1822
|
let idx = funcIndex[name] ?? importedFuncs[name];
|
1618
1823
|
if (idx === undefined && builtinFuncs[name]) {
|
@@ -1622,20 +1827,20 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1622
1827
|
idx = funcIndex[name];
|
1623
1828
|
|
1624
1829
|
// infer arguments types from builtins params
|
1625
|
-
const func = funcs.find(x => x.name === name);
|
1626
|
-
for (let i = 0; i < decl.arguments.length; i++) {
|
1627
|
-
|
1628
|
-
|
1629
|
-
|
1630
|
-
|
1631
|
-
|
1632
|
-
|
1633
|
-
|
1634
|
-
|
1635
|
-
|
1636
|
-
|
1637
|
-
|
1638
|
-
}
|
1830
|
+
// const func = funcs.find(x => x.name === name);
|
1831
|
+
// for (let i = 0; i < decl.arguments.length; i++) {
|
1832
|
+
// const arg = decl.arguments[i];
|
1833
|
+
// if (!arg.name) continue;
|
1834
|
+
|
1835
|
+
// const local = scope.locals[arg.name];
|
1836
|
+
// if (!local) continue;
|
1837
|
+
|
1838
|
+
// local.type = func.params[i];
|
1839
|
+
// if (local.type === Valtype.v128) {
|
1840
|
+
// // specify vec subtype inferred from last vec type in function name
|
1841
|
+
// local.vecType = name.split('_').reverse().find(x => x.includes('x'));
|
1842
|
+
// }
|
1843
|
+
// }
|
1639
1844
|
}
|
1640
1845
|
|
1641
1846
|
if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
@@ -1645,16 +1850,62 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1645
1850
|
idx = -1;
|
1646
1851
|
}
|
1647
1852
|
|
1853
|
+
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1854
|
+
const wasmOps = {
|
1855
|
+
// pointer, align, offset
|
1856
|
+
i32_load: { imms: 2, args: [ true ], returns: 1 },
|
1857
|
+
// pointer, value, align, offset
|
1858
|
+
i32_store: { imms: 2, args: [ true, true ], returns: 0 },
|
1859
|
+
// pointer, align, offset
|
1860
|
+
i32_load8_u: { imms: 2, args: [ true ], returns: 1 },
|
1861
|
+
// pointer, value, align, offset
|
1862
|
+
i32_store8: { imms: 2, args: [ true, true ], returns: 0 },
|
1863
|
+
// pointer, align, offset
|
1864
|
+
i32_load16_u: { imms: 2, args: [ true ], returns: 1 },
|
1865
|
+
// pointer, value, align, offset
|
1866
|
+
i32_store16: { imms: 2, args: [ true, true ], returns: 0 },
|
1867
|
+
|
1868
|
+
// pointer, align, offset
|
1869
|
+
f64_load: { imms: 2, args: [ true ], returns: 0 }, // 0 due to not i32
|
1870
|
+
// pointer, value, align, offset
|
1871
|
+
f64_store: { imms: 2, args: [ true, false ], returns: 0 },
|
1872
|
+
|
1873
|
+
// value
|
1874
|
+
i32_const: { imms: 1, args: [], returns: 1 },
|
1875
|
+
};
|
1876
|
+
|
1877
|
+
const opName = name.slice('__Porffor_wasm_'.length);
|
1878
|
+
|
1879
|
+
if (wasmOps[opName]) {
|
1880
|
+
const op = wasmOps[opName];
|
1881
|
+
|
1882
|
+
const argOut = [];
|
1883
|
+
for (let i = 0; i < op.args.length; i++) argOut.push(
|
1884
|
+
...generate(scope, decl.arguments[i]),
|
1885
|
+
...(op.args[i] ? [ Opcodes.i32_to ] : [])
|
1886
|
+
);
|
1887
|
+
|
1888
|
+
// literals only
|
1889
|
+
const imms = decl.arguments.slice(op.args.length).map(x => x.value);
|
1890
|
+
|
1891
|
+
return [
|
1892
|
+
...argOut,
|
1893
|
+
[ Opcodes[opName], ...imms ],
|
1894
|
+
...(new Array(op.returns).fill(Opcodes.i32_from))
|
1895
|
+
];
|
1896
|
+
}
|
1897
|
+
}
|
1898
|
+
|
1648
1899
|
if (idx === undefined) {
|
1649
|
-
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function
|
1650
|
-
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined
|
1900
|
+
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1901
|
+
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
1651
1902
|
}
|
1652
1903
|
|
1653
1904
|
const func = funcs.find(x => x.index === idx);
|
1654
1905
|
|
1655
1906
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1656
1907
|
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1657
|
-
const
|
1908
|
+
const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
|
1658
1909
|
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1659
1910
|
|
1660
1911
|
let args = decl.arguments;
|
@@ -1671,14 +1922,24 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1671
1922
|
if (func && func.throws) scope.throws = true;
|
1672
1923
|
|
1673
1924
|
let out = [];
|
1674
|
-
for (
|
1925
|
+
for (let i = 0; i < args.length; i++) {
|
1926
|
+
const arg = args[i];
|
1675
1927
|
out = out.concat(generate(scope, arg));
|
1928
|
+
|
1929
|
+
if (builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1930
|
+
out.push(Opcodes.i32_to);
|
1931
|
+
}
|
1932
|
+
|
1933
|
+
if (importedFuncs[name] && name.startsWith('profile')) {
|
1934
|
+
out.push(Opcodes.i32_to);
|
1935
|
+
}
|
1936
|
+
|
1676
1937
|
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1677
1938
|
}
|
1678
1939
|
|
1679
1940
|
out.push([ Opcodes.call, idx ]);
|
1680
1941
|
|
1681
|
-
if (!
|
1942
|
+
if (!typedReturns) {
|
1682
1943
|
// let type;
|
1683
1944
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1684
1945
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1688,7 +1949,11 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1688
1949
|
// ...number(type, Valtype.i32),
|
1689
1950
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1690
1951
|
// );
|
1691
|
-
} else out.push(setLastType(scope));
|
1952
|
+
} else out.push(...setLastType(scope));
|
1953
|
+
|
1954
|
+
if (builtinFuncs[name] && builtinFuncs[name].returns?.[0] === Valtype.i32 && valtypeBinary !== Valtype.i32) {
|
1955
|
+
out.push(Opcodes.i32_from);
|
1956
|
+
}
|
1692
1957
|
|
1693
1958
|
return out;
|
1694
1959
|
};
|
@@ -1696,8 +1961,21 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1696
1961
|
const generateNew = (scope, decl, _global, _name) => {
|
1697
1962
|
// hack: basically treat this as a normal call for builtins for now
|
1698
1963
|
const name = mapName(decl.callee.name);
|
1964
|
+
|
1699
1965
|
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1700
|
-
|
1966
|
+
|
1967
|
+
if (builtinFuncs[name + '$constructor']) {
|
1968
|
+
// custom ...$constructor override builtin func
|
1969
|
+
return generateCall(scope, {
|
1970
|
+
...decl,
|
1971
|
+
callee: {
|
1972
|
+
type: 'Identifier',
|
1973
|
+
name: name + '$constructor'
|
1974
|
+
}
|
1975
|
+
}, _global, _name);
|
1976
|
+
}
|
1977
|
+
|
1978
|
+
if (!builtinFuncs[name]) return todo(scope, `new statement is not supported yet`); // return todo(scope, `new statement is not supported yet (new ${unhackName(name)})`);
|
1701
1979
|
|
1702
1980
|
return generateCall(scope, decl, _global, _name);
|
1703
1981
|
};
|
@@ -1814,14 +2092,14 @@ const brTable = (input, bc, returns) => {
|
|
1814
2092
|
};
|
1815
2093
|
|
1816
2094
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1817
|
-
if (!
|
2095
|
+
if (!Prefs.bytestring) delete bc[TYPES._bytestring];
|
1818
2096
|
|
1819
2097
|
const known = knownType(scope, type);
|
1820
2098
|
if (known != null) {
|
1821
2099
|
return bc[known] ?? bc.default;
|
1822
2100
|
}
|
1823
2101
|
|
1824
|
-
if (
|
2102
|
+
if (Prefs.typeswitchUseBrtable)
|
1825
2103
|
return brTable(type, bc, returns);
|
1826
2104
|
|
1827
2105
|
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
@@ -1831,8 +2109,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1831
2109
|
[ Opcodes.block, returns ]
|
1832
2110
|
];
|
1833
2111
|
|
1834
|
-
// todo: use br_table?
|
1835
|
-
|
1836
2112
|
for (const x in bc) {
|
1837
2113
|
if (x === 'default') continue;
|
1838
2114
|
|
@@ -1856,7 +2132,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1856
2132
|
return out;
|
1857
2133
|
};
|
1858
2134
|
|
1859
|
-
const allocVar = (scope, name, global = false) => {
|
2135
|
+
const allocVar = (scope, name, global = false, type = true) => {
|
1860
2136
|
const target = global ? globals : scope.locals;
|
1861
2137
|
|
1862
2138
|
// already declared
|
@@ -1870,8 +2146,10 @@ const allocVar = (scope, name, global = false) => {
|
|
1870
2146
|
let idx = global ? globalInd++ : scope.localInd++;
|
1871
2147
|
target[name] = { idx, type: valtypeBinary };
|
1872
2148
|
|
1873
|
-
|
1874
|
-
|
2149
|
+
if (type) {
|
2150
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
2151
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
2152
|
+
}
|
1875
2153
|
|
1876
2154
|
return idx;
|
1877
2155
|
};
|
@@ -1886,11 +2164,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
|
1886
2164
|
};
|
1887
2165
|
|
1888
2166
|
const typeAnnoToPorfType = x => {
|
1889
|
-
if (
|
1890
|
-
if (TYPES[
|
2167
|
+
if (!x) return null;
|
2168
|
+
if (TYPES[x.toLowerCase()] != null) return TYPES[x.toLowerCase()];
|
2169
|
+
if (TYPES['_' + x.toLowerCase()] != null) return TYPES['_' + x.toLowerCase()];
|
1891
2170
|
|
1892
2171
|
switch (x) {
|
1893
2172
|
case 'i32':
|
2173
|
+
case 'i64':
|
2174
|
+
case 'f64':
|
1894
2175
|
return TYPES.number;
|
1895
2176
|
}
|
1896
2177
|
|
@@ -1901,7 +2182,7 @@ const extractTypeAnnotation = decl => {
|
|
1901
2182
|
let a = decl;
|
1902
2183
|
while (a.typeAnnotation) a = a.typeAnnotation;
|
1903
2184
|
|
1904
|
-
let type, elementType;
|
2185
|
+
let type = null, elementType = null;
|
1905
2186
|
if (a.typeName) {
|
1906
2187
|
type = a.typeName.name;
|
1907
2188
|
} else if (a.type.endsWith('Keyword')) {
|
@@ -1914,7 +2195,7 @@ const extractTypeAnnotation = decl => {
|
|
1914
2195
|
const typeName = type;
|
1915
2196
|
type = typeAnnoToPorfType(type);
|
1916
2197
|
|
1917
|
-
if (type === TYPES._bytestring && !
|
2198
|
+
if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
|
1918
2199
|
|
1919
2200
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1920
2201
|
|
@@ -1928,11 +2209,12 @@ const generateVar = (scope, decl) => {
|
|
1928
2209
|
|
1929
2210
|
// global variable if in top scope (main) and var ..., or if wanted
|
1930
2211
|
const global = topLevel || decl._bare; // decl.kind === 'var';
|
2212
|
+
const target = global ? globals : scope.locals;
|
1931
2213
|
|
1932
2214
|
for (const x of decl.declarations) {
|
1933
2215
|
const name = mapName(x.id.name);
|
1934
2216
|
|
1935
|
-
if (!name) return todo('destructuring is not supported yet');
|
2217
|
+
if (!name) return todo(scope, 'destructuring is not supported yet');
|
1936
2218
|
|
1937
2219
|
if (x.init && isFuncType(x.init.type)) {
|
1938
2220
|
// hack for let a = function () { ... }
|
@@ -1949,16 +2231,29 @@ const generateVar = (scope, decl) => {
|
|
1949
2231
|
continue; // always ignore
|
1950
2232
|
}
|
1951
2233
|
|
1952
|
-
|
2234
|
+
// // generate init before allocating var
|
2235
|
+
// let generated;
|
2236
|
+
// if (x.init) generated = generate(scope, x.init, global, name);
|
1953
2237
|
|
1954
|
-
|
2238
|
+
const typed = typedInput && x.id.typeAnnotation;
|
2239
|
+
let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
|
2240
|
+
|
2241
|
+
if (typed) {
|
1955
2242
|
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1956
2243
|
}
|
1957
2244
|
|
1958
2245
|
if (x.init) {
|
1959
|
-
|
1960
|
-
|
1961
|
-
|
2246
|
+
const generated = generate(scope, x.init, global, name);
|
2247
|
+
if (scope.arrays?.get(name) != null) {
|
2248
|
+
// hack to set local as pointer before
|
2249
|
+
out.push(...number(scope.arrays.get(name)), [ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2250
|
+
if (generated.at(-1) == Opcodes.i32_from_u) generated.pop();
|
2251
|
+
generated.pop();
|
2252
|
+
out = out.concat(generated);
|
2253
|
+
} else {
|
2254
|
+
out = out.concat(generated);
|
2255
|
+
out.push([ global ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2256
|
+
}
|
1962
2257
|
out.push(...setType(scope, name, getNodeType(scope, x.init)));
|
1963
2258
|
}
|
1964
2259
|
|
@@ -1969,7 +2264,8 @@ const generateVar = (scope, decl) => {
|
|
1969
2264
|
return out;
|
1970
2265
|
};
|
1971
2266
|
|
1972
|
-
|
2267
|
+
// todo: optimize this func for valueUnused
|
2268
|
+
const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
1973
2269
|
const { type, name } = decl.left;
|
1974
2270
|
|
1975
2271
|
if (type === 'ObjectPattern') {
|
@@ -1984,22 +2280,30 @@ const generateAssign = (scope, decl) => {
|
|
1984
2280
|
return [];
|
1985
2281
|
}
|
1986
2282
|
|
2283
|
+
const op = decl.operator.slice(0, -1) || '=';
|
2284
|
+
|
1987
2285
|
// hack: .length setter
|
1988
2286
|
if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
|
1989
2287
|
const name = decl.left.object.name;
|
1990
|
-
const pointer = arrays
|
2288
|
+
const pointer = scope.arrays?.get(name);
|
1991
2289
|
|
1992
|
-
const aotPointer = pointer != null;
|
2290
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1993
2291
|
|
1994
2292
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
2293
|
+
const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1995
2294
|
|
1996
2295
|
return [
|
1997
2296
|
...(aotPointer ? number(0, Valtype.i32) : [
|
1998
2297
|
...generate(scope, decl.left.object),
|
1999
2298
|
Opcodes.i32_to_u
|
2000
2299
|
]),
|
2300
|
+
...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
2001
2301
|
|
2002
|
-
...generate(scope, decl.right),
|
2302
|
+
...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
|
2303
|
+
[ Opcodes.local_get, pointerTmp ],
|
2304
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
2305
|
+
Opcodes.i32_from_u
|
2306
|
+
], generate(scope, decl.right), number(TYPES.number, Valtype.i32), getNodeType(scope, decl.right))),
|
2003
2307
|
[ Opcodes.local_tee, newValueTmp ],
|
2004
2308
|
|
2005
2309
|
Opcodes.i32_to_u,
|
@@ -2009,14 +2313,12 @@ const generateAssign = (scope, decl) => {
|
|
2009
2313
|
];
|
2010
2314
|
}
|
2011
2315
|
|
2012
|
-
const op = decl.operator.slice(0, -1) || '=';
|
2013
|
-
|
2014
2316
|
// arr[i]
|
2015
2317
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
2016
2318
|
const name = decl.left.object.name;
|
2017
|
-
const pointer = arrays
|
2319
|
+
const pointer = scope.arrays?.get(name);
|
2018
2320
|
|
2019
|
-
const aotPointer = pointer != null;
|
2321
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2020
2322
|
|
2021
2323
|
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
2022
2324
|
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
@@ -2072,7 +2374,7 @@ const generateAssign = (scope, decl) => {
|
|
2072
2374
|
];
|
2073
2375
|
}
|
2074
2376
|
|
2075
|
-
if (!name) return todo('destructuring is not supported yet');
|
2377
|
+
if (!name) return todo(scope, 'destructuring is not supported yet', true);
|
2076
2378
|
|
2077
2379
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2078
2380
|
|
@@ -2120,9 +2422,7 @@ const generateAssign = (scope, decl) => {
|
|
2120
2422
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
2121
2423
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
2122
2424
|
|
2123
|
-
getLastType(scope)
|
2124
|
-
// hack: type is idx+1
|
2125
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2425
|
+
...setType(scope, name, getLastType(scope))
|
2126
2426
|
];
|
2127
2427
|
}
|
2128
2428
|
|
@@ -2133,9 +2433,7 @@ const generateAssign = (scope, decl) => {
|
|
2133
2433
|
|
2134
2434
|
// todo: string concat types
|
2135
2435
|
|
2136
|
-
|
2137
|
-
...number(TYPES.number, Valtype.i32),
|
2138
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2436
|
+
...setType(scope, name, TYPES.number)
|
2139
2437
|
];
|
2140
2438
|
};
|
2141
2439
|
|
@@ -2181,7 +2479,7 @@ const generateUnary = (scope, decl) => {
|
|
2181
2479
|
return out;
|
2182
2480
|
}
|
2183
2481
|
|
2184
|
-
case 'delete':
|
2482
|
+
case 'delete': {
|
2185
2483
|
let toReturn = true, toGenerate = true;
|
2186
2484
|
|
2187
2485
|
if (decl.argument.type === 'Identifier') {
|
@@ -2203,9 +2501,26 @@ const generateUnary = (scope, decl) => {
|
|
2203
2501
|
|
2204
2502
|
out.push(...number(toReturn ? 1 : 0));
|
2205
2503
|
return out;
|
2504
|
+
}
|
2505
|
+
|
2506
|
+
case 'typeof': {
|
2507
|
+
let overrideType, toGenerate = true;
|
2508
|
+
|
2509
|
+
if (decl.argument.type === 'Identifier') {
|
2510
|
+
const out = generateIdent(scope, decl.argument);
|
2206
2511
|
|
2207
|
-
|
2208
|
-
|
2512
|
+
// if ReferenceError (undeclared var), ignore and return undefined
|
2513
|
+
if (out[1]) {
|
2514
|
+
// does not exist (2 ops from throw)
|
2515
|
+
overrideType = number(TYPES.undefined, Valtype.i32);
|
2516
|
+
toGenerate = false;
|
2517
|
+
}
|
2518
|
+
}
|
2519
|
+
|
2520
|
+
const out = toGenerate ? generate(scope, decl.argument) : [];
|
2521
|
+
disposeLeftover(out);
|
2522
|
+
|
2523
|
+
out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
|
2209
2524
|
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
2210
2525
|
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
2211
2526
|
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
@@ -2216,27 +2531,30 @@ const generateUnary = (scope, decl) => {
|
|
2216
2531
|
|
2217
2532
|
// object and internal types
|
2218
2533
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2219
|
-
});
|
2534
|
+
}));
|
2535
|
+
|
2536
|
+
return out;
|
2537
|
+
}
|
2220
2538
|
|
2221
2539
|
default:
|
2222
|
-
return todo(`unary operator ${decl.operator} not implemented yet
|
2540
|
+
return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
|
2223
2541
|
}
|
2224
2542
|
};
|
2225
2543
|
|
2226
|
-
const generateUpdate = (scope, decl) => {
|
2544
|
+
const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
|
2227
2545
|
const { name } = decl.argument;
|
2228
2546
|
|
2229
2547
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2230
2548
|
|
2231
2549
|
if (local === undefined) {
|
2232
|
-
return todo(`update expression with undefined variable
|
2550
|
+
return todo(scope, `update expression with undefined variable`, true);
|
2233
2551
|
}
|
2234
2552
|
|
2235
2553
|
const idx = local.idx;
|
2236
2554
|
const out = [];
|
2237
2555
|
|
2238
2556
|
out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2239
|
-
if (!decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2557
|
+
if (!decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2240
2558
|
|
2241
2559
|
switch (decl.operator) {
|
2242
2560
|
case '++':
|
@@ -2249,7 +2567,7 @@ const generateUpdate = (scope, decl) => {
|
|
2249
2567
|
}
|
2250
2568
|
|
2251
2569
|
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2252
|
-
if (decl.prefix) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2570
|
+
if (decl.prefix && !valueUnused) out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2253
2571
|
|
2254
2572
|
return out;
|
2255
2573
|
};
|
@@ -2289,7 +2607,7 @@ const generateConditional = (scope, decl) => {
|
|
2289
2607
|
// note type
|
2290
2608
|
out.push(
|
2291
2609
|
...getNodeType(scope, decl.consequent),
|
2292
|
-
setLastType(scope)
|
2610
|
+
...setLastType(scope)
|
2293
2611
|
);
|
2294
2612
|
|
2295
2613
|
out.push([ Opcodes.else ]);
|
@@ -2298,7 +2616,7 @@ const generateConditional = (scope, decl) => {
|
|
2298
2616
|
// note type
|
2299
2617
|
out.push(
|
2300
2618
|
...getNodeType(scope, decl.alternate),
|
2301
|
-
setLastType(scope)
|
2619
|
+
...setLastType(scope)
|
2302
2620
|
);
|
2303
2621
|
|
2304
2622
|
out.push([ Opcodes.end ]);
|
@@ -2312,7 +2630,7 @@ const generateFor = (scope, decl) => {
|
|
2312
2630
|
const out = [];
|
2313
2631
|
|
2314
2632
|
if (decl.init) {
|
2315
|
-
out.push(...generate(scope, decl.init));
|
2633
|
+
out.push(...generate(scope, decl.init, false, undefined, true));
|
2316
2634
|
disposeLeftover(out);
|
2317
2635
|
}
|
2318
2636
|
|
@@ -2330,7 +2648,7 @@ const generateFor = (scope, decl) => {
|
|
2330
2648
|
out.push(...generate(scope, decl.body));
|
2331
2649
|
out.push([ Opcodes.end ]);
|
2332
2650
|
|
2333
|
-
if (decl.update) out.push(...generate(scope, decl.update));
|
2651
|
+
if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
|
2334
2652
|
|
2335
2653
|
out.push([ Opcodes.br, 1 ]);
|
2336
2654
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2358,6 +2676,36 @@ const generateWhile = (scope, decl) => {
|
|
2358
2676
|
return out;
|
2359
2677
|
};
|
2360
2678
|
|
2679
|
+
const generateDoWhile = (scope, decl) => {
|
2680
|
+
const out = [];
|
2681
|
+
|
2682
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
2683
|
+
depth.push('dowhile');
|
2684
|
+
|
2685
|
+
// block for break (includes all)
|
2686
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2687
|
+
depth.push('block');
|
2688
|
+
|
2689
|
+
// block for continue
|
2690
|
+
// includes body but not test+loop so we can exit body at anytime
|
2691
|
+
// and still test+loop after
|
2692
|
+
out.push([ Opcodes.block, Blocktype.void ]);
|
2693
|
+
depth.push('block');
|
2694
|
+
|
2695
|
+
out.push(...generate(scope, decl.body));
|
2696
|
+
|
2697
|
+
out.push([ Opcodes.end ]);
|
2698
|
+
depth.pop();
|
2699
|
+
|
2700
|
+
out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2701
|
+
out.push([ Opcodes.br_if, 1 ]);
|
2702
|
+
|
2703
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
2704
|
+
depth.pop(); depth.pop();
|
2705
|
+
|
2706
|
+
return out;
|
2707
|
+
};
|
2708
|
+
|
2361
2709
|
const generateForOf = (scope, decl) => {
|
2362
2710
|
const out = [];
|
2363
2711
|
|
@@ -2387,8 +2735,17 @@ const generateForOf = (scope, decl) => {
|
|
2387
2735
|
// setup local for left
|
2388
2736
|
generate(scope, decl.left);
|
2389
2737
|
|
2390
|
-
|
2738
|
+
let leftName = decl.left.declarations?.[0]?.id?.name;
|
2739
|
+
if (!leftName && decl.left.name) {
|
2740
|
+
leftName = decl.left.name;
|
2741
|
+
|
2742
|
+
generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name: leftName } } ] })
|
2743
|
+
}
|
2744
|
+
|
2745
|
+
// if (!leftName) console.log(decl.left?.declarations?.[0]?.id ?? decl.left);
|
2746
|
+
|
2391
2747
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2748
|
+
if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
|
2392
2749
|
|
2393
2750
|
depth.push('block');
|
2394
2751
|
depth.push('block');
|
@@ -2397,6 +2754,7 @@ const generateForOf = (scope, decl) => {
|
|
2397
2754
|
// hack: this is naughty and will break things!
|
2398
2755
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2399
2756
|
if (pages.hasAnyString) {
|
2757
|
+
// todo: we use i16 even for bytestrings which should not make a bad thing happen, just be confusing for debugging?
|
2400
2758
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2401
2759
|
rawElements: new Array(1)
|
2402
2760
|
}, isGlobal, leftName, true, 'i16');
|
@@ -2488,6 +2846,56 @@ const generateForOf = (scope, decl) => {
|
|
2488
2846
|
[ Opcodes.end ],
|
2489
2847
|
[ Opcodes.end ]
|
2490
2848
|
],
|
2849
|
+
[TYPES._bytestring]: [
|
2850
|
+
...setType(scope, leftName, TYPES._bytestring),
|
2851
|
+
|
2852
|
+
[ Opcodes.loop, Blocktype.void ],
|
2853
|
+
|
2854
|
+
// setup new/out array
|
2855
|
+
...newOut,
|
2856
|
+
[ Opcodes.drop ],
|
2857
|
+
|
2858
|
+
...number(0, Valtype.i32), // base 0 for store after
|
2859
|
+
|
2860
|
+
// load current string ind {arg}
|
2861
|
+
[ Opcodes.local_get, pointer ],
|
2862
|
+
[ Opcodes.local_get, counter ],
|
2863
|
+
[ Opcodes.i32_add ],
|
2864
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
2865
|
+
|
2866
|
+
// store to new string ind 0
|
2867
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2868
|
+
|
2869
|
+
// return new string (page)
|
2870
|
+
...number(newPointer),
|
2871
|
+
|
2872
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2873
|
+
|
2874
|
+
[ Opcodes.block, Blocktype.void ],
|
2875
|
+
[ Opcodes.block, Blocktype.void ],
|
2876
|
+
...generate(scope, decl.body),
|
2877
|
+
[ Opcodes.end ],
|
2878
|
+
|
2879
|
+
// increment iter pointer
|
2880
|
+
// [ Opcodes.local_get, pointer ],
|
2881
|
+
// ...number(1, Valtype.i32),
|
2882
|
+
// [ Opcodes.i32_add ],
|
2883
|
+
// [ Opcodes.local_set, pointer ],
|
2884
|
+
|
2885
|
+
// increment counter by 1
|
2886
|
+
[ Opcodes.local_get, counter ],
|
2887
|
+
...number(1, Valtype.i32),
|
2888
|
+
[ Opcodes.i32_add ],
|
2889
|
+
[ Opcodes.local_tee, counter ],
|
2890
|
+
|
2891
|
+
// loop if counter != length
|
2892
|
+
[ Opcodes.local_get, length ],
|
2893
|
+
[ Opcodes.i32_ne ],
|
2894
|
+
[ Opcodes.br_if, 1 ],
|
2895
|
+
|
2896
|
+
[ Opcodes.end ],
|
2897
|
+
[ Opcodes.end ]
|
2898
|
+
],
|
2491
2899
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2492
2900
|
}, Blocktype.void));
|
2493
2901
|
|
@@ -2498,28 +2906,65 @@ const generateForOf = (scope, decl) => {
|
|
2498
2906
|
return out;
|
2499
2907
|
};
|
2500
2908
|
|
2909
|
+
// find the nearest loop in depth map by type
|
2501
2910
|
const getNearestLoop = () => {
|
2502
2911
|
for (let i = depth.length - 1; i >= 0; i--) {
|
2503
|
-
if (
|
2912
|
+
if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
|
2504
2913
|
}
|
2505
2914
|
|
2506
2915
|
return -1;
|
2507
2916
|
};
|
2508
2917
|
|
2509
2918
|
const generateBreak = (scope, decl) => {
|
2510
|
-
const
|
2919
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2920
|
+
const type = depth[target];
|
2921
|
+
|
2922
|
+
// different loop types have different branch offsets
|
2923
|
+
// as they have different wasm block/loop/if structures
|
2924
|
+
// we need to use the right offset by type to branch to the one we want
|
2925
|
+
// for a break: exit the loop without executing anything else inside it
|
2926
|
+
const offset = ({
|
2927
|
+
for: 2, // loop > if (wanted branch) > block (we are here)
|
2928
|
+
while: 2, // loop > if (wanted branch) (we are here)
|
2929
|
+
dowhile: 2, // loop > block (wanted branch) > block (we are here)
|
2930
|
+
forof: 2, // loop > block (wanted branch) > block (we are here)
|
2931
|
+
if: 1 // break inside if, branch 0 to skip the rest of the if
|
2932
|
+
})[type];
|
2933
|
+
|
2511
2934
|
return [
|
2512
|
-
[ Opcodes.br, ...signedLEB128(
|
2935
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2513
2936
|
];
|
2514
2937
|
};
|
2515
2938
|
|
2516
2939
|
const generateContinue = (scope, decl) => {
|
2517
|
-
const
|
2940
|
+
const target = decl.label ? scope.labels.get(decl.label.name) : getNearestLoop();
|
2941
|
+
const type = depth[target];
|
2942
|
+
|
2943
|
+
// different loop types have different branch offsets
|
2944
|
+
// as they have different wasm block/loop/if structures
|
2945
|
+
// we need to use the right offset by type to branch to the one we want
|
2946
|
+
// for a continue: do test for the loop, and then loop depending on that success
|
2947
|
+
const offset = ({
|
2948
|
+
for: 3, // loop (wanted branch) > if > block (we are here)
|
2949
|
+
while: 1, // loop (wanted branch) > if (we are here)
|
2950
|
+
dowhile: 3, // loop > block > block (wanted branch) (we are here)
|
2951
|
+
forof: 3 // loop > block > block (wanted branch) (we are here)
|
2952
|
+
})[type];
|
2953
|
+
|
2518
2954
|
return [
|
2519
|
-
[ Opcodes.br, ...signedLEB128(
|
2955
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2520
2956
|
];
|
2521
2957
|
};
|
2522
2958
|
|
2959
|
+
const generateLabel = (scope, decl) => {
|
2960
|
+
scope.labels ??= new Map();
|
2961
|
+
|
2962
|
+
const name = decl.label.name;
|
2963
|
+
scope.labels.set(name, depth.length);
|
2964
|
+
|
2965
|
+
return generate(scope, decl.body);
|
2966
|
+
};
|
2967
|
+
|
2523
2968
|
const generateThrow = (scope, decl) => {
|
2524
2969
|
scope.throws = true;
|
2525
2970
|
|
@@ -2540,6 +2985,9 @@ const generateThrow = (scope, decl) => {
|
|
2540
2985
|
let exceptId = exceptions.push({ constructor, message }) - 1;
|
2541
2986
|
let tagIdx = tags[0].idx;
|
2542
2987
|
|
2988
|
+
scope.exceptions ??= [];
|
2989
|
+
scope.exceptions.push(exceptId);
|
2990
|
+
|
2543
2991
|
// todo: write a description of how this works lol
|
2544
2992
|
|
2545
2993
|
return [
|
@@ -2549,7 +2997,7 @@ const generateThrow = (scope, decl) => {
|
|
2549
2997
|
};
|
2550
2998
|
|
2551
2999
|
const generateTry = (scope, decl) => {
|
2552
|
-
if (decl.finalizer) return todo('try finally not implemented yet');
|
3000
|
+
if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
|
2553
3001
|
|
2554
3002
|
const out = [];
|
2555
3003
|
|
@@ -2580,11 +3028,11 @@ const generateAssignPat = (scope, decl) => {
|
|
2580
3028
|
// TODO
|
2581
3029
|
// if identifier declared, use that
|
2582
3030
|
// else, use default (right)
|
2583
|
-
return todo('assignment pattern (optional arg)');
|
3031
|
+
return todo(scope, 'assignment pattern (optional arg)');
|
2584
3032
|
};
|
2585
3033
|
|
2586
3034
|
let pages = new Map();
|
2587
|
-
const allocPage = (reason, type) => {
|
3035
|
+
const allocPage = (scope, reason, type) => {
|
2588
3036
|
if (pages.has(reason)) return pages.get(reason).ind;
|
2589
3037
|
|
2590
3038
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
@@ -2595,16 +3043,20 @@ const allocPage = (reason, type) => {
|
|
2595
3043
|
const ind = pages.size;
|
2596
3044
|
pages.set(reason, { ind, type });
|
2597
3045
|
|
2598
|
-
|
3046
|
+
scope.pages ??= new Map();
|
3047
|
+
scope.pages.set(reason, { ind, type });
|
3048
|
+
|
3049
|
+
if (Prefs.allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
2599
3050
|
|
2600
3051
|
return ind;
|
2601
3052
|
};
|
2602
3053
|
|
3054
|
+
// todo: add scope.pages
|
2603
3055
|
const freePage = reason => {
|
2604
3056
|
const { ind } = pages.get(reason);
|
2605
3057
|
pages.delete(reason);
|
2606
3058
|
|
2607
|
-
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
3059
|
+
if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2608
3060
|
|
2609
3061
|
return ind;
|
2610
3062
|
};
|
@@ -2630,15 +3082,14 @@ const StoreOps = {
|
|
2630
3082
|
|
2631
3083
|
let data = [];
|
2632
3084
|
|
2633
|
-
const compileBytes = (val, itemType
|
3085
|
+
const compileBytes = (val, itemType) => {
|
2634
3086
|
// todo: this is a mess and needs confirming / ????
|
2635
3087
|
switch (itemType) {
|
2636
3088
|
case 'i8': return [ val % 256 ];
|
2637
|
-
case 'i16': return [ val % 256,
|
2638
|
-
|
2639
|
-
case 'i32':
|
2640
|
-
|
2641
|
-
return enforceFourBytes(signedLEB128(val));
|
3089
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3090
|
+
case 'i16': return [ val % 256, (val / 256 | 0) % 256 ];
|
3091
|
+
case 'i32': return [...new Uint8Array(new Int32Array([ val ]).buffer)];
|
3092
|
+
// todo: i64
|
2642
3093
|
|
2643
3094
|
case 'f64': return ieee754_binary64(val);
|
2644
3095
|
}
|
@@ -2656,16 +3107,22 @@ const getAllocType = itemType => {
|
|
2656
3107
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2657
3108
|
const out = [];
|
2658
3109
|
|
3110
|
+
scope.arrays ??= new Map();
|
3111
|
+
|
2659
3112
|
let firstAssign = false;
|
2660
|
-
if (!arrays.has(name) || name === '$undeclared') {
|
3113
|
+
if (!scope.arrays.has(name) || name === '$undeclared') {
|
2661
3114
|
firstAssign = true;
|
2662
3115
|
|
2663
3116
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2664
3117
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2665
|
-
|
3118
|
+
|
3119
|
+
if (Prefs.scopedPageNames) scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType) * pageSize);
|
3120
|
+
else scope.arrays.set(name, allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2666
3121
|
}
|
2667
3122
|
|
2668
|
-
const pointer = arrays.get(name);
|
3123
|
+
const pointer = scope.arrays.get(name);
|
3124
|
+
|
3125
|
+
const local = global ? globals[name] : scope.locals[name];
|
2669
3126
|
|
2670
3127
|
const useRawElements = !!decl.rawElements;
|
2671
3128
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
@@ -2673,19 +3130,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2673
3130
|
const valtype = itemTypeToValtype[itemType];
|
2674
3131
|
const length = elements.length;
|
2675
3132
|
|
2676
|
-
if (firstAssign && useRawElements) {
|
2677
|
-
|
3133
|
+
if (firstAssign && useRawElements && !Prefs.noData) {
|
3134
|
+
// if length is 0 memory/data will just be 0000... anyway
|
3135
|
+
if (length !== 0) {
|
3136
|
+
let bytes = compileBytes(length, 'i32');
|
2678
3137
|
|
2679
|
-
|
2680
|
-
|
3138
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
3139
|
+
if (elements[i] == null) continue;
|
2681
3140
|
|
2682
|
-
|
2683
|
-
|
3141
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
3142
|
+
}
|
2684
3143
|
|
2685
|
-
|
2686
|
-
|
2687
|
-
|
2688
|
-
|
3144
|
+
const ind = data.push({
|
3145
|
+
offset: pointer,
|
3146
|
+
bytes
|
3147
|
+
}) - 1;
|
3148
|
+
|
3149
|
+
scope.data ??= [];
|
3150
|
+
scope.data.push(ind);
|
3151
|
+
}
|
2689
3152
|
|
2690
3153
|
// local value as pointer
|
2691
3154
|
out.push(...number(pointer));
|
@@ -2693,11 +3156,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2693
3156
|
return [ out, pointer ];
|
2694
3157
|
}
|
2695
3158
|
|
3159
|
+
const pointerTmp = local != null ? localTmp(scope, '#makearray_pointer_tmp', Valtype.i32) : null;
|
3160
|
+
if (pointerTmp != null) {
|
3161
|
+
out.push(
|
3162
|
+
[ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
3163
|
+
Opcodes.i32_to_u,
|
3164
|
+
[ Opcodes.local_set, pointerTmp ]
|
3165
|
+
);
|
3166
|
+
}
|
3167
|
+
|
3168
|
+
const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
|
3169
|
+
|
2696
3170
|
// store length as 0th array
|
2697
3171
|
out.push(
|
2698
|
-
...
|
3172
|
+
...pointerWasm,
|
2699
3173
|
...number(length, Valtype.i32),
|
2700
|
-
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1,
|
3174
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
|
2701
3175
|
);
|
2702
3176
|
|
2703
3177
|
const storeOp = StoreOps[itemType];
|
@@ -2706,20 +3180,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2706
3180
|
if (elements[i] == null) continue;
|
2707
3181
|
|
2708
3182
|
out.push(
|
2709
|
-
...
|
3183
|
+
...pointerWasm,
|
2710
3184
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2711
|
-
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(
|
3185
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2712
3186
|
);
|
2713
3187
|
}
|
2714
3188
|
|
2715
3189
|
// local value as pointer
|
2716
|
-
out.push(...
|
3190
|
+
out.push(...pointerWasm, Opcodes.i32_from_u);
|
2717
3191
|
|
2718
3192
|
return [ out, pointer ];
|
2719
3193
|
};
|
2720
3194
|
|
2721
3195
|
const byteStringable = str => {
|
2722
|
-
if (!
|
3196
|
+
if (!Prefs.bytestring) return false;
|
2723
3197
|
|
2724
3198
|
for (let i = 0; i < str.length; i++) {
|
2725
3199
|
if (str.charCodeAt(i) > 0xFF) return false;
|
@@ -2728,9 +3202,9 @@ const byteStringable = str => {
|
|
2728
3202
|
return true;
|
2729
3203
|
};
|
2730
3204
|
|
2731
|
-
const makeString = (scope, str, global = false, name = '$undeclared') => {
|
3205
|
+
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2732
3206
|
const rawElements = new Array(str.length);
|
2733
|
-
let byteStringable =
|
3207
|
+
let byteStringable = Prefs.bytestring;
|
2734
3208
|
for (let i = 0; i < str.length; i++) {
|
2735
3209
|
const c = str.charCodeAt(i);
|
2736
3210
|
rawElements[i] = c;
|
@@ -2738,25 +3212,36 @@ const makeString = (scope, str, global = false, name = '$undeclared') => {
|
|
2738
3212
|
if (byteStringable && c > 0xFF) byteStringable = false;
|
2739
3213
|
}
|
2740
3214
|
|
3215
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
3216
|
+
|
2741
3217
|
return makeArray(scope, {
|
2742
3218
|
rawElements
|
2743
3219
|
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2744
3220
|
};
|
2745
3221
|
|
2746
|
-
let arrays = new Map();
|
2747
3222
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
2748
3223
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
2749
3224
|
};
|
2750
3225
|
|
2751
3226
|
export const generateMember = (scope, decl, _global, _name) => {
|
2752
3227
|
const name = decl.object.name;
|
2753
|
-
const pointer = arrays
|
3228
|
+
const pointer = scope.arrays?.get(name);
|
2754
3229
|
|
2755
|
-
const aotPointer = pointer != null;
|
3230
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2756
3231
|
|
2757
3232
|
// hack: .length
|
2758
3233
|
if (decl.property.name === 'length') {
|
2759
|
-
|
3234
|
+
const func = funcs.find(x => x.name === name);
|
3235
|
+
if (func) {
|
3236
|
+
const userFunc = funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name];
|
3237
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
3238
|
+
return number(typedParams ? func.params.length / 2 : func.params.length);
|
3239
|
+
}
|
3240
|
+
|
3241
|
+
if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
|
3242
|
+
if (importedFuncs[name]) return number(importedFuncs[name].params);
|
3243
|
+
if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
|
3244
|
+
|
2760
3245
|
return [
|
2761
3246
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2762
3247
|
...generate(scope, decl.object),
|
@@ -2800,7 +3285,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2800
3285
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2801
3286
|
|
2802
3287
|
...number(TYPES.number, Valtype.i32),
|
2803
|
-
setLastType(scope)
|
3288
|
+
...setLastType(scope)
|
2804
3289
|
],
|
2805
3290
|
|
2806
3291
|
[TYPES.string]: [
|
@@ -2832,7 +3317,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2832
3317
|
...number(newPointer),
|
2833
3318
|
|
2834
3319
|
...number(TYPES.string, Valtype.i32),
|
2835
|
-
setLastType(scope)
|
3320
|
+
...setLastType(scope)
|
2836
3321
|
],
|
2837
3322
|
[TYPES._bytestring]: [
|
2838
3323
|
// setup new/out array
|
@@ -2851,19 +3336,19 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2851
3336
|
]),
|
2852
3337
|
|
2853
3338
|
// load current string ind {arg}
|
2854
|
-
[ Opcodes.i32_load8_u,
|
3339
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2855
3340
|
|
2856
3341
|
// store to new string ind 0
|
2857
|
-
[ Opcodes.i32_store8,
|
3342
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2858
3343
|
|
2859
3344
|
// return new string (page)
|
2860
3345
|
...number(newPointer),
|
2861
3346
|
|
2862
3347
|
...number(TYPES._bytestring, Valtype.i32),
|
2863
|
-
setLastType(scope)
|
3348
|
+
...setLastType(scope)
|
2864
3349
|
],
|
2865
3350
|
|
2866
|
-
default:
|
3351
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
|
2867
3352
|
});
|
2868
3353
|
};
|
2869
3354
|
|
@@ -2873,28 +3358,36 @@ const objectHack = node => {
|
|
2873
3358
|
if (!node) return node;
|
2874
3359
|
|
2875
3360
|
if (node.type === 'MemberExpression') {
|
2876
|
-
|
3361
|
+
const out = (() => {
|
3362
|
+
if (node.computed || node.optional) return;
|
2877
3363
|
|
2878
|
-
|
3364
|
+
let objectName = node.object.name;
|
2879
3365
|
|
2880
|
-
|
2881
|
-
|
3366
|
+
// if object is not identifier or another member exp, give up
|
3367
|
+
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return;
|
3368
|
+
if (objectName && ['undefined', 'null', 'NaN', 'Infinity'].includes(objectName)) return;
|
2882
3369
|
|
2883
|
-
|
3370
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2884
3371
|
|
2885
|
-
|
2886
|
-
|
3372
|
+
// if .length, give up (hack within a hack!)
|
3373
|
+
if (node.property.name === 'length') {
|
3374
|
+
node.object = objectHack(node.object);
|
3375
|
+
return;
|
3376
|
+
}
|
2887
3377
|
|
2888
|
-
|
2889
|
-
|
3378
|
+
// no object name, give up
|
3379
|
+
if (!objectName) return;
|
2890
3380
|
|
2891
|
-
|
2892
|
-
|
3381
|
+
const name = '__' + objectName + '_' + node.property.name;
|
3382
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2893
3383
|
|
2894
|
-
|
2895
|
-
|
2896
|
-
|
2897
|
-
|
3384
|
+
return {
|
3385
|
+
type: 'Identifier',
|
3386
|
+
name
|
3387
|
+
};
|
3388
|
+
})();
|
3389
|
+
|
3390
|
+
if (out) return out;
|
2898
3391
|
}
|
2899
3392
|
|
2900
3393
|
for (const x in node) {
|
@@ -2908,8 +3401,8 @@ const objectHack = node => {
|
|
2908
3401
|
};
|
2909
3402
|
|
2910
3403
|
const generateFunc = (scope, decl) => {
|
2911
|
-
if (decl.async) return todo('async functions are not supported');
|
2912
|
-
if (decl.generator) return todo('generator functions are not supported');
|
3404
|
+
if (decl.async) return todo(scope, 'async functions are not supported');
|
3405
|
+
if (decl.generator) return todo(scope, 'generator functions are not supported');
|
2913
3406
|
|
2914
3407
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2915
3408
|
const params = decl.params ?? [];
|
@@ -2925,6 +3418,11 @@ const generateFunc = (scope, decl) => {
|
|
2925
3418
|
name
|
2926
3419
|
};
|
2927
3420
|
|
3421
|
+
if (typedInput && decl.returnType) {
|
3422
|
+
innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
|
3423
|
+
innerScope.returns = [ valtypeBinary ];
|
3424
|
+
}
|
3425
|
+
|
2928
3426
|
for (let i = 0; i < params.length; i++) {
|
2929
3427
|
allocVar(innerScope, params[i].name, false);
|
2930
3428
|
|
@@ -2951,6 +3449,8 @@ const generateFunc = (scope, decl) => {
|
|
2951
3449
|
};
|
2952
3450
|
funcIndex[name] = func.index;
|
2953
3451
|
|
3452
|
+
if (name === 'main') func.gotLastType = true;
|
3453
|
+
|
2954
3454
|
// quick hack fixes
|
2955
3455
|
for (const inst of wasm) {
|
2956
3456
|
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
@@ -3002,7 +3502,7 @@ const internalConstrs = {
|
|
3002
3502
|
|
3003
3503
|
// todo: check in wasm instead of here
|
3004
3504
|
const literalValue = arg.value ?? 0;
|
3005
|
-
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length');
|
3505
|
+
if (literalValue < 0 || !Number.isFinite(literalValue) || literalValue > 4294967295) return internalThrow(scope, 'RangeThrow', 'Invalid array length', true);
|
3006
3506
|
|
3007
3507
|
return [
|
3008
3508
|
...number(0, Valtype.i32),
|
@@ -3013,7 +3513,8 @@ const internalConstrs = {
|
|
3013
3513
|
...number(pointer)
|
3014
3514
|
];
|
3015
3515
|
},
|
3016
|
-
type: TYPES._array
|
3516
|
+
type: TYPES._array,
|
3517
|
+
length: 1
|
3017
3518
|
},
|
3018
3519
|
|
3019
3520
|
__Array_of: {
|
@@ -3025,7 +3526,131 @@ const internalConstrs = {
|
|
3025
3526
|
}, global, name);
|
3026
3527
|
},
|
3027
3528
|
type: TYPES._array,
|
3529
|
+
notConstr: true,
|
3530
|
+
length: 0
|
3531
|
+
},
|
3532
|
+
|
3533
|
+
__Porffor_fastOr: {
|
3534
|
+
generate: (scope, decl) => {
|
3535
|
+
const out = [];
|
3536
|
+
|
3537
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3538
|
+
out.push(
|
3539
|
+
...generate(scope, decl.arguments[i]),
|
3540
|
+
Opcodes.i32_to_u,
|
3541
|
+
...(i > 0 ? [ [ Opcodes.i32_or ] ] : [])
|
3542
|
+
);
|
3543
|
+
}
|
3544
|
+
|
3545
|
+
out.push(Opcodes.i32_from_u);
|
3546
|
+
|
3547
|
+
return out;
|
3548
|
+
},
|
3549
|
+
type: TYPES.boolean,
|
3028
3550
|
notConstr: true
|
3551
|
+
},
|
3552
|
+
|
3553
|
+
__Porffor_fastAnd: {
|
3554
|
+
generate: (scope, decl) => {
|
3555
|
+
const out = [];
|
3556
|
+
|
3557
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3558
|
+
out.push(
|
3559
|
+
...generate(scope, decl.arguments[i]),
|
3560
|
+
Opcodes.i32_to_u,
|
3561
|
+
...(i > 0 ? [ [ Opcodes.i32_and ] ] : [])
|
3562
|
+
);
|
3563
|
+
}
|
3564
|
+
|
3565
|
+
out.push(Opcodes.i32_from_u);
|
3566
|
+
|
3567
|
+
return out;
|
3568
|
+
},
|
3569
|
+
type: TYPES.boolean,
|
3570
|
+
notConstr: true
|
3571
|
+
},
|
3572
|
+
|
3573
|
+
Boolean: {
|
3574
|
+
generate: (scope, decl) => {
|
3575
|
+
// todo: boolean object when used as constructor
|
3576
|
+
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
3577
|
+
return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
|
3578
|
+
},
|
3579
|
+
type: TYPES.boolean,
|
3580
|
+
length: 1
|
3581
|
+
},
|
3582
|
+
|
3583
|
+
__Math_max: {
|
3584
|
+
generate: (scope, decl) => {
|
3585
|
+
const out = [
|
3586
|
+
...number(-Infinity)
|
3587
|
+
];
|
3588
|
+
|
3589
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3590
|
+
out.push(
|
3591
|
+
...generate(scope, decl.arguments[i]),
|
3592
|
+
[ Opcodes.f64_max ]
|
3593
|
+
);
|
3594
|
+
}
|
3595
|
+
|
3596
|
+
return out;
|
3597
|
+
},
|
3598
|
+
type: TYPES.number,
|
3599
|
+
notConstr: true,
|
3600
|
+
length: 2
|
3601
|
+
},
|
3602
|
+
|
3603
|
+
__Math_min: {
|
3604
|
+
generate: (scope, decl) => {
|
3605
|
+
const out = [
|
3606
|
+
...number(Infinity)
|
3607
|
+
];
|
3608
|
+
|
3609
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3610
|
+
out.push(
|
3611
|
+
...generate(scope, decl.arguments[i]),
|
3612
|
+
[ Opcodes.f64_min ]
|
3613
|
+
);
|
3614
|
+
}
|
3615
|
+
|
3616
|
+
return out;
|
3617
|
+
},
|
3618
|
+
type: TYPES.number,
|
3619
|
+
notConstr: true,
|
3620
|
+
length: 2
|
3621
|
+
},
|
3622
|
+
|
3623
|
+
__console_log: {
|
3624
|
+
generate: (scope, decl) => {
|
3625
|
+
const out = [];
|
3626
|
+
|
3627
|
+
for (let i = 0; i < decl.arguments.length; i++) {
|
3628
|
+
out.push(
|
3629
|
+
...generateCall(scope, {
|
3630
|
+
callee: {
|
3631
|
+
type: 'Identifier',
|
3632
|
+
name: '__Porffor_print'
|
3633
|
+
},
|
3634
|
+
arguments: [ decl.arguments[i] ]
|
3635
|
+
}),
|
3636
|
+
|
3637
|
+
// print space
|
3638
|
+
...number(32),
|
3639
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3640
|
+
);
|
3641
|
+
}
|
3642
|
+
|
3643
|
+
// print newline
|
3644
|
+
out.push(
|
3645
|
+
...number(10),
|
3646
|
+
[ Opcodes.call, importedFuncs.printChar ]
|
3647
|
+
);
|
3648
|
+
|
3649
|
+
return out;
|
3650
|
+
},
|
3651
|
+
type: TYPES.undefined,
|
3652
|
+
notConstr: true,
|
3653
|
+
length: 0
|
3029
3654
|
}
|
3030
3655
|
};
|
3031
3656
|
|
@@ -3054,20 +3679,23 @@ export default program => {
|
|
3054
3679
|
funcs = [];
|
3055
3680
|
funcIndex = {};
|
3056
3681
|
depth = [];
|
3057
|
-
arrays = new Map();
|
3058
3682
|
pages = new Map();
|
3059
3683
|
data = [];
|
3060
3684
|
currentFuncIndex = importedFuncs.length;
|
3061
3685
|
|
3062
3686
|
globalThis.valtype = 'f64';
|
3063
3687
|
|
3064
|
-
const valtypeOpt = process.argv.find(x => x.startsWith('
|
3688
|
+
const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
|
3065
3689
|
if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
|
3066
3690
|
|
3067
3691
|
globalThis.valtypeBinary = Valtype[valtype];
|
3068
3692
|
|
3069
3693
|
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
3070
3694
|
|
3695
|
+
globalThis.pageSize = PageSize;
|
3696
|
+
const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
|
3697
|
+
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3698
|
+
|
3071
3699
|
// set generic opcodes for current valtype
|
3072
3700
|
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
3073
3701
|
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
@@ -3076,10 +3704,10 @@ export default program => {
|
|
3076
3704
|
Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
|
3077
3705
|
Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
|
3078
3706
|
|
3079
|
-
Opcodes.i32_to = [ [
|
3080
|
-
Opcodes.i32_to_u = [ [
|
3081
|
-
Opcodes.i32_from = [ [
|
3082
|
-
Opcodes.i32_from_u = [ [
|
3707
|
+
Opcodes.i32_to = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_s ][valtypeInd];
|
3708
|
+
Opcodes.i32_to_u = [ [], [ Opcodes.i32_wrap_i64 ], Opcodes.i32_trunc_sat_f64_u ][valtypeInd];
|
3709
|
+
Opcodes.i32_from = [ [], [ Opcodes.i64_extend_i32_s ], [ Opcodes.f64_convert_i32_s ] ][valtypeInd];
|
3710
|
+
Opcodes.i32_from_u = [ [], [ Opcodes.i64_extend_i32_u ], [ Opcodes.f64_convert_i32_u ] ][valtypeInd];
|
3083
3711
|
|
3084
3712
|
Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
|
3085
3713
|
Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
|
@@ -3092,10 +3720,6 @@ export default program => {
|
|
3092
3720
|
|
3093
3721
|
program.id = { name: 'main' };
|
3094
3722
|
|
3095
|
-
globalThis.pageSize = PageSize;
|
3096
|
-
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
3097
|
-
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3098
|
-
|
3099
3723
|
const scope = {
|
3100
3724
|
locals: {},
|
3101
3725
|
localInd: 0
|
@@ -3106,7 +3730,7 @@ export default program => {
|
|
3106
3730
|
body: program.body
|
3107
3731
|
};
|
3108
3732
|
|
3109
|
-
if (
|
3733
|
+
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
3110
3734
|
|
3111
3735
|
generateFunc(scope, program);
|
3112
3736
|
|
@@ -3123,7 +3747,11 @@ export default program => {
|
|
3123
3747
|
}
|
3124
3748
|
|
3125
3749
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
3126
|
-
|
3750
|
+
if (lastInst[0] === Opcodes.local_set && lastInst[1] === main.locals['#last_type'].idx) {
|
3751
|
+
main.wasm.splice(main.wasm.length - 1, 1);
|
3752
|
+
} else {
|
3753
|
+
main.returns = [];
|
3754
|
+
}
|
3127
3755
|
}
|
3128
3756
|
|
3129
3757
|
if (lastInst[0] === Opcodes.call) {
|