From 8421b8a8983b5531b1cc299461f7747c829b054a Mon Sep 17 00:00:00 2001 From: Vexu Date: Tue, 6 Oct 2020 13:22:33 +0300 Subject: [PATCH 01/19] stage2: detect import outside file path --- src/Module.zig | 29 ++++++++++++++++++++++------- src/zir_sema.zig | 6 +++--- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index c9c77991e8..425165c8be 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2400,25 +2400,40 @@ pub fn analyzeSlice(self: *Module, scope: *Scope, src: usize, array_ptr: *Inst, } pub fn analyzeImport(self: *Module, scope: *Scope, src: usize, target_string: []const u8) !*Scope.File { - // TODO if (package_table.get(target_string)) |pkg| - if (self.import_table.get(target_string)) |some| { + // TODO scope.getCurPkg(); + const cur_pkg = self.root_pkg; + const cur_pkg_dir_path = cur_pkg.root_src_directory.path orelse "."; + const found_pkg = cur_pkg.table.get(target_string); + + const resolved_path = if (found_pkg) |pkg| + try std.fs.path.resolve(self.gpa, &[_][]const u8{ pkg.root_src_directory.path orelse ".", pkg.root_src_path }) + else + try std.fs.path.resolve(self.gpa, &[_][]const u8{ cur_pkg_dir_path, target_string }); + errdefer self.gpa.free(resolved_path); + + if (self.import_table.get(resolved_path)) |some| { + self.gpa.free(resolved_path); return some; } - // TODO check for imports outside of pkg path - if (false) return error.ImportOutsidePkgPath; + if (found_pkg == null) { + const resolved_root_path = try std.fs.path.resolve(self.gpa, &[_][]const u8{cur_pkg_dir_path}); + defer self.gpa.free(resolved_root_path); + + if (!mem.startsWith(u8, resolved_path, resolved_root_path)) { + return error.ImportOutsidePkgPath; + } + } // TODO Scope.Container arena for ty and sub_file_path const struct_payload = try self.gpa.create(Type.Payload.EmptyStruct); errdefer self.gpa.destroy(struct_payload); const file_scope = try self.gpa.create(Scope.File); errdefer self.gpa.destroy(file_scope); - const file_path = try self.gpa.dupe(u8, target_string); - errdefer self.gpa.free(file_path); struct_payload.* = .{ .scope = &file_scope.root_container }; file_scope.* = .{ - .sub_file_path = file_path, + .sub_file_path = resolved_path, .source = .{ .unloaded = {} }, .contents = .{ .not_available = {} }, .status = .never_loaded, diff --git a/src/zir_sema.zig b/src/zir_sema.zig index d02c21ec47..1e6bec3ff8 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -1208,9 +1208,9 @@ fn analyzeInstImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerErr const operand = try resolveConstString(mod, scope, inst.positionals.operand); const file_scope = mod.analyzeImport(scope, inst.base.src, operand) catch |err| switch (err) { - // error.ImportOutsidePkgPath => { - // return mod.fail(scope, inst.base.src, "import of file outside package path: '{}'", .{operand}); - // }, + error.ImportOutsidePkgPath => { + return mod.fail(scope, inst.base.src, "import of file outside package path: '{}'", .{operand}); + }, error.FileNotFound => { return mod.fail(scope, inst.base.src, "unable to find '{}'", .{operand}); }, From a1d7f0053d6fa56bcc879e83987babd42bb21a20 Mon Sep 17 00:00:00 2001 From: Vexu Date: Tue, 6 Oct 2020 13:56:26 +0300 Subject: [PATCH 02/19] stage2: support imports inside packages --- src/Compilation.zig | 1 + src/Module.zig | 24 +++++++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 4b7dc720e1..472f367b91 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -660,6 +660,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .source = .{ .unloaded = {} }, .contents = .{ .not_available = {} }, .status = .never_loaded, + .pkg = root_pkg, .root_container = .{ .file_scope = root_scope, .decls = .{}, diff --git a/src/Module.zig b/src/Module.zig index 425165c8be..0876b7f8d2 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -469,6 +469,22 @@ pub const Scope = struct { } } + pub fn getOwnerPkg(base: *Scope) *Package { + var cur = base; + while (true) { + cur = switch (cur.tag) { + .container => return @fieldParentPtr(Container, "base", cur).file_scope.pkg, + .file => return @fieldParentPtr(File, "base", cur).pkg, + .zir_module => unreachable, // TODO are zir modules allowed to import packages? + .gen_zir => @fieldParentPtr(GenZIR, "base", cur).parent, + .local_val => @fieldParentPtr(LocalVal, "base", cur).parent, + .local_ptr => @fieldParentPtr(LocalPtr, "base", cur).parent, + .block => @fieldParentPtr(Block, "base", cur).decl.scope, + .decl => @fieldParentPtr(DeclAnalysis, "base", cur).decl.scope, + }; + } + } + /// Asserts the scope is a namespace Scope and removes the Decl from the namespace. pub fn removeDecl(base: *Scope, child: *Decl) void { switch (base.tag) { @@ -576,6 +592,8 @@ pub const Scope = struct { unloaded_parse_failure, loaded_success, }, + /// Package that this file is a part of, managed externally. + pkg: *Package, root_container: Container, @@ -614,7 +632,7 @@ pub const Scope = struct { pub fn getSource(self: *File, module: *Module) ![:0]const u8 { switch (self.source) { .unloaded => { - const source = try module.root_pkg.root_src_directory.handle.readFileAllocOptions( + const source = try self.pkg.root_src_directory.handle.readFileAllocOptions( module.gpa, self.sub_file_path, std.math.maxInt(u32), @@ -2400,8 +2418,7 @@ pub fn analyzeSlice(self: *Module, scope: *Scope, src: usize, array_ptr: *Inst, } pub fn analyzeImport(self: *Module, scope: *Scope, src: usize, target_string: []const u8) !*Scope.File { - // TODO scope.getCurPkg(); - const cur_pkg = self.root_pkg; + const cur_pkg = scope.getOwnerPkg(); const cur_pkg_dir_path = cur_pkg.root_src_directory.path orelse "."; const found_pkg = cur_pkg.table.get(target_string); @@ -2437,6 +2454,7 @@ pub fn analyzeImport(self: *Module, scope: *Scope, src: usize, target_string: [] .source = .{ .unloaded = {} }, .contents = .{ .not_available = {} }, .status = .never_loaded, + .pkg = found_pkg orelse cur_pkg, .root_container = .{ .file_scope = file_scope, .decls = .{}, From ad32e46bceb03c0b0d67fe73e0de0e308f0675e6 Mon Sep 17 00:00:00 2001 From: Vexu Date: Sun, 11 Oct 2020 23:52:08 +0300 Subject: [PATCH 03/19] stage2: switch astgen --- src/astgen.zig | 154 ++++++++++++++++++++++++++++++++++++++++++++++- src/zir.zig | 75 +++++++++++++++++++++++ src/zir_sema.zig | 1 + 3 files changed, 228 insertions(+), 2 deletions(-) diff --git a/src/astgen.zig b/src/astgen.zig index 19b522b9c6..19d7b87eeb 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -183,6 +183,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .VarDecl => unreachable, // Handled in `blockExpr`. .SwitchCase => unreachable, // Handled in `switchExpr`. .SwitchElse => unreachable, // Handled in `switchExpr`. + .Range => unreachable, // Handled in `switchExpr`. .Else => unreachable, // Handled explicitly the control flow expression functions. .Payload => unreachable, // Handled explicitly. .PointerPayload => unreachable, // Handled explicitly. @@ -279,9 +280,9 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .Catch => return catchExpr(mod, scope, rl, node.castTag(.Catch).?), .Comptime => return comptimeKeyword(mod, scope, rl, node.castTag(.Comptime).?), .OrElse => return orelseExpr(mod, scope, rl, node.castTag(.OrElse).?), + .Switch => return switchExpr(mod, scope, rl, node.castTag(.Switch).?), .Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}), - .Range => return mod.failNode(scope, node, "TODO implement astgen.expr for .Range", .{}), .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", .{}), @@ -289,7 +290,6 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .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", .{}), .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", .{}), @@ -1561,6 +1561,156 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For) return &for_block.base; } +fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node.Switch) InnerError!*zir.Inst { + var block_scope: Scope.GenZIR = .{ + .parent = scope, + .decl = scope.decl().?, + .arena = scope.arena(), + .instructions = .{}, + }; + defer block_scope.instructions.deinit(mod.gpa); + + const tree = scope.tree(); + const switch_src = tree.token_locs[switch_node.switch_token].start; + const target_ptr = try expr(mod, &block_scope.base, .ref, switch_node.expr); + const cases = try scope.arena().alloc(zir.Inst.Switch.Case, switch_node.cases_len); + var kw_args: std.meta.fieldInfo(zir.Inst.Switch, "kw_args").field_type = .{}; + + // first we gather all the switch items and check else/'_' prongs + var case_index: usize = 0; + var else_src: ?usize = null; + var underscore_src: ?usize = null; + for (switch_node.cases()) |uncasted_case| { + const case = uncasted_case.castTag(.SwitchCase).?; + const case_src = tree.token_locs[case.firstToken()].start; + + if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) { + if (else_src) |src| { + return mod.fail(scope, case_src, "multiple else prongs in switch expression", .{}); + // TODO notes "previous else prong is here" + } + kw_args.special_case = .@"else"; + else_src = case_src; + cases[cases.len - 1] = .{ + .values = &[_]*zir.Inst{}, + .body = undefined, // filled below + }; + continue; + } else if (case.items_len == 1 and case.items()[0].tag == .Identifier and + mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_")) + { + if (underscore_src) |src| { + return mod.fail(scope, case_src, "multiple '_' prongs in switch expression", .{}); + // TODO notes "previous '_' prong is here" + } + kw_args.special_case = .underscore; + underscore_src = case_src; + cases[cases.len - 1] = .{ + .values = &[_]*zir.Inst{}, + .body = undefined, // filled below + }; + continue; + } + + if (else_src) |some_else| { + if (underscore_src) |some_underscore| { + return mod.fail(scope, case_src, "else and '_' prong in switch expression", .{}); + // TODO notes "else prong is here" + // TODO notes "'_' prong is here" + } + } + + // Regular case, we need to fill `values`. + const values = try block_scope.arena.alloc(*zir.Inst, case.items_len); + for (case.items()) |item, i| { + if (item.castTag(.Range)) |range| { + values[i] = try switchRange(mod, &block_scope.base, range); + if (kw_args.support_range == null) + kw_args.support_range = values[i]; + } else { + values[i] = try expr(mod, &block_scope.base, .none, item); + } + } + cases[case_index] = .{ + .values = values, + .body = undefined, // filled below + }; + case_index += 1; + } + + // Then we add the switch instruction to finish the block. + _ = try addZIRInst(mod, scope, switch_src, zir.Inst.Switch, .{ + .target_ptr = target_ptr, + .cases = cases, + }, kw_args); + const block = try addZIRInstBlock(mod, scope, switch_src, .block, .{ + .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 switch case. + const case_rl: ResultLoc = switch (rl) { + .discard, .none, .ty, .ptr, .ref => rl, + .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block }, + }; + + var case_scope: Scope.GenZIR = .{ + .parent = scope, + .decl = block_scope.decl, + .arena = block_scope.arena, + .instructions = .{}, + }; + defer case_scope.instructions.deinit(mod.gpa); + + // And finally we fill generate the bodies of each case. + case_index = 0; + for (switch_node.cases()) |uncasted_case| { + const case = uncasted_case.castTag(.SwitchCase).?; + const case_src = tree.token_locs[case.firstToken()].start; + // reset without freeing to reduce allocations. + defer case_scope.instructions.items.len = 0; + + // What index in positionals.cases should this one be placed at. + // For special cases it will be at the end. + var cur_index = case_index; + if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) { + // validated above + cur_index = cases.len - 1; + } else if (case.items_len == 1 and case.items()[0].tag == .Identifier and + mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_")) + { + // validated above + cur_index = cases.len - 1; + } + + // Generate the body of this case. + const case_body = try expr(mod, &case_scope.base, case_rl, case.expr); + if (!case_body.tag.isNoReturn()) { + _ = try addZIRInst(mod, &case_scope.base, case_src, zir.Inst.Break, .{ + .block = block, + .operand = case_body, + }, .{}); + } + cases[cur_index].body = .{ + .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), + }; + } + + return &block.base; +} + +/// Only used for `a...b` in switches. +fn switchRange(mod: *Module, scope: *Scope, node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst { + const tree = scope.tree(); + const src = tree.token_locs[node.op_token].start; + + const start = try expr(mod, scope, .none, node.lhs); + const end = try expr(mod, scope, .none, node.rhs); + + return try addZIRBinOp(mod, scope, src, .switch_range, start, end); +} + 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/zir.zig b/src/zir.zig index 76ba89e9c7..d2a22cdf52 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -272,6 +272,10 @@ pub const Inst = struct { ensure_err_payload_void, /// Enum literal enum_literal, + /// A switch expression. + @"switch", + /// A range in a switch case, `lhs...rhs`. + switch_range, pub fn Type(tag: Tag) type { return switch (tag) { @@ -351,6 +355,7 @@ pub const Inst = struct { .error_union_type, .merge_error_sets, .slice_start, + .switch_range, => BinOp, .block, @@ -389,6 +394,7 @@ pub const Inst = struct { .enum_literal => EnumLiteral, .error_set => ErrorSet, .slice => Slice, + .@"switch" => Switch, }; } @@ -493,6 +499,7 @@ pub const Inst = struct { .slice, .slice_start, .import, + .switch_range, => false, .@"break", @@ -504,6 +511,7 @@ pub const Inst = struct { .unreach_nocheck, .@"unreachable", .loop, + .@"switch", => true, }; } @@ -987,6 +995,33 @@ pub const Inst = struct { sentinel: ?*Inst = null, }, }; + + pub const Switch = struct { + pub const base_tag = Tag.@"switch"; + base: Inst, + + positionals: struct { + target_ptr: *Inst, + cases: []Case, + }, + kw_args: struct { + /// if not null target must support ranges, (be int) + support_range: ?*Inst = null, + special_case: enum { + /// all of positionals.cases are regular cases + none, + /// last case in positionals.cases is an else case + @"else", + /// last case in positionals.cases is an underscore case + underscore, + } = .none, + }, + + pub const Case = struct { + values: []*Inst, + body: Module.Body, + }; + }; }; pub const ErrorMsg = struct { @@ -1238,6 +1273,26 @@ const Writer = struct { } try stream.writeByte(']'); }, + []Inst.Switch.Case => { + if (param.len == 0) { + return stream.writeAll("{}"); + } + try stream.writeAll("{\n"); + self.indent += 2; + for (param) |*case, i| { + if (i != 0) { + try stream.writeAll(",\n"); + } + try stream.writeByteNTimes(' ', self.indent); + try self.writeParamToStream(stream, &case.values); + try stream.writeAll(" => "); + try self.writeParamToStream(stream, &case.body); + } + try stream.writeByte('\n'); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeByte('}'); + }, else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)), } } @@ -1650,6 +1705,26 @@ const Parser = struct { try requireEatBytes(self, "]"); return strings.toOwnedSlice(); }, + []Inst.Switch.Case => { + try requireEatBytes(self, "{"); + skipSpace(self); + if (eatByte(self, '}')) return &[0]Inst.Switch.Case{}; + + var cases = std.ArrayList(Inst.Switch.Case).init(&self.arena.allocator); + while (true) { + const cur = try cases.addOne(); + skipSpace(self); + cur.values = try self.parseParameterGeneric([]*Inst, body_ctx); + skipSpace(self); + try requireEatBytes(self, "=>"); + cur.body = try self.parseBody(body_ctx); + skipSpace(self); + if (!eatByte(self, ',')) break; + } + skipSpace(self); + try requireEatBytes(self, "}"); + return cases.toOwnedSlice(); + }, else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)), } return self.fail("TODO parse parameter {}", .{@typeName(T)}); diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 1e6bec3ff8..cc9e8182bb 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -135,6 +135,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .slice => return analyzeInstSlice(mod, scope, old_inst.castTag(.slice).?), .slice_start => return analyzeInstSliceStart(mod, scope, old_inst.castTag(.slice_start).?), .import => return analyzeInstImport(mod, scope, old_inst.castTag(.import).?), + .@"switch", .switch_range => @panic("TODO switch sema"), } } From 27d233cef76f9ae3086a6ec5e33c347e2af64796 Mon Sep 17 00:00:00 2001 From: Vexu Date: Mon, 12 Oct 2020 12:20:32 +0300 Subject: [PATCH 04/19] stage2: basic switch validation --- src/astgen.zig | 18 +++---- src/value.zig | 4 ++ src/zir.zig | 8 +-- src/zir_sema.zig | 123 ++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 140 insertions(+), 13 deletions(-) diff --git a/src/astgen.zig b/src/astgen.zig index 19d7b87eeb..0b74886729 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -1592,7 +1592,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node kw_args.special_case = .@"else"; else_src = case_src; cases[cases.len - 1] = .{ - .values = &[_]*zir.Inst{}, + .items = &[0]*zir.Inst{}, .body = undefined, // filled below }; continue; @@ -1606,7 +1606,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node kw_args.special_case = .underscore; underscore_src = case_src; cases[cases.len - 1] = .{ - .values = &[_]*zir.Inst{}, + .items = &[0]*zir.Inst{}, .body = undefined, // filled below }; continue; @@ -1620,26 +1620,26 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node } } - // Regular case, we need to fill `values`. - const values = try block_scope.arena.alloc(*zir.Inst, case.items_len); + // Regular case, we need to fill `items`. + const items = try block_scope.arena.alloc(*zir.Inst, case.items_len); for (case.items()) |item, i| { if (item.castTag(.Range)) |range| { - values[i] = try switchRange(mod, &block_scope.base, range); + items[i] = try switchRange(mod, &block_scope.base, range); if (kw_args.support_range == null) - kw_args.support_range = values[i]; + kw_args.support_range = items[i]; } else { - values[i] = try expr(mod, &block_scope.base, .none, item); + items[i] = try expr(mod, &block_scope.base, .none, item); } } cases[case_index] = .{ - .values = values, + .items = items, .body = undefined, // filled below }; case_index += 1; } // Then we add the switch instruction to finish the block. - _ = try addZIRInst(mod, scope, switch_src, zir.Inst.Switch, .{ + _ = try addZIRInst(mod, &block_scope.base, switch_src, zir.Inst.Switch, .{ .target_ptr = target_ptr, .cases = cases, }, kw_args); diff --git a/src/value.zig b/src/value.zig index e8fe848d5d..a7c3afbd6b 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1242,6 +1242,10 @@ pub const Value = extern union { return compare(a, .eq, b); } + pub fn hash(a: Value) u64 { + @panic("TODO Value.hash"); + } + /// Asserts the value is a pointer and dereferences it. /// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis. pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value { diff --git a/src/zir.zig b/src/zir.zig index d2a22cdf52..5dc7a2eee6 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -275,6 +275,8 @@ pub const Inst = struct { /// A switch expression. @"switch", /// A range in a switch case, `lhs...rhs`. + /// Only checks that `lhs >= rhs` if they are ints or floats, everything else is + /// validated by the .switch instruction. switch_range, pub fn Type(tag: Tag) type { @@ -1018,7 +1020,7 @@ pub const Inst = struct { }, pub const Case = struct { - values: []*Inst, + items: []*Inst, body: Module.Body, }; }; @@ -1284,7 +1286,7 @@ const Writer = struct { try stream.writeAll(",\n"); } try stream.writeByteNTimes(' ', self.indent); - try self.writeParamToStream(stream, &case.values); + try self.writeParamToStream(stream, &case.items); try stream.writeAll(" => "); try self.writeParamToStream(stream, &case.body); } @@ -1714,7 +1716,7 @@ const Parser = struct { while (true) { const cur = try cases.addOne(); skipSpace(self); - cur.values = try self.parseParameterGeneric([]*Inst, body_ctx); + cur.items = try self.parseParameterGeneric([]*Inst, body_ctx); skipSpace(self); try requireEatBytes(self, "=>"); cur.body = try self.parseBody(body_ctx); diff --git a/src/zir_sema.zig b/src/zir_sema.zig index cc9e8182bb..26b2714ec8 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -135,7 +135,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .slice => return analyzeInstSlice(mod, scope, old_inst.castTag(.slice).?), .slice_start => return analyzeInstSliceStart(mod, scope, old_inst.castTag(.slice_start).?), .import => return analyzeInstImport(mod, scope, old_inst.castTag(.import).?), - .@"switch", .switch_range => @panic("TODO switch sema"), + .@"switch" => return analyzeInstSwitch(mod, scope, old_inst.castTag(.@"switch").?), + .switch_range => return analyzeInstSwitchRange(mod, scope, old_inst.castTag(.switch_range).?), } } @@ -1205,6 +1206,126 @@ fn analyzeInstSliceStart(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) Inn return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null); } +fn analyzeInstSwitchRange(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { + const start = try resolveInst(mod, scope, inst.positionals.lhs); + const end = try resolveInst(mod, scope, inst.positionals.rhs); + + switch (start.ty.zigTypeTag()) { + .Int, .ComptimeInt, .Float, .ComptimeFloat => {}, + else => return mod.constVoid(scope, inst.base.src), + } + switch (end.ty.zigTypeTag()) { + .Int, .ComptimeInt, .Float, .ComptimeFloat => {}, + else => return mod.constVoid(scope, inst.base.src), + } + if (start.value()) |start_val| { + if (end.value()) |end_val| { + if (start_val.compare(.gte, end_val)) { + return mod.fail(scope, inst.base.src, "range start value is greater than the end value", .{}); + } + } + } + return mod.constVoid(scope, inst.base.src); +} + +fn analyzeInstSwitch(mod: *Module, scope: *Scope, inst: *zir.Inst.Switch) InnerError!*Inst { + const target_ptr = try resolveInst(mod, scope, inst.positionals.target_ptr); + const target = try mod.analyzeDeref(scope, inst.base.src, target_ptr, inst.positionals.target_ptr.src); + try validateSwitch(mod, scope, target, inst); + + return mod.fail(scope, inst.base.src, "TODO analyzeInstSwitch", .{}); +} + +fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.Switch) InnerError!void { + // validate usage of '_' prongs + if (inst.kw_args.special_case == .underscore and target.ty.zigTypeTag() != .Enum) { + return mod.fail(scope, inst.base.src, "'_' prong only allowed when switching on non-exhaustive enums", .{}); + // TODO notes "'_' prong here" inst.positionals.cases[last].src + } + + // check that target type supports ranges + if (inst.kw_args.support_range) |some| { + switch (target.ty.zigTypeTag()) { + .Int, .ComptimeInt, .Float, .ComptimeFloat => {}, + else => { + return mod.fail(scope, target.src, "ranges not allowed when switching on type {}", .{target.ty}); + // TODO notes "range used here" some.src + }, + } + } + + // validate for duplicate items/missing else prong + switch (target.ty.zigTypeTag()) { + .Int, .ComptimeInt => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Int, .ComptimeInt", .{}), + .Float, .ComptimeFloat => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Float, .ComptimeFloat", .{}), + .Enum => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Enum", .{}), + .ErrorSet => return mod.fail(scope, inst.base.src, "TODO validateSwitch .ErrorSet", .{}), + .Union => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Union", .{}), + .Bool => { + var true_count: u8 = 0; + var false_count: u8 = 0; + for (inst.positionals.cases) |case| { + for (case.items) |item| { + const resolved = try resolveInst(mod, scope, item); + const casted = try mod.coerce(scope, Type.initTag(.bool), resolved); + if ((try mod.resolveConstValue(scope, casted)).toBool()) { + true_count += 1; + } else { + false_count += 1; + } + + if (true_count > 1 or false_count > 1) { + return mod.fail(scope, item.src, "duplicate switch value", .{}); + } + } + } + if ((true_count == 0 or false_count == 0) and inst.kw_args.special_case != .@"else") { + return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{}); + } + if ((true_count == 1 and false_count == 1) and inst.kw_args.special_case == .@"else") { + return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{}); + } + }, + .EnumLiteral, .Void, .Fn, .Pointer, .Type => { + if (inst.kw_args.special_case != .@"else") { + return mod.fail(scope, inst.base.src, "else prong required when switching on type '{}'", .{target.ty}); + } + + var seen_values = std.HashMap(Value, usize, Value.hash, Value.eql, std.hash_map.DefaultMaxLoadPercentage).init(mod.gpa); + defer seen_values.deinit(); + + for (inst.positionals.cases) |case| { + for (case.items) |item| { + const resolved = try resolveInst(mod, scope, item); + const casted = try mod.coerce(scope, target.ty, resolved); + const val = try mod.resolveConstValue(scope, casted); + + if (try seen_values.fetchPut(val, item.src)) |prev| { + return mod.fail(scope, item.src, "duplicate switch value", .{}); + // TODO notes "previous value here" prev.value + } + } + } + }, + + .ErrorUnion, + .NoReturn, + .Array, + .Struct, + .Undefined, + .Null, + .Optional, + .BoundFn, + .Opaque, + .Vector, + .Frame, + .AnyFrame, + => { + return mod.fail(scope, target.src, "invalid switch target type '{}'", .{target.ty}); + }, + } +} + fn analyzeInstImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { const operand = try resolveConstString(mod, scope, inst.positionals.operand); From 7e2774367e5420a8f388b57f80bede8f0f680ca7 Mon Sep 17 00:00:00 2001 From: Vexu Date: Mon, 12 Oct 2020 12:53:44 +0300 Subject: [PATCH 05/19] stage2: implement Value.hash --- src/value.zig | 135 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 2 deletions(-) diff --git a/src/value.zig b/src/value.zig index a7c3afbd6b..54fa541bdf 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1242,8 +1242,139 @@ pub const Value = extern union { return compare(a, .eq, b); } - pub fn hash(a: Value) u64 { - @panic("TODO Value.hash"); + pub fn hash(self: Value) u64 { + var hasher = std.hash.Wyhash.init(0); + std.hash.autoHash(&hasher, self.tag()); + + switch (self.tag()) { + .u8_type, + .i8_type, + .u16_type, + .i16_type, + .u32_type, + .i32_type, + .u64_type, + .i64_type, + .usize_type, + .isize_type, + .c_short_type, + .c_ushort_type, + .c_int_type, + .c_uint_type, + .c_long_type, + .c_ulong_type, + .c_longlong_type, + .c_ulonglong_type, + .c_longdouble_type, + .f16_type, + .f32_type, + .f64_type, + .f128_type, + .c_void_type, + .bool_type, + .void_type, + .type_type, + .anyerror_type, + .comptime_int_type, + .comptime_float_type, + .noreturn_type, + .null_type, + .undefined_type, + .fn_noreturn_no_args_type, + .fn_void_no_args_type, + .fn_naked_noreturn_no_args_type, + .fn_ccc_void_no_args_type, + .single_const_pointer_to_comptime_int_type, + .const_slice_u8_type, + .enum_literal_type, + .anyframe_type, + .ty, + => { + // Directly return Type.hash, toType can only fail for .int_type and .error_set. + var allocator = std.heap.FixedBufferAllocator.init(&[_]u8{}); + return (self.toType(&allocator.allocator) catch unreachable).hash(); + }, + .error_set => { + // Payload.decl should be same for all instances of the type. + const payload = @fieldParentPtr(Payload.ErrorSet, "base", self.ptr_otherwise); + std.hash.autoHash(&hasher, payload.decl); + }, + .int_type => { + const payload = self.cast(Payload.IntType).?; + if (payload.signed) { + var new = Type.Payload.IntSigned{ .bits = payload.bits }; + return Type.initPayload(&new.base).hash(); + } else { + var new = Type.Payload.IntUnsigned{ .bits = payload.bits }; + return Type.initPayload(&new.base).hash(); + } + }, + + .undef, + .zero, + .one, + .void_value, + .unreachable_value, + .empty_struct_value, + .empty_array, + .null_value, + .bool_true, + .bool_false, + => {}, + + .float_16, .float_32, .float_64, .float_128 => {}, + .enum_literal, .bytes => { + const payload = @fieldParentPtr(Payload.Bytes, "base", self.ptr_otherwise); + hasher.update(payload.data); + }, + .int_u64 => { + const payload = @fieldParentPtr(Payload.Int_u64, "base", self.ptr_otherwise); + std.hash.autoHash(&hasher, payload.int); + }, + .int_i64 => { + const payload = @fieldParentPtr(Payload.Int_i64, "base", self.ptr_otherwise); + std.hash.autoHash(&hasher, payload.int); + }, + .repeated => { + const payload = @fieldParentPtr(Payload.Repeated, "base", self.ptr_otherwise); + std.hash.autoHash(&hasher, payload.val.hash()); + }, + .ref_val => { + const payload = @fieldParentPtr(Payload.RefVal, "base", self.ptr_otherwise); + std.hash.autoHash(&hasher, payload.val.hash()); + }, + .int_big_positive, .int_big_negative => { + var space: BigIntSpace = undefined; + const big = self.toBigInt(&space); + std.hash.autoHash(&hasher, big.positive); + for (big.limbs) |limb| { + std.hash.autoHash(&hasher, limb); + } + }, + .elem_ptr => { + const payload = @fieldParentPtr(Payload.ElemPtr, "base", self.ptr_otherwise); + std.hash.autoHash(&hasher, payload.array_ptr.hash()); + std.hash.autoHash(&hasher, payload.index); + }, + .decl_ref => { + const payload = @fieldParentPtr(Payload.DeclRef, "base", self.ptr_otherwise); + std.hash.autoHash(&hasher, payload.decl); + }, + .function => { + const payload = @fieldParentPtr(Payload.Function, "base", self.ptr_otherwise); + std.hash.autoHash(&hasher, payload.func); + }, + .variable => { + const payload = @fieldParentPtr(Payload.Variable, "base", self.ptr_otherwise); + std.hash.autoHash(&hasher, payload.variable); + }, + .@"error" => { + const payload = @fieldParentPtr(Payload.Error, "base", self.ptr_otherwise); + hasher.update(payload.name); + std.hash.autoHash(&hasher, payload.value); + }, + } + return hasher.final(); } /// Asserts the value is a pointer and dereferences it. From 2c12f4a993343abb65ebb881f095c38ba0310306 Mon Sep 17 00:00:00 2001 From: Vexu Date: Mon, 12 Oct 2020 13:13:14 +0300 Subject: [PATCH 06/19] stage2: implement Value.eql for void, null and types --- src/value.zig | 102 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 5 deletions(-) diff --git a/src/value.zig b/src/value.zig index 54fa541bdf..f6a7b24d48 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1233,12 +1233,23 @@ pub const Value = extern union { } pub fn eql(a: Value, b: Value) bool { - if (a.tag() == b.tag() and a.tag() == .enum_literal) { - const a_name = @fieldParentPtr(Payload.Bytes, "base", a.ptr_otherwise).data; - const b_name = @fieldParentPtr(Payload.Bytes, "base", b.ptr_otherwise).data; - return std.mem.eql(u8, a_name, b_name); + if (a.tag() == b.tag()) { + if (a.tag() == .void_value or a.tag() == .null_value) { + return true; + } else if (a.tag() == .enum_literal) { + const a_name = @fieldParentPtr(Payload.Bytes, "base", a.ptr_otherwise).data; + const b_name = @fieldParentPtr(Payload.Bytes, "base", b.ptr_otherwise).data; + return std.mem.eql(u8, a_name, b_name); + } + } + if (a.isType() and b.isType()) { + // 128 bytes should be enough to hold both types + var buf: [128]u8 = undefined; + var fib = std.heap.FixedBufferAllocator.init(&buf); + const a_type = a.toType(&fib.allocator) catch unreachable; + const b_type = b.toType(&fib.allocator) catch unreachable; + return a_type.eql(b_type); } - // TODO non numerical comparisons return compare(a, .eq, b); } @@ -1656,6 +1667,87 @@ pub const Value = extern union { }; } + /// Valid for all types. Asserts the value is not undefined. + pub fn isType(self: Value) bool { + return switch (self.tag()) { + .ty, + .int_type, + .u8_type, + .i8_type, + .u16_type, + .i16_type, + .u32_type, + .i32_type, + .u64_type, + .i64_type, + .usize_type, + .isize_type, + .c_short_type, + .c_ushort_type, + .c_int_type, + .c_uint_type, + .c_long_type, + .c_ulong_type, + .c_longlong_type, + .c_ulonglong_type, + .c_longdouble_type, + .f16_type, + .f32_type, + .f64_type, + .f128_type, + .c_void_type, + .bool_type, + .void_type, + .type_type, + .anyerror_type, + .comptime_int_type, + .comptime_float_type, + .noreturn_type, + .null_type, + .undefined_type, + .fn_noreturn_no_args_type, + .fn_void_no_args_type, + .fn_naked_noreturn_no_args_type, + .fn_ccc_void_no_args_type, + .single_const_pointer_to_comptime_int_type, + .const_slice_u8_type, + .enum_literal_type, + .anyframe_type, + .error_set, + => true, + + .zero, + .one, + .empty_array, + .bool_true, + .bool_false, + .function, + .variable, + .int_u64, + .int_i64, + .int_big_positive, + .int_big_negative, + .ref_val, + .decl_ref, + .elem_ptr, + .bytes, + .repeated, + .float_16, + .float_32, + .float_64, + .float_128, + .void_value, + .enum_literal, + .@"error", + .empty_struct_value, + .null_value, + => false, + + .undef => unreachable, + .unreachable_value => unreachable, + }; + } + /// This type is not copyable since it may contain pointers to its inner data. pub const Payload = struct { tag: Tag, From 11998d2972bf1f7253351fc756c4f1766a412f1d Mon Sep 17 00:00:00 2001 From: Vexu Date: Mon, 12 Oct 2020 15:35:48 +0300 Subject: [PATCH 07/19] stage2: basic switch analysis --- src/astgen.zig | 4 +++ src/codegen.zig | 7 ++++++ src/ir.zig | 24 ++++++++++++++++++ src/zir.zig | 3 +++ src/zir_sema.zig | 65 +++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 102 insertions(+), 1 deletion(-) diff --git a/src/astgen.zig b/src/astgen.zig index 0b74886729..fa624c31ee 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -1584,6 +1584,10 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node const case = uncasted_case.castTag(.SwitchCase).?; const case_src = tree.token_locs[case.firstToken()].start; + if (case.payload != null) { + return mod.fail(scope, case_src, "TODO switch case payload capture", .{}); + } + if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) { if (else_src) |src| { return mod.fail(scope, case_src, "multiple else prongs in switch expression", .{}); diff --git a/src/codegen.zig b/src/codegen.zig index 5e5215c992..9deeab82a5 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -786,6 +786,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .unwrap_optional => return self.genUnwrapOptional(inst.castTag(.unwrap_optional).?), .wrap_optional => return self.genWrapOptional(inst.castTag(.wrap_optional).?), .varptr => return self.genVarPtr(inst.castTag(.varptr).?), + .@"switch" => return self.genSwitch(inst.castTag(.@"switch").?), } } @@ -1989,6 +1990,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return @bitCast(MCValue, inst.codegen.mcv); } + fn genSwitch(self: *Self, inst: *ir.Inst.Switch) !MCValue { + switch (arch) { + else => return self.fail(inst.base.src, "TODO genSwitch for {}", .{self.target.cpu.arch}), + } + } + fn performReloc(self: *Self, src: usize, reloc: Reloc) !void { switch (reloc) { .rel32 => |pos| { diff --git a/src/ir.zig b/src/ir.zig index 26afa52929..e71935e18a 100644 --- a/src/ir.zig +++ b/src/ir.zig @@ -91,6 +91,7 @@ pub const Inst = struct { intcast, unwrap_optional, wrap_optional, + @"switch", pub fn Type(tag: Tag) type { return switch (tag) { @@ -137,6 +138,7 @@ pub const Inst = struct { .constant => Constant, .loop => Loop, .varptr => VarPtr, + .@"switch" => Switch, }; } @@ -458,6 +460,28 @@ pub const Inst = struct { return null; } }; + + pub const Switch = struct { + pub const base_tag = Tag.@"switch"; + + base: Inst, + target_ptr: *Inst, + cases: []Case, + @"else": ?Body, + + pub const Case = struct { + items: []Value, + body: Body, + }; + + pub fn operandCount(self: *const Switch) usize { + return 1; + } + pub fn getOperand(self: *const Switch, index: usize) ?*Inst { + return self.target_ptr; + } + // TODO case body deaths + }; }; pub const Body = struct { diff --git a/src/zir.zig b/src/zir.zig index 5dc7a2eee6..6bee72c66b 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -2549,6 +2549,9 @@ const EmitZIR = struct { }, .varptr => @panic("TODO"), + .@"switch" => { + @panic("TODO"); + }, }; try self.metadata.put(new_inst, .{ .deaths = inst.deaths, diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 26b2714ec8..6c2b7a864a 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -1233,7 +1233,70 @@ fn analyzeInstSwitch(mod: *Module, scope: *Scope, inst: *zir.Inst.Switch) InnerE const target = try mod.analyzeDeref(scope, inst.base.src, target_ptr, inst.positionals.target_ptr.src); try validateSwitch(mod, scope, target, inst); - return mod.fail(scope, inst.base.src, "TODO analyzeInstSwitch", .{}); + // TODO comptime execution + + // excludes else and '_' cases + const case_count = inst.positionals.cases.len - @boolToInt(inst.kw_args.special_case != .none); + + const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src); + const switch_inst = try parent_block.arena.create(Inst.Switch); + switch_inst.* = .{ + .base = .{ + .tag = Inst.Switch.base_tag, + .ty = Type.initTag(.noreturn), + .src = inst.base.src, + }, + .target_ptr = target_ptr, + .@"else" = null, + .cases = try parent_block.arena.alloc(Inst.Switch.Case, case_count), + }; + + var case_block: Scope.Block = .{ + .parent = parent_block, + .func = parent_block.func, + .decl = parent_block.decl, + .instructions = .{}, + .arena = parent_block.arena, + .is_comptime = parent_block.is_comptime, + }; + defer case_block.instructions.deinit(mod.gpa); + + var items_tmp = std.ArrayList(Value).init(mod.gpa); + defer items_tmp.deinit(); + + for (inst.positionals.cases[0..case_count]) |case, i| { + // Reset without freeing. + case_block.instructions.items.len = 0; + items_tmp.items.len = 0; + + for (case.items) |item| { + if (item.castTag(.switch_range)) |range| { + return mod.fail(scope, item.src, "genSwitch expand range", .{}); + } + const resolved = try resolveInst(mod, scope, item); + const casted = try mod.coerce(scope, target.ty, resolved); + const val = try mod.resolveConstValue(scope, casted); + try items_tmp.append(val); + } + + try analyzeBody(mod, &case_block.base, case.body); + + switch_inst.cases[i] = .{ + .items = try parent_block.arena.dupe(Value, items_tmp.items), + .body = .{ .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items) }, + }; + } + + if (inst.kw_args.special_case != .none) { + case_block.instructions.items.len = 0; + + try analyzeBody(mod, &case_block.base, inst.positionals.cases[case_count].body); + switch_inst.@"else" = .{ + .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items), + }; + } + + return &switch_inst.base; } fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.Switch) InnerError!void { From 2020ca640e8db50f1cb5a1ceaa42c28a25483bad Mon Sep 17 00:00:00 2001 From: Vexu Date: Tue, 13 Oct 2020 18:08:15 +0300 Subject: [PATCH 08/19] stage2: switch emit zir --- src/Module.zig | 23 ++++++++++++++ src/astgen.zig | 6 ++-- src/codegen.zig | 4 +-- src/ir.zig | 35 ++++++++++++++++----- src/zir.zig | 79 ++++++++++++++++++++++++++++++++++++++---------- src/zir_sema.zig | 28 ++++++----------- 6 files changed, 127 insertions(+), 48 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index 0876b7f8d2..e821e5863a 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2098,6 +2098,29 @@ pub fn addCall( return &inst.base; } +pub fn addSwitchBr( + self: *Module, + block: *Scope.Block, + src: usize, + target_ptr: *Inst, + cases: []Inst.SwitchBr.Case, + else_body: ?Module.Body, +) !*Inst { + const inst = try block.arena.create(Inst.SwitchBr); + inst.* = .{ + .base = .{ + .tag = .switchbr, + .ty = Type.initTag(.noreturn), + .src = src, + }, + .target_ptr = target_ptr, + .cases = cases, + .@"else" = else_body, + }; + try block.instructions.append(self.gpa, &inst.base); + return &inst.base; +} + pub fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*Inst { const const_inst = try scope.arena().create(Inst.Constant); const_inst.* = .{ diff --git a/src/astgen.zig b/src/astgen.zig index fa624c31ee..d7a7e55f2b 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -1573,8 +1573,8 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node const tree = scope.tree(); const switch_src = tree.token_locs[switch_node.switch_token].start; const target_ptr = try expr(mod, &block_scope.base, .ref, switch_node.expr); - const cases = try scope.arena().alloc(zir.Inst.Switch.Case, switch_node.cases_len); - var kw_args: std.meta.fieldInfo(zir.Inst.Switch, "kw_args").field_type = .{}; + const cases = try scope.arena().alloc(zir.Inst.SwitchBr.Case, switch_node.cases_len); + var kw_args: std.meta.fieldInfo(zir.Inst.SwitchBr, "kw_args").field_type = .{}; // first we gather all the switch items and check else/'_' prongs var case_index: usize = 0; @@ -1643,7 +1643,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node } // Then we add the switch instruction to finish the block. - _ = try addZIRInst(mod, &block_scope.base, switch_src, zir.Inst.Switch, .{ + _ = try addZIRInst(mod, &block_scope.base, switch_src, zir.Inst.SwitchBr, .{ .target_ptr = target_ptr, .cases = cases, }, kw_args); diff --git a/src/codegen.zig b/src/codegen.zig index 9deeab82a5..0fea2ae216 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -786,7 +786,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .unwrap_optional => return self.genUnwrapOptional(inst.castTag(.unwrap_optional).?), .wrap_optional => return self.genWrapOptional(inst.castTag(.wrap_optional).?), .varptr => return self.genVarPtr(inst.castTag(.varptr).?), - .@"switch" => return self.genSwitch(inst.castTag(.@"switch").?), + .switchbr => return self.genSwitch(inst.castTag(.switchbr).?), } } @@ -1990,7 +1990,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return @bitCast(MCValue, inst.codegen.mcv); } - fn genSwitch(self: *Self, inst: *ir.Inst.Switch) !MCValue { + fn genSwitch(self: *Self, inst: *ir.Inst.SwitchBr) !MCValue { switch (arch) { else => return self.fail(inst.base.src, "TODO genSwitch for {}", .{self.target.cpu.arch}), } diff --git a/src/ir.zig b/src/ir.zig index e71935e18a..da20387175 100644 --- a/src/ir.zig +++ b/src/ir.zig @@ -91,7 +91,7 @@ pub const Inst = struct { intcast, unwrap_optional, wrap_optional, - @"switch", + switchbr, pub fn Type(tag: Tag) type { return switch (tag) { @@ -138,7 +138,7 @@ pub const Inst = struct { .constant => Constant, .loop => Loop, .varptr => VarPtr, - .@"switch" => Switch, + .switchbr => SwitchBr, }; } @@ -461,26 +461,45 @@ pub const Inst = struct { } }; - pub const Switch = struct { - pub const base_tag = Tag.@"switch"; + pub const SwitchBr = struct { + pub const base_tag = Tag.switchbr; base: Inst, target_ptr: *Inst, cases: []Case, @"else": ?Body, + /// Set of instructions whose lifetimes end at the start of one of the cases. + /// In same order as cases, deaths[0..case_0_count, case_0_count .. case_1_count, ... , case_n_count ... else_count]. + deaths: [*]*Inst = undefined, + else_index: u32 = 0, + else_deaths: u32 = 0, pub const Case = struct { items: []Value, body: Body, + index: u32 = 0, + deaths: u32 = 0, }; - pub fn operandCount(self: *const Switch) usize { + pub fn operandCount(self: *const SwitchBr) usize { return 1; } - pub fn getOperand(self: *const Switch, index: usize) ?*Inst { - return self.target_ptr; + pub fn getOperand(self: *const SwitchBr, index: usize) ?*Inst { + var i = index; + + if (i < 1) + return self.target_ptr; + i -= 1; + + return null; + } + pub fn caseDeaths(self: *const SwitchBr, case_index: usize) []*Inst { + const case = self.cases[case_index]; + return (self.deaths + case.index)[0..case.deaths]; + } + pub fn elseDeaths(self: *const SwitchBr) []*Inst { + return (self.deaths + self.else_deaths)[0..self.else_deaths]; } - // TODO case body deaths }; }; diff --git a/src/zir.zig b/src/zir.zig index 6bee72c66b..980672ce1b 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -273,7 +273,7 @@ pub const Inst = struct { /// Enum literal enum_literal, /// A switch expression. - @"switch", + switchbr, /// A range in a switch case, `lhs...rhs`. /// Only checks that `lhs >= rhs` if they are ints or floats, everything else is /// validated by the .switch instruction. @@ -396,7 +396,7 @@ pub const Inst = struct { .enum_literal => EnumLiteral, .error_set => ErrorSet, .slice => Slice, - .@"switch" => Switch, + .switchbr => SwitchBr, }; } @@ -513,7 +513,7 @@ pub const Inst = struct { .unreach_nocheck, .@"unreachable", .loop, - .@"switch", + .switchbr, => true, }; } @@ -998,8 +998,8 @@ pub const Inst = struct { }, }; - pub const Switch = struct { - pub const base_tag = Tag.@"switch"; + pub const SwitchBr = struct { + pub const base_tag = Tag.switchbr; base: Inst, positionals: struct { @@ -1275,24 +1275,24 @@ const Writer = struct { } try stream.writeByte(']'); }, - []Inst.Switch.Case => { + []Inst.SwitchBr.Case => { if (param.len == 0) { return stream.writeAll("{}"); } try stream.writeAll("{\n"); - self.indent += 2; for (param) |*case, i| { if (i != 0) { try stream.writeAll(",\n"); } try stream.writeByteNTimes(' ', self.indent); + self.indent += 2; try self.writeParamToStream(stream, &case.items); try stream.writeAll(" => "); try self.writeParamToStream(stream, &case.body); + self.indent -= 2; } try stream.writeByte('\n'); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); + try stream.writeByteNTimes(' ', self.indent - 2); try stream.writeByte('}'); }, else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)), @@ -1707,12 +1707,12 @@ const Parser = struct { try requireEatBytes(self, "]"); return strings.toOwnedSlice(); }, - []Inst.Switch.Case => { + []Inst.SwitchBr.Case => { try requireEatBytes(self, "{"); skipSpace(self); - if (eatByte(self, '}')) return &[0]Inst.Switch.Case{}; + if (eatByte(self, '}')) return &[0]Inst.SwitchBr.Case{}; - var cases = std.ArrayList(Inst.Switch.Case).init(&self.arena.allocator); + var cases = std.ArrayList(Inst.SwitchBr.Case).init(&self.arena.allocator); while (true) { const cur = try cases.addOne(); skipSpace(self); @@ -1824,7 +1824,7 @@ pub fn dumpFn(old_module: IrModule, module_fn: *IrModule.Fn) void { .arena = std.heap.ArenaAllocator.init(allocator), .old_module = &old_module, .next_auto_name = 0, - .names = std.StringHashMap(void).init(allocator), + .names = std.StringArrayHashMap(void).init(allocator), .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator), .indent = 0, .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator), @@ -2547,11 +2547,58 @@ const EmitZIR = struct { }; break :blk &new_inst.base; }, + .switchbr => blk: { + const old_inst = inst.castTag(.switchbr).?; + const case_count = old_inst.cases.len + @boolToInt(old_inst.@"else" != null); + const cases = try self.arena.allocator.alloc(Inst.SwitchBr.Case, case_count); + const new_inst = try self.arena.allocator.create(Inst.SwitchBr); + new_inst.* = .{ + .base = .{ + .src = inst.src, + .tag = Inst.SwitchBr.base_tag, + }, + .positionals = .{ + .target_ptr = try self.resolveInst(new_body, old_inst.target_ptr), + .cases = cases, + }, + .kw_args = .{ + .special_case = if (old_inst.@"else" != null) .@"else" else .none, + .support_range = null, + }, + }; - .varptr => @panic("TODO"), - .@"switch" => { - @panic("TODO"); + var body_tmp = std.ArrayList(*Inst).init(self.allocator); + defer body_tmp.deinit(); + + for (old_inst.cases) |case, i| { + body_tmp.items.len = 0; + + try self.emitBody(case.body, inst_table, &body_tmp); + const items = try self.arena.allocator.alloc(*Inst, case.items.len); + for (case.items) |item, j| { + items[j] = (try self.emitTypedValue(inst.src, .{ + .ty = old_inst.target_ptr.ty.elemType(), + .val = item, + })).inst; + } + + cases[i] = .{ + .items = items, + .body = .{ .instructions = try self.arena.allocator.dupe(*Inst, body_tmp.items) }, + }; + } + if (old_inst.@"else") |some| { + body_tmp.items.len = 0; + + try self.emitBody(some, inst_table, &body_tmp); + cases[cases.len - 1] = .{ + .items = &[0]*Inst{}, + .body = .{ .instructions = try self.arena.allocator.dupe(*Inst, body_tmp.items) }, + }; + } + break :blk &new_inst.base; }, + .varptr => @panic("TODO"), }; try self.metadata.put(new_inst, .{ .deaths = inst.deaths, diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 6c2b7a864a..095fc4ede2 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -135,7 +135,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .slice => return analyzeInstSlice(mod, scope, old_inst.castTag(.slice).?), .slice_start => return analyzeInstSliceStart(mod, scope, old_inst.castTag(.slice_start).?), .import => return analyzeInstImport(mod, scope, old_inst.castTag(.import).?), - .@"switch" => return analyzeInstSwitch(mod, scope, old_inst.castTag(.@"switch").?), + .switchbr => return analyzeInstSwitchBr(mod, scope, old_inst.castTag(.switchbr).?), .switch_range => return analyzeInstSwitchRange(mod, scope, old_inst.castTag(.switch_range).?), } } @@ -1228,7 +1228,7 @@ fn analyzeInstSwitchRange(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) In return mod.constVoid(scope, inst.base.src); } -fn analyzeInstSwitch(mod: *Module, scope: *Scope, inst: *zir.Inst.Switch) InnerError!*Inst { +fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) InnerError!*Inst { const target_ptr = try resolveInst(mod, scope, inst.positionals.target_ptr); const target = try mod.analyzeDeref(scope, inst.base.src, target_ptr, inst.positionals.target_ptr.src); try validateSwitch(mod, scope, target, inst); @@ -1239,17 +1239,7 @@ fn analyzeInstSwitch(mod: *Module, scope: *Scope, inst: *zir.Inst.Switch) InnerE const case_count = inst.positionals.cases.len - @boolToInt(inst.kw_args.special_case != .none); const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src); - const switch_inst = try parent_block.arena.create(Inst.Switch); - switch_inst.* = .{ - .base = .{ - .tag = Inst.Switch.base_tag, - .ty = Type.initTag(.noreturn), - .src = inst.base.src, - }, - .target_ptr = target_ptr, - .@"else" = null, - .cases = try parent_block.arena.alloc(Inst.Switch.Case, case_count), - }; + const cases = try parent_block.arena.alloc(Inst.SwitchBr.Case, case_count); var case_block: Scope.Block = .{ .parent = parent_block, @@ -1281,25 +1271,25 @@ fn analyzeInstSwitch(mod: *Module, scope: *Scope, inst: *zir.Inst.Switch) InnerE try analyzeBody(mod, &case_block.base, case.body); - switch_inst.cases[i] = .{ + cases[i] = .{ .items = try parent_block.arena.dupe(Value, items_tmp.items), .body = .{ .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items) }, }; } - if (inst.kw_args.special_case != .none) { + const else_body = if (inst.kw_args.special_case != .none) blk: { case_block.instructions.items.len = 0; try analyzeBody(mod, &case_block.base, inst.positionals.cases[case_count].body); - switch_inst.@"else" = .{ + break: blk Body{ .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items), }; - } + } else null; - return &switch_inst.base; + return mod.addSwitchBr(parent_block, inst.base.src, target_ptr, cases, else_body); } -fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.Switch) InnerError!void { +fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.SwitchBr) InnerError!void { // validate usage of '_' prongs if (inst.kw_args.special_case == .underscore and target.ty.zigTypeTag() != .Enum) { return mod.fail(scope, inst.base.src, "'_' prong only allowed when switching on non-exhaustive enums", .{}); From 570f610341e0b79a055bb5533ff4d11d0fe6534f Mon Sep 17 00:00:00 2001 From: Vexu Date: Thu, 15 Oct 2020 16:31:03 +0300 Subject: [PATCH 09/19] stage2: fix test harness tmp path handling --- src/test.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test.zig b/src/test.zig index c0ac56aa2d..44c9bfd8fc 100644 --- a/src/test.zig +++ b/src/test.zig @@ -463,10 +463,10 @@ pub const TestContext = struct { var cache_dir = try tmp.dir.makeOpenPath("zig-cache", .{}); defer cache_dir.close(); - const bogus_path = "bogus"; // TODO this will need to be fixed before we can test LLVM extensions + const tmp_path = try std.fs.path.join(arena, &[_][]const u8{ ".", "zig-cache", "tmp", &tmp.sub_path }); const zig_cache_directory: Compilation.Directory = .{ .handle = cache_dir, - .path = try std.fs.path.join(arena, &[_][]const u8{ bogus_path, "zig-cache" }), + .path = try std.fs.path.join(arena, &[_][]const u8{ tmp_path, "zig-cache" }), }; const tmp_src_path = switch (case.extension) { @@ -475,7 +475,7 @@ pub const TestContext = struct { }; var root_pkg: Package = .{ - .root_src_directory = .{ .path = bogus_path, .handle = tmp.dir }, + .root_src_directory = .{ .path = tmp_path, .handle = tmp.dir }, .root_src_path = tmp_src_path, }; @@ -488,7 +488,7 @@ pub const TestContext = struct { }); const emit_directory: Compilation.Directory = .{ - .path = bogus_path, + .path = tmp_path, .handle = tmp.dir, }; const emit_bin: Compilation.EmitLoc = .{ From 95467f324909055d014e989ca9be7b0bb04237c4 Mon Sep 17 00:00:00 2001 From: Vexu Date: Fri, 16 Oct 2020 15:41:43 +0300 Subject: [PATCH 10/19] stage2: dump generated zir with --verbose-ir --- src/Module.zig | 18 +++++++++++++++++ src/zir.zig | 53 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index e821e5863a..dc4a739790 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1054,6 +1054,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .param_types = param_types, }, .{}); + if (self.comp.verbose_ir) { + zir.dumpZir(self.gpa, "fn_type", decl.name, fn_type_scope.instructions.items) catch {}; + } + // We need the memory for the Type to go into the arena for the Decl var decl_arena = std.heap.ArenaAllocator.init(self.gpa); errdefer decl_arena.deinit(); @@ -1127,6 +1131,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { _ = try astgen.addZIRNoOp(self, &gen_scope.base, src, .returnvoid); } + if (self.comp.verbose_ir) { + zir.dumpZir(self.gpa, "fn_body", decl.name, gen_scope.instructions.items) catch {}; + } + const fn_zir = try gen_scope_arena.allocator.create(Fn.ZIR); fn_zir.* = .{ .body = .{ @@ -1258,6 +1266,9 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { const src = tree.token_locs[init_node.firstToken()].start; const init_inst = try astgen.expr(self, &gen_scope.base, init_result_loc, init_node); + if (self.comp.verbose_ir) { + zir.dumpZir(self.gpa, "var_init", decl.name, gen_scope.instructions.items) catch {}; + } var inner_block: Scope.Block = .{ .parent = null, @@ -1299,6 +1310,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .val = Value.initTag(.type_type), }); const var_type = try astgen.expr(self, &type_scope.base, .{ .ty = type_type }, type_node); + if (self.comp.verbose_ir) { + zir.dumpZir(self.gpa, "var_type", decl.name, type_scope.instructions.items) catch {}; + } + const ty = try zir_sema.analyzeBodyValueAsType(self, &block_scope, var_type, .{ .instructions = type_scope.instructions.items, }); @@ -1372,6 +1387,9 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { defer gen_scope.instructions.deinit(self.gpa); _ = try astgen.comptimeExpr(self, &gen_scope.base, .none, comptime_decl.expr); + if (self.comp.verbose_ir) { + zir.dumpZir(self.gpa, "comptime_block", decl.name, gen_scope.instructions.items) catch {}; + } var block_scope: Scope.Block = .{ .parent = null, diff --git a/src/zir.zig b/src/zir.zig index 980672ce1b..e4a3402bac 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -1255,8 +1255,8 @@ const Writer = struct { bool => return stream.writeByte("01"[@boolToInt(param)]), []u8, []const u8 => return stream.print("\"{Z}\"", .{param}), BigIntConst, usize => return stream.print("{}", .{param}), - TypedValue => unreachable, // this is a special case - *IrModule.Decl => unreachable, // this is a special case + TypedValue => return stream.print("TypedValue{{ .ty = {}, .val = {}}}", .{ param.ty, param.val }), + *IrModule.Decl => return stream.print("Decl({s})", .{param.name}), *Inst.Block => { const name = self.block_table.get(param).?; return stream.print("\"{Z}\"", .{name}); @@ -2830,3 +2830,52 @@ const EmitZIR = struct { return decl; } }; + +/// For debugging purposes, like dumpFn but for unanalyzed zir blocks +pub fn dumpZir(allocator: *Allocator, kind: []const u8, decl_name: [*:0]const u8, instructions: []*Inst) !void { + var fib = std.heap.FixedBufferAllocator.init(&[_]u8{}); + var module = Module{ + .decls = &[_]*Decl{}, + .arena = std.heap.ArenaAllocator.init(&fib.allocator), + .metadata = std.AutoHashMap(*Inst, Module.MetaData).init(&fib.allocator), + .body_metadata = std.AutoHashMap(*Module.Body, Module.BodyMetaData).init(&fib.allocator), + }; + var write = Writer{ + .module = &module, + .inst_table = InstPtrTable.init(allocator), + .block_table = std.AutoHashMap(*Inst.Block, []const u8).init(allocator), + .loop_table = std.AutoHashMap(*Inst.Loop, []const u8).init(allocator), + .arena = std.heap.ArenaAllocator.init(allocator), + .indent = 2, + .next_instr_index = 0, + }; + defer write.arena.deinit(); + defer write.inst_table.deinit(); + defer write.block_table.deinit(); + defer write.loop_table.deinit(); + + try write.inst_table.ensureCapacity(@intCast(u32, instructions.len)); + + const stderr = std.io.getStdErr().outStream(); + try stderr.print("{} {s} {{ // unanalyzed\n", .{ kind, decl_name }); + + for (instructions) |inst| { + const my_i = write.next_instr_index; + write.next_instr_index += 1; + + if (inst.cast(Inst.Block)) |block| { + const name = try std.fmt.allocPrint(&write.arena.allocator, "label_{}", .{my_i}); + try write.block_table.put(block, name); + } else if (inst.cast(Inst.Loop)) |loop| { + const name = try std.fmt.allocPrint(&write.arena.allocator, "loop_{}", .{my_i}); + try write.loop_table.put(loop, name); + } + + try write.inst_table.putNoClobber(inst, .{ .inst = inst, .index = my_i, .name = "inst" }); + try stderr.print(" %{} ", .{my_i}); + try write.writeInstToStream(stderr, inst); + try stderr.writeByte('\n'); + } + + try stderr.print("}} // {} {s}\n\n", .{ kind, decl_name }); +} From 7db17a2d89c866efadf9a487acf2f9b0535ba859 Mon Sep 17 00:00:00 2001 From: Vexu Date: Fri, 16 Oct 2020 17:01:05 +0300 Subject: [PATCH 11/19] stage2: redesign switchbr Switchbr now only handles single item prongs. Ranges and multi item prongs are checked with condbrs after the switchbr. --- src/Module.zig | 4 +- src/astgen.zig | 131 +++++++++++++++++++++++++++++------------------ src/ir.zig | 8 +-- src/zir.zig | 54 +++++++------------ src/zir_sema.zig | 95 +++++++++++++--------------------- 5 files changed, 139 insertions(+), 153 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index dc4a739790..a9b4272298 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2122,18 +2122,16 @@ pub fn addSwitchBr( src: usize, target_ptr: *Inst, cases: []Inst.SwitchBr.Case, - else_body: ?Module.Body, ) !*Inst { const inst = try block.arena.create(Inst.SwitchBr); inst.* = .{ .base = .{ .tag = .switchbr, - .ty = Type.initTag(.noreturn), + .ty = Type.initTag(.void), .src = src, }, .target_ptr = target_ptr, .cases = cases, - .@"else" = else_body, }; try block.instructions.append(self.gpa, &inst.base); return &inst.base; diff --git a/src/astgen.zig b/src/astgen.zig index d7a7e55f2b..607a5a32cd 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -1570,16 +1570,33 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node }; defer block_scope.instructions.deinit(mod.gpa); + var item_scope: Scope.GenZIR = .{ + .parent = scope, + .decl = scope.decl().?, + .arena = scope.arena(), + .instructions = .{}, + }; + defer item_scope.instructions.deinit(mod.gpa); + const tree = scope.tree(); const switch_src = tree.token_locs[switch_node.switch_token].start; const target_ptr = try expr(mod, &block_scope.base, .ref, switch_node.expr); - const cases = try scope.arena().alloc(zir.Inst.SwitchBr.Case, switch_node.cases_len); - var kw_args: std.meta.fieldInfo(zir.Inst.SwitchBr, "kw_args").field_type = .{}; + // Add the switch instruction here so that it comes before any range checks. + const switch_inst = (try addZIRInst(mod, &block_scope.base, switch_src, zir.Inst.SwitchBr, .{ + .target_ptr = target_ptr, + .cases = undefined, // populated below + .items = &[_]*zir.Inst{}, // populated below + }, .{})).castTag(.switchbr).?; + + var items = std.ArrayList(*zir.Inst).init(mod.gpa); + defer items.deinit(); + var cases = std.ArrayList(zir.Inst.SwitchBr.Case).init(mod.gpa); + defer cases.deinit(); // first we gather all the switch items and check else/'_' prongs - var case_index: usize = 0; var else_src: ?usize = null; var underscore_src: ?usize = null; + var range_inst: ?*zir.Inst = null; for (switch_node.cases()) |uncasted_case| { const case = uncasted_case.castTag(.SwitchCase).?; const case_src = tree.token_locs[case.firstToken()].start; @@ -1593,12 +1610,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node return mod.fail(scope, case_src, "multiple else prongs in switch expression", .{}); // TODO notes "previous else prong is here" } - kw_args.special_case = .@"else"; else_src = case_src; - cases[cases.len - 1] = .{ - .items = &[0]*zir.Inst{}, - .body = undefined, // filled below - }; continue; } else if (case.items_len == 1 and case.items()[0].tag == .Identifier and mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_")) @@ -1607,48 +1619,44 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node return mod.fail(scope, case_src, "multiple '_' prongs in switch expression", .{}); // TODO notes "previous '_' prong is here" } - kw_args.special_case = .underscore; underscore_src = case_src; - cases[cases.len - 1] = .{ - .items = &[0]*zir.Inst{}, - .body = undefined, // filled below - }; continue; } if (else_src) |some_else| { if (underscore_src) |some_underscore| { - return mod.fail(scope, case_src, "else and '_' prong in switch expression", .{}); + return mod.fail(scope, switch_src, "else and '_' prong in switch expression", .{}); // TODO notes "else prong is here" // TODO notes "'_' prong is here" } } - // Regular case, we need to fill `items`. - const items = try block_scope.arena.alloc(*zir.Inst, case.items_len); - for (case.items()) |item, i| { - if (item.castTag(.Range)) |range| { - items[i] = try switchRange(mod, &block_scope.base, range); - if (kw_args.support_range == null) - kw_args.support_range = items[i]; - } else { - items[i] = try expr(mod, &block_scope.base, .none, item); - } + // TODO and not range + if (case.items_len == 1) { + const item = try expr(mod, &item_scope.base, .none, case.items()[0]); + try cases.append(.{ + .item = item, + .body = undefined, // populated below + }); + continue; } - cases[case_index] = .{ - .items = items, - .body = undefined, // filled below - }; - case_index += 1; + return mod.fail(scope, case_src, "TODO switch ranges", .{}); } - // Then we add the switch instruction to finish the block. - _ = try addZIRInst(mod, &block_scope.base, switch_src, zir.Inst.SwitchBr, .{ - .target_ptr = target_ptr, - .cases = cases, - }, kw_args); + // Actually populate switch instruction values. + if (else_src != null) switch_inst.kw_args.special_prong = .@"else"; + if (underscore_src != null) switch_inst.kw_args.special_prong = .underscore; + switch_inst.positionals.cases = try block_scope.arena.dupe(zir.Inst.SwitchBr.Case, cases.items); + switch_inst.positionals.items = try block_scope.arena.dupe(*zir.Inst, items.items); + switch_inst.kw_args.range = range_inst; + + // Add comptime block containing all prong items first, + _ = try addZIRInstBlock(mod, scope, switch_src, .block_comptime_flat, .{ + .instructions = try block_scope.arena.dupe(*zir.Inst, item_scope.instructions.items), + }); + // then add block containing the switch. const block = try addZIRInstBlock(mod, scope, switch_src, .block, .{ - .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), + .instructions = undefined, // populated below }); // Most result location types can be forwarded directly; however @@ -1668,39 +1676,64 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node defer case_scope.instructions.deinit(mod.gpa); // And finally we fill generate the bodies of each case. - case_index = 0; + var case_index: usize = 0; + var special_case: ?*ast.Node.SwitchCase = null; for (switch_node.cases()) |uncasted_case| { const case = uncasted_case.castTag(.SwitchCase).?; const case_src = tree.token_locs[case.firstToken()].start; // reset without freeing to reduce allocations. defer case_scope.instructions.items.len = 0; - // What index in positionals.cases should this one be placed at. - // For special cases it will be at the end. - var cur_index = case_index; if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) { - // validated above - cur_index = cases.len - 1; + // validated earlier + special_case = case; + continue; } else if (case.items_len == 1 and case.items()[0].tag == .Identifier and mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_")) { - // validated above - cur_index = cases.len - 1; + // validated earlier + special_case = case; + continue; } - // Generate the body of this case. - const case_body = try expr(mod, &case_scope.base, case_rl, case.expr); + if (case.items_len == 1) { + // Generate the body of this case. + const case_body = try expr(mod, &case_scope.base, case_rl, case.expr); + if (!case_body.tag.isNoReturn()) { + _ = try addZIRInst(mod, &case_scope.base, case_src, zir.Inst.Break, .{ + .block = block, + .operand = case_body, + }, .{}); + } + switch_inst.positionals.cases[case_index].body = .{ + .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), + }; + case_index += 1; + continue; + } + return mod.fail(scope, case_src, "TODO switch ranges", .{}); + } + + // Generate else block or a break last to finish the block. + if (special_case) |case| { + const case_src = tree.token_locs[case.firstToken()].start; + const case_body = try expr(mod, &block_scope.base, case_rl, case.expr); if (!case_body.tag.isNoReturn()) { - _ = try addZIRInst(mod, &case_scope.base, case_src, zir.Inst.Break, .{ + _ = try addZIRInst(mod, &block_scope.base, case_src, zir.Inst.Break, .{ .block = block, .operand = case_body, }, .{}); } - cases[cur_index].body = .{ - .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), - }; + } else { + _ = try addZIRInst(mod, &block_scope.base, switch_src, zir.Inst.BreakVoid, .{ + .block = block, + }, .{}); } + // Set block instructions now that it is finished. + block.positionals.body = .{ + .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), + }; return &block.base; } diff --git a/src/ir.zig b/src/ir.zig index da20387175..a6ee75bd88 100644 --- a/src/ir.zig +++ b/src/ir.zig @@ -467,15 +467,12 @@ pub const Inst = struct { base: Inst, target_ptr: *Inst, cases: []Case, - @"else": ?Body, /// Set of instructions whose lifetimes end at the start of one of the cases. /// In same order as cases, deaths[0..case_0_count, case_0_count .. case_1_count, ... , case_n_count ... else_count]. deaths: [*]*Inst = undefined, - else_index: u32 = 0, - else_deaths: u32 = 0, pub const Case = struct { - items: []Value, + item: Value, body: Body, index: u32 = 0, deaths: u32 = 0, @@ -497,9 +494,6 @@ pub const Inst = struct { const case = self.cases[case_index]; return (self.deaths + case.index)[0..case.deaths]; } - pub fn elseDeaths(self: *const SwitchBr) []*Inst { - return (self.deaths + self.else_deaths)[0..self.else_deaths]; - } }; }; diff --git a/src/zir.zig b/src/zir.zig index e4a3402bac..e7ca2ce9c4 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -501,7 +501,7 @@ pub const Inst = struct { .slice, .slice_start, .import, - .switch_range, + .switchbr, => false, .@"break", @@ -513,7 +513,7 @@ pub const Inst = struct { .unreach_nocheck, .@"unreachable", .loop, - .switchbr, + .switch_range, => true, }; } @@ -1005,22 +1005,21 @@ pub const Inst = struct { positionals: struct { target_ptr: *Inst, cases: []Case, + /// List of all individual items and ranges + items: []*Inst, }, kw_args: struct { - /// if not null target must support ranges, (be int) - support_range: ?*Inst = null, - special_case: enum { - /// all of positionals.cases are regular cases + /// Pointer to first range if such exists. + range: ?*Inst = null, + special_prong: enum { none, - /// last case in positionals.cases is an else case @"else", - /// last case in positionals.cases is an underscore case underscore, } = .none, }, pub const Case = struct { - items: []*Inst, + item: *Inst, body: Module.Body, }; }; @@ -1286,7 +1285,7 @@ const Writer = struct { } try stream.writeByteNTimes(' ', self.indent); self.indent += 2; - try self.writeParamToStream(stream, &case.items); + try self.writeParamToStream(stream, &case.item); try stream.writeAll(" => "); try self.writeParamToStream(stream, &case.body); self.indent -= 2; @@ -1716,7 +1715,7 @@ const Parser = struct { while (true) { const cur = try cases.addOne(); skipSpace(self); - cur.items = try self.parseParameterGeneric([]*Inst, body_ctx); + cur.item = try self.parseParameterGeneric(*Inst, body_ctx); skipSpace(self); try requireEatBytes(self, "=>"); cur.body = try self.parseBody(body_ctx); @@ -2549,8 +2548,7 @@ const EmitZIR = struct { }, .switchbr => blk: { const old_inst = inst.castTag(.switchbr).?; - const case_count = old_inst.cases.len + @boolToInt(old_inst.@"else" != null); - const cases = try self.arena.allocator.alloc(Inst.SwitchBr.Case, case_count); + const cases = try self.arena.allocator.alloc(Inst.SwitchBr.Case, old_inst.cases.len); const new_inst = try self.arena.allocator.create(Inst.SwitchBr); new_inst.* = .{ .base = .{ @@ -2560,11 +2558,9 @@ const EmitZIR = struct { .positionals = .{ .target_ptr = try self.resolveInst(new_body, old_inst.target_ptr), .cases = cases, + .items = &[_]*Inst{}, // TODO this should actually be populated }, - .kw_args = .{ - .special_case = if (old_inst.@"else" != null) .@"else" else .none, - .support_range = null, - }, + .kw_args = .{}, }; var body_tmp = std.ArrayList(*Inst).init(self.allocator); @@ -2574,25 +2570,13 @@ const EmitZIR = struct { body_tmp.items.len = 0; try self.emitBody(case.body, inst_table, &body_tmp); - const items = try self.arena.allocator.alloc(*Inst, case.items.len); - for (case.items) |item, j| { - items[j] = (try self.emitTypedValue(inst.src, .{ - .ty = old_inst.target_ptr.ty.elemType(), - .val = item, - })).inst; - } + const item = (try self.emitTypedValue(inst.src, .{ + .ty = old_inst.target_ptr.ty.elemType(), + .val = case.item, + })).inst; cases[i] = .{ - .items = items, - .body = .{ .instructions = try self.arena.allocator.dupe(*Inst, body_tmp.items) }, - }; - } - if (old_inst.@"else") |some| { - body_tmp.items.len = 0; - - try self.emitBody(some, inst_table, &body_tmp); - cases[cases.len - 1] = .{ - .items = &[0]*Inst{}, + .item = item, .body = .{ .instructions = try self.arena.allocator.dupe(*Inst, body_tmp.items) }, }; } @@ -2846,7 +2830,7 @@ pub fn dumpZir(allocator: *Allocator, kind: []const u8, decl_name: [*:0]const u8 .block_table = std.AutoHashMap(*Inst.Block, []const u8).init(allocator), .loop_table = std.AutoHashMap(*Inst.Loop, []const u8).init(allocator), .arena = std.heap.ArenaAllocator.init(allocator), - .indent = 2, + .indent = 4, .next_instr_index = 0, }; defer write.arena.deinit(); diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 095fc4ede2..c0b5b7b7bc 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -553,10 +553,13 @@ fn analyzeInstBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_c try analyzeBody(mod, &child_block.base, inst.positionals.body); - const copied_instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items); - try parent_block.instructions.appendSlice(mod.gpa, copied_instructions); + try parent_block.instructions.appendSlice(mod.gpa, child_block.instructions.items); - return copied_instructions[copied_instructions.len - 1]; + // comptime blocks won't generate any runtime values + if (child_block.instructions.items.len == 0) + return mod.constVoid(scope, inst.base.src); + + return parent_block.instructions.items[parent_block.instructions.items.len - 1]; } fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_comptime: bool) InnerError!*Inst { @@ -1235,11 +1238,8 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In // TODO comptime execution - // excludes else and '_' cases - const case_count = inst.positionals.cases.len - @boolToInt(inst.kw_args.special_case != .none); - const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src); - const cases = try parent_block.arena.alloc(Inst.SwitchBr.Case, case_count); + const cases = try parent_block.arena.alloc(Inst.SwitchBr.Case, inst.positionals.cases.len); var case_block: Scope.Block = .{ .parent = parent_block, @@ -1251,58 +1251,39 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In }; defer case_block.instructions.deinit(mod.gpa); - var items_tmp = std.ArrayList(Value).init(mod.gpa); - defer items_tmp.deinit(); - - for (inst.positionals.cases[0..case_count]) |case, i| { + for (inst.positionals.cases[0..inst.positionals.cases.len]) |case, i| { // Reset without freeing. case_block.instructions.items.len = 0; - items_tmp.items.len = 0; - for (case.items) |item| { - if (item.castTag(.switch_range)) |range| { - return mod.fail(scope, item.src, "genSwitch expand range", .{}); - } - const resolved = try resolveInst(mod, scope, item); - const casted = try mod.coerce(scope, target.ty, resolved); - const val = try mod.resolveConstValue(scope, casted); - try items_tmp.append(val); - } + const resolved = try resolveInst(mod, scope, case.item); + const casted = try mod.coerce(scope, target.ty, resolved); + const item = try mod.resolveConstValue(scope, casted); try analyzeBody(mod, &case_block.base, case.body); cases[i] = .{ - .items = try parent_block.arena.dupe(Value, items_tmp.items), + .item = item, .body = .{ .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items) }, }; } - - const else_body = if (inst.kw_args.special_case != .none) blk: { - case_block.instructions.items.len = 0; - - try analyzeBody(mod, &case_block.base, inst.positionals.cases[case_count].body); - break: blk Body{ - .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items), - }; - } else null; - return mod.addSwitchBr(parent_block, inst.base.src, target_ptr, cases, else_body); + return mod.addSwitchBr(parent_block, inst.base.src, target_ptr, cases); } fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.SwitchBr) InnerError!void { // validate usage of '_' prongs - if (inst.kw_args.special_case == .underscore and target.ty.zigTypeTag() != .Enum) { + if (inst.kw_args.special_prong == .underscore and target.ty.zigTypeTag() != .Enum) { return mod.fail(scope, inst.base.src, "'_' prong only allowed when switching on non-exhaustive enums", .{}); // TODO notes "'_' prong here" inst.positionals.cases[last].src } // check that target type supports ranges - if (inst.kw_args.support_range) |some| { + if (inst.kw_args.range) |range_inst| { switch (target.ty.zigTypeTag()) { .Int, .ComptimeInt, .Float, .ComptimeFloat => {}, else => { return mod.fail(scope, target.src, "ranges not allowed when switching on type {}", .{target.ty}); - // TODO notes "range used here" some.src + // TODO notes "range used here" range_inst.src }, } } @@ -1317,46 +1298,42 @@ fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.Sw .Bool => { var true_count: u8 = 0; var false_count: u8 = 0; - for (inst.positionals.cases) |case| { - for (case.items) |item| { - const resolved = try resolveInst(mod, scope, item); - const casted = try mod.coerce(scope, Type.initTag(.bool), resolved); - if ((try mod.resolveConstValue(scope, casted)).toBool()) { - true_count += 1; - } else { - false_count += 1; - } + for (inst.positionals.items) |item| { + const resolved = try resolveInst(mod, scope, item); + const casted = try mod.coerce(scope, Type.initTag(.bool), resolved); + if ((try mod.resolveConstValue(scope, casted)).toBool()) { + true_count += 1; + } else { + false_count += 1; + } - if (true_count > 1 or false_count > 1) { - return mod.fail(scope, item.src, "duplicate switch value", .{}); - } + if (true_count > 1 or false_count > 1) { + return mod.fail(scope, item.src, "duplicate switch value", .{}); } } - if ((true_count == 0 or false_count == 0) and inst.kw_args.special_case != .@"else") { + if ((true_count == 0 or false_count == 0) and inst.kw_args.special_prong != .@"else") { return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{}); } - if ((true_count == 1 and false_count == 1) and inst.kw_args.special_case == .@"else") { + if ((true_count == 1 and false_count == 1) and inst.kw_args.special_prong == .@"else") { return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{}); } }, .EnumLiteral, .Void, .Fn, .Pointer, .Type => { - if (inst.kw_args.special_case != .@"else") { + if (inst.kw_args.special_prong != .@"else") { return mod.fail(scope, inst.base.src, "else prong required when switching on type '{}'", .{target.ty}); } var seen_values = std.HashMap(Value, usize, Value.hash, Value.eql, std.hash_map.DefaultMaxLoadPercentage).init(mod.gpa); defer seen_values.deinit(); - for (inst.positionals.cases) |case| { - for (case.items) |item| { - const resolved = try resolveInst(mod, scope, item); - const casted = try mod.coerce(scope, target.ty, resolved); - const val = try mod.resolveConstValue(scope, casted); + for (inst.positionals.items) |item| { + const resolved = try resolveInst(mod, scope, item); + const casted = try mod.coerce(scope, target.ty, resolved); + const val = try mod.resolveConstValue(scope, casted); - if (try seen_values.fetchPut(val, item.src)) |prev| { - return mod.fail(scope, item.src, "duplicate switch value", .{}); - // TODO notes "previous value here" prev.value - } + if (try seen_values.fetchPut(val, item.src)) |prev| { + return mod.fail(scope, item.src, "duplicate switch value", .{}); + // TODO notes "previous value here" prev.value } } }, From 3c96d799531dbfaf4127ed2fcaa0e69658f90e23 Mon Sep 17 00:00:00 2001 From: Vexu Date: Fri, 16 Oct 2020 17:05:13 +0300 Subject: [PATCH 12/19] stage2: disallow switching on floats --- src/zir.zig | 2 +- src/zir_sema.zig | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/zir.zig b/src/zir.zig index e7ca2ce9c4..13830947ea 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -275,7 +275,7 @@ pub const Inst = struct { /// A switch expression. switchbr, /// A range in a switch case, `lhs...rhs`. - /// Only checks that `lhs >= rhs` if they are ints or floats, everything else is + /// Only checks that `lhs >= rhs` if they are ints, everything else is /// validated by the .switch instruction. switch_range, diff --git a/src/zir_sema.zig b/src/zir_sema.zig index c0b5b7b7bc..9bad703ce7 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -1214,11 +1214,11 @@ fn analyzeInstSwitchRange(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) In const end = try resolveInst(mod, scope, inst.positionals.rhs); switch (start.ty.zigTypeTag()) { - .Int, .ComptimeInt, .Float, .ComptimeFloat => {}, + .Int, .ComptimeInt => {}, else => return mod.constVoid(scope, inst.base.src), } switch (end.ty.zigTypeTag()) { - .Int, .ComptimeInt, .Float, .ComptimeFloat => {}, + .Int, .ComptimeInt => {}, else => return mod.constVoid(scope, inst.base.src), } if (start.value()) |start_val| { @@ -1280,7 +1280,7 @@ fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.Sw // check that target type supports ranges if (inst.kw_args.range) |range_inst| { switch (target.ty.zigTypeTag()) { - .Int, .ComptimeInt, .Float, .ComptimeFloat => {}, + .Int, .ComptimeInt => {}, else => { return mod.fail(scope, target.src, "ranges not allowed when switching on type {}", .{target.ty}); // TODO notes "range used here" range_inst.src @@ -1291,7 +1291,6 @@ fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.Sw // validate for duplicate items/missing else prong switch (target.ty.zigTypeTag()) { .Int, .ComptimeInt => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Int, .ComptimeInt", .{}), - .Float, .ComptimeFloat => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Float, .ComptimeFloat", .{}), .Enum => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Enum", .{}), .ErrorSet => return mod.fail(scope, inst.base.src, "TODO validateSwitch .ErrorSet", .{}), .Union => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Union", .{}), @@ -1350,6 +1349,8 @@ fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.Sw .Vector, .Frame, .AnyFrame, + .ComptimeFloat, + .Float, => { return mod.fail(scope, target.src, "invalid switch target type '{}'", .{target.ty}); }, From 4155d2ae242d18c0bc280aa22f733bf7dcb6e1f0 Mon Sep 17 00:00:00 2001 From: Vexu Date: Fri, 16 Oct 2020 23:11:35 +0300 Subject: [PATCH 13/19] stage2: switch ranges and multi item prongs --- src/astgen.zig | 239 ++++++++++++++++++++++++++++------------------- src/codegen.zig | 10 +- src/ir.zig | 4 + src/zir.zig | 12 ++- src/zir_sema.zig | 26 +++++- 5 files changed, 193 insertions(+), 98 deletions(-) diff --git a/src/astgen.zig b/src/astgen.zig index 607a5a32cd..e2d30f9905 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -1561,6 +1561,17 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For) return &for_block.base; } +fn getRangeNode(node: *ast.Node) ?*ast.Node.SimpleInfixOp { + var cur = node; + while (true) { + switch (cur.tag) { + .Range => return @fieldParentPtr(ast.Node.SimpleInfixOp, "base", cur), + .GroupedExpression => cur = @fieldParentPtr(ast.Node.GroupedExpression, "base", cur).expr, + else => return null, + } + } +} + fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node.Switch) InnerError!*zir.Inst { var block_scope: Scope.GenZIR = .{ .parent = scope, @@ -1581,6 +1592,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node const tree = scope.tree(); const switch_src = tree.token_locs[switch_node.switch_token].start; const target_ptr = try expr(mod, &block_scope.base, .ref, switch_node.expr); + const target = try addZIRUnOp(mod, &block_scope.base, target_ptr.src, .deref, target_ptr); // Add the switch instruction here so that it comes before any range checks. const switch_inst = (try addZIRInst(mod, &block_scope.base, switch_src, zir.Inst.SwitchBr, .{ .target_ptr = target_ptr, @@ -1593,66 +1605,9 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node var cases = std.ArrayList(zir.Inst.SwitchBr.Case).init(mod.gpa); defer cases.deinit(); - // first we gather all the switch items and check else/'_' prongs - var else_src: ?usize = null; - var underscore_src: ?usize = null; - var range_inst: ?*zir.Inst = null; - for (switch_node.cases()) |uncasted_case| { - const case = uncasted_case.castTag(.SwitchCase).?; - const case_src = tree.token_locs[case.firstToken()].start; - - if (case.payload != null) { - return mod.fail(scope, case_src, "TODO switch case payload capture", .{}); - } - - if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) { - if (else_src) |src| { - return mod.fail(scope, case_src, "multiple else prongs in switch expression", .{}); - // TODO notes "previous else prong is here" - } - else_src = case_src; - continue; - } else if (case.items_len == 1 and case.items()[0].tag == .Identifier and - mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_")) - { - if (underscore_src) |src| { - return mod.fail(scope, case_src, "multiple '_' prongs in switch expression", .{}); - // TODO notes "previous '_' prong is here" - } - underscore_src = case_src; - continue; - } - - if (else_src) |some_else| { - if (underscore_src) |some_underscore| { - return mod.fail(scope, switch_src, "else and '_' prong in switch expression", .{}); - // TODO notes "else prong is here" - // TODO notes "'_' prong is here" - } - } - - // TODO and not range - if (case.items_len == 1) { - const item = try expr(mod, &item_scope.base, .none, case.items()[0]); - try cases.append(.{ - .item = item, - .body = undefined, // populated below - }); - continue; - } - return mod.fail(scope, case_src, "TODO switch ranges", .{}); - } - - // Actually populate switch instruction values. - if (else_src != null) switch_inst.kw_args.special_prong = .@"else"; - if (underscore_src != null) switch_inst.kw_args.special_prong = .underscore; - switch_inst.positionals.cases = try block_scope.arena.dupe(zir.Inst.SwitchBr.Case, cases.items); - switch_inst.positionals.items = try block_scope.arena.dupe(*zir.Inst, items.items); - switch_inst.kw_args.range = range_inst; - // Add comptime block containing all prong items first, - _ = try addZIRInstBlock(mod, scope, switch_src, .block_comptime_flat, .{ - .instructions = try block_scope.arena.dupe(*zir.Inst, item_scope.instructions.items), + const item_block = try addZIRInstBlock(mod, scope, switch_src, .block_comptime_flat, .{ + .instructions = undefined, // populated below }); // then add block containing the switch. const block = try addZIRInstBlock(mod, scope, switch_src, .block, .{ @@ -1675,59 +1630,148 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node }; defer case_scope.instructions.deinit(mod.gpa); - // And finally we fill generate the bodies of each case. - var case_index: usize = 0; + // first we gather all the switch items and check else/'_' prongs + var else_src: ?usize = null; + var underscore_src: ?usize = null; + var first_range: ?*zir.Inst = null; var special_case: ?*ast.Node.SwitchCase = null; for (switch_node.cases()) |uncasted_case| { const case = uncasted_case.castTag(.SwitchCase).?; const case_src = tree.token_locs[case.firstToken()].start; // reset without freeing to reduce allocations. - defer case_scope.instructions.items.len = 0; + case_scope.instructions.items.len = 0; + assert(case.items_len != 0); + // Check for else/_ prong, those are handled last. if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) { - // validated earlier + if (else_src) |src| { + return mod.fail(scope, case_src, "multiple else prongs in switch expression", .{}); + // TODO notes "previous else prong is here" + } + else_src = case_src; special_case = case; continue; } else if (case.items_len == 1 and case.items()[0].tag == .Identifier and mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_")) { - // validated earlier + if (underscore_src) |src| { + return mod.fail(scope, case_src, "multiple '_' prongs in switch expression", .{}); + // TODO notes "previous '_' prong is here" + } + underscore_src = case_src; special_case = case; continue; } - if (case.items_len == 1) { - // Generate the body of this case. - const case_body = try expr(mod, &case_scope.base, case_rl, case.expr); - if (!case_body.tag.isNoReturn()) { - _ = try addZIRInst(mod, &case_scope.base, case_src, zir.Inst.Break, .{ - .block = block, - .operand = case_body, - }, .{}); + if (else_src) |some_else| { + if (underscore_src) |some_underscore| { + return mod.fail(scope, switch_src, "else and '_' prong in switch expression", .{}); + // TODO notes "else prong is here" + // TODO notes "'_' prong is here" } - switch_inst.positionals.cases[case_index].body = .{ - .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), - }; - case_index += 1; + } + + // If this is a simple one item prong then it is handled by the switchbr. + if (case.items_len == 1 and getRangeNode(case.items()[0]) == null) { + const item = try expr(mod, &item_scope.base, .none, case.items()[0]); + try items.append(item); + try switchCaseExpr(mod, &case_scope.base, case_rl, block, case); + + try cases.append(.{ + .item = item, + .body = .{ .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items) }, + }); continue; } - return mod.fail(scope, case_src, "TODO switch ranges", .{}); + + // TODO if the case has few items and no ranges it might be better + // to just handle them as switch prongs. + + // Check if the target matches any of the items. + // 1, 2, 3..6 will result in + // target == 1 or target == 2 or (target >= 3 and target <= 6) + var any_ok: ?*zir.Inst = null; + for (case.items()) |item| { + if (getRangeNode(item)) |range| { + const start = try expr(mod, &item_scope.base, .none, range.lhs); + const end = try expr(mod, &item_scope.base, .none, range.rhs); + const range_src = tree.token_locs[range.op_token].start; + const range_inst = try addZIRBinOp(mod, &item_scope.base, range_src, .switch_range, start, end); + try items.append(range_inst); + if (first_range == null) first_range = range_inst; + + // target >= start and target <= end + const range_start_ok = try addZIRBinOp(mod, &block_scope.base, range_src, .cmp_gte, target, start); + const range_end_ok = try addZIRBinOp(mod, &block_scope.base, range_src, .cmp_lte, target, end); + const range_ok = try addZIRBinOp(mod, &block_scope.base, range_src, .booland, range_start_ok, range_end_ok); + + if (any_ok) |some| { + any_ok = try addZIRBinOp(mod, &block_scope.base, range_src, .boolor, some, range_ok); + } else { + any_ok = range_ok; + } + continue; + } + + const item_inst = try expr(mod, &item_scope.base, .none, item); + try items.append(item_inst); + const cpm_ok = try addZIRBinOp(mod, &block_scope.base, item_inst.src, .cmp_eq, target, item_inst); + + if (any_ok) |some| { + any_ok = try addZIRBinOp(mod, &block_scope.base, item_inst.src, .boolor, some, cpm_ok); + } else { + any_ok = cpm_ok; + } + } + + const condbr = try addZIRInstSpecial(mod, &block_scope.base, case_src, zir.Inst.CondBr, .{ + .condition = any_ok.?, + .then_body = undefined, // populated below + .else_body = undefined, // populated below + }, .{}); + + try switchCaseExpr(mod, &case_scope.base, case_rl, block, case); + condbr.positionals.then_body = .{ + .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), + }; + + // reset to add the empty block + case_scope.instructions.items.len = 0; + const empty_block = try addZIRInstBlock(mod, &case_scope.base, case_src, .block, .{ + .instructions = undefined, // populated below + }); + condbr.positionals.else_body = .{ + .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), + }; + + // reset to add a break to the empty block + case_scope.instructions.items.len = 0; + _ = try addZIRInst(mod, &case_scope.base, case_src, zir.Inst.BreakVoid, .{ + .block = empty_block, + }, .{}); + empty_block.positionals.body = .{ + .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), + }; } + // All items have been generated, add the instructions to the comptime block. + item_block.positionals.body = .{ + .instructions = try block_scope.arena.dupe(*zir.Inst, item_scope.instructions.items), + }; + + // Actually populate switch instruction values. + if (else_src != null) switch_inst.kw_args.special_prong = .@"else"; + if (underscore_src != null) switch_inst.kw_args.special_prong = .underscore; + switch_inst.positionals.cases = try block_scope.arena.dupe(zir.Inst.SwitchBr.Case, cases.items); + switch_inst.positionals.items = try block_scope.arena.dupe(*zir.Inst, items.items); + switch_inst.kw_args.range = first_range; + // Generate else block or a break last to finish the block. if (special_case) |case| { - const case_src = tree.token_locs[case.firstToken()].start; - const case_body = try expr(mod, &block_scope.base, case_rl, case.expr); - if (!case_body.tag.isNoReturn()) { - _ = try addZIRInst(mod, &block_scope.base, case_src, zir.Inst.Break, .{ - .block = block, - .operand = case_body, - }, .{}); - } + try switchCaseExpr(mod, &block_scope.base, case_rl, block, case); } else { - _ = try addZIRInst(mod, &block_scope.base, switch_src, zir.Inst.BreakVoid, .{ - .block = block, - }, .{}); + // Not handling all possible cases is a compile error. + _ = try addZIRNoOp(mod, &block_scope.base, switch_src, .unreach_nocheck); } // Set block instructions now that it is finished. @@ -1737,15 +1781,20 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node return &block.base; } -/// Only used for `a...b` in switches. -fn switchRange(mod: *Module, scope: *Scope, node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst { +fn switchCaseExpr(mod: *Module, scope: *Scope, rl: ResultLoc, block: *zir.Inst.Block, case: *ast.Node.SwitchCase) !void { const tree = scope.tree(); - const src = tree.token_locs[node.op_token].start; + const case_src = tree.token_locs[case.firstToken()].start; + if (case.payload != null) { + return mod.fail(scope, case_src, "TODO switch case payload capture", .{}); + } - const start = try expr(mod, scope, .none, node.lhs); - const end = try expr(mod, scope, .none, node.rhs); - - return try addZIRBinOp(mod, scope, src, .switch_range, start, end); + const case_body = try expr(mod, scope, rl, case.expr); + if (!case_body.tag.isNoReturn()) { + _ = try addZIRInst(mod, scope, case_src, zir.Inst.Break, .{ + .block = block, + .operand = case_body, + }, .{}); + } } fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst { diff --git a/src/codegen.zig b/src/codegen.zig index 0fea2ae216..1745f86a94 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -758,6 +758,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .br => return self.genBr(inst.castTag(.br).?), .breakpoint => return self.genBreakpoint(inst.src), .brvoid => return self.genBrVoid(inst.castTag(.brvoid).?), + .booland => return self.genBoolOp(inst.castTag(.booland).?), + .boolor => return self.genBoolOp(inst.castTag(.boolor).?), .call => return self.genCall(inst.castTag(.call).?), .cmp_lt => return self.genCmp(inst.castTag(.cmp_lt).?, .lt), .cmp_lte => return self.genCmp(inst.castTag(.cmp_lte).?, .lte), @@ -782,11 +784,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .retvoid => return self.genRetVoid(inst.castTag(.retvoid).?), .store => return self.genStore(inst.castTag(.store).?), .sub => return self.genSub(inst.castTag(.sub).?), + .switchbr => return self.genSwitch(inst.castTag(.switchbr).?), .unreach => return MCValue{ .unreach = {} }, .unwrap_optional => return self.genUnwrapOptional(inst.castTag(.unwrap_optional).?), .wrap_optional => return self.genWrapOptional(inst.castTag(.wrap_optional).?), .varptr => return self.genVarPtr(inst.castTag(.varptr).?), - .switchbr => return self.genSwitch(inst.castTag(.switchbr).?), } } @@ -2030,6 +2032,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.brVoid(inst.base.src, inst.block); } + fn genBoolOp(self: *Self, inst: *ir.Inst.BinOp) !MCValue { + switch (arch) { + else => return self.fail(inst.base.src, "TODO genBoolOp for {}", .{self.target.cpu.arch}), + } + } + fn brVoid(self: *Self, src: usize, block: *ir.Inst.Block) !MCValue { // Emit a jump with a relocation. It will be patched up after the block ends. try block.codegen.relocs.ensureCapacity(self.gpa, block.codegen.relocs.items.len + 1); diff --git a/src/ir.zig b/src/ir.zig index a6ee75bd88..e81fe38cfa 100644 --- a/src/ir.zig +++ b/src/ir.zig @@ -74,6 +74,8 @@ pub const Inst = struct { isnonnull, isnull, iserr, + booland, + boolor, /// Read a value from a pointer. load, loop, @@ -126,6 +128,8 @@ pub const Inst = struct { .cmp_gt, .cmp_neq, .store, + .booland, + .boolor, => BinOp, .arg => Arg, diff --git a/src/zir.zig b/src/zir.zig index 13830947ea..b86e5778cb 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -85,8 +85,12 @@ pub const Inst = struct { block_comptime, /// Same as `block_flat` but additionally makes the inner instructions execute at comptime. block_comptime_flat, + /// Boolean AND. See also `bitand`. + booland, /// Boolean NOT. See also `bitnot`. boolnot, + /// Boolean OR. See also `bitor`. + boolor, /// Return a value from a `Block`. @"break", breakpoint, @@ -333,6 +337,8 @@ pub const Inst = struct { .array_type, .bitand, .bitor, + .booland, + .boolor, .div, .mod_rem, .mul, @@ -425,6 +431,8 @@ pub const Inst = struct { .block_comptime, .block_comptime_flat, .boolnot, + .booland, + .boolor, .breakpoint, .call, .cmp_lt, @@ -502,6 +510,7 @@ pub const Inst = struct { .slice_start, .import, .switchbr, + .switch_range, => false, .@"break", @@ -513,7 +522,6 @@ pub const Inst = struct { .unreach_nocheck, .@"unreachable", .loop, - .switch_range, => true, }; } @@ -2320,6 +2328,8 @@ const EmitZIR = struct { .cmp_gte => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_gte).?, .cmp_gte), .cmp_gt => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_gt).?, .cmp_gt), .cmp_neq => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_neq).?, .cmp_neq), + .booland => try self.emitBinOp(inst.src, new_body, inst.castTag(.booland).?, .booland), + .boolor => try self.emitBinOp(inst.src, new_body, inst.castTag(.boolor).?, .boolor), .bitcast => try self.emitCast(inst.src, new_body, inst.castTag(.bitcast).?, .bitcast), .intcast => try self.emitCast(inst.src, new_body, inst.castTag(.intcast).?, .intcast), diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 9bad703ce7..7c6618661d 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -137,6 +137,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .import => return analyzeInstImport(mod, scope, old_inst.castTag(.import).?), .switchbr => return analyzeInstSwitchBr(mod, scope, old_inst.castTag(.switchbr).?), .switch_range => return analyzeInstSwitchRange(mod, scope, old_inst.castTag(.switch_range).?), + .booland => return analyzeInstBoolOp(mod, scope, old_inst.castTag(.booland).?), + .boolor => return analyzeInstBoolOp(mod, scope, old_inst.castTag(.boolor).?), } } @@ -1224,7 +1226,7 @@ fn analyzeInstSwitchRange(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) In if (start.value()) |start_val| { if (end.value()) |end_val| { if (start_val.compare(.gte, end_val)) { - return mod.fail(scope, inst.base.src, "range start value is greater than the end value", .{}); + return mod.fail(scope, inst.base.src, "range start value must be smaller than the end value", .{}); } } } @@ -1609,6 +1611,28 @@ fn analyzeInstBoolNot(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerEr return mod.addUnOp(b, inst.base.src, bool_type, .not, operand); } +fn analyzeInstBoolOp(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { + const bool_type = Type.initTag(.bool); + const uncasted_lhs = try resolveInst(mod, scope, inst.positionals.lhs); + const lhs = try mod.coerce(scope, bool_type, uncasted_lhs); + const uncasted_rhs = try resolveInst(mod, scope, inst.positionals.rhs); + const rhs = try mod.coerce(scope, bool_type, uncasted_rhs); + + const is_bool_or = inst.base.tag == .boolor; + + if (lhs.value()) |lhs_val| { + if (rhs.value()) |rhs_val| { + if (is_bool_or) { + return mod.constBool(scope, inst.base.src, lhs_val.toBool() or rhs_val.toBool()); + } else { + return mod.constBool(scope, inst.base.src, lhs_val.toBool() and rhs_val.toBool()); + } + } + } + const b = try mod.requireRuntimeBlock(scope, inst.base.src); + return mod.addBinOp(b, inst.base.src, bool_type, if (is_bool_or) .boolor else .booland, lhs, rhs); +} + fn analyzeInstIsNonNull(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp, invert_logic: bool) InnerError!*Inst { const operand = try resolveInst(mod, scope, inst.positionals.operand); return mod.analyzeIsNull(scope, inst.base.src, operand, invert_logic); From 12e4c648ccc68f5190dd5076088b3959ebeee65d Mon Sep 17 00:00:00 2001 From: Vexu Date: Sat, 17 Oct 2020 01:09:42 +0300 Subject: [PATCH 14/19] stage2: implement switch validation for integers --- src/RangeSet.zig | 76 ++++++++++++++++++++++++++++++++++++++++++++++++ src/type.zig | 72 +++++++++++++++++++++++++++++++++++++++++++++ src/zir_sema.zig | 50 +++++++++++++++++++++++++++++-- 3 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 src/RangeSet.zig diff --git a/src/RangeSet.zig b/src/RangeSet.zig new file mode 100644 index 0000000000..5daacbbf08 --- /dev/null +++ b/src/RangeSet.zig @@ -0,0 +1,76 @@ +const std = @import("std"); +const Order = std.math.Order; +const Value = @import("value.zig").Value; +const RangeSet = @This(); + +ranges: std.ArrayList(Range), + +pub const Range = struct { + start: Value, + end: Value, + src: usize, +}; + +pub fn init(allocator: *std.mem.Allocator) RangeSet { + return .{ + .ranges = std.ArrayList(Range).init(allocator), + }; +} + +pub fn deinit(self: *RangeSet) void { + self.ranges.deinit(); +} + +pub fn add(self: *RangeSet, start: Value, end: Value, src: usize) !?usize { + for (self.ranges.items) |range| { + if ((start.compare(.gte, range.start) and start.compare(.lte, range.end)) or + (end.compare(.gte, range.start) and end.compare(.lte, range.end))) + { + // ranges overlap + return range.src; + } + } + try self.ranges.append(.{ + .start = start, + .end = end, + .src = src, + }); + return null; +} + +/// Assumes a and b do not overlap +fn lessThan(_: void, a: Range, b: Range) bool { + return a.start.compare(.lt, b.start); +} + +pub fn spans(self: *RangeSet, start: Value, end: Value) !bool { + std.sort.sort(Range, self.ranges.items, {}, lessThan); + + if (!self.ranges.items[0].start.eql(start) or + !self.ranges.items[self.ranges.items.len - 1].end.eql(end)) + { + return false; + } + + var space: Value.BigIntSpace = undefined; + + var counter = try std.math.big.int.Managed.init(self.ranges.allocator); + defer counter.deinit(); + + // look for gaps + for (self.ranges.items[1..]) |cur, i| { + // i starts counting from the second item. + const prev = self.ranges.items[i]; + + // prev.end + 1 == cur.start + try counter.copy(prev.end.toBigInt(&space)); + try counter.addScalar(counter.toConst(), 1); + + const cur_start_int = cur.start.toBigInt(&space); + if (!cur_start_int.eq(counter.toConst())) { + return false; + } + } + + return true; +} diff --git a/src/type.zig b/src/type.zig index f07df290fc..a706ad34bc 100644 --- a/src/type.zig +++ b/src/type.zig @@ -2863,6 +2863,78 @@ pub const Type = extern union { }; } + /// Asserts that self.zigTypeTag() == .Int. + pub fn minInt(self: Type, arena: *std.heap.ArenaAllocator, target: Target) !Value { + assert(self.zigTypeTag() == .Int); + const info = self.intInfo(target); + + if (!info.signed) { + return Value.initTag(.zero); + } + + if ((info.bits - 1) <= std.math.maxInt(u6)) { + const payload = try arena.allocator.create(Value.Payload.Int_i64); + payload.* = .{ + .int = -(@as(i64, 1) << @truncate(u6, info.bits - 1)), + }; + return Value.initPayload(&payload.base); + } + + var res = try std.math.big.int.Managed.initSet(&arena.allocator, 1); + try res.shiftLeft(res, info.bits - 1); + res.negate(); + + const res_const = res.toConst(); + if (res_const.positive) { + const val_payload = try arena.allocator.create(Value.Payload.IntBigPositive); + val_payload.* = .{ .limbs = res_const.limbs }; + return Value.initPayload(&val_payload.base); + } else { + const val_payload = try arena.allocator.create(Value.Payload.IntBigNegative); + val_payload.* = .{ .limbs = res_const.limbs }; + return Value.initPayload(&val_payload.base); + } + } + + /// Asserts that self.zigTypeTag() == .Int. + pub fn maxInt(self: Type, arena: *std.heap.ArenaAllocator, target: Target) !Value { + assert(self.zigTypeTag() == .Int); + const info = self.intInfo(target); + + if (info.signed and (info.bits - 1) <= std.math.maxInt(u6)) { + const payload = try arena.allocator.create(Value.Payload.Int_i64); + payload.* = .{ + .int = (@as(i64, 1) << @truncate(u6, info.bits - 1)) - 1, + }; + return Value.initPayload(&payload.base); + } else if (!info.signed and info.bits <= std.math.maxInt(u6)) { + const payload = try arena.allocator.create(Value.Payload.Int_u64); + payload.* = .{ + .int = (@as(u64, 1) << @truncate(u6, info.bits)) - 1, + }; + return Value.initPayload(&payload.base); + } + + var res = try std.math.big.int.Managed.initSet(&arena.allocator, 1); + try res.shiftLeft(res, info.bits - @boolToInt(info.signed)); + const one = std.math.big.int.Const{ + .limbs = &[_]std.math.big.Limb{1}, + .positive = true, + }; + res.sub(res.toConst(), one) catch unreachable; + + const res_const = res.toConst(); + if (res_const.positive) { + const val_payload = try arena.allocator.create(Value.Payload.IntBigPositive); + val_payload.* = .{ .limbs = res_const.limbs }; + return Value.initPayload(&val_payload.base); + } else { + const val_payload = try arena.allocator.create(Value.Payload.IntBigNegative); + val_payload.* = .{ .limbs = res_const.limbs }; + return Value.initPayload(&val_payload.base); + } + } + /// 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/zir_sema.zig b/src/zir_sema.zig index 7c6618661d..e438b53c94 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -1268,7 +1268,7 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In .body = .{ .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items) }, }; } - + return mod.addSwitchBr(parent_block, inst.base.src, target_ptr, cases); } @@ -1292,10 +1292,56 @@ fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.Sw // validate for duplicate items/missing else prong switch (target.ty.zigTypeTag()) { - .Int, .ComptimeInt => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Int, .ComptimeInt", .{}), .Enum => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Enum", .{}), .ErrorSet => return mod.fail(scope, inst.base.src, "TODO validateSwitch .ErrorSet", .{}), .Union => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Union", .{}), + .Int, .ComptimeInt => { + var range_set = @import("RangeSet.zig").init(mod.gpa); + defer range_set.deinit(); + + for (inst.positionals.items) |item| { + const maybe_src = if (item.castTag(.switch_range)) |range| blk: { + const start_resolved = try resolveInst(mod, scope, range.positionals.lhs); + const start_casted = try mod.coerce(scope, target.ty, start_resolved); + const end_resolved = try resolveInst(mod, scope, range.positionals.rhs); + const end_casted = try mod.coerce(scope, target.ty, end_resolved); + + break :blk try range_set.add( + try mod.resolveConstValue(scope, start_casted), + try mod.resolveConstValue(scope, end_casted), + item.src, + ); + } else blk: { + const resolved = try resolveInst(mod, scope, item); + const casted = try mod.coerce(scope, target.ty, resolved); + const value = try mod.resolveConstValue(scope, casted); + break :blk try range_set.add(value, value, item.src); + }; + + if (maybe_src) |previous_src| { + return mod.fail(scope, item.src, "duplicate switch value", .{}); + // TODO notes "previous value is here" previous_src + } + } + + if (target.ty.zigTypeTag() == .Int) { + var arena = std.heap.ArenaAllocator.init(mod.gpa); + defer arena.deinit(); + + const start = try target.ty.minInt(&arena, mod.getTarget()); + const end = try target.ty.maxInt(&arena, mod.getTarget()); + if (try range_set.spans(start, end)) { + if (inst.kw_args.special_prong == .@"else") { + return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{}); + } + return; + } + } + + if (inst.kw_args.special_prong != .@"else") { + return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{}); + } + }, .Bool => { var true_count: u8 = 0; var false_count: u8 = 0; From 769d5a9c435c5c145983e4d3af1706924248e367 Mon Sep 17 00:00:00 2001 From: Vexu Date: Tue, 20 Oct 2020 22:00:30 +0300 Subject: [PATCH 15/19] stage2: switch comptime execution --- src/Module.zig | 4 ++- src/astgen.zig | 63 +++++++++++++++++++++++++++--------------------- src/ir.zig | 8 +++++- src/zir.zig | 12 +++++++-- src/zir_sema.zig | 34 ++++++++++++++++++++------ 5 files changed, 82 insertions(+), 39 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index a9b4272298..81dc72e601 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2122,16 +2122,18 @@ pub fn addSwitchBr( src: usize, target_ptr: *Inst, cases: []Inst.SwitchBr.Case, + else_body: ir.Body, ) !*Inst { const inst = try block.arena.create(Inst.SwitchBr); inst.* = .{ .base = .{ .tag = .switchbr, - .ty = Type.initTag(.void), + .ty = Type.initTag(.noreturn), .src = src, }, .target_ptr = target_ptr, .cases = cases, + .else_body = else_body, }; try block.instructions.append(self.gpa, &inst.base); return &inst.base; diff --git a/src/astgen.zig b/src/astgen.zig index e2d30f9905..f098e2b8c0 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -1581,14 +1581,6 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node }; defer block_scope.instructions.deinit(mod.gpa); - var item_scope: Scope.GenZIR = .{ - .parent = scope, - .decl = scope.decl().?, - .arena = scope.arena(), - .instructions = .{}, - }; - defer item_scope.instructions.deinit(mod.gpa); - const tree = scope.tree(); const switch_src = tree.token_locs[switch_node.switch_token].start; const target_ptr = try expr(mod, &block_scope.base, .ref, switch_node.expr); @@ -1598,6 +1590,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node .target_ptr = target_ptr, .cases = undefined, // populated below .items = &[_]*zir.Inst{}, // populated below + .else_body = undefined, // populated below }, .{})).castTag(.switchbr).?; var items = std.ArrayList(*zir.Inst).init(mod.gpa); @@ -1611,7 +1604,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node }); // then add block containing the switch. const block = try addZIRInstBlock(mod, scope, switch_src, .block, .{ - .instructions = undefined, // populated below + .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), }); // Most result location types can be forwarded directly; however @@ -1622,6 +1615,14 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block }, }; + var item_scope: Scope.GenZIR = .{ + .parent = scope, + .decl = scope.decl().?, + .arena = scope.arena(), + .instructions = .{}, + }; + defer item_scope.instructions.deinit(mod.gpa); + var case_scope: Scope.GenZIR = .{ .parent = scope, .decl = block_scope.decl, @@ -1630,6 +1631,14 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node }; defer case_scope.instructions.deinit(mod.gpa); + var else_scope: Scope.GenZIR = .{ + .parent = scope, + .decl = block_scope.decl, + .arena = block_scope.arena, + .instructions = .{}, + }; + defer else_scope.instructions.deinit(mod.gpa); + // first we gather all the switch items and check else/'_' prongs var else_src: ?usize = null; var underscore_src: ?usize = null; @@ -1701,12 +1710,12 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node if (first_range == null) first_range = range_inst; // target >= start and target <= end - const range_start_ok = try addZIRBinOp(mod, &block_scope.base, range_src, .cmp_gte, target, start); - const range_end_ok = try addZIRBinOp(mod, &block_scope.base, range_src, .cmp_lte, target, end); - const range_ok = try addZIRBinOp(mod, &block_scope.base, range_src, .booland, range_start_ok, range_end_ok); + const range_start_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_gte, target, start); + const range_end_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_lte, target, end); + const range_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .booland, range_start_ok, range_end_ok); if (any_ok) |some| { - any_ok = try addZIRBinOp(mod, &block_scope.base, range_src, .boolor, some, range_ok); + any_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .boolor, some, range_ok); } else { any_ok = range_ok; } @@ -1715,16 +1724,16 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node const item_inst = try expr(mod, &item_scope.base, .none, item); try items.append(item_inst); - const cpm_ok = try addZIRBinOp(mod, &block_scope.base, item_inst.src, .cmp_eq, target, item_inst); + const cpm_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .cmp_eq, target, item_inst); if (any_ok) |some| { - any_ok = try addZIRBinOp(mod, &block_scope.base, item_inst.src, .boolor, some, cpm_ok); + any_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .boolor, some, cpm_ok); } else { any_ok = cpm_ok; } } - const condbr = try addZIRInstSpecial(mod, &block_scope.base, case_src, zir.Inst.CondBr, .{ + const condbr = try addZIRInstSpecial(mod, &else_scope.base, case_src, zir.Inst.CondBr, .{ .condition = any_ok.?, .then_body = undefined, // populated below .else_body = undefined, // populated below @@ -1754,6 +1763,14 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node }; } + // Generate else block or a break last to finish the block. + if (special_case) |case| { + try switchCaseExpr(mod, &else_scope.base, case_rl, block, case); + } else { + // Not handling all possible cases is a compile error. + _ = try addZIRNoOp(mod, &else_scope.base, switch_src, .unreach_nocheck); + } + // All items have been generated, add the instructions to the comptime block. item_block.positionals.body = .{ .instructions = try block_scope.arena.dupe(*zir.Inst, item_scope.instructions.items), @@ -1765,18 +1782,8 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node switch_inst.positionals.cases = try block_scope.arena.dupe(zir.Inst.SwitchBr.Case, cases.items); switch_inst.positionals.items = try block_scope.arena.dupe(*zir.Inst, items.items); switch_inst.kw_args.range = first_range; - - // Generate else block or a break last to finish the block. - if (special_case) |case| { - try switchCaseExpr(mod, &block_scope.base, case_rl, block, case); - } else { - // Not handling all possible cases is a compile error. - _ = try addZIRNoOp(mod, &block_scope.base, switch_src, .unreach_nocheck); - } - - // Set block instructions now that it is finished. - block.positionals.body = .{ - .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), + switch_inst.positionals.else_body = .{ + .instructions = try block_scope.arena.dupe(*zir.Inst, else_scope.instructions.items), }; return &block.base; } diff --git a/src/ir.zig b/src/ir.zig index e81fe38cfa..1a31044ab7 100644 --- a/src/ir.zig +++ b/src/ir.zig @@ -472,8 +472,11 @@ pub const Inst = struct { target_ptr: *Inst, cases: []Case, /// Set of instructions whose lifetimes end at the start of one of the cases. - /// In same order as cases, deaths[0..case_0_count, case_0_count .. case_1_count, ... , case_n_count ... else_count]. + /// In same order as cases, deaths[0..case_0_count, case_0_count .. case_1_count, ... ]. deaths: [*]*Inst = undefined, + else_index: u32 = 0, + else_deaths: u32 = 0, + else_body: Body, pub const Case = struct { item: Value, @@ -498,6 +501,9 @@ pub const Inst = struct { const case = self.cases[case_index]; return (self.deaths + case.index)[0..case.deaths]; } + pub fn elseDeaths(self: *const SwitchBr) []*Inst { + return (self.deaths + self.else_index)[0..self.else_deaths]; + } }; }; diff --git a/src/zir.zig b/src/zir.zig index b86e5778cb..da93a7250e 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -509,7 +509,6 @@ pub const Inst = struct { .slice, .slice_start, .import, - .switchbr, .switch_range, => false, @@ -522,6 +521,7 @@ pub const Inst = struct { .unreach_nocheck, .@"unreachable", .loop, + .switchbr, => true, }; } @@ -1012,9 +1012,10 @@ pub const Inst = struct { positionals: struct { target_ptr: *Inst, - cases: []Case, /// List of all individual items and ranges items: []*Inst, + cases: []Case, + else_body: Module.Body, }, kw_args: struct { /// Pointer to first range if such exists. @@ -2569,6 +2570,7 @@ const EmitZIR = struct { .target_ptr = try self.resolveInst(new_body, old_inst.target_ptr), .cases = cases, .items = &[_]*Inst{}, // TODO this should actually be populated + .else_body = undefined, // populated below }, .kw_args = .{}, }; @@ -2590,6 +2592,12 @@ const EmitZIR = struct { .body = .{ .instructions = try self.arena.allocator.dupe(*Inst, body_tmp.items) }, }; } + + body_tmp.items.len = 0; + try self.emitBody(old_inst.else_body, inst_table, &body_tmp); + new_inst.positionals.else_body = .{ + .instructions = try self.arena.allocator.dupe(*Inst, body_tmp.items), + }; break :blk &new_inst.base; }, .varptr => @panic("TODO"), diff --git a/src/zir_sema.zig b/src/zir_sema.zig index e438b53c94..fc7291aa85 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -1238,7 +1238,20 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In const target = try mod.analyzeDeref(scope, inst.base.src, target_ptr, inst.positionals.target_ptr.src); try validateSwitch(mod, scope, target, inst); - // TODO comptime execution + if (try mod.resolveDefinedValue(scope, target)) |target_val| { + for (inst.positionals.cases) |case| { + const resolved = try resolveInst(mod, scope, case.item); + const casted = try mod.coerce(scope, target.ty, resolved); + const item = try mod.resolveConstValue(scope, casted); + + if (target_val.eql(item)) { + try analyzeBody(mod, scope, case.body); + return mod.constNoReturn(scope, inst.base.src); + } + } + try analyzeBody(mod, scope, inst.positionals.else_body); + return mod.constNoReturn(scope, inst.base.src); + } const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src); const cases = try parent_block.arena.alloc(Inst.SwitchBr.Case, inst.positionals.cases.len); @@ -1253,7 +1266,7 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In }; defer case_block.instructions.deinit(mod.gpa); - for (inst.positionals.cases[0..inst.positionals.cases.len]) |case, i| { + for (inst.positionals.cases) |case, i| { // Reset without freeing. case_block.instructions.items.len = 0; @@ -1269,7 +1282,14 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In }; } - return mod.addSwitchBr(parent_block, inst.base.src, target_ptr, cases); + case_block.instructions.items.len = 0; + try analyzeBody(mod, &case_block.base, inst.positionals.else_body); + + const else_body: ir.Body = .{ + .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items), + }; + + return mod.addSwitchBr(parent_block, inst.base.src, target_ptr, cases, else_body); } fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.SwitchBr) InnerError!void { @@ -1354,14 +1374,14 @@ fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.Sw false_count += 1; } - if (true_count > 1 or false_count > 1) { + if (true_count + false_count > 2) { return mod.fail(scope, item.src, "duplicate switch value", .{}); } } - if ((true_count == 0 or false_count == 0) and inst.kw_args.special_prong != .@"else") { + if ((true_count + false_count < 2) and inst.kw_args.special_prong != .@"else") { return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{}); } - if ((true_count == 1 and false_count == 1) and inst.kw_args.special_prong == .@"else") { + if ((true_count + false_count == 2) and inst.kw_args.special_prong == .@"else") { return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{}); } }, @@ -1696,7 +1716,7 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE if (try mod.resolveDefinedValue(scope, cond)) |cond_val| { const body = if (cond_val.toBool()) &inst.positionals.then_body else &inst.positionals.else_body; try analyzeBody(mod, scope, body.*); - return mod.constVoid(scope, inst.base.src); + return mod.constNoReturn(scope, inst.base.src); } const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src); From 3cc68bd9138b5fda6d6066d9c49f7e1b5e04a5ee Mon Sep 17 00:00:00 2001 From: Vexu Date: Tue, 20 Oct 2020 23:10:15 +0300 Subject: [PATCH 16/19] stage2: switch liveness analysis --- src/liveness.zig | 85 ++++++++++++++++++++++++++++++++++++++++++++++++ src/zir.zig | 26 +++++++++++---- 2 files changed, 105 insertions(+), 6 deletions(-) diff --git a/src/liveness.zig b/src/liveness.zig index d528e09ce7..7b5ef8dc35 100644 --- a/src/liveness.zig +++ b/src/liveness.zig @@ -144,6 +144,91 @@ fn analyzeInst( // instruction, and the deaths flag for the CondBr instruction will indicate whether the // condition's lifetime ends immediately before entering any branch. }, + .switchbr => { + const inst = base.castTag(.switchbr).?; + + const Table = std.AutoHashMap(*ir.Inst, void); + const case_tables = try table.allocator.alloc(Table, inst.cases.len + 1); // +1 for else + defer table.allocator.free(case_tables); + + std.mem.set(Table, case_tables, Table.init(table.allocator)); + defer for (case_tables) |*ct| ct.deinit(); + + for (inst.cases) |case, i| { + try analyzeWithTable(arena, table, &case_tables[i], case.body); + + // Reset the table back to its state from before the case. + var it = case_tables[i].iterator(); + while (it.next()) |entry| { + table.removeAssertDiscard(entry.key); + } + } + { // else + try analyzeWithTable(arena, table, &case_tables[case_tables.len - 1], inst.else_body); + + // Reset the table back to its state from before the case. + var it = case_tables[case_tables.len - 1].iterator(); + while (it.next()) |entry| { + table.removeAssertDiscard(entry.key); + } + } + + const List = std.ArrayList(*ir.Inst); + const case_deaths = try table.allocator.alloc(List, case_tables.len); // +1 for else + defer table.allocator.free(case_deaths); + + std.mem.set(List, case_deaths, List.init(table.allocator)); + defer for (case_deaths) |*cd| cd.deinit(); + + var total_deaths: u32 = 0; + for (case_tables) |*ct, i| { + total_deaths += ct.count(); + var it = ct.iterator(); + while (it.next()) |entry| { + const case_death = entry.key; + for (case_tables) |*ct_inner, j| { + if (i == j) continue; + if (!ct_inner.contains(case_death)) { + try case_deaths[i].append(case_death); + } + } + // undo resetting the table + _ = try table.put(case_death, {}); + } + } + + // Now we have to correctly populate new_set. + if (new_set) |ns| { + try ns.ensureCapacity(@intCast(u32, ns.count() + total_deaths)); + for (case_tables) |*ct| { + var it = ct.iterator(); + while (it.next()) |entry| { + _ = ns.putAssumeCapacity(entry.key, {}); + } + } + } + + total_deaths = 0; + for (case_deaths[0 .. case_deaths.len - 1]) |*ct, i| { + inst.cases[i].index = total_deaths; + const len = std.math.cast(@TypeOf(inst.else_deaths), ct.items.len) catch return error.OutOfMemory; + inst.cases[i].deaths = len; + total_deaths += len; + } + { // else + const else_deaths = std.math.cast(@TypeOf(inst.else_deaths), case_deaths[case_deaths.len - 1].items.len) catch return error.OutOfMemory; + inst.else_index = total_deaths; + inst.else_deaths = else_deaths; + total_deaths += else_deaths; + } + + const allocated_slice = try arena.alloc(*ir.Inst, total_deaths); + inst.deaths = allocated_slice.ptr; + for (case_deaths[0 .. case_deaths.len - 1]) |*cd, i| { + std.mem.copy(*ir.Inst, inst.caseDeaths(i), cd.items); + } + std.mem.copy(*ir.Inst, inst.elseDeaths(), case_deaths[case_deaths.len - 1].items); + }, else => {}, } diff --git a/src/zir.zig b/src/zir.zig index da93a7250e..a03b492bd5 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -2578,9 +2578,15 @@ const EmitZIR = struct { var body_tmp = std.ArrayList(*Inst).init(self.allocator); defer body_tmp.deinit(); - for (old_inst.cases) |case, i| { + for (old_inst.cases) |*case, i| { body_tmp.items.len = 0; + const case_deaths = try self.arena.allocator.alloc(*Inst, old_inst.caseDeaths(i).len); + for (old_inst.caseDeaths(i)) |death, j| { + case_deaths[j] = try self.resolveInst(new_body, death); + } + try self.body_metadata.put(&cases[i].body, .{ .deaths = case_deaths }); + try self.emitBody(case.body, inst_table, &body_tmp); const item = (try self.emitTypedValue(inst.src, .{ .ty = old_inst.target_ptr.ty.elemType(), @@ -2592,12 +2598,20 @@ const EmitZIR = struct { .body = .{ .instructions = try self.arena.allocator.dupe(*Inst, body_tmp.items) }, }; } + { // else + const else_deaths = try self.arena.allocator.alloc(*Inst, old_inst.elseDeaths().len); + for (old_inst.elseDeaths()) |death, j| { + else_deaths[j] = try self.resolveInst(new_body, death); + } + try self.body_metadata.put(&new_inst.positionals.else_body, .{ .deaths = else_deaths }); + + body_tmp.items.len = 0; + try self.emitBody(old_inst.else_body, inst_table, &body_tmp); + new_inst.positionals.else_body = .{ + .instructions = try self.arena.allocator.dupe(*Inst, body_tmp.items), + }; + } - body_tmp.items.len = 0; - try self.emitBody(old_inst.else_body, inst_table, &body_tmp); - new_inst.positionals.else_body = .{ - .instructions = try self.arena.allocator.dupe(*Inst, body_tmp.items), - }; break :blk &new_inst.base; }, .varptr => @panic("TODO"), From e2e0b6272b98ef2ec810fbabcdc91a21e54a71a3 Mon Sep 17 00:00:00 2001 From: Vexu Date: Fri, 30 Oct 2020 13:24:47 +0200 Subject: [PATCH 17/19] stage2: return same hash for different representations of same value --- src/value.zig | 52 +++++++++++++++++++++++++++++++++++------------- src/zir_sema.zig | 6 ++++++ 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/value.zig b/src/value.zig index f6a7b24d48..8cbffecab6 100644 --- a/src/value.zig +++ b/src/value.zig @@ -565,7 +565,7 @@ pub const Value = extern union { .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(), .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt(), - .int_big_negative => return self.cast(Payload.IntBigPositive).?.asBigInt(), + .int_big_negative => return self.cast(Payload.IntBigNegative).?.asBigInt(), } } @@ -1255,7 +1255,6 @@ pub const Value = extern union { pub fn hash(self: Value) u64 { var hasher = std.hash.Wyhash.init(0); - std.hash.autoHash(&hasher, self.tag()); switch (self.tag()) { .u8_type, @@ -1321,18 +1320,19 @@ pub const Value = extern union { } }, - .undef, - .zero, - .one, - .void_value, - .unreachable_value, .empty_struct_value, .empty_array, - .null_value, - .bool_true, - .bool_false, => {}, + .undef, + .null_value, + .void_value, + .unreachable_value, + => std.hash.autoHash(&hasher, self.tag()), + + .zero, .bool_false => std.hash.autoHash(&hasher, @as(u64, 0)), + .one, .bool_true => std.hash.autoHash(&hasher, @as(u64, 1)), + .float_16, .float_32, .float_64, .float_128 => {}, .enum_literal, .bytes => { const payload = @fieldParentPtr(Payload.Bytes, "base", self.ptr_otherwise); @@ -1357,9 +1357,18 @@ pub const Value = extern union { .int_big_positive, .int_big_negative => { var space: BigIntSpace = undefined; const big = self.toBigInt(&space); - std.hash.autoHash(&hasher, big.positive); - for (big.limbs) |limb| { - std.hash.autoHash(&hasher, limb); + if (big.limbs.len == 1) { + // handle like {u,i}64 to ensure same hash as with Int{i,u}64 + if (big.positive) { + std.hash.autoHash(&hasher, @as(u64, big.limbs[0])); + } else { + std.hash.autoHash(&hasher, @as(u64, @bitCast(usize, -@bitCast(isize, big.limbs[0])))); + } + } else { + std.hash.autoHash(&hasher, big.positive); + for (big.limbs) |limb| { + std.hash.autoHash(&hasher, limb); + } } }, .elem_ptr => { @@ -1741,7 +1750,7 @@ pub const Value = extern union { .@"error", .empty_struct_value, .null_value, - => false, + => false, .undef => unreachable, .unreachable_value => unreachable, @@ -1882,3 +1891,18 @@ pub const Value = extern union { limbs: [(@sizeOf(u64) / @sizeOf(std.math.big.Limb)) + 1]std.math.big.Limb, }; }; + +test "hash same value different representation" { + const zero_1 = Value.initTag(.zero); + var payload_1 = Value.Payload.Int_u64{ .int = 0 }; + const zero_2 = Value.initPayload(&payload_1.base); + std.testing.expectEqual(zero_1.hash(), zero_2.hash()); + + var payload_2 = Value.Payload.Int_i64{ .int = 0 }; + const zero_3 = Value.initPayload(&payload_2.base); + std.testing.expectEqual(zero_2.hash(), zero_3.hash()); + + var payload_3 = Value.Payload.IntBigNegative{ .limbs = &[_]std.math.big.Limb{0} }; + const zero_4 = Value.initPayload(&payload_3.base); + std.testing.expectEqual(zero_3.hash(), zero_4.hash()); +} diff --git a/src/zir_sema.zig b/src/zir_sema.zig index fc7291aa85..602da97f81 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -1253,6 +1253,12 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In return mod.constNoReturn(scope, inst.base.src); } + if (inst.positionals.cases.len == 0) { + // no cases just analyze else_branch + try analyzeBody(mod, scope, inst.positionals.else_body); + return mod.constNoReturn(scope, inst.base.src); + } + const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src); const cases = try parent_block.arena.alloc(Inst.SwitchBr.Case, inst.positionals.cases.len); From 4ed2c52fb7b3b6d541235b6e9096a6e72b99ca2a Mon Sep 17 00:00:00 2001 From: Vexu Date: Fri, 30 Oct 2020 15:47:12 +0200 Subject: [PATCH 18/19] stage2: switch put swap condbr and block condbr is noreturn so having the other way around caused subsequent cases to be eliminated as dead --- src/astgen.zig | 22 +++++++++------------- src/codegen.zig | 11 ++++++++++- src/main.zig | 1 + 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/astgen.zig b/src/astgen.zig index f098e2b8c0..37b1eab9a6 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -1733,32 +1733,28 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node } } - const condbr = try addZIRInstSpecial(mod, &else_scope.base, case_src, zir.Inst.CondBr, .{ + const condbr = try addZIRInstSpecial(mod, &case_scope.base, case_src, zir.Inst.CondBr, .{ .condition = any_ok.?, .then_body = undefined, // populated below .else_body = undefined, // populated below }, .{}); + const cond_block = try addZIRInstBlock(mod, &else_scope.base, case_src, .block, .{ + .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), + }); + // reset cond_scope for then_body + case_scope.instructions.items.len = 0; try switchCaseExpr(mod, &case_scope.base, case_rl, block, case); condbr.positionals.then_body = .{ .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), }; - // reset to add the empty block - case_scope.instructions.items.len = 0; - const empty_block = try addZIRInstBlock(mod, &case_scope.base, case_src, .block, .{ - .instructions = undefined, // populated below - }); - condbr.positionals.else_body = .{ - .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), - }; - - // reset to add a break to the empty block + // reset cond_scope for else_body case_scope.instructions.items.len = 0; _ = try addZIRInst(mod, &case_scope.base, case_src, zir.Inst.BreakVoid, .{ - .block = empty_block, + .block = cond_block, }, .{}); - empty_block.positionals.body = .{ + condbr.positionals.else_body = .{ .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), }; } diff --git a/src/codegen.zig b/src/codegen.zig index 1745f86a94..cbc038ca65 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2033,8 +2033,17 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } fn genBoolOp(self: *Self, inst: *ir.Inst.BinOp) !MCValue { + if (inst.base.isUnused()) + return MCValue.dead; switch (arch) { - else => return self.fail(inst.base.src, "TODO genBoolOp for {}", .{self.target.cpu.arch}), + .x86_64 => if (inst.base.tag == .booland) { + // lhs AND rhs + return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs, 4, 0x20); + } else { + // lhs OR rhs + return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs, 1, 0x08); + }, + else => return self.fail(inst.base.src, "TODO implement sub for {}", .{self.target.cpu.arch}), } } diff --git a/src/main.zig b/src/main.zig index 8162bc46b6..45fcb5ce61 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2421,6 +2421,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { var stdin_flag: bool = false; var check_flag: bool = false; var input_files = ArrayList([]const u8).init(gpa); + defer input_files.deinit(); { var i: usize = 0; From 22ec5e085914d9fd7b17a28a8d3ad01258f3ad03 Mon Sep 17 00:00:00 2001 From: Vexu Date: Fri, 30 Oct 2020 15:57:18 +0200 Subject: [PATCH 19/19] stage2: fix typo in liveness; add comptime switch test --- src/liveness.zig | 3 ++- test/stage2/test.zig | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/liveness.zig b/src/liveness.zig index 7b5ef8dc35..0d759f8312 100644 --- a/src/liveness.zig +++ b/src/liveness.zig @@ -189,7 +189,8 @@ fn analyzeInst( for (case_tables) |*ct_inner, j| { if (i == j) continue; if (!ct_inner.contains(case_death)) { - try case_deaths[i].append(case_death); + // instruction is not referenced in this case + try case_deaths[j].append(case_death); } } // undo resetting the table diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 7e1de126b6..51bde87ab0 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -974,6 +974,43 @@ pub fn addCases(ctx: *TestContext) !void { , "hello\nhello\nhello\nhello\nhello\n", ); + + // comptime switch + + // Basic for loop + case.addCompareOutput( + \\pub export fn _start() noreturn { + \\ assert(foo() == 1); + \\ exit(); + \\} + \\ + \\fn foo() u32 { + \\ const a: comptime_int = 1; + \\ var b: u32 = 0; + \\ switch (a) { + \\ 1 => b = 1, + \\ 2 => b = 2, + \\ else => unreachable, + \\ } + \\ return b; + \\} + \\ + \\pub fn assert(ok: bool) void { + \\ if (!ok) unreachable; // assertion failure + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "", + ); } {