aboutsummaryrefslogtreecommitdiff
path: root/src-self-hosted
diff options
context:
space:
mode:
Diffstat (limited to 'src-self-hosted')
-rw-r--r--src-self-hosted/Module.zig59
-rw-r--r--src-self-hosted/astgen.zig307
-rw-r--r--src-self-hosted/codegen.zig412
-rw-r--r--src-self-hosted/link/Elf.zig17
-rw-r--r--src-self-hosted/link/MachO.zig181
-rw-r--r--src-self-hosted/main.zig2
-rw-r--r--src-self-hosted/translate_c.zig12
-rw-r--r--src-self-hosted/type.zig11
-rw-r--r--src-self-hosted/value.zig36
-rw-r--r--src-self-hosted/zir.zig20
-rw-r--r--src-self-hosted/zir_sema.zig64
11 files changed, 828 insertions, 293 deletions
diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig
index 82029c1e9f..c476c307d2 100644
--- a/src-self-hosted/Module.zig
+++ b/src-self-hosted/Module.zig
@@ -1256,8 +1256,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
};
defer fn_type_scope.instructions.deinit(self.gpa);
- decl.is_pub = fn_proto.getTrailer("visib_token") != null;
- const body_node = fn_proto.getTrailer("body_node") orelse
+ decl.is_pub = fn_proto.getVisibToken() != null;
+ const body_node = fn_proto.getBodyNode() orelse
return self.failTok(&fn_type_scope.base, fn_proto.fn_token, "TODO implement extern functions", .{});
const param_decls = fn_proto.params();
@@ -1276,19 +1276,19 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
};
param_types[i] = try astgen.expr(self, &fn_type_scope.base, type_type_rl, param_type_node);
}
- if (fn_proto.getTrailer("var_args_token")) |var_args_token| {
+ if (fn_proto.getVarArgsToken()) |var_args_token| {
return self.failTok(&fn_type_scope.base, var_args_token, "TODO implement var args", .{});
}
- if (fn_proto.getTrailer("lib_name")) |lib_name| {
+ if (fn_proto.getLibName()) |lib_name| {
return self.failNode(&fn_type_scope.base, lib_name, "TODO implement function library name", .{});
}
- if (fn_proto.getTrailer("align_expr")) |align_expr| {
+ if (fn_proto.getAlignExpr()) |align_expr| {
return self.failNode(&fn_type_scope.base, align_expr, "TODO implement function align expression", .{});
}
- if (fn_proto.getTrailer("section_expr")) |sect_expr| {
+ if (fn_proto.getSectionExpr()) |sect_expr| {
return self.failNode(&fn_type_scope.base, sect_expr, "TODO implement function section expression", .{});
}
- if (fn_proto.getTrailer("callconv_expr")) |callconv_expr| {
+ if (fn_proto.getCallconvExpr()) |callconv_expr| {
return self.failNode(
&fn_type_scope.base,
callconv_expr,
@@ -1430,10 +1430,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
self.bin_file.freeDecl(decl);
}
- if (fn_proto.getTrailer("extern_export_inline_token")) |maybe_export_token| {
+ if (fn_proto.getExternExportInlineToken()) |maybe_export_token| {
if (tree.token_ids[maybe_export_token] == .Keyword_export) {
const export_src = tree.token_locs[maybe_export_token].start;
- const name_loc = tree.token_locs[fn_proto.getTrailer("name_token").?];
+ const name_loc = tree.token_locs[fn_proto.getNameToken().?];
const name = tree.tokenSliceLoc(name_loc);
// The scope needs to have the decl in it.
try self.analyzeExport(&block_scope.base, export_src, name, decl);
@@ -1460,37 +1460,37 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
};
defer block_scope.instructions.deinit(self.gpa);
- decl.is_pub = var_decl.getTrailer("visib_token") != null;
+ decl.is_pub = var_decl.getVisibToken() != null;
const is_extern = blk: {
- const maybe_extern_token = var_decl.getTrailer("extern_export_token") orelse
+ const maybe_extern_token = var_decl.getExternExportToken() orelse
break :blk false;
if (tree.token_ids[maybe_extern_token] != .Keyword_extern) break :blk false;
- if (var_decl.getTrailer("init_node")) |some| {
+ if (var_decl.getInitNode()) |some| {
return self.failNode(&block_scope.base, some, "extern variables have no initializers", .{});
}
break :blk true;
};
- if (var_decl.getTrailer("lib_name")) |lib_name| {
+ if (var_decl.getLibName()) |lib_name| {
assert(is_extern);
return self.failNode(&block_scope.base, lib_name, "TODO implement function library name", .{});
}
const is_mutable = tree.token_ids[var_decl.mut_token] == .Keyword_var;
- const is_threadlocal = if (var_decl.getTrailer("thread_local_token")) |some| blk: {
+ const is_threadlocal = if (var_decl.getThreadLocalToken()) |some| blk: {
if (!is_mutable) {
return self.failTok(&block_scope.base, some, "threadlocal variable cannot be constant", .{});
}
break :blk true;
} else false;
- assert(var_decl.getTrailer("comptime_token") == null);
- if (var_decl.getTrailer("align_node")) |align_expr| {
+ assert(var_decl.getComptimeToken() == null);
+ if (var_decl.getAlignNode()) |align_expr| {
return self.failNode(&block_scope.base, align_expr, "TODO implement function align expression", .{});
}
- if (var_decl.getTrailer("section_node")) |sect_expr| {
+ if (var_decl.getSectionNode()) |sect_expr| {
return self.failNode(&block_scope.base, sect_expr, "TODO implement function section expression", .{});
}
const explicit_type = blk: {
- const type_node = var_decl.getTrailer("type_node") orelse
+ const type_node = var_decl.getTypeNode() orelse
break :blk null;
// Temporary arena for the zir instructions.
@@ -1517,7 +1517,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
};
var var_type: Type = undefined;
- const value: ?Value = if (var_decl.getTrailer("init_node")) |init_node| blk: {
+ const value: ?Value = if (var_decl.getInitNode()) |init_node| blk: {
var gen_scope_arena = std.heap.ArenaAllocator.init(self.gpa);
defer gen_scope_arena.deinit();
var gen_scope: Scope.GenZIR = .{
@@ -1602,7 +1602,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
decl.analysis = .complete;
decl.generation = self.generation;
- if (var_decl.getTrailer("extern_export_token")) |maybe_export_token| {
+ if (var_decl.getExternExportToken()) |maybe_export_token| {
if (tree.token_ids[maybe_export_token] == .Keyword_export) {
const export_src = tree.token_locs[maybe_export_token].start;
const name_loc = tree.token_locs[var_decl.name_token];
@@ -1768,7 +1768,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
for (decls) |src_decl, decl_i| {
if (src_decl.cast(ast.Node.FnProto)) |fn_proto| {
// We will create a Decl for it regardless of analysis status.
- const name_tok = fn_proto.getTrailer("name_token") orelse {
+ const name_tok = fn_proto.getNameToken() orelse {
@panic("TODO missing function name");
};
@@ -1804,7 +1804,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
} else {
const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash);
root_scope.decls.appendAssumeCapacity(new_decl);
- if (fn_proto.getTrailer("extern_export_inline_token")) |maybe_export_token| {
+ if (fn_proto.getExternExportInlineToken()) |maybe_export_token| {
if (tree.token_ids[maybe_export_token] == .Keyword_export) {
self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });
}
@@ -1831,7 +1831,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
} else {
const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash);
root_scope.decls.appendAssumeCapacity(new_decl);
- if (var_decl.getTrailer("extern_export_token")) |maybe_export_token| {
+ if (var_decl.getExternExportToken()) |maybe_export_token| {
if (tree.token_ids[maybe_export_token] == .Keyword_export) {
self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });
}
@@ -2570,7 +2570,18 @@ pub fn analyzeIsNull(
operand: *Inst,
invert_logic: bool,
) InnerError!*Inst {
- return self.fail(scope, src, "TODO implement analysis of isnull and isnotnull", .{});
+ if (operand.value()) |opt_val| {
+ const is_null = opt_val.isNull();
+ const bool_value = if (invert_logic) !is_null else is_null;
+ return self.constBool(scope, src, bool_value);
+ }
+ const b = try self.requireRuntimeBlock(scope, src);
+ const inst_tag: Inst.Tag = if (invert_logic) .isnonnull else .isnull;
+ return self.addUnOp(b, src, Type.initTag(.bool), inst_tag, operand);
+}
+
+pub fn analyzeIsErr(self: *Module, scope: *Scope, src: usize, operand: *Inst) InnerError!*Inst {
+ return self.fail(scope, src, "TODO implement analysis of iserr", .{});
}
/// Asserts that lhs and rhs types are both numeric.
diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig
index 0d8e0dc874..472f8deaa0 100644
--- a/src-self-hosted/astgen.zig
+++ b/src-self-hosted/astgen.zig
@@ -13,7 +13,8 @@ const Scope = Module.Scope;
const InnerError = Module.InnerError;
pub const ResultLoc = union(enum) {
- /// The expression is the right-hand side of assignment to `_`.
+ /// The expression is the right-hand side of assignment to `_`. Only the side-effects of the
+ /// expression should be generated.
discard,
/// The expression has an inferred type, and it will be evaluated as an rvalue.
none,
@@ -272,22 +273,22 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
.AnyFrameType => return rlWrap(mod, scope, rl, try anyFrameType(mod, scope, node.castTag(.AnyFrameType).?)),
.ErrorSetDecl => return errorSetDecl(mod, scope, rl, node.castTag(.ErrorSetDecl).?),
.ErrorType => return rlWrap(mod, scope, rl, try errorType(mod, scope, node.castTag(.ErrorType).?)),
+ .For => return forExpr(mod, scope, rl, node.castTag(.For).?),
+ .ArrayAccess => return arrayAccess(mod, scope, rl, node.castTag(.ArrayAccess).?),
+ .Catch => return catchExpr(mod, scope, rl, node.castTag(.Catch).?),
.Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}),
- .Catch => return mod.failNode(scope, node, "TODO implement astgen.expr for .Catch", .{}),
.Range => return mod.failNode(scope, node, "TODO implement astgen.expr for .Range", .{}),
.OrElse => return mod.failNode(scope, node, "TODO implement astgen.expr for .OrElse", .{}),
.Await => return mod.failNode(scope, node, "TODO implement astgen.expr for .Await", .{}),
.Resume => return mod.failNode(scope, node, "TODO implement astgen.expr for .Resume", .{}),
.Try => return mod.failNode(scope, node, "TODO implement astgen.expr for .Try", .{}),
.Slice => return mod.failNode(scope, node, "TODO implement astgen.expr for .Slice", .{}),
- .ArrayAccess => return mod.failNode(scope, node, "TODO implement astgen.expr for .ArrayAccess", .{}),
.ArrayInitializer => return mod.failNode(scope, node, "TODO implement astgen.expr for .ArrayInitializer", .{}),
.ArrayInitializerDot => return mod.failNode(scope, node, "TODO implement astgen.expr for .ArrayInitializerDot", .{}),
.StructInitializer => return mod.failNode(scope, node, "TODO implement astgen.expr for .StructInitializer", .{}),
.StructInitializerDot => return mod.failNode(scope, node, "TODO implement astgen.expr for .StructInitializerDot", .{}),
.Switch => return mod.failNode(scope, node, "TODO implement astgen.expr for .Switch", .{}),
- .For => return mod.failNode(scope, node, "TODO implement astgen.expr for .For", .{}),
.Suspend => return mod.failNode(scope, node, "TODO implement astgen.expr for .Suspend", .{}),
.Continue => return mod.failNode(scope, node, "TODO implement astgen.expr for .Continue", .{}),
.AnyType => return mod.failNode(scope, node, "TODO implement astgen.expr for .AnyType", .{}),
@@ -450,16 +451,16 @@ fn varDecl(
block_arena: *Allocator,
) InnerError!*Scope {
// TODO implement detection of shadowing
- if (node.getTrailer("comptime_token")) |comptime_token| {
+ if (node.getComptimeToken()) |comptime_token| {
return mod.failTok(scope, comptime_token, "TODO implement comptime locals", .{});
}
- if (node.getTrailer("align_node")) |align_node| {
+ if (node.getAlignNode()) |align_node| {
return mod.failNode(scope, align_node, "TODO implement alignment on locals", .{});
}
const tree = scope.tree();
const name_src = tree.token_locs[node.name_token].start;
const ident_name = try identifierTokenString(mod, scope, node.name_token);
- const init_node = node.getTrailer("init_node") orelse
+ const init_node = node.getInitNode() orelse
return mod.fail(scope, name_src, "variables must be initialized", .{});
switch (tree.token_ids[node.mut_token]) {
@@ -468,7 +469,7 @@ fn varDecl(
// or an rvalue as a result location. If it is an rvalue, we can use the instruction as
// the variable, no memory location needed.
const result_loc = if (nodeMayNeedMemoryLocation(init_node)) r: {
- if (node.getTrailer("type_node")) |type_node| {
+ if (node.getTypeNode()) |type_node| {
const type_inst = try typeExpr(mod, scope, type_node);
const alloc = try addZIRUnOp(mod, scope, name_src, .alloc, type_inst);
break :r ResultLoc{ .ptr = alloc };
@@ -477,7 +478,7 @@ fn varDecl(
break :r ResultLoc{ .inferred_ptr = alloc };
}
} else r: {
- if (node.getTrailer("type_node")) |type_node|
+ if (node.getTypeNode()) |type_node|
break :r ResultLoc{ .ty = try typeExpr(mod, scope, type_node) }
else
break :r .none;
@@ -493,10 +494,10 @@ fn varDecl(
return &sub_scope.base;
},
.Keyword_var => {
- const var_data: struct { result_loc: ResultLoc, alloc: *zir.Inst } = if (node.getTrailer("type_node")) |type_node| a: {
+ const var_data: struct { result_loc: ResultLoc, alloc: *zir.Inst } = if (node.getTypeNode()) |type_node| a: {
const type_inst = try typeExpr(mod, scope, type_node);
const alloc = try addZIRUnOp(mod, scope, name_src, .alloc, type_inst);
- break :a .{ .alloc = try addZIRUnOp(mod, scope, name_src, .alloc, type_inst), .result_loc = .{ .ptr = alloc } };
+ break :a .{ .alloc = alloc, .result_loc = .{ .ptr = alloc } };
} else a: {
const alloc = try addZIRNoOp(mod, scope, name_src, .alloc_inferred);
break :a .{ .alloc = alloc, .result_loc = .{ .inferred_ptr = alloc.castTag(.alloc_inferred).? } };
@@ -623,7 +624,7 @@ fn ptrSliceType(mod: *Module, scope: *Scope, src: usize, ptr_info: *ast.PtrInfo,
.One => if (mutable) T.single_mut_ptr_type else T.single_const_ptr_type,
.Many => if (mutable) T.many_mut_ptr_type else T.many_const_ptr_type,
.C => if (mutable) T.c_mut_ptr_type else T.c_const_ptr_type,
- .Slice => if (mutable) T.mut_slice_type else T.mut_slice_type,
+ .Slice => if (mutable) T.mut_slice_type else T.const_slice_type,
}, child_type);
}
@@ -749,6 +750,93 @@ fn errorType(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError!*
});
}
+fn catchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Catch) InnerError!*zir.Inst {
+ const tree = scope.tree();
+ const src = tree.token_locs[node.op_token].start;
+
+ const err_union_ptr = try expr(mod, scope, .ref, node.lhs);
+ // TODO we could avoid an unnecessary copy if .iserr took a pointer
+ const err_union = try addZIRUnOp(mod, scope, src, .deref, err_union_ptr);
+ const cond = try addZIRUnOp(mod, scope, src, .iserr, err_union);
+
+ var block_scope: Scope.GenZIR = .{
+ .parent = scope,
+ .decl = scope.decl().?,
+ .arena = scope.arena(),
+ .instructions = .{},
+ };
+ defer block_scope.instructions.deinit(mod.gpa);
+
+ const condbr = try addZIRInstSpecial(mod, &block_scope.base, src, zir.Inst.CondBr, .{
+ .condition = cond,
+ .then_body = undefined, // populated below
+ .else_body = undefined, // populated below
+ }, .{});
+
+ const block = try addZIRInstBlock(mod, scope, src, .{
+ .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
+ });
+
+ // Most result location types can be forwarded directly; however
+ // if we need to write to a pointer which has an inferred type,
+ // proper type inference requires peer type resolution on the if's
+ // branches.
+ const branch_rl: ResultLoc = switch (rl) {
+ .discard, .none, .ty, .ptr, .ref => rl,
+ .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block },
+ };
+
+ var err_scope: Scope.GenZIR = .{
+ .parent = scope,
+ .decl = block_scope.decl,
+ .arena = block_scope.arena,
+ .instructions = .{},
+ };
+ defer err_scope.instructions.deinit(mod.gpa);
+
+ var err_val_scope: Scope.LocalVal = undefined;
+ const err_sub_scope = blk: {
+ const payload = node.payload orelse
+ break :blk &err_scope.base;
+
+ const err_name = tree.tokenSlice(payload.castTag(.Payload).?.error_symbol.firstToken());
+ if (mem.eql(u8, err_name, "_"))
+ break :blk &err_scope.base;
+
+ const unwrapped_err_ptr = try addZIRUnOp(mod, &err_scope.base, src, .unwrap_err_code, err_union_ptr);
+ err_val_scope = .{
+ .parent = &err_scope.base,
+ .gen_zir = &err_scope,
+ .name = err_name,
+ .inst = try addZIRUnOp(mod, &err_scope.base, src, .deref, unwrapped_err_ptr),
+ };
+ break :blk &err_val_scope.base;
+ };
+
+ _ = try addZIRInst(mod, &err_scope.base, src, zir.Inst.Break, .{
+ .block = block,
+ .operand = try expr(mod, err_sub_scope, branch_rl, node.rhs),
+ }, .{});
+
+ var not_err_scope: Scope.GenZIR = .{
+ .parent = scope,
+ .decl = block_scope.decl,
+ .arena = block_scope.arena,
+ .instructions = .{},
+ };
+ defer not_err_scope.instructions.deinit(mod.gpa);
+
+ const unwrapped_payload = try addZIRUnOp(mod, &not_err_scope.base, src, .unwrap_err_unsafe, err_union_ptr);
+ _ = try addZIRInst(mod, &not_err_scope.base, src, zir.Inst.Break, .{
+ .block = block,
+ .operand = unwrapped_payload,
+ }, .{});
+
+ condbr.positionals.then_body = .{ .instructions = try err_scope.arena.dupe(*zir.Inst, err_scope.instructions.items) };
+ condbr.positionals.else_body = .{ .instructions = try not_err_scope.arena.dupe(*zir.Inst, not_err_scope.instructions.items) };
+ return rlWrap(mod, scope, rl, &block.base);
+}
+
/// Return whether the identifier names of two tokens are equal. Resolves @"" tokens without allocating.
/// OK in theory it could do it without allocating. This implementation allocates when the @"" form is used.
fn tokenIdentEql(mod: *Module, scope: *Scope, token1: ast.TokenIndex, token2: ast.TokenIndex) !bool {
@@ -793,9 +881,17 @@ fn field(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.SimpleInfix
const lhs = try expr(mod, scope, .ref, node.lhs);
const field_name = try identifierStringInst(mod, scope, node.rhs.castTag(.Identifier).?);
- const pointer = try addZIRInst(mod, scope, src, zir.Inst.FieldPtr, .{ .object_ptr = lhs, .field_name = field_name }, .{});
- if (rl == .ref) return pointer;
- return rlWrap(mod, scope, rl, try addZIRUnOp(mod, scope, src, .deref, pointer));
+ return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.FieldPtr, .{ .object_ptr = lhs, .field_name = field_name }, .{}));
+}
+
+fn arrayAccess(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.ArrayAccess) InnerError!*zir.Inst {
+ const tree = scope.tree();
+ const src = tree.token_locs[node.rtoken].start;
+
+ const array_ptr = try expr(mod, scope, .ref, node.lhs);
+ const index = try expr(mod, scope, .none, node.index_expr);
+
+ return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.ElemPtr, .{ .array_ptr = array_ptr, .index = index }, .{}));
}
fn deref(mod: *Module, scope: *Scope, node: *ast.Node.SimpleSuffixOp) InnerError!*zir.Inst {
@@ -1079,6 +1175,12 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W
}
}
+ if (while_node.label) |tok|
+ return mod.failTok(scope, tok, "TODO labeled while", .{});
+
+ if (while_node.inline_token) |tok|
+ return mod.failTok(scope, tok, "TODO inline while", .{});
+
var expr_scope: Scope.GenZIR = .{
.parent = scope,
.decl = scope.decl().?,
@@ -1197,6 +1299,181 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W
return &while_block.base;
}
+fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For) InnerError!*zir.Inst {
+ if (for_node.label) |tok|
+ return mod.failTok(scope, tok, "TODO labeled for", .{});
+
+ if (for_node.inline_token) |tok|
+ return mod.failTok(scope, tok, "TODO inline for", .{});
+
+ var for_scope: Scope.GenZIR = .{
+ .parent = scope,
+ .decl = scope.decl().?,
+ .arena = scope.arena(),
+ .instructions = .{},
+ };
+ defer for_scope.instructions.deinit(mod.gpa);
+
+ // setup variables and constants
+ const tree = scope.tree();
+ const for_src = tree.token_locs[for_node.for_token].start;
+ const index_ptr = blk: {
+ const usize_type = try addZIRInstConst(mod, &for_scope.base, for_src, .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.usize_type),
+ });
+ const index_ptr = try addZIRUnOp(mod, &for_scope.base, for_src, .alloc, usize_type);
+ // initialize to zero
+ const zero = try addZIRInstConst(mod, &for_scope.base, for_src, .{
+ .ty = Type.initTag(.usize),
+ .val = Value.initTag(.zero),
+ });
+ _ = try addZIRBinOp(mod, &for_scope.base, for_src, .store, index_ptr, zero);
+ break :blk index_ptr;
+ };
+ const array_ptr = try expr(mod, &for_scope.base, .ref, for_node.array_expr);
+ _ = try addZIRUnOp(mod, &for_scope.base, for_node.array_expr.firstToken(), .ensure_indexable, array_ptr);
+ const cond_src = tree.token_locs[for_node.array_expr.firstToken()].start;
+ const len_ptr = try addZIRInst(mod, &for_scope.base, cond_src, zir.Inst.FieldPtr, .{
+ .object_ptr = array_ptr,
+ .field_name = try addZIRInst(mod, &for_scope.base, cond_src, zir.Inst.Str, .{ .bytes = "len" }, .{}),
+ }, .{});
+
+ var loop_scope: Scope.GenZIR = .{
+ .parent = &for_scope.base,
+ .decl = for_scope.decl,
+ .arena = for_scope.arena,
+ .instructions = .{},
+ };
+ defer loop_scope.instructions.deinit(mod.gpa);
+
+ var cond_scope: Scope.GenZIR = .{
+ .parent = &loop_scope.base,
+ .decl = loop_scope.decl,
+ .arena = loop_scope.arena,
+ .instructions = .{},
+ };
+ defer cond_scope.instructions.deinit(mod.gpa);
+
+ // check condition i < array_expr.len
+ const index = try addZIRUnOp(mod, &cond_scope.base, cond_src, .deref, index_ptr);
+ const len = try addZIRUnOp(mod, &cond_scope.base, cond_src, .deref, len_ptr);
+ const cond = try addZIRBinOp(mod, &cond_scope.base, cond_src, .cmp_lt, index, len);
+
+ const condbr = try addZIRInstSpecial(mod, &cond_scope.base, for_src, zir.Inst.CondBr, .{
+ .condition = cond,
+ .then_body = undefined, // populated below
+ .else_body = undefined, // populated below
+ }, .{});
+ const cond_block = try addZIRInstBlock(mod, &loop_scope.base, for_src, .{
+ .instructions = try loop_scope.arena.dupe(*zir.Inst, cond_scope.instructions.items),
+ });
+
+ // increment index variable
+ const one = try addZIRInstConst(mod, &loop_scope.base, for_src, .{
+ .ty = Type.initTag(.usize),
+ .val = Value.initTag(.one),
+ });
+ const index_2 = try addZIRUnOp(mod, &loop_scope.base, cond_src, .deref, index_ptr);
+ const index_plus_one = try addZIRBinOp(mod, &loop_scope.base, for_src, .add, index_2, one);
+ _ = try addZIRBinOp(mod, &loop_scope.base, for_src, .store, index_ptr, index_plus_one);
+
+ // looping stuff
+ const loop = try addZIRInstLoop(mod, &for_scope.base, for_src, .{
+ .instructions = try for_scope.arena.dupe(*zir.Inst, loop_scope.instructions.items),
+ });
+ const for_block = try addZIRInstBlock(mod, scope, for_src, .{
+ .instructions = try for_scope.arena.dupe(*zir.Inst, for_scope.instructions.items),
+ });
+
+ // while body
+ const then_src = tree.token_locs[for_node.body.lastToken()].start;
+ var then_scope: Scope.GenZIR = .{
+ .parent = &cond_scope.base,
+ .decl = cond_scope.decl,
+ .arena = cond_scope.arena,
+ .instructions = .{},
+ };
+ defer then_scope.instructions.deinit(mod.gpa);
+
+ // Most result location types can be forwarded directly; however
+ // if we need to write to a pointer which has an inferred type,
+ // proper type inference requires peer type resolution on the while's
+ // branches.
+ const branch_rl: ResultLoc = switch (rl) {
+ .discard, .none, .ty, .ptr, .ref => rl,
+ .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = for_block },
+ };
+
+ var index_scope: Scope.LocalPtr = undefined;
+ const then_sub_scope = blk: {
+ const payload = for_node.payload.castTag(.PointerIndexPayload).?;
+ const is_ptr = payload.ptr_token != null;
+ const value_name = tree.tokenSlice(payload.value_symbol.firstToken());
+ if (!mem.eql(u8, value_name, "_")) {
+ return mod.failNode(&then_scope.base, payload.value_symbol, "TODO implement for value payload", .{});
+ } else if (is_ptr) {
+ return mod.failTok(&then_scope.base, payload.ptr_token.?, "pointer modifier invalid on discard", .{});
+ }
+
+ const index_symbol_node = payload.index_symbol orelse
+ break :blk &then_scope.base;
+
+ const index_name = tree.tokenSlice(index_symbol_node.firstToken());
+ if (mem.eql(u8, index_name, "_")) {
+ break :blk &then_scope.base;
+ }
+ // TODO make this const without an extra copy?
+ index_scope = .{
+ .parent = &then_scope.base,
+ .gen_zir = &then_scope,
+ .name = index_name,
+ .ptr = index_ptr,
+ };
+ break :blk &index_scope.base;
+ };
+
+ const then_result = try expr(mod, then_sub_scope, branch_rl, for_node.body);
+ if (!then_result.tag.isNoReturn()) {
+ _ = try addZIRInst(mod, then_sub_scope, then_src, zir.Inst.Break, .{
+ .block = cond_block,
+ .operand = then_result,
+ }, .{});
+ }
+ condbr.positionals.then_body = .{
+ .instructions = try then_scope.arena.dupe(*zir.Inst, then_scope.instructions.items),
+ };
+
+ // else branch
+ var else_scope: Scope.GenZIR = .{
+ .parent = &cond_scope.base,
+ .decl = cond_scope.decl,
+ .arena = cond_scope.arena,
+ .instructions = .{},
+ };
+ defer else_scope.instructions.deinit(mod.gpa);
+
+ if (for_node.@"else") |else_node| {
+ const else_src = tree.token_locs[else_node.body.lastToken()].start;
+ const else_result = try expr(mod, &else_scope.base, branch_rl, else_node.body);
+ if (!else_result.tag.isNoReturn()) {
+ _ = try addZIRInst(mod, &else_scope.base, else_src, zir.Inst.Break, .{
+ .block = for_block,
+ .operand = else_result,
+ }, .{});
+ }
+ } else {
+ const else_src = tree.token_locs[for_node.lastToken()].start;
+ _ = try addZIRInst(mod, &else_scope.base, else_src, zir.Inst.BreakVoid, .{
+ .block = for_block,
+ }, .{});
+ }
+ condbr.positionals.else_body = .{
+ .instructions = try else_scope.arena.dupe(*zir.Inst, else_scope.instructions.items),
+ };
+ return &for_block.base;
+}
+
fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst {
const tree = scope.tree();
const src = tree.token_locs[cfe.ltoken].start;
diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig
index cb12211206..82c06d8003 100644
--- a/src-self-hosted/codegen.zig
+++ b/src-self-hosted/codegen.zig
@@ -273,8 +273,22 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
/// across each runtime branch upon joining.
branch_stack: *std.ArrayList(Branch),
+ /// The key must be canonical register.
+ registers: std.AutoHashMapUnmanaged(Register, *ir.Inst) = .{},
+ free_registers: FreeRegInt = math.maxInt(FreeRegInt),
+ /// Maps offset to what is stored there.
+ stack: std.AutoHashMapUnmanaged(u32, StackAllocation) = .{},
+
+ /// Offset from the stack base, representing the end of the stack frame.
+ max_end_stack: u32 = 0,
+ /// Represents the current end stack offset. If there is no existing slot
+ /// to place a new stack allocation, it goes here, and then bumps `max_end_stack`.
+ next_stack_offset: u32 = 0,
+
const MCValue = union(enum) {
/// No runtime bits. `void` types, empty structs, u0, enums with 1 tag, etc.
+ /// TODO Look into deleting this tag and using `dead` instead, since every use
+ /// of MCValue.none should be instead looking at the type and noticing it is 0 bits.
none,
/// Control flow will not allow this value to be observed.
unreach,
@@ -346,71 +360,55 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
const Branch = struct {
inst_table: std.AutoHashMapUnmanaged(*ir.Inst, MCValue) = .{},
- /// The key must be canonical register.
- registers: std.AutoHashMapUnmanaged(Register, RegisterAllocation) = .{},
- free_registers: FreeRegInt = math.maxInt(FreeRegInt),
-
- /// Maps offset to what is stored there.
- stack: std.AutoHashMapUnmanaged(u32, StackAllocation) = .{},
- /// Offset from the stack base, representing the end of the stack frame.
- max_end_stack: u32 = 0,
- /// Represents the current end stack offset. If there is no existing slot
- /// to place a new stack allocation, it goes here, and then bumps `max_end_stack`.
- next_stack_offset: u32 = 0,
-
- fn markRegUsed(self: *Branch, reg: Register) void {
- if (FreeRegInt == u0) return;
- const index = reg.allocIndex() orelse return;
- const ShiftInt = math.Log2Int(FreeRegInt);
- const shift = @intCast(ShiftInt, index);
- self.free_registers &= ~(@as(FreeRegInt, 1) << shift);
- }
-
- fn markRegFree(self: *Branch, reg: Register) void {
- if (FreeRegInt == u0) return;
- const index = reg.allocIndex() orelse return;
- const ShiftInt = math.Log2Int(FreeRegInt);
- const shift = @intCast(ShiftInt, index);
- self.free_registers |= @as(FreeRegInt, 1) << shift;
- }
-
- /// Before calling, must ensureCapacity + 1 on branch.registers.
- /// Returns `null` if all registers are allocated.
- fn allocReg(self: *Branch, inst: *ir.Inst) ?Register {
- const free_index = @ctz(FreeRegInt, self.free_registers);
- if (free_index >= callee_preserved_regs.len) {
- return null;
- }
- self.free_registers &= ~(@as(FreeRegInt, 1) << free_index);
- const reg = callee_preserved_regs[free_index];
- self.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst });
- log.debug("alloc {} => {*}", .{reg, inst});
- return reg;
- }
-
- /// Does not track the register.
- fn findUnusedReg(self: *Branch) ?Register {
- const free_index = @ctz(FreeRegInt, self.free_registers);
- if (free_index >= callee_preserved_regs.len) {
- return null;
- }
- return callee_preserved_regs[free_index];
- }
fn deinit(self: *Branch, gpa: *Allocator) void {
self.inst_table.deinit(gpa);
- self.registers.deinit(gpa);
- self.stack.deinit(gpa);
self.* = undefined;
}
};
- const RegisterAllocation = struct {
- inst: *ir.Inst,
- };
+ fn markRegUsed(self: *Self, reg: Register) void {
+ if (FreeRegInt == u0) return;
+ const index = reg.allocIndex() orelse return;
+ const ShiftInt = math.Log2Int(FreeRegInt);
+ const shift = @intCast(ShiftInt, index);
+ self.free_registers &= ~(@as(FreeRegInt, 1) << shift);
+ }
+
+ fn markRegFree(self: *Self, reg: Register) void {
+ if (FreeRegInt == u0) return;
+ const index = reg.allocIndex() orelse return;
+ const ShiftInt = math.Log2Int(FreeRegInt);
+ const shift = @intCast(ShiftInt, index);
+ self.free_registers |= @as(FreeRegInt, 1) << shift;
+ }
+
+ /// Before calling, must ensureCapacity + 1 on self.registers.
+ /// Returns `null` if all registers are allocated.
+ fn allocReg(self: *Self, inst: *ir.Inst) ?Register {
+ const free_index = @ctz(FreeRegInt, self.free_registers);
+ if (free_index >= callee_preserved_regs.len) {
+ return null;
+ }
+ self.free_registers &= ~(@as(FreeRegInt, 1) << free_index);
+ const reg = callee_preserved_regs[free_index];
+ self.registers.putAssumeCapacityNoClobber(reg, inst);
+ log.debug("alloc {} => {*}", .{reg, inst});
+ return reg;
+ }
+
+ /// Does not track the register.
+ fn findUnusedReg(self: *Self) ?Register {
+ const free_index = @ctz(FreeRegInt, self.free_registers);
+ if (free_index >= callee_preserved_regs.len) {
+ return null;
+ }
+ return callee_preserved_regs[free_index];
+ }
const StackAllocation = struct {
inst: *ir.Inst,
+ /// TODO do we need size? should be determined by inst.ty.abiSize()
size: u32,
};
@@ -435,14 +433,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
branch_stack.items[0].deinit(bin_file.allocator);
branch_stack.deinit();
}
- const branch = try branch_stack.addOne();
- branch.* = .{};
+ try branch_stack.append(.{});
const src_data: struct {lbrace_src: usize, rbrace_src: usize, source: []const u8} = blk: {
if (module_fn.owner_decl.scope.cast(Module.Scope.File)) |scope_file| {
const tree = scope_file.contents.tree;
const fn_proto = tree.root_node.decls()[module_fn.owner_decl.src_index].castTag(.FnProto).?;
- const block = fn_proto.body().?.castTag(.Block).?;
+ const block = fn_proto.getBodyNode().?.castTag(.Block).?;
const lbrace_src = tree.token_locs[block.lbrace].start;
const rbrace_src = tree.token_locs[block.rbrace].start;
break :blk .{ .lbrace_src = lbrace_src, .rbrace_src = rbrace_src, .source = tree.source };
@@ -476,6 +473,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.rbrace_src = src_data.rbrace_src,
.source = src_data.source,
};
+ defer function.registers.deinit(bin_file.allocator);
+ defer function.stack.deinit(bin_file.allocator);
defer function.exitlude_jump_relocs.deinit(bin_file.allocator);
var call_info = function.resolveCallingConventionValues(src, fn_type) catch |err| switch (err) {
@@ -487,7 +486,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
function.args = call_info.args;
function.ret_mcv = call_info.return_value;
function.stack_align = call_info.stack_align;
- branch.max_end_stack = call_info.stack_byte_count;
+ function.max_end_stack = call_info.stack_byte_count;
function.gen() catch |err| switch (err) {
error.CodegenFail => return Result{ .fail = function.err_msg.? },
@@ -523,7 +522,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
try self.dbgSetPrologueEnd();
try self.genBody(self.mod_fn.analysis.success);
- const stack_end = self.branch_stack.items[0].max_end_stack;
+ const stack_end = self.max_end_stack;
if (stack_end > math.maxInt(i32))
return self.fail(self.src, "too much stack used in call parameters", .{});
const aligned_stack_end = mem.alignForward(stack_end, self.stack_align);
@@ -580,13 +579,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
fn genBody(self: *Self, body: ir.Body) InnerError!void {
- const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
- const inst_table = &branch.inst_table;
for (body.instructions) |inst| {
+ try self.ensureProcessDeathCapacity(@popCount(@TypeOf(inst.deaths), inst.deaths));
+
const mcv = try self.genFuncInst(inst);
- log.debug("{*} => {}", .{inst, mcv});
- // TODO don't put void or dead things in here
- try inst_table.putNoClobber(self.gpa, inst, mcv);
+ if (!inst.isUnused()) {
+ log.debug("{*} => {}", .{inst, mcv});
+ const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
+ try branch.inst_table.putNoClobber(self.gpa, inst, mcv);
+ }
var i: ir.Inst.DeathsBitIndex = 0;
while (inst.getOperand(i)) |operand| : (i += 1) {
@@ -628,21 +629,28 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
self.dbg_line.appendAssumeCapacity(DW.LNS_copy);
}
+ /// Asserts there is already capacity to insert into top branch inst_table.
fn processDeath(self: *Self, inst: *ir.Inst) void {
+ if (inst.tag == .constant) return; // Constants are immortal.
+ // When editing this function, note that the logic must synchronize with `reuseOperand`.
+ const prev_value = self.getResolvedInstValue(inst);
const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
- const entry = branch.inst_table.getEntry(inst) orelse return;
- const prev_value = entry.value;
- entry.value = .dead;
+ branch.inst_table.putAssumeCapacity(inst, .dead);
switch (prev_value) {
.register => |reg| {
const canon_reg = toCanonicalReg(reg);
- _ = branch.registers.remove(canon_reg);
- branch.markRegFree(canon_reg);
+ _ = self.registers.remove(canon_reg);
+ self.markRegFree(canon_reg);
},
else => {}, // TODO process stack allocation death
}
}
+ fn ensureProcessDeathCapacity(self: *Self, additional_count: usize) !void {
+ const table = &self.branch_stack.items[self.branch_stack.items.len - 1].inst_table;
+ try table.ensureCapacity(self.gpa, table.items().len + additional_count);
+ }
+
/// Adds a Type to the .debug_info at the current position. The bytes will be populated later,
/// after codegen for this symbol is done.
fn addDbgInfoTypeReloc(self: *Self, ty: Type) !void {
@@ -705,13 +713,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
fn allocMem(self: *Self, inst: *ir.Inst, abi_size: u32, abi_align: u32) !u32 {
if (abi_align > self.stack_align)
self.stack_align = abi_align;
- const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
// TODO find a free slot instead of always appending
- const offset = mem.alignForwardGeneric(u32, branch.next_stack_offset, abi_align);
- branch.next_stack_offset = offset + abi_size;
- if (branch.next_stack_offset > branch.max_end_stack)
- branch.max_end_stack = branch.next_stack_offset;
- try branch.stack.putNoClobber(self.gpa, offset, .{
+ const offset = mem.alignForwardGeneric(u32, self.next_stack_offset, abi_align);
+ self.next_stack_offset = offset + abi_size;
+ if (self.next_stack_offset > self.max_end_stack)
+ self.max_end_stack = self.next_stack_offset;
+ try self.stack.putNoClobber(self.gpa, offset, .{
.inst = inst,
.size = abi_size,
});
@@ -737,15 +744,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
const abi_align = elem_ty.abiAlignment(self.target.*);
if (abi_align > self.stack_align)
self.stack_align = abi_align;
- const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
if (reg_ok) {
// Make sure the type can fit in a register before we try to allocate one.
const ptr_bits = arch.ptrBitWidth();
const ptr_bytes: u64 = @divExact(ptr_bits, 8);
if (abi_size <= ptr_bytes) {
- try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1);
- if (branch.allocReg(inst)) |reg| {
+ try self.registers.ensureCapacity(self.gpa, self.registers.items().len + 1);
+ if (self.allocReg(inst)) |reg| {
return MCValue{ .register = registerAlias(reg, abi_size) };
}
}
@@ -758,20 +764,18 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
/// allocated. A second call to `copyToTmpRegister` may return the same register.
/// This can have a side effect of spilling instructions to the stack to free up a register.
fn copyToTmpRegister(self: *Self, src: usize, mcv: MCValue) !Register {
- const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
-
- const reg = branch.findUnusedReg() orelse b: {
+ const reg = self.findUnusedReg() orelse b: {
// We'll take over the first register. Move the instruction that was previously
// there to a stack allocation.
const reg = callee_preserved_regs[0];
- const regs_entry = branch.registers.remove(reg).?;
- const spilled_inst = regs_entry.value.inst;
+ const regs_entry = self.registers.remove(reg).?;
+ const spilled_inst = regs_entry.value;
const stack_mcv = try self.allocRegOrMem(spilled_inst, false);
- const inst_entry = branch.inst_table.getEntry(spilled_inst).?;
- const reg_mcv = inst_entry.value;
+ const reg_mcv = self.getResolvedInstValue(spilled_inst);
assert(reg == toCanonicalReg(reg_mcv.register));
- inst_entry.value = stack_mcv;
+ const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
+ try branch.inst_table.put(self.gpa, spilled_inst, stack_mcv);
try self.genSetStack(src, spilled_inst.ty, stack_mcv.stack_offset, reg_mcv);
break :b reg;
@@ -784,22 +788,21 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
/// `reg_owner` is the instruction that gets associated with the register in the register table.
/// This can have a side effect of spilling instructions to the stack to free up a register.
fn copyToNewRegister(self: *Self, reg_owner: *ir.Inst, mcv: MCValue) !MCValue {
- const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
- try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1);
+ try self.registers.ensureCapacity(self.gpa, self.registers.items().len + 1);
- const reg = branch.allocReg(reg_owner) orelse b: {
+ const reg = self.allocReg(reg_owner) orelse b: {
// We'll take over the first register. Move the instruction that was previously
// there to a stack allocation.
const reg = callee_preserved_regs[0];
- const regs_entry = branch.registers.getEntry(reg).?;
- const spilled_inst = regs_entry.value.inst;
- regs_entry.value = .{ .inst = reg_owner };
+ const regs_entry = self.registers.getEntry(reg).?;
+ const spilled_inst = regs_entry.value;
+ regs_entry.value = reg_owner;
const stack_mcv = try self.allocRegOrMem(spilled_inst, false);
- const inst_entry = branch.inst_table.getEntry(spilled_inst).?;
- const reg_mcv = inst_entry.value;
+ const reg_mcv = self.getResolvedInstValue(spilled_inst);
assert(reg == toCanonicalReg(reg_mcv.register));
- inst_entry.value = stack_mcv;
+ const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
+ try branch.inst_table.put(self.gpa, spilled_inst, stack_mcv);
try self.genSetStack(reg_owner.src, spilled_inst.ty, stack_mcv.stack_offset, reg_mcv);
break :b reg;
@@ -826,6 +829,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// No side effects, so if it's unreferenced, do nothing.
if (inst.base.isUnused())
return MCValue.dead;
+
+ const operand = try self.resolveInst(inst.operand);
+ const info_a = inst.operand.ty.intInfo(self.target.*);
+ const info_b = inst.base.ty.intInfo(self.target.*);
+ if (info_a.signed != info_b.signed)
+ return self.fail(inst.base.src, "TODO gen intcast sign safety in semantic analysis", .{});
+
+ if (info_a.bits == info_b.bits)
+ return operand;
+
switch (arch) {
else => return self.fail(inst.base.src, "TODO implement intCast for {}", .{self.target.cpu.arch}),
}
@@ -934,9 +947,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.register => |reg| {
// If it's in the registers table, need to associate the register with the
// new instruction.
- const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
- if (branch.registers.getEntry(toCanonicalReg(reg))) |entry| {
- entry.value = .{ .inst = inst };
+ if (self.registers.getEntry(toCanonicalReg(reg))) |entry| {
+ entry.value = inst;
}
log.debug("reusing {} => {*}", .{reg, inst});
},
@@ -950,6 +962,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// Prevent the operand deaths processing code from deallocating it.
inst.clearOperandDeath(op_index);
+ // That makes us responsible for doing the rest of the stuff that processDeath would have done.
+ const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
+ branch.inst_table.putAssumeCapacity(inst.getOperand(op_index).?, .dead);
+
return true;
}
@@ -1231,8 +1247,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
if (inst.base.isUnused())
return MCValue.dead;
- const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
- try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1);
+ try self.registers.ensureCapacity(self.gpa, self.registers.items().len + 1);
const result = self.args[self.arg_index];
self.arg_index += 1;
@@ -1240,8 +1255,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
const name_with_null = inst.name[0..mem.lenZ(inst.name) + 1];
switch (result) {
.register => |reg| {
- branch.registers.putAssumeCapacityNoClobber(toCanonicalReg(reg), .{ .inst = &inst.base });
- branch.markRegUsed(reg);
+ self.registers.putAssumeCapacityNoClobber(toCanonicalReg(reg), &inst.base);
+ self.markRegUsed(reg);
try self.dbg_info.ensureCapacity(self.dbg_info.items.len + 8 + name_with_null.len);
self.dbg_info.appendAssumeCapacity(link.File.Elf.abbrev_parameter);
@@ -1536,18 +1551,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
fn genDbgStmt(self: *Self, inst: *ir.Inst.NoOp) !MCValue {
try self.dbgAdvancePCAndLine(inst.base.src);
- return MCValue.none;
+ assert(inst.base.isUnused());
+ return MCValue.dead;
}
fn genCondBr(self: *Self, inst: *ir.Inst.CondBr) !MCValue {
- // TODO Rework this so that the arch-independent logic isn't buried and duplicated.
- switch (arch) {
- .x86_64 => {
+ const cond = try self.resolveInst(inst.condition);
+
+ const reloc: Reloc = switch (arch) {
+ .i386, .x86_64 => reloc: {
try self.code.ensureCapacity(self.code.items.len + 6);
- const cond = try self.resolveInst(inst.condition);
- switch (cond) {
- .compare_flags_signed => |cmp_op| {
+ const opcode: u8 = switch (cond) {
+ .compare_flags_signed => |cmp_op| blk: {
// Here we map to the opposite opcode because the jump is to the false branch.
const opcode: u8 = switch (cmp_op) {
.gte => 0x8c,
@@ -1557,9 +1573,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.lte => 0x8f,
.eq => 0x85,
};
- return self.genX86CondBr(inst, opcode);
+ break :blk opcode;
},
- .compare_flags_unsigned => |cmp_op| {
+ .compare_flags_unsigned => |cmp_op| blk: {
// Here we map to the opposite opcode because the jump is to the false branch.
const opcode: u8 = switch (cmp_op) {
.gte => 0x82,
@@ -1569,9 +1585,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.lte => 0x87,
.eq => 0x85,
};
- return self.genX86CondBr(inst, opcode);
+ break :blk opcode;
},
- .register => |reg| {
+ .register => |reg| blk: {
// test reg, 1
// TODO detect al, ax, eax
try self.code.ensureCapacity(self.code.items.len + 4);
@@ -1583,23 +1599,128 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
@as(u8, 0xC0) | (0 << 3) | @truncate(u3, reg.id()),
0x01,
});
- return self.genX86CondBr(inst, 0x84);
+ break :blk 0x84;
},
else => return self.fail(inst.base.src, "TODO implement condbr {} when condition is {}", .{ self.target.cpu.arch, @tagName(cond) }),
- }
+ };
+ self.code.appendSliceAssumeCapacity(&[_]u8{ 0x0f, opcode });
+ const reloc = Reloc{ .rel32 = self.code.items.len };
+ self.code.items.len += 4;
+ break :reloc reloc;
},
- else => return self.fail(inst.base.src, "TODO implement condbr for {}", .{self.target.cpu.arch}),
- }
- }
+ else => return self.fail(inst.base.src, "TODO implement condbr {}", .{ self.target.cpu.arch }),
+ };
- fn genX86CondBr(self: *Self, inst: *ir.Inst.CondBr, opcode: u8) !MCValue {
- // TODO deal with liveness / deaths condbr's then_entry_deaths and else_entry_deaths
- self.code.appendSliceAssumeCapacity(&[_]u8{ 0x0f, opcode });
- const reloc = Reloc{ .rel32 = self.code.items.len };
- self.code.items.len += 4;
+ // Capture the state of register and stack allocation state so that we can revert to it.
+ const parent_next_stack_offset = self.next_stack_offset;
+ const parent_free_registers = self.free_registers;
+ var parent_stack = try self.stack.clone(self.gpa);
+ defer parent_stack.deinit(self.gpa);
+ var parent_registers = try self.registers.clone(self.gpa);
+ defer parent_registers.deinit(self.gpa);
+
+ try self.branch_stack.append(.{});
+
+ const then_deaths = inst.thenDeaths();
+ try self.ensureProcessDeathCapacity(then_deaths.len);
+ for (then_deaths) |operand| {
+ self.processDeath(operand);
+ }
try self.genBody(inst.then_body);
+
+ // Revert to the previous register and stack allocation state.
+
+ var saved_then_branch = self.branch_stack.pop();
+ defer saved_then_branch.deinit(self.gpa);
+
+ self.registers.deinit(self.gpa);
+ self.registers = parent_registers;
+ parent_registers = .{};
+
+ self.stack.deinit(self.gpa);
+ self.stack = parent_stack;
+ parent_stack = .{};
+
+ self.next_stack_offset = parent_next_stack_offset;
+ self.free_registers = parent_free_registers;
+
try self.performReloc(inst.base.src, reloc);
+ const else_branch = self.branch_stack.addOneAssumeCapacity();
+ else_branch.* = .{};
+
+ const else_deaths = inst.elseDeaths();
+ try self.ensureProcessDeathCapacity(else_deaths.len);
+ for (else_deaths) |operand| {
+ self.processDeath(operand);
+ }
try self.genBody(inst.else_body);
+
+ // At this point, each branch will possibly have conflicting values for where
+ // each instruction is stored. They agree, however, on which instructions are alive/dead.
+ // We use the first ("then") branch as canonical, and here emit
+ // instructions into the second ("else") branch to make it conform.
+ // We continue respect the data structure semantic guarantees of the else_branch so
+ // that we can use all the code emitting abstractions. This is why at the bottom we
+ // assert that parent_branch.free_registers equals the saved_then_branch.free_registers
+ // rather than assigning it.
+ const parent_branch = &self.branch_stack.items[self.branch_stack.items.len - 2];
+ try parent_branch.inst_table.ensureCapacity(self.gpa, parent_branch.inst_table.items().len +
+ else_branch.inst_table.items().len);
+ for (else_branch.inst_table.items()) |else_entry| {
+ const canon_mcv = if (saved_then_branch.inst_table.remove(else_entry.key)) |then_entry| blk: {
+ // The instruction's MCValue is overridden in both branches.
+ parent_branch.inst_table.putAssumeCapacity(else_entry.key, then_entry.value);
+ if (else_entry.value == .dead) {
+ assert(then_entry.value == .dead);
+ continue;
+ }
+ break :blk then_entry.value;
+ } else blk: {
+ if (else_entry.value == .dead)
+ continue;
+ // The instruction is only overridden in the else branch.
+ var i: usize = self.branch_stack.items.len - 2;
+ while (true) {
+ i -= 1; // If this overflows, the question is: why wasn't the instruction marked dead?
+ if (self.branch_stack.items[i].inst_table.get(else_entry.key)) |mcv| {
+ assert(mcv != .dead);
+ break :blk mcv;
+ }
+ }
+ };
+ log.debug("consolidating else_entry {*} {}=>{}", .{else_entry.key, else_entry.value, canon_mcv});
+ // TODO make sure the destination stack offset / register does not already have something
+ // going on there.
+ try self.setRegOrMem(inst.base.src, else_entry.key.ty, canon_mcv, else_entry.value);
+ // TODO track the new register / stack allocation
+ }
+ try parent_branch.inst_table.ensureCapacity(self.gpa, parent_branch.inst_table.items().len +
+ saved_then_branch.inst_table.items().len);
+ for (saved_then_branch.inst_table.items()) |then_entry| {
+ // We already deleted the items from this table that matched the else_branch.
+ // So these are all instructions that are only overridden in the then branch.
+ parent_branch.inst_table.putAssumeCapacity(then_entry.key, then_entry.value);
+ if (then_entry.value == .dead)
+ continue;
+ const parent_mcv = blk: {
+ var i: usize = self.branch_stack.items.len - 2;
+ while (true) {
+ i -= 1;
+ if (self.branch_stack.items[i].inst_table.get(then_entry.key)) |mcv| {
+ assert(mcv != .dead);
+ break :blk mcv;
+ }
+ }
+ };
+ log.debug("consolidating then_entry {*} {}=>{}", .{then_entry.key, parent_mcv, then_entry.value});
+ // TODO make sure the destination stack offset / register does not already have something
+ // going on there.
+ try self.setRegOrMem(inst.base.src, then_entry.key.ty, parent_mcv, then_entry.value);
+ // TODO track the new register / stack allocation
+ }
+
+ self.branch_stack.pop().deinit(self.gpa);
+
return MCValue.unreach;
}
@@ -1673,11 +1794,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
switch (reloc) {
.rel32 => |pos| {
const amt = self.code.items.len - (pos + 4);
- // If it wouldn't jump at all, elide it.
- if (amt == 0) {
- self.code.items.len -= 5;
- return;
- }
+ // Here it would be tempting to implement testing for amt == 0 and then elide the
+ // jump. However, that will cause a problem because other jumps may assume that they
+ // can jump to this code. Or maybe I didn't understand something when I was debugging.
+ // It could be worth another look. Anyway, that's why that isn't done here. Probably the
+ // best place to elide jumps will be in semantic analysis, by inlining blocks that only
+ // only have 1 break instruction.
const s32_amt = math.cast(i32, amt) catch
return self.fail(src, "unable to perform relocation: jump too far", .{});
mem.writeIntLittle(i32, self.code.items[pos..][0..4], s32_amt);
@@ -1927,15 +2049,29 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x);
},
8 => {
- return self.fail(src, "TODO implement set abi_size=8 stack variable with immediate", .{});
+ // We have a positive stack offset value but we want a twos complement negative
+ // offset from rbp, which is at the top of the stack frame.
+ const negative_offset = @intCast(i8, -@intCast(i32, adj_off));
+ const twos_comp = @bitCast(u8, negative_offset);
+
+ // 64 bit write to memory would take two mov's anyways so we
+ // insted just use two 32 bit writes to avoid register allocation
+ try self.code.ensureCapacity(self.code.items.len + 14);
+ var buf: [8]u8 = undefined;
+ mem.writeIntLittle(u64, &buf, x_big);
+
+ // mov DWORD PTR [rbp+offset+4], immediate
+ self.code.appendSliceAssumeCapacity(&[_]u8{ 0xc7, 0x45, twos_comp + 4});
+ self.code.appendSliceAssumeCapacity(buf[4..8]);
+
+ // mov DWORD PTR [rbp+offset], immediate
+ self.code.appendSliceAssumeCapacity(&[_]u8{ 0xc7, 0x45, twos_comp });
+ self.code.appendSliceAssumeCapacity(buf[0..4]);
},
else => {
return self.fail(src, "TODO implement set abi_size=large stack variable with immediate", .{});
},
}
- if (x_big <= math.maxInt(u32)) {} else {
- return self.fail(src, "TODO implement set stack variable with large immediate", .{});
- }
},
.embedded_in_code => |code_offset| {
return self.fail(src, "TODO implement set stack variable from embedded_in_code", .{});
@@ -2282,8 +2418,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
fn resolveInst(self: *Self, inst: *ir.Inst) !MCValue {
+ // If the type has no codegen bits, no need to store it.
+ if (!inst.ty.hasCodeGenBits())
+ return MCValue.none;
+
// Constants have static lifetimes, so they are always memoized in the outer most table.
- if (inst.cast(ir.Inst.Constant)) |const_inst| {
+ if (inst.castTag(.constant)) |const_inst| {
const branch = &self.branch_stack.items[0];
const gop = try branch.inst_table.getOrPut(self.gpa, inst);
if (!gop.found_existing) {
@@ -2292,6 +2432,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return gop.entry.value;
}
+ return self.getResolvedInstValue(inst);
+ }
+
+ fn getResolvedInstValue(self: *Self, inst: *ir.Inst) MCValue {
// Treat each stack item as a "layer" on top of the previous one.
var i: usize = self.branch_stack.items.len;
while (true) {
diff --git a/src-self-hosted/link/Elf.zig b/src-self-hosted/link/Elf.zig
index 1d18344cbb..8bf28557b4 100644
--- a/src-self-hosted/link/Elf.zig
+++ b/src-self-hosted/link/Elf.zig
@@ -17,6 +17,7 @@ const Type = @import("../type.zig").Type;
const link = @import("../link.zig");
const File = link.File;
const Elf = @This();
+const build_options = @import("build_options");
const default_entry_addr = 0x8000000;
@@ -1640,9 +1641,15 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
else => false,
};
if (is_fn) {
- //if (mem.eql(u8, mem.spanZ(decl.name), "add")) {
- // typed_value.val.cast(Value.Payload.Function).?.func.dump(module.*);
- //}
+ const zir_dumps = if (std.builtin.is_test) &[0][]const u8{} else build_options.zir_dumps;
+ if (zir_dumps.len != 0) {
+ for (zir_dumps) |fn_name| {
+ if (mem.eql(u8, mem.spanZ(decl.name), fn_name)) {
+ std.debug.print("\n{}\n", .{decl.name});
+ typed_value.val.cast(Value.Payload.Function).?.func.dump(module.*);
+ }
+ }
+ }
// For functions we need to add a prologue to the debug line program.
try dbg_line_buffer.ensureCapacity(26);
@@ -1654,7 +1661,7 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
// TODO Look into improving the performance here by adding a token-index-to-line
// lookup table. Currently this involves scanning over the source code for newlines.
const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?;
- const block = fn_proto.body().?.castTag(.Block).?;
+ const block = fn_proto.getBodyNode().?.castTag(.Block).?;
const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start);
break :blk @intCast(u28, line_delta);
} else if (decl.scope.cast(Module.Scope.ZIRModule)) |zir_module| {
@@ -2153,7 +2160,7 @@ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Dec
// TODO Look into improving the performance here by adding a token-index-to-line
// lookup table. Currently this involves scanning over the source code for newlines.
const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?;
- const block = fn_proto.body().?.castTag(.Block).?;
+ const block = fn_proto.getBodyNode().?.castTag(.Block).?;
const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start);
const casted_line_off = @intCast(u28, line_delta);
diff --git a/src-self-hosted/link/MachO.zig b/src-self-hosted/link/MachO.zig
index a65366261a..047e62f950 100644
--- a/src-self-hosted/link/MachO.zig
+++ b/src-self-hosted/link/MachO.zig
@@ -16,8 +16,6 @@ const Module = @import("../Module.zig");
const link = @import("../link.zig");
const File = link.File;
-const is_darwin = std.Target.current.os.tag.isDarwin();
-
pub const base_tag: File.Tag = File.Tag.macho;
base: File,
@@ -30,24 +28,26 @@ command_file_offset: ?u64 = null,
/// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write.
/// Same order as in the file.
segments: std.ArrayListUnmanaged(macho.segment_command_64) = std.ArrayListUnmanaged(macho.segment_command_64){},
+/// Section (headers) *always* follow segment (load commands) directly!
sections: std.ArrayListUnmanaged(macho.section_64) = std.ArrayListUnmanaged(macho.section_64){},
-segment_table_offset: ?u64 = null,
+
+/// Offset (index) into __TEXT segment load command.
+text_segment_offset: ?u64 = null,
+/// Offset (index) into __LINKEDIT segment load command.
+linkedit_segment_offset: ?u664 = null,
/// Entry point load command
entry_point_cmd: ?macho.entry_point_command = null,
entry_addr: ?u64 = null,
-/// Default VM start address set at 4GB
+/// The first 4GB of process' memory is reserved for the null (__PAGEZERO) segment.
+/// This is also the start address for our binary.
vm_start_address: u64 = 0x100000000,
seg_table_dirty: bool = false,
error_flags: File.ErrorFlags = File.ErrorFlags{},
-/// TODO ultimately this will be propagated down from main() and set (in this form or another)
-/// when user links against system lib.
-link_against_system: bool = false,
-
/// `alloc_num / alloc_den` is the factor of padding when allocating.
const alloc_num = 4;
const alloc_den = 3;
@@ -138,8 +138,8 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Mach
.vmsize = self.vm_start_address,
.fileoff = 0,
.filesize = 0,
- .maxprot = 0,
- .initprot = 0,
+ .maxprot = macho.VM_PROT_NONE,
+ .initprot = macho.VM_PROT_NONE,
.nsects = 0,
.flags = 0,
};
@@ -225,67 +225,61 @@ pub fn flush(self: *MachO, module: *Module) !void {
switch (self.base.options.output_mode) {
.Exe => {
- if (self.link_against_system) {
- if (is_darwin) {
- {
- // Specify path to dynamic linker dyld
- const cmdsize = commandSize(@intCast(u32, @sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH)));
- const load_dylinker = [1]macho.dylinker_command{
- .{
- .cmd = macho.LC_LOAD_DYLINKER,
- .cmdsize = cmdsize,
- .name = @sizeOf(macho.dylinker_command),
- },
- };
- try self.commands.append(self.base.allocator, .{
- .cmd = macho.LC_LOAD_DYLINKER,
- .cmdsize = cmdsize,
- });
-
- try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylinker[0..1]), self.command_file_offset.?);
-
- const file_offset = self.command_file_offset.? + @sizeOf(macho.dylinker_command);
- try self.addPadding(cmdsize - @sizeOf(macho.dylinker_command), file_offset);
-
- try self.base.file.?.pwriteAll(mem.spanZ(DEFAULT_DYLD_PATH), file_offset);
- self.command_file_offset.? += cmdsize;
- }
-
- {
- // Link against libSystem
- const cmdsize = commandSize(@intCast(u32, @sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH)));
- // According to Apple's manual, we should obtain current libSystem version using libc call
- // NSVersionOfRunTimeLibrary.
- const version = std.c.NSVersionOfRunTimeLibrary(LIB_SYSTEM_NAME);
- const dylib = .{
- .name = @sizeOf(macho.dylib_command),
- .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files
- .current_version = version,
- .compatibility_version = 0x10000, // not sure why this either; value from reverse engineering
- };
- const load_dylib = [1]macho.dylib_command{
- .{
- .cmd = macho.LC_LOAD_DYLIB,
- .cmdsize = cmdsize,
- .dylib = dylib,
- },
- };
- try self.commands.append(self.base.allocator, .{
- .cmd = macho.LC_LOAD_DYLIB,
- .cmdsize = cmdsize,
- });
-
- try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylib[0..1]), self.command_file_offset.?);
-
- const file_offset = self.command_file_offset.? + @sizeOf(macho.dylib_command);
- try self.addPadding(cmdsize - @sizeOf(macho.dylib_command), file_offset);
-
- try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), file_offset);
- self.command_file_offset.? += cmdsize;
- }
- } else {
- @panic("linking against libSystem on non-native target is unsupported");
- }
+ {
+ // Specify path to dynamic linker dyld
+ const cmdsize = commandSize(@sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH));
+ const load_dylinker = [1]macho.dylinker_command{
+ .{
+ .cmd = macho.LC_LOAD_DYLINKER,
+ .cmdsize = cmdsize,
+ .name = @sizeOf(macho.dylinker_command),
+ },
+ };
+ try self.commands.append(self.base.allocator, .{
+ .cmd = macho.LC_LOAD_DYLINKER,
+ .cmdsize = cmdsize,
+ });
+
+ try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylinker[0..1]), self.command_file_offset.?);
+
+ const file_offset = self.command_file_offset.? + @sizeOf(macho.dylinker_command);
+ try self.addPadding(cmdsize - @sizeOf(macho.dylinker_command), file_offset);
+
+ try self.base.file.?.pwriteAll(mem.spanZ(DEFAULT_DYLD_PATH), file_offset);
+ self.command_file_offset.? += cmdsize;
+ }
+
+ {
+ // Link against libSystem
+ const cmdsize = commandSize(@sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH));
+ // TODO Find a way to work out runtime version from the OS version triple stored in std.Target.
+ // In the meantime, we're gonna hardcode to the minimum compatibility version of 1.0.0.
+ const min_version = 0x10000;
+ const dylib = .{
+ .name = @sizeOf(macho.dylib_command),
+ .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files
+ .current_version = min_version,
+ .compatibility_version = min_version,
+ };
+ const load_dylib = [1]macho.dylib_command{
+ .{
+ .cmd = macho.LC_LOAD_DYLIB,
+ .cmdsize = cmdsize,
+ .dylib = dylib,
+ },
+ };
+ try self.commands.append(self.base.allocator, .{
+ .cmd = macho.LC_LOAD_DYLIB,
+ .cmdsize = cmdsize,
+ });
+
+ try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylib[0..1]), self.command_file_offset.?);
+
+ const file_offset = self.command_file_offset.? + @sizeOf(macho.dylib_command);
+ try self.addPadding(cmdsize - @sizeOf(macho.dylib_command), file_offset);
+
+ try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), file_offset);
+ self.command_file_offset.? += cmdsize;
}
},
.Obj => return error.TODOImplementWritingObjFiles,
@@ -327,20 +321,53 @@ pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 {
@panic("TODO implement getDeclVAddr for MachO");
}
-pub fn populateMissingMetadata(self: *MachO) !void {}
+pub fn populateMissingMetadata(self: *MachO) !void {
+ if (self.text_segment_offset == null) {
+ self.text_segment_offset = @intCast(u64, self.segments.items.len);
+ const file_size = alignSize(u64, self.base.options.program_code_size_hint, 0x1000);
+ log.debug("vmsize/filesize = {}", .{file_size});
+ const file_offset = 0;
+ const vm_address = self.vm_start_address; // the end of __PAGEZERO segment in VM
+ const protection = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE;
+ const cmdsize = commandSize(@sizeOf(macho.segment_command_64));
+ const text_segment = .{
+ .cmd = macho.LC_SEGMENT_64,
+ .cmdsize = cmdsize,
+ .segname = makeString("__TEXT"),
+ .vmaddr = vm_address,
+ .vmsize = file_size,
+ .fileoff = 0, // __TEXT segment *always* starts at 0 file offset
+ .filesize = 0, //file_size,
+ .maxprot = protection,
+ .initprot = protection,
+ .nsects = 0,
+ .flags = 0,
+ };
+ try self.commands.append(self.base.allocator, .{
+ .cmd = macho.LC_SEGMENT_64,
+ .cmdsize = cmdsize,
+ });
+ try self.segments.append(self.base.allocator, text_segment);
+ }
+}
fn makeString(comptime bytes: []const u8) [16]u8 {
- var buf: [16]u8 = undefined;
+ var buf = [_]u8{0} ** 16;
if (bytes.len > buf.len) @compileError("MachO segment/section name too long");
mem.copy(u8, buf[0..], bytes);
return buf;
}
-fn commandSize(min_size: u32) u32 {
- if (min_size % @sizeOf(u64) == 0) return min_size;
+fn alignSize(comptime Int: type, min_size: anytype, alignment: Int) Int {
+ const size = @intCast(Int, min_size);
+ if (size % alignment == 0) return size;
+
+ const div = size / alignment;
+ return (div + 1) * alignment;
+}
- const div = min_size / @sizeOf(u64);
- return (div + 1) * @sizeOf(u64);
+fn commandSize(min_size: anytype) u32 {
+ return alignSize(u32, min_size, @sizeOf(u64));
}
fn addPadding(self: *MachO, size: u32, file_offset: u64) !void {
diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig
index 7a67e197cc..ac25cd2eb8 100644
--- a/src-self-hosted/main.zig
+++ b/src-self-hosted/main.zig
@@ -911,7 +911,7 @@ pub const info_zen =
\\ * Reduce the amount one must remember.
\\ * Minimize energy spent on coding style.
\\ * Resource deallocation must succeed.
- \\ * Together we serve end users.
+ \\ * Together we serve the users.
\\
\\
;
diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig
index 6628f5b99e..b333d24c51 100644
--- a/src-self-hosted/translate_c.zig
+++ b/src-self-hosted/translate_c.zig
@@ -168,7 +168,7 @@ const Scope = struct {
fn localContains(scope: *Block, name: []const u8) bool {
for (scope.variables.items) |p| {
- if (mem.eql(u8, p.name, name))
+ if (mem.eql(u8, p.alias, name))
return true;
}
return false;
@@ -675,7 +675,7 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void {
}
const body_node = try block_scope.complete(rp.c);
- proto_node.setTrailer("body_node", body_node);
+ proto_node.setBodyNode(body_node);
return addTopLevelDecl(c, fn_name, &proto_node.base);
}
@@ -4493,7 +4493,7 @@ fn transCreateNodeMacroFn(c: *Context, name: []const u8, ref: *ast.Node, proto_a
const block_lbrace = try appendToken(c, .LBrace, "{");
const return_kw = try appendToken(c, .Keyword_return, "return");
- const unwrap_expr = try transCreateNodeUnwrapNull(c, ref.cast(ast.Node.VarDecl).?.getTrailer("init_node").?);
+ const unwrap_expr = try transCreateNodeUnwrapNull(c, ref.cast(ast.Node.VarDecl).?.getInitNode().?);
const call_expr = try c.createCall(unwrap_expr, fn_params.items.len);
const call_params = call_expr.params();
@@ -6361,7 +6361,7 @@ fn getContainer(c: *Context, node: *ast.Node) ?*ast.Node {
const ident = node.castTag(.Identifier).?;
if (c.global_scope.sym_table.get(tokenSlice(c, ident.token))) |value| {
if (value.cast(ast.Node.VarDecl)) |var_decl|
- return getContainer(c, var_decl.getTrailer("init_node").?);
+ return getContainer(c, var_decl.getInitNode().?);
}
},
@@ -6390,7 +6390,7 @@ fn getContainerTypeOf(c: *Context, ref: *ast.Node) ?*ast.Node {
if (ref.castTag(.Identifier)) |ident| {
if (c.global_scope.sym_table.get(tokenSlice(c, ident.token))) |value| {
if (value.cast(ast.Node.VarDecl)) |var_decl| {
- if (var_decl.getTrailer("type_node")) |ty|
+ if (var_decl.getTypeNode()) |ty|
return getContainer(c, ty);
}
}
@@ -6412,7 +6412,7 @@ fn getContainerTypeOf(c: *Context, ref: *ast.Node) ?*ast.Node {
}
fn getFnProto(c: *Context, ref: *ast.Node) ?*ast.Node.FnProto {
- const init = if (ref.cast(ast.Node.VarDecl)) |v| v.getTrailer("init_node").? else return null;
+ const init = if (ref.cast(ast.Node.VarDecl)) |v| v.getInitNode().? else return null;
if (getContainerTypeOf(c, init)) |ty_node| {
if (ty_node.castTag(.OptionalType)) |prefix| {
if (prefix.rhs.cast(ast.Node.FnProto)) |fn_proto| {
diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig
index eb8fa2acd7..13024f34de 100644
--- a/src-self-hosted/type.zig
+++ b/src-self-hosted/type.zig
@@ -771,8 +771,8 @@ pub const Type = extern union {
.array => self.elemType().hasCodeGenBits() and self.arrayLen() != 0,
.array_u8 => self.arrayLen() != 0,
.array_sentinel, .single_const_pointer, .single_mut_pointer, .many_const_pointer, .many_mut_pointer, .c_const_pointer, .c_mut_pointer, .const_slice, .mut_slice, .pointer => self.elemType().hasCodeGenBits(),
- .int_signed => self.cast(Payload.IntSigned).?.bits == 0,
- .int_unsigned => self.cast(Payload.IntUnsigned).?.bits == 0,
+ .int_signed => self.cast(Payload.IntSigned).?.bits != 0,
+ .int_unsigned => self.cast(Payload.IntUnsigned).?.bits != 0,
.error_union => {
const payload = self.cast(Payload.ErrorUnion).?;
@@ -2675,6 +2675,13 @@ pub const Type = extern union {
};
}
+ pub fn isIndexable(self: Type) bool {
+ const zig_tag = self.zigTypeTag();
+ // TODO tuples are indexable
+ return zig_tag == .Array or zig_tag == .Vector or self.isSlice() or
+ (self.isSinglePointer() and self.elemType().zigTypeTag() == .Array);
+ }
+
/// This enum does not directly correspond to `std.builtin.TypeId` because
/// it has extra enum tags in it, as a way of using less memory. For example,
/// even though Zig recognizes `*align(10) i32` and `*i32` both as Pointer types
diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig
index f2e36b59a4..6a39371ebe 100644
--- a/src-self-hosted/value.zig
+++ b/src-self-hosted/value.zig
@@ -65,6 +65,7 @@ pub const Value = extern union {
undef,
zero,
+ one,
void_value,
unreachable_value,
empty_array,
@@ -174,6 +175,7 @@ pub const Value = extern union {
.anyframe_type,
.undef,
.zero,
+ .one,
.void_value,
.unreachable_value,
.empty_array,
@@ -313,6 +315,7 @@ pub const Value = extern union {
.null_value => return out_stream.writeAll("null"),
.undef => return out_stream.writeAll("undefined"),
.zero => return out_stream.writeAll("0"),
+ .one => return out_stream.writeAll("1"),
.void_value => return out_stream.writeAll("{}"),
.unreachable_value => return out_stream.writeAll("unreachable"),
.bool_true => return out_stream.writeAll("true"),
@@ -447,6 +450,7 @@ pub const Value = extern union {
.undef,
.zero,
+ .one,
.void_value,
.unreachable_value,
.empty_array,
@@ -546,7 +550,9 @@ pub const Value = extern union {
.bool_false,
=> return BigIntMutable.init(&space.limbs, 0).toConst(),
- .bool_true => return BigIntMutable.init(&space.limbs, 1).toConst(),
+ .one,
+ .bool_true,
+ => return BigIntMutable.init(&space.limbs, 1).toConst(),
.int_u64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_u64).?.int).toConst(),
.int_i64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_i64).?.int).toConst(),
@@ -627,7 +633,9 @@ pub const Value = extern union {
.bool_false,
=> return 0,
- .bool_true => return 1,
+ .one,
+ .bool_true,
+ => return 1,
.int_u64 => return self.cast(Payload.Int_u64).?.int,
.int_i64 => return @intCast(u64, self.cast(Payload.Int_i64).?.int),
@@ -708,7 +716,9 @@ pub const Value = extern union {
.bool_false,
=> return 0,
- .bool_true => return 1,
+ .one,
+ .bool_true,
+ => return 1,
.int_u64 => return @intCast(i64, self.cast(Payload.Int_u64).?.int),
.int_i64 => return self.cast(Payload.Int_i64).?.int,
@@ -734,6 +744,7 @@ pub const Value = extern union {
.float_128 => @floatCast(T, self.cast(Payload.Float_128).?.val),
.zero => 0,
+ .one => 1,
.int_u64 => @intToFloat(T, self.cast(Payload.Int_u64).?.int),
.int_i64 => @intToFloat(T, self.cast(Payload.Int_i64).?.int),
@@ -814,7 +825,9 @@ pub const Value = extern union {
.bool_false,
=> return 0,
- .bool_true => return 1,
+ .one,
+ .bool_true,
+ => return 1,
.int_u64 => {
const x = self.cast(Payload.Int_u64).?.int;
@@ -900,7 +913,9 @@ pub const Value = extern union {
.bool_false,
=> return true,
- .bool_true => {
+ .one,
+ .bool_true,
+ => {
const info = ty.intInfo(target);
if (info.signed) {
return info.bits >= 2;
@@ -1064,7 +1079,9 @@ pub const Value = extern union {
.@"error",
=> unreachable,
- .zero => false,
+ .zero,
+ .one,
+ => false,
.float_16 => @rem(self.cast(Payload.Float_16).?.val, 1) != 0,
.float_32 => @rem(self.cast(Payload.Float_32).?.val, 1) != 0,
@@ -1140,7 +1157,9 @@ pub const Value = extern union {
.bool_false,
=> .eq,
- .bool_true => .gt,
+ .one,
+ .bool_true,
+ => .gt,
.int_u64 => std.math.order(lhs.cast(Payload.Int_u64).?.int, 0),
.int_i64 => std.math.order(lhs.cast(Payload.Int_i64).?.int, 0),
@@ -1257,6 +1276,7 @@ pub const Value = extern union {
.enum_literal_type,
.anyframe_type,
.zero,
+ .one,
.bool_true,
.bool_false,
.null_value,
@@ -1339,6 +1359,7 @@ pub const Value = extern union {
.enum_literal_type,
.anyframe_type,
.zero,
+ .one,
.bool_true,
.bool_false,
.null_value,
@@ -1438,6 +1459,7 @@ pub const Value = extern union {
.enum_literal_type,
.anyframe_type,
.zero,
+ .one,
.empty_array,
.bool_true,
.bool_false,
diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig
index 3557c88f4e..4e8967f8dc 100644
--- a/src-self-hosted/zir.zig
+++ b/src-self-hosted/zir.zig
@@ -137,6 +137,8 @@ pub const Inst = struct {
ensure_result_used,
/// Emits a compile error if an error is ignored.
ensure_result_non_error,
+ /// Emits a compile error if operand cannot be indexed.
+ ensure_indexable,
/// Create a `E!T` type.
error_union_type,
/// Create an error set.
@@ -251,6 +253,8 @@ pub const Inst = struct {
unwrap_err_safe,
/// Same as previous, but without safety checks. Used for orelse, if and while
unwrap_err_unsafe,
+ /// Gets the error code value of an error union
+ unwrap_err_code,
/// Takes a *E!T and raises a compiler error if T != void
ensure_err_payload_void,
/// Enum literal
@@ -278,6 +282,7 @@ pub const Inst = struct {
.alloc,
.ensure_result_used,
.ensure_result_non_error,
+ .ensure_indexable,
.bitcast_result_ptr,
.ref,
.bitcast_ref,
@@ -295,6 +300,7 @@ pub const Inst = struct {
.unwrap_optional_unsafe,
.unwrap_err_safe,
.unwrap_err_unsafe,
+ .unwrap_err_code,
.ensure_err_payload_void,
.anyframe_type,
.bitnot,
@@ -409,6 +415,7 @@ pub const Inst = struct {
.elemptr,
.ensure_result_used,
.ensure_result_non_error,
+ .ensure_indexable,
.@"export",
.floatcast,
.fieldptr,
@@ -450,6 +457,7 @@ pub const Inst = struct {
.unwrap_optional_unsafe,
.unwrap_err_safe,
.unwrap_err_unsafe,
+ .unwrap_err_code,
.ptr_type,
.ensure_err_payload_void,
.enum_literal,
@@ -954,6 +962,7 @@ pub const Module = struct {
pub const MetaData = struct {
deaths: ir.Inst.DeathsInt,
+ addr: usize,
};
pub const BodyMetaData = struct {
@@ -1152,6 +1161,12 @@ const Writer = struct {
try self.writeInstToStream(stream, inst);
if (self.module.metadata.get(inst)) |metadata| {
try stream.print(" ; deaths=0b{b}", .{metadata.deaths});
+ // This is conditionally compiled in because addresses mess up the tests due
+ // to Address Space Layout Randomization. It's super useful when debugging
+ // codegen.zig though.
+ if (!std.builtin.is_test) {
+ try stream.print(" 0x{x}", .{metadata.addr});
+ }
}
self.indent -= 2;
try stream.writeByte('\n');
@@ -2417,7 +2432,10 @@ const EmitZIR = struct {
.varptr => @panic("TODO"),
};
- try self.metadata.put(new_inst, .{ .deaths = inst.deaths });
+ try self.metadata.put(new_inst, .{
+ .deaths = inst.deaths,
+ .addr = @ptrToInt(inst),
+ });
try instructions.append(new_inst);
try inst_table.put(inst, new_inst);
}
diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig
index 7106bda090..2ac14f8bb4 100644
--- a/src-self-hosted/zir_sema.zig
+++ b/src-self-hosted/zir_sema.zig
@@ -48,6 +48,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
.declval_in_module => return analyzeInstDeclValInModule(mod, scope, old_inst.castTag(.declval_in_module).?),
.ensure_result_used => return analyzeInstEnsureResultUsed(mod, scope, old_inst.castTag(.ensure_result_used).?),
.ensure_result_non_error => return analyzeInstEnsureResultNonError(mod, scope, old_inst.castTag(.ensure_result_non_error).?),
+ .ensure_indexable => return analyzeInstEnsureIndexable(mod, scope, old_inst.castTag(.ensure_indexable).?),
.ref => return analyzeInstRef(mod, scope, old_inst.castTag(.ref).?),
.ret_ptr => return analyzeInstRetPtr(mod, scope, old_inst.castTag(.ret_ptr).?),
.ret_type => return analyzeInstRetType(mod, scope, old_inst.castTag(.ret_type).?),
@@ -111,7 +112,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
.condbr => return analyzeInstCondBr(mod, scope, old_inst.castTag(.condbr).?),
.isnull => return analyzeInstIsNonNull(mod, scope, old_inst.castTag(.isnull).?, true),
.isnonnull => return analyzeInstIsNonNull(mod, scope, old_inst.castTag(.isnonnull).?, false),
- .iserr => return analyzeInstIsErr(mod, scope, old_inst.castTag(.iserr).?, true),
+ .iserr => return analyzeInstIsErr(mod, scope, old_inst.castTag(.iserr).?),
.boolnot => return analyzeInstBoolNot(mod, scope, old_inst.castTag(.boolnot).?),
.typeof => return analyzeInstTypeOf(mod, scope, old_inst.castTag(.typeof).?),
.optional_type => return analyzeInstOptionalType(mod, scope, old_inst.castTag(.optional_type).?),
@@ -119,6 +120,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
.unwrap_optional_unsafe => return analyzeInstUnwrapOptional(mod, scope, old_inst.castTag(.unwrap_optional_unsafe).?, false),
.unwrap_err_safe => return analyzeInstUnwrapErr(mod, scope, old_inst.castTag(.unwrap_err_safe).?, true),
.unwrap_err_unsafe => return analyzeInstUnwrapErr(mod, scope, old_inst.castTag(.unwrap_err_unsafe).?, false),
+ .unwrap_err_code => return analyzeInstUnwrapErrCode(mod, scope, old_inst.castTag(.unwrap_err_code).?),
.ensure_err_payload_void => return analyzeInstEnsureErrPayloadVoid(mod, scope, old_inst.castTag(.ensure_err_payload_void).?),
.array_type => return analyzeInstArrayType(mod, scope, old_inst.castTag(.array_type).?),
.array_type_sentinel => return analyzeInstArrayTypeSentinel(mod, scope, old_inst.castTag(.array_type_sentinel).?),
@@ -382,6 +384,19 @@ fn analyzeInstEnsureResultNonError(mod: *Module, scope: *Scope, inst: *zir.Inst.
}
}
+fn analyzeInstEnsureIndexable(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
+ const operand = try resolveInst(mod, scope, inst.positionals.operand);
+ const elem_ty = operand.ty.elemType();
+ if (elem_ty.isIndexable()) {
+ return mod.constVoid(scope, operand.src);
+ } else {
+ // TODO error notes
+ // error: type '{}' does not support indexing
+ // note: for loop operand must be an array, a slice or a tuple
+ return mod.fail(scope, operand.src, "for loop operand must be an array, a slice or a tuple", .{});
+ }
+}
+
fn analyzeInstAlloc(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
const var_type = try resolveType(mod, scope, inst.positionals.operand);
// TODO this should happen only for var allocs
@@ -786,11 +801,12 @@ fn analyzeInstUnwrapOptional(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp
const operand = try resolveInst(mod, scope, unwrap.positionals.operand);
assert(operand.ty.zigTypeTag() == .Pointer);
- if (operand.ty.elemType().zigTypeTag() != .Optional) {
- return mod.fail(scope, unwrap.base.src, "expected optional type, found {}", .{operand.ty.elemType()});
+ const elem_type = operand.ty.elemType();
+ if (elem_type.zigTypeTag() != .Optional) {
+ return mod.fail(scope, unwrap.base.src, "expected optional type, found {}", .{elem_type});
}
- const child_type = try operand.ty.elemType().optionalChildAlloc(scope.arena());
+ const child_type = try elem_type.optionalChildAlloc(scope.arena());
const child_pointer = try mod.simplePtrType(scope, unwrap.base.src, child_type, operand.ty.isConstPtr(), .One);
if (operand.value()) |val| {
@@ -815,6 +831,10 @@ fn analyzeInstUnwrapErr(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp, saf
return mod.fail(scope, unwrap.base.src, "TODO implement analyzeInstUnwrapErr", .{});
}
+fn analyzeInstUnwrapErrCode(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp) InnerError!*Inst {
+ return mod.fail(scope, unwrap.base.src, "TODO implement analyzeInstUnwrapErrCode", .{});
+}
+
fn analyzeInstEnsureErrPayloadVoid(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp) InnerError!*Inst {
return mod.fail(scope, unwrap.base.src, "TODO implement analyzeInstEnsureErrPayloadVoid", .{});
}
@@ -950,7 +970,8 @@ fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr
const entry = if (val.cast(Value.Payload.ErrorSet)) |payload|
(payload.fields.getEntry(field_name) orelse
return mod.fail(scope, fieldptr.base.src, "no error named '{}' in '{}'", .{ field_name, child_type })).*
- else try mod.getErrorValue(field_name);
+ else
+ try mod.getErrorValue(field_name);
const error_payload = try scope.arena().create(Value.Payload.Error);
error_payload.* = .{
@@ -1062,9 +1083,19 @@ fn analyzeInstElemPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) Inne
const array_ptr = try resolveInst(mod, scope, inst.positionals.array_ptr);
const uncasted_index = try resolveInst(mod, scope, inst.positionals.index);
const elem_index = try mod.coerce(scope, Type.initTag(.usize), uncasted_index);
+
+ const elem_ty = switch (array_ptr.ty.zigTypeTag()) {
+ .Pointer => array_ptr.ty.elemType(),
+ else => return mod.fail(scope, inst.positionals.array_ptr.src, "expected pointer, found '{}'", .{array_ptr.ty}),
+ };
+ if (!elem_ty.isIndexable()) {
+ return mod.fail(scope, inst.base.src, "array access of non-array type '{}'", .{elem_ty});
+ }
- if (array_ptr.ty.isSinglePointer() and array_ptr.ty.elemType().zigTypeTag() == .Array) {
- if (array_ptr.value()) |array_ptr_val| {
+ if (elem_ty.isSinglePointer() and elem_ty.elemType().zigTypeTag() == .Array) {
+ // we have to deref the ptr operand to get the actual array pointer
+ const array_ptr_deref = try mod.analyzeDeref(scope, inst.base.src, array_ptr, inst.positionals.array_ptr.src);
+ if (array_ptr_deref.value()) |array_ptr_val| {
if (elem_index.value()) |index_val| {
// Both array pointer and index are compile-time known.
const index_u64 = index_val.toUnsignedInt();
@@ -1075,7 +1106,7 @@ fn analyzeInstElemPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) Inne
const type_payload = try scope.arena().create(Type.Payload.PointerSimple);
type_payload.* = .{
.base = .{ .tag = .single_const_pointer },
- .pointee_type = array_ptr.ty.elemType().elemType(),
+ .pointee_type = elem_ty.elemType().elemType(),
};
return mod.constInst(scope, inst.base.src, .{
@@ -1274,17 +1305,7 @@ fn analyzeInstCmp(
{
// comparing null with optionals
const opt_operand = if (lhs_ty_tag == .Optional) lhs else rhs;
- if (opt_operand.value()) |opt_val| {
- const is_null = opt_val.isNull();
- return mod.constBool(scope, inst.base.src, if (op == .eq) is_null else !is_null);
- }
- const b = try mod.requireRuntimeBlock(scope, inst.base.src);
- const inst_tag: Inst.Tag = switch (op) {
- .eq => .isnull,
- .neq => .isnonnull,
- else => unreachable,
- };
- return mod.addUnOp(b, inst.base.src, Type.initTag(.bool), inst_tag, opt_operand);
+ return mod.analyzeIsNull(scope, inst.base.src, opt_operand, op == .neq);
} else if (is_equality_cmp and
((lhs_ty_tag == .Null and rhs.ty.isCPtr()) or (rhs_ty_tag == .Null and lhs.ty.isCPtr())))
{
@@ -1332,8 +1353,9 @@ fn analyzeInstIsNonNull(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp, inver
return mod.analyzeIsNull(scope, inst.base.src, operand, invert_logic);
}
-fn analyzeInstIsErr(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp, invert_logic: bool) InnerError!*Inst {
- return mod.fail(scope, inst.base.src, "TODO implement analyzeInstIsErr", .{});
+fn analyzeInstIsErr(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
+ const operand = try resolveInst(mod, scope, inst.positionals.operand);
+ return mod.analyzeIsErr(scope, inst.base.src, operand);
}
fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerError!*Inst {