stage2: switch comptime execution
parent
12e4c648cc
commit
769d5a9c43
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
|
12
src/zir.zig
12
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"),
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue