| author | |
| committer | |
| log | 7c8d9cfa40ab96aede2a7fe8ec9dce6f10bc910a |
| tree | bac19bf24748e7d63e3bbf86ca5b34c4cfa2c754 |
| parent | bb6e39e274eb0a68bfb1029ab75d1791abeb2911 |
| parent | 22ec5e085914d9fd7b17a28a8d3ad01258f3ad03 |
| signature |
Stage2 switch and package imports14 files changed, 1379 insertions(+), 30 deletions(-)
src/Compilation.zig+1| ... | ... | @@ -660,6 +660,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { |
| 660 | 660 | .source = .{ .unloaded = {} }, |
| 661 | 661 | .contents = .{ .not_available = {} }, |
| 662 | 662 | .status = .never_loaded, |
| 663 | .pkg = root_pkg, | |
| 663 | 664 | .root_container = .{ |
| 664 | 665 | .file_scope = root_scope, |
| 665 | 666 | .decls = .{}, |
src/Module.zig+82-8| ... | ... | @@ -469,6 +469,22 @@ pub const Scope = struct { |
| 469 | 469 | } |
| 470 | 470 | } |
| 471 | 471 | |
| 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 | 488 | /// Asserts the scope is a namespace Scope and removes the Decl from the namespace. |
| 473 | 489 | pub fn removeDecl(base: *Scope, child: *Decl) void { |
| 474 | 490 | switch (base.tag) { |
| ... | ... | @@ -576,6 +592,8 @@ pub const Scope = struct { |
| 576 | 592 | unloaded_parse_failure, |
| 577 | 593 | loaded_success, |
| 578 | 594 | }, |
| 595 | /// Package that this file is a part of, managed externally. | |
| 596 | pkg: *Package, | |
| 579 | 597 | |
| 580 | 598 | root_container: Container, |
| 581 | 599 | |
| ... | ... | @@ -614,7 +632,7 @@ pub const Scope = struct { |
| 614 | 632 | pub fn getSource(self: *File, module: *Module) ![:0]const u8 { |
| 615 | 633 | switch (self.source) { |
| 616 | 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 | 636 | module.gpa, |
| 619 | 637 | self.sub_file_path, |
| 620 | 638 | std.math.maxInt(u32), |
| ... | ... | @@ -1036,6 +1054,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1036 | 1054 | .param_types = param_types, |
| 1037 | 1055 | }, .{}); |
| 1038 | 1056 | |
| 1057 | if (self.comp.verbose_ir) { | |
| 1058 | zir.dumpZir(self.gpa, "fn_type", decl.name, fn_type_scope.instructions.items) catch {}; | |
| 1059 | } | |
| 1060 | ||
| 1039 | 1061 | // We need the memory for the Type to go into the arena for the Decl |
| 1040 | 1062 | var decl_arena = std.heap.ArenaAllocator.init(self.gpa); |
| 1041 | 1063 | errdefer decl_arena.deinit(); |
| ... | ... | @@ -1109,6 +1131,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1109 | 1131 | _ = try astgen.addZIRNoOp(self, &gen_scope.base, src, .returnvoid); |
| 1110 | 1132 | } |
| 1111 | 1133 | |
| 1134 | if (self.comp.verbose_ir) { | |
| 1135 | zir.dumpZir(self.gpa, "fn_body", decl.name, gen_scope.instructions.items) catch {}; | |
| 1136 | } | |
| 1137 | ||
| 1112 | 1138 | const fn_zir = try gen_scope_arena.allocator.create(Fn.ZIR); |
| 1113 | 1139 | fn_zir.* = .{ |
| 1114 | 1140 | .body = .{ |
| ... | ... | @@ -1240,6 +1266,9 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1240 | 1266 | |
| 1241 | 1267 | const src = tree.token_locs[init_node.firstToken()].start; |
| 1242 | 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 | } | |
| 1243 | 1272 | |
| 1244 | 1273 | var inner_block: Scope.Block = .{ |
| 1245 | 1274 | .parent = null, |
| ... | ... | @@ -1281,6 +1310,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1281 | 1310 | .val = Value.initTag(.type_type), |
| 1282 | 1311 | }); |
| 1283 | 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 | 1317 | const ty = try zir_sema.analyzeBodyValueAsType(self, &block_scope, var_type, .{ |
| 1285 | 1318 | .instructions = type_scope.instructions.items, |
| 1286 | 1319 | }); |
| ... | ... | @@ -1354,6 +1387,9 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1354 | 1387 | defer gen_scope.instructions.deinit(self.gpa); |
| 1355 | 1388 | |
| 1356 | 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 | } | |
| 1357 | 1393 | |
| 1358 | 1394 | var block_scope: Scope.Block = .{ |
| 1359 | 1395 | .parent = null, |
| ... | ... | @@ -2080,6 +2116,29 @@ pub fn addCall( |
| 2080 | 2116 | return &inst.base; |
| 2081 | 2117 | } |
| 2082 | 2118 | |
| 2119 | pub 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 | ||
| 2083 | 2142 | pub fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*Inst { |
| 2084 | 2143 | const const_inst = try scope.arena().create(Inst.Constant); |
| 2085 | 2144 | const_inst.* = .{ |
| ... | ... | @@ -2400,28 +2459,43 @@ pub fn analyzeSlice(self: *Module, scope: *Scope, src: usize, array_ptr: *Inst, |
| 2400 | 2459 | } |
| 2401 | 2460 | |
| 2402 | 2461 | pub fn analyzeImport(self: *Module, scope: *Scope, src: usize, target_string: []const u8) !*Scope.File { |
| 2403 | // TODO if (package_table.get(target_string)) |pkg| | |
| 2404 | if (self.import_table.get(target_string)) |some| { | |
| 2462 | const cur_pkg = scope.getOwnerPkg(); | |
| 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 | 2474 | return some; |
| 2406 | 2475 | } |
| 2407 | 2476 | |
| 2408 | // TODO check for imports outside of pkg path | |
| 2409 | if (false) return error.ImportOutsidePkgPath; | |
| 2477 | if (found_pkg == null) { | |
| 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 | } | |
| 2410 | 2485 | |
| 2411 | 2486 | // TODO Scope.Container arena for ty and sub_file_path |
| 2412 | 2487 | const struct_payload = try self.gpa.create(Type.Payload.EmptyStruct); |
| 2413 | 2488 | errdefer self.gpa.destroy(struct_payload); |
| 2414 | 2489 | const file_scope = try self.gpa.create(Scope.File); |
| 2415 | 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); | |
| 2418 | 2491 | |
| 2419 | 2492 | struct_payload.* = .{ .scope = &file_scope.root_container }; |
| 2420 | 2493 | file_scope.* = .{ |
| 2421 | .sub_file_path = file_path, | |
| 2494 | .sub_file_path = resolved_path, | |
| 2422 | 2495 | .source = .{ .unloaded = {} }, |
| 2423 | 2496 | .contents = .{ .not_available = {} }, |
| 2424 | 2497 | .status = .never_loaded, |
| 2498 | .pkg = found_pkg orelse cur_pkg, | |
| 2425 | 2499 | .root_container = .{ |
| 2426 | 2500 | .file_scope = file_scope, |
| 2427 | 2501 | .decls = .{}, |
src/RangeSet.zig created+76| ... | ... | @@ -0,0 +1,76 @@ |
| 1 | const std = @import("std"); | |
| 2 | const Order = std.math.Order; | |
| 3 | const Value = @import("value.zig").Value; | |
| 4 | const RangeSet = @This(); | |
| 5 | ||
| 6 | ranges: std.ArrayList(Range), | |
| 7 | ||
| 8 | pub const Range = struct { | |
| 9 | start: Value, | |
| 10 | end: Value, | |
| 11 | src: usize, | |
| 12 | }; | |
| 13 | ||
| 14 | pub fn init(allocator: *std.mem.Allocator) RangeSet { | |
| 15 | return .{ | |
| 16 | .ranges = std.ArrayList(Range).init(allocator), | |
| 17 | }; | |
| 18 | } | |
| 19 | ||
| 20 | pub fn deinit(self: *RangeSet) void { | |
| 21 | self.ranges.deinit(); | |
| 22 | } | |
| 23 | ||
| 24 | pub 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 | |
| 42 | fn lessThan(_: void, a: Range, b: Range) bool { | |
| 43 | return a.start.compare(.lt, b.start); | |
| 44 | } | |
| 45 | ||
| 46 | pub 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 | 183 | .VarDecl => unreachable, // Handled in `blockExpr`. |
| 184 | 184 | .SwitchCase => unreachable, // Handled in `switchExpr`. |
| 185 | 185 | .SwitchElse => unreachable, // Handled in `switchExpr`. |
| 186 | .Range => unreachable, // Handled in `switchExpr`. | |
| 186 | 187 | .Else => unreachable, // Handled explicitly the control flow expression functions. |
| 187 | 188 | .Payload => unreachable, // Handled explicitly. |
| 188 | 189 | .PointerPayload => unreachable, // Handled explicitly. |
| ... | ... | @@ -279,9 +280,9 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr |
| 279 | 280 | .Catch => return catchExpr(mod, scope, rl, node.castTag(.Catch).?), |
| 280 | 281 | .Comptime => return comptimeKeyword(mod, scope, rl, node.castTag(.Comptime).?), |
| 281 | 282 | .OrElse => return orelseExpr(mod, scope, rl, node.castTag(.OrElse).?), |
| 283 | .Switch => return switchExpr(mod, scope, rl, node.castTag(.Switch).?), | |
| 282 | 284 | |
| 283 | 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 | 286 | .Await => return mod.failNode(scope, node, "TODO implement astgen.expr for .Await", .{}), |
| 286 | 287 | .Resume => return mod.failNode(scope, node, "TODO implement astgen.expr for .Resume", .{}), |
| 287 | 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 | 290 | .ArrayInitializerDot => return mod.failNode(scope, node, "TODO implement astgen.expr for .ArrayInitializerDot", .{}), |
| 290 | 291 | .StructInitializer => return mod.failNode(scope, node, "TODO implement astgen.expr for .StructInitializer", .{}), |
| 291 | 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 | 293 | .Suspend => return mod.failNode(scope, node, "TODO implement astgen.expr for .Suspend", .{}), |
| 294 | 294 | .Continue => return mod.failNode(scope, node, "TODO implement astgen.expr for .Continue", .{}), |
| 295 | 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 | 1561 | return &for_block.base; |
| 1562 | 1562 | } |
| 1563 | 1563 | |
| 1564 | fn 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 | ||
| 1575 | fn 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 | ||
| 1787 | fn 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 | ||
| 1564 | 1803 | fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst { |
| 1565 | 1804 | const tree = scope.tree(); |
| 1566 | 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 | 758 | .br => return self.genBr(inst.castTag(.br).?), |
| 759 | 759 | .breakpoint => return self.genBreakpoint(inst.src), |
| 760 | 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 | 763 | .call => return self.genCall(inst.castTag(.call).?), |
| 762 | 764 | .cmp_lt => return self.genCmp(inst.castTag(.cmp_lt).?, .lt), |
| 763 | 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 | 784 | .retvoid => return self.genRetVoid(inst.castTag(.retvoid).?), |
| 783 | 785 | .store => return self.genStore(inst.castTag(.store).?), |
| 784 | 786 | .sub => return self.genSub(inst.castTag(.sub).?), |
| 787 | .switchbr => return self.genSwitch(inst.castTag(.switchbr).?), | |
| 785 | 788 | .unreach => return MCValue{ .unreach = {} }, |
| 786 | 789 | .unwrap_optional => return self.genUnwrapOptional(inst.castTag(.unwrap_optional).?), |
| 787 | 790 | .wrap_optional => return self.genWrapOptional(inst.castTag(.wrap_optional).?), |
| ... | ... | @@ -1989,6 +1992,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1989 | 1992 | return @bitCast(MCValue, inst.codegen.mcv); |
| 1990 | 1993 | } |
| 1991 | 1994 | |
| 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 | 2001 | fn performReloc(self: *Self, src: usize, reloc: Reloc) !void { |
| 1993 | 2002 | switch (reloc) { |
| 1994 | 2003 | .rel32 => |pos| { |
| ... | ... | @@ -2023,6 +2032,21 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2023 | 2032 | return self.brVoid(inst.base.src, inst.block); |
| 2024 | 2033 | } |
| 2025 | 2034 | |
| 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 | 2050 | fn brVoid(self: *Self, src: usize, block: *ir.Inst.Block) !MCValue { |
| 2027 | 2051 | // Emit a jump with a relocation. It will be patched up after the block ends. |
| 2028 | 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 | 74 | isnonnull, |
| 75 | 75 | isnull, |
| 76 | 76 | iserr, |
| 77 | booland, | |
| 78 | boolor, | |
| 77 | 79 | /// Read a value from a pointer. |
| 78 | 80 | load, |
| 79 | 81 | loop, |
| ... | ... | @@ -91,6 +93,7 @@ pub const Inst = struct { |
| 91 | 93 | intcast, |
| 92 | 94 | unwrap_optional, |
| 93 | 95 | wrap_optional, |
| 96 | switchbr, | |
| 94 | 97 | |
| 95 | 98 | pub fn Type(tag: Tag) type { |
| 96 | 99 | return switch (tag) { |
| ... | ... | @@ -125,6 +128,8 @@ pub const Inst = struct { |
| 125 | 128 | .cmp_gt, |
| 126 | 129 | .cmp_neq, |
| 127 | 130 | .store, |
| 131 | .booland, | |
| 132 | .boolor, | |
| 128 | 133 | => BinOp, |
| 129 | 134 | |
| 130 | 135 | .arg => Arg, |
| ... | ... | @@ -137,6 +142,7 @@ pub const Inst = struct { |
| 137 | 142 | .constant => Constant, |
| 138 | 143 | .loop => Loop, |
| 139 | 144 | .varptr => VarPtr, |
| 145 | .switchbr => SwitchBr, | |
| 140 | 146 | }; |
| 141 | 147 | } |
| 142 | 148 | |
| ... | ... | @@ -458,6 +464,47 @@ pub const Inst = struct { |
| 458 | 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 | }; |
| 462 | 509 | |
| 463 | 510 | pub const Body = struct { |
src/liveness.zig+86| ... | ... | @@ -144,6 +144,92 @@ fn analyzeInst( |
| 144 | 144 | // instruction, and the deaths flag for the CondBr instruction will indicate whether the |
| 145 | 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 | 233 | else => {}, |
| 148 | 234 | } |
| 149 | 235 |
src/main.zig+1| ... | ... | @@ -2421,6 +2421,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { |
| 2421 | 2421 | var stdin_flag: bool = false; |
| 2422 | 2422 | var check_flag: bool = false; |
| 2423 | 2423 | var input_files = ArrayList([]const u8).init(gpa); |
| 2424 | defer input_files.deinit(); | |
| 2424 | 2425 | |
| 2425 | 2426 | { |
| 2426 | 2427 | var i: usize = 0; |
src/test.zig+4-4| ... | ... | @@ -463,10 +463,10 @@ pub const TestContext = struct { |
| 463 | 463 | |
| 464 | 464 | var cache_dir = try tmp.dir.makeOpenPath("zig-cache", .{}); |
| 465 | 465 | defer cache_dir.close(); |
| 466 | const bogus_path = "bogus"; // TODO this will need to be fixed before we can test LLVM extensions | |
| 466 | const tmp_path = try std.fs.path.join(arena, &[_][]const u8{ ".", "zig-cache", "tmp", &tmp.sub_path }); | |
| 467 | 467 | const zig_cache_directory: Compilation.Directory = .{ |
| 468 | 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 | }; |
| 471 | 471 | |
| 472 | 472 | const tmp_src_path = switch (case.extension) { |
| ... | ... | @@ -475,7 +475,7 @@ pub const TestContext = struct { |
| 475 | 475 | }; |
| 476 | 476 | |
| 477 | 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 | 479 | .root_src_path = tmp_src_path, |
| 480 | 480 | }; |
| 481 | 481 | |
| ... | ... | @@ -488,7 +488,7 @@ pub const TestContext = struct { |
| 488 | 488 | }); |
| 489 | 489 | |
| 490 | 490 | const emit_directory: Compilation.Directory = .{ |
| 491 | .path = bogus_path, | |
| 491 | .path = tmp_path, | |
| 492 | 492 | .handle = tmp.dir, |
| 493 | 493 | }; |
| 494 | 494 | const emit_bin: Compilation.EmitLoc = .{ |
src/type.zig+72| ... | ... | @@ -2863,6 +2863,78 @@ pub const Type = extern union { |
| 2863 | 2863 | }; |
| 2864 | 2864 | } |
| 2865 | 2865 | |
| 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 | 2938 | /// This enum does not directly correspond to `std.builtin.TypeId` because |
| 2867 | 2939 | /// it has extra enum tags in it, as a way of using less memory. For example, |
| 2868 | 2940 | /// 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 | 565 | .int_u64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_u64).?.int).toConst(), |
| 566 | 566 | .int_i64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_i64).?.int).toConst(), |
| 567 | 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 | } |
| 571 | 571 | |
| ... | ... | @@ -1233,15 +1233,170 @@ pub const Value = extern union { |
| 1233 | 1233 | } |
| 1234 | 1234 | |
| 1235 | 1235 | pub fn eql(a: Value, b: Value) bool { |
| 1236 | if (a.tag() == b.tag() and a.tag() == .enum_literal) { | |
| 1237 | const a_name = @fieldParentPtr(Payload.Bytes, "base", a.ptr_otherwise).data; | |
| 1238 | const b_name = @fieldParentPtr(Payload.Bytes, "base", b.ptr_otherwise).data; | |
| 1239 | return std.mem.eql(u8, a_name, b_name); | |
| 1236 | if (a.tag() == b.tag()) { | |
| 1237 | if (a.tag() == .void_value or a.tag() == .null_value) { | |
| 1238 | return true; | |
| 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 | 1253 | return compare(a, .eq, b); |
| 1243 | 1254 | } |
| 1244 | 1255 | |
| 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 | 1400 | /// Asserts the value is a pointer and dereferences it. |
| 1246 | 1401 | /// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis. |
| 1247 | 1402 | pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value { |
| ... | ... | @@ -1521,6 +1676,87 @@ pub const Value = extern union { |
| 1521 | 1676 | }; |
| 1522 | 1677 | } |
| 1523 | 1678 | |
| 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 | 1760 | /// This type is not copyable since it may contain pointers to its inner data. |
| 1525 | 1761 | pub const Payload = struct { |
| 1526 | 1762 | tag: Tag, |
| ... | ... | @@ -1655,3 +1891,18 @@ pub const Value = extern union { |
| 1655 | 1891 | limbs: [(@sizeOf(u64) / @sizeOf(std.math.big.Limb)) + 1]std.math.big.Limb, |
| 1656 | 1892 | }; |
| 1657 | 1893 | }; |
| 1894 | ||
| 1895 | test "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 | 85 | block_comptime, |
| 86 | 86 | /// Same as `block_flat` but additionally makes the inner instructions execute at comptime. |
| 87 | 87 | block_comptime_flat, |
| 88 | /// Boolean AND. See also `bitand`. | |
| 89 | booland, | |
| 88 | 90 | /// Boolean NOT. See also `bitnot`. |
| 89 | 91 | boolnot, |
| 92 | /// Boolean OR. See also `bitor`. | |
| 93 | boolor, | |
| 90 | 94 | /// Return a value from a `Block`. |
| 91 | 95 | @"break", |
| 92 | 96 | breakpoint, |
| ... | ... | @@ -272,6 +276,12 @@ pub const Inst = struct { |
| 272 | 276 | ensure_err_payload_void, |
| 273 | 277 | /// Enum literal |
| 274 | 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, | |
| 275 | 285 | |
| 276 | 286 | pub fn Type(tag: Tag) type { |
| 277 | 287 | return switch (tag) { |
| ... | ... | @@ -327,6 +337,8 @@ pub const Inst = struct { |
| 327 | 337 | .array_type, |
| 328 | 338 | .bitand, |
| 329 | 339 | .bitor, |
| 340 | .booland, | |
| 341 | .boolor, | |
| 330 | 342 | .div, |
| 331 | 343 | .mod_rem, |
| 332 | 344 | .mul, |
| ... | ... | @@ -351,6 +363,7 @@ pub const Inst = struct { |
| 351 | 363 | .error_union_type, |
| 352 | 364 | .merge_error_sets, |
| 353 | 365 | .slice_start, |
| 366 | .switch_range, | |
| 354 | 367 | => BinOp, |
| 355 | 368 | |
| 356 | 369 | .block, |
| ... | ... | @@ -389,6 +402,7 @@ pub const Inst = struct { |
| 389 | 402 | .enum_literal => EnumLiteral, |
| 390 | 403 | .error_set => ErrorSet, |
| 391 | 404 | .slice => Slice, |
| 405 | .switchbr => SwitchBr, | |
| 392 | 406 | }; |
| 393 | 407 | } |
| 394 | 408 | |
| ... | ... | @@ -417,6 +431,8 @@ pub const Inst = struct { |
| 417 | 431 | .block_comptime, |
| 418 | 432 | .block_comptime_flat, |
| 419 | 433 | .boolnot, |
| 434 | .booland, | |
| 435 | .boolor, | |
| 420 | 436 | .breakpoint, |
| 421 | 437 | .call, |
| 422 | 438 | .cmp_lt, |
| ... | ... | @@ -493,6 +509,7 @@ pub const Inst = struct { |
| 493 | 509 | .slice, |
| 494 | 510 | .slice_start, |
| 495 | 511 | .import, |
| 512 | .switch_range, | |
| 496 | 513 | => false, |
| 497 | 514 | |
| 498 | 515 | .@"break", |
| ... | ... | @@ -504,6 +521,7 @@ pub const Inst = struct { |
| 504 | 521 | .unreach_nocheck, |
| 505 | 522 | .@"unreachable", |
| 506 | 523 | .loop, |
| 524 | .switchbr, | |
| 507 | 525 | => true, |
| 508 | 526 | }; |
| 509 | 527 | } |
| ... | ... | @@ -987,6 +1005,33 @@ pub const Inst = struct { |
| 987 | 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 | }; |
| 991 | 1036 | |
| 992 | 1037 | pub const ErrorMsg = struct { |
| ... | ... | @@ -1218,8 +1263,8 @@ const Writer = struct { |
| 1218 | 1263 | bool => return stream.writeByte("01"[@boolToInt(param)]), |
| 1219 | 1264 | []u8, []const u8 => return stream.print("\"{Z}\"", .{param}), |
| 1220 | 1265 | BigIntConst, usize => return stream.print("{}", .{param}), |
| 1221 | TypedValue => unreachable, // this is a special case | |
| 1222 | *IrModule.Decl => unreachable, // this is a special case | |
| 1266 | TypedValue => return stream.print("TypedValue{{ .ty = {}, .val = {}}}", .{ param.ty, param.val }), | |
| 1267 | *IrModule.Decl => return stream.print("Decl({s})", .{param.name}), | |
| 1223 | 1268 | *Inst.Block => { |
| 1224 | 1269 | const name = self.block_table.get(param).?; |
| 1225 | 1270 | return stream.print("\"{Z}\"", .{name}); |
| ... | ... | @@ -1238,6 +1283,26 @@ const Writer = struct { |
| 1238 | 1283 | } |
| 1239 | 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 | 1306 | else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)), |
| 1242 | 1307 | } |
| 1243 | 1308 | } |
| ... | ... | @@ -1650,6 +1715,26 @@ const Parser = struct { |
| 1650 | 1715 | try requireEatBytes(self, "]"); |
| 1651 | 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 | 1738 | else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)), |
| 1654 | 1739 | } |
| 1655 | 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 | 1832 | .arena = std.heap.ArenaAllocator.init(allocator), |
| 1748 | 1833 | .old_module = &old_module, |
| 1749 | 1834 | .next_auto_name = 0, |
| 1750 | .names = std.StringHashMap(void).init(allocator), | |
| 1835 | .names = std.StringArrayHashMap(void).init(allocator), | |
| 1751 | 1836 | .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator), |
| 1752 | 1837 | .indent = 0, |
| 1753 | 1838 | .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator), |
| ... | ... | @@ -2244,6 +2329,8 @@ const EmitZIR = struct { |
| 2244 | 2329 | .cmp_gte => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_gte).?, .cmp_gte), |
| 2245 | 2330 | .cmp_gt => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_gt).?, .cmp_gt), |
| 2246 | 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), | |
| 2247 | 2334 | |
| 2248 | 2335 | .bitcast => try self.emitCast(inst.src, new_body, inst.castTag(.bitcast).?, .bitcast), |
| 2249 | 2336 | .intcast => try self.emitCast(inst.src, new_body, inst.castTag(.intcast).?, .intcast), |
| ... | ... | @@ -2470,7 +2557,63 @@ const EmitZIR = struct { |
| 2470 | 2557 | }; |
| 2471 | 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 | }; | |
| 2473 | 2577 | |
| 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 | 2617 | .varptr => @panic("TODO"), |
| 2475 | 2618 | }; |
| 2476 | 2619 | try self.metadata.put(new_inst, .{ |
| ... | ... | @@ -2703,3 +2846,52 @@ const EmitZIR = struct { |
| 2703 | 2846 | return decl; |
| 2704 | 2847 | } |
| 2705 | 2848 | }; |
| 2849 | ||
| 2850 | /// For debugging purposes, like dumpFn but for unanalyzed zir blocks | |
| 2851 | pub 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 | 135 | .slice => return analyzeInstSlice(mod, scope, old_inst.castTag(.slice).?), |
| 136 | 136 | .slice_start => return analyzeInstSliceStart(mod, scope, old_inst.castTag(.slice_start).?), |
| 137 | 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 | } |
| 140 | 144 | |
| ... | ... | @@ -551,10 +555,13 @@ fn analyzeInstBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_c |
| 551 | 555 | |
| 552 | 556 | try analyzeBody(mod, &child_block.base, inst.positionals.body); |
| 553 | 557 | |
| 554 | const copied_instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items); | |
| 555 | try parent_block.instructions.appendSlice(mod.gpa, copied_instructions); | |
| 558 | try parent_block.instructions.appendSlice(mod.gpa, child_block.instructions.items); | |
| 556 | 559 | |
| 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 | } |
| 559 | 566 | |
| 560 | 567 | fn 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 | 1211 | return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null); |
| 1205 | 1212 | } |
| 1206 | 1213 | |
| 1214 | fn 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 | ||
| 1236 | fn 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 | ||
| 1301 | fn 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 | ||
| 1207 | 1434 | fn analyzeInstImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { |
| 1208 | 1435 | const operand = try resolveConstString(mod, scope, inst.positionals.operand); |
| 1209 | 1436 | |
| 1210 | 1437 | const file_scope = mod.analyzeImport(scope, inst.base.src, operand) catch |err| switch (err) { |
| 1211 | // error.ImportOutsidePkgPath => { | |
| 1212 | // return mod.fail(scope, inst.base.src, "import of file outside package path: '{}'", .{operand}); | |
| 1213 | // }, | |
| 1438 | error.ImportOutsidePkgPath => { | |
| 1439 | return mod.fail(scope, inst.base.src, "import of file outside package path: '{}'", .{operand}); | |
| 1440 | }, | |
| 1214 | 1441 | error.FileNotFound => { |
| 1215 | 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 | 1683 | return mod.addUnOp(b, inst.base.src, bool_type, .not, operand); |
| 1457 | 1684 | } |
| 1458 | 1685 | |
| 1686 | fn 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 | ||
| 1459 | 1708 | fn analyzeInstIsNonNull(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp, invert_logic: bool) InnerError!*Inst { |
| 1460 | 1709 | const operand = try resolveInst(mod, scope, inst.positionals.operand); |
| 1461 | 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 | 1722 | if (try mod.resolveDefinedValue(scope, cond)) |cond_val| { |
| 1474 | 1723 | const body = if (cond_val.toBool()) &inst.positionals.then_body else &inst.positionals.else_body; |
| 1475 | 1724 | try analyzeBody(mod, scope, body.*); |
| 1476 | return mod.constVoid(scope, inst.base.src); | |
| 1725 | return mod.constNoReturn(scope, inst.base.src); | |
| 1477 | 1726 | } |
| 1478 | 1727 | |
| 1479 | 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 | 974 | , |
| 975 | 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 | } |
| 978 | 1015 | |
| 979 | 1016 | { |