| ... | ... | @@ -26,8 +26,7 @@ decl_table: std.AutoArrayHashMapUnmanaged(*const Module.Decl, DeclBlock) = .{}, |
| 26 | 26 | /// Accumulates allocations and then there is a periodic garbage collection after flush(). |
| 27 | 27 | arena: std.heap.ArenaAllocator, |
| 28 | 28 | |
| 29 | | /// Per-declaration data. For functions this is the body, and |
| 30 | | /// the forward declaration is stored in the FnBlock. |
| 29 | /// Per-declaration data. |
| 31 | 30 | const DeclBlock = struct { |
| 32 | 31 | code: std.ArrayListUnmanaged(u8) = .{}, |
| 33 | 32 | fwd_decl: std.ArrayListUnmanaged(u8) = .{}, |
| ... | ... | @@ -243,28 +242,26 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { |
| 243 | 242 | const tracy = trace(@src()); |
| 244 | 243 | defer tracy.end(); |
| 245 | 244 | |
| 245 | const gpa = comp.gpa; |
| 246 | 246 | const module = self.base.options.module.?; |
| 247 | 247 | |
| 248 | 248 | // This code path happens exclusively with -ofmt=c. The flush logic for |
| 249 | 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 😎 |
| 252 | | var all_buffers = std.ArrayList(std.os.iovec_const).init(comp.gpa); |
| 253 | | defer all_buffers.deinit(); |
| 251 | var f: Flush = .{}; |
| 252 | defer f.deinit(gpa); |
| 254 | 253 | |
| 255 | 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; |
| 259 | | all_buffers.appendAssumeCapacity(.{ |
| 257 | f.all_buffers.appendAssumeCapacity(.{ |
| 260 | 258 | .iov_base = zig_h, |
| 261 | 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); |
| 265 | | defer err_typedef_buf.deinit(); |
| 266 | | const err_typedef_writer = err_typedef_buf.writer(); |
| 267 | | const err_typedef_item = all_buffers.addOneAssumeCapacity(); |
| 263 | const err_typedef_writer = f.err_typedef_buf.writer(gpa); |
| 264 | const err_typedef_item = f.all_buffers.addOneAssumeCapacity(); |
| 268 | 265 | |
| 269 | 266 | render_errors: { |
| 270 | 267 | if (module.global_error_set.size == 0) break :render_errors; |
| ... | ... | @@ -275,73 +272,121 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { |
| 275 | 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 | 275 | // Typedefs, forward decls, and non-functions first. |
| 283 | | // TODO: performance investigation: would keeping a list of Decls that we should |
| 284 | | // generate, rather than querying here, be faster? |
| 276 | // Unlike other backends, the .c code we are emitting is order-dependent. Therefore |
| 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 | 282 | const decl_keys = self.decl_table.keys(); |
| 286 | 283 | const decl_values = self.decl_table.values(); |
| 287 | | for (decl_keys) |decl, i| { |
| 288 | | if (!decl.has_tv) continue; // TODO do we really need this branch? |
| 289 | | |
| 290 | | const decl_block = &decl_values[i]; |
| 291 | | |
| 292 | | if (decl_block.fwd_decl.items.len != 0) { |
| 293 | | try typedefs.ensureUnusedCapacity(@intCast(u32, decl_block.typedefs.count())); |
| 294 | | var it = decl_block.typedefs.iterator(); |
| 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 | | } |
| 284 | for (decl_keys) |decl| { |
| 285 | assert(decl.has_tv); |
| 286 | f.remaining_decls.putAssumeCapacityNoClobber(decl, {}); |
| 287 | } |
| 288 | |
| 289 | while (f.remaining_decls.popOrNull()) |kv| { |
| 290 | const decl = kv.key; |
| 291 | try flushDecl(self, &f, decl); |
| 318 | 292 | } |
| 319 | 293 | |
| 320 | 294 | err_typedef_item.* = .{ |
| 321 | | .iov_base = err_typedef_buf.items.ptr, |
| 322 | | .iov_len = err_typedef_buf.items.len, |
| 295 | .iov_base = f.err_typedef_buf.items.ptr, |
| 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 | 300 | // Now the function bodies. |
| 327 | | try all_buffers.ensureUnusedCapacity(fn_count); |
| 301 | try f.all_buffers.ensureUnusedCapacity(gpa, f.fn_count); |
| 328 | 302 | for (decl_keys) |decl, i| { |
| 329 | 303 | if (decl.getFunction() != null) { |
| 330 | 304 | const decl_block = &decl_values[i]; |
| 331 | 305 | const buf = decl_block.code.items; |
| 332 | 306 | if (buf.len != 0) { |
| 333 | | all_buffers.appendAssumeCapacity(.{ |
| 307 | f.all_buffers.appendAssumeCapacity(.{ |
| 334 | 308 | .iov_base = buf.ptr, |
| 335 | 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 | 316 | const file = self.base.file.?; |
| 343 | | try file.setEndPos(file_size); |
| 344 | | try file.pwritevAll(all_buffers.items, 0); |
| 317 | try file.setEndPos(f.file_size); |
| 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 | 392 | pub fn flushEmitH(module: *Module) !void { |