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.
This commit is contained in:
Andrew Kelley 2021-07-19 17:35:14 -07:00
parent caa0de545e
commit ea902ffe8f
4 changed files with 217 additions and 165 deletions

View File

@ -352,9 +352,10 @@ pub const SwitchBr = struct {
else_body_len: u32, else_body_len: u32,
/// Trailing: /// Trailing:
/// * item: Inst.Ref // for each `items_len`.
/// * instruction index for each `body_len`. /// * instruction index for each `body_len`.
pub const Case = struct { pub const Case = struct {
item: Inst.Ref, items_len: u32,
body_len: u32, body_len: u32,
}; };
}; };

View File

@ -1300,6 +1300,10 @@ pub const Scope = struct {
} }
pub fn addInst(block: *Block, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Ref { 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 sema = block.sema;
const gpa = sema.gpa; const gpa = sema.gpa;
@ -1309,7 +1313,7 @@ pub const Scope = struct {
const result_index = @intCast(Air.Inst.Index, sema.air_instructions.len); const result_index = @intCast(Air.Inst.Index, sema.air_instructions.len);
sema.air_instructions.appendAssumeCapacity(inst); sema.air_instructions.appendAssumeCapacity(inst);
block.instructions.appendAssumeCapacity(result_index); block.instructions.appendAssumeCapacity(result_index);
return Air.indexToRef(result_index); return result_index;
} }
}; };
}; };

View File

