@grain/stdlib 0.6.3 → 0.6.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.4](https://github.com/grain-lang/grain/compare/stdlib-v0.6.3...stdlib-v0.6.4) (2024-06-27)
4
+
5
+
6
+ ### Features
7
+
8
+ * **stdlib:** Faster memory allocator ([#2124](https://github.com/grain-lang/grain/issues/2124)) ([03e10c4](https://github.com/grain-lang/grain/commit/03e10c49d204a488f8bd56c7b7262e717ee61762))
9
+
3
10
  ## [0.6.3](https://github.com/grain-lang/grain/compare/stdlib-v0.6.2...stdlib-v0.6.3) (2024-04-06)
4
11
 
5
12
 
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2017-2023 Oscar Spencer <oscar@grain-lang.org> and Philip Blair <philip@grain-lang.org>
3
+ Copyright (c) 2017-2024 Oscar Spencer <oscar@grain-lang.org> and Philip Blair <philip@grain-lang.org>
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/int16.gr CHANGED
@@ -2,6 +2,9 @@
2
2
  * Utilities for working with the Int16 type.
3
3
  * @example from "int16" include Int16
4
4
  *
5
+ * @example 1S
6
+ * @example -1S
7
+ *
5
8
  * @since v0.6.0
6
9
  */
7
10
  module Int16
@@ -51,6 +54,8 @@ let signExtend = x => (x << _DATA_OFFSET) >> _DATA_OFFSET
51
54
  * @param number: The value to convert
52
55
  * @returns The Uint16 represented as an Int16
53
56
  *
57
+ * @example Int16.fromUint16(1uS) == 1S
58
+ *
54
59
  * @since v0.6.0
55
60
  */
56
61
  @unsafe
@@ -67,6 +72,9 @@ provide let fromUint16 = (number: Uint16) => {
67
72
  * @param value: The value to increment
68
73
  * @returns The incremented value
69
74
  *
75
+ * @example Int16.incr(1S) == 2S
76
+ * @example Int16.incr(-2S) == -1S
77
+ *
70
78
  * @since v0.6.0
71
79
  */
72
80
  @unsafe
@@ -83,6 +91,9 @@ provide let incr = (value: Int16) => {
83
91
  * @param value: The value to decrement
84
92
  * @returns The decremented value
85
93
  *
94
+ * @example Int16.decr(2S) == 1S
95
+ * @example Int16.decr(0S) == -1S
96
+ *
86
97
  * @since v0.6.0
87
98
  */
88
99
  @unsafe
@@ -100,6 +111,10 @@ provide let decr = (value: Int16) => {
100
111
  * @param y: The second operand
101
112
  * @returns The sum of the two operands
102
113
  *
114
+ * @example
115
+ * use Int16.{ (+) }
116
+ * assert 1S + 1S == 2S
117
+ *
103
118
  * @since v0.6.0
104
119
  */
105
120
  @unsafe
@@ -121,6 +136,10 @@ provide let (+) = (x: Int16, y: Int16) => {
121
136
  * @param y: The second operand
122
137
  * @returns The difference of the two operands
123
138
  *
139
+ * @example
140
+ * use Int16.{ (-) }
141
+ * assert 2S - 1S == 1S
142
+ *
124
143
  * @since v0.6.0
125
144
  */
126
145
  @unsafe
@@ -139,6 +158,10 @@ provide let (-) = (x: Int16, y: Int16) => {
139
158
  * @param y: The second operand
140
159
  * @returns The product of the two operands
141
160
  *
161
+ * @example
162
+ * use Int16.{ (*) }
163
+ * assert 2S * 2S == 4S
164
+ *
142
165
  * @since v0.6.0
143
166
  */
144
167
  @unsafe
@@ -156,6 +179,10 @@ provide let (*) = (x: Int16, y: Int16) => {
156
179
  * @param y: The second operand
157
180
  * @returns The quotient of its operands
158
181
  *
182
+ * @example
183
+ * use Int16.{ (/) }
184
+ * assert 8S / 2S == 4S
185
+ *
159
186
  * @since v0.6.0
160
187
  */
161
188
  @unsafe
@@ -174,6 +201,8 @@ provide let (/) = (x: Int16, y: Int16) => {
174
201
  * @param y: The second operand
175
202
  * @returns The remainder of its operands
176
203
  *
204
+ * @example Int16.rem(8S, 3S) == 2S
205
+ *
177
206
  * @since v0.6.0
178
207
  */
179
208
  @unsafe
@@ -202,6 +231,10 @@ let abs = n => {
202
231
  *
203
232
  * @throws ModuloByZero: When `y` is zero
204
233
  *
234
+ * @example
235
+ * use Int16.{ (%) }
236
+ * assert -5S % 3S == 1S
237
+ *
205
238
  * @since v0.6.0
206
239
  */
207
240
  @unsafe
@@ -233,6 +266,10 @@ provide let (%) = (x: Int16, y: Int16) => {
233
266
  * @param amount: The number of bits to shift by
234
267
  * @returns The shifted value
235
268
  *
269
+ * @example
270
+ * use Int16.{ (<<) }
271
+ * assert (5S << 1S) == 10S
272
+ *
236
273
  * @since v0.6.0
237
274
  */
238
275
  @unsafe
@@ -252,6 +289,10 @@ provide let (<<) = (value: Int16, amount: Int16) => {
252
289
  * @param amount: The amount to shift by
253
290
  * @returns The shifted value
254
291
  *
292
+ * @example
293
+ * use Int16.{ (>>) }
294
+ * assert (5S >> 1S) == 2S
295
+ *
255
296
  * @since v0.6.0
256
297
  */
257
298
  @unsafe
@@ -271,6 +312,10 @@ provide let (>>) = (value: Int16, amount: Int16) => {
271
312
  * @param y: The second value
272
313
  * @returns `true` if the first value is equal to the second value or `false` otherwise
273
314
  *
315
+ * @example
316
+ * use Int16.{ (==) }
317
+ * assert 1S == 1S
318
+ *
274
319
  * @since v0.6.0
275
320
  */
276
321
  @unsafe
@@ -287,6 +332,10 @@ provide let (==) = (x: Int16, y: Int16) => {
287
332
  * @param y: The second value
288
333
  * @returns `true` if the first value is not equal to the second value or `false` otherwise
289
334
  *
335
+ * @example
336
+ * use Int16.{ (!=) }
337
+ * assert 1S != 2S
338
+ *
290
339
  * @since v0.6.0
291
340
  */
292
341
  @unsafe
@@ -303,6 +352,10 @@ provide let (!=) = (x: Int16, y: Int16) => {
303
352
  * @param y: The second value
304
353
  * @returns `true` if the first value is less than the second value or `false` otherwise
305
354
  *
355
+ * @example
356
+ * use Int16.{ (<) }
357
+ * assert 1S < 2S
358
+ *
306
359
  * @since v0.6.0
307
360
  */
308
361
  @unsafe
@@ -319,6 +372,10 @@ provide let (<) = (x: Int16, y: Int16) => {
319
372
  * @param y: The second value
320
373
  * @returns `true` if the first value is greater than the second value or `false` otherwise
321
374
  *
375
+ * @example
376
+ * use Int16.{ (>) }
377
+ * assert 2S > 1S
378
+ *
322
379
  * @since v0.6.0
323
380
  */
324
381
  @unsafe
@@ -335,6 +392,13 @@ provide let (>) = (x: Int16, y: Int16) => {
335
392
  * @param y: The second value
336
393
  * @returns `true` if the first value is less than or equal to the second value or `false` otherwise
337
394
  *
395
+ * @example
396
+ * use Int16.{ (<=) }
397
+ * assert 1S <= 2S
398
+ * @example
399
+ * use Int16.{ (<=) }
400
+ * assert 1S <= 1S
401
+ *
338
402
  * @since v0.6.0
339
403
  */
340
404
  @unsafe
@@ -351,6 +415,13 @@ provide let (<=) = (x: Int16, y: Int16) => {
351
415
  * @param y: The second value
352
416
  * @returns `true` if the first value is greater than or equal to the second value or `false` otherwise
353
417
  *
418
+ * @example
419
+ * use Int16.{ (>=) }
420
+ * assert 2S >= 1S
421
+ * @example
422
+ * use Int16.{ (>=) }
423
+ * assert 1S >= 1S
424
+ *
354
425
  * @since v0.6.0
355
426
  */
356
427
  @unsafe
@@ -366,6 +437,8 @@ provide let (>=) = (x: Int16, y: Int16) => {
366
437
  * @param value: The given value
367
438
  * @returns Containing the inverted bits of the given value
368
439
  *
440
+ * @example Int16.lnot(-5S) == 4S
441
+ *
369
442
  * @since v0.6.0
370
443
  */
371
444
  @unsafe
@@ -381,6 +454,10 @@ provide let lnot = (value: Int16) => {
381
454
  * @param y: The second operand
382
455
  * @returns Containing a `1` in each bit position for which the corresponding bits of both operands are `1`
383
456
  *
457
+ * @example
458
+ * use Int16.{ (&) }
459
+ * assert (3S & 4S) == 0S
460
+ *
384
461
  * @since v0.6.0
385
462
  */
386
463
  @unsafe
@@ -399,6 +476,10 @@ provide let (&) = (x: Int16, y: Int16) => {
399
476
  * @param y: The second operand
400
477
  * @returns Containing a `1` in each bit position for which the corresponding bits of either or both operands are `1`
401
478
  *
479
+ * @example
480
+ * use Int16.{ (|) }
481
+ * assert (3S | 4S) == 7S
482
+ *
402
483
  * @since v0.6.0
403
484
  */
404
485
  @unsafe
@@ -417,6 +498,10 @@ provide let (|) = (x: Int16, y: Int16) => {
417
498
  * @param y: The second operand
418
499
  * @returns Containing a `1` in each bit position for which the corresponding bits of either but not both operands are `1`
419
500
  *
501
+ * @example
502
+ * use Int16.{ (^) }
503
+ * assert (3S ^ 5S) == 6S
504
+ *
420
505
  * @since v0.6.0
421
506
  */
422
507
  @unsafe
package/int16.md CHANGED
@@ -13,6 +13,14 @@ No other changes yet.
13
13
  from "int16" include Int16
14
14
  ```
15
15
 
16
+ ```grain
17
+ 1S
18
+ ```
19
+
20
+ ```grain
21
+ -1S
22
+ ```
23
+
16
24
  ## Values
17
25
 
18
26
  Functions and constants included in the Int16 module.
@@ -92,6 +100,12 @@ Returns:
92
100
  |----|-----------|
93
101
  |`Int16`|The Uint16 represented as an Int16|
94
102
 
103
+ Examples:
104
+
105
+ ```grain
106
+ Int16.fromUint16(1uS) == 1S
107
+ ```
108
+
95
109
  ### Int16.**incr**
96
110
 
97
111
  <details disabled>
@@ -117,6 +131,16 @@ Returns:
117
131
  |----|-----------|
118
132
  |`Int16`|The incremented value|
119
133
 
134
+ Examples:
135
+
136
+ ```grain
137
+ Int16.incr(1S) == 2S
138
+ ```
139
+
140
+ ```grain
141
+ Int16.incr(-2S) == -1S
142
+ ```
143
+
120
144
  ### Int16.**decr**
121
145
 
122
146
  <details disabled>
@@ -142,6 +166,16 @@ Returns:
142
166
  |----|-----------|
143
167
  |`Int16`|The decremented value|
144
168
 
169
+ Examples:
170
+
171
+ ```grain
172
+ Int16.decr(2S) == 1S
173
+ ```
174
+
175
+ ```grain
176
+ Int16.decr(0S) == -1S
177
+ ```
178
+
145
179
  ### Int16.**(+)**
146
180
 
147
181
  <details disabled>
@@ -168,6 +202,13 @@ Returns:
168
202
  |----|-----------|
169
203
  |`Int16`|The sum of the two operands|
170
204
 
205
+ Examples:
206
+
207
+ ```grain
208
+ use Int16.{ (+) }
209
+ assert 1S + 1S == 2S
210
+ ```
211
+
171
212
  ### Int16.**(-)**
172
213
 
173
214
  <details disabled>
@@ -194,6 +235,13 @@ Returns:
194
235
  |----|-----------|
195
236
  |`Int16`|The difference of the two operands|
196
237
 
238
+ Examples:
239
+
240
+ ```grain
241
+ use Int16.{ (-) }
242
+ assert 2S - 1S == 1S
243
+ ```
244
+
197
245
  ### Int16.**(*)**
198
246
 
199
247
  <details disabled>
@@ -220,6 +268,13 @@ Returns:
220
268
  |----|-----------|
221
269
  |`Int16`|The product of the two operands|
222
270
 
271
+ Examples:
272
+
273
+ ```grain
274
+ use Int16.{ (*) }
275
+ assert 2S * 2S == 4S
276
+ ```
277
+
223
278
  ### Int16.**(/)**
224
279
 
225
280
  <details disabled>
@@ -246,6 +301,13 @@ Returns:
246
301
  |----|-----------|
247
302
  |`Int16`|The quotient of its operands|
248
303
 
304
+ Examples:
305
+
306
+ ```grain
307
+ use Int16.{ (/) }
308
+ assert 8S / 2S == 4S
309
+ ```
310
+
249
311
  ### Int16.**rem**
250
312
 
251
313
  <details disabled>
@@ -272,6 +334,12 @@ Returns:
272
334
  |----|-----------|
273
335
  |`Int16`|The remainder of its operands|
274
336
 
337
+ Examples:
338
+
339
+ ```grain
340
+ Int16.rem(8S, 3S) == 2S
341
+ ```
342
+
275
343
  ### Int16.**(%)**
276
344
 
277
345
  <details disabled>
@@ -305,6 +373,13 @@ Throws:
305
373
 
306
374
  * When `y` is zero
307
375
 
376
+ Examples:
377
+
378
+ ```grain
379
+ use Int16.{ (%) }
380
+ assert -5S % 3S == 1S
381
+ ```
382
+
308
383
  ### Int16.**(<<)**
309
384
 
310
385
  <details disabled>
@@ -331,6 +406,13 @@ Returns:
331
406
  |----|-----------|
332
407
  |`Int16`|The shifted value|
333
408
 
409
+ Examples:
410
+
411
+ ```grain
412
+ use Int16.{ (<<) }
413
+ assert (5S << 1S) == 10S
414
+ ```
415
+
334
416
  ### Int16.**(>>)**
335
417
 
336
418
  <details disabled>
@@ -357,6 +439,13 @@ Returns:
357
439
  |----|-----------|
358
440
  |`Int16`|The shifted value|
359
441
 
442
+ Examples:
443
+
444
+ ```grain
445
+ use Int16.{ (>>) }
446
+ assert (5S >> 1S) == 2S
447
+ ```
448
+
360
449
  ### Int16.**(==)**
361
450
 
362
451
  <details disabled>
@@ -383,6 +472,13 @@ Returns:
383
472
  |----|-----------|
384
473
  |`Bool`|`true` if the first value is equal to the second value or `false` otherwise|
385
474
 
475
+ Examples:
476
+
477
+ ```grain
478
+ use Int16.{ (==) }
479
+ assert 1S == 1S
480
+ ```
481
+
386
482
  ### Int16.**(!=)**
387
483
 
388
484
  <details disabled>
@@ -409,6 +505,13 @@ Returns:
409
505
  |----|-----------|
410
506
  |`Bool`|`true` if the first value is not equal to the second value or `false` otherwise|
411
507
 
508
+ Examples:
509
+
510
+ ```grain
511
+ use Int16.{ (!=) }
512
+ assert 1S != 2S
513
+ ```
514
+
412
515
  ### Int16.**(<)**
413
516
 
414
517
  <details disabled>
@@ -435,6 +538,13 @@ Returns:
435
538
  |----|-----------|
436
539
  |`Bool`|`true` if the first value is less than the second value or `false` otherwise|
437
540
 
541
+ Examples:
542
+
543
+ ```grain
544
+ use Int16.{ (<) }
545
+ assert 1S < 2S
546
+ ```
547
+
438
548
  ### Int16.**(>)**
439
549
 
440
550
  <details disabled>
@@ -461,6 +571,13 @@ Returns:
461
571
  |----|-----------|
462
572
  |`Bool`|`true` if the first value is greater than the second value or `false` otherwise|
463
573
 
574
+ Examples:
575
+
576
+ ```grain
577
+ use Int16.{ (>) }
578
+ assert 2S > 1S
579
+ ```
580
+
464
581
  ### Int16.**(<=)**
465
582
 
466
583
  <details disabled>
@@ -487,6 +604,18 @@ Returns:
487
604
  |----|-----------|
488
605
  |`Bool`|`true` if the first value is less than or equal to the second value or `false` otherwise|
489
606
 
607
+ Examples:
608
+
609
+ ```grain
610
+ use Int16.{ (<=) }
611
+ assert 1S <= 2S
612
+ ```
613
+
614
+ ```grain
615
+ use Int16.{ (<=) }
616
+ assert 1S <= 1S
617
+ ```
618
+
490
619
  ### Int16.**(>=)**
491
620
 
492
621
  <details disabled>
@@ -513,6 +642,18 @@ Returns:
513
642
  |----|-----------|
514
643
  |`Bool`|`true` if the first value is greater than or equal to the second value or `false` otherwise|
515
644
 
645
+ Examples:
646
+
647
+ ```grain
648
+ use Int16.{ (>=) }
649
+ assert 2S >= 1S
650
+ ```
651
+
652
+ ```grain
653
+ use Int16.{ (>=) }
654
+ assert 1S >= 1S
655
+ ```
656
+
516
657
  ### Int16.**lnot**
517
658
 
518
659
  <details disabled>
@@ -538,6 +679,12 @@ Returns:
538
679
  |----|-----------|
539
680
  |`Int16`|Containing the inverted bits of the given value|
540
681
 
682
+ Examples:
683
+
684
+ ```grain
685
+ Int16.lnot(-5S) == 4S
686
+ ```
687
+
541
688
  ### Int16.**(&)**
542
689
 
543
690
  <details disabled>
@@ -564,6 +711,13 @@ Returns:
564
711
  |----|-----------|
565
712
  |`Int16`|Containing a `1` in each bit position for which the corresponding bits of both operands are `1`|
566
713
 
714
+ Examples:
715
+
716
+ ```grain
717
+ use Int16.{ (&) }
718
+ assert (3S & 4S) == 0S
719
+ ```
720
+
567
721
  ### Int16.**(|)**
568
722
 
569
723
  <details disabled>
@@ -590,6 +744,13 @@ Returns:
590
744
  |----|-----------|
591
745
  |`Int16`|Containing a `1` in each bit position for which the corresponding bits of either or both operands are `1`|
592
746
 
747
+ Examples:
748
+
749
+ ```grain
750
+ use Int16.{ (|) }
751
+ assert (3S | 4S) == 7S
752
+ ```
753
+
593
754
  ### Int16.**(^)**
594
755
 
595
756
  <details disabled>
@@ -616,3 +777,10 @@ Returns:
616
777
  |----|-----------|
617
778
  |`Int16`|Containing a `1` in each bit position for which the corresponding bits of either but not both operands are `1`|
618
779
 
780
+ Examples:
781
+
782
+ ```grain
783
+ use Int16.{ (^) }
784
+ assert (3S ^ 5S) == 6S
785
+ ```
786
+
package/int8.gr CHANGED
@@ -433,7 +433,7 @@ provide let (>=) = (x: Int8, y: Int8) => {
433
433
  * @param value: The given value
434
434
  * @returns Containing the inverted bits of the given value
435
435
  *
436
- * @example Int.lnot(-5s) == 4s
436
+ * @example Int8.lnot(-5s) == 4s
437
437
  *
438
438
  * @since v0.6.0
439
439
  */
package/int8.md CHANGED
@@ -682,7 +682,7 @@ Returns:
682
682
  Examples:
683
683
 
684
684
  ```grain
685
- Int.lnot(-5s) == 4s
685
+ Int8.lnot(-5s) == 4s
686
686
  ```
687
687
 
688
688
  ### Int8.**(&)**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grain/stdlib",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
4
4
  "description": "The standard library for the Grain language.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://grain-lang.org",
package/runtime/malloc.gr CHANGED
@@ -3,8 +3,6 @@ module Malloc
3
3
 
4
4
  /*
5
5
  * This module implements a generic memory allocator.
6
- * The algorithm is quite simple, being based on the memory allocator
7
- * from pages 185-188 of K&R C (2nd edition).
8
6
  */
9
7
 
10
8
  from "runtime/unsafe/wasmi32" include WasmI32
@@ -20,6 +18,8 @@ use WasmI32.{
20
18
  (>>>),
21
19
  (==),
22
20
  (!=),
21
+ (&),
22
+ (^),
23
23
  }
24
24
  from "runtime/exception" include Exception
25
25
 
@@ -32,44 +32,64 @@ primitive (||) = "@or"
32
32
 
33
33
  primitive heapStart = "@heap.start"
34
34
 
35
- /* UNDERSTANDING THE STRUCTURE OF THE FREE LIST
36
- * The original K&R definition for the free list entry type was the following:
35
+ /* UNDERSTANDING THE STRUCTURE OF THE FREE LISTS
37
36
  *
38
- * union header {
39
- * struct {
40
- * union header *ptr;
41
- * unsigned size;
42
- * } s;
43
- * long x; // <- forces 8-byte alignment
44
- * };
37
+ * `malloc` allocates memory and `free` releases this memory. Two separate free
38
+ * lists are maintained, one for small blocks of 64 bytes, and one for larger
39
+ * blocks of multiples of 64 bytes. Each block has an 8-byte header and 8-byte
40
+ * footer to keep track of block sizes and maintain the free list.
41
+ *
42
+ * Most allocations in programs are small, so the separate free lists allow us
43
+ * to implement `malloc` and `free` in O(1) for small allocations and O(n)
44
+ * `malloc` and O(1) `free` for large allocations, where `n` is the size of the
45
+ * free list for large blocks.
46
+ *
47
+ * The small blocks are able to service:
48
+ * - Numbers (with the exception of large BigInts/Rationals)
49
+ * - Tuples/Arrays up to 8 elements
50
+ * - Records up to 6 elements
51
+ * - Variants up to 5 elements
52
+ * - Closures up to 6 elements
53
+ * - Bytes/Strings up to length 32
54
+ *
55
+ * Blocks in memory look like this:
45
56
  *
46
- * In memory, this is really just two ints (assuming we're working in 32-bit mode).
47
- * As such, we manually lay out the entries on the heap as follows (note that we
48
- * use helpers to facilitate accessing and setting these values):
57
+ * 8 bytes 8 bytes 64n - 16 bytes 8 bytes 8 bytes
58
+ * ┌─────────────────────┬────────────────┬─────────────────┬────────────────┬─────────────────────┐
59
+ * <prev block footer> <block header> <block content> │ <block footer> │ <next block header> │
60
+ * └─────────────────────┴────────────────┴─────────────────┴────────────────┴─────────────────────┘
49
61
  *
50
- * Grain C Equivalent
51
- * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
52
- * let ptr === union header *ptr
53
- * getNext(ptr) === ptr->s.ptr
54
- * getSize(ptr) === ptr->s.size
62
+ * Block headers look like this:
63
+ * ┌───────────────────────┬──────────────┐
64
+ * │ <prev free block ptr> <block size> │
65
+ * └───────────────────────┴──────────────┘
66
+ *
67
+ * Block footers look like this:
68
+ * ┌───────────────────────┬──────────────┐
69
+ * │ <next free block ptr> │ <block size> │
70
+ * └───────────────────────┴──────────────┘
71
+ *
72
+ * The size is kept in the header and footer to allow us to quickly combine
73
+ * free blocks when blocks are freed.
74
+ *
75
+ * Pointers to the previous/next free blocks give us doubly-linked free lists,
76
+ * which makes it possible to remove blocks from the free list in constant
77
+ * time.
78
+ *
79
+ * A block is considered in use when the previous/next pointers are both zero.
55
80
  */
56
81
 
57
82
  /**
58
- * Pointer to the start of the free list. This is always a multiple of
83
+ * Pointers to the start of the free lists. This is always a multiple of
59
84
  * 8, with the exception of its initial value (used as a sentinel).
60
85
  */
61
- let mut freePtr = 1n
86
+ let mut smallBlockFreePtr = 1n
87
+ let mut largeBlockFreePtr = 1n
62
88
 
63
89
  /**
64
90
  * Size (in bytes) of entries in the free list.
65
91
  */
66
- let mallocHeaderSize = 8n
67
-
68
- /**
69
- * log_2(mallocHeaderSize) (multiplication by the header
70
- * size is equivalent to left-shifting by this amount)
71
- */
72
- let logMallocHeaderSize = 3n
92
+ let _HEADER_FOOTER_SIZE = 8n
73
93
 
74
94
  /**
75
95
  * The current size (in bytes) of the heap.
@@ -87,9 +107,9 @@ let _BASE = heapStart() + _RESERVED_RUNTIME_SPACE
87
107
  /**
88
108
  * The start pointer of the heap.
89
109
  */
90
- let _HEAP_START = _BASE + mallocHeaderSize
110
+ let _HEAP_START = _BASE + _HEADER_FOOTER_SIZE
91
111
 
92
- let _NEXT_OFFSET = 0n
112
+ let _PREV_NEXT_OFFSET = 0n
93
113
  let _SIZE_OFFSET = 4n
94
114
 
95
115
  /**
@@ -97,33 +117,65 @@ let _SIZE_OFFSET = 4n
97
117
  */
98
118
  let _PAGE_SIZE = 65536n
99
119
 
100
- let getNext = (ptr: WasmI32) => {
101
- WasmI32.load(ptr, _NEXT_OFFSET)
120
+ /**
121
+ * Size (in bytes) of blocks allocated by the allocator
122
+ */
123
+ let _UNIT_SIZE = 64n
124
+
125
+ /**
126
+ * log_2(_UNIT_SIZE) (multiplication by the header
127
+ * size is equivalent to left-shifting by this amount)
128
+ */
129
+ let logUnitSize = 6n
130
+
131
+ let headerGetPrevious = (headerPtr: WasmI32) => {
132
+ WasmI32.load(headerPtr, _PREV_NEXT_OFFSET)
133
+ }
134
+
135
+ let headerSetPrevious = (headerPtr: WasmI32, val: WasmI32) => {
136
+ WasmI32.store(headerPtr, val, _PREV_NEXT_OFFSET)
137
+ }
138
+
139
+ let headerGetSize = (headerPtr: WasmI32) => {
140
+ WasmI32.load(headerPtr, _SIZE_OFFSET)
141
+ }
142
+
143
+ let headerSetSize = (headerPtr: WasmI32, val: WasmI32) => {
144
+ WasmI32.store(headerPtr, val, _SIZE_OFFSET)
102
145
  }
103
146
 
104
- let setNext = (ptr: WasmI32, val: WasmI32) => {
105
- WasmI32.store(ptr, val, _NEXT_OFFSET)
147
+ // These functions are no different than the ones above, but exist to make the
148
+ // code much easier to follow
149
+
150
+ let footerGetNext = (footerPtr: WasmI32) => {
151
+ WasmI32.load(footerPtr, _PREV_NEXT_OFFSET)
152
+ }
153
+
154
+ let footerSetNext = (footerPtr: WasmI32, val: WasmI32) => {
155
+ WasmI32.store(footerPtr, val, _PREV_NEXT_OFFSET)
106
156
  }
107
157
 
108
- let getSize = (ptr: WasmI32) => {
109
- WasmI32.load(ptr, _SIZE_OFFSET)
158
+ let footerGetSize = (footerPtr: WasmI32) => {
159
+ WasmI32.load(footerPtr, _SIZE_OFFSET)
110
160
  }
111
161
 
112
- let setSize = (ptr: WasmI32, val: WasmI32) => {
113
- WasmI32.store(ptr, val, _SIZE_OFFSET)
162
+ let footerSetSize = (footerPtr: WasmI32, val: WasmI32) => {
163
+ WasmI32.store(footerPtr, val, _SIZE_OFFSET)
114
164
  }
115
165
 
116
166
  /**
117
- * Requests that the heap be grown by the given number of bytes.
167
+ * Requests that the heap be grown by the given number of units.
118
168
  *
119
- * @param nbytes: The number of bytes requested
169
+ * @param nunits: The number of units requested
120
170
  * @returns The pointer to the beginning of the extended region if successful or -1 otherwise
121
171
  */
122
- let growHeap = (nbytes: WasmI32) => {
172
+ let growHeap = (nunits: WasmI32) => {
123
173
  let mut reqSize = 0n
124
174
  let mut reqResult = 0n
125
175
  let mut origSize = heapSize
126
176
 
177
+ let nbytes = nunits << logUnitSize
178
+
127
179
  // If the size has not been initialized, do so.
128
180
  if (heapSize == 0n) {
129
181
  heapSize = memorySize() * _PAGE_SIZE - _HEAP_START
@@ -131,8 +183,7 @@ let growHeap = (nbytes: WasmI32) => {
131
183
  // More bytes requested than the initial heap size,
132
184
  // so we need to request more anyway.
133
185
  reqSize = nbytes - heapSize
134
- reqSize = reqSize >>> 16n
135
- reqSize += 1n
186
+ reqSize = (reqSize + _PAGE_SIZE - 1n) >>> 16n
136
187
  reqResult = memoryGrow(reqSize)
137
188
  if (reqResult == -1n) {
138
189
  -1n
@@ -161,49 +212,105 @@ let growHeap = (nbytes: WasmI32) => {
161
212
  }
162
213
  }
163
214
 
215
+ let removeFromFreeList = (blockPtr: WasmI32) => {
216
+ let blockSize = headerGetSize(blockPtr)
217
+ let blockFooterPtr = blockPtr + blockSize * _UNIT_SIZE - _HEADER_FOOTER_SIZE
218
+ let nextPtr = footerGetNext(blockFooterPtr)
219
+
220
+ let prevPtr = headerGetPrevious(blockPtr)
221
+ if (prevPtr == 1n) {
222
+ // this block was the start of the free list
223
+ if (blockSize == 1n) {
224
+ smallBlockFreePtr = nextPtr
225
+ } else {
226
+ largeBlockFreePtr = nextPtr
227
+ }
228
+
229
+ headerSetPrevious(nextPtr, prevPtr)
230
+ } else {
231
+ let prevSize = headerGetSize(prevPtr)
232
+ let prevFooterPtr = prevPtr + prevSize * _UNIT_SIZE - _HEADER_FOOTER_SIZE
233
+ footerSetNext(prevFooterPtr, nextPtr)
234
+ headerSetPrevious(nextPtr, prevPtr)
235
+ }
236
+ }
237
+
164
238
  /**
165
239
  * Frees the given allocated pointer.
166
240
  *
167
241
  * @param ap: The pointer to free
168
242
  */
169
243
  provide let free = (ap: WasmI32) => {
170
- let mut blockPtr = ap - 8n // 8 bytes for malloc header
171
- let mut p = freePtr
172
-
173
- // Edge case: for the first free (called by morecore), the free pointer
174
- // is actually already pointing to this node, so we don't do anything.
175
- if (blockPtr != freePtr) {
176
- // Find the location to insert this block into the free list
177
- while (true) {
178
- let nextp = getNext(p)
179
- if (
180
- blockPtr > p && blockPtr < nextp ||
181
- p >= nextp && (blockPtr > p || blockPtr < nextp)
182
- ) {
183
- break
184
- }
185
- p = nextp
244
+ let mut blockPtr = ap - _HEADER_FOOTER_SIZE
245
+ let mut blockSize = headerGetSize(blockPtr)
246
+
247
+ let nextBlockPtr = blockPtr + blockSize * _UNIT_SIZE
248
+ if (headerGetPrevious(nextBlockPtr) > 0n) {
249
+ // adjacent block is free, so merge
250
+ removeFromFreeList(nextBlockPtr)
251
+
252
+ let nextBlockSize = headerGetSize(nextBlockPtr)
253
+ blockSize += nextBlockSize
254
+ headerSetSize(blockPtr, blockSize)
255
+
256
+ let footerPtr = blockPtr + blockSize * _UNIT_SIZE - _HEADER_FOOTER_SIZE
257
+ footerSetSize(footerPtr, blockSize)
258
+ }
259
+
260
+ let prevBlockFooterPtr = blockPtr - _HEADER_FOOTER_SIZE
261
+ if (footerGetNext(prevBlockFooterPtr) > 0n) {
262
+ // (prev) adjacent block is free, so merge
263
+ let prevBlockSize = footerGetSize(prevBlockFooterPtr)
264
+ let prevBlockPtr = blockPtr - prevBlockSize * _UNIT_SIZE
265
+
266
+ if (prevBlockSize == 1n) {
267
+ // Since we merged, this block is already a part of the free list. If
268
+ // the old block was size 1, it needs to be switched to the large list.
269
+ removeFromFreeList(prevBlockPtr)
186
270
  }
187
271
 
188
- // Merge the block into the adjacent free list entry above, if needed
189
- let blockPtrSize = getSize(blockPtr)
190
- let next = getNext(p)
191
- if (blockPtr + blockPtrSize == next) {
192
- setSize(blockPtr, blockPtrSize + getSize(next))
193
- setNext(blockPtr, getNext(next))
194
- } else {
195
- setNext(blockPtr, next)
272
+ blockPtr = prevBlockPtr
273
+
274
+ blockSize += prevBlockSize
275
+ headerSetSize(blockPtr, blockSize)
276
+
277
+ let footerPtr = blockPtr + blockSize * _UNIT_SIZE - _HEADER_FOOTER_SIZE
278
+ footerSetSize(footerPtr, blockSize)
279
+ footerSetNext(footerPtr, footerGetNext(prevBlockFooterPtr))
280
+
281
+ if (prevBlockSize == 1n) {
282
+ if (largeBlockFreePtr != 1n) {
283
+ headerSetPrevious(largeBlockFreePtr, blockPtr)
284
+ }
285
+
286
+ let footerPtr = blockPtr + blockSize * _UNIT_SIZE - _HEADER_FOOTER_SIZE
287
+ footerSetNext(footerPtr, largeBlockFreePtr)
288
+ headerSetPrevious(blockPtr, 1n)
289
+
290
+ largeBlockFreePtr = blockPtr
196
291
  }
197
- // Merge the previous (adjacent) free list entry into this block, if needed
198
- let pSize = getSize(p)
199
- if (p + pSize == blockPtr) {
200
- setSize(p, pSize + getSize(blockPtr))
201
- setNext(p, getNext(blockPtr))
292
+ } else {
293
+ if (blockSize == 1n) {
294
+ if (smallBlockFreePtr != 1n) {
295
+ headerSetPrevious(smallBlockFreePtr, blockPtr)
296
+ }
297
+
298
+ let footerPtr = blockPtr + _UNIT_SIZE - _HEADER_FOOTER_SIZE
299
+ footerSetNext(footerPtr, smallBlockFreePtr)
300
+ headerSetPrevious(blockPtr, 1n)
301
+
302
+ smallBlockFreePtr = blockPtr
202
303
  } else {
203
- setNext(p, blockPtr)
304
+ if (largeBlockFreePtr != 1n) {
305
+ headerSetPrevious(largeBlockFreePtr, blockPtr)
306
+ }
307
+
308
+ let footerPtr = blockPtr + blockSize * _UNIT_SIZE - _HEADER_FOOTER_SIZE
309
+ footerSetNext(footerPtr, largeBlockFreePtr)
310
+ headerSetPrevious(blockPtr, 1n)
311
+
312
+ largeBlockFreePtr = blockPtr
204
313
  }
205
- // Set the free list head to this block
206
- freePtr = p
207
314
  }
208
315
  }
209
316
 
@@ -215,25 +322,48 @@ provide let free = (ap: WasmI32) => {
215
322
  * @param nbytes: The number of bytes to try to grow the heap by
216
323
  * @returns A pointer to the start of the free list if successful or -1 otherwise
217
324
  */
218
- let morecore = (nbytes: WasmI32) => {
325
+ let morecore = (nunits: WasmI32) => {
219
326
  let origSize = heapSize
220
- let mut cp = growHeap(nbytes)
327
+
328
+ let cp = growHeap(nunits + 1n) // include an extra unit for 4 headers/footers
221
329
 
222
330
  // If there was an error, fail
223
331
  if (cp == -1n) {
224
332
  Exception.panic("OutOfMemory: Maximum memory size exceeded")
225
333
  } else {
226
- // Set the size of the new block to the amount the
227
- // heap was grown.
334
+ // Set up the block. We'll add dummy headers/footers before and after the
335
+ // block to avoid unnecessary bounds checks elsewhere in the code.
228
336
  let grownAmount = heapSize - origSize
229
- setSize(cp, grownAmount)
337
+ let units = (grownAmount >>> logUnitSize) - 1n
338
+
339
+ let dummyFooter = cp
340
+ footerSetSize(dummyFooter, 0n)
341
+ footerSetNext(dummyFooter, 0n)
342
+
343
+ let blockHeader = dummyFooter + _HEADER_FOOTER_SIZE
344
+ headerSetSize(blockHeader, units)
345
+ headerSetPrevious(blockHeader, 0n)
346
+
347
+ let blockFooter = blockHeader + units * _UNIT_SIZE - _HEADER_FOOTER_SIZE
348
+ footerSetSize(blockFooter, units)
349
+ footerSetNext(blockFooter, 0n)
350
+
351
+ let dummyHeader = blockFooter + _HEADER_FOOTER_SIZE
352
+ headerSetSize(dummyHeader, 0n)
353
+ headerSetPrevious(dummyHeader, 0n)
354
+
230
355
  // Call free() with the new block to add it to the free list.
231
- free(cp + 8n)
356
+ free(blockHeader + _HEADER_FOOTER_SIZE)
357
+
232
358
  // Return the free list pointer.
233
- freePtr
359
+ largeBlockFreePtr
234
360
  }
235
361
  }
236
362
 
363
+ let roundBytesToUnits = bytes => {
364
+ (bytes + _UNIT_SIZE - 1n) >>> logUnitSize
365
+ }
366
+
237
367
  /**
238
368
  * Allocates the requested number of bytes, returning a pointer.
239
369
  *
@@ -241,70 +371,74 @@ let morecore = (nbytes: WasmI32) => {
241
371
  * @returns The pointer to the allocated region (8-byte aligned) or -1 if the allocation failed
242
372
  */
243
373
  provide let malloc = (nbytes: WasmI32) => {
244
- let mut nbytes = nbytes
245
- let mut prevp = freePtr
374
+ let mut nunits = roundBytesToUnits(nbytes + _HEADER_FOOTER_SIZE * 2n)
246
375
 
247
- // Set nbytes to the next multiple of mallocHeaderSize greater
248
- // than the given size
249
- let mut nunits = (nbytes + mallocHeaderSize - 1n) / mallocHeaderSize + 1n
250
- nbytes = nunits << logMallocHeaderSize // multiply by header size
376
+ // Fast path for small blocks
377
+ if (nunits == 1n && smallBlockFreePtr != 1n) {
378
+ let blockPtr = smallBlockFreePtr
379
+ headerSetPrevious(blockPtr, 0n)
380
+ let footer = blockPtr + _UNIT_SIZE - _HEADER_FOOTER_SIZE
381
+ let next = footerGetNext(footer)
382
+ footerSetNext(footer, 0n)
251
383
 
252
- // Handle initialization
253
- if (heapSize == 0n) {
254
- WasmI32.store(_BASE, _BASE, _NEXT_OFFSET)
255
- freePtr = _BASE
256
- prevp = _BASE
257
- WasmI32.store(_BASE, 0n, _SIZE_OFFSET)
384
+ headerSetPrevious(next, 1n)
385
+ smallBlockFreePtr = next
386
+
387
+ return blockPtr + _HEADER_FOOTER_SIZE
258
388
  }
259
389
 
260
- let mut ret = -1n
261
-
262
- // Search the freelist for any blocks large enough.
263
- for (let mut p = getNext(prevp);; {
264
- prevp = p
265
- p = getNext(p)
266
- }) {
267
- let size = getSize(p)
268
- if (size >= nbytes) {
269
- // If this block is big enough, allocate from it.
270
- if (size == nbytes) {
271
- // It's exactly the right size!
272
- setNext(prevp, getNext(p))
273
- } else {
274
- // Shrink it as needed
275
- let newSize = size - nbytes
276
- setSize(p, newSize)
277
- p += newSize
278
- setSize(p, nbytes)
279
- }
280
- // Update the pointer to the free list.
281
- freePtr = prevp
390
+ // Find a large enough block
391
+ let mut freeBlockPtr = largeBlockFreePtr
392
+ while (true) {
393
+ // Free list is empty; grow the heap
394
+ if (freeBlockPtr == 1n) {
395
+ freeBlockPtr = morecore(nunits)
396
+ }
397
+
398
+ let blockSize = headerGetSize(freeBlockPtr)
399
+ let footerPtr = freeBlockPtr + blockSize * _UNIT_SIZE - _HEADER_FOOTER_SIZE
282
400
 
283
- // Return region past the header
284
- ret = p + 8n
285
- break
401
+ // Perfectly sized block, or one unit larger to avoid leaving size 1 blocks
402
+ // in the large block free list
403
+ if (blockSize == nunits || blockSize == nunits + 1n) {
404
+ let blockPtr = freeBlockPtr
405
+
406
+ removeFromFreeList(blockPtr)
407
+ headerSetPrevious(blockPtr, 0n)
408
+ footerSetNext(footerPtr, 0n)
409
+
410
+ return blockPtr + _HEADER_FOOTER_SIZE
286
411
  }
287
412
 
288
- // We've reached the end of the free list. Time to grow the heap.
289
- if (p == freePtr) {
290
- // Attempt to grow the heap
291
- p = morecore(nbytes)
292
- // If growing the heap failed, return -1.
293
- if (p == -1n) {
294
- ret = -1n
295
- break
296
- }
413
+ // Take a chunk of this larger block
414
+ if (blockSize > nunits) {
415
+ let blockPtr = freeBlockPtr
416
+
417
+ let newSize = blockSize - nunits
418
+ headerSetSize(blockPtr, newSize)
419
+ let newFooterPtr = blockPtr + newSize * _UNIT_SIZE - _HEADER_FOOTER_SIZE
420
+ footerSetSize(newFooterPtr, newSize)
421
+ footerSetNext(newFooterPtr, footerGetNext(footerPtr))
422
+
423
+ let newBlockPtr = newFooterPtr + _HEADER_FOOTER_SIZE
424
+ headerSetSize(newBlockPtr, nunits)
425
+ headerSetPrevious(newBlockPtr, 0n)
426
+ footerSetSize(footerPtr, nunits)
427
+ footerSetNext(footerPtr, 0n)
428
+
429
+ return newBlockPtr + _HEADER_FOOTER_SIZE
297
430
  }
431
+
432
+ freeBlockPtr = footerGetNext(footerPtr)
298
433
  }
299
- ret
434
+
435
+ return -1n
300
436
  }
301
437
 
302
438
  /**
303
- * Returns the current free list pointer.
304
- * Used for debugging.
305
- *
306
- * @returns The free list pointer
439
+ * Leaks all memory in all free lists; used for testing.
307
440
  */
308
- provide let getFreePtr = () => {
309
- freePtr
441
+ provide let leakAll = () => {
442
+ smallBlockFreePtr = 1n
443
+ largeBlockFreePtr = 1n
310
444
  }
package/runtime/malloc.md CHANGED
@@ -46,18 +46,11 @@ Returns:
46
46
  |----|-----------|
47
47
  |`WasmI32`|The pointer to the allocated region (8-byte aligned) or -1 if the allocation failed|
48
48
 
49
- ### Malloc.**getFreePtr**
49
+ ### Malloc.**leakAll**
50
50
 
51
51
  ```grain
52
- getFreePtr : () => WasmI32
52
+ leakAll : () => Void
53
53
  ```
54
54
 
55
- Returns the current free list pointer.
56
- Used for debugging.
57
-
58
- Returns:
59
-
60
- |type|description|
61
- |----|-----------|
62
- |`WasmI32`|The free list pointer|
55
+ Leaks all memory in all free lists; used for testing.
63
56
 
package/set.gr CHANGED
@@ -445,11 +445,6 @@ provide let intersect = (set1, set2) => {
445
445
  add(key, set)
446
446
  }
447
447
  }, set1)
448
- forEach(key => {
449
- if (contains(key, set1)) {
450
- add(key, set)
451
- }
452
- }, set2)
453
448
  set
454
449
  }
455
450
 
package/wasi/process.gr CHANGED
@@ -145,16 +145,17 @@ provide let argv = () => {
145
145
  }
146
146
 
147
147
  let argc = WasmI32.load(argcPtr, 0n)
148
- Memory.free(argcPtr)
149
148
 
150
149
  let argsLength = argc * 4n
151
150
  let arr = allocateArray(argc)
152
151
 
153
152
  if (WasmI32.eqz(argsLength)) {
153
+ Memory.free(argcPtr)
154
154
  return Ok(WasmI32.toGrain(arr): Array<String>)
155
155
  }
156
156
 
157
157
  let argvBufSize = WasmI32.load(argvBufSizePtr, 0n)
158
+ Memory.free(argcPtr)
158
159
 
159
160
  let argvPtr = Memory.malloc(argc * 4n)
160
161
  let argvBufPtr = Memory.malloc(argvBufSize)