1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
use crate::util::constants::*;
use crate::util::heap::layout::vm_layout::*;
use crate::util::linear_scan::Region;
use crate::util::metadata::side_metadata::constants::{
    GLOBAL_SIDE_METADATA_BASE_OFFSET, LOCAL_SIDE_METADATA_BASE_OFFSET,
};
use crate::util::metadata::side_metadata::SideMetadataOffset;
use crate::util::metadata::side_metadata::SideMetadataSpec;

// This macro helps define side metadata specs, and layout their offsets one after another.
// The macro is implemented with the incremental TT muncher pattern (see https://danielkeep.github.io/tlborm/book/pat-incremental-tt-munchers.html).
// This should only be used twice within mmtk-core: one for global specs, and one for local specs.
// This should not be used to layout VM specs (we have provided side_first()/side_after() for the VM side metadata specs).
macro_rules! define_side_metadata_specs {
    // Internal patterns

    // Define the first spec with offset at either GLOBAL/LOCAL_SIDE_METADATA_BASE_OFFSET
    (@first_spec $name: ident = (global: $is_global: expr, log_num_of_bits: $log_num_of_bits: expr, log_bytes_in_region: $log_bytes_in_region: expr)) => {
        pub const $name: SideMetadataSpec = SideMetadataSpec {
            name: stringify!($name),
            is_global: $is_global,
            offset: if $is_global { GLOBAL_SIDE_METADATA_BASE_OFFSET } else { LOCAL_SIDE_METADATA_BASE_OFFSET },
            log_num_of_bits: $log_num_of_bits,
            log_bytes_in_region: $log_bytes_in_region,
        };
    };
    // Define any spec that follows a previous spec. The new spec will be created and laid out after the previous spec.
    (@prev_spec $last_spec: ident as $last_spec_ident: ident, $name: ident = (global: $is_global: expr, log_num_of_bits: $log_num_of_bits: expr, log_bytes_in_region: $log_bytes_in_region: expr), $($tail:tt)*) => {
        pub const $name: SideMetadataSpec = SideMetadataSpec {
            name: stringify!($name),
            is_global: $is_global,
            offset: SideMetadataOffset::layout_after(&$last_spec),
            log_num_of_bits: $log_num_of_bits,
            log_bytes_in_region: $log_bytes_in_region,
        };
        define_side_metadata_specs!(@prev_spec $name as $last_spec_ident, $($tail)*);
    };
    // Define the last spec with the given identifier.
    (@prev_spec $last_spec: ident as $last_spec_ident: ident,) => {
        pub const $last_spec_ident: SideMetadataSpec = $last_spec;
    };

    // The actual macro

    // This is the pattern that should be used outside this macro.
    (last_spec_as $last_spec_ident: ident, $name0: ident = (global: $is_global0: expr, log_num_of_bits: $log_num_of_bits0: expr, log_bytes_in_region: $log_bytes_in_region0: expr), $($tail:tt)*) => {
        // Defines the first spec
        define_side_metadata_specs!(@first_spec $name0 = (global: $is_global0, log_num_of_bits: $log_num_of_bits0, log_bytes_in_region: $log_bytes_in_region0));
        // The rest specs
        define_side_metadata_specs!(@prev_spec $name0 as $last_spec_ident, $($tail)*);
    };
}

// This defines all GLOBAL side metadata used by mmtk-core.
define_side_metadata_specs!(
    last_spec_as LAST_GLOBAL_SIDE_METADATA_SPEC,
    // Mark the start of an object
    VO_BIT       = (global: true, log_num_of_bits: 0, log_bytes_in_region: LOG_MIN_OBJECT_SIZE as usize),
    // Track chunks used by (malloc) marksweep
    MS_ACTIVE_CHUNK = (global: true, log_num_of_bits: 3, log_bytes_in_region: LOG_BYTES_IN_CHUNK),
    // Track the index in SFT map for a chunk (only used for SFT sparse chunk map)
    SFT_DENSE_CHUNK_MAP_INDEX   = (global: true, log_num_of_bits: 3, log_bytes_in_region: LOG_BYTES_IN_CHUNK),
);