@ -4170,159 +4170,201 @@ fn analyzeSwitch(
try sema.requireRuntimeBlock(block, src); try sema.requireRuntimeBlock(block, src);
// TODO when reworking AIR memory layout make multi cases get generated as cases, var cases_extra: std.ArrayListUnmanaged(u32) = .{};
// not as part of the "else" block. defer cases_extra.deinit(gpa);
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(); try cases_extra.ensureTotalCapacity(gpa, (scalar_cases_len + multi_cases_len) *
//case_block.runtime_loop = null; @typeInfo(Air.SwitchBr.Case).Struct.fields.len + 2);
//case_block.runtime_cond = operand.src;
//case_block.runtime_index += 1;
//defer case_block.instructions.deinit(gpa);
//var extra_index: usize = special.end; 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 scalar_i: usize = 0; var extra_index: usize = special.end;
//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;
// case_block.instructions.shrinkRetainingCapacity(0); var scalar_i: usize = 0;
// const item = sema.resolveInst(item_ref); while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
// // We validate these above; these two calls are guaranteed to succeed. const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
// const item_val = sema.resolveConstValue(&case_block, .unneeded, item) catch unreachable; 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;
// _ = try sema.analyzeBody(&case_block, body); case_block.instructions.shrinkRetainingCapacity(0);
const item = sema.resolveInst(item_ref);
// `item` is already guaranteed to be constant known.
// cases[scalar_i] = .{ _ = try sema.analyzeBody(&case_block, body);
// .item = item_val,
// .body = .{ .instructions = try sema.arena.dupe(Air.Inst.Index, case_block.instructions.items) },
// };
//}
//var first_else_body: Body = undefined; try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
//var prev_condbr: ?*Inst.CondBr = null; 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 multi_i: usize = 0; var is_first = true;
//while (multi_i < multi_cases_len) : (multi_i += 1) { var prev_cond_br: Air.Inst.Index = undefined;
// const items_len = sema.code.extra[extra_index]; var first_else_body: []const Air.Inst.Index = &.{};
// extra_index += 1; defer gpa.free(first_else_body);
// const ranges_len = sema.code.extra[extra_index]; var prev_then_body: []const Air.Inst.Index = &.{};
// extra_index += 1; defer gpa.free(prev_then_body);
// 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 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;
// var any_ok: ?Air.Inst.Index = null; case_block.instructions.shrinkRetainingCapacity(0);
// for (items) |item_ref| { var any_ok: Air.Inst.Ref = .none;
// 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 there are any ranges, we have to put all the items into the
// if (any_ok) |some| { // else prong. Otherwise, we can take advantage of multiple items
// any_ok = try case_block.addBinOp(.bool_or, some, cmp_ok); // mapping to the same body.
// } else { if (ranges_len == 0) {
// any_ok = cmp_ok; const body = sema.code.extra[extra_index..][0..body_len];
// } extra_index += body_len;
// } _ = try sema.analyzeBody(&case_block, body);
// var range_i: usize = 0; try cases_extra.ensureUnusedCapacity(gpa, 2 + items.len +
// while (range_i < ranges_len) : (range_i += 1) { case_block.instructions.items.len);
// 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); cases_extra.appendAssumeCapacity(1); // items_len
// const item_last = sema.resolveInst(last_ref); cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len));
// _ = try sema.resolveConstValue(&child_block, item_first.src, item_first); for (items) |item_ref| {
// _ = try sema.resolveConstValue(&child_block, item_last.src, item_last); const item = sema.resolveInst(item_ref);
cases_extra.appendAssumeCapacity(@enumToInt(item));
}
// // operand >= first and operand <= last cases_extra.appendSliceAssumeCapacity(case_block.instructions.items);
// const range_first_ok = try case_block.addBinOp( } else {
// .cmp_gte, for (items) |item_ref| {
// operand, const item = sema.resolveInst(item_ref);
// item_first, const cmp_ok = try case_block.addBinOp(.cmp_eq, operand, item);
// ); if (any_ok != .none) {
// const range_last_ok = try case_block.addBinOp( any_ok = try case_block.addBinOp(.bool_or, any_ok, cmp_ok);
// .cmp_lte, } else {
// operand, any_ok = cmp_ok;
// 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); var range_i: usize = 0;
// new_condbr.* = .{ while (range_i < ranges_len) : (range_i += 1) {
// .base = .{ const first_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
// .tag = .condbr, extra_index += 1;
// .ty = Type.initTag(.noreturn), const last_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
// .src = src, extra_index += 1;
// },
// .condition = any_ok.?,
// .then_body = undefined,
// .else_body = undefined,
// };
// try case_block.instructions.append(gpa, &new_condbr.base);
// const cond_body: Body = .{ const item_first = sema.resolveInst(first_ref);
// .instructions = try sema.arena.dupe(Air.Inst.Index, case_block.instructions.items), const item_last = sema.resolveInst(last_ref);
// };
// case_block.instructions.shrinkRetainingCapacity(0); // operand >= first and operand <= last
// const body = sema.code.extra[extra_index..][0..body_len]; const range_first_ok = try case_block.addBinOp(
// extra_index += body_len; .cmp_gte,
// _ = try sema.analyzeBody(&case_block, body); operand,
// new_condbr.then_body = .{ item_first,
// .instructions = try sema.arena.dupe(Air.Inst.Index, case_block.instructions.items), );
// }; const range_last_ok = try case_block.addBinOp(
// if (prev_condbr) |condbr| { .cmp_lte,
// condbr.else_body = cond_body; operand,
// } else { item_last,
// first_else_body = cond_body; );
// } const range_ok = try case_block.addBinOp(
// prev_condbr = new_condbr; .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 final_else_body: Body = blk: { const new_cond_br = try case_block.addInstAsIndex(.{ .tag = .cond_br, .data = .{
// if (special.body.len != 0) { .pl_op = .{
// case_block.instructions.shrinkRetainingCapacity(0); .operand = any_ok,
// _ = try sema.analyzeBody(&case_block, special.body); .payload = undefined,
// const else_body: Body = .{ },
// .instructions = try sema.arena.dupe(Air.Inst.Index, case_block.instructions.items), } });
// }; var cond_body = case_block.instructions.toOwnedSlice(gpa);
// if (prev_condbr) |condbr| { defer gpa.free(cond_body);
// 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); case_block.instructions.shrinkRetainingCapacity(0);
//return sema.analyzeBlockBody(block, src, &child_block, merges); 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( fn resolveSwitchItemVal(

View File

@ -1282,44 +1282,49 @@ pub const Context = struct {
// result type is always 'noreturn' // result type is always 'noreturn'
const blocktype = wasm.block_empty; const blocktype = wasm.block_empty;
const signedness: std.builtin.Signedness = blk: { _ = valtype;
// by default we tell the operand type is unsigned (i.e. bools and enum values) _ = blocktype;
if (target_ty.zigTypeTag() != .Int) break :blk .unsigned; _ = 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 // // incase of an actual integer, we emit the correct signedness
break :blk target_ty.intInfo(self.target).signedness; // break :blk target_ty.intInfo(self.target).signedness;
}; //};
for (cases) |case_idx| { //for (cases) |case_idx| {
const case = self.air.extraData(Air.SwitchBr.Case, 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]; // 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 // // 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.startBlock(.block, blocktype, null);
try self.emitWValue(target); // try self.emitWValue(target);
const val = self.air.value(case.data.item).?; // const val = self.air.value(case.data.item).?;
try self.emitConstant(val, target_ty); // try self.emitConstant(val, target_ty);
const opcode = buildOpcode(.{ // const opcode = buildOpcode(.{
.valtype1 = valtype, // .valtype1 = valtype,
.op = .ne, // not equal because we jump out the block if it does not match the condition // .op = .ne, // not equal because we jump out the block if it does not match the condition
.signedness = signedness, // .signedness = signedness,
}); // });
try self.code.append(wasm.opcode(opcode)); // try self.code.append(wasm.opcode(opcode));
try self.code.append(wasm.opcode(.br_if)); // try self.code.append(wasm.opcode(.br_if));
try leb.writeULEB128(self.code.writer(), @as(u32, 0)); // try leb.writeULEB128(self.code.writer(), @as(u32, 0));
// emit our block code // // emit our block code
try self.genBody(case_body); // try self.genBody(case_body);
// end the block we created earlier // // end the block we created earlier
try self.endBlock(); // try self.endBlock();
} //}
// finally, emit the else case if it exists. Here we will not have to //// 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. //// check for a condition, so also no need to emit a block.
try self.genBody(else_body); //try self.genBody(else_body);
return .none; //return .none;
} }
fn airIsErr(self: *Context, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue { fn airIsErr(self: *Context, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {