* introduce std.ArrayListUnmanaged for when you have the allocator stored elsewhere * move std.heap.ArenaAllocator implementation to its own file. extract the main state into std.heap.ArenaAllocator.State, which can be stored as an alternative to storing the entire ArenaAllocator, saving 24 bytes per ArenaAllocator on 64 bit targets. * std.LinkedList.Node pointer field now defaults to being null initialized. * Rework self-hosted compiler Package API * Delete almost all the bitrotted self-hosted compiler code. The only bit rotted code left is in main.zig and compilation.zig * Add call instruction to ZIR * self-hosted compiler ir API and link API are reworked to support a long-running compiler that incrementally updates declarations * Introduce the concept of scopes to ZIR semantic analysis * ZIR text format supports referencing named decls that are declared later in the file * Figure out how memory management works for the long-running compiler and incremental compilation. The main roots are top level declarations. There is a table of decls. The key is a cryptographic hash of the fully qualified decl name. Each decl has an arena allocator where all of the memory related to that decl is stored. Each code block has its own arena allocator for the lifetime of the block. Values that want to survive when going out of scope in a block must get copied into the outer block. Finally, values must get copied into the Decl arena to be long-lived. * Delete the unused MemoryCell struct. Instead, comptime pointers are based on references to Decl structs. * Figure out how caching works. Each Decl will store a set of other Decls which must be recompiled when it changes. This branch is still work-in-progress; this commit breaks the build.
248 lines
7.8 KiB
Zig
248 lines
7.8 KiB
Zig
const std = @import("std");
|
|
const link = @import("link.zig");
|
|
const ir = @import("ir.zig");
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
test "self-hosted" {
|
|
var ctx: TestContext = undefined;
|
|
try ctx.init();
|
|
defer ctx.deinit();
|
|
|
|
try @import("stage2_tests").addCases(&ctx);
|
|
|
|
try ctx.run();
|
|
}
|
|
|
|
pub const TestContext = struct {
|
|
zir_cmp_output_cases: std.ArrayList(ZIRCompareOutputCase),
|
|
zir_transform_cases: std.ArrayList(ZIRTransformCase),
|
|
|
|
pub const ZIRCompareOutputCase = struct {
|
|
name: []const u8,
|
|
src: [:0]const u8,
|
|
expected_stdout: []const u8,
|
|
};
|
|
|
|
pub const ZIRTransformCase = struct {
|
|
name: []const u8,
|
|
src: [:0]const u8,
|
|
expected_zir: []const u8,
|
|
};
|
|
|
|
pub fn addZIRCompareOutput(
|
|
ctx: *TestContext,
|
|
name: []const u8,
|
|
src: [:0]const u8,
|
|
expected_stdout: []const u8,
|
|
) void {
|
|
ctx.zir_cmp_output_cases.append(.{
|
|
.name = name,
|
|
.src = src,
|
|
.expected_stdout = expected_stdout,
|
|
}) catch unreachable;
|
|
}
|
|
|
|
pub fn addZIRTransform(
|
|
ctx: *TestContext,
|
|
name: []const u8,
|
|
src: [:0]const u8,
|
|
expected_zir: []const u8,
|
|
) void {
|
|
ctx.zir_transform_cases.append(.{
|
|
.name = name,
|
|
.src = src,
|
|
.expected_zir = expected_zir,
|
|
}) catch unreachable;
|
|
}
|
|
|
|
fn init(self: *TestContext) !void {
|
|
self.* = .{
|
|
.zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(std.heap.page_allocator),
|
|
.zir_transform_cases = std.ArrayList(ZIRTransformCase).init(std.heap.page_allocator),
|
|
};
|
|
}
|
|
|
|
fn deinit(self: *TestContext) void {
|
|
self.zir_cmp_output_cases.deinit();
|
|
self.zir_transform_cases.deinit();
|
|
self.* = undefined;
|
|
}
|
|
|
|
fn run(self: *TestContext) !void {
|
|
var progress = std.Progress{};
|
|
const root_node = try progress.start("zir", self.zir_cmp_output_cases.items.len +
|
|
self.zir_transform_cases.items.len);
|
|
defer root_node.end();
|
|
|
|
const native_info = try std.zig.system.NativeTargetInfo.detect(std.heap.page_allocator, .{});
|
|
|
|
for (self.zir_cmp_output_cases.items) |case| {
|
|
std.testing.base_allocator_instance.reset();
|
|
try self.runOneZIRCmpOutputCase(std.testing.allocator, root_node, case, native_info.target);
|
|
try std.testing.allocator_instance.validate();
|
|
}
|
|
for (self.zir_transform_cases.items) |case| {
|
|
std.testing.base_allocator_instance.reset();
|
|
try self.runOneZIRTransformCase(std.testing.allocator, root_node, case, native_info.target);
|
|
try std.testing.allocator_instance.validate();
|
|
}
|
|
}
|
|
|
|
fn runOneZIRCmpOutputCase(
|
|
self: *TestContext,
|
|
allocator: *Allocator,
|
|
root_node: *std.Progress.Node,
|
|
case: ZIRCompareOutputCase,
|
|
target: std.Target,
|
|
) !void {
|
|
var tmp = std.testing.tmpDir(.{});
|
|
defer tmp.cleanup();
|
|
|
|
var prg_node = root_node.start(case.name, 4);
|
|
prg_node.activate();
|
|
defer prg_node.end();
|
|
|
|
var zir_module = x: {
|
|
var parse_node = prg_node.start("parse", null);
|
|
parse_node.activate();
|
|
defer parse_node.end();
|
|
|
|
break :x try ir.text.parse(allocator, case.src);
|
|
};
|
|
defer zir_module.deinit(allocator);
|
|
if (zir_module.errors.len != 0) {
|
|
debugPrintErrors(case.src, zir_module.errors);
|
|
return error.ParseFailure;
|
|
}
|
|
|
|
var analyzed_module = x: {
|
|
var analyze_node = prg_node.start("analyze", null);
|
|
analyze_node.activate();
|
|
defer analyze_node.end();
|
|
|
|
break :x try ir.analyze(allocator, zir_module, .{
|
|
.target = target,
|
|
.output_mode = .Exe,
|
|
.link_mode = .Static,
|
|
.optimize_mode = .Debug,
|
|
});
|
|
};
|
|
defer analyzed_module.deinit(allocator);
|
|
if (analyzed_module.errors.len != 0) {
|
|
debugPrintErrors(case.src, analyzed_module.errors);
|
|
return error.ParseFailure;
|
|
}
|
|
|
|
var link_result = x: {
|
|
var link_node = prg_node.start("link", null);
|
|
link_node.activate();
|
|
defer link_node.end();
|
|
|
|
break :x try link.updateFilePath(allocator, analyzed_module, tmp.dir, "a.out");
|
|
};
|
|
defer link_result.deinit(allocator);
|
|
if (link_result.errors.len != 0) {
|
|
debugPrintErrors(case.src, link_result.errors);
|
|
return error.LinkFailure;
|
|
}
|
|
|
|
var exec_result = x: {
|
|
var exec_node = prg_node.start("execute", null);
|
|
exec_node.activate();
|
|
defer exec_node.end();
|
|
|
|
break :x try std.ChildProcess.exec(.{
|
|
.allocator = allocator,
|
|
.argv = &[_][]const u8{"./a.out"},
|
|
.cwd_dir = tmp.dir,
|
|
});
|
|
};
|
|
defer allocator.free(exec_result.stdout);
|
|
defer allocator.free(exec_result.stderr);
|
|
switch (exec_result.term) {
|
|
.Exited => |code| {
|
|
if (code != 0) {
|
|
std.debug.warn("elf file exited with code {}\n", .{code});
|
|
return error.BinaryBadExitCode;
|
|
}
|
|
},
|
|
else => return error.BinaryCrashed,
|
|
}
|
|
std.testing.expectEqualSlices(u8, case.expected_stdout, exec_result.stdout);
|
|
}
|
|
|
|
fn runOneZIRTransformCase(
|
|
self: *TestContext,
|
|
allocator: *Allocator,
|
|
root_node: *std.Progress.Node,
|
|
case: ZIRTransformCase,
|
|
target: std.Target,
|
|
) !void {
|
|
var prg_node = root_node.start(case.name, 4);
|
|
prg_node.activate();
|
|
defer prg_node.end();
|
|
|
|
var parse_node = prg_node.start("parse", null);
|
|
parse_node.activate();
|
|
var zir_module = try ir.text.parse(allocator, case.src);
|
|
defer zir_module.deinit(allocator);
|
|
if (zir_module.errors.len != 0) {
|
|
debugPrintErrors(case.src, zir_module.errors);
|
|
return error.ParseFailure;
|
|
}
|
|
parse_node.end();
|
|
|
|
var analyze_node = prg_node.start("analyze", null);
|
|
analyze_node.activate();
|
|
var analyzed_module = try ir.analyze(allocator, zir_module, .{
|
|
.target = target,
|
|
.output_mode = .Obj,
|
|
.link_mode = .Static,
|
|
.optimize_mode = .Debug,
|
|
});
|
|
defer analyzed_module.deinit(allocator);
|
|
if (analyzed_module.errors.len != 0) {
|
|
debugPrintErrors(case.src, analyzed_module.errors);
|
|
return error.ParseFailure;
|
|
}
|
|
analyze_node.end();
|
|
|
|
var emit_node = prg_node.start("emit", null);
|
|
emit_node.activate();
|
|
var new_zir_module = try ir.text.emit_zir(allocator, analyzed_module);
|
|
defer new_zir_module.deinit(allocator);
|
|
emit_node.end();
|
|
|
|
var write_node = prg_node.start("write", null);
|
|
write_node.activate();
|
|
var out_zir = std.ArrayList(u8).init(allocator);
|
|
defer out_zir.deinit();
|
|
try new_zir_module.writeToStream(allocator, out_zir.outStream());
|
|
write_node.end();
|
|
|
|
std.testing.expectEqualSlices(u8, case.expected_zir, out_zir.items);
|
|
}
|
|
};
|
|
|
|
fn debugPrintErrors(src: []const u8, errors: var) void {
|
|
std.debug.warn("\n", .{});
|
|
var nl = true;
|
|
var line: usize = 1;
|
|
for (src) |byte| {
|
|
if (nl) {
|
|
std.debug.warn("{: >3}| ", .{line});
|
|
nl = false;
|
|
}
|
|
if (byte == '\n') {
|
|
nl = true;
|
|
line += 1;
|
|
}
|
|
std.debug.warn("{c}", .{byte});
|
|
}
|
|
std.debug.warn("\n", .{});
|
|
for (errors) |err_msg| {
|
|
const loc = std.zig.findLineColumn(src, err_msg.byte_offset);
|
|
std.debug.warn("{}:{}: error: {}\n", .{ loc.line + 1, loc.column + 1, err_msg.msg });
|
|
}
|
|
}
|