porffor 0.0.0-828ee15 → 0.0.0-a2afb57

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.
@@ -42,8 +42,8 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
42
42
  out += ` ${read_ieee754_binary64(inst.slice(1))}`;
43
43
  } else if (inst[0] === Opcodes.i32_const || inst[0] === Opcodes.i64_const) {
44
44
  out += ` ${read_signedLEB128(inst.slice(1))}`;
45
- } else if (inst[0] === Opcodes.i32_load || inst[0] === Opcodes.i64_load || inst[0] === Opcodes.f64_load || inst[0] === Opcodes.i32_store || inst[0] === Opcodes.i64_store || inst[0] === Opcodes.f64_store) {
46
- out += ` ${inst[1]} ${read_unsignedLEB128(inst.slice(2))}`
45
+ } else if (inst[0] === Opcodes.i32_load || inst[0] === Opcodes.i64_load || inst[0] === Opcodes.f64_load || inst[0] === Opcodes.i32_store || inst[0] === Opcodes.i64_store || inst[0] === Opcodes.f64_store || inst[0] === Opcodes.i32_store16 || inst[0] === Opcodes.i32_load16_u) {
46
+ out += ` ${inst[1]} ${read_unsignedLEB128(inst.slice(2))}`;
47
47
  } else for (const operand of inst.slice(1)) {
48
48
  if (inst[0] === Opcodes.if || inst[0] === Opcodes.loop || inst[0] === Opcodes.block) {
49
49
  if (operand === Blocktype.void) continue;
@@ -57,7 +57,7 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
57
57
  out += ` ;; label @${depth}`;
58
58
  }
59
59
 
60
- if (inst[0] === Opcodes.br) {
60
+ if (inst[0] === Opcodes.br || inst[0] === Opcodes.br_if) {
61
61
  out += ` ;; goto @${depth - inst[1]}`;
62
62
  }
63
63
 
@@ -9,11 +9,15 @@ export const number = (n, valtype = valtypeBinary) => {
9
9
  }
10
10
  };
11
11
 
12
- const enforceTwoBytes = arr => [ arr[0] ?? 0, arr[1] ?? 0, arr[2] ?? 0, arr[3] ?? 0 ];
12
+ export const enforceOneByte = arr => [ arr[0] ?? 0 ];
13
+ export const enforceTwoBytes = arr => [ arr[0] ?? 0, arr[1] ?? 0 ];
14
+ export const enforceFourBytes = arr => [ arr[0] ?? 0, arr[1] ?? 0, arr[2] ?? 0, arr[3] ?? 0 ];
15
+ export const enforceEightBytes = arr => [ arr[0] ?? 0, arr[1] ?? 0, arr[2] ?? 0, arr[3] ?? 0, arr[4] ?? 0, arr[5] ?? 0, arr[6] ?? 0, arr[7] ?? 0 ];
16
+
13
17
  export const i32x4 = (a, b, c, d) => [ [
14
18
  ...Opcodes.v128_const,
15
- ...enforceTwoBytes(signedLEB128(a)),
16
- ...enforceTwoBytes(signedLEB128(b)),
17
- ...enforceTwoBytes(signedLEB128(c)),
18
- ...enforceTwoBytes(signedLEB128(d))
19
+ ...enforceFourBytes(signedLEB128(a)),
20
+ ...enforceFourBytes(signedLEB128(b)),
21
+ ...enforceFourBytes(signedLEB128(c)),
22
+ ...enforceFourBytes(signedLEB128(d))
19
23
  ] ];
@@ -22,15 +22,15 @@ export const encodeLocal = (count, type) => [
22
22
  type
23
23
  ];
24
24
 
25
+ // todo: this only works with integers within 32 bit range
25
26
  export const signedLEB128 = n => {
26
- // todo: this only works with integers within 32 bit range
27
+ n |= 0;
27
28
 
28
29
  // just input for small numbers (for perf as common)
29
30
  if (n >= 0 && n <= 63) return [ n ];
30
31
  if (n >= -64 && n <= 0) return [ 128 + n ];
31
32
 
32
33
  const buffer = [];
33
- n |= 0;
34
34
 
35
35
  while (true) {
36
36
  let byte = n & 0x7f;
@@ -50,6 +50,8 @@ export const signedLEB128 = n => {
50
50
  };
51
51
 
52
52
  export const unsignedLEB128 = n => {
53
+ n |= 0;
54
+
53
55
  // just input for small numbers (for perf as common)
54
56
  if (n >= 0 && n <= 127) return [ n ];
55
57
 
package/compiler/index.js CHANGED
@@ -4,6 +4,8 @@ import opt from './opt.js';
4
4
  import produceSections from './sections.js';
5
5
  import decompile from './decompile.js';
6
6
  import { BuiltinPreludes } from './builtins.js';
7
+ import toc from './2c.js';
8
+
7
9
 
8
10
  globalThis.decompile = decompile;
9
11
 
@@ -14,7 +16,9 @@ const bold = x => `\u001b[1m${x}\u001b[0m`;
14
16
  const areaColors = {
15
17
  codegen: [ 20, 80, 250 ],
16
18
  opt: [ 250, 20, 80 ],
17
- sections: [ 20, 250, 80 ]
19
+ sections: [ 20, 250, 80 ],
20
+ alloc: [ 250, 250, 20 ],
21
+ '2c': [ 20, 250, 250 ]
18
22
  };
19
23
 
20
24
  globalThis.log = (area, ...args) => console.log(`\u001b[90m[\u001b[0m${rgb(...areaColors[area], area)}\u001b[90m]\u001b[0m`, ...args);
@@ -35,9 +39,16 @@ const logFuncs = (funcs, globals, exceptions) => {
35
39
  console.log();
36
40
  };
37
41
 
42
+ const getArg = name => process.argv.find(x => x.startsWith(`-${name}=`))?.slice(name.length + 2);
43
+
44
+ const writeFileSync = (typeof process !== 'undefined' ? (await import('node:fs')).writeFileSync : undefined);
45
+ const execSync = (typeof process !== 'undefined' ? (await import('node:child_process')).execSync : undefined);
46
+
38
47
  export default (code, flags) => {
39
48
  globalThis.optLog = process.argv.includes('-opt-log');
40
49
  globalThis.codeLog = process.argv.includes('-code-log');
50
+ globalThis.allocLog = process.argv.includes('-alloc-log');
51
+ globalThis.regexLog = process.argv.includes('-regex-log');
41
52
 
42
53
  for (const x in BuiltinPreludes) {
43
54
  if (code.indexOf(x + '(') !== -1) code = BuiltinPreludes[x] + code;
@@ -48,7 +59,7 @@ export default (code, flags) => {
48
59
  if (flags.includes('info')) console.log(`1. parsed in ${(performance.now() - t0).toFixed(2)}ms`);
49
60
 
50
61
  const t1 = performance.now();
51
- const { funcs, globals, tags, exceptions, pages } = codeGen(program);
62
+ const { funcs, globals, tags, exceptions, pages, data } = codeGen(program);
52
63
  if (flags.includes('info')) console.log(`2. generated code in ${(performance.now() - t1).toFixed(2)}ms`);
53
64
 
54
65
  if (process.argv.includes('-funcs')) logFuncs(funcs, globals, exceptions);
@@ -60,8 +71,48 @@ export default (code, flags) => {
60
71
  if (process.argv.includes('-opt-funcs')) logFuncs(funcs, globals, exceptions);
61
72
 
62
73
  const t3 = performance.now();
63
- const sections = produceSections(funcs, globals, tags, pages, flags);
74
+ const sections = produceSections(funcs, globals, tags, pages, data, flags);
64
75
  if (flags.includes('info')) console.log(`4. produced sections in ${(performance.now() - t3).toFixed(2)}ms`);
65
76
 
66
- return { wasm: sections, funcs, globals, tags, exceptions, pages };
77
+ if (allocLog) {
78
+ const wasmPages = Math.ceil((pages.size * pageSize) / 65536);
79
+ const bytes = wasmPages * 65536;
80
+ log('alloc', `\x1B[1mallocated ${bytes / 1024}KiB\x1B[0m for ${pages.size} things using ${wasmPages} Wasm page${wasmPages === 1 ? '' : 's'}`);
81
+ console.log([...pages.keys()].map(x => `\x1B[36m - ${x}\x1B[0m`).join('\n') + '\n');
82
+ }
83
+
84
+ const out = { wasm: sections, funcs, globals, tags, exceptions, pages };
85
+
86
+ const target = getArg('target') ?? getArg('t') ?? 'wasm';
87
+ const outFile = getArg('o');
88
+
89
+ if (target === 'c') {
90
+ const c = toc(out);
91
+
92
+ if (outFile) {
93
+ writeFileSync(outFile, c);
94
+ } else {
95
+ console.log(c);
96
+ }
97
+
98
+ process.exit();
99
+ }
100
+
101
+ if (target === 'native') {
102
+ const compiler = getArg('compiler') ?? 'clang';
103
+ const cO = getArg('cO') ?? 'Ofast';
104
+
105
+ const tmpfile = 'tmp.c';
106
+ const args = [ compiler, tmpfile, '-o', outFile ?? (process.platform === 'win32' ? 'out.exe' : 'out'), '-' + cO, '-march=native' ];
107
+
108
+ const c = toc(out);
109
+ writeFileSync(tmpfile, c);
110
+
111
+ // obvious command escape is obvious
112
+ execSync(args.join(' '), { stdio: 'inherit' });
113
+
114
+ process.exit();
115
+ }
116
+
117
+ return out;
67
118
  };
package/compiler/opt.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Opcodes, Valtype } from "./wasmSpec.js";
2
2
  import { number } from "./embedding.js";
3
+ import { read_signedLEB128, read_ieee754_binary64 } from "./encoding.js";
3
4
 
4
5
  // deno compat
5
6
  if (typeof process === 'undefined' && typeof Deno !== 'undefined') {
@@ -20,7 +21,7 @@ export default (funcs, globals) => {
20
21
  if (optLevel === 0) return;
21
22
 
22
23
  const tailCall = process.argv.includes('-tail-call');
23
- if (tailCall) log('opt', 'tail call proposal is not widely implemented! (you used -tail-call)');
24
+ if (tailCall) log('opt', 'warning: tail call proposal is not widely implemented! (you used -tail-call)');
24
25
 
25
26
  if (optLevel >= 2 && !process.argv.includes('-opt-no-inline')) {
26
27
  // inline pass (very WIP)
@@ -95,7 +96,6 @@ export default (funcs, globals) => {
95
96
  }
96
97
 
97
98
  if (t.index > c.index) t.index--; // adjust index if after removed func
98
- if (c.memory) t.memory = true;
99
99
  }
100
100
 
101
101
  funcs.splice(funcs.indexOf(c), 1); // remove func from funcs
@@ -142,7 +142,7 @@ export default (funcs, globals) => {
142
142
  depth--;
143
143
  if (depth <= 0) break;
144
144
  }
145
- if (op === Opcodes.br) {
145
+ if (op === Opcodes.br || op === Opcodes.br_if) {
146
146
  hasBranch = true;
147
147
  break;
148
148
  }
@@ -213,9 +213,9 @@ export default (funcs, globals) => {
213
213
  // i32.const 0
214
214
  // drop
215
215
  // -->
216
- // <nothing>>
216
+ // <nothing>
217
217
 
218
- wasm.splice(i - 1, 2); // remove this inst
218
+ wasm.splice(i - 1, 2); // remove these inst
219
219
  i -= 2;
220
220
  continue;
221
221
  }
@@ -259,6 +259,36 @@ export default (funcs, globals) => {
259
259
  continue;
260
260
  }
261
261
 
262
+ if (lastInst[0] === Opcodes.const && (inst === Opcodes.i32_to || inst === Opcodes.i32_to_u)) {
263
+ // change const and immediate i32 convert to i32 const
264
+ // f64.const 0
265
+ // i32.trunc_sat_f64_s || i32.trunc_sat_f64_u
266
+ // -->
267
+ // i32.const 0
268
+
269
+ wasm[i - 1] = number((valtype === 'f64' ? read_ieee754_binary64 : read_signedLEB128)(lastInst.slice(1)), Valtype.i32)[0]; // f64.const -> i32.const
270
+
271
+ wasm.splice(i, 1); // remove this inst
272
+ i--;
273
+ if (optLog) log('opt', `converted const -> i32 convert into i32 const`);
274
+ continue;
275
+ }
276
+
277
+ if (lastInst[0] === Opcodes.i32_const && (inst === Opcodes.i32_from || inst === Opcodes.i32_from_u)) {
278
+ // change i32 const and immediate convert to const (opposite way of previous)
279
+ // i32.const 0
280
+ // f64.convert_i32_s || f64.convert_i32_u
281
+ // -->
282
+ // f64.const 0
283
+
284
+ wasm[i - 1] = number(read_signedLEB128(lastInst.slice(1)))[0]; // i32.const -> f64.const
285
+
286
+ wasm.splice(i, 1); // remove this inst
287
+ i--;
288
+ if (optLog) log('opt', `converted i32 const -> convert into const`);
289
+ continue;
290
+ }
291
+
262
292
  if (tailCall && lastInst[0] === Opcodes.call && inst[0] === Opcodes.return) {
263
293
  // replace call, return with tail calls (return_call)
264
294
  // call X
@@ -287,28 +317,24 @@ export default (funcs, globals) => {
287
317
  continue;
288
318
  }
289
319
 
290
- if (i < 2) continue;
291
- const lastLastInst = wasm[i - 2];
320
+ // remove unneeded before get with update exprs (n++, etc) when value is unused
321
+ if (i < wasm.length - 4 && lastInst[1] === inst[1] && lastInst[0] === Opcodes.local_get && inst[0] === Opcodes.local_get && wasm[i + 1][0] === Opcodes.const && [Opcodes.add, Opcodes.sub].includes(wasm[i + 2][0]) && wasm[i + 3][0] === Opcodes.local_set && wasm[i + 3][1] === inst[1] && (wasm[i + 4][0] === Opcodes.drop || wasm[i + 4][0] === Opcodes.br)) {
322
+ // local.get 1
323
+ // local.get 1
324
+ // -->
325
+ // local.get 1
292
326
 
293
- if (depth.length === 2) {
294
- // hack to remove unneeded before get in for loops with (...; i++)
295
- if (lastLastInst[0] === Opcodes.end && lastInst[1] === inst[1] && lastInst[0] === Opcodes.local_get && inst[0] === Opcodes.local_get) {
296
- // local.get 1
297
- // local.get 1
298
- // -->
299
- // local.get 1
300
-
301
- // remove drop at the end as well
302
- if (wasm[i + 4][0] === Opcodes.drop) {
303
- wasm.splice(i + 4, 1);
304
- }
327
+ // remove drop at the end as well
328
+ if (wasm[i + 4][0] === Opcodes.drop) wasm.splice(i + 4, 1);
305
329
 
306
- wasm.splice(i, 1); // remove this inst (second get)
307
- i--;
308
- continue;
309
- }
330
+ wasm.splice(i, 1); // remove this inst (second get)
331
+ i--;
332
+ continue;
310
333
  }
311
334
 
335
+ if (i < 2) continue;
336
+ const lastLastInst = wasm[i - 2];
337
+
312
338
  if (lastLastInst[1] === inst[1] && inst[0] === Opcodes.local_get && lastInst[0] === Opcodes.local_tee && lastLastInst[0] === Opcodes.local_set) {
313
339
  // local.set x
314
340
  // local.tee y
package/compiler/parse.js CHANGED
@@ -1,3 +1,4 @@
1
+ // import { parse } from 'acorn';
1
2
  const { parse } = (await import(globalThis.document ? 'https://esm.sh/acorn' : 'acorn'));
2
3
 
3
4
  export default (input, flags) => {
@@ -15,17 +15,20 @@ const TYPES = {
15
15
  bigint: 0xffffffffffff7,
16
16
 
17
17
  // these are not "typeof" types but tracked internally
18
- _array: 0xffffffffffff8
18
+ _array: 0xfffffffffff0f,
19
+ _regexp: 0xfffffffffff1f
19
20
  };
20
21
 
21
22
  // todo: turn these into built-ins once arrays and these become less hacky
22
23
 
23
24
  export const PrototypeFuncs = function() {
24
25
  const noUnlikelyChecks = process.argv.includes('-funsafe-no-unlikely-proto-checks');
26
+ let zeroChecks = process.argv.find(x => x.startsWith('-funsafe-zero-proto-checks='));
27
+ if (zeroChecks) zeroChecks = zeroChecks.split('=')[1].split(',').reduce((acc, x) => { acc[x.toLowerCase()] = true; return acc; }, {});
28
+ else zeroChecks = {};
25
29
 
26
30
  this[TYPES._array] = {
27
31
  // lX = local accessor of X ({ get, set }), iX = local index of X, wX = wasm ops of X
28
- // todo: out of bounds (>) properly
29
32
  at: (pointer, length, wIndex, iTmp) => [
30
33
  ...wIndex,
31
34
  Opcodes.i32_to,
@@ -36,7 +39,7 @@ export const PrototypeFuncs = function() {
36
39
  [ Opcodes.i32_lt_s ],
37
40
  [ Opcodes.if, Blocktype.void ],
38
41
  [ Opcodes.local_get, iTmp ],
39
- ...length.cachedI32,
42
+ ...length.getCachedI32(),
40
43
  [ Opcodes.i32_add ],
41
44
  [ Opcodes.local_set, iTmp ],
42
45
  [ Opcodes.end ],
@@ -47,7 +50,7 @@ export const PrototypeFuncs = function() {
47
50
  [ Opcodes.i32_lt_s ],
48
51
 
49
52
  [ Opcodes.local_get, iTmp ],
50
- ...length.cachedI32,
53
+ ...length.getCachedI32(),
51
54
  [ Opcodes.i32_ge_s ],
52
55
  [ Opcodes.i32_or ],
53
56
 
@@ -67,7 +70,7 @@ export const PrototypeFuncs = function() {
67
70
  // todo: only for 1 argument
68
71
  push: (pointer, length, wNewMember) => [
69
72
  // get memory offset of array at last index (length)
70
- ...length.cachedI32,
73
+ ...length.getCachedI32(),
71
74
  ...number(ValtypeSize[valtype], Valtype.i32),
72
75
  [ Opcodes.i32_mul ],
73
76
 
@@ -79,17 +82,17 @@ export const PrototypeFuncs = function() {
79
82
 
80
83
  // bump array length by 1 and return it
81
84
  ...length.setI32([
82
- ...length.cachedI32,
85
+ ...length.getCachedI32(),
83
86
  ...number(1, Valtype.i32),
84
87
  [ Opcodes.i32_add ]
85
88
  ]),
86
89
 
87
- ...length.get
90
+ ...length.get()
88
91
  ],
89
92
 
90
93
  pop: (pointer, length) => [
91
94
  // if length == 0, noop
92
- ...length.cachedI32,
95
+ ...length.getCachedI32(),
93
96
  [ Opcodes.i32_eqz ],
94
97
  [ Opcodes.if, Blocktype.void ],
95
98
  ...number(UNDEFINED),
@@ -100,13 +103,13 @@ export const PrototypeFuncs = function() {
100
103
 
101
104
  // decrement length by 1
102
105
  ...length.setI32([
103
- ...length.cachedI32,
106
+ ...length.getCachedI32(),
104
107
  ...number(1, Valtype.i32),
105
108
  [ Opcodes.i32_sub ]
106
109
  ]),
107
110
 
108
111
  // load last element
109
- ...length.cachedI32,
112
+ ...length.getCachedI32(),
110
113
  ...number(ValtypeSize[valtype], Valtype.i32),
111
114
  [ Opcodes.i32_mul ],
112
115
 
@@ -115,7 +118,7 @@ export const PrototypeFuncs = function() {
115
118
 
116
119
  shift: (pointer, length) => [
117
120
  // if length == 0, noop
118
- ...length.cachedI32,
121
+ ...length.getCachedI32(),
119
122
  Opcodes.i32_eqz,
120
123
  [ Opcodes.if, Blocktype.void ],
121
124
  ...number(UNDEFINED),
@@ -126,7 +129,7 @@ export const PrototypeFuncs = function() {
126
129
 
127
130
  // decrement length by 1
128
131
  ...length.setI32([
129
- ...length.cachedI32,
132
+ ...length.getCachedI32(),
130
133
  ...number(1, Valtype.i32),
131
134
  [ Opcodes.i32_sub ]
132
135
  ]),
@@ -140,15 +143,69 @@ export const PrototypeFuncs = function() {
140
143
  ...number(pointer + ValtypeSize.i32 + ValtypeSize[valtype], Valtype.i32), // src = base array index + length size + an index
141
144
  ...number(pageSize - ValtypeSize.i32 - ValtypeSize[valtype], Valtype.i32), // size = PageSize - length size - an index
142
145
  [ ...Opcodes.memory_copy, 0x00, 0x00 ]
146
+ ],
147
+
148
+ fill: (pointer, length, wElement, iTmp) => [
149
+ ...wElement,
150
+ [ Opcodes.local_set, iTmp ],
151
+
152
+ // use cached length i32 as pointer
153
+ ...length.getCachedI32(),
154
+
155
+ // length - 1 for indexes
156
+ ...number(1, Valtype.i32),
157
+ [ Opcodes.i32_sub ],
158
+
159
+ // * sizeof value
160
+ ...number(ValtypeSize[valtype], Valtype.i32),
161
+ [ Opcodes.i32_mul ],
162
+
163
+ ...length.setCachedI32(),
164
+
165
+ ...(noUnlikelyChecks ? [] : [
166
+ ...length.getCachedI32(),
167
+ ...number(0, Valtype.i32),
168
+ [ Opcodes.i32_lt_s ],
169
+ [ Opcodes.if, Blocktype.void ],
170
+ ...number(pointer),
171
+ [ Opcodes.br, 1 ],
172
+ [ Opcodes.end ]
173
+ ]),
174
+
175
+ [ Opcodes.loop, Blocktype.void ],
176
+
177
+ // set element using pointer
178
+ ...length.getCachedI32(),
179
+ [ Opcodes.local_get, iTmp ],
180
+ [ Opcodes.store, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32) ],
181
+
182
+ // pointer - sizeof value
183
+ ...length.getCachedI32(),
184
+ ...number(ValtypeSize[valtype], Valtype.i32),
185
+ [ Opcodes.i32_sub ],
186
+
187
+ ...length.setCachedI32(),
188
+
189
+ // if pointer >= 0, loop
190
+ ...length.getCachedI32(),
191
+ ...number(0, Valtype.i32),
192
+ [ Opcodes.i32_ge_s ],
193
+ [ Opcodes.br_if, 0 ],
194
+
195
+ [ Opcodes.end ],
196
+
197
+ // return this array
198
+ ...number(pointer)
143
199
  ]
144
200
  };
145
201
 
146
202
  this[TYPES._array].at.local = Valtype.i32;
147
203
  this[TYPES._array].push.noArgRetLength = true;
204
+ this[TYPES._array].fill.local = valtypeBinary;
205
+ this[TYPES._array].fill.returnType = TYPES._array;
148
206
 
149
207
  this[TYPES.string] = {
150
- // todo: out of bounds properly
151
- at: (pointer, length, wIndex, iTmp, arrayShell) => {
208
+ at: (pointer, length, wIndex, iTmp, _, arrayShell) => {
152
209
  const [ newOut, newPointer ] = arrayShell(1, 'i16');
153
210
 
154
211
  return [
@@ -157,9 +214,9 @@ export const PrototypeFuncs = function() {
157
214
  [ Opcodes.drop ],
158
215
 
159
216
  ...number(0, Valtype.i32), // base 0 for store later
160
- Opcodes.i32_to_u,
161
217
 
162
218
  ...wIndex,
219
+ Opcodes.i32_to_u,
163
220
  [ Opcodes.local_tee, iTmp ],
164
221
 
165
222
  // if index < 0: access index + array length
@@ -167,7 +224,7 @@ export const PrototypeFuncs = function() {
167
224
  [ Opcodes.i32_lt_s ],
168
225
  [ Opcodes.if, Blocktype.void ],
169
226
  [ Opcodes.local_get, iTmp ],
170
- ...length.cachedI32,
227
+ ...length.getCachedI32(),
171
228
  [ Opcodes.i32_add ],
172
229
  [ Opcodes.local_set, iTmp ],
173
230
  [ Opcodes.end ],
@@ -178,7 +235,7 @@ export const PrototypeFuncs = function() {
178
235
  [ Opcodes.i32_lt_s ],
179
236
 
180
237
  [ Opcodes.local_get, iTmp ],
181
- ...length.cachedI32,
238
+ ...length.getCachedI32(),
182
239
  [ Opcodes.i32_ge_s ],
183
240
  [ Opcodes.i32_or ],
184
241
 
@@ -203,7 +260,7 @@ export const PrototypeFuncs = function() {
203
260
  },
204
261
 
205
262
  // todo: out of bounds properly
206
- charAt: (pointer, length, wIndex, _, arrayShell) => {
263
+ charAt: (pointer, length, wIndex, _1, _2, arrayShell) => {
207
264
  const [ newOut, newPointer ] = arrayShell(1, 'i16');
208
265
 
209
266
  return [
@@ -234,39 +291,120 @@ export const PrototypeFuncs = function() {
234
291
  return [
235
292
  ...wIndex,
236
293
  Opcodes.i32_to,
237
- [ Opcodes.local_set, iTmp ],
238
294
 
239
- // index < 0
240
- ...(noUnlikelyChecks ? [] : [
295
+ ...(zeroChecks.charcodeat ? [] : [
296
+ [ Opcodes.local_set, iTmp ],
297
+
298
+ // index < 0
299
+ ...(noUnlikelyChecks ? [] : [
300
+ [ Opcodes.local_get, iTmp ],
301
+ ...number(0, Valtype.i32),
302
+ [ Opcodes.i32_lt_s ],
303
+ ]),
304
+
305
+ // index >= length
306
+ [ Opcodes.local_get, iTmp ],
307
+ ...length.getCachedI32(),
308
+ [ Opcodes.i32_ge_s ],
309
+
310
+ ...(noUnlikelyChecks ? [] : [ [ Opcodes.i32_or ] ]),
311
+ [ Opcodes.if, Blocktype.void ],
312
+ ...number(NaN),
313
+ [ Opcodes.br, 1 ],
314
+ [ Opcodes.end ],
315
+
241
316
  [ Opcodes.local_get, iTmp ],
242
- ...number(0, Valtype.i32),
243
- [ Opcodes.i32_lt_s ],
244
317
  ]),
245
318
 
246
- // index >= length
319
+ ...number(ValtypeSize.i16, Valtype.i32),
320
+ [ Opcodes.i32_mul ],
321
+
322
+ // load current string ind {arg}
323
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32) ],
324
+ Opcodes.i32_from_u
325
+ ];
326
+ },
327
+
328
+ isWellFormed: (pointer, length, wIndex, iTmp, iTmp2, arrayShell, { wellFormed } = {}) => {
329
+ // aot approx metadata
330
+ if (wellFormed != null) return number(wellFormed ? 1 : 0);
331
+
332
+ return [
333
+ // note: we cannot presume it begins as 0 in case it was used previously
334
+ ...number(0, Valtype.i32),
335
+ [ Opcodes.local_set, iTmp ],
336
+
337
+ [ Opcodes.loop, Blocktype.void ],
338
+
339
+ [ Opcodes.block, Blocktype.void ],
340
+
247
341
  [ Opcodes.local_get, iTmp ],
248
- ...length.cachedI32,
342
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32) ],
343
+ [ Opcodes.local_set, iTmp2 ],
344
+
345
+ // if not surrogate, continue
346
+ [ Opcodes.local_get, iTmp2 ],
347
+ ...number(0xF800, Valtype.i32),
348
+ [ Opcodes.i32_and ],
349
+ ...number(0xD800, Valtype.i32),
350
+ [ Opcodes.i32_ne ],
351
+ [ Opcodes.br_if, 0 ],
352
+
353
+ // if not leading surrogate, return false
354
+ [ Opcodes.local_get, iTmp2 ],
355
+ ...number(0xDC00, Valtype.i32),
249
356
  [ Opcodes.i32_ge_s ],
357
+ [ Opcodes.if, Blocktype.void ],
358
+ ...number(0),
359
+ [ Opcodes.br, 3 ],
360
+ [ Opcodes.end ],
250
361
 
251
- ...(noUnlikelyChecks ? [] : [ [ Opcodes.i32_or ] ]),
362
+ // if not followed by trailing surrogate, return false
363
+ [ Opcodes.local_get, iTmp ],
364
+ [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + ValtypeSize.i16) ],
365
+ ...number(0xFC00, Valtype.i32),
366
+ [ Opcodes.i32_and ],
367
+ ...number(0xDC00, Valtype.i32),
368
+ [ Opcodes.i32_ne ],
252
369
  [ Opcodes.if, Blocktype.void ],
253
- ...number(NaN),
254
- [ Opcodes.br, 1 ],
370
+ ...number(0),
371
+ [ Opcodes.br, 3 ],
255
372
  [ Opcodes.end ],
256
373
 
374
+ // bump index again since gone through two valid chars
257
375
  [ Opcodes.local_get, iTmp ],
258
376
  ...number(ValtypeSize.i16, Valtype.i32),
377
+ [ Opcodes.i32_add ],
378
+ [ Opcodes.local_set, iTmp ],
379
+
380
+ [ Opcodes.end ],
381
+
382
+ // bump pointer and loop if not at the end
383
+ [ Opcodes.local_get, iTmp ],
384
+ ...number(ValtypeSize.i16, Valtype.i32),
385
+ [ Opcodes.i32_add ],
386
+ [ Opcodes.local_tee, iTmp ],
387
+
388
+ ...length.getCachedI32(),
389
+ ...number(ValtypeSize.i16, Valtype.i32),
259
390
  [ Opcodes.i32_mul ],
391
+ [ Opcodes.i32_ne ],
392
+ [ Opcodes.br_if, 0 ],
260
393
 
261
- // load current string ind {arg}
262
- [ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32) ],
263
- Opcodes.i32_from_u
264
- ];
265
- },
394
+ [ Opcodes.end ],
395
+
396
+ // return true
397
+ ...number(1)
398
+ ]
399
+ }
266
400
  };
267
401
 
268
- this[TYPES.string].at.local = valtypeBinary;
402
+ this[TYPES.string].at.local = Valtype.i32;
269
403
  this[TYPES.string].at.returnType = TYPES.string;
270
404
  this[TYPES.string].charAt.returnType = TYPES.string;
271
405
  this[TYPES.string].charCodeAt.local = Valtype.i32;
406
+
407
+ this[TYPES.string].isWellFormed.local = Valtype.i32;
408
+ this[TYPES.string].isWellFormed.local2 = Valtype.i32;
409
+ this[TYPES.string].isWellFormed.returnType = TYPES.boolean;
272
410
  };