| ... | @@ -26,8 +26,7 @@ decl_table: std.AutoArrayHashMapUnmanaged(*const Module.Decl, DeclBlock) = .{}, | ... | @@ -26,8 +26,7 @@ decl_table: std.AutoArrayHashMapUnmanaged(*const Module.Decl, DeclBlock) = .{}, |
| 26 | /// Accumulates allocations and then there is a periodic garbage collection after flush(). | 26 | /// Accumulates allocations and then there is a periodic garbage collection after flush(). |
| 27 | arena: std.heap.ArenaAllocator, | 27 | arena: std.heap.ArenaAllocator, |
| 28 | | 28 | |
| 29 | /// Per-declaration data. For functions this is the body, and | 29 | /// Per-declaration data. |
| 30 | /// the forward declaration is stored in the FnBlock. | | |
| 31 | const DeclBlock = struct { | 30 | const DeclBlock = struct { |
| 32 | code: std.ArrayListUnmanaged(u8) = .{}, | 31 | code: std.ArrayListUnmanaged(u8) = .{}, |
| 33 | fwd_decl: std.ArrayListUnmanaged(u8) = .{}, | 32 | fwd_decl: std.ArrayListUnmanaged(u8) = .{}, |
| ... | @@ -243,28 +242,26 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { | ... | @@ -243,28 +242,26 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { |
| 243 | const tracy = trace(@src()); | 242 | const tracy = trace(@src()); |
| 244 | defer tracy.end(); | 243 | defer tracy.end(); |
| 245 | | 244 | |
| | 245 | const gpa = comp.gpa; |
| 246 | const module = self.base.options.module.?; | 246 | const module = self.base.options.module.?; |
| 247 | | 247 | |
| 248 | // This code path happens exclusively with -ofmt=c. The flush logic for | 248 | // This code path happens exclusively with -ofmt=c. The flush logic for |
| 249 | // emit-h is in `flushEmitH` below. | 249 | // emit-h is in `flushEmitH` below. |
| 250 | | 250 | |
| 251 | // We collect a list of buffers to write, and write them all at once with pwritev 😎 | 251 | var f: Flush = .{}; |
| 252 | var all_buffers = std.ArrayList(std.os.iovec_const).init(comp.gpa); | 252 | defer f.deinit(gpa); |
| 253 | defer all_buffers.deinit(); | | |
| 254 | | 253 | |
| 255 | // This is at least enough until we get to the function bodies without error handling. | 254 | // This is at least enough until we get to the function bodies without error handling. |
| 256 | try all_buffers.ensureTotalCapacity(self.decl_table.count() + 2); | 255 | try f.all_buffers.ensureTotalCapacity(gpa, self.decl_table.count() + 2); |
| 257 | | 256 | |
| 258 | var file_size: u64 = zig_h.len; | 257 | f.all_buffers.appendAssumeCapacity(.{ |
| 259 | all_buffers.appendAssumeCapacity(.{ | | |
| 260 | .iov_base = zig_h, | 258 | .iov_base = zig_h, |
| 261 | .iov_len = zig_h.len, | 259 | .iov_len = zig_h.len, |
| 262 | }); | 260 | }); |
| | 261 | f.file_size += zig_h.len; |
| 263 | | 262 | |
| 264 | var err_typedef_buf = std.ArrayList(u8).init(comp.gpa); | 263 | const err_typedef_writer = f.err_typedef_buf.writer(gpa); |
| 265 | defer err_typedef_buf.deinit(); | 264 | const err_typedef_item = f.all_buffers.addOneAssumeCapacity(); |
| 266 | const err_typedef_writer = err_typedef_buf.writer(); | | |
| 267 | const err_typedef_item = all_buffers.addOneAssumeCapacity(); | | |
| 268 | | 265 | |
| 269 | render_errors: { | 266 | render_errors: { |
| 270 | if (module.global_error_set.size == 0) break :render_errors; | 267 | if (module.global_error_set.size == 0) break :render_errors; |
| ... | @@ -275,73 +272,121 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { | ... | @@ -275,73 +272,121 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { |
| 275 | try err_typedef_writer.writeByte('\n'); | 272 | try err_typedef_writer.writeByte('\n'); |
| 276 | } | 273 | } |
| 277 | | 274 | |
| 278 | var fn_count: usize = 0; | | |
| 279 | var typedefs = std.HashMap(Type, void, Type.HashContext64, std.hash_map.default_max_load_percentage).init(comp.gpa); | | |
| 280 | defer typedefs.deinit(); | | |
| 281 | | | |
| 282 | // Typedefs, forward decls, and non-functions first. | 275 | // Typedefs, forward decls, and non-functions first. |
| 283 | // TODO: performance investigation: would keeping a list of Decls that we should | 276 | // Unlike other backends, the .c code we are emitting is order-dependent. Therefore |
| 284 | // generate, rather than querying here, be faster? | 277 | // we must traverse the set of Decls that we are emitting according to their dependencies. |
| | 278 | // Our strategy is to populate a set of remaining decls, pop Decls one by one, |
| | 279 | // recursively chasing their dependencies. |
| | 280 | try f.remaining_decls.ensureUnusedCapacity(gpa, self.decl_table.count()); |
| | 281 | |
| 285 | const decl_keys = self.decl_table.keys(); | 282 | const decl_keys = self.decl_table.keys(); |
| 286 | const decl_values = self.decl_table.values(); | 283 | const decl_values = self.decl_table.values(); |
| 287 | for (decl_keys) |decl, i| { | 284 | for (decl_keys) |decl| { |
| 288 | if (!decl.has_tv) continue; // TODO do we really need this branch? | 285 | assert(decl.has_tv); |
| 289 | | 286 | f.remaining_decls.putAssumeCapacityNoClobber(decl, {}); |
| 290 | const decl_block = &decl_values[i]; | 287 | } |
| 291 | | 288 | |
| 292 | if (decl_block.fwd_decl.items.len != 0) { | 289 | while (f.remaining_decls.popOrNull()) |kv| { |
| 293 | try typedefs.ensureUnusedCapacity(@intCast(u32, decl_block.typedefs.count())); | 290 | const decl = kv.key; |
| 294 | var it = decl_block.typedefs.iterator(); | 291 | try flushDecl(self, &f, decl); |
| 295 | while (it.next()) |new| { | | |
| 296 | const gop = typedefs.getOrPutAssumeCapacity(new.key_ptr.*); | | |
| 297 | if (!gop.found_existing) { | | |
| 298 | try err_typedef_writer.writeAll(new.value_ptr.rendered); | | |
| 299 | } | | |
| 300 | } | | |
| 301 | const buf = decl_block.fwd_decl.items; | | |
| 302 | all_buffers.appendAssumeCapacity(.{ | | |
| 303 | .iov_base = buf.ptr, | | |
| 304 | .iov_len = buf.len, | | |
| 305 | }); | | |
| 306 | file_size += buf.len; | | |
| 307 | } | | |
| 308 | if (decl.getFunction() != null) { | | |
| 309 | fn_count += 1; | | |
| 310 | } else if (decl_block.code.items.len != 0) { | | |
| 311 | const buf = decl_block.code.items; | | |
| 312 | all_buffers.appendAssumeCapacity(.{ | | |
| 313 | .iov_base = buf.ptr, | | |
| 314 | .iov_len = buf.len, | | |
| 315 | }); | | |
| 316 | file_size += buf.len; | | |
| 317 | } | | |
| 318 | } | 292 | } |
| 319 | | 293 | |
| 320 | err_typedef_item.* = .{ | 294 | err_typedef_item.* = .{ |
| 321 | .iov_base = err_typedef_buf.items.ptr, | 295 | .iov_base = f.err_typedef_buf.items.ptr, |
| 322 | .iov_len = err_typedef_buf.items.len, | 296 | .iov_len = f.err_typedef_buf.items.len, |
| 323 | }; | 297 | }; |
| 324 | file_size += err_typedef_buf.items.len; | 298 | f.file_size += f.err_typedef_buf.items.len; |
| 325 | | 299 | |
| 326 | // Now the function bodies. | 300 | // Now the function bodies. |
| 327 | try all_buffers.ensureUnusedCapacity(fn_count); | 301 | try f.all_buffers.ensureUnusedCapacity(gpa, f.fn_count); |
| 328 | for (decl_keys) |decl, i| { | 302 | for (decl_keys) |decl, i| { |
| 329 | if (decl.getFunction() != null) { | 303 | if (decl.getFunction() != null) { |
| 330 | const decl_block = &decl_values[i]; | 304 | const decl_block = &decl_values[i]; |
| 331 | const buf = decl_block.code.items; | 305 | const buf = decl_block.code.items; |
| 332 | if (buf.len != 0) { | 306 | if (buf.len != 0) { |
| 333 | all_buffers.appendAssumeCapacity(.{ | 307 | f.all_buffers.appendAssumeCapacity(.{ |
| 334 | .iov_base = buf.ptr, | 308 | .iov_base = buf.ptr, |
| 335 | .iov_len = buf.len, | 309 | .iov_len = buf.len, |
| 336 | }); | 310 | }); |
| 337 | file_size += buf.len; | 311 | f.file_size += buf.len; |
| 338 | } | 312 | } |
| 339 | } | 313 | } |
| 340 | } | 314 | } |
| 341 | | 315 | |
| 342 | const file = self.base.file.?; | 316 | const file = self.base.file.?; |
| 343 | try file.setEndPos(file_size); | 317 | try file.setEndPos(f.file_size); |
| 344 | try file.pwritevAll(all_buffers.items, 0); | 318 | try file.pwritevAll(f.all_buffers.items, 0); |
| | 319 | } |
| | 320 | |
| | 321 | const Flush = struct { |
| | 322 | remaining_decls: std.AutoArrayHashMapUnmanaged(*const Module.Decl, void) = .{}, |
| | 323 | typedefs: Typedefs = .{}, |
| | 324 | err_typedef_buf: std.ArrayListUnmanaged(u8) = .{}, |
| | 325 | /// We collect a list of buffers to write, and write them all at once with pwritev 😎 |
| | 326 | all_buffers: std.ArrayListUnmanaged(std.os.iovec_const) = .{}, |
| | 327 | /// Keeps track of the total bytes of `all_buffers`. |
| | 328 | file_size: u64 = 0, |
| | 329 | fn_count: usize = 0, |
| | 330 | |
| | 331 | const Typedefs = std.HashMapUnmanaged( |
| | 332 | Type, |
| | 333 | void, |
| | 334 | Type.HashContext64, |
| | 335 | std.hash_map.default_max_load_percentage, |
| | 336 | ); |
| | 337 | |
| | 338 | fn deinit(f: *Flush, gpa: *Allocator) void { |
| | 339 | f.all_buffers.deinit(gpa); |
| | 340 | f.err_typedef_buf.deinit(gpa); |
| | 341 | f.typedefs.deinit(gpa); |
| | 342 | f.remaining_decls.deinit(gpa); |
| | 343 | } |
| | 344 | }; |
| | 345 | |
| | 346 | const FlushDeclError = error{ |
| | 347 | OutOfMemory, |
| | 348 | }; |
| | 349 | |
| | 350 | /// Assumes `decl` was in the `remaining_decls` set, and has already been removed. |
| | 351 | fn flushDecl(self: *C, f: *Flush, decl: *const Module.Decl) FlushDeclError!void { |
| | 352 | // Before flushing any particular Decl we must ensure its |
| | 353 | // dependencies are already flushed, so that the order in the .c |
| | 354 | // file comes out correctly. |
| | 355 | for (decl.dependencies.keys()) |dep| { |
| | 356 | if (f.remaining_decls.swapRemove(dep)) { |
| | 357 | try flushDecl(self, f, dep); |
| | 358 | } |
| | 359 | } |
| | 360 | |
| | 361 | const decl_block = self.decl_table.getPtr(decl).?; |
| | 362 | const gpa = self.base.allocator; |
| | 363 | |
| | 364 | if (decl_block.fwd_decl.items.len != 0) { |
| | 365 | try f.typedefs.ensureUnusedCapacity(gpa, @intCast(u32, decl_block.typedefs.count())); |
| | 366 | var it = decl_block.typedefs.iterator(); |
| | 367 | while (it.next()) |new| { |
| | 368 | const gop = f.typedefs.getOrPutAssumeCapacity(new.key_ptr.*); |
| | 369 | if (!gop.found_existing) { |
| | 370 | try f.err_typedef_buf.appendSlice(gpa, new.value_ptr.rendered); |
| | 371 | } |
| | 372 | } |
| | 373 | const buf = decl_block.fwd_decl.items; |
| | 374 | f.all_buffers.appendAssumeCapacity(.{ |
| | 375 | .iov_base = buf.ptr, |
| | 376 | .iov_len = buf.len, |
| | 377 | }); |
| | 378 | f.file_size += buf.len; |
| | 379 | } |
| | 380 | if (decl.getFunction() != null) { |
| | 381 | f.fn_count += 1; |
| | 382 | } else if (decl_block.code.items.len != 0) { |
| | 383 | const buf = decl_block.code.items; |
| | 384 | f.all_buffers.appendAssumeCapacity(.{ |
| | 385 | .iov_base = buf.ptr, |
| | 386 | .iov_len = buf.len, |
| | 387 | }); |
| | 388 | f.file_size += buf.len; |
| | 389 | } |
| 345 | } | 390 | } |
| 346 | | 391 | |
| 347 | pub fn flushEmitH(module: *Module) !void { | 392 | pub fn flushEmitH(module: *Module) !void { |