porffor 0.0.0-ebc0491 → 0.0.0-ee33579

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,11 +1,10 @@
1
1
  # porffor
2
- a basic experimental wip *aot* optimizing js -> wasm engine/compiler/runtime in js. not serious/intended for (real) use. (this is a straight forward, honest readme)<br>
2
+ a basic experimental wip *aot* optimizing js -> wasm/c engine/compiler/runtime in js. not serious/intended for (real) use. (this is a straight forward, honest readme)<br>
3
3
  age: ~1 month
4
4
 
5
5
  ## design
6
6
  porffor is a very unique js engine, due a very different approach. it is seriously limited, but what it can do, it does pretty well. key differences:
7
7
  - 100% aot compiled *(not jit)*
8
- - everything is a number
9
8
  - no constant runtime/preluded code
10
9
  - least Wasm imports possible (only stdio)
11
10
 
@@ -18,6 +17,12 @@ porffor is mostly built from scratch, the only thing that is not is the parser (
18
17
  - no variables between scopes (except args and globals)
19
18
  - literal callees only in calls (eg `print()` works, `a = print; a()` does not)
20
19
 
20
+ ## rhemyn
21
+ rhemyn is porffor's own regex engine; it compiles literal regex to wasm bytecode aot (remind you of anything?). it is quite basic and wip. see [its readme](rhemyn/README.md) for more details.
22
+
23
+ ## 2c
24
+ 2c is porffor's own wasm -> c compiler, using generated wasm bytecode and internal info to generate specific and efficient/fast c code. no boilerplate/preluded code or required external files, just for cli binaries (not like wasm2c very much at all).
25
+
21
26
  ## supported
22
27
  see [optimizations](#optimizations) for opts implemented/supported.
23
28
 
@@ -116,8 +121,6 @@ no particular order and no guarentees, just what could happen soon™
116
121
  - basic object expressions (eg `{}`, `{ a: 0 }`)
117
122
  - wasm
118
123
  - *basic* wasm engine (interpreter) in js
119
- - regex
120
- - *basic* regex engine (in wasm compiled aot or js interpreter?)
121
124
  - more math operators (`**`, etc)
122
125
  - `do { ... } while (...)`
123
126
  - rewrite `console.log` to work with strings/arrays
@@ -136,9 +139,6 @@ no particular order and no guarentees, just what could happen soon™
136
139
 
137
140
  ![Screenshot of comparison chart](https://github.com/CanadaHonk/porffor/assets/19228318/76c75264-cc68-4be1-8891-c06dc389d97a)
138
141
 
139
- ## test262
140
- porffor can run test262 via some hacks/transforms which remove unsupported features whilst still doing the same asserts (eg simpler error messages using literals only). it currently passes >10% (see latest commit desc for latest and details). use `node test262` to test, it will also show a difference of overall results between the last commit and current results.
141
-
142
142
  ## optimizations
143
143
  mostly for reducing size. do not really care about compiler perf/time as long as it is reasonable. we do not use/rely on external opt tools (`wasm-opt`, etc), instead doing optimization inside the compiler itself creating even smaller code sizes than `wasm-opt` itself can produce as we have more internal information. (this also enables fast + small runtime use as a potential cursed jit in frontend).
144
144
 
@@ -163,6 +163,9 @@ mostly for reducing size. do not really care about compiler perf/time as long as
163
163
  - type cache/index (no repeated types)
164
164
  - no main func if empty (and other exports)
165
165
 
166
+ ## test262
167
+ porffor can run test262 via some hacks/transforms which remove unsupported features whilst still doing the same asserts (eg simpler error messages using literals only). it currently passes >10% (see latest commit desc for latest and details). use `node test262` to test, it will also show a difference of overall results between the last commit and current results.
168
+
166
169
  ## codebase
167
170
  - `compiler`: contains the compiler itself
168
171
  - `builtins.js`: all built-ins of the engine (spec, custom. vars, funcs)
@@ -183,6 +186,10 @@ mostly for reducing size. do not really care about compiler perf/time as long as
183
186
  - `info.js`: runs with extra info printed
184
187
  - `repl.js`: basic repl (uses `node:repl`)
185
188
 
189
+ - `rhemyn`: contains [rhemyn](#rhemyn) - the regex engine used by porffor
190
+ - `compile.js`: compiles regex ast into wasm bytecode
191
+ - `parse.js`: own regex parser
192
+
186
193
  - `test`: contains many test files for majority of supported features
187
194
  - `test262`: test262 runner and utils
188
195
 
@@ -200,8 +207,13 @@ basically nothing will work :). see files in `test` for examples.
200
207
  you can also use deno (`deno run -A ...` instead of `node ...`), or bun (`bun ...` instead of `node ...`)
201
208
 
202
209
  ### flags
203
- - `-raw` for no info logs (just raw js output)
204
- - `-valtype=i32|i64|f64` to set valtype, f64 by default
210
+ - `-target=wasm|c|native` (default: `wasm`) to set target output (native compiles c output to binary, see args below)
211
+ - `-target=c|native` only:
212
+ - `-o=out.c|out.exe|out` to set file to output c or binary
213
+ - `-target=native` only:
214
+ - `-compiler=clang` to set compiler binary (path/name) to use to compile
215
+ - `-cO=O3` to set compiler opt argument
216
+ - `-valtype=i32|i64|f64` (default: `f64`) to set valtype
205
217
  - `-O0` to disable opt
206
218
  - `-O1` (default) to enable basic opt (simplify insts, treeshake wasm imports)
207
219
  - `-O2` to enable advanced opt (inlining)
@@ -209,6 +221,7 @@ you can also use deno (`deno run -A ...` instead of `node ...`), or bun (`bun ..
209
221
  - `-no-run` to not run wasm output, just compile
210
222
  - `-opt-log` to log some opts
211
223
  - `-code-log` to log some codegen (you probably want `-funcs`)
224
+ - `-regex-log` to log some regex
212
225
  - `-funcs` to log funcs
213
226
  - `-opt-funcs` to log funcs after opt
214
227
  - `-sections` to log sections as hex
package/c ADDED
Binary file
package/c.exe ADDED
Binary file
package/compiler/2c.js ADDED
@@ -0,0 +1,255 @@
1
+ import { read_ieee754_binary64, read_signedLEB128 } from './encoding.js';
2
+ import { Blocktype, Opcodes, Valtype } from './wasmSpec.js';
3
+ import { operatorOpcode } from './expression.js';
4
+
5
+ const CValtype = {
6
+ i8: 'char',
7
+ i16: 'unsigned short', // presume all i16 stuff is unsigned
8
+ i32: 'long',
9
+ i32_u: 'unsigned long',
10
+ i64: 'long long',
11
+ i64_u: 'unsigned long long',
12
+
13
+ f32: 'float',
14
+ f64: 'double',
15
+
16
+ undefined: 'void'
17
+ };
18
+
19
+ const inv = (obj, keyMap = x => x) => Object.keys(obj).reduce((acc, x) => { acc[keyMap(obj[x])] = x; return acc; }, {});
20
+ const invOpcodes = inv(Opcodes);
21
+
22
+ for (const x in CValtype) {
23
+ if (Valtype[x]) CValtype[Valtype[x]] = CValtype[x];
24
+ }
25
+
26
+ const todo = msg => {
27
+ class TodoError extends Error {
28
+ constructor(message) {
29
+ super(message);
30
+ this.name = 'TodoError';
31
+ }
32
+ }
33
+
34
+ throw new TodoError(`todo: ${msg}`);
35
+ };
36
+
37
+ export default ({ funcs, globals, tags, exceptions, pages }) => {
38
+ const invOperatorOpcode = inv(operatorOpcode[valtype]);
39
+ const invGlobals = inv(globals, x => x.idx);
40
+
41
+ const includes = new Map();
42
+ let out = '';
43
+
44
+ for (const x in globals) {
45
+ const g = globals[x];
46
+
47
+ out += `${CValtype[g.type]} ${x}`;
48
+ if (x.init) out += ` ${x.init}`;
49
+ out += ';\n';
50
+ }
51
+
52
+ for (const [ x, p ] of pages) {
53
+ out += `${CValtype[p.type]} ${x.replace(': ', '_').replace(/[^0-9a-zA-Z_]/g, '')}[100]`;
54
+ out += ';\n';
55
+ }
56
+
57
+ if (out) out += '\n';
58
+
59
+ for (const f of funcs) {
60
+ const invLocals = inv(f.locals, x => x.idx);
61
+ if (f.returns.length > 1) todo('funcs returning >1 value unsupported');
62
+
63
+ const sanitize = str => str.replace(/[^0-9a-zA-Z_]/g, _ => String.fromCharCode(97 + _.charCodeAt(0) % 32));
64
+
65
+ const returns = f.returns.length === 1;
66
+
67
+ const shouldInline = ['f64_%'].includes(f.name);
68
+ out += `${f.name === 'main' ? 'int' : CValtype[f.returns[0]]} ${shouldInline ? 'inline ' : ''}${sanitize(f.name)}(${f.params.map((x, i) => `${CValtype[x]} ${invLocals[i]}`).join(', ')}) {\n`;
69
+
70
+ let depth = 1;
71
+ const line = (str, semi = true) => out += `${' '.repeat(depth * 2)}${str}${semi ? ';' : ''}\n`;
72
+
73
+ const localKeys = Object.keys(f.locals).sort((a, b) => f.locals[a].idx - f.locals[b].idx).slice(f.params.length).sort((a, b) => f.locals[a].idx - f.locals[b].idx);
74
+ for (const x of localKeys) {
75
+ const l = f.locals[x];
76
+ line(`${CValtype[l.type]} ${x}`);
77
+ }
78
+
79
+ if (localKeys.length !== 0) out += '\n';
80
+
81
+ let vals = [];
82
+ const endNeedsCurly = [], ignoreEnd = [];
83
+ let beginLoop = false, lastCond = false, ifTernary = false;
84
+ for (let _ = 0; _ < f.wasm.length; _++) {
85
+ const i = f.wasm[_];
86
+
87
+ if (invOperatorOpcode[i[0]]) {
88
+ const b = vals.pop();
89
+ const a = vals.pop();
90
+
91
+ let op = invOperatorOpcode[i[0]];
92
+ if (op.length === 3) op = op.slice(0, 2);
93
+
94
+ if (['==', '!=', '>', '>=', '<', '<='].includes(op)) lastCond = true;
95
+ else lastCond = false;
96
+
97
+ vals.push(`${a} ${op} ${b}`);
98
+ continue;
99
+ }
100
+
101
+ // misc insts
102
+ if (i[0] === 0xfc) {
103
+ switch (i[1]) {
104
+ // i32_trunc_sat_f64_s
105
+ case 0x02:
106
+ vals.push(`(${CValtype.i32})${vals.pop()}`);
107
+ break;
108
+
109
+ // i32_trunc_sat_f64_u
110
+ case 0x03:
111
+ vals.push(`(${CValtype.i32})(${CValtype.i32_u})${vals.pop()}`);
112
+ break;
113
+ }
114
+
115
+ lastCond = false;
116
+ continue;
117
+ }
118
+
119
+ switch (i[0]) {
120
+ case Opcodes.i32_const:
121
+ vals.push(read_signedLEB128(i.slice(1)).toString());
122
+ break;
123
+
124
+ case Opcodes.f64_const:
125
+ vals.push(read_ieee754_binary64(i.slice(1)).toExponential());
126
+ break;
127
+
128
+ case Opcodes.local_get:
129
+ vals.push(`${invLocals[i[1]]}`);
130
+ break;
131
+
132
+ case Opcodes.local_set:
133
+ line(`${invLocals[i[1]]} = ${vals.pop()}`);
134
+ break;
135
+
136
+ case Opcodes.local_tee:
137
+ vals.push(`${invLocals[i[1]]} = ${vals.pop()}`);
138
+ break;
139
+
140
+ case Opcodes.f64_trunc:
141
+ // vals.push(`trunc(${vals.pop()})`);
142
+ vals.push(`(int)(${vals.pop()})`); // this is ~10x faster with clang. what the fuck.
143
+ break;
144
+
145
+ case Opcodes.return:
146
+ line(`return${returns ? ` ${vals.pop()}` : ''}`);
147
+ break;
148
+
149
+ case Opcodes.if:
150
+ let cond = vals.pop();
151
+ if (!lastCond) {
152
+ if (cond.startsWith('(long)')) cond = `${cond.slice(6)} == 1e+0`;
153
+ else cond += ' == 1';
154
+ }
155
+
156
+ ifTernary = i[1] !== Blocktype.void;
157
+ if (ifTernary) {
158
+ ifTernary = cond;
159
+ break;
160
+ }
161
+
162
+ if (beginLoop) {
163
+ beginLoop = false;
164
+ line(`while (${cond}) {`, false);
165
+
166
+ depth++;
167
+ endNeedsCurly.push(true);
168
+ ignoreEnd.push(false, true);
169
+ break;
170
+ }
171
+
172
+ line(`if (${cond}) {`, false);
173
+
174
+ depth++;
175
+ endNeedsCurly.push(true);
176
+ ignoreEnd.push(false);
177
+ break;
178
+
179
+ case Opcodes.else:
180
+ if (ifTernary) break;
181
+
182
+ depth--;
183
+ line(`} else {`, false);
184
+ depth++;
185
+ break;
186
+
187
+ case Opcodes.loop:
188
+ // not doing properly, fake a while loop
189
+ beginLoop = true;
190
+ break;
191
+
192
+ case Opcodes.end:
193
+ if (ignoreEnd.pop()) break;
194
+
195
+ if (ifTernary) {
196
+ const b = vals.pop();
197
+ const a = vals.pop();
198
+ vals.push(`${ifTernary} ? ${a} : ${b}`);
199
+ break;
200
+ }
201
+
202
+ depth--;
203
+ if (endNeedsCurly.pop() === true) line('}', false);
204
+ break;
205
+
206
+ case Opcodes.call:
207
+ let func = funcs.find(x => x.index === i[1]);
208
+ if (!func) {
209
+ const importFunc = importFuncs[i[1]];
210
+ switch (importFunc.name) {
211
+ case 'print':
212
+ line(`printf("%f\\n", ${vals.pop()})`);
213
+ includes.set('stdio.h', true);
214
+ break;
215
+ }
216
+ break;
217
+ }
218
+
219
+ let args = [];
220
+ for (let j = 0; j < func.params.length; j++) args.unshift(vals.pop());
221
+
222
+ if (func.returns.length === 1) vals.push(`${sanitize(func.name)}(${args.join(', ')})`)
223
+ else line(`${sanitize(func.name)}(${args.join(', ')})`);
224
+
225
+ break;
226
+
227
+ case Opcodes.drop:
228
+ line(vals.pop());
229
+ break;
230
+
231
+ case Opcodes.br:
232
+ // ignore
233
+ // reset "stack"
234
+ vals = [];
235
+ break;
236
+
237
+ default:
238
+ log('2c', `unimplemented op: ${invOpcodes[i[0]]}`);
239
+ // todo(`unimplemented op: ${invOpcodes[i[0]]}`);
240
+ }
241
+
242
+ lastCond = false;
243
+ }
244
+
245
+ if (vals.length === 1 && returns) {
246
+ line(`return ${vals.pop()}`);
247
+ }
248
+
249
+ out += '}\n\n';
250
+ }
251
+
252
+ out = [...includes.keys()].map(x => `#include <${x}>`).join('\n') + '\n\n' + out;
253
+
254
+ return out;
255
+ };
@@ -568,7 +568,6 @@ export const BuiltinFuncs = function() {
568
568
  params: [ Valtype.i32 ],
569
569
  locals: [],
570
570
  returns: [ Valtype.v128 ],
571
- memory: true,
572
571
  wasm: [
573
572
  [ Opcodes.local_get, 0 ],
574
573
  [ ...Opcodes.v128_load, 0, 0 ]
@@ -5,6 +5,7 @@ import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./bui
5
5
  import { PrototypeFuncs } from "./prototype.js";
6
6
  import { number, i32x4 } 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;
@@ -745,7 +746,7 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
745
746
  return out;
746
747
  };
747
748
 
748
- const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, memory, localNames = [], globalNames = [] }) => {
749
+ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
749
750
  const existing = funcs.find(x => x.name === name);
750
751
  if (existing) return existing;
751
752
 
@@ -781,7 +782,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
781
782
  returns,
782
783
  returnType: TYPES[returnType ?? 'number'],
783
784
  wasm,
784
- memory,
785
785
  internal: true,
786
786
  index: currentFuncIndex++
787
787
  };
@@ -814,7 +814,8 @@ const TYPES = {
814
814
  bigint: 0xffffffffffff7,
815
815
 
816
816
  // these are not "typeof" types but tracked internally
817
- _array: 0xffffffffffff8
817
+ _array: 0xfffffffffff0f,
818
+ _regexp: 0xfffffffffff1f
818
819
  };
819
820
 
820
821
  const TYPE_NAMES = {
@@ -849,6 +850,8 @@ const getType = (scope, _name) => {
849
850
  const getNodeType = (scope, node) => {
850
851
  if (node.type === 'Literal') {
851
852
  if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
853
+ if (node.regex) return TYPES._regexp;
854
+
852
855
  return TYPES[typeof node.value];
853
856
  }
854
857
 
@@ -882,6 +885,11 @@ const getNodeType = (scope, node) => {
882
885
 
883
886
  // literal.func()
884
887
  if (!name && node.callee.type === 'MemberExpression') {
888
+ if (node.callee.object.regex) {
889
+ const funcName = node.callee.property.name;
890
+ return Rhemyn[funcName] ? TYPES.boolean : TYPES.undefined;
891
+ }
892
+
885
893
  const baseType = getNodeType(scope, node.callee.object);
886
894
 
887
895
  const func = node.callee.property.name;
@@ -925,6 +933,11 @@ const getNodeType = (scope, node) => {
925
933
  const generateLiteral = (scope, decl, global, name) => {
926
934
  if (decl.value === null) return number(NULL);
927
935
 
936
+ if (decl.regex) {
937
+ scope.regex[name] = decl.regex;
938
+ return number(1);
939
+ }
940
+
928
941
  switch (typeof decl.value) {
929
942
  case 'number':
930
943
  return number(decl.value);
@@ -1084,6 +1097,25 @@ const generateCall = (scope, decl, _global, _name) => {
1084
1097
 
1085
1098
  // literal.func()
1086
1099
  if (!name && decl.callee.type === 'MemberExpression') {
1100
+ // megahack for /regex/.func()
1101
+ if (decl.callee.object.regex) {
1102
+ const funcName = decl.callee.property.name;
1103
+ const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
1104
+
1105
+ funcIndex[func.name] = func.index;
1106
+ funcs.push(func);
1107
+
1108
+ return [
1109
+ // make string arg
1110
+ ...generate(scope, decl.arguments[0]),
1111
+
1112
+ // call regex func
1113
+ Opcodes.i32_to_u,
1114
+ [ Opcodes.call, func.index ],
1115
+ Opcodes.i32_from
1116
+ ];
1117
+ }
1118
+
1087
1119
  baseType = getNodeType(scope, decl.callee.object);
1088
1120
 
1089
1121
  const func = decl.callee.property.name;
@@ -1096,6 +1128,31 @@ const generateCall = (scope, decl, _global, _name) => {
1096
1128
  baseName = [...arrays.keys()].pop();
1097
1129
  }
1098
1130
 
1131
+ if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
1132
+ const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
1133
+
1134
+ funcIndex[func.name] = func.index;
1135
+ funcs.push(func);
1136
+
1137
+ const pointer = arrays.get(baseName);
1138
+ const [ local, isGlobal ] = lookupName(scope, baseName);
1139
+
1140
+ return [
1141
+ ...out,
1142
+
1143
+ ...(pointer == null ? [
1144
+ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
1145
+ Opcodes.i32_to_u,
1146
+ ] : [
1147
+ ...number(pointer, Valtype.i32)
1148
+ ]),
1149
+
1150
+ // call regex func
1151
+ [ Opcodes.call, func.index ],
1152
+ Opcodes.i32_from
1153
+ ];
1154
+ }
1155
+
1099
1156
  if (protoFunc) {
1100
1157
  let pointer = arrays.get(baseName);
1101
1158
 
@@ -1705,19 +1762,19 @@ const generateAssignPat = (scope, decl) => {
1705
1762
  };
1706
1763
 
1707
1764
  let pages = new Map();
1708
- const allocPage = reason => {
1709
- if (pages.has(reason)) return pages.get(reason);
1765
+ const allocPage = (reason, type) => {
1766
+ if (pages.has(reason)) return pages.get(reason).ind;
1710
1767
 
1711
- let ind = pages.size;
1712
- pages.set(reason, ind);
1768
+ const ind = pages.size;
1769
+ pages.set(reason, { ind, type });
1713
1770
 
1714
- if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason}`);
1771
+ if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
1715
1772
 
1716
1773
  return ind;
1717
1774
  };
1718
1775
 
1719
1776
  const freePage = reason => {
1720
- let ind = pages.get(reason);
1777
+ const { ind } = pages.get(reason);
1721
1778
  pages.delete(reason);
1722
1779
 
1723
1780
  if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
@@ -1749,7 +1806,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1749
1806
  if (!arrays.has(name) || name === '$undeclared') {
1750
1807
  // todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
1751
1808
  const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
1752
- arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`) * pageSize);
1809
+ arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
1753
1810
  }
1754
1811
 
1755
1812
  const pointer = arrays.get(name);
@@ -1929,7 +1986,6 @@ const generateFunc = (scope, decl) => {
1929
1986
  localInd: 0,
1930
1987
  returns: [ valtypeBinary ],
1931
1988
  returnType: null,
1932
- memory: false,
1933
1989
  throws: false,
1934
1990
  name
1935
1991
  };
@@ -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
 
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
 
@@ -15,7 +17,8 @@ const areaColors = {
15
17
  codegen: [ 20, 80, 250 ],
16
18
  opt: [ 250, 20, 80 ],
17
19
  sections: [ 20, 250, 80 ],
18
- alloc: [ 250, 250, 20 ]
20
+ alloc: [ 250, 250, 20 ],
21
+ '2c': [ 20, 250, 250 ]
19
22
  };
20
23
 
21
24
  globalThis.log = (area, ...args) => console.log(`\u001b[90m[\u001b[0m${rgb(...areaColors[area], area)}\u001b[90m]\u001b[0m`, ...args);
@@ -36,10 +39,16 @@ const logFuncs = (funcs, globals, exceptions) => {
36
39
  console.log();
37
40
  };
38
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
+
39
47
  export default (code, flags) => {
40
48
  globalThis.optLog = process.argv.includes('-opt-log');
41
49
  globalThis.codeLog = process.argv.includes('-code-log');
42
50
  globalThis.allocLog = process.argv.includes('-alloc-log');
51
+ globalThis.regexLog = process.argv.includes('-regex-log');
43
52
 
44
53
  for (const x in BuiltinPreludes) {
45
54
  if (code.indexOf(x + '(') !== -1) code = BuiltinPreludes[x] + code;
@@ -72,5 +81,38 @@ export default (code, flags) => {
72
81
  // console.log([...pages.keys()].map(x => `\x1B[36m - ${x}\x1B[0m`).join('\n'));
73
82
  }
74
83
 
75
- return { wasm: sections, funcs, globals, tags, exceptions, pages };
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') ?? 'O3';
104
+
105
+ const tmpfile = 'tmp.c';
106
+ const args = [ compiler, tmpfile, '-o', outFile ?? (process.platform === 'win32' ? 'out.exe' : 'out'), '-' + cO ];
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;
76
118
  };
package/compiler/opt.js CHANGED
@@ -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
  }
package/compiler/parse.js CHANGED
@@ -1,4 +1,5 @@
1
- const { parse } = (await import(globalThis.document ? 'https://esm.sh/acorn' : 'acorn'));
1
+ import { parse } from 'acorn';
2
+ // const { parse } = (await import(globalThis.document ? 'https://esm.sh/acorn' : 'acorn'));
2
3
 
3
4
  export default (input, flags) => {
4
5
  return parse(input, {
@@ -15,7 +15,8 @@ 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
@@ -76,6 +76,7 @@ export default (funcs, globals, tags, pages, flags) => {
76
76
  }
77
77
  }
78
78
  }
79
+ globalThis.importFuncs = importFuncs;
79
80
 
80
81
  if (optLog) log('sections', `treeshake: using ${importFuncs.length}/${importedFuncs.length} imports`);
81
82
 
package/compiler/wrap.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import compile from './index.js';
2
2
  import decompile from './decompile.js';
3
- import fs from 'node:fs';
3
+ // import fs from 'node:fs';
4
4
 
5
5
  const bold = x => `\u001b[1m${x}\u001b[0m`;
6
6
 
7
7
  const typeBase = 0xffffffffffff0;
8
+ const internalTypeBase = 0xfffffffffff0f;
8
9
  const TYPES = {
9
10
  [typeBase]: 'number',
10
11
  [typeBase + 1]: 'boolean',
@@ -16,7 +17,8 @@ const TYPES = {
16
17
  [typeBase + 7]: 'bigint',
17
18
 
18
19
  // internal
19
- [typeBase + 8]: '_array'
20
+ [internalTypeBase]: '_array',
21
+ [internalTypeBase + 1]: '_regexp'
20
22
  };
21
23
 
22
24
  export default async (source, flags = [ 'module' ], customImports = {}, print = str => process.stdout.write(str)) => {
@@ -27,7 +29,7 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
27
29
 
28
30
  if (source.includes('export function')) flags.push('module');
29
31
 
30
- fs.writeFileSync('out.wasm', Buffer.from(wasm));
32
+ // fs.writeFileSync('out.wasm', Buffer.from(wasm));
31
33
 
32
34
  times.push(performance.now() - t1);
33
35
  if (flags.includes('info')) console.log(bold(`compiled in ${times[0].toFixed(2)}ms`));
package/cool.exe ADDED
Binary file
package/g ADDED
Binary file
package/g.exe ADDED
Binary file