| ... | @@ -318,48 +318,96 @@ pub const FixedBufferAllocator = struct { | ... | @@ -318,48 +318,96 @@ pub const FixedBufferAllocator = struct { |
| 318 | } | 318 | } |
| 319 | }; | 319 | }; |
| 320 | | 320 | |
| | 321 | extern fn @"llvm.wasm.memory.size.i32"(u32) u32; |
| 321 | extern fn @"llvm.wasm.memory.grow.i32"(u32, u32) i32; | 322 | extern fn @"llvm.wasm.memory.grow.i32"(u32, u32) i32; |
| | 323 | const WASM_PAGE_SIZE = 64 * 1024; // 64 kilobytes |
| | 324 | |
| | 325 | pub const wasm_allocator = &wasm_allocator_state.allocator; |
| | 326 | var wasm_allocator_state = WasmAllocator{ |
| | 327 | .allocator = Allocator{ |
| | 328 | .reallocFn = WasmAllocator.realloc, |
| | 329 | .shrinkFn = WasmAllocator.shrink, |
| | 330 | }, |
| | 331 | .start_ptr = undefined, |
| | 332 | .num_pages = 0, |
| | 333 | .end_index = 0, |
| | 334 | }; |
| 322 | | 335 | |
| 323 | /// This allocator tries to allocate the specified number of 64 KB pages and uses FixedBufferAllocator internally | 336 | pub const WasmAllocator = struct { |
| 324 | pub const WasmAllocator = blk: { | 337 | allocator: Allocator, |
| 325 | if (builtin.arch != builtin.Arch.wasm32) { | 338 | start_ptr: [*]u8, |
| 326 | @compileError("only supported in wasm32"); | 339 | num_pages: usize, |
| 327 | } else { | 340 | end_index: usize, |
| 328 | const mem_grow = @"llvm.wasm.memory.grow.i32"; | | |
| 329 | | | |
| 330 | const WASM_PAGE_SIZE = 64 * 1024; // 64 kilobytes | | |
| 331 | | 341 | |
| 332 | break :blk struct { | 342 | fn alloc(allocator: *Allocator, size: usize, alignment: u29) ![]u8 { |
| 333 | allocator: Allocator, | 343 | const self = @fieldParentPtr(WasmAllocator, "allocator", allocator); |
| 334 | fb_allocator: FixedBufferAllocator, | | |
| 335 | | 344 | |
| 336 | pub fn init(num_pages: u32) !WasmAllocator { | 345 | const addr = @ptrToInt(self.start_ptr) + self.end_index; |
| 337 | const prev_block = mem_grow(0, num_pages); | 346 | const adjusted_addr = mem.alignForward(addr, alignment); |
| 338 | if (prev_block == -1) { | 347 | const adjusted_index = self.end_index + (adjusted_addr - addr); |
| 339 | return error.OutOfMemory; | 348 | const new_end_index = adjusted_index + size; |
| 340 | } | | |
| 341 | | 349 | |
| 342 | const buffer_slice = @intToPtr([*]u8, @intCast(usize, prev_block) * WASM_PAGE_SIZE)[0..(WASM_PAGE_SIZE * num_pages)]; | 350 | const required_memory = new_end_index - (self.num_pages * WASM_PAGE_SIZE); |
| 343 | | 351 | |
| 344 | return WasmAllocator{ | 352 | if (required_memory > 0) { |
| 345 | .allocator = Allocator{ | 353 | var num_pages: u32 = @divTrunc(required_memory, WASM_PAGE_SIZE); |
| 346 | .reallocFn = realloc, | 354 | if (@rem(required_memory, WASM_PAGE_SIZE) != 0) { |
| 347 | .shrinkFn = shrink, | 355 | num_pages += 1; |
| 348 | }, | | |
| 349 | .fb_allocator = FixedBufferAllocator.init(buffer_slice), | | |
| 350 | }; | | |
| 351 | } | 356 | } |
| 352 | | 357 | |
| 353 | fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { | 358 | const prev_page = @"llvm.wasm.memory.grow.i32"(0, num_pages); |
| 354 | const self = @fieldParentPtr(WasmAllocator, "allocator", allocator); | 359 | if (prev_page == -1) { |
| 355 | return FixedBufferAllocator.realloc(&self.fb_allocator.allocator, old_mem, old_align, new_size, new_align); | 360 | return error.OutOfMemory; |
| 356 | } | 361 | } |
| 357 | | 362 | |
| 358 | fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { | 363 | self.num_pages += num_pages; |
| 359 | const self = @fieldParentPtr(WasmAllocator, "allocator", allocator); | 364 | } |
| 360 | return FixedBufferAllocator.shrink(&self.fb_allocator.allocator, old_mem, old_align, new_size, new_align); | 365 | |
| | 366 | const result = self.start_ptr[adjusted_index..new_end_index]; |
| | 367 | self.end_index = new_end_index; |
| | 368 | |
| | 369 | |
| | 370 | |
| | 371 | return result; |
| | 372 | } |
| | 373 | |
| | 374 | // Check if memory is the last "item" and it aligns. That lets us expand or reclaim memory |
| | 375 | fn is_last_item(allocator: *Allocator, memory: []u8, alignment: u29) bool { |
| | 376 | const self = @fieldParentPtr(WasmAllocator, "allocator", allocator); |
| | 377 | return memory.ptr == self.start_ptr + self.end_index - memory.len and mem.alignForward(@ptrToInt(memory.ptr), alignment) == @ptrToInt(memory.ptr); |
| | 378 | } |
| | 379 | |
| | 380 | fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { |
| | 381 | const self = @fieldParentPtr(WasmAllocator, "allocator", allocator); |
| | 382 | |
| | 383 | // Initialize start_ptr at the first realloc |
| | 384 | if (self.num_pages == 0) { |
| | 385 | self.start_ptr = @intToPtr([*]u8, @intCast(usize, @"llvm.wasm.memory.size.i32"(0)) * WASM_PAGE_SIZE); |
| | 386 | } |
| | 387 | |
| | 388 | if (is_last_item(allocator, old_mem, new_align)) { |
| | 389 | const start_index = self.end_index - old_mem.len; |
| | 390 | const new_end_index = start_index + new_size; |
| | 391 | |
| | 392 | if (new_end_index > self.num_pages * WASM_PAGE_SIZE) { |
| | 393 | _ = try alloc(allocator, new_end_index - self.end_index, new_align); |
| 361 | } | 394 | } |
| 362 | }; | 395 | const result = self.start_ptr[start_index..new_end_index]; |
| | 396 | |
| | 397 | self.end_index = new_end_index; |
| | 398 | return result; |
| | 399 | } else if (new_size <= old_mem.len and new_align <= old_align) { |
| | 400 | return error.OutOfMemory; |
| | 401 | } else { |
| | 402 | const result = try alloc(allocator, new_size, new_align); |
| | 403 | mem.copy(u8, result, old_mem); |
| | 404 | return result; |
| | 405 | } |
| | 406 | } |
| | 407 | |
| | 408 | fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { |
| | 409 | // TODO: Use is_last_item or other heuristic here |
| | 410 | return old_mem[0..new_size]; |
| 363 | } | 411 | } |
| 364 | }; | 412 | }; |
| 365 | | 413 | |