// This defines all LOCAL side metadata used by mmtk-core.
define_side_metadata_specs!(
    last_spec_as LAST_LOCAL_SIDE_METADATA_SPEC,
    // Mark pages by (malloc) marksweep
    MALLOC_MS_ACTIVE_PAGE  = (global: false, log_num_of_bits: 3, log_bytes_in_region: crate::util::malloc::library::LOG_BYTES_IN_MALLOC_PAGE as usize),
    // Record objects allocated with some offset
    MS_OFFSET_MALLOC = (global: false, log_num_of_bits: 0, log_bytes_in_region: LOG_MIN_OBJECT_SIZE as usize),
    // Mark lines by immix
    IX_LINE_MARK    = (global: false, log_num_of_bits: 3, log_bytes_in_region: crate::policy::immix::line::Line::LOG_BYTES),
    // Record defrag state for immix blocks
    IX_BLOCK_DEFRAG = (global: false, log_num_of_bits: 3, log_bytes_in_region: crate::policy::immix::block::Block::LOG_BYTES),
    // Mark blocks by immix
    IX_BLOCK_MARK   = (global: false, log_num_of_bits: 3, log_bytes_in_region: crate::policy::immix::block::Block::LOG_BYTES),
    // Mark chunks (any plan that uses the chunk map should include this spec in their local sidemetadata specs)
    CHUNK_MARK   = (global: false, log_num_of_bits: 3, log_bytes_in_region: crate::util::heap::chunk_map::Chunk::LOG_BYTES),
    // Mark blocks by (native mimalloc) marksweep
    MS_BLOCK_MARK   = (global: false, log_num_of_bits: 3, log_bytes_in_region: crate::policy::marksweepspace::native_ms::Block::LOG_BYTES),
    // Next block in list for native mimalloc
    MS_BLOCK_NEXT   = (global: false, log_num_of_bits: LOG_BITS_IN_ADDRESS, log_bytes_in_region: crate::policy::marksweepspace::native_ms::Block::LOG_BYTES),
    // Previous block in list for native mimalloc
    MS_BLOCK_PREV   = (global: false, log_num_of_bits: LOG_BITS_IN_ADDRESS, log_bytes_in_region: crate::policy::marksweepspace::native_ms::Block::LOG_BYTES),
    // Pointer to owning list for blocks for native mimalloc
    MS_BLOCK_LIST   = (global: false, log_num_of_bits: LOG_BITS_IN_ADDRESS, log_bytes_in_region: crate::policy::marksweepspace::native_ms::Block::LOG_BYTES),
    // Size of cells in block for native mimalloc FIXME: do we actually need usize?
    MS_BLOCK_SIZE         = (global: false, log_num_of_bits: LOG_BITS_IN_ADDRESS, log_bytes_in_region: crate::policy::marksweepspace::native_ms::Block::LOG_BYTES),
    // TLS of owning mutator of block for native mimalloc
    MS_BLOCK_TLS    = (global: false, log_num_of_bits: LOG_BITS_IN_ADDRESS, log_bytes_in_region: crate::policy::marksweepspace::native_ms::Block::LOG_BYTES),
    // First cell of free list in block for native mimalloc
    MS_FREE         = (global: false, log_num_of_bits: LOG_BITS_IN_ADDRESS, log_bytes_in_region: crate::policy::marksweepspace::native_ms::Block::LOG_BYTES),
    // The following specs are only used for manual malloc/free
    // First cell of local free list in block for native mimalloc
    MS_LOCAL_FREE   = (global: false, log_num_of_bits: LOG_BITS_IN_ADDRESS, log_bytes_in_region: crate::policy::marksweepspace::native_ms::Block::LOG_BYTES),
    // First cell of thread free list in block for native mimalloc
    MS_THREAD_FREE  = (global: false, log_num_of_bits: LOG_BITS_IN_ADDRESS, log_bytes_in_region: crate::policy::marksweepspace::native_ms::Block::LOG_BYTES),
);

#[cfg(test)]
mod tests {
    // We assert on constants to test if the macro is working properly.
    #![allow(clippy::assertions_on_constants)]

    use super::*;
    #[test]
    fn first_global_spec() {
        define_side_metadata_specs!(last_spec_as LAST_GLOBAL_SPEC, TEST_SPEC = (global: true, log_num_of_bits: 0, log_bytes_in_region: 3),);
        assert!(TEST_SPEC.is_global);
        assert!(TEST_SPEC.offset == GLOBAL_SIDE_METADATA_BASE_OFFSET);
        assert_eq!(TEST_SPEC.log_num_of_bits, 0);
        assert_eq!(TEST_SPEC.log_bytes_in_region, 3);
        assert_eq!(TEST_SPEC, LAST_GLOBAL_SPEC);
    }

    #[test]
    fn first_local_spec() {
        define_side_metadata_specs!(last_spec_as LAST_LOCAL_SPEC, TEST_SPEC = (global: false, log_num_of_bits: 0, log_bytes_in_region: 3),);
        assert!(!TEST_SPEC.is_global);
        assert!(TEST_SPEC.offset == LOCAL_SIDE_METADATA_BASE_OFFSET);
        assert_eq!(TEST_SPEC.log_num_of_bits, 0);
        assert_eq!(TEST_SPEC.log_bytes_in_region, 3);
        assert_eq!(TEST_SPEC, LAST_LOCAL_SPEC);
    }

