| ... | ... | @@ -67,6 +67,7 @@ fn alloc(ctx: *anyopaque, len: usize, alignment: u29, len_align: u29, ra: usize) |
| 67 | 67 | _ = ctx; |
| 68 | 68 | _ = len_align; |
| 69 | 69 | _ = ra; |
| 70 | if (alignment > wasm.page_size) return error.OutOfMemory; // calm down |
| 70 | 71 | const aligned_len = @max(len, alignment); |
| 71 | 72 | const slot_size = math.ceilPowerOfTwo(usize, aligned_len) catch return error.OutOfMemory; |
| 72 | 73 | const class = math.log2(slot_size); |
| ... | ... | @@ -110,11 +111,8 @@ fn resize( |
| 110 | 111 | buf_align: u29, |
| 111 | 112 | new_len: usize, |
| 112 | 113 | len_align: u29, |
| 113 | | return_address: usize, |
| 114 | ra: usize, |
| 114 | 115 | ) ?usize { |
| 115 | | _ = ctx; |
| 116 | | _ = return_address; |
| 117 | | _ = len_align; |
| 118 | 116 | // We don't want to move anything from one size class to another. But we can recover bytes |
| 119 | 117 | // in between powers of two. |
| 120 | 118 | const old_aligned_len = @max(buf.len, buf_align); |
| ... | ... | @@ -126,13 +124,29 @@ fn resize( |
| 126 | 124 | //std.debug.print("resize: old_small_slot_size={d} new_small_slot_size={d}\n", .{ |
| 127 | 125 | // old_small_slot_size, new_small_slot_size, |
| 128 | 126 | //}); |
| 129 | | if (old_small_slot_size != new_small_slot_size) return null; |
| 127 | if (old_small_slot_size != new_small_slot_size) { |
| 128 | if (new_aligned_len >= old_aligned_len) { |
| 129 | return null; |
| 130 | } |
| 131 | // TODO this panic is a design flaw in the Allocator interface that |
| 132 | // should be addressed. |
| 133 | const new = alloc(ctx, new_len, buf_align, len_align, ra) catch @panic("out of memory"); |
| 134 | @memcpy(new.ptr, buf.ptr, buf.len); |
| 135 | } |
| 130 | 136 | } else { |
| 131 | 137 | const old_bigpages_needed = (old_aligned_len + (bigpage_size - 1)) / bigpage_size; |
| 132 | 138 | const old_big_slot_size = math.ceilPowerOfTwoAssert(usize, old_bigpages_needed); |
| 133 | 139 | const new_bigpages_needed = (new_aligned_len + (bigpage_size - 1)) / bigpage_size; |
| 134 | 140 | const new_big_slot_size = math.ceilPowerOfTwo(usize, new_bigpages_needed) catch return null; |
| 135 | | if (old_big_slot_size != new_big_slot_size) return null; |
| 141 | if (old_big_slot_size != new_big_slot_size) { |
| 142 | if (new_aligned_len >= old_aligned_len) { |
| 143 | return null; |
| 144 | } |
| 145 | // TODO this panic is a design flaw in the Allocator interface that |
| 146 | // should be addressed. |
| 147 | const new = alloc(ctx, new_len, buf_align, len_align, ra) catch @panic("out of memory"); |
| 148 | @memcpy(new.ptr, buf.ptr, buf.len); |
| 149 | } |
| 136 | 150 | } |
| 137 | 151 | return new_len; |
| 138 | 152 | } |
| ... | ... | @@ -291,3 +305,68 @@ test "shrink" { |
| 291 | 305 | try std.testing.expect(b == 0x11); |
| 292 | 306 | } |
| 293 | 307 | } |
| 308 | |
| 309 | test "large object - grow" { |
| 310 | var slice1 = try test_ally.alloc(u8, bigpage_size * 2 - 20); |
| 311 | defer test_ally.free(slice1); |
| 312 | |
| 313 | const old = slice1; |
| 314 | slice1 = try test_ally.realloc(slice1, bigpage_size * 2 - 10); |
| 315 | try std.testing.expect(slice1.ptr == old.ptr); |
| 316 | |
| 317 | slice1 = try test_ally.realloc(slice1, bigpage_size * 2); |
| 318 | try std.testing.expect(slice1.ptr == old.ptr); |
| 319 | |
| 320 | slice1 = try test_ally.realloc(slice1, bigpage_size * 2 + 1); |
| 321 | } |
| 322 | |
| 323 | test "realloc small object to large object" { |
| 324 | var slice = try test_ally.alloc(u8, 70); |
| 325 | defer test_ally.free(slice); |
| 326 | slice[0] = 0x12; |
| 327 | slice[60] = 0x34; |
| 328 | |
| 329 | // This requires upgrading to a large object |
| 330 | const large_object_size = bigpage_size * 2 + 50; |
| 331 | slice = try test_ally.realloc(slice, large_object_size); |
| 332 | try std.testing.expect(slice[0] == 0x12); |
| 333 | try std.testing.expect(slice[60] == 0x34); |
| 334 | } |
| 335 | |
| 336 | test "shrink large object to large object" { |
| 337 | var slice = try test_ally.alloc(u8, bigpage_size * 2 + 50); |
| 338 | defer test_ally.free(slice); |
| 339 | slice[0] = 0x12; |
| 340 | slice[60] = 0x34; |
| 341 | |
| 342 | slice = test_ally.resize(slice, bigpage_size * 2 + 1) orelse return; |
| 343 | try std.testing.expect(slice[0] == 0x12); |
| 344 | try std.testing.expect(slice[60] == 0x34); |
| 345 | |
| 346 | slice = test_ally.shrink(slice, bigpage_size * 2 + 1); |
| 347 | try std.testing.expect(slice[0] == 0x12); |
| 348 | try std.testing.expect(slice[60] == 0x34); |
| 349 | |
| 350 | slice = try test_ally.realloc(slice, bigpage_size * 2); |
| 351 | try std.testing.expect(slice[0] == 0x12); |
| 352 | try std.testing.expect(slice[60] == 0x34); |
| 353 | } |
| 354 | |
| 355 | test "realloc large object to small object" { |
| 356 | var slice = try test_ally.alloc(u8, bigpage_size * 2 + 50); |
| 357 | defer test_ally.free(slice); |
| 358 | slice[0] = 0x12; |
| 359 | slice[16] = 0x34; |
| 360 | |
| 361 | slice = try test_ally.realloc(slice, 19); |
| 362 | try std.testing.expect(slice[0] == 0x12); |
| 363 | try std.testing.expect(slice[16] == 0x34); |
| 364 | } |
| 365 | |
| 366 | test "objects of size 1024 and 2048" { |
| 367 | const slice = try test_ally.alloc(u8, 1025); |
| 368 | const slice2 = try test_ally.alloc(u8, 3000); |
| 369 | |
| 370 | test_ally.free(slice); |
| 371 | test_ally.free(slice2); |
| 372 | } |