forked from factor/factor
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathobject_start_map.zig
More file actions
407 lines (326 loc) · 12.7 KB
/
Copy pathobject_start_map.zig
File metadata and controls
407 lines (326 loc) · 12.7 KB
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
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
const std = @import("std");
const layouts = @import("layouts.zig");
const vm = @import("vm.zig");
const mark_bits = @import("mark_bits.zig");
const Cell = layouts.Cell;
// Map stores offsets at card granularity
// Each entry is the offset (in bytes) from the card start to the object start
// A value of 0xFF means "look at the previous card"
pub const invalid_offset: u8 = 0xFF;
pub const ObjectStartMap = struct {
start: Cell,
size: Cell,
entries: []u8,
allocator: std.mem.Allocator,
const Self = @This();
pub fn init(allocator: std.mem.Allocator, start: Cell, size: Cell) !Self {
const start_offset = start & (vm.card_size - 1);
const card_count = (start_offset + size + vm.card_size - 1) / vm.card_size;
const entries = try allocator.alloc(u8, card_count);
// Initialize all entries to invalid
@memset(entries, invalid_offset);
return Self{
.start = start,
.size = size,
.entries = entries,
.allocator = allocator,
};
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.entries);
self.entries = &[_]u8{};
}
fn addressToCard(self: *const Self, address: Cell) usize {
std.debug.assert(address >= self.start);
std.debug.assert(address < self.start + self.size);
return (address - self.start) / vm.card_size;
}
fn cardToAddress(self: *const Self, card_index: usize) Cell {
return self.start + card_index * vm.card_size;
}
pub fn recordObjectStart(self: *Self, address: Cell) void {
std.debug.assert((address & 0xF) == 0); // Objects must be 16-byte aligned
const card_index = self.addressToCard(address);
const card_start = self.cardToAddress(card_index);
const offset = address - card_start;
// Only update if this is the first object in the card, or if it's earlier
if (self.entries[card_index] == invalid_offset or offset < self.entries[card_index]) {
self.entries[card_index] = @truncate(offset);
}
}
pub fn recordAllocation(self: *Self, address: Cell) void {
self.recordObjectStart(address);
}
pub fn clear(self: *Self) void {
@memset(self.entries, invalid_offset);
}
// Find the first object at or after the start of a card
// Used when scanning cards for write barrier
pub fn findFirstObjectInCard(self: *const Self, card_index: usize) ?Cell {
if (card_index >= self.entries.len) {
return null;
}
// If this card has a valid offset, use it
if (self.entries[card_index] != invalid_offset) {
return self.cardToAddress(card_index) + self.entries[card_index];
}
// Otherwise, walk back to find the object spanning into this card
var idx = card_index;
while (idx > 0) {
idx -= 1;
if (self.entries[idx] != invalid_offset) {
var object_addr = self.cardToAddress(idx) + self.entries[idx];
const card_start = self.cardToAddress(card_index);
// Walk forward to find object in or spanning into the card
while (object_addr < card_start + vm.card_size) {
const size = @import("free_list.zig").objectSizeFromHeader(object_addr);
if (object_addr + size > card_start) {
return object_addr;
}
object_addr += size;
if (object_addr >= self.start + self.size) {
return null;
}
}
return null;
}
}
return null;
}
// Find the object containing the start of this card by walking backward to a
// card with a known object start.
pub fn findObjectContainingCard(self: *const Self, card_index: usize) ?Cell {
if (card_index >= self.entries.len) {
return null;
}
if (card_index == 0) return self.start;
// Walk backward until a card with a valid object-start entry is found.
var idx = card_index - 1;
while (self.entries[idx] == invalid_offset) {
if (idx == 0) return self.start;
idx -= 1;
}
const result = self.cardToAddress(idx) + self.entries[idx];
std.debug.assert((result & 0xF) == 0); // result must be 16-byte aligned
return result;
}
// Update after compaction - rebuild the map from scratch
pub fn rebuild(self: *Self, start: Cell, end: Cell) void {
self.clear();
var current = start;
while (current < end) {
self.recordObjectStart(current);
const size = @import("free_list.zig").objectSizeFromHeader(current);
if (size == 0) break;
current += size;
}
}
// Update card offsets after sweep using mark bits.
pub fn updateForSweep(self: *Self, marks: *const mark_bits.MarkBits) void {
const is_64 = @sizeOf(Cell) == 8;
var index: usize = 0;
while (index < marks.bits_size) : (index += 1) {
const mask: Cell = marks.marked[index];
if (mask == 0) {
if (is_64) {
const first = index * 4;
if (first < self.entries.len) {
const last = @min(first + 4, self.entries.len);
@memset(self.entries[first..last], invalid_offset);
}
} else {
const first = index * 2;
if (first < self.entries.len) {
const last = @min(first + 2, self.entries.len);
@memset(self.entries[first..last], invalid_offset);
}
}
continue;
}
if (is_64) {
self.updateCardForSweep(index * 4, @intCast(mask & 0xFFFF));
self.updateCardForSweep(index * 4 + 1, @intCast((mask >> 16) & 0xFFFF));
self.updateCardForSweep(index * 4 + 2, @intCast((mask >> 32) & 0xFFFF));
self.updateCardForSweep(index * 4 + 3, @intCast((mask >> 48) & 0xFFFF));
} else {
self.updateCardForSweep(index * 2, @intCast(mask & 0xFFFF));
self.updateCardForSweep(index * 2 + 1, @intCast((mask >> 16) & 0xFFFF));
}
}
}
pub fn updateCardForSweep(self: *Self, card_index: usize, mask: u16) void {
if (card_index >= self.entries.len) return;
const offset = self.entries[card_index];
if (offset == invalid_offset) return;
// Validate existing offset is aligned
std.debug.assert((offset & 0xF) == 0); // existing offset must be aligned
var shifted = mask;
const shift_bits: u4 = @truncate(offset / layouts.data_alignment);
shifted >>= shift_bits;
if (shifted == 0) {
self.entries[card_index] = invalid_offset;
} else {
const bit = rightmostSetBit(shifted);
const new_offset: u16 = @intCast(@as(u16, offset) + bit * @as(u16, layouts.data_alignment));
self.entries[card_index] = @truncate(new_offset);
}
}
fn rightmostSetBit(mask: u16) u16 {
return @intCast(@ctz(mask));
}
};
// Tracks which cards have been written to (dirty)
// Used alongside object start map for write barrier
pub const CardTable = struct {
start: Cell,
size: Cell,
cards: []u8,
allocator: std.mem.Allocator,
const Self = @This();
pub fn init(allocator: std.mem.Allocator, start: Cell, size: Cell) !Self {
const start_offset = start & (vm.card_size - 1);
const card_count = (start_offset + size + vm.card_size - 1) / vm.card_size;
const cards = try allocator.alloc(u8, card_count);
@memset(cards, 0);
return Self{
.start = start,
.size = size,
.cards = cards,
.allocator = allocator,
};
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.cards);
self.cards = &[_]u8{};
}
fn addressToCard(self: *const Self, address: Cell) usize {
return (address - self.start) / vm.card_size;
}
pub fn markCard(self: *Self, address: Cell) void {
const card = self.addressToCard(address);
self.cards[card] = vm.card_mark_mask;
}
pub fn isCardDirty(self: *const Self, card_index: usize) bool {
return (self.cards[card_index] & vm.card_mark_mask) != 0;
}
pub fn clearCard(self: *Self, card_index: usize) void {
self.cards[card_index] = 0;
}
pub fn clearAll(self: *Self) void {
@memset(self.cards, 0);
}
pub fn cardCount(self: *const Self) usize {
return self.cards.len;
}
};
// Deck table - coarse-grained tracking
// Each deck covers deck_size (256KB) bytes
pub const DeckTable = struct {
start: Cell,
size: Cell,
decks: []u8,
allocator: std.mem.Allocator,
const Self = @This();
pub fn init(allocator: std.mem.Allocator, start: Cell, size: Cell) !Self {
const start_offset = start & (vm.deck_size - 1);
const deck_count = (start_offset + size + vm.deck_size - 1) / vm.deck_size;
const decks = try allocator.alloc(u8, deck_count);
@memset(decks, 0);
return Self{
.start = start,
.size = size,
.decks = decks,
.allocator = allocator,
};
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.decks);
self.decks = &[_]u8{};
}
fn addressToDeck(self: *const Self, address: Cell) usize {
return (address - self.start) / vm.deck_size;
}
pub fn markDeck(self: *Self, address: Cell) void {
const deck = self.addressToDeck(address);
self.decks[deck] = vm.card_mark_mask;
}
pub fn isDeckDirty(self: *const Self, deck_index: usize) bool {
return (self.decks[deck_index] & vm.card_mark_mask) != 0;
}
pub fn clearDeck(self: *Self, deck_index: usize) void {
self.decks[deck_index] = 0;
}
pub fn clearAll(self: *Self) void {
@memset(self.decks, 0);
}
pub fn deckCount(self: *const Self) usize {
return self.decks.len;
}
pub fn firstCardInDeck(deck_index: usize) usize {
return deck_index * (vm.deck_size / vm.card_size);
}
pub fn lastCardInDeck(deck_index: usize) usize {
return (deck_index + 1) * (vm.deck_size / vm.card_size) - 1;
}
};
// Tests
test "object_start_map basic operations" {
const allocator = std.testing.allocator;
const start: Cell = 0x10000;
const size: Cell = 0x10000; // 64KB
var osm = try ObjectStartMap.init(allocator, start, size);
defer osm.deinit();
// Record some object starts (all 16-byte aligned)
osm.recordObjectStart(start);
osm.recordObjectStart(start + 96);
osm.recordObjectStart(start + 304);
// Check entries were recorded
// Card 0 should have offset 0 (object at start of card)
try std.testing.expectEqual(@as(u8, 0), osm.entries[0]);
}
test "object_start_map multiple objects in card" {
const allocator = std.testing.allocator;
const start: Cell = 0x10000;
const size: Cell = 0x10000;
var osm = try ObjectStartMap.init(allocator, start, size);
defer osm.deinit();
// Multiple objects in same card - earliest one wins
osm.recordObjectStart(start + 48);
osm.recordObjectStart(start + 16);
osm.recordObjectStart(start + 96);
try std.testing.expectEqual(@as(u8, 16), osm.entries[0]);
}
test "card_table operations" {
const allocator = std.testing.allocator;
const start: Cell = 0x10000;
const size: Cell = 0x10000;
var ct = try CardTable.init(allocator, start, size);
defer ct.deinit();
// Initially clean
try std.testing.expect(!ct.isCardDirty(0));
try std.testing.expect(!ct.isCardDirty(1));
// Mark some cards
ct.markCard(start);
ct.markCard(start + vm.card_size * 5);
try std.testing.expect(ct.isCardDirty(0));
try std.testing.expect(!ct.isCardDirty(1));
try std.testing.expect(ct.isCardDirty(5));
// Clear
ct.clearCard(0);
try std.testing.expect(!ct.isCardDirty(0));
}
test "deck_table operations" {
const allocator = std.testing.allocator;
const start: Cell = 0x10000;
const size: Cell = 0x1000000; // 16MB
var dt = try DeckTable.init(allocator, start, size);
defer dt.deinit();
// Initially clean
try std.testing.expect(!dt.isDeckDirty(0));
// Mark a deck
dt.markDeck(start);
try std.testing.expect(dt.isDeckDirty(0));
// Check deck math
try std.testing.expectEqual(@as(usize, 0), DeckTable.firstCardInDeck(0));
try std.testing.expectEqual(@as(usize, 1023), DeckTable.lastCardInDeck(0));
}