    #[test]
    fn two_global_specs() {
        define_side_metadata_specs!(
            last_spec_as LAST_GLOBAL_SPEC,
            TEST_SPEC1 = (global: true, log_num_of_bits: 0, log_bytes_in_region: 3),
            TEST_SPEC2 = (global: true, log_num_of_bits: 1, log_bytes_in_region: 4),
        );

        assert!(TEST_SPEC1.is_global);
        assert!(TEST_SPEC1.offset == GLOBAL_SIDE_METADATA_BASE_OFFSET);
        assert_eq!(TEST_SPEC1.log_num_of_bits, 0);
        assert_eq!(TEST_SPEC1.log_bytes_in_region, 3);

        assert!(TEST_SPEC2.is_global);
        assert!(TEST_SPEC2.offset == SideMetadataOffset::layout_after(&TEST_SPEC1));
        assert_eq!(TEST_SPEC2.log_num_of_bits, 1);
        assert_eq!(TEST_SPEC2.log_bytes_in_region, 4);

        assert_eq!(TEST_SPEC2, LAST_GLOBAL_SPEC);
    }

    #[test]
    fn three_global_specs() {
        define_side_metadata_specs!(
            last_spec_as LAST_GLOBAL_SPEC,
            TEST_SPEC1 = (global: true, log_num_of_bits: 0, log_bytes_in_region: 3),
            TEST_SPEC2 = (global: true, log_num_of_bits: 1, log_bytes_in_region: 4),
            TEST_SPEC3 = (global: true, log_num_of_bits: 2, log_bytes_in_region: 5),
        );

        assert!(TEST_SPEC1.is_global);
        assert!(TEST_SPEC1.offset == GLOBAL_SIDE_METADATA_BASE_OFFSET);
        assert_eq!(TEST_SPEC1.log_num_of_bits, 0);
        assert_eq!(TEST_SPEC1.log_bytes_in_region, 3);

        assert!(TEST_SPEC2.is_global);
        assert!(TEST_SPEC2.offset == SideMetadataOffset::layout_after(&TEST_SPEC1));
        assert_eq!(TEST_SPEC2.log_num_of_bits, 1);
        assert_eq!(TEST_SPEC2.log_bytes_in_region, 4);

        assert!(TEST_SPEC3.is_global);
        assert!(TEST_SPEC3.offset == SideMetadataOffset::layout_after(&TEST_SPEC2));
        assert_eq!(TEST_SPEC3.log_num_of_bits, 2);
        assert_eq!(TEST_SPEC3.log_bytes_in_region, 5);

        assert_eq!(TEST_SPEC3, LAST_GLOBAL_SPEC);
    }

    #[test]
    fn both_global_and_local() {
        define_side_metadata_specs!(
            last_spec_as LAST_GLOBAL_SPEC,
            TEST_GSPEC1 = (global: true, log_num_of_bits: 0, log_bytes_in_region: 3),
            TEST_GSPEC2 = (global: true, log_num_of_bits: 1, log_bytes_in_region: 4),
        );
        define_side_metadata_specs!(
            last_spec_as LAST_LOCAL_SPEC,
            TEST_LSPEC1 = (global: false, log_num_of_bits: 2, log_bytes_in_region: 5),
            TEST_LSPEC2 = (global: false, log_num_of_bits: 3, log_bytes_in_region: 6),
        );

        assert!(TEST_GSPEC1.is_global);
        assert!(TEST_GSPEC1.offset == GLOBAL_SIDE_METADATA_BASE_OFFSET);
        assert_eq!(TEST_GSPEC1.log_num_of_bits, 0);
        assert_eq!(TEST_GSPEC1.log_bytes_in_region, 3);

        assert!(TEST_GSPEC2.is_global);
        assert!(TEST_GSPEC2.offset == SideMetadataOffset::layout_after(&TEST_GSPEC1));
        assert_eq!(TEST_GSPEC2.log_num_of_bits, 1);
        assert_eq!(TEST_GSPEC2.log_bytes_in_region, 4);

        assert_eq!(TEST_GSPEC2, LAST_GLOBAL_SPEC);

        assert!(!TEST_LSPEC1.is_global);
        assert!(TEST_LSPEC1.offset == LOCAL_SIDE_METADATA_BASE_OFFSET);
        assert_eq!(TEST_LSPEC1.log_num_of_bits, 2);
        assert_eq!(TEST_LSPEC1.log_bytes_in_region, 5);

        assert!(!TEST_LSPEC2.is_global);
        assert!(TEST_LSPEC2.offset == SideMetadataOffset::layout_after(&TEST_LSPEC1));
        assert_eq!(TEST_LSPEC2.log_num_of_bits, 3);
        assert_eq!(TEST_LSPEC2.log_bytes_in_region, 6);

        assert_eq!(TEST_LSPEC2, LAST_LOCAL_SPEC);
    }
}