Commit 769d5a9c43

Vexu <git@vexu.eu>
2020-10-20 21:00:30
stage2: switch comptime execution
1 parent 12e4c64
src/astgen.zig
@@ -1581,14 +1581,6 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node
     };
     defer block_scope.instructions.deinit(mod.gpa);
 
-    var item_scope: Scope.GenZIR = .{
-        .parent = scope,
-        .decl = scope.decl().?,
-        .arena = scope.arena(),
-        .instructions = .{},
-    };
-    defer item_scope.instructions.deinit(mod.gpa);
-
     const tree = scope.tree();
     const switch_src = tree.token_locs[switch_node.switch_token].start;
     const target_ptr = try expr(mod, &block_scope.base, .ref, switch_node.expr);
@@ -1598,6 +1590,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node
         .target_ptr = target_ptr,
         .cases = undefined, // populated below
         .items = &[_]*zir.Inst{}, // populated below
+        .else_body = undefined, // populated below
     }, .{})).castTag(.switchbr).?;
 
     var items = std.ArrayList(*zir.Inst).init(mod.gpa);
@@ -1611,7 +1604,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node
     });
     // then add block containing the switch.
     const block = try addZIRInstBlock(mod, scope, switch_src, .block, .{
-        .instructions = undefined, // populated below
+        .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
     });
 
     // Most result location types can be forwarded directly; however
@@ -1622,6 +1615,14 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node
         .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block },
     };
 
+    var item_scope: Scope.GenZIR = .{
+        .parent = scope,
+        .decl = scope.decl().?,
+        .arena = scope.arena(),
+        .instructions = .{},
+    };
+    defer item_scope.instructions.deinit(mod.gpa);
+
     var case_scope: Scope.GenZIR = .{
         .parent = scope,
         .decl = block_scope.decl,
@@ -1630,6 +1631,14 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node
     };
     defer case_scope.instructions.deinit(mod.gpa);
 
+    var else_scope: Scope.GenZIR = .{
+        .parent = scope,
+        .decl = block_scope.decl,
+        .arena = block_scope.arena,
+        .instructions = .{},
+    };
+    defer else_scope.instructions.deinit(mod.gpa);
+
     // first we gather all the switch items and check else/'_' prongs
     var else_src: ?usize = null;
     var underscore_src: ?usize = null;
@@ -1701,12 +1710,12 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node
                 if (first_range == null) first_range = range_inst;
 
                 // target >= start and target <= end
-                const range_start_ok = try addZIRBinOp(mod, &block_scope.base, range_src, .cmp_gte, target, start);
-                const range_end_ok = try addZIRBinOp(mod, &block_scope.base, range_src, .cmp_lte, target, end);
-                const range_ok = try addZIRBinOp(mod, &block_scope.base, range_src, .booland, range_start_ok, range_end_ok);
+                const range_start_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_gte, target, start);
+                const range_end_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_lte, target, end);
+                const range_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .booland, range_start_ok, range_end_ok);
 
                 if (any_ok) |some| {
-                    any_ok = try addZIRBinOp(mod, &block_scope.base, range_src, .boolor, some, range_ok);
+                    any_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .boolor, some, range_ok);
                 } else {
                     any_ok = range_ok;
                 }
@@ -1715,16 +1724,16 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node
 
             const item_inst = try expr(mod, &item_scope.base, .none, item);
             try items.append(item_inst);
-            const cpm_ok = try addZIRBinOp(mod, &block_scope.base, item_inst.src, .cmp_eq, target, item_inst);
+            const cpm_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .cmp_eq, target, item_inst);
 
             if (any_ok) |some| {
-                any_ok = try addZIRBinOp(mod, &block_scope.base, item_inst.src, .boolor, some, cpm_ok);
+                any_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .boolor, some, cpm_ok);
             } else {
                 any_ok = cpm_ok;
             }
         }
 
-        const condbr = try addZIRInstSpecial(mod, &block_scope.base, case_src, zir.Inst.CondBr, .{
+        const condbr = try addZIRInstSpecial(mod, &else_scope.base, case_src, zir.Inst.CondBr, .{
             .condition = any_ok.?,
             .then_body = undefined, // populated below
             .else_body = undefined, // populated below
@@ -1754,6 +1763,14 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node
         };
     }
 
+    // Generate else block or a break last to finish the block.
+    if (special_case) |case| {
+        try switchCaseExpr(mod, &else_scope.base, case_rl, block, case);
+    } else {
+        // Not handling all possible cases is a compile error.
+        _ = try addZIRNoOp(mod, &else_scope.base, switch_src, .unreach_nocheck);
+    }
+
     // All items have been generated, add the instructions to the comptime block.
     item_block.positionals.body = .{
         .instructions = try block_scope.arena.dupe(*zir.Inst, item_scope.instructions.items),
@@ -1765,18 +1782,8 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node
     switch_inst.positionals.cases = try block_scope.arena.dupe(zir.Inst.SwitchBr.Case, cases.items);
     switch_inst.positionals.items = try block_scope.arena.dupe(*zir.Inst, items.items);
     switch_inst.kw_args.range = first_range;
-
-    // Generate else block or a break last to finish the block.
-    if (special_case) |case| {
-        try switchCaseExpr(mod, &block_scope.base, case_rl, block, case);
-    } else {
-        // Not handling all possible cases is a compile error.
-        _ = try addZIRNoOp(mod, &block_scope.base, switch_src, .unreach_nocheck);
-    }
-
-    // Set block instructions now that it is finished.
-    block.positionals.body = .{
-        .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
+    switch_inst.positionals.else_body = .{
+        .instructions = try block_scope.arena.dupe(*zir.Inst, else_scope.instructions.items),
     };
     return &block.base;
 }
src/ir.zig
@@ -472,8 +472,11 @@ pub const Inst = struct {
         target_ptr: *Inst,
         cases: []Case,
         /// Set of instructions whose lifetimes end at the start of one of the cases.
-        /// In same order as cases, deaths[0..case_0_count, case_0_count .. case_1_count, ... , case_n_count ... else_count].
+        /// In same order as cases, deaths[0..case_0_count, case_0_count .. case_1_count, ... ].
         deaths: [*]*Inst = undefined,
+        else_index: u32 = 0,
+        else_deaths: u32 = 0,
+        else_body: Body,
 
         pub const Case = struct {
             item: Value,
@@ -498,6 +501,9 @@ pub const Inst = struct {
             const case = self.cases[case_index];
             return (self.deaths + case.index)[0..case.deaths];
         }
+        pub fn elseDeaths(self: *const SwitchBr) []*Inst {
+            return (self.deaths + self.else_index)[0..self.else_deaths];
+        }
     };
 };
 
src/Module.zig
@@ -2122,16 +2122,18 @@ pub fn addSwitchBr(
     src: usize,
     target_ptr: *Inst,
     cases: []Inst.SwitchBr.Case,
+    else_body: ir.Body,
 ) !*Inst {
     const inst = try block.arena.create(Inst.SwitchBr);
     inst.* = .{
         .base = .{
             .tag = .switchbr,
-            .ty = Type.initTag(.void),
+            .ty = Type.initTag(.noreturn),
             .src = src,
         },
         .target_ptr = target_ptr,
         .cases = cases,
+        .else_body = else_body,
     };
     try block.instructions.append(self.gpa, &inst.base);
     return &inst.base;
src/zir.zig
@@ -509,7 +509,6 @@ pub const Inst = struct {
                 .slice,
                 .slice_start,
                 .import,
-                .switchbr,
                 .switch_range,
                 => false,
 
@@ -522,6 +521,7 @@ pub const Inst = struct {
                 .unreach_nocheck,
                 .@"unreachable",
                 .loop,
+                .switchbr,
                 => true,
             };
         }
@@ -1012,9 +1012,10 @@ pub const Inst = struct {
 
         positionals: struct {
             target_ptr: *Inst,
-            cases: []Case,
             /// List of all individual items and ranges
             items: []*Inst,
+            cases: []Case,
+            else_body: Module.Body,
         },
         kw_args: struct {
             /// Pointer to first range if such exists.
@@ -2569,6 +2570,7 @@ const EmitZIR = struct {
                             .target_ptr = try self.resolveInst(new_body, old_inst.target_ptr),
                             .cases = cases,
                             .items = &[_]*Inst{}, // TODO this should actually be populated
+                            .else_body = undefined, // populated below
                         },
                         .kw_args = .{},
                     };
@@ -2590,6 +2592,12 @@ const EmitZIR = struct {
                             .body = .{ .instructions = try self.arena.allocator.dupe(*Inst, body_tmp.items) },
                         };
                     }
+
+                    body_tmp.items.len = 0;
+                    try self.emitBody(old_inst.else_body, inst_table, &body_tmp);
+                    new_inst.positionals.else_body = .{
+                        .instructions = try self.arena.allocator.dupe(*Inst, body_tmp.items),
+                    };
                     break :blk &new_inst.base;
                 },
                 .varptr => @panic("TODO"),
src/zir_sema.zig
@@ -1238,7 +1238,20 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In
     const target = try mod.analyzeDeref(scope, inst.base.src, target_ptr, inst.positionals.target_ptr.src);
     try validateSwitch(mod, scope, target, inst);
 
-    // TODO comptime execution
+    if (try mod.resolveDefinedValue(scope, target)) |target_val| {
+        for (inst.positionals.cases) |case| {
+            const resolved = try resolveInst(mod, scope, case.item);
+            const casted = try mod.coerce(scope, target.ty, resolved);
+            const item = try mod.resolveConstValue(scope, casted);
+
+            if (target_val.eql(item)) {
+                try analyzeBody(mod, scope, case.body);
+                return mod.constNoReturn(scope, inst.base.src);
+            }
+        }
+        try analyzeBody(mod, scope, inst.positionals.else_body);
+        return mod.constNoReturn(scope, inst.base.src);
+    }
 
     const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src);
     const cases = try parent_block.arena.alloc(Inst.SwitchBr.Case, inst.positionals.cases.len);
@@ -1253,7 +1266,7 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In
     };
     defer case_block.instructions.deinit(mod.gpa);
 
-    for (inst.positionals.cases[0..inst.positionals.cases.len]) |case, i| {
+    for (inst.positionals.cases) |case, i| {
         // Reset without freeing.
         case_block.instructions.items.len = 0;
 
@@ -1269,7 +1282,14 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In
         };
     }
 
-    return mod.addSwitchBr(parent_block, inst.base.src, target_ptr, cases);
+    case_block.instructions.items.len = 0;
+    try analyzeBody(mod, &case_block.base, inst.positionals.else_body);
+
+    const else_body: ir.Body = .{
+        .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items),
+    };
+
+    return mod.addSwitchBr(parent_block, inst.base.src, target_ptr, cases, else_body);
 }
 
 fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.SwitchBr) InnerError!void {
@@ -1354,14 +1374,14 @@ fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.Sw
                     false_count += 1;
                 }
 
-                if (true_count > 1 or false_count > 1) {
+                if (true_count + false_count > 2) {
                     return mod.fail(scope, item.src, "duplicate switch value", .{});
                 }
             }
-            if ((true_count == 0 or false_count == 0) and inst.kw_args.special_prong != .@"else") {
+            if ((true_count + false_count < 2) and inst.kw_args.special_prong != .@"else") {
                 return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{});
             }
-            if ((true_count == 1 and false_count == 1) and inst.kw_args.special_prong == .@"else") {
+            if ((true_count + false_count == 2) and inst.kw_args.special_prong == .@"else") {
                 return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{});
             }
         },
@@ -1696,7 +1716,7 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE
     if (try mod.resolveDefinedValue(scope, cond)) |cond_val| {
         const body = if (cond_val.toBool()) &inst.positionals.then_body else &inst.positionals.else_body;
         try analyzeBody(mod, scope, body.*);
-        return mod.constVoid(scope, inst.base.src);
+        return mod.constNoReturn(scope, inst.base.src);
     }
 
     const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src);