porffor 0.0.0-758fed5 → 0.0.0-7d5ae9c

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.
@@ -1,10 +1,11 @@
1
1
  import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
2
- import { signedLEB128, unsignedLEB128 } from "./encoding.js";
2
+ import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
3
3
  import { operatorOpcode } from "./expression.js";
4
4
  import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
5
5
  import { PrototypeFuncs } from "./prototype.js";
6
- import { number, i32x4 } from "./embedding.js";
6
+ import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enforceEightBytes } from "./embedding.js";
7
7
  import parse from "./parse.js";
8
+ import * as Rhemyn from "../rhemyn/compile.js";
8
9
 
9
10
  let globals = {};
10
11
  let globalInd = 0;
@@ -108,8 +109,8 @@ const generate = (scope, decl, global = false, name = undefined) => {
108
109
  case 'WhileStatement':
109
110
  return generateWhile(scope, decl);
110
111
 
111
- /* case 'ForOfStatement':
112
- return generateForOf(scope, decl); */
112
+ case 'ForOfStatement':
113
+ return generateForOf(scope, decl);
113
114
 
114
115
  case 'BreakStatement':
115
116
  return generateBreak(scope, decl);
@@ -151,44 +152,65 @@ const generate = (scope, decl, global = false, name = undefined) => {
151
152
 
152
153
  return [];
153
154
 
154
- case 'TaggedTemplateExpression':
155
- // hack for inline asm
156
- if (decl.tag.name !== 'asm') return todo('tagged template expressions not implemented');
155
+ case 'TaggedTemplateExpression': {
156
+ const funcs = {
157
+ asm: str => {
158
+ let out = [];
157
159
 
158
- const str = decl.quasi.quasis[0].value.raw;
159
- let out = [];
160
+ for (const line of str.split('\n')) {
161
+ const asm = line.trim().split(';;')[0].split(' ');
162
+ if (asm[0] === '') continue; // blank
160
163
 
161
- for (const line of str.split('\n')) {
162
- const asm = line.trim().split(';;')[0].split(' ');
163
- if (asm[0] === '') continue; // blank
164
+ if (asm[0] === 'local') {
165
+ const [ name, idx, type ] = asm.slice(1);
166
+ scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
167
+ continue;
168
+ }
164
169
 
165
- if (asm[0] === 'local') {
166
- const [ name, idx, type ] = asm.slice(1);
167
- scope.locals[name] = { idx: parseInt(idx), type: Valtype[type] };
168
- continue;
169
- }
170
+ if (asm[0] === 'returns') {
171
+ scope.returns = asm.slice(1).map(x => Valtype[x]);
172
+ continue;
173
+ }
170
174
 
171
- if (asm[0] === 'returns') {
172
- scope.returns = asm.slice(1).map(x => Valtype[x]);
173
- continue;
174
- }
175
+ if (asm[0] === 'memory') {
176
+ allocPage('asm instrinsic');
177
+ // todo: add to store/load offset insts
178
+ continue;
179
+ }
175
180
 
176
- if (asm[0] === 'memory') {
177
- allocPage('asm instrinsic');
178
- // todo: add to store/load offset insts
179
- continue;
180
- }
181
+ let inst = Opcodes[asm[0].replace('.', '_')];
182
+ if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
183
+
184
+ if (!Array.isArray(inst)) inst = [ inst ];
185
+ const immediates = asm.slice(1).map(x => parseInt(x));
181
186
 
182
- let inst = Opcodes[asm[0].replace('.', '_')];
183
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
187
+ out.push([ ...inst, ...immediates ]);
188
+ }
184
189
 
185
- if (!Array.isArray(inst)) inst = [ inst ];
186
- const immediates = asm.slice(1).map(x => parseInt(x));
190
+ return out;
191
+ },
187
192
 
188
- out.push([ ...inst, ...immediates ]);
193
+ __internal_print_type: str => {
194
+ const type = getType(scope, str) - TYPES.number;
195
+
196
+ return [
197
+ ...number(type),
198
+ [ Opcodes.call, importedFuncs.print ],
199
+
200
+ // newline
201
+ ...number(10),
202
+ [ Opcodes.call, importedFuncs.printChar ]
203
+ ];
204
+ }
189
205
  }
190
206
 
191
- return out;
207
+ const name = decl.tag.name;
208
+ // hack for inline asm
209
+ if (!funcs[name]) return todo('tagged template expressions not implemented');
210
+
211
+ const str = decl.quasi.quasis[0].value.raw;
212
+ return funcs[name](str);
213
+ }
192
214
 
193
215
  default:
194
216
  return todo(`no generation for ${decl.type}!`);
@@ -304,6 +326,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
304
326
  return idx;
305
327
  };
306
328
 
329
+ const isIntOp = op => op[0] >= 0xb7 && op[0] <= 0xba;
330
+
307
331
  const performLogicOp = (scope, op, left, right, leftType, rightType) => {
308
332
  const checks = {
309
333
  '||': falsy,
@@ -316,6 +340,32 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
316
340
  // generic structure for {a} OP {b}
317
341
  // -->
318
342
  // _ = {a}; if (OP_CHECK) {b} else _
343
+
344
+ // if we can, use int tmp and convert at the end to help prevent unneeded conversions
345
+ // (like if we are in an if condition - very common)
346
+ const leftIsInt = isIntOp(left[left.length - 1]);
347
+ const rightIsInt = isIntOp(right[right.length - 1]);
348
+
349
+ const canInt = leftIsInt && rightIsInt;
350
+
351
+ if (canInt) {
352
+ // remove int -> float conversions from left and right
353
+ left.pop();
354
+ right.pop();
355
+
356
+ return [
357
+ ...left,
358
+ [ Opcodes.local_tee, localTmp(scope, 'logictmpi', Valtype.i32) ],
359
+ ...checks[op](scope, [], leftType, true),
360
+ [ Opcodes.if, Valtype.i32 ],
361
+ ...right,
362
+ [ Opcodes.else ],
363
+ [ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
364
+ [ Opcodes.end ],
365
+ Opcodes.i32_from
366
+ ];
367
+ }
368
+
319
369
  return [
320
370
  ...left,
321
371
  [ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
@@ -339,6 +389,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
339
389
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
340
390
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
341
391
 
392
+ const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
393
+ if (aotWFA) addVarMeta(name, { wellFormed: undefined });
394
+
342
395
  if (assign) {
343
396
  const pointer = arrays.get(name ?? '$undeclared');
344
397
 
@@ -566,12 +619,12 @@ const compareStrings = (scope, left, right) => {
566
619
  ];
567
620
  };
568
621
 
569
- const truthy = (scope, wasm, type) => {
622
+ const truthy = (scope, wasm, type, int = false) => {
570
623
  // arrays are always truthy
571
624
  if (type === TYPES._array) return [
572
625
  ...wasm,
573
626
  [ Opcodes.drop ],
574
- ...number(1)
627
+ ...number(1, int ? Valtype.i32 : valtypeBinary)
575
628
  ];
576
629
 
577
630
  if (type === TYPES.string) {
@@ -587,8 +640,8 @@ const truthy = (scope, wasm, type) => {
587
640
  // if length != 0
588
641
  /* [ Opcodes.i32_eqz ],
589
642
  [ Opcodes.i32_eqz ], */
590
- Opcodes.i32_from_u
591
- ]
643
+ ...(int ? [] : [ Opcodes.i32_from_u ])
644
+ ];
592
645
  }
593
646
 
594
647
  // if != 0
@@ -601,12 +654,12 @@ const truthy = (scope, wasm, type) => {
601
654
  ];
602
655
  };
603
656
 
604
- const falsy = (scope, wasm, type) => {
657
+ const falsy = (scope, wasm, type, int = false) => {
605
658
  // arrays are always truthy
606
659
  if (type === TYPES._array) return [
607
660
  ...wasm,
608
661
  [ Opcodes.drop ],
609
- ...number(0)
662
+ ...number(0, int ? Valtype.i32 : valtypeBinary)
610
663
  ];
611
664
 
612
665
  if (type === TYPES.string) {
@@ -621,7 +674,7 @@ const falsy = (scope, wasm, type) => {
621
674
 
622
675
  // if length == 0
623
676
  [ Opcodes.i32_eqz ],
624
- Opcodes.i32_from_u
677
+ ...(int ? [] : [ Opcodes.i32_from_u ])
625
678
  ]
626
679
  }
627
680
 
@@ -629,31 +682,29 @@ const falsy = (scope, wasm, type) => {
629
682
  return [
630
683
  ...wasm,
631
684
 
632
- ...Opcodes.eqz,
633
- Opcodes.i32_from_u
685
+ ...(int ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz, Opcodes.i32_from_u ])
634
686
  ];
635
687
  };
636
688
 
637
- const nullish = (scope, wasm, type) => {
689
+ const nullish = (scope, wasm, type, int = false) => {
638
690
  // undefined
639
691
  if (type === TYPES.undefined) return [
640
692
  ...wasm,
641
693
  [ Opcodes.drop ],
642
- ...number(1)
694
+ ...number(1, int ? Valtype.i32 : valtypeBinary)
643
695
  ];
644
696
 
645
697
  // null (if object and = "0")
646
698
  if (type === TYPES.object) return [
647
699
  ...wasm,
648
- ...Opcodes.eqz,
649
- Opcodes.i32_from_u
700
+ ...(int ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz, Opcodes.i32_from_u ])
650
701
  ];
651
702
 
652
703
  // not
653
704
  return [
654
705
  ...wasm,
655
706
  [ Opcodes.drop ],
656
- ...number(0)
707
+ ...number(0, int ? Valtype.i32 : valtypeBinary)
657
708
  ];
658
709
  };
659
710
 
@@ -664,29 +715,44 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
664
715
 
665
716
  if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
666
717
 
667
- // if strict (in)equal and known types mismatch, return false (===)/true (!==)
668
- if ((op === '===' || op === '!==') && leftType && rightType && leftType !== rightType) {
718
+ const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
719
+
720
+ if (leftType && rightType && (
721
+ // if strict (in)equal and known types mismatch, return false (===)/true (!==)
722
+ ((op === '===' || op === '!==') && leftType !== rightType) ||
723
+
724
+ // if equality op and an operand is undefined, return false
725
+ (eqOp && leftType === TYPES.undefined ^ rightType === TYPES.undefined)
726
+ )) {
727
+ // undefined == null
728
+ if (((leftType === TYPES.undefined && rightType === TYPES.object) || (leftType === TYPES.object && rightType === TYPES.undefined)) && (op === '==' || op === '!=')) return [
729
+ ...(leftType === TYPES.object ? left : right),
730
+ ...Opcodes.eqz,
731
+ ...(op === '!=' ? [ [ Opcodes.i32_eqz ] ] : [])
732
+ ];
733
+
669
734
  return [
670
735
  ...left,
671
- ...right,
672
-
673
- // drop values
674
736
  [ Opcodes.drop ],
737
+
738
+ ...right,
675
739
  [ Opcodes.drop ],
676
740
 
677
- // return false (===)/true (!==)
678
- ...number(op === '===' ? 0 : 1, Valtype.i32)
741
+ // return true (!=/!==) or false (else)
742
+ ...number(op === '!=' || op === '!==' ? 1 : 0, Valtype.i32)
679
743
  ];
680
744
  }
681
745
 
746
+ // todo: niche null hell with 0
747
+
682
748
  if (leftType === TYPES.string || rightType === TYPES.string) {
683
749
  if (op === '+') {
684
750
  // string concat (a + b)
685
751
  return concatStrings(scope, left, right, _global, _name, assign);
686
752
  }
687
753
 
688
- // any other math op, NaN
689
- if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
754
+ // not an equality op, NaN
755
+ if (!eqOp) return number(NaN);
690
756
 
691
757
  // else leave bool ops
692
758
  // todo: convert string to number if string and number/bool
@@ -731,21 +797,15 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
731
797
  ];
732
798
  };
733
799
 
734
- let binaryExpDepth = 0;
735
800
  const generateBinaryExp = (scope, decl, _global, _name) => {
736
- binaryExpDepth++;
737
-
738
- const out = [
739
- ...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
740
- ];
801
+ const out = performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name);
741
802
 
742
803
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
743
804
 
744
- binaryExpDepth--;
745
805
  return out;
746
806
  };
747
807
 
748
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, memory, localNames = [], globalNames = [] }) => {
808
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
749
809
  const existing = funcs.find(x => x.name === name);
750
810
  if (existing) return existing;
751
811
 
@@ -781,7 +841,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
781
841
  returns,
782
842
  returnType: TYPES[returnType ?? 'number'],
783
843
  wasm,
784
- memory,
785
844
  internal: true,
786
845
  index: currentFuncIndex++
787
846
  };
@@ -814,7 +873,8 @@ const TYPES = {
814
873
  bigint: 0xffffffffffff7,
815
874
 
816
875
  // these are not "typeof" types but tracked internally
817
- _array: 0xffffffffffff8
876
+ _array: 0xfffffffffff0f,
877
+ _regexp: 0xfffffffffff1f
818
878
  };
819
879
 
820
880
  const TYPE_NAMES = {
@@ -849,6 +909,8 @@ const getType = (scope, _name) => {
849
909
  const getNodeType = (scope, node) => {
850
910
  if (node.type === 'Literal') {
851
911
  if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
912
+ if (node.regex) return TYPES._regexp;
913
+
852
914
  return TYPES[typeof node.value];
853
915
  }
854
916
 
@@ -882,6 +944,11 @@ const getNodeType = (scope, node) => {
882
944
 
883
945
  // literal.func()
884
946
  if (!name && node.callee.type === 'MemberExpression') {
947
+ if (node.callee.object.regex) {
948
+ const funcName = node.callee.property.name;
949
+ return Rhemyn[funcName] ? TYPES.boolean : TYPES.undefined;
950
+ }
951
+
885
952
  const baseType = getNodeType(scope, node.callee.object);
886
953
 
887
954
  const func = node.callee.property.name;
@@ -925,6 +992,11 @@ const getNodeType = (scope, node) => {
925
992
  const generateLiteral = (scope, decl, global, name) => {
926
993
  if (decl.value === null) return number(NULL);
927
994
 
995
+ if (decl.regex) {
996
+ scope.regex[name] = decl.regex;
997
+ return number(1);
998
+ }
999
+
928
1000
  switch (typeof decl.value) {
929
1001
  case 'number':
930
1002
  return number(decl.value);
@@ -946,12 +1018,38 @@ const generateLiteral = (scope, decl, global, name) => {
946
1018
  case 'bigint': return number(TYPES.bigint);
947
1019
  }
948
1020
 
1021
+ const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
1022
+ let wellFormed = aotWFA ? true : undefined;
1023
+
949
1024
  const str = decl.value;
950
1025
  const rawElements = new Array(str.length);
1026
+ let j = 0;
951
1027
  for (let i = 0; i < str.length; i++) {
952
1028
  rawElements[i] = str.charCodeAt(i);
1029
+
1030
+ if (wellFormed) {
1031
+ // check if surrogate
1032
+ if ((str.charCodeAt(j) & 0xF800) === 0xD800) {
1033
+ // unpaired trailing surrogate
1034
+ if (str.charCodeAt(j) >= 0xDC00) {
1035
+ wellFormed = false;
1036
+ }
1037
+
1038
+ // unpaired leading surrogate
1039
+ // if (++j >= str.length || (str.charCodeAt(j) & 0xFC00) != 0xDC00) {
1040
+ if ((str.charCodeAt(++j) & 0xFC00) != 0xDC00) {
1041
+ wellFormed = false;
1042
+ }
1043
+ }
1044
+
1045
+ j++;
1046
+ }
953
1047
  }
954
1048
 
1049
+ // console.log(wellFormed, str);
1050
+
1051
+ if (aotWFA) addVarMeta(name, { wellFormed });
1052
+
955
1053
  return makeArray(scope, {
956
1054
  rawElements
957
1055
  }, global, name, false, 'i16')[0];
@@ -986,6 +1084,8 @@ const countLeftover = wasm => {
986
1084
  } else count--;
987
1085
  if (func) count += func.returns.length;
988
1086
  } else count--;
1087
+
1088
+ // console.log(count, decompile([ inst ]).slice(0, -1));
989
1089
  }
990
1090
 
991
1091
  return count;
@@ -1084,6 +1184,25 @@ const generateCall = (scope, decl, _global, _name) => {
1084
1184
 
1085
1185
  // literal.func()
1086
1186
  if (!name && decl.callee.type === 'MemberExpression') {
1187
+ // megahack for /regex/.func()
1188
+ if (decl.callee.object.regex) {
1189
+ const funcName = decl.callee.property.name;
1190
+ const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1191
+
1192
+ funcIndex[func.name] = func.index;
1193
+ funcs.push(func);
1194
+
1195
+ return [
1196
+ // make string arg
1197
+ ...generate(scope, decl.arguments[0]),
1198
+
1199
+ // call regex func
1200
+ Opcodes.i32_to_u,
1201
+ [ Opcodes.call, func.index ],
1202
+ Opcodes.i32_from
1203
+ ];
1204
+ }
1205
+
1087
1206
  baseType = getNodeType(scope, decl.callee.object);
1088
1207
 
1089
1208
  const func = decl.callee.property.name;
@@ -1096,6 +1215,31 @@ const generateCall = (scope, decl, _global, _name) => {
1096
1215
  baseName = [...arrays.keys()].pop();
1097
1216
  }
1098
1217
 
1218
+ if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
1219
+ const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
1220
+
1221
+ funcIndex[func.name] = func.index;
1222
+ funcs.push(func);
1223
+
1224
+ const pointer = arrays.get(baseName);
1225
+ const [ local, isGlobal ] = lookupName(scope, baseName);
1226
+
1227
+ return [
1228
+ ...out,
1229
+
1230
+ ...(pointer == null ? [
1231
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1232
+ Opcodes.i32_to_u,
1233
+ ] : [
1234
+ ...number(pointer, Valtype.i32)
1235
+ ]),
1236
+
1237
+ // call regex func
1238
+ [ Opcodes.call, func.index ],
1239
+ Opcodes.i32_from
1240
+ ];
1241
+ }
1242
+
1099
1243
  if (protoFunc) {
1100
1244
  let pointer = arrays.get(baseName);
1101
1245
 
@@ -1124,28 +1268,39 @@ const generateCall = (scope, decl, _global, _name) => {
1124
1268
  if (protoFunc.noArgRetLength && decl.arguments.length === 0) return arrayUtil.getLength(pointer)
1125
1269
 
1126
1270
  let protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp`, protoFunc.local) : -1;
1271
+ let protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp2`, protoFunc.local2) : -1;
1127
1272
 
1128
1273
  // use local for cached i32 length as commonly used
1129
1274
  let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1130
1275
 
1276
+ let lengthI32CacheUsed = false;
1277
+
1278
+ const protoOut = protoFunc(pointer, {
1279
+ getCachedI32: () => {
1280
+ lengthI32CacheUsed = true;
1281
+ return [ [ Opcodes.local_get, lengthLocal ] ]
1282
+ },
1283
+ setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
1284
+ get: () => arrayUtil.getLength(pointer),
1285
+ getI32: () => arrayUtil.getLengthI32(pointer),
1286
+ set: value => arrayUtil.setLength(pointer, value),
1287
+ setI32: value => arrayUtil.setLengthI32(pointer, value)
1288
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
1289
+ return makeArray(scope, {
1290
+ rawElements: new Array(length)
1291
+ }, _global, _name, true, itemType);
1292
+ }, varMetadata.get(baseName));
1293
+
1131
1294
  return [
1132
1295
  ...out,
1133
1296
 
1134
- ...arrayUtil.getLengthI32(pointer),
1135
- [ Opcodes.local_set, lengthLocal ],
1297
+ ...(!lengthI32CacheUsed ? [] : [
1298
+ ...arrayUtil.getLengthI32(pointer),
1299
+ [ Opcodes.local_set, lengthLocal ],
1300
+ ]),
1136
1301
 
1137
1302
  [ Opcodes.block, valtypeBinary ],
1138
- ...protoFunc(pointer, {
1139
- cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
1140
- get: arrayUtil.getLength(pointer),
1141
- getI32: arrayUtil.getLengthI32(pointer),
1142
- set: value => arrayUtil.setLength(pointer, value),
1143
- setI32: value => arrayUtil.setLengthI32(pointer, value)
1144
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
1145
- return makeArray(scope, {
1146
- rawElements: new Array(length)
1147
- }, _global, _name, true, itemType);
1148
- }),
1303
+ ...protoOut,
1149
1304
  [ Opcodes.end ]
1150
1305
  ];
1151
1306
  }
@@ -1205,7 +1360,7 @@ const generateCall = (scope, decl, _global, _name) => {
1205
1360
  if (func && func.throws) scope.throws = true;
1206
1361
 
1207
1362
  for (const arg of args) {
1208
- out.push(...generate(scope, arg));
1363
+ out = out.concat(generate(scope, arg));
1209
1364
  }
1210
1365
 
1211
1366
  out.push([ Opcodes.call, idx ]);
@@ -1216,7 +1371,7 @@ const generateCall = (scope, decl, _global, _name) => {
1216
1371
  const generateNew = (scope, decl, _global, _name) => {
1217
1372
  // hack: basically treat this as a normal call for builtins for now
1218
1373
  const name = mapName(decl.callee.name);
1219
- if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1374
+ if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1220
1375
  if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1221
1376
 
1222
1377
  return generateCall(scope, decl, _global, _name);
@@ -1234,12 +1389,12 @@ const unhackName = name => {
1234
1389
  };
1235
1390
 
1236
1391
  const generateVar = (scope, decl) => {
1237
- const out = [];
1392
+ let out = [];
1238
1393
 
1239
1394
  const topLevel = scope.name === 'main';
1240
1395
 
1241
1396
  // global variable if in top scope (main) and var ..., or if wanted
1242
- const global = decl.kind === 'var';
1397
+ const global = topLevel || decl._bare; // decl.kind === 'var';
1243
1398
  const target = global ? globals : scope.locals;
1244
1399
 
1245
1400
  for (const x of decl.declarations) {
@@ -1276,7 +1431,7 @@ const generateVar = (scope, decl) => {
1276
1431
 
1277
1432
  // x.init ??= DEFAULT_VALUE;
1278
1433
  if (x.init) {
1279
- out.push(...generate(scope, x.init, global, name));
1434
+ out = out.concat(generate(scope, x.init, global, name));
1280
1435
 
1281
1436
  // if our value is the result of a function, infer the type from that func's return value
1282
1437
  if (out[out.length - 1][0] === Opcodes.call) {
@@ -1345,13 +1500,60 @@ const generateAssign = (scope, decl) => {
1345
1500
  ];
1346
1501
  }
1347
1502
 
1503
+ const op = decl.operator.slice(0, -1) || '=';
1504
+
1505
+ // arr[i] | str[i]
1506
+ if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1507
+ const name = decl.left.object.name;
1508
+ const pointer = arrays.get(name);
1509
+
1510
+ const aotPointer = pointer != null;
1511
+
1512
+ const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
1513
+ const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1514
+
1515
+ const parentType = getNodeType(scope, decl.left.object);
1516
+
1517
+ return [
1518
+ ...(aotPointer ? [] : [
1519
+ ...generate(scope, decl.left.object),
1520
+ Opcodes.i32_to_u
1521
+ ]),
1522
+
1523
+ // get index as valtype
1524
+ ...generate(scope, decl.left.property),
1525
+
1526
+ // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
1527
+ Opcodes.i32_to_u,
1528
+ ...number(ValtypeSize[valtype], Valtype.i32),
1529
+ [ Opcodes.i32_mul ],
1530
+ ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
1531
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
1532
+
1533
+ ...(op === '=' ? generate(scope, decl.right, false, name) : performOp(scope, op, [
1534
+ [ Opcodes.local_get, pointerTmp ],
1535
+ [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1536
+ ], generate(scope, decl.right), parentType === TYPES._array ? TYPES.number : TYPES.string, getNodeType(scope, decl.right), false, name, true)),
1537
+ [ Opcodes.local_tee, newValueTmp ],
1538
+
1539
+ ...(parentType === TYPES._array ? [
1540
+ [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1541
+ ] : [
1542
+ Opcodes.i32_to_u,
1543
+ [ StoreOps.i16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1544
+ ]),
1545
+
1546
+ [ Opcodes.local_get, newValueTmp ]
1547
+ ];
1548
+ }
1549
+
1348
1550
  const [ local, isGlobal ] = lookupName(scope, name);
1349
1551
 
1350
1552
  if (local === undefined) {
1351
- // todo: this should be a devtools/repl/??? only thing
1553
+ // todo: this should be a sloppy mode only thing
1352
1554
 
1353
1555
  // only allow = for this
1354
- if (decl.operator !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1556
+ if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1355
1557
 
1356
1558
  if (builtinVars[name]) {
1357
1559
  // just return rhs (eg `NaN = 2`)
@@ -1360,13 +1562,15 @@ const generateAssign = (scope, decl) => {
1360
1562
 
1361
1563
  // set global and return (eg a = 2)
1362
1564
  return [
1363
- ...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
1565
+ ...generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name }, init: decl.right } ] }),
1364
1566
  [ Opcodes.global_get, globals[name].idx ]
1365
1567
  ];
1366
1568
  }
1367
1569
 
1368
- if (decl.operator === '=') {
1369
- typeStates[name] = getNodeType(scope, decl.right);
1570
+ typeStates[name] = getNodeType(scope, decl.right);
1571
+
1572
+ if (op === '=') {
1573
+ // typeStates[name] = getNodeType(scope, decl.right);
1370
1574
 
1371
1575
  return [
1372
1576
  ...generate(scope, decl.right, isGlobal, name),
@@ -1375,7 +1579,6 @@ const generateAssign = (scope, decl) => {
1375
1579
  ];
1376
1580
  }
1377
1581
 
1378
- const op = decl.operator.slice(0, -1);
1379
1582
  if (op === '||' || op === '&&' || op === '??') {
1380
1583
  // todo: is this needed?
1381
1584
  // for logical assignment ops, it is not left @= right ~= left = left @ right
@@ -1510,7 +1713,7 @@ const generateUpdate = (scope, decl) => {
1510
1713
  };
1511
1714
 
1512
1715
  const generateIf = (scope, decl) => {
1513
- const out = truthy(scope, generate(scope, decl.test), decl.test);
1716
+ const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test));
1514
1717
 
1515
1718
  out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1516
1719
  depth.push('if');
@@ -1603,18 +1806,106 @@ const generateWhile = (scope, decl) => {
1603
1806
  const generateForOf = (scope, decl) => {
1604
1807
  const out = [];
1605
1808
 
1809
+ const rightType = getNodeType(scope, decl.right);
1810
+ const valtypeSize = rightType === TYPES._array ? ValtypeSize[valtype] : ValtypeSize.i16; // presume array (:()
1811
+
1812
+ // todo: for of inside for of might fuck up?
1813
+ const pointer = localTmp(scope, 'forof_base_pointer', Valtype.i32);
1814
+ const length = localTmp(scope, 'forof_length', Valtype.i32);
1815
+ const counter = localTmp(scope, 'forof_counter', Valtype.i32);
1816
+
1817
+ out.push(
1818
+ // set pointer as right
1819
+ ...generate(scope, decl.right),
1820
+ Opcodes.i32_to_u,
1821
+ [ Opcodes.local_set, pointer ],
1822
+
1823
+ // get length
1824
+ [ Opcodes.local_get, pointer ],
1825
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
1826
+ [ Opcodes.local_set, length ]
1827
+ );
1828
+
1606
1829
  out.push([ Opcodes.loop, Blocktype.void ]);
1607
- depth.push('while');
1830
+ depth.push('forof');
1608
1831
 
1609
- out.push(...generate(scope, decl.test));
1610
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1611
- depth.push('if');
1832
+ // setup local for left
1833
+ generate(scope, decl.left);
1612
1834
 
1613
- out.push(...generate(scope, decl.body));
1835
+ const leftName = decl.left.declarations[0].id.name;
1614
1836
 
1615
- out.push([ Opcodes.br, 1 ]);
1616
- out.push([ Opcodes.end ], [ Opcodes.end ]);
1617
- depth.pop(); depth.pop();
1837
+ // set type for local
1838
+ typeStates[leftName] = rightType === TYPES._array ? TYPES.number : TYPES.string;
1839
+
1840
+ const [ local, isGlobal ] = lookupName(scope, leftName);
1841
+
1842
+ if (rightType === TYPES._array) { // array
1843
+ out.push(
1844
+ [ Opcodes.local_get, pointer ],
1845
+ [ Opcodes.load, Math.log2(valtypeSize) - 1, ...unsignedLEB128(ValtypeSize.i32) ]
1846
+ );
1847
+ } else { // string
1848
+ const [ newOut, newPointer ] = makeArray(scope, {
1849
+ rawElements: new Array(1)
1850
+ }, isGlobal, leftName, true, 'i16');
1851
+
1852
+ out.push(
1853
+ // setup new/out array
1854
+ ...newOut,
1855
+ [ Opcodes.drop ],
1856
+
1857
+ ...number(0, Valtype.i32), // base 0 for store after
1858
+
1859
+ // load current string ind {arg}
1860
+ [ Opcodes.local_get, pointer ],
1861
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
1862
+
1863
+ // store to new string ind 0
1864
+ [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
1865
+
1866
+ // return new string (page)
1867
+ ...number(newPointer)
1868
+ );
1869
+ }
1870
+
1871
+ // set left value
1872
+ out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ]);
1873
+
1874
+ out.push(
1875
+ [ Opcodes.block, Blocktype.void ],
1876
+ [ Opcodes.block, Blocktype.void ]
1877
+ );
1878
+ depth.push('block');
1879
+ depth.push('block');
1880
+
1881
+ out.push(
1882
+ ...generate(scope, decl.body),
1883
+ [ Opcodes.end ]
1884
+ );
1885
+ depth.pop();
1886
+
1887
+ out.push(
1888
+ // increment iter pointer by valtype size
1889
+ [ Opcodes.local_get, pointer ],
1890
+ ...number(valtypeSize, Valtype.i32),
1891
+ [ Opcodes.i32_add ],
1892
+ [ Opcodes.local_set, pointer ],
1893
+
1894
+ // increment counter by 1
1895
+ [ Opcodes.local_get, counter ],
1896
+ ...number(1, Valtype.i32),
1897
+ [ Opcodes.i32_add ],
1898
+ [ Opcodes.local_tee, counter ],
1899
+
1900
+ // loop if counter != length
1901
+ [ Opcodes.local_get, length ],
1902
+ [ Opcodes.i32_ne ],
1903
+ [ Opcodes.br_if, 1 ],
1904
+
1905
+ [ Opcodes.end ], [ Opcodes.end ]
1906
+ );
1907
+ depth.pop();
1908
+ depth.pop();
1618
1909
 
1619
1910
  return out;
1620
1911
  };
@@ -1705,19 +1996,19 @@ const generateAssignPat = (scope, decl) => {
1705
1996
  };
1706
1997
 
1707
1998
  let pages = new Map();
1708
- const allocPage = reason => {
1709
- if (pages.has(reason)) return pages.get(reason);
1999
+ const allocPage = (reason, type) => {
2000
+ if (pages.has(reason)) return pages.get(reason).ind;
1710
2001
 
1711
- let ind = pages.size;
1712
- pages.set(reason, ind);
2002
+ const ind = pages.size;
2003
+ pages.set(reason, { ind, type });
1713
2004
 
1714
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason}`);
2005
+ if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
1715
2006
 
1716
2007
  return ind;
1717
2008
  };
1718
2009
 
1719
2010
  const freePage = reason => {
1720
- let ind = pages.get(reason);
2011
+ const { ind } = pages.get(reason);
1721
2012
  pages.delete(reason);
1722
2013
 
1723
2014
  if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
@@ -1734,7 +2025,7 @@ const itemTypeToValtype = {
1734
2025
  i16: 'i32'
1735
2026
  };
1736
2027
 
1737
- const storeOps = {
2028
+ const StoreOps = {
1738
2029
  i32: Opcodes.i32_store,
1739
2030
  i64: Opcodes.i64_store,
1740
2031
  f64: Opcodes.f64_store,
@@ -1743,13 +2034,31 @@ const storeOps = {
1743
2034
  i16: Opcodes.i32_store16
1744
2035
  };
1745
2036
 
2037
+ let data = [];
2038
+
2039
+ const compileBytes = (val, itemType, signed = true) => {
2040
+ switch (itemType) {
2041
+ case 'i8': return [ val % 256 ];
2042
+ case 'i16': return [ val % 256, Math.floor(val / 256) ];
2043
+
2044
+ case 'i32':
2045
+ case 'i64':
2046
+ return enforceFourBytes(signedLEB128(val));
2047
+
2048
+ case 'f64': return ieee754_binary64(val);
2049
+ }
2050
+ };
2051
+
1746
2052
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
1747
2053
  const out = [];
1748
2054
 
2055
+ let firstAssign = false;
1749
2056
  if (!arrays.has(name) || name === '$undeclared') {
2057
+ firstAssign = true;
2058
+
1750
2059
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1751
2060
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1752
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`) * pageSize);
2061
+ arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
1753
2062
  }
1754
2063
 
1755
2064
  const pointer = arrays.get(name);
@@ -1757,8 +2066,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1757
2066
  const useRawElements = !!decl.rawElements;
1758
2067
  const elements = useRawElements ? decl.rawElements : decl.elements;
1759
2068
 
2069
+ const valtype = itemTypeToValtype[itemType];
1760
2070
  const length = elements.length;
1761
2071
 
2072
+ if (firstAssign && useRawElements) {
2073
+ let bytes = compileBytes(length, 'i32');
2074
+
2075
+ if (!initEmpty) for (let i = 0; i < length; i++) {
2076
+ if (elements[i] == null) continue;
2077
+
2078
+ bytes.push(...compileBytes(elements[i], itemType));
2079
+ }
2080
+
2081
+ data.push({
2082
+ offset: pointer,
2083
+ bytes
2084
+ });
2085
+
2086
+ // local value as pointer
2087
+ out.push(...number(pointer));
2088
+
2089
+ return [ out, pointer ];
2090
+ }
2091
+
1762
2092
  // store length as 0th array
1763
2093
  out.push(
1764
2094
  ...number(0, Valtype.i32),
@@ -1766,8 +2096,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1766
2096
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
1767
2097
  );
1768
2098
 
1769
- const storeOp = storeOps[itemType];
1770
- const valtype = itemTypeToValtype[itemType];
2099
+ const storeOp = StoreOps[itemType];
1771
2100
 
1772
2101
  if (!initEmpty) for (let i = 0; i < length; i++) {
1773
2102
  if (elements[i] == null) continue;
@@ -1790,6 +2119,17 @@ const generateArray = (scope, decl, global = false, name = '$undeclared', initEm
1790
2119
  return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
1791
2120
  };
1792
2121
 
2122
+ let varMetadata = new Map();
2123
+ const addVarMeta = (_name, obj) => {
2124
+ const name = _name ?? '$undeclared';
2125
+ if (!varMetadata.has(name)) varMetadata.set(name, {});
2126
+
2127
+ const meta = varMetadata.get(name);
2128
+ for (const k in obj) {
2129
+ meta[k] = obj[k];
2130
+ }
2131
+ };
2132
+
1793
2133
  export const generateMember = (scope, decl, _global, _name) => {
1794
2134
  const type = getNodeType(scope, decl.object);
1795
2135
 
@@ -1929,7 +2269,6 @@ const generateFunc = (scope, decl) => {
1929
2269
  localInd: 0,
1930
2270
  returns: [ valtypeBinary ],
1931
2271
  returnType: null,
1932
- memory: false,
1933
2272
  throws: false,
1934
2273
  name
1935
2274
  };
@@ -2092,10 +2431,10 @@ const generateFunc = (scope, decl) => {
2092
2431
  };
2093
2432
 
2094
2433
  const generateCode = (scope, decl) => {
2095
- const out = [];
2434
+ let out = [];
2096
2435
 
2097
2436
  for (const x of decl.body) {
2098
- out.push(...generate(scope, x));
2437
+ out = out.concat(generate(scope, x));
2099
2438
  }
2100
2439
 
2101
2440
  return out;
@@ -2131,6 +2470,18 @@ const internalConstrs = {
2131
2470
  ];
2132
2471
  },
2133
2472
  type: TYPES._array
2473
+ },
2474
+
2475
+ __Array_of: {
2476
+ // this is not a constructor but best fits internal structure here
2477
+ generate: (scope, decl, global, name) => {
2478
+ // Array.of(i0, i1, ...)
2479
+ return generateArray(scope, {
2480
+ elements: decl.arguments
2481
+ }, global, name);
2482
+ },
2483
+ type: TYPES._array,
2484
+ notConstr: true
2134
2485
  }
2135
2486
  };
2136
2487
 
@@ -2144,7 +2495,9 @@ export default program => {
2144
2495
  depth = [];
2145
2496
  typeStates = {};
2146
2497
  arrays = new Map();
2498
+ varMetadata = new Map();
2147
2499
  pages = new Map();
2500
+ data = [];
2148
2501
  currentFuncIndex = importedFuncs.length;
2149
2502
 
2150
2503
  globalThis.valtype = 'f64';
@@ -2221,5 +2574,5 @@ export default program => {
2221
2574
  // if blank main func and other exports, remove it
2222
2575
  if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
2223
2576
 
2224
- return { funcs, globals, tags, exceptions, pages };
2577
+ return { funcs, globals, tags, exceptions, pages, data };
2225
2578
  };