diff options
Diffstat (limited to 'src-self-hosted')
| -rw-r--r-- | src-self-hosted/Module.zig | 59 | ||||
| -rw-r--r-- | src-self-hosted/astgen.zig | 307 | ||||
| -rw-r--r-- | src-self-hosted/codegen.zig | 412 | ||||
| -rw-r--r-- | src-self-hosted/link/Elf.zig | 17 | ||||
| -rw-r--r-- | src-self-hosted/link/MachO.zig | 181 | ||||
| -rw-r--r-- | src-self-hosted/main.zig | 2 | ||||
| -rw-r--r-- | src-self-hosted/translate_c.zig | 12 | ||||
| -rw-r--r-- | src-self-hosted/type.zig | 11 | ||||
| -rw-r--r-- | src-self-hosted/value.zig | 36 | ||||
| -rw-r--r-- | src-self-hosted/zir.zig | 20 | ||||
| -rw-r--r-- | src-self-hosted/zir_sema.zig | 64 |
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, ¬_err_scope.base, src, .unwrap_err_unsafe, err_union_ptr); + _ = try addZIRInst(mod, ¬_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 { |
