authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-11-10 18:49:02-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-11-29 23:46:02-07:00
log3ea04ed64cd47e65bee3bad1771d349042bb4392
tree170d0281a177ee5c33a4f5bb7c0fdd61a127bdf6
parent9f8c19210b73704f67b64439c76837ce698cd1af

introduce std.heap.WasmAllocator

fast allocator for WebAssembly eventually this is intended to be merged into `std.heap.GeneralPurposeAllocator`

2 files changed, 228 insertions(+), 3 deletions(-)

lib/std/heap/WasmAllocator.zig created+222
......@@ -0,0 +1,222 @@
1//! This is intended to be merged into GeneralPurposeAllocator at some point.
2
3const std = @import("../std.zig");
4const builtin = @import("builtin");
5const Allocator = std.mem.Allocator;
6const mem = std.mem;
7const assert = std.debug.assert;
8const wasm = std.wasm;
9const math = std.math;
10
11comptime {
12 if (!builtin.target.isWasm()) {
13 @compileError("WasmPageAllocator is only available for wasm32 arch");
14 }
15}
16
17pub const vtable = Allocator.VTable{
18 .alloc = alloc,
19 .resize = resize,
20 .free = free,
21};
22
23pub const Error = Allocator.Error;
24
25const max_usize = math.maxInt(usize);
26const bigpage_size = 512 * 1024;
27const pages_per_bigpage = bigpage_size / wasm.page_size;
28const bigpage_count = max_usize / bigpage_size;
29
30//// This has a length of 1024 usizes.
31//var bigpages_used = [1]usize{0} ** (bigpage_count / @bitSizeOf(usize));
32
33/// We have a small size class for all sizes up to 512kb.
34const size_class_count = math.log2(bigpage_size);
35
36const FreeList = struct {
37 /// Each element is the address of a freed pointer.
38 ptr: [*]usize,
39 len: usize,
40 cap: usize,
41
42 const init: FreeList = .{
43 .ptr = undefined,
44 .len = 0,
45 .cap = 0,
46 };
47};
48
49var next_addrs = [1]usize{0} ** size_class_count;
50var frees = [1]FreeList{FreeList.init} ** size_class_count;
51var bigpage_free_list: FreeList = .{
52 .ptr = &bigpage_free_buf,
53 .len = 0,
54 .cap = bigpage_free_buf.len,
55};
56var bigpage_free_buf: [16]usize = undefined;
57
58fn alloc(ctx: *anyopaque, len: usize, alignment: u29, len_align: u29, ra: usize) Error![]u8 {
59 _ = ctx;
60 _ = len_align;
61 _ = ra;
62 const slot_size = math.ceilPowerOfTwoAssert(usize, @max(len, alignment));
63 const class = math.log2(slot_size);
64 if (class < size_class_count) {
65 const addr = a: {
66 const free_list = &frees[class];
67 if (free_list.len > 0) {
68 free_list.len -= 1;
69 break :a free_list.ptr[free_list.len];
70 }
71
72 // Ensure unused capacity in the corresponding free list.
73 // This prevents memory allocation within free().
74 if (free_list.len >= free_list.cap) {
75 const old_bigpage_count = free_list.cap / bigpage_size;
76 if (bigpage_free_list.cap - bigpage_free_list.len < old_bigpage_count) {
77 return error.OutOfMemory;
78 }
79 const new_bigpage_count = old_bigpage_count + 1;
80 const addr = try allocBigPages(new_bigpage_count);
81 const new_ptr = @intToPtr([*]usize, addr);
82 const old_ptr = free_list.ptr;
83 @memcpy(
84 @ptrCast([*]u8, new_ptr),
85 @ptrCast([*]u8, old_ptr),
86 @sizeOf(usize) * free_list.len,
87 );
88 free_list.ptr = new_ptr;
89 free_list.cap = new_bigpage_count * (bigpage_size / @sizeOf(usize));
90
91 var i: usize = 0;
92 while (i < old_bigpage_count) : (i += 1) {
93 bigpage_free_list.ptr[bigpage_free_list.len] = @ptrToInt(old_ptr) +
94 i * bigpage_size;
95 bigpage_free_list.len += 1;
96 }
97 }
98
99 const next_addr = next_addrs[class];
100 if (next_addr % bigpage_size == 0) {
101 //std.debug.print("alloc big page len={d} class={d} slot_size={d}\n", .{
102 // len, class, slot_size,
103 //});
104 const addr = try allocBigPages(1);
105 next_addrs[class] = addr + slot_size;
106 break :a addr;
107 } else {
108 //std.debug.print("easy! len={d} class={d} slot_size={d}\n", .{
109 // len, class, slot_size,
110 //});
111 next_addrs[class] = next_addr + slot_size;
112 break :a next_addr;
113 }
114 };
115 return @intToPtr([*]u8, addr)[0..len];
116 } else {
117 std.debug.panic("big alloc: len={d} align={d} slot_size={d} class={d}", .{
118 len, alignment, slot_size, class,
119 });
120 }
121}
122
123fn resize(
124 ctx: *anyopaque,
125 buf: []u8,
126 buf_align: u29,
127 new_len: usize,
128 len_align: u29,
129 return_address: usize,
130) ?usize {
131 _ = ctx;
132 _ = buf_align;
133 _ = return_address;
134 _ = len_align;
135 _ = new_len;
136 _ = buf;
137 @panic("handle resize");
138}
139
140fn free(
141 ctx: *anyopaque,
142 buf: []u8,
143 buf_align: u29,
144 return_address: usize,
145) void {
146 _ = ctx;
147 _ = return_address;
148 const class_size = @max(buf.len, buf_align);
149 const class = math.log2(class_size);
150 if (class < size_class_count) {
151 const free_list = &frees[class];
152 assert(free_list.len < free_list.cap);
153 free_list.ptr[free_list.len] = @ptrToInt(buf.ptr);
154 free_list.len += 1;
155 } else {
156 std.debug.panic("big free: len={d} align={d}", .{
157 buf.len, buf_align,
158 });
159 }
160}
161
162inline fn allocBigPages(n: usize) !usize {
163 if (n == 1 and bigpage_free_list.len > 0) {
164 bigpage_free_list.len -= 1;
165 return bigpage_free_list.ptr[bigpage_free_list.len];
166 }
167 const page_index = @wasmMemoryGrow(0, n * pages_per_bigpage);
168 if (page_index <= 0)
169 return error.OutOfMemory;
170 return @intCast(u32, page_index) * wasm.page_size;
171}
172
173const test_ally = Allocator{
174 .ptr = undefined,
175 .vtable = &vtable,
176};
177
178test "small allocations - free in same order" {
179 var list: [513]*u64 = undefined;
180
181 var i: usize = 0;
182 while (i < 513) : (i += 1) {
183 const ptr = try test_ally.create(u64);
184 list[i] = ptr;
185 }
186
187 for (list) |ptr| {
188 test_ally.destroy(ptr);
189 }
190}
191
192test "small allocations - free in reverse order" {
193 var list: [513]*u64 = undefined;
194
195 var i: usize = 0;
196 while (i < 513) : (i += 1) {
197 const ptr = try test_ally.create(u64);
198 list[i] = ptr;
199 }
200
201 i = list.len;
202 while (i > 0) {
203 i -= 1;
204 const ptr = list[i];
205 test_ally.destroy(ptr);
206 }
207}
208
209test "large allocations" {
210 std.debug.print("alloc ptr1\n", .{});
211 const ptr1 = try test_ally.alloc(u64, 42768);
212 std.debug.print("alloc ptr2\n", .{});
213 const ptr2 = try test_ally.alloc(u64, 52768);
214 std.debug.print("free ptr1\n", .{});
215 test_ally.free(ptr1);
216 std.debug.print("alloc ptr3\n", .{});
217 const ptr3 = try test_ally.alloc(u64, 62768);
218 std.debug.print("free ptr3\n", .{});
219 test_ally.free(ptr3);
220 std.debug.print("free ptr2\n", .{});
221 test_ally.free(ptr2);
222}
lib/std/heap/WasmPageAllocator.zig+6-3
......@@ -102,7 +102,8 @@ fn nPages(memsize: usize) usize {
102102 return mem.alignForward(memsize, mem.page_size) / mem.page_size;
103103}
104104
105fn alloc(_: *anyopaque, len: usize, log2_align: u8, ra: usize) ?[*]u8 {
105fn alloc(ctx: *anyopaque, len: usize, log2_align: u8, ra: usize) ?[*]u8 {
106 _ = ctx;
106107 _ = ra;
107108 if (len > maxInt(usize) - (mem.page_size - 1)) return null;
108109 const page_count = nPages(len);
......@@ -159,12 +160,13 @@ fn freePages(start: usize, end: usize) void {
159160}
160161
161162fn resize(
162 _: *anyopaque,
163 ctx: *anyopaque,
163164 buf: []u8,
164165 log2_buf_align: u8,
165166 new_len: usize,
166167 return_address: usize,
167168) bool {
169 _ = ctx;
168170 _ = log2_buf_align;
169171 _ = return_address;
170172 const aligned_len = mem.alignForward(buf.len, mem.page_size);
......@@ -179,11 +181,12 @@ fn resize(
179181}
180182
181183fn free(
182 _: *anyopaque,
184 ctx: *anyopaque,
183185 buf: []u8,
184186 log2_buf_align: u8,
185187 return_address: usize,
186188) void {
189 _ = ctx;
187190 _ = log2_buf_align;
188191 _ = return_address;
189192 const aligned_len = mem.alignForward(buf.len, mem.page_size);