porffor 0.16.0-ab08df866 → 0.16.0-c4cdb5460

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CONTRIBUTING.md CHANGED
@@ -26,7 +26,7 @@ You can also swap out `node` in the alias to use another runtime like Deno (`den
26
26
 
27
27
  ### Precompile
28
28
 
29
- **If you update any file inside `compiler/builtins` you will need to do this for it to update inside Porffor otherwise your changes will have no effect.** Run `node compiler/precompile.js` to precompile. It may error during this, if so, you might have an error in your code or there could be a compiler error with Porffor (feel free to ask for help as soon as you encounter any errors with it).
29
+ **If you update any file inside `compiler/builtins` you will need to do this for it to update inside Porffor otherwise your changes will have no effect.** Run `./porf precompile` to precompile. It may error during this, if so, you might have an error in your code or there could be a compiler error with Porffor (feel free to ask for help as soon as you encounter any errors with it).
30
30
 
31
31
  <br>
32
32
 
package/compiler/2c.js CHANGED
@@ -2,6 +2,7 @@ import { read_ieee754_binary64, read_signedLEB128, read_unsignedLEB128 } from '.
2
2
  import { Blocktype, Opcodes, Valtype } from './wasmSpec.js';
3
3
  import { operatorOpcode } from './expression.js';
4
4
  import { log } from './log.js';
5
+ import Prefs from './prefs.js';
5
6
 
6
7
  const CValtype = {
7
8
  i8: 'i8',
@@ -33,11 +34,11 @@ struct ReturnValue {
33
34
  i32 type;
34
35
  };\n\n`;
35
36
 
36
- // todo: is memcpy/etc safe with host endianness?
37
+ // todo: review whether 2cMemcpy should be default or not
37
38
 
38
39
  // all:
39
40
  // immediates: ['align', 'offset']
40
- const CMemFuncs = {
41
+ const CMemFuncs = Prefs['2cMemcpy'] ? {
41
42
  [Opcodes.i32_store]: {
42
43
  c: `memcpy(_memory + offset + pointer, &value, sizeof(value));`,
43
44
  args: ['pointer', 'value'],
@@ -96,6 +97,57 @@ return out;`,
96
97
  argTypes: ['i32'],
97
98
  returns: 'f64'
98
99
  },
100
+ } : {
101
+ [Opcodes.i32_store]: {
102
+ c: `*((i32*)(_memory + offset + pointer)) = value;`,
103
+ args: ['pointer', 'value'],
104
+ argTypes: ['i32', 'i32'],
105
+ returns: false
106
+ },
107
+ [Opcodes.i32_store16]: {
108
+ c: `*((i16*)(_memory + offset + pointer)) = value;`,
109
+ args: ['pointer', 'value'],
110
+ argTypes: ['i32', 'i16'],
111
+ returns: false
112
+ },
113
+ [Opcodes.i32_store8]: {
114
+ c: `*((i8*)(_memory + offset + pointer)) = value;`,
115
+ args: ['pointer', 'value'],
116
+ argTypes: ['i32', 'i8'],
117
+ returns: false
118
+ },
119
+
120
+ [Opcodes.i32_load]: {
121
+ c: `return *((i32*)(_memory + offset + pointer));`,
122
+ args: ['pointer'],
123
+ argTypes: ['i32'],
124
+ returns: 'i32'
125
+ },
126
+ [Opcodes.i32_load16_u]: {
127
+ c: `return *((u16*)(_memory + offset + pointer));`,
128
+ args: ['pointer'],
129
+ argTypes: ['i32'],
130
+ returns: 'i32'
131
+ },
132
+ [Opcodes.i32_load8_u]: {
133
+ c: `return *((i8*)(_memory + offset + pointer));`,
134
+ args: ['pointer'],
135
+ argTypes: ['i32'],
136
+ returns: 'i32'
137
+ },
138
+
139
+ [Opcodes.f64_store]: {
140
+ c: `*((f64*)(_memory + offset + pointer)) = value;`,
141
+ args: ['pointer', 'value'],
142
+ argTypes: ['i32', 'f64'],
143
+ returns: false
144
+ },
145
+ [Opcodes.f64_load]: {
146
+ c: `return *((f64*)(_memory + offset + pointer));`,
147
+ args: ['pointer'],
148
+ argTypes: ['i32'],
149
+ returns: 'f64'
150
+ },
99
151
  };
100
152
 
101
153
  const inv = (obj, keyMap = x => x) => Object.keys(obj).reduce((acc, x) => { acc[keyMap(obj[x])] = x; return acc; }, {});
@@ -152,11 +204,16 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
152
204
 
153
205
  if (pages.size > 0) {
154
206
  prepend.set('_memory', `char _memory[${pages.size * pageSize}];\n`);
155
- includes.set('string.h', true);
207
+ if (Prefs['2cMemcpy']) includes.set('string.h', true);
156
208
  }
157
209
 
158
210
  if (data.length > 0) {
159
- prependMain.set('_data', data.map(x => `memcpy(_memory + ${x.offset}, (unsigned char[]){${x.bytes.join(',')}}, ${x.bytes.length});`).join('\n '));
211
+ if (Prefs['2cMemcpy']) {
212
+ prependMain.set('_data', data.map(x => `memcpy(_memory + ${x.offset}, (unsigned char[]){${x.bytes.join(',')}}, ${x.bytes.length});`).join('\n '));
213
+ includes.set('string.h', true);
214
+ } else {
215
+ prependMain.set('_data', data.map(x => x.bytes.reduce((acc, y, i) => acc + `_memory[${x.offset + i}]=(i8)${y};`, '')).join('\n '));
216
+ }
160
217
  }
161
218
 
162
219
  if (importFuncs.find(x => x.name === '__Porffor_readArgv')) {
@@ -362,7 +419,10 @@ export default ({ funcs, globals, tags, data, exceptions, pages }) => {
362
419
 
363
420
  case Opcodes.return:
364
421
  // line(`return${returns ? ` ${removeBrackets(vals.pop())}` : ''}`);
365
- line(`return${returns ? ` (struct ReturnValue){ ${removeBrackets(vals.pop())}, ${removeBrackets(vals.pop())} }` : ''}`);
422
+ const b = vals.pop();
423
+ const a = vals.pop();
424
+ line(`return${returns ? ` (struct ReturnValue){ ${removeBrackets(a)}, ${removeBrackets(b)} }` : ''}`);
425
+ // line(`return${returns ? ` (struct ReturnValue){ ${removeBrackets(vals.pop())}, ${removeBrackets(vals.pop())} }` : ''}`);
366
426
  break;
367
427
 
368
428
  case Opcodes.if: {
@@ -474,7 +534,7 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
474
534
  out[read++] = ch;
475
535
  }
476
536
 
477
- memcpy(_memory + outPtr, &read, sizeof(read));
537
+ *((i32*)(_memory + outPtr)) = (i32)read;
478
538
  return read;
479
539
  }`);
480
540
 
@@ -504,7 +564,7 @@ _time_out = _time.tv_nsec / 1000000. + _time.tv_sec * 1000.;`);
504
564
 
505
565
  fclose(fp);
506
566
 
507
- memcpy(_memory + outPtr, &read, sizeof(read));
567
+ *((i32*)(_memory + outPtr)) = (i32)read;
508
568
  return read;
509
569
  }`);
510
570
  const outPtr = vals.pop();
@@ -0,0 +1,128 @@
1
+ import { Opcodes, PageSize, Valtype } from './wasmSpec.js';
2
+ import { number } from './embedding.js';
3
+ import Prefs from './prefs.js';
4
+
5
+ // we currently have 3 allocators:
6
+ // - static (default): a static/compile-time allocator. fast (no grow/run-time alloc needed) but can break some code
7
+ // - grow: perform a memory.grow every allocation. simple but maybe slow?
8
+ // - chunk: perform large memory.grow's in chunks when needed. needs investigation
9
+
10
+ export default name => {
11
+ switch (name) {
12
+ case 'static': return new StaticAllocator();
13
+ case 'grow': return new GrowAllocator();
14
+ case 'chunk': return new ChunkAllocator();
15
+ default: throw new Error(`unknown allocator: ${name}`);
16
+ }
17
+ };
18
+
19
+ export class StaticAllocator {
20
+ constructor() {
21
+ }
22
+
23
+ allocType(itemType) {
24
+ switch (itemType) {
25
+ case 'i8': return 'bytestring';
26
+ case 'i16': return 'string';
27
+
28
+ default: return 'array';
29
+ }
30
+ }
31
+
32
+ ptr(ind) {
33
+ if (ind === 0) return 4;
34
+ return ind * PageSize;
35
+ }
36
+
37
+ alloc({ scope, pages }, name, { itemType }) {
38
+ const reason = `${this.allocType(itemType)}: ${Prefs.scopedPageNames ? (scope.name + '/') : ''}${name}`;
39
+
40
+ if (pages.has(reason)) return number(this.ptr(pages.get(reason).ind), Valtype.i32);
41
+
42
+ if (reason.startsWith('array:')) pages.hasArray = true;
43
+ if (reason.startsWith('string:')) pages.hasString = true;
44
+ if (reason.startsWith('bytestring:')) pages.hasByteString = true;
45
+ if (reason.includes('string:')) pages.hasAnyString = true;
46
+
47
+ let ind = pages.size;
48
+ pages.set(reason, { ind, type: itemType });
49
+
50
+ scope.pages ??= new Map();
51
+ scope.pages.set(reason, { ind, type: itemType });
52
+
53
+ return number(this.ptr(ind), Valtype.i32);
54
+ }
55
+ }
56
+
57
+ export class GrowAllocator {
58
+ constructor() {
59
+ Prefs.rmUnusedTypes = false;
60
+ }
61
+
62
+ alloc() {
63
+ return [
64
+ // grow by 1 page
65
+ [ Opcodes.i32_const, 1 ],
66
+ [ Opcodes.memory_grow, 0 ], // returns old page count
67
+
68
+ // get ptr (page count * page size)
69
+ number(65536, Valtype.i32)[0],
70
+ [ Opcodes.i32_mul ]
71
+ ];
72
+ }
73
+ }
74
+
75
+ export class ChunkAllocator {
76
+ constructor(chunkSize) {
77
+ Prefs.rmUnusedTypes = false;
78
+
79
+ // 64KiB * chunk size each growth
80
+ // 16: 1MiB chunks
81
+ this.chunkSize = chunkSize ?? Prefs.chunkAllocatorSize ?? 16;
82
+ }
83
+
84
+ alloc({ asmFunc, funcIndex }) {
85
+ const func = funcIndex['#chunkallocator_alloc'] ?? asmFunc('#chunkallocator_alloc', {
86
+ wasm: [
87
+ [ Opcodes.global_get, 0 ],
88
+ [ Opcodes.global_get, 1 ],
89
+ [ Opcodes.i32_ge_s ],
90
+ [ Opcodes.if, Valtype.i32 ], // ptr >= next
91
+ // grow by chunk size pages
92
+ [ Opcodes.i32_const, this.chunkSize ],
93
+ [ Opcodes.memory_grow, 0 ],
94
+
95
+ // ptr = prev memory size * PageSize
96
+ number(65536, Valtype.i32)[0],
97
+ [ Opcodes.i32_mul ],
98
+ [ Opcodes.global_set, 0 ],
99
+
100
+ // next = ptr + ((chunkSize - 1) * PageSize)
101
+ [ Opcodes.global_get, 0 ],
102
+ number(65536 * (this.chunkSize - 1), Valtype.i32)[0],
103
+ [ Opcodes.i32_add ],
104
+ [ Opcodes.global_set, 1 ],
105
+
106
+ // return ptr
107
+ [ Opcodes.global_get, 0 ],
108
+ [ Opcodes.else ],
109
+ // return ptr = ptr + PageSize
110
+ [ Opcodes.global_get, 0 ],
111
+ number(65536, Valtype.i32)[0],
112
+ [ Opcodes.i32_add ],
113
+ [ Opcodes.global_set, 0 ],
114
+ [ Opcodes.global_get, 0 ],
115
+ [ Opcodes.end ],
116
+ ],
117
+ params: [],
118
+ locals: [],
119
+ globals: [ Valtype.i32, Valtype.i32 ],
120
+ globalNames: ['#chunkallocator_ptr', '#chunkallocator_next'],
121
+ returns: [ Valtype.i32 ],
122
+ }).index;
123
+
124
+ return [
125
+ [ Opcodes.call, func ]
126
+ ];
127
+ }
128
+ }
@@ -21,7 +21,7 @@ const chHint = (topTier, baselineTier, strategy) => {
21
21
  return (strategy | (baselineTier << 2) | (topTier << 4));
22
22
  };
23
23
 
24
- export default (funcs, globals, tags, pages, data, flags) => {
24
+ export default (funcs, globals, tags, pages, data, flags, noTreeshake = false) => {
25
25
  const types = [], typeCache = {};
26
26
 
27
27
  const optLevel = parseInt(process.argv.find(x => x.startsWith('-O'))?.[2] ?? 1);
@@ -44,7 +44,7 @@ export default (funcs, globals, tags, pages, data, flags) => {
44
44
 
45
45
  let importFuncs = [];
46
46
 
47
- if (optLevel < 1 || !Prefs.treeshakeWasmImports) {
47
+ if (optLevel < 1 || !Prefs.treeshakeWasmImports || noTreeshake) {
48
48
  importFuncs = importedFuncs;
49
49
  } else {
50
50
  let imports = new Map();
@@ -94,7 +94,7 @@ export default (funcs, globals, tags, pages, data, flags) => {
94
94
 
95
95
  const importSection = importFuncs.length === 0 ? [] : createSection(
96
96
  Section.import,
97
- encodeVector(importFuncs.map(x => [ 0, ...encodeString(x.import), ExportDesc.func, getType(new Array(x.params).fill(x.name.startsWith('profile') ? Valtype.i32 : valtypeBinary), new Array(x.returns).fill(valtypeBinary)) ]))
97
+ encodeVector(importFuncs.map(x => [ 0, ...encodeString(x.import), ExportDesc.func, getType(typeof x.params === 'object' ? x.params : new Array(x.params).fill(valtypeBinary), new Array(x.returns).fill(valtypeBinary)) ]))
98
98
  );
99
99
 
100
100
  const funcSection = createSection(
@@ -116,7 +116,7 @@ export default (funcs, globals, tags, pages, data, flags) => {
116
116
  ] ])
117
117
  );
118
118
 
119
- if (pages.has('func argc lut')) {
119
+ if (pages.has('func argc lut') && !data.addedFuncArgcLut) {
120
120
  // generate func argc lut data
121
121
  const bytes = [];
122
122
  for (let i = 0; i < funcs.length; i++) {
@@ -128,6 +128,7 @@ export default (funcs, globals, tags, pages, data, flags) => {
128
128
  offset: pages.get('func argc lut').ind * pageSize,
129
129
  bytes
130
130
  });
131
+ data.addedFuncArgcLut = true;
131
132
  }
132
133
 
133
134
  // const t0 = performance.now();
@@ -239,7 +240,13 @@ export default (funcs, globals, tags, pages, data, flags) => {
239
240
 
240
241
  const dataSection = data.length === 0 ? [] : createSection(
241
242
  Section.data,
242
- encodeVector(data.map(x => [ 0x00, Opcodes.i32_const, ...signedLEB128(x.offset), Opcodes.end, ...encodeVector(x.bytes) ]))
243
+ encodeVector(data.map(x => {
244
+ // type: active
245
+ if (x.offset != null) return [ 0x00, Opcodes.i32_const, ...signedLEB128(x.offset), Opcodes.end, ...encodeVector(x.bytes) ];
246
+
247
+ // type: passive
248
+ return [ 0x01, ...encodeVector(x.bytes) ];
249
+ }))
243
250
  );
244
251
 
245
252
  const dataCountSection = data.length === 0 ? [] : createSection(
@@ -171,12 +171,11 @@ export const __Array_prototype_filter = (_this: any[], callbackFn: any) => {
171
171
  };
172
172
 
173
173
  export const __Array_prototype_map = (_this: any[], callbackFn: any) => {
174
- const out: any[] = [];
175
-
176
- const len: i32 = _this.length;
177
174
  let i: i32 = 0;
175
+ const len: i32 = _this.length;
176
+ const out: any[] = new Array(len);
178
177
  while (i < len) {
179
- out.push(callbackFn(_this[i], i++, _this));
178
+ out[i] = callbackFn(_this[i], i++, _this);
180
179
  }
181
180
 
182
181
  return out;
@@ -722,12 +722,9 @@ export const __Porffor_date_allocate = (): Date => {
722
722
  const hack: bytestring = '';
723
723
 
724
724
  if (hack.length == 0) {
725
- hack.length = Porffor.wasm`i32.const 1
726
- memory.grow 0
727
- drop
728
- memory.size 0
725
+ hack.length = Porffor.wasm`
729
726
  i32.const 1
730
- i32.sub
727
+ memory.grow 0
731
728
  i32.const 65536
732
729
  i32.mul
733
730
  i32.from_u`;
@@ -2,12 +2,9 @@ import type {} from './porffor.d.ts';
2
2
 
3
3
  // dark wasm magic for dealing with memory, sorry.
4
4
  export const __Porffor_allocate = (): number => {
5
- Porffor.wasm`i32.const 1
6
- memory.grow 0
7
- drop
8
- memory.size 0
5
+ Porffor.wasm`
9
6
  i32.const 1
10
- i32.sub
7
+ memory.grow 0
11
8
  i32.const 65536
12
9
  i32.mul
13
10
  i32.from_u
@@ -32,13 +32,13 @@ export const importedFuncs = [
32
32
  {
33
33
  name: 'profile1',
34
34
  import: 'y',
35
- params: 1,
35
+ params: [ Valtype.i32 ],
36
36
  returns: 0
37
37
  },
38
38
  {
39
39
  name: 'profile2',
40
40
  import: 'z',
41
- params: 1,
41
+ params: [ Valtype.i32 ],
42
42
  returns: 0
43
43
  },
44
44
  {