porffor 0.0.0-61de729 → 0.0.0-679c4ea

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,45 +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
- scope.memory = true;
178
- allocPage('asm instrinsic');
179
- // todo: add to store/load offset insts
180
- continue;
181
- }
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));
182
186
 
183
- let inst = Opcodes[asm[0].replace('.', '_')];
184
- if (!inst) throw new Error(`inline asm: inst ${asm[0]} not found`);
187
+ out.push([ ...inst, ...immediates ]);
188
+ }
185
189
 
186
- if (!Array.isArray(inst)) inst = [ inst ];
187
- const immediates = asm.slice(1).map(x => parseInt(x));
190
+ return out;
191
+ },
188
192
 
189
- 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
+ }
190
205
  }
191
206
 
192
- 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
+ }
193
214
 
194
215
  default:
195
216
  return todo(`no generation for ${decl.type}!`);
@@ -288,7 +309,7 @@ const generateReturn = (scope, decl) => {
288
309
  ];
289
310
  }
290
311
 
291
- if (!scope.returnType) scope.returnType = getNodeType(scope, decl.argument);
312
+ scope.returnType = getNodeType(scope, decl.argument);
292
313
 
293
314
  return [
294
315
  ...generate(scope, decl.argument),
@@ -309,7 +330,7 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
309
330
  const checks = {
310
331
  '||': falsy,
311
332
  '&&': truthy,
312
- // todo: ??
333
+ '??': nullish
313
334
  };
314
335
 
315
336
  if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
@@ -336,8 +357,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
336
357
  // todo: optimize by looking up names in arrays and using that if exists?
337
358
  // todo: optimize this if using literals/known lengths?
338
359
 
339
- scope.memory = true;
340
-
341
360
  const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
342
361
  const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
343
362
  const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
@@ -472,8 +491,6 @@ const compareStrings = (scope, left, right) => {
472
491
  // todo: optimize by looking up names in arrays and using that if exists?
473
492
  // todo: optimize this if using literals/known lengths?
474
493
 
475
- scope.memory = true;
476
-
477
494
  const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
478
495
  const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
479
496
  const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
@@ -575,8 +592,8 @@ const truthy = (scope, wasm, type) => {
575
592
  // arrays are always truthy
576
593
  if (type === TYPES._array) return [
577
594
  ...wasm,
578
- ...(wasm.length === 0 ? [] : [ [ Opcodes.drop ] ]),
579
- number(1)
595
+ [ Opcodes.drop ],
596
+ ...number(1)
580
597
  ];
581
598
 
582
599
  if (type === TYPES.string) {
@@ -610,8 +627,8 @@ const falsy = (scope, wasm, type) => {
610
627
  // arrays are always truthy
611
628
  if (type === TYPES._array) return [
612
629
  ...wasm,
613
- ...(wasm.length === 0 ? [] : [ [ Opcodes.drop ] ]),
614
- number(0)
630
+ [ Opcodes.drop ],
631
+ ...number(0)
615
632
  ];
616
633
 
617
634
  if (type === TYPES.string) {
@@ -639,6 +656,29 @@ const falsy = (scope, wasm, type) => {
639
656
  ];
640
657
  };
641
658
 
659
+ const nullish = (scope, wasm, type) => {
660
+ // undefined
661
+ if (type === TYPES.undefined) return [
662
+ ...wasm,
663
+ [ Opcodes.drop ],
664
+ ...number(1)
665
+ ];
666
+
667
+ // null (if object and = "0")
668
+ if (type === TYPES.object) return [
669
+ ...wasm,
670
+ ...Opcodes.eqz,
671
+ Opcodes.i32_from_u
672
+ ];
673
+
674
+ // not
675
+ return [
676
+ ...wasm,
677
+ [ Opcodes.drop ],
678
+ ...number(0)
679
+ ];
680
+ };
681
+
642
682
  const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
643
683
  if (op === '||' || op === '&&' || op === '??') {
644
684
  return performLogicOp(scope, op, left, right, leftType, rightType);
@@ -646,29 +686,44 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
646
686
 
647
687
  if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
648
688
 
649
- // if strict (in)equal and known types mismatch, return false (===)/true (!==)
650
- if ((op === '===' || op === '!==') && leftType && rightType && leftType !== rightType) {
689
+ const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
690
+
691
+ if (leftType && rightType && (
692
+ // if strict (in)equal and known types mismatch, return false (===)/true (!==)
693
+ ((op === '===' || op === '!==') && leftType !== rightType) ||
694
+
695
+ // if equality op and an operand is undefined, return false
696
+ (eqOp && leftType === TYPES.undefined ^ rightType === TYPES.undefined)
697
+ )) {
698
+ // undefined == null
699
+ if (((leftType === TYPES.undefined && rightType === TYPES.object) || (leftType === TYPES.object && rightType === TYPES.undefined)) && (op === '==' || op === '!=')) return [
700
+ ...(leftType === TYPES.object ? left : right),
701
+ ...Opcodes.eqz,
702
+ ...(op === '!=' ? [ [ Opcodes.i32_eqz ] ] : [])
703
+ ];
704
+
651
705
  return [
652
706
  ...left,
653
- ...right,
654
-
655
- // drop values
656
707
  [ Opcodes.drop ],
708
+
709
+ ...right,
657
710
  [ Opcodes.drop ],
658
711
 
659
- // return false (===)/true (!==)
660
- ...number(op === '===' ? 0 : 1, Valtype.i32)
712
+ // return true (!=/!==) or false (else)
713
+ ...number(op === '!=' || op === '!==' ? 1 : 0, Valtype.i32)
661
714
  ];
662
715
  }
663
716
 
717
+ // todo: niche null hell with 0
718
+
664
719
  if (leftType === TYPES.string || rightType === TYPES.string) {
665
720
  if (op === '+') {
666
721
  // string concat (a + b)
667
722
  return concatStrings(scope, left, right, _global, _name, assign);
668
723
  }
669
724
 
670
- // any other math op, NaN
671
- if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
725
+ // not an equality op, NaN
726
+ if (!eqOp) return number(NaN);
672
727
 
673
728
  // else leave bool ops
674
729
  // todo: convert string to number if string and number/bool
@@ -713,21 +768,15 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
713
768
  ];
714
769
  };
715
770
 
716
- let binaryExpDepth = 0;
717
771
  const generateBinaryExp = (scope, decl, _global, _name) => {
718
- binaryExpDepth++;
719
-
720
- const out = [
721
- ...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
722
- ];
772
+ const out = performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name);
723
773
 
724
774
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
725
775
 
726
- binaryExpDepth--;
727
776
  return out;
728
777
  };
729
778
 
730
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, memory, localNames = [], globalNames = [] }) => {
779
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
731
780
  const existing = funcs.find(x => x.name === name);
732
781
  if (existing) return existing;
733
782
 
@@ -763,7 +812,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
763
812
  returns,
764
813
  returnType: TYPES[returnType ?? 'number'],
765
814
  wasm,
766
- memory,
767
815
  internal: true,
768
816
  index: currentFuncIndex++
769
817
  };
@@ -796,7 +844,8 @@ const TYPES = {
796
844
  bigint: 0xffffffffffff7,
797
845
 
798
846
  // these are not "typeof" types but tracked internally
799
- _array: 0xffffffffffff8
847
+ _array: 0xfffffffffff0f,
848
+ _regexp: 0xfffffffffff1f
800
849
  };
801
850
 
802
851
  const TYPE_NAMES = {
@@ -831,6 +880,8 @@ const getType = (scope, _name) => {
831
880
  const getNodeType = (scope, node) => {
832
881
  if (node.type === 'Literal') {
833
882
  if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
883
+ if (node.regex) return TYPES._regexp;
884
+
834
885
  return TYPES[typeof node.value];
835
886
  }
836
887
 
@@ -864,6 +915,11 @@ const getNodeType = (scope, node) => {
864
915
 
865
916
  // literal.func()
866
917
  if (!name && node.callee.type === 'MemberExpression') {
918
+ if (node.callee.object.regex) {
919
+ const funcName = node.callee.property.name;
920
+ return Rhemyn[funcName] ? TYPES.boolean : TYPES.undefined;
921
+ }
922
+
867
923
  const baseType = getNodeType(scope, node.callee.object);
868
924
 
869
925
  const func = node.callee.property.name;
@@ -907,6 +963,11 @@ const getNodeType = (scope, node) => {
907
963
  const generateLiteral = (scope, decl, global, name) => {
908
964
  if (decl.value === null) return number(NULL);
909
965
 
966
+ if (decl.regex) {
967
+ scope.regex[name] = decl.regex;
968
+ return number(1);
969
+ }
970
+
910
971
  switch (typeof decl.value) {
911
972
  case 'number':
912
973
  return number(decl.value);
@@ -946,7 +1007,8 @@ const generateLiteral = (scope, decl, global, name) => {
946
1007
  const countLeftover = wasm => {
947
1008
  let count = 0, depth = 0;
948
1009
 
949
- for (const inst of wasm) {
1010
+ for (let i = 0; i < wasm.length; i++) {
1011
+ const inst = wasm[i];
950
1012
  if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
951
1013
  if (inst[0] === Opcodes.if) count--;
952
1014
  if (inst[1] !== Blocktype.void) count++;
@@ -967,6 +1029,8 @@ const countLeftover = wasm => {
967
1029
  } else count--;
968
1030
  if (func) count += func.returns.length;
969
1031
  } else count--;
1032
+
1033
+ // console.log(count, decompile([ inst ]).slice(0, -1));
970
1034
  }
971
1035
 
972
1036
  return count;
@@ -1050,7 +1114,7 @@ const generateCall = (scope, decl, _global, _name) => {
1050
1114
  }
1051
1115
 
1052
1116
  let out = [];
1053
- let protoFunc, protoName, baseType, baseName = '$undeclared';
1117
+ let protoFunc, protoName, baseType, baseName;
1054
1118
  // ident.func()
1055
1119
  if (name && name.startsWith('__')) {
1056
1120
  const spl = name.slice(2).split('_');
@@ -1065,6 +1129,25 @@ const generateCall = (scope, decl, _global, _name) => {
1065
1129
 
1066
1130
  // literal.func()
1067
1131
  if (!name && decl.callee.type === 'MemberExpression') {
1132
+ // megahack for /regex/.func()
1133
+ if (decl.callee.object.regex) {
1134
+ const funcName = decl.callee.property.name;
1135
+ const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1136
+
1137
+ funcIndex[func.name] = func.index;
1138
+ funcs.push(func);
1139
+
1140
+ return [
1141
+ // make string arg
1142
+ ...generate(scope, decl.arguments[0]),
1143
+
1144
+ // call regex func
1145
+ Opcodes.i32_to_u,
1146
+ [ Opcodes.call, func.index ],
1147
+ Opcodes.i32_from
1148
+ ];
1149
+ }
1150
+
1068
1151
  baseType = getNodeType(scope, decl.callee.object);
1069
1152
 
1070
1153
  const func = decl.callee.property.name;
@@ -1073,11 +1156,36 @@ const generateCall = (scope, decl, _global, _name) => {
1073
1156
 
1074
1157
  out = generate(scope, decl.callee.object);
1075
1158
  out.push([ Opcodes.drop ]);
1159
+
1160
+ baseName = [...arrays.keys()].pop();
1076
1161
  }
1077
1162
 
1078
- if (protoFunc) {
1079
- scope.memory = true;
1163
+ if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
1164
+ const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
1165
+
1166
+ funcIndex[func.name] = func.index;
1167
+ funcs.push(func);
1080
1168
 
1169
+ const pointer = arrays.get(baseName);
1170
+ const [ local, isGlobal ] = lookupName(scope, baseName);
1171
+
1172
+ return [
1173
+ ...out,
1174
+
1175
+ ...(pointer == null ? [
1176
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1177
+ Opcodes.i32_to_u,
1178
+ ] : [
1179
+ ...number(pointer, Valtype.i32)
1180
+ ]),
1181
+
1182
+ // call regex func
1183
+ [ Opcodes.call, func.index ],
1184
+ Opcodes.i32_from
1185
+ ];
1186
+ }
1187
+
1188
+ if (protoFunc) {
1081
1189
  let pointer = arrays.get(baseName);
1082
1190
 
1083
1191
  if (pointer == null) {
@@ -1085,7 +1193,7 @@ const generateCall = (scope, decl, _global, _name) => {
1085
1193
  if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
1086
1194
 
1087
1195
  // register array
1088
- const [ , pointer ] = makeArray(scope, {
1196
+ 0, [ , pointer ] = makeArray(scope, {
1089
1197
  rawElements: new Array(0)
1090
1198
  }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
1091
1199
 
@@ -1109,24 +1217,34 @@ const generateCall = (scope, decl, _global, _name) => {
1109
1217
  // use local for cached i32 length as commonly used
1110
1218
  let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
1111
1219
 
1220
+ let lengthI32CacheUsed = false;
1221
+
1222
+ const protoOut = protoFunc(pointer, {
1223
+ getCachedI32: () => {
1224
+ lengthI32CacheUsed = true;
1225
+ return [ [ Opcodes.local_get, lengthLocal ] ]
1226
+ },
1227
+ setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
1228
+ get: () => arrayUtil.getLength(pointer),
1229
+ getI32: () => arrayUtil.getLengthI32(pointer),
1230
+ set: value => arrayUtil.setLength(pointer, value),
1231
+ setI32: value => arrayUtil.setLengthI32(pointer, value)
1232
+ }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
1233
+ return makeArray(scope, {
1234
+ rawElements: new Array(length)
1235
+ }, _global, _name, true, itemType);
1236
+ });
1237
+
1112
1238
  return [
1113
1239
  ...out,
1114
1240
 
1115
- ...arrayUtil.getLengthI32(pointer),
1116
- [ Opcodes.local_set, lengthLocal ],
1241
+ ...(!lengthI32CacheUsed ? [] : [
1242
+ ...arrayUtil.getLengthI32(pointer),
1243
+ [ Opcodes.local_set, lengthLocal ],
1244
+ ]),
1117
1245
 
1118
1246
  [ Opcodes.block, valtypeBinary ],
1119
- ...protoFunc(pointer, {
1120
- cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
1121
- get: arrayUtil.getLength(pointer),
1122
- getI32: arrayUtil.getLengthI32(pointer),
1123
- set: value => arrayUtil.setLength(pointer, value),
1124
- setI32: value => arrayUtil.setLengthI32(pointer, value)
1125
- }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
1126
- return makeArray(scope, {
1127
- rawElements: new Array(length)
1128
- }, _global, _name, true, itemType);
1129
- }),
1247
+ ...protoOut,
1130
1248
  [ Opcodes.end ]
1131
1249
  ];
1132
1250
  }
@@ -1183,11 +1301,10 @@ const generateCall = (scope, decl, _global, _name) => {
1183
1301
  args = args.slice(0, func.params.length);
1184
1302
  }
1185
1303
 
1186
- if (func && func.memory) scope.memory = true;
1187
1304
  if (func && func.throws) scope.throws = true;
1188
1305
 
1189
1306
  for (const arg of args) {
1190
- out.push(...generate(scope, arg));
1307
+ out = out.concat(generate(scope, arg));
1191
1308
  }
1192
1309
 
1193
1310
  out.push([ Opcodes.call, idx ]);
@@ -1198,7 +1315,7 @@ const generateCall = (scope, decl, _global, _name) => {
1198
1315
  const generateNew = (scope, decl, _global, _name) => {
1199
1316
  // hack: basically treat this as a normal call for builtins for now
1200
1317
  const name = mapName(decl.callee.name);
1201
- if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
1318
+ if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
1202
1319
  if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
1203
1320
 
1204
1321
  return generateCall(scope, decl, _global, _name);
@@ -1307,8 +1424,6 @@ const generateAssign = (scope, decl) => {
1307
1424
  const name = decl.left.object.name;
1308
1425
  const pointer = arrays.get(name);
1309
1426
 
1310
- scope.memory = true;
1311
-
1312
1427
  const aotPointer = pointer != null;
1313
1428
 
1314
1429
  const newValueTmp = localTmp(scope, '__length_setter_tmp');
@@ -1329,13 +1444,60 @@ const generateAssign = (scope, decl) => {
1329
1444
  ];
1330
1445
  }
1331
1446
 
1447
+ const op = decl.operator.slice(0, -1) || '=';
1448
+
1449
+ // arr[i] | str[i]
1450
+ if (decl.left.type === 'MemberExpression' && decl.left.computed) {
1451
+ const name = decl.left.object.name;
1452
+ const pointer = arrays.get(name);
1453
+
1454
+ const aotPointer = pointer != null;
1455
+
1456
+ const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
1457
+ const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
1458
+
1459
+ const parentType = getNodeType(scope, decl.left.object);
1460
+
1461
+ return [
1462
+ ...(aotPointer ? [] : [
1463
+ ...generate(scope, decl.left.object),
1464
+ Opcodes.i32_to_u
1465
+ ]),
1466
+
1467
+ // get index as valtype
1468
+ ...generate(scope, decl.left.property),
1469
+
1470
+ // convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
1471
+ Opcodes.i32_to_u,
1472
+ ...number(ValtypeSize[valtype], Valtype.i32),
1473
+ [ Opcodes.i32_mul ],
1474
+ ...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
1475
+ ...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
1476
+
1477
+ ...(op === '=' ? generate(scope, decl.right, false, name) : performOp(scope, op, [
1478
+ [ Opcodes.local_get, pointerTmp ],
1479
+ [ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1480
+ ], generate(scope, decl.right), parentType === TYPES._array ? TYPES.number : TYPES.string, getNodeType(scope, decl.right), false, name, true)),
1481
+ [ Opcodes.local_tee, newValueTmp ],
1482
+
1483
+ ...(parentType === TYPES._array ? [
1484
+ [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1485
+ ] : [
1486
+ Opcodes.i32_to_u,
1487
+ [ StoreOps.i16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
1488
+ ]),
1489
+
1490
+ [ Opcodes.local_get, newValueTmp ]
1491
+ ];
1492
+ }
1493
+
1332
1494
  const [ local, isGlobal ] = lookupName(scope, name);
1333
1495
 
1334
1496
  if (local === undefined) {
1335
1497
  // todo: this should be a devtools/repl/??? only thing
1336
1498
 
1337
1499
  // only allow = for this
1338
- if (decl.operator !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1500
+ if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
1339
1501
 
1340
1502
  if (builtinVars[name]) {
1341
1503
  // just return rhs (eg `NaN = 2`)
@@ -1349,8 +1511,10 @@ const generateAssign = (scope, decl) => {
1349
1511
  ];
1350
1512
  }
1351
1513
 
1352
- if (decl.operator === '=') {
1353
- typeStates[name] = getNodeType(scope, decl.right);
1514
+ typeStates[name] = getNodeType(scope, decl.right);
1515
+
1516
+ if (op === '=') {
1517
+ // typeStates[name] = getNodeType(scope, decl.right);
1354
1518
 
1355
1519
  return [
1356
1520
  ...generate(scope, decl.right, isGlobal, name),
@@ -1359,8 +1523,26 @@ const generateAssign = (scope, decl) => {
1359
1523
  ];
1360
1524
  }
1361
1525
 
1526
+ if (op === '||' || op === '&&' || op === '??') {
1527
+ // todo: is this needed?
1528
+ // for logical assignment ops, it is not left @= right ~= left = left @ right
1529
+ // instead, left @ (left = right)
1530
+ // eg, x &&= y ~= x && (x = y)
1531
+
1532
+ return [
1533
+ ...performOp(scope, op, [
1534
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1535
+ ], [
1536
+ ...generate(scope, decl.right),
1537
+ [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1538
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1539
+ ], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1540
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1541
+ ];
1542
+ }
1543
+
1362
1544
  return [
1363
- ...performOp(scope, decl.operator.slice(0, -1), [ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ] ], generate(scope, decl.right), getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1545
+ ...performOp(scope, op, [ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ] ], generate(scope, decl.right), getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
1364
1546
  [ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
1365
1547
  [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
1366
1548
  ];
@@ -1390,10 +1572,11 @@ const generateUnary = (scope, decl) => {
1390
1572
  return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
1391
1573
 
1392
1574
  case '~':
1575
+ // todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
1393
1576
  return [
1394
1577
  ...generate(scope, decl.argument),
1395
1578
  Opcodes.i32_to,
1396
- [ Opcodes.i32_const, signedLEB128(-1) ],
1579
+ [ Opcodes.i32_const, ...signedLEB128(-1) ],
1397
1580
  [ Opcodes.i32_xor ],
1398
1581
  Opcodes.i32_from
1399
1582
  ];
@@ -1474,7 +1657,7 @@ const generateUpdate = (scope, decl) => {
1474
1657
  };
1475
1658
 
1476
1659
  const generateIf = (scope, decl) => {
1477
- const out = truthy(scope, generate(scope, decl.test), decl.test);
1660
+ const out = truthy(scope, generate(scope, decl.test), getNodeType(scope, decl.test));
1478
1661
 
1479
1662
  out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1480
1663
  depth.push('if');
@@ -1567,18 +1750,106 @@ const generateWhile = (scope, decl) => {
1567
1750
  const generateForOf = (scope, decl) => {
1568
1751
  const out = [];
1569
1752
 
1753
+ const rightType = getNodeType(scope, decl.right);
1754
+ const valtypeSize = rightType === TYPES._array ? ValtypeSize[valtype] : ValtypeSize.i16; // presume array (:()
1755
+
1756
+ // todo: for of inside for of might fuck up?
1757
+ const pointer = localTmp(scope, 'forof_base_pointer', Valtype.i32);
1758
+ const length = localTmp(scope, 'forof_length', Valtype.i32);
1759
+ const counter = localTmp(scope, 'forof_counter', Valtype.i32);
1760
+
1761
+ out.push(
1762
+ // set pointer as right
1763
+ ...generate(scope, decl.right),
1764
+ Opcodes.i32_to_u,
1765
+ [ Opcodes.local_set, pointer ],
1766
+
1767
+ // get length
1768
+ [ Opcodes.local_get, pointer ],
1769
+ [ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
1770
+ [ Opcodes.local_set, length ]
1771
+ );
1772
+
1570
1773
  out.push([ Opcodes.loop, Blocktype.void ]);
1571
- depth.push('while');
1774
+ depth.push('forof');
1572
1775
 
1573
- out.push(...generate(scope, decl.test));
1574
- out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
1575
- depth.push('if');
1776
+ // setup local for left
1777
+ generate(scope, decl.left);
1576
1778
 
1577
- out.push(...generate(scope, decl.body));
1779
+ const leftName = decl.left.declarations[0].id.name;
1578
1780
 
1579
- out.push([ Opcodes.br, 1 ]);
1580
- out.push([ Opcodes.end ], [ Opcodes.end ]);
1581
- depth.pop(); depth.pop();
1781
+ // set type for local
1782
+ typeStates[leftName] = rightType === TYPES._array ? TYPES.number : TYPES.string;
1783
+
1784
+ const [ local, isGlobal ] = lookupName(scope, leftName);
1785
+
1786
+ if (rightType === TYPES._array) { // array
1787
+ out.push(
1788
+ [ Opcodes.local_get, pointer ],
1789
+ [ Opcodes.load, Math.log2(valtypeSize) - 1, ...unsignedLEB128(ValtypeSize.i32) ]
1790
+ );
1791
+ } else { // string
1792
+ const [ newOut, newPointer ] = makeArray(scope, {
1793
+ rawElements: new Array(1)
1794
+ }, isGlobal, leftName, true, 'i16');
1795
+
1796
+ out.push(
1797
+ // setup new/out array
1798
+ ...newOut,
1799
+ [ Opcodes.drop ],
1800
+
1801
+ ...number(0, Valtype.i32), // base 0 for store after
1802
+
1803
+ // load current string ind {arg}
1804
+ [ Opcodes.local_get, pointer ],
1805
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
1806
+
1807
+ // store to new string ind 0
1808
+ [ Opcodes.i32_store16, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
1809
+
1810
+ // return new string (page)
1811
+ ...number(newPointer)
1812
+ );
1813
+ }
1814
+
1815
+ // set left value
1816
+ out.push([ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ]);
1817
+
1818
+ out.push(
1819
+ [ Opcodes.block, Blocktype.void ],
1820
+ [ Opcodes.block, Blocktype.void ]
1821
+ );
1822
+ depth.push('block');
1823
+ depth.push('block');
1824
+
1825
+ out.push(
1826
+ ...generate(scope, decl.body),
1827
+ [ Opcodes.end ]
1828
+ );
1829
+ depth.pop();
1830
+
1831
+ out.push(
1832
+ // increment iter pointer by valtype size
1833
+ [ Opcodes.local_get, pointer ],
1834
+ ...number(valtypeSize, Valtype.i32),
1835
+ [ Opcodes.i32_add ],
1836
+ [ Opcodes.local_set, pointer ],
1837
+
1838
+ // increment counter by 1
1839
+ [ Opcodes.local_get, counter ],
1840
+ ...number(1, Valtype.i32),
1841
+ [ Opcodes.i32_add ],
1842
+ [ Opcodes.local_tee, counter ],
1843
+
1844
+ // loop if counter != length
1845
+ [ Opcodes.local_get, length ],
1846
+ [ Opcodes.i32_ne ],
1847
+ [ Opcodes.br_if, 1 ],
1848
+
1849
+ [ Opcodes.end ], [ Opcodes.end ]
1850
+ );
1851
+ depth.pop();
1852
+ depth.pop();
1582
1853
 
1583
1854
  return out;
1584
1855
  };
@@ -1669,19 +1940,19 @@ const generateAssignPat = (scope, decl) => {
1669
1940
  };
1670
1941
 
1671
1942
  let pages = new Map();
1672
- const allocPage = reason => {
1673
- if (pages.has(reason)) return pages.get(reason);
1943
+ const allocPage = (reason, type) => {
1944
+ if (pages.has(reason)) return pages.get(reason).ind;
1674
1945
 
1675
- let ind = pages.size;
1676
- pages.set(reason, ind);
1946
+ const ind = pages.size;
1947
+ pages.set(reason, { ind, type });
1677
1948
 
1678
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason}`);
1949
+ if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
1679
1950
 
1680
1951
  return ind;
1681
1952
  };
1682
1953
 
1683
1954
  const freePage = reason => {
1684
- let ind = pages.get(reason);
1955
+ const { ind } = pages.get(reason);
1685
1956
  pages.delete(reason);
1686
1957
 
1687
1958
  if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
@@ -1698,7 +1969,7 @@ const itemTypeToValtype = {
1698
1969
  i16: 'i32'
1699
1970
  };
1700
1971
 
1701
- const storeOps = {
1972
+ const StoreOps = {
1702
1973
  i32: Opcodes.i32_store,
1703
1974
  i64: Opcodes.i64_store,
1704
1975
  f64: Opcodes.f64_store,
@@ -1707,13 +1978,28 @@ const storeOps = {
1707
1978
  i16: Opcodes.i32_store16
1708
1979
  };
1709
1980
 
1981
+ let data = [];
1982
+
1983
+ const compileBytes = (val, itemType, signed = true) => {
1984
+ switch (itemType) {
1985
+ case 'i8': return enforceOneByte(unsignedLEB128(val));
1986
+ case 'i16': return enforceTwoBytes(unsignedLEB128(val));
1987
+ case 'i32': return enforceFourBytes(signedLEB128(val));
1988
+ case 'i64': return enforceEightBytes(signedLEB128(val));
1989
+ case 'f64': return enforceEightBytes(ieee754_binary64(val));
1990
+ }
1991
+ };
1992
+
1710
1993
  const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
1711
1994
  const out = [];
1712
1995
 
1996
+ let firstAssign = false;
1713
1997
  if (!arrays.has(name) || name === '$undeclared') {
1998
+ firstAssign = true;
1999
+
1714
2000
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1715
2001
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1716
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`) * pageSize);
2002
+ arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
1717
2003
  }
1718
2004
 
1719
2005
  const pointer = arrays.get(name);
@@ -1721,8 +2007,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1721
2007
  const useRawElements = !!decl.rawElements;
1722
2008
  const elements = useRawElements ? decl.rawElements : decl.elements;
1723
2009
 
2010
+ const valtype = itemTypeToValtype[itemType];
1724
2011
  const length = elements.length;
1725
2012
 
2013
+ if (firstAssign && useRawElements) {
2014
+ let bytes = compileBytes(length, 'i32');
2015
+
2016
+ if (!initEmpty) for (let i = 0; i < length; i++) {
2017
+ if (elements[i] == null) continue;
2018
+
2019
+ bytes.push(...compileBytes(elements[i], itemType));
2020
+ }
2021
+
2022
+ data.push({
2023
+ offset: pointer,
2024
+ bytes
2025
+ });
2026
+
2027
+ // local value as pointer
2028
+ out.push(...number(pointer));
2029
+
2030
+ return [ out, pointer ];
2031
+ }
2032
+
1726
2033
  // store length as 0th array
1727
2034
  out.push(
1728
2035
  ...number(0, Valtype.i32),
@@ -1730,8 +2037,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1730
2037
  [ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ]
1731
2038
  );
1732
2039
 
1733
- const storeOp = storeOps[itemType];
1734
- const valtype = itemTypeToValtype[itemType];
2040
+ const storeOp = StoreOps[itemType];
1735
2041
 
1736
2042
  if (!initEmpty) for (let i = 0; i < length; i++) {
1737
2043
  if (elements[i] == null) continue;
@@ -1746,8 +2052,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1746
2052
  // local value as pointer
1747
2053
  out.push(...number(pointer));
1748
2054
 
1749
- scope.memory = true;
1750
-
1751
2055
  return [ out, pointer ];
1752
2056
  };
1753
2057
 
@@ -1766,8 +2070,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1766
2070
  const name = decl.object.name;
1767
2071
  const pointer = arrays.get(name);
1768
2072
 
1769
- scope.memory = true;
1770
-
1771
2073
  const aotPointer = pointer != null;
1772
2074
 
1773
2075
  return [
@@ -1787,8 +2089,6 @@ export const generateMember = (scope, decl, _global, _name) => {
1787
2089
  const name = decl.object.name;
1788
2090
  const pointer = arrays.get(name);
1789
2091
 
1790
- scope.memory = true;
1791
-
1792
2092
  const aotPointer = pointer != null;
1793
2093
 
1794
2094
  if (type === TYPES._array) {
@@ -1898,7 +2198,7 @@ const generateFunc = (scope, decl) => {
1898
2198
  locals: {},
1899
2199
  localInd: 0,
1900
2200
  returns: [ valtypeBinary ],
1901
- memory: false,
2201
+ returnType: null,
1902
2202
  throws: false,
1903
2203
  name
1904
2204
  };
@@ -1924,7 +2224,6 @@ const generateFunc = (scope, decl) => {
1924
2224
  returns: innerScope.returns,
1925
2225
  returnType: innerScope.returnType,
1926
2226
  locals: innerScope.locals,
1927
- memory: innerScope.memory,
1928
2227
  throws: innerScope.throws,
1929
2228
  index: currentFuncIndex++
1930
2229
  };
@@ -1939,6 +2238,8 @@ const generateFunc = (scope, decl) => {
1939
2238
 
1940
2239
  if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
1941
2240
  wasm.push(...number(0), [ Opcodes.return ]);
2241
+
2242
+ if (func.returnType === null) func.returnType = TYPES.undefined;
1942
2243
  }
1943
2244
 
1944
2245
  // change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
@@ -1949,9 +2250,7 @@ const generateFunc = (scope, decl) => {
1949
2250
  if (local.type === Valtype.v128) {
1950
2251
  vecParams++;
1951
2252
 
1952
- /* func.memory = true; // mark func as using memory
1953
-
1954
- wasm.unshift( // add v128 load for param
2253
+ /* wasm.unshift( // add v128 load for param
1955
2254
  [ Opcodes.i32_const, 0 ],
1956
2255
  [ ...Opcodes.v128_load, 0, i * 16 ],
1957
2256
  [ Opcodes.local_set, local.idx ]
@@ -2062,10 +2361,10 @@ const generateFunc = (scope, decl) => {
2062
2361
  };
2063
2362
 
2064
2363
  const generateCode = (scope, decl) => {
2065
- const out = [];
2364
+ let out = [];
2066
2365
 
2067
2366
  for (const x of decl.body) {
2068
- out.push(...generate(scope, x));
2367
+ out = out.concat(generate(scope, x));
2069
2368
  }
2070
2369
 
2071
2370
  return out;
@@ -2101,6 +2400,18 @@ const internalConstrs = {
2101
2400
  ];
2102
2401
  },
2103
2402
  type: TYPES._array
2403
+ },
2404
+
2405
+ __Array_of: {
2406
+ // this is not a constructor but best fits internal structure here
2407
+ generate: (scope, decl, global, name) => {
2408
+ // Array.of(i0, i1, ...)
2409
+ return generateArray(scope, {
2410
+ elements: decl.arguments
2411
+ }, global, name);
2412
+ },
2413
+ type: TYPES._array,
2414
+ notConstr: true
2104
2415
  }
2105
2416
  };
2106
2417
 
@@ -2115,6 +2426,7 @@ export default program => {
2115
2426
  typeStates = {};
2116
2427
  arrays = new Map();
2117
2428
  pages = new Map();
2429
+ data = [];
2118
2430
  currentFuncIndex = importedFuncs.length;
2119
2431
 
2120
2432
  globalThis.valtype = 'f64';
@@ -2191,5 +2503,5 @@ export default program => {
2191
2503
  // if blank main func and other exports, remove it
2192
2504
  if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
2193
2505
 
2194
- return { funcs, globals, tags, exceptions, pages };
2506
+ return { funcs, globals, tags, exceptions, pages, data };
2195
2507
  };