porffor 0.2.0-f2bbe1f → 0.2.0-f647e42
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} +1153 -418
- 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 +51 -36
- package/compiler/parse.js +33 -23
- package/compiler/precompile.js +128 -0
- package/compiler/prefs.js +27 -0
- package/compiler/prototype.js +177 -37
- package/compiler/types.js +37 -0
- package/compiler/wasmSpec.js +30 -7
- package/compiler/wrap.js +56 -40
- 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,39 +25,41 @@ const debug = str => {
|
|
23
25
|
const logChar = n => {
|
24
26
|
code.push(...number(n));
|
25
27
|
|
26
|
-
code.push(Opcodes.call);
|
27
|
-
code.push(...unsignedLEB128(0));
|
28
|
+
code.push([ Opcodes.call, 0 ]);
|
28
29
|
};
|
29
30
|
|
30
31
|
for (let i = 0; i < str.length; i++) {
|
31
32
|
logChar(str.charCodeAt(i));
|
32
33
|
}
|
33
34
|
|
34
|
-
logChar(
|
35
|
+
logChar(10); // new line
|
35
36
|
|
36
37
|
return code;
|
37
38
|
};
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
this.name = 'TodoError';
|
44
|
-
}
|
40
|
+
class TodoError extends Error {
|
41
|
+
constructor(message) {
|
42
|
+
super(message);
|
43
|
+
this.name = 'TodoError';
|
45
44
|
}
|
45
|
+
}
|
46
|
+
const todo = (scope, msg, expectsValue = undefined) => {
|
47
|
+
switch (Prefs.todoTime ?? 'runtime') {
|
48
|
+
case 'compile':
|
49
|
+
throw new TodoError(msg);
|
46
50
|
|
47
|
-
|
48
|
-
|
49
|
-
const code = [];
|
50
|
-
|
51
|
-
code.push(...debug(`todo! ` + msg));
|
52
|
-
code.push(Opcodes.unreachable);
|
51
|
+
case 'runtime':
|
52
|
+
return internalThrow(scope, 'TodoError', msg, expectsValue);
|
53
53
|
|
54
|
-
|
54
|
+
// return [
|
55
|
+
// ...debug(`todo! ${msg}`),
|
56
|
+
// [ Opcodes.unreachable ]
|
57
|
+
// ];
|
58
|
+
}
|
55
59
|
};
|
56
60
|
|
57
61
|
const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
|
58
|
-
const generate = (scope, decl, global = false, name = undefined) => {
|
62
|
+
const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
|
59
63
|
switch (decl.type) {
|
60
64
|
case 'BinaryExpression':
|
61
65
|
return generateBinaryExp(scope, decl, global, name);
|
@@ -86,7 +90,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
86
90
|
return generateExp(scope, decl);
|
87
91
|
|
88
92
|
case 'CallExpression':
|
89
|
-
return generateCall(scope, decl, global, name);
|
93
|
+
return generateCall(scope, decl, global, name, valueUnused);
|
90
94
|
|
91
95
|
case 'NewExpression':
|
92
96
|
return generateNew(scope, decl, global, name);
|
@@ -104,7 +108,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
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) => {
|
|
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) => {
|
|
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) => {
|
|
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) => {
|
|
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) => {
|
|
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) => {
|
|
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;
|
216
243
|
|
217
|
-
|
218
|
-
|
244
|
+
for (let i = 0; i < expressions.length; i++) {
|
245
|
+
const e = expressions[i];
|
246
|
+
if (!e.name) {
|
247
|
+
if (e.type === 'BinaryExpression' && e.operator === '+' && e.left.type === 'Identifier' && e.right.type === 'Literal') {
|
248
|
+
str += lookupName(scope, e.left.name)[0].idx + e.right.value;
|
249
|
+
} else todo(scope, 'unsupported expression in intrinsic');
|
250
|
+
} else str += lookupName(scope, e.name)[0].idx;
|
251
|
+
|
252
|
+
str += quasis[i + 1].value.raw;
|
253
|
+
}
|
254
|
+
|
255
|
+
return funcs[func](str);
|
219
256
|
}
|
220
257
|
|
221
258
|
default:
|
222
|
-
|
223
|
-
|
259
|
+
// ignore typescript nodes
|
260
|
+
if (decl.type.startsWith('TS') ||
|
261
|
+
decl.type === 'ImportDeclaration' && decl.importKind === 'type') {
|
224
262
|
return [];
|
225
263
|
}
|
226
264
|
|
227
|
-
return todo(`no generation for ${decl.type}!`);
|
265
|
+
return todo(scope, `no generation for ${decl.type}!`);
|
228
266
|
}
|
229
267
|
};
|
230
268
|
|
@@ -252,7 +290,7 @@ const lookupName = (scope, _name) => {
|
|
252
290
|
return [ undefined, undefined ];
|
253
291
|
};
|
254
292
|
|
255
|
-
const internalThrow = (scope, constructor, message, expectsValue =
|
293
|
+
const internalThrow = (scope, constructor, message, expectsValue = Prefs.alwaysValueInternalThrows) => [
|
256
294
|
...generateThrow(scope, {
|
257
295
|
argument: {
|
258
296
|
type: 'NewExpression',
|
@@ -274,25 +312,33 @@ const generateIdent = (scope, decl) => {
|
|
274
312
|
const name = mapName(rawName);
|
275
313
|
let local = scope.locals[rawName];
|
276
314
|
|
277
|
-
if (builtinVars
|
315
|
+
if (Object.hasOwn(builtinVars, name)) {
|
278
316
|
if (builtinVars[name].floatOnly && valtype[0] === 'i') throw new Error(`Cannot use ${unhackName(name)} with integer valtype`);
|
279
|
-
|
317
|
+
|
318
|
+
let wasm = builtinVars[name];
|
319
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name });
|
320
|
+
return wasm.slice();
|
280
321
|
}
|
281
322
|
|
282
|
-
if (builtinFuncs
|
323
|
+
if (Object.hasOwn(builtinFuncs, name) || Object.hasOwn(internalConstrs, name)) {
|
283
324
|
// todo: return an actual something
|
284
325
|
return number(1);
|
285
326
|
}
|
286
327
|
|
287
|
-
if (
|
328
|
+
if (isExistingProtoFunc(name)) {
|
329
|
+
// todo: return an actual something
|
330
|
+
return number(1);
|
331
|
+
}
|
332
|
+
|
333
|
+
if (local?.idx === undefined) {
|
288
334
|
// no local var with name
|
289
|
-
if (
|
290
|
-
if (funcIndex
|
335
|
+
if (Object.hasOwn(importedFuncs, name)) return number(importedFuncs[name]);
|
336
|
+
if (Object.hasOwn(funcIndex, name)) return number(funcIndex[name]);
|
291
337
|
|
292
|
-
if (globals
|
338
|
+
if (Object.hasOwn(globals, name)) return [ [ Opcodes.global_get, globals[name].idx ] ];
|
293
339
|
}
|
294
340
|
|
295
|
-
if (local === undefined && rawName.startsWith('__')) {
|
341
|
+
if (local?.idx === undefined && rawName.startsWith('__')) {
|
296
342
|
// return undefined if unknown key in already known var
|
297
343
|
let parent = rawName.slice(2).split('_').slice(0, -1).join('_');
|
298
344
|
if (parent.includes('_')) parent = '__' + parent;
|
@@ -301,7 +347,7 @@ const generateIdent = (scope, decl) => {
|
|
301
347
|
if (!parentLookup[1]) return number(UNDEFINED);
|
302
348
|
}
|
303
349
|
|
304
|
-
if (local === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
350
|
+
if (local?.idx === undefined) return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
305
351
|
|
306
352
|
return [ [ Opcodes.local_get, local.idx ] ];
|
307
353
|
};
|
@@ -314,14 +360,18 @@ const generateReturn = (scope, decl) => {
|
|
314
360
|
// just bare "return"
|
315
361
|
return [
|
316
362
|
...number(UNDEFINED), // "undefined" if func returns
|
317
|
-
...
|
363
|
+
...(scope.returnType != null ? [] : [
|
364
|
+
...number(TYPES.undefined, Valtype.i32) // type undefined
|
365
|
+
]),
|
318
366
|
[ Opcodes.return ]
|
319
367
|
];
|
320
368
|
}
|
321
369
|
|
322
370
|
return [
|
323
371
|
...generate(scope, decl.argument),
|
324
|
-
...
|
372
|
+
...(scope.returnType != null ? [] : [
|
373
|
+
...getNodeType(scope, decl.argument)
|
374
|
+
]),
|
325
375
|
[ Opcodes.return ]
|
326
376
|
];
|
327
377
|
};
|
@@ -335,7 +385,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
335
385
|
return idx;
|
336
386
|
};
|
337
387
|
|
338
|
-
const isIntOp = op => op && (op[0] >=
|
388
|
+
const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
|
389
|
+
const isFloatToIntOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
|
339
390
|
|
340
391
|
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
341
392
|
const checks = {
|
@@ -344,7 +395,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
344
395
|
'??': nullish
|
345
396
|
};
|
346
397
|
|
347
|
-
if (!checks[op]) return todo(`logic operator ${op} not implemented yet
|
398
|
+
if (!checks[op]) return todo(scope, `logic operator ${op} not implemented yet`, true);
|
348
399
|
|
349
400
|
// generic structure for {a} OP {b}
|
350
401
|
// -->
|
@@ -352,8 +403,8 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
352
403
|
|
353
404
|
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
354
405
|
// (like if we are in an if condition - very common)
|
355
|
-
const leftIsInt =
|
356
|
-
const rightIsInt =
|
406
|
+
const leftIsInt = isFloatToIntOp(left[left.length - 1]);
|
407
|
+
const rightIsInt = isFloatToIntOp(right[right.length - 1]);
|
357
408
|
|
358
409
|
const canInt = leftIsInt && rightIsInt;
|
359
410
|
|
@@ -370,12 +421,12 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
370
421
|
...right,
|
371
422
|
// note type
|
372
423
|
...rightType,
|
373
|
-
setLastType(scope),
|
424
|
+
...setLastType(scope),
|
374
425
|
[ Opcodes.else ],
|
375
426
|
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
376
427
|
// note type
|
377
428
|
...leftType,
|
378
|
-
setLastType(scope),
|
429
|
+
...setLastType(scope),
|
379
430
|
[ Opcodes.end ],
|
380
431
|
Opcodes.i32_from
|
381
432
|
];
|
@@ -389,17 +440,17 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
389
440
|
...right,
|
390
441
|
// note type
|
391
442
|
...rightType,
|
392
|
-
setLastType(scope),
|
443
|
+
...setLastType(scope),
|
393
444
|
[ Opcodes.else ],
|
394
445
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
395
446
|
// note type
|
396
447
|
...leftType,
|
397
|
-
setLastType(scope),
|
448
|
+
...setLastType(scope),
|
398
449
|
[ Opcodes.end ]
|
399
450
|
];
|
400
451
|
};
|
401
452
|
|
402
|
-
const concatStrings = (scope, left, right, global, name, assign) => {
|
453
|
+
const concatStrings = (scope, left, right, global, name, assign = false, bytestrings = false) => {
|
403
454
|
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
404
455
|
// todo: convert left and right to strings if not
|
405
456
|
// todo: optimize by looking up names in arrays and using that if exists?
|
@@ -409,11 +460,8 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
409
460
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
410
461
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
411
462
|
|
412
|
-
|
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
|
@@ -685,16 +739,27 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
685
739
|
[ Opcodes.i32_eqz ], */
|
686
740
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
687
741
|
],
|
742
|
+
[TYPES._bytestring]: [ // duplicate of string
|
743
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
744
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
745
|
+
|
746
|
+
// get length
|
747
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
748
|
+
|
749
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
750
|
+
],
|
688
751
|
default: def
|
689
752
|
}, intOut ? Valtype.i32 : valtypeBinary)
|
690
753
|
];
|
691
754
|
};
|
692
755
|
|
693
756
|
const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
694
|
-
const
|
757
|
+
const useTmp = knownType(scope, type) == null;
|
758
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
759
|
+
|
695
760
|
return [
|
696
761
|
...wasm,
|
697
|
-
[ Opcodes.local_set, tmp ],
|
762
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
698
763
|
|
699
764
|
...typeSwitch(scope, type, {
|
700
765
|
[TYPES._array]: [
|
@@ -702,7 +767,18 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
702
767
|
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
703
768
|
],
|
704
769
|
[TYPES.string]: [
|
705
|
-
[ Opcodes.local_get, tmp ],
|
770
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
771
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
772
|
+
|
773
|
+
// get length
|
774
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
775
|
+
|
776
|
+
// if length == 0
|
777
|
+
[ Opcodes.i32_eqz ],
|
778
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
779
|
+
],
|
780
|
+
[TYPES._bytestring]: [ // duplicate of string
|
781
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
706
782
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
707
783
|
|
708
784
|
// get length
|
@@ -714,7 +790,7 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
714
790
|
],
|
715
791
|
default: [
|
716
792
|
// if value == 0
|
717
|
-
[ Opcodes.local_get, tmp ],
|
793
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
718
794
|
|
719
795
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
720
796
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -724,10 +800,12 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
724
800
|
};
|
725
801
|
|
726
802
|
const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
727
|
-
const
|
803
|
+
const useTmp = knownType(scope, type) == null;
|
804
|
+
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
805
|
+
|
728
806
|
return [
|
729
807
|
...wasm,
|
730
|
-
[ Opcodes.local_set, tmp ],
|
808
|
+
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
731
809
|
|
732
810
|
...typeSwitch(scope, type, {
|
733
811
|
[TYPES.undefined]: [
|
@@ -736,7 +814,7 @@ const nullish = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
736
814
|
],
|
737
815
|
[TYPES.object]: [
|
738
816
|
// object, null if == 0
|
739
|
-
[ Opcodes.local_get, tmp ],
|
817
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
740
818
|
|
741
819
|
...(intIn ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz ]),
|
742
820
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
@@ -765,11 +843,14 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
765
843
|
return performLogicOp(scope, op, left, right, leftType, rightType);
|
766
844
|
}
|
767
845
|
|
846
|
+
const knownLeft = knownType(scope, leftType);
|
847
|
+
const knownRight = knownType(scope, rightType);
|
848
|
+
|
768
849
|
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
769
850
|
const strictOp = op === '===' || op === '!==';
|
770
851
|
|
771
852
|
const startOut = [], endOut = [];
|
772
|
-
const
|
853
|
+
const finalize = out => startOut.concat(out, endOut);
|
773
854
|
|
774
855
|
// if strict (in)equal check types match
|
775
856
|
if (strictOp) {
|
@@ -814,31 +895,59 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
814
895
|
// todo: if equality op and an operand is undefined, return false
|
815
896
|
// todo: niche null hell with 0
|
816
897
|
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
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
|
+
}
|
842
951
|
|
843
952
|
let ops = operatorOpcode[valtype][op];
|
844
953
|
|
@@ -848,33 +957,69 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
848
957
|
includeBuiltin(scope, builtinName);
|
849
958
|
const idx = funcIndex[builtinName];
|
850
959
|
|
851
|
-
return
|
960
|
+
return finalize([
|
852
961
|
...left,
|
853
962
|
...right,
|
854
963
|
[ Opcodes.call, idx ]
|
855
964
|
]);
|
856
965
|
}
|
857
966
|
|
858
|
-
if (!ops) return todo(`operator ${op} not implemented yet
|
967
|
+
if (!ops) return todo(scope, `operator ${op} not implemented yet`, true);
|
859
968
|
|
860
969
|
if (!Array.isArray(ops)) ops = [ ops ];
|
861
970
|
ops = [ ops ];
|
862
971
|
|
863
972
|
let tmpLeft, tmpRight;
|
864
973
|
// if equal op, check if strings for compareStrings
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
// todo: intelligent partial skip later
|
870
|
-
// if neither known are string, stop this madness
|
871
|
-
if ((knownLeft != null && knownLeft !== TYPES.string) && (knownRight != null && knownRight !== TYPES.string)) {
|
872
|
-
return;
|
873
|
-
}
|
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
|
874
977
|
|
978
|
+
if ((op === '===' || op === '==' || op === '!==' || op === '!=') && (knownLeft == null && knownRight == null)) {
|
875
979
|
tmpLeft = localTmp(scope, '__tmpop_left');
|
876
980
|
tmpRight = localTmp(scope, '__tmpop_right');
|
877
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)
|
878
1023
|
ops.unshift(...stringOnly([
|
879
1024
|
// if left is string
|
880
1025
|
...leftType,
|
@@ -886,30 +1031,28 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
886
1031
|
...number(TYPES.string, Valtype.i32),
|
887
1032
|
[ Opcodes.i32_eq ],
|
888
1033
|
|
889
|
-
// if
|
890
|
-
[ Opcodes.
|
1034
|
+
// if both are true
|
1035
|
+
[ Opcodes.i32_and ],
|
891
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 ],
|
892
1041
|
|
893
|
-
//
|
894
|
-
// if left is not string
|
1042
|
+
// if left is bytestring
|
895
1043
|
...leftType,
|
896
|
-
...number(TYPES.
|
897
|
-
[ Opcodes.
|
1044
|
+
...number(TYPES._bytestring, Valtype.i32),
|
1045
|
+
[ Opcodes.i32_eq ],
|
898
1046
|
|
899
|
-
// if right is
|
1047
|
+
// if right is bytestring
|
900
1048
|
...rightType,
|
901
|
-
...number(TYPES.
|
902
|
-
[ Opcodes.
|
1049
|
+
...number(TYPES._bytestring, Valtype.i32),
|
1050
|
+
[ Opcodes.i32_eq ],
|
903
1051
|
|
904
|
-
// if
|
905
|
-
[ Opcodes.
|
1052
|
+
// if both are true
|
1053
|
+
[ Opcodes.i32_and ],
|
906
1054
|
[ Opcodes.if, Blocktype.void ],
|
907
|
-
...
|
908
|
-
[ Opcodes.br, 1 ],
|
909
|
-
[ Opcodes.end ],
|
910
|
-
|
911
|
-
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
912
|
-
// ...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
1055
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], true),
|
913
1056
|
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
914
1057
|
[ Opcodes.br, 1 ],
|
915
1058
|
[ Opcodes.end ],
|
@@ -921,9 +1064,9 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
921
1064
|
// endOut.push(stringOnly([ Opcodes.end ]));
|
922
1065
|
endOut.unshift(stringOnly([ Opcodes.end ]));
|
923
1066
|
// }
|
924
|
-
}
|
1067
|
+
}
|
925
1068
|
|
926
|
-
return
|
1069
|
+
return finalize([
|
927
1070
|
...left,
|
928
1071
|
...(tmpLeft != null ? stringOnly([ [ Opcodes.local_tee, tmpLeft ] ]) : []),
|
929
1072
|
...right,
|
@@ -940,7 +1083,22 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
940
1083
|
return out;
|
941
1084
|
};
|
942
1085
|
|
943
|
-
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 = [] }) => {
|
944
1102
|
const existing = funcs.find(x => x.name === name);
|
945
1103
|
if (existing) return existing;
|
946
1104
|
|
@@ -952,18 +1110,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
952
1110
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
953
1111
|
}
|
954
1112
|
|
955
|
-
|
956
|
-
const
|
957
|
-
|
958
|
-
|
959
|
-
locals,
|
960
|
-
returns,
|
961
|
-
localInd: allLocals.length,
|
962
|
-
};
|
963
|
-
|
964
|
-
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);
|
965
1117
|
}
|
966
1118
|
|
1119
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, { name, params, locals, returns, localInd: allLocals.length });
|
1120
|
+
|
967
1121
|
let baseGlobalIdx, i = 0;
|
968
1122
|
for (const type of globalTypes) {
|
969
1123
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -986,7 +1140,7 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
986
1140
|
params,
|
987
1141
|
locals,
|
988
1142
|
returns,
|
989
|
-
returnType:
|
1143
|
+
returnType: returnType ?? TYPES.number,
|
990
1144
|
wasm,
|
991
1145
|
internal: true,
|
992
1146
|
index: currentFuncIndex++
|
@@ -1009,6 +1163,7 @@ const generateLogicExp = (scope, decl) => {
|
|
1009
1163
|
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
1010
1164
|
};
|
1011
1165
|
|
1166
|
+
// potential future ideas for nan boxing (unused):
|
1012
1167
|
// T = JS type, V = value/pointer
|
1013
1168
|
// 0bTTT
|
1014
1169
|
// qNAN: 0 11111111111 1000000000000000000000000000000000000000000000000001
|
@@ -1032,47 +1187,29 @@ const generateLogicExp = (scope, decl) => {
|
|
1032
1187
|
// 4: internal type
|
1033
1188
|
// 5: pointer
|
1034
1189
|
|
1035
|
-
const
|
1036
|
-
|
1037
|
-
|
1038
|
-
string: 0x02,
|
1039
|
-
undefined: 0x03,
|
1040
|
-
object: 0x04,
|
1041
|
-
function: 0x05,
|
1042
|
-
symbol: 0x06,
|
1043
|
-
bigint: 0x07,
|
1190
|
+
const isExistingProtoFunc = name => {
|
1191
|
+
if (name.startsWith('__Array_prototype')) return !!prototypeFuncs[TYPES._array][name.slice(18)];
|
1192
|
+
if (name.startsWith('__String_prototype_')) return !!prototypeFuncs[TYPES.string][name.slice(19)];
|
1044
1193
|
|
1045
|
-
|
1046
|
-
_array: 0x10,
|
1047
|
-
_regexp: 0x11
|
1048
|
-
};
|
1049
|
-
|
1050
|
-
const TYPE_NAMES = {
|
1051
|
-
[TYPES.number]: 'Number',
|
1052
|
-
[TYPES.boolean]: 'Boolean',
|
1053
|
-
[TYPES.string]: 'String',
|
1054
|
-
[TYPES.undefined]: 'undefined',
|
1055
|
-
[TYPES.object]: 'Object',
|
1056
|
-
[TYPES.function]: 'Function',
|
1057
|
-
[TYPES.symbol]: 'Symbol',
|
1058
|
-
[TYPES.bigint]: 'BigInt',
|
1059
|
-
|
1060
|
-
[TYPES._array]: 'Array',
|
1061
|
-
[TYPES._regexp]: 'RegExp'
|
1194
|
+
return false;
|
1062
1195
|
};
|
1063
1196
|
|
1064
1197
|
const getType = (scope, _name) => {
|
1065
1198
|
const name = mapName(_name);
|
1066
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);
|
1067
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);
|
1068
1206
|
if (globals[name]) return [ [ Opcodes.global_get, globals[name + '#type'].idx ] ];
|
1069
1207
|
|
1070
1208
|
let type = TYPES.undefined;
|
1071
|
-
if (builtinVars[name]) type =
|
1209
|
+
if (builtinVars[name]) type = builtinVars[name].type ?? TYPES.number;
|
1072
1210
|
if (builtinFuncs[name] !== undefined || importedFuncs[name] !== undefined || funcIndex[name] !== undefined || internalConstrs[name] !== undefined) type = TYPES.function;
|
1073
1211
|
|
1074
|
-
if (name
|
1075
|
-
name.startsWith('__String_prototype_') && prototypeFuncs[TYPES.string][name.slice(19)]) type = TYPES.function;
|
1212
|
+
if (isExistingProtoFunc(name)) type = TYPES.function;
|
1076
1213
|
|
1077
1214
|
return number(type, Valtype.i32);
|
1078
1215
|
};
|
@@ -1095,15 +1232,16 @@ const setType = (scope, _name, type) => {
|
|
1095
1232
|
];
|
1096
1233
|
|
1097
1234
|
// throw new Error('could not find var');
|
1235
|
+
return [];
|
1098
1236
|
};
|
1099
1237
|
|
1100
1238
|
const getLastType = scope => {
|
1101
1239
|
scope.gotLastType = true;
|
1102
|
-
return [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ];
|
1240
|
+
return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1103
1241
|
};
|
1104
1242
|
|
1105
1243
|
const setLastType = scope => {
|
1106
|
-
return [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ];
|
1244
|
+
return [ [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1107
1245
|
};
|
1108
1246
|
|
1109
1247
|
const getNodeType = (scope, node) => {
|
@@ -1111,6 +1249,8 @@ const getNodeType = (scope, node) => {
|
|
1111
1249
|
if (node.type === 'Literal') {
|
1112
1250
|
if (node.regex) return TYPES._regexp;
|
1113
1251
|
|
1252
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
|
1253
|
+
|
1114
1254
|
return TYPES[typeof node.value];
|
1115
1255
|
}
|
1116
1256
|
|
@@ -1124,6 +1264,27 @@ const getNodeType = (scope, node) => {
|
|
1124
1264
|
|
1125
1265
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1126
1266
|
const name = node.callee.name;
|
1267
|
+
if (!name) {
|
1268
|
+
// iife
|
1269
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1270
|
+
|
1271
|
+
// presume
|
1272
|
+
// todo: warn here?
|
1273
|
+
return TYPES.number;
|
1274
|
+
}
|
1275
|
+
|
1276
|
+
if (node.type === 'NewExpression' && builtinFuncs[name + '$constructor']) {
|
1277
|
+
if (builtinFuncs[name + '$constructor'].typedReturns) {
|
1278
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1279
|
+
|
1280
|
+
// presume
|
1281
|
+
// todo: warn here?
|
1282
|
+
return TYPES.number;
|
1283
|
+
}
|
1284
|
+
|
1285
|
+
return builtinFuncs[name + '$constructor'].returnType ?? TYPES.number;
|
1286
|
+
}
|
1287
|
+
|
1127
1288
|
const func = funcs.find(x => x.name === name);
|
1128
1289
|
|
1129
1290
|
if (func) {
|
@@ -1131,7 +1292,7 @@ const getNodeType = (scope, node) => {
|
|
1131
1292
|
if (func.returnType) return func.returnType;
|
1132
1293
|
}
|
1133
1294
|
|
1134
|
-
if (builtinFuncs[name]) return
|
1295
|
+
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
1135
1296
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
1136
1297
|
|
1137
1298
|
// check if this is a prototype function
|
@@ -1142,11 +1303,16 @@ const getNodeType = (scope, node) => {
|
|
1142
1303
|
const spl = name.slice(2).split('_');
|
1143
1304
|
|
1144
1305
|
const func = spl[spl.length - 1];
|
1145
|
-
const protoFuncs = Object.
|
1306
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
|
1146
1307
|
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1147
1308
|
}
|
1148
1309
|
|
1149
|
-
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);
|
1150
1316
|
|
1151
1317
|
// presume
|
1152
1318
|
// todo: warn here?
|
@@ -1194,6 +1360,15 @@ const getNodeType = (scope, node) => {
|
|
1194
1360
|
|
1195
1361
|
if (node.type === 'BinaryExpression') {
|
1196
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
|
+
|
1197
1372
|
return TYPES.number;
|
1198
1373
|
|
1199
1374
|
// todo: string concat types
|
@@ -1218,7 +1393,7 @@ const getNodeType = (scope, node) => {
|
|
1218
1393
|
if (node.operator === '!') return TYPES.boolean;
|
1219
1394
|
if (node.operator === 'void') return TYPES.undefined;
|
1220
1395
|
if (node.operator === 'delete') return TYPES.boolean;
|
1221
|
-
if (node.operator === 'typeof') return TYPES.string;
|
1396
|
+
if (node.operator === 'typeof') return Prefs.bytestring ? TYPES._bytestring : TYPES.string;
|
1222
1397
|
|
1223
1398
|
return TYPES.number;
|
1224
1399
|
}
|
@@ -1227,11 +1402,23 @@ const getNodeType = (scope, node) => {
|
|
1227
1402
|
// hack: if something.length, number type
|
1228
1403
|
if (node.property.name === 'length') return TYPES.number;
|
1229
1404
|
|
1230
|
-
//
|
1405
|
+
// ts hack
|
1406
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
|
1407
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES._bytestring) return TYPES._bytestring;
|
1408
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
|
1409
|
+
|
1410
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1411
|
+
|
1412
|
+
// presume
|
1231
1413
|
return TYPES.number;
|
1232
1414
|
}
|
1233
1415
|
|
1234
|
-
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);
|
1235
1422
|
|
1236
1423
|
// presume
|
1237
1424
|
// todo: warn here?
|
@@ -1244,28 +1431,11 @@ const getNodeType = (scope, node) => {
|
|
1244
1431
|
return ret;
|
1245
1432
|
};
|
1246
1433
|
|
1247
|
-
const toString = (scope, wasm, type) => {
|
1248
|
-
const tmp = localTmp(scope, '#tostring_tmp');
|
1249
|
-
return [
|
1250
|
-
...wasm,
|
1251
|
-
[ Opcodes.local_set, tmp ],
|
1252
|
-
|
1253
|
-
...typeSwitch(scope, type, {
|
1254
|
-
[TYPES.string]: [
|
1255
|
-
[ Opcodes.local_get, tmp ]
|
1256
|
-
],
|
1257
|
-
[TYPES.undefined]: [
|
1258
|
-
// [ Opcodes.]
|
1259
|
-
]
|
1260
|
-
})
|
1261
|
-
]
|
1262
|
-
};
|
1263
|
-
|
1264
1434
|
const generateLiteral = (scope, decl, global, name) => {
|
1265
1435
|
if (decl.value === null) return number(NULL);
|
1266
1436
|
|
1437
|
+
// hack: just return 1 for regex literals
|
1267
1438
|
if (decl.regex) {
|
1268
|
-
scope.regex[name] = decl.regex;
|
1269
1439
|
return number(1);
|
1270
1440
|
}
|
1271
1441
|
|
@@ -1281,7 +1451,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1281
1451
|
return makeString(scope, decl.value, global, name);
|
1282
1452
|
|
1283
1453
|
default:
|
1284
|
-
return todo(`cannot generate literal of type ${typeof decl.value}
|
1454
|
+
return todo(scope, `cannot generate literal of type ${typeof decl.value}`, true);
|
1285
1455
|
}
|
1286
1456
|
};
|
1287
1457
|
|
@@ -1290,6 +1460,8 @@ const countLeftover = wasm => {
|
|
1290
1460
|
|
1291
1461
|
for (let i = 0; i < wasm.length; i++) {
|
1292
1462
|
const inst = wasm[i];
|
1463
|
+
if (inst[0] == null) continue;
|
1464
|
+
|
1293
1465
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
1294
1466
|
if (inst[0] === Opcodes.if) count--;
|
1295
1467
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1298,18 +1470,25 @@ const countLeftover = wasm => {
|
|
1298
1470
|
if (inst[0] === Opcodes.end) depth--;
|
1299
1471
|
|
1300
1472
|
if (depth === 0)
|
1301
|
-
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1302
|
-
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
1303
|
-
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1304
|
-
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
|
1473
|
+
if ([Opcodes.throw, Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1474
|
+
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
1475
|
+
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const, Opcodes.memory_size].includes(inst[0])) count++;
|
1476
|
+
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1305
1477
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1306
1478
|
else if (inst[0] === Opcodes.return) count = 0;
|
1307
1479
|
else if (inst[0] === Opcodes.call) {
|
1308
1480
|
let func = funcs.find(x => x.index === inst[1]);
|
1309
|
-
if (
|
1310
|
-
count
|
1311
|
-
} else
|
1312
|
-
|
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
|
+
}
|
1313
1492
|
} else count--;
|
1314
1493
|
|
1315
1494
|
// console.log(count, decompile([ inst ]).slice(0, -1));
|
@@ -1327,7 +1506,7 @@ const disposeLeftover = wasm => {
|
|
1327
1506
|
const generateExp = (scope, decl) => {
|
1328
1507
|
const expression = decl.expression;
|
1329
1508
|
|
1330
|
-
const out = generate(scope, expression);
|
1509
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1331
1510
|
disposeLeftover(out);
|
1332
1511
|
|
1333
1512
|
return out;
|
@@ -1385,7 +1564,7 @@ const RTArrayUtil = {
|
|
1385
1564
|
]
|
1386
1565
|
};
|
1387
1566
|
|
1388
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1567
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1389
1568
|
/* const callee = decl.callee;
|
1390
1569
|
const args = decl.arguments;
|
1391
1570
|
|
@@ -1401,10 +1580,21 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1401
1580
|
name = func.name;
|
1402
1581
|
}
|
1403
1582
|
|
1404
|
-
if (name === 'eval' && decl.arguments[0]
|
1583
|
+
if (name === 'eval' && decl.arguments[0]?.type === 'Literal') {
|
1405
1584
|
// literal eval hack
|
1406
|
-
const code = decl.arguments[0]
|
1407
|
-
|
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
|
+
}
|
1408
1598
|
|
1409
1599
|
const out = generate(scope, {
|
1410
1600
|
type: 'BlockStatement',
|
@@ -1418,13 +1608,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1418
1608
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1419
1609
|
out.push(
|
1420
1610
|
...getNodeType(scope, finalStatement),
|
1421
|
-
setLastType(scope)
|
1611
|
+
...setLastType(scope)
|
1422
1612
|
);
|
1423
1613
|
} else if (countLeftover(out) === 0) {
|
1424
1614
|
out.push(...number(UNDEFINED));
|
1425
1615
|
out.push(
|
1426
1616
|
...number(TYPES.undefined, Valtype.i32),
|
1427
|
-
setLastType(scope)
|
1617
|
+
...setLastType(scope)
|
1428
1618
|
);
|
1429
1619
|
}
|
1430
1620
|
|
@@ -1446,29 +1636,39 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1446
1636
|
|
1447
1637
|
target = { ...decl.callee };
|
1448
1638
|
target.name = spl.slice(0, -1).join('_');
|
1639
|
+
|
1640
|
+
// failed to lookup name, abort
|
1641
|
+
if (!lookupName(scope, target.name)[0]) protoName = null;
|
1449
1642
|
}
|
1450
1643
|
|
1451
1644
|
// literal.func()
|
1452
1645
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1453
1646
|
// megahack for /regex/.func()
|
1454
|
-
|
1455
|
-
|
1456
|
-
const
|
1647
|
+
const funcName = decl.callee.property.name;
|
1648
|
+
if (decl.callee.object.regex && Object.hasOwn(Rhemyn, funcName)) {
|
1649
|
+
const regex = decl.callee.object.regex.pattern;
|
1650
|
+
const rhemynName = `regex_${funcName}_${regex}`;
|
1457
1651
|
|
1458
|
-
funcIndex[
|
1459
|
-
|
1652
|
+
if (!funcIndex[rhemynName]) {
|
1653
|
+
const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
|
1460
1654
|
|
1655
|
+
funcIndex[func.name] = func.index;
|
1656
|
+
funcs.push(func);
|
1657
|
+
}
|
1658
|
+
|
1659
|
+
const idx = funcIndex[rhemynName];
|
1461
1660
|
return [
|
1462
1661
|
// make string arg
|
1463
1662
|
...generate(scope, decl.arguments[0]),
|
1663
|
+
Opcodes.i32_to_u,
|
1664
|
+
...getNodeType(scope, decl.arguments[0]),
|
1464
1665
|
|
1465
1666
|
// call regex func
|
1466
|
-
Opcodes.
|
1467
|
-
[ Opcodes.call, func.index ],
|
1667
|
+
[ Opcodes.call, idx ],
|
1468
1668
|
Opcodes.i32_from_u,
|
1469
1669
|
|
1470
1670
|
...number(TYPES.boolean, Valtype.i32),
|
1471
|
-
setLastType(scope)
|
1671
|
+
...setLastType(scope)
|
1472
1672
|
];
|
1473
1673
|
}
|
1474
1674
|
|
@@ -1493,23 +1693,48 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1493
1693
|
// }
|
1494
1694
|
|
1495
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
|
+
|
1496
1716
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1497
|
-
|
1498
|
-
if (f) acc[x] = f;
|
1717
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1499
1718
|
return acc;
|
1500
1719
|
}, {});
|
1501
1720
|
|
1502
|
-
// no prototype function candidates, ignore
|
1503
1721
|
if (Object.keys(protoCands).length > 0) {
|
1504
1722
|
// use local for cached i32 length as commonly used
|
1505
1723
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1506
1724
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1507
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1508
1725
|
|
1509
1726
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1510
1727
|
|
1728
|
+
const rawPointer = [
|
1729
|
+
...generate(scope, target),
|
1730
|
+
Opcodes.i32_to_u
|
1731
|
+
];
|
1732
|
+
|
1733
|
+
const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
|
1734
|
+
const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
|
1735
|
+
|
1736
|
+
let allOptUnused = true;
|
1511
1737
|
let lengthI32CacheUsed = false;
|
1512
|
-
const protoBC = {};
|
1513
1738
|
for (const x in protoCands) {
|
1514
1739
|
const protoFunc = protoCands[x];
|
1515
1740
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
@@ -1517,7 +1742,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1517
1742
|
...RTArrayUtil.getLength(getPointer),
|
1518
1743
|
|
1519
1744
|
...number(TYPES.number, Valtype.i32),
|
1520
|
-
setLastType(scope)
|
1745
|
+
...setLastType(scope)
|
1521
1746
|
];
|
1522
1747
|
continue;
|
1523
1748
|
}
|
@@ -1527,6 +1752,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1527
1752
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1528
1753
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1529
1754
|
|
1755
|
+
let optUnused = false;
|
1530
1756
|
const protoOut = protoFunc(getPointer, {
|
1531
1757
|
getCachedI32: () => {
|
1532
1758
|
lengthI32CacheUsed = true;
|
@@ -1541,23 +1767,30 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1541
1767
|
return makeArray(scope, {
|
1542
1768
|
rawElements: new Array(length)
|
1543
1769
|
}, _global, _name, true, itemType);
|
1770
|
+
}, () => {
|
1771
|
+
optUnused = true;
|
1772
|
+
return unusedValue;
|
1544
1773
|
});
|
1545
1774
|
|
1775
|
+
if (!optUnused) allOptUnused = false;
|
1776
|
+
|
1546
1777
|
protoBC[x] = [
|
1547
|
-
[ Opcodes.block, valtypeBinary ],
|
1778
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1548
1779
|
...protoOut,
|
1549
1780
|
|
1550
1781
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1551
|
-
setLastType(scope),
|
1782
|
+
...setLastType(scope),
|
1552
1783
|
[ Opcodes.end ]
|
1553
1784
|
];
|
1554
1785
|
}
|
1555
1786
|
|
1556
|
-
|
1557
|
-
...generate(scope, target),
|
1787
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1558
1788
|
|
1559
|
-
|
1560
|
-
|
1789
|
+
return [
|
1790
|
+
...(usePointerCache ? [
|
1791
|
+
...rawPointer,
|
1792
|
+
[ Opcodes.local_set, pointerLocal ],
|
1793
|
+
] : []),
|
1561
1794
|
|
1562
1795
|
...(!lengthI32CacheUsed ? [] : [
|
1563
1796
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1569,13 +1802,22 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1569
1802
|
|
1570
1803
|
// TODO: error better
|
1571
1804
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1572
|
-
}, valtypeBinary),
|
1805
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1573
1806
|
];
|
1574
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
|
+
}
|
1575
1817
|
}
|
1576
1818
|
|
1577
1819
|
// TODO: only allows callee as literal
|
1578
|
-
if (!name) return todo(`only literal callees (got ${decl.callee.type})`);
|
1820
|
+
if (!name) return todo(scope, `only literal callees (got ${decl.callee.type})`);
|
1579
1821
|
|
1580
1822
|
let idx = funcIndex[name] ?? importedFuncs[name];
|
1581
1823
|
if (idx === undefined && builtinFuncs[name]) {
|
@@ -1585,20 +1827,20 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1585
1827
|
idx = funcIndex[name];
|
1586
1828
|
|
1587
1829
|
// infer arguments types from builtins params
|
1588
|
-
const func = funcs.find(x => x.name === name);
|
1589
|
-
for (let i = 0; i < decl.arguments.length; i++) {
|
1590
|
-
|
1591
|
-
|
1592
|
-
|
1593
|
-
|
1594
|
-
|
1595
|
-
|
1596
|
-
|
1597
|
-
|
1598
|
-
|
1599
|
-
|
1600
|
-
|
1601
|
-
}
|
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
|
+
// }
|
1602
1844
|
}
|
1603
1845
|
|
1604
1846
|
if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
@@ -1608,16 +1850,62 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1608
1850
|
idx = -1;
|
1609
1851
|
}
|
1610
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
|
+
|
1611
1899
|
if (idx === undefined) {
|
1612
|
-
if (scope.locals[name] !== undefined || globals[name] !== undefined || builtinVars[name] !== undefined) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function
|
1613
|
-
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);
|
1614
1902
|
}
|
1615
1903
|
|
1616
1904
|
const func = funcs.find(x => x.index === idx);
|
1617
1905
|
|
1618
1906
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1619
1907
|
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1620
|
-
const
|
1908
|
+
const typedReturns = (func ? func.returnType == null : userFunc) || builtinFuncs[name]?.typedReturns;
|
1621
1909
|
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1622
1910
|
|
1623
1911
|
let args = decl.arguments;
|
@@ -1634,14 +1922,24 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1634
1922
|
if (func && func.throws) scope.throws = true;
|
1635
1923
|
|
1636
1924
|
let out = [];
|
1637
|
-
for (
|
1925
|
+
for (let i = 0; i < args.length; i++) {
|
1926
|
+
const arg = args[i];
|
1638
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
|
+
|
1639
1937
|
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1640
1938
|
}
|
1641
1939
|
|
1642
1940
|
out.push([ Opcodes.call, idx ]);
|
1643
1941
|
|
1644
|
-
if (!
|
1942
|
+
if (!typedReturns) {
|
1645
1943
|
// let type;
|
1646
1944
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1647
1945
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1651,7 +1949,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1651
1949
|
// ...number(type, Valtype.i32),
|
1652
1950
|
// [ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1653
1951
|
// );
|
1654
|
-
} 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
|
+
}
|
1655
1957
|
|
1656
1958
|
return out;
|
1657
1959
|
};
|
@@ -1659,8 +1961,21 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1659
1961
|
const generateNew = (scope, decl, _global, _name) => {
|
1660
1962
|
// hack: basically treat this as a normal call for builtins for now
|
1661
1963
|
const name = mapName(decl.callee.name);
|
1964
|
+
|
1662
1965
|
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1663
|
-
|
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)})`);
|
1664
1979
|
|
1665
1980
|
return generateCall(scope, decl, _global, _name);
|
1666
1981
|
};
|
@@ -1777,12 +2092,14 @@ const brTable = (input, bc, returns) => {
|
|
1777
2092
|
};
|
1778
2093
|
|
1779
2094
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
2095
|
+
if (!Prefs.bytestring) delete bc[TYPES._bytestring];
|
2096
|
+
|
1780
2097
|
const known = knownType(scope, type);
|
1781
2098
|
if (known != null) {
|
1782
2099
|
return bc[known] ?? bc.default;
|
1783
2100
|
}
|
1784
2101
|
|
1785
|
-
if (
|
2102
|
+
if (Prefs.typeswitchUseBrtable)
|
1786
2103
|
return brTable(type, bc, returns);
|
1787
2104
|
|
1788
2105
|
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
@@ -1792,8 +2109,6 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1792
2109
|
[ Opcodes.block, returns ]
|
1793
2110
|
];
|
1794
2111
|
|
1795
|
-
// todo: use br_table?
|
1796
|
-
|
1797
2112
|
for (const x in bc) {
|
1798
2113
|
if (x === 'default') continue;
|
1799
2114
|
|
@@ -1817,7 +2132,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
1817
2132
|
return out;
|
1818
2133
|
};
|
1819
2134
|
|
1820
|
-
const allocVar = (scope, name, global = false) => {
|
2135
|
+
const allocVar = (scope, name, global = false, type = true) => {
|
1821
2136
|
const target = global ? globals : scope.locals;
|
1822
2137
|
|
1823
2138
|
// already declared
|
@@ -1831,8 +2146,10 @@ const allocVar = (scope, name, global = false) => {
|
|
1831
2146
|
let idx = global ? globalInd++ : scope.localInd++;
|
1832
2147
|
target[name] = { idx, type: valtypeBinary };
|
1833
2148
|
|
1834
|
-
|
1835
|
-
|
2149
|
+
if (type) {
|
2150
|
+
let typeIdx = global ? globalInd++ : scope.localInd++;
|
2151
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
2152
|
+
}
|
1836
2153
|
|
1837
2154
|
return idx;
|
1838
2155
|
};
|
@@ -1847,11 +2164,14 @@ const addVarMetadata = (scope, name, global = false, metadata = {}) => {
|
|
1847
2164
|
};
|
1848
2165
|
|
1849
2166
|
const typeAnnoToPorfType = x => {
|
1850
|
-
if (
|
1851
|
-
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()];
|
1852
2170
|
|
1853
2171
|
switch (x) {
|
1854
2172
|
case 'i32':
|
2173
|
+
case 'i64':
|
2174
|
+
case 'f64':
|
1855
2175
|
return TYPES.number;
|
1856
2176
|
}
|
1857
2177
|
|
@@ -1862,7 +2182,7 @@ const extractTypeAnnotation = decl => {
|
|
1862
2182
|
let a = decl;
|
1863
2183
|
while (a.typeAnnotation) a = a.typeAnnotation;
|
1864
2184
|
|
1865
|
-
let type, elementType;
|
2185
|
+
let type = null, elementType = null;
|
1866
2186
|
if (a.typeName) {
|
1867
2187
|
type = a.typeName.name;
|
1868
2188
|
} else if (a.type.endsWith('Keyword')) {
|
@@ -1875,6 +2195,8 @@ const extractTypeAnnotation = decl => {
|
|
1875
2195
|
const typeName = type;
|
1876
2196
|
type = typeAnnoToPorfType(type);
|
1877
2197
|
|
2198
|
+
if (type === TYPES._bytestring && !Prefs.bytestring) type = TYPES.string;
|
2199
|
+
|
1878
2200
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1879
2201
|
|
1880
2202
|
return { type, typeName, elementType };
|
@@ -1887,10 +2209,13 @@ const generateVar = (scope, decl) => {
|
|
1887
2209
|
|
1888
2210
|
// global variable if in top scope (main) and var ..., or if wanted
|
1889
2211
|
const global = topLevel || decl._bare; // decl.kind === 'var';
|
2212
|
+
const target = global ? globals : scope.locals;
|
1890
2213
|
|
1891
2214
|
for (const x of decl.declarations) {
|
1892
2215
|
const name = mapName(x.id.name);
|
1893
2216
|
|
2217
|
+
if (!name) return todo(scope, 'destructuring is not supported yet');
|
2218
|
+
|
1894
2219
|
if (x.init && isFuncType(x.init.type)) {
|
1895
2220
|
// hack for let a = function () { ... }
|
1896
2221
|
x.init.id = { name };
|
@@ -1906,16 +2231,29 @@ const generateVar = (scope, decl) => {
|
|
1906
2231
|
continue; // always ignore
|
1907
2232
|
}
|
1908
2233
|
|
1909
|
-
|
2234
|
+
// // generate init before allocating var
|
2235
|
+
// let generated;
|
2236
|
+
// if (x.init) generated = generate(scope, x.init, global, name);
|
2237
|
+
|
2238
|
+
const typed = typedInput && x.id.typeAnnotation;
|
2239
|
+
let idx = allocVar(scope, name, global, !(typed && extractTypeAnnotation(x.id).type != null));
|
1910
2240
|
|
1911
|
-
if (
|
2241
|
+
if (typed) {
|
1912
2242
|
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1913
2243
|
}
|
1914
2244
|
|
1915
2245
|
if (x.init) {
|
1916
|
-
|
1917
|
-
|
1918
|
-
|
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
|
+
}
|
1919
2257
|
out.push(...setType(scope, name, getNodeType(scope, x.init)));
|
1920
2258
|
}
|
1921
2259
|
|
@@ -1926,7 +2264,8 @@ const generateVar = (scope, decl) => {
|
|
1926
2264
|
return out;
|
1927
2265
|
};
|
1928
2266
|
|
1929
|
-
|
2267
|
+
// todo: optimize this func for valueUnused
|
2268
|
+
const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
1930
2269
|
const { type, name } = decl.left;
|
1931
2270
|
|
1932
2271
|
if (type === 'ObjectPattern') {
|
@@ -1941,22 +2280,30 @@ const generateAssign = (scope, decl) => {
|
|
1941
2280
|
return [];
|
1942
2281
|
}
|
1943
2282
|
|
2283
|
+
const op = decl.operator.slice(0, -1) || '=';
|
2284
|
+
|
1944
2285
|
// hack: .length setter
|
1945
2286
|
if (decl.left.type === 'MemberExpression' && decl.left.property.name === 'length') {
|
1946
2287
|
const name = decl.left.object.name;
|
1947
|
-
const pointer = arrays
|
2288
|
+
const pointer = scope.arrays?.get(name);
|
1948
2289
|
|
1949
|
-
const aotPointer = pointer != null;
|
2290
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1950
2291
|
|
1951
2292
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
2293
|
+
const pointerTmp = op === '=' ? null : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1952
2294
|
|
1953
2295
|
return [
|
1954
2296
|
...(aotPointer ? number(0, Valtype.i32) : [
|
1955
2297
|
...generate(scope, decl.left.object),
|
1956
2298
|
Opcodes.i32_to_u
|
1957
2299
|
]),
|
2300
|
+
...(!pointerTmp ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
1958
2301
|
|
1959
|
-
...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))),
|
1960
2307
|
[ Opcodes.local_tee, newValueTmp ],
|
1961
2308
|
|
1962
2309
|
Opcodes.i32_to_u,
|
@@ -1966,14 +2313,12 @@ const generateAssign = (scope, decl) => {
|
|
1966
2313
|
];
|
1967
2314
|
}
|
1968
2315
|
|
1969
|
-
const op = decl.operator.slice(0, -1) || '=';
|
1970
|
-
|
1971
2316
|
// arr[i]
|
1972
2317
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
1973
2318
|
const name = decl.left.object.name;
|
1974
|
-
const pointer = arrays
|
2319
|
+
const pointer = scope.arrays?.get(name);
|
1975
2320
|
|
1976
|
-
const aotPointer = pointer != null;
|
2321
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
1977
2322
|
|
1978
2323
|
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
1979
2324
|
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
@@ -2029,6 +2374,8 @@ const generateAssign = (scope, decl) => {
|
|
2029
2374
|
];
|
2030
2375
|
}
|
2031
2376
|
|
2377
|
+
if (!name) return todo(scope, 'destructuring is not supported yet', true);
|
2378
|
+
|
2032
2379
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2033
2380
|
|
2034
2381
|
if (local === undefined) {
|
@@ -2075,9 +2422,7 @@ const generateAssign = (scope, decl) => {
|
|
2075
2422
|
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
2076
2423
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
2077
2424
|
|
2078
|
-
getLastType(scope)
|
2079
|
-
// hack: type is idx+1
|
2080
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2425
|
+
...setType(scope, name, getLastType(scope))
|
2081
2426
|
];
|
2082
2427
|
}
|
2083
2428
|
|
@@ -2088,9 +2433,7 @@ const generateAssign = (scope, decl) => {
|
|
2088
2433
|
|
2089
2434
|
// todo: string concat types
|
2090
2435
|
|
2091
|
-
|
2092
|
-
...number(TYPES.number, Valtype.i32),
|
2093
|
-
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx + 1 ],
|
2436
|
+
...setType(scope, name, TYPES.number)
|
2094
2437
|
];
|
2095
2438
|
};
|
2096
2439
|
|
@@ -2136,7 +2479,7 @@ const generateUnary = (scope, decl) => {
|
|
2136
2479
|
return out;
|
2137
2480
|
}
|
2138
2481
|
|
2139
|
-
case 'delete':
|
2482
|
+
case 'delete': {
|
2140
2483
|
let toReturn = true, toGenerate = true;
|
2141
2484
|
|
2142
2485
|
if (decl.argument.type === 'Identifier') {
|
@@ -2158,38 +2501,60 @@ const generateUnary = (scope, decl) => {
|
|
2158
2501
|
|
2159
2502
|
out.push(...number(toReturn ? 1 : 0));
|
2160
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);
|
2511
|
+
|
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
|
+
}
|
2161
2519
|
|
2162
|
-
|
2163
|
-
|
2520
|
+
const out = toGenerate ? generate(scope, decl.argument) : [];
|
2521
|
+
disposeLeftover(out);
|
2522
|
+
|
2523
|
+
out.push(...typeSwitch(scope, overrideType ?? getNodeType(scope, decl.argument), {
|
2164
2524
|
[TYPES.number]: makeString(scope, 'number', false, '#typeof_result'),
|
2165
2525
|
[TYPES.boolean]: makeString(scope, 'boolean', false, '#typeof_result'),
|
2166
2526
|
[TYPES.string]: makeString(scope, 'string', false, '#typeof_result'),
|
2167
2527
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
2168
2528
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
2169
2529
|
|
2530
|
+
[TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2531
|
+
|
2170
2532
|
// object and internal types
|
2171
2533
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2172
|
-
});
|
2534
|
+
}));
|
2535
|
+
|
2536
|
+
return out;
|
2537
|
+
}
|
2173
2538
|
|
2174
2539
|
default:
|
2175
|
-
return todo(`unary operator ${decl.operator} not implemented yet
|
2540
|
+
return todo(scope, `unary operator ${decl.operator} not implemented yet`, true);
|
2176
2541
|
}
|
2177
2542
|
};
|
2178
2543
|
|
2179
|
-
const generateUpdate = (scope, decl) => {
|
2544
|
+
const generateUpdate = (scope, decl, _global, _name, valueUnused = false) => {
|
2180
2545
|
const { name } = decl.argument;
|
2181
2546
|
|
2182
2547
|
const [ local, isGlobal ] = lookupName(scope, name);
|
2183
2548
|
|
2184
2549
|
if (local === undefined) {
|
2185
|
-
return todo(`update expression with undefined variable
|
2550
|
+
return todo(scope, `update expression with undefined variable`, true);
|
2186
2551
|
}
|
2187
2552
|
|
2188
2553
|
const idx = local.idx;
|
2189
2554
|
const out = [];
|
2190
2555
|
|
2191
2556
|
out.push([ isGlobal ? Opcodes.global_get : Opcodes.local_get, idx ]);
|
2192
|
-
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 ]);
|
2193
2558
|
|
2194
2559
|
switch (decl.operator) {
|
2195
2560
|
case '++':
|
@@ -2202,7 +2567,7 @@ const generateUpdate = (scope, decl) => {
|
|
2202
2567
|
}
|
2203
2568
|
|
2204
2569
|
out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, idx ]);
|
2205
|
-
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 ]);
|
2206
2571
|
|
2207
2572
|
return out;
|
2208
2573
|
};
|
@@ -2242,7 +2607,7 @@ const generateConditional = (scope, decl) => {
|
|
2242
2607
|
// note type
|
2243
2608
|
out.push(
|
2244
2609
|
...getNodeType(scope, decl.consequent),
|
2245
|
-
setLastType(scope)
|
2610
|
+
...setLastType(scope)
|
2246
2611
|
);
|
2247
2612
|
|
2248
2613
|
out.push([ Opcodes.else ]);
|
@@ -2251,7 +2616,7 @@ const generateConditional = (scope, decl) => {
|
|
2251
2616
|
// note type
|
2252
2617
|
out.push(
|
2253
2618
|
...getNodeType(scope, decl.alternate),
|
2254
|
-
setLastType(scope)
|
2619
|
+
...setLastType(scope)
|
2255
2620
|
);
|
2256
2621
|
|
2257
2622
|
out.push([ Opcodes.end ]);
|
@@ -2265,15 +2630,17 @@ const generateFor = (scope, decl) => {
|
|
2265
2630
|
const out = [];
|
2266
2631
|
|
2267
2632
|
if (decl.init) {
|
2268
|
-
out.push(...generate(scope, decl.init));
|
2633
|
+
out.push(...generate(scope, decl.init, false, undefined, true));
|
2269
2634
|
disposeLeftover(out);
|
2270
2635
|
}
|
2271
2636
|
|
2272
2637
|
out.push([ Opcodes.loop, Blocktype.void ]);
|
2273
2638
|
depth.push('for');
|
2274
2639
|
|
2275
|
-
out.push(...generate(scope, decl.test));
|
2276
|
-
|
2640
|
+
if (decl.test) out.push(...generate(scope, decl.test), Opcodes.i32_to);
|
2641
|
+
else out.push(...number(1, Valtype.i32));
|
2642
|
+
|
2643
|
+
out.push([ Opcodes.if, Blocktype.void ]);
|
2277
2644
|
depth.push('if');
|
2278
2645
|
|
2279
2646
|
out.push([ Opcodes.block, Blocktype.void ]);
|
@@ -2281,8 +2648,7 @@ const generateFor = (scope, decl) => {
|
|
2281
2648
|
out.push(...generate(scope, decl.body));
|
2282
2649
|
out.push([ Opcodes.end ]);
|
2283
2650
|
|
2284
|
-
out.push(...generate(scope, decl.update));
|
2285
|
-
depth.pop();
|
2651
|
+
if (decl.update) out.push(...generate(scope, decl.update, false, undefined, true));
|
2286
2652
|
|
2287
2653
|
out.push([ Opcodes.br, 1 ]);
|
2288
2654
|
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
@@ -2310,6 +2676,36 @@ const generateWhile = (scope, decl) => {
|
|
2310
2676
|
return out;
|
2311
2677
|
};
|
2312
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
|
+
|
2313
2709
|
const generateForOf = (scope, decl) => {
|
2314
2710
|
const out = [];
|
2315
2711
|
|
@@ -2339,8 +2735,17 @@ const generateForOf = (scope, decl) => {
|
|
2339
2735
|
// setup local for left
|
2340
2736
|
generate(scope, decl.left);
|
2341
2737
|
|
2342
|
-
|
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
|
+
|
2343
2747
|
const [ local, isGlobal ] = lookupName(scope, leftName);
|
2748
|
+
if (!local) return todo(scope, 'for of failed to get left local (probably destructure)');
|
2344
2749
|
|
2345
2750
|
depth.push('block');
|
2346
2751
|
depth.push('block');
|
@@ -2348,13 +2753,15 @@ const generateForOf = (scope, decl) => {
|
|
2348
2753
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2349
2754
|
// hack: this is naughty and will break things!
|
2350
2755
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2351
|
-
if (pages.
|
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?
|
2352
2758
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2353
2759
|
rawElements: new Array(1)
|
2354
2760
|
}, isGlobal, leftName, true, 'i16');
|
2355
2761
|
}
|
2356
2762
|
|
2357
2763
|
// set type for local
|
2764
|
+
// todo: optimize away counter and use end pointer
|
2358
2765
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2359
2766
|
[TYPES._array]: [
|
2360
2767
|
...setType(scope, leftName, TYPES.number),
|
@@ -2439,6 +2846,56 @@ const generateForOf = (scope, decl) => {
|
|
2439
2846
|
[ Opcodes.end ],
|
2440
2847
|
[ Opcodes.end ]
|
2441
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
|
+
],
|
2442
2899
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2443
2900
|
}, Blocktype.void));
|
2444
2901
|
|
@@ -2449,28 +2906,65 @@ const generateForOf = (scope, decl) => {
|
|
2449
2906
|
return out;
|
2450
2907
|
};
|
2451
2908
|
|
2909
|
+
// find the nearest loop in depth map by type
|
2452
2910
|
const getNearestLoop = () => {
|
2453
2911
|
for (let i = depth.length - 1; i >= 0; i--) {
|
2454
|
-
if (
|
2912
|
+
if (['while', 'dowhile', 'for', 'forof'].includes(depth[i])) return i;
|
2455
2913
|
}
|
2456
2914
|
|
2457
2915
|
return -1;
|
2458
2916
|
};
|
2459
2917
|
|
2460
2918
|
const generateBreak = (scope, decl) => {
|
2461
|
-
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
|
+
|
2462
2934
|
return [
|
2463
|
-
[ Opcodes.br, ...signedLEB128(
|
2935
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2464
2936
|
];
|
2465
2937
|
};
|
2466
2938
|
|
2467
2939
|
const generateContinue = (scope, decl) => {
|
2468
|
-
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
|
+
|
2469
2954
|
return [
|
2470
|
-
[ Opcodes.br, ...signedLEB128(
|
2955
|
+
[ Opcodes.br, ...signedLEB128(depth.length - target - offset) ]
|
2471
2956
|
];
|
2472
2957
|
};
|
2473
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
|
+
|
2474
2968
|
const generateThrow = (scope, decl) => {
|
2475
2969
|
scope.throws = true;
|
2476
2970
|
|
@@ -2479,7 +2973,7 @@ const generateThrow = (scope, decl) => {
|
|
2479
2973
|
// hack: throw new X("...") -> throw "..."
|
2480
2974
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2481
2975
|
constructor = decl.argument.callee.name;
|
2482
|
-
message = decl.argument.arguments[0]
|
2976
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2483
2977
|
}
|
2484
2978
|
|
2485
2979
|
if (tags.length === 0) tags.push({
|
@@ -2491,6 +2985,9 @@ const generateThrow = (scope, decl) => {
|
|
2491
2985
|
let exceptId = exceptions.push({ constructor, message }) - 1;
|
2492
2986
|
let tagIdx = tags[0].idx;
|
2493
2987
|
|
2988
|
+
scope.exceptions ??= [];
|
2989
|
+
scope.exceptions.push(exceptId);
|
2990
|
+
|
2494
2991
|
// todo: write a description of how this works lol
|
2495
2992
|
|
2496
2993
|
return [
|
@@ -2500,7 +2997,7 @@ const generateThrow = (scope, decl) => {
|
|
2500
2997
|
};
|
2501
2998
|
|
2502
2999
|
const generateTry = (scope, decl) => {
|
2503
|
-
if (decl.finalizer) return todo('try finally not implemented yet');
|
3000
|
+
if (decl.finalizer) return todo(scope, 'try finally not implemented yet');
|
2504
3001
|
|
2505
3002
|
const out = [];
|
2506
3003
|
|
@@ -2531,29 +3028,35 @@ const generateAssignPat = (scope, decl) => {
|
|
2531
3028
|
// TODO
|
2532
3029
|
// if identifier declared, use that
|
2533
3030
|
// else, use default (right)
|
2534
|
-
return todo('assignment pattern (optional arg)');
|
3031
|
+
return todo(scope, 'assignment pattern (optional arg)');
|
2535
3032
|
};
|
2536
3033
|
|
2537
3034
|
let pages = new Map();
|
2538
|
-
const allocPage = (reason, type) => {
|
3035
|
+
const allocPage = (scope, reason, type) => {
|
2539
3036
|
if (pages.has(reason)) return pages.get(reason).ind;
|
2540
3037
|
|
2541
3038
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2542
3039
|
if (reason.startsWith('string:')) pages.hasString = true;
|
3040
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
3041
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2543
3042
|
|
2544
3043
|
const ind = pages.size;
|
2545
3044
|
pages.set(reason, { ind, type });
|
2546
3045
|
|
2547
|
-
|
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})`);
|
2548
3050
|
|
2549
3051
|
return ind;
|
2550
3052
|
};
|
2551
3053
|
|
3054
|
+
// todo: add scope.pages
|
2552
3055
|
const freePage = reason => {
|
2553
3056
|
const { ind } = pages.get(reason);
|
2554
3057
|
pages.delete(reason);
|
2555
3058
|
|
2556
|
-
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
3059
|
+
if (Prefs.allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
2557
3060
|
|
2558
3061
|
return ind;
|
2559
3062
|
};
|
@@ -2573,38 +3076,53 @@ const StoreOps = {
|
|
2573
3076
|
f64: Opcodes.f64_store,
|
2574
3077
|
|
2575
3078
|
// expects i32 input!
|
2576
|
-
|
3079
|
+
i8: Opcodes.i32_store8,
|
3080
|
+
i16: Opcodes.i32_store16,
|
2577
3081
|
};
|
2578
3082
|
|
2579
3083
|
let data = [];
|
2580
3084
|
|
2581
|
-
const compileBytes = (val, itemType
|
3085
|
+
const compileBytes = (val, itemType) => {
|
2582
3086
|
// todo: this is a mess and needs confirming / ????
|
2583
3087
|
switch (itemType) {
|
2584
3088
|
case 'i8': return [ val % 256 ];
|
2585
|
-
case 'i16': return [ val % 256,
|
2586
|
-
|
2587
|
-
case 'i32':
|
2588
|
-
|
2589
|
-
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
|
2590
3093
|
|
2591
3094
|
case 'f64': return ieee754_binary64(val);
|
2592
3095
|
}
|
2593
3096
|
};
|
2594
3097
|
|
3098
|
+
const getAllocType = itemType => {
|
3099
|
+
switch (itemType) {
|
3100
|
+
case 'i8': return 'bytestring';
|
3101
|
+
case 'i16': return 'string';
|
3102
|
+
|
3103
|
+
default: return 'array';
|
3104
|
+
}
|
3105
|
+
};
|
3106
|
+
|
2595
3107
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2596
3108
|
const out = [];
|
2597
3109
|
|
3110
|
+
scope.arrays ??= new Map();
|
3111
|
+
|
2598
3112
|
let firstAssign = false;
|
2599
|
-
if (!arrays.has(name) || name === '$undeclared') {
|
3113
|
+
if (!scope.arrays.has(name) || name === '$undeclared') {
|
2600
3114
|
firstAssign = true;
|
2601
3115
|
|
2602
3116
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2603
3117
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2604
|
-
|
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);
|
2605
3121
|
}
|
2606
3122
|
|
2607
|
-
const pointer = arrays.get(name);
|
3123
|
+
const pointer = scope.arrays.get(name);
|
3124
|
+
|
3125
|
+
const local = global ? globals[name] : scope.locals[name];
|
2608
3126
|
|
2609
3127
|
const useRawElements = !!decl.rawElements;
|
2610
3128
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
@@ -2612,19 +3130,25 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2612
3130
|
const valtype = itemTypeToValtype[itemType];
|
2613
3131
|
const length = elements.length;
|
2614
3132
|
|
2615
|
-
if (firstAssign && useRawElements) {
|
2616
|
-
|
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');
|
2617
3137
|
|
2618
|
-
|
2619
|
-
|
3138
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
3139
|
+
if (elements[i] == null) continue;
|
2620
3140
|
|
2621
|
-
|
2622
|
-
|
3141
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
3142
|
+
}
|
2623
3143
|
|
2624
|
-
|
2625
|
-
|
2626
|
-
|
2627
|
-
|
3144
|
+
const ind = data.push({
|
3145
|
+
offset: pointer,
|
3146
|
+
bytes
|
3147
|
+
}) - 1;
|
3148
|
+
|
3149
|
+
scope.data ??= [];
|
3150
|
+
scope.data.push(ind);
|
3151
|
+
}
|
2628
3152
|
|
2629
3153
|
// local value as pointer
|
2630
3154
|
out.push(...number(pointer));
|
@@ -2632,11 +3156,22 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2632
3156
|
return [ out, pointer ];
|
2633
3157
|
}
|
2634
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
|
+
|
2635
3170
|
// store length as 0th array
|
2636
3171
|
out.push(
|
2637
|
-
...
|
3172
|
+
...pointerWasm,
|
2638
3173
|
...number(length, Valtype.i32),
|
2639
|
-
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1,
|
3174
|
+
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, 0 ]
|
2640
3175
|
);
|
2641
3176
|
|
2642
3177
|
const storeOp = StoreOps[itemType];
|
@@ -2645,43 +3180,68 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2645
3180
|
if (elements[i] == null) continue;
|
2646
3181
|
|
2647
3182
|
out.push(
|
2648
|
-
...
|
3183
|
+
...pointerWasm,
|
2649
3184
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2650
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(
|
3185
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2651
3186
|
);
|
2652
3187
|
}
|
2653
3188
|
|
2654
3189
|
// local value as pointer
|
2655
|
-
out.push(...
|
3190
|
+
out.push(...pointerWasm, Opcodes.i32_from_u);
|
2656
3191
|
|
2657
3192
|
return [ out, pointer ];
|
2658
3193
|
};
|
2659
3194
|
|
2660
|
-
const
|
3195
|
+
const byteStringable = str => {
|
3196
|
+
if (!Prefs.bytestring) return false;
|
3197
|
+
|
3198
|
+
for (let i = 0; i < str.length; i++) {
|
3199
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
3200
|
+
}
|
3201
|
+
|
3202
|
+
return true;
|
3203
|
+
};
|
3204
|
+
|
3205
|
+
const makeString = (scope, str, global = false, name = '$undeclared', forceBytestring = undefined) => {
|
2661
3206
|
const rawElements = new Array(str.length);
|
3207
|
+
let byteStringable = Prefs.bytestring;
|
2662
3208
|
for (let i = 0; i < str.length; i++) {
|
2663
|
-
|
3209
|
+
const c = str.charCodeAt(i);
|
3210
|
+
rawElements[i] = c;
|
3211
|
+
|
3212
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2664
3213
|
}
|
2665
3214
|
|
3215
|
+
if (byteStringable && forceBytestring === false) byteStringable = false;
|
3216
|
+
|
2666
3217
|
return makeArray(scope, {
|
2667
3218
|
rawElements
|
2668
|
-
}, global, name, false, 'i16')[0];
|
3219
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2669
3220
|
};
|
2670
3221
|
|
2671
|
-
let arrays = new Map();
|
2672
3222
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
2673
3223
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
2674
3224
|
};
|
2675
3225
|
|
2676
3226
|
export const generateMember = (scope, decl, _global, _name) => {
|
2677
3227
|
const name = decl.object.name;
|
2678
|
-
const pointer = arrays
|
3228
|
+
const pointer = scope.arrays?.get(name);
|
2679
3229
|
|
2680
|
-
const aotPointer = pointer != null;
|
3230
|
+
const aotPointer = Prefs.aotPointerOpt && pointer != null;
|
2681
3231
|
|
2682
3232
|
// hack: .length
|
2683
3233
|
if (decl.property.name === 'length') {
|
2684
|
-
|
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
|
+
|
2685
3245
|
return [
|
2686
3246
|
...(aotPointer ? number(0, Valtype.i32) : [
|
2687
3247
|
...generate(scope, decl.object),
|
@@ -2693,10 +3253,13 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2693
3253
|
];
|
2694
3254
|
}
|
2695
3255
|
|
3256
|
+
const object = generate(scope, decl.object);
|
3257
|
+
const property = generate(scope, decl.property);
|
3258
|
+
|
2696
3259
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2697
3260
|
// hack: this is naughty and will break things!
|
2698
3261
|
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2699
|
-
if (pages.
|
3262
|
+
if (pages.hasAnyString) {
|
2700
3263
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2701
3264
|
rawElements: new Array(1)
|
2702
3265
|
}, _global, _name, true, 'i16');
|
@@ -2705,7 +3268,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2705
3268
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2706
3269
|
[TYPES._array]: [
|
2707
3270
|
// get index as valtype
|
2708
|
-
...
|
3271
|
+
...property,
|
2709
3272
|
|
2710
3273
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2711
3274
|
Opcodes.i32_to_u,
|
@@ -2713,7 +3276,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2713
3276
|
[ Opcodes.i32_mul ],
|
2714
3277
|
|
2715
3278
|
...(aotPointer ? [] : [
|
2716
|
-
...
|
3279
|
+
...object,
|
2717
3280
|
Opcodes.i32_to_u,
|
2718
3281
|
[ Opcodes.i32_add ]
|
2719
3282
|
]),
|
@@ -2722,7 +3285,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2722
3285
|
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2723
3286
|
|
2724
3287
|
...number(TYPES.number, Valtype.i32),
|
2725
|
-
setLastType(scope)
|
3288
|
+
...setLastType(scope)
|
2726
3289
|
],
|
2727
3290
|
|
2728
3291
|
[TYPES.string]: [
|
@@ -2732,14 +3295,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2732
3295
|
|
2733
3296
|
...number(0, Valtype.i32), // base 0 for store later
|
2734
3297
|
|
2735
|
-
...
|
2736
|
-
|
3298
|
+
...property,
|
2737
3299
|
Opcodes.i32_to_u,
|
3300
|
+
|
2738
3301
|
...number(ValtypeSize.i16, Valtype.i32),
|
2739
3302
|
[ Opcodes.i32_mul ],
|
2740
3303
|
|
2741
3304
|
...(aotPointer ? [] : [
|
2742
|
-
...
|
3305
|
+
...object,
|
2743
3306
|
Opcodes.i32_to_u,
|
2744
3307
|
[ Opcodes.i32_add ]
|
2745
3308
|
]),
|
@@ -2754,10 +3317,38 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2754
3317
|
...number(newPointer),
|
2755
3318
|
|
2756
3319
|
...number(TYPES.string, Valtype.i32),
|
2757
|
-
setLastType(scope)
|
3320
|
+
...setLastType(scope)
|
2758
3321
|
],
|
3322
|
+
[TYPES._bytestring]: [
|
3323
|
+
// setup new/out array
|
3324
|
+
...newOut,
|
3325
|
+
[ Opcodes.drop ],
|
3326
|
+
|
3327
|
+
...number(0, Valtype.i32), // base 0 for store later
|
3328
|
+
|
3329
|
+
...property,
|
3330
|
+
Opcodes.i32_to_u,
|
3331
|
+
|
3332
|
+
...(aotPointer ? [] : [
|
3333
|
+
...object,
|
3334
|
+
Opcodes.i32_to_u,
|
3335
|
+
[ Opcodes.i32_add ]
|
3336
|
+
]),
|
3337
|
+
|
3338
|
+
// load current string ind {arg}
|
3339
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2759
3340
|
|
2760
|
-
|
3341
|
+
// store to new string ind 0
|
3342
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
3343
|
+
|
3344
|
+
// return new string (page)
|
3345
|
+
...number(newPointer),
|
3346
|
+
|
3347
|
+
...number(TYPES._bytestring, Valtype.i32),
|
3348
|
+
...setLastType(scope)
|
3349
|
+
],
|
3350
|
+
|
3351
|
+
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
|
2761
3352
|
});
|
2762
3353
|
};
|
2763
3354
|
|
@@ -2767,25 +3358,36 @@ const objectHack = node => {
|
|
2767
3358
|
if (!node) return node;
|
2768
3359
|
|
2769
3360
|
if (node.type === 'MemberExpression') {
|
2770
|
-
|
3361
|
+
const out = (() => {
|
3362
|
+
if (node.computed || node.optional) return;
|
2771
3363
|
|
2772
|
-
|
3364
|
+
let objectName = node.object.name;
|
2773
3365
|
|
2774
|
-
|
2775
|
-
|
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;
|
2776
3369
|
|
2777
|
-
|
3370
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2778
3371
|
|
2779
|
-
|
2780
|
-
|
3372
|
+
// if .length, give up (hack within a hack!)
|
3373
|
+
if (node.property.name === 'length') {
|
3374
|
+
node.object = objectHack(node.object);
|
3375
|
+
return;
|
3376
|
+
}
|
2781
3377
|
|
2782
|
-
|
2783
|
-
|
3378
|
+
// no object name, give up
|
3379
|
+
if (!objectName) return;
|
2784
3380
|
|
2785
|
-
|
2786
|
-
|
2787
|
-
|
2788
|
-
|
3381
|
+
const name = '__' + objectName + '_' + node.property.name;
|
3382
|
+
if (Prefs.codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
3383
|
+
|
3384
|
+
return {
|
3385
|
+
type: 'Identifier',
|
3386
|
+
name
|
3387
|
+
};
|
3388
|
+
})();
|
3389
|
+
|
3390
|
+
if (out) return out;
|
2789
3391
|
}
|
2790
3392
|
|
2791
3393
|
for (const x in node) {
|
@@ -2799,8 +3401,8 @@ const objectHack = node => {
|
|
2799
3401
|
};
|
2800
3402
|
|
2801
3403
|
const generateFunc = (scope, decl) => {
|
2802
|
-
if (decl.async) return todo('async functions are not supported');
|
2803
|
-
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');
|
2804
3406
|
|
2805
3407
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
2806
3408
|
const params = decl.params ?? [];
|
@@ -2816,6 +3418,11 @@ const generateFunc = (scope, decl) => {
|
|
2816
3418
|
name
|
2817
3419
|
};
|
2818
3420
|
|
3421
|
+
if (typedInput && decl.returnType) {
|
3422
|
+
innerScope.returnType = extractTypeAnnotation(decl.returnType).type;
|
3423
|
+
innerScope.returns = [ valtypeBinary ];
|
3424
|
+
}
|
3425
|
+
|
2819
3426
|
for (let i = 0; i < params.length; i++) {
|
2820
3427
|
allocVar(innerScope, params[i].name, false);
|
2821
3428
|
|
@@ -2837,13 +3444,13 @@ const generateFunc = (scope, decl) => {
|
|
2837
3444
|
const func = {
|
2838
3445
|
name,
|
2839
3446
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2840
|
-
|
2841
|
-
|
2842
|
-
throws: innerScope.throws,
|
2843
|
-
index: currentFuncIndex++
|
3447
|
+
index: currentFuncIndex++,
|
3448
|
+
...innerScope
|
2844
3449
|
};
|
2845
3450
|
funcIndex[name] = func.index;
|
2846
3451
|
|
3452
|
+
if (name === 'main') func.gotLastType = true;
|
3453
|
+
|
2847
3454
|
// quick hack fixes
|
2848
3455
|
for (const inst of wasm) {
|
2849
3456
|
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
@@ -2895,7 +3502,7 @@ const internalConstrs = {
|
|
2895
3502
|
|
2896
3503
|
// todo: check in wasm instead of here
|
2897
3504
|
const literalValue = arg.value ?? 0;
|
2898
|
-
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);
|
2899
3506
|
|
2900
3507
|
return [
|
2901
3508
|
...number(0, Valtype.i32),
|
@@ -2906,7 +3513,8 @@ const internalConstrs = {
|
|
2906
3513
|
...number(pointer)
|
2907
3514
|
];
|
2908
3515
|
},
|
2909
|
-
type: TYPES._array
|
3516
|
+
type: TYPES._array,
|
3517
|
+
length: 1
|
2910
3518
|
},
|
2911
3519
|
|
2912
3520
|
__Array_of: {
|
@@ -2918,7 +3526,131 @@ const internalConstrs = {
|
|
2918
3526
|
}, global, name);
|
2919
3527
|
},
|
2920
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,
|
2921
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
|
2922
3654
|
}
|
2923
3655
|
};
|
2924
3656
|
|
@@ -2947,20 +3679,23 @@ export default program => {
|
|
2947
3679
|
funcs = [];
|
2948
3680
|
funcIndex = {};
|
2949
3681
|
depth = [];
|
2950
|
-
arrays = new Map();
|
2951
3682
|
pages = new Map();
|
2952
3683
|
data = [];
|
2953
3684
|
currentFuncIndex = importedFuncs.length;
|
2954
3685
|
|
2955
3686
|
globalThis.valtype = 'f64';
|
2956
3687
|
|
2957
|
-
const valtypeOpt = process.argv.find(x => x.startsWith('
|
3688
|
+
const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
|
2958
3689
|
if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
|
2959
3690
|
|
2960
3691
|
globalThis.valtypeBinary = Valtype[valtype];
|
2961
3692
|
|
2962
3693
|
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
2963
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
|
+
|
2964
3699
|
// set generic opcodes for current valtype
|
2965
3700
|
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
2966
3701
|
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
@@ -2969,10 +3704,10 @@ export default program => {
|
|
2969
3704
|
Opcodes.add = [ Opcodes.i32_add, Opcodes.i64_add, Opcodes.f64_add ][valtypeInd];
|
2970
3705
|
Opcodes.sub = [ Opcodes.i32_sub, Opcodes.i64_sub, Opcodes.f64_sub ][valtypeInd];
|
2971
3706
|
|
2972
|
-
Opcodes.i32_to = [ [
|
2973
|
-
Opcodes.i32_to_u = [ [
|
2974
|
-
Opcodes.i32_from = [ [
|
2975
|
-
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];
|
2976
3711
|
|
2977
3712
|
Opcodes.load = [ Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load ][valtypeInd];
|
2978
3713
|
Opcodes.store = [ Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store ][valtypeInd];
|
@@ -2985,10 +3720,6 @@ export default program => {
|
|
2985
3720
|
|
2986
3721
|
program.id = { name: 'main' };
|
2987
3722
|
|
2988
|
-
globalThis.pageSize = PageSize;
|
2989
|
-
const pageSizeOpt = process.argv.find(x => x.startsWith('-page-size='));
|
2990
|
-
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
2991
|
-
|
2992
3723
|
const scope = {
|
2993
3724
|
locals: {},
|
2994
3725
|
localInd: 0
|
@@ -2999,7 +3730,7 @@ export default program => {
|
|
2999
3730
|
body: program.body
|
3000
3731
|
};
|
3001
3732
|
|
3002
|
-
if (
|
3733
|
+
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
3003
3734
|
|
3004
3735
|
generateFunc(scope, program);
|
3005
3736
|
|
@@ -3016,7 +3747,11 @@ export default program => {
|
|
3016
3747
|
}
|
3017
3748
|
|
3018
3749
|
if (lastInst[0] === Opcodes.end || lastInst[0] === Opcodes.local_set || lastInst[0] === Opcodes.global_set) {
|
3019
|
-
|
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
|
+
}
|
3020
3755
|
}
|
3021
3756
|
|
3022
3757
|
if (lastInst[0] === Opcodes.call) {
|