| ... | @@ -1004,7 +1004,10 @@ pub const Blake3 = struct { | ... | @@ -1004,7 +1004,10 @@ pub const Blake3 = struct { |
| 1004 | return hash(b, out, options); | 1004 | return hash(b, out, options); |
| 1005 | } | 1005 | } |
| 1006 | | 1006 | |
| 1007 | const cvs = try allocator.alloc([8]u32, num_full_chunks); | 1007 | const remaining_bytes = b.len % chunk_length; |
| | 1008 | const num_leaves = @divCeil(b.len, chunk_length); |
| | 1009 | |
| | 1010 | const cvs = try allocator.alloc([8]u32, num_leaves); |
| 1008 | defer allocator.free(cvs); | 1011 | defer allocator.free(cvs); |
| 1009 | | 1012 | |
| 1010 | // Process chunks in parallel | 1013 | // Process chunks in parallel |
| ... | @@ -1028,8 +1031,16 @@ pub const Blake3 = struct { | ... | @@ -1028,8 +1031,16 @@ pub const Blake3 = struct { |
| 1028 | } | 1031 | } |
| 1029 | try group.await(io); | 1032 | try group.await(io); |
| 1030 | | 1033 | |
| | 1034 | if (remaining_bytes > 0) { |
| | 1035 | var chunk_state = ChunkState.init(key_words, flags); |
| | 1036 | chunk_state.chunk_counter = num_full_chunks; |
| | 1037 | chunk_state.update(b[num_full_chunks * chunk_length ..]); |
| | 1038 | const output = chunk_state.output(); |
| | 1039 | cvs[num_full_chunks] = output.chainingValue(); |
| | 1040 | } |
| | 1041 | |
| 1031 | // Build Merkle tree in parallel layers using ping-pong buffers | 1042 | // Build Merkle tree in parallel layers using ping-pong buffers |
| 1032 | const max_intermediate_size = (num_full_chunks + 1) / 2; | 1043 | const max_intermediate_size = @divCeil(num_leaves, 2); |
| 1033 | const buffer0 = try allocator.alloc([8]u32, max_intermediate_size); | 1044 | const buffer0 = try allocator.alloc([8]u32, max_intermediate_size); |
| 1034 | defer allocator.free(buffer0); | 1045 | defer allocator.free(buffer0); |
| 1035 | const buffer1 = try allocator.alloc([8]u32, max_intermediate_size); | 1046 | const buffer1 = try allocator.alloc([8]u32, max_intermediate_size); |
| ... | @@ -1064,14 +1075,6 @@ pub const Blake3 = struct { | ... | @@ -1064,14 +1075,6 @@ pub const Blake3 = struct { |
| 1064 | // Finalize remaining small tree sequentially | 1075 | // Finalize remaining small tree sequentially |
| 1065 | var hasher = init_internal(key_words, flags); | 1076 | var hasher = init_internal(key_words, flags); |
| 1066 | for (current_level, 0..) |cv, i| hasher.pushCv(cv, i); | 1077 | for (current_level, 0..) |cv, i| hasher.pushCv(cv, i); |
| 1067 | | | |
| 1068 | hasher.chunk.chunk_counter = num_full_chunks; | | |
| 1069 | const remaining_bytes = b.len % chunk_length; | | |
| 1070 | if (remaining_bytes > 0) { | | |
| 1071 | hasher.chunk.update(b[num_full_chunks * chunk_length ..]); | | |
| 1072 | hasher.mergeCvStack(hasher.chunk.chunk_counter); | | |
| 1073 | } | | |
| 1074 | | | |
| 1075 | hasher.final(out); | 1078 | hasher.final(out); |
| 1076 | } | 1079 | } |
| 1077 | | 1080 | |
| ... | @@ -1453,3 +1456,34 @@ test "BLAKE3 parallel vs sequential" { | ... | @@ -1453,3 +1456,34 @@ test "BLAKE3 parallel vs sequential" { |
| 1453 | try std.testing.expectEqualSlices(u8, &expected_keyed, &actual_keyed); | 1456 | try std.testing.expectEqualSlices(u8, &expected_keyed, &actual_keyed); |
| 1454 | } | 1457 | } |
| 1455 | } | 1458 | } |
| | 1459 | |
| | 1460 | test "BLAKE3 parallel with partial trailing chunk" { |
| | 1461 | const allocator = std.testing.allocator; |
| | 1462 | const io = std.testing.io; |
| | 1463 | |
| | 1464 | const test_sizes = [_]usize{ |
| | 1465 | 3072 * 1024, |
| | 1466 | 3072 * 1024 + 1, |
| | 1467 | 3073 * 1024 + 1, |
| | 1468 | 3074 * 1024 + 1, |
| | 1469 | 3075 * 1024, |
| | 1470 | 3075 * 1024 + 1, |
| | 1471 | 3075 * 1024 + 1023, |
| | 1472 | 3077 * 1024 + 1, |
| | 1473 | 4095 * 1024 + 1, |
| | 1474 | }; |
| | 1475 | |
| | 1476 | for (test_sizes) |size| { |
| | 1477 | const input = try allocator.alloc(u8, size); |
| | 1478 | defer allocator.free(input); |
| | 1479 | for (input, 0..) |*byte, i| byte.* = @truncate(i); |
| | 1480 | |
| | 1481 | var expected: [32]u8 = undefined; |
| | 1482 | Blake3.hash(input, &expected, .{}); |
| | 1483 | |
| | 1484 | var actual: [32]u8 = undefined; |
| | 1485 | try Blake3.hashParallel(input, &actual, .{}, allocator, io); |
| | 1486 | |
| | 1487 | try std.testing.expectEqualSlices(u8, &expected, &actual); |
| | 1488 | } |
| | 1489 | } |