authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2020-10-31 09:39:28+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-10-31 09:39:28+02:00
log7c8d9cfa40ab96aede2a7fe8ec9dce6f10bc910a
treebac19bf24748e7d63e3bbf86ca5b34c4cfa2c754
parentbb6e39e274eb0a68bfb1029ab75d1791abeb2911
parent22ec5e085914d9fd7b17a28a8d3ad01258f3ad03
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #6660 from Vexu/stage2

Stage2 switch and package imports

14 files changed, 1379 insertions(+), 30 deletions(-)

src/Compilation.zig+1
...@@ -660,6 +660,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {...@@ -660,6 +660,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
660 .source = .{ .unloaded = {} },660 .source = .{ .unloaded = {} },
661 .contents = .{ .not_available = {} },661 .contents = .{ .not_available = {} },
662 .status = .never_loaded,662 .status = .never_loaded,
663 .pkg = root_pkg,
663 .root_container = .{664 .root_container = .{
664 .file_scope = root_scope,665 .file_scope = root_scope,
665 .decls = .{},666 .decls = .{},
src/Module.zig+82-8
...@@ -469,6 +469,22 @@ pub const Scope = struct {...@@ -469,6 +469,22 @@ pub const Scope = struct {
469 }469 }
470 }470 }
471471
472 pub fn getOwnerPkg(base: *Scope) *Package {
473 var cur = base;
474 while (true) {
475 cur = switch (cur.tag) {
476 .container => return @fieldParentPtr(Container, "base", cur).file_scope.pkg,
477 .file => return @fieldParentPtr(File, "base", cur).pkg,
478 .zir_module => unreachable, // TODO are zir modules allowed to import packages?
479 .gen_zir => @fieldParentPtr(GenZIR, "base", cur).parent,
480 .local_val => @fieldParentPtr(LocalVal, "base", cur).parent,
481 .local_ptr => @fieldParentPtr(LocalPtr, "base", cur).parent,
482 .block => @fieldParentPtr(Block, "base", cur).decl.scope,
483 .decl => @fieldParentPtr(DeclAnalysis, "base", cur).decl.scope,
484 };
485 }
486 }
487
472 /// Asserts the scope is a namespace Scope and removes the Decl from the namespace.488 /// Asserts the scope is a namespace Scope and removes the Decl from the namespace.
473 pub fn removeDecl(base: *Scope, child: *Decl) void {489 pub fn removeDecl(base: *Scope, child: *Decl) void {
474 switch (base.tag) {490 switch (base.tag) {
...@@ -576,6 +592,8 @@ pub const Scope = struct {...@@ -576,6 +592,8 @@ pub const Scope = struct {
576 unloaded_parse_failure,592 unloaded_parse_failure,
577 loaded_success,593 loaded_success,
578 },594 },
595 /// Package that this file is a part of, managed externally.
596 pkg: *Package,
579597
580 root_container: Container,598 root_container: Container,
581599
...@@ -614,7 +632,7 @@ pub const Scope = struct {...@@ -614,7 +632,7 @@ pub const Scope = struct {
614 pub fn getSource(self: *File, module: *Module) ![:0]const u8 {632 pub fn getSource(self: *File, module: *Module) ![:0]const u8 {
615 switch (self.source) {633 switch (self.source) {
616 .unloaded => {634 .unloaded => {
617 const source = try module.root_pkg.root_src_directory.handle.readFileAllocOptions(635 const source = try self.pkg.root_src_directory.handle.readFileAllocOptions(
618 module.gpa,636 module.gpa,
619 self.sub_file_path,637 self.sub_file_path,
620 std.math.maxInt(u32),638 std.math.maxInt(u32),
...@@ -1036,6 +1054,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {...@@ -1036,6 +1054,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
1036 .param_types = param_types,1054 .param_types = param_types,
1037 }, .{});1055 }, .{});
10381056
1057 if (self.comp.verbose_ir) {
1058 zir.dumpZir(self.gpa, "fn_type", decl.name, fn_type_scope.instructions.items) catch {};
1059 }
1060
1039 // We need the memory for the Type to go into the arena for the Decl1061 // We need the memory for the Type to go into the arena for the Decl
1040 var decl_arena = std.heap.ArenaAllocator.init(self.gpa);1062 var decl_arena = std.heap.ArenaAllocator.init(self.gpa);
1041 errdefer decl_arena.deinit();1063 errdefer decl_arena.deinit();
...@@ -1109,6 +1131,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {...@@ -1109,6 +1131,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
1109 _ = try astgen.addZIRNoOp(self, &gen_scope.base, src, .returnvoid);1131 _ = try astgen.addZIRNoOp(self, &gen_scope.base, src, .returnvoid);
1110 }1132 }
11111133
1134 if (self.comp.verbose_ir) {
1135 zir.dumpZir(self.gpa, "fn_body", decl.name, gen_scope.instructions.items) catch {};
1136 }
1137
1112 const fn_zir = try gen_scope_arena.allocator.create(Fn.ZIR);1138 const fn_zir = try gen_scope_arena.allocator.create(Fn.ZIR);
1113 fn_zir.* = .{1139 fn_zir.* = .{
1114 .body = .{1140 .body = .{
...@@ -1240,6 +1266,9 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {...@@ -1240,6 +1266,9 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
12401266
1241 const src = tree.token_locs[init_node.firstToken()].start;1267 const src = tree.token_locs[init_node.firstToken()].start;
1242 const init_inst = try astgen.expr(self, &gen_scope.base, init_result_loc, init_node);1268 const init_inst = try astgen.expr(self, &gen_scope.base, init_result_loc, init_node);
1269 if (self.comp.verbose_ir) {
1270 zir.dumpZir(self.gpa, "var_init", decl.name, gen_scope.instructions.items) catch {};
1271 }
12431272
1244 var inner_block: Scope.Block = .{1273 var inner_block: Scope.Block = .{
1245 .parent = null,1274 .parent = null,
...@@ -1281,6 +1310,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {...@@ -1281,6 +1310,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
1281 .val = Value.initTag(.type_type),1310 .val = Value.initTag(.type_type),
1282 });1311 });
1283 const var_type = try astgen.expr(self, &type_scope.base, .{ .ty = type_type }, type_node);1312 const var_type = try astgen.expr(self, &type_scope.base, .{ .ty = type_type }, type_node);
1313 if (self.comp.verbose_ir) {
1314 zir.dumpZir(self.gpa, "var_type", decl.name, type_scope.instructions.items) catch {};
1315 }
1316
1284 const ty = try zir_sema.analyzeBodyValueAsType(self, &block_scope, var_type, .{1317 const ty = try zir_sema.analyzeBodyValueAsType(self, &block_scope, var_type, .{
1285 .instructions = type_scope.instructions.items,1318 .instructions = type_scope.instructions.items,
1286 });1319 });
...@@ -1354,6 +1387,9 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {...@@ -1354,6 +1387,9 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
1354 defer gen_scope.instructions.deinit(self.gpa);1387 defer gen_scope.instructions.deinit(self.gpa);
13551388
1356 _ = try astgen.comptimeExpr(self, &gen_scope.base, .none, comptime_decl.expr);1389 _ = try astgen.comptimeExpr(self, &gen_scope.base, .none, comptime_decl.expr);
1390 if (self.comp.verbose_ir) {
1391 zir.dumpZir(self.gpa, "comptime_block", decl.name, gen_scope.instructions.items) catch {};
1392 }
13571393
1358 var block_scope: Scope.Block = .{1394 var block_scope: Scope.Block = .{
1359 .parent = null,1395 .parent = null,
...@@ -2080,6 +2116,29 @@ pub fn addCall(...@@ -2080,6 +2116,29 @@ pub fn addCall(
2080 return &inst.base;2116 return &inst.base;
2081}2117}
20822118
2119pub fn addSwitchBr(
2120 self: *Module,
2121 block: *Scope.Block,
2122 src: usize,
2123 target_ptr: *Inst,
2124 cases: []Inst.SwitchBr.Case,
2125 else_body: ir.Body,
2126) !*Inst {
2127 const inst = try block.arena.create(Inst.SwitchBr);
2128 inst.* = .{
2129 .base = .{
2130 .tag = .switchbr,
2131 .ty = Type.initTag(.noreturn),
2132 .src = src,
2133 },
2134 .target_ptr = target_ptr,
2135 .cases = cases,
2136 .else_body = else_body,
2137 };
2138 try block.instructions.append(self.gpa, &inst.base);
2139 return &inst.base;
2140}
2141
2083pub fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*Inst {2142pub fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*Inst {
2084 const const_inst = try scope.arena().create(Inst.Constant);2143 const const_inst = try scope.arena().create(Inst.Constant);
2085 const_inst.* = .{2144 const_inst.* = .{
...@@ -2400,28 +2459,43 @@ pub fn analyzeSlice(self: *Module, scope: *Scope, src: usize, array_ptr: *Inst,...@@ -2400,28 +2459,43 @@ pub fn analyzeSlice(self: *Module, scope: *Scope, src: usize, array_ptr: *Inst,
2400}2459}
24012460
2402pub fn analyzeImport(self: *Module, scope: *Scope, src: usize, target_string: []const u8) !*Scope.File {2461pub fn analyzeImport(self: *Module, scope: *Scope, src: usize, target_string: []const u8) !*Scope.File {
2403 // TODO if (package_table.get(target_string)) |pkg|2462 const cur_pkg = scope.getOwnerPkg();
2404 if (self.import_table.get(target_string)) |some| {2463 const cur_pkg_dir_path = cur_pkg.root_src_directory.path orelse ".";
2464 const found_pkg = cur_pkg.table.get(target_string);
2465
2466 const resolved_path = if (found_pkg) |pkg|
2467 try std.fs.path.resolve(self.gpa, &[_][]const u8{ pkg.root_src_directory.path orelse ".", pkg.root_src_path })
2468 else
2469 try std.fs.path.resolve(self.gpa, &[_][]const u8{ cur_pkg_dir_path, target_string });
2470 errdefer self.gpa.free(resolved_path);
2471
2472 if (self.import_table.get(resolved_path)) |some| {
2473 self.gpa.free(resolved_path);
2405 return some;2474 return some;
2406 }2475 }
24072476
2408 // TODO check for imports outside of pkg path2477 if (found_pkg == null) {
2409 if (false) return error.ImportOutsidePkgPath;2478 const resolved_root_path = try std.fs.path.resolve(self.gpa, &[_][]const u8{cur_pkg_dir_path});
2479 defer self.gpa.free(resolved_root_path);
2480
2481 if (!mem.startsWith(u8, resolved_path, resolved_root_path)) {
2482 return error.ImportOutsidePkgPath;
2483 }
2484 }
24102485
2411 // TODO Scope.Container arena for ty and sub_file_path2486 // TODO Scope.Container arena for ty and sub_file_path
2412 const struct_payload = try self.gpa.create(Type.Payload.EmptyStruct);2487 const struct_payload = try self.gpa.create(Type.Payload.EmptyStruct);
2413 errdefer self.gpa.destroy(struct_payload);2488 errdefer self.gpa.destroy(struct_payload);
2414 const file_scope = try self.gpa.create(Scope.File);2489 const file_scope = try self.gpa.create(Scope.File);
2415 errdefer self.gpa.destroy(file_scope);2490 errdefer self.gpa.destroy(file_scope);
2416 const file_path = try self.gpa.dupe(u8, target_string);
2417 errdefer self.gpa.free(file_path);
24182491
2419 struct_payload.* = .{ .scope = &file_scope.root_container };2492 struct_payload.* = .{ .scope = &file_scope.root_container };
2420 file_scope.* = .{2493 file_scope.* = .{
2421 .sub_file_path = file_path,2494 .sub_file_path = resolved_path,
2422 .source = .{ .unloaded = {} },2495 .source = .{ .unloaded = {} },
2423 .contents = .{ .not_available = {} },2496 .contents = .{ .not_available = {} },
2424 .status = .never_loaded,2497 .status = .never_loaded,
2498 .pkg = found_pkg orelse cur_pkg,
2425 .root_container = .{2499 .root_container = .{
2426 .file_scope = file_scope,2500 .file_scope = file_scope,
2427 .decls = .{},2501 .decls = .{},
src/RangeSet.zig created+76
...@@ -0,0 +1,76 @@
1const std = @import("std");
2const Order = std.math.Order;
3const Value = @import("value.zig").Value;
4const RangeSet = @This();
5
6ranges: std.ArrayList(Range),
7
8pub const Range = struct {
9 start: Value,
10 end: Value,
11 src: usize,
12};
13
14pub fn init(allocator: *std.mem.Allocator) RangeSet {
15 return .{
16 .ranges = std.ArrayList(Range).init(allocator),
17 };
18}
19
20pub fn deinit(self: *RangeSet) void {
21 self.ranges.deinit();
22}
23
24pub fn add(self: *RangeSet, start: Value, end: Value, src: usize) !?usize {
25 for (self.ranges.items) |range| {
26 if ((start.compare(.gte, range.start) and start.compare(.lte, range.end)) or
27 (end.compare(.gte, range.start) and end.compare(.lte, range.end)))
28 {
29 // ranges overlap
30 return range.src;
31 }
32 }
33 try self.ranges.append(.{
34 .start = start,
35 .end = end,
36 .src = src,
37 });
38 return null;
39}
40
41/// Assumes a and b do not overlap
42fn lessThan(_: void, a: Range, b: Range) bool {
43 return a.start.compare(.lt, b.start);
44}
45
46pub fn spans(self: *RangeSet, start: Value, end: Value) !bool {
47 std.sort.sort(Range, self.ranges.items, {}, lessThan);
48
49 if (!self.ranges.items[0].start.eql(start) or
50 !self.ranges.items[self.ranges.items.len - 1].end.eql(end))
51 {
52 return false;
53 }
54
55 var space: Value.BigIntSpace = undefined;
56
57 var counter = try std.math.big.int.Managed.init(self.ranges.allocator);
58 defer counter.deinit();
59
60 // look for gaps
61 for (self.ranges.items[1..]) |cur, i| {
62 // i starts counting from the second item.
63 const prev = self.ranges.items[i];
64
65 // prev.end + 1 == cur.start
66 try counter.copy(prev.end.toBigInt(&space));
67 try counter.addScalar(counter.toConst(), 1);
68
69 const cur_start_int = cur.start.toBigInt(&space);
70 if (!cur_start_int.eq(counter.toConst())) {
71 return false;
72 }
73 }
74
75 return true;
76}
src/astgen.zig+241-2
...@@ -183,6 +183,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr...@@ -183,6 +183,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
183 .VarDecl => unreachable, // Handled in `blockExpr`.183 .VarDecl => unreachable, // Handled in `blockExpr`.
184 .SwitchCase => unreachable, // Handled in `switchExpr`.184 .SwitchCase => unreachable, // Handled in `switchExpr`.
185 .SwitchElse => unreachable, // Handled in `switchExpr`.185 .SwitchElse => unreachable, // Handled in `switchExpr`.
186 .Range => unreachable, // Handled in `switchExpr`.
186 .Else => unreachable, // Handled explicitly the control flow expression functions.187 .Else => unreachable, // Handled explicitly the control flow expression functions.
187 .Payload => unreachable, // Handled explicitly.188 .Payload => unreachable, // Handled explicitly.
188 .PointerPayload => unreachable, // Handled explicitly.189 .PointerPayload => unreachable, // Handled explicitly.
...@@ -279,9 +280,9 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr...@@ -279,9 +280,9 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
279 .Catch => return catchExpr(mod, scope, rl, node.castTag(.Catch).?),280 .Catch => return catchExpr(mod, scope, rl, node.castTag(.Catch).?),
280 .Comptime => return comptimeKeyword(mod, scope, rl, node.castTag(.Comptime).?),281 .Comptime => return comptimeKeyword(mod, scope, rl, node.castTag(.Comptime).?),
281 .OrElse => return orelseExpr(mod, scope, rl, node.castTag(.OrElse).?),282 .OrElse => return orelseExpr(mod, scope, rl, node.castTag(.OrElse).?),
283 .Switch => return switchExpr(mod, scope, rl, node.castTag(.Switch).?),
282284
283 .Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}),285 .Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}),
284 .Range => return mod.failNode(scope, node, "TODO implement astgen.expr for .Range", .{}),
285 .Await => return mod.failNode(scope, node, "TODO implement astgen.expr for .Await", .{}),286 .Await => return mod.failNode(scope, node, "TODO implement astgen.expr for .Await", .{}),
286 .Resume => return mod.failNode(scope, node, "TODO implement astgen.expr for .Resume", .{}),287 .Resume => return mod.failNode(scope, node, "TODO implement astgen.expr for .Resume", .{}),
287 .Try => return mod.failNode(scope, node, "TODO implement astgen.expr for .Try", .{}),288 .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...@@ -289,7 +290,6 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
289 .ArrayInitializerDot => return mod.failNode(scope, node, "TODO implement astgen.expr for .ArrayInitializerDot", .{}),290 .ArrayInitializerDot => return mod.failNode(scope, node, "TODO implement astgen.expr for .ArrayInitializerDot", .{}),
290 .StructInitializer => return mod.failNode(scope, node, "TODO implement astgen.expr for .StructInitializer", .{}),291 .StructInitializer => return mod.failNode(scope, node, "TODO implement astgen.expr for .StructInitializer", .{}),
291 .StructInitializerDot => return mod.failNode(scope, node, "TODO implement astgen.expr for .StructInitializerDot", .{}),292 .StructInitializerDot => return mod.failNode(scope, node, "TODO implement astgen.expr for .StructInitializerDot", .{}),
292 .Switch => return mod.failNode(scope, node, "TODO implement astgen.expr for .Switch", .{}),
293 .Suspend => return mod.failNode(scope, node, "TODO implement astgen.expr for .Suspend", .{}),293 .Suspend => return mod.failNode(scope, node, "TODO implement astgen.expr for .Suspend", .{}),
294 .Continue => return mod.failNode(scope, node, "TODO implement astgen.expr for .Continue", .{}),294 .Continue => return mod.failNode(scope, node, "TODO implement astgen.expr for .Continue", .{}),
295 .AnyType => return mod.failNode(scope, node, "TODO implement astgen.expr for .AnyType", .{}),295 .AnyType => return mod.failNode(scope, node, "TODO implement astgen.expr for .AnyType", .{}),
...@@ -1561,6 +1561,245 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For)...@@ -1561,6 +1561,245 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For)
1561 return &for_block.base;1561 return &for_block.base;
1562}1562}
15631563
1564fn getRangeNode(node: *ast.Node) ?*ast.Node.SimpleInfixOp {
1565 var cur = node;
1566 while (true) {
1567 switch (cur.tag) {
1568 .Range => return @fieldParentPtr(ast.Node.SimpleInfixOp, "base", cur),
1569 .GroupedExpression => cur = @fieldParentPtr(ast.Node.GroupedExpression, "base", cur).expr,
1570 else => return null,
1571 }
1572 }
1573}
1574
1575fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node.Switch) InnerError!*zir.Inst {
1576 var block_scope: Scope.GenZIR = .{
1577 .parent = scope,
1578 .decl = scope.decl().?,
1579 .arena = scope.arena(),
1580 .instructions = .{},
1581 };
1582 defer block_scope.instructions.deinit(mod.gpa);
1583
1584 const tree = scope.tree();
1585 const switch_src = tree.token_locs[switch_node.switch_token].start;
1586 const target_ptr = try expr(mod, &block_scope.base, .ref, switch_node.expr);
1587 const target = try addZIRUnOp(mod, &block_scope.base, target_ptr.src, .deref, target_ptr);
1588 // Add the switch instruction here so that it comes before any range checks.
1589 const switch_inst = (try addZIRInst(mod, &block_scope.base, switch_src, zir.Inst.SwitchBr, .{
1590 .target_ptr = target_ptr,
1591 .cases = undefined, // populated below
1592 .items = &[_]*zir.Inst{}, // populated below
1593 .else_body = undefined, // populated below
1594 }, .{})).castTag(.switchbr).?;
1595
1596 var items = std.ArrayList(*zir.Inst).init(mod.gpa);
1597 defer items.deinit();
1598 var cases = std.ArrayList(zir.Inst.SwitchBr.Case).init(mod.gpa);
1599 defer cases.deinit();
1600
1601 // Add comptime block containing all prong items first,
1602 const item_block = try addZIRInstBlock(mod, scope, switch_src, .block_comptime_flat, .{
1603 .instructions = undefined, // populated below
1604 });
1605 // then add block containing the switch.
1606 const block = try addZIRInstBlock(mod, scope, switch_src, .block, .{
1607 .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
1608 });
1609
1610 // Most result location types can be forwarded directly; however
1611 // if we need to write to a pointer which has an inferred type,
1612 // proper type inference requires peer type resolution on the switch case.
1613 const case_rl: ResultLoc = switch (rl) {
1614 .discard, .none, .ty, .ptr, .ref => rl,
1615 .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block },
1616 };
1617
1618 var item_scope: Scope.GenZIR = .{
1619 .parent = scope,
1620 .decl = scope.decl().?,
1621 .arena = scope.arena(),
1622 .instructions = .{},
1623 };
1624 defer item_scope.instructions.deinit(mod.gpa);
1625
1626 var case_scope: Scope.GenZIR = .{
1627 .parent = scope,
1628 .decl = block_scope.decl,
1629 .arena = block_scope.arena,
1630 .instructions = .{},
1631 };
1632 defer case_scope.instructions.deinit(mod.gpa);
1633
1634 var else_scope: Scope.GenZIR = .{
1635 .parent = scope,
1636 .decl = block_scope.decl,
1637 .arena = block_scope.arena,
1638 .instructions = .{},
1639 };
1640 defer else_scope.instructions.deinit(mod.gpa);
1641
1642 // first we gather all the switch items and check else/'_' prongs
1643 var else_src: ?usize = null;
1644 var underscore_src: ?usize = null;
1645 var first_range: ?*zir.Inst = null;
1646 var special_case: ?*ast.Node.SwitchCase = null;
1647 for (switch_node.cases()) |uncasted_case| {
1648 const case = uncasted_case.castTag(.SwitchCase).?;
1649 const case_src = tree.token_locs[case.firstToken()].start;
1650 // reset without freeing to reduce allocations.
1651 case_scope.instructions.items.len = 0;
1652 assert(case.items_len != 0);
1653
1654 // Check for else/_ prong, those are handled last.
1655 if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) {
1656 if (else_src) |src| {
1657 return mod.fail(scope, case_src, "multiple else prongs in switch expression", .{});
1658 // TODO notes "previous else prong is here"
1659 }
1660 else_src = case_src;
1661 special_case = case;
1662 continue;
1663 } else if (case.items_len == 1 and case.items()[0].tag == .Identifier and
1664 mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_"))
1665 {
1666 if (underscore_src) |src| {
1667 return mod.fail(scope, case_src, "multiple '_' prongs in switch expression", .{});
1668 // TODO notes "previous '_' prong is here"
1669 }
1670 underscore_src = case_src;
1671 special_case = case;
1672 continue;
1673 }
1674
1675 if (else_src) |some_else| {
1676 if (underscore_src) |some_underscore| {
1677 return mod.fail(scope, switch_src, "else and '_' prong in switch expression", .{});
1678 // TODO notes "else prong is here"
1679 // TODO notes "'_' prong is here"
1680 }
1681 }
1682
1683 // If this is a simple one item prong then it is handled by the switchbr.
1684 if (case.items_len == 1 and getRangeNode(case.items()[0]) == null) {
1685 const item = try expr(mod, &item_scope.base, .none, case.items()[0]);
1686 try items.append(item);
1687 try switchCaseExpr(mod, &case_scope.base, case_rl, block, case);
1688
1689 try cases.append(.{
1690 .item = item,
1691 .body = .{ .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items) },
1692 });
1693 continue;
1694 }
1695
1696 // TODO if the case has few items and no ranges it might be better
1697 // to just handle them as switch prongs.
1698
1699 // Check if the target matches any of the items.
1700 // 1, 2, 3..6 will result in
1701 // target == 1 or target == 2 or (target >= 3 and target <= 6)
1702 var any_ok: ?*zir.Inst = null;
1703 for (case.items()) |item| {
1704 if (getRangeNode(item)) |range| {
1705 const start = try expr(mod, &item_scope.base, .none, range.lhs);
1706 const end = try expr(mod, &item_scope.base, .none, range.rhs);
1707 const range_src = tree.token_locs[range.op_token].start;
1708 const range_inst = try addZIRBinOp(mod, &item_scope.base, range_src, .switch_range, start, end);
1709 try items.append(range_inst);
1710 if (first_range == null) first_range = range_inst;
1711
1712 // target >= start and target <= end
1713 const range_start_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_gte, target, start);
1714 const range_end_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_lte, target, end);
1715 const range_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .booland, range_start_ok, range_end_ok);
1716
1717 if (any_ok) |some| {
1718 any_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .boolor, some, range_ok);
1719 } else {
1720 any_ok = range_ok;
1721 }
1722 continue;
1723 }
1724
1725 const item_inst = try expr(mod, &item_scope.base, .none, item);
1726 try items.append(item_inst);
1727 const cpm_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .cmp_eq, target, item_inst);
1728
1729 if (any_ok) |some| {
1730 any_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .boolor, some, cpm_ok);
1731 } else {
1732 any_ok = cpm_ok;
1733 }
1734 }
1735
1736 const condbr = try addZIRInstSpecial(mod, &case_scope.base, case_src, zir.Inst.CondBr, .{
1737 .condition = any_ok.?,
1738 .then_body = undefined, // populated below
1739 .else_body = undefined, // populated below
1740 }, .{});
1741 const cond_block = try addZIRInstBlock(mod, &else_scope.base, case_src, .block, .{
1742 .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items),
1743 });
1744
1745 // reset cond_scope for then_body
1746 case_scope.instructions.items.len = 0;
1747 try switchCaseExpr(mod, &case_scope.base, case_rl, block, case);
1748 condbr.positionals.then_body = .{
1749 .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items),
1750 };
1751
1752 // reset cond_scope for else_body
1753 case_scope.instructions.items.len = 0;
1754 _ = try addZIRInst(mod, &case_scope.base, case_src, zir.Inst.BreakVoid, .{
1755 .block = cond_block,
1756 }, .{});
1757 condbr.positionals.else_body = .{
1758 .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items),
1759 };
1760 }
1761
1762 // Generate else block or a break last to finish the block.
1763 if (special_case) |case| {
1764 try switchCaseExpr(mod, &else_scope.base, case_rl, block, case);
1765 } else {
1766 // Not handling all possible cases is a compile error.
1767 _ = try addZIRNoOp(mod, &else_scope.base, switch_src, .unreach_nocheck);
1768 }
1769
1770 // All items have been generated, add the instructions to the comptime block.
1771 item_block.positionals.body = .{
1772 .instructions = try block_scope.arena.dupe(*zir.Inst, item_scope.instructions.items),
1773 };
1774
1775 // Actually populate switch instruction values.
1776 if (else_src != null) switch_inst.kw_args.special_prong = .@"else";
1777 if (underscore_src != null) switch_inst.kw_args.special_prong = .underscore;
1778 switch_inst.positionals.cases = try block_scope.arena.dupe(zir.Inst.SwitchBr.Case, cases.items);
1779 switch_inst.positionals.items = try block_scope.arena.dupe(*zir.Inst, items.items);
1780 switch_inst.kw_args.range = first_range;
1781 switch_inst.positionals.else_body = .{
1782 .instructions = try block_scope.arena.dupe(*zir.Inst, else_scope.instructions.items),
1783 };
1784 return &block.base;
1785}
1786
1787fn switchCaseExpr(mod: *Module, scope: *Scope, rl: ResultLoc, block: *zir.Inst.Block, case: *ast.Node.SwitchCase) !void {
1788 const tree = scope.tree();
1789 const case_src = tree.token_locs[case.firstToken()].start;
1790 if (case.payload != null) {
1791 return mod.fail(scope, case_src, "TODO switch case payload capture", .{});
1792 }
1793
1794 const case_body = try expr(mod, scope, rl, case.expr);
1795 if (!case_body.tag.isNoReturn()) {
1796 _ = try addZIRInst(mod, scope, case_src, zir.Inst.Break, .{
1797 .block = block,
1798 .operand = case_body,
1799 }, .{});
1800 }
1801}
1802
1564fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst {1803fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst {
1565 const tree = scope.tree();1804 const tree = scope.tree();
1566 const src = tree.token_locs[cfe.ltoken].start;1805 const src = tree.token_locs[cfe.ltoken].start;
src/codegen.zig+24
...@@ -758,6 +758,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -758,6 +758,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
758 .br => return self.genBr(inst.castTag(.br).?),758 .br => return self.genBr(inst.castTag(.br).?),
759 .breakpoint => return self.genBreakpoint(inst.src),759 .breakpoint => return self.genBreakpoint(inst.src),
760 .brvoid => return self.genBrVoid(inst.castTag(.brvoid).?),760 .brvoid => return self.genBrVoid(inst.castTag(.brvoid).?),
761 .booland => return self.genBoolOp(inst.castTag(.booland).?),
762 .boolor => return self.genBoolOp(inst.castTag(.boolor).?),
761 .call => return self.genCall(inst.castTag(.call).?),763 .call => return self.genCall(inst.castTag(.call).?),
762 .cmp_lt => return self.genCmp(inst.castTag(.cmp_lt).?, .lt),764 .cmp_lt => return self.genCmp(inst.castTag(.cmp_lt).?, .lt),
763 .cmp_lte => return self.genCmp(inst.castTag(.cmp_lte).?, .lte),765 .cmp_lte => return self.genCmp(inst.castTag(.cmp_lte).?, .lte),
...@@ -782,6 +784,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -782,6 +784,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
782 .retvoid => return self.genRetVoid(inst.castTag(.retvoid).?),784 .retvoid => return self.genRetVoid(inst.castTag(.retvoid).?),
783 .store => return self.genStore(inst.castTag(.store).?),785 .store => return self.genStore(inst.castTag(.store).?),
784 .sub => return self.genSub(inst.castTag(.sub).?),786 .sub => return self.genSub(inst.castTag(.sub).?),
787 .switchbr => return self.genSwitch(inst.castTag(.switchbr).?),
785 .unreach => return MCValue{ .unreach = {} },788 .unreach => return MCValue{ .unreach = {} },
786 .unwrap_optional => return self.genUnwrapOptional(inst.castTag(.unwrap_optional).?),789 .unwrap_optional => return self.genUnwrapOptional(inst.castTag(.unwrap_optional).?),
787 .wrap_optional => return self.genWrapOptional(inst.castTag(.wrap_optional).?),790 .wrap_optional => return self.genWrapOptional(inst.castTag(.wrap_optional).?),
...@@ -1989,6 +1992,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1989,6 +1992,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1989 return @bitCast(MCValue, inst.codegen.mcv);1992 return @bitCast(MCValue, inst.codegen.mcv);
1990 }1993 }
19911994
1995 fn genSwitch(self: *Self, inst: *ir.Inst.SwitchBr) !MCValue {
1996 switch (arch) {
1997 else => return self.fail(inst.base.src, "TODO genSwitch for {}", .{self.target.cpu.arch}),
1998 }
1999 }
2000
1992 fn performReloc(self: *Self, src: usize, reloc: Reloc) !void {2001 fn performReloc(self: *Self, src: usize, reloc: Reloc) !void {
1993 switch (reloc) {2002 switch (reloc) {
1994 .rel32 => |pos| {2003 .rel32 => |pos| {
...@@ -2023,6 +2032,21 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -2023,6 +2032,21 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
2023 return self.brVoid(inst.base.src, inst.block);2032 return self.brVoid(inst.base.src, inst.block);
2024 }2033 }
20252034
2035 fn genBoolOp(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
2036 if (inst.base.isUnused())
2037 return MCValue.dead;
2038 switch (arch) {
2039 .x86_64 => if (inst.base.tag == .booland) {
2040 // lhs AND rhs
2041 return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs, 4, 0x20);
2042 } else {
2043 // lhs OR rhs
2044 return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs, 1, 0x08);
2045 },
2046 else => return self.fail(inst.base.src, "TODO implement sub for {}", .{self.target.cpu.arch}),
2047 }
2048 }
2049
2026 fn brVoid(self: *Self, src: usize, block: *ir.Inst.Block) !MCValue {2050 fn brVoid(self: *Self, src: usize, block: *ir.Inst.Block) !MCValue {
2027 // Emit a jump with a relocation. It will be patched up after the block ends.2051 // Emit a jump with a relocation. It will be patched up after the block ends.
2028 try block.codegen.relocs.ensureCapacity(self.gpa, block.codegen.relocs.items.len + 1);2052 try block.codegen.relocs.ensureCapacity(self.gpa, block.codegen.relocs.items.len + 1);
src/ir.zig+47
...@@ -74,6 +74,8 @@ pub const Inst = struct {...@@ -74,6 +74,8 @@ pub const Inst = struct {
74 isnonnull,74 isnonnull,
75 isnull,75 isnull,
76 iserr,76 iserr,
77 booland,
78 boolor,
77 /// Read a value from a pointer.79 /// Read a value from a pointer.
78 load,80 load,
79 loop,81 loop,
...@@ -91,6 +93,7 @@ pub const Inst = struct {...@@ -91,6 +93,7 @@ pub const Inst = struct {
91 intcast,93 intcast,
92 unwrap_optional,94 unwrap_optional,
93 wrap_optional,95 wrap_optional,
96 switchbr,
9497
95 pub fn Type(tag: Tag) type {98 pub fn Type(tag: Tag) type {
96 return switch (tag) {99 return switch (tag) {
...@@ -125,6 +128,8 @@ pub const Inst = struct {...@@ -125,6 +128,8 @@ pub const Inst = struct {
125 .cmp_gt,128 .cmp_gt,
126 .cmp_neq,129 .cmp_neq,
127 .store,130 .store,
131 .booland,
132 .boolor,
128 => BinOp,133 => BinOp,
129134
130 .arg => Arg,135 .arg => Arg,
...@@ -137,6 +142,7 @@ pub const Inst = struct {...@@ -137,6 +142,7 @@ pub const Inst = struct {
137 .constant => Constant,142 .constant => Constant,
138 .loop => Loop,143 .loop => Loop,
139 .varptr => VarPtr,144 .varptr => VarPtr,
145 .switchbr => SwitchBr,
140 };146 };
141 }147 }
142148
...@@ -458,6 +464,47 @@ pub const Inst = struct {...@@ -458,6 +464,47 @@ pub const Inst = struct {
458 return null;464 return null;
459 }465 }
460 };466 };
467
468 pub const SwitchBr = struct {
469 pub const base_tag = Tag.switchbr;
470
471 base: Inst,
472 target_ptr: *Inst,
473 cases: []Case,
474 /// Set of instructions whose lifetimes end at the start of one of the cases.
475 /// In same order as cases, deaths[0..case_0_count, case_0_count .. case_1_count, ... ].
476 deaths: [*]*Inst = undefined,
477 else_index: u32 = 0,
478 else_deaths: u32 = 0,
479 else_body: Body,
480
481 pub const Case = struct {
482 item: Value,
483 body: Body,
484 index: u32 = 0,
485 deaths: u32 = 0,
486 };
487
488 pub fn operandCount(self: *const SwitchBr) usize {
489 return 1;
490 }
491 pub fn getOperand(self: *const SwitchBr, index: usize) ?*Inst {
492 var i = index;
493
494 if (i < 1)
495 return self.target_ptr;
496 i -= 1;
497
498 return null;
499 }
500 pub fn caseDeaths(self: *const SwitchBr, case_index: usize) []*Inst {
501 const case = self.cases[case_index];
502 return (self.deaths + case.index)[0..case.deaths];
503 }
504 pub fn elseDeaths(self: *const SwitchBr) []*Inst {
505 return (self.deaths + self.else_index)[0..self.else_deaths];
506 }
507 };
461};508};
462509
463pub const Body = struct {510pub const Body = struct {
src/liveness.zig+86
...@@ -144,6 +144,92 @@ fn analyzeInst(...@@ -144,6 +144,92 @@ fn analyzeInst(
144 // instruction, and the deaths flag for the CondBr instruction will indicate whether the144 // instruction, and the deaths flag for the CondBr instruction will indicate whether the
145 // condition's lifetime ends immediately before entering any branch.145 // condition's lifetime ends immediately before entering any branch.
146 },146 },
147 .switchbr => {
148 const inst = base.castTag(.switchbr).?;
149
150 const Table = std.AutoHashMap(*ir.Inst, void);
151 const case_tables = try table.allocator.alloc(Table, inst.cases.len + 1); // +1 for else
152 defer table.allocator.free(case_tables);
153
154 std.mem.set(Table, case_tables, Table.init(table.allocator));
155 defer for (case_tables) |*ct| ct.deinit();
156
157 for (inst.cases) |case, i| {
158 try analyzeWithTable(arena, table, &case_tables[i], case.body);
159
160 // Reset the table back to its state from before the case.
161 var it = case_tables[i].iterator();
162 while (it.next()) |entry| {
163 table.removeAssertDiscard(entry.key);
164 }
165 }
166 { // else
167 try analyzeWithTable(arena, table, &case_tables[case_tables.len - 1], inst.else_body);
168
169 // Reset the table back to its state from before the case.
170 var it = case_tables[case_tables.len - 1].iterator();
171 while (it.next()) |entry| {
172 table.removeAssertDiscard(entry.key);
173 }
174 }
175
176 const List = std.ArrayList(*ir.Inst);
177 const case_deaths = try table.allocator.alloc(List, case_tables.len); // +1 for else
178 defer table.allocator.free(case_deaths);
179
180 std.mem.set(List, case_deaths, List.init(table.allocator));
181 defer for (case_deaths) |*cd| cd.deinit();
182
183 var total_deaths: u32 = 0;
184 for (case_tables) |*ct, i| {
185 total_deaths += ct.count();
186 var it = ct.iterator();
187 while (it.next()) |entry| {
188 const case_death = entry.key;
189 for (case_tables) |*ct_inner, j| {
190 if (i == j) continue;
191 if (!ct_inner.contains(case_death)) {
192 // instruction is not referenced in this case
193 try case_deaths[j].append(case_death);
194 }
195 }
196 // undo resetting the table
197 _ = try table.put(case_death, {});
198 }
199 }
200
201 // Now we have to correctly populate new_set.
202 if (new_set) |ns| {
203 try ns.ensureCapacity(@intCast(u32, ns.count() + total_deaths));
204 for (case_tables) |*ct| {
205 var it = ct.iterator();
206 while (it.next()) |entry| {
207 _ = ns.putAssumeCapacity(entry.key, {});
208 }
209 }
210 }
211
212 total_deaths = 0;
213 for (case_deaths[0 .. case_deaths.len - 1]) |*ct, i| {
214 inst.cases[i].index = total_deaths;
215 const len = std.math.cast(@TypeOf(inst.else_deaths), ct.items.len) catch return error.OutOfMemory;
216 inst.cases[i].deaths = len;
217 total_deaths += len;
218 }
219 { // else
220 const else_deaths = std.math.cast(@TypeOf(inst.else_deaths), case_deaths[case_deaths.len - 1].items.len) catch return error.OutOfMemory;
221 inst.else_index = total_deaths;
222 inst.else_deaths = else_deaths;
223 total_deaths += else_deaths;
224 }
225
226 const allocated_slice = try arena.alloc(*ir.Inst, total_deaths);
227 inst.deaths = allocated_slice.ptr;
228 for (case_deaths[0 .. case_deaths.len - 1]) |*cd, i| {
229 std.mem.copy(*ir.Inst, inst.caseDeaths(i), cd.items);
230 }
231 std.mem.copy(*ir.Inst, inst.elseDeaths(), case_deaths[case_deaths.len - 1].items);
232 },
147 else => {},233 else => {},
148 }234 }
149235
src/main.zig+1
...@@ -2421,6 +2421,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {...@@ -2421,6 +2421,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
2421 var stdin_flag: bool = false;2421 var stdin_flag: bool = false;
2422 var check_flag: bool = false;2422 var check_flag: bool = false;
2423 var input_files = ArrayList([]const u8).init(gpa);2423 var input_files = ArrayList([]const u8).init(gpa);
2424 defer input_files.deinit();
24242425
2425 {2426 {
2426 var i: usize = 0;2427 var i: usize = 0;
src/test.zig+4-4
...@@ -463,10 +463,10 @@ pub const TestContext = struct {...@@ -463,10 +463,10 @@ pub const TestContext = struct {
463463
464 var cache_dir = try tmp.dir.makeOpenPath("zig-cache", .{});464 var cache_dir = try tmp.dir.makeOpenPath("zig-cache", .{});
465 defer cache_dir.close();465 defer cache_dir.close();
466 const bogus_path = "bogus"; // TODO this will need to be fixed before we can test LLVM extensions466 const tmp_path = try std.fs.path.join(arena, &[_][]const u8{ ".", "zig-cache", "tmp", &tmp.sub_path });
467 const zig_cache_directory: Compilation.Directory = .{467 const zig_cache_directory: Compilation.Directory = .{
468 .handle = cache_dir,468 .handle = cache_dir,
469 .path = try std.fs.path.join(arena, &[_][]const u8{ bogus_path, "zig-cache" }),469 .path = try std.fs.path.join(arena, &[_][]const u8{ tmp_path, "zig-cache" }),
470 };470 };
471471
472 const tmp_src_path = switch (case.extension) {472 const tmp_src_path = switch (case.extension) {
...@@ -475,7 +475,7 @@ pub const TestContext = struct {...@@ -475,7 +475,7 @@ pub const TestContext = struct {
475 };475 };
476476
477 var root_pkg: Package = .{477 var root_pkg: Package = .{
478 .root_src_directory = .{ .path = bogus_path, .handle = tmp.dir },478 .root_src_directory = .{ .path = tmp_path, .handle = tmp.dir },
479 .root_src_path = tmp_src_path,479 .root_src_path = tmp_src_path,
480 };480 };
481481
...@@ -488,7 +488,7 @@ pub const TestContext = struct {...@@ -488,7 +488,7 @@ pub const TestContext = struct {
488 });488 });
489489
490 const emit_directory: Compilation.Directory = .{490 const emit_directory: Compilation.Directory = .{
491 .path = bogus_path,491 .path = tmp_path,
492 .handle = tmp.dir,492 .handle = tmp.dir,
493 };493 };
494 const emit_bin: Compilation.EmitLoc = .{494 const emit_bin: Compilation.EmitLoc = .{
src/type.zig+72
...@@ -2863,6 +2863,78 @@ pub const Type = extern union {...@@ -2863,6 +2863,78 @@ pub const Type = extern union {
2863 };2863 };
2864 }2864 }
28652865
2866 /// Asserts that self.zigTypeTag() == .Int.
2867 pub fn minInt(self: Type, arena: *std.heap.ArenaAllocator, target: Target) !Value {
2868 assert(self.zigTypeTag() == .Int);
2869 const info = self.intInfo(target);
2870
2871 if (!info.signed) {
2872 return Value.initTag(.zero);
2873 }
2874
2875 if ((info.bits - 1) <= std.math.maxInt(u6)) {
2876 const payload = try arena.allocator.create(Value.Payload.Int_i64);
2877 payload.* = .{
2878 .int = -(@as(i64, 1) << @truncate(u6, info.bits - 1)),
2879 };
2880 return Value.initPayload(&payload.base);
2881 }
2882
2883 var res = try std.math.big.int.Managed.initSet(&arena.allocator, 1);
2884 try res.shiftLeft(res, info.bits - 1);
2885 res.negate();
2886
2887 const res_const = res.toConst();
2888 if (res_const.positive) {
2889 const val_payload = try arena.allocator.create(Value.Payload.IntBigPositive);
2890 val_payload.* = .{ .limbs = res_const.limbs };
2891 return Value.initPayload(&val_payload.base);
2892 } else {
2893 const val_payload = try arena.allocator.create(Value.Payload.IntBigNegative);
2894 val_payload.* = .{ .limbs = res_const.limbs };
2895 return Value.initPayload(&val_payload.base);
2896 }
2897 }
2898
2899 /// Asserts that self.zigTypeTag() == .Int.
2900 pub fn maxInt(self: Type, arena: *std.heap.ArenaAllocator, target: Target) !Value {
2901 assert(self.zigTypeTag() == .Int);
2902 const info = self.intInfo(target);
2903
2904 if (info.signed and (info.bits - 1) <= std.math.maxInt(u6)) {
2905 const payload = try arena.allocator.create(Value.Payload.Int_i64);
2906 payload.* = .{
2907 .int = (@as(i64, 1) << @truncate(u6, info.bits - 1)) - 1,
2908 };
2909 return Value.initPayload(&payload.base);
2910 } else if (!info.signed and info.bits <= std.math.maxInt(u6)) {
2911 const payload = try arena.allocator.create(Value.Payload.Int_u64);
2912 payload.* = .{
2913 .int = (@as(u64, 1) << @truncate(u6, info.bits)) - 1,
2914 };
2915 return Value.initPayload(&payload.base);
2916 }
2917
2918 var res = try std.math.big.int.Managed.initSet(&arena.allocator, 1);
2919 try res.shiftLeft(res, info.bits - @boolToInt(info.signed));
2920 const one = std.math.big.int.Const{
2921 .limbs = &[_]std.math.big.Limb{1},
2922 .positive = true,
2923 };
2924 res.sub(res.toConst(), one) catch unreachable;
2925
2926 const res_const = res.toConst();
2927 if (res_const.positive) {
2928 const val_payload = try arena.allocator.create(Value.Payload.IntBigPositive);
2929 val_payload.* = .{ .limbs = res_const.limbs };
2930 return Value.initPayload(&val_payload.base);
2931 } else {
2932 const val_payload = try arena.allocator.create(Value.Payload.IntBigNegative);
2933 val_payload.* = .{ .limbs = res_const.limbs };
2934 return Value.initPayload(&val_payload.base);
2935 }
2936 }
2937
2866 /// This enum does not directly correspond to `std.builtin.TypeId` because2938 /// This enum does not directly correspond to `std.builtin.TypeId` because
2867 /// it has extra enum tags in it, as a way of using less memory. For example,2939 /// it has extra enum tags in it, as a way of using less memory. For example,
2868 /// even though Zig recognizes `*align(10) i32` and `*i32` both as Pointer types2940 /// even though Zig recognizes `*align(10) i32` and `*i32` both as Pointer types
src/value.zig+257-6
...@@ -565,7 +565,7 @@ pub const Value = extern union {...@@ -565,7 +565,7 @@ pub const Value = extern union {
565 .int_u64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_u64).?.int).toConst(),565 .int_u64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_u64).?.int).toConst(),
566 .int_i64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_i64).?.int).toConst(),566 .int_i64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_i64).?.int).toConst(),
567 .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt(),567 .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt(),
568 .int_big_negative => return self.cast(Payload.IntBigPositive).?.asBigInt(),568 .int_big_negative => return self.cast(Payload.IntBigNegative).?.asBigInt(),
569 }569 }
570 }570 }
571571
...@@ -1233,15 +1233,170 @@ pub const Value = extern union {...@@ -1233,15 +1233,170 @@ pub const Value = extern union {
1233 }1233 }
12341234
1235 pub fn eql(a: Value, b: Value) bool {1235 pub fn eql(a: Value, b: Value) bool {
1236 if (a.tag() == b.tag() and a.tag() == .enum_literal) {1236 if (a.tag() == b.tag()) {
1237 const a_name = @fieldParentPtr(Payload.Bytes, "base", a.ptr_otherwise).data;1237 if (a.tag() == .void_value or a.tag() == .null_value) {
1238 const b_name = @fieldParentPtr(Payload.Bytes, "base", b.ptr_otherwise).data;1238 return true;
1239 return std.mem.eql(u8, a_name, b_name);1239 } else if (a.tag() == .enum_literal) {
1240 const a_name = @fieldParentPtr(Payload.Bytes, "base", a.ptr_otherwise).data;
1241 const b_name = @fieldParentPtr(Payload.Bytes, "base", b.ptr_otherwise).data;
1242 return std.mem.eql(u8, a_name, b_name);
1243 }
1244 }
1245 if (a.isType() and b.isType()) {
1246 // 128 bytes should be enough to hold both types
1247 var buf: [128]u8 = undefined;
1248 var fib = std.heap.FixedBufferAllocator.init(&buf);
1249 const a_type = a.toType(&fib.allocator) catch unreachable;
1250 const b_type = b.toType(&fib.allocator) catch unreachable;
1251 return a_type.eql(b_type);
1240 }1252 }
1241 // TODO non numerical comparisons
1242 return compare(a, .eq, b);1253 return compare(a, .eq, b);
1243 }1254 }
12441255
1256 pub fn hash(self: Value) u64 {
1257 var hasher = std.hash.Wyhash.init(0);
1258
1259 switch (self.tag()) {
1260 .u8_type,
1261 .i8_type,
1262 .u16_type,
1263 .i16_type,
1264 .u32_type,
1265 .i32_type,
1266 .u64_type,
1267 .i64_type,
1268 .usize_type,
1269 .isize_type,
1270 .c_short_type,
1271 .c_ushort_type,
1272 .c_int_type,
1273 .c_uint_type,
1274 .c_long_type,
1275 .c_ulong_type,
1276 .c_longlong_type,
1277 .c_ulonglong_type,
1278 .c_longdouble_type,
1279 .f16_type,
1280 .f32_type,
1281 .f64_type,
1282 .f128_type,
1283 .c_void_type,
1284 .bool_type,
1285 .void_type,
1286 .type_type,
1287 .anyerror_type,
1288 .comptime_int_type,
1289 .comptime_float_type,
1290 .noreturn_type,
1291 .null_type,
1292 .undefined_type,
1293 .fn_noreturn_no_args_type,
1294 .fn_void_no_args_type,
1295 .fn_naked_noreturn_no_args_type,
1296 .fn_ccc_void_no_args_type,
1297 .single_const_pointer_to_comptime_int_type,
1298 .const_slice_u8_type,
1299 .enum_literal_type,
1300 .anyframe_type,
1301 .ty,
1302 => {
1303 // Directly return Type.hash, toType can only fail for .int_type and .error_set.
1304 var allocator = std.heap.FixedBufferAllocator.init(&[_]u8{});
1305 return (self.toType(&allocator.allocator) catch unreachable).hash();
1306 },
1307 .error_set => {
1308 // Payload.decl should be same for all instances of the type.
1309 const payload = @fieldParentPtr(Payload.ErrorSet, "base", self.ptr_otherwise);
1310 std.hash.autoHash(&hasher, payload.decl);
1311 },
1312 .int_type => {
1313 const payload = self.cast(Payload.IntType).?;
1314 if (payload.signed) {
1315 var new = Type.Payload.IntSigned{ .bits = payload.bits };
1316 return Type.initPayload(&new.base).hash();
1317 } else {
1318 var new = Type.Payload.IntUnsigned{ .bits = payload.bits };
1319 return Type.initPayload(&new.base).hash();
1320 }
1321 },
1322
1323 .empty_struct_value,
1324 .empty_array,
1325 => {},
1326
1327 .undef,
1328 .null_value,
1329 .void_value,
1330 .unreachable_value,
1331 => std.hash.autoHash(&hasher, self.tag()),
1332
1333 .zero, .bool_false => std.hash.autoHash(&hasher, @as(u64, 0)),
1334 .one, .bool_true => std.hash.autoHash(&hasher, @as(u64, 1)),
1335
1336 .float_16, .float_32, .float_64, .float_128 => {},
1337 .enum_literal, .bytes => {
1338 const payload = @fieldParentPtr(Payload.Bytes, "base", self.ptr_otherwise);
1339 hasher.update(payload.data);
1340 },
1341 .int_u64 => {
1342 const payload = @fieldParentPtr(Payload.Int_u64, "base", self.ptr_otherwise);
1343 std.hash.autoHash(&hasher, payload.int);
1344 },
1345 .int_i64 => {
1346 const payload = @fieldParentPtr(Payload.Int_i64, "base", self.ptr_otherwise);
1347 std.hash.autoHash(&hasher, payload.int);
1348 },
1349 .repeated => {
1350 const payload = @fieldParentPtr(Payload.Repeated, "base", self.ptr_otherwise);
1351 std.hash.autoHash(&hasher, payload.val.hash());
1352 },
1353 .ref_val => {
1354 const payload = @fieldParentPtr(Payload.RefVal, "base", self.ptr_otherwise);
1355 std.hash.autoHash(&hasher, payload.val.hash());
1356 },
1357 .int_big_positive, .int_big_negative => {
1358 var space: BigIntSpace = undefined;
1359 const big = self.toBigInt(&space);
1360 if (big.limbs.len == 1) {
1361 // handle like {u,i}64 to ensure same hash as with Int{i,u}64
1362 if (big.positive) {
1363 std.hash.autoHash(&hasher, @as(u64, big.limbs[0]));
1364 } else {
1365 std.hash.autoHash(&hasher, @as(u64, @bitCast(usize, -@bitCast(isize, big.limbs[0]))));
1366 }
1367 } else {
1368 std.hash.autoHash(&hasher, big.positive);
1369 for (big.limbs) |limb| {
1370 std.hash.autoHash(&hasher, limb);
1371 }
1372 }
1373 },
1374 .elem_ptr => {
1375 const payload = @fieldParentPtr(Payload.ElemPtr, "base", self.ptr_otherwise);
1376 std.hash.autoHash(&hasher, payload.array_ptr.hash());
1377 std.hash.autoHash(&hasher, payload.index);
1378 },
1379 .decl_ref => {
1380 const payload = @fieldParentPtr(Payload.DeclRef, "base", self.ptr_otherwise);
1381 std.hash.autoHash(&hasher, payload.decl);
1382 },
1383 .function => {
1384 const payload = @fieldParentPtr(Payload.Function, "base", self.ptr_otherwise);
1385 std.hash.autoHash(&hasher, payload.func);
1386 },
1387 .variable => {
1388 const payload = @fieldParentPtr(Payload.Variable, "base", self.ptr_otherwise);
1389 std.hash.autoHash(&hasher, payload.variable);
1390 },
1391 .@"error" => {
1392 const payload = @fieldParentPtr(Payload.Error, "base", self.ptr_otherwise);
1393 hasher.update(payload.name);
1394 std.hash.autoHash(&hasher, payload.value);
1395 },
1396 }
1397 return hasher.final();
1398 }
1399
1245 /// Asserts the value is a pointer and dereferences it.1400 /// Asserts the value is a pointer and dereferences it.
1246 /// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis.1401 /// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis.
1247 pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value {1402 pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value {
...@@ -1521,6 +1676,87 @@ pub const Value = extern union {...@@ -1521,6 +1676,87 @@ pub const Value = extern union {
1521 };1676 };
1522 }1677 }
15231678
1679 /// Valid for all types. Asserts the value is not undefined.
1680 pub fn isType(self: Value) bool {
1681 return switch (self.tag()) {
1682 .ty,
1683 .int_type,
1684 .u8_type,
1685 .i8_type,
1686 .u16_type,
1687 .i16_type,
1688 .u32_type,
1689 .i32_type,
1690 .u64_type,
1691 .i64_type,
1692 .usize_type,
1693 .isize_type,
1694 .c_short_type,
1695 .c_ushort_type,
1696 .c_int_type,
1697 .c_uint_type,
1698 .c_long_type,
1699 .c_ulong_type,
1700 .c_longlong_type,
1701 .c_ulonglong_type,
1702 .c_longdouble_type,
1703 .f16_type,
1704 .f32_type,
1705 .f64_type,
1706 .f128_type,
1707 .c_void_type,
1708 .bool_type,
1709 .void_type,
1710 .type_type,
1711 .anyerror_type,
1712 .comptime_int_type,
1713 .comptime_float_type,
1714 .noreturn_type,
1715 .null_type,
1716 .undefined_type,
1717 .fn_noreturn_no_args_type,
1718 .fn_void_no_args_type,
1719 .fn_naked_noreturn_no_args_type,
1720 .fn_ccc_void_no_args_type,
1721 .single_const_pointer_to_comptime_int_type,
1722 .const_slice_u8_type,
1723 .enum_literal_type,
1724 .anyframe_type,
1725 .error_set,
1726 => true,
1727
1728 .zero,
1729 .one,
1730 .empty_array,
1731 .bool_true,
1732 .bool_false,
1733 .function,
1734 .variable,
1735 .int_u64,
1736 .int_i64,
1737 .int_big_positive,
1738 .int_big_negative,
1739 .ref_val,
1740 .decl_ref,
1741 .elem_ptr,
1742 .bytes,
1743 .repeated,
1744 .float_16,
1745 .float_32,
1746 .float_64,
1747 .float_128,
1748 .void_value,
1749 .enum_literal,
1750 .@"error",
1751 .empty_struct_value,
1752 .null_value,
1753 => false,
1754
1755 .undef => unreachable,
1756 .unreachable_value => unreachable,
1757 };
1758 }
1759
1524 /// This type is not copyable since it may contain pointers to its inner data.1760 /// This type is not copyable since it may contain pointers to its inner data.
1525 pub const Payload = struct {1761 pub const Payload = struct {
1526 tag: Tag,1762 tag: Tag,
...@@ -1655,3 +1891,18 @@ pub const Value = extern union {...@@ -1655,3 +1891,18 @@ pub const Value = extern union {
1655 limbs: [(@sizeOf(u64) / @sizeOf(std.math.big.Limb)) + 1]std.math.big.Limb,1891 limbs: [(@sizeOf(u64) / @sizeOf(std.math.big.Limb)) + 1]std.math.big.Limb,
1656 };1892 };
1657};1893};
1894
1895test "hash same value different representation" {
1896 const zero_1 = Value.initTag(.zero);
1897 var payload_1 = Value.Payload.Int_u64{ .int = 0 };
1898 const zero_2 = Value.initPayload(&payload_1.base);
1899 std.testing.expectEqual(zero_1.hash(), zero_2.hash());
1900
1901 var payload_2 = Value.Payload.Int_i64{ .int = 0 };
1902 const zero_3 = Value.initPayload(&payload_2.base);
1903 std.testing.expectEqual(zero_2.hash(), zero_3.hash());
1904
1905 var payload_3 = Value.Payload.IntBigNegative{ .limbs = &[_]std.math.big.Limb{0} };
1906 const zero_4 = Value.initPayload(&payload_3.base);
1907 std.testing.expectEqual(zero_3.hash(), zero_4.hash());
1908}
src/zir.zig+195-3
...@@ -85,8 +85,12 @@ pub const Inst = struct {...@@ -85,8 +85,12 @@ pub const Inst = struct {
85 block_comptime,85 block_comptime,
86 /// Same as `block_flat` but additionally makes the inner instructions execute at comptime.86 /// Same as `block_flat` but additionally makes the inner instructions execute at comptime.
87 block_comptime_flat,87 block_comptime_flat,
88 /// Boolean AND. See also `bitand`.
89 booland,
88 /// Boolean NOT. See also `bitnot`.90 /// Boolean NOT. See also `bitnot`.
89 boolnot,91 boolnot,
92 /// Boolean OR. See also `bitor`.
93 boolor,
90 /// Return a value from a `Block`.94 /// Return a value from a `Block`.
91 @"break",95 @"break",
92 breakpoint,96 breakpoint,
...@@ -272,6 +276,12 @@ pub const Inst = struct {...@@ -272,6 +276,12 @@ pub const Inst = struct {
272 ensure_err_payload_void,276 ensure_err_payload_void,
273 /// Enum literal277 /// Enum literal
274 enum_literal,278 enum_literal,
279 /// A switch expression.
280 switchbr,
281 /// A range in a switch case, `lhs...rhs`.
282 /// Only checks that `lhs >= rhs` if they are ints, everything else is
283 /// validated by the .switch instruction.
284 switch_range,
275285
276 pub fn Type(tag: Tag) type {286 pub fn Type(tag: Tag) type {
277 return switch (tag) {287 return switch (tag) {
...@@ -327,6 +337,8 @@ pub const Inst = struct {...@@ -327,6 +337,8 @@ pub const Inst = struct {
327 .array_type,337 .array_type,
328 .bitand,338 .bitand,
329 .bitor,339 .bitor,
340 .booland,
341 .boolor,
330 .div,342 .div,
331 .mod_rem,343 .mod_rem,
332 .mul,344 .mul,
...@@ -351,6 +363,7 @@ pub const Inst = struct {...@@ -351,6 +363,7 @@ pub const Inst = struct {
351 .error_union_type,363 .error_union_type,
352 .merge_error_sets,364 .merge_error_sets,
353 .slice_start,365 .slice_start,
366 .switch_range,
354 => BinOp,367 => BinOp,
355368
356 .block,369 .block,
...@@ -389,6 +402,7 @@ pub const Inst = struct {...@@ -389,6 +402,7 @@ pub const Inst = struct {
389 .enum_literal => EnumLiteral,402 .enum_literal => EnumLiteral,
390 .error_set => ErrorSet,403 .error_set => ErrorSet,
391 .slice => Slice,404 .slice => Slice,
405 .switchbr => SwitchBr,
392 };406 };
393 }407 }
394408
...@@ -417,6 +431,8 @@ pub const Inst = struct {...@@ -417,6 +431,8 @@ pub const Inst = struct {
417 .block_comptime,431 .block_comptime,
418 .block_comptime_flat,432 .block_comptime_flat,
419 .boolnot,433 .boolnot,
434 .booland,
435 .boolor,
420 .breakpoint,436 .breakpoint,
421 .call,437 .call,
422 .cmp_lt,438 .cmp_lt,
...@@ -493,6 +509,7 @@ pub const Inst = struct {...@@ -493,6 +509,7 @@ pub const Inst = struct {
493 .slice,509 .slice,
494 .slice_start,510 .slice_start,
495 .import,511 .import,
512 .switch_range,
496 => false,513 => false,
497514
498 .@"break",515 .@"break",
...@@ -504,6 +521,7 @@ pub const Inst = struct {...@@ -504,6 +521,7 @@ pub const Inst = struct {
504 .unreach_nocheck,521 .unreach_nocheck,
505 .@"unreachable",522 .@"unreachable",
506 .loop,523 .loop,
524 .switchbr,
507 => true,525 => true,
508 };526 };
509 }527 }
...@@ -987,6 +1005,33 @@ pub const Inst = struct {...@@ -987,6 +1005,33 @@ pub const Inst = struct {
987 sentinel: ?*Inst = null,1005 sentinel: ?*Inst = null,
988 },1006 },
989 };1007 };
1008
1009 pub const SwitchBr = struct {
1010 pub const base_tag = Tag.switchbr;
1011 base: Inst,
1012
1013 positionals: struct {
1014 target_ptr: *Inst,
1015 /// List of all individual items and ranges
1016 items: []*Inst,
1017 cases: []Case,
1018 else_body: Module.Body,
1019 },
1020 kw_args: struct {
1021 /// Pointer to first range if such exists.
1022 range: ?*Inst = null,
1023 special_prong: enum {
1024 none,
1025 @"else",
1026 underscore,
1027 } = .none,
1028 },
1029
1030 pub const Case = struct {
1031 item: *Inst,
1032 body: Module.Body,
1033 };
1034 };
990};1035};
9911036
992pub const ErrorMsg = struct {1037pub const ErrorMsg = struct {
...@@ -1218,8 +1263,8 @@ const Writer = struct {...@@ -1218,8 +1263,8 @@ const Writer = struct {
1218 bool => return stream.writeByte("01"[@boolToInt(param)]),1263 bool => return stream.writeByte("01"[@boolToInt(param)]),
1219 []u8, []const u8 => return stream.print("\"{Z}\"", .{param}),1264 []u8, []const u8 => return stream.print("\"{Z}\"", .{param}),
1220 BigIntConst, usize => return stream.print("{}", .{param}),1265 BigIntConst, usize => return stream.print("{}", .{param}),
1221 TypedValue => unreachable, // this is a special case1266 TypedValue => return stream.print("TypedValue{{ .ty = {}, .val = {}}}", .{ param.ty, param.val }),
1222 *IrModule.Decl => unreachable, // this is a special case1267 *IrModule.Decl => return stream.print("Decl({s})", .{param.name}),
1223 *Inst.Block => {1268 *Inst.Block => {
1224 const name = self.block_table.get(param).?;1269 const name = self.block_table.get(param).?;
1225 return stream.print("\"{Z}\"", .{name});1270 return stream.print("\"{Z}\"", .{name});
...@@ -1238,6 +1283,26 @@ const Writer = struct {...@@ -1238,6 +1283,26 @@ const Writer = struct {
1238 }1283 }
1239 try stream.writeByte(']');1284 try stream.writeByte(']');
1240 },1285 },
1286 []Inst.SwitchBr.Case => {
1287 if (param.len == 0) {
1288 return stream.writeAll("{}");
1289 }
1290 try stream.writeAll("{\n");
1291 for (param) |*case, i| {
1292 if (i != 0) {
1293 try stream.writeAll(",\n");
1294 }
1295 try stream.writeByteNTimes(' ', self.indent);
1296 self.indent += 2;
1297 try self.writeParamToStream(stream, &case.item);
1298 try stream.writeAll(" => ");
1299 try self.writeParamToStream(stream, &case.body);
1300 self.indent -= 2;
1301 }
1302 try stream.writeByte('\n');
1303 try stream.writeByteNTimes(' ', self.indent - 2);
1304 try stream.writeByte('}');
1305 },
1241 else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)),1306 else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)),
1242 }1307 }
1243 }1308 }
...@@ -1650,6 +1715,26 @@ const Parser = struct {...@@ -1650,6 +1715,26 @@ const Parser = struct {
1650 try requireEatBytes(self, "]");1715 try requireEatBytes(self, "]");
1651 return strings.toOwnedSlice();1716 return strings.toOwnedSlice();
1652 },1717 },
1718 []Inst.SwitchBr.Case => {
1719 try requireEatBytes(self, "{");
1720 skipSpace(self);
1721 if (eatByte(self, '}')) return &[0]Inst.SwitchBr.Case{};
1722
1723 var cases = std.ArrayList(Inst.SwitchBr.Case).init(&self.arena.allocator);
1724 while (true) {
1725 const cur = try cases.addOne();
1726 skipSpace(self);
1727 cur.item = try self.parseParameterGeneric(*Inst, body_ctx);
1728 skipSpace(self);
1729 try requireEatBytes(self, "=>");
1730 cur.body = try self.parseBody(body_ctx);
1731 skipSpace(self);
1732 if (!eatByte(self, ',')) break;
1733 }
1734 skipSpace(self);
1735 try requireEatBytes(self, "}");
1736 return cases.toOwnedSlice();
1737 },
1653 else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)),1738 else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)),
1654 }1739 }
1655 return self.fail("TODO parse parameter {}", .{@typeName(T)});1740 return self.fail("TODO parse parameter {}", .{@typeName(T)});
...@@ -1747,7 +1832,7 @@ pub fn dumpFn(old_module: IrModule, module_fn: *IrModule.Fn) void {...@@ -1747,7 +1832,7 @@ pub fn dumpFn(old_module: IrModule, module_fn: *IrModule.Fn) void {
1747 .arena = std.heap.ArenaAllocator.init(allocator),1832 .arena = std.heap.ArenaAllocator.init(allocator),
1748 .old_module = &old_module,1833 .old_module = &old_module,
1749 .next_auto_name = 0,1834 .next_auto_name = 0,
1750 .names = std.StringHashMap(void).init(allocator),1835 .names = std.StringArrayHashMap(void).init(allocator),
1751 .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator),1836 .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator),
1752 .indent = 0,1837 .indent = 0,
1753 .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator),1838 .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator),
...@@ -2244,6 +2329,8 @@ const EmitZIR = struct {...@@ -2244,6 +2329,8 @@ const EmitZIR = struct {
2244 .cmp_gte => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_gte).?, .cmp_gte),2329 .cmp_gte => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_gte).?, .cmp_gte),
2245 .cmp_gt => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_gt).?, .cmp_gt),2330 .cmp_gt => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_gt).?, .cmp_gt),
2246 .cmp_neq => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_neq).?, .cmp_neq),2331 .cmp_neq => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_neq).?, .cmp_neq),
2332 .booland => try self.emitBinOp(inst.src, new_body, inst.castTag(.booland).?, .booland),
2333 .boolor => try self.emitBinOp(inst.src, new_body, inst.castTag(.boolor).?, .boolor),
22472334
2248 .bitcast => try self.emitCast(inst.src, new_body, inst.castTag(.bitcast).?, .bitcast),2335 .bitcast => try self.emitCast(inst.src, new_body, inst.castTag(.bitcast).?, .bitcast),
2249 .intcast => try self.emitCast(inst.src, new_body, inst.castTag(.intcast).?, .intcast),2336 .intcast => try self.emitCast(inst.src, new_body, inst.castTag(.intcast).?, .intcast),
...@@ -2470,7 +2557,63 @@ const EmitZIR = struct {...@@ -2470,7 +2557,63 @@ const EmitZIR = struct {
2470 };2557 };
2471 break :blk &new_inst.base;2558 break :blk &new_inst.base;
2472 },2559 },
2560 .switchbr => blk: {
2561 const old_inst = inst.castTag(.switchbr).?;
2562 const cases = try self.arena.allocator.alloc(Inst.SwitchBr.Case, old_inst.cases.len);
2563 const new_inst = try self.arena.allocator.create(Inst.SwitchBr);
2564 new_inst.* = .{
2565 .base = .{
2566 .src = inst.src,
2567 .tag = Inst.SwitchBr.base_tag,
2568 },
2569 .positionals = .{
2570 .target_ptr = try self.resolveInst(new_body, old_inst.target_ptr),
2571 .cases = cases,
2572 .items = &[_]*Inst{}, // TODO this should actually be populated
2573 .else_body = undefined, // populated below
2574 },
2575 .kw_args = .{},
2576 };
24732577
2578 var body_tmp = std.ArrayList(*Inst).init(self.allocator);
2579 defer body_tmp.deinit();
2580
2581 for (old_inst.cases) |*case, i| {
2582 body_tmp.items.len = 0;
2583
2584 const case_deaths = try self.arena.allocator.alloc(*Inst, old_inst.caseDeaths(i).len);
2585 for (old_inst.caseDeaths(i)) |death, j| {
2586 case_deaths[j] = try self.resolveInst(new_body, death);
2587 }
2588 try self.body_metadata.put(&cases[i].body, .{ .deaths = case_deaths });
2589
2590 try self.emitBody(case.body, inst_table, &body_tmp);
2591 const item = (try self.emitTypedValue(inst.src, .{
2592 .ty = old_inst.target_ptr.ty.elemType(),
2593 .val = case.item,
2594 })).inst;
2595
2596 cases[i] = .{
2597 .item = item,
2598 .body = .{ .instructions = try self.arena.allocator.dupe(*Inst, body_tmp.items) },
2599 };
2600 }
2601 { // else
2602 const else_deaths = try self.arena.allocator.alloc(*Inst, old_inst.elseDeaths().len);
2603 for (old_inst.elseDeaths()) |death, j| {
2604 else_deaths[j] = try self.resolveInst(new_body, death);
2605 }
2606 try self.body_metadata.put(&new_inst.positionals.else_body, .{ .deaths = else_deaths });
2607
2608 body_tmp.items.len = 0;
2609 try self.emitBody(old_inst.else_body, inst_table, &body_tmp);
2610 new_inst.positionals.else_body = .{
2611 .instructions = try self.arena.allocator.dupe(*Inst, body_tmp.items),
2612 };
2613 }
2614
2615 break :blk &new_inst.base;
2616 },
2474 .varptr => @panic("TODO"),2617 .varptr => @panic("TODO"),
2475 };2618 };
2476 try self.metadata.put(new_inst, .{2619 try self.metadata.put(new_inst, .{
...@@ -2703,3 +2846,52 @@ const EmitZIR = struct {...@@ -2703,3 +2846,52 @@ const EmitZIR = struct {
2703 return decl;2846 return decl;
2704 }2847 }
2705};2848};
2849
2850/// For debugging purposes, like dumpFn but for unanalyzed zir blocks
2851pub fn dumpZir(allocator: *Allocator, kind: []const u8, decl_name: [*:0]const u8, instructions: []*Inst) !void {
2852 var fib = std.heap.FixedBufferAllocator.init(&[_]u8{});
2853 var module = Module{
2854 .decls = &[_]*Decl{},
2855 .arena = std.heap.ArenaAllocator.init(&fib.allocator),
2856 .metadata = std.AutoHashMap(*Inst, Module.MetaData).init(&fib.allocator),
2857 .body_metadata = std.AutoHashMap(*Module.Body, Module.BodyMetaData).init(&fib.allocator),
2858 };
2859 var write = Writer{
2860 .module = &module,
2861 .inst_table = InstPtrTable.init(allocator),
2862 .block_table = std.AutoHashMap(*Inst.Block, []const u8).init(allocator),
2863 .loop_table = std.AutoHashMap(*Inst.Loop, []const u8).init(allocator),
2864 .arena = std.heap.ArenaAllocator.init(allocator),
2865 .indent = 4,
2866 .next_instr_index = 0,
2867 };
2868 defer write.arena.deinit();
2869 defer write.inst_table.deinit();
2870 defer write.block_table.deinit();
2871 defer write.loop_table.deinit();
2872
2873 try write.inst_table.ensureCapacity(@intCast(u32, instructions.len));
2874
2875 const stderr = std.io.getStdErr().outStream();
2876 try stderr.print("{} {s} {{ // unanalyzed\n", .{ kind, decl_name });
2877
2878 for (instructions) |inst| {
2879 const my_i = write.next_instr_index;
2880 write.next_instr_index += 1;
2881
2882 if (inst.cast(Inst.Block)) |block| {
2883 const name = try std.fmt.allocPrint(&write.arena.allocator, "label_{}", .{my_i});
2884 try write.block_table.put(block, name);
2885 } else if (inst.cast(Inst.Loop)) |loop| {
2886 const name = try std.fmt.allocPrint(&write.arena.allocator, "loop_{}", .{my_i});
2887 try write.loop_table.put(loop, name);
2888 }
2889
2890 try write.inst_table.putNoClobber(inst, .{ .inst = inst, .index = my_i, .name = "inst" });
2891 try stderr.print(" %{} ", .{my_i});
2892 try write.writeInstToStream(stderr, inst);
2893 try stderr.writeByte('\n');
2894 }
2895
2896 try stderr.print("}} // {} {s}\n\n", .{ kind, decl_name });
2897}
src/zir_sema.zig+256-7
...@@ -135,6 +135,10 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!...@@ -135,6 +135,10 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
135 .slice => return analyzeInstSlice(mod, scope, old_inst.castTag(.slice).?),135 .slice => return analyzeInstSlice(mod, scope, old_inst.castTag(.slice).?),
136 .slice_start => return analyzeInstSliceStart(mod, scope, old_inst.castTag(.slice_start).?),136 .slice_start => return analyzeInstSliceStart(mod, scope, old_inst.castTag(.slice_start).?),
137 .import => return analyzeInstImport(mod, scope, old_inst.castTag(.import).?),137 .import => return analyzeInstImport(mod, scope, old_inst.castTag(.import).?),
138 .switchbr => return analyzeInstSwitchBr(mod, scope, old_inst.castTag(.switchbr).?),
139 .switch_range => return analyzeInstSwitchRange(mod, scope, old_inst.castTag(.switch_range).?),
140 .booland => return analyzeInstBoolOp(mod, scope, old_inst.castTag(.booland).?),
141 .boolor => return analyzeInstBoolOp(mod, scope, old_inst.castTag(.boolor).?),
138 }142 }
139}143}
140144
...@@ -551,10 +555,13 @@ fn analyzeInstBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_c...@@ -551,10 +555,13 @@ fn analyzeInstBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_c
551555
552 try analyzeBody(mod, &child_block.base, inst.positionals.body);556 try analyzeBody(mod, &child_block.base, inst.positionals.body);
553557
554 const copied_instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items);558 try parent_block.instructions.appendSlice(mod.gpa, child_block.instructions.items);
555 try parent_block.instructions.appendSlice(mod.gpa, copied_instructions);
556559
557 return copied_instructions[copied_instructions.len - 1];560 // comptime blocks won't generate any runtime values
561 if (child_block.instructions.items.len == 0)
562 return mod.constVoid(scope, inst.base.src);
563
564 return parent_block.instructions.items[parent_block.instructions.items.len - 1];
558}565}
559566
560fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_comptime: bool) InnerError!*Inst {567fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_comptime: bool) InnerError!*Inst {
...@@ -1204,13 +1211,233 @@ fn analyzeInstSliceStart(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) Inn...@@ -1204,13 +1211,233 @@ fn analyzeInstSliceStart(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) Inn
1204 return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null);1211 return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null);
1205}1212}
12061213
1214fn analyzeInstSwitchRange(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
1215 const start = try resolveInst(mod, scope, inst.positionals.lhs);
1216 const end = try resolveInst(mod, scope, inst.positionals.rhs);
1217
1218 switch (start.ty.zigTypeTag()) {
1219 .Int, .ComptimeInt => {},
1220 else => return mod.constVoid(scope, inst.base.src),
1221 }
1222 switch (end.ty.zigTypeTag()) {
1223 .Int, .ComptimeInt => {},
1224 else => return mod.constVoid(scope, inst.base.src),
1225 }
1226 if (start.value()) |start_val| {
1227 if (end.value()) |end_val| {
1228 if (start_val.compare(.gte, end_val)) {
1229 return mod.fail(scope, inst.base.src, "range start value must be smaller than the end value", .{});
1230 }
1231 }
1232 }
1233 return mod.constVoid(scope, inst.base.src);
1234}
1235
1236fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) InnerError!*Inst {
1237 const target_ptr = try resolveInst(mod, scope, inst.positionals.target_ptr);
1238 const target = try mod.analyzeDeref(scope, inst.base.src, target_ptr, inst.positionals.target_ptr.src);
1239 try validateSwitch(mod, scope, target, inst);
1240
1241 if (try mod.resolveDefinedValue(scope, target)) |target_val| {
1242 for (inst.positionals.cases) |case| {
1243 const resolved = try resolveInst(mod, scope, case.item);
1244 const casted = try mod.coerce(scope, target.ty, resolved);
1245 const item = try mod.resolveConstValue(scope, casted);
1246
1247 if (target_val.eql(item)) {
1248 try analyzeBody(mod, scope, case.body);
1249 return mod.constNoReturn(scope, inst.base.src);
1250 }
1251 }
1252 try analyzeBody(mod, scope, inst.positionals.else_body);
1253 return mod.constNoReturn(scope, inst.base.src);
1254 }
1255
1256 if (inst.positionals.cases.len == 0) {
1257 // no cases just analyze else_branch
1258 try analyzeBody(mod, scope, inst.positionals.else_body);
1259 return mod.constNoReturn(scope, inst.base.src);
1260 }
1261
1262 const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src);
1263 const cases = try parent_block.arena.alloc(Inst.SwitchBr.Case, inst.positionals.cases.len);
1264
1265 var case_block: Scope.Block = .{
1266 .parent = parent_block,
1267 .func = parent_block.func,
1268 .decl = parent_block.decl,
1269 .instructions = .{},
1270 .arena = parent_block.arena,
1271 .is_comptime = parent_block.is_comptime,
1272 };
1273 defer case_block.instructions.deinit(mod.gpa);
1274
1275 for (inst.positionals.cases) |case, i| {
1276 // Reset without freeing.
1277 case_block.instructions.items.len = 0;
1278
1279 const resolved = try resolveInst(mod, scope, case.item);
1280 const casted = try mod.coerce(scope, target.ty, resolved);
1281 const item = try mod.resolveConstValue(scope, casted);
1282
1283 try analyzeBody(mod, &case_block.base, case.body);
1284
1285 cases[i] = .{
1286 .item = item,
1287 .body = .{ .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items) },
1288 };
1289 }
1290
1291 case_block.instructions.items.len = 0;
1292 try analyzeBody(mod, &case_block.base, inst.positionals.else_body);
1293
1294 const else_body: ir.Body = .{
1295 .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items),
1296 };
1297
1298 return mod.addSwitchBr(parent_block, inst.base.src, target_ptr, cases, else_body);
1299}
1300
1301fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.SwitchBr) InnerError!void {
1302 // validate usage of '_' prongs
1303 if (inst.kw_args.special_prong == .underscore and target.ty.zigTypeTag() != .Enum) {
1304 return mod.fail(scope, inst.base.src, "'_' prong only allowed when switching on non-exhaustive enums", .{});
1305 // TODO notes "'_' prong here" inst.positionals.cases[last].src
1306 }
1307
1308 // check that target type supports ranges
1309 if (inst.kw_args.range) |range_inst| {
1310 switch (target.ty.zigTypeTag()) {
1311 .Int, .ComptimeInt => {},
1312 else => {
1313 return mod.fail(scope, target.src, "ranges not allowed when switching on type {}", .{target.ty});
1314 // TODO notes "range used here" range_inst.src
1315 },
1316 }
1317 }
1318
1319 // validate for duplicate items/missing else prong
1320 switch (target.ty.zigTypeTag()) {
1321 .Enum => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Enum", .{}),
1322 .ErrorSet => return mod.fail(scope, inst.base.src, "TODO validateSwitch .ErrorSet", .{}),
1323 .Union => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Union", .{}),
1324 .Int, .ComptimeInt => {
1325 var range_set = @import("RangeSet.zig").init(mod.gpa);
1326 defer range_set.deinit();
1327
1328 for (inst.positionals.items) |item| {
1329 const maybe_src = if (item.castTag(.switch_range)) |range| blk: {
1330 const start_resolved = try resolveInst(mod, scope, range.positionals.lhs);
1331 const start_casted = try mod.coerce(scope, target.ty, start_resolved);
1332 const end_resolved = try resolveInst(mod, scope, range.positionals.rhs);
1333 const end_casted = try mod.coerce(scope, target.ty, end_resolved);
1334
1335 break :blk try range_set.add(
1336 try mod.resolveConstValue(scope, start_casted),
1337 try mod.resolveConstValue(scope, end_casted),
1338 item.src,
1339 );
1340 } else blk: {
1341 const resolved = try resolveInst(mod, scope, item);
1342 const casted = try mod.coerce(scope, target.ty, resolved);
1343 const value = try mod.resolveConstValue(scope, casted);
1344 break :blk try range_set.add(value, value, item.src);
1345 };
1346
1347 if (maybe_src) |previous_src| {
1348 return mod.fail(scope, item.src, "duplicate switch value", .{});
1349 // TODO notes "previous value is here" previous_src
1350 }
1351 }
1352
1353 if (target.ty.zigTypeTag() == .Int) {
1354 var arena = std.heap.ArenaAllocator.init(mod.gpa);
1355 defer arena.deinit();
1356
1357 const start = try target.ty.minInt(&arena, mod.getTarget());
1358 const end = try target.ty.maxInt(&arena, mod.getTarget());
1359 if (try range_set.spans(start, end)) {
1360 if (inst.kw_args.special_prong == .@"else") {
1361 return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{});
1362 }
1363 return;
1364 }
1365 }
1366
1367 if (inst.kw_args.special_prong != .@"else") {
1368 return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{});
1369 }
1370 },
1371 .Bool => {
1372 var true_count: u8 = 0;
1373 var false_count: u8 = 0;
1374 for (inst.positionals.items) |item| {
1375 const resolved = try resolveInst(mod, scope, item);
1376 const casted = try mod.coerce(scope, Type.initTag(.bool), resolved);
1377 if ((try mod.resolveConstValue(scope, casted)).toBool()) {
1378 true_count += 1;
1379 } else {
1380 false_count += 1;
1381 }
1382
1383 if (true_count + false_count > 2) {
1384 return mod.fail(scope, item.src, "duplicate switch value", .{});
1385 }
1386 }
1387 if ((true_count + false_count < 2) and inst.kw_args.special_prong != .@"else") {
1388 return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{});
1389 }
1390 if ((true_count + false_count == 2) and inst.kw_args.special_prong == .@"else") {
1391 return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{});
1392 }
1393 },
1394 .EnumLiteral, .Void, .Fn, .Pointer, .Type => {
1395 if (inst.kw_args.special_prong != .@"else") {
1396 return mod.fail(scope, inst.base.src, "else prong required when switching on type '{}'", .{target.ty});
1397 }
1398
1399 var seen_values = std.HashMap(Value, usize, Value.hash, Value.eql, std.hash_map.DefaultMaxLoadPercentage).init(mod.gpa);
1400 defer seen_values.deinit();
1401
1402 for (inst.positionals.items) |item| {
1403 const resolved = try resolveInst(mod, scope, item);
1404 const casted = try mod.coerce(scope, target.ty, resolved);
1405 const val = try mod.resolveConstValue(scope, casted);
1406
1407 if (try seen_values.fetchPut(val, item.src)) |prev| {
1408 return mod.fail(scope, item.src, "duplicate switch value", .{});
1409 // TODO notes "previous value here" prev.value
1410 }
1411 }
1412 },
1413
1414 .ErrorUnion,
1415 .NoReturn,
1416 .Array,
1417 .Struct,
1418 .Undefined,
1419 .Null,
1420 .Optional,
1421 .BoundFn,
1422 .Opaque,
1423 .Vector,
1424 .Frame,
1425 .AnyFrame,
1426 .ComptimeFloat,
1427 .Float,
1428 => {
1429 return mod.fail(scope, target.src, "invalid switch target type '{}'", .{target.ty});
1430 },
1431 }
1432}
1433
1207fn analyzeInstImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {1434fn analyzeInstImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
1208 const operand = try resolveConstString(mod, scope, inst.positionals.operand);1435 const operand = try resolveConstString(mod, scope, inst.positionals.operand);
12091436
1210 const file_scope = mod.analyzeImport(scope, inst.base.src, operand) catch |err| switch (err) {1437 const file_scope = mod.analyzeImport(scope, inst.base.src, operand) catch |err| switch (err) {
1211 // error.ImportOutsidePkgPath => {1438 error.ImportOutsidePkgPath => {
1212 // return mod.fail(scope, inst.base.src, "import of file outside package path: '{}'", .{operand});1439 return mod.fail(scope, inst.base.src, "import of file outside package path: '{}'", .{operand});
1213 // },1440 },
1214 error.FileNotFound => {1441 error.FileNotFound => {
1215 return mod.fail(scope, inst.base.src, "unable to find '{}'", .{operand});1442 return mod.fail(scope, inst.base.src, "unable to find '{}'", .{operand});
1216 },1443 },
...@@ -1456,6 +1683,28 @@ fn analyzeInstBoolNot(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerEr...@@ -1456,6 +1683,28 @@ fn analyzeInstBoolNot(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerEr
1456 return mod.addUnOp(b, inst.base.src, bool_type, .not, operand);1683 return mod.addUnOp(b, inst.base.src, bool_type, .not, operand);
1457}1684}
14581685
1686fn analyzeInstBoolOp(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
1687 const bool_type = Type.initTag(.bool);
1688 const uncasted_lhs = try resolveInst(mod, scope, inst.positionals.lhs);
1689 const lhs = try mod.coerce(scope, bool_type, uncasted_lhs);
1690 const uncasted_rhs = try resolveInst(mod, scope, inst.positionals.rhs);
1691 const rhs = try mod.coerce(scope, bool_type, uncasted_rhs);
1692
1693 const is_bool_or = inst.base.tag == .boolor;
1694
1695 if (lhs.value()) |lhs_val| {
1696 if (rhs.value()) |rhs_val| {
1697 if (is_bool_or) {
1698 return mod.constBool(scope, inst.base.src, lhs_val.toBool() or rhs_val.toBool());
1699 } else {
1700 return mod.constBool(scope, inst.base.src, lhs_val.toBool() and rhs_val.toBool());
1701 }
1702 }
1703 }
1704 const b = try mod.requireRuntimeBlock(scope, inst.base.src);
1705 return mod.addBinOp(b, inst.base.src, bool_type, if (is_bool_or) .boolor else .booland, lhs, rhs);
1706}
1707
1459fn analyzeInstIsNonNull(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp, invert_logic: bool) InnerError!*Inst {1708fn analyzeInstIsNonNull(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp, invert_logic: bool) InnerError!*Inst {
1460 const operand = try resolveInst(mod, scope, inst.positionals.operand);1709 const operand = try resolveInst(mod, scope, inst.positionals.operand);
1461 return mod.analyzeIsNull(scope, inst.base.src, operand, invert_logic);1710 return mod.analyzeIsNull(scope, inst.base.src, operand, invert_logic);
...@@ -1473,7 +1722,7 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE...@@ -1473,7 +1722,7 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE
1473 if (try mod.resolveDefinedValue(scope, cond)) |cond_val| {1722 if (try mod.resolveDefinedValue(scope, cond)) |cond_val| {
1474 const body = if (cond_val.toBool()) &inst.positionals.then_body else &inst.positionals.else_body;1723 const body = if (cond_val.toBool()) &inst.positionals.then_body else &inst.positionals.else_body;
1475 try analyzeBody(mod, scope, body.*);1724 try analyzeBody(mod, scope, body.*);
1476 return mod.constVoid(scope, inst.base.src);1725 return mod.constNoReturn(scope, inst.base.src);
1477 }1726 }
14781727
1479 const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src);1728 const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src);
test/stage2/test.zig+37
...@@ -974,6 +974,43 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -974,6 +974,43 @@ pub fn addCases(ctx: *TestContext) !void {
974 ,974 ,
975 "hello\nhello\nhello\nhello\nhello\n",975 "hello\nhello\nhello\nhello\nhello\n",
976 );976 );
977
978 // comptime switch
979
980 // Basic for loop
981 case.addCompareOutput(
982 \\pub export fn _start() noreturn {
983 \\ assert(foo() == 1);
984 \\ exit();
985 \\}
986 \\
987 \\fn foo() u32 {
988 \\ const a: comptime_int = 1;
989 \\ var b: u32 = 0;
990 \\ switch (a) {
991 \\ 1 => b = 1,
992 \\ 2 => b = 2,
993 \\ else => unreachable,
994 \\ }
995 \\ return b;
996 \\}
997 \\
998 \\pub fn assert(ok: bool) void {
999 \\ if (!ok) unreachable; // assertion failure
1000 \\}
1001 \\
1002 \\fn exit() noreturn {
1003 \\ asm volatile ("syscall"
1004 \\ :
1005 \\ : [number] "{rax}" (231),
1006 \\ [arg1] "{rdi}" (0)
1007 \\ : "rcx", "r11", "memory"
1008 \\ );
1009 \\ unreachable;
1010 \\}
1011 ,
1012 "",
1013 );
977 }1014 }
9781015
979 {1016 {