stage2: zig test now works with the LLVM backend

Frontend improvements:

 * When compiling in `zig test` mode, put a task on the work queue to
   analyze the main package root file. Normally, start code does
   `_ = import("root");` to make Zig analyze the user's code, however in
   the case of `zig test`, the root source file is the test runner.
   Without this change, no tests are picked up.
 * In the main pipeline, once semantic analysis is finished, if there
   are no compile errors, populate the `test_functions` Decl with the
   set of test functions picked up from semantic analysis.
 * Value: add `array` and `slice` Tags.

LLVM backend improvements:

 * Fix incremental updates of globals. Previously the
   value of a global would not get replaced with a new value.
 * Fix LLVM type of arrays. They were incorrectly sending
   the ABI size as the element count.
 * Remove the FuncGen parameter from genTypedValue. This function is for
   generating global constants and there is no function available when
   it is being called.
   - The `ref_val` case is now commented out. I'd like to eliminate
     `ref_val` as one of the possible Value Tags. Instead it should
     always be done via `decl_ref`.
 * Implement constant value generation for slices, arrays, and structs.
 * Constant value generation for functions supports the `decl_ref` tag.
This commit is contained in:
Andrew Kelley
2021-07-27 14:06:42 -07:00
parent ba71b96fe6
commit a8e964eadd
6 changed files with 332 additions and 82 deletions

View File

