* [PATCH v3 00/11] Handle cloning of objects larger than 4GB on Windows
From: Johannes Schindelin via GitGitGadget @ 2026-05-08 8:16 UTC (permalink / raw)
To: git
Cc: Derrick Stolee, Torsten Bögershausen, Jeff King,
Patrick Steinhardt, Johannes Schindelin
In-Reply-To: <pull.2102.v2.git.1777914508.gitgitgadget@gmail.com>
On Windows, unsigned long is 32-bit even on 64-bit systems. This causes
multiple problems when Git handles objects larger than 4GB. This patch
series is a very targeted fix for a very early part of the problem: it
addresses the most fundamental truncation points that prevent a >4GB object
from surviving a clone at all.
Specifically, this fixes:
* zlib's uLong wrapping and triggering BUG() assertions in the git_zstream
wrapper
* Object sizes being truncated in pack streaming, delta headers, and
index-pack/unpack-objects
* pack-objects re-encoding reused pack entries with a truncated size,
producing corrupt packs on the wire
Many other code paths still use unsigned long for object sizes (e.g.,
cat-file -s, object_info.sizep, the delta machinery) and will need their own
conversions. This series does not attempt to fix those.
Based on work by @LordKiRon in git-for-windows/git#6076.
For testing, add a test helper that synthesizes a pack with a >4GB blob and
regression tests that clone it via both the unpack-objects and index-pack
code paths using file:// transport. Since these test cases are quite slow
(even after optimizing the pack generation part, the git clone test has no
chance but to hash 2x4GB of data), they are marked as EXPENSIVE. To ensure
that they are passing well in advance of any release, the CI is changed to
run them in the CI builds of relatively infrequent integration branch
updates.
Changes since v2:
* Now uses the proper data type for the varint decoding value (thanks,
Torsten!)
* The callers that now would silently narrow size_t to unsigned long
properly check and error out instead (thanks, Torsten!)
Changes since v1:
* dramatically accelerated the test helper that generates 4GB pack files,
via two separate strategies:
1. using the "unsafe" SHA-1 for the blob OID computation.
2. using pre-computed "Lego blocks" to construct the 4GB packs needed in
the test cases, where the size (and therefore the involved OIDs) are
well-known in advance.
* even with these improvements, the actual git clone is still slow (of
course, because it cannot use any of those shortcuts), therefore the
tests are marked as EXPENSIVE.
* to exercise those tests nevertheless, the last patch lets all EXPENSIVE
test cases be run for the integration branches other than seen.
Johannes Schindelin (11):
index-pack, unpack-objects: use size_t for object size
git-zlib: handle data streams larger than 4GB
odb, packfile: use size_t for streaming object sizes
delta, packfile: use size_t for delta header sizes
test-tool: add a helper to synthesize large packfiles
t5608: add regression test for >4GB object clone
test-tool synthesize: use the unsafe hash for speed
test-tool synthesize: precompute pack for 4 GiB + 1
test-tool synthesize: add precomputed SHA-256 pack for 4 GiB + 1
t5608: mark >4GB tests as EXPENSIVE
ci: run expensive tests on push builds to integration branches
Makefile | 1 +
builtin/index-pack.c | 8 +-
builtin/pack-objects.c | 34 ++-
builtin/unpack-objects.c | 4 +-
ci/lib.sh | 9 +
compat/zlib-compat.h | 2 +
delta.h | 14 +-
git-zlib.c | 25 +-
git-zlib.h | 4 +-
object-file.c | 12 +-
odb/streaming.c | 13 +-
odb/streaming.h | 2 +-
oss-fuzz/fuzz-pack-headers.c | 2 +-
pack-bitmap.c | 2 +-
pack-check.c | 6 +-
packfile.c | 57 ++--
packfile.h | 4 +-
t/helper/meson.build | 1 +
t/helper/test-synthesize.c | 541 +++++++++++++++++++++++++++++++++++
t/helper/test-tool.c | 1 +
t/helper/test-tool.h | 1 +
t/t5608-clone-2gb.sh | 37 +++
22 files changed, 724 insertions(+), 56 deletions(-)
create mode 100644 t/helper/test-synthesize.c
base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2102%2Fdscho%2Ffix-large-clones-on-windows-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2102/dscho/fix-large-clones-on-windows-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/2102
Range-diff vs v2:
1: dc660106ea ! 1: 311cdc601d index-pack, unpack-objects: use size_t for object size
@@ Commit message
64-bit platforms, and ensuring the shift arithmetic occurs in 64-bit
space.
+ Declare the per-byte continuation variable `c` as size_t as well,
+ matching the canonical varint decoder unpack_object_header_buffer()
+ in packfile.c. With c as size_t the expression (c & 0x7f) << shift
+ is naturally size_t-typed, so the explicit cast that an earlier
+ iteration carried at the use site is no longer needed.
+
+ While at it, add the same overflow guard that
+ unpack_object_header_buffer() carries: if the cumulative shift would
+ exceed bitsizeof(size_t) - 7, refuse the input rather than invoking
+ undefined behavior. Unlike unpack_object_header_buffer(), which
+ labels this case "bad object header", report it as the platform
+ limit it actually is: a header may be perfectly well-formed and
+ still encode a size we cannot represent locally (notably on a
+ 32-bit build consuming a packfile produced on a 64-bit host).
+
This was originally authored by LordKiRon <https://github.com/LordKiRon>,
who preferred not to reveal their real name and therefore agreed that I
take over authorship.
+ Helped-by: Torsten Bögershausen <tboegi@web.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
## builtin/index-pack.c ##
@@ builtin/index-pack.c: static void *unpack_raw_entry(struct object_entry *obj,
{
unsigned char *p;
- unsigned long size, c;
-+ size_t size;
-+ unsigned long c;
++ size_t size, c;
off_t base_offset;
unsigned shift;
void *data;
@@ builtin/index-pack.c: static void *unpack_raw_entry(struct object_entry *obj,
+ size = (c & 15);
+ shift = 4;
+ while (c & 0x80) {
++ if ((bitsizeof(size_t) - 7) < shift)
++ die(_("object size too large for this platform"));
p = fill(1);
c = *p;
use(1);
-- size += (c & 0x7f) << shift;
-+ size += ((size_t)c & 0x7f) << shift;
- shift += 7;
- }
- obj->size = size;
## builtin/unpack-objects.c ##
@@ builtin/unpack-objects.c: static void unpack_one(unsigned nr)
@@ builtin/unpack-objects.c: static void unpack_one(unsigned nr)
unsigned shift;
unsigned char *pack;
- unsigned long size, c;
-+ size_t size;
-+ unsigned long c;
++ size_t size, c;
enum object_type type;
obj_list[nr].offset = consumed_bytes;
@@ builtin/unpack-objects.c: static void unpack_one(unsigned nr)
+ size = (c & 15);
+ shift = 4;
+ while (c & 0x80) {
++ if ((bitsizeof(size_t) - 7) < shift)
++ die(_("object size too large for this platform"));
pack = fill(1);
c = *pack;
use(1);
-- size += (c & 0x7f) << shift;
-+ size += ((size_t)c & 0x7f) << shift;
- shift += 7;
- }
-
2: 92f4327b1f = 2: c611913194 git-zlib: handle data streams larger than 4GB
3: 3a539061c5 ! 3: b789f57de9 odb, packfile: use size_t for streaming object sizes
@@ Commit message
temporary variables where the types differ, with comments noting the
truncation limitation for code paths that still use unsigned long.
+ Widening the producers to size_t in this way introduces a handful of
+ silent size_t -> unsigned long narrowings on Windows, all in
+ builtin/pack-objects.c, where the consumers are still typed
+ unsigned long. Make those narrowings explicit with
+ cast_size_t_to_ulong() so they assert loudly the moment an object
+ actually exceeds ULONG_MAX bytes:
+
+ - oe_get_size_slow() returns unsigned long but holds a size_t
+ locally; cast at the return.
+ - write_reuse_object() passes a size_t into check_pack_inflate(),
+ whose expect parameter is unsigned long; cast at the call.
+ - check_object() routes a size_t through SET_SIZE() and
+ SET_DELTA_SIZE(), both of which take unsigned long via
+ oe_set_size() / oe_set_delta_size(); cast at the three call
+ sites in the OBJ_OFS_DELTA / OBJ_REF_DELTA branches and in the
+ non-delta default arm.
+
+ The cast-only treatment is deliberately a stop-gap. Properly
+ widening oe_set_size, oe_get_size_slow's return type,
+ check_pack_inflate's expect parameter, object_info.sizep,
+ patch_delta, and the OE_SIZE_BITS bit-fields cascades into a series
+ that is too large to be reviewable, so the proper widening is
+ deferred to a follow-up topic. Until then,
+ cast_size_t_to_ulong() at least makes the truncation explicit at
+ the source: it documents the boundary, and on a 64-bit non-Windows
+ platform it is a no-op.
+
This was originally authored by LordKiRon <https://github.com/LordKiRon>,
who preferred not to reveal their real name and therefore agreed that I
take over authorship.
+ Helped-by: Torsten Bögershausen <tboegi@web.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
## builtin/pack-objects.c ##
@@ builtin/pack-objects.c: static off_t write_reuse_object(struct hashfile *f, stru
if (DELTA(entry))
type = (allow_ofs_delta && DELTA(entry)->idx.offset) ?
+@@ builtin/pack-objects.c: static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,
+ datalen -= entry->in_pack_header_size;
+
+ if (!pack_to_stdout && p->index_version == 1 &&
+- check_pack_inflate(p, &w_curs, offset, datalen, entry_size)) {
++ check_pack_inflate(p, &w_curs, offset, datalen,
++ cast_size_t_to_ulong(entry_size))) {
+ error(_("corrupt packed object for %s"),
+ oid_to_hex(&entry->idx.oid));
+ unuse_pack(&w_curs);
@@ builtin/pack-objects.c: static void write_reused_pack_one(struct packed_git *reuse_packfile,
{
off_t offset, next, cur;
@@ builtin/pack-objects.c: static void check_object(struct object_entry *entry, uin
buf = use_pack(p, &w_curs, entry->in_pack_offset, &avail);
+@@ builtin/pack-objects.c: static void check_object(struct object_entry *entry, uint32_t object_index)
+ default:
+ /* Not a delta hence we've already got all we need. */
+ oe_set_type(entry, entry->in_pack_type);
+- SET_SIZE(entry, in_pack_size);
++ SET_SIZE(entry, cast_size_t_to_ulong(in_pack_size));
+ entry->in_pack_header_size = used;
+ if (oe_type(entry) < OBJ_COMMIT || oe_type(entry) > OBJ_BLOB)
+ goto give_up;
+@@ builtin/pack-objects.c: static void check_object(struct object_entry *entry, uint32_t object_index)
+ if (have_base &&
+ can_reuse_delta(&base_ref, entry, &base_entry)) {
+ oe_set_type(entry, entry->in_pack_type);
+- SET_SIZE(entry, in_pack_size); /* delta size */
+- SET_DELTA_SIZE(entry, in_pack_size);
++ SET_SIZE(entry, cast_size_t_to_ulong(in_pack_size)); /* delta size */
++ SET_DELTA_SIZE(entry, cast_size_t_to_ulong(in_pack_size));
+
+ if (base_entry) {
+ SET_DELTA(entry, base_entry);
@@ builtin/pack-objects.c: unsigned long oe_get_size_slow(struct packing_data *pack,
struct pack_window *w_curs;
unsigned char *buf;
@@ builtin/pack-objects.c: unsigned long oe_get_size_slow(struct packing_data *pack
}
p = oe_in_pack(pack, e);
+@@ builtin/pack-objects.c: unsigned long oe_get_size_slow(struct packing_data *pack,
+
+ unuse_pack(&w_curs);
+ packing_data_unlock(&to_pack);
+- return size;
++ return cast_size_t_to_ulong(size);
+ }
+
+ static int try_delta(struct unpacked *trg, struct unpacked *src,
## object-file.c ##
@@ object-file.c: int odb_source_loose_read_object_stream(struct odb_read_stream **out,
4: 3274cba862 = 4: 8e87a4e71f delta, packfile: use size_t for delta header sizes
5: afa74a3a2b = 5: 34fec4a32d test-tool: add a helper to synthesize large packfiles
6: a3019888d8 = 6: 88f992903f t5608: add regression test for >4GB object clone
7: 859e93e7a9 = 7: 4f207c8a47 test-tool synthesize: use the unsafe hash for speed
8: 29b9a74e91 = 8: 2751c21c6e test-tool synthesize: precompute pack for 4 GiB + 1
9: 8e6e720804 = 9: 3a006d96c3 test-tool synthesize: add precomputed SHA-256 pack for 4 GiB + 1
10: 5b44410b2f = 10: 86c09af4f5 t5608: mark >4GB tests as EXPENSIVE
11: 1eaaa7fad7 = 11: 2159f6a271 ci: run expensive tests on push builds to integration branches
--
gitgitgadget
^ permalink raw reply
* [PATCH v3 01/11] index-pack, unpack-objects: use size_t for object size
From: Johannes Schindelin via GitGitGadget @ 2026-05-08 8:16 UTC (permalink / raw)
To: git
Cc: Derrick Stolee, Torsten Bögershausen, Jeff King,
Patrick Steinhardt, Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2102.v3.git.1778228209.gitgitgadget@gmail.com>
From: Johannes Schindelin <johannes.schindelin@gmx.de>
When unpacking objects from a packfile, the object size is decoded
from a variable-length encoding. On platforms where unsigned long is
32-bit (such as Windows, even in 64-bit builds), the shift operation
overflows when decoding sizes larger than 4GB. The result is a
truncated size value, causing the unpacked object to be corrupted or
rejected.
Fix this by changing the size variable to size_t, which is 64-bit on
64-bit platforms, and ensuring the shift arithmetic occurs in 64-bit
space.
Declare the per-byte continuation variable `c` as size_t as well,
matching the canonical varint decoder unpack_object_header_buffer()
in packfile.c. With c as size_t the expression (c & 0x7f) << shift
is naturally size_t-typed, so the explicit cast that an earlier
iteration carried at the use site is no longer needed.
While at it, add the same overflow guard that
unpack_object_header_buffer() carries: if the cumulative shift would
exceed bitsizeof(size_t) - 7, refuse the input rather than invoking
undefined behavior. Unlike unpack_object_header_buffer(), which
labels this case "bad object header", report it as the platform
limit it actually is: a header may be perfectly well-formed and
still encode a size we cannot represent locally (notably on a
32-bit build consuming a packfile produced on a 64-bit host).
This was originally authored by LordKiRon <https://github.com/LordKiRon>,
who preferred not to reveal their real name and therefore agreed that I
take over authorship.
Helped-by: Torsten Bögershausen <tboegi@web.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
builtin/index-pack.c | 8 +++++---
builtin/unpack-objects.c | 4 +++-
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index ca7784dc2c..2e4b42fa12 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -37,7 +37,7 @@ static const char index_pack_usage[] =
struct object_entry {
struct pack_idx_entry idx;
- unsigned long size;
+ size_t size;
unsigned char hdr_size;
signed char type;
signed char real_type;
@@ -469,7 +469,7 @@ static int is_delta_type(enum object_type type)
return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA);
}
-static void *unpack_entry_data(off_t offset, unsigned long size,
+static void *unpack_entry_data(off_t offset, size_t size,
enum object_type type, struct object_id *oid)
{
static char fixed_buf[8192];
@@ -524,7 +524,7 @@ static void *unpack_raw_entry(struct object_entry *obj,
struct object_id *oid)
{
unsigned char *p;
- unsigned long size, c;
+ size_t size, c;
off_t base_offset;
unsigned shift;
void *data;
@@ -539,6 +539,8 @@ static void *unpack_raw_entry(struct object_entry *obj,
size = (c & 15);
shift = 4;
while (c & 0x80) {
+ if ((bitsizeof(size_t) - 7) < shift)
+ die(_("object size too large for this platform"));
p = fill(1);
c = *p;
use(1);
diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c
index e01cf6e360..76b3d0dee3 100644
--- a/builtin/unpack-objects.c
+++ b/builtin/unpack-objects.c
@@ -533,7 +533,7 @@ static void unpack_one(unsigned nr)
{
unsigned shift;
unsigned char *pack;
- unsigned long size, c;
+ size_t size, c;
enum object_type type;
obj_list[nr].offset = consumed_bytes;
@@ -545,6 +545,8 @@ static void unpack_one(unsigned nr)
size = (c & 15);
shift = 4;
while (c & 0x80) {
+ if ((bitsizeof(size_t) - 7) < shift)
+ die(_("object size too large for this platform"));
pack = fill(1);
c = *pack;
use(1);
--
gitgitgadget
^ permalink raw reply related
* [PATCH v3 02/11] git-zlib: handle data streams larger than 4GB
From: Johannes Schindelin via GitGitGadget @ 2026-05-08 8:16 UTC (permalink / raw)
To: git
Cc: Derrick Stolee, Torsten Bögershausen, Jeff King,
Patrick Steinhardt, Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2102.v3.git.1778228209.gitgitgadget@gmail.com>
From: Johannes Schindelin <johannes.schindelin@gmx.de>
On Windows, zlib's `uLong` type is 32-bit even on 64-bit systems. When
processing data streams larger than 4GB, the `total_in` and `total_out`
fields in zlib's `z_stream` structure wrap around, which caused the
sanity checks in `zlib_post_call()` to trigger `BUG()` assertions.
The git_zstream wrapper now tracks its own 64-bit totals rather than
copying them from zlib. The sanity checks compare only the low bits,
using `maximum_unsigned_value_of_type(uLong)` to mask appropriately for
the platform's `uLong` size.
This is based on work by LordKiRon in git-for-windows#6076.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
git-zlib.c | 25 +++++++++++++++++--------
git-zlib.h | 4 ++--
object-file.c | 2 +-
3 files changed, 20 insertions(+), 11 deletions(-)
diff --git a/git-zlib.c b/git-zlib.c
index df9604910e..b91cb323ae 100644
--- a/git-zlib.c
+++ b/git-zlib.c
@@ -30,6 +30,9 @@ static const char *zerr_to_string(int status)
*/
/* #define ZLIB_BUF_MAX ((uInt)-1) */
#define ZLIB_BUF_MAX ((uInt) 1024 * 1024 * 1024) /* 1GB */
+
+/* uLong is 32-bit on Windows, even on 64-bit systems */
+#define ULONG_MAX_VALUE maximum_unsigned_value_of_type(uLong)
static inline uInt zlib_buf_cap(unsigned long len)
{
return (ZLIB_BUF_MAX < len) ? ZLIB_BUF_MAX : len;
@@ -39,31 +42,37 @@ static void zlib_pre_call(git_zstream *s)
{
s->z.next_in = s->next_in;
s->z.next_out = s->next_out;
- s->z.total_in = s->total_in;
- s->z.total_out = s->total_out;
+ s->z.total_in = (uLong)(s->total_in & ULONG_MAX_VALUE);
+ s->z.total_out = (uLong)(s->total_out & ULONG_MAX_VALUE);
s->z.avail_in = zlib_buf_cap(s->avail_in);
s->z.avail_out = zlib_buf_cap(s->avail_out);
}
static void zlib_post_call(git_zstream *s, int status)
{
- unsigned long bytes_consumed;
- unsigned long bytes_produced;
+ size_t bytes_consumed;
+ size_t bytes_produced;
bytes_consumed = s->z.next_in - s->next_in;
bytes_produced = s->z.next_out - s->next_out;
- if (s->z.total_out != s->total_out + bytes_produced)
+ /*
+ * zlib's total_out/total_in are uLong which may wrap for >4GB.
+ * We track our own totals and verify only the low bits match.
+ */
+ if ((s->z.total_out & ULONG_MAX_VALUE) !=
+ ((s->total_out + bytes_produced) & ULONG_MAX_VALUE))
BUG("total_out mismatch");
/*
* zlib does not update total_in when it returns Z_NEED_DICT,
* causing a mismatch here. Skip the sanity check in that case.
*/
if (status != Z_NEED_DICT &&
- s->z.total_in != s->total_in + bytes_consumed)
+ (s->z.total_in & ULONG_MAX_VALUE) !=
+ ((s->total_in + bytes_consumed) & ULONG_MAX_VALUE))
BUG("total_in mismatch");
- s->total_out = s->z.total_out;
- s->total_in = s->z.total_in;
+ s->total_out += bytes_produced;
+ s->total_in += bytes_consumed;
/* zlib-ng marks `next_in` as `const`, so we have to cast it away. */
s->next_in = (unsigned char *) s->z.next_in;
s->next_out = s->z.next_out;
diff --git a/git-zlib.h b/git-zlib.h
index 0e66fefa8c..44380e8ad3 100644
--- a/git-zlib.h
+++ b/git-zlib.h
@@ -7,8 +7,8 @@ typedef struct git_zstream {
struct z_stream_s z;
unsigned long avail_in;
unsigned long avail_out;
- unsigned long total_in;
- unsigned long total_out;
+ size_t total_in;
+ size_t total_out;
unsigned char *next_in;
unsigned char *next_out;
} git_zstream;
diff --git a/object-file.c b/object-file.c
index 2acc9522df..086b2b65ff 100644
--- a/object-file.c
+++ b/object-file.c
@@ -1118,7 +1118,7 @@ int odb_source_loose_write_stream(struct odb_source *source,
} while (ret == Z_OK || ret == Z_BUF_ERROR);
if (stream.total_in != len + hdrlen)
- die(_("write stream object %ld != %"PRIuMAX), stream.total_in,
+ die(_("write stream object %"PRIuMAX" != %"PRIuMAX), (uintmax_t)stream.total_in,
(uintmax_t)len + hdrlen);
/*
--
gitgitgadget
^ permalink raw reply related
* [PATCH v3 03/11] odb, packfile: use size_t for streaming object sizes
From: Johannes Schindelin via GitGitGadget @ 2026-05-08 8:16 UTC (permalink / raw)
To: git
Cc: Derrick Stolee, Torsten Bögershausen, Jeff King,
Patrick Steinhardt, Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2102.v3.git.1778228209.gitgitgadget@gmail.com>
From: Johannes Schindelin <johannes.schindelin@gmx.de>
The odb_read_stream structure uses unsigned long for the size field,
which is 32-bit on Windows even in 64-bit builds. When streaming
objects larger than 4GB, the size would be truncated to zero or an
incorrect value, resulting in empty files being written to disk.
Change the size field in odb_read_stream to size_t and introduce
unpack_object_header_sz() to return sizes via size_t pointer. Since
object_info.sizep remains unsigned long for API compatibility, use
temporary variables where the types differ, with comments noting the
truncation limitation for code paths that still use unsigned long.
Widening the producers to size_t in this way introduces a handful of
silent size_t -> unsigned long narrowings on Windows, all in
builtin/pack-objects.c, where the consumers are still typed
unsigned long. Make those narrowings explicit with
cast_size_t_to_ulong() so they assert loudly the moment an object
actually exceeds ULONG_MAX bytes:
- oe_get_size_slow() returns unsigned long but holds a size_t
locally; cast at the return.
- write_reuse_object() passes a size_t into check_pack_inflate(),
whose expect parameter is unsigned long; cast at the call.
- check_object() routes a size_t through SET_SIZE() and
SET_DELTA_SIZE(), both of which take unsigned long via
oe_set_size() / oe_set_delta_size(); cast at the three call
sites in the OBJ_OFS_DELTA / OBJ_REF_DELTA branches and in the
non-delta default arm.
The cast-only treatment is deliberately a stop-gap. Properly
widening oe_set_size, oe_get_size_slow's return type,
check_pack_inflate's expect parameter, object_info.sizep,
patch_delta, and the OE_SIZE_BITS bit-fields cascades into a series
that is too large to be reviewable, so the proper widening is
deferred to a follow-up topic. Until then,
cast_size_t_to_ulong() at least makes the truncation explicit at
the source: it documents the boundary, and on a 64-bit non-Windows
platform it is a no-op.
This was originally authored by LordKiRon <https://github.com/LordKiRon>,
who preferred not to reveal their real name and therefore agreed that I
take over authorship.
Helped-by: Torsten Bögershausen <tboegi@web.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
builtin/pack-objects.c | 34 ++++++++++++++++++++++------------
object-file.c | 10 +++++++++-
odb/streaming.c | 13 ++++++++++++-
odb/streaming.h | 2 +-
| 2 +-
pack-bitmap.c | 2 +-
pack-check.c | 6 ++++--
packfile.c | 24 +++++++++++++++---------
packfile.h | 4 ++--
9 files changed, 67 insertions(+), 30 deletions(-)
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index dd2480a73d..480cc0bd8c 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -629,14 +629,21 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,
struct packed_git *p = IN_PACK(entry);
struct pack_window *w_curs = NULL;
uint32_t pos;
- off_t offset;
+ off_t offset, cur;
enum object_type type = oe_type(entry);
+ enum object_type in_pack_type;
off_t datalen;
unsigned char header[MAX_PACK_OBJECT_HEADER],
dheader[MAX_PACK_OBJECT_HEADER];
unsigned hdrlen;
const unsigned hashsz = the_hash_algo->rawsz;
- unsigned long entry_size = SIZE(entry);
+ size_t entry_size;
+
+ cur = entry->in_pack_offset;
+ in_pack_type = unpack_object_header(p, &w_curs, &cur, &entry_size);
+ if (in_pack_type < 0)
+ die(_("write_reuse_object: unable to parse object header of %s"),
+ oid_to_hex(&entry->idx.oid));
if (DELTA(entry))
type = (allow_ofs_delta && DELTA(entry)->idx.offset) ?
@@ -664,7 +671,8 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,
datalen -= entry->in_pack_header_size;
if (!pack_to_stdout && p->index_version == 1 &&
- check_pack_inflate(p, &w_curs, offset, datalen, entry_size)) {
+ check_pack_inflate(p, &w_curs, offset, datalen,
+ cast_size_t_to_ulong(entry_size))) {
error(_("corrupt packed object for %s"),
oid_to_hex(&entry->idx.oid));
unuse_pack(&w_curs);
@@ -1087,7 +1095,7 @@ static void write_reused_pack_one(struct packed_git *reuse_packfile,
{
off_t offset, next, cur;
enum object_type type;
- unsigned long size;
+ size_t size;
offset = pack_pos_to_offset(reuse_packfile, pos);
next = pack_pos_to_offset(reuse_packfile, pos + 1);
@@ -2243,7 +2251,7 @@ static void check_object(struct object_entry *entry, uint32_t object_index)
off_t ofs;
unsigned char *buf, c;
enum object_type type;
- unsigned long in_pack_size;
+ size_t in_pack_size;
buf = use_pack(p, &w_curs, entry->in_pack_offset, &avail);
@@ -2270,7 +2278,7 @@ static void check_object(struct object_entry *entry, uint32_t object_index)
default:
/* Not a delta hence we've already got all we need. */
oe_set_type(entry, entry->in_pack_type);
- SET_SIZE(entry, in_pack_size);
+ SET_SIZE(entry, cast_size_t_to_ulong(in_pack_size));
entry->in_pack_header_size = used;
if (oe_type(entry) < OBJ_COMMIT || oe_type(entry) > OBJ_BLOB)
goto give_up;
@@ -2324,8 +2332,8 @@ static void check_object(struct object_entry *entry, uint32_t object_index)
if (have_base &&
can_reuse_delta(&base_ref, entry, &base_entry)) {
oe_set_type(entry, entry->in_pack_type);
- SET_SIZE(entry, in_pack_size); /* delta size */
- SET_DELTA_SIZE(entry, in_pack_size);
+ SET_SIZE(entry, cast_size_t_to_ulong(in_pack_size)); /* delta size */
+ SET_DELTA_SIZE(entry, cast_size_t_to_ulong(in_pack_size));
if (base_entry) {
SET_DELTA(entry, base_entry);
@@ -2734,16 +2742,18 @@ unsigned long oe_get_size_slow(struct packing_data *pack,
struct pack_window *w_curs;
unsigned char *buf;
enum object_type type;
- unsigned long used, avail, size;
+ unsigned long used, avail;
+ size_t size;
if (e->type_ != OBJ_OFS_DELTA && e->type_ != OBJ_REF_DELTA) {
+ unsigned long sz;
packing_data_lock(&to_pack);
if (odb_read_object_info(the_repository->objects,
- &e->idx.oid, &size) < 0)
+ &e->idx.oid, &sz) < 0)
die(_("unable to get size of %s"),
oid_to_hex(&e->idx.oid));
packing_data_unlock(&to_pack);
- return size;
+ return sz;
}
p = oe_in_pack(pack, e);
@@ -2760,7 +2770,7 @@ unsigned long oe_get_size_slow(struct packing_data *pack,
unuse_pack(&w_curs);
packing_data_unlock(&to_pack);
- return size;
+ return cast_size_t_to_ulong(size);
}
static int try_delta(struct unpacked *trg, struct unpacked *src,
diff --git a/object-file.c b/object-file.c
index 086b2b65ff..0be2981c7a 100644
--- a/object-file.c
+++ b/object-file.c
@@ -2326,6 +2326,7 @@ int odb_source_loose_read_object_stream(struct odb_read_stream **out,
struct object_info oi = OBJECT_INFO_INIT;
struct odb_loose_read_stream *st;
unsigned long mapsize;
+ unsigned long size_ul;
void *mapped;
mapped = odb_source_loose_map_object(source, oid, &mapsize);
@@ -2349,11 +2350,18 @@ int odb_source_loose_read_object_stream(struct odb_read_stream **out,
goto error;
}
- oi.sizep = &st->base.size;
+ /*
+ * object_info.sizep is unsigned long* (32-bit on Windows), but
+ * st->base.size is size_t (64-bit). Use temporary variable.
+ * Note: loose objects >4GB would still truncate here, but such
+ * large loose objects are uncommon (they'd normally be packed).
+ */
+ oi.sizep = &size_ul;
oi.typep = &st->base.type;
if (parse_loose_header(st->hdr, &oi) < 0 || st->base.type < 0)
goto error;
+ st->base.size = size_ul;
st->mapped = mapped;
st->mapsize = mapsize;
diff --git a/odb/streaming.c b/odb/streaming.c
index 5927a12954..af2adf5ce7 100644
--- a/odb/streaming.c
+++ b/odb/streaming.c
@@ -157,15 +157,26 @@ static int open_istream_incore(struct odb_read_stream **out,
.base.read = read_istream_incore,
};
struct odb_incore_read_stream *st;
+ unsigned long size_ul;
int ret;
oi.typep = &stream.base.type;
- oi.sizep = &stream.base.size;
+ /*
+ * object_info.sizep is unsigned long* (32-bit on Windows), but
+ * stream.base.size is size_t (64-bit). We use a temporary variable
+ * because the types are incompatible. Note: this path still truncates
+ * for >4GB objects, but large objects should use pack streaming
+ * (packfile_store_read_object_stream) which handles size_t properly.
+ * This incore fallback is only used for small objects or when pack
+ * streaming is unavailable.
+ */
+ oi.sizep = &size_ul;
oi.contentp = (void **)&stream.buf;
ret = odb_read_object_info_extended(odb, oid, &oi,
OBJECT_INFO_DIE_IF_CORRUPT);
if (ret)
return ret;
+ stream.base.size = size_ul;
CALLOC_ARRAY(st, 1);
*st = stream;
diff --git a/odb/streaming.h b/odb/streaming.h
index c7861f7e13..517e2ea2d3 100644
--- a/odb/streaming.h
+++ b/odb/streaming.h
@@ -21,7 +21,7 @@ struct odb_read_stream {
odb_read_stream_close_fn close;
odb_read_stream_read_fn read;
enum object_type type;
- unsigned long size; /* inflated size of full object */
+ size_t size; /* inflated size of full object */
};
/*
--git a/oss-fuzz/fuzz-pack-headers.c b/oss-fuzz/fuzz-pack-headers.c
index 150c0f5fa2..ef61ab577c 100644
--- a/oss-fuzz/fuzz-pack-headers.c
+++ b/oss-fuzz/fuzz-pack-headers.c
@@ -6,7 +6,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
enum object_type type;
- unsigned long len;
+ size_t len;
unpack_object_header_buffer((const unsigned char *)data,
(unsigned long)size, &type, &len);
diff --git a/pack-bitmap.c b/pack-bitmap.c
index f6ec18d83a..f9af8a96bd 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -2270,7 +2270,7 @@ static int try_partial_reuse(struct bitmap_index *bitmap_git,
{
off_t delta_obj_offset;
enum object_type type;
- unsigned long size;
+ size_t size;
if (pack_pos >= pack->p->num_objects)
return -1; /* not actually in the pack */
diff --git a/pack-check.c b/pack-check.c
index 79992bb509..2792f34d25 100644
--- a/pack-check.c
+++ b/pack-check.c
@@ -110,7 +110,7 @@ static int verify_packfile(struct repository *r,
void *data;
struct object_id oid;
enum object_type type;
- unsigned long size;
+ size_t size;
off_t curpos;
int data_valid;
@@ -143,7 +143,9 @@ static int verify_packfile(struct repository *r,
data = NULL;
data_valid = 0;
} else {
- data = unpack_entry(r, p, entries[i].offset, &type, &size);
+ unsigned long sz;
+ data = unpack_entry(r, p, entries[i].offset, &type, &sz);
+ size = sz;
data_valid = 1;
}
diff --git a/packfile.c b/packfile.c
index b012d648ad..fdae91dd11 100644
--- a/packfile.c
+++ b/packfile.c
@@ -1133,7 +1133,7 @@ out:
}
unsigned long unpack_object_header_buffer(const unsigned char *buf,
- unsigned long len, enum object_type *type, unsigned long *sizep)
+ unsigned long len, enum object_type *type, size_t *sizep)
{
unsigned shift;
size_t size, c;
@@ -1144,7 +1144,11 @@ unsigned long unpack_object_header_buffer(const unsigned char *buf,
size = c & 15;
shift = 4;
while (c & 0x80) {
- if (len <= used || (bitsizeof(long) - 7) < shift) {
+ /*
+ * Each continuation byte adds 7 bits. Ensure shift won't
+ * overflow size_t (use size_t not long for 64-bit on Windows).
+ */
+ if (len <= used || (bitsizeof(size_t) - 7) < shift) {
error("bad object header");
size = used = 0;
break;
@@ -1153,7 +1157,7 @@ unsigned long unpack_object_header_buffer(const unsigned char *buf,
size = st_add(size, st_left_shift(c & 0x7f, shift));
shift += 7;
}
- *sizep = cast_size_t_to_ulong(size);
+ *sizep = size;
return used;
}
@@ -1215,7 +1219,7 @@ unsigned long get_size_from_delta(struct packed_git *p,
int unpack_object_header(struct packed_git *p,
struct pack_window **w_curs,
off_t *curpos,
- unsigned long *sizep)
+ size_t *sizep)
{
unsigned char *base;
unsigned long left;
@@ -1367,7 +1371,7 @@ static enum object_type packed_to_object_type(struct repository *r,
while (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) {
off_t base_offset;
- unsigned long size;
+ size_t size;
/* Push the object we're going to leave behind */
if (poi_stack_nr >= poi_stack_alloc && poi_stack == small_poi_stack) {
poi_stack_alloc = alloc_nr(poi_stack_nr);
@@ -1586,7 +1590,7 @@ static int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_off
uint32_t *maybe_index_pos, struct object_info *oi)
{
struct pack_window *w_curs = NULL;
- unsigned long size;
+ size_t size;
off_t curpos = obj_offset;
enum object_type type = OBJ_NONE;
uint32_t pack_pos;
@@ -1778,7 +1782,7 @@ void *unpack_entry(struct repository *r, struct packed_git *p, off_t obj_offset,
struct pack_window *w_curs = NULL;
off_t curpos = obj_offset;
void *data = NULL;
- unsigned long size;
+ size_t size;
enum object_type type;
struct unpack_entry_stack_ent small_delta_stack[UNPACK_ENTRY_STACK_PREALLOC];
struct unpack_entry_stack_ent *delta_stack = small_delta_stack;
@@ -1943,8 +1947,10 @@ void *unpack_entry(struct repository *r, struct packed_git *p, off_t obj_offset,
(uintmax_t)curpos, p->pack_name);
data = NULL;
} else {
+ unsigned long sz;
data = patch_delta(base, base_size, delta_data,
- delta_size, &size);
+ delta_size, &sz);
+ size = sz;
/*
* We could not apply the delta; warn the user, but
@@ -2929,7 +2935,7 @@ int packfile_read_object_stream(struct odb_read_stream **out,
struct odb_packed_read_stream *stream;
struct pack_window *window = NULL;
enum object_type in_pack_type;
- unsigned long size;
+ size_t size;
in_pack_type = unpack_object_header(pack, &window, &offset, &size);
unuse_pack(&window);
diff --git a/packfile.h b/packfile.h
index 9b647da7dd..49d6bdecf6 100644
--- a/packfile.h
+++ b/packfile.h
@@ -456,9 +456,9 @@ off_t find_pack_entry_one(const struct object_id *oid, struct packed_git *);
int is_pack_valid(struct packed_git *);
void *unpack_entry(struct repository *r, struct packed_git *, off_t, enum object_type *, unsigned long *);
-unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep);
+unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, size_t *sizep);
unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t);
-int unpack_object_header(struct packed_git *, struct pack_window **, off_t *, unsigned long *);
+int unpack_object_header(struct packed_git *, struct pack_window **, off_t *, size_t *);
off_t get_delta_base(struct packed_git *p, struct pack_window **w_curs,
off_t *curpos, enum object_type type,
off_t delta_obj_offset);
--
gitgitgadget
^ permalink raw reply related
* [PATCH v3 04/11] delta, packfile: use size_t for delta header sizes
From: Johannes Schindelin via GitGitGadget @ 2026-05-08 8:16 UTC (permalink / raw)
To: git
Cc: Derrick Stolee, Torsten Bögershausen, Jeff King,
Patrick Steinhardt, Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2102.v3.git.1778228209.gitgitgadget@gmail.com>
From: Johannes Schindelin <johannes.schindelin@gmx.de>
The delta header decoding functions return unsigned long, which
truncates on Windows for objects larger than 4GB. Introduce size_t
variants get_delta_hdr_size_sz() and get_size_from_delta_sz() that
preserve the full 64-bit size, and use them in packed_object_info()
where the size is needed for streaming decisions.
This was originally authored by LordKiRon <https://github.com/LordKiRon>,
who preferred not to reveal their real name and therefore agreed that I
take over authorship.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
delta.h | 14 ++++++++++++--
packfile.c | 33 ++++++++++++++++++++++++---------
2 files changed, 36 insertions(+), 11 deletions(-)
diff --git a/delta.h b/delta.h
index 8a56ec0799..fad68cfc45 100644
--- a/delta.h
+++ b/delta.h
@@ -86,8 +86,11 @@ void *patch_delta(const void *src_buf, unsigned long src_size,
* This must be called twice on the delta data buffer, first to get the
* expected source buffer size, and again to get the target buffer size.
*/
-static inline unsigned long get_delta_hdr_size(const unsigned char **datap,
- const unsigned char *top)
+/*
+ * Size_t variant that doesn't truncate - use for >4GB objects on Windows.
+ */
+static inline size_t get_delta_hdr_size_sz(const unsigned char **datap,
+ const unsigned char *top)
{
const unsigned char *data = *datap;
size_t cmd, size = 0;
@@ -98,6 +101,13 @@ static inline unsigned long get_delta_hdr_size(const unsigned char **datap,
i += 7;
} while (cmd & 0x80 && data < top);
*datap = data;
+ return size;
+}
+
+static inline unsigned long get_delta_hdr_size(const unsigned char **datap,
+ const unsigned char *top)
+{
+ size_t size = get_delta_hdr_size_sz(datap, top);
return cast_size_t_to_ulong(size);
}
diff --git a/packfile.c b/packfile.c
index fdae91dd11..4208f53046 100644
--- a/packfile.c
+++ b/packfile.c
@@ -1161,9 +1161,12 @@ unsigned long unpack_object_header_buffer(const unsigned char *buf,
return used;
}
-unsigned long get_size_from_delta(struct packed_git *p,
- struct pack_window **w_curs,
- off_t curpos)
+/*
+ * Size_t variant for >4GB delta results on Windows.
+ */
+static size_t get_size_from_delta_sz(struct packed_git *p,
+ struct pack_window **w_curs,
+ off_t curpos)
{
const unsigned char *data;
unsigned char delta_head[20], *in;
@@ -1210,10 +1213,18 @@ unsigned long get_size_from_delta(struct packed_git *p,
data = delta_head;
/* ignore base size */
- get_delta_hdr_size(&data, delta_head+sizeof(delta_head));
+ get_delta_hdr_size_sz(&data, delta_head+sizeof(delta_head));
/* Read the result size */
- return get_delta_hdr_size(&data, delta_head+sizeof(delta_head));
+ return get_delta_hdr_size_sz(&data, delta_head+sizeof(delta_head));
+}
+
+unsigned long get_size_from_delta(struct packed_git *p,
+ struct pack_window **w_curs,
+ off_t curpos)
+{
+ size_t size = get_size_from_delta_sz(p, w_curs, curpos);
+ return cast_size_t_to_ulong(size);
}
int unpack_object_header(struct packed_git *p,
@@ -1618,14 +1629,18 @@ static int packed_object_info_with_index_pos(struct packed_git *p, off_t obj_off
ret = -1;
goto out;
}
- *oi->sizep = get_size_from_delta(p, &w_curs, tmp_pos);
- if (*oi->sizep == 0) {
+ /*
+ * Use size_t variant to avoid die() on >4GB deltas.
+ * oi->sizep is unsigned long, so truncation may occur,
+ * but streaming code uses its own size_t tracking.
+ */
+ size = get_size_from_delta_sz(p, &w_curs, tmp_pos);
+ if (size == 0) {
ret = -1;
goto out;
}
- } else {
- *oi->sizep = size;
}
+ *oi->sizep = (unsigned long)size;
}
if (oi->disk_sizep || (oi->mtimep && p->is_cruft)) {
--
gitgitgadget
^ permalink raw reply related
* [PATCH v3 05/11] test-tool: add a helper to synthesize large packfiles
From: Johannes Schindelin via GitGitGadget @ 2026-05-08 8:16 UTC (permalink / raw)
To: git
Cc: Derrick Stolee, Torsten Bögershausen, Jeff King,
Patrick Steinhardt, Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2102.v3.git.1778228209.gitgitgadget@gmail.com>
From: Johannes Schindelin <johannes.schindelin@gmx.de>
To test Git's behavior with very large pack files, we need a way to
generate such files quickly.
A naive approach using only readily-available Git commands would take
over 10 hours for a 4GB pack file, which is prohibitive.
Side-stepping Git's machinery and actual zlib compression by writing
uncompressed content with the appropriate zlib header makes things
much faster. The fastest method using this approach generates many
small, unreachable blob objects and takes about 1.5 minutes for 4GB.
However, this cannot be used because we need to test git clone, which
requires a reachable commit history.
Generating many reachable commits with small, uncompressed blobs takes
about 4 minutes for 4GB. But this approach 1) does not reproduce the
issues we want to fix (which require individual objects larger than
4GB) and 2) is comparatively slow because of the many SHA-1
calculations.
The approach taken here generates a single large blob (filled with NUL
bytes), along with the trees and commits needed to make it reachable.
This takes about 2.5 minutes for 4.5GB, which is the fastest option
that produces a valid, clonable repository with an object large enough
to trigger the bugs we want to test.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
Makefile | 1 +
compat/zlib-compat.h | 2 +
t/helper/meson.build | 1 +
t/helper/test-synthesize.c | 250 +++++++++++++++++++++++++++++++++++++
t/helper/test-tool.c | 1 +
t/helper/test-tool.h | 1 +
6 files changed, 256 insertions(+)
create mode 100644 t/helper/test-synthesize.c
diff --git a/Makefile b/Makefile
index cedc234173..85405cb5b8 100644
--- a/Makefile
+++ b/Makefile
@@ -872,6 +872,7 @@ TEST_BUILTINS_OBJS += test-submodule-config.o
TEST_BUILTINS_OBJS += test-submodule-nested-repo-config.o
TEST_BUILTINS_OBJS += test-submodule.o
TEST_BUILTINS_OBJS += test-subprocess.o
+TEST_BUILTINS_OBJS += test-synthesize.o
TEST_BUILTINS_OBJS += test-trace2.o
TEST_BUILTINS_OBJS += test-truncate.o
TEST_BUILTINS_OBJS += test-userdiff.o
diff --git a/compat/zlib-compat.h b/compat/zlib-compat.h
index ac08276622..5078c5ef6c 100644
--- a/compat/zlib-compat.h
+++ b/compat/zlib-compat.h
@@ -7,6 +7,8 @@
# define z_stream_s zng_stream_s
# define gz_header_s zng_gz_header_s
+# define adler32(adler, buf, len) zng_adler32(adler, buf, len)
+
# define crc32(crc, buf, len) zng_crc32(crc, buf, len)
# define inflate(strm, bits) zng_inflate(strm, bits)
diff --git a/t/helper/meson.build b/t/helper/meson.build
index 675e64c010..3235f10ab8 100644
--- a/t/helper/meson.build
+++ b/t/helper/meson.build
@@ -69,6 +69,7 @@ test_tool_sources = [
'test-submodule-nested-repo-config.c',
'test-submodule.c',
'test-subprocess.c',
+ 'test-synthesize.c',
'test-tool.c',
'test-trace2.c',
'test-truncate.c',
diff --git a/t/helper/test-synthesize.c b/t/helper/test-synthesize.c
new file mode 100644
index 0000000000..3ce7078078
--- /dev/null
+++ b/t/helper/test-synthesize.c
@@ -0,0 +1,250 @@
+#define USE_THE_REPOSITORY_VARIABLE
+
+#include "test-tool.h"
+#include "git-compat-util.h"
+#include "git-zlib.h"
+#include "hash.h"
+#include "hex.h"
+#include "object-file.h"
+#include "object.h"
+#include "pack.h"
+#include "parse-options.h"
+#include "parse.h"
+#include "repository.h"
+#include "setup.h"
+#include "strbuf.h"
+#include "write-or-die.h"
+
+#define BLOCK_SIZE 0xffff
+static const unsigned char zeros[BLOCK_SIZE];
+
+/*
+ * Write data as an uncompressed zlib stream.
+ * For data larger than 64KB, writes multiple uncompressed blocks.
+ * If data is NULL, writes zeros.
+ * Updates the pack checksum context.
+ */
+static void write_uncompressed_zlib(FILE *f, struct git_hash_ctx *pack_ctx,
+ const void *data, size_t len,
+ const struct git_hash_algo *algo)
+{
+ unsigned char zlib_header[2] = { 0x78, 0x01 }; /* CMF, FLG */
+ unsigned char block_header[5];
+ const unsigned char *p = data;
+ size_t remaining = len;
+ uint32_t adler = 1L; /* adler32 initial value */
+ unsigned char adler_buf[4];
+
+ /* Write zlib header */
+ fwrite_or_die(f, zlib_header, sizeof(zlib_header));
+ algo->update_fn(pack_ctx, zlib_header, 2);
+
+ /* Write uncompressed blocks (max 64KB each) */
+ do {
+ size_t block_len = remaining > BLOCK_SIZE ? BLOCK_SIZE : remaining;
+ int is_final = (block_len == remaining);
+ const unsigned char *block_data = data ? p : zeros;
+
+ block_header[0] = is_final ? 0x01 : 0x00;
+ block_header[1] = block_len & 0xff;
+ block_header[2] = (block_len >> 8) & 0xff;
+ block_header[3] = block_header[1] ^ 0xff;
+ block_header[4] = block_header[2] ^ 0xff;
+
+ fwrite_or_die(f, block_header, sizeof(block_header));
+ algo->update_fn(pack_ctx, block_header, 5);
+
+ if (block_len) {
+ fwrite_or_die(f, block_data, block_len);
+ algo->update_fn(pack_ctx, block_data, block_len);
+ adler = adler32(adler, block_data, block_len);
+ }
+
+ if (data)
+ p += block_len;
+ remaining -= block_len;
+ } while (remaining > 0);
+
+ /* Write adler32 checksum */
+ put_be32(adler_buf, adler);
+ fwrite_or_die(f, adler_buf, sizeof(adler_buf));
+ algo->update_fn(pack_ctx, adler_buf, 4);
+}
+
+/*
+ * Write an uncompressed object to the pack file.
+ * If `data == NULL`, it is treated like a buffer to NUL bytes.
+ * Updates the pack checksum context.
+ */
+static void write_pack_object(FILE *f, struct git_hash_ctx *pack_ctx,
+ enum object_type type,
+ const void *data, size_t len,
+ struct object_id *oid,
+ const struct git_hash_algo *algo)
+{
+ unsigned char pack_header[MAX_PACK_OBJECT_HEADER];
+ char object_header[32];
+ int pack_header_len, object_header_len;
+ struct git_hash_ctx ctx;
+
+ /* Write pack object header */
+ pack_header_len = encode_in_pack_object_header(pack_header,
+ sizeof(pack_header),
+ type, len);
+ fwrite_or_die(f, pack_header, pack_header_len);
+ algo->update_fn(pack_ctx, pack_header, pack_header_len);
+
+ /* Write the data as uncompressed zlib */
+ write_uncompressed_zlib(f, pack_ctx, data, len, algo);
+
+ algo->init_fn(&ctx);
+ object_header_len = format_object_header(object_header,
+ sizeof(object_header),
+ type, len);
+ algo->update_fn(&ctx, object_header, object_header_len);
+ if (data)
+ algo->update_fn(&ctx, data, len);
+ else {
+ for (size_t i = len / BLOCK_SIZE; i; i--)
+ algo->update_fn(&ctx, zeros, BLOCK_SIZE);
+ algo->update_fn(&ctx, zeros, len % BLOCK_SIZE);
+ }
+ algo->final_oid_fn(oid, &ctx);
+}
+
+/*
+ * Generate a pack file with a single large (>4GB) reachable object.
+ *
+ * Creates:
+ * 1. A large blob (all NUL bytes)
+ * 2. A tree containing that blob as "file"
+ * 3. A commit using that tree
+ * 4. The empty tree
+ * 5. A child commit using the empty tree
+ *
+ * This is useful for testing that Git can handle objects larger than 4GB.
+ */
+static int generate_pack_with_large_object(const char *path, size_t blob_size,
+ const struct git_hash_algo *algo)
+{
+ FILE *f = xfopen(path, "wb");
+ struct git_hash_ctx pack_ctx;
+ unsigned char pack_hash[GIT_MAX_RAWSZ];
+ struct object_id blob_oid, tree_oid, commit_oid, empty_tree_oid, final_commit_oid;
+ struct strbuf buf = STRBUF_INIT;
+ const uint32_t object_count = 5;
+ struct pack_header pack_header = {
+ .hdr_signature = htonl(PACK_SIGNATURE),
+ .hdr_version = htonl(PACK_VERSION),
+ .hdr_entries = htonl(object_count),
+ };
+
+ algo->init_fn(&pack_ctx);
+
+ /* Write pack header */
+ fwrite_or_die(f, &pack_header, sizeof(pack_header));
+ algo->update_fn(&pack_ctx, &pack_header, sizeof(pack_header));
+
+ /* 1. Write the large blob */
+ write_pack_object(f, &pack_ctx, OBJ_BLOB, NULL, blob_size, &blob_oid, algo);
+
+ /* 2. Write tree containing the blob as "file" */
+ strbuf_addf(&buf, "100644 file%c", '\0');
+ strbuf_add(&buf, blob_oid.hash, algo->rawsz);
+ write_pack_object(f, &pack_ctx, OBJ_TREE, buf.buf, buf.len, &tree_oid, algo);
+
+ /* 3. Write commit using that tree */
+ strbuf_reset(&buf);
+ strbuf_addf(&buf,
+ "tree %s\n"
+ "author A U Thor <author@example.com> 1234567890 +0000\n"
+ "committer C O Mitter <committer@example.com> 1234567890 +0000\n"
+ "\n"
+ "Large blob commit\n",
+ oid_to_hex(&tree_oid));
+ write_pack_object(f, &pack_ctx, OBJ_COMMIT, buf.buf, buf.len, &commit_oid, algo);
+
+ /* 4. Write the empty tree */
+ write_pack_object(f, &pack_ctx, OBJ_TREE, "", 0, &empty_tree_oid, algo);
+
+ /* 5. Write final commit using empty tree, with previous commit as parent */
+ strbuf_reset(&buf);
+ strbuf_addf(&buf,
+ "tree %s\n"
+ "parent %s\n"
+ "author A U Thor <author@example.com> 1234567890 +0000\n"
+ "committer C O Mitter <committer@example.com> 1234567890 +0000\n"
+ "\n"
+ "Empty tree commit\n",
+ oid_to_hex(&empty_tree_oid),
+ oid_to_hex(&commit_oid));
+ write_pack_object(f, &pack_ctx, OBJ_COMMIT, buf.buf, buf.len, &final_commit_oid, algo);
+
+ /* Write pack trailer (checksum) */
+ algo->final_fn(pack_hash, &pack_ctx);
+ fwrite_or_die(f, pack_hash, algo->rawsz);
+ if (fclose(f))
+ die_errno(_("could not close '%s'"), path);
+
+ strbuf_release(&buf);
+
+ /* Print the final commit OID so caller can set up refs */
+ printf("%s\n", oid_to_hex(&final_commit_oid));
+
+ return 0;
+}
+
+static int cmd__synthesize__pack(int argc, const char **argv,
+ const char *prefix UNUSED,
+ struct repository *repo)
+{
+ int non_git;
+ int reachable_large = 0;
+ const struct git_hash_algo *algo;
+ size_t blob_size;
+ uintmax_t blob_size_u;
+ const char *path;
+ const char * const usage[] = {
+ "test-tool synthesize pack "
+ "--reachable-large <blob-size> <filename>",
+ NULL
+ };
+ struct option options[] = {
+ OPT_BOOL(0, "reachable-large", &reachable_large,
+ N_("write a pack with a single reachable large blob")),
+ OPT_END()
+ };
+
+ setup_git_directory_gently(&non_git);
+ repo = the_repository;
+ algo = repo->hash_algo;
+
+ argc = parse_options(argc, argv, NULL, options, usage,
+ PARSE_OPT_KEEP_ARGV0);
+ if (argc != 3 || !reachable_large)
+ usage_with_options(usage, options);
+
+ if (!git_parse_unsigned(argv[1], &blob_size_u,
+ maximum_unsigned_value_of_type(size_t)))
+ die(_("'%s' is not a valid blob size"), argv[1]);
+ blob_size = blob_size_u;
+ path = argv[2];
+
+ return !!generate_pack_with_large_object(path, blob_size, algo);
+}
+
+int cmd__synthesize(int argc, const char **argv)
+{
+ const char *prefix = NULL;
+ char const * const synthesize_usage[] = {
+ "test-tool synthesize pack <options>",
+ NULL,
+ };
+ parse_opt_subcommand_fn *fn = NULL;
+ struct option options[] = {
+ OPT_SUBCOMMAND("pack", &fn, cmd__synthesize__pack),
+ OPT_END()
+ };
+ argc = parse_options(argc, argv, prefix, options, synthesize_usage, 0);
+ return !!fn(argc, argv, prefix, NULL);
+}
diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
index a7abc618b3..b71a22b43b 100644
--- a/t/helper/test-tool.c
+++ b/t/helper/test-tool.c
@@ -82,6 +82,7 @@ static struct test_cmd cmds[] = {
{ "submodule-config", cmd__submodule_config },
{ "submodule-nested-repo-config", cmd__submodule_nested_repo_config },
{ "subprocess", cmd__subprocess },
+ { "synthesize", cmd__synthesize },
{ "trace2", cmd__trace2 },
{ "truncate", cmd__truncate },
{ "userdiff", cmd__userdiff },
diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
index 7f150fa1eb..f2885b33d5 100644
--- a/t/helper/test-tool.h
+++ b/t/helper/test-tool.h
@@ -75,6 +75,7 @@ int cmd__submodule(int argc, const char **argv);
int cmd__submodule_config(int argc, const char **argv);
int cmd__submodule_nested_repo_config(int argc, const char **argv);
int cmd__subprocess(int argc, const char **argv);
+int cmd__synthesize(int argc, const char **argv);
int cmd__trace2(int argc, const char **argv);
int cmd__truncate(int argc, const char **argv);
int cmd__userdiff(int argc, const char **argv);
--
gitgitgadget
^ permalink raw reply related
* [PATCH v3 06/11] t5608: add regression test for >4GB object clone
From: Johannes Schindelin via GitGitGadget @ 2026-05-08 8:16 UTC (permalink / raw)
To: git
Cc: Derrick Stolee, Torsten Bögershausen, Jeff King,
Patrick Steinhardt, Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2102.v3.git.1778228209.gitgitgadget@gmail.com>
From: Johannes Schindelin <johannes.schindelin@gmx.de>
The shift overflow bug in index-pack and unpack-objects caused incorrect
object size calculation when the encoded size required more than 32 bits
of shift. This would result in corrupted or failed unpacking of objects
larger than 4GB.
Add a test that creates a pack file containing a 4GB+ blob using the
new 'test-tool synthesize pack --reachable-large' command, then clones
the repository to verify the fix works correctly.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
t/t5608-clone-2gb.sh | 37 +++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/t/t5608-clone-2gb.sh b/t/t5608-clone-2gb.sh
index 87a8cd9f98..af93302dde 100755
--- a/t/t5608-clone-2gb.sh
+++ b/t/t5608-clone-2gb.sh
@@ -49,4 +49,41 @@ test_expect_success 'clone - with worktree, file:// protocol' '
'
+test_expect_success SIZE_T_IS_64BIT 'set up repo with >4GB object' '
+ large_blob_size=$((4*1024*1024*1024+1)) &&
+ git init --bare 4gb-repo &&
+ head_oid=$(test-tool synthesize pack \
+ --reachable-large "$large_blob_size" \
+ 4gb-repo/objects/pack/test.pack) &&
+ git -C 4gb-repo index-pack objects/pack/test.pack &&
+ git -C 4gb-repo update-ref refs/heads/main $head_oid &&
+ git -C 4gb-repo symbolic-ref HEAD refs/heads/main
+'
+
+test_expect_success SIZE_T_IS_64BIT 'clone >4GB object via unpack-objects' '
+ # The synthesized pack has five objects, so a large unpack limit keeps
+ # fetch-pack on the unpack-objects path.
+ git -c fetch.unpackLimit=100 clone --bare \
+ "file://$(pwd)/4gb-repo" 4gb-clone-unpack &&
+
+ # Verify the large blob survived the clone by comparing its OID
+ # between source and clone. We cannot use "cat-file -s" because
+ # object_info.sizep is still unsigned long, which truncates >4GB
+ # sizes on Windows. OID equality proves content integrity since
+ # the clone already verified checksums via index-pack/unpack-objects.
+ source_blob=$(git -C 4gb-repo rev-parse main^:file) &&
+ clone_blob=$(git -C 4gb-clone-unpack rev-parse main^:file) &&
+ test "$source_blob" = "$clone_blob"
+'
+
+test_expect_success SIZE_T_IS_64BIT 'clone with >4GB object via index-pack' '
+ # Force fetch-pack to hand the pack to index-pack instead.
+ git -c fetch.unpackLimit=1 clone --bare \
+ "file://$(pwd)/4gb-repo" 4gb-clone-index &&
+
+ source_blob=$(git -C 4gb-repo rev-parse main^:file) &&
+ clone_blob=$(git -C 4gb-clone-index rev-parse main^:file) &&
+ test "$source_blob" = "$clone_blob"
+'
+
test_done
--
gitgitgadget
^ permalink raw reply related
* [PATCH v3 07/11] test-tool synthesize: use the unsafe hash for speed
From: Johannes Schindelin via GitGitGadget @ 2026-05-08 8:16 UTC (permalink / raw)
To: git
Cc: Derrick Stolee, Torsten Bögershausen, Jeff King,
Patrick Steinhardt, Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2102.v3.git.1778228209.gitgitgadget@gmail.com>
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Jeff King pointed out on the mailing list [1] that t5608's new >4GB
test cases dominate the entire test suite runtime: 160 seconds on his
laptop when the rest of the suite finishes in under 90 seconds, and
305-850 seconds across CI jobs. The bottleneck is that the synthesize
helper hashes roughly 8 GB of data through SHA-1 (4 GB for the pack
checksum plus 4 GB for the blob OID) for a 4 GB+1 blob.
Since the helper generates known test data, collision detection is
unnecessary. Switch from repo->hash_algo to unsafe_hash_algo(), which
uses hardware-accelerated SHA-1 (via OpenSSL or Apple CommonCrypto)
when available.
Benchmarks on an x86_64 machine generating a 4 GB+1 pack (2 runs
each, interleaved):
SHA-1 backend Run 1 Run 2
SHA1DC (safe) 75s 80s
OpenSSL (unsafe) 21s 19s
The effect scales linearly. At 64 MB with 10 randomized interleaved
runs, the OpenSSL unsafe backend shows a 5.4x improvement (median
0.202s vs 1.088s) with tight variance (stdev 0.028s vs 0.095s).
The speedup is only realized when the build has a fast unsafe backend
compiled in. The CI's linux-TEST-vars job already sets
OPENSSL_SHA1_UNSAFE=YesPlease; macOS benefits from Apple CommonCrypto
when configured. On builds without a separate unsafe backend (such as
the default Windows builds), unsafe_hash_algo() returns the regular
collision-detecting implementation and the change is a no-op.
[1] https://lore.kernel.org/git/20260501063805.GA2038915@coredump.intra.peff.net/
Assisted-by: Claude Opus 4.6
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
t/helper/test-synthesize.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/t/helper/test-synthesize.c b/t/helper/test-synthesize.c
index 3ce7078078..e2faaad7b4 100644
--- a/t/helper/test-synthesize.c
+++ b/t/helper/test-synthesize.c
@@ -217,7 +217,7 @@ static int cmd__synthesize__pack(int argc, const char **argv,
setup_git_directory_gently(&non_git);
repo = the_repository;
- algo = repo->hash_algo;
+ algo = unsafe_hash_algo(repo->hash_algo);
argc = parse_options(argc, argv, NULL, options, usage,
PARSE_OPT_KEEP_ARGV0);
--
gitgitgadget
^ permalink raw reply related
* [PATCH v3 08/11] test-tool synthesize: precompute pack for 4 GiB + 1
From: Johannes Schindelin via GitGitGadget @ 2026-05-08 8:16 UTC (permalink / raw)
To: git
Cc: Derrick Stolee, Torsten Bögershausen, Jeff King,
Patrick Steinhardt, Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2102.v3.git.1778228209.gitgitgadget@gmail.com>
From: Johannes Schindelin <johannes.schindelin@gmx.de>
The synthesize helper hashes roughly 8 GiB of data through SHA-1 to
produce a 4 GiB + 1 pack (4 GiB for the pack checksum, 4 GiB for
the blob OID). Since the blob content is all NUL bytes, every byte
in the resulting pack file is deterministic for a given blob size and
hash algorithm.
Add a fast path that writes the pack from precomputed constants:
a 25-byte prefix (pack header, object header, zlib header, first
block header), the zero-filled bulk with periodic 5-byte deflate
block headers, and a 513-byte suffix (tree, two commits, empty tree,
pack SHA-1 checksum). This eliminates all SHA-1 and adler32
computation, making the helper purely I/O-bound.
The precomputed constants are stored in a struct fast_pack array
keyed by hash algorithm format_id, so that adding SHA-256 support
later requires only adding another array entry with its suffix.
The constants were generated by running the generic path and
extracting the non-zero bytes from the resulting pack file.
Benchmarks generating a 4 GiB + 1 pack (3 runs each, SHA1DC on
x86_64):
generic path: 88s / 81s / 140s
fast path: 14s / 13s / 15s
On CI, where t5608 currently takes 200-850 seconds depending on the
job, the fast path cuts the pack-generation phase from minutes to
seconds, leaving only the clone operations themselves.
Assisted-by: Claude Opus 4.6
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
t/helper/test-synthesize.c | 202 ++++++++++++++++++++++++++++++++++++-
1 file changed, 201 insertions(+), 1 deletion(-)
diff --git a/t/helper/test-synthesize.c b/t/helper/test-synthesize.c
index e2faaad7b4..83c40ee02a 100644
--- a/t/helper/test-synthesize.c
+++ b/t/helper/test-synthesize.c
@@ -112,6 +112,201 @@ static void write_pack_object(FILE *f, struct git_hash_ctx *pack_ctx,
algo->final_oid_fn(oid, &ctx);
}
+/*
+ * Fast path: precomputed pack data for a 4 GiB + 1 all-NUL blob.
+ *
+ * The generated pack is almost entirely zeros with a small constant
+ * prefix, periodic deflate block headers, and a constant suffix
+ * containing the tree, two commits, and the pack checksum. Because
+ * every byte is deterministic for a given blob size and hash algorithm,
+ * we can write the pack without computing any hashes at all, reducing
+ * runtime from minutes of hash computation to seconds of pure I/O.
+ *
+ * The blob is stored as an uncompressed deflate stream: a two-byte
+ * zlib header, then 65538 blocks of up to 0xffff bytes each, followed
+ * by an adler32 checksum. The pack header and deflate framing are
+ * shared across hash algorithms; only the suffix (which contains OIDs
+ * and the pack checksum) differs.
+ *
+ * Constants were generated by running the generic path and extracting
+ * the non-zero bytes from the resulting pack file.
+ */
+
+#define FAST_PACK_4G1_BLOB_SIZE ((size_t)4 * 1024 * 1024 * 1024 + 1)
+#define FAST_PACK_4G1_N_FULL_BLOCKS 65537
+
+/*
+ * Per-hash-algorithm constants for the fast path. The prefix and
+ * deflate block structure are identical across algorithms; only the
+ * suffix (tree, commits, pack checksum) and the commit OID differ.
+ */
+struct fast_pack {
+ uint32_t format_id;
+ const unsigned char *suffix;
+ size_t suffix_len;
+ const char *commit_oid;
+};
+
+/* Pack header + pack object header + zlib header + first block header */
+static const unsigned char fast_pack_prefix[] = {
+ /* PACK header: signature, version 2, 5 objects */
+ 0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x05,
+ /* pack object header: blob, size = 4294967297 */
+ 0xb1, 0x80, 0x80, 0x80, 0x80, 0x01,
+ /* zlib header: CMF=0x78, FLG=0x01 */
+ 0x78, 0x01,
+ /* first non-final block header: BFINAL=0, LEN=0xffff, NLEN=0x0000 */
+ 0x00, 0xff, 0xff, 0x00, 0x00
+};
+
+/* Every non-final deflate block header is identical */
+static const unsigned char fast_pack_block_header[] = {
+ 0x00, 0xff, 0xff, 0x00, 0x00
+};
+
+/* Final block (2 data bytes) + adler32 of 4294967297 NUL bytes */
+static const unsigned char fast_pack_final_block[] = {
+ /* BFINAL=1, LEN=2, NLEN=0xfffd */
+ 0x01, 0x02, 0x00, 0xfd, 0xff,
+ /* 2 NUL data bytes */
+ 0x00, 0x00,
+ /* adler32 */
+ 0x00, 0xe2, 0x00, 0x01
+};
+
+/*
+ * SHA-1 suffix: tree, commit, empty tree, final commit, pack checksum.
+ */
+static const unsigned char fast_pack_sha1_suffix[] = {
+ 0xa0, 0x02, 0x78, 0x01, 0x01, 0x20, 0x00, 0xdf,
+ 0xff, 0x31, 0x30, 0x30, 0x36, 0x34, 0x34, 0x20,
+ 0x66, 0x69, 0x6c, 0x65, 0x00, 0x3e, 0xb7, 0xfe,
+ 0xb1, 0x41, 0x3c, 0x75, 0x7f, 0x0d, 0x81, 0x81,
+ 0xde, 0xb2, 0x8d, 0x1d, 0xab, 0x03, 0xd6, 0x48,
+ 0x46, 0xb4, 0xb4, 0x0c, 0x60, 0x95, 0x0b, 0x78,
+ 0x01, 0x01, 0xb5, 0x00, 0x4a, 0xff, 0x74, 0x72,
+ 0x65, 0x65, 0x20, 0x63, 0x36, 0x38, 0x33, 0x66,
+ 0x63, 0x63, 0x37, 0x64, 0x31, 0x64, 0x38, 0x33,
+ 0x65, 0x66, 0x32, 0x66, 0x65, 0x31, 0x61, 0x66,
+ 0x35, 0x35, 0x32, 0x31, 0x35, 0x64, 0x30, 0x31,
+ 0x36, 0x38, 0x64, 0x62, 0x35, 0x32, 0x61, 0x33,
+ 0x61, 0x33, 0x62, 0x0a, 0x61, 0x75, 0x74, 0x68,
+ 0x6f, 0x72, 0x20, 0x41, 0x20, 0x55, 0x20, 0x54,
+ 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61, 0x75, 0x74,
+ 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d,
+ 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e,
+ 0x20, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x30, 0x20, 0x2b, 0x30, 0x30, 0x30,
+ 0x30, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74,
+ 0x74, 0x65, 0x72, 0x20, 0x43, 0x20, 0x4f, 0x20,
+ 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x20, 0x3c,
+ 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65,
+ 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c,
+ 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e, 0x20, 0x31,
+ 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+ 0x30, 0x20, 0x2b, 0x30, 0x30, 0x30, 0x30, 0x0a,
+ 0x0a, 0x4c, 0x61, 0x72, 0x67, 0x65, 0x20, 0x62,
+ 0x6c, 0x6f, 0x62, 0x20, 0x63, 0x6f, 0x6d, 0x6d,
+ 0x69, 0x74, 0x0a, 0xc6, 0x55, 0x37, 0x6b, 0x20,
+ 0x78, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x00,
+ 0x00, 0x00, 0x01, 0x95, 0x0e, 0x78, 0x01, 0x01,
+ 0xe5, 0x00, 0x1a, 0xff, 0x74, 0x72, 0x65, 0x65,
+ 0x20, 0x34, 0x62, 0x38, 0x32, 0x35, 0x64, 0x63,
+ 0x36, 0x34, 0x32, 0x63, 0x62, 0x36, 0x65, 0x62,
+ 0x39, 0x61, 0x30, 0x36, 0x30, 0x65, 0x35, 0x34,
+ 0x62, 0x66, 0x38, 0x64, 0x36, 0x39, 0x32, 0x38,
+ 0x38, 0x66, 0x62, 0x65, 0x65, 0x34, 0x39, 0x30,
+ 0x34, 0x0a, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74,
+ 0x20, 0x63, 0x35, 0x62, 0x32, 0x31, 0x63, 0x36,
+ 0x31, 0x31, 0x61, 0x61, 0x35, 0x39, 0x34, 0x65,
+ 0x63, 0x39, 0x66, 0x64, 0x37, 0x65, 0x39, 0x32,
+ 0x63, 0x66, 0x39, 0x36, 0x34, 0x38, 0x39, 0x31,
+ 0x34, 0x63, 0x61, 0x34, 0x63, 0x32, 0x34, 0x31,
+ 0x32, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72,
+ 0x20, 0x41, 0x20, 0x55, 0x20, 0x54, 0x68, 0x6f,
+ 0x72, 0x20, 0x3c, 0x61, 0x75, 0x74, 0x68, 0x6f,
+ 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c,
+ 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e, 0x20, 0x31,
+ 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+ 0x30, 0x20, 0x2b, 0x30, 0x30, 0x30, 0x30, 0x0a,
+ 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65,
+ 0x72, 0x20, 0x43, 0x20, 0x4f, 0x20, 0x4d, 0x69,
+ 0x74, 0x74, 0x65, 0x72, 0x20, 0x3c, 0x63, 0x6f,
+ 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x40,
+ 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x3e, 0x20, 0x31, 0x32, 0x33,
+ 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x20,
+ 0x2b, 0x30, 0x30, 0x30, 0x30, 0x0a, 0x0a, 0x45,
+ 0x6d, 0x70, 0x74, 0x79, 0x20, 0x74, 0x72, 0x65,
+ 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74,
+ 0x0a, 0xaa, 0xb8, 0x45, 0x01, 0x8e, 0xfc, 0xf0,
+ 0x2f, 0x9c, 0xc5, 0xcc, 0x4f, 0x6a, 0x1a, 0xc9,
+ 0x2b, 0x23, 0xa9, 0xff, 0x91, 0x06, 0xc2, 0x70,
+ 0xe3
+};
+
+static const struct fast_pack fast_packs[] = {
+ {
+ .format_id = GIT_SHA1_FORMAT_ID,
+ .suffix = fast_pack_sha1_suffix,
+ .suffix_len = sizeof(fast_pack_sha1_suffix),
+ .commit_oid = "aac43daf40d0377af31aa9c798a4ae8a31b55c1d",
+ },
+};
+
+/*
+ * Try the fast path for known blob sizes. Returns 1 if the pack was
+ * written from precomputed constants, 0 if the caller should fall
+ * through to the generic path.
+ */
+static int generate_fast_pack(const char *path, size_t blob_size,
+ const struct git_hash_algo *algo)
+{
+ const struct fast_pack *fp = NULL;
+ FILE *f;
+ size_t i;
+
+ if (blob_size != FAST_PACK_4G1_BLOB_SIZE)
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(fast_packs); i++) {
+ if (fast_packs[i].format_id == algo->format_id) {
+ fp = &fast_packs[i];
+ break;
+ }
+ }
+ if (!fp)
+ return 0;
+
+ f = xfopen(path, "wb");
+
+ fwrite_or_die(f, fast_pack_prefix, sizeof(fast_pack_prefix));
+
+ /* First full block: 0xffff zero bytes (header already in prefix) */
+ fwrite_or_die(f, zeros, BLOCK_SIZE);
+
+ /* Remaining non-final full blocks */
+ for (i = 1; i < FAST_PACK_4G1_N_FULL_BLOCKS; i++) {
+ fwrite_or_die(f, fast_pack_block_header,
+ sizeof(fast_pack_block_header));
+ fwrite_or_die(f, zeros, BLOCK_SIZE);
+ }
+
+ /* Final block (2 data bytes) + adler32 */
+ fwrite_or_die(f, fast_pack_final_block,
+ sizeof(fast_pack_final_block));
+
+ /* Tree, commits, and pack checksum */
+ fwrite_or_die(f, fp->suffix, fp->suffix_len);
+
+ if (fclose(f))
+ die_errno(_("could not close '%s'"), path);
+
+ printf("%s\n", fp->commit_oid);
+ return 1;
+}
+
/*
* Generate a pack file with a single large (>4GB) reachable object.
*
@@ -127,7 +322,7 @@ static void write_pack_object(FILE *f, struct git_hash_ctx *pack_ctx,
static int generate_pack_with_large_object(const char *path, size_t blob_size,
const struct git_hash_algo *algo)
{
- FILE *f = xfopen(path, "wb");
+ FILE *f;
struct git_hash_ctx pack_ctx;
unsigned char pack_hash[GIT_MAX_RAWSZ];
struct object_id blob_oid, tree_oid, commit_oid, empty_tree_oid, final_commit_oid;
@@ -139,6 +334,11 @@ static int generate_pack_with_large_object(const char *path, size_t blob_size,
.hdr_entries = htonl(object_count),
};
+ if (generate_fast_pack(path, blob_size, algo))
+ return 0;
+
+ f = xfopen(path, "wb");
+
algo->init_fn(&pack_ctx);
/* Write pack header */
--
gitgitgadget
^ permalink raw reply related
* [PATCH v3 09/11] test-tool synthesize: add precomputed SHA-256 pack for 4 GiB + 1
From: Johannes Schindelin via GitGitGadget @ 2026-05-08 8:16 UTC (permalink / raw)
To: git
Cc: Derrick Stolee, Torsten Bögershausen, Jeff King,
Patrick Steinhardt, Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2102.v3.git.1778228209.gitgitgadget@gmail.com>
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Add a SHA-256 entry to the fast_packs[] table. The pack prefix and
deflate block structure are identical to SHA-1 (the pack format does
not encode the hash algorithm in its header). Only the suffix differs:
SHA-256 OIDs are 32 bytes instead of 20, giving a 609-byte suffix
compared to 513 for SHA-1, and a different pack checksum.
The constants were generated by running the generic path inside a
repository initialized with --object-format=sha256.
Assisted-by: Claude Opus 4.6
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
t/helper/test-synthesize.c | 91 ++++++++++++++++++++++++++++++++++++++
1 file changed, 91 insertions(+)
diff --git a/t/helper/test-synthesize.c b/t/helper/test-synthesize.c
index 83c40ee02a..1f28ecf0f2 100644
--- a/t/helper/test-synthesize.c
+++ b/t/helper/test-synthesize.c
@@ -246,6 +246,90 @@ static const unsigned char fast_pack_sha1_suffix[] = {
0xe3
};
+/*
+ * SHA-256 suffix: same structure, but with 32-byte OIDs and SHA-256
+ * pack checksum (609 bytes vs 513 for SHA-1).
+ */
+static const unsigned char fast_pack_sha256_suffix[] = {
+ 0xac, 0x02, 0x78, 0x01, 0x01, 0x2c, 0x00, 0xd3,
+ 0xff, 0x31, 0x30, 0x30, 0x36, 0x34, 0x34, 0x20,
+ 0x66, 0x69, 0x6c, 0x65, 0x00, 0x42, 0x53, 0xc1,
+ 0x8a, 0x9f, 0x5e, 0xc3, 0xbb, 0x47, 0xb0, 0x83,
+ 0x8a, 0x19, 0xdb, 0x31, 0xbb, 0x7b, 0x0f, 0x3b,
+ 0x80, 0xa4, 0xbc, 0x2f, 0xaf, 0x72, 0x6b, 0xdb,
+ 0x62, 0xaa, 0xba, 0xdd, 0xde, 0x77, 0xc6, 0x13,
+ 0xeb, 0x9d, 0x0c, 0x78, 0x01, 0x01, 0xcd, 0x00,
+ 0x32, 0xff, 0x74, 0x72, 0x65, 0x65, 0x20, 0x62,
+ 0x36, 0x30, 0x39, 0x37, 0x37, 0x64, 0x37, 0x63,
+ 0x34, 0x63, 0x32, 0x64, 0x31, 0x65, 0x63, 0x63,
+ 0x33, 0x66, 0x62, 0x61, 0x31, 0x64, 0x39, 0x38,
+ 0x65, 0x65, 0x31, 0x32, 0x30, 0x61, 0x64, 0x63,
+ 0x32, 0x34, 0x38, 0x33, 0x34, 0x39, 0x35, 0x30,
+ 0x62, 0x65, 0x34, 0x31, 0x32, 0x64, 0x39, 0x34,
+ 0x63, 0x38, 0x30, 0x39, 0x34, 0x38, 0x30, 0x66,
+ 0x35, 0x38, 0x62, 0x61, 0x39, 0x64, 0x61, 0x0a,
+ 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x20, 0x41,
+ 0x20, 0x55, 0x20, 0x54, 0x68, 0x6f, 0x72, 0x20,
+ 0x3c, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x40,
+ 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x3e, 0x20, 0x31, 0x32, 0x33,
+ 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x20,
+ 0x2b, 0x30, 0x30, 0x30, 0x30, 0x0a, 0x63, 0x6f,
+ 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x72, 0x20,
+ 0x43, 0x20, 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74,
+ 0x65, 0x72, 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d,
+ 0x69, 0x74, 0x74, 0x65, 0x72, 0x40, 0x65, 0x78,
+ 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x3e, 0x20, 0x31, 0x32, 0x33, 0x34, 0x35,
+ 0x36, 0x37, 0x38, 0x39, 0x30, 0x20, 0x2b, 0x30,
+ 0x30, 0x30, 0x30, 0x0a, 0x0a, 0x4c, 0x61, 0x72,
+ 0x67, 0x65, 0x20, 0x62, 0x6c, 0x6f, 0x62, 0x20,
+ 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x0a, 0xb7,
+ 0x80, 0x3d, 0xd7, 0x20, 0x78, 0x01, 0x01, 0x00,
+ 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x95,
+ 0x11, 0x78, 0x01, 0x01, 0x15, 0x01, 0xea, 0xfe,
+ 0x74, 0x72, 0x65, 0x65, 0x20, 0x36, 0x65, 0x66,
+ 0x31, 0x39, 0x62, 0x34, 0x31, 0x32, 0x32, 0x35,
+ 0x63, 0x35, 0x33, 0x36, 0x39, 0x66, 0x31, 0x63,
+ 0x31, 0x30, 0x34, 0x64, 0x34, 0x35, 0x64, 0x38,
+ 0x64, 0x38, 0x35, 0x65, 0x66, 0x61, 0x39, 0x62,
+ 0x30, 0x35, 0x37, 0x62, 0x35, 0x33, 0x62, 0x31,
+ 0x34, 0x62, 0x34, 0x62, 0x39, 0x62, 0x39, 0x33,
+ 0x39, 0x64, 0x64, 0x37, 0x34, 0x64, 0x65, 0x63,
+ 0x63, 0x35, 0x33, 0x32, 0x31, 0x0a, 0x70, 0x61,
+ 0x72, 0x65, 0x6e, 0x74, 0x20, 0x37, 0x35, 0x62,
+ 0x66, 0x30, 0x63, 0x34, 0x37, 0x61, 0x65, 0x34,
+ 0x62, 0x62, 0x33, 0x30, 0x38, 0x65, 0x37, 0x63,
+ 0x63, 0x32, 0x34, 0x38, 0x32, 0x65, 0x32, 0x32,
+ 0x65, 0x66, 0x61, 0x65, 0x33, 0x37, 0x38, 0x37,
+ 0x61, 0x39, 0x36, 0x38, 0x34, 0x38, 0x62, 0x64,
+ 0x31, 0x37, 0x34, 0x39, 0x35, 0x36, 0x37, 0x31,
+ 0x34, 0x37, 0x31, 0x35, 0x32, 0x34, 0x36, 0x64,
+ 0x64, 0x62, 0x64, 0x35, 0x34, 0x0a, 0x61, 0x75,
+ 0x74, 0x68, 0x6f, 0x72, 0x20, 0x41, 0x20, 0x55,
+ 0x20, 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61,
+ 0x75, 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78,
+ 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x3e, 0x20, 0x31, 0x32, 0x33, 0x34, 0x35,
+ 0x36, 0x37, 0x38, 0x39, 0x30, 0x20, 0x2b, 0x30,
+ 0x30, 0x30, 0x30, 0x0a, 0x63, 0x6f, 0x6d, 0x6d,
+ 0x69, 0x74, 0x74, 0x65, 0x72, 0x20, 0x43, 0x20,
+ 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72,
+ 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74,
+ 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d,
+ 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e,
+ 0x20, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x30, 0x20, 0x2b, 0x30, 0x30, 0x30,
+ 0x30, 0x0a, 0x0a, 0x45, 0x6d, 0x70, 0x74, 0x79,
+ 0x20, 0x74, 0x72, 0x65, 0x65, 0x20, 0x63, 0x6f,
+ 0x6d, 0x6d, 0x69, 0x74, 0x0a, 0x6d, 0x6d, 0x51,
+ 0x9a, 0xc9, 0x11, 0x76, 0x61, 0xa3, 0x89, 0x49,
+ 0xb7, 0xa1, 0x58, 0xc6, 0x1d, 0x8c, 0x33, 0x75,
+ 0x8d, 0x7e, 0x4d, 0x8e, 0x58, 0x91, 0xf8, 0x5c,
+ 0x57, 0xd9, 0x89, 0x9e, 0xb8, 0xd2, 0x9a, 0xd8,
+ 0xc9
+};
+
static const struct fast_pack fast_packs[] = {
{
.format_id = GIT_SHA1_FORMAT_ID,
@@ -253,6 +337,13 @@ static const struct fast_pack fast_packs[] = {
.suffix_len = sizeof(fast_pack_sha1_suffix),
.commit_oid = "aac43daf40d0377af31aa9c798a4ae8a31b55c1d",
},
+ {
+ .format_id = GIT_SHA256_FORMAT_ID,
+ .suffix = fast_pack_sha256_suffix,
+ .suffix_len = sizeof(fast_pack_sha256_suffix),
+ .commit_oid = "63c46ca51267b1d45be69a044bb84b4bf0559f09"
+ "d727f861d2ae94ddebdddbc9",
+ },
};
/*
--
gitgitgadget
^ permalink raw reply related
* [PATCH v3 10/11] t5608: mark >4GB tests as EXPENSIVE
From: Johannes Schindelin via GitGitGadget @ 2026-05-08 8:16 UTC (permalink / raw)
To: git
Cc: Derrick Stolee, Torsten Bögershausen, Jeff King,
Patrick Steinhardt, Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2102.v3.git.1778228209.gitgitgadget@gmail.com>
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Even with precomputed pack constants that reduced the helper's
runtime from minutes to seconds, the >4GB clone tests still take
200-850 seconds across CI jobs. The bottleneck is no longer the
pack generation but the clone operations themselves: transporting,
unpacking, and indexing 4 GiB of data through unpack-objects and
index-pack is inherently expensive.
As Jeff King pointed out [1], t5608 alone takes 160 seconds on his
laptop while the rest of the entire test suite finishes in under 90
seconds, and the test's disk footprint (4+ GiB source repo, then
two clones) is problematic for developers who use RAM disks for
their trash directories.
Gate the >4GB tests on the EXPENSIVE prereq (which requires
GIT_TEST_LONG to be set) in addition to SIZE_T_IS_64BIT, keeping
them out of normal local test runs.
[1] https://lore.kernel.org/git/20260501063805.GA2038915@coredump.intra.peff.net/
Assisted-by: Claude Opus 4.6
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
t/t5608-clone-2gb.sh | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/t/t5608-clone-2gb.sh b/t/t5608-clone-2gb.sh
index af93302dde..4f8a95ddda 100755
--- a/t/t5608-clone-2gb.sh
+++ b/t/t5608-clone-2gb.sh
@@ -49,7 +49,7 @@ test_expect_success 'clone - with worktree, file:// protocol' '
'
-test_expect_success SIZE_T_IS_64BIT 'set up repo with >4GB object' '
+test_expect_success SIZE_T_IS_64BIT,EXPENSIVE 'set up repo with >4GB object' '
large_blob_size=$((4*1024*1024*1024+1)) &&
git init --bare 4gb-repo &&
head_oid=$(test-tool synthesize pack \
@@ -60,7 +60,7 @@ test_expect_success SIZE_T_IS_64BIT 'set up repo with >4GB object' '
git -C 4gb-repo symbolic-ref HEAD refs/heads/main
'
-test_expect_success SIZE_T_IS_64BIT 'clone >4GB object via unpack-objects' '
+test_expect_success SIZE_T_IS_64BIT,EXPENSIVE 'clone >4GB object via unpack-objects' '
# The synthesized pack has five objects, so a large unpack limit keeps
# fetch-pack on the unpack-objects path.
git -c fetch.unpackLimit=100 clone --bare \
@@ -76,7 +76,7 @@ test_expect_success SIZE_T_IS_64BIT 'clone >4GB object via unpack-objects' '
test "$source_blob" = "$clone_blob"
'
-test_expect_success SIZE_T_IS_64BIT 'clone with >4GB object via index-pack' '
+test_expect_success SIZE_T_IS_64BIT,EXPENSIVE 'clone with >4GB object via index-pack' '
# Force fetch-pack to hand the pack to index-pack instead.
git -c fetch.unpackLimit=1 clone --bare \
"file://$(pwd)/4gb-repo" 4gb-clone-index &&
--
gitgitgadget
^ permalink raw reply related
* [PATCH v3 11/11] ci: run expensive tests on push builds to integration branches
From: Johannes Schindelin via GitGitGadget @ 2026-05-08 8:16 UTC (permalink / raw)
To: git
Cc: Derrick Stolee, Torsten Bögershausen, Jeff King,
Patrick Steinhardt, Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2102.v3.git.1778228209.gitgitgadget@gmail.com>
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Derrick Stolee suggested [1] that expensive tests should be run at a
regular cadence rather than on every PR iteration. Gate GIT_TEST_LONG
on push builds to the integration branches (next, master, main, maint)
so that the EXPENSIVE prereq is satisfied there but not during PR
validation, where the extra minutes of wall-clock time do not justify
themselves.
[1] https://lore.kernel.org/git/e1e8837f-7374-4079-ba87-ab95dd156e33@gmail.com/
Helped-by: Derrick Stolee <derrickstolee@github.com>
Assisted-by: Claude Opus 4.6
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
ci/lib.sh | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/ci/lib.sh b/ci/lib.sh
index 42a2b6a318..a671994bdf 100755
--- a/ci/lib.sh
+++ b/ci/lib.sh
@@ -314,6 +314,15 @@ export DEFAULT_TEST_TARGET=prove
export GIT_TEST_CLONE_2GB=true
export SKIP_DASHED_BUILT_INS=YesPlease
+# Enable expensive tests on push builds to integration branches, but
+# not on PR builds where the extra time is not justified for every
+# iteration.
+case "$GITHUB_EVENT_NAME,$CI_BRANCH" in
+push,*next*|push,*master*|push,*main*|push,*maint*)
+ export GIT_TEST_LONG=YesPlease
+ ;;
+esac
+
case "$distro" in
ubuntu-*)
# Python 2 is end of life, and Ubuntu 23.04 and newer don't actually
--
gitgitgadget
^ permalink raw reply related
* [PATCH] git-jump: pick a mode automatically when invoked without arguments
From: Greg Hurrell via GitGitGadget @ 2026-05-08 9:07 UTC (permalink / raw)
To: git; +Cc: Jeff King, Greg Hurrell, Greg Hurrell
From: Greg Hurrell <greg.hurrell@datadoghq.com>
When `git jump` is invoked with no positional arguments (and no
arguments after `--stdout`) it currently prints usage and exits with
status 1.
But there are two situations where we can usefully infer the most
valuable and likely mode that a user would want to use, and select it
automatically when they run `git jump` without arguments:
1. When there are unmerged paths in the index, the user likely
wants `git jump merge`.
2. When the working tree has unstaged changes, the user likely
wants `git jump diff`.
Detect these two cases and dispatch to the corresponding mode
automatically, falling back to the existing usage-and-exit behavior
when neither holds.
Signed-off-by: Greg Hurrell <greg.hurrell@datadoghq.com>
---
git-jump: pick a mode automatically when invoked without arguments
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2108%2Fwincent%2Fauto-jump-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2108/wincent/auto-jump-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/2108
contrib/git-jump/README | 4 ++++
contrib/git-jump/git-jump | 16 +++++++++++++---
2 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/contrib/git-jump/README b/contrib/git-jump/README
index 3211841305..420b20b6a2 100644
--- a/contrib/git-jump/README
+++ b/contrib/git-jump/README
@@ -55,6 +55,10 @@ To use it, just drop git-jump in your PATH, and then invoke it like
this:
--------------------------------------------------
+# pick a mode automatically: "merge" if there are unmerged paths,
+# "diff" if the worktree has unstaged changes, otherwise show usage
+git jump
+
# jump to changes not yet staged for commit
git jump diff
diff --git a/contrib/git-jump/git-jump b/contrib/git-jump/git-jump
index 8d1d5d79a6..ac0ad2f037 100755
--- a/contrib/git-jump/git-jump
+++ b/contrib/git-jump/git-jump
@@ -2,7 +2,7 @@
usage() {
cat <<\EOF
-usage: git jump [--stdout] <mode> [<args>]
+usage: git jump [--stdout] [<mode>] [<args>]
Jump to interesting elements in an editor.
The <mode> parameter is one of:
@@ -99,8 +99,18 @@ while test $# -gt 0; do
shift
done
if test $# -lt 1; then
- usage >&2
- exit 1
+ if test "$(git rev-parse --is-inside-work-tree 2>/dev/null)" != "true"; then
+ usage >&2
+ exit 1
+ fi
+ if test -n "$(git ls-files -u)"; then
+ set -- merge
+ elif ! git diff --quiet; then
+ set -- diff
+ else
+ usage >&2
+ exit 1
+ fi
fi
mode=$1; shift
type "mode_$mode" >/dev/null 2>&1 || { usage >&2; exit 1; }
base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0
--
gitgitgadget
^ permalink raw reply related
* Re: [PATCH] Makefile: link osxkeychain helper against Rust
From: Koji Nakamaru @ 2026-05-08 9:33 UTC (permalink / raw)
To: Junio C Hamano
Cc: Shardul Natu via GitGitGadget, git, Shnatu, brian m. carlson
In-Reply-To: <xmqqlddufw5d.fsf@gitster.g>
On Fri, May 8, 2026 at 11:54 AM Junio C Hamano <gitster@pobox.com> wrote:
>
> "Shardul Natu via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
> > From: Shnatu <snatu@google.com>
>
> If your name is "Shardul Natu", we'd prefer (not 'require', but
> 'prefer') that the patches authored by you also identify with that
> name, both on "From:" and "Signed-off-by:"..
>
> > When Rust is enabled, ensure that the git-credential-osxkeychain
> > helper is linked with the necessary Rust libraries.
> >
> > Introduce the RUST_LIBS variable inside ifndef NO_RUST block
> > to hold the Rust library dependency, and use it in the helper's
> > build target. This cleanly handles cases where Rust is disabled,
> > making it a no-op and avoiding any build failures on systems
> > without Cargo.
> >
> > This addresses reviewer feedback from internal CL 910223487
> > by simplifying the variables and avoiding confusing "LINK"
> > terminology.
> >
> > Signed-off-by: Shnatu <snatu@google.com>
> > ---
> > Makefile: link osxkeychain helper against Rust
>
> Thanks. I've added to CC: a few folks who may be more clueful in
> the affected area than I am. It somehow feels strange that we have
> to have RUST_LIB and RUST_LIBS separately, and apparently with the
> new definition the latter is expected to be a superset of the
> former, and it is unclear what are the things that should be added
> to the latter without getting added to the former.
>
> > Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2288%2Fkiranani%2Fnext-v1
> > Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2288/kiranani/next-v1
> > Pull-Request: https://github.com/git/git/pull/2288
> >
> > Makefile | 5 +++--
> > 1 file changed, 3 insertions(+), 2 deletions(-)
> >
> > diff --git a/Makefile b/Makefile
> > index f86173f93a..a17dca22b1 100644
> > --- a/Makefile
> > +++ b/Makefile
> > @@ -1593,6 +1593,7 @@ ALL_LDFLAGS = $(LDFLAGS) $(LDFLAGS_APPEND)
> > ifndef NO_RUST
> > BASIC_CFLAGS += -DWITH_RUST
> > GITLIBS += $(RUST_LIB)
> > +RUST_LIBS = $(RUST_LIB)
> > ifeq ($(uname_S),Windows)
> > EXTLIBS += -luserenv
> > endif
> > @@ -4082,9 +4083,9 @@ $(LIBGIT_HIDDEN_EXPORT): $(LIBGIT_PARTIAL_EXPORT)
> > contrib/libgit-sys/libgitpub.a: $(LIBGIT_HIDDEN_EXPORT)
> > $(AR) $(ARFLAGS) $@ $^
> >
> > -contrib/credential/osxkeychain/git-credential-osxkeychain: contrib/credential/osxkeychain/git-credential-osxkeychain.o $(LIB_FILE) GIT-LDFLAGS
> > +contrib/credential/osxkeychain/git-credential-osxkeychain: contrib/credential/osxkeychain/git-credential-osxkeychain.o $(LIB_FILE) $(RUST_LIBS) GIT-LDFLAGS
> > $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
> > - $(filter %.o,$^) $(LIB_FILE) $(EXTLIBS) -framework Security -framework CoreFoundation
> > + $(filter %.o,$^) $(LIB_FILE) $(RUST_LIBS) $(EXTLIBS) -framework Security -framework CoreFoundation
> >
> > contrib/credential/osxkeychain/git-credential-osxkeychain.o: contrib/credential/osxkeychain/git-credential-osxkeychain.c GIT-CFLAGS
> > $(QUIET_LINK)$(CC) -o $@ -c $(dep_args) $(compdb_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
> >
> > base-commit: 4f69b47b940100b02630f745a52f9d9850f122b2
How about simply wrapping the RUST_LIB-related sections in ifndef
NO_RUST, as shown below? This way, we can avoid defining
RUST_LIBS.
diff --git a/Makefile b/Makefile
index f86173f93a..daa1691950 100644
--- a/Makefile
+++ b/Makefile
@@ -947,11 +947,13 @@ else
RUST_TARGET_DIR = target/release
endif
+ifndef NO_RUST
ifeq ($(uname_S),Windows)
RUST_LIB = $(RUST_TARGET_DIR)/gitcore.lib
else
RUST_LIB = $(RUST_TARGET_DIR)/libgitcore.a
endif
+endif
GITLIBS = common-main.o $(LIB_FILE)
EXTLIBS =
@@ -3027,11 +3029,13 @@ scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS)
$(LIB_FILE): $(LIB_OBJS)
$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
+ifndef NO_RUST
$(RUST_LIB): Cargo.toml $(RUST_SOURCES) $(LIB_FILE)
$(QUIET_CARGO)cargo build $(CARGO_ARGS)
.PHONY: rust
rust: $(RUST_LIB)
+endif
export DEFAULT_EDITOR DEFAULT_PAGER
@@ -4082,9 +4086,9 @@ $(LIBGIT_HIDDEN_EXPORT): $(LIBGIT_PARTIAL_EXPORT)
contrib/libgit-sys/libgitpub.a: $(LIBGIT_HIDDEN_EXPORT)
$(AR) $(ARFLAGS) $@ $^
-contrib/credential/osxkeychain/git-credential-osxkeychain:
contrib/credential/osxkeychain/git-credential-osxkeychain.o
$(LIB_FILE) GIT-LDFLAGS
+contrib/credential/osxkeychain/git-credential-osxkeychain:
contrib/credential/osxkeychain/git-credential-osxkeychain.o
$(LIB_FILE) $(RUST_LIB) GIT-LDFLAGS
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
- $(filter %.o,$^) $(LIB_FILE) $(EXTLIBS) -framework
Security -framework CoreFoundation
+ $(filter %.o,$^) $(LIB_FILE) $(RUST_LIB) $(EXTLIBS)
-framework Security -framework CoreFoundation
contrib/credential/osxkeychain/git-credential-osxkeychain.o:
contrib/credential/osxkeychain/git-credential-osxkeychain.c GIT-CFLAGS
$(QUIET_LINK)$(CC) -o $@ -c $(dep_args) $(compdb_args)
$(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $<
^ permalink raw reply related
* Re: [PATCH/RFC 1/5] replay: support replaying 2-parent merges
From: Phillip Wood @ 2026-05-08 9:36 UTC (permalink / raw)
To: Johannes Schindelin via GitGitGadget, git
Cc: Elijah Newren, Patrick Steinhardt, Johannes Schindelin
In-Reply-To: <034ab0f83822e6db67baa423d9fcb753b12b5ac8.1778107405.git.gitgitgadget@gmail.com>
Hi Johannes
On 06/05/2026 23:43, Johannes Schindelin via GitGitGadget wrote:
>
> Elijah Newren spelled out a way to lift this limitation in his
> replay-design-notes [1] and prototyped it in a 2022
> work-in-progress sketch [2]. The idea is that a merge commit M on
> parents (P1, P2) records both an automatic merge of those parents
> AND any manual layer the author put on top of that automatic merge
> (textual conflict resolution and any semantic edit outside conflict
> markers). Replaying M onto rewritten parents (P1', P2') must
> preserve that manual layer, but the rewritten parents change the
> automatic merge, so a simple cherry-pick is wrong: the manual layer
> would be re-introduced on top of stale auto-merge text.
>
> What works instead is a three-way merge of three trees the existing
> infrastructure already knows how to compute. Let R be the recursive
> auto-merge of (P1, P2), O be M's actual tree and N be the recursive
> auto-merge of (P1', P2'). Then `git diff R O` is morally
> `git show --remerge-diff M`: it captures exactly what the author
> added on top of the automatic merge. A non-recursive 3-way merge
> with R as the merge base, O as side 1 and N as side 2 layers that
> manual contribution onto the freshly auto-merged rewritten parents
> (N) and produces the replayed tree.
So we cherry-pick the difference between the user's conflict resolution
O and the auto-merge M of the original parents onto the auto-merge N of
the replayed parents. If we have a topology that looks like
|
A
/|\
/ B \
E | D
C /
|/
O
then running
git replay --onto E --ancestry-path B..O
will replay C and O onto E. If the changes in E and D conflict but those
conflicts do not overlap with the conflicts in M that were resolved to
create O then the replayed version of O will contain conflict markers
from the conflicting changes in E and D. Because the previous conflict
resolution applies to N without conflicts we do not recognize that there
are still conflicts in N that need to be resolved.
Having realized this I went to look at Elijah's notes and they recognize
this possibility and suggest extending the xdiff merge code to detect
when N has conflicts that do not correspond to the conflicts in M. That
sounds like quite a lot of work. I've not put much effort into coming up
with a counterexample but think that because "git replay" and "git
history" do not yet allow the commits in the merged branches to be
edited we may be able to safely use the implementation proposed in this
series if both merge parents have been rebased (or we might want all the
merge bases of the new merge to be a descendants of "--onto"). In the
example above if both the parents were rebased onto E then any new
conflicts would happen when picking D rather than when recreating the merge.
Thanks
Phillip
> Implement `pick_merge_commit()` along those lines and dispatch to it
> from `replay_revisions()` when the commit being replayed has exactly
> two parents. Two specific points (learned the hard way) keep
> non-trivial cases working where the WIP sketch [2] bailed out.
> First, R and N use identical `merge_options.branch1` and `branch2`
> labels ("ours"/"theirs"). When the original parents conflicted on a
> region of a file, both R and N produce textually identical conflict
> markers; the outer non-recursive merge then sees N == R in that
> region and the user's manual resolution from O wins cleanly. Without
> this, the conflict-marker text would differ between R and N (because
> the inner merges would label the conflicts differently), and the
> outer merge would itself be unclean even when the user did supply a
> clean resolution. Second, an unclean inner merge
> (`result.clean == 0`) is _not_ fatal: the tree merge-ort produces in
> that case still has well-defined contents (with conflict markers in
> the conflicted files) and is a valid input to the outer
> non-recursive merge. Only a real error (`< 0`) propagates as
> failure.
>
> The replay propagates the textual diffs the user actually made in M;
> it does _not_ extrapolate symbol-level intent. If rewriting the
> parents pulls in genuinely new content (for example, a brand-new
> caller of a function that the merge renamed), that new content stays
> as the rewritten parents have it. Symbol-aware refactoring is out of
> scope here, just as it is for plain rebase.
>
> Octopus merges (more than two parents) and revert-of-merge are not
> supported and are surfaced as explicit errors at the dispatch point.
> The "split" sub-command of `git history` continues to refuse when
> the targeted commit is itself a merge: split semantics do not apply
> to merges. The pre-walk gate in `builtin/history.c` that previously
> rejected any merge in the rewrite path now only rejects octopus
> merges; rename it accordingly.
>
> A small refactor in `create_commit()` makes the merge case possible:
> the helper now takes a `struct commit_list *parents` rather than a
> single parent pointer and takes ownership of the list. The single
> existing caller in `pick_regular_commit()` builds and passes a
> one-element list; the new `pick_merge_commit()` builds a two-element
> list, with the order of the `from` and `merge` parents preserved.
>
> Update the negative expectations in t3451, t3452 and t3650 that were
> asserting the now-retired "not supported yet" message, replacing
> them with positive coverage where it fits. Octopus rejection and
> revert-of-merge rejection are covered by new positive tests in
> t3650. A dedicated test script with merge-replay scenarios driven by
> a new test-tool fixture builder will follow in a subsequent commit.
>
> [1] https://github.com/newren/git/blob/replay/replay-design-notes.txt
> [2] https://github.com/newren/git/commit/4c45e8955ef9bf7d01fd15d9106b3bdb8ea91b45
>
> Helped-by: Elijah Newren <newren@gmail.com>
> Assisted-by: Claude Opus 4.7
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
> builtin/history.c | 16 ++-
> replay.c | 209 ++++++++++++++++++++++++++++++++++++--
> t/t3451-history-reword.sh | 21 ++--
> t/t3452-history-split.sh | 6 +-
> t/t3650-replay-basics.sh | 46 ++++++++-
> 5 files changed, 269 insertions(+), 29 deletions(-)
>
> diff --git a/builtin/history.c b/builtin/history.c
> index 9526938085..00097b2226 100644
> --- a/builtin/history.c
> +++ b/builtin/history.c
> @@ -195,15 +195,15 @@ static int parse_ref_action(const struct option *opt, const char *value, int uns
> return 0;
> }
>
> -static int revwalk_contains_merges(struct repository *repo,
> - const struct strvec *revwalk_args)
> +static int revwalk_contains_octopus_merges(struct repository *repo,
> + const struct strvec *revwalk_args)
> {
> struct strvec args = STRVEC_INIT;
> struct rev_info revs;
> int ret;
>
> strvec_pushv(&args, revwalk_args->v);
> - strvec_push(&args, "--min-parents=2");
> + strvec_push(&args, "--min-parents=3");
>
> repo_init_revisions(repo, &revs, NULL);
>
> @@ -217,7 +217,7 @@ static int revwalk_contains_merges(struct repository *repo,
> }
>
> if (get_revision(&revs)) {
> - ret = error(_("replaying merge commits is not supported yet!"));
> + ret = error(_("replaying octopus merges is not supported"));
> goto out;
> }
>
> @@ -289,7 +289,7 @@ static int setup_revwalk(struct repository *repo,
> strvec_push(&args, "HEAD");
> }
>
> - ret = revwalk_contains_merges(repo, &args);
> + ret = revwalk_contains_octopus_merges(repo, &args);
> if (ret < 0)
> goto out;
>
> @@ -482,6 +482,9 @@ static int cmd_history_reword(int argc,
> if (ret < 0) {
> ret = error(_("failed replaying descendants"));
> goto out;
> + } else if (ret) {
> + ret = error(_("conflict during replay; some descendants were not rewritten"));
> + goto out;
> }
>
> ret = 0;
> @@ -721,6 +724,9 @@ static int cmd_history_split(int argc,
> if (ret < 0) {
> ret = error(_("failed replaying descendants"));
> goto out;
> + } else if (ret) {
> + ret = error(_("conflict during replay; some descendants were not rewritten"));
> + goto out;
> }
>
> ret = 0;
> diff --git a/replay.c b/replay.c
> index f96f1f6551..3dbce095f9 100644
> --- a/replay.c
> +++ b/replay.c
> @@ -1,6 +1,7 @@
> #define USE_THE_REPOSITORY_VARIABLE
>
> #include "git-compat-util.h"
> +#include "commit-reach.h"
> #include "environment.h"
> #include "hex.h"
> #include "merge-ort.h"
> @@ -77,15 +78,21 @@ static void generate_revert_message(struct strbuf *msg,
> repo_unuse_commit_buffer(repo, commit, message);
> }
>
> +/*
> + * Build a new commit with the given tree and parent list, copying author,
> + * extra headers and (for pick mode) the commit message from `based_on`.
> + *
> + * Takes ownership of `parents`: it will be freed before returning, even on
> + * error. Parent order is preserved as supplied by the caller.
> + */
> static struct commit *create_commit(struct repository *repo,
> struct tree *tree,
> struct commit *based_on,
> - struct commit *parent,
> + struct commit_list *parents,
> enum replay_mode mode)
> {
> struct object_id ret;
> struct object *obj = NULL;
> - struct commit_list *parents = NULL;
> char *author = NULL;
> char *sign_commit = NULL; /* FIXME: cli users might want to sign again */
> struct commit_extra_header *extra = NULL;
> @@ -96,7 +103,6 @@ static struct commit *create_commit(struct repository *repo,
> const char *orig_message = NULL;
> const char *exclude_gpgsig[] = { "gpgsig", "gpgsig-sha256", NULL };
>
> - commit_list_insert(parent, &parents);
> extra = read_commit_extra_headers(based_on, exclude_gpgsig);
> if (mode == REPLAY_MODE_REVERT) {
> generate_revert_message(&msg, based_on, repo);
> @@ -273,6 +279,7 @@ static struct commit *pick_regular_commit(struct repository *repo,
> {
> struct commit *base, *replayed_base;
> struct tree *pickme_tree, *base_tree, *replayed_base_tree;
> + struct commit_list *parents = NULL;
>
> if (pickme->parents) {
> base = pickme->parents->item;
> @@ -327,7 +334,143 @@ static struct commit *pick_regular_commit(struct repository *repo,
> if (oideq(&replayed_base_tree->object.oid, &result->tree->object.oid) &&
> !oideq(&pickme_tree->object.oid, &base_tree->object.oid))
> return replayed_base;
> - return create_commit(repo, result->tree, pickme, replayed_base, mode);
> + commit_list_insert(replayed_base, &parents);
> + return create_commit(repo, result->tree, pickme, parents, mode);
> +}
> +
> +/*
> + * Replay a 2-parent merge commit by composing three calls into merge-ort:
> + *
> + * R = recursive merge of pickme's two original parents (auto-remerge of
> + * the original merge, accepting any conflicts)
> + * N = recursive merge of the (possibly rewritten) parents
> + * O = pickme's tree (the user's actual merge, including any manual
> + * resolutions)
> + *
> + * The picked tree comes from a non-recursive merge using R as the base,
> + * O as side1 and N as side2. `git diff R O` is morally `git show
> + * --remerge-diff $oldmerge`, so this layers the user's original manual
> + * resolution on top of the freshly auto-merged rewritten parents (see
> + * `replay-design-notes.txt` on the `replay` branch of newren/git).
> + *
> + * If the outer 3-way merge is unclean, propagate the conflict status to
> + * the caller via `result->clean = 0` and return NULL. The two inner
> + * merges (R and N) being unclean is _not_ fatal: the conflict-markered
> + * trees they produce are valid inputs to the outer merge, and using
> + * identical labels for both inner merges keeps the marker text
> + * byte-equal between R and N so the user's resolution recorded in O
> + * collapses the conflict cleanly there. Octopus merges (more than two
> + * parents) and revert-of-merge are rejected by the caller before this
> + * function is invoked.
> + */
> +static struct commit *pick_merge_commit(struct repository *repo,
> + struct commit *pickme,
> + kh_oid_map_t *replayed_commits,
> + struct merge_options *merge_opt,
> + struct merge_result *result)
> +{
> + struct commit *parent1, *parent2;
> + struct commit *replayed_par1, *replayed_par2;
> + struct tree *pickme_tree;
> + struct merge_options remerge_opt = { 0 };
> + struct merge_options new_merge_opt = { 0 };
> + struct merge_result remerge_res = { 0 };
> + struct merge_result new_merge_res = { 0 };
> + struct commit_list *parent_bases = NULL;
> + struct commit_list *replayed_bases = NULL;
> + struct commit_list *parents;
> + struct commit *picked = NULL;
> + char *ancestor_name = NULL;
> +
> + parent1 = pickme->parents->item;
> + parent2 = pickme->parents->next->item;
> +
> + /*
> + * Map the merge's parents to their replayed counterparts. With the
> + * boundary commits pre-seeded into `replayed_commits`, every parent
> + * either has an explicit mapping (rewritten or boundary -> onto) or
> + * sits outside the rewrite range entirely; the latter must stay at
> + * the original parent commit, so use `parent` itself as the fallback
> + * for both sides.
> + */
> + replayed_par1 = mapped_commit(replayed_commits, parent1, parent1);
> + replayed_par2 = mapped_commit(replayed_commits, parent2, parent2);
> +
> + /*
> + * R: auto-remerge of the original parents.
> + *
> + * Use the same branch labels for the inner merges that compute R
> + * and N so conflict markers (if any) are textually identical
> + * between the two; the outer non-recursive merge can then collapse
> + * the manual resolution from O against them.
> + */
> + init_basic_merge_options(&remerge_opt, repo);
> + remerge_opt.show_rename_progress = 0;
> + remerge_opt.branch1 = "ours";
> + remerge_opt.branch2 = "theirs";
> + if (repo_get_merge_bases(repo, parent1, parent2, &parent_bases) < 0) {
> + result->clean = -1;
> + goto out;
> + }
> + merge_incore_recursive(&remerge_opt, parent_bases,
> + parent1, parent2, &remerge_res);
> + parent_bases = NULL; /* consumed by merge_incore_recursive */
> + if (remerge_res.clean < 0) {
> + result->clean = remerge_res.clean;
> + goto out;
> + }
> +
> + /* N: fresh merge of the (possibly rewritten) parents. */
> + init_basic_merge_options(&new_merge_opt, repo);
> + new_merge_opt.show_rename_progress = 0;
> + new_merge_opt.branch1 = "ours";
> + new_merge_opt.branch2 = "theirs";
> + if (repo_get_merge_bases(repo, replayed_par1, replayed_par2,
> + &replayed_bases) < 0) {
> + result->clean = -1;
> + goto out;
> + }
> + merge_incore_recursive(&new_merge_opt, replayed_bases,
> + replayed_par1, replayed_par2, &new_merge_res);
> + replayed_bases = NULL; /* consumed by merge_incore_recursive */
> + if (new_merge_res.clean < 0) {
> + result->clean = new_merge_res.clean;
> + goto out;
> + }
> +
> + /*
> + * Outer non-recursive merge: base=R, side1=O (pickme), side2=N.
> + */
> + pickme_tree = repo_get_commit_tree(repo, pickme);
> + ancestor_name = xstrfmt("auto-remerge of %s",
> + oid_to_hex(&pickme->object.oid));
> + merge_opt->ancestor = ancestor_name;
> + merge_opt->branch1 = short_commit_name(repo, pickme);
> + merge_opt->branch2 = "merge of replayed parents";
> + merge_incore_nonrecursive(merge_opt,
> + remerge_res.tree,
> + pickme_tree,
> + new_merge_res.tree,
> + result);
> + merge_opt->ancestor = NULL;
> + merge_opt->branch1 = NULL;
> + merge_opt->branch2 = NULL;
> + if (!result->clean)
> + goto out;
> +
> + parents = NULL;
> + commit_list_insert(replayed_par2, &parents);
> + commit_list_insert(replayed_par1, &parents);
> + picked = create_commit(repo, result->tree, pickme, parents,
> + REPLAY_MODE_PICK);
> +
> +out:
> + free(ancestor_name);
> + free_commit_list(parent_bases);
> + free_commit_list(replayed_bases);
> + merge_finalize(&remerge_opt, &remerge_res);
> + merge_finalize(&new_merge_opt, &new_merge_res);
> + return picked;
> }
>
> void replay_result_release(struct replay_result *result)
> @@ -407,17 +550,63 @@ int replay_revisions(struct rev_info *revs,
> merge_opt.show_rename_progress = 0;
> last_commit = onto;
> replayed_commits = kh_init_oid_map();
> +
> + /*
> + * Seed the rewritten-commit map with each negative-side ("BOTTOM")
> + * cmdline entry pointing at `onto`. This matters for merge replay:
> + * a 2-parent merge whose first parent is the boundary (e.g. the
> + * commit being reworded) must replay onto the rewritten boundary,
> + * yet pick_merge_commit uses a self fallback so the second parent
> + * (a side branch outside the rewrite range) is preserved as-is.
> + * Pre-seeding the boundary disambiguates the two: in the map ->
> + * rewritten, missing -> kept as-is.
> + *
> + * Only do this for the pick path; revert mode chains reverts
> + * through last_commit and a pre-seeded boundary would short-circuit
> + * that chain.
> + */
> + if (mode == REPLAY_MODE_PICK) {
> + for (size_t i = 0; i < revs->cmdline.nr; i++) {
> + struct rev_cmdline_entry *e = &revs->cmdline.rev[i];
> + struct commit *boundary;
> + khint_t pos;
> + int hr;
> +
> + if (!(e->flags & BOTTOM))
> + continue;
> + boundary = lookup_commit_reference_gently(revs->repo,
> + &e->item->oid, 1);
> + if (!boundary)
> + continue;
> + pos = kh_put_oid_map(replayed_commits,
> + boundary->object.oid, &hr);
> + if (hr != 0)
> + kh_value(replayed_commits, pos) = onto;
> + }
> + }
> +
> while ((commit = get_revision(revs))) {
> const struct name_decoration *decoration;
> khint_t pos;
> int hr;
>
> - if (commit->parents && commit->parents->next)
> - die(_("replaying merge commits is not supported yet!"));
> -
> - last_commit = pick_regular_commit(revs->repo, commit, replayed_commits,
> - mode == REPLAY_MODE_REVERT ? last_commit : onto,
> - &merge_opt, &result, mode);
> + if (commit->parents && commit->parents->next) {
> + if (commit->parents->next->next) {
> + ret = error(_("replaying octopus merges is not supported"));
> + goto out;
> + }
> + if (mode == REPLAY_MODE_REVERT) {
> + ret = error(_("reverting merge commits is not supported"));
> + goto out;
> + }
> + last_commit = pick_merge_commit(revs->repo, commit,
> + replayed_commits,
> + &merge_opt, &result);
> + } else {
> + last_commit = pick_regular_commit(revs->repo, commit, replayed_commits,
> + mode == REPLAY_MODE_REVERT ? last_commit : onto,
> + &merge_opt, &result, mode);
> + }
> if (!last_commit)
> break;
>
> diff --git a/t/t3451-history-reword.sh b/t/t3451-history-reword.sh
> index de7b357685..d103f866a2 100755
> --- a/t/t3451-history-reword.sh
> +++ b/t/t3451-history-reword.sh
> @@ -201,12 +201,21 @@ test_expect_success 'can reword a merge commit' '
> git switch - &&
> git merge theirs &&
>
> - # It is not possible to replay merge commits embedded in the
> - # history (yet).
> - test_must_fail git -c core.editor=false history reword HEAD~ 2>err &&
> - test_grep "replaying merge commits is not supported yet" err &&
> + # Reword a non-merge commit whose descendants include the
> + # merge: replay carries the merge through.
> + reword_with_message HEAD~ <<-EOF &&
> + ours reworded
> + EOF
> + expect_graph <<-EOF &&
> + * Merge tag ${SQ}theirs${SQ}
> + |\\
> + | * theirs
> + * | ours reworded
> + |/
> + * base
> + EOF
>
> - # But it is possible to reword a merge commit directly.
> + # And reword a merge commit directly.
> reword_with_message HEAD <<-EOF &&
> Reworded merge commit
> EOF
> @@ -214,7 +223,7 @@ test_expect_success 'can reword a merge commit' '
> * Reworded merge commit
> |\
> | * theirs
> - * | ours
> + * | ours reworded
> |/
> * base
> EOF
> diff --git a/t/t3452-history-split.sh b/t/t3452-history-split.sh
> index 8ed0cebb50..ad6309f98b 100755
> --- a/t/t3452-history-split.sh
> +++ b/t/t3452-history-split.sh
> @@ -36,7 +36,7 @@ expect_tree_entries () {
> test_cmp expect actual
> }
>
> -test_expect_success 'refuses to work with merge commits' '
> +test_expect_success 'refuses to split a merge commit' '
> test_when_finished "rm -rf repo" &&
> git init repo &&
> (
> @@ -49,9 +49,7 @@ test_expect_success 'refuses to work with merge commits' '
> git switch - &&
> git merge theirs &&
> test_must_fail git history split HEAD 2>err &&
> - test_grep "cannot split up merge commit" err &&
> - test_must_fail git history split HEAD~ 2>err &&
> - test_grep "replaying merge commits is not supported yet" err
> + test_grep "cannot split up merge commit" err
> )
> '
>
> diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
> index 3353bc4a4d..368b1b0f9a 100755
> --- a/t/t3650-replay-basics.sh
> +++ b/t/t3650-replay-basics.sh
> @@ -103,10 +103,48 @@ test_expect_success 'cannot advance target ... ordering would be ill-defined' '
> test_cmp expect actual
> '
>
> -test_expect_success 'replaying merge commits is not supported yet' '
> - echo "fatal: replaying merge commits is not supported yet!" >expect &&
> - test_must_fail git replay --advance=main main..topic-with-merge 2>actual &&
> - test_cmp expect actual
> +test_expect_success 'using replay to rebase a 2-parent merge' '
> + # main..topic-with-merge contains a 2-parent merge (P) introduced
> + # via test_merge. Use --ref-action=print so this test does not
> + # mutate state for subsequent tests in this file.
> + git replay --ref-action=print --onto main main..topic-with-merge >result &&
> + test_line_count = 1 result &&
> +
> + new_tip=$(cut -f 3 -d " " result) &&
> +
> + # Result is still a 2-parent merge.
> + git cat-file -p $new_tip >cat &&
> + grep -c "^parent " cat >count &&
> + echo 2 >expect &&
> + test_cmp expect count &&
> +
> + # Merge subject is preserved.
> + echo P >expect &&
> + git log -1 --format=%s $new_tip >actual &&
> + test_cmp expect actual &&
> +
> + # The replayed merge sits on top of main: walking back via the
> + # first-parent chain reaches main.
> + git merge-base --is-ancestor main $new_tip
> +'
> +
> +test_expect_success 'replaying an octopus merge is rejected' '
> + # Build an octopus side-branch so the rest of the test state stays
> + # untouched.
> + test_when_finished "git update-ref -d refs/heads/octopus-tip" &&
> + octopus_tip=$(git commit-tree -p topic4 -p topic1 -p topic3 \
> + -m "octopus" $(git rev-parse topic4^{tree})) &&
> + git update-ref refs/heads/octopus-tip "$octopus_tip" &&
> +
> + test_must_fail git replay --ref-action=print --onto main \
> + topic4..octopus-tip 2>actual &&
> + test_grep "octopus merges" actual
> +'
> +
> +test_expect_success 'reverting a merge commit is rejected' '
> + test_must_fail git replay --ref-action=print --revert=topic-with-merge \
> + topic4..topic-with-merge 2>actual &&
> + test_grep "reverting merge commits" actual
> '
>
> test_expect_success 'using replay to rebase two branches, one on top of other' '
^ permalink raw reply
* Re: [PATCH/RFC 1/5] replay: support replaying 2-parent merges
From: Phillip Wood @ 2026-05-08 10:05 UTC (permalink / raw)
To: Johannes Schindelin via GitGitGadget, git
Cc: Elijah Newren, Patrick Steinhardt, Johannes Schindelin
In-Reply-To: <72901ee2-1212-46cd-b752-f451cce6e1ff@gmail.com>
On 08/05/2026 10:36, Phillip Wood wrote:
> Hi Johannes
>
> On 06/05/2026 23:43, Johannes Schindelin via GitGitGadget wrote:
>>
>> Elijah Newren spelled out a way to lift this limitation in his
>> replay-design-notes [1] and prototyped it in a 2022
>> work-in-progress sketch [2]. The idea is that a merge commit M on
>> parents (P1, P2) records both an automatic merge of those parents
>> AND any manual layer the author put on top of that automatic merge
>> (textual conflict resolution and any semantic edit outside conflict
>> markers). Replaying M onto rewritten parents (P1', P2') must
>> preserve that manual layer, but the rewritten parents change the
>> automatic merge, so a simple cherry-pick is wrong: the manual layer
>> would be re-introduced on top of stale auto-merge text.
>>
>> What works instead is a three-way merge of three trees the existing
>> infrastructure already knows how to compute. Let R be the recursive
>> auto-merge of (P1, P2), O be M's actual tree and N be the recursive
>> auto-merge of (P1', P2'). Then `git diff R O` is morally
>> `git show --remerge-diff M`: it captures exactly what the author
>> added on top of the automatic merge. A non-recursive 3-way merge
>> with R as the merge base, O as side 1 and N as side 2 layers that
>> manual contribution onto the freshly auto-merged rewritten parents
>> (N) and produces the replayed tree.
>
> So we cherry-pick the difference between the user's conflict resolution
> O and the auto-merge M of the original parents onto the auto-merge N of
> the replayed parents. If we have a topology that looks like
>
> |
> A
> /|\
> / B \
> E | D
> C /
> |/
> O
>
> then running
>
> git replay --onto E --ancestry-path B..O
>
> will replay C and O onto E. If the changes in E and D conflict but those
> conflicts do not overlap with the conflicts in M that were resolved to
> create O then the replayed version of O will contain conflict markers
> from the conflicting changes in E and D. Because the previous conflict
> resolution applies to N without conflicts we do not recognize that there
> are still conflicts in N that need to be resolved.
>
> Having realized this I went to look at Elijah's notes and they recognize
> this possibility and suggest extending the xdiff merge code to detect
> when N has conflicts that do not correspond to the conflicts in M. That
> sounds like quite a lot of work. I've not put much effort into coming up
> with a counterexample but think that because "git replay" and "git
> history" do not yet allow the commits in the merged branches to be
> edited we may be able to safely use the implementation proposed in this
> series if both merge parents have been rebased (or we might want all the
> merge bases of the new merge to be a descendants of "--onto"). In the
> example above if both the parents were rebased onto E then any new
> conflicts would happen when picking D rather than when recreating the
> merge.
One further thought - if only one of the parents has been rebased (i.e.
we're replaying O with parents P1' and P2) then can we just cherry-pick
the merge - instead of merging P1' and P2, use P1 as the merge-base with
O and P1' as the merge heads?
Thanks
Phillip
> Thanks
>
> Phillip
>
>> Implement `pick_merge_commit()` along those lines and dispatch to it
>> from `replay_revisions()` when the commit being replayed has exactly
>> two parents. Two specific points (learned the hard way) keep
>> non-trivial cases working where the WIP sketch [2] bailed out.
>> First, R and N use identical `merge_options.branch1` and `branch2`
>> labels ("ours"/"theirs"). When the original parents conflicted on a
>> region of a file, both R and N produce textually identical conflict
>> markers; the outer non-recursive merge then sees N == R in that
>> region and the user's manual resolution from O wins cleanly. Without
>> this, the conflict-marker text would differ between R and N (because
>> the inner merges would label the conflicts differently), and the
>> outer merge would itself be unclean even when the user did supply a
>> clean resolution. Second, an unclean inner merge
>> (`result.clean == 0`) is _not_ fatal: the tree merge-ort produces in
>> that case still has well-defined contents (with conflict markers in
>> the conflicted files) and is a valid input to the outer
>> non-recursive merge. Only a real error (`< 0`) propagates as
>> failure.
>>
>> The replay propagates the textual diffs the user actually made in M;
>> it does _not_ extrapolate symbol-level intent. If rewriting the
>> parents pulls in genuinely new content (for example, a brand-new
>> caller of a function that the merge renamed), that new content stays
>> as the rewritten parents have it. Symbol-aware refactoring is out of
>> scope here, just as it is for plain rebase.
>>
>> Octopus merges (more than two parents) and revert-of-merge are not
>> supported and are surfaced as explicit errors at the dispatch point.
>> The "split" sub-command of `git history` continues to refuse when
>> the targeted commit is itself a merge: split semantics do not apply
>> to merges. The pre-walk gate in `builtin/history.c` that previously
>> rejected any merge in the rewrite path now only rejects octopus
>> merges; rename it accordingly.
>>
>> A small refactor in `create_commit()` makes the merge case possible:
>> the helper now takes a `struct commit_list *parents` rather than a
>> single parent pointer and takes ownership of the list. The single
>> existing caller in `pick_regular_commit()` builds and passes a
>> one-element list; the new `pick_merge_commit()` builds a two-element
>> list, with the order of the `from` and `merge` parents preserved.
>>
>> Update the negative expectations in t3451, t3452 and t3650 that were
>> asserting the now-retired "not supported yet" message, replacing
>> them with positive coverage where it fits. Octopus rejection and
>> revert-of-merge rejection are covered by new positive tests in
>> t3650. A dedicated test script with merge-replay scenarios driven by
>> a new test-tool fixture builder will follow in a subsequent commit.
>>
>> [1] https://github.com/newren/git/blob/replay/replay-design-notes.txt
>> [2] https://github.com/newren/git/
>> commit/4c45e8955ef9bf7d01fd15d9106b3bdb8ea91b45
>>
>> Helped-by: Elijah Newren <newren@gmail.com>
>> Assisted-by: Claude Opus 4.7
>> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
>> ---
>> builtin/history.c | 16 ++-
>> replay.c | 209 ++++++++++++++++++++++++++++++++++++--
>> t/t3451-history-reword.sh | 21 ++--
>> t/t3452-history-split.sh | 6 +-
>> t/t3650-replay-basics.sh | 46 ++++++++-
>> 5 files changed, 269 insertions(+), 29 deletions(-)
>>
>> diff --git a/builtin/history.c b/builtin/history.c
>> index 9526938085..00097b2226 100644
>> --- a/builtin/history.c
>> +++ b/builtin/history.c
>> @@ -195,15 +195,15 @@ static int parse_ref_action(const struct option
>> *opt, const char *value, int uns
>> return 0;
>> }
>> -static int revwalk_contains_merges(struct repository *repo,
>> - const struct strvec *revwalk_args)
>> +static int revwalk_contains_octopus_merges(struct repository *repo,
>> + const struct strvec *revwalk_args)
>> {
>> struct strvec args = STRVEC_INIT;
>> struct rev_info revs;
>> int ret;
>> strvec_pushv(&args, revwalk_args->v);
>> - strvec_push(&args, "--min-parents=2");
>> + strvec_push(&args, "--min-parents=3");
>> repo_init_revisions(repo, &revs, NULL);
>> @@ -217,7 +217,7 @@ static int revwalk_contains_merges(struct
>> repository *repo,
>> }
>> if (get_revision(&revs)) {
>> - ret = error(_("replaying merge commits is not supported yet!"));
>> + ret = error(_("replaying octopus merges is not supported"));
>> goto out;
>> }
>> @@ -289,7 +289,7 @@ static int setup_revwalk(struct repository *repo,
>> strvec_push(&args, "HEAD");
>> }
>> - ret = revwalk_contains_merges(repo, &args);
>> + ret = revwalk_contains_octopus_merges(repo, &args);
>> if (ret < 0)
>> goto out;
>> @@ -482,6 +482,9 @@ static int cmd_history_reword(int argc,
>> if (ret < 0) {
>> ret = error(_("failed replaying descendants"));
>> goto out;
>> + } else if (ret) {
>> + ret = error(_("conflict during replay; some descendants were
>> not rewritten"));
>> + goto out;
>> }
>> ret = 0;
>> @@ -721,6 +724,9 @@ static int cmd_history_split(int argc,
>> if (ret < 0) {
>> ret = error(_("failed replaying descendants"));
>> goto out;
>> + } else if (ret) {
>> + ret = error(_("conflict during replay; some descendants were
>> not rewritten"));
>> + goto out;
>> }
>> ret = 0;
>> diff --git a/replay.c b/replay.c
>> index f96f1f6551..3dbce095f9 100644
>> --- a/replay.c
>> +++ b/replay.c
>> @@ -1,6 +1,7 @@
>> #define USE_THE_REPOSITORY_VARIABLE
>> #include "git-compat-util.h"
>> +#include "commit-reach.h"
>> #include "environment.h"
>> #include "hex.h"
>> #include "merge-ort.h"
>> @@ -77,15 +78,21 @@ static void generate_revert_message(struct strbuf
>> *msg,
>> repo_unuse_commit_buffer(repo, commit, message);
>> }
>> +/*
>> + * Build a new commit with the given tree and parent list, copying
>> author,
>> + * extra headers and (for pick mode) the commit message from `based_on`.
>> + *
>> + * Takes ownership of `parents`: it will be freed before returning,
>> even on
>> + * error. Parent order is preserved as supplied by the caller.
>> + */
>> static struct commit *create_commit(struct repository *repo,
>> struct tree *tree,
>> struct commit *based_on,
>> - struct commit *parent,
>> + struct commit_list *parents,
>> enum replay_mode mode)
>> {
>> struct object_id ret;
>> struct object *obj = NULL;
>> - struct commit_list *parents = NULL;
>> char *author = NULL;
>> char *sign_commit = NULL; /* FIXME: cli users might want to sign
>> again */
>> struct commit_extra_header *extra = NULL;
>> @@ -96,7 +103,6 @@ static struct commit *create_commit(struct
>> repository *repo,
>> const char *orig_message = NULL;
>> const char *exclude_gpgsig[] = { "gpgsig", "gpgsig-sha256", NULL };
>> - commit_list_insert(parent, &parents);
>> extra = read_commit_extra_headers(based_on, exclude_gpgsig);
>> if (mode == REPLAY_MODE_REVERT) {
>> generate_revert_message(&msg, based_on, repo);
>> @@ -273,6 +279,7 @@ static struct commit *pick_regular_commit(struct
>> repository *repo,
>> {
>> struct commit *base, *replayed_base;
>> struct tree *pickme_tree, *base_tree, *replayed_base_tree;
>> + struct commit_list *parents = NULL;
>> if (pickme->parents) {
>> base = pickme->parents->item;
>> @@ -327,7 +334,143 @@ static struct commit *pick_regular_commit(struct
>> repository *repo,
>> if (oideq(&replayed_base_tree->object.oid, &result->tree-
>> >object.oid) &&
>> !oideq(&pickme_tree->object.oid, &base_tree->object.oid))
>> return replayed_base;
>> - return create_commit(repo, result->tree, pickme, replayed_base,
>> mode);
>> + commit_list_insert(replayed_base, &parents);
>> + return create_commit(repo, result->tree, pickme, parents, mode);
>> +}
>> +
>> +/*
>> + * Replay a 2-parent merge commit by composing three calls into
>> merge-ort:
>> + *
>> + * R = recursive merge of pickme's two original parents (auto-
>> remerge of
>> + * the original merge, accepting any conflicts)
>> + * N = recursive merge of the (possibly rewritten) parents
>> + * O = pickme's tree (the user's actual merge, including any manual
>> + * resolutions)
>> + *
>> + * The picked tree comes from a non-recursive merge using R as the base,
>> + * O as side1 and N as side2. `git diff R O` is morally `git show
>> + * --remerge-diff $oldmerge`, so this layers the user's original manual
>> + * resolution on top of the freshly auto-merged rewritten parents (see
>> + * `replay-design-notes.txt` on the `replay` branch of newren/git).
>> + *
>> + * If the outer 3-way merge is unclean, propagate the conflict status to
>> + * the caller via `result->clean = 0` and return NULL. The two inner
>> + * merges (R and N) being unclean is _not_ fatal: the conflict-markered
>> + * trees they produce are valid inputs to the outer merge, and using
>> + * identical labels for both inner merges keeps the marker text
>> + * byte-equal between R and N so the user's resolution recorded in O
>> + * collapses the conflict cleanly there. Octopus merges (more than two
>> + * parents) and revert-of-merge are rejected by the caller before this
>> + * function is invoked.
>> + */
>> +static struct commit *pick_merge_commit(struct repository *repo,
>> + struct commit *pickme,
>> + kh_oid_map_t *replayed_commits,
>> + struct merge_options *merge_opt,
>> + struct merge_result *result)
>> +{
>> + struct commit *parent1, *parent2;
>> + struct commit *replayed_par1, *replayed_par2;
>> + struct tree *pickme_tree;
>> + struct merge_options remerge_opt = { 0 };
>> + struct merge_options new_merge_opt = { 0 };
>> + struct merge_result remerge_res = { 0 };
>> + struct merge_result new_merge_res = { 0 };
>> + struct commit_list *parent_bases = NULL;
>> + struct commit_list *replayed_bases = NULL;
>> + struct commit_list *parents;
>> + struct commit *picked = NULL;
>> + char *ancestor_name = NULL;
>> +
>> + parent1 = pickme->parents->item;
>> + parent2 = pickme->parents->next->item;
>> +
>> + /*
>> + * Map the merge's parents to their replayed counterparts. With the
>> + * boundary commits pre-seeded into `replayed_commits`, every parent
>> + * either has an explicit mapping (rewritten or boundary -> onto) or
>> + * sits outside the rewrite range entirely; the latter must stay at
>> + * the original parent commit, so use `parent` itself as the
>> fallback
>> + * for both sides.
>> + */
>> + replayed_par1 = mapped_commit(replayed_commits, parent1, parent1);
>> + replayed_par2 = mapped_commit(replayed_commits, parent2, parent2);
>> +
>> + /*
>> + * R: auto-remerge of the original parents.
>> + *
>> + * Use the same branch labels for the inner merges that compute R
>> + * and N so conflict markers (if any) are textually identical
>> + * between the two; the outer non-recursive merge can then collapse
>> + * the manual resolution from O against them.
>> + */
>> + init_basic_merge_options(&remerge_opt, repo);
>> + remerge_opt.show_rename_progress = 0;
>> + remerge_opt.branch1 = "ours";
>> + remerge_opt.branch2 = "theirs";
>> + if (repo_get_merge_bases(repo, parent1, parent2, &parent_bases) <
>> 0) {
>> + result->clean = -1;
>> + goto out;
>> + }
>> + merge_incore_recursive(&remerge_opt, parent_bases,
>> + parent1, parent2, &remerge_res);
>> + parent_bases = NULL; /* consumed by merge_incore_recursive */
>> + if (remerge_res.clean < 0) {
>> + result->clean = remerge_res.clean;
>> + goto out;
>> + }
>> +
>> + /* N: fresh merge of the (possibly rewritten) parents. */
>> + init_basic_merge_options(&new_merge_opt, repo);
>> + new_merge_opt.show_rename_progress = 0;
>> + new_merge_opt.branch1 = "ours";
>> + new_merge_opt.branch2 = "theirs";
>> + if (repo_get_merge_bases(repo, replayed_par1, replayed_par2,
>> + &replayed_bases) < 0) {
>> + result->clean = -1;
>> + goto out;
>> + }
>> + merge_incore_recursive(&new_merge_opt, replayed_bases,
>> + replayed_par1, replayed_par2, &new_merge_res);
>> + replayed_bases = NULL; /* consumed by merge_incore_recursive */
>> + if (new_merge_res.clean < 0) {
>> + result->clean = new_merge_res.clean;
>> + goto out;
>> + }
>> +
>> + /*
>> + * Outer non-recursive merge: base=R, side1=O (pickme), side2=N.
>> + */
>> + pickme_tree = repo_get_commit_tree(repo, pickme);
>> + ancestor_name = xstrfmt("auto-remerge of %s",
>> + oid_to_hex(&pickme->object.oid));
>> + merge_opt->ancestor = ancestor_name;
>> + merge_opt->branch1 = short_commit_name(repo, pickme);
>> + merge_opt->branch2 = "merge of replayed parents";
>> + merge_incore_nonrecursive(merge_opt,
>> + remerge_res.tree,
>> + pickme_tree,
>> + new_merge_res.tree,
>> + result);
>> + merge_opt->ancestor = NULL;
>> + merge_opt->branch1 = NULL;
>> + merge_opt->branch2 = NULL;
>> + if (!result->clean)
>> + goto out;
>> +
>> + parents = NULL;
>> + commit_list_insert(replayed_par2, &parents);
>> + commit_list_insert(replayed_par1, &parents);
>> + picked = create_commit(repo, result->tree, pickme, parents,
>> + REPLAY_MODE_PICK);
>> +
>> +out:
>> + free(ancestor_name);
>> + free_commit_list(parent_bases);
>> + free_commit_list(replayed_bases);
>> + merge_finalize(&remerge_opt, &remerge_res);
>> + merge_finalize(&new_merge_opt, &new_merge_res);
>> + return picked;
>> }
>> void replay_result_release(struct replay_result *result)
>> @@ -407,17 +550,63 @@ int replay_revisions(struct rev_info *revs,
>> merge_opt.show_rename_progress = 0;
>> last_commit = onto;
>> replayed_commits = kh_init_oid_map();
>> +
>> + /*
>> + * Seed the rewritten-commit map with each negative-side ("BOTTOM")
>> + * cmdline entry pointing at `onto`. This matters for merge replay:
>> + * a 2-parent merge whose first parent is the boundary (e.g. the
>> + * commit being reworded) must replay onto the rewritten boundary,
>> + * yet pick_merge_commit uses a self fallback so the second parent
>> + * (a side branch outside the rewrite range) is preserved as-is.
>> + * Pre-seeding the boundary disambiguates the two: in the map ->
>> + * rewritten, missing -> kept as-is.
>> + *
>> + * Only do this for the pick path; revert mode chains reverts
>> + * through last_commit and a pre-seeded boundary would short-circuit
>> + * that chain.
>> + */
>> + if (mode == REPLAY_MODE_PICK) {
>> + for (size_t i = 0; i < revs->cmdline.nr; i++) {
>> + struct rev_cmdline_entry *e = &revs->cmdline.rev[i];
>> + struct commit *boundary;
>> + khint_t pos;
>> + int hr;
>> +
>> + if (!(e->flags & BOTTOM))
>> + continue;
>> + boundary = lookup_commit_reference_gently(revs->repo,
>> + &e->item->oid, 1);
>> + if (!boundary)
>> + continue;
>> + pos = kh_put_oid_map(replayed_commits,
>> + boundary->object.oid, &hr);
>> + if (hr != 0)
>> + kh_value(replayed_commits, pos) = onto;
>> + }
>> + }
>> +
>> while ((commit = get_revision(revs))) {
>> const struct name_decoration *decoration;
>> khint_t pos;
>> int hr;
>> - if (commit->parents && commit->parents->next)
>> - die(_("replaying merge commits is not supported yet!"));
>> -
>> - last_commit = pick_regular_commit(revs->repo, commit,
>> replayed_commits,
>> - mode == REPLAY_MODE_REVERT ? last_commit :
>> onto,
>> - &merge_opt, &result, mode);
>> + if (commit->parents && commit->parents->next) {
>> + if (commit->parents->next->next) {
>> + ret = error(_("replaying octopus merges is not
>> supported"));
>> + goto out;
>> + }
>> + if (mode == REPLAY_MODE_REVERT) {
>> + ret = error(_("reverting merge commits is not
>> supported"));
>> + goto out;
>> + }
>> + last_commit = pick_merge_commit(revs->repo, commit,
>> + replayed_commits,
>> + &merge_opt, &result);
>> + } else {
>> + last_commit = pick_regular_commit(revs->repo, commit,
>> replayed_commits,
>> + mode == REPLAY_MODE_REVERT ?
>> last_commit : onto,
>> + &merge_opt, &result, mode);
>> + }
>> if (!last_commit)
>> break;
>> diff --git a/t/t3451-history-reword.sh b/t/t3451-history-reword.sh
>> index de7b357685..d103f866a2 100755
>> --- a/t/t3451-history-reword.sh
>> +++ b/t/t3451-history-reword.sh
>> @@ -201,12 +201,21 @@ test_expect_success 'can reword a merge commit' '
>> git switch - &&
>> git merge theirs &&
>> - # It is not possible to replay merge commits embedded in the
>> - # history (yet).
>> - test_must_fail git -c core.editor=false history reword HEAD~
>> 2>err &&
>> - test_grep "replaying merge commits is not supported yet" err &&
>> + # Reword a non-merge commit whose descendants include the
>> + # merge: replay carries the merge through.
>> + reword_with_message HEAD~ <<-EOF &&
>> + ours reworded
>> + EOF
>> + expect_graph <<-EOF &&
>> + * Merge tag ${SQ}theirs${SQ}
>> + |\\
>> + | * theirs
>> + * | ours reworded
>> + |/
>> + * base
>> + EOF
>> - # But it is possible to reword a merge commit directly.
>> + # And reword a merge commit directly.
>> reword_with_message HEAD <<-EOF &&
>> Reworded merge commit
>> EOF
>> @@ -214,7 +223,7 @@ test_expect_success 'can reword a merge commit' '
>> * Reworded merge commit
>> |\
>> | * theirs
>> - * | ours
>> + * | ours reworded
>> |/
>> * base
>> EOF
>> diff --git a/t/t3452-history-split.sh b/t/t3452-history-split.sh
>> index 8ed0cebb50..ad6309f98b 100755
>> --- a/t/t3452-history-split.sh
>> +++ b/t/t3452-history-split.sh
>> @@ -36,7 +36,7 @@ expect_tree_entries () {
>> test_cmp expect actual
>> }
>> -test_expect_success 'refuses to work with merge commits' '
>> +test_expect_success 'refuses to split a merge commit' '
>> test_when_finished "rm -rf repo" &&
>> git init repo &&
>> (
>> @@ -49,9 +49,7 @@ test_expect_success 'refuses to work with merge
>> commits' '
>> git switch - &&
>> git merge theirs &&
>> test_must_fail git history split HEAD 2>err &&
>> - test_grep "cannot split up merge commit" err &&
>> - test_must_fail git history split HEAD~ 2>err &&
>> - test_grep "replaying merge commits is not supported yet" err
>> + test_grep "cannot split up merge commit" err
>> )
>> '
>> diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
>> index 3353bc4a4d..368b1b0f9a 100755
>> --- a/t/t3650-replay-basics.sh
>> +++ b/t/t3650-replay-basics.sh
>> @@ -103,10 +103,48 @@ test_expect_success 'cannot advance target ...
>> ordering would be ill-defined' '
>> test_cmp expect actual
>> '
>> -test_expect_success 'replaying merge commits is not supported yet' '
>> - echo "fatal: replaying merge commits is not supported yet!"
>> >expect &&
>> - test_must_fail git replay --advance=main main..topic-with-merge
>> 2>actual &&
>> - test_cmp expect actual
>> +test_expect_success 'using replay to rebase a 2-parent merge' '
>> + # main..topic-with-merge contains a 2-parent merge (P) introduced
>> + # via test_merge. Use --ref-action=print so this test does not
>> + # mutate state for subsequent tests in this file.
>> + git replay --ref-action=print --onto main main..topic-with-merge
>> >result &&
>> + test_line_count = 1 result &&
>> +
>> + new_tip=$(cut -f 3 -d " " result) &&
>> +
>> + # Result is still a 2-parent merge.
>> + git cat-file -p $new_tip >cat &&
>> + grep -c "^parent " cat >count &&
>> + echo 2 >expect &&
>> + test_cmp expect count &&
>> +
>> + # Merge subject is preserved.
>> + echo P >expect &&
>> + git log -1 --format=%s $new_tip >actual &&
>> + test_cmp expect actual &&
>> +
>> + # The replayed merge sits on top of main: walking back via the
>> + # first-parent chain reaches main.
>> + git merge-base --is-ancestor main $new_tip
>> +'
>> +
>> +test_expect_success 'replaying an octopus merge is rejected' '
>> + # Build an octopus side-branch so the rest of the test state stays
>> + # untouched.
>> + test_when_finished "git update-ref -d refs/heads/octopus-tip" &&
>> + octopus_tip=$(git commit-tree -p topic4 -p topic1 -p topic3 \
>> + -m "octopus" $(git rev-parse topic4^{tree})) &&
>> + git update-ref refs/heads/octopus-tip "$octopus_tip" &&
>> +
>> + test_must_fail git replay --ref-action=print --onto main \
>> + topic4..octopus-tip 2>actual &&
>> + test_grep "octopus merges" actual
>> +'
>> +
>> +test_expect_success 'reverting a merge commit is rejected' '
>> + test_must_fail git replay --ref-action=print --revert=topic-with-
>> merge \
>> + topic4..topic-with-merge 2>actual &&
>> + test_grep "reverting merge commits" actual
>> '
>> test_expect_success 'using replay to rebase two branches, one on top
>> of other' '
>
^ permalink raw reply
* Re: [PATCH v2] rebase: ignore non-branch update-refs
From: Phillip Wood @ 2026-05-08 10:07 UTC (permalink / raw)
To: mail, git, Phillip Wood; +Cc: Derrick Stolee, Junio C Hamano
In-Reply-To: <20260508015817.86177-1-mail@abhinavg.net>
Hi Abhinav
Thanks for re-working the test - this looks great.
Phillip
On 08/05/2026 02:58, mail@abhinavg.net wrote:
> From: Abhinav Gupta <mail@abhinavg.net>
>
> The following Git configuration breaks git rebase --update-refs:
>
> [rebase]
> instructionFormat = %s%d
>
> The '%d' format requests all available decorations for a commit,
> filling the global decoration table with all of them,
> which --update-refs then uses to populate 'update-ref' instructions
> in the rebase todo list.
>
> Specifically, this results in the following instruction:
>
> update-ref HEAD
>
> The todo parser then rejects the instruction:
>
> error: update-ref requires a fully qualified refname e.g. refs/heads/HEAD
> error: invalid line 3: update-ref HEAD
>
> To fix, ignore decorations that are not local branches
> when scanning through the table.
>
> This matches the documented contract:
> it moves branch refs under refs/heads/
> and leaves display-only decorations (HEAD, tags, etc.) alone.
>
> Verification:
> A regression test that fails without this fix is included.
>
> Signed-off-by: Abhinav Gupta <mail@abhinavg.net>
> ---
> Updates:
> v2: incorporate suggestions to simplify the test
>
> sequencer.c | 10 ++++++++++
> t/t3404-rebase-interactive.sh | 18 ++++++++++++++++++
> 2 files changed, 28 insertions(+)
>
> diff --git a/sequencer.c b/sequencer.c
> index b7d8dca47f..25bcfc5da0 100644
> --- a/sequencer.c
> +++ b/sequencer.c
> @@ -6428,6 +6428,16 @@ static int add_decorations_to_list(const struct commit *commit,
> const char *path;
> size_t base_offset = ctx->buf->len;
>
> + /*
> + * The global decoration table may contain names loaded by
> + * a previous pretty format such as "%d".
> + * This will result in refs such as "HEAD" being present.
> + */
> + if (decoration->type != DECORATION_REF_LOCAL) {
> + decoration = decoration->next;
> + continue;
> + }
> +
> /*
> * If the branch is the current HEAD, then it will be
> * updated by the default rebase behavior.
> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
> index 3e44562afa..58b3bb0c27 100755
> --- a/t/t3404-rebase-interactive.sh
> +++ b/t/t3404-rebase-interactive.sh
> @@ -1960,6 +1960,24 @@ test_expect_success '--update-refs adds commands with --rebase-merges' '
> )
> '
>
> +test_expect_success '--update-refs ignores non-branch decorations' '
> + test_when_finished "git branch -D update-refs" &&
> + test_when_finished "git checkout primary" &&
> + git checkout -B update-refs no-conflict-branch &&
> + (
> + set_cat_todo_editor &&
> +
> + # rebase.instructionFormat=%d loads normal log decorations before
> + # --update-refs adds its branch placeholders so we must ignore
> + # all non-local decorations.
> + test_must_fail git -c rebase.instructionFormat="%s%d" \
> + rebase -i --update-refs HEAD^ >todo
> + ) &&
> + grep ^update-ref todo >actual &&
> + test_write_lines "update-ref refs/heads/no-conflict-branch" >expect &&
> + test_cmp expect actual
> +'
> +
> test_expect_success '--update-refs updates refs correctly' '
> git checkout -B update-refs no-conflict-branch &&
> git branch -f base HEAD~4 &&
>
> base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0
^ permalink raw reply
* Re: [PATCH v2 6/8] promisor-remote: trust known remotes matching acceptFromServerUrl
From: Toon Claes @ 2026-05-08 12:45 UTC (permalink / raw)
To: Christian Couder, git
Cc: Junio C Hamano, Patrick Steinhardt, Taylor Blau, Karthik Nayak,
Elijah Newren, Christian Couder, Christian Couder
In-Reply-To: <20260427124108.3524129-7-christian.couder@gmail.com>
Christian Couder <christian.couder@gmail.com> writes:
> A previous commit introduced the `promisor.acceptFromServerUrl` config
> variable along with the machinery to parse and validate the URL glob
> patterns and optional remote name prefixes it contains. However, these
> URL patterns are not yet tied into the client's acceptance logic.
>
> When a promisor remote is already configured locally, its fields (like
> authentication tokens) may occasionally need to be refreshed by the
> server. If `promisor.acceptFromServer` is set to the secure default
> ("None"), these updates are rejected, potentially causing future
> fetches to fail.
>
> To enable such targeted updates for trusted URLs, let's use the URL
> patterns from `promisor.acceptFromServerUrl` as an additional URL
> based allowlist.
>
> Concretely, let's check the advertised URLs against the URL glob
> patterns by introducing a new small helper function called
> url_matches_accept_list(), which iterates over the glob patterns and
> returns the first matching allowed_url entry (or NULL).
>
> The URL matching is done component by component: scheme and port are
> compared exactly, the host is matched with wildmatch() using the
> WM_PATHNAME flag (so '*' cannot cross the '/' boundary into the path),
> and the path is matched with wildmatch() without WM_PATHNAME (so '*'
> can still match multi-level paths). Before matching, the advertised
> URL is passed through url_normalize() so that case variations in the
> scheme/host, percent-encoding tricks, and ".." path segments cannot
> bypass the allowlist.
>
> Let's then use this helper at the tail of should_accept_remote() so
> that, when `accept == ACCEPT_NONE`, a known remote whose URL matches
> the allowlist is still accepted.
>
> To prepare for this new logic, let's also:
>
> - Add an 'accept_urls' parameter to should_accept_remote().
>
> - Replace the BUG() guard in the ACCEPT_KNOWN_URL case with an
> explicit 'if (accept == ACCEPT_KNOWN_URL) return' and a new
> BUG() guard in the ACCEPT_NONE case, so url_matches_accept_list()
> is only called in the ACCEPT_NONE case.
>
> - Call accept_from_server_url() from filter_promisor_remote()
> and relax its early return so that the function is entered when
> `accept_urls` has entries even if `accept == ACCEPT_NONE`.
>
> With this, many organizations may only need something like:
>
> git config set --global \
> promisor.acceptFromServerUrl "https://my-org.com/*"
>
> to accept only their own remotes. And if they need to accept additional
> remotes in some specific repos, they can also set:
>
> git config set promisor.acceptFromServer knownUrl
>
> and configure the additional remote manually only in the repos where
> they are needed.
>
> Let's then properly document `promisor.acceptFromServerUrl` in
> "promisor.adoc" as an additive security allowlist for known remotes,
> including the URL normalization behavior and the component-wise
> matching, and let's mention it in "gitprotocol-v2.adoc".
>
> Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
> ---
> Documentation/config/promisor.adoc | 52 ++++++++++++++
> Documentation/gitprotocol-v2.adoc | 9 +--
> promisor-remote.c | 98 +++++++++++++++++++++++++--
> t/t5710-promisor-remote-capability.sh | 71 +++++++++++++++++++
> 4 files changed, 220 insertions(+), 10 deletions(-)
>
> diff --git a/Documentation/config/promisor.adoc b/Documentation/config/promisor.adoc
> index b0fa43b839..efc066c3f2 100644
> --- a/Documentation/config/promisor.adoc
> +++ b/Documentation/config/promisor.adoc
> @@ -51,6 +51,58 @@ promisor.acceptFromServer::
> to "fetch" and "clone" requests from the client. Name and URL
> comparisons are case sensitive. See linkgit:gitprotocol-v2[5].
>
> +promisor.acceptFromServerUrl::
> + A glob pattern to specify which server-advertised URLs a
> + client is allowed to act on. When a URL matches, the client
> + will accept the advertised remote as a promisor remote and may
> + automatically accept field updates (such as authentication
> + tokens) from the server, even if `promisor.acceptFromServer`
> + is set to `none` (the default).
> ++
> +This option can appear multiple times in config files. An advertised
> +URL will be accepted if it matches _ANY_ glob pattern specified by
> +this option in _ANY_ config file read by Git.
> ++
> +Be _VERY_ careful with these patterns: `*` matches any sequence of
> +characters within the 'host' and 'path' parts of a URL (but cannot
> +cross part boundaries). An overly broad pattern is a major security
> +risk, as a matching URL allows a server to update fields (such as
> +authentication tokens) on known remotes without further confirmation.
> +To minimize security risks, follow these guidelines:
> ++
> +1. Start with a secure protocol scheme, like `https://` or `ssh://`.
> ++
> +2. Only allow domain names or paths where you control and trust _ALL_
> + the content. Be especially careful with shared hosting platforms
> + like `github.com` or `gitlab.com`. A broad pattern like
> + `https://gitlab.com/*` is dangerous because it trusts every
> + repository on the entire platform. Always restrict such patterns to
> + your specific organization or namespace (e.g.,
> + `https://gitlab.com/your-org/*`).
> ++
> +3. Never use globs at the end of domain names. For example,
> + `https://cdn.your-org.com/*` might be safe, but
> + `https://cdn.your-org.com*/*` is a major security risk because
> + the latter matches `https://cdn.your-org.com.hacker.net/repo`.
> ++
> +4. Be careful using globs at the beginning of domain names. While the
> + code ensures a `*` in the host cannot cross into the path, a
> + pattern like `https://*.example.com/*` will still match any
> + subdomain. This is extremely dangerous on shared hosting platforms
> + (e.g., `https://*.github.io/*` trusts every user's site on the
> + entire platform).
> ++
> +Before matching, both the advertised URL and the pattern are
> +normalized: the scheme and host are lowercased, percent-encoded
> +characters are decoded where possible, and path segments like `..`
> +are resolved. The port must also match exactly (e.g.,
> +`https://example.com:8080/*` will not match a URL advertised on
> +port 9999).
> ++
> +For the security implications of accepting a promisor remote, see the
> +documentation of `promisor.acceptFromServer`. For details on the
> +protocol, see linkgit:gitprotocol-v2[5].
> +
> promisor.checkFields::
> A comma or space separated list of additional remote related
> field names. A client checks if the values of these fields
> diff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc
> index befa697d21..2beb70595f 100644
> --- a/Documentation/gitprotocol-v2.adoc
> +++ b/Documentation/gitprotocol-v2.adoc
> @@ -866,10 +866,11 @@ the server advertised, the client shouldn't advertise the
>
> On the server side, the "promisor.advertise" and "promisor.sendFields"
> configuration options can be used to control what it advertises. On
> -the client side, the "promisor.acceptFromServer" configuration option
> -can be used to control what it accepts, and the "promisor.storeFields"
> -option, to control what it stores. See the documentation of these
> -configuration options in linkgit:git-config[1] for more information.
> +the client side, the "promisor.acceptFromServer" and
> +"promisor.acceptFromServerUrl" configuration options can be used to
> +control what it accepts, and the "promisor.storeFields" option, to
> +control what it stores. See the documentation of these configuration
> +options in linkgit:git-config[1] for more information.
>
> Note that in the future it would be nice if the "promisor-remote"
> protocol capability could be used by the server, when responding to
> diff --git a/promisor-remote.c b/promisor-remote.c
> index 3f3924f587..72d5b94bf7 100644
> --- a/promisor-remote.c
> +++ b/promisor-remote.c
> @@ -14,6 +14,7 @@
> #include "url.h"
> #include "urlmatch.h"
> #include "version.h"
> +#include "wildmatch.h"
>
> struct promisor_remote_config {
> struct promisor_remote *promisors;
> @@ -742,8 +743,82 @@ static void load_accept_from_server_url(struct repository *repo,
> }
> }
>
> +static bool match_one_url(const struct url_info *pi, const struct url_info *ui)
> +{
> + const char *pat = pi->url;
> + const char *url = ui->url;
> + char *p_str, *u_str;
> + bool res;
> +
> + /*
> + * Schemes must match exactly. They are case-folded by
> + * url_normalize(), so strncmp() suffices.
> + */
> + if (pi->scheme_len != ui->scheme_len || strncmp(pat, url, pi->scheme_len))
> + return false;
> +
> + /*
> + * Ports must match exactly. url_normalize() strips default
> + * ports (like 443 for https), so length and content
> + * comparisons are sufficient.
> + */
> + if (pi->port_len != ui->port_len ||
> + strncmp(pat + pi->port_off, url + ui->port_off, pi->port_len))
> + return false;
> +
> + /*
> + * Match host and path separately to prevent a '*' in the host
> + * portion of the pattern from matching across the '/'
> + * boundary into the path. Use WM_PATHNAME for the host so '*'
> + * cannot cross '/' there, and 0 for the path so '*' can still
> + * match multi-level paths.
> + */
Do we actually need WM_PATHNAME, because we only xstrndup() the host
part anyway?
> +
> + p_str = xstrndup(pat + pi->host_off, pi->host_len);
> + u_str = xstrndup(url + ui->host_off, ui->host_len);
> + res = !wildmatch(p_str, u_str, WM_PATHNAME);
> + free(p_str);
> + free(u_str);
> +
> + if (!res)
> + return false;
> +
> + p_str = xstrndup(pat + pi->path_off, pi->path_len);
> + u_str = xstrndup(url + ui->path_off, ui->path_len);
> + res = !wildmatch(p_str, u_str, 0);
> + free(p_str);
> + free(u_str);
Is it correct we intentionally do not compare the user and pass (at
`user_off` and `passwd_off`)? I assume so, because this allows the
server to update those?
> +
> + return res;
> +}
> +
> +static struct allowed_url *url_matches_accept_list(
> + struct string_list *accept_urls, const char *url)
> +{
> + struct string_list_item *item;
> + struct url_info url_info;
> +
> + url_info.url = url_normalize(url, &url_info);
> +
> + if (!url_info.url)
> + return NULL;
> +
> + for_each_string_list_item(item, accept_urls) {
> + struct allowed_url *allowed = item->util;
> +
> + if (match_one_url(&allowed->pattern_info, &url_info)) {
> + free(url_info.url);
> + return allowed;
> + }
> + }
> +
> + free(url_info.url);
> + return NULL;
> +}
> +
> static int should_accept_remote(enum accept_promisor accept,
> struct promisor_info *advertised,
> + struct string_list *accept_urls,
> struct string_list *config_info)
> {
> struct promisor_info *p;
> @@ -771,9 +846,6 @@ static int should_accept_remote(enum accept_promisor accept,
> if (accept == ACCEPT_KNOWN_NAME)
> return all_fields_match(advertised, config_info, p);
>
> - if (accept != ACCEPT_KNOWN_URL)
> - BUG("Unhandled 'enum accept_promisor' value '%d'", accept);
> -
> if (strcmp(p->url, remote_url)) {
> warning(_("known remote named '%s' but with URL '%s' instead of '%s', "
> "ignoring this remote"),
> @@ -781,7 +853,21 @@ static int should_accept_remote(enum accept_promisor accept,
> return 0;
> }
>
> - return all_fields_match(advertised, config_info, p);
> + if (accept == ACCEPT_KNOWN_URL)
> + return all_fields_match(advertised, config_info, p);
> +
> + if (accept != ACCEPT_NONE)
> + BUG("Unhandled 'enum accept_promisor' value '%d'", accept);
> +
> + /*
> + * Even if accept == ACCEPT_NONE, we MUST trust this known
> + * remote to update its token or other such fields if its URL
> + * matches the acceptFromServerUrl allowlist!
> + */
> + if (url_matches_accept_list(accept_urls, remote_url))
> + return all_fields_match(advertised, config_info, p);
I should verify in the following patches, but it seems to me only when
promisor.AcceptFromServer is set to None it will store the advertised
servers to the local .git/config, or not?
> +
> + return 0;
> }
>
> static int skip_field_name_prefix(const char *elem, const char *field_name, const char **value)
> @@ -991,7 +1077,7 @@ static void filter_promisor_remote(struct repository *repo,
> /* Load and validate the acceptFromServerUrl config */
> load_accept_from_server_url(repo, &accept_urls);
>
> - if (accept == ACCEPT_NONE)
> + if (accept == ACCEPT_NONE && !accept_urls.nr)
> return;
>
> /* Parse remote info received */
> @@ -1011,7 +1097,7 @@ static void filter_promisor_remote(struct repository *repo,
> string_list_sort(&config_info);
> }
>
> - if (should_accept_remote(accept, advertised, &config_info)) {
> + if (should_accept_remote(accept, advertised, &accept_urls, &config_info)) {
> if (!store_info)
> store_info = store_info_new(repo);
> if (promisor_store_advertised_fields(advertised, store_info))
> diff --git a/t/t5710-promisor-remote-capability.sh b/t/t5710-promisor-remote-capability.sh
> index 3b39505380..0659b2ac15 100755
> --- a/t/t5710-promisor-remote-capability.sh
> +++ b/t/t5710-promisor-remote-capability.sh
> @@ -387,6 +387,77 @@ test_expect_success "clone with 'KnownUrl' and empty url, so not advertised" '
> check_missing_objects server 1 "$oid"
> '
>
> +test_expect_success "clone with 'None' but URL allowlisted" '
> + git -C server config promisor.advertise true &&
> + test_when_finished "rm -rf client" &&
> +
> + GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
> + -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
> + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
> + -c promisor.acceptfromserver=None \
> + -c promisor.acceptFromServerUrl="$ENCODED_TRASH_DIRECTORY_URL/*" \
> + --no-local --filter="blob:limit=5k" server client &&
> +
> + # Check that the largest object is still missing on the server
> + check_missing_objects server 1 "$oid"
> +'
Why do some tests end with `initialize_server 1 "$oid"` and this one
not? Isn't it weird tests prepare for the next test?
> +
> +test_expect_success "clone with 'None' but URL not in allowlist" '
> + git -C server config promisor.advertise true &&
> + test_when_finished "rm -rf client" &&
> +
> + GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
> + -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
> + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
> + -c promisor.acceptfromserver=None \
> + -c promisor.acceptFromServerUrl="https://example.com/*" \
> + --no-local --filter="blob:limit=5k" server client &&
> +
> + # Check that the largest object is not missing on the server
> + check_missing_objects server 0 "" &&
> +
> + # Reinitialize server so that the largest object is missing again
> + initialize_server 1 "$oid"
> +'
> +
> +test_expect_success "clone with 'None' but URL allowlisted in one pattern out of two" '
> + git -C server config promisor.advertise true &&
> + test_when_finished "rm -rf client" &&
> +
> + GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
> + -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
> + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
> + -c promisor.acceptfromserver=None \
> + -c promisor.acceptFromServerUrl="https://example.com/*" \
> + -c promisor.acceptFromServerUrl="$ENCODED_TRASH_DIRECTORY_URL/*" \
> + --no-local --filter="blob:limit=5k" server client &&
> +
> + # Check that the largest object is still missing on the server
> + check_missing_objects server 1 "$oid"
> +'
> +
> +test_expect_success "clone with 'None', URL allowlisted, but client has different URL" '
> + git -C server config promisor.advertise true &&
> + test_when_finished "rm -rf client" &&
> +
> + # The client configures "lop" with a different URL (serverTwo) than
> + # what the server advertises (lop). Even though the advertised URL
> + # matches the allowlist, the remote is rejected because the
> + # configured URL does not match the advertised one.
> + GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
> + -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
> + -c remote.lop.url="$TRASH_DIRECTORY_URL/serverTwo" \
> + -c promisor.acceptfromserver=None \
> + -c promisor.acceptFromServerUrl="$ENCODED_TRASH_DIRECTORY_URL/*" \
> + --no-local --filter="blob:limit=5k" server client &&
> +
> + # Check that the largest object is not missing on the server
> + check_missing_objects server 0 "" &&
> +
> + # Reinitialize server so that the largest object is missing again
> + initialize_server 1 "$oid"
> +'
> +
> test_expect_success "clone with invalid promisor.acceptFromServerUrl" '
> git -C server config promisor.advertise true &&
> test_when_finished "rm -rf client" &&
> --
> 2.54.0.19.gb68b9497aa
>
>
--
Cheers,
Toon
^ permalink raw reply
* Re: [BUG] "git diff --word-diff" gives a diff while they are only space changes
From: Johannes Sixt @ 2026-05-08 12:48 UTC (permalink / raw)
To: Vincent Lefevre; +Cc: git
In-Reply-To: <20260506010927.GE5260@qaa.vinc17.org>
Am 06.05.26 um 03:09 schrieb Vincent Lefevre:
> Consider the following two 5-line files:
>
> file1:
>
> 1
> 2
> 3
> 2
> 4
>
> file2:
>
> 1
> 2
> 3
> 2
> 4
>
> On these files, "git diff --word-diff file1 file2" gives
>
> --- a/file1
> +++ b/file2
> @@ -1,5 +1,5 @@
> 1
> [-2-]
> [-3-]
> 2
> {+3+}
> {+ 2+}
> 4
>
> instead of
>
> --- a/file1
> +++ b/file2
> @@ -1,5 +1,5 @@
> 1
> 2
> 3
> 2
> 4
>
> (e.g. as output by GNU wdiff 1.2.2).
This is expected behavior.
git diff --word-diff is not agnostic to whitespace; if you drop
--word-diff, you see the line-diff that the word-diff is based on. If
you want whitespace-agnostic word-diff, you have to add -w.
-- Hannes
^ permalink raw reply
* [PATCH v3 0/6] mingw: stop using nedmalloc
From: Johannes Schindelin via GitGitGadget @ 2026-05-08 12:50 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Johannes Schindelin
In-Reply-To: <pull.2104.v2.git.1778169613.gitgitgadget@gmail.com>
Git for Windows' SDK wants to update GCC to v16. Since it is used in the CI
builds also of the git/git repository, it is crucial that GCC can compile
even the latter all right, but currently it does not, see
https://github.com/git-for-windows/git-sdk-64/actions/runs/25244795074.
Git for Windows switched away from nedmalloc to mimalloc a long time ago,
but recent benchmarks across Windows, macOS, and Linux (see
https://github.com/git-for-windows/git/pull/6231) show no measurable benefit
from mimalloc over the platforms' default allocators, so rather than
upstreaming the mimalloc support, I will drop it from Git for Windows
entirely.
This series therefore disables nedmalloc for MINGW builds and removes the
vendored-in nedmalloc from Git's source code; my earlier sketch in
https://lore.kernel.org/git/00fd3145-b3d2-ddab-466d-d06fd27298ec@gmx.de/ had
the opposite ordering only because it assumed mimalloc would land first.
Since that's not going to happen, it's best to move forward with this, so
that the CI builds can switch to using GCC 16 (and the current Git for
Windows SDK) on Windows.
The patches that remove the vendored sources have a slightly unusual shape:
the Git mailing list rejects messages over 100kB and
compat/nedmalloc/malloc.c.h alone is ~196kB of source, so the deletion of
that file is split at section boundaries into three commits, each
comfortably under the cap. The intention (as documented by the last three
commit messages) is for them to be squashed by the Git maintainer before
merging.
Changes since v2:
* Reworded the last 4 patches as recommended by Junio, in preparation for
squashing them on his end.
Changes since v1:
* Also remove nedmalloc from the CMake and Meson configurations in the
first patch.
* Add follow-up patches that drop the nedmalloc build-system plumbing and
source files.
Johannes Schindelin (6):
mingw: stop using nedmalloc
mingw: drop the build-system plumbing for nedmalloc
mingw: remove the vendored compat/nedmalloc/ subtree
to be squashed into 3/6 (chunk 1 of 3)
to be squashed into 3/6 (chunk 2 of 3)
to be squashed into 3/6 (chunk 3 of 3)
Makefile | 17 -
compat/nedmalloc/License.txt | 23 -
compat/nedmalloc/Readme.txt | 136 -
compat/nedmalloc/malloc.c.h | 5761 ---------------------------
compat/nedmalloc/nedmalloc.c | 954 -----
compat/nedmalloc/nedmalloc.h | 180 -
config.mak.uname | 4 -
contrib/buildsystems/CMakeLists.txt | 3 +-
contrib/vscode/init.sh | 1 -
meson.build | 2 -
10 files changed, 1 insertion(+), 7080 deletions(-)
delete mode 100644 compat/nedmalloc/License.txt
delete mode 100644 compat/nedmalloc/Readme.txt
delete mode 100644 compat/nedmalloc/malloc.c.h
delete mode 100644 compat/nedmalloc/nedmalloc.c
delete mode 100644 compat/nedmalloc/nedmalloc.h
base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2104%2Fdscho%2Fstop-using-nedmalloc-with-mingw-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2104/dscho/stop-using-nedmalloc-with-mingw-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/2104
Range-diff vs v2:
1: d547877ae3 = 1: d547877ae3 mingw: stop using nedmalloc
2: 7b5daae659 = 2: 7b5daae659 mingw: drop the build-system plumbing for nedmalloc
3: a5ccd4dae3 ! 3: fc7f5cae10 mingw: drop the small nedmalloc auxiliary files
@@ Metadata
Author: Johannes Schindelin <johannes.schindelin@gmx.de>
## Commit message ##
- mingw: drop the small nedmalloc auxiliary files
+ mingw: remove the vendored compat/nedmalloc/ subtree
- The Git mailing list rejects messages over 100 KB, and
- compat/nedmalloc/malloc.c.h alone is ~196 KB of source, so the
- deletion of that file has to be split across several commits.
- Carving the four smaller files (the LICENSE, the README, and the
- nedmalloc.{c,h} wrappers) into a commit of their own gives the
- malloc.c.h-chunk commits that follow enough headroom to comfortably
- fit under the cap once email-envelope overhead is accounted for.
+ The previous two commits stopped opting into nedmalloc on Windows
+ and stripped out the build-system plumbing that referenced it; the
+ compat/nedmalloc/ subtree now has no callers and no consumers in
+ the build, so retire it from the tree.
+
+ Logically this is a single deletion of compat/nedmalloc/ in its
+ entirety: License.txt, Readme.txt, nedmalloc.{c,h}, and the bulk of
+ the subtree, malloc.c.h. Unfortunately malloc.c.h alone is roughly
+ 196 KB while the Git mailing list rejects messages over 100 KB, so
+ the deletion is artificially split across four commits cut at file
+ or section-banner boundaries: this commit (the smaller auxiliary
+ files) plus three chunks of malloc.c.h cut at its own top-level
+ section banners ("Overlaid data structures" and "System
+ allocation"). The split is purely a mailing-list accommodation,
+ not a logical separation; the three follow-up patches in this
+ series carry "to be squashed into 3/6" subjects so they can be
+ folded back into this commit at integration time, per Junio's
+ suggestion in
+ <https://lore.kernel.org/git/xmqqfr42fw30.fsf@gitster.g/>.
Assisted-by: Opus 4.7
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
4: 95682cc2c6 ! 4: 9085f00910 mingw: drop the first chunk of compat/nedmalloc/malloc.c.h
@@ Metadata
Author: Johannes Schindelin <johannes.schindelin@gmx.de>
## Commit message ##
- mingw: drop the first chunk of compat/nedmalloc/malloc.c.h
-
- The vendored malloc.c.h is around 196 KB of source, which does not
- fit in a single mailing-list-sized message; the deletion is split
- across three commits cut at the file's own top-level section
- banners. This first chunk ends just before the "Overlaid data
- structures" banner.
+ to be squashed into 3/6 (chunk 1 of 3)
Assisted-by: Opus 4.7
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
5: a605b58586 ! 5: fa28d50d18 mingw: drop the second chunk of compat/nedmalloc/malloc.c.h
@@ Metadata
Author: Johannes Schindelin <johannes.schindelin@gmx.de>
## Commit message ##
- mingw: drop the second chunk of compat/nedmalloc/malloc.c.h
-
- This is the second of three chunks splitting the malloc.c.h
- deletion (see the preceding commit for the rationale); it picks up
- at the "Overlaid data structures" banner and ends just before the
- "System allocation" banner.
+ to be squashed into 3/6 (chunk 2 of 3)
Assisted-by: Opus 4.7
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
6: 90d4137018 ! 6: 6e0fd5182f mingw: drop the rest of compat/nedmalloc/malloc.c.h
@@ Metadata
Author: Johannes Schindelin <johannes.schindelin@gmx.de>
## Commit message ##
- mingw: drop the rest of compat/nedmalloc/malloc.c.h
-
- The third and final chunk removes the remainder of malloc.c.h, from
- the "System allocation" banner to the end of the file, and the file
- itself. With this commit the compat/nedmalloc/ directory is fully
- retired from the tree.
+ to be squashed into 3/6 (chunk 3 of 3)
Assisted-by: Opus 4.7
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
--
gitgitgadget
^ permalink raw reply
* [PATCH v3 1/6] mingw: stop using nedmalloc
From: Johannes Schindelin via GitGitGadget @ 2026-05-08 12:50 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2104.v3.git.1778244661.gitgitgadget@gmail.com>
From: Johannes Schindelin <johannes.schindelin@gmx.de>
The vendored nedmalloc allocator under compat/nedmalloc/ has been
unmaintained upstream for a very long time: the original repository at
https://github.com/ned14/nedmalloc received its last commit on July 5,
2014, and was archived (made read-only) by its owner on March 15, 2019.
Our copy has been carried forward unchanged ever since.
The Git for Windows commit that introduced mimalloc as a replacement
on Windows ("mingw: use mimalloc", 2019-06-24, present in the Git for
Windows branch thicket but not upstream) already observed at that time
that nedmalloc had ceased to see any updates for several years.
This came to a head when the Git for Windows SDK upgraded to GCC 16:
the `add_segment()` function in `compat/nedmalloc/malloc.c.h` declares
`int nfences = 0` and only references it inside an `assert()`, which
GCC 16 now flags as `-Wunused-but-set-variable`. Combined with the
`-Werror` enabled by `DEVELOPER=1`, this turns into a hard build
failure:
compat/nedmalloc/malloc.c.h: In function 'add_segment':
compat/nedmalloc/malloc.c.h:3897:7: error: variable 'nfences' set but not used [-Werror=unused-but-set-variable=]
3897 | int nfences = 0;
| ^~~~~~~
cc1.exe: all warnings being treated as errors
The same source built without complaint under GCC 15.2.0; the
regression was bisected to the SDK package update at
https://github.com/git-for-windows/git-sdk-64/commit/188d93dd455
(`mingw-w64-x86_64-gcc 15.2.0-14 -> 16.1.0-1`), with the failing CI
run captured at
https://github.com/git-for-windows/git-sdk-64/actions/runs/25244795074.
Rather than patch the unmaintained vendored sources to silence the
warning, stop opting into nedmalloc altogether on Windows. The
platform allocator is what every non-MINGW build already uses, and a
fresh build of git.git's master against a minimal Git for Windows SDK
upgraded to GCC 16 completes successfully.
The compat/nedmalloc/ subtree itself is removed by subsequent commits
in this series.
Assisted-by: Opus 4.7
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
config.mak.uname | 3 ---
contrib/buildsystems/CMakeLists.txt | 3 +--
meson.build | 1 -
3 files changed, 1 insertion(+), 6 deletions(-)
diff --git a/config.mak.uname b/config.mak.uname
index 5feb582558..3636b98238 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -758,9 +758,6 @@ ifeq ($(uname_S),MINGW)
HAVE_LIBCHARSET_H = YesPlease
USE_GETTEXT_SCHEME = fallthrough
USE_LIBPCRE = YesPlease
- ifneq (CLANGARM64,$(MSYSTEM))
- USE_NED_ALLOCATOR = YesPlease
- endif
ifeq (/mingw64,$(subst 32,64,$(subst clangarm,mingw,$(prefix))))
# Move system config into top-level /etc/
ETC_GITCONFIG = ../etc/gitconfig
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index 81b4306e72..9d6b98ecb6 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -255,7 +255,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
add_compile_definitions(HAVE_ALLOCA_H NO_POSIX_GOODIES NATIVE_CRLF NO_UNIX_SOCKETS WIN32
_CONSOLE DETECT_MSYS_TTY STRIP_EXTENSION=".exe" NO_SYMLINK_HEAD UNRELIABLE_FSTAT
NOGDI OBJECT_CREATION_MODE=1 __USE_MINGW_ANSI_STDIO=0
- USE_NED_ALLOCATOR OVERRIDE_STRDUP MMAP_PREVENTS_DELETE USE_WIN32_MMAP
+ OVERRIDE_STRDUP MMAP_PREVENTS_DELETE USE_WIN32_MMAP
HAVE_WPGMPTR ENSURE_MSYSTEM_IS_SET HAVE_RTLGENRANDOM)
list(APPEND compat_SOURCES
compat/mingw.c
@@ -267,7 +267,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
compat/win32/syslog.c
compat/win32/trace2_win32_process_info.c
compat/win32/dirent.c
- compat/nedmalloc/nedmalloc.c
compat/strdup.c)
set(NO_UNIX_SOCKETS 1)
diff --git a/meson.build b/meson.build
index 11488623bf..e896bc15a1 100644
--- a/meson.build
+++ b/meson.build
@@ -1285,7 +1285,6 @@ elif host_machine.system() == 'windows'
'compat/win32/pthread.c',
'compat/win32/syslog.c',
'compat/win32mmap.c',
- 'compat/nedmalloc/nedmalloc.c',
]
libgit_c_args += [
--
gitgitgadget
^ permalink raw reply related
* [PATCH v3 2/6] mingw: drop the build-system plumbing for nedmalloc
From: Johannes Schindelin via GitGitGadget @ 2026-05-08 12:50 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2104.v3.git.1778244661.gitgitgadget@gmail.com>
From: Johannes Schindelin <johannes.schindelin@gmx.de>
With the previous commit removing every opt-in, the build-system
plumbing for nedmalloc has nothing left to switch on. Remove it so
that the upcoming deletion of the compat/nedmalloc/ tree is a pure
file removal.
Assisted-by: Opus 4.7
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
Makefile | 17 -----------------
config.mak.uname | 1 -
contrib/vscode/init.sh | 1 -
meson.build | 1 -
4 files changed, 20 deletions(-)
diff --git a/Makefile b/Makefile
index cedc234173..2f490d402e 100644
--- a/Makefile
+++ b/Makefile
@@ -283,13 +283,9 @@ include shared.mak
# Define SKIP_DASHED_BUILT_INS if you do not need the dashed versions of the
# built-ins to be linked/copied at all.
#
-# Define USE_NED_ALLOCATOR if you want to replace the platforms default
-# memory allocators with the nedmalloc allocator written by Niall Douglas.
-#
# Define OVERRIDE_STRDUP to override the libc version of strdup(3).
# This is necessary when using a custom allocator in order to avoid
# crashes due to allocation and free working on different 'heaps'.
-# It's defined automatically if USE_NED_ALLOCATOR is set.
#
# Define NO_REGEX if your C library lacks regex support with REG_STARTEND
# feature.
@@ -1511,7 +1507,6 @@ BUILTIN_OBJS += builtin/write-tree.o
# upstream unnecessarily (making merging in future changes easier).
THIRD_PARTY_SOURCES += compat/inet_ntop.c
THIRD_PARTY_SOURCES += compat/inet_pton.c
-THIRD_PARTY_SOURCES += compat/nedmalloc/%
THIRD_PARTY_SOURCES += compat/obstack.%
THIRD_PARTY_SOURCES += compat/poll/%
THIRD_PARTY_SOURCES += compat/regex/%
@@ -2267,12 +2262,6 @@ ifdef NATIVE_CRLF
BASIC_CFLAGS += -DNATIVE_CRLF
endif
-ifdef USE_NED_ALLOCATOR
- COMPAT_CFLAGS += -Icompat/nedmalloc
- COMPAT_OBJS += compat/nedmalloc/nedmalloc.o
- OVERRIDE_STRDUP = YesPlease
-endif
-
ifdef OVERRIDE_STRDUP
COMPAT_CFLAGS += -DOVERRIDE_STRDUP
COMPAT_OBJS += compat/strdup.o
@@ -2983,12 +2972,6 @@ compat/regex/regex.sp compat/regex/regex.o: EXTRA_CPPFLAGS = \
-DGAWK -DNO_MBSUPPORT
endif
-ifdef USE_NED_ALLOCATOR
-compat/nedmalloc/nedmalloc.sp compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \
- -DNDEBUG -DREPLACE_SYSTEM_ALLOCATOR
-compat/nedmalloc/nedmalloc.sp: SP_EXTRA_FLAGS += -Wno-non-pointer-null
-endif
-
headless-git.o: compat/win32/headless.c GIT-CFLAGS
$(QUIET_CC)$(CC) $(ALL_CFLAGS) $(COMPAT_CFLAGS) \
-fno-stack-protector -o $@ -c -Wall -Wwrite-strings $<
diff --git a/config.mak.uname b/config.mak.uname
index 3636b98238..25345e02c6 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -491,7 +491,6 @@ ifeq ($(uname_S),Windows)
USE_WIN32_IPC = YesPlease
USE_WIN32_MMAP = YesPlease
MMAP_PREVENTS_DELETE = UnfortunatelyYes
- # USE_NED_ALLOCATOR = YesPlease
UNRELIABLE_FSTAT = UnfortunatelyYes
OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
NO_REGEX = YesPlease
diff --git a/contrib/vscode/init.sh b/contrib/vscode/init.sh
index f2d61bb0e6..3d58f7307a 100755
--- a/contrib/vscode/init.sh
+++ b/contrib/vscode/init.sh
@@ -202,7 +202,6 @@ cat >.vscode/settings.json.new <<\EOF ||
"\\bUSE_STDEV\\b",
"\\Wchar *\\*\\W*utfs\\W",
"cURL's",
- "nedmalloc'ed",
"ntifs\\.h",
],
}
diff --git a/meson.build b/meson.build
index e896bc15a1..0e00c6c57e 100644
--- a/meson.build
+++ b/meson.build
@@ -698,7 +698,6 @@ third_party_excludes = [
':!contrib',
':!compat/inet_ntop.c',
':!compat/inet_pton.c',
- ':!compat/nedmalloc',
':!compat/obstack.*',
':!compat/poll',
':!compat/regex',
--
gitgitgadget
^ permalink raw reply related
* [PATCH v3 3/6] mingw: remove the vendored compat/nedmalloc/ subtree
From: Johannes Schindelin via GitGitGadget @ 2026-05-08 12:50 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2104.v3.git.1778244661.gitgitgadget@gmail.com>
From: Johannes Schindelin <johannes.schindelin@gmx.de>
The previous two commits stopped opting into nedmalloc on Windows
and stripped out the build-system plumbing that referenced it; the
compat/nedmalloc/ subtree now has no callers and no consumers in
the build, so retire it from the tree.
Logically this is a single deletion of compat/nedmalloc/ in its
entirety: License.txt, Readme.txt, nedmalloc.{c,h}, and the bulk of
the subtree, malloc.c.h. Unfortunately malloc.c.h alone is roughly
196 KB while the Git mailing list rejects messages over 100 KB, so
the deletion is artificially split across four commits cut at file
or section-banner boundaries: this commit (the smaller auxiliary
files) plus three chunks of malloc.c.h cut at its own top-level
section banners ("Overlaid data structures" and "System
allocation"). The split is purely a mailing-list accommodation,
not a logical separation; the three follow-up patches in this
series carry "to be squashed into 3/6" subjects so they can be
folded back into this commit at integration time, per Junio's
suggestion in
<https://lore.kernel.org/git/xmqqfr42fw30.fsf@gitster.g/>.
Assisted-by: Opus 4.7
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
compat/nedmalloc/License.txt | 23 -
compat/nedmalloc/Readme.txt | 136 -----
compat/nedmalloc/nedmalloc.c | 954 -----------------------------------
compat/nedmalloc/nedmalloc.h | 180 -------
4 files changed, 1293 deletions(-)
delete mode 100644 compat/nedmalloc/License.txt
delete mode 100644 compat/nedmalloc/Readme.txt
delete mode 100644 compat/nedmalloc/nedmalloc.c
delete mode 100644 compat/nedmalloc/nedmalloc.h
diff --git a/compat/nedmalloc/License.txt b/compat/nedmalloc/License.txt
deleted file mode 100644
index 36b7cd93cd..0000000000
--- a/compat/nedmalloc/License.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-Boost Software License - Version 1.0 - August 17th, 2003
-
-Permission is hereby granted, free of charge, to any person or organization
-obtaining a copy of the software and accompanying documentation covered by
-this license (the "Software") to use, reproduce, display, distribute,
-execute, and transmit the Software, and to prepare derivative works of the
-Software, and to permit third-parties to whom the Software is furnished to
-do so, all subject to the following:
-
-The copyright notices in the Software and this entire statement, including
-the above license grant, this restriction and the following disclaimer,
-must be included in all copies of the Software, in whole or in part, and
-all derivative works of the Software, unless such copies or derivative
-works are solely in the form of machine-executable object code generated by
-a source language processor.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
-SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
-FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
-ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-DEALINGS IN THE SOFTWARE.
diff --git a/compat/nedmalloc/Readme.txt b/compat/nedmalloc/Readme.txt
deleted file mode 100644
index 07cbf50c0f..0000000000
--- a/compat/nedmalloc/Readme.txt
+++ /dev/null
@@ -1,136 +0,0 @@
-nedalloc v1.05 15th June 2008:
--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
-
-by Niall Douglas (http://www.nedprod.com/programs/portable/nedmalloc/)
-
-Enclosed is nedalloc, an alternative malloc implementation for multiple
-threads without lock contention based on dlmalloc v2.8.4. It is more
-or less a newer implementation of ptmalloc2, the standard allocator in
-Linux (which is based on dlmalloc v2.7.0) but also contains a per-thread
-cache for maximum CPU scalability.
-
-It is licensed under the Boost Software License which basically means
-you can do anything you like with it. This does not apply to the malloc.c.h
-file which remains copyright to others.
-
-It has been tested on win32 (x86), win64 (x64), Linux (x64), FreeBSD (x64)
-and Apple MacOS X (x86). It works very well on all of these and is very
-significantly faster than the system allocator on all of these platforms.
-
-By literally dropping in this allocator as a replacement for your system
-allocator, you can see real world improvements of up to three times in normal
-code!
-
-To use:
--=-=-=-
-Drop in nedmalloc.h, nedmalloc.c and malloc.c.h into your project.
-Configure using the instructions in nedmalloc.h. Run and enjoy.
-
-To test, compile test.c. It will run a comparison between your system
-allocator and nedalloc and tell you how much faster nedalloc is. It also
-serves as an example of usage.
-
-Notes:
--=-=-=
-If you want the very latest version of this allocator, get it from the
-TnFOX SVN repository at svn://svn.berlios.de/viewcvs/tnfox/trunk/src/nedmalloc
-
-Because of how nedalloc allocates an mspace per thread, it can cause
-severe bloating of memory usage under certain allocation patterns.
-You can substantially reduce this wastage by setting MAXTHREADSINPOOL
-or the threads parameter to nedcreatepool() to a fraction of the number of
-threads which would normally be in a pool at once. This will reduce
-bloating at the cost of an increase in lock contention. If allocated size
-is less than THREADCACHEMAX, locking is avoided 90-99% of the time and
-if most of your allocations are below this value, you can safely set
-MAXTHREADSINPOOL to one.
-
-You will suffer memory leakage unless you call neddisablethreadcache()
-per pool for every thread which exits. This is because nedalloc cannot
-portably know when a thread exits and thus when its thread cache can
-be returned for use by other code. Don't forget pool zero, the system pool.
-
-For C++ type allocation patterns (where the same sizes of memory are
-regularly allocated and deallocated as objects are created and destroyed),
-the threadcache always benefits performance. If however your allocation
-patterns are different, searching the threadcache may significantly slow
-down your code - as a rule of thumb, if cache utilisation is below 80%
-(see the source for neddisablethreadcache() for how to enable debug
-printing in release mode) then you should disable the thread cache for
-that thread. You can compile out the threadcache code by setting
-THREADCACHEMAX to zero.
-
-Speed comparisons:
--=-=-=-=-=-=-=-=-=
-See Benchmarks.xls for details.
-
-The enclosed test.c can do two things: it can be a torture test or a speed
-test. The speed test is designed to be a representative synthetic
-memory allocator test. It works by randomly mixing allocations with frees
-with half of the allocation sizes being a two power multiple less than
-512 bytes (to mimic C++ stack instantiated objects) and the other half
-being a simple random value less than 16Kb.
-
-The real world code results are from Tn's TestIO benchmark. This is a
-heavily multithreaded and memory intensive benchmark with a lot of branching
-and other stuff modern processors don't like so much. As you'll note, the
-test doesn't show the benefits of the threadcache mostly due to the saturation
-of the memory bus being the limiting factor.
-
-ChangeLog:
--=-=-=-=-=
-v1.05 15th June 2008:
- * { 1042 } Added error check for TLSSET() and TLSFREE() macros. Thanks to
-Markus Elfring for reporting this.
- * { 1043 } Fixed a segfault when freeing memory allocated using
-nedindependent_comalloc(). Thanks to Pavel Vozenilek for reporting this.
-
-v1.04 14th July 2007:
- * Fixed a bug with the new optimised implementation that failed to lock
-on a realloc under certain conditions.
- * Fixed lack of thread synchronisation in InitPool() causing pool corruption
- * Fixed a memory leak of thread cache contents on disabling. Thanks to Earl
-Chew for reporting this.
- * Added a sanity check for freed blocks being valid.
- * Reworked test.c into being a torture test.
- * Fixed GCC assembler optimisation misspecification
-
-v1.04alpha_svn915 7th October 2006:
- * Fixed failure to unlock thread cache list if allocating a new list failed.
-Thanks to Dmitry Chichkov for reporting this. Further thanks to Aleksey Sanin.
- * Fixed realloc(0, <size>) segfaulting. Thanks to Dmitry Chichkov for
-reporting this.
- * Made config defines #ifndef so they can be overridden by the build system.
-Thanks to Aleksey Sanin for suggesting this.
- * Fixed deadlock in nedprealloc() due to unnecessary locking of preferred
-thread mspace when mspace_realloc() always uses the original block's mspace
-anyway. Thanks to Aleksey Sanin for reporting this.
- * Made some speed improvements by hacking mspace_malloc() to no longer lock
-its mspace, thus allowing the recursive mutex implementation to be removed
-with an associated speed increase. Thanks to Aleksey Sanin for suggesting this.
- * Fixed a bug where allocating mspaces overran its max limit. Thanks to
-Aleksey Sanin for reporting this.
-
-v1.03 10th July 2006:
- * Fixed memory corruption bug in threadcache code which only appeared with >4
-threads and in heavy use of the threadcache.
-
-v1.02 15th May 2006:
- * Integrated dlmalloc v2.8.4, fixing the win32 memory release problem and
-improving performance still further. Speed is now up to twice the speed of v1.01
-(average is 67% faster).
- * Fixed win32 critical section implementation. Thanks to Pavel Kuznetsov
-for reporting this.
- * Wasn't locking mspace if all mspaces were locked. Thanks to Pavel Kuznetsov
-for reporting this.
- * Added Apple Mac OS X support.
-
-v1.01 24th February 2006:
- * Fixed multiprocessor scaling problems by removing sources of cache sloshing
- * Earl Chew <earl_chew <at> agilent <dot> com> sent patches for the following:
- 1. size2binidx() wasn't working for default code path (non x86)
- 2. Fixed failure to release mspace lock under certain circumstances which
- caused a deadlock
-
-v1.00 1st January 2006:
- * First release
diff --git a/compat/nedmalloc/nedmalloc.c b/compat/nedmalloc/nedmalloc.c
deleted file mode 100644
index 145255da43..0000000000
--- a/compat/nedmalloc/nedmalloc.c
+++ /dev/null
@@ -1,954 +0,0 @@
-/* Alternative malloc implementation for multiple threads without
-lock contention based on dlmalloc. (C) 2005-2006 Niall Douglas
-
-Boost Software License - Version 1.0 - August 17th, 2003
-
-Permission is hereby granted, free of charge, to any person or organization
-obtaining a copy of the software and accompanying documentation covered by
-this license (the "Software") to use, reproduce, display, distribute,
-execute, and transmit the Software, and to prepare derivative works of the
-Software, and to permit third-parties to whom the Software is furnished to
-do so, all subject to the following:
-
-The copyright notices in the Software and this entire statement, including
-the above license grant, this restriction and the following disclaimer,
-must be included in all copies of the Software, in whole or in part, and
-all derivative works of the Software, unless such copies or derivative
-works are solely in the form of machine-executable object code generated by
-a source language processor.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
-SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
-FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
-ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-DEALINGS IN THE SOFTWARE.
-*/
-
-#ifdef _MSC_VER
-/* Enable full aliasing on MSVC */
-/*#pragma optimize("a", on)*/
-#endif
-
-#pragma GCC diagnostic ignored "-Wunused-parameter"
-
-/*#define FULLSANITYCHECKS*/
-
-#include "nedmalloc.h"
-#if defined(WIN32)
- #include <malloc.h>
-#endif
-#define MSPACES 1
-#define ONLY_MSPACES 1
-#ifndef USE_LOCKS
- #define USE_LOCKS 1
-#endif
-#define FOOTERS 1 /* Need to enable footers so frees lock the right mspace */
-#undef DEBUG /* dlmalloc wants DEBUG either 0 or 1 */
-#ifdef _DEBUG
- #define DEBUG 1
-#else
- #define DEBUG 0
-#endif
-#ifdef NDEBUG /* Disable assert checking on release builds */
- #undef DEBUG
-#endif
-/* The default of 64Kb means we spend too much time kernel-side */
-#ifndef DEFAULT_GRANULARITY
-#define DEFAULT_GRANULARITY (1*1024*1024)
-#endif
-/*#define USE_SPIN_LOCKS 0*/
-
-
-/*#define FORCEINLINE*/
-#include "malloc.c.h"
-#ifdef NDEBUG /* Disable assert checking on release builds */
- #undef DEBUG
-#endif
-
-/* The maximum concurrent threads in a pool possible */
-#ifndef MAXTHREADSINPOOL
-#define MAXTHREADSINPOOL 16
-#endif
-/* The maximum number of threadcaches which can be allocated */
-#ifndef THREADCACHEMAXCACHES
-#define THREADCACHEMAXCACHES 256
-#endif
-/* The maximum size to be allocated from the thread cache */
-#ifndef THREADCACHEMAX
-#define THREADCACHEMAX 8192
-#endif
-#if 0
-/* The number of cache entries for finer grained bins. This is (topbitpos(THREADCACHEMAX)-4)*2 */
-#define THREADCACHEMAXBINS ((13-4)*2)
-#else
-/* The number of cache entries. This is (topbitpos(THREADCACHEMAX)-4) */
-#define THREADCACHEMAXBINS (13-4)
-#endif
-/* Point at which the free space in a thread cache is garbage collected */
-#ifndef THREADCACHEMAXFREESPACE
-#define THREADCACHEMAXFREESPACE (512*1024)
-#endif
-
-
-#ifdef WIN32
- #define TLSVAR DWORD
- #define TLSALLOC(k) (*(k)=TlsAlloc(), TLS_OUT_OF_INDEXES==*(k))
- #define TLSFREE(k) (!TlsFree(k))
- #define TLSGET(k) TlsGetValue(k)
- #define TLSSET(k, a) (!TlsSetValue(k, a))
- #ifdef DEBUG
-static LPVOID ChkedTlsGetValue(DWORD idx)
-{
- LPVOID ret=TlsGetValue(idx);
- assert(S_OK==GetLastError());
- return ret;
-}
- #undef TLSGET
- #define TLSGET(k) ChkedTlsGetValue(k)
- #endif
-#else
- #define TLSVAR pthread_key_t
- #define TLSALLOC(k) pthread_key_create(k, 0)
- #define TLSFREE(k) pthread_key_delete(k)
- #define TLSGET(k) pthread_getspecific(k)
- #define TLSSET(k, a) pthread_setspecific(k, a)
-#endif
-
-#if 0
-/* Only enable if testing with valgrind. Causes misoperation */
-#define mspace_malloc(p, s) malloc(s)
-#define mspace_realloc(p, m, s) realloc(m, s)
-#define mspace_calloc(p, n, s) calloc(n, s)
-#define mspace_free(p, m) free(m)
-#endif
-
-
-#if defined(__cplusplus)
-#if !defined(NO_NED_NAMESPACE)
-namespace nedalloc {
-#else
-extern "C" {
-#endif
-#endif
-
-size_t nedblksize(void *mem) THROWSPEC
-{
-#if 0
- /* Only enable if testing with valgrind. Causes misoperation */
- return THREADCACHEMAX;
-#else
- if(mem)
- {
- mchunkptr p=mem2chunk(mem);
- assert(cinuse(p)); /* If this fails, someone tried to free a block twice */
- if(cinuse(p))
- return chunksize(p)-overhead_for(p);
- }
- return 0;
-#endif
-}
-
-void nedsetvalue(void *v) THROWSPEC { nedpsetvalue(0, v); }
-void * nedmalloc(size_t size) THROWSPEC { return nedpmalloc(0, size); }
-void * nedcalloc(size_t no, size_t size) THROWSPEC { return nedpcalloc(0, no, size); }
-void * nedrealloc(void *mem, size_t size) THROWSPEC { return nedprealloc(0, mem, size); }
-void nedfree(void *mem) THROWSPEC { nedpfree(0, mem); }
-void * nedmemalign(size_t alignment, size_t bytes) THROWSPEC { return nedpmemalign(0, alignment, bytes); }
-#if !NO_MALLINFO
-struct mallinfo nedmallinfo(void) THROWSPEC { return nedpmallinfo(0); }
-#endif
-int nedmallopt(int parno, int value) THROWSPEC { return nedpmallopt(0, parno, value); }
-int nedmalloc_trim(size_t pad) THROWSPEC { return nedpmalloc_trim(0, pad); }
-void nedmalloc_stats(void) THROWSPEC { nedpmalloc_stats(0); }
-size_t nedmalloc_footprint(void) THROWSPEC { return nedpmalloc_footprint(0); }
-void **nedindependent_calloc(size_t elemsno, size_t elemsize, void **chunks) THROWSPEC { return nedpindependent_calloc(0, elemsno, elemsize, chunks); }
-void **nedindependent_comalloc(size_t elems, size_t *sizes, void **chunks) THROWSPEC { return nedpindependent_comalloc(0, elems, sizes, chunks); }
-
-struct threadcacheblk_t;
-typedef struct threadcacheblk_t threadcacheblk;
-struct threadcacheblk_t
-{ /* Keep less than 16 bytes on 32 bit systems and 32 bytes on 64 bit systems */
-#ifdef FULLSANITYCHECKS
- unsigned int magic;
-#endif
- unsigned int lastUsed, size;
- threadcacheblk *next, *prev;
-};
-typedef struct threadcache_t
-{
-#ifdef FULLSANITYCHECKS
- unsigned int magic1;
-#endif
- int mymspace; /* Last mspace entry this thread used */
- long threadid;
- unsigned int mallocs, frees, successes;
- size_t freeInCache; /* How much free space is stored in this cache */
- threadcacheblk *bins[(THREADCACHEMAXBINS+1)*2];
-#ifdef FULLSANITYCHECKS
- unsigned int magic2;
-#endif
-} threadcache;
-struct nedpool_t
-{
- MLOCK_T mutex;
- void *uservalue;
- int threads; /* Max entries in m to use */
- threadcache *caches[THREADCACHEMAXCACHES];
- TLSVAR mycache; /* Thread cache for this thread. 0 for unset, negative for use mspace-1 directly, otherwise is cache-1 */
- mstate m[MAXTHREADSINPOOL+1]; /* mspace entries for this pool */
-};
-static nedpool syspool;
-
-static FORCEINLINE unsigned int size2binidx(size_t _size) THROWSPEC
-{ /* 8=1000 16=10000 20=10100 24=11000 32=100000 48=110000 4096=1000000000000 */
- unsigned int topbit, size=(unsigned int)(_size>>4);
- /* 16=1 20=1 24=1 32=10 48=11 64=100 96=110 128=1000 4096=100000000 */
-
-#if defined(__GNUC__)
- topbit = sizeof(size)*__CHAR_BIT__ - 1 - __builtin_clz(size);
-#elif defined(_MSC_VER) && _MSC_VER>=1300
- {
- unsigned long bsrTopBit;
-
- _BitScanReverse(&bsrTopBit, size);
-
- topbit = bsrTopBit;
- }
-#else
-#if 0
- union {
- unsigned asInt[2];
- double asDouble;
- };
- int n;
-
- asDouble = (double)size + 0.5;
- topbit = (asInt[!FOX_BIGENDIAN] >> 20) - 1023;
-#else
- {
- unsigned int x=size;
- x = x | (x >> 1);
- x = x | (x >> 2);
- x = x | (x >> 4);
- x = x | (x >> 8);
- x = x | (x >>16);
- x = ~x;
- x = x - ((x >> 1) & 0x55555555);
- x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
- x = (x + (x >> 4)) & 0x0F0F0F0F;
- x = x + (x << 8);
- x = x + (x << 16);
- topbit=31 - (x >> 24);
- }
-#endif
-#endif
- return topbit;
-}
-
-
-#ifdef FULLSANITYCHECKS
-static void tcsanitycheck(threadcacheblk **ptr) THROWSPEC
-{
- assert((ptr[0] && ptr[1]) || (!ptr[0] && !ptr[1]));
- if(ptr[0] && ptr[1])
- {
- assert(nedblksize(ptr[0])>=sizeof(threadcacheblk));
- assert(nedblksize(ptr[1])>=sizeof(threadcacheblk));
- assert(*(unsigned int *) "NEDN"==ptr[0]->magic);
- assert(*(unsigned int *) "NEDN"==ptr[1]->magic);
- assert(!ptr[0]->prev);
- assert(!ptr[1]->next);
- if(ptr[0]==ptr[1])
- {
- assert(!ptr[0]->next);
- assert(!ptr[1]->prev);
- }
- }
-}
-static void tcfullsanitycheck(threadcache *tc) THROWSPEC
-{
- threadcacheblk **tcbptr=tc->bins;
- int n;
- for(n=0; n<=THREADCACHEMAXBINS; n++, tcbptr+=2)
- {
- threadcacheblk *b, *ob=0;
- tcsanitycheck(tcbptr);
- for(b=tcbptr[0]; b; ob=b, b=b->next)
- {
- assert(*(unsigned int *) "NEDN"==b->magic);
- assert(!ob || ob->next==b);
- assert(!ob || b->prev==ob);
- }
- }
-}
-#endif
-
-static NOINLINE void RemoveCacheEntries(nedpool *p, threadcache *tc, unsigned int age) THROWSPEC
-{
-#ifdef FULLSANITYCHECKS
- tcfullsanitycheck(tc);
-#endif
- if(tc->freeInCache)
- {
- threadcacheblk **tcbptr=tc->bins;
- int n;
- for(n=0; n<=THREADCACHEMAXBINS; n++, tcbptr+=2)
- {
- threadcacheblk **tcb=tcbptr+1; /* come from oldest end of list */
- /*tcsanitycheck(tcbptr);*/
- for(; *tcb && tc->frees-(*tcb)->lastUsed>=age; )
- {
- threadcacheblk *f=*tcb;
- size_t blksize=f->size; /*nedblksize(f);*/
- assert(blksize<=nedblksize(f));
- assert(blksize);
-#ifdef FULLSANITYCHECKS
- assert(*(unsigned int *) "NEDN"==(*tcb)->magic);
-#endif
- *tcb=(*tcb)->prev;
- if(*tcb)
- (*tcb)->next=0;
- else
- *tcbptr=0;
- tc->freeInCache-=blksize;
- assert((long) tc->freeInCache>=0);
- mspace_free(0, f);
- /*tcsanitycheck(tcbptr);*/
- }
- }
- }
-#ifdef FULLSANITYCHECKS
- tcfullsanitycheck(tc);
-#endif
-}
-static void DestroyCaches(nedpool *p) THROWSPEC
-{
- {
- threadcache *tc;
- int n;
- for(n=0; n<THREADCACHEMAXCACHES; n++)
- {
- if((tc=p->caches[n]))
- {
- tc->frees++;
- RemoveCacheEntries(p, tc, 0);
- assert(!tc->freeInCache);
- tc->mymspace=-1;
- tc->threadid=0;
- mspace_free(0, tc);
- p->caches[n]=0;
- }
- }
- }
-}
-
-static NOINLINE threadcache *AllocCache(nedpool *p) THROWSPEC
-{
- threadcache *tc=0;
- int n, end;
- ACQUIRE_LOCK(&p->mutex);
- for(n=0; n<THREADCACHEMAXCACHES && p->caches[n]; n++);
- if(THREADCACHEMAXCACHES==n)
- { /* List exhausted, so disable for this thread */
- RELEASE_LOCK(&p->mutex);
- return 0;
- }
- tc=p->caches[n]=(threadcache *) mspace_calloc(p->m[0], 1, sizeof(threadcache));
- if(!tc)
- {
- RELEASE_LOCK(&p->mutex);
- return 0;
- }
-#ifdef FULLSANITYCHECKS
- tc->magic1=*(unsigned int *)"NEDMALC1";
- tc->magic2=*(unsigned int *)"NEDMALC2";
-#endif
- tc->threadid=(long)(size_t)CURRENT_THREAD;
- for(end=0; p->m[end]; end++);
- tc->mymspace=tc->threadid % end;
- RELEASE_LOCK(&p->mutex);
- if(TLSSET(p->mycache, (void *)(size_t)(n+1))) abort();
- return tc;
-}
-
-static void *threadcache_malloc(nedpool *p, threadcache *tc, size_t *size) THROWSPEC
-{
- void *ret=0;
- unsigned int bestsize;
- unsigned int idx=size2binidx(*size);
- size_t blksize=0;
- threadcacheblk *blk, **binsptr;
-#ifdef FULLSANITYCHECKS
- tcfullsanitycheck(tc);
-#endif
- /* Calculate best fit bin size */
- bestsize=1<<(idx+4);
-#if 0
- /* Finer grained bin fit */
- idx<<=1;
- if(*size>bestsize)
- {
- idx++;
- bestsize+=bestsize>>1;
- }
- if(*size>bestsize)
- {
- idx++;
- bestsize=1<<(4+(idx>>1));
- }
-#else
- if(*size>bestsize)
- {
- idx++;
- bestsize<<=1;
- }
-#endif
- assert(bestsize>=*size);
- if(*size<bestsize) *size=bestsize;
- assert(*size<=THREADCACHEMAX);
- assert(idx<=THREADCACHEMAXBINS);
- binsptr=&tc->bins[idx*2];
- /* Try to match close, but move up a bin if necessary */
- blk=*binsptr;
- if(!blk || blk->size<*size)
- { /* Bump it up a bin */
- if(idx<THREADCACHEMAXBINS)
- {
- idx++;
- binsptr+=2;
- blk=*binsptr;
- }
- }
- if(blk)
- {
- blksize=blk->size; /*nedblksize(blk);*/
- assert(nedblksize(blk)>=blksize);
- assert(blksize>=*size);
- if(blk->next)
- blk->next->prev=0;
- *binsptr=blk->next;
- if(!*binsptr)
- binsptr[1]=0;
-#ifdef FULLSANITYCHECKS
- blk->magic=0;
-#endif
- assert(binsptr[0]!=blk && binsptr[1]!=blk);
- assert(nedblksize(blk)>=sizeof(threadcacheblk) && nedblksize(blk)<=THREADCACHEMAX+CHUNK_OVERHEAD);
- /*printf("malloc: %p, %p, %p, %lu\n", p, tc, blk, (long) size);*/
- ret=(void *) blk;
- }
- ++tc->mallocs;
- if(ret)
- {
- assert(blksize>=*size);
- ++tc->successes;
- tc->freeInCache-=blksize;
- assert((long) tc->freeInCache>=0);
- }
-#if defined(DEBUG) && 0
- if(!(tc->mallocs & 0xfff))
- {
- printf("*** threadcache=%u, mallocs=%u (%f), free=%u (%f), freeInCache=%u\n", (unsigned int) tc->threadid, tc->mallocs,
- (float) tc->successes/tc->mallocs, tc->frees, (float) tc->successes/tc->frees, (unsigned int) tc->freeInCache);
- }
-#endif
-#ifdef FULLSANITYCHECKS
- tcfullsanitycheck(tc);
-#endif
- return ret;
-}
-static NOINLINE void ReleaseFreeInCache(nedpool *p, threadcache *tc, int mymspace) THROWSPEC
-{
- unsigned int age=THREADCACHEMAXFREESPACE/8192;
- /*ACQUIRE_LOCK(&p->m[mymspace]->mutex);*/
- while(age && tc->freeInCache>=THREADCACHEMAXFREESPACE)
- {
- RemoveCacheEntries(p, tc, age);
- /*printf("*** Removing cache entries older than %u (%u)\n", age, (unsigned int) tc->freeInCache);*/
- age>>=1;
- }
- /*RELEASE_LOCK(&p->m[mymspace]->mutex);*/
-}
-static void threadcache_free(nedpool *p, threadcache *tc, int mymspace, void *mem, size_t size) THROWSPEC
-{
- unsigned int bestsize;
- unsigned int idx=size2binidx(size);
- threadcacheblk **binsptr, *tck=(threadcacheblk *) mem;
- assert(size>=sizeof(threadcacheblk) && size<=THREADCACHEMAX+CHUNK_OVERHEAD);
-#ifdef DEBUG
- { /* Make sure this is a valid memory block */
- mchunkptr p = mem2chunk(mem);
- mstate fm = get_mstate_for(p);
- if (!ok_magic(fm)) {
- USAGE_ERROR_ACTION(fm, p);
- return;
- }
- }
-#endif
-#ifdef FULLSANITYCHECKS
- tcfullsanitycheck(tc);
-#endif
- /* Calculate best fit bin size */
- bestsize=1<<(idx+4);
-#if 0
- /* Finer grained bin fit */
- idx<<=1;
- if(size>bestsize)
- {
- unsigned int biggerbestsize=bestsize+bestsize<<1;
- if(size>=biggerbestsize)
- {
- idx++;
- bestsize=biggerbestsize;
- }
- }
-#endif
- if(bestsize!=size) /* dlmalloc can round up, so we round down to preserve indexing */
- size=bestsize;
- binsptr=&tc->bins[idx*2];
- assert(idx<=THREADCACHEMAXBINS);
- if(tck==*binsptr)
- {
- fprintf(stderr, "Attempt to free already freed memory block %p - aborting!\n", (void *)tck);
- abort();
- }
-#ifdef FULLSANITYCHECKS
- tck->magic=*(unsigned int *) "NEDN";
-#endif
- tck->lastUsed=++tc->frees;
- tck->size=(unsigned int) size;
- tck->next=*binsptr;
- tck->prev=0;
- if(tck->next)
- tck->next->prev=tck;
- else
- binsptr[1]=tck;
- assert(!*binsptr || (*binsptr)->size==tck->size);
- *binsptr=tck;
- assert(tck==tc->bins[idx*2]);
- assert(tc->bins[idx*2+1]==tck || binsptr[0]->next->prev==tck);
- /*printf("free: %p, %p, %p, %lu\n", p, tc, mem, (long) size);*/
- tc->freeInCache+=size;
-#ifdef FULLSANITYCHECKS
- tcfullsanitycheck(tc);
-#endif
-#if 1
- if(tc->freeInCache>=THREADCACHEMAXFREESPACE)
- ReleaseFreeInCache(p, tc, mymspace);
-#endif
-}
-
-
-
-
-static NOINLINE int InitPool(nedpool *p, size_t capacity, int threads) THROWSPEC
-{ /* threads is -1 for system pool */
- ensure_initialization();
- ACQUIRE_MALLOC_GLOBAL_LOCK();
- if(p->threads) goto done;
- if(INITIAL_LOCK(&p->mutex)) goto err;
- if(TLSALLOC(&p->mycache)) goto err;
- if(!(p->m[0]=(mstate) create_mspace(capacity, 1))) goto err;
- p->m[0]->extp=p;
- p->threads=(threads<1 || threads>MAXTHREADSINPOOL) ? MAXTHREADSINPOOL : threads;
-done:
- RELEASE_MALLOC_GLOBAL_LOCK();
- return 1;
-err:
- if(threads<0)
- abort(); /* If you can't allocate for system pool, we're screwed */
- DestroyCaches(p);
- if(p->m[0])
- {
- destroy_mspace(p->m[0]);
- p->m[0]=0;
- }
- if(p->mycache)
- {
- if(TLSFREE(p->mycache)) abort();
- p->mycache=0;
- }
- RELEASE_MALLOC_GLOBAL_LOCK();
- return 0;
-}
-static NOINLINE mstate FindMSpace(nedpool *p, threadcache *tc, int *lastUsed, size_t size) THROWSPEC
-{ /* Gets called when thread's last used mspace is in use. The strategy
- is to run through the list of all available mspaces looking for an
- unlocked one and if we fail, we create a new one so long as we don't
- exceed p->threads */
- int n, end;
- for(n=end=*lastUsed+1; p->m[n]; end=++n)
- {
- if(TRY_LOCK(&p->m[n]->mutex)) goto found;
- }
- for(n=0; n<*lastUsed && p->m[n]; n++)
- {
- if(TRY_LOCK(&p->m[n]->mutex)) goto found;
- }
- if(end<p->threads)
- {
- mstate temp;
- if(!(temp=(mstate) create_mspace(size, 1)))
- goto badexit;
- /* Now we're ready to modify the lists, we lock */
- ACQUIRE_LOCK(&p->mutex);
- while(p->m[end] && end<p->threads)
- end++;
- if(end>=p->threads)
- { /* Drat, must destroy it now */
- RELEASE_LOCK(&p->mutex);
- destroy_mspace((mspace) temp);
- goto badexit;
- }
- /* We really want to make sure this goes into memory now but we
- have to be careful of breaking aliasing rules, so write it twice */
- {
- volatile struct malloc_state **_m=(volatile struct malloc_state **) &p->m[end];
- *_m=(p->m[end]=temp);
- }
- ACQUIRE_LOCK(&p->m[end]->mutex);
- /*printf("Created mspace idx %d\n", end);*/
- RELEASE_LOCK(&p->mutex);
- n=end;
- goto found;
- }
- /* Let it lock on the last one it used */
-badexit:
- ACQUIRE_LOCK(&p->m[*lastUsed]->mutex);
- return p->m[*lastUsed];
-found:
- *lastUsed=n;
- if(tc)
- tc->mymspace=n;
- else
- {
- if(TLSSET(p->mycache, (void *)(size_t)(-(n+1)))) abort();
- }
- return p->m[n];
-}
-
-nedpool *nedcreatepool(size_t capacity, int threads) THROWSPEC
-{
- nedpool *ret;
- if(!(ret=(nedpool *) nedpcalloc(0, 1, sizeof(nedpool)))) return 0;
- if(!InitPool(ret, capacity, threads))
- {
- nedpfree(0, ret);
- return 0;
- }
- return ret;
-}
-void neddestroypool(nedpool *p) THROWSPEC
-{
- int n;
- ACQUIRE_LOCK(&p->mutex);
- DestroyCaches(p);
- for(n=0; p->m[n]; n++)
- {
- destroy_mspace(p->m[n]);
- p->m[n]=0;
- }
- RELEASE_LOCK(&p->mutex);
- if(TLSFREE(p->mycache)) abort();
- nedpfree(0, p);
-}
-
-void nedpsetvalue(nedpool *p, void *v) THROWSPEC
-{
- if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); }
- p->uservalue=v;
-}
-void *nedgetvalue(nedpool **p, void *mem) THROWSPEC
-{
- nedpool *np=0;
- mchunkptr mcp=mem2chunk(mem);
- mstate fm;
- if(!(is_aligned(chunk2mem(mcp))) && mcp->head != FENCEPOST_HEAD) return 0;
- if(!cinuse(mcp)) return 0;
- if(!next_pinuse(mcp)) return 0;
- if(!is_mmapped(mcp) && !pinuse(mcp))
- {
- if(next_chunk(prev_chunk(mcp))!=mcp) return 0;
- }
- fm=get_mstate_for(mcp);
- if(!ok_magic(fm)) return 0;
- if(!ok_address(fm, mcp)) return 0;
- if(!fm->extp) return 0;
- np=(nedpool *) fm->extp;
- if(p) *p=np;
- return np->uservalue;
-}
-
-void neddisablethreadcache(nedpool *p) THROWSPEC
-{
- int mycache;
- if(!p)
- {
- p=&syspool;
- if(!syspool.threads) InitPool(&syspool, 0, -1);
- }
- mycache=(int)(size_t) TLSGET(p->mycache);
- if(!mycache)
- { /* Set to mspace 0 */
- if(TLSSET(p->mycache, (void *)-1)) abort();
- }
- else if(mycache>0)
- { /* Set to last used mspace */
- threadcache *tc=p->caches[mycache-1];
-#if defined(DEBUG)
- printf("Threadcache utilisation: %lf%% in cache with %lf%% lost to other threads\n",
- 100.0*tc->successes/tc->mallocs, 100.0*((double) tc->mallocs-tc->frees)/tc->mallocs);
-#endif
- if(TLSSET(p->mycache, (void *)(size_t)(-tc->mymspace))) abort();
- tc->frees++;
- RemoveCacheEntries(p, tc, 0);
- assert(!tc->freeInCache);
- tc->mymspace=-1;
- tc->threadid=0;
- mspace_free(0, p->caches[mycache-1]);
- p->caches[mycache-1]=0;
- }
-}
-
-#define GETMSPACE(m,p,tc,ms,s,action) \
- do \
- { \
- mstate m = GetMSpace((p),(tc),(ms),(s)); \
- action; \
- RELEASE_LOCK(&m->mutex); \
- } while (0)
-
-static FORCEINLINE mstate GetMSpace(nedpool *p, threadcache *tc, int mymspace, size_t size) THROWSPEC
-{ /* Returns a locked and ready for use mspace */
- mstate m=p->m[mymspace];
- assert(m);
- if(!TRY_LOCK(&p->m[mymspace]->mutex)) m=FindMSpace(p, tc, &mymspace, size);\
- /*assert(IS_LOCKED(&p->m[mymspace]->mutex));*/
- return m;
-}
-static FORCEINLINE void GetThreadCache(nedpool **p, threadcache **tc, int *mymspace, size_t *size) THROWSPEC
-{
- int mycache;
- if(size && *size<sizeof(threadcacheblk)) *size=sizeof(threadcacheblk);
- if(!*p)
- {
- *p=&syspool;
- if(!syspool.threads) InitPool(&syspool, 0, -1);
- }
- mycache=(int)(size_t) TLSGET((*p)->mycache);
- if(mycache>0)
- {
- *tc=(*p)->caches[mycache-1];
- *mymspace=(*tc)->mymspace;
- }
- else if(!mycache)
- {
- *tc=AllocCache(*p);
- if(!*tc)
- { /* Disable */
- if(TLSSET((*p)->mycache, (void *)-1)) abort();
- *mymspace=0;
- }
- else
- *mymspace=(*tc)->mymspace;
- }
- else
- {
- *tc=0;
- *mymspace=-mycache-1;
- }
- assert(*mymspace>=0);
- assert((long)(size_t)CURRENT_THREAD==(*tc)->threadid);
-#ifdef FULLSANITYCHECKS
- if(*tc)
- {
- if(*(unsigned int *)"NEDMALC1"!=(*tc)->magic1 || *(unsigned int *)"NEDMALC2"!=(*tc)->magic2)
- {
- abort();
- }
- }
-#endif
-}
-
-void * nedpmalloc(nedpool *p, size_t size) THROWSPEC
-{
- void *ret=0;
- threadcache *tc;
- int mymspace;
- GetThreadCache(&p, &tc, &mymspace, &size);
-#if THREADCACHEMAX
- if(tc && size<=THREADCACHEMAX)
- { /* Use the thread cache */
- ret=threadcache_malloc(p, tc, &size);
- }
-#endif
- if(!ret)
- { /* Use this thread's mspace */
- GETMSPACE(m, p, tc, mymspace, size,
- ret=mspace_malloc(m, size));
- }
- return ret;
-}
-void * nedpcalloc(nedpool *p, size_t no, size_t size) THROWSPEC
-{
- size_t rsize=size*no;
- void *ret=0;
- threadcache *tc;
- int mymspace;
- GetThreadCache(&p, &tc, &mymspace, &rsize);
-#if THREADCACHEMAX
- if(tc && rsize<=THREADCACHEMAX)
- { /* Use the thread cache */
- if((ret=threadcache_malloc(p, tc, &rsize)))
- memset(ret, 0, rsize);
- }
-#endif
- if(!ret)
- { /* Use this thread's mspace */
- GETMSPACE(m, p, tc, mymspace, rsize,
- ret=mspace_calloc(m, 1, rsize));
- }
- return ret;
-}
-void * nedprealloc(nedpool *p, void *mem, size_t size) THROWSPEC
-{
- void *ret=0;
- threadcache *tc;
- int mymspace;
- if(!mem) return nedpmalloc(p, size);
- GetThreadCache(&p, &tc, &mymspace, &size);
-#if THREADCACHEMAX
- if(tc && size && size<=THREADCACHEMAX)
- { /* Use the thread cache */
- size_t memsize=nedblksize(mem);
- assert(memsize);
- if((ret=threadcache_malloc(p, tc, &size)))
- {
- memcpy(ret, mem, memsize<size ? memsize : size);
- if(memsize<=THREADCACHEMAX)
- threadcache_free(p, tc, mymspace, mem, memsize);
- else
- mspace_free(0, mem);
- }
- }
-#endif
- if(!ret)
- { /* Reallocs always happen in the mspace they happened in, so skip
- locking the preferred mspace for this thread */
- ret=mspace_realloc(0, mem, size);
- }
- return ret;
-}
-void nedpfree(nedpool *p, void *mem) THROWSPEC
-{ /* Frees always happen in the mspace they happened in, so skip
- locking the preferred mspace for this thread */
- threadcache *tc;
- int mymspace;
- size_t memsize;
- assert(mem);
- GetThreadCache(&p, &tc, &mymspace, 0);
-#if THREADCACHEMAX
- memsize=nedblksize(mem);
- assert(memsize);
- if(mem && tc && memsize<=(THREADCACHEMAX+CHUNK_OVERHEAD))
- threadcache_free(p, tc, mymspace, mem, memsize);
- else
-#endif
- mspace_free(0, mem);
-}
-void * nedpmemalign(nedpool *p, size_t alignment, size_t bytes) THROWSPEC
-{
- void *ret;
- threadcache *tc;
- int mymspace;
- GetThreadCache(&p, &tc, &mymspace, &bytes);
- { /* Use this thread's mspace */
- GETMSPACE(m, p, tc, mymspace, bytes,
- ret=mspace_memalign(m, alignment, bytes));
- }
- return ret;
-}
-#if !NO_MALLINFO
-struct mallinfo nedpmallinfo(nedpool *p) THROWSPEC
-{
- int n;
- struct mallinfo ret={0};
- if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); }
- for(n=0; p->m[n]; n++)
- {
- struct mallinfo t=mspace_mallinfo(p->m[n]);
- ret.arena+=t.arena;
- ret.ordblks+=t.ordblks;
- ret.hblkhd+=t.hblkhd;
- ret.usmblks+=t.usmblks;
- ret.uordblks+=t.uordblks;
- ret.fordblks+=t.fordblks;
- ret.keepcost+=t.keepcost;
- }
- return ret;
-}
-#endif
-int nedpmallopt(nedpool *p, int parno, int value) THROWSPEC
-{
- return mspace_mallopt(parno, value);
-}
-int nedpmalloc_trim(nedpool *p, size_t pad) THROWSPEC
-{
- int n, ret=0;
- if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); }
- for(n=0; p->m[n]; n++)
- {
- ret+=mspace_trim(p->m[n], pad);
- }
- return ret;
-}
-void nedpmalloc_stats(nedpool *p) THROWSPEC
-{
- int n;
- if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); }
- for(n=0; p->m[n]; n++)
- {
- mspace_malloc_stats(p->m[n]);
- }
-}
-size_t nedpmalloc_footprint(nedpool *p) THROWSPEC
-{
- size_t ret=0;
- int n;
- if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); }
- for(n=0; p->m[n]; n++)
- {
- ret+=mspace_footprint(p->m[n]);
- }
- return ret;
-}
-void **nedpindependent_calloc(nedpool *p, size_t elemsno, size_t elemsize, void **chunks) THROWSPEC
-{
- void **ret;
- threadcache *tc;
- int mymspace;
- GetThreadCache(&p, &tc, &mymspace, &elemsize);
- GETMSPACE(m, p, tc, mymspace, elemsno*elemsize,
- ret=mspace_independent_calloc(m, elemsno, elemsize, chunks));
- return ret;
-}
-void **nedpindependent_comalloc(nedpool *p, size_t elems, size_t *sizes, void **chunks) THROWSPEC
-{
- void **ret;
- threadcache *tc;
- int mymspace;
- size_t i, *adjustedsizes=(size_t *) alloca(elems*sizeof(size_t));
- if(!adjustedsizes) return 0;
- for(i=0; i<elems; i++)
- adjustedsizes[i]=sizes[i]<sizeof(threadcacheblk) ? sizeof(threadcacheblk) : sizes[i];
- GetThreadCache(&p, &tc, &mymspace, 0);
- GETMSPACE(m, p, tc, mymspace, 0,
- ret=mspace_independent_comalloc(m, elems, adjustedsizes, chunks));
- return ret;
-}
-
-#if defined(__cplusplus)
-}
-#endif
diff --git a/compat/nedmalloc/nedmalloc.h b/compat/nedmalloc/nedmalloc.h
deleted file mode 100644
index f960e66063..0000000000
--- a/compat/nedmalloc/nedmalloc.h
+++ /dev/null
@@ -1,180 +0,0 @@
-/* nedalloc, an alternative malloc implementation for multiple threads without
-lock contention based on dlmalloc v2.8.3. (C) 2005 Niall Douglas
-
-Boost Software License - Version 1.0 - August 17th, 2003
-
-Permission is hereby granted, free of charge, to any person or organization
-obtaining a copy of the software and accompanying documentation covered by
-this license (the "Software") to use, reproduce, display, distribute,
-execute, and transmit the Software, and to prepare derivative works of the
-Software, and to permit third-parties to whom the Software is furnished to
-do so, all subject to the following:
-
-The copyright notices in the Software and this entire statement, including
-the above license grant, this restriction and the following disclaimer,
-must be included in all copies of the Software, in whole or in part, and
-all derivative works of the Software, unless such copies or derivative
-works are solely in the form of machine-executable object code generated by
-a source language processor.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
-SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
-FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
-ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-DEALINGS IN THE SOFTWARE.
-*/
-
-#ifndef NEDMALLOC_H
-#define NEDMALLOC_H
-
-
-/* See malloc.c.h for what each function does.
-
-REPLACE_SYSTEM_ALLOCATOR causes nedalloc's functions to be called malloc,
-free etc. instead of nedmalloc, nedfree etc. You may or may not want this.
-
-NO_NED_NAMESPACE prevents the functions from being defined in the nedalloc
-namespace when in C++ (uses the global namespace instead).
-
-EXTSPEC can be defined to be __declspec(dllexport) or
-__attribute__ ((visibility("default"))) or whatever you like. It defaults
-to extern.
-
-USE_LOCKS can be 2 if you want to define your own MLOCK_T, INITIAL_LOCK,
-ACQUIRE_LOCK, RELEASE_LOCK, TRY_LOCK, IS_LOCKED and NULL_LOCK_INITIALIZER.
-
-*/
-
-#include <stddef.h> /* for size_t */
-
-#ifndef EXTSPEC
- #define EXTSPEC extern
-#endif
-
-#if defined(_MSC_VER) && _MSC_VER>=1400
- #define MALLOCATTR __declspec(restrict)
-#endif
-#ifdef __GNUC__
- #define MALLOCATTR __attribute__ ((malloc))
-#endif
-#ifndef MALLOCATTR
- #define MALLOCATTR
-#endif
-
-#ifdef REPLACE_SYSTEM_ALLOCATOR
- #define nedmalloc malloc
- #define nedcalloc calloc
- #define nedrealloc realloc
- #define nedfree free
- #define nedmemalign memalign
- #define nedmallinfo mallinfo
- #define nedmallopt mallopt
- #define nedmalloc_trim malloc_trim
- #define nedmalloc_stats malloc_stats
- #define nedmalloc_footprint malloc_footprint
- #define nedindependent_calloc independent_calloc
- #define nedindependent_comalloc independent_comalloc
- #ifdef _MSC_VER
- #define nedblksize _msize
- #endif
-#endif
-
-#ifndef NO_MALLINFO
-#define NO_MALLINFO 0
-#endif
-
-#if !NO_MALLINFO
-struct mallinfo;
-#endif
-
-#if defined(__cplusplus)
- #if !defined(NO_NED_NAMESPACE)
-namespace nedalloc {
- #else
-extern "C" {
- #endif
- #define THROWSPEC throw()
-#else
- #define THROWSPEC
-#endif
-
-/* These are the global functions */
-
-/* Gets the usable size of an allocated block. Note this will always be bigger than what was
-asked for due to rounding etc.
-*/
-EXTSPEC size_t nedblksize(void *mem) THROWSPEC;
-
-EXTSPEC void nedsetvalue(void *v) THROWSPEC;
-
-EXTSPEC MALLOCATTR void * nedmalloc(size_t size) THROWSPEC;
-EXTSPEC MALLOCATTR void * nedcalloc(size_t no, size_t size) THROWSPEC;
-EXTSPEC MALLOCATTR void * nedrealloc(void *mem, size_t size) THROWSPEC;
-EXTSPEC void nedfree(void *mem) THROWSPEC;
-EXTSPEC MALLOCATTR void * nedmemalign(size_t alignment, size_t bytes) THROWSPEC;
-#if !NO_MALLINFO
-EXTSPEC struct mallinfo nedmallinfo(void) THROWSPEC;
-#endif
-EXTSPEC int nedmallopt(int parno, int value) THROWSPEC;
-EXTSPEC int nedmalloc_trim(size_t pad) THROWSPEC;
-EXTSPEC void nedmalloc_stats(void) THROWSPEC;
-EXTSPEC size_t nedmalloc_footprint(void) THROWSPEC;
-EXTSPEC MALLOCATTR void **nedindependent_calloc(size_t elemsno, size_t elemsize, void **chunks) THROWSPEC;
-EXTSPEC MALLOCATTR void **nedindependent_comalloc(size_t elems, size_t *sizes, void **chunks) THROWSPEC;
-
-/* These are the pool functions */
-struct nedpool_t;
-typedef struct nedpool_t nedpool;
-
-/* Creates a memory pool for use with the nedp* functions below.
-Capacity is how much to allocate immediately (if you know you'll be allocating a lot
-of memory very soon) which you can leave at zero. Threads specifies how many threads
-will *normally* be accessing the pool concurrently. Setting this to zero means it
-extends on demand, but be careful of this as it can rapidly consume system resources
-where bursts of concurrent threads use a pool at once.
-*/
-EXTSPEC MALLOCATTR nedpool *nedcreatepool(size_t capacity, int threads) THROWSPEC;
-
-/* Destroys a memory pool previously created by nedcreatepool().
-*/
-EXTSPEC void neddestroypool(nedpool *p) THROWSPEC;
-
-/* Sets a value to be associated with a pool. You can retrieve this value by passing
-any memory block allocated from that pool.
-*/
-EXTSPEC void nedpsetvalue(nedpool *p, void *v) THROWSPEC;
-/* Gets a previously set value using nedpsetvalue() or zero if memory is unknown.
-Optionally can also retrieve pool.
-*/
-EXTSPEC void *nedgetvalue(nedpool **p, void *mem) THROWSPEC;
-
-/* Disables the thread cache for the calling thread, returning any existing cache
-data to the central pool.
-*/
-EXTSPEC void neddisablethreadcache(nedpool *p) THROWSPEC;
-
-EXTSPEC MALLOCATTR void * nedpmalloc(nedpool *p, size_t size) THROWSPEC;
-EXTSPEC MALLOCATTR void * nedpcalloc(nedpool *p, size_t no, size_t size) THROWSPEC;
-EXTSPEC MALLOCATTR void * nedprealloc(nedpool *p, void *mem, size_t size) THROWSPEC;
-EXTSPEC void nedpfree(nedpool *p, void *mem) THROWSPEC;
-EXTSPEC MALLOCATTR void * nedpmemalign(nedpool *p, size_t alignment, size_t bytes) THROWSPEC;
-#if !NO_MALLINFO
-EXTSPEC struct mallinfo nedpmallinfo(nedpool *p) THROWSPEC;
-#endif
-EXTSPEC int nedpmallopt(nedpool *p, int parno, int value) THROWSPEC;
-EXTSPEC int nedpmalloc_trim(nedpool *p, size_t pad) THROWSPEC;
-EXTSPEC void nedpmalloc_stats(nedpool *p) THROWSPEC;
-EXTSPEC size_t nedpmalloc_footprint(nedpool *p) THROWSPEC;
-EXTSPEC MALLOCATTR void **nedpindependent_calloc(nedpool *p, size_t elemsno, size_t elemsize, void **chunks) THROWSPEC;
-EXTSPEC MALLOCATTR void **nedpindependent_comalloc(nedpool *p, size_t elems, size_t *sizes, void **chunks) THROWSPEC;
-
-#if defined(__cplusplus)
-}
-#endif
-
-#undef MALLOCATTR
-#undef EXTSPEC
-
-#endif
--
gitgitgadget
^ permalink raw reply related
* [PATCH v3 5/6] to be squashed into 3/6 (chunk 2 of 3)
From: Johannes Schindelin via GitGitGadget @ 2026-05-08 12:51 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2104.v3.git.1778244661.gitgitgadget@gmail.com>
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Assisted-by: Opus 4.7
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
compat/nedmalloc/malloc.c.h | 1699 -----------------------------------
1 file changed, 1699 deletions(-)
diff --git a/compat/nedmalloc/malloc.c.h b/compat/nedmalloc/malloc.c.h
index b4fb8c8846..0c663cf49c 100644
--- a/compat/nedmalloc/malloc.c.h
+++ b/compat/nedmalloc/malloc.c.h
@@ -1,1702 +1,3 @@
-/* ---------------------- Overlaid data structures ----------------------- */
-
-/*
- When chunks are not in use, they are treated as nodes of either
- lists or trees.
-
- "Small" chunks are stored in circular doubly-linked lists, and look
- like this:
-
- chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Size of previous chunk |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- `head:' | Size of chunk, in bytes |P|
- mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Forward pointer to next chunk in list |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Back pointer to previous chunk in list |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Unused space (may be 0 bytes long) .
- . .
- . |
-nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- `foot:' | Size of chunk, in bytes |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
- Larger chunks are kept in a form of bitwise digital trees (aka
- tries) keyed on chunksizes. Because malloc_tree_chunks are only for
- free chunks greater than 256 bytes, their size doesn't impose any
- constraints on user chunk sizes. Each node looks like:
-
- chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Size of previous chunk |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- `head:' | Size of chunk, in bytes |P|
- mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Forward pointer to next chunk of same size |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Back pointer to previous chunk of same size |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Pointer to left child (child[0]) |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Pointer to right child (child[1]) |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Pointer to parent |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | bin index of this chunk |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Unused space .
- . |
-nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- `foot:' | Size of chunk, in bytes |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
- Each tree holding treenodes is a tree of unique chunk sizes. Chunks
- of the same size are arranged in a circularly-linked list, with only
- the oldest chunk (the next to be used, in our FIFO ordering)
- actually in the tree. (Tree members are distinguished by a non-null
- parent pointer.) If a chunk with the same size as an existing node
- is inserted, it is linked off the existing node using pointers that
- work in the same way as fd/bk pointers of small chunks.
-
- Each tree contains a power of 2 sized range of chunk sizes (the
- smallest is 0x100 <= x < 0x180), which is divided in half at each
- tree level, with the chunks in the smaller half of the range (0x100
- <= x < 0x140 for the top nose) in the left subtree and the larger
- half (0x140 <= x < 0x180) in the right subtree. This is, of course,
- done by inspecting individual bits.
-
- Using these rules, each node's left subtree contains all smaller
- sizes than its right subtree. However, the node at the root of each
- subtree has no particular ordering relationship to either. (The
- dividing line between the subtree sizes is based on trie relation.)
- If we remove the last chunk of a given size from the interior of the
- tree, we need to replace it with a leaf node. The tree ordering
- rules permit a node to be replaced by any leaf below it.
-
- The smallest chunk in a tree (a common operation in a best-fit
- allocator) can be found by walking a path to the leftmost leaf in
- the tree. Unlike a usual binary tree, where we follow left child
- pointers until we reach a null, here we follow the right child
- pointer any time the left one is null, until we reach a leaf with
- both child pointers null. The smallest chunk in the tree will be
- somewhere along that path.
-
- The worst case number of steps to add, find, or remove a node is
- bounded by the number of bits differentiating chunks within
- bins. Under current bin calculations, this ranges from 6 up to 21
- (for 32 bit sizes) or up to 53 (for 64 bit sizes). The typical case
- is of course much better.
-*/
-
-struct malloc_tree_chunk {
- /* The first four fields must be compatible with malloc_chunk */
- size_t prev_foot;
- size_t head;
- struct malloc_tree_chunk* fd;
- struct malloc_tree_chunk* bk;
-
- struct malloc_tree_chunk* child[2];
- struct malloc_tree_chunk* parent;
- bindex_t index;
-};
-
-typedef struct malloc_tree_chunk tchunk;
-typedef struct malloc_tree_chunk* tchunkptr;
-typedef struct malloc_tree_chunk* tbinptr; /* The type of bins of trees */
-
-/* A little helper macro for trees */
-#define leftmost_child(t) ((t)->child[0] != 0? (t)->child[0] : (t)->child[1])
-
-/* ----------------------------- Segments -------------------------------- */
-
-/*
- Each malloc space may include non-contiguous segments, held in a
- list headed by an embedded malloc_segment record representing the
- top-most space. Segments also include flags holding properties of
- the space. Large chunks that are directly allocated by mmap are not
- included in this list. They are instead independently created and
- destroyed without otherwise keeping track of them.
-
- Segment management mainly comes into play for spaces allocated by
- MMAP. Any call to MMAP might or might not return memory that is
- adjacent to an existing segment. MORECORE normally contiguously
- extends the current space, so this space is almost always adjacent,
- which is simpler and faster to deal with. (This is why MORECORE is
- used preferentially to MMAP when both are available -- see
- sys_alloc.) When allocating using MMAP, we don't use any of the
- hinting mechanisms (inconsistently) supported in various
- implementations of unix mmap, or distinguish reserving from
- committing memory. Instead, we just ask for space, and exploit
- contiguity when we get it. It is probably possible to do
- better than this on some systems, but no general scheme seems
- to be significantly better.
-
- Management entails a simpler variant of the consolidation scheme
- used for chunks to reduce fragmentation -- new adjacent memory is
- normally prepended or appended to an existing segment. However,
- there are limitations compared to chunk consolidation that mostly
- reflect the fact that segment processing is relatively infrequent
- (occurring only when getting memory from system) and that we
- don't expect to have huge numbers of segments:
-
- * Segments are not indexed, so traversal requires linear scans. (It
- would be possible to index these, but is not worth the extra
- overhead and complexity for most programs on most platforms.)
- * New segments are only appended to old ones when holding top-most
- memory; if they cannot be prepended to others, they are held in
- different segments.
-
- Except for the top-most segment of an mstate, each segment record
- is kept at the tail of its segment. Segments are added by pushing
- segment records onto the list headed by &mstate.seg for the
- containing mstate.
-
- Segment flags control allocation/merge/deallocation policies:
- * If EXTERN_BIT set, then we did not allocate this segment,
- and so should not try to deallocate or merge with others.
- (This currently holds only for the initial segment passed
- into create_mspace_with_base.)
- * If IS_MMAPPED_BIT set, the segment may be merged with
- other surrounding mmapped segments and trimmed/de-allocated
- using munmap.
- * If neither bit is set, then the segment was obtained using
- MORECORE so can be merged with surrounding MORECORE'd segments
- and deallocated/trimmed using MORECORE with negative arguments.
-*/
-
-struct malloc_segment {
- char* base; /* base address */
- size_t size; /* allocated size */
- struct malloc_segment* next; /* ptr to next segment */
- flag_t sflags; /* mmap and extern flag */
-};
-
-#define is_mmapped_segment(S) ((S)->sflags & IS_MMAPPED_BIT)
-#define is_extern_segment(S) ((S)->sflags & EXTERN_BIT)
-
-typedef struct malloc_segment msegment;
-typedef struct malloc_segment* msegmentptr;
-
-/* ---------------------------- malloc_state ----------------------------- */
-
-/*
- A malloc_state holds all of the bookkeeping for a space.
- The main fields are:
-
- Top
- The topmost chunk of the currently active segment. Its size is
- cached in topsize. The actual size of topmost space is
- topsize+TOP_FOOT_SIZE, which includes space reserved for adding
- fenceposts and segment records if necessary when getting more
- space from the system. The size at which to autotrim top is
- cached from mparams in trim_check, except that it is disabled if
- an autotrim fails.
-
- Designated victim (dv)
- This is the preferred chunk for servicing small requests that
- don't have exact fits. It is normally the chunk split off most
- recently to service another small request. Its size is cached in
- dvsize. The link fields of this chunk are not maintained since it
- is not kept in a bin.
-
- SmallBins
- An array of bin headers for free chunks. These bins hold chunks
- with sizes less than MIN_LARGE_SIZE bytes. Each bin contains
- chunks of all the same size, spaced 8 bytes apart. To simplify
- use in double-linked lists, each bin header acts as a malloc_chunk
- pointing to the real first node, if it exists (else pointing to
- itself). This avoids special-casing for headers. But to avoid
- waste, we allocate only the fd/bk pointers of bins, and then use
- repositioning tricks to treat these as the fields of a chunk.
-
- TreeBins
- Treebins are pointers to the roots of trees holding a range of
- sizes. There are 2 equally spaced treebins for each power of two
- from TREE_SHIFT to TREE_SHIFT+16. The last bin holds anything
- larger.
-
- Bin maps
- There is one bit map for small bins ("smallmap") and one for
- treebins ("treemap). Each bin sets its bit when non-empty, and
- clears the bit when empty. Bit operations are then used to avoid
- bin-by-bin searching -- nearly all "search" is done without ever
- looking at bins that won't be selected. The bit maps
- conservatively use 32 bits per map word, even if on 64bit system.
- For a good description of some of the bit-based techniques used
- here, see Henry S. Warren Jr's book "Hacker's Delight" (and
- supplement at http://hackersdelight.org/). Many of these are
- intended to reduce the branchiness of paths through malloc etc, as
- well as to reduce the number of memory locations read or written.
-
- Segments
- A list of segments headed by an embedded malloc_segment record
- representing the initial space.
-
- Address check support
- The least_addr field is the least address ever obtained from
- MORECORE or MMAP. Attempted frees and reallocs of any address less
- than this are trapped (unless INSECURE is defined).
-
- Magic tag
- A cross-check field that should always hold same value as mparams.magic.
-
- Flags
- Bits recording whether to use MMAP, locks, or contiguous MORECORE
-
- Statistics
- Each space keeps track of current and maximum system memory
- obtained via MORECORE or MMAP.
-
- Trim support
- Fields holding the amount of unused topmost memory that should trigger
- timing, and a counter to force periodic scanning to release unused
- non-topmost segments.
-
- Locking
- If USE_LOCKS is defined, the "mutex" lock is acquired and released
- around every public call using this mspace.
-
- Extension support
- A void* pointer and a size_t field that can be used to help implement
- extensions to this malloc.
-*/
-
-/* Bin types, widths and sizes */
-#define NSMALLBINS (32U)
-#define NTREEBINS (32U)
-#define SMALLBIN_SHIFT (3U)
-#define SMALLBIN_WIDTH (SIZE_T_ONE << SMALLBIN_SHIFT)
-#define TREEBIN_SHIFT (8U)
-#define MIN_LARGE_SIZE (SIZE_T_ONE << TREEBIN_SHIFT)
-#define MAX_SMALL_SIZE (MIN_LARGE_SIZE - SIZE_T_ONE)
-#define MAX_SMALL_REQUEST (MAX_SMALL_SIZE - CHUNK_ALIGN_MASK - CHUNK_OVERHEAD)
-
-struct malloc_state {
- binmap_t smallmap;
- binmap_t treemap;
- size_t dvsize;
- size_t topsize;
- char* least_addr;
- mchunkptr dv;
- mchunkptr top;
- size_t trim_check;
- size_t release_checks;
- size_t magic;
- mchunkptr smallbins[(NSMALLBINS+1)*2];
- tbinptr treebins[NTREEBINS];
- size_t footprint;
- size_t max_footprint;
- flag_t mflags;
-#if USE_LOCKS
- MLOCK_T mutex; /* locate lock among fields that rarely change */
-#endif /* USE_LOCKS */
- msegment seg;
- void* extp; /* Unused but available for extensions */
- size_t exts;
-};
-
-typedef struct malloc_state* mstate;
-
-/* ------------- Global malloc_state and malloc_params ------------------- */
-
-/*
- malloc_params holds global properties, including those that can be
- dynamically set using mallopt. There is a single instance, mparams,
- initialized in init_mparams. Note that the non-zeroness of "magic"
- also serves as an initialization flag.
-*/
-
-struct malloc_params {
- volatile size_t magic;
- size_t page_size;
- size_t granularity;
- size_t mmap_threshold;
- size_t trim_threshold;
- flag_t default_mflags;
-};
-
-static struct malloc_params mparams;
-
-/* Ensure mparams initialized */
-#define ensure_initialization() ((void)(mparams.magic != 0 || init_mparams()))
-
-#if !ONLY_MSPACES
-
-/* The global malloc_state used for all non-"mspace" calls */
-static struct malloc_state _gm_;
-#define gm (&_gm_)
-#define is_global(M) ((M) == &_gm_)
-
-#endif /* !ONLY_MSPACES */
-
-#define is_initialized(M) ((M)->top != 0)
-
-/* -------------------------- system alloc setup ------------------------- */
-
-/* Operations on mflags */
-
-#define use_lock(M) ((M)->mflags & USE_LOCK_BIT)
-#define enable_lock(M) ((M)->mflags |= USE_LOCK_BIT)
-#define disable_lock(M) ((M)->mflags &= ~USE_LOCK_BIT)
-
-#define use_mmap(M) ((M)->mflags & USE_MMAP_BIT)
-#define enable_mmap(M) ((M)->mflags |= USE_MMAP_BIT)
-#define disable_mmap(M) ((M)->mflags &= ~USE_MMAP_BIT)
-
-#define use_noncontiguous(M) ((M)->mflags & USE_NONCONTIGUOUS_BIT)
-#define disable_contiguous(M) ((M)->mflags |= USE_NONCONTIGUOUS_BIT)
-
-#define set_lock(M,L)\
- ((M)->mflags = (L)?\
- ((M)->mflags | USE_LOCK_BIT) :\
- ((M)->mflags & ~USE_LOCK_BIT))
-
-/* page-align a size */
-#define page_align(S)\
- (((S) + (mparams.page_size - SIZE_T_ONE)) & ~(mparams.page_size - SIZE_T_ONE))
-
-/* granularity-align a size */
-#define granularity_align(S)\
- (((S) + (mparams.granularity - SIZE_T_ONE))\
- & ~(mparams.granularity - SIZE_T_ONE))
-
-
-/* For mmap, use granularity alignment on windows, else page-align */
-#ifdef WIN32
-#define mmap_align(S) granularity_align(S)
-#else
-#define mmap_align(S) page_align(S)
-#endif
-
-/* For sys_alloc, enough padding to ensure can malloc request on success */
-#define SYS_ALLOC_PADDING (TOP_FOOT_SIZE + MALLOC_ALIGNMENT)
-
-#define is_page_aligned(S)\
- (((size_t)(S) & (mparams.page_size - SIZE_T_ONE)) == 0)
-#define is_granularity_aligned(S)\
- (((size_t)(S) & (mparams.granularity - SIZE_T_ONE)) == 0)
-
-/* True if segment S holds address A */
-#define segment_holds(S, A)\
- ((char*)(A) >= S->base && (char*)(A) < S->base + S->size)
-
-/* Return segment holding given address */
-static msegmentptr segment_holding(mstate m, char* addr) {
- msegmentptr sp = &m->seg;
- for (;;) {
- if (addr >= sp->base && addr < sp->base + sp->size)
- return sp;
- if ((sp = sp->next) == 0)
- return 0;
- }
-}
-
-/* Return true if segment contains a segment link */
-static int has_segment_link(mstate m, msegmentptr ss) {
- msegmentptr sp = &m->seg;
- for (;;) {
- if ((char*)sp >= ss->base && (char*)sp < ss->base + ss->size)
- return 1;
- if ((sp = sp->next) == 0)
- return 0;
- }
-}
-
-#ifndef MORECORE_CANNOT_TRIM
-#define should_trim(M,s) ((s) > (M)->trim_check)
-#else /* MORECORE_CANNOT_TRIM */
-#define should_trim(M,s) (0)
-#endif /* MORECORE_CANNOT_TRIM */
-
-/*
- TOP_FOOT_SIZE is padding at the end of a segment, including space
- that may be needed to place segment records and fenceposts when new
- noncontiguous segments are added.
-*/
-#define TOP_FOOT_SIZE\
- (align_offset(chunk2mem(0))+pad_request(sizeof(struct malloc_segment))+MIN_CHUNK_SIZE)
-
-
-/* ------------------------------- Hooks -------------------------------- */
-
-/*
- PREACTION should be defined to return 0 on success, and nonzero on
- failure. If you are not using locking, you can redefine these to do
- anything you like.
-*/
-
-#if USE_LOCKS
-
-#define PREACTION(M) ((use_lock(M))? ACQUIRE_LOCK(&(M)->mutex) : 0)
-#define POSTACTION(M) { if (use_lock(M)) RELEASE_LOCK(&(M)->mutex); }
-#else /* USE_LOCKS */
-
-#ifndef PREACTION
-#define PREACTION(M) (0)
-#endif /* PREACTION */
-
-#ifndef POSTACTION
-#define POSTACTION(M)
-#endif /* POSTACTION */
-
-#endif /* USE_LOCKS */
-
-/*
- CORRUPTION_ERROR_ACTION is triggered upon detected bad addresses.
- USAGE_ERROR_ACTION is triggered on detected bad frees and
- reallocs. The argument p is an address that might have triggered the
- fault. It is ignored by the two predefined actions, but might be
- useful in custom actions that try to help diagnose errors.
-*/
-
-#if PROCEED_ON_ERROR
-
-/* A count of the number of corruption errors causing resets */
-int malloc_corruption_error_count;
-
-/* default corruption action */
-static void reset_on_error(mstate m);
-
-#define CORRUPTION_ERROR_ACTION(m) reset_on_error(m)
-#define USAGE_ERROR_ACTION(m, p)
-
-#else /* PROCEED_ON_ERROR */
-
-#ifndef CORRUPTION_ERROR_ACTION
-#define CORRUPTION_ERROR_ACTION(m) ABORT
-#endif /* CORRUPTION_ERROR_ACTION */
-
-#ifndef USAGE_ERROR_ACTION
-#define USAGE_ERROR_ACTION(m,p) ABORT
-#endif /* USAGE_ERROR_ACTION */
-
-#endif /* PROCEED_ON_ERROR */
-
-/* -------------------------- Debugging setup ---------------------------- */
-
-#if ! DEBUG
-
-#define check_free_chunk(M,P)
-#define check_inuse_chunk(M,P)
-#define check_malloced_chunk(M,P,N)
-#define check_mmapped_chunk(M,P)
-#define check_malloc_state(M)
-#define check_top_chunk(M,P)
-
-#else /* DEBUG */
-#define check_free_chunk(M,P) do_check_free_chunk(M,P)
-#define check_inuse_chunk(M,P) do_check_inuse_chunk(M,P)
-#define check_top_chunk(M,P) do_check_top_chunk(M,P)
-#define check_malloced_chunk(M,P,N) do_check_malloced_chunk(M,P,N)
-#define check_mmapped_chunk(M,P) do_check_mmapped_chunk(M,P)
-#define check_malloc_state(M) do_check_malloc_state(M)
-
-static void do_check_any_chunk(mstate m, mchunkptr p);
-static void do_check_top_chunk(mstate m, mchunkptr p);
-static void do_check_mmapped_chunk(mstate m, mchunkptr p);
-static void do_check_inuse_chunk(mstate m, mchunkptr p);
-static void do_check_free_chunk(mstate m, mchunkptr p);
-static void do_check_malloced_chunk(mstate m, void* mem, size_t s);
-static void do_check_tree(mstate m, tchunkptr t);
-static void do_check_treebin(mstate m, bindex_t i);
-static void do_check_smallbin(mstate m, bindex_t i);
-static void do_check_malloc_state(mstate m);
-static int bin_find(mstate m, mchunkptr x);
-static size_t traverse_and_check(mstate m);
-#endif /* DEBUG */
-
-/* ---------------------------- Indexing Bins ---------------------------- */
-
-#define is_small(s) (((s) >> SMALLBIN_SHIFT) < NSMALLBINS)
-#define small_index(s) ((s) >> SMALLBIN_SHIFT)
-#define small_index2size(i) ((i) << SMALLBIN_SHIFT)
-#define MIN_SMALL_INDEX (small_index(MIN_CHUNK_SIZE))
-
-/* addressing by index. See above about smallbin repositioning */
-#define smallbin_at(M, i) ((sbinptr)((char*)&((M)->smallbins[(i)<<1])))
-#define treebin_at(M,i) (&((M)->treebins[i]))
-
-/* assign tree index for size S to variable I. Use x86 asm if possible */
-#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
-#define compute_tree_index(S, I)\
-{\
- unsigned int X = S >> TREEBIN_SHIFT;\
- if (X == 0)\
- I = 0;\
- else if (X > 0xFFFF)\
- I = NTREEBINS-1;\
- else {\
- unsigned int K;\
- __asm__("bsrl\t%1, %0\n\t" : "=r" (K) : "rm" (X));\
- I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\
- }\
-}
-
-#elif defined (__INTEL_COMPILER)
-#define compute_tree_index(S, I)\
-{\
- size_t X = S >> TREEBIN_SHIFT;\
- if (X == 0)\
- I = 0;\
- else if (X > 0xFFFF)\
- I = NTREEBINS-1;\
- else {\
- unsigned int K = _bit_scan_reverse (X); \
- I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\
- }\
-}
-
-#elif defined(_MSC_VER) && _MSC_VER>=1300
-#define compute_tree_index(S, I)\
-{\
- size_t X = S >> TREEBIN_SHIFT;\
- if (X == 0)\
- I = 0;\
- else if (X > 0xFFFF)\
- I = NTREEBINS-1;\
- else {\
- unsigned int K;\
- _BitScanReverse((DWORD *) &K, X);\
- I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\
- }\
-}
-
-#else /* GNUC */
-#define compute_tree_index(S, I)\
-{\
- size_t X = S >> TREEBIN_SHIFT;\
- if (X == 0)\
- I = 0;\
- else if (X > 0xFFFF)\
- I = NTREEBINS-1;\
- else {\
- unsigned int Y = (unsigned int)X;\
- unsigned int N = ((Y - 0x100) >> 16) & 8;\
- unsigned int K = (((Y <<= N) - 0x1000) >> 16) & 4;\
- N += K;\
- N += K = (((Y <<= K) - 0x4000) >> 16) & 2;\
- K = 14 - N + ((Y <<= K) >> 15);\
- I = (K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1));\
- }\
-}
-#endif /* GNUC */
-
-/* Bit representing maximum resolved size in a treebin at i */
-#define bit_for_tree_index(i) \
- (i == NTREEBINS-1)? (SIZE_T_BITSIZE-1) : (((i) >> 1) + TREEBIN_SHIFT - 2)
-
-/* Shift placing maximum resolved bit in a treebin at i as sign bit */
-#define leftshift_for_tree_index(i) \
- ((i == NTREEBINS-1)? 0 : \
- ((SIZE_T_BITSIZE-SIZE_T_ONE) - (((i) >> 1) + TREEBIN_SHIFT - 2)))
-
-/* The size of the smallest chunk held in bin with index i */
-#define minsize_for_tree_index(i) \
- ((SIZE_T_ONE << (((i) >> 1) + TREEBIN_SHIFT)) | \
- (((size_t)((i) & SIZE_T_ONE)) << (((i) >> 1) + TREEBIN_SHIFT - 1)))
-
-
-/* ------------------------ Operations on bin maps ----------------------- */
-
-/* bit corresponding to given index */
-#define idx2bit(i) ((binmap_t)(1) << (i))
-
-/* Mark/Clear bits with given index */
-#define mark_smallmap(M,i) ((M)->smallmap |= idx2bit(i))
-#define clear_smallmap(M,i) ((M)->smallmap &= ~idx2bit(i))
-#define smallmap_is_marked(M,i) ((M)->smallmap & idx2bit(i))
-
-#define mark_treemap(M,i) ((M)->treemap |= idx2bit(i))
-#define clear_treemap(M,i) ((M)->treemap &= ~idx2bit(i))
-#define treemap_is_marked(M,i) ((M)->treemap & idx2bit(i))
-
-/* isolate the least set bit of a bitmap */
-#define least_bit(x) ((x) & -(x))
-
-/* mask with all bits to left of least bit of x on */
-#define left_bits(x) ((x<<1) | -(x<<1))
-
-/* mask with all bits to left of or equal to least bit of x on */
-#define same_or_left_bits(x) ((x) | -(x))
-
-/* index corresponding to given bit. Use x86 asm if possible */
-
-#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
-#define compute_bit2idx(X, I)\
-{\
- unsigned int J;\
- __asm__("bsfl\t%1, %0\n\t" : "=r" (J) : "rm" (X));\
- I = (bindex_t)J;\
-}
-
-#elif defined (__INTEL_COMPILER)
-#define compute_bit2idx(X, I)\
-{\
- unsigned int J;\
- J = _bit_scan_forward (X); \
- I = (bindex_t)J;\
-}
-
-#elif defined(_MSC_VER) && _MSC_VER>=1300
-#define compute_bit2idx(X, I)\
-{\
- unsigned int J;\
- _BitScanForward((DWORD *) &J, X);\
- I = (bindex_t)J;\
-}
-
-#elif USE_BUILTIN_FFS
-#define compute_bit2idx(X, I) I = ffs(X)-1
-
-#else
-#define compute_bit2idx(X, I)\
-{\
- unsigned int Y = X - 1;\
- unsigned int K = Y >> (16-4) & 16;\
- unsigned int N = K; Y >>= K;\
- N += K = Y >> (8-3) & 8; Y >>= K;\
- N += K = Y >> (4-2) & 4; Y >>= K;\
- N += K = Y >> (2-1) & 2; Y >>= K;\
- N += K = Y >> (1-0) & 1; Y >>= K;\
- I = (bindex_t)(N + Y);\
-}
-#endif /* GNUC */
-
-
-/* ----------------------- Runtime Check Support ------------------------- */
-
-/*
- For security, the main invariant is that malloc/free/etc never
- writes to a static address other than malloc_state, unless static
- malloc_state itself has been corrupted, which cannot occur via
- malloc (because of these checks). In essence this means that we
- believe all pointers, sizes, maps etc held in malloc_state, but
- check all of those linked or offsetted from other embedded data
- structures. These checks are interspersed with main code in a way
- that tends to minimize their run-time cost.
-
- When FOOTERS is defined, in addition to range checking, we also
- verify footer fields of inuse chunks, which can be used guarantee
- that the mstate controlling malloc/free is intact. This is a
- streamlined version of the approach described by William Robertson
- et al in "Run-time Detection of Heap-based Overflows" LISA'03
- http://www.usenix.org/events/lisa03/tech/robertson.html The footer
- of an inuse chunk holds the xor of its mstate and a random seed,
- that is checked upon calls to free() and realloc(). This is
- (probablistically) unguessable from outside the program, but can be
- computed by any code successfully malloc'ing any chunk, so does not
- itself provide protection against code that has already broken
- security through some other means. Unlike Robertson et al, we
- always dynamically check addresses of all offset chunks (previous,
- next, etc). This turns out to be cheaper than relying on hashes.
-*/
-
-#if !INSECURE
-/* Check if address a is at least as high as any from MORECORE or MMAP */
-#define ok_address(M, a) ((char*)(a) >= (M)->least_addr)
-/* Check if address of next chunk n is higher than base chunk p */
-#define ok_next(p, n) ((char*)(p) < (char*)(n))
-/* Check if p has its cinuse bit on */
-#define ok_cinuse(p) cinuse(p)
-/* Check if p has its pinuse bit on */
-#define ok_pinuse(p) pinuse(p)
-
-#else /* !INSECURE */
-#define ok_address(M, a) (1)
-#define ok_next(b, n) (1)
-#define ok_cinuse(p) (1)
-#define ok_pinuse(p) (1)
-#endif /* !INSECURE */
-
-#if (FOOTERS && !INSECURE)
-/* Check if (alleged) mstate m has expected magic field */
-#define ok_magic(M) ((M)->magic == mparams.magic)
-#else /* (FOOTERS && !INSECURE) */
-#define ok_magic(M) (1)
-#endif /* (FOOTERS && !INSECURE) */
-
-
-/* In gcc, use __builtin_expect to minimize impact of checks */
-#if !INSECURE
-#if defined(__GNUC__) && __GNUC__ >= 3
-#define RTCHECK(e) __builtin_expect(e, 1)
-#else /* GNUC */
-#define RTCHECK(e) (e)
-#endif /* GNUC */
-#else /* !INSECURE */
-#define RTCHECK(e) (1)
-#endif /* !INSECURE */
-
-/* macros to set up inuse chunks with or without footers */
-
-#if !FOOTERS
-
-#define mark_inuse_foot(M,p,s)
-
-/* Set cinuse bit and pinuse bit of next chunk */
-#define set_inuse(M,p,s)\
- ((p)->head = (((p)->head & PINUSE_BIT)|s|CINUSE_BIT),\
- ((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT)
-
-/* Set cinuse and pinuse of this chunk and pinuse of next chunk */
-#define set_inuse_and_pinuse(M,p,s)\
- ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\
- ((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT)
-
-/* Set size, cinuse and pinuse bit of this chunk */
-#define set_size_and_pinuse_of_inuse_chunk(M, p, s)\
- ((p)->head = (s|PINUSE_BIT|CINUSE_BIT))
-
-#else /* FOOTERS */
-
-/* Set foot of inuse chunk to be xor of mstate and seed */
-#define mark_inuse_foot(M,p,s)\
- (((mchunkptr)((char*)(p) + (s)))->prev_foot = ((size_t)(M) ^ mparams.magic))
-
-#define get_mstate_for(p)\
- ((mstate)(((mchunkptr)((char*)(p) +\
- (chunksize(p))))->prev_foot ^ mparams.magic))
-
-#define set_inuse(M,p,s)\
- ((p)->head = (((p)->head & PINUSE_BIT)|s|CINUSE_BIT),\
- (((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT), \
- mark_inuse_foot(M,p,s))
-
-#define set_inuse_and_pinuse(M,p,s)\
- ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\
- (((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT),\
- mark_inuse_foot(M,p,s))
-
-#define set_size_and_pinuse_of_inuse_chunk(M, p, s)\
- ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\
- mark_inuse_foot(M, p, s))
-
-#endif /* !FOOTERS */
-
-/* ---------------------------- setting mparams -------------------------- */
-
-/* Initialize mparams */
-static int init_mparams(void) {
-#ifdef NEED_GLOBAL_LOCK_INIT
- if (malloc_global_mutex_status <= 0)
- init_malloc_global_mutex();
-#endif
-
- ACQUIRE_MALLOC_GLOBAL_LOCK();
- if (mparams.magic == 0) {
- size_t magic;
- size_t psize;
- size_t gsize;
-
-#ifndef WIN32
- psize = malloc_getpagesize;
- gsize = ((DEFAULT_GRANULARITY != 0)? DEFAULT_GRANULARITY : psize);
-#else /* WIN32 */
- {
- SYSTEM_INFO system_info;
- GetSystemInfo(&system_info);
- psize = system_info.dwPageSize;
- gsize = ((DEFAULT_GRANULARITY != 0)?
- DEFAULT_GRANULARITY : system_info.dwAllocationGranularity);
- }
-#endif /* WIN32 */
-
- /* Sanity-check configuration:
- size_t must be unsigned and as wide as pointer type.
- ints must be at least 4 bytes.
- alignment must be at least 8.
- Alignment, min chunk size, and page size must all be powers of 2.
- */
- if ((sizeof(size_t) != sizeof(char*)) ||
- (MAX_SIZE_T < MIN_CHUNK_SIZE) ||
- (sizeof(int) < 4) ||
- (MALLOC_ALIGNMENT < (size_t)8U) ||
- ((MALLOC_ALIGNMENT & (MALLOC_ALIGNMENT-SIZE_T_ONE)) != 0) ||
- ((MCHUNK_SIZE & (MCHUNK_SIZE-SIZE_T_ONE)) != 0) ||
- ((gsize & (gsize-SIZE_T_ONE)) != 0) ||
- ((psize & (psize-SIZE_T_ONE)) != 0))
- ABORT;
-
- mparams.granularity = gsize;
- mparams.page_size = psize;
- mparams.mmap_threshold = DEFAULT_MMAP_THRESHOLD;
- mparams.trim_threshold = DEFAULT_TRIM_THRESHOLD;
-#if MORECORE_CONTIGUOUS
- mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT;
-#else /* MORECORE_CONTIGUOUS */
- mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT|USE_NONCONTIGUOUS_BIT;
-#endif /* MORECORE_CONTIGUOUS */
-
-#if !ONLY_MSPACES
- /* Set up lock for main malloc area */
- gm->mflags = mparams.default_mflags;
- (void)INITIAL_LOCK(&gm->mutex);
-#endif
-
-#if (FOOTERS && !INSECURE)
- {
-#if USE_DEV_RANDOM
- int fd;
- unsigned char buf[sizeof(size_t)];
- /* Try to use /dev/urandom, else fall back on using time */
- if ((fd = open("/dev/urandom", O_RDONLY)) >= 0 &&
- read(fd, buf, sizeof(buf)) == sizeof(buf)) {
- magic = *((size_t *) buf);
- close(fd);
- }
- else
-#endif /* USE_DEV_RANDOM */
-#ifdef WIN32
- magic = (size_t)(GetTickCount() ^ (size_t)0x55555555U);
-#else
- magic = (size_t)(time(0) ^ (size_t)0x55555555U);
-#endif
- magic |= (size_t)8U; /* ensure nonzero */
- magic &= ~(size_t)7U; /* improve chances of fault for bad values */
- }
-#else /* (FOOTERS && !INSECURE) */
- magic = (size_t)0x58585858U;
-#endif /* (FOOTERS && !INSECURE) */
-
- mparams.magic = magic;
- }
-
- RELEASE_MALLOC_GLOBAL_LOCK();
- return 1;
-}
-
-/* support for mallopt */
-static int change_mparam(int param_number, int value) {
- size_t val = (value == -1)? MAX_SIZE_T : (size_t)value;
- ensure_initialization();
- switch(param_number) {
- case M_TRIM_THRESHOLD:
- mparams.trim_threshold = val;
- return 1;
- case M_GRANULARITY:
- if (val >= mparams.page_size && ((val & (val-1)) == 0)) {
- mparams.granularity = val;
- return 1;
- }
- else
- return 0;
- case M_MMAP_THRESHOLD:
- mparams.mmap_threshold = val;
- return 1;
- default:
- return 0;
- }
-}
-
-#if DEBUG
-/* ------------------------- Debugging Support --------------------------- */
-
-/* Check properties of any chunk, whether free, inuse, mmapped etc */
-static void do_check_any_chunk(mstate m, mchunkptr p) {
- assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD));
- assert(ok_address(m, p));
-}
-
-/* Check properties of top chunk */
-static void do_check_top_chunk(mstate m, mchunkptr p) {
- msegmentptr sp = segment_holding(m, (char*)p);
- size_t sz = p->head & ~INUSE_BITS; /* third-lowest bit can be set! */
- assert(sp != 0);
- assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD));
- assert(ok_address(m, p));
- assert(sz == m->topsize);
- assert(sz > 0);
- assert(sz == ((sp->base + sp->size) - (char*)p) - TOP_FOOT_SIZE);
- assert(pinuse(p));
- assert(!pinuse(chunk_plus_offset(p, sz)));
-}
-
-/* Check properties of (inuse) mmapped chunks */
-static void do_check_mmapped_chunk(mstate m, mchunkptr p) {
- size_t sz = chunksize(p);
- size_t len = (sz + (p->prev_foot & ~IS_MMAPPED_BIT) + MMAP_FOOT_PAD);
- assert(is_mmapped(p));
- assert(use_mmap(m));
- assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD));
- assert(ok_address(m, p));
- assert(!is_small(sz));
- assert((len & (mparams.page_size-SIZE_T_ONE)) == 0);
- assert(chunk_plus_offset(p, sz)->head == FENCEPOST_HEAD);
- assert(chunk_plus_offset(p, sz+SIZE_T_SIZE)->head == 0);
-}
-
-/* Check properties of inuse chunks */
-static void do_check_inuse_chunk(mstate m, mchunkptr p) {
- do_check_any_chunk(m, p);
- assert(cinuse(p));
- assert(next_pinuse(p));
- /* If not pinuse and not mmapped, previous chunk has OK offset */
- assert(is_mmapped(p) || pinuse(p) || next_chunk(prev_chunk(p)) == p);
- if (is_mmapped(p))
- do_check_mmapped_chunk(m, p);
-}
-
-/* Check properties of free chunks */
-static void do_check_free_chunk(mstate m, mchunkptr p) {
- size_t sz = chunksize(p);
- mchunkptr next = chunk_plus_offset(p, sz);
- do_check_any_chunk(m, p);
- assert(!cinuse(p));
- assert(!next_pinuse(p));
- assert (!is_mmapped(p));
- if (p != m->dv && p != m->top) {
- if (sz >= MIN_CHUNK_SIZE) {
- assert((sz & CHUNK_ALIGN_MASK) == 0);
- assert(is_aligned(chunk2mem(p)));
- assert(next->prev_foot == sz);
- assert(pinuse(p));
- assert (next == m->top || cinuse(next));
- assert(p->fd->bk == p);
- assert(p->bk->fd == p);
- }
- else /* markers are always of size SIZE_T_SIZE */
- assert(sz == SIZE_T_SIZE);
- }
-}
-
-/* Check properties of malloced chunks at the point they are malloced */
-static void do_check_malloced_chunk(mstate m, void* mem, size_t s) {
- if (mem != 0) {
- mchunkptr p = mem2chunk(mem);
- size_t sz = p->head & ~(PINUSE_BIT|CINUSE_BIT);
- do_check_inuse_chunk(m, p);
- assert((sz & CHUNK_ALIGN_MASK) == 0);
- assert(sz >= MIN_CHUNK_SIZE);
- assert(sz >= s);
- /* unless mmapped, size is less than MIN_CHUNK_SIZE more than request */
- assert(is_mmapped(p) || sz < (s + MIN_CHUNK_SIZE));
- }
-}
-
-/* Check a tree and its subtrees. */
-static void do_check_tree(mstate m, tchunkptr t) {
- tchunkptr head = 0;
- tchunkptr u = t;
- bindex_t tindex = t->index;
- size_t tsize = chunksize(t);
- bindex_t idx;
- compute_tree_index(tsize, idx);
- assert(tindex == idx);
- assert(tsize >= MIN_LARGE_SIZE);
- assert(tsize >= minsize_for_tree_index(idx));
- assert((idx == NTREEBINS-1) || (tsize < minsize_for_tree_index((idx+1))));
-
- do { /* traverse through chain of same-sized nodes */
- do_check_any_chunk(m, ((mchunkptr)u));
- assert(u->index == tindex);
- assert(chunksize(u) == tsize);
- assert(!cinuse(u));
- assert(!next_pinuse(u));
- assert(u->fd->bk == u);
- assert(u->bk->fd == u);
- if (u->parent == 0) {
- assert(u->child[0] == 0);
- assert(u->child[1] == 0);
- }
- else {
- assert(head == 0); /* only one node on chain has parent */
- head = u;
- assert(u->parent != u);
- assert (u->parent->child[0] == u ||
- u->parent->child[1] == u ||
- *((tbinptr*)(u->parent)) == u);
- if (u->child[0] != 0) {
- assert(u->child[0]->parent == u);
- assert(u->child[0] != u);
- do_check_tree(m, u->child[0]);
- }
- if (u->child[1] != 0) {
- assert(u->child[1]->parent == u);
- assert(u->child[1] != u);
- do_check_tree(m, u->child[1]);
- }
- if (u->child[0] != 0 && u->child[1] != 0) {
- assert(chunksize(u->child[0]) < chunksize(u->child[1]));
- }
- }
- u = u->fd;
- } while (u != t);
- assert(head != 0);
-}
-
-/* Check all the chunks in a treebin. */
-static void do_check_treebin(mstate m, bindex_t i) {
- tbinptr* tb = treebin_at(m, i);
- tchunkptr t = *tb;
- int empty = (m->treemap & (1U << i)) == 0;
- if (t == 0)
- assert(empty);
- if (!empty)
- do_check_tree(m, t);
-}
-
-/* Check all the chunks in a smallbin. */
-static void do_check_smallbin(mstate m, bindex_t i) {
- sbinptr b = smallbin_at(m, i);
- mchunkptr p = b->bk;
- unsigned int empty = (m->smallmap & (1U << i)) == 0;
- if (p == b)
- assert(empty);
- if (!empty) {
- for (; p != b; p = p->bk) {
- size_t size = chunksize(p);
- mchunkptr q;
- /* each chunk claims to be free */
- do_check_free_chunk(m, p);
- /* chunk belongs in bin */
- assert(small_index(size) == i);
- assert(p->bk == b || chunksize(p->bk) == chunksize(p));
- /* chunk is followed by an inuse chunk */
- q = next_chunk(p);
- if (q->head != FENCEPOST_HEAD)
- do_check_inuse_chunk(m, q);
- }
- }
-}
-
-/* Find x in a bin. Used in other check functions. */
-static int bin_find(mstate m, mchunkptr x) {
- size_t size = chunksize(x);
- if (is_small(size)) {
- bindex_t sidx = small_index(size);
- sbinptr b = smallbin_at(m, sidx);
- if (smallmap_is_marked(m, sidx)) {
- mchunkptr p = b;
- do {
- if (p == x)
- return 1;
- } while ((p = p->fd) != b);
- }
- }
- else {
- bindex_t tidx;
- compute_tree_index(size, tidx);
- if (treemap_is_marked(m, tidx)) {
- tchunkptr t = *treebin_at(m, tidx);
- size_t sizebits = size << leftshift_for_tree_index(tidx);
- while (t != 0 && chunksize(t) != size) {
- t = t->child[(sizebits >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1];
- sizebits <<= 1;
- }
- if (t != 0) {
- tchunkptr u = t;
- do {
- if (u == (tchunkptr)x)
- return 1;
- } while ((u = u->fd) != t);
- }
- }
- }
- return 0;
-}
-
-/* Traverse each chunk and check it; return total */
-static size_t traverse_and_check(mstate m) {
- size_t sum = 0;
- if (is_initialized(m)) {
- msegmentptr s = &m->seg;
- sum += m->topsize + TOP_FOOT_SIZE;
- while (s != 0) {
- mchunkptr q = align_as_chunk(s->base);
- mchunkptr lastq = 0;
- assert(pinuse(q));
- while (segment_holds(s, q) &&
- q != m->top && q->head != FENCEPOST_HEAD) {
- sum += chunksize(q);
- if (cinuse(q)) {
- assert(!bin_find(m, q));
- do_check_inuse_chunk(m, q);
- }
- else {
- assert(q == m->dv || bin_find(m, q));
- assert(lastq == 0 || cinuse(lastq)); /* Not 2 consecutive free */
- do_check_free_chunk(m, q);
- }
- lastq = q;
- q = next_chunk(q);
- }
- s = s->next;
- }
- }
- return sum;
-}
-
-/* Check all properties of malloc_state. */
-static void do_check_malloc_state(mstate m) {
- bindex_t i;
- size_t total;
- /* check bins */
- for (i = 0; i < NSMALLBINS; ++i)
- do_check_smallbin(m, i);
- for (i = 0; i < NTREEBINS; ++i)
- do_check_treebin(m, i);
-
- if (m->dvsize != 0) { /* check dv chunk */
- do_check_any_chunk(m, m->dv);
- assert(m->dvsize == chunksize(m->dv));
- assert(m->dvsize >= MIN_CHUNK_SIZE);
- assert(bin_find(m, m->dv) == 0);
- }
-
- if (m->top != 0) { /* check top chunk */
- do_check_top_chunk(m, m->top);
- /*assert(m->topsize == chunksize(m->top)); redundant */
- assert(m->topsize > 0);
- assert(bin_find(m, m->top) == 0);
- }
-
- total = traverse_and_check(m);
- assert(total <= m->footprint);
- assert(m->footprint <= m->max_footprint);
-}
-#endif /* DEBUG */
-
-/* ----------------------------- statistics ------------------------------ */
-
-#if !NO_MALLINFO
-static struct mallinfo internal_mallinfo(mstate m) {
- struct mallinfo nm = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
- ensure_initialization();
- if (!PREACTION(m)) {
- check_malloc_state(m);
- if (is_initialized(m)) {
- size_t nfree = SIZE_T_ONE; /* top always free */
- size_t mfree = m->topsize + TOP_FOOT_SIZE;
- size_t sum = mfree;
- msegmentptr s = &m->seg;
- while (s != 0) {
- mchunkptr q = align_as_chunk(s->base);
- while (segment_holds(s, q) &&
- q != m->top && q->head != FENCEPOST_HEAD) {
- size_t sz = chunksize(q);
- sum += sz;
- if (!cinuse(q)) {
- mfree += sz;
- ++nfree;
- }
- q = next_chunk(q);
- }
- s = s->next;
- }
-
- nm.arena = sum;
- nm.ordblks = nfree;
- nm.hblkhd = m->footprint - sum;
- nm.usmblks = m->max_footprint;
- nm.uordblks = m->footprint - mfree;
- nm.fordblks = mfree;
- nm.keepcost = m->topsize;
- }
-
- POSTACTION(m);
- }
- return nm;
-}
-#endif /* !NO_MALLINFO */
-
-static void internal_malloc_stats(mstate m) {
- ensure_initialization();
- if (!PREACTION(m)) {
- size_t maxfp = 0;
- size_t fp = 0;
- size_t used = 0;
- check_malloc_state(m);
- if (is_initialized(m)) {
- msegmentptr s = &m->seg;
- maxfp = m->max_footprint;
- fp = m->footprint;
- used = fp - (m->topsize + TOP_FOOT_SIZE);
-
- while (s != 0) {
- mchunkptr q = align_as_chunk(s->base);
- while (segment_holds(s, q) &&
- q != m->top && q->head != FENCEPOST_HEAD) {
- if (!cinuse(q))
- used -= chunksize(q);
- q = next_chunk(q);
- }
- s = s->next;
- }
- }
-
- fprintf(stderr, "max system bytes = %10lu\n", (unsigned long)(maxfp));
- fprintf(stderr, "system bytes = %10lu\n", (unsigned long)(fp));
- fprintf(stderr, "in use bytes = %10lu\n", (unsigned long)(used));
-
- POSTACTION(m);
- }
-}
-
-/* ----------------------- Operations on smallbins ----------------------- */
-
-/*
- Various forms of linking and unlinking are defined as macros. Even
- the ones for trees, which are very long but have very short typical
- paths. This is ugly but reduces reliance on inlining support of
- compilers.
-*/
-
-/* Link a free chunk into a smallbin */
-#define insert_small_chunk(M, P, S) {\
- bindex_t I = small_index(S);\
- mchunkptr B = smallbin_at(M, I);\
- mchunkptr F = B;\
- assert(S >= MIN_CHUNK_SIZE);\
- if (!smallmap_is_marked(M, I))\
- mark_smallmap(M, I);\
- else if (RTCHECK(ok_address(M, B->fd)))\
- F = B->fd;\
- else {\
- CORRUPTION_ERROR_ACTION(M);\
- }\
- B->fd = P;\
- F->bk = P;\
- P->fd = F;\
- P->bk = B;\
-}
-
-/* Unlink a chunk from a smallbin */
-#define unlink_small_chunk(M, P, S) {\
- mchunkptr F = P->fd;\
- mchunkptr B = P->bk;\
- bindex_t I = small_index(S);\
- assert(P != B);\
- assert(P != F);\
- assert(chunksize(P) == small_index2size(I));\
- if (F == B)\
- clear_smallmap(M, I);\
- else if (RTCHECK((F == smallbin_at(M,I) || ok_address(M, F)) &&\
- (B == smallbin_at(M,I) || ok_address(M, B)))) {\
- F->bk = B;\
- B->fd = F;\
- }\
- else {\
- CORRUPTION_ERROR_ACTION(M);\
- }\
-}
-
-/* Unlink the first chunk from a smallbin */
-#define unlink_first_small_chunk(M, B, P, I) {\
- mchunkptr F = P->fd;\
- assert(P != B);\
- assert(P != F);\
- assert(chunksize(P) == small_index2size(I));\
- if (B == F)\
- clear_smallmap(M, I);\
- else if (RTCHECK(ok_address(M, F))) {\
- B->fd = F;\
- F->bk = B;\
- }\
- else {\
- CORRUPTION_ERROR_ACTION(M);\
- }\
-}
-
-
-
-/* Replace dv node, binning the old one */
-/* Used only when dvsize known to be small */
-#define replace_dv(M, P, S) {\
- size_t DVS = M->dvsize;\
- if (DVS != 0) {\
- mchunkptr DV = M->dv;\
- assert(is_small(DVS));\
- insert_small_chunk(M, DV, DVS);\
- }\
- M->dvsize = S;\
- M->dv = P;\
-}
-
-/* ------------------------- Operations on trees ------------------------- */
-
-/* Insert chunk into tree */
-#define insert_large_chunk(M, X, S) {\
- tbinptr* H;\
- bindex_t I;\
- compute_tree_index(S, I);\
- H = treebin_at(M, I);\
- X->index = I;\
- X->child[0] = X->child[1] = 0;\
- if (!treemap_is_marked(M, I)) {\
- mark_treemap(M, I);\
- *H = X;\
- X->parent = (tchunkptr)H;\
- X->fd = X->bk = X;\
- }\
- else {\
- tchunkptr T = *H;\
- size_t K = S << leftshift_for_tree_index(I);\
- for (;;) {\
- if (chunksize(T) != S) {\
- tchunkptr* C = &(T->child[(K >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]);\
- K <<= 1;\
- if (*C != 0)\
- T = *C;\
- else if (RTCHECK(ok_address(M, C))) {\
- *C = X;\
- X->parent = T;\
- X->fd = X->bk = X;\
- break;\
- }\
- else {\
- CORRUPTION_ERROR_ACTION(M);\
- break;\
- }\
- }\
- else {\
- tchunkptr F = T->fd;\
- if (RTCHECK(ok_address(M, T) && ok_address(M, F))) {\
- T->fd = F->bk = X;\
- X->fd = F;\
- X->bk = T;\
- X->parent = 0;\
- break;\
- }\
- else {\
- CORRUPTION_ERROR_ACTION(M);\
- break;\
- }\
- }\
- }\
- }\
-}
-
-/*
- Unlink steps:
-
- 1. If x is a chained node, unlink it from its same-sized fd/bk links
- and choose its bk node as its replacement.
- 2. If x was the last node of its size, but not a leaf node, it must
- be replaced with a leaf node (not merely one with an open left or
- right), to make sure that lefts and rights of descendants
- correspond properly to bit masks. We use the rightmost descendant
- of x. We could use any other leaf, but this is easy to locate and
- tends to counteract removal of leftmosts elsewhere, and so keeps
- paths shorter than minimally guaranteed. This doesn't loop much
- because on average a node in a tree is near the bottom.
- 3. If x is the base of a chain (i.e., has parent links) relink
- x's parent and children to x's replacement (or null if none).
-*/
-
-#define unlink_large_chunk(M, X) {\
- tchunkptr XP = X->parent;\
- tchunkptr R;\
- if (X->bk != X) {\
- tchunkptr F = X->fd;\
- R = X->bk;\
- if (RTCHECK(ok_address(M, F))) {\
- F->bk = R;\
- R->fd = F;\
- }\
- else {\
- CORRUPTION_ERROR_ACTION(M);\
- }\
- }\
- else {\
- tchunkptr* RP;\
- if (((R = *(RP = &(X->child[1]))) != 0) ||\
- ((R = *(RP = &(X->child[0]))) != 0)) {\
- tchunkptr* CP;\
- while ((*(CP = &(R->child[1])) != 0) ||\
- (*(CP = &(R->child[0])) != 0)) {\
- R = *(RP = CP);\
- }\
- if (RTCHECK(ok_address(M, RP)))\
- *RP = 0;\
- else {\
- CORRUPTION_ERROR_ACTION(M);\
- }\
- }\
- }\
- if (XP != 0) {\
- tbinptr* H = treebin_at(M, X->index);\
- if (X == *H) {\
- if ((*H = R) == 0) \
- clear_treemap(M, X->index);\
- }\
- else if (RTCHECK(ok_address(M, XP))) {\
- if (XP->child[0] == X) \
- XP->child[0] = R;\
- else \
- XP->child[1] = R;\
- }\
- else\
- CORRUPTION_ERROR_ACTION(M);\
- if (R != 0) {\
- if (RTCHECK(ok_address(M, R))) {\
- tchunkptr C0, C1;\
- R->parent = XP;\
- if ((C0 = X->child[0]) != 0) {\
- if (RTCHECK(ok_address(M, C0))) {\
- R->child[0] = C0;\
- C0->parent = R;\
- }\
- else\
- CORRUPTION_ERROR_ACTION(M);\
- }\
- if ((C1 = X->child[1]) != 0) {\
- if (RTCHECK(ok_address(M, C1))) {\
- R->child[1] = C1;\
- C1->parent = R;\
- }\
- else\
- CORRUPTION_ERROR_ACTION(M);\
- }\
- }\
- else\
- CORRUPTION_ERROR_ACTION(M);\
- }\
- }\
-}
-
-/* Relays to large vs small bin operations */
-
-#define insert_chunk(M, P, S)\
- if (is_small(S)) insert_small_chunk(M, P, S)\
- else { tchunkptr TP = (tchunkptr)(P); insert_large_chunk(M, TP, S); }
-
-#define unlink_chunk(M, P, S)\
- if (is_small(S)) unlink_small_chunk(M, P, S)\
- else { tchunkptr TP = (tchunkptr)(P); unlink_large_chunk(M, TP); }
-
-
-/* Relays to internal calls to malloc/free from realloc, memalign etc */
-
-#if ONLY_MSPACES
-#define internal_malloc(m, b) mspace_malloc(m, b)
-#define internal_free(m, mem) mspace_free(m,mem);
-#else /* ONLY_MSPACES */
-#if MSPACES
-#define internal_malloc(m, b)\
- (m == gm)? dlmalloc(b) : mspace_malloc(m, b)
-#define internal_free(m, mem)\
- if (m == gm) dlfree(mem); else mspace_free(m,mem);
-#else /* MSPACES */
-#define internal_malloc(m, b) dlmalloc(b)
-#define internal_free(m, mem) dlfree(mem)
-#endif /* MSPACES */
-#endif /* ONLY_MSPACES */
-
-/* ----------------------- Direct-mmapping chunks ----------------------- */
-
-/*
- Directly mmapped chunks are set up with an offset to the start of
- the mmapped region stored in the prev_foot field of the chunk. This
- allows reconstruction of the required argument to MUNMAP when freed,
- and also allows adjustment of the returned chunk to meet alignment
- requirements (especially in memalign). There is also enough space
- allocated to hold a fake next chunk of size SIZE_T_SIZE to maintain
- the PINUSE bit so frees can be checked.
-*/
-
-/* Malloc using mmap */
-static void* mmap_alloc(mstate m, size_t nb) {
- size_t mmsize = mmap_align(nb + SIX_SIZE_T_SIZES + CHUNK_ALIGN_MASK);
- if (mmsize > nb) { /* Check for wrap around 0 */
- char* mm = (char*)(CALL_DIRECT_MMAP(mmsize));
- if (mm != CMFAIL) {
- size_t offset = align_offset(chunk2mem(mm));
- size_t psize = mmsize - offset - MMAP_FOOT_PAD;
- mchunkptr p = (mchunkptr)(mm + offset);
- p->prev_foot = offset | IS_MMAPPED_BIT;
- (p)->head = (psize|CINUSE_BIT);
- mark_inuse_foot(m, p, psize);
- chunk_plus_offset(p, psize)->head = FENCEPOST_HEAD;
- chunk_plus_offset(p, psize+SIZE_T_SIZE)->head = 0;
-
- if (mm < m->least_addr)
- m->least_addr = mm;
- if ((m->footprint += mmsize) > m->max_footprint)
- m->max_footprint = m->footprint;
- assert(is_aligned(chunk2mem(p)));
- check_mmapped_chunk(m, p);
- return chunk2mem(p);
- }
- }
- return 0;
-}
-
-/* Realloc using mmap */
-static mchunkptr mmap_resize(mstate m, mchunkptr oldp, size_t nb) {
- size_t oldsize = chunksize(oldp);
- if (is_small(nb)) /* Can't shrink mmap regions below small size */
- return 0;
- /* Keep old chunk if big enough but not too big */
- if (oldsize >= nb + SIZE_T_SIZE &&
- (oldsize - nb) <= (mparams.granularity << 1))
- return oldp;
- else {
- size_t offset = oldp->prev_foot & ~IS_MMAPPED_BIT;
- size_t oldmmsize = oldsize + offset + MMAP_FOOT_PAD;
- size_t newmmsize = mmap_align(nb + SIX_SIZE_T_SIZES + CHUNK_ALIGN_MASK);
- char* cp = (char*)CALL_MREMAP((char*)oldp - offset,
- oldmmsize, newmmsize, 1);
- if (cp != CMFAIL) {
- mchunkptr newp = (mchunkptr)(cp + offset);
- size_t psize = newmmsize - offset - MMAP_FOOT_PAD;
- newp->head = (psize|CINUSE_BIT);
- mark_inuse_foot(m, newp, psize);
- chunk_plus_offset(newp, psize)->head = FENCEPOST_HEAD;
- chunk_plus_offset(newp, psize+SIZE_T_SIZE)->head = 0;
-
- if (cp < m->least_addr)
- m->least_addr = cp;
- if ((m->footprint += newmmsize - oldmmsize) > m->max_footprint)
- m->max_footprint = m->footprint;
- check_mmapped_chunk(m, newp);
- return newp;
- }
- }
- return 0;
-}
-
-/* -------------------------- mspace management -------------------------- */
-
-/* Initialize top chunk and its size */
-static void init_top(mstate m, mchunkptr p, size_t psize) {
- /* Ensure alignment */
- size_t offset = align_offset(chunk2mem(p));
- p = (mchunkptr)((char*)p + offset);
- psize -= offset;
-
- m->top = p;
- m->topsize = psize;
- p->head = psize | PINUSE_BIT;
- /* set size of fake trailing chunk holding overhead space only once */
- chunk_plus_offset(p, psize)->head = TOP_FOOT_SIZE;
- m->trim_check = mparams.trim_threshold; /* reset on each update */
-}
-
-/* Initialize bins for a new mstate that is otherwise zeroed out */
-static void init_bins(mstate m) {
- /* Establish circular links for smallbins */
- bindex_t i;
- for (i = 0; i < NSMALLBINS; ++i) {
- sbinptr bin = smallbin_at(m,i);
- bin->fd = bin->bk = bin;
- }
-}
-
-#if PROCEED_ON_ERROR
-
-/* default corruption action */
-static void reset_on_error(mstate m) {
- int i;
- ++malloc_corruption_error_count;
- /* Reinitialize fields to forget about all memory */
- m->smallbins = m->treebins = 0;
- m->dvsize = m->topsize = 0;
- m->seg.base = 0;
- m->seg.size = 0;
- m->seg.next = 0;
- m->top = m->dv = 0;
- for (i = 0; i < NTREEBINS; ++i)
- *treebin_at(m, i) = 0;
- init_bins(m);
-}
-#endif /* PROCEED_ON_ERROR */
-
-/* Allocate chunk and prepend remainder with chunk in successor base. */
-static void* prepend_alloc(mstate m, char* newbase, char* oldbase,
- size_t nb) {
- mchunkptr p = align_as_chunk(newbase);
- mchunkptr oldfirst = align_as_chunk(oldbase);
- size_t psize = (char*)oldfirst - (char*)p;
- mchunkptr q = chunk_plus_offset(p, nb);
- size_t qsize = psize - nb;
- set_size_and_pinuse_of_inuse_chunk(m, p, nb);
-
- assert((char*)oldfirst > (char*)q);
- assert(pinuse(oldfirst));
- assert(qsize >= MIN_CHUNK_SIZE);
-
- /* consolidate remainder with first chunk of old base */
- if (oldfirst == m->top) {
- size_t tsize = m->topsize += qsize;
- m->top = q;
- q->head = tsize | PINUSE_BIT;
- check_top_chunk(m, q);
- }
- else if (oldfirst == m->dv) {
- size_t dsize = m->dvsize += qsize;
- m->dv = q;
- set_size_and_pinuse_of_free_chunk(q, dsize);
- }
- else {
- if (!cinuse(oldfirst)) {
- size_t nsize = chunksize(oldfirst);
- unlink_chunk(m, oldfirst, nsize);
- oldfirst = chunk_plus_offset(oldfirst, nsize);
- qsize += nsize;
- }
- set_free_with_pinuse(q, qsize, oldfirst);
- insert_chunk(m, q, qsize);
- check_free_chunk(m, q);
- }
-
- check_malloced_chunk(m, chunk2mem(p), nb);
- return chunk2mem(p);
-}
-
-/* Add a segment to hold a new noncontiguous region */
-static void add_segment(mstate m, char* tbase, size_t tsize, flag_t mmapped) {
- /* Determine locations and sizes of segment, fenceposts, old top */
- char* old_top = (char*)m->top;
- msegmentptr oldsp = segment_holding(m, old_top);
- char* old_end = oldsp->base + oldsp->size;
- size_t ssize = pad_request(sizeof(struct malloc_segment));
- char* rawsp = old_end - (ssize + FOUR_SIZE_T_SIZES + CHUNK_ALIGN_MASK);
- size_t offset = align_offset(chunk2mem(rawsp));
- char* asp = rawsp + offset;
- char* csp = (asp < (old_top + MIN_CHUNK_SIZE))? old_top : asp;
- mchunkptr sp = (mchunkptr)csp;
- msegmentptr ss = (msegmentptr)(chunk2mem(sp));
- mchunkptr tnext = chunk_plus_offset(sp, ssize);
- mchunkptr p = tnext;
- int nfences = 0;
-
- /* reset top to new space */
- init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE);
-
- /* Set up segment record */
- assert(is_aligned(ss));
- set_size_and_pinuse_of_inuse_chunk(m, sp, ssize);
- *ss = m->seg; /* Push current record */
- m->seg.base = tbase;
- m->seg.size = tsize;
- m->seg.sflags = mmapped;
- m->seg.next = ss;
-
- /* Insert trailing fenceposts */
- for (;;) {
- mchunkptr nextp = chunk_plus_offset(p, SIZE_T_SIZE);
- p->head = FENCEPOST_HEAD;
- ++nfences;
- if ((char*)(&(nextp->head)) < old_end)
- p = nextp;
- else
- break;
- }
- assert(nfences >= 2);
-
- /* Insert the rest of old top into a bin as an ordinary free chunk */
- if (csp != old_top) {
- mchunkptr q = (mchunkptr)old_top;
- size_t psize = csp - old_top;
- mchunkptr tn = chunk_plus_offset(q, psize);
- set_free_with_pinuse(q, psize, tn);
- insert_chunk(m, q, psize);
- }
-
- check_top_chunk(m, m->top);
-}
-
/* -------------------------- System allocation -------------------------- */
/* Get memory from system using MORECORE or MMAP */
--
gitgitgadget
^ permalink raw reply related
* [PATCH v3 4/6] to be squashed into 3/6 (chunk 1 of 3)
From: Johannes Schindelin via GitGitGadget @ 2026-05-08 12:50 UTC (permalink / raw)
To: git; +Cc: Patrick Steinhardt, Johannes Schindelin, Johannes Schindelin
In-Reply-To: <pull.2104.v3.git.1778244661.gitgitgadget@gmail.com>
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Assisted-by: Opus 4.7
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
compat/nedmalloc/malloc.c.h | 2235 -----------------------------------
1 file changed, 2235 deletions(-)
diff --git a/compat/nedmalloc/malloc.c.h b/compat/nedmalloc/malloc.c.h
index e0c567586c..b4fb8c8846 100644
--- a/compat/nedmalloc/malloc.c.h
+++ b/compat/nedmalloc/malloc.c.h
@@ -1,2238 +1,3 @@
-/*
- This is a version (aka dlmalloc) of malloc/free/realloc written by
- Doug Lea and released to the public domain, as explained at
- http://creativecommons.org/licenses/publicdomain. Send questions,
- comments, complaints, performance data, etc to dl@cs.oswego.edu
-
-* Version pre-2.8.4 Mon Nov 27 11:22:37 2006 (dl at gee)
-
- Note: There may be an updated version of this malloc obtainable at
- ftp://gee.cs.oswego.edu/pub/misc/malloc.c
- Check before installing!
-
-* Quickstart
-
- This library is all in one file to simplify the most common usage:
- ftp it, compile it (-O3), and link it into another program. All of
- the compile-time options default to reasonable values for use on
- most platforms. You might later want to step through various
- compile-time and dynamic tuning options.
-
- For convenience, an include file for code using this malloc is at:
- ftp://gee.cs.oswego.edu/pub/misc/malloc-2.8.4.h
- You don't really need this .h file unless you call functions not
- defined in your system include files. The .h file contains only the
- excerpts from this file needed for using this malloc on ANSI C/C++
- systems, so long as you haven't changed compile-time options about
- naming and tuning parameters. If you do, then you can create your
- own malloc.h that does include all settings by cutting at the point
- indicated below. Note that you may already by default be using a C
- library containing a malloc that is based on some version of this
- malloc (for example in linux). You might still want to use the one
- in this file to customize settings or to avoid overheads associated
- with library versions.
-
-* Vital statistics:
-
- Supported pointer/size_t representation: 4 or 8 bytes
- size_t MUST be an unsigned type of the same width as
- pointers. (If you are using an ancient system that declares
- size_t as a signed type, or need it to be a different width
- than pointers, you can use a previous release of this malloc
- (e.g. 2.7.2) supporting these.)
-
- Alignment: 8 bytes (default)
- This suffices for nearly all current machines and C compilers.
- However, you can define MALLOC_ALIGNMENT to be wider than this
- if necessary (up to 128bytes), at the expense of using more space.
-
- Minimum overhead per allocated chunk: 4 or 8 bytes (if 4byte sizes)
- 8 or 16 bytes (if 8byte sizes)
- Each malloced chunk has a hidden word of overhead holding size
- and status information, and additional cross-check word
- if FOOTERS is defined.
-
- Minimum allocated size: 4-byte ptrs: 16 bytes (including overhead)
- 8-byte ptrs: 32 bytes (including overhead)
-
- Even a request for zero bytes (i.e., malloc(0)) returns a
- pointer to something of the minimum allocatable size.
- The maximum overhead wastage (i.e., number of extra bytes
- allocated than were requested in malloc) is less than or equal
- to the minimum size, except for requests >= mmap_threshold that
- are serviced via mmap(), where the worst case wastage is about
- 32 bytes plus the remainder from a system page (the minimal
- mmap unit); typically 4096 or 8192 bytes.
-
- Security: static-safe; optionally more or less
- The "security" of malloc refers to the ability of malicious
- code to accentuate the effects of errors (for example, freeing
- space that is not currently malloc'ed or overwriting past the
- ends of chunks) in code that calls malloc. This malloc
- guarantees not to modify any memory locations below the base of
- heap, i.e., static variables, even in the presence of usage
- errors. The routines additionally detect most improper frees
- and reallocs. All this holds as long as the static bookkeeping
- for malloc itself is not corrupted by some other means. This
- is only one aspect of security -- these checks do not, and
- cannot, detect all possible programming errors.
-
- If FOOTERS is defined nonzero, then each allocated chunk
- carries an additional check word to verify that it was malloced
- from its space. These check words are the same within each
- execution of a program using malloc, but differ across
- executions, so externally crafted fake chunks cannot be
- freed. This improves security by rejecting frees/reallocs that
- could corrupt heap memory, in addition to the checks preventing
- writes to statics that are always on. This may further improve
- security at the expense of time and space overhead. (Note that
- FOOTERS may also be worth using with MSPACES.)
-
- By default detected errors cause the program to abort (calling
- "abort()"). You can override this to instead proceed past
- errors by defining PROCEED_ON_ERROR. In this case, a bad free
- has no effect, and a malloc that encounters a bad address
- caused by user overwrites will ignore the bad address by
- dropping pointers and indices to all known memory. This may
- be appropriate for programs that should continue if at all
- possible in the face of programming errors, although they may
- run out of memory because dropped memory is never reclaimed.
-
- If you don't like either of these options, you can define
- CORRUPTION_ERROR_ACTION and USAGE_ERROR_ACTION to do anything
- else. And if you are sure that your program using malloc has
- no errors or vulnerabilities, you can define INSECURE to 1,
- which might (or might not) provide a small performance improvement.
-
- Thread-safety: NOT thread-safe unless USE_LOCKS defined
- When USE_LOCKS is defined, each public call to malloc, free,
- etc is surrounded with either a pthread mutex or a win32
- spinlock (depending on WIN32). This is not especially fast, and
- can be a major bottleneck. It is designed only to provide
- minimal protection in concurrent environments, and to provide a
- basis for extensions. If you are using malloc in a concurrent
- program, consider instead using nedmalloc
- (http://www.nedprod.com/programs/portable/nedmalloc/) or
- ptmalloc (See http://www.malloc.de), which are derived
- from versions of this malloc.
-
- System requirements: Any combination of MORECORE and/or MMAP/MUNMAP
- This malloc can use unix sbrk or any emulation (invoked using
- the CALL_MORECORE macro) and/or mmap/munmap or any emulation
- (invoked using CALL_MMAP/CALL_MUNMAP) to get and release system
- memory. On most unix systems, it tends to work best if both
- MORECORE and MMAP are enabled. On Win32, it uses emulations
- based on VirtualAlloc. It also uses common C library functions
- like memset.
-
- Compliance: I believe it is compliant with the Single Unix Specification
- (See http://www.unix.org). Also SVID/XPG, ANSI C, and probably
- others as well.
-
-* Overview of algorithms
-
- This is not the fastest, most space-conserving, most portable, or
- most tunable malloc ever written. However it is among the fastest
- while also being among the most space-conserving, portable and
- tunable. Consistent balance across these factors results in a good
- general-purpose allocator for malloc-intensive programs.
-
- In most ways, this malloc is a best-fit allocator. Generally, it
- chooses the best-fitting existing chunk for a request, with ties
- broken in approximately least-recently-used order. (This strategy
- normally maintains low fragmentation.) However, for requests less
- than 256bytes, it deviates from best-fit when there is not an
- exactly fitting available chunk by preferring to use space adjacent
- to that used for the previous small request, as well as by breaking
- ties in approximately most-recently-used order. (These enhance
- locality of series of small allocations.) And for very large requests
- (>= 256Kb by default), it relies on system memory mapping
- facilities, if supported. (This helps avoid carrying around and
- possibly fragmenting memory used only for large chunks.)
-
- All operations (except malloc_stats and mallinfo) have execution
- times that are bounded by a constant factor of the number of bits in
- a size_t, not counting any clearing in calloc or copying in realloc,
- or actions surrounding MORECORE and MMAP that have times
- proportional to the number of non-contiguous regions returned by
- system allocation routines, which is often just 1. In real-time
- applications, you can optionally suppress segment traversals using
- NO_SEGMENT_TRAVERSAL, which assures bounded execution even when
- system allocators return non-contiguous spaces, at the typical
- expense of carrying around more memory and increased fragmentation.
-
- The implementation is not very modular and seriously overuses
- macros. Perhaps someday all C compilers will do as good a job
- inlining modular code as can now be done by brute-force expansion,
- but now, enough of them seem not to.
-
- Some compilers issue a lot of warnings about code that is
- dead/unreachable only on some platforms, and also about intentional
- uses of negation on unsigned types. All known cases of each can be
- ignored.
-
- For a longer but out of date high-level description, see
- http://gee.cs.oswego.edu/dl/html/malloc.html
-
-* MSPACES
- If MSPACES is defined, then in addition to malloc, free, etc.,
- this file also defines mspace_malloc, mspace_free, etc. These
- are versions of malloc routines that take an "mspace" argument
- obtained using create_mspace, to control all internal bookkeeping.
- If ONLY_MSPACES is defined, only these versions are compiled.
- So if you would like to use this allocator for only some allocations,
- and your system malloc for others, you can compile with
- ONLY_MSPACES and then do something like...
- static mspace mymspace = create_mspace(0,0); // for example
- #define mymalloc(bytes) mspace_malloc(mymspace, bytes)
-
- (Note: If you only need one instance of an mspace, you can instead
- use "USE_DL_PREFIX" to relabel the global malloc.)
-
- You can similarly create thread-local allocators by storing
- mspaces as thread-locals. For example:
- static __thread mspace tlms = 0;
- void* tlmalloc(size_t bytes) {
- if (tlms == 0) tlms = create_mspace(0, 0);
- return mspace_malloc(tlms, bytes);
- }
- void tlfree(void* mem) { mspace_free(tlms, mem); }
-
- Unless FOOTERS is defined, each mspace is completely independent.
- You cannot allocate from one and free to another (although
- conformance is only weakly checked, so usage errors are not always
- caught). If FOOTERS is defined, then each chunk carries around a tag
- indicating its originating mspace, and frees are directed to their
- originating spaces.
-
- ------------------------- Compile-time options ---------------------------
-
-Be careful in setting #define values for numerical constants of type
-size_t. On some systems, literal values are not automatically extended
-to size_t precision unless they are explicitly casted. You can also
-use the symbolic values MAX_SIZE_T, SIZE_T_ONE, etc below.
-
-WIN32 default: defined if _WIN32 defined
- Defining WIN32 sets up defaults for MS environment and compilers.
- Otherwise defaults are for unix. Beware that there seem to be some
- cases where this malloc might not be a pure drop-in replacement for
- Win32 malloc: Random-looking failures from Win32 GDI API's (eg;
- SetDIBits()) may be due to bugs in some video driver implementations
- when pixel buffers are malloc()ed, and the region spans more than
- one VirtualAlloc()ed region. Because dlmalloc uses a small (64Kb)
- default granularity, pixel buffers may straddle virtual allocation
- regions more often than when using the Microsoft allocator. You can
- avoid this by using VirtualAlloc() and VirtualFree() for all pixel
- buffers rather than using malloc(). If this is not possible,
- recompile this malloc with a larger DEFAULT_GRANULARITY.
-
-MALLOC_ALIGNMENT default: (size_t)8
- Controls the minimum alignment for malloc'ed chunks. It must be a
- power of two and at least 8, even on machines for which smaller
- alignments would suffice. It may be defined as larger than this
- though. Note however that code and data structures are optimized for
- the case of 8-byte alignment.
-
-MSPACES default: 0 (false)
- If true, compile in support for independent allocation spaces.
- This is only supported if HAVE_MMAP is true.
-
-ONLY_MSPACES default: 0 (false)
- If true, only compile in mspace versions, not regular versions.
-
-USE_LOCKS default: 0 (false)
- Causes each call to each public routine to be surrounded with
- pthread or WIN32 mutex lock/unlock. (If set true, this can be
- overridden on a per-mspace basis for mspace versions.) If set to a
- non-zero value other than 1, locks are used, but their
- implementation is left out, so lock functions must be supplied manually.
-
-USE_SPIN_LOCKS default: 1 iff USE_LOCKS and on x86 using gcc or MSC
- If true, uses custom spin locks for locking. This is currently
- supported only for x86 platforms using gcc or recent MS compilers.
- Otherwise, posix locks or win32 critical sections are used.
-
-FOOTERS default: 0
- If true, provide extra checking and dispatching by placing
- information in the footers of allocated chunks. This adds
- space and time overhead.
-
-INSECURE default: 0
- If true, omit checks for usage errors and heap space overwrites.
-
-USE_DL_PREFIX default: NOT defined
- Causes compiler to prefix all public routines with the string 'dl'.
- This can be useful when you only want to use this malloc in one part
- of a program, using your regular system malloc elsewhere.
-
-ABORT default: defined as abort()
- Defines how to abort on failed checks. On most systems, a failed
- check cannot die with an "assert" or even print an informative
- message, because the underlying print routines in turn call malloc,
- which will fail again. Generally, the best policy is to simply call
- abort(). It's not very useful to do more than this because many
- errors due to overwriting will show up as address faults (null, odd
- addresses etc) rather than malloc-triggered checks, so will also
- abort. Also, most compilers know that abort() does not return, so
- can better optimize code conditionally calling it.
-
-PROCEED_ON_ERROR default: defined as 0 (false)
- Controls whether detected bad addresses cause them to bypassed
- rather than aborting. If set, detected bad arguments to free and
- realloc are ignored. And all bookkeeping information is zeroed out
- upon a detected overwrite of freed heap space, thus losing the
- ability to ever return it from malloc again, but enabling the
- application to proceed. If PROCEED_ON_ERROR is defined, the
- static variable malloc_corruption_error_count is compiled in
- and can be examined to see if errors have occurred. This option
- generates slower code than the default abort policy.
-
-DEBUG default: NOT defined
- The DEBUG setting is mainly intended for people trying to modify
- this code or diagnose problems when porting to new platforms.
- However, it may also be able to better isolate user errors than just
- using runtime checks. The assertions in the check routines spell
- out in more detail the assumptions and invariants underlying the
- algorithms. The checking is fairly extensive, and will slow down
- execution noticeably. Calling malloc_stats or mallinfo with DEBUG
- set will attempt to check every non-mmapped allocated and free chunk
- in the course of computing the summaries.
-
-ABORT_ON_ASSERT_FAILURE default: defined as 1 (true)
- Debugging assertion failures can be nearly impossible if your
- version of the assert macro causes malloc to be called, which will
- lead to a cascade of further failures, blowing the runtime stack.
- ABORT_ON_ASSERT_FAILURE cause assertions failures to call abort(),
- which will usually make debugging easier.
-
-MALLOC_FAILURE_ACTION default: sets errno to ENOMEM, or no-op on win32
- The action to take before "return 0" when malloc fails to be able to
- return memory because there is none available.
-
-HAVE_MORECORE default: 1 (true) unless win32 or ONLY_MSPACES
- True if this system supports sbrk or an emulation of it.
-
-MORECORE default: sbrk
- The name of the sbrk-style system routine to call to obtain more
- memory. See below for guidance on writing custom MORECORE
- functions. The type of the argument to sbrk/MORECORE varies across
- systems. It cannot be size_t, because it supports negative
- arguments, so it is normally the signed type of the same width as
- size_t (sometimes declared as "intptr_t"). It doesn't much matter
- though. Internally, we only call it with arguments less than half
- the max value of a size_t, which should work across all reasonable
- possibilities, although sometimes generating compiler warnings.
-
-MORECORE_CONTIGUOUS default: 1 (true) if HAVE_MORECORE
- If true, take advantage of fact that consecutive calls to MORECORE
- with positive arguments always return contiguous increasing
- addresses. This is true of unix sbrk. It does not hurt too much to
- set it true anyway, since malloc copes with non-contiguities.
- Setting it false when definitely non-contiguous saves time
- and possibly wasted space it would take to discover this though.
-
-MORECORE_CANNOT_TRIM default: NOT defined
- True if MORECORE cannot release space back to the system when given
- negative arguments. This is generally necessary only if you are
- using a hand-crafted MORECORE function that cannot handle negative
- arguments.
-
-NO_SEGMENT_TRAVERSAL default: 0
- If non-zero, suppresses traversals of memory segments
- returned by either MORECORE or CALL_MMAP. This disables
- merging of segments that are contiguous, and selectively
- releasing them to the OS if unused, but bounds execution times.
-
-HAVE_MMAP default: 1 (true)
- True if this system supports mmap or an emulation of it. If so, and
- HAVE_MORECORE is not true, MMAP is used for all system
- allocation. If set and HAVE_MORECORE is true as well, MMAP is
- primarily used to directly allocate very large blocks. It is also
- used as a backup strategy in cases where MORECORE fails to provide
- space from system. Note: A single call to MUNMAP is assumed to be
- able to unmap memory that may have be allocated using multiple calls
- to MMAP, so long as they are adjacent.
-
-HAVE_MREMAP default: 1 on linux, else 0
- If true realloc() uses mremap() to re-allocate large blocks and
- extend or shrink allocation spaces.
-
-MMAP_CLEARS default: 1 except on WINCE.
- True if mmap clears memory so calloc doesn't need to. This is true
- for standard unix mmap using /dev/zero and on WIN32 except for WINCE.
-
-USE_BUILTIN_FFS default: 0 (i.e., not used)
- Causes malloc to use the builtin ffs() function to compute indices.
- Some compilers may recognize and intrinsify ffs to be faster than the
- supplied C version. Also, the case of x86 using gcc is special-cased
- to an asm instruction, so is already as fast as it can be, and so
- this setting has no effect. Similarly for Win32 under recent MS compilers.
- (On most x86s, the asm version is only slightly faster than the C version.)
-
-malloc_getpagesize default: derive from system includes, or 4096.
- The system page size. To the extent possible, this malloc manages
- memory from the system in page-size units. This may be (and
- usually is) a function rather than a constant. This is ignored
- if WIN32, where page size is determined using getSystemInfo during
- initialization.
-
-USE_DEV_RANDOM default: 0 (i.e., not used)
- Causes malloc to use /dev/random to initialize secure magic seed for
- stamping footers. Otherwise, the current time is used.
-
-NO_MALLINFO default: 0
- If defined, don't compile "mallinfo". This can be a simple way
- of dealing with mismatches between system declarations and
- those in this file.
-
-MALLINFO_FIELD_TYPE default: size_t
- The type of the fields in the mallinfo struct. This was originally
- defined as "int" in SVID etc, but is more usefully defined as
- size_t. The value is used only if HAVE_USR_INCLUDE_MALLOC_H is not set
-
-REALLOC_ZERO_BYTES_FREES default: not defined
- This should be set if a call to realloc with zero bytes should
- be the same as a call to free. Some people think it should. Otherwise,
- since this malloc returns a unique pointer for malloc(0), so does
- realloc(p, 0).
-
-LACKS_UNISTD_H, LACKS_FCNTL_H, LACKS_SYS_PARAM_H, LACKS_SYS_MMAN_H
-LACKS_STRINGS_H, LACKS_STRING_H, LACKS_SYS_TYPES_H, LACKS_ERRNO_H
-LACKS_STDLIB_H default: NOT defined unless on WIN32
- Define these if your system does not have these header files.
- You might need to manually insert some of the declarations they provide.
-
-DEFAULT_GRANULARITY default: page size if MORECORE_CONTIGUOUS,
- system_info.dwAllocationGranularity in WIN32,
- otherwise 64K.
- Also settable using mallopt(M_GRANULARITY, x)
- The unit for allocating and deallocating memory from the system. On
- most systems with contiguous MORECORE, there is no reason to
- make this more than a page. However, systems with MMAP tend to
- either require or encourage larger granularities. You can increase
- this value to prevent system allocation functions to be called so
- often, especially if they are slow. The value must be at least one
- page and must be a power of two. Setting to 0 causes initialization
- to either page size or win32 region size. (Note: In previous
- versions of malloc, the equivalent of this option was called
- "TOP_PAD")
-
-DEFAULT_TRIM_THRESHOLD default: 2MB
- Also settable using mallopt(M_TRIM_THRESHOLD, x)
- The maximum amount of unused top-most memory to keep before
- releasing via malloc_trim in free(). Automatic trimming is mainly
- useful in long-lived programs using contiguous MORECORE. Because
- trimming via sbrk can be slow on some systems, and can sometimes be
- wasteful (in cases where programs immediately afterward allocate
- more large chunks) the value should be high enough so that your
- overall system performance would improve by releasing this much
- memory. As a rough guide, you might set to a value close to the
- average size of a process (program) running on your system.
- Releasing this much memory would allow such a process to run in
- memory. Generally, it is worth tuning trim thresholds when a
- program undergoes phases where several large chunks are allocated
- and released in ways that can reuse each other's storage, perhaps
- mixed with phases where there are no such chunks at all. The trim
- value must be greater than page size to have any useful effect. To
- disable trimming completely, you can set to MAX_SIZE_T. Note that the trick
- some people use of mallocing a huge space and then freeing it at
- program startup, in an attempt to reserve system memory, doesn't
- have the intended effect under automatic trimming, since that memory
- will immediately be returned to the system.
-
-DEFAULT_MMAP_THRESHOLD default: 256K
- Also settable using mallopt(M_MMAP_THRESHOLD, x)
- The request size threshold for using MMAP to directly service a
- request. Requests of at least this size that cannot be allocated
- using already-existing space will be serviced via mmap. (If enough
- normal freed space already exists it is used instead.) Using mmap
- segregates relatively large chunks of memory so that they can be
- individually obtained and released from the host system. A request
- serviced through mmap is never reused by any other request (at least
- not directly; the system may just so happen to remap successive
- requests to the same locations). Segregating space in this way has
- the benefits that: Mmapped space can always be individually released
- back to the system, which helps keep the system level memory demands
- of a long-lived program low. Also, mapped memory doesn't become
- `locked' between other chunks, as can happen with normally allocated
- chunks, which means that even trimming via malloc_trim would not
- release them. However, it has the disadvantage that the space
- cannot be reclaimed, consolidated, and then used to service later
- requests, as happens with normal chunks. The advantages of mmap
- nearly always outweigh disadvantages for "large" chunks, but the
- value of "large" may vary across systems. The default is an
- empirically derived value that works well in most systems. You can
- disable mmap by setting to MAX_SIZE_T.
-
-MAX_RELEASE_CHECK_RATE default: 4095 unless not HAVE_MMAP
- The number of consolidated frees between checks to release
- unused segments when freeing. When using non-contiguous segments,
- especially with multiple mspaces, checking only for topmost space
- doesn't always suffice to trigger trimming. To compensate for this,
- free() will, with a period of MAX_RELEASE_CHECK_RATE (or the
- current number of segments, if greater) try to release unused
- segments to the OS when freeing chunks that result in
- consolidation. The best value for this parameter is a compromise
- between slowing down frees with relatively costly checks that
- rarely trigger versus holding on to unused memory. To effectively
- disable, set to MAX_SIZE_T. This may lead to a very slight speed
- improvement at the expense of carrying around more memory.
-*/
-
-/* Version identifier to allow people to support multiple versions */
-#ifndef DLMALLOC_VERSION
-#define DLMALLOC_VERSION 20804
-#endif /* DLMALLOC_VERSION */
-
-#if defined(linux)
-#define _GNU_SOURCE 1
-#endif
-
-#ifndef WIN32
-#ifdef _WIN32
-#define WIN32 1
-#endif /* _WIN32 */
-#ifdef _WIN32_WCE
-#define LACKS_FCNTL_H
-#define WIN32 1
-#endif /* _WIN32_WCE */
-#endif /* WIN32 */
-#ifdef WIN32
-#define WIN32_LEAN_AND_MEAN
-#ifndef _WIN32_WINNT
-#define _WIN32_WINNT 0x603
-#endif
-#include <windows.h>
-#define HAVE_MMAP 1
-#define HAVE_MORECORE 0
-#define LACKS_UNISTD_H
-#define LACKS_SYS_PARAM_H
-#define LACKS_SYS_MMAN_H
-#define LACKS_STRING_H
-#define LACKS_STRINGS_H
-#define LACKS_SYS_TYPES_H
-#define LACKS_ERRNO_H
-#ifndef MALLOC_FAILURE_ACTION
-#define MALLOC_FAILURE_ACTION
-#endif /* MALLOC_FAILURE_ACTION */
-#ifdef _WIN32_WCE /* WINCE reportedly does not clear */
-#define MMAP_CLEARS 0
-#else
-#define MMAP_CLEARS 1
-#endif /* _WIN32_WCE */
-#endif /* WIN32 */
-
-#if defined(DARWIN) || defined(_DARWIN)
-/* Mac OSX docs advise not to use sbrk; it seems better to use mmap */
-#ifndef HAVE_MORECORE
-#define HAVE_MORECORE 0
-#define HAVE_MMAP 1
-/* OSX allocators provide 16 byte alignment */
-#ifndef MALLOC_ALIGNMENT
-#define MALLOC_ALIGNMENT ((size_t)16U)
-#endif
-#endif /* HAVE_MORECORE */
-#endif /* DARWIN */
-
-#ifndef LACKS_SYS_TYPES_H
-#include <sys/types.h> /* For size_t */
-#endif /* LACKS_SYS_TYPES_H */
-
-/* The maximum possible size_t value has all bits set */
-#define MAX_SIZE_T (~(size_t)0)
-
-#ifndef ONLY_MSPACES
-#define ONLY_MSPACES 0 /* define to a value */
-#else
-#define ONLY_MSPACES 1
-#endif /* ONLY_MSPACES */
-#ifndef MSPACES
-#if ONLY_MSPACES
-#define MSPACES 1
-#else /* ONLY_MSPACES */
-#define MSPACES 0
-#endif /* ONLY_MSPACES */
-#endif /* MSPACES */
-#ifndef MALLOC_ALIGNMENT
-#define MALLOC_ALIGNMENT ((size_t)8U)
-#endif /* MALLOC_ALIGNMENT */
-#ifndef FOOTERS
-#define FOOTERS 0
-#endif /* FOOTERS */
-#ifndef ABORT
-#define ABORT abort()
-#endif /* ABORT */
-#ifndef ABORT_ON_ASSERT_FAILURE
-#define ABORT_ON_ASSERT_FAILURE 1
-#endif /* ABORT_ON_ASSERT_FAILURE */
-#ifndef PROCEED_ON_ERROR
-#define PROCEED_ON_ERROR 0
-#endif /* PROCEED_ON_ERROR */
-#ifndef USE_LOCKS
-#define USE_LOCKS 0
-#endif /* USE_LOCKS */
-#ifndef USE_SPIN_LOCKS
-#if USE_LOCKS && (defined(__GNUC__) && ((defined(__i386__) || defined(__x86_64__)))) || (defined(_MSC_VER) && _MSC_VER>=1310)
-#define USE_SPIN_LOCKS 1
-#else
-#define USE_SPIN_LOCKS 0
-#endif /* USE_LOCKS && ... */
-#endif /* USE_SPIN_LOCKS */
-#ifndef INSECURE
-#define INSECURE 0
-#endif /* INSECURE */
-#ifndef HAVE_MMAP
-#define HAVE_MMAP 1
-#endif /* HAVE_MMAP */
-#ifndef MMAP_CLEARS
-#define MMAP_CLEARS 1
-#endif /* MMAP_CLEARS */
-#ifndef HAVE_MREMAP
-#ifdef linux
-#define HAVE_MREMAP 1
-#else /* linux */
-#define HAVE_MREMAP 0
-#endif /* linux */
-#endif /* HAVE_MREMAP */
-#ifndef MALLOC_FAILURE_ACTION
-#define MALLOC_FAILURE_ACTION errno = ENOMEM;
-#endif /* MALLOC_FAILURE_ACTION */
-#ifndef HAVE_MORECORE
-#if ONLY_MSPACES
-#define HAVE_MORECORE 0
-#else /* ONLY_MSPACES */
-#define HAVE_MORECORE 1
-#endif /* ONLY_MSPACES */
-#endif /* HAVE_MORECORE */
-#if !HAVE_MORECORE
-#define MORECORE_CONTIGUOUS 0
-#else /* !HAVE_MORECORE */
-#define MORECORE_DEFAULT sbrk
-#ifndef MORECORE_CONTIGUOUS
-#define MORECORE_CONTIGUOUS 1
-#endif /* MORECORE_CONTIGUOUS */
-#endif /* HAVE_MORECORE */
-#ifndef DEFAULT_GRANULARITY
-#if (MORECORE_CONTIGUOUS || defined(WIN32))
-#define DEFAULT_GRANULARITY (0) /* 0 means to compute in init_mparams */
-#else /* MORECORE_CONTIGUOUS */
-#define DEFAULT_GRANULARITY ((size_t)64U * (size_t)1024U)
-#endif /* MORECORE_CONTIGUOUS */
-#endif /* DEFAULT_GRANULARITY */
-#ifndef DEFAULT_TRIM_THRESHOLD
-#ifndef MORECORE_CANNOT_TRIM
-#define DEFAULT_TRIM_THRESHOLD ((size_t)2U * (size_t)1024U * (size_t)1024U)
-#else /* MORECORE_CANNOT_TRIM */
-#define DEFAULT_TRIM_THRESHOLD MAX_SIZE_T
-#endif /* MORECORE_CANNOT_TRIM */
-#endif /* DEFAULT_TRIM_THRESHOLD */
-#ifndef DEFAULT_MMAP_THRESHOLD
-#if HAVE_MMAP
-#define DEFAULT_MMAP_THRESHOLD ((size_t)256U * (size_t)1024U)
-#else /* HAVE_MMAP */
-#define DEFAULT_MMAP_THRESHOLD MAX_SIZE_T
-#endif /* HAVE_MMAP */
-#endif /* DEFAULT_MMAP_THRESHOLD */
-#ifndef MAX_RELEASE_CHECK_RATE
-#if HAVE_MMAP
-#define MAX_RELEASE_CHECK_RATE 4095
-#else
-#define MAX_RELEASE_CHECK_RATE MAX_SIZE_T
-#endif /* HAVE_MMAP */
-#endif /* MAX_RELEASE_CHECK_RATE */
-#ifndef USE_BUILTIN_FFS
-#define USE_BUILTIN_FFS 0
-#endif /* USE_BUILTIN_FFS */
-#ifndef USE_DEV_RANDOM
-#define USE_DEV_RANDOM 0
-#endif /* USE_DEV_RANDOM */
-#ifndef NO_MALLINFO
-#define NO_MALLINFO 0
-#endif /* NO_MALLINFO */
-#ifndef MALLINFO_FIELD_TYPE
-#define MALLINFO_FIELD_TYPE size_t
-#endif /* MALLINFO_FIELD_TYPE */
-#ifndef NO_SEGMENT_TRAVERSAL
-#define NO_SEGMENT_TRAVERSAL 0
-#endif /* NO_SEGMENT_TRAVERSAL */
-
-/*
- mallopt tuning options. SVID/XPG defines four standard parameter
- numbers for mallopt, normally defined in malloc.h. None of these
- are used in this malloc, so setting them has no effect. But this
- malloc does support the following options.
-*/
-
-#define M_TRIM_THRESHOLD (-1)
-#define M_GRANULARITY (-2)
-#define M_MMAP_THRESHOLD (-3)
-
-/* ------------------------ Mallinfo declarations ------------------------ */
-
-#if !NO_MALLINFO
-/*
- This version of malloc supports the standard SVID/XPG mallinfo
- routine that returns a struct containing usage properties and
- statistics. It should work on any system that has a
- /usr/include/malloc.h defining struct mallinfo. The main
- declaration needed is the mallinfo struct that is returned (by-copy)
- by mallinfo(). The malloinfo struct contains a bunch of fields that
- are not even meaningful in this version of malloc. These fields are
- are instead filled by mallinfo() with other numbers that might be of
- interest.
-
- HAVE_USR_INCLUDE_MALLOC_H should be set if you have a
- /usr/include/malloc.h file that includes a declaration of struct
- mallinfo. If so, it is included; else a compliant version is
- declared below. These must be precisely the same for mallinfo() to
- work. The original SVID version of this struct, defined on most
- systems with mallinfo, declares all fields as ints. But some others
- define as unsigned long. If your system defines the fields using a
- type of different width than listed here, you MUST #include your
- system version and #define HAVE_USR_INCLUDE_MALLOC_H.
-*/
-
-/* #define HAVE_USR_INCLUDE_MALLOC_H */
-
-#ifdef HAVE_USR_INCLUDE_MALLOC_H
-#include "/usr/include/malloc.h"
-#else /* HAVE_USR_INCLUDE_MALLOC_H */
-#ifndef STRUCT_MALLINFO_DECLARED
-#define STRUCT_MALLINFO_DECLARED 1
-struct mallinfo {
- MALLINFO_FIELD_TYPE arena; /* non-mmapped space allocated from system */
- MALLINFO_FIELD_TYPE ordblks; /* number of free chunks */
- MALLINFO_FIELD_TYPE smblks; /* always 0 */
- MALLINFO_FIELD_TYPE hblks; /* always 0 */
- MALLINFO_FIELD_TYPE hblkhd; /* space in mmapped regions */
- MALLINFO_FIELD_TYPE usmblks; /* maximum total allocated space */
- MALLINFO_FIELD_TYPE fsmblks; /* always 0 */
- MALLINFO_FIELD_TYPE uordblks; /* total allocated space */
- MALLINFO_FIELD_TYPE fordblks; /* total free space */
- MALLINFO_FIELD_TYPE keepcost; /* releasable (via malloc_trim) space */
-};
-#endif /* STRUCT_MALLINFO_DECLARED */
-#endif /* HAVE_USR_INCLUDE_MALLOC_H */
-#endif /* NO_MALLINFO */
-
-/*
- Try to persuade compilers to inline. The most critical functions for
- inlining are defined as macros, so these aren't used for them.
-*/
-
-#ifdef __MINGW64_VERSION_MAJOR
-#undef FORCEINLINE
-#endif
-#ifndef FORCEINLINE
- #if defined(__GNUC__)
-#define FORCEINLINE __inline __attribute__ ((always_inline))
- #elif defined(_MSC_VER)
- #define FORCEINLINE __forceinline
- #endif
-#endif
-#ifndef NOINLINE
- #if defined(__GNUC__)
- #define NOINLINE __attribute__ ((noinline))
- #elif defined(_MSC_VER)
- #define NOINLINE __declspec(noinline)
- #else
- #define NOINLINE
- #endif
-#endif
-
-#ifdef __cplusplus
-extern "C" {
-#ifndef FORCEINLINE
- #define FORCEINLINE inline
-#endif
-#endif /* __cplusplus */
-#ifndef FORCEINLINE
- #define FORCEINLINE
-#endif
-
-#if !ONLY_MSPACES
-
-/* ------------------- Declarations of public routines ------------------- */
-
-#ifndef USE_DL_PREFIX
-#define dlcalloc calloc
-#define dlfree free
-#define dlmalloc malloc
-#define dlmemalign memalign
-#define dlrealloc realloc
-#define dlvalloc valloc
-#define dlpvalloc pvalloc
-#define dlmallinfo mallinfo
-#define dlmallopt mallopt
-#define dlmalloc_trim malloc_trim
-#define dlmalloc_stats malloc_stats
-#define dlmalloc_usable_size malloc_usable_size
-#define dlmalloc_footprint malloc_footprint
-#define dlmalloc_max_footprint malloc_max_footprint
-#define dlindependent_calloc independent_calloc
-#define dlindependent_comalloc independent_comalloc
-#endif /* USE_DL_PREFIX */
-
-
-/*
- malloc(size_t n)
- Returns a pointer to a newly allocated chunk of at least n bytes, or
- null if no space is available, in which case errno is set to ENOMEM
- on ANSI C systems.
-
- If n is zero, malloc returns a minimum-sized chunk. (The minimum
- size is 16 bytes on most 32bit systems, and 32 bytes on 64bit
- systems.) Note that size_t is an unsigned type, so calls with
- arguments that would be negative if signed are interpreted as
- requests for huge amounts of space, which will often fail. The
- maximum supported value of n differs across systems, but is in all
- cases less than the maximum representable value of a size_t.
-*/
-void* dlmalloc(size_t);
-
-/*
- free(void* p)
- Releases the chunk of memory pointed to by p, that had been previously
- allocated using malloc or a related routine such as realloc.
- It has no effect if p is null. If p was not malloced or already
- freed, free(p) will by default cause the current program to abort.
-*/
-void dlfree(void*);
-
-/*
- calloc(size_t n_elements, size_t element_size);
- Returns a pointer to n_elements * element_size bytes, with all locations
- set to zero.
-*/
-void* dlcalloc(size_t, size_t);
-
-/*
- realloc(void* p, size_t n)
- Returns a pointer to a chunk of size n that contains the same data
- as does chunk p up to the minimum of (n, p's size) bytes, or null
- if no space is available.
-
- The returned pointer may or may not be the same as p. The algorithm
- prefers extending p in most cases when possible, otherwise it
- employs the equivalent of a malloc-copy-free sequence.
-
- If p is null, realloc is equivalent to malloc.
-
- If space is not available, realloc returns null, errno is set (if on
- ANSI) and p is NOT freed.
-
- if n is for fewer bytes than already held by p, the newly unused
- space is lopped off and freed if possible. realloc with a size
- argument of zero (re)allocates a minimum-sized chunk.
-
- The old unix realloc convention of allowing the last-free'd chunk
- to be used as an argument to realloc is not supported.
-*/
-
-void* dlrealloc(void*, size_t);
-
-/*
- memalign(size_t alignment, size_t n);
- Returns a pointer to a newly allocated chunk of n bytes, aligned
- in accord with the alignment argument.
-
- The alignment argument should be a power of two. If the argument is
- not a power of two, the nearest greater power is used.
- 8-byte alignment is guaranteed by normal malloc calls, so don't
- bother calling memalign with an argument of 8 or less.
-
- Overreliance on memalign is a sure way to fragment space.
-*/
-void* dlmemalign(size_t, size_t);
-
-/*
- valloc(size_t n);
- Equivalent to memalign(pagesize, n), where pagesize is the page
- size of the system. If the pagesize is unknown, 4096 is used.
-*/
-void* dlvalloc(size_t);
-
-/*
- mallopt(int parameter_number, int parameter_value)
- Sets tunable parameters The format is to provide a
- (parameter-number, parameter-value) pair. mallopt then sets the
- corresponding parameter to the argument value if it can (i.e., so
- long as the value is meaningful), and returns 1 if successful else
- 0. To workaround the fact that mallopt is specified to use int,
- not size_t parameters, the value -1 is specially treated as the
- maximum unsigned size_t value.
-
- SVID/XPG/ANSI defines four standard param numbers for mallopt,
- normally defined in malloc.h. None of these are use in this malloc,
- so setting them has no effect. But this malloc also supports other
- options in mallopt. See below for details. Briefly, supported
- parameters are as follows (listed defaults are for "typical"
- configurations).
-
- Symbol param # default allowed param values
- M_TRIM_THRESHOLD -1 2*1024*1024 any (-1 disables)
- M_GRANULARITY -2 page size any power of 2 >= page size
- M_MMAP_THRESHOLD -3 256*1024 any (or 0 if no MMAP support)
-*/
-int dlmallopt(int, int);
-
-/*
- malloc_footprint();
- Returns the number of bytes obtained from the system. The total
- number of bytes allocated by malloc, realloc etc., is less than this
- value. Unlike mallinfo, this function returns only a precomputed
- result, so can be called frequently to monitor memory consumption.
- Even if locks are otherwise defined, this function does not use them,
- so results might not be up to date.
-*/
-size_t dlmalloc_footprint(void);
-
-/*
- malloc_max_footprint();
- Returns the maximum number of bytes obtained from the system. This
- value will be greater than current footprint if deallocated space
- has been reclaimed by the system. The peak number of bytes allocated
- by malloc, realloc etc., is less than this value. Unlike mallinfo,
- this function returns only a precomputed result, so can be called
- frequently to monitor memory consumption. Even if locks are
- otherwise defined, this function does not use them, so results might
- not be up to date.
-*/
-size_t dlmalloc_max_footprint(void);
-
-#if !NO_MALLINFO
-/*
- mallinfo()
- Returns (by copy) a struct containing various summary statistics:
-
- arena: current total non-mmapped bytes allocated from system
- ordblks: the number of free chunks
- smblks: always zero.
- hblks: current number of mmapped regions
- hblkhd: total bytes held in mmapped regions
- usmblks: the maximum total allocated space. This will be greater
- than current total if trimming has occurred.
- fsmblks: always zero
- uordblks: current total allocated space (normal or mmapped)
- fordblks: total free space
- keepcost: the maximum number of bytes that could ideally be released
- back to system via malloc_trim. ("ideally" means that
- it ignores page restrictions etc.)
-
- Because these fields are ints, but internal bookkeeping may
- be kept as longs, the reported values may wrap around zero and
- thus be inaccurate.
-*/
-struct mallinfo dlmallinfo(void);
-#endif /* NO_MALLINFO */
-
-/*
- independent_calloc(size_t n_elements, size_t element_size, void* chunks[]);
-
- independent_calloc is similar to calloc, but instead of returning a
- single cleared space, it returns an array of pointers to n_elements
- independent elements that can hold contents of size elem_size, each
- of which starts out cleared, and can be independently freed,
- realloc'ed etc. The elements are guaranteed to be adjacently
- allocated (this is not guaranteed to occur with multiple callocs or
- mallocs), which may also improve cache locality in some
- applications.
-
- The "chunks" argument is optional (i.e., may be null, which is
- probably the most typical usage). If it is null, the returned array
- is itself dynamically allocated and should also be freed when it is
- no longer needed. Otherwise, the chunks array must be of at least
- n_elements in length. It is filled in with the pointers to the
- chunks.
-
- In either case, independent_calloc returns this pointer array, or
- null if the allocation failed. If n_elements is zero and "chunks"
- is null, it returns a chunk representing an array with zero elements
- (which should be freed if not wanted).
-
- Each element must be individually freed when it is no longer
- needed. If you'd like to instead be able to free all at once, you
- should instead use regular calloc and assign pointers into this
- space to represent elements. (In this case though, you cannot
- independently free elements.)
-
- independent_calloc simplifies and speeds up implementations of many
- kinds of pools. It may also be useful when constructing large data
- structures that initially have a fixed number of fixed-sized nodes,
- but the number is not known at compile time, and some of the nodes
- may later need to be freed. For example:
-
- struct Node { int item; struct Node* next; };
-
- struct Node* build_list() {
- struct Node** pool;
- int n = read_number_of_nodes_needed();
- if (n <= 0) return 0;
- pool = (struct Node**)(independent_calloc(n, sizeof(struct Node), 0);
- if (pool == 0) die();
- // organize into a linked list...
- struct Node* first = pool[0];
- for (i = 0; i < n-1; ++i)
- pool[i]->next = pool[i+1];
- free(pool); // Can now free the array (or not, if it is needed later)
- return first;
- }
-*/
-void** dlindependent_calloc(size_t, size_t, void**);
-
-/*
- independent_comalloc(size_t n_elements, size_t sizes[], void* chunks[]);
-
- independent_comalloc allocates, all at once, a set of n_elements
- chunks with sizes indicated in the "sizes" array. It returns
- an array of pointers to these elements, each of which can be
- independently freed, realloc'ed etc. The elements are guaranteed to
- be adjacently allocated (this is not guaranteed to occur with
- multiple callocs or mallocs), which may also improve cache locality
- in some applications.
-
- The "chunks" argument is optional (i.e., may be null). If it is null
- the returned array is itself dynamically allocated and should also
- be freed when it is no longer needed. Otherwise, the chunks array
- must be of at least n_elements in length. It is filled in with the
- pointers to the chunks.
-
- In either case, independent_comalloc returns this pointer array, or
- null if the allocation failed. If n_elements is zero and chunks is
- null, it returns a chunk representing an array with zero elements
- (which should be freed if not wanted).
-
- Each element must be individually freed when it is no longer
- needed. If you'd like to instead be able to free all at once, you
- should instead use a single regular malloc, and assign pointers at
- particular offsets in the aggregate space. (In this case though, you
- cannot independently free elements.)
-
- independent_comallac differs from independent_calloc in that each
- element may have a different size, and also that it does not
- automatically clear elements.
-
- independent_comalloc can be used to speed up allocation in cases
- where several structs or objects must always be allocated at the
- same time. For example:
-
- struct Head { ... }
- struct Foot { ... }
-
- void send_message(char* msg) {
- int msglen = strlen(msg);
- size_t sizes[3] = { sizeof(struct Head), msglen, sizeof(struct Foot) };
- void* chunks[3];
- if (independent_comalloc(3, sizes, chunks) == 0)
- die();
- struct Head* head = (struct Head*)(chunks[0]);
- char* body = (char*)(chunks[1]);
- struct Foot* foot = (struct Foot*)(chunks[2]);
- // ...
- }
-
- In general though, independent_comalloc is worth using only for
- larger values of n_elements. For small values, you probably won't
- detect enough difference from series of malloc calls to bother.
-
- Overuse of independent_comalloc can increase overall memory usage,
- since it cannot reuse existing noncontiguous small chunks that
- might be available for some of the elements.
-*/
-void** dlindependent_comalloc(size_t, size_t*, void**);
-
-
-/*
- pvalloc(size_t n);
- Equivalent to valloc(minimum-page-that-holds(n)), that is,
- round up n to nearest pagesize.
- */
-void* dlpvalloc(size_t);
-
-/*
- malloc_trim(size_t pad);
-
- If possible, gives memory back to the system (via negative arguments
- to sbrk) if there is unused memory at the `high' end of the malloc
- pool or in unused MMAP segments. You can call this after freeing
- large blocks of memory to potentially reduce the system-level memory
- requirements of a program. However, it cannot guarantee to reduce
- memory. Under some allocation patterns, some large free blocks of
- memory will be locked between two used chunks, so they cannot be
- given back to the system.
-
- The `pad' argument to malloc_trim represents the amount of free
- trailing space to leave untrimmed. If this argument is zero, only
- the minimum amount of memory to maintain internal data structures
- will be left. Non-zero arguments can be supplied to maintain enough
- trailing space to service future expected allocations without having
- to re-obtain memory from the system.
-
- Malloc_trim returns 1 if it actually released any memory, else 0.
-*/
-int dlmalloc_trim(size_t);
-
-/*
- malloc_stats();
- Prints on stderr the amount of space obtained from the system (both
- via sbrk and mmap), the maximum amount (which may be more than
- current if malloc_trim and/or munmap got called), and the current
- number of bytes allocated via malloc (or realloc, etc) but not yet
- freed. Note that this is the number of bytes allocated, not the
- number requested. It will be larger than the number requested
- because of alignment and bookkeeping overhead. Because it includes
- alignment wastage as being in use, this figure may be greater than
- zero even when no user-level chunks are allocated.
-
- The reported current and maximum system memory can be inaccurate if
- a program makes other calls to system memory allocation functions
- (normally sbrk) outside of malloc.
-
- malloc_stats prints only the most commonly interesting statistics.
- More information can be obtained by calling mallinfo.
-*/
-void dlmalloc_stats(void);
-
-#endif /* ONLY_MSPACES */
-
-/*
- malloc_usable_size(void* p);
-
- Returns the number of bytes you can actually use in
- an allocated chunk, which may be more than you requested (although
- often not) due to alignment and minimum size constraints.
- You can use this many bytes without worrying about
- overwriting other allocated objects. This is not a particularly great
- programming practice. malloc_usable_size can be more useful in
- debugging and assertions, for example:
-
- p = malloc(n);
- assert(malloc_usable_size(p) >= 256);
-*/
-size_t dlmalloc_usable_size(void*);
-
-
-#if MSPACES
-
-/*
- mspace is an opaque type representing an independent
- region of space that supports mspace_malloc, etc.
-*/
-typedef void* mspace;
-
-/*
- create_mspace creates and returns a new independent space with the
- given initial capacity, or, if 0, the default granularity size. It
- returns null if there is no system memory available to create the
- space. If argument locked is non-zero, the space uses a separate
- lock to control access. The capacity of the space will grow
- dynamically as needed to service mspace_malloc requests. You can
- control the sizes of incremental increases of this space by
- compiling with a different DEFAULT_GRANULARITY or dynamically
- setting with mallopt(M_GRANULARITY, value).
-*/
-mspace create_mspace(size_t capacity, int locked);
-
-/*
- destroy_mspace destroys the given space, and attempts to return all
- of its memory back to the system, returning the total number of
- bytes freed. After destruction, the results of access to all memory
- used by the space become undefined.
-*/
-size_t destroy_mspace(mspace msp);
-
-/*
- create_mspace_with_base uses the memory supplied as the initial base
- of a new mspace. Part (less than 128*sizeof(size_t) bytes) of this
- space is used for bookkeeping, so the capacity must be at least this
- large. (Otherwise 0 is returned.) When this initial space is
- exhausted, additional memory will be obtained from the system.
- Destroying this space will deallocate all additionally allocated
- space (if possible) but not the initial base.
-*/
-mspace create_mspace_with_base(void* base, size_t capacity, int locked);
-
-/*
- mspace_mmap_large_chunks controls whether requests for large chunks
- are allocated in their own mmapped regions, separate from others in
- this mspace. By default this is enabled, which reduces
- fragmentation. However, such chunks are not necessarily released to
- the system upon destroy_mspace. Disabling by setting to false may
- increase fragmentation, but avoids leakage when relying on
- destroy_mspace to release all memory allocated using this space.
-*/
-int mspace_mmap_large_chunks(mspace msp, int enable);
-
-
-/*
- mspace_malloc behaves as malloc, but operates within
- the given space.
-*/
-void* mspace_malloc(mspace msp, size_t bytes);
-
-/*
- mspace_free behaves as free, but operates within
- the given space.
-
- If compiled with FOOTERS==1, mspace_free is not actually needed.
- free may be called instead of mspace_free because freed chunks from
- any space are handled by their originating spaces.
-*/
-void mspace_free(mspace msp, void* mem);
-
-/*
- mspace_realloc behaves as realloc, but operates within
- the given space.
-
- If compiled with FOOTERS==1, mspace_realloc is not actually
- needed. realloc may be called instead of mspace_realloc because
- realloced chunks from any space are handled by their originating
- spaces.
-*/
-void* mspace_realloc(mspace msp, void* mem, size_t newsize);
-
-/*
- mspace_calloc behaves as calloc, but operates within
- the given space.
-*/
-void* mspace_calloc(mspace msp, size_t n_elements, size_t elem_size);
-
-/*
- mspace_memalign behaves as memalign, but operates within
- the given space.
-*/
-void* mspace_memalign(mspace msp, size_t alignment, size_t bytes);
-
-/*
- mspace_independent_calloc behaves as independent_calloc, but
- operates within the given space.
-*/
-void** mspace_independent_calloc(mspace msp, size_t n_elements,
- size_t elem_size, void* chunks[]);
-
-/*
- mspace_independent_comalloc behaves as independent_comalloc, but
- operates within the given space.
-*/
-void** mspace_independent_comalloc(mspace msp, size_t n_elements,
- size_t sizes[], void* chunks[]);
-
-/*
- mspace_footprint() returns the number of bytes obtained from the
- system for this space.
-*/
-size_t mspace_footprint(mspace msp);
-
-/*
- mspace_max_footprint() returns the peak number of bytes obtained from the
- system for this space.
-*/
-size_t mspace_max_footprint(mspace msp);
-
-
-#if !NO_MALLINFO
-/*
- mspace_mallinfo behaves as mallinfo, but reports properties of
- the given space.
-*/
-struct mallinfo mspace_mallinfo(mspace msp);
-#endif /* NO_MALLINFO */
-
-/*
- malloc_usable_size(void* p) behaves the same as malloc_usable_size;
-*/
- size_t mspace_usable_size(void* mem);
-
-/*
- mspace_malloc_stats behaves as malloc_stats, but reports
- properties of the given space.
-*/
-void mspace_malloc_stats(mspace msp);
-
-/*
- mspace_trim behaves as malloc_trim, but
- operates within the given space.
-*/
-int mspace_trim(mspace msp, size_t pad);
-
-/*
- An alias for mallopt.
-*/
-int mspace_mallopt(int, int);
-
-#endif /* MSPACES */
-
-#ifdef __cplusplus
-}; /* end of extern "C" */
-#endif /* __cplusplus */
-
-/*
- ========================================================================
- To make a fully customizable malloc.h header file, cut everything
- above this line, put into file malloc.h, edit to suit, and #include it
- on the next line, as well as in programs that use this malloc.
- ========================================================================
-*/
-
-/* #include "malloc.h" */
-
-/*------------------------------ internal #includes ---------------------- */
-
-#ifdef WIN32
-#ifndef __GNUC__
-#pragma warning( disable : 4146 ) /* no "unsigned" warnings */
-#endif
-#endif /* WIN32 */
-
-#include <stdio.h> /* for printing in malloc_stats */
-
-#ifndef LACKS_ERRNO_H
-#include <errno.h> /* for MALLOC_FAILURE_ACTION */
-#endif /* LACKS_ERRNO_H */
-#if FOOTERS
-#include <time.h> /* for magic initialization */
-#endif /* FOOTERS */
-#ifndef LACKS_STDLIB_H
-#include <stdlib.h> /* for abort() */
-#endif /* LACKS_STDLIB_H */
-#ifdef DEBUG
-#if ABORT_ON_ASSERT_FAILURE
-#define assert(x) if(!(x)) ABORT
-#else /* ABORT_ON_ASSERT_FAILURE */
-#include <assert.h>
-#endif /* ABORT_ON_ASSERT_FAILURE */
-#else /* DEBUG */
-#ifndef assert
-#define assert(x)
-#endif
-#define DEBUG 0
-#endif /* DEBUG */
-#ifndef LACKS_STRING_H
-#include <string.h> /* for memset etc */
-#endif /* LACKS_STRING_H */
-#if USE_BUILTIN_FFS
-#ifndef LACKS_STRINGS_H
-#include <strings.h> /* for ffs */
-#endif /* LACKS_STRINGS_H */
-#endif /* USE_BUILTIN_FFS */
-#if HAVE_MMAP
-#ifndef LACKS_SYS_MMAN_H
-#include <sys/mman.h> /* for mmap */
-#endif /* LACKS_SYS_MMAN_H */
-#ifndef LACKS_FCNTL_H
-#include <fcntl.h>
-#endif /* LACKS_FCNTL_H */
-#endif /* HAVE_MMAP */
-#ifndef LACKS_UNISTD_H
-#include <unistd.h> /* for sbrk, sysconf */
-#else /* LACKS_UNISTD_H */
-#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__)
-extern void* sbrk(ptrdiff_t);
-#endif /* FreeBSD etc */
-#endif /* LACKS_UNISTD_H */
-
-/* Declarations for locking */
-#if USE_LOCKS
-#ifndef WIN32
-#include <pthread.h>
-#if defined (__SVR4) && defined (__sun) /* solaris */
-#include <thread.h>
-#endif /* solaris */
-#else
-#ifndef _M_AMD64
-/* These are already defined on AMD64 builds */
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-#ifndef __MINGW32__
-LONG __cdecl _InterlockedCompareExchange(LONG volatile *Dest, LONG Exchange, LONG Comp);
-LONG __cdecl _InterlockedExchange(LONG volatile *Target, LONG Value);
-#endif
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
-#endif /* _M_AMD64 */
-#ifndef __MINGW32__
-#pragma intrinsic (_InterlockedCompareExchange)
-#pragma intrinsic (_InterlockedExchange)
-#else
- /* --[ start GCC compatibility ]----------------------------------------------
- * Compatibility <intrin_x86.h> header for GCC -- GCC equivalents of intrinsic
- * Microsoft Visual C++ functions. Originally developed for the ReactOS
- * (<http://www.reactos.org/>) and TinyKrnl (<http://www.tinykrnl.org/>)
- * projects.
- *
- * Copyright (c) 2006 KJK::Hyperion <hackbunny@reactos.com>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- */
-
- /*** Atomic operations ***/
- #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) > 40100
- #undef _ReadWriteBarrier
- #define _ReadWriteBarrier() __sync_synchronize()
- #else
- static __inline__ __attribute__((always_inline)) long __sync_lock_test_and_set(volatile long * const Target, const long Value)
- {
- long res;
- __asm__ __volatile__("xchg%z0 %2, %0" : "=g" (*(Target)), "=r" (res) : "1" (Value));
- return res;
- }
- static void __inline__ __attribute__((always_inline)) _MemoryBarrier(void)
- {
- __asm__ __volatile__("" : : : "memory");
- }
- #define _ReadWriteBarrier() _MemoryBarrier()
- #endif
- /* BUGBUG: GCC only supports full barriers */
- static __inline__ __attribute__((always_inline)) long _InterlockedExchange(volatile long * const Target, const long Value)
- {
- /* NOTE: __sync_lock_test_and_set would be an acquire barrier, so we force a full barrier */
- _ReadWriteBarrier();
- return __sync_lock_test_and_set(Target, Value);
- }
- /* --[ end GCC compatibility ]---------------------------------------------- */
-#endif
-#define interlockedcompareexchange _InterlockedCompareExchange
-#define interlockedexchange _InterlockedExchange
-#endif /* Win32 */
-#endif /* USE_LOCKS */
-
-/* Declarations for bit scanning on win32 */
-#if defined(_MSC_VER) && _MSC_VER>=1300
-#ifndef BitScanForward /* Try to avoid pulling in WinNT.h */
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-unsigned char _BitScanForward(unsigned long *index, unsigned long mask);
-unsigned char _BitScanReverse(unsigned long *index, unsigned long mask);
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
-
-#define BitScanForward _BitScanForward
-#define BitScanReverse _BitScanReverse
-#pragma intrinsic(_BitScanForward)
-#pragma intrinsic(_BitScanReverse)
-#endif /* BitScanForward */
-#endif /* defined(_MSC_VER) && _MSC_VER>=1300 */
-
-#ifndef WIN32
-#ifndef malloc_getpagesize
-# ifdef _SC_PAGESIZE /* some SVR4 systems omit an underscore */
-# ifndef _SC_PAGE_SIZE
-# define _SC_PAGE_SIZE _SC_PAGESIZE
-# endif
-# endif
-# ifdef _SC_PAGE_SIZE
-# define malloc_getpagesize sysconf(_SC_PAGE_SIZE)
-# else
-# if defined(BSD) || defined(DGUX) || defined(HAVE_GETPAGESIZE)
- extern size_t getpagesize();
-# define malloc_getpagesize getpagesize()
-# else
-# ifdef WIN32 /* use supplied emulation of getpagesize */
-# define malloc_getpagesize getpagesize()
-# else
-# ifndef LACKS_SYS_PARAM_H
-# include <sys/param.h>
-# endif
-# ifdef EXEC_PAGESIZE
-# define malloc_getpagesize EXEC_PAGESIZE
-# else
-# ifdef NBPG
-# ifndef CLSIZE
-# define malloc_getpagesize NBPG
-# else
-# define malloc_getpagesize (NBPG * CLSIZE)
-# endif
-# else
-# ifdef NBPC
-# define malloc_getpagesize NBPC
-# else
-# ifdef PAGESIZE
-# define malloc_getpagesize PAGESIZE
-# else /* just guess */
-# define malloc_getpagesize ((size_t)4096U)
-# endif
-# endif
-# endif
-# endif
-# endif
-# endif
-# endif
-#endif
-#endif
-
-
-
-/* ------------------- size_t and alignment properties -------------------- */
-
-/* The byte and bit size of a size_t */
-#define SIZE_T_SIZE (sizeof(size_t))
-#define SIZE_T_BITSIZE (sizeof(size_t) << 3)
-
-/* Some constants coerced to size_t */
-/* Annoying but necessary to avoid errors on some platforms */
-#define SIZE_T_ZERO ((size_t)0)
-#define SIZE_T_ONE ((size_t)1)
-#define SIZE_T_TWO ((size_t)2)
-#define SIZE_T_FOUR ((size_t)4)
-#define TWO_SIZE_T_SIZES (SIZE_T_SIZE<<1)
-#define FOUR_SIZE_T_SIZES (SIZE_T_SIZE<<2)
-#define SIX_SIZE_T_SIZES (FOUR_SIZE_T_SIZES+TWO_SIZE_T_SIZES)
-#define HALF_MAX_SIZE_T (MAX_SIZE_T / 2U)
-
-/* The bit mask value corresponding to MALLOC_ALIGNMENT */
-#define CHUNK_ALIGN_MASK (MALLOC_ALIGNMENT - SIZE_T_ONE)
-
-/* True if address a has acceptable alignment */
-#define is_aligned(A) (((size_t)((A)) & (CHUNK_ALIGN_MASK)) == 0)
-
-/* the number of bytes to offset an address to align it */
-#define align_offset(A)\
- ((((size_t)(A) & CHUNK_ALIGN_MASK) == 0)? 0 :\
- ((MALLOC_ALIGNMENT - ((size_t)(A) & CHUNK_ALIGN_MASK)) & CHUNK_ALIGN_MASK))
-
-/* -------------------------- MMAP preliminaries ------------------------- */
-
-/*
- If HAVE_MORECORE or HAVE_MMAP are false, we just define calls and
- checks to fail so compiler optimizer can delete code rather than
- using so many "#if"s.
-*/
-
-
-/* MORECORE and MMAP must return MFAIL on failure */
-#define MFAIL ((void*)(MAX_SIZE_T))
-#define CMFAIL ((char*)(MFAIL)) /* defined for convenience */
-
-#if HAVE_MMAP
-
-#ifndef WIN32
-#define MUNMAP_DEFAULT(a, s) munmap((a), (s))
-#define MMAP_PROT (PROT_READ|PROT_WRITE)
-#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
-#define MAP_ANONYMOUS MAP_ANON
-#endif /* MAP_ANON */
-#ifdef MAP_ANONYMOUS
-#define MMAP_FLAGS (MAP_PRIVATE|MAP_ANONYMOUS)
-#define MMAP_DEFAULT(s) mmap(0, (s), MMAP_PROT, MMAP_FLAGS, -1, 0)
-#else /* MAP_ANONYMOUS */
-/*
- Nearly all versions of mmap support MAP_ANONYMOUS, so the following
- is unlikely to be needed, but is supplied just in case.
-*/
-#define MMAP_FLAGS (MAP_PRIVATE)
-static int dev_zero_fd = -1; /* Cached file descriptor for /dev/zero. */
-#define MMAP_DEFAULT(s) ((dev_zero_fd < 0) ? \
- (dev_zero_fd = open("/dev/zero", O_RDWR), \
- mmap(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0)) : \
- mmap(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0))
-#endif /* MAP_ANONYMOUS */
-
-#define DIRECT_MMAP_DEFAULT(s) MMAP_DEFAULT(s)
-
-#else /* WIN32 */
-
-/* Win32 MMAP via VirtualAlloc */
-static FORCEINLINE void* win32mmap(size_t size) {
- void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
- return (ptr != 0)? ptr: MFAIL;
-}
-
-/* For direct MMAP, use MEM_TOP_DOWN to minimize interference */
-static FORCEINLINE void* win32direct_mmap(size_t size) {
- void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN,
- PAGE_READWRITE);
- return (ptr != 0)? ptr: MFAIL;
-}
-
-/* This function supports releasing coalesced segments */
-static FORCEINLINE int win32munmap(void* ptr, size_t size) {
- MEMORY_BASIC_INFORMATION minfo;
- char* cptr = (char*)ptr;
- while (size) {
- if (VirtualQuery(cptr, &minfo, sizeof(minfo)) == 0)
- return -1;
- if (minfo.BaseAddress != cptr || minfo.AllocationBase != cptr ||
- minfo.State != MEM_COMMIT || minfo.RegionSize > size)
- return -1;
- if (VirtualFree(cptr, 0, MEM_RELEASE) == 0)
- return -1;
- cptr += minfo.RegionSize;
- size -= minfo.RegionSize;
- }
- return 0;
-}
-
-#define MMAP_DEFAULT(s) win32mmap(s)
-#define MUNMAP_DEFAULT(a, s) win32munmap((a), (s))
-#define DIRECT_MMAP_DEFAULT(s) win32direct_mmap(s)
-#endif /* WIN32 */
-#endif /* HAVE_MMAP */
-
-#if HAVE_MREMAP
-#ifndef WIN32
-#define MREMAP_DEFAULT(addr, osz, nsz, mv) mremap((addr), (osz), (nsz), (mv))
-#endif /* WIN32 */
-#endif /* HAVE_MREMAP */
-
-
-/**
- * Define CALL_MORECORE
- */
-#if HAVE_MORECORE
- #ifdef MORECORE
- #define CALL_MORECORE(S) MORECORE(S)
- #else /* MORECORE */
- #define CALL_MORECORE(S) MORECORE_DEFAULT(S)
- #endif /* MORECORE */
-#else /* HAVE_MORECORE */
- #define CALL_MORECORE(S) MFAIL
-#endif /* HAVE_MORECORE */
-
-/**
- * Define CALL_MMAP/CALL_MUNMAP/CALL_DIRECT_MMAP
- */
-#if HAVE_MMAP
- #define IS_MMAPPED_BIT (SIZE_T_ONE)
- #define USE_MMAP_BIT (SIZE_T_ONE)
-
- #ifdef MMAP
- #define CALL_MMAP(s) MMAP(s)
- #else /* MMAP */
- #define CALL_MMAP(s) MMAP_DEFAULT(s)
- #endif /* MMAP */
- #ifdef MUNMAP
- #define CALL_MUNMAP(a, s) MUNMAP((a), (s))
- #else /* MUNMAP */
- #define CALL_MUNMAP(a, s) MUNMAP_DEFAULT((a), (s))
- #endif /* MUNMAP */
- #ifdef DIRECT_MMAP
- #define CALL_DIRECT_MMAP(s) DIRECT_MMAP(s)
- #else /* DIRECT_MMAP */
- #define CALL_DIRECT_MMAP(s) DIRECT_MMAP_DEFAULT(s)
- #endif /* DIRECT_MMAP */
-#else /* HAVE_MMAP */
- #define IS_MMAPPED_BIT (SIZE_T_ZERO)
- #define USE_MMAP_BIT (SIZE_T_ZERO)
-
- #define MMAP(s) MFAIL
- #define MUNMAP(a, s) (-1)
- #define DIRECT_MMAP(s) MFAIL
- #define CALL_DIRECT_MMAP(s) DIRECT_MMAP(s)
- #define CALL_MMAP(s) MMAP(s)
- #define CALL_MUNMAP(a, s) MUNMAP((a), (s))
-#endif /* HAVE_MMAP */
-
-/**
- * Define CALL_MREMAP
- */
-#if HAVE_MMAP && HAVE_MREMAP
- #ifdef MREMAP
- #define CALL_MREMAP(addr, osz, nsz, mv) MREMAP((addr), (osz), (nsz), (mv))
- #else /* MREMAP */
- #define CALL_MREMAP(addr, osz, nsz, mv) MREMAP_DEFAULT((addr), (osz), (nsz), (mv))
- #endif /* MREMAP */
-#else /* HAVE_MMAP && HAVE_MREMAP */
- #define CALL_MREMAP(addr, osz, nsz, mv) MFAIL
-#endif /* HAVE_MMAP && HAVE_MREMAP */
-
-/* mstate bit set if contiguous morecore disabled or failed */
-#define USE_NONCONTIGUOUS_BIT (4U)
-
-/* segment bit set in create_mspace_with_base */
-#define EXTERN_BIT (8U)
-
-
-/* --------------------------- Lock preliminaries ------------------------ */
-
-/*
- When locks are defined, there is one global lock, plus
- one per-mspace lock.
-
- The global lock_ensures that mparams.magic and other unique
- mparams values are initialized only once. It also protects
- sequences of calls to MORECORE. In many cases sys_alloc requires
- two calls, that should not be interleaved with calls by other
- threads. This does not protect against direct calls to MORECORE
- by other threads not using this lock, so there is still code to
- cope the best we can on interference.
-
- Per-mspace locks surround calls to malloc, free, etc. To enable use
- in layered extensions, per-mspace locks are reentrant.
-
- Because lock-protected regions generally have bounded times, it is
- OK to use the supplied simple spinlocks in the custom versions for
- x86.
-
- If USE_LOCKS is > 1, the definitions of lock routines here are
- bypassed, in which case you will need to define at least
- INITIAL_LOCK, ACQUIRE_LOCK, RELEASE_LOCK and possibly TRY_LOCK
- (which is not used in this malloc, but commonly needed in
- extensions.)
-*/
-
-#if USE_LOCKS == 1
-
-#if USE_SPIN_LOCKS
-#ifndef WIN32
-
-/* Custom pthread-style spin locks on x86 and x64 for gcc */
-struct pthread_mlock_t {
- volatile unsigned int l;
- volatile unsigned int c;
- volatile pthread_t threadid;
-};
-#define MLOCK_T struct pthread_mlock_t
-#define CURRENT_THREAD pthread_self()
-#define INITIAL_LOCK(sl) (memset(sl, 0, sizeof(MLOCK_T)), 0)
-#define ACQUIRE_LOCK(sl) pthread_acquire_lock(sl)
-#define RELEASE_LOCK(sl) pthread_release_lock(sl)
-#define TRY_LOCK(sl) pthread_try_lock(sl)
-#define SPINS_PER_YIELD 63
-
-static MLOCK_T malloc_global_mutex = { 0, 0, 0};
-
-static FORCEINLINE int pthread_acquire_lock (MLOCK_T *sl) {
- int spins = 0;
- volatile unsigned int* lp = &sl->l;
- for (;;) {
- if (*lp != 0) {
- if (sl->threadid == CURRENT_THREAD) {
- ++sl->c;
- return 0;
- }
- }
- else {
- /* place args to cmpxchgl in locals to evade oddities in some gccs */
- int cmp = 0;
- int val = 1;
- int ret;
- __asm__ __volatile__ ("lock; cmpxchgl %1, %2"
- : "=a" (ret)
- : "r" (val), "m" (*(lp)), "0"(cmp)
- : "memory", "cc");
- if (!ret) {
- assert(!sl->threadid);
- sl->c = 1;
- sl->threadid = CURRENT_THREAD;
- return 0;
- }
- if ((++spins & SPINS_PER_YIELD) == 0) {
-#if defined (__SVR4) && defined (__sun) /* solaris */
- thr_yield();
-#else
-#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__)
- sched_yield();
-#else /* no-op yield on unknown systems */
- ;
-#endif /* __linux__ || __FreeBSD__ || __APPLE__ */
-#endif /* solaris */
- }
- }
- }
-}
-
-static FORCEINLINE void pthread_release_lock (MLOCK_T *sl) {
- assert(sl->l != 0);
- assert(sl->threadid == CURRENT_THREAD);
- if (--sl->c == 0) {
- volatile unsigned int* lp = &sl->l;
- int prev = 0;
- int ret;
- sl->threadid = 0;
- __asm__ __volatile__ ("lock; xchgl %0, %1"
- : "=r" (ret)
- : "m" (*(lp)), "0"(prev)
- : "memory");
- }
-}
-
-static FORCEINLINE int pthread_try_lock (MLOCK_T *sl) {
- volatile unsigned int* lp = &sl->l;
- if (*lp != 0) {
- if (sl->threadid == CURRENT_THREAD) {
- ++sl->c;
- return 1;
- }
- }
- else {
- int cmp = 0;
- int val = 1;
- int ret;
- __asm__ __volatile__ ("lock; cmpxchgl %1, %2"
- : "=a" (ret)
- : "r" (val), "m" (*(lp)), "0"(cmp)
- : "memory", "cc");
- if (!ret) {
- assert(!sl->threadid);
- sl->c = 1;
- sl->threadid = CURRENT_THREAD;
- return 1;
- }
- }
- return 0;
-}
-
-
-#else /* WIN32 */
-/* Custom win32-style spin locks on x86 and x64 for MSC */
-struct win32_mlock_t
-{
- volatile long l;
- volatile unsigned int c;
- volatile long threadid;
-};
-
-static inline int return_0(int i) { return 0; }
-#define MLOCK_T struct win32_mlock_t
-#define CURRENT_THREAD win32_getcurrentthreadid()
-#define INITIAL_LOCK(sl) (memset(sl, 0, sizeof(MLOCK_T)), return_0(0))
-#define ACQUIRE_LOCK(sl) win32_acquire_lock(sl)
-#define RELEASE_LOCK(sl) win32_release_lock(sl)
-#define TRY_LOCK(sl) win32_try_lock(sl)
-#define SPINS_PER_YIELD 63
-
-static MLOCK_T malloc_global_mutex = { 0, 0, 0};
-
-static FORCEINLINE long win32_getcurrentthreadid(void) {
-#ifdef _MSC_VER
-#if defined(_M_IX86)
- long *threadstruct=(long *)__readfsdword(0x18);
- long threadid=threadstruct[0x24/sizeof(long)];
- return threadid;
-#elif defined(_M_X64)
- /* todo */
- return GetCurrentThreadId();
-#else
- return GetCurrentThreadId();
-#endif
-#else
- return GetCurrentThreadId();
-#endif
-}
-
-static FORCEINLINE int win32_acquire_lock (MLOCK_T *sl) {
- int spins = 0;
- for (;;) {
- if (sl->l != 0) {
- if (sl->threadid == CURRENT_THREAD) {
- ++sl->c;
- return 0;
- }
- }
- else {
- if (!interlockedexchange(&sl->l, 1)) {
- assert(!sl->threadid);
- sl->c=CURRENT_THREAD;
- sl->threadid = CURRENT_THREAD;
- sl->c = 1;
- return 0;
- }
- }
- if ((++spins & SPINS_PER_YIELD) == 0)
- SleepEx(0, FALSE);
- }
-}
-
-static FORCEINLINE void win32_release_lock (MLOCK_T *sl) {
- assert(sl->threadid == CURRENT_THREAD);
- assert(sl->l != 0);
- if (--sl->c == 0) {
- sl->threadid = 0;
- interlockedexchange (&sl->l, 0);
- }
-}
-
-static FORCEINLINE int win32_try_lock (MLOCK_T *sl) {
- if(sl->l != 0) {
- if (sl->threadid == CURRENT_THREAD) {
- ++sl->c;
- return 1;
- }
- }
- else {
- if (!interlockedexchange(&sl->l, 1)){
- assert(!sl->threadid);
- sl->threadid = CURRENT_THREAD;
- sl->c = 1;
- return 1;
- }
- }
- return 0;
-}
-
-#endif /* WIN32 */
-#else /* USE_SPIN_LOCKS */
-
-#ifndef WIN32
-/* pthreads-based locks */
-
-#define MLOCK_T pthread_mutex_t
-#define CURRENT_THREAD pthread_self()
-#define INITIAL_LOCK(sl) pthread_init_lock(sl)
-#define ACQUIRE_LOCK(sl) pthread_mutex_lock(sl)
-#define RELEASE_LOCK(sl) pthread_mutex_unlock(sl)
-#define TRY_LOCK(sl) (!pthread_mutex_trylock(sl))
-
-static MLOCK_T malloc_global_mutex = PTHREAD_MUTEX_INITIALIZER;
-
-/* Cope with old-style linux recursive lock initialization by adding */
-/* skipped internal declaration from pthread.h */
-#ifdef linux
-#ifndef PTHREAD_MUTEX_RECURSIVE
-extern int pthread_mutexattr_setkind_np __P ((pthread_mutexattr_t *__attr,
- int __kind));
-#define PTHREAD_MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE_NP
-#define pthread_mutexattr_settype(x,y) pthread_mutexattr_setkind_np(x,y)
-#endif
-#endif
-
-static int pthread_init_lock (MLOCK_T *sl) {
- pthread_mutexattr_t attr;
- if (pthread_mutexattr_init(&attr)) return 1;
- if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) return 1;
- if (pthread_mutex_init(sl, &attr)) return 1;
- if (pthread_mutexattr_destroy(&attr)) return 1;
- return 0;
-}
-
-#else /* WIN32 */
-/* Win32 critical sections */
-#define MLOCK_T CRITICAL_SECTION
-#define CURRENT_THREAD GetCurrentThreadId()
-#define INITIAL_LOCK(s) (!InitializeCriticalSectionAndSpinCount((s), 0x80000000|4000))
-#define ACQUIRE_LOCK(s) (EnterCriticalSection(s), 0)
-#define RELEASE_LOCK(s) LeaveCriticalSection(s)
-#define TRY_LOCK(s) TryEnterCriticalSection(s)
-#define NEED_GLOBAL_LOCK_INIT
-
-static MLOCK_T malloc_global_mutex;
-static volatile long malloc_global_mutex_status;
-
-/* Use spin loop to initialize global lock */
-static void init_malloc_global_mutex() {
- for (;;) {
- long stat = malloc_global_mutex_status;
- if (stat > 0)
- return;
- /* transition to < 0 while initializing, then to > 0) */
- if (stat == 0 &&
- interlockedcompareexchange(&malloc_global_mutex_status, -1, 0) == 0) {
- InitializeCriticalSection(&malloc_global_mutex);
- interlockedexchange(&malloc_global_mutex_status,1);
- return;
- }
- SleepEx(0, FALSE);
- }
-}
-
-#endif /* WIN32 */
-#endif /* USE_SPIN_LOCKS */
-#endif /* USE_LOCKS == 1 */
-
-/* ----------------------- User-defined locks ------------------------ */
-
-#if USE_LOCKS > 1
-/* Define your own lock implementation here */
-/* #define INITIAL_LOCK(sl) ... */
-/* #define ACQUIRE_LOCK(sl) ... */
-/* #define RELEASE_LOCK(sl) ... */
-/* #define TRY_LOCK(sl) ... */
-/* static MLOCK_T malloc_global_mutex = ... */
-#endif /* USE_LOCKS > 1 */
-
-/* ----------------------- Lock-based state ------------------------ */
-
-#if USE_LOCKS
-#define USE_LOCK_BIT (2U)
-#else /* USE_LOCKS */
-#define USE_LOCK_BIT (0U)
-#define INITIAL_LOCK(l)
-#endif /* USE_LOCKS */
-
-#if USE_LOCKS
-#define ACQUIRE_MALLOC_GLOBAL_LOCK() ACQUIRE_LOCK(&malloc_global_mutex);
-#define RELEASE_MALLOC_GLOBAL_LOCK() RELEASE_LOCK(&malloc_global_mutex);
-#else /* USE_LOCKS */
-#define ACQUIRE_MALLOC_GLOBAL_LOCK()
-#define RELEASE_MALLOC_GLOBAL_LOCK()
-#endif /* USE_LOCKS */
-
-
-/* ----------------------- Chunk representations ------------------------ */
-
-/*
- (The following includes lightly edited explanations by Colin Plumb.)
-
- The malloc_chunk declaration below is misleading (but accurate and
- necessary). It declares a "view" into memory allowing access to
- necessary fields at known offsets from a given base.
-
- Chunks of memory are maintained using a `boundary tag' method as
- originally described by Knuth. (See the paper by Paul Wilson
- ftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps for a survey of such
- techniques.) Sizes of free chunks are stored both in the front of
- each chunk and at the end. This makes consolidating fragmented
- chunks into bigger chunks fast. The head fields also hold bits
- representing whether chunks are free or in use.
-
- Here are some pictures to make it clearer. They are "exploded" to
- show that the state of a chunk can be thought of as extending from
- the high 31 bits of the head field of its header through the
- prev_foot and PINUSE_BIT bit of the following chunk header.
-
- A chunk that's in use looks like:
-
- chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Size of previous chunk (if P = 0) |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P|
- | Size of this chunk 1| +-+
- mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | |
- +- -+
- | |
- +- -+
- | :
- +- size - sizeof(size_t) available payload bytes -+
- : |
- chunk-> +- -+
- | |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |1|
- | Size of next chunk (may or may not be in use) | +-+
- mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
- And if it's free, it looks like this:
-
- chunk-> +- -+
- | User payload (must be in use, or we would have merged!) |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P|
- | Size of this chunk 0| +-+
- mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Next pointer |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Prev pointer |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | :
- +- size - sizeof(struct chunk) unused bytes -+
- : |
- chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Size of this chunk |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |0|
- | Size of next chunk (must be in use, or we would have merged)| +-+
- mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | :
- +- User payload -+
- : |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- |0|
- +-+
- Note that since we always merge adjacent free chunks, the chunks
- adjacent to a free chunk must be in use.
-
- Given a pointer to a chunk (which can be derived trivially from the
- payload pointer) we can, in O(1) time, find out whether the adjacent
- chunks are free, and if so, unlink them from the lists that they
- are on and merge them with the current chunk.
-
- Chunks always begin on even word boundaries, so the mem portion
- (which is returned to the user) is also on an even word boundary, and
- thus at least double-word aligned.
-
- The P (PINUSE_BIT) bit, stored in the unused low-order bit of the
- chunk size (which is always a multiple of two words), is an in-use
- bit for the *previous* chunk. If that bit is *clear*, then the
- word before the current chunk size contains the previous chunk
- size, and can be used to find the front of the previous chunk.
- The very first chunk allocated always has this bit set, preventing
- access to non-existent (or non-owned) memory. If pinuse is set for
- any given chunk, then you CANNOT determine the size of the
- previous chunk, and might even get a memory addressing fault when
- trying to do so.
-
- The C (CINUSE_BIT) bit, stored in the unused second-lowest bit of
- the chunk size redundantly records whether the current chunk is
- inuse. This redundancy enables usage checks within free and realloc,
- and reduces indirection when freeing and consolidating chunks.
-
- Each freshly allocated chunk must have both cinuse and pinuse set.
- That is, each allocated chunk borders either a previously allocated
- and still in-use chunk, or the base of its memory arena. This is
- ensured by making all allocations from the `lowest' part of any
- found chunk. Further, no free chunk physically borders another one,
- so each free chunk is known to be preceded and followed by either
- inuse chunks or the ends of memory.
-
- Note that the `foot' of the current chunk is actually represented
- as the prev_foot of the NEXT chunk. This makes it easier to
- deal with alignments etc but can be very confusing when trying
- to extend or adapt this code.
-
- The exceptions to all this are
-
- 1. The special chunk `top' is the top-most available chunk (i.e.,
- the one bordering the end of available memory). It is treated
- specially. Top is never included in any bin, is used only if
- no other chunk is available, and is released back to the
- system if it is very large (see M_TRIM_THRESHOLD). In effect,
- the top chunk is treated as larger (and thus less well
- fitting) than any other available chunk. The top chunk
- doesn't update its trailing size field since there is no next
- contiguous chunk that would have to index off it. However,
- space is still allocated for it (TOP_FOOT_SIZE) to enable
- separation or merging when space is extended.
-
- 3. Chunks allocated via mmap, which have the lowest-order bit
- (IS_MMAPPED_BIT) set in their prev_foot fields, and do not set
- PINUSE_BIT in their head fields. Because they are allocated
- one-by-one, each must carry its own prev_foot field, which is
- also used to hold the offset this chunk has within its mmapped
- region, which is needed to preserve alignment. Each mmapped
- chunk is trailed by the first two fields of a fake next-chunk
- for sake of usage checks.
-
-*/
-
-struct malloc_chunk {
- size_t prev_foot; /* Size of previous chunk (if free). */
- size_t head; /* Size and inuse bits. */
- struct malloc_chunk* fd; /* double links -- used only if free. */
- struct malloc_chunk* bk;
-};
-
-typedef struct malloc_chunk mchunk;
-typedef struct malloc_chunk* mchunkptr;
-typedef struct malloc_chunk* sbinptr; /* The type of bins of chunks */
-typedef unsigned int bindex_t; /* Described below */
-typedef unsigned int binmap_t; /* Described below */
-typedef unsigned int flag_t; /* The type of various bit flag sets */
-
-/* ------------------- Chunks sizes and alignments ----------------------- */
-
-#define MCHUNK_SIZE (sizeof(mchunk))
-
-#if FOOTERS
-#define CHUNK_OVERHEAD (TWO_SIZE_T_SIZES)
-#else /* FOOTERS */
-#define CHUNK_OVERHEAD (SIZE_T_SIZE)
-#endif /* FOOTERS */
-
-/* MMapped chunks need a second word of overhead ... */
-#define MMAP_CHUNK_OVERHEAD (TWO_SIZE_T_SIZES)
-/* ... and additional padding for fake next-chunk at foot */
-#define MMAP_FOOT_PAD (FOUR_SIZE_T_SIZES)
-
-/* The smallest size we can malloc is an aligned minimal chunk */
-#define MIN_CHUNK_SIZE\
- ((MCHUNK_SIZE + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK)
-
-/* conversion from malloc headers to user pointers, and back */
-#define chunk2mem(p) ((void*)((char*)(p) + TWO_SIZE_T_SIZES))
-#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - TWO_SIZE_T_SIZES))
-/* chunk associated with aligned address A */
-#define align_as_chunk(A) (mchunkptr)((A) + align_offset(chunk2mem(A)))
-
-/* Bounds on request (not chunk) sizes. */
-#define MAX_REQUEST ((-MIN_CHUNK_SIZE) << 2)
-#define MIN_REQUEST (MIN_CHUNK_SIZE - CHUNK_OVERHEAD - SIZE_T_ONE)
-
-/* pad request bytes into a usable size */
-#define pad_request(req) \
- (((req) + CHUNK_OVERHEAD + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK)
-
-/* pad request, checking for minimum (but not maximum) */
-#define request2size(req) \
- (((req) < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(req))
-
-
-/* ------------------ Operations on head and foot fields ----------------- */
-
-/*
- The head field of a chunk is or'ed with PINUSE_BIT when previous
- adjacent chunk in use, and or'ed with CINUSE_BIT if this chunk is in
- use. If the chunk was obtained with mmap, the prev_foot field has
- IS_MMAPPED_BIT set, otherwise holding the offset of the base of the
- mmapped region to the base of the chunk.
-
- FLAG4_BIT is not used by this malloc, but might be useful in extensions.
-*/
-
-#define PINUSE_BIT (SIZE_T_ONE)
-#define CINUSE_BIT (SIZE_T_TWO)
-#define FLAG4_BIT (SIZE_T_FOUR)
-#define INUSE_BITS (PINUSE_BIT|CINUSE_BIT)
-#define FLAG_BITS (PINUSE_BIT|CINUSE_BIT|FLAG4_BIT)
-
-/* Head value for fenceposts */
-#define FENCEPOST_HEAD (INUSE_BITS|SIZE_T_SIZE)
-
-/* extraction of fields from head words */
-#define cinuse(p) ((p)->head & CINUSE_BIT)
-#define pinuse(p) ((p)->head & PINUSE_BIT)
-#define chunksize(p) ((p)->head & ~(FLAG_BITS))
-
-#define clear_pinuse(p) ((p)->head &= ~PINUSE_BIT)
-#define clear_cinuse(p) ((p)->head &= ~CINUSE_BIT)
-
-/* Treat space at ptr +/- offset as a chunk */
-#define chunk_plus_offset(p, s) ((mchunkptr)(((char*)(p)) + (s)))
-#define chunk_minus_offset(p, s) ((mchunkptr)(((char*)(p)) - (s)))
-
-/* Ptr to next or previous physical malloc_chunk. */
-#define next_chunk(p) ((mchunkptr)( ((char*)(p)) + ((p)->head & ~FLAG_BITS)))
-#define prev_chunk(p) ((mchunkptr)( ((char*)(p)) - ((p)->prev_foot) ))
-
-/* extract next chunk's pinuse bit */
-#define next_pinuse(p) ((next_chunk(p)->head) & PINUSE_BIT)
-
-/* Get/set size at footer */
-#define get_foot(p, s) (((mchunkptr)((char*)(p) + (s)))->prev_foot)
-#define set_foot(p, s) (((mchunkptr)((char*)(p) + (s)))->prev_foot = (s))
-
-/* Set size, pinuse bit, and foot */
-#define set_size_and_pinuse_of_free_chunk(p, s)\
- ((p)->head = (s|PINUSE_BIT), set_foot(p, s))
-
-/* Set size, pinuse bit, foot, and clear next pinuse */
-#define set_free_with_pinuse(p, s, n)\
- (clear_pinuse(n), set_size_and_pinuse_of_free_chunk(p, s))
-
-#define is_mmapped(p)\
- (!((p)->head & PINUSE_BIT) && ((p)->prev_foot & IS_MMAPPED_BIT))
-
-/* Get the internal overhead associated with chunk p */
-#define overhead_for(p)\
- (is_mmapped(p)? MMAP_CHUNK_OVERHEAD : CHUNK_OVERHEAD)
-
-/* Return true if malloced space is not necessarily cleared */
-#if MMAP_CLEARS
-#define calloc_must_clear(p) (!is_mmapped(p))
-#else /* MMAP_CLEARS */
-#define calloc_must_clear(p) (1)
-#endif /* MMAP_CLEARS */
-
/* ---------------------- Overlaid data structures ----------------------- */
/*
--
gitgitgadget
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox