Commit ea902ffe8f

Andrew Kelley <andrew@ziglang.org>
2021-07-20 02:35:14
Sema: reimplement runtime switch
Now supports multiple items pointing to the same body. This is a common pattern even when using a jump table, with multiple cases pointing to the same block of code. In the case of a range specified, the items are moved to branches in the else body. A future improvement may make it possible to have jump table items as well as ranges pointing to the same block of code.
1 parent caa0de5
Changed files (4)
src/codegen/wasm.zig
@@ -1282,44 +1282,49 @@ pub const Context = struct {
         // result type is always 'noreturn'
         const blocktype = wasm.block_empty;
 
-        const signedness: std.builtin.Signedness = blk: {
-            // by default we tell the operand type is unsigned (i.e. bools and enum values)
-            if (target_ty.zigTypeTag() != .Int) break :blk .unsigned;
-
-            // incase of an actual integer, we emit the correct signedness
-            break :blk target_ty.intInfo(self.target).signedness;
-        };
-        for (cases) |case_idx| {
-            const case = self.air.extraData(Air.SwitchBr.Case, case_idx);
-            const case_body = self.air.extra[case.end..][0..case.data.body_len];
-
-            // create a block for each case, when the condition does not match we break out of it
-            try self.startBlock(.block, blocktype, null);
-            try self.emitWValue(target);
-
-            const val = self.air.value(case.data.item).?;
-            try self.emitConstant(val, target_ty);
-            const opcode = buildOpcode(.{
-                .valtype1 = valtype,
-                .op = .ne, // not equal because we jump out the block if it does not match the condition
-                .signedness = signedness,
-            });
-            try self.code.append(wasm.opcode(opcode));
-            try self.code.append(wasm.opcode(.br_if));
-            try leb.writeULEB128(self.code.writer(), @as(u32, 0));
-
-            // emit our block code
-            try self.genBody(case_body);
-
-            // end the block we created earlier
-            try self.endBlock();
-        }
-
-        // finally, emit the else case if it exists. Here we will not have to
-        // check for a condition, so also no need to emit a block.
-        try self.genBody(else_body);
-
-        return .none;
+        _ = valtype;
+        _ = blocktype;
+        _ = target;
+        _ = else_body;
+        return self.fail("TODO implement wasm codegen for switch", .{});
+        //const signedness: std.builtin.Signedness = blk: {
+        //    // by default we tell the operand type is unsigned (i.e. bools and enum values)
+        //    if (target_ty.zigTypeTag() != .Int) break :blk .unsigned;
+
+        //    // incase of an actual integer, we emit the correct signedness
+        //    break :blk target_ty.intInfo(self.target).signedness;
+        //};
+        //for (cases) |case_idx| {
+        //    const case = self.air.extraData(Air.SwitchBr.Case, case_idx);
+        //    const case_body = self.air.extra[case.end..][0..case.data.body_len];
+
+        //    // create a block for each case, when the condition does not match we break out of it
+        //    try self.startBlock(.block, blocktype, null);
+        //    try self.emitWValue(target);
+
+        //    const val = self.air.value(case.data.item).?;
+        //    try self.emitConstant(val, target_ty);
+        //    const opcode = buildOpcode(.{
+        //        .valtype1 = valtype,
+        //        .op = .ne, // not equal because we jump out the block if it does not match the condition
+        //        .signedness = signedness,
+        //    });
+        //    try self.code.append(wasm.opcode(opcode));
+        //    try self.code.append(wasm.opcode(.br_if));
+        //    try leb.writeULEB128(self.code.writer(), @as(u32, 0));
+
+        //    // emit our block code
+        //    try self.genBody(case_body);
+
+        //    // end the block we created earlier
+        //    try self.endBlock();
+        //}
+
+        //// finally, emit the else case if it exists. Here we will not have to
+        //// check for a condition, so also no need to emit a block.
+        //try self.genBody(else_body);
+
+        //return .none;
     }
 
     fn airIsErr(self: *Context, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {
src/Air.zig
@@ -352,9 +352,10 @@ pub const SwitchBr = struct {
     else_body_len: u32,
 
     /// Trailing:
+    /// * item: Inst.Ref // for each `items_len`.
     /// * instruction index for each `body_len`.
     pub const Case = struct {
-        item: Inst.Ref,
+        items_len: u32,
         body_len: u32,
     };
 };
src/Module.zig
@@ -1300,6 +1300,10 @@ pub const Scope = struct {
         }
 
         pub fn addInst(block: *Block, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Ref {
+            return Air.indexToRef(try block.addInstAsIndex(inst));
+        }
+
+        pub fn addInstAsIndex(block: *Block, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Index {
             const sema = block.sema;
             const gpa = sema.gpa;
 
@@ -1309,7 +1313,7 @@ pub const Scope = struct {
             const result_index = @intCast(Air.Inst.Index, sema.air_instructions.len);
             sema.air_instructions.appendAssumeCapacity(inst);
             block.instructions.appendAssumeCapacity(result_index);
-            return Air.indexToRef(result_index);
+            return result_index;
         }
     };
 };
src/Sema.zig
@@ -4170,159 +4170,201 @@ fn analyzeSwitch(
 
     try sema.requireRuntimeBlock(block, src);
 
-    // TODO when reworking AIR memory layout make multi cases get generated as cases,
-    // not as part of the "else" block.
-    return mod.fail(&block.base, src, "TODO rework runtime switch Sema", .{});
-    //const cases = try sema.arena.alloc(Inst.SwitchBr.Case, scalar_cases_len);
-
-    //var case_block = child_block.makeSubBlock();
-    //case_block.runtime_loop = null;
-    //case_block.runtime_cond = operand.src;
-    //case_block.runtime_index += 1;
-    //defer case_block.instructions.deinit(gpa);
-
-    //var extra_index: usize = special.end;
-
-    //var scalar_i: usize = 0;
-    //while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
-    //    const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
-    //    extra_index += 1;
-    //    const body_len = sema.code.extra[extra_index];
-    //    extra_index += 1;
-    //    const body = sema.code.extra[extra_index..][0..body_len];
-    //    extra_index += body_len;
+    var cases_extra: std.ArrayListUnmanaged(u32) = .{};
+    defer cases_extra.deinit(gpa);
 
-    //    case_block.instructions.shrinkRetainingCapacity(0);
-    //    const item = sema.resolveInst(item_ref);
-    //    // We validate these above; these two calls are guaranteed to succeed.
-    //    const item_val = sema.resolveConstValue(&case_block, .unneeded, item) catch unreachable;
+    try cases_extra.ensureTotalCapacity(gpa, (scalar_cases_len + multi_cases_len) *
+        @typeInfo(Air.SwitchBr.Case).Struct.fields.len + 2);
 
-    //    _ = try sema.analyzeBody(&case_block, body);
+    var case_block = child_block.makeSubBlock();
+    case_block.runtime_loop = null;
+    case_block.runtime_cond = operand_src;
+    case_block.runtime_index += 1;
+    defer case_block.instructions.deinit(gpa);
 
-    //    cases[scalar_i] = .{
-    //        .item = item_val,
-    //        .body = .{ .instructions = try sema.arena.dupe(Air.Inst.Index, case_block.instructions.items) },
-    //    };
-    //}
+    var extra_index: usize = special.end;
 
-    //var first_else_body: Body = undefined;
-    //var prev_condbr: ?*Inst.CondBr = null;
+    var scalar_i: usize = 0;
+    while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
+        const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
+        extra_index += 1;
+        const body_len = sema.code.extra[extra_index];
+        extra_index += 1;
+        const body = sema.code.extra[extra_index..][0..body_len];
+        extra_index += body_len;
 
-    //var multi_i: usize = 0;
-    //while (multi_i < multi_cases_len) : (multi_i += 1) {
-    //    const items_len = sema.code.extra[extra_index];
-    //    extra_index += 1;
-    //    const ranges_len = sema.code.extra[extra_index];
-    //    extra_index += 1;
-    //    const body_len = sema.code.extra[extra_index];
-    //    extra_index += 1;
-    //    const items = sema.code.refSlice(extra_index, items_len);
-    //    extra_index += items_len;
-
-    //    case_block.instructions.shrinkRetainingCapacity(0);
-
-    //    var any_ok: ?Air.Inst.Index = null;
-
-    //    for (items) |item_ref| {
-    //        const item = sema.resolveInst(item_ref);
-    //        _ = try sema.resolveConstValue(&child_block, item.src, item);
-
-    //        const cmp_ok = try case_block.addBinOp(.cmp_eq, operand, item);
-    //        if (any_ok) |some| {
-    //            any_ok = try case_block.addBinOp(.bool_or, some, cmp_ok);
-    //        } else {
-    //            any_ok = cmp_ok;
-    //        }
-    //    }
-
-    //    var range_i: usize = 0;
-    //    while (range_i < ranges_len) : (range_i += 1) {
-    //        const first_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
-    //        extra_index += 1;
-    //        const last_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
-    //        extra_index += 1;
-
-    //        const item_first = sema.resolveInst(first_ref);
-    //        const item_last = sema.resolveInst(last_ref);
-
-    //        _ = try sema.resolveConstValue(&child_block, item_first.src, item_first);
-    //        _ = try sema.resolveConstValue(&child_block, item_last.src, item_last);
-
-    //        // operand >= first and operand <= last
-    //        const range_first_ok = try case_block.addBinOp(
-    //            .cmp_gte,
-    //            operand,
-    //            item_first,
-    //        );
-    //        const range_last_ok = try case_block.addBinOp(
-    //            .cmp_lte,
-    //            operand,
-    //            item_last,
-    //        );
-    //        const range_ok = try case_block.addBinOp(
-    //            .bool_and,
-    //            range_first_ok,
-    //            range_last_ok,
-    //        );
-    //        if (any_ok) |some| {
-    //            any_ok = try case_block.addBinOp(.bool_or, some, range_ok);
-    //        } else {
-    //            any_ok = range_ok;
-    //        }
-    //    }
-
-    //    const new_condbr = try sema.arena.create(Inst.CondBr);
-    //    new_condbr.* = .{
-    //        .base = .{
-    //            .tag = .condbr,
-    //            .ty = Type.initTag(.noreturn),
-    //            .src = src,
-    //        },
-    //        .condition = any_ok.?,
-    //        .then_body = undefined,
-    //        .else_body = undefined,
-    //    };
-    //    try case_block.instructions.append(gpa, &new_condbr.base);
-
-    //    const cond_body: Body = .{
-    //        .instructions = try sema.arena.dupe(Air.Inst.Index, case_block.instructions.items),
-    //    };
-
-    //    case_block.instructions.shrinkRetainingCapacity(0);
-    //    const body = sema.code.extra[extra_index..][0..body_len];
-    //    extra_index += body_len;
-    //    _ = try sema.analyzeBody(&case_block, body);
-    //    new_condbr.then_body = .{
-    //        .instructions = try sema.arena.dupe(Air.Inst.Index, case_block.instructions.items),
-    //    };
-    //    if (prev_condbr) |condbr| {
-    //        condbr.else_body = cond_body;
-    //    } else {
-    //        first_else_body = cond_body;
-    //    }
-    //    prev_condbr = new_condbr;
-    //}
-
-    //const final_else_body: Body = blk: {
-    //    if (special.body.len != 0) {
-    //        case_block.instructions.shrinkRetainingCapacity(0);
-    //        _ = try sema.analyzeBody(&case_block, special.body);
-    //        const else_body: Body = .{
-    //            .instructions = try sema.arena.dupe(Air.Inst.Index, case_block.instructions.items),
-    //        };
-    //        if (prev_condbr) |condbr| {
-    //            condbr.else_body = else_body;
-    //            break :blk first_else_body;
-    //        } else {
-    //            break :blk else_body;
-    //        }
-    //    } else {
-    //        break :blk .{ .instructions = &.{} };
-    //    }
-    //};
-
-    //_ = try child_block.addSwitchBr(src, operand, cases, final_else_body);
-    //return sema.analyzeBlockBody(block, src, &child_block, merges);
+        case_block.instructions.shrinkRetainingCapacity(0);
+        const item = sema.resolveInst(item_ref);
+        // `item` is already guaranteed to be constant known.
+
+        _ = try sema.analyzeBody(&case_block, body);
+
+        try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
+        cases_extra.appendAssumeCapacity(1); // items_len
+        cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len));
+        cases_extra.appendAssumeCapacity(@enumToInt(item));
+        cases_extra.appendSliceAssumeCapacity(case_block.instructions.items);
+    }
+
+    var is_first = true;
+    var prev_cond_br: Air.Inst.Index = undefined;
+    var first_else_body: []const Air.Inst.Index = &.{};
+    defer gpa.free(first_else_body);
+    var prev_then_body: []const Air.Inst.Index = &.{};
+    defer gpa.free(prev_then_body);
+
+    var multi_i: usize = 0;
+    while (multi_i < multi_cases_len) : (multi_i += 1) {
+        const items_len = sema.code.extra[extra_index];
+        extra_index += 1;
+        const ranges_len = sema.code.extra[extra_index];
+        extra_index += 1;
+        const body_len = sema.code.extra[extra_index];
+        extra_index += 1;
+        const items = sema.code.refSlice(extra_index, items_len);
+        extra_index += items_len;
+
+        case_block.instructions.shrinkRetainingCapacity(0);
+
+        var any_ok: Air.Inst.Ref = .none;
+
+        // If there are any ranges, we have to put all the items into the
+        // else prong. Otherwise, we can take advantage of multiple items
+        // mapping to the same body.
+        if (ranges_len == 0) {
+            const body = sema.code.extra[extra_index..][0..body_len];
+            extra_index += body_len;
+            _ = try sema.analyzeBody(&case_block, body);
+
+            try cases_extra.ensureUnusedCapacity(gpa, 2 + items.len +
+                case_block.instructions.items.len);
+
+            cases_extra.appendAssumeCapacity(1); // items_len
+            cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len));
+
+            for (items) |item_ref| {
+                const item = sema.resolveInst(item_ref);
+                cases_extra.appendAssumeCapacity(@enumToInt(item));
+            }
+
+            cases_extra.appendSliceAssumeCapacity(case_block.instructions.items);
+        } else {
+            for (items) |item_ref| {
+                const item = sema.resolveInst(item_ref);
+                const cmp_ok = try case_block.addBinOp(.cmp_eq, operand, item);
+                if (any_ok != .none) {
+                    any_ok = try case_block.addBinOp(.bool_or, any_ok, cmp_ok);
+                } else {
+                    any_ok = cmp_ok;
+                }
+            }
+
+            var range_i: usize = 0;
+            while (range_i < ranges_len) : (range_i += 1) {
+                const first_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
+                extra_index += 1;
+                const last_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
+                extra_index += 1;
+
+                const item_first = sema.resolveInst(first_ref);
+                const item_last = sema.resolveInst(last_ref);
+
+                // operand >= first and operand <= last
+                const range_first_ok = try case_block.addBinOp(
+                    .cmp_gte,
+                    operand,
+                    item_first,
+                );
+                const range_last_ok = try case_block.addBinOp(
+                    .cmp_lte,
+                    operand,
+                    item_last,
+                );
+                const range_ok = try case_block.addBinOp(
+                    .bool_and,
+                    range_first_ok,
+                    range_last_ok,
+                );
+                if (any_ok != .none) {
+                    any_ok = try case_block.addBinOp(.bool_or, any_ok, range_ok);
+                } else {
+                    any_ok = range_ok;
+                }
+            }
+
+            const new_cond_br = try case_block.addInstAsIndex(.{ .tag = .cond_br, .data = .{
+                .pl_op = .{
+                    .operand = any_ok,
+                    .payload = undefined,
+                },
+            } });
+            var cond_body = case_block.instructions.toOwnedSlice(gpa);
+            defer gpa.free(cond_body);
+
+            case_block.instructions.shrinkRetainingCapacity(0);
+            const body = sema.code.extra[extra_index..][0..body_len];
+            extra_index += body_len;
+            _ = try sema.analyzeBody(&case_block, body);
+
+            if (is_first) {
+                is_first = false;
+                first_else_body = cond_body;
+                cond_body = &.{};
+            } else {
+                try sema.air_extra.ensureUnusedCapacity(
+                    gpa,
+                    @typeInfo(Air.CondBr).Struct.fields.len + prev_then_body.len + cond_body.len,
+                );
+
+                sema.air_instructions.items(.data)[prev_cond_br].pl_op.payload =
+                    sema.addExtraAssumeCapacity(Air.CondBr{
+                    .then_body_len = @intCast(u32, prev_then_body.len),
+                    .else_body_len = @intCast(u32, cond_body.len),
+                });
+                sema.air_extra.appendSliceAssumeCapacity(prev_then_body);
+                sema.air_extra.appendSliceAssumeCapacity(cond_body);
+            }
+            prev_then_body = case_block.instructions.toOwnedSlice(gpa);
+            prev_cond_br = new_cond_br;
+        }
+    }
+
+    var final_else_body: []const Air.Inst.Index = &.{};
+    if (special.body.len != 0) {
+        case_block.instructions.shrinkRetainingCapacity(0);
+        _ = try sema.analyzeBody(&case_block, special.body);
+
+        if (is_first) {
+            final_else_body = case_block.instructions.items;
+        } else {
+            try sema.air_extra.ensureUnusedCapacity(gpa, prev_then_body.len +
+                @typeInfo(Air.CondBr).Struct.fields.len + case_block.instructions.items.len);
+
+            sema.air_instructions.items(.data)[prev_cond_br].pl_op.payload =
+                sema.addExtraAssumeCapacity(Air.CondBr{
+                .then_body_len = @intCast(u32, prev_then_body.len),
+                .else_body_len = @intCast(u32, case_block.instructions.items.len),
+            });
+            sema.air_extra.appendSliceAssumeCapacity(prev_then_body);
+            sema.air_extra.appendSliceAssumeCapacity(case_block.instructions.items);
+            final_else_body = first_else_body;
+        }
+    }
+
+    try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr).Struct.fields.len +
+        cases_extra.items.len);
+
+    _ = try child_block.addInst(.{ .tag = .switch_br, .data = .{ .pl_op = .{
+        .operand = operand,
+        .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
+            .cases_len = @intCast(u32, scalar_cases_len + multi_cases_len),
+            .else_body_len = @intCast(u32, final_else_body.len),
+        }),
+    } } });
+    sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
+    sema.air_extra.appendSliceAssumeCapacity(final_else_body);
+
+    return sema.analyzeBlockBody(block, src, &child_block, merges);
 }
 
 fn resolveSwitchItemVal(