| ... | ... | @@ -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 | 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 |
| 324 | | pub const WasmAllocator = blk: { |
| 325 | | if (builtin.arch != builtin.Arch.wasm32) { |
| 326 | | @compileError("only supported in wasm32"); |
| 327 | | } else { |
| 328 | | const mem_grow = @"llvm.wasm.memory.grow.i32"; |
| 329 | | |
| 330 | | const WASM_PAGE_SIZE = 64 * 1024; // 64 kilobytes |
| 336 | pub const WasmAllocator = struct { |
| 337 | allocator: Allocator, |
| 338 | start_ptr: [*]u8, |
| 339 | num_pages: usize, |
| 340 | end_index: usize, |
| 331 | 341 | |
| 332 | | break :blk struct { |
| 333 | | allocator: Allocator, |
| 334 | | fb_allocator: FixedBufferAllocator, |
| 342 | fn alloc(allocator: *Allocator, size: usize, alignment: u29) ![]u8 { |
| 343 | const self = @fieldParentPtr(WasmAllocator, "allocator", allocator); |
| 335 | 344 | |
| 336 | | pub fn init(num_pages: u32) !WasmAllocator { |
| 337 | | const prev_block = mem_grow(0, num_pages); |
| 338 | | if (prev_block == -1) { |
| 339 | | return error.OutOfMemory; |
| 340 | | } |
| 345 | const addr = @ptrToInt(self.start_ptr) + self.end_index; |
| 346 | const adjusted_addr = mem.alignForward(addr, alignment); |
| 347 | const adjusted_index = self.end_index + (adjusted_addr - addr); |
| 348 | const new_end_index = adjusted_index + size; |
| 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{ |
| 345 | | .allocator = Allocator{ |
| 346 | | .reallocFn = realloc, |
| 347 | | .shrinkFn = shrink, |
| 348 | | }, |
| 349 | | .fb_allocator = FixedBufferAllocator.init(buffer_slice), |
| 350 | | }; |
| 352 | if (required_memory > 0) { |
| 353 | var num_pages: u32 = @divTrunc(required_memory, WASM_PAGE_SIZE); |
| 354 | if (@rem(required_memory, WASM_PAGE_SIZE) != 0) { |
| 355 | num_pages += 1; |
| 351 | 356 | } |
| 352 | 357 | |
| 353 | | fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { |
| 354 | | const self = @fieldParentPtr(WasmAllocator, "allocator", allocator); |
| 355 | | return FixedBufferAllocator.realloc(&self.fb_allocator.allocator, old_mem, old_align, new_size, new_align); |
| 358 | const prev_page = @"llvm.wasm.memory.grow.i32"(0, num_pages); |
| 359 | if (prev_page == -1) { |
| 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 { |
| 359 | | const self = @fieldParentPtr(WasmAllocator, "allocator", allocator); |
| 360 | | return FixedBufferAllocator.shrink(&self.fb_allocator.allocator, old_mem, old_align, new_size, new_align); |
| 363 | self.num_pages += num_pages; |
| 364 | } |
| 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 | |