wingo pushed a commit to branch wip-whippet in repository guile. commit 44a7240e16de8fd906bcef5ddb6d329800d420db Author: Andy Wingo <wi...@igalia.com> AuthorDate: Mon Sep 2 13:18:07 2024 +0200
Rename "whippet" collector to "mmc": mostly marking collector --- Makefile | 50 +++++++++++----------- README.md | 57 +++++++++++++++----------- api/{whippet-attrs.h => mmc-attrs.h} | 6 +-- benchmarks/README.md | 6 +-- doc/{collector-whippet.md => collector-mmc.md} | 49 ++++++++++------------ doc/collectors.md | 17 ++++---- doc/guile.md | 2 +- doc/manual.md | 42 +++++++++++-------- embed.mk | 26 ++++++------ src/{whippet.c => mmc.c} | 2 +- 10 files changed, 133 insertions(+), 124 deletions(-) diff --git a/Makefile b/Makefile index 56c7325c4..2a1fded30 100644 --- a/Makefile +++ b/Makefile @@ -5,21 +5,21 @@ COLLECTORS = \ scc \ pcc \ \ - whippet \ - stack-conservative-whippet \ - heap-conservative-whippet \ + mmc \ + stack-conservative-mmc \ + heap-conservative-mmc \ \ - parallel-whippet \ - stack-conservative-parallel-whippet \ - heap-conservative-parallel-whippet \ + parallel-mmc \ + stack-conservative-parallel-mmc \ + heap-conservative-parallel-mmc \ \ - generational-whippet \ - stack-conservative-generational-whippet \ - heap-conservative-generational-whippet \ + generational-mmc \ + stack-conservative-generational-mmc \ + heap-conservative-generational-mmc \ \ - parallel-generational-whippet \ - stack-conservative-parallel-generational-whippet \ - heap-conservative-parallel-generational-whippet + parallel-generational-mmc \ + stack-conservative-parallel-generational-mmc \ + heap-conservative-parallel-generational-mmc DEFAULT_BUILD := opt @@ -70,28 +70,28 @@ GC_CFLAGS_scc = -DGC_PRECISE_ROOTS=1 GC_STEM_pcc = pcc GC_CFLAGS_pcc = -DGC_PRECISE_ROOTS=1 -DGC_PARALLEL=1 -define whippet_variant -GC_STEM_$(1) = whippet +define mmc_variant +GC_STEM_$(1) = mmc GC_CFLAGS_$(1) = $(2) endef -define generational_whippet_variants -$(call whippet_variant,$(1)whippet,$(2)) -$(call whippet_variant,$(1)generational_whippet,$(2) -DGC_GENERATIONAL=1) +define generational_mmc_variants +$(call mmc_variant,$(1)mmc,$(2)) +$(call mmc_variant,$(1)generational_mmc,$(2) -DGC_GENERATIONAL=1) endef -define parallel_whippet_variants -$(call generational_whippet_variants,$(1),$(2)) -$(call generational_whippet_variants,$(1)parallel_,$(2) -DGC_PARALLEL=1) +define parallel_mmc_variants +$(call generational_mmc_variants,$(1),$(2)) +$(call generational_mmc_variants,$(1)parallel_,$(2) -DGC_PARALLEL=1) endef -define trace_whippet_variants -$(call parallel_whippet_variants,,-DGC_PRECISE_ROOTS=1) -$(call parallel_whippet_variants,stack_conservative_,-DGC_CONSERVATIVE_ROOTS=1) -$(call parallel_whippet_variants,heap_conservative_,-DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1) +define trace_mmc_variants +$(call parallel_mmc_variants,,-DGC_PRECISE_ROOTS=1) +$(call parallel_mmc_variants,stack_conservative_,-DGC_CONSERVATIVE_ROOTS=1) +$(call parallel_mmc_variants,heap_conservative_,-DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1) endef -$(eval $(call trace_whippet_variants)) +$(eval $(call trace_mmc_variants)) # $(1) is the benchmark, $(2) is the collector configuration make_gc_var = $$($(1)$(subst -,_,$(2))) diff --git a/README.md b/README.md index c922af64d..601646cc5 100644 --- a/README.md +++ b/README.md @@ -12,50 +12,59 @@ allocation, and provides a number of implementations of that API. See the [documentation](./doc/README.md). +## Features + + - Per-object pinning (with `mmc` collectors) + - Finalization (supporting resuscitation) + - Ephemerons (except on `bdw`, which has a polyfill) + - Conservative roots (optionally with `mmc` or always with `bdw`) + - Precise roots (optionally with `mmc` or always with `semi` / `pcc` / + `scc`) + - Precise embedder-parameterized heap tracing (except with `bdw`) + - Conservative heap tracing (optionally with `mmc`, always with `bdw`) + - Parallel tracing (except `semi` and `scc`) + - Parallel mutators (except `semi`) + - Inline allocation / write barrier fast paths (supporting JIT) + - One unified API with no-overhead abstraction: switch collectors when + you like + ## Source repository structure * [api/](./api/): The user-facing API. Also, the "embedder API"; see the [manual](./doc/manual.md) for more. * [doc/](./doc/): Documentation, such as it is. - * [src/](./src/): The actual GC implementation. The specific - implementations of the Whippet API are [`semi.c`](./src/semi.c), a - semi-space collector; [`bdw.c`](./src/bdw.c), the third-party - [BDW-GC](https://github.com/ivmai/bdwgc) conservative parallel - stop-the-world mark-sweep segregated-fits collector with lazy - sweeping; and [`whippet.c`](./src/whippet.c), the whippet collector. + * [src/](./src/): The actual GC implementation, containing a number of + collector implementations. The embedder chooses which collector to + use at compile-time. See the [documentation](./doc/collectors.md) + for more on the different collectors (`semi`, `bdw`, `scc`, `pcc`, + and the different flavors of `mmc`). * [benchmarks/](./benchmarks/): Benchmarks. A work in progress. * [test/](./test/): A dusty attic of minimal testing. -## To do - -### Missing features before Guile can use Whippet +## Status and roadmap - - [X] Pinning - - [X] Conservative stacks - - [X] Conservative data segments - - [ ] Heap growth/shrinking - - [ ] Debugging/tracing - - [X] Finalizers - - [X] Weak references / weak maps +As of September 2024, Whippet is almost feature-complete. The main +missing feature is dynamic heap growth and shrinkage +(https://github.com/wingo/whippet/issues/5), which should land soon. -### Features that would improve Whippet performance +After that, the next phase on the roadmap is support for tracing, and +some performance noodling. - - [X] Immix-style opportunistic evacuation - - ~~[ ] Overflow allocation~~ (should just evacuate instead) - - [X] Generational GC via sticky mark bits - - [ ] Generational GC with semi-space nursery - - [ ] Concurrent marking with SATB barrier +Once that is done, the big task is integrating Whippet into the [Guile +Scheme](https://gnu.org/s/guile) language run-time, replacing BDW-GC. +Fingers crossed! ## About the name It sounds better than WIP (work-in-progress) garbage collector, doesn't it? Also apparently a whippet is a kind of dog that is fast for its -size. It would be nice if whippet-gc turns out to have this property. +size. It would be nice if the Whippet collectors turn out to have this +property. ## License ``` -Copyright (c) 2022-2023 Andy Wingo +Copyright (c) 2022-2024 Andy Wingo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/api/whippet-attrs.h b/api/mmc-attrs.h similarity index 95% rename from api/whippet-attrs.h rename to api/mmc-attrs.h index e6e5b22b9..111b2512c 100644 --- a/api/whippet-attrs.h +++ b/api/mmc-attrs.h @@ -1,5 +1,5 @@ -#ifndef WHIPPET_ATTRS_H -#define WHIPPET_ATTRS_H +#ifndef MMC_ATTRS_H +#define MMC_ATTRS_H #include "gc-config.h" #include "gc-assert.h" @@ -61,4 +61,4 @@ static inline enum gc_safepoint_mechanism gc_safepoint_mechanism(void) { return GC_SAFEPOINT_MECHANISM_COOPERATIVE; } -#endif // WHIPPET_ATTRS_H +#endif // MMC_ATTRS_H diff --git a/benchmarks/README.md b/benchmarks/README.md index 1a9f1ac87..00ec1f731 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -7,14 +7,14 @@ threads. We analytically compute the peak amount of live data and then size the GC heap as a multiplier of that size. It has a peak heap consumption of 10 MB or so per mutator thread: not very large. - At a 2x heap multiplier, it causes about 30 collections for the - whippet collector, and runs somewhere around 200-400 milliseconds in + At a 2x heap multiplier, it causes about 30 collections for the `mmc` + collector, and runs somewhere around 200-400 milliseconds in single-threaded mode, on the machines I have in 2022. For low thread counts, the GCBench benchmark is small; but then again many Guile processes also are quite short-lived, so perhaps it is useful to ensure that small heaps remain lightweight. - To stress Whippet's handling of fragmentation, we modified this + To stress `mmc`'s handling of fragmentation, we modified this benchmark to intersperse pseudorandomly-sized holes between tree nodes. diff --git a/doc/collector-whippet.md b/doc/collector-mmc.md similarity index 80% rename from doc/collector-whippet.md rename to doc/collector-mmc.md index 23fb2a1bb..e88b1409d 100644 --- a/doc/collector-whippet.md +++ b/doc/collector-mmc.md @@ -1,19 +1,14 @@ -# Whippet collector +# Mostly-copying collector -One collector implementation in the Whippet garbage collection library -is also called Whippet. Naming-wise this is a somewhat confusing -situation; perhaps it will change. - -Anyway, the `whippet` collector is mainly a mark-region collector, -inspired by +The `mmc` collector is mainly a mark-region collector, inspired by [Immix](http://users.cecs.anu.edu.au/~steveb/pubs/papers/immix-pldi-2008.pdf). -To a first approximation, Whippet is a whole-heap Immix collector with a +To a first approximation, `mmc` is a whole-heap Immix collector with a large object space on the side. -When tracing, `whippet` mostly marks objects in place. If the heap is +When tracing, `mmc` mostly marks objects in place. If the heap is too fragmented, it can compact the heap by choosing to evacuate sparsely-populated heap blocks instead of marking in place. However -evacuation is strictly optional, which means that `whippet` is also +evacuation is strictly optional, which means that `mmc` is also compatible with conservative root-finding, making it a good replacement for embedders that currently use the [Boehm-Demers-Weiser collector](./collector-bdw.md). @@ -33,7 +28,7 @@ recycled block from the global block store, it allocates into those holes. For an exposition of Immix, see the lovely detailed [Rust implementation](http://users.cecs.anu.edu.au/~steveb/pubs/papers/rust-ismm-2016.pdf). -The essential difference of `whippet` from Immix stems from a simple +The essential difference of `mmc` from Immix stems from a simple observation: Immix needs a side table of line mark bytes and also a mark bit or bits in each object (or in a side table). But if instead you choose to store mark bytes instead of bits (for concurrency reasons) in @@ -54,13 +49,13 @@ just read the mark table and can avoid looking at object memory. ## Optional features -The `whippet` collector has a few feature flags that can be turned on or +The `mmc` collector has a few feature flags that can be turned on or off. If you use the [standard embedder makefile include](../embed.mk), -then there is a name for each combination of features: `whippet` has no -additional features, `parallel-whippet` enables parallel marking, -`parallel-generational-whippet` enables generations, -`stack-conservative-parallel-generational-whippet` uses conservative -root-finding, and `heap-conservative-parallel-generational-whippet` +then there is a name for each combination of features: `mmc` has no +additional features, `parallel-mmc` enables parallel marking, +`parallel-generational-mmc` enables generations, +`stack-conservative-parallel-generational-mmc` uses conservative +root-finding, and `heap-conservative-parallel-generational-mmc` additionally traces the heap conservatively. You can leave off components of the name to get a collector without those features. Underneath this corresponds to some pre-processor definitions passed to @@ -68,7 +63,7 @@ the compiler on the command line. ### Generations -Whippet supports generational tracing via the [sticky mark-bit +`mmc` supports generational tracing via the [sticky mark-bit algorithm](https://wingolog.org/archives/2022/10/22/the-sticky-mark-bit-algorithm). This requires that the embedder emit [write barriers](https://github.com/wingo/whippet/blob/main/doc/manual.md#write-barriers); @@ -84,7 +79,7 @@ two-megabyte aligned slabs. ### Parallel tracing -You almost certainly want this on! `parallel-whippet` uses a the +You almost certainly want this on! `parallel-mmc` uses a the [fine-grained work-stealing parallel tracer](../src/parallel-tracer.h). Each trace worker maintains a [local queue of objects that need tracing](../src/local-worklist.h), which currently has a capacity of @@ -96,17 +91,17 @@ then will try to steal from other workers. The memory used for the external worklist is dynamically allocated from the OS and is not currently counted as contributing to the heap size. -If you absolutely need to avoid dynamic allocation during GC, `whippet` -(even serial whippet) would need some work for your use case, to -allocate a fixed-size space for a marking queue and to gracefully handle -mark queue overflow. +If you absolutely need to avoid dynamic allocation during GC, `mmc` +(even `serial-mmc`) would need some work for your use case, to allocate +a fixed-size space for a marking queue and to gracefully handle mark +queue overflow. ### Conservative stack scanning With `semi` and `pcc`, embedders must precisely enumerate the set of *roots*: the edges into the heap from outside. Commonly, roots include global variables, as well as working variables from each mutator's -stack. Whippet can optionally mark mutator stacks *conservatively*: +stack. `mmc` can optionally mark mutator stacks *conservatively*: treating each word on the stack as if it may be an object reference, and marking any object at that address. @@ -124,7 +119,7 @@ place roots in traceable locations published to the garbage collector. And the [performance question is still open](https://dl.acm.org/doi/10.1145/2660193.2660198). -Anyway. Whippet can scan roots conservatively. Those roots are pinned +Anyway. `mmc` can scan roots conservatively. Those roots are pinned for the collection; even if the collection will compact via evacuation, referents of conservative roots won't be moved. Objects not directly referenced by roots can be evacuated, however. @@ -133,14 +128,14 @@ referenced by roots can be evacuated, however. In addition to stack and global references, the Boehm-Demers-Weiser collector scans heap objects conservatively as well, treating each word -of each heap object as if it were a reference. Whippet can do that, if +of each heap object as if it were a reference. `mmc` can do that, if the embedder is unable to provide a `gc_trace_object` implementation. However this is generally a performance lose, and it prevents evacuation. ## Other implementation tidbits -`whippet` does lazy sweeping: as a mutator grabs a fresh block, it +`mmc` does lazy sweeping: as a mutator grabs a fresh block, it reclaims memory that was unmarked in the previous collection before making the memory available for allocation. This makes sweeping naturally cache-friendly and parallel. diff --git a/doc/collectors.md b/doc/collectors.md index cdf4dcb8b..1c23f3e9d 100644 --- a/doc/collectors.md +++ b/doc/collectors.md @@ -7,7 +7,7 @@ Whippet has five collectors currently: but with support for multiple mutator threads. - [Parallel copying collector (`pcc`)](./collector-pcc.md): Like `scc`, but with support for multiple tracing threads. - - [Whippet collector (`whippet`)](./collector-whippet.md): + - [Mostly marking collector (`mmc`)](./collector-mmc.md): Immix-inspired collector. Optionally parallel, conservative (stack and/or heap), and/or generational. - [Boehm-Demers-Weiser collector (`bdw`)](./collector-bdw.md): @@ -17,11 +17,11 @@ Whippet has five collectors currently: ## How to choose? If you are migrating an embedder off BDW-GC, then it could be reasonable -to first go to `bdw`, then `stack-conservative-parallel-whippet`. +to first go to `bdw`, then `stack-conservative-parallel-mmc`. If you have an embedder with precise roots, use `pcc`. That will shake out mutator/embedder bugs. Then if memory is tight, switch to -`parallel-whippet`, possibly `parallel-generational-whippet`. +`parallel-mmc`, possibly `parallel-generational-mmc`. If you are aiming for maximum simplicity and minimal code size (ten kilobytes or so), use `semi`. @@ -30,17 +30,16 @@ Only use `scc` if you are investigating GC internals. If you are writing a new project, you have a choice as to whether to pay the development cost of precise roots or not. If you choose to not have -precise roots, then go for `stack-conservative-parallel-whippet` -directly. +precise roots, then go for `stack-conservative-parallel-mmc` directly. ## More collectors It would be nice to have a classic generational GC, perhaps using -parallel-whippet for the old generation but a pcc-style copying nursery. +`parallel-mmc` for the old generation but a pcc-style copying nursery. -Support for concurrent marking in `whippet` would be good as well, -perhaps with a SATB barrier. (Or, if you are the sort of person to bet -on conservative stack scanning, perhaps a retreating-wavefront barrier +Support for concurrent marking in `mmc` would be good as well, perhaps +with a SATB barrier. (Or, if you are the sort of person to bet on +conservative stack scanning, perhaps a retreating-wavefront barrier would be more appropriate.) Contributions are welcome, provided they have no more dependencies! diff --git a/doc/guile.md b/doc/guile.md index 05bc17e15..12bdb97fc 100644 --- a/doc/guile.md +++ b/doc/guile.md @@ -1,6 +1,6 @@ # Whippet and Guile -If the Whippet collector works out, it could replace Guile's garbage +If the `mmc` collector works out, it could replace Guile's garbage collector. Guile currently uses BDW-GC. Guile has a widely used C API and implements part of its run-time in C. For this reason it may be infeasible to require precise enumeration of GC roots -- we may need to diff --git a/doc/manual.md b/doc/manual.md index 73aa537eb..d856df219 100644 --- a/doc/manual.md +++ b/doc/manual.md @@ -21,7 +21,7 @@ for full details, but for a cheat sheet, you might do something like this to copy Whippet into the `whippet/` directory of your project root: ``` -git remote add whippet https://github.com/wingo/whippet-gc +git remote add whippet https://github.com/wingo/whippet git fetch whippet git merge -s ours --no-commit --allow-unrelated-histories whippet/main git read-tree --prefix=whippet/ -u whippet/main @@ -92,7 +92,7 @@ that all "large" or potentially large objects have a flag bit reserved for use of the garbage collector. A large object is one whose size exceeds the `gc_allocator_large_threshold()` (see [`gc-attrs.h`](../api/gc-attrs.h)), which is a collector-specific value. -Currently the only generational collector is the in-place Whippet +Currently the only generational collector is the in-place `mmc` collector, whose large object threshold is 4096 bytes. The `gc_object_set_remembered`, `gc_object_is_remembered_nonatomic`, and `gc_object_clear_remembered_nonatomic` embedder functions manage the @@ -187,15 +187,10 @@ implementations of that API: `semi`, a simple semi-space collector; `pcc`, a parallel copying collector (like semi but multithreaded); `bdw`, an implementation via the third-party [Boehm-Demers-Weiser](https://github.com/ivmai/bdwgc) conservative -collector; and `whippet`, an Immix-like collector. - -There is a bit of name overloading between the Whippet abstract API, the -collection of GC implementations, and the specific Whippet collector; -our apologies. It's just like that, and we hope to make the usage -obvious from context. +collector; and `mmc`, a mostly-marking collector inspired by Immix. The program that embeds Whippet selects the collector implementation at -build-time. In the case of the specific Whippet collector, the program +build-time. In the case of the `mmc` collector, the program also configures a specific collector mode, again at build-time: generational or not, parallel or not, stack-conservative or not, and heap-conservative or not. It may be nice in the future to be able to @@ -353,15 +348,26 @@ $(COMPILE) -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 \ -include foo-embedder.h -o gc.o -c bdw.c ``` -#### Building `whippet` +#### Building `pcc` + +The parallel copying collector is like `semi` but better in every way: +it supports multiple mutator threads, and evacuates in parallel if +multiple threads are available. + +``` +$(COMPILE) -DGC_PARALLEL=1 -DGC_PRECISE_ROOTS=1 \ + -include foo-embedder.h -o gc.o -c pcc.c +``` + +#### Building `mmc` -Finally, there is the whippet collector. It can collect roots precisely -or conservatively, trace precisely or conservatively, be parallel or -not, and be generational or not. +Finally, there is the mostly-marking collector. It can collect roots +precisely or conservatively, trace precisely or conservatively, be +parallel or not, and be generational or not. ``` $(COMPILE) -DGC_PARALLEL=1 -DGC_GENERATIONAL=1 -DGC_PRECISE_ROOTS=1 \ - -include foo-embedder.h -o gc.o -c whippet.c + -include foo-embedder.h -o gc.o -c mvv.c ``` ### Compiling your program @@ -370,12 +376,12 @@ Any compilation unit that uses the GC API should have the same set of compile-time options defined as when compiling the collector. Additionally those compilation units should include the "attributes" header for the collector in question, namely `semi-attrs.h`, -`bdw-attrs.h`, or `whippet-attrs.h`. For example, for parallel -generational whippet, you might have: +`bdw-attrs.h`, `pcc-attrs.h`, or `mmc-attrs.h`. For example, for +parallel generational mmc, you might have: ``` $(COMPILE) -DGC_PARALLEL=1 -DGC_GENERATIONAL=1 -DGC_PRECISE_ROOTS=1 \ - -include whippet-attrs.h -o my-program.o -c my-program.c + -include mmc-attrs.h -o my-program.o -c my-program.c ``` ### Linking the collector into your program @@ -462,7 +468,7 @@ defined for all collectors: You can set these options via `gc_option_set_int` and so on; see [`gc-options.h`](../api/gc-options.h). Or, you can parse options from -strings: `heap-size-policy`, `heap-size`, `maximum-heap-size`, and so +trings: `heap-size-policy`, `heap-size`, `maximum-heap-size`, and so on. Use `gc_option_from_string` to determine if a string is really an option. Use `gc_option_parse_and_set` to parse a value for an option. Use `gc_options_parse_and_set_many` to parse a number of comma-delimited diff --git a/embed.mk b/embed.mk index 020cb10d3..fef8de8b1 100644 --- a/embed.mk +++ b/embed.mk @@ -48,28 +48,28 @@ GC_CFLAGS_scc = -DGC_PRECISE_ROOTS=1 GC_STEM_pcc = pcc GC_CFLAGS_pcc = -DGC_PRECISE_ROOTS=1 -DGC_PARALLEL=1 -define whippet_variant -GC_STEM_$(1) = whippet +define mmc_variant +GC_STEM_$(1) = mmc GC_CFLAGS_$(1) = $(2) endef -define generational_whippet_variants -$(call whippet_variant,$(1)whippet,$(2)) -$(call whippet_variant,$(1)generational_whippet,$(2) -DGC_GENERATIONAL=1) +define generational_mmc_variants +$(call mmc_variant,$(1)mmc,$(2)) +$(call mmc_variant,$(1)generational_mmc,$(2) -DGC_GENERATIONAL=1) endef -define parallel_whippet_variants -$(call generational_whippet_variants,$(1),$(2)) -$(call generational_whippet_variants,$(1)parallel_,$(2) -DGC_PARALLEL=1) +define parallel_mmc_variants +$(call generational_mmc_variants,$(1),$(2)) +$(call generational_mmc_variants,$(1)parallel_,$(2) -DGC_PARALLEL=1) endef -define trace_whippet_variants -$(call parallel_whippet_variants,,-DGC_PRECISE_ROOTS=1) -$(call parallel_whippet_variants,stack_conservative_,-DGC_CONSERVATIVE_ROOTS=1) -$(call parallel_whippet_variants,heap_conservative_,-DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1) +define trace_mmc_variants +$(call parallel_mmc_variants,,-DGC_PRECISE_ROOTS=1) +$(call parallel_mmc_variants,stack_conservative_,-DGC_CONSERVATIVE_ROOTS=1) +$(call parallel_mmc_variants,heap_conservative_,-DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1) endef -$(eval $(call trace_whippet_variants)) +$(eval $(call trace_mmc_variants)) gc_var = $($(1)$(subst -,_,$(2))) gc_impl = $(call gc_var,GC_STEM_,$(1)).c diff --git a/src/whippet.c b/src/mmc.c similarity index 99% rename from src/whippet.c rename to src/mmc.c index 05597b4f8..f33ed4509 100644 --- a/src/whippet.c +++ b/src/mmc.c @@ -26,7 +26,7 @@ #include "serial-tracer.h" #endif #include "spin.h" -#include "whippet-attrs.h" +#include "mmc-attrs.h" #define LARGE_OBJECT_THRESHOLD 8192