@@ -112,6 +112,8 @@ compile_log_text: ArrayListUnmanaged(u8) = .{},
emit_h: ?*GlobalEmitH,
test_functions: std.AutoArrayHashMapUnmanaged(*Decl, void) = .{},
/// A `Module` has zero or one of these depending on whether `-femit-h` is enabled.
pub const GlobalEmitH = struct {
/// Where to put the output.
@@ -282,6 +284,7 @@ pub const Decl = struct {
pub fn destroy(decl: *Decl, module: *Module) void {
const gpa = module.gpa;
log.debug("destroy {*} ({s})", .{ decl, decl.name });
_ = module.test_functions.swapRemove(decl);
if (decl.deletion_flag) {
assert(module.deletion_set.swapRemove(decl));
}
@@ -3319,6 +3322,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi
// the test name filter.
if (!mod.comp.bin_file.options.is_test) break :blk false;
if (decl_pkg != mod.main_pkg) break :blk false;
try mod.test_functions.put(gpa, new_decl, {});
break :blk true;
},
else => blk: {
@@ -3326,6 +3330,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi
if (!mod.comp.bin_file.options.is_test) break :blk false;
if (decl_pkg != mod.main_pkg) break :blk false;
// TODO check the name against --test-filter
try mod.test_functions.put(gpa, new_decl, {});
break :blk true;
},
};
@@ -3765,17 +3770,38 @@ pub fn createAnonymousDeclNamed(
scope: *Scope,
typed_value: TypedValue,
name: [:0]u8,
) !*Decl {
return mod.createAnonymousDeclFromDeclNamed(scope.ownerDecl().?, typed_value, name);
}
pub fn createAnonymousDecl(mod: *Module, scope: *Scope, typed_value: TypedValue) !*Decl {
return mod.createAnonymousDeclFromDecl(scope.ownerDecl().?, typed_value);
}
pub fn createAnonymousDeclFromDecl(mod: *Module, owner_decl: *Decl, tv: TypedValue) !*Decl {
const name_index = mod.getNextAnonNameIndex();
const name = try std.fmt.allocPrintZ(mod.gpa, "{s}__anon_{d}", .{
owner_decl.name, name_index,
});
return mod.createAnonymousDeclFromDeclNamed(owner_decl, tv, name);
}
/// Takes ownership of `name` even if it returns an error.
pub fn createAnonymousDeclFromDeclNamed(
mod: *Module,
owner_decl: *Decl,
typed_value: TypedValue,
name: [:0]u8,
) !*Decl {
errdefer mod.gpa.free(name);
const scope_decl = scope.ownerDecl().?;
const namespace = scope_decl.namespace;
const namespace = owner_decl.namespace;
try namespace.anon_decls.ensureUnusedCapacity(mod.gpa, 1);
const new_decl = try mod.allocateNewDecl(namespace, scope_decl.src_node);
const new_decl = try mod.allocateNewDecl(namespace, owner_decl.src_node);
new_decl.name = name;
new_decl.src_line = scope_decl.src_line;
new_decl.src_line = owner_decl.src_line;
new_decl.ty = typed_value.ty;
new_decl.val = typed_value.val;
new_decl.has_tv = true;
@@ -3796,15 +3822,6 @@ pub fn createAnonymousDeclNamed(
return new_decl;
}
pub fn createAnonymousDecl(mod: *Module, scope: *Scope, typed_value: TypedValue) !*Decl {
const scope_decl = scope.ownerDecl().?;
const name_index = mod.getNextAnonNameIndex();
const name = try std.fmt.allocPrintZ(mod.gpa, "{s}__anon_{d}", .{
scope_decl.name, name_index,
});
return mod.createAnonymousDeclNamed(scope, typed_value, name);
}
pub fn getNextAnonNameIndex(mod: *Module) usize {
return @atomicRmw(usize, &mod.next_anon_name_index, .Add, 1, .Monotonic);
}
@@ -4801,3 +4818,95 @@ pub fn processExports(mod: *Module) !void {
};
}
}
pub fn populateTestFunctions(mod: *Module) !void {
const gpa = mod.gpa;
const builtin_pkg = mod.main_pkg.table.get("builtin").?;
const builtin_file = (mod.importPkg(builtin_pkg) catch unreachable).file;
const builtin_namespace = builtin_file.root_decl.?.namespace;
const decl = builtin_namespace.decls.get("test_functions").?;
var buf: Type.Payload.ElemType = undefined;
const tmp_test_fn_ty = decl.ty.slicePtrFieldType(&buf).elemType();
const array_decl = d: {
// Add mod.test_functions to an array decl then make the test_functions
// decl reference it as a slice.
var new_decl_arena = std.heap.ArenaAllocator.init(gpa);
errdefer new_decl_arena.deinit();
const arena = &new_decl_arena.allocator;
const test_fn_vals = try arena.alloc(Value, mod.test_functions.count());
const array_decl = try mod.createAnonymousDeclFromDecl(decl, .{
.ty = try Type.Tag.array.create(arena, .{
.len = test_fn_vals.len,
.elem_type = try tmp_test_fn_ty.copy(arena),
}),
.val = try Value.Tag.array.create(arena, test_fn_vals),
});
for (mod.test_functions.keys()) |test_decl, i| {
const test_name_slice = mem.sliceTo(test_decl.name, 0);
const test_name_decl = n: {
var name_decl_arena = std.heap.ArenaAllocator.init(gpa);
errdefer name_decl_arena.deinit();
const bytes = try name_decl_arena.allocator.dupe(u8, test_name_slice);
const test_name_decl = try mod.createAnonymousDeclFromDecl(array_decl, .{
.ty = try Type.Tag.array_u8.create(&name_decl_arena.allocator, bytes.len),
.val = try Value.Tag.bytes.create(&name_decl_arena.allocator, bytes),
});
try test_name_decl.finalizeNewArena(&name_decl_arena);
break :n test_name_decl;
};
try mod.linkerUpdateDecl(test_name_decl);
const field_vals = try arena.create([3]Value);
field_vals.* = .{
try Value.Tag.slice.create(arena, .{
.ptr = try Value.Tag.decl_ref.create(arena, test_name_decl),
.len = try Value.Tag.int_u64.create(arena, test_name_slice.len),
}), // name
try Value.Tag.decl_ref.create(arena, test_decl), // func
Value.initTag(.null_value), // async_frame_size
};
test_fn_vals[i] = try Value.Tag.@"struct".create(arena, field_vals);
}
try array_decl.finalizeNewArena(&new_decl_arena);
break :d array_decl;
};
try mod.linkerUpdateDecl(array_decl);
{
var arena_instance = decl.value_arena.?.promote(gpa);
defer decl.value_arena.?.* = arena_instance.state;
const arena = &arena_instance.allocator;
decl.ty = try Type.Tag.const_slice.create(arena, try tmp_test_fn_ty.copy(arena));
decl.val = try Value.Tag.slice.create(arena, .{
.ptr = try Value.Tag.decl_ref.create(arena, array_decl),
.len = try Value.Tag.int_u64.create(arena, mod.test_functions.count()),
});
}
try mod.linkerUpdateDecl(decl);
}
pub fn linkerUpdateDecl(mod: *Module, decl: *Decl) !void {
mod.comp.bin_file.updateDecl(mod, decl) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.AnalysisFail => {
decl.analysis = .codegen_failure;
return;
},
else => {
const gpa = mod.gpa;
try mod.failed_decls.ensureUnusedCapacity(gpa, 1);
mod.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
gpa,
decl.srcLoc(),
"unable to codegen: {s}",
.{@errorName(err)},
));
decl.analysis = .codegen_failure_retryable;
return;
},
};
}