mirror of
https://github.com/ziglang/zig.git
synced 2026-02-21 16:54:52 +00:00
Merge pull request #7892 from Vexu/stage2-cbe
Stage2 cbe: more control flow
This commit is contained in:
commit
11f6916f9b
@ -2215,7 +2215,7 @@ pub fn addSwitchBr(
|
||||
self: *Module,
|
||||
block: *Scope.Block,
|
||||
src: usize,
|
||||
target_ptr: *Inst,
|
||||
target: *Inst,
|
||||
cases: []Inst.SwitchBr.Case,
|
||||
else_body: ir.Body,
|
||||
) !*Inst {
|
||||
@ -2226,7 +2226,7 @@ pub fn addSwitchBr(
|
||||
.ty = Type.initTag(.noreturn),
|
||||
.src = src,
|
||||
},
|
||||
.target_ptr = target_ptr,
|
||||
.target = target,
|
||||
.cases = cases,
|
||||
.else_body = else_body,
|
||||
};
|
||||
|
||||
313
src/astgen.zig
313
src/astgen.zig
@ -309,7 +309,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
|
||||
.Catch => return catchExpr(mod, scope, rl, node.castTag(.Catch).?),
|
||||
.Comptime => return comptimeKeyword(mod, scope, rl, node.castTag(.Comptime).?),
|
||||
.OrElse => return orelseExpr(mod, scope, rl, node.castTag(.OrElse).?),
|
||||
.Switch => return mod.failNode(scope, node, "TODO implement astgen.expr for .Switch", .{}),
|
||||
.Switch => return switchExpr(mod, scope, rl, node.castTag(.Switch).?),
|
||||
.ContainerDecl => return containerDecl(mod, scope, rl, node.castTag(.ContainerDecl).?),
|
||||
|
||||
.Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}),
|
||||
@ -2246,6 +2246,317 @@ fn forExpr(
|
||||
);
|
||||
}
|
||||
|
||||
fn switchCaseUsesRef(node: *ast.Node.Switch) bool {
|
||||
for (node.cases()) |uncasted_case| {
|
||||
const case = uncasted_case.castTag(.SwitchCase).?;
|
||||
const uncasted_payload = case.payload orelse continue;
|
||||
const payload = uncasted_payload.castTag(.PointerPayload).?;
|
||||
if (payload.ptr_token) |_| return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn getRangeNode(node: *ast.Node) ?*ast.Node.SimpleInfixOp {
|
||||
var cur = node;
|
||||
while (true) {
|
||||
switch (cur.tag) {
|
||||
.Range => return @fieldParentPtr(ast.Node.SimpleInfixOp, "base", cur),
|
||||
.GroupedExpression => cur = @fieldParentPtr(ast.Node.GroupedExpression, "base", cur).expr,
|
||||
else => return null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node.Switch) InnerError!*zir.Inst {
|
||||
const tree = scope.tree();
|
||||
const switch_src = tree.token_locs[switch_node.switch_token].start;
|
||||
const use_ref = switchCaseUsesRef(switch_node);
|
||||
|
||||
var block_scope: Scope.GenZIR = .{
|
||||
.parent = scope,
|
||||
.decl = scope.ownerDecl().?,
|
||||
.arena = scope.arena(),
|
||||
.force_comptime = scope.isComptime(),
|
||||
.instructions = .{},
|
||||
};
|
||||
setBlockResultLoc(&block_scope, rl);
|
||||
defer block_scope.instructions.deinit(mod.gpa);
|
||||
|
||||
var items = std.ArrayList(*zir.Inst).init(mod.gpa);
|
||||
defer items.deinit();
|
||||
|
||||
// first we gather all the switch items and check else/'_' prongs
|
||||
var else_src: ?usize = null;
|
||||
var underscore_src: ?usize = null;
|
||||
var first_range: ?*zir.Inst = null;
|
||||
var simple_case_count: usize = 0;
|
||||
for (switch_node.cases()) |uncasted_case| {
|
||||
const case = uncasted_case.castTag(.SwitchCase).?;
|
||||
const case_src = tree.token_locs[case.firstToken()].start;
|
||||
assert(case.items_len != 0);
|
||||
|
||||
// Check for else/_ prong, those are handled last.
|
||||
if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) {
|
||||
if (else_src) |src| {
|
||||
const msg = msg: {
|
||||
const msg = try mod.errMsg(
|
||||
scope,
|
||||
case_src,
|
||||
"multiple else prongs in switch expression",
|
||||
.{},
|
||||
);
|
||||
errdefer msg.destroy(mod.gpa);
|
||||
try mod.errNote(scope, src, msg, "previous else prong is here", .{});
|
||||
break :msg msg;
|
||||
};
|
||||
return mod.failWithOwnedErrorMsg(scope, msg);
|
||||
}
|
||||
else_src = case_src;
|
||||
continue;
|
||||
} else if (case.items_len == 1 and case.items()[0].tag == .Identifier and
|
||||
mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_"))
|
||||
{
|
||||
if (underscore_src) |src| {
|
||||
const msg = msg: {
|
||||
const msg = try mod.errMsg(
|
||||
scope,
|
||||
case_src,
|
||||
"multiple '_' prongs in switch expression",
|
||||
.{},
|
||||
);
|
||||
errdefer msg.destroy(mod.gpa);
|
||||
try mod.errNote(scope, src, msg, "previous '_' prong is here", .{});
|
||||
break :msg msg;
|
||||
};
|
||||
return mod.failWithOwnedErrorMsg(scope, msg);
|
||||
}
|
||||
underscore_src = case_src;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (else_src) |some_else| {
|
||||
if (underscore_src) |some_underscore| {
|
||||
const msg = msg: {
|
||||
const msg = try mod.errMsg(
|
||||
scope,
|
||||
switch_src,
|
||||
"else and '_' prong in switch expression",
|
||||
.{},
|
||||
);
|
||||
errdefer msg.destroy(mod.gpa);
|
||||
try mod.errNote(scope, some_else, msg, "else prong is here", .{});
|
||||
try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{});
|
||||
break :msg msg;
|
||||
};
|
||||
return mod.failWithOwnedErrorMsg(scope, msg);
|
||||
}
|
||||
}
|
||||
|
||||
if (case.items_len == 1 and getRangeNode(case.items()[0]) == null) simple_case_count += 1;
|
||||
|
||||
// generate all the switch items as comptime expressions
|
||||
for (case.items()) |item| {
|
||||
if (getRangeNode(item)) |range| {
|
||||
const start = try comptimeExpr(mod, &block_scope.base, .none, range.lhs);
|
||||
const end = try comptimeExpr(mod, &block_scope.base, .none, range.rhs);
|
||||
const range_src = tree.token_locs[range.op_token].start;
|
||||
const range_inst = try addZIRBinOp(mod, &block_scope.base, range_src, .switch_range, start, end);
|
||||
try items.append(range_inst);
|
||||
} else {
|
||||
const item_inst = try comptimeExpr(mod, &block_scope.base, .none, item);
|
||||
try items.append(item_inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var special_prong: zir.Inst.SwitchBr.SpecialProng = .none;
|
||||
if (else_src != null) special_prong = .@"else";
|
||||
if (underscore_src != null) special_prong = .underscore;
|
||||
var cases = try block_scope.arena.alloc(zir.Inst.SwitchBr.Case, simple_case_count);
|
||||
|
||||
const target_ptr = if (use_ref) try expr(mod, &block_scope.base, .ref, switch_node.expr) else null;
|
||||
const target = if (target_ptr) |some|
|
||||
try addZIRUnOp(mod, &block_scope.base, some.src, .deref, some)
|
||||
else
|
||||
try expr(mod, &block_scope.base, .none, switch_node.expr);
|
||||
const switch_inst = try addZIRInst(mod, &block_scope.base, switch_src, zir.Inst.SwitchBr, .{
|
||||
.target = target,
|
||||
.cases = cases,
|
||||
.items = try block_scope.arena.dupe(*zir.Inst, items.items),
|
||||
.else_body = undefined, // populated below
|
||||
}, .{
|
||||
.range = first_range,
|
||||
.special_prong = special_prong,
|
||||
});
|
||||
|
||||
const block = try addZIRInstBlock(mod, scope, switch_src, .block, .{
|
||||
.instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
|
||||
});
|
||||
|
||||
var case_scope: Scope.GenZIR = .{
|
||||
.parent = scope,
|
||||
.decl = block_scope.decl,
|
||||
.arena = block_scope.arena,
|
||||
.force_comptime = block_scope.force_comptime,
|
||||
.instructions = .{},
|
||||
};
|
||||
defer case_scope.instructions.deinit(mod.gpa);
|
||||
|
||||
var else_scope: Scope.GenZIR = .{
|
||||
.parent = scope,
|
||||
.decl = case_scope.decl,
|
||||
.arena = case_scope.arena,
|
||||
.force_comptime = case_scope.force_comptime,
|
||||
.instructions = .{},
|
||||
};
|
||||
defer else_scope.instructions.deinit(mod.gpa);
|
||||
|
||||
// Now generate all but the special cases
|
||||
var special_case: ?*ast.Node.SwitchCase = null;
|
||||
var items_index: usize = 0;
|
||||
var case_index: usize = 0;
|
||||
for (switch_node.cases()) |uncasted_case| {
|
||||
const case = uncasted_case.castTag(.SwitchCase).?;
|
||||
const case_src = tree.token_locs[case.firstToken()].start;
|
||||
// reset without freeing to reduce allocations.
|
||||
case_scope.instructions.items.len = 0;
|
||||
|
||||
// Check for else/_ prong, those are handled last.
|
||||
if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) {
|
||||
special_case = case;
|
||||
continue;
|
||||
} else if (case.items_len == 1 and case.items()[0].tag == .Identifier and
|
||||
mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_"))
|
||||
{
|
||||
special_case = case;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this is a simple one item prong then it is handled by the switchbr.
|
||||
if (case.items_len == 1 and getRangeNode(case.items()[0]) == null) {
|
||||
const item = items.items[items_index];
|
||||
items_index += 1;
|
||||
try switchCaseExpr(mod, &case_scope.base, block_scope.break_result_loc, block, case, target, target_ptr);
|
||||
|
||||
cases[case_index] = .{
|
||||
.item = item,
|
||||
.body = .{ .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items) },
|
||||
};
|
||||
case_index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO if the case has few items and no ranges it might be better
|
||||
// to just handle them as switch prongs.
|
||||
|
||||
// Check if the target matches any of the items.
|
||||
// 1, 2, 3..6 will result in
|
||||
// target == 1 or target == 2 or (target >= 3 and target <= 6)
|
||||
var any_ok: ?*zir.Inst = null;
|
||||
for (case.items()) |item| {
|
||||
if (getRangeNode(item)) |range| {
|
||||
const range_src = tree.token_locs[range.op_token].start;
|
||||
const range_inst = items.items[items_index].castTag(.switch_range).?;
|
||||
items_index += 1;
|
||||
|
||||
// target >= start and target <= end
|
||||
const range_start_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_gte, target, range_inst.positionals.lhs);
|
||||
const range_end_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_lte, target, range_inst.positionals.rhs);
|
||||
const range_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_and, range_start_ok, range_end_ok);
|
||||
|
||||
if (any_ok) |some| {
|
||||
any_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_or, some, range_ok);
|
||||
} else {
|
||||
any_ok = range_ok;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const item_inst = items.items[items_index];
|
||||
items_index += 1;
|
||||
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, &else_scope.base, item_inst.src, .bool_or, some, cpm_ok);
|
||||
} else {
|
||||
any_ok = cpm_ok;
|
||||
}
|
||||
}
|
||||
|
||||
const condbr = try addZIRInstSpecial(mod, &case_scope.base, case_src, zir.Inst.CondBr, .{
|
||||
.condition = any_ok.?,
|
||||
.then_body = undefined, // populated below
|
||||
.else_body = undefined, // populated below
|
||||
}, .{});
|
||||
const cond_block = try addZIRInstBlock(mod, &else_scope.base, case_src, .block, .{
|
||||
.instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items),
|
||||
});
|
||||
|
||||
// reset cond_scope for then_body
|
||||
case_scope.instructions.items.len = 0;
|
||||
try switchCaseExpr(mod, &case_scope.base, block_scope.break_result_loc, block, case, target, target_ptr);
|
||||
condbr.positionals.then_body = .{
|
||||
.instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items),
|
||||
};
|
||||
|
||||
// reset cond_scope for else_body
|
||||
case_scope.instructions.items.len = 0;
|
||||
_ = try addZIRInst(mod, &case_scope.base, case_src, zir.Inst.BreakVoid, .{
|
||||
.block = cond_block,
|
||||
}, .{});
|
||||
condbr.positionals.else_body = .{
|
||||
.instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items),
|
||||
};
|
||||
}
|
||||
|
||||
// Finally generate else block or a break.
|
||||
if (special_case) |case| {
|
||||
try switchCaseExpr(mod, &else_scope.base, block_scope.break_result_loc, block, case, target, target_ptr);
|
||||
} else {
|
||||
// Not handling all possible cases is a compile error.
|
||||
_ = try addZIRNoOp(mod, &else_scope.base, switch_src, .unreachable_unsafe);
|
||||
}
|
||||
switch_inst.castTag(.switchbr).?.positionals.else_body = .{
|
||||
.instructions = try block_scope.arena.dupe(*zir.Inst, else_scope.instructions.items),
|
||||
};
|
||||
|
||||
return &block.base;
|
||||
}
|
||||
|
||||
fn switchCaseExpr(
|
||||
mod: *Module,
|
||||
scope: *Scope,
|
||||
rl: ResultLoc,
|
||||
block: *zir.Inst.Block,
|
||||
case: *ast.Node.SwitchCase,
|
||||
target: *zir.Inst,
|
||||
target_ptr: ?*zir.Inst,
|
||||
) !void {
|
||||
const tree = scope.tree();
|
||||
const case_src = tree.token_locs[case.firstToken()].start;
|
||||
const sub_scope = blk: {
|
||||
const uncasted_payload = case.payload orelse break :blk scope;
|
||||
const payload = uncasted_payload.castTag(.PointerPayload).?;
|
||||
const is_ptr = payload.ptr_token != null;
|
||||
const value_name = tree.tokenSlice(payload.value_symbol.firstToken());
|
||||
if (mem.eql(u8, value_name, "_")) {
|
||||
if (is_ptr) {
|
||||
return mod.failTok(scope, payload.ptr_token.?, "pointer modifier invalid on discard", .{});
|
||||
}
|
||||
break :blk scope;
|
||||
}
|
||||
return mod.failNode(scope, payload.value_symbol, "TODO implement switch value payload", .{});
|
||||
};
|
||||
|
||||
const case_body = try expr(mod, sub_scope, rl, case.expr);
|
||||
if (!case_body.tag.isNoReturn()) {
|
||||
_ = try addZIRInst(mod, sub_scope, case_src, zir.Inst.Break, .{
|
||||
.block = block,
|
||||
.operand = case_body,
|
||||
}, .{});
|
||||
}
|
||||
}
|
||||
|
||||
fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst {
|
||||
const tree = scope.tree();
|
||||
const src = tree.token_locs[cfe.ltoken].start;
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
const log = std.log.scoped(.c);
|
||||
const Writer = std.ArrayList(u8).Writer;
|
||||
|
||||
const link = @import("../link.zig");
|
||||
const Module = @import("../Module.zig");
|
||||
const Compilation = @import("../Compilation.zig");
|
||||
const Inst = @import("../ir.zig").Inst;
|
||||
const ir = @import("../ir.zig");
|
||||
const Inst = ir.Inst;
|
||||
const Value = @import("../value.zig").Value;
|
||||
const Type = @import("../type.zig").Type;
|
||||
const TypedValue = @import("../TypedValue.zig");
|
||||
@ -41,6 +41,8 @@ pub const Object = struct {
|
||||
value_map: CValueMap,
|
||||
next_arg_index: usize = 0,
|
||||
next_local_index: usize = 0,
|
||||
next_block_index: usize = 0,
|
||||
indent_writer: std.io.AutoIndentingStream(std.ArrayList(u8).Writer),
|
||||
|
||||
fn resolveInst(o: *Object, inst: *Inst) !CValue {
|
||||
if (inst.value()) |_| {
|
||||
@ -57,31 +59,28 @@ pub const Object = struct {
|
||||
|
||||
fn allocLocal(o: *Object, ty: Type, mutability: Mutability) !CValue {
|
||||
const local_value = o.allocLocalValue();
|
||||
try o.renderTypeAndName(o.code.writer(), ty, local_value, mutability);
|
||||
try o.renderTypeAndName(o.writer(), ty, local_value, mutability);
|
||||
return local_value;
|
||||
}
|
||||
|
||||
fn indent(o: *Object) !void {
|
||||
const indent_size = 4;
|
||||
const indent_level = 1;
|
||||
const indent_amt = indent_size * indent_level;
|
||||
try o.code.writer().writeByteNTimes(' ', indent_amt);
|
||||
fn writer(o: *Object) std.io.AutoIndentingStream(std.ArrayList(u8).Writer).Writer {
|
||||
return o.indent_writer.writer();
|
||||
}
|
||||
|
||||
fn writeCValue(o: *Object, writer: Writer, c_value: CValue) !void {
|
||||
fn writeCValue(o: *Object, w: anytype, c_value: CValue) !void {
|
||||
switch (c_value) {
|
||||
.none => unreachable,
|
||||
.local => |i| return writer.print("t{d}", .{i}),
|
||||
.local_ref => |i| return writer.print("&t{d}", .{i}),
|
||||
.constant => |inst| return o.dg.renderValue(writer, inst.ty, inst.value().?),
|
||||
.arg => |i| return writer.print("a{d}", .{i}),
|
||||
.decl => |decl| return writer.writeAll(mem.span(decl.name)),
|
||||
.local => |i| return w.print("t{d}", .{i}),
|
||||
.local_ref => |i| return w.print("&t{d}", .{i}),
|
||||
.constant => |inst| return o.dg.renderValue(w, inst.ty, inst.value().?),
|
||||
.arg => |i| return w.print("a{d}", .{i}),
|
||||
.decl => |decl| return w.writeAll(mem.span(decl.name)),
|
||||
}
|
||||
}
|
||||
|
||||
fn renderTypeAndName(
|
||||
o: *Object,
|
||||
writer: Writer,
|
||||
w: anytype,
|
||||
ty: Type,
|
||||
name: CValue,
|
||||
mutability: Mutability,
|
||||
@ -97,15 +96,15 @@ pub const Object = struct {
|
||||
render_ty = render_ty.elemType();
|
||||
}
|
||||
|
||||
try o.dg.renderType(writer, render_ty);
|
||||
try o.dg.renderType(w, render_ty);
|
||||
|
||||
const const_prefix = switch (mutability) {
|
||||
.Const => "const ",
|
||||
.Mut => "",
|
||||
};
|
||||
try writer.print(" {s}", .{const_prefix});
|
||||
try o.writeCValue(writer, name);
|
||||
try writer.writeAll(suffix.items);
|
||||
try w.print(" {s}", .{const_prefix});
|
||||
try o.writeCValue(w, name);
|
||||
try w.writeAll(suffix.items);
|
||||
}
|
||||
};
|
||||
|
||||
@ -126,10 +125,13 @@ pub const DeclGen = struct {
|
||||
|
||||
fn renderValue(
|
||||
dg: *DeclGen,
|
||||
writer: Writer,
|
||||
writer: anytype,
|
||||
t: Type,
|
||||
val: Value,
|
||||
) error{ OutOfMemory, AnalysisFail }!void {
|
||||
if (val.isUndef()) {
|
||||
return dg.fail(dg.decl.src(), "TODO: C backend: properly handle undefined in all cases (with debug safety?)", .{});
|
||||
}
|
||||
switch (t.zigTypeTag()) {
|
||||
.Int => {
|
||||
if (t.isSignedInt())
|
||||
@ -197,13 +199,14 @@ pub const DeclGen = struct {
|
||||
},
|
||||
}
|
||||
},
|
||||
.Bool => return writer.print("{}", .{val.toBool()}),
|
||||
else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{
|
||||
@tagName(e),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn renderFunctionSignature(dg: *DeclGen, w: Writer, is_global: bool) !void {
|
||||
fn renderFunctionSignature(dg: *DeclGen, w: anytype, is_global: bool) !void {
|
||||
if (!is_global) {
|
||||
try w.writeAll("static ");
|
||||
}
|
||||
@ -227,7 +230,7 @@ pub const DeclGen = struct {
|
||||
try w.writeByte(')');
|
||||
}
|
||||
|
||||
fn renderType(dg: *DeclGen, w: Writer, t: Type) error{ OutOfMemory, AnalysisFail }!void {
|
||||
fn renderType(dg: *DeclGen, w: anytype, t: Type) error{ OutOfMemory, AnalysisFail }!void {
|
||||
switch (t.zigTypeTag()) {
|
||||
.NoReturn => {
|
||||
try w.writeAll("zig_noreturn void");
|
||||
@ -257,8 +260,8 @@ pub const DeclGen = struct {
|
||||
.int_signed, .int_unsigned => {
|
||||
const info = t.intInfo(dg.module.getTarget());
|
||||
const sign_prefix = switch (info.signedness) {
|
||||
.signed => "i",
|
||||
.unsigned => "",
|
||||
.signed => "",
|
||||
.unsigned => "u",
|
||||
};
|
||||
inline for (.{ 8, 16, 32, 64, 128 }) |nbits| {
|
||||
if (info.bits <= nbits) {
|
||||
@ -290,6 +293,7 @@ pub const DeclGen = struct {
|
||||
try dg.renderType(w, t.elemType());
|
||||
try w.writeAll(" *");
|
||||
},
|
||||
.Null, .Undefined => unreachable, // must be const or comptime
|
||||
else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{
|
||||
@tagName(e),
|
||||
}),
|
||||
@ -324,58 +328,20 @@ pub fn genDecl(o: *Object) !void {
|
||||
try fwd_decl_writer.writeAll(";\n");
|
||||
|
||||
const func: *Module.Fn = func_payload.data;
|
||||
const instructions = func.body.instructions;
|
||||
const writer = o.code.writer();
|
||||
try writer.writeAll("\n");
|
||||
try o.dg.renderFunctionSignature(writer, is_global);
|
||||
if (instructions.len == 0) {
|
||||
try writer.writeAll(" {}\n");
|
||||
return;
|
||||
}
|
||||
try o.indent_writer.insertNewline();
|
||||
try o.dg.renderFunctionSignature(o.writer(), is_global);
|
||||
|
||||
try writer.writeAll(" {");
|
||||
try o.writer().writeByte(' ');
|
||||
try genBody(o, func.body);
|
||||
|
||||
try writer.writeAll("\n");
|
||||
for (instructions) |inst| {
|
||||
const result_value = switch (inst.tag) {
|
||||
.add => try genBinOp(o, inst.castTag(.add).?, " + "),
|
||||
.alloc => try genAlloc(o, inst.castTag(.alloc).?),
|
||||
.arg => genArg(o),
|
||||
.assembly => try genAsm(o, inst.castTag(.assembly).?),
|
||||
.block => try genBlock(o, inst.castTag(.block).?),
|
||||
.bitcast => try genBitcast(o, inst.castTag(.bitcast).?),
|
||||
.breakpoint => try genBreakpoint(o, inst.castTag(.breakpoint).?),
|
||||
.call => try genCall(o, inst.castTag(.call).?),
|
||||
.cmp_eq => try genBinOp(o, inst.castTag(.cmp_eq).?, " == "),
|
||||
.cmp_gt => try genBinOp(o, inst.castTag(.cmp_gt).?, " > "),
|
||||
.cmp_gte => try genBinOp(o, inst.castTag(.cmp_gte).?, " >= "),
|
||||
.cmp_lt => try genBinOp(o, inst.castTag(.cmp_lt).?, " < "),
|
||||
.cmp_lte => try genBinOp(o, inst.castTag(.cmp_lte).?, " <= "),
|
||||
.cmp_neq => try genBinOp(o, inst.castTag(.cmp_neq).?, " != "),
|
||||
.dbg_stmt => try genDbgStmt(o, inst.castTag(.dbg_stmt).?),
|
||||
.intcast => try genIntCast(o, inst.castTag(.intcast).?),
|
||||
.load => try genLoad(o, inst.castTag(.load).?),
|
||||
.ret => try genRet(o, inst.castTag(.ret).?),
|
||||
.retvoid => try genRetVoid(o),
|
||||
.store => try genStore(o, inst.castTag(.store).?),
|
||||
.sub => try genBinOp(o, inst.castTag(.sub).?, " - "),
|
||||
.unreach => try genUnreach(o, inst.castTag(.unreach).?),
|
||||
else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}),
|
||||
};
|
||||
switch (result_value) {
|
||||
.none => {},
|
||||
else => try o.value_map.putNoClobber(inst, result_value),
|
||||
}
|
||||
}
|
||||
|
||||
try writer.writeAll("}\n");
|
||||
try o.indent_writer.insertNewline();
|
||||
} else if (tv.val.tag() == .extern_fn) {
|
||||
const writer = o.code.writer();
|
||||
const writer = o.writer();
|
||||
try writer.writeAll("ZIG_EXTERN_C ");
|
||||
try o.dg.renderFunctionSignature(writer, true);
|
||||
try writer.writeAll(";\n");
|
||||
} else {
|
||||
const writer = o.code.writer();
|
||||
const writer = o.writer();
|
||||
try writer.writeAll("static ");
|
||||
|
||||
// TODO ask the Decl if it is const
|
||||
@ -410,11 +376,69 @@ pub fn genHeader(dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!void {
|
||||
const writer = o.writer();
|
||||
if (body.instructions.len == 0) {
|
||||
try writer.writeAll("{}");
|
||||
return;
|
||||
}
|
||||
|
||||
try writer.writeAll("{\n");
|
||||
o.indent_writer.pushIndent();
|
||||
|
||||
for (body.instructions) |inst| {
|
||||
const result_value = switch (inst.tag) {
|
||||
.constant => unreachable, // excluded from function bodies
|
||||
.add => try genBinOp(o, inst.castTag(.add).?, " + "),
|
||||
.alloc => try genAlloc(o, inst.castTag(.alloc).?),
|
||||
.arg => genArg(o),
|
||||
.assembly => try genAsm(o, inst.castTag(.assembly).?),
|
||||
.block => try genBlock(o, inst.castTag(.block).?),
|
||||
.bitcast => try genBitcast(o, inst.castTag(.bitcast).?),
|
||||
.breakpoint => try genBreakpoint(o, inst.castTag(.breakpoint).?),
|
||||
.call => try genCall(o, inst.castTag(.call).?),
|
||||
.cmp_eq => try genBinOp(o, inst.castTag(.cmp_eq).?, " == "),
|
||||
.cmp_gt => try genBinOp(o, inst.castTag(.cmp_gt).?, " > "),
|
||||
.cmp_gte => try genBinOp(o, inst.castTag(.cmp_gte).?, " >= "),
|
||||
.cmp_lt => try genBinOp(o, inst.castTag(.cmp_lt).?, " < "),
|
||||
.cmp_lte => try genBinOp(o, inst.castTag(.cmp_lte).?, " <= "),
|
||||
.cmp_neq => try genBinOp(o, inst.castTag(.cmp_neq).?, " != "),
|
||||
.dbg_stmt => try genDbgStmt(o, inst.castTag(.dbg_stmt).?),
|
||||
.intcast => try genIntCast(o, inst.castTag(.intcast).?),
|
||||
.load => try genLoad(o, inst.castTag(.load).?),
|
||||
.ret => try genRet(o, inst.castTag(.ret).?),
|
||||
.retvoid => try genRetVoid(o),
|
||||
.store => try genStore(o, inst.castTag(.store).?),
|
||||
.sub => try genBinOp(o, inst.castTag(.sub).?, " - "),
|
||||
.unreach => try genUnreach(o, inst.castTag(.unreach).?),
|
||||
.loop => try genLoop(o, inst.castTag(.loop).?),
|
||||
.condbr => try genCondBr(o, inst.castTag(.condbr).?),
|
||||
.br => try genBr(o, inst.castTag(.br).?),
|
||||
.br_void => try genBrVoid(o, inst.castTag(.br_void).?.block),
|
||||
.switchbr => try genSwitchBr(o, inst.castTag(.switchbr).?),
|
||||
// bool_and and bool_or are non-short-circuit operations
|
||||
.bool_and => try genBinOp(o, inst.castTag(.bool_and).?, " & "),
|
||||
.bool_or => try genBinOp(o, inst.castTag(.bool_or).?, " | "),
|
||||
.bit_and => try genBinOp(o, inst.castTag(.bit_and).?, " & "),
|
||||
.bit_or => try genBinOp(o, inst.castTag(.bit_or).?, " | "),
|
||||
.xor => try genBinOp(o, inst.castTag(.xor).?, " ^ "),
|
||||
.not => try genUnOp(o, inst.castTag(.not).?, "!"),
|
||||
else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}),
|
||||
};
|
||||
switch (result_value) {
|
||||
.none => {},
|
||||
else => try o.value_map.putNoClobber(inst, result_value),
|
||||
}
|
||||
}
|
||||
|
||||
o.indent_writer.popIndent();
|
||||
try writer.writeAll("}");
|
||||
}
|
||||
|
||||
fn genAlloc(o: *Object, alloc: *Inst.NoOp) !CValue {
|
||||
const writer = o.code.writer();
|
||||
const writer = o.writer();
|
||||
|
||||
// First line: the variable used as data storage.
|
||||
try o.indent();
|
||||
const elem_type = alloc.base.ty.elemType();
|
||||
const mutability: Mutability = if (alloc.base.ty.isConstPtr()) .Const else .Mut;
|
||||
const local = try o.allocLocal(elem_type, mutability);
|
||||
@ -430,15 +454,13 @@ fn genArg(o: *Object) CValue {
|
||||
}
|
||||
|
||||
fn genRetVoid(o: *Object) !CValue {
|
||||
try o.indent();
|
||||
try o.code.writer().print("return;\n", .{});
|
||||
try o.writer().print("return;\n", .{});
|
||||
return CValue.none;
|
||||
}
|
||||
|
||||
fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue {
|
||||
const operand = try o.resolveInst(inst.operand);
|
||||
const writer = o.code.writer();
|
||||
try o.indent();
|
||||
const writer = o.writer();
|
||||
const local = try o.allocLocal(inst.base.ty, .Const);
|
||||
switch (operand) {
|
||||
.local_ref => |i| {
|
||||
@ -458,8 +480,7 @@ fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue {
|
||||
|
||||
fn genRet(o: *Object, inst: *Inst.UnOp) !CValue {
|
||||
const operand = try o.resolveInst(inst.operand);
|
||||
try o.indent();
|
||||
const writer = o.code.writer();
|
||||
const writer = o.writer();
|
||||
try writer.writeAll("return ");
|
||||
try o.writeCValue(writer, operand);
|
||||
try writer.writeAll(";\n");
|
||||
@ -472,8 +493,7 @@ fn genIntCast(o: *Object, inst: *Inst.UnOp) !CValue {
|
||||
|
||||
const from = try o.resolveInst(inst.operand);
|
||||
|
||||
try o.indent();
|
||||
const writer = o.code.writer();
|
||||
const writer = o.writer();
|
||||
const local = try o.allocLocal(inst.base.ty, .Const);
|
||||
try writer.writeAll(" = (");
|
||||
try o.dg.renderType(writer, inst.base.ty);
|
||||
@ -488,8 +508,7 @@ fn genStore(o: *Object, inst: *Inst.BinOp) !CValue {
|
||||
const dest_ptr = try o.resolveInst(inst.lhs);
|
||||
const src_val = try o.resolveInst(inst.rhs);
|
||||
|
||||
try o.indent();
|
||||
const writer = o.code.writer();
|
||||
const writer = o.writer();
|
||||
switch (dest_ptr) {
|
||||
.local_ref => |i| {
|
||||
const dest: CValue = .{ .local = i };
|
||||
@ -516,8 +535,7 @@ fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue {
|
||||
const lhs = try o.resolveInst(inst.lhs);
|
||||
const rhs = try o.resolveInst(inst.rhs);
|
||||
|
||||
try o.indent();
|
||||
const writer = o.code.writer();
|
||||
const writer = o.writer();
|
||||
const local = try o.allocLocal(inst.base.ty, .Const);
|
||||
|
||||
try writer.writeAll(" = ");
|
||||
@ -529,6 +547,22 @@ fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue {
|
||||
return local;
|
||||
}
|
||||
|
||||
fn genUnOp(o: *Object, inst: *Inst.UnOp, operator: []const u8) !CValue {
|
||||
if (inst.base.isUnused())
|
||||
return CValue.none;
|
||||
|
||||
const operand = try o.resolveInst(inst.operand);
|
||||
|
||||
const writer = o.writer();
|
||||
const local = try o.allocLocal(inst.base.ty, .Const);
|
||||
|
||||
try writer.print(" = {s}", .{operator});
|
||||
try o.writeCValue(writer, operand);
|
||||
try writer.writeAll(";\n");
|
||||
|
||||
return local;
|
||||
}
|
||||
|
||||
fn genCall(o: *Object, inst: *Inst.Call) !CValue {
|
||||
if (inst.func.castTag(.constant)) |func_inst| {
|
||||
const fn_decl = if (func_inst.val.castTag(.extern_fn)) |extern_fn|
|
||||
@ -543,8 +577,7 @@ fn genCall(o: *Object, inst: *Inst.Call) !CValue {
|
||||
const unused_result = inst.base.isUnused();
|
||||
var result_local: CValue = .none;
|
||||
|
||||
try o.indent();
|
||||
const writer = o.code.writer();
|
||||
const writer = o.writer();
|
||||
if (unused_result) {
|
||||
if (ret_ty.hasCodeGenBits()) {
|
||||
try writer.print("(void)", .{});
|
||||
@ -581,14 +614,53 @@ fn genDbgStmt(o: *Object, inst: *Inst.NoOp) !CValue {
|
||||
}
|
||||
|
||||
fn genBlock(o: *Object, inst: *Inst.Block) !CValue {
|
||||
return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement blocks", .{});
|
||||
const block_id: usize = o.next_block_index;
|
||||
o.next_block_index += 1;
|
||||
const writer = o.writer();
|
||||
|
||||
// store the block id in relocs.capacity as it is not used for anything else in the C backend.
|
||||
inst.codegen.relocs.capacity = block_id;
|
||||
const result = if (inst.base.ty.tag() != .void and !inst.base.isUnused()) blk: {
|
||||
// allocate a location for the result
|
||||
const local = try o.allocLocal(inst.base.ty, .Mut);
|
||||
try writer.writeAll(";\n");
|
||||
break :blk local;
|
||||
} else
|
||||
CValue{ .none = {} };
|
||||
|
||||
inst.codegen.mcv = @bitCast(@import("../codegen.zig").AnyMCValue, result);
|
||||
try genBody(o, inst.body);
|
||||
try o.indent_writer.insertNewline();
|
||||
// label must be followed by an expression, add an empty one.
|
||||
try writer.print("zig_block_{d}:;\n", .{block_id});
|
||||
return result;
|
||||
}
|
||||
|
||||
fn genBr(o: *Object, inst: *Inst.Br) !CValue {
|
||||
const result = @bitCast(CValue, inst.block.codegen.mcv);
|
||||
const writer = o.writer();
|
||||
|
||||
// If result is .none then the value of the block is unused.
|
||||
if (inst.operand.ty.tag() != .void and result != .none) {
|
||||
const operand = try o.resolveInst(inst.operand);
|
||||
try o.writeCValue(writer, result);
|
||||
try writer.writeAll(" = ");
|
||||
try o.writeCValue(writer, operand);
|
||||
try writer.writeAll(";\n");
|
||||
}
|
||||
|
||||
return genBrVoid(o, inst.block);
|
||||
}
|
||||
|
||||
fn genBrVoid(o: *Object, block: *Inst.Block) !CValue {
|
||||
try o.writer().print("goto zig_block_{d};\n", .{block.codegen.relocs.capacity});
|
||||
return CValue.none;
|
||||
}
|
||||
|
||||
fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue {
|
||||
const operand = try o.resolveInst(inst.operand);
|
||||
|
||||
const writer = o.code.writer();
|
||||
try o.indent();
|
||||
const writer = o.writer();
|
||||
if (inst.base.ty.zigTypeTag() == .Pointer and inst.operand.ty.zigTypeTag() == .Pointer) {
|
||||
const local = try o.allocLocal(inst.base.ty, .Const);
|
||||
try writer.writeAll(" = (");
|
||||
@ -602,7 +674,6 @@ fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue {
|
||||
|
||||
const local = try o.allocLocal(inst.base.ty, .Mut);
|
||||
try writer.writeAll(";\n");
|
||||
try o.indent();
|
||||
|
||||
try writer.writeAll("memcpy(&");
|
||||
try o.writeCValue(writer, local);
|
||||
@ -616,14 +687,61 @@ fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue {
|
||||
}
|
||||
|
||||
fn genBreakpoint(o: *Object, inst: *Inst.NoOp) !CValue {
|
||||
try o.indent();
|
||||
try o.code.writer().writeAll("zig_breakpoint();\n");
|
||||
try o.writer().writeAll("zig_breakpoint();\n");
|
||||
return CValue.none;
|
||||
}
|
||||
|
||||
fn genUnreach(o: *Object, inst: *Inst.NoOp) !CValue {
|
||||
try o.indent();
|
||||
try o.code.writer().writeAll("zig_unreachable();\n");
|
||||
try o.writer().writeAll("zig_unreachable();\n");
|
||||
return CValue.none;
|
||||
}
|
||||
|
||||
fn genLoop(o: *Object, inst: *Inst.Loop) !CValue {
|
||||
try o.writer().writeAll("while (true) ");
|
||||
try genBody(o, inst.body);
|
||||
try o.indent_writer.insertNewline();
|
||||
return CValue.none;
|
||||
}
|
||||
|
||||
fn genCondBr(o: *Object, inst: *Inst.CondBr) !CValue {
|
||||
const cond = try o.resolveInst(inst.condition);
|
||||
const writer = o.writer();
|
||||
|
||||
try writer.writeAll("if (");
|
||||
try o.writeCValue(writer, cond);
|
||||
try writer.writeAll(") ");
|
||||
try genBody(o, inst.then_body);
|
||||
try writer.writeAll(" else ");
|
||||
try genBody(o, inst.else_body);
|
||||
try o.indent_writer.insertNewline();
|
||||
|
||||
return CValue.none;
|
||||
}
|
||||
|
||||
fn genSwitchBr(o: *Object, inst: *Inst.SwitchBr) !CValue {
|
||||
const target = try o.resolveInst(inst.target);
|
||||
const writer = o.writer();
|
||||
|
||||
try writer.writeAll("switch (");
|
||||
try o.writeCValue(writer, target);
|
||||
try writer.writeAll(") {\n");
|
||||
o.indent_writer.pushIndent();
|
||||
|
||||
for (inst.cases) |case| {
|
||||
try writer.writeAll("case ");
|
||||
try o.dg.renderValue(writer, inst.target.ty, case.item);
|
||||
try writer.writeAll(": ");
|
||||
// the case body must be noreturn so we don't need to insert a break
|
||||
try genBody(o, case.body);
|
||||
try o.indent_writer.insertNewline();
|
||||
}
|
||||
|
||||
try writer.writeAll("default: ");
|
||||
try genBody(o, inst.else_body);
|
||||
try o.indent_writer.insertNewline();
|
||||
|
||||
o.indent_writer.popIndent();
|
||||
try writer.writeAll("}\n");
|
||||
return CValue.none;
|
||||
}
|
||||
|
||||
@ -631,13 +749,12 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue {
|
||||
if (as.base.isUnused() and !as.is_volatile)
|
||||
return CValue.none;
|
||||
|
||||
const writer = o.code.writer();
|
||||
const writer = o.writer();
|
||||
for (as.inputs) |i, index| {
|
||||
if (i[0] == '{' and i[i.len - 1] == '}') {
|
||||
const reg = i[1 .. i.len - 1];
|
||||
const arg = as.args[index];
|
||||
const arg_c_value = try o.resolveInst(arg);
|
||||
try o.indent();
|
||||
try writer.writeAll("register ");
|
||||
try o.dg.renderType(writer, arg.ty);
|
||||
|
||||
@ -648,7 +765,6 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue {
|
||||
return o.dg.fail(o.dg.decl.src(), "TODO non-explicit inline asm regs", .{});
|
||||
}
|
||||
}
|
||||
try o.indent();
|
||||
const volatile_string: []const u8 = if (as.is_volatile) "volatile " else "";
|
||||
try writer.print("__asm {s}(\"{s}\"", .{ volatile_string, as.asm_source });
|
||||
if (as.output) |_| {
|
||||
|
||||
@ -521,7 +521,7 @@ pub const Inst = struct {
|
||||
pub const base_tag = Tag.switchbr;
|
||||
|
||||
base: Inst,
|
||||
target_ptr: *Inst,
|
||||
target: *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, ... ].
|
||||
@ -544,7 +544,7 @@ pub const Inst = struct {
|
||||
var i = index;
|
||||
|
||||
if (i < 1)
|
||||
return self.target_ptr;
|
||||
return self.target;
|
||||
i -= 1;
|
||||
|
||||
return null;
|
||||
|
||||
@ -95,7 +95,9 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
|
||||
.gpa = module.gpa,
|
||||
.code = code.toManaged(module.gpa),
|
||||
.value_map = codegen.CValueMap.init(module.gpa),
|
||||
.indent_writer = undefined, // set later so we can get a pointer to object.code
|
||||
};
|
||||
object.indent_writer = std.io.autoIndentingStream(4, object.code.writer());
|
||||
defer object.value_map.deinit();
|
||||
defer object.code.deinit();
|
||||
defer object.dg.fwd_decl.deinit();
|
||||
|
||||
60
src/zir.zig
60
src/zir.zig
@ -338,6 +338,12 @@ pub const Inst = struct {
|
||||
enum_type,
|
||||
/// Does nothing; returns a void value.
|
||||
void_value,
|
||||
/// A switch expression.
|
||||
switchbr,
|
||||
/// A range in a switch case, `lhs...rhs`.
|
||||
/// Only checks that `lhs >= rhs` if they are ints, everything else is
|
||||
/// validated by the .switch instruction.
|
||||
switch_range,
|
||||
|
||||
pub fn Type(tag: Tag) type {
|
||||
return switch (tag) {
|
||||
@ -435,6 +441,7 @@ pub const Inst = struct {
|
||||
.error_union_type,
|
||||
.merge_error_sets,
|
||||
.slice_start,
|
||||
.switch_range,
|
||||
=> BinOp,
|
||||
|
||||
.block,
|
||||
@ -478,6 +485,7 @@ pub const Inst = struct {
|
||||
.enum_type => EnumType,
|
||||
.union_type => UnionType,
|
||||
.struct_type => StructType,
|
||||
.switchbr => SwitchBr,
|
||||
};
|
||||
}
|
||||
|
||||
@ -605,6 +613,8 @@ pub const Inst = struct {
|
||||
.union_type,
|
||||
.struct_type,
|
||||
.void_value,
|
||||
.switch_range,
|
||||
.switchbr,
|
||||
=> false,
|
||||
|
||||
.@"break",
|
||||
@ -1171,6 +1181,36 @@ pub const Inst = struct {
|
||||
none,
|
||||
};
|
||||
};
|
||||
|
||||
pub const SwitchBr = struct {
|
||||
pub const base_tag = Tag.switchbr;
|
||||
base: Inst,
|
||||
|
||||
positionals: struct {
|
||||
target: *Inst,
|
||||
/// List of all individual items and ranges
|
||||
items: []*Inst,
|
||||
cases: []Case,
|
||||
else_body: Body,
|
||||
},
|
||||
kw_args: struct {
|
||||
/// Pointer to first range if such exists.
|
||||
range: ?*Inst = null,
|
||||
special_prong: SpecialProng = .none,
|
||||
},
|
||||
|
||||
// Not anonymous due to stage1 limitations
|
||||
pub const SpecialProng = enum {
|
||||
none,
|
||||
@"else",
|
||||
underscore,
|
||||
};
|
||||
|
||||
pub const Case = struct {
|
||||
item: *Inst,
|
||||
body: Body,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
pub const ErrorMsg = struct {
|
||||
@ -1431,6 +1471,26 @@ const Writer = struct {
|
||||
}
|
||||
try stream.writeByte(']');
|
||||
},
|
||||
[]Inst.SwitchBr.Case => {
|
||||
if (param.len == 0) {
|
||||
return stream.writeAll("{}");
|
||||
}
|
||||
try stream.writeAll("{\n");
|
||||
for (param) |*case, i| {
|
||||
if (i != 0) {
|
||||
try stream.writeAll(",\n");
|
||||
}
|
||||
try stream.writeByteNTimes(' ', self.indent);
|
||||
self.indent += 2;
|
||||
try self.writeParamToStream(stream, &case.item);
|
||||
try stream.writeAll(" => ");
|
||||
try self.writeParamToStream(stream, &case.body);
|
||||
self.indent -= 2;
|
||||
}
|
||||
try stream.writeByte('\n');
|
||||
try stream.writeByteNTimes(' ', self.indent - 2);
|
||||
try stream.writeByte('}');
|
||||
},
|
||||
else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)),
|
||||
}
|
||||
}
|
||||
|
||||
228
src/zir_sema.zig
228
src/zir_sema.zig
@ -154,6 +154,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
|
||||
.bool_and => return zirBoolOp(mod, scope, old_inst.castTag(.bool_and).?),
|
||||
.bool_or => return zirBoolOp(mod, scope, old_inst.castTag(.bool_or).?),
|
||||
.void_value => return mod.constVoid(scope, old_inst.src),
|
||||
.switchbr => return zirSwitchBr(mod, scope, old_inst.castTag(.switchbr).?),
|
||||
.switch_range => return zirSwitchRange(mod, scope, old_inst.castTag(.switch_range).?),
|
||||
|
||||
.container_field_named,
|
||||
.container_field_typed,
|
||||
@ -1535,6 +1537,232 @@ fn zirSliceStart(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!
|
||||
return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null);
|
||||
}
|
||||
|
||||
fn zirSwitchRange(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
const start = try resolveInst(mod, scope, inst.positionals.lhs);
|
||||
const end = try resolveInst(mod, scope, inst.positionals.rhs);
|
||||
|
||||
switch (start.ty.zigTypeTag()) {
|
||||
.Int, .ComptimeInt => {},
|
||||
else => return mod.constVoid(scope, inst.base.src),
|
||||
}
|
||||
switch (end.ty.zigTypeTag()) {
|
||||
.Int, .ComptimeInt => {},
|
||||
else => return mod.constVoid(scope, inst.base.src),
|
||||
}
|
||||
// .switch_range must be inside a comptime scope
|
||||
const start_val = start.value().?;
|
||||
const end_val = end.value().?;
|
||||
if (start_val.compare(.gte, end_val)) {
|
||||
return mod.fail(scope, inst.base.src, "range start value must be smaller than the end value", .{});
|
||||
}
|
||||
return mod.constVoid(scope, inst.base.src);
|
||||
}
|
||||
|
||||
fn zirSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) InnerError!*Inst {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
const target = try resolveInst(mod, scope, inst.positionals.target);
|
||||
try validateSwitch(mod, scope, target, inst);
|
||||
|
||||
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.cast(Scope.Block).?, case.body);
|
||||
return mod.constNoReturn(scope, inst.base.src);
|
||||
}
|
||||
}
|
||||
try analyzeBody(mod, scope.cast(Scope.Block).?, inst.positionals.else_body);
|
||||
return mod.constNoReturn(scope, inst.base.src);
|
||||
}
|
||||
|
||||
if (inst.positionals.cases.len == 0) {
|
||||
// no cases just analyze else_branch
|
||||
try analyzeBody(mod, scope.cast(Scope.Block).?, 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);
|
||||
|
||||
var case_block: Scope.Block = .{
|
||||
.parent = parent_block,
|
||||
.inst_table = parent_block.inst_table,
|
||||
.func = parent_block.func,
|
||||
.owner_decl = parent_block.owner_decl,
|
||||
.src_decl = parent_block.src_decl,
|
||||
.instructions = .{},
|
||||
.arena = parent_block.arena,
|
||||
.inlining = parent_block.inlining,
|
||||
.is_comptime = parent_block.is_comptime,
|
||||
.branch_quota = parent_block.branch_quota,
|
||||
};
|
||||
defer case_block.instructions.deinit(mod.gpa);
|
||||
|
||||
for (inst.positionals.cases) |case, i| {
|
||||
// Reset without freeing.
|
||||
case_block.instructions.items.len = 0;
|
||||
|
||||
const resolved = try resolveInst(mod, scope, case.item);
|
||||
const casted = try mod.coerce(scope, target.ty, resolved);
|
||||
const item = try mod.resolveConstValue(scope, casted);
|
||||
|
||||
try analyzeBody(mod, &case_block, case.body);
|
||||
|
||||
cases[i] = .{
|
||||
.item = item,
|
||||
.body = .{ .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items) },
|
||||
};
|
||||
}
|
||||
|
||||
case_block.instructions.items.len = 0;
|
||||
try analyzeBody(mod, &case_block, 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, cases, else_body);
|
||||
}
|
||||
|
||||
fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.SwitchBr) InnerError!void {
|
||||
// validate usage of '_' prongs
|
||||
if (inst.kw_args.special_prong == .underscore and target.ty.zigTypeTag() != .Enum) {
|
||||
return mod.fail(scope, inst.base.src, "'_' prong only allowed when switching on non-exhaustive enums", .{});
|
||||
// TODO notes "'_' prong here" inst.positionals.cases[last].src
|
||||
}
|
||||
|
||||
// check that target type supports ranges
|
||||
if (inst.kw_args.range) |range_inst| {
|
||||
switch (target.ty.zigTypeTag()) {
|
||||
.Int, .ComptimeInt => {},
|
||||
else => {
|
||||
return mod.fail(scope, target.src, "ranges not allowed when switching on type {}", .{target.ty});
|
||||
// TODO notes "range used here" range_inst.src
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// validate for duplicate items/missing else prong
|
||||
switch (target.ty.zigTypeTag()) {
|
||||
.Enum => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Enum", .{}),
|
||||
.ErrorSet => return mod.fail(scope, inst.base.src, "TODO validateSwitch .ErrorSet", .{}),
|
||||
.Union => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Union", .{}),
|
||||
.Int, .ComptimeInt => {
|
||||
var range_set = @import("RangeSet.zig").init(mod.gpa);
|
||||
defer range_set.deinit();
|
||||
|
||||
for (inst.positionals.items) |item| {
|
||||
const maybe_src = if (item.castTag(.switch_range)) |range| blk: {
|
||||
const start_resolved = try resolveInst(mod, scope, range.positionals.lhs);
|
||||
const start_casted = try mod.coerce(scope, target.ty, start_resolved);
|
||||
const end_resolved = try resolveInst(mod, scope, range.positionals.rhs);
|
||||
const end_casted = try mod.coerce(scope, target.ty, end_resolved);
|
||||
|
||||
break :blk try range_set.add(
|
||||
try mod.resolveConstValue(scope, start_casted),
|
||||
try mod.resolveConstValue(scope, end_casted),
|
||||
item.src,
|
||||
);
|
||||
} else blk: {
|
||||
const resolved = try resolveInst(mod, scope, item);
|
||||
const casted = try mod.coerce(scope, target.ty, resolved);
|
||||
const value = try mod.resolveConstValue(scope, casted);
|
||||
break :blk try range_set.add(value, value, item.src);
|
||||
};
|
||||
|
||||
if (maybe_src) |previous_src| {
|
||||
return mod.fail(scope, item.src, "duplicate switch value", .{});
|
||||
// TODO notes "previous value is here" previous_src
|
||||
}
|
||||
}
|
||||
|
||||
if (target.ty.zigTypeTag() == .Int) {
|
||||
var arena = std.heap.ArenaAllocator.init(mod.gpa);
|
||||
defer arena.deinit();
|
||||
|
||||
const start = try target.ty.minInt(&arena, mod.getTarget());
|
||||
const end = try target.ty.maxInt(&arena, mod.getTarget());
|
||||
if (try range_set.spans(start, end)) {
|
||||
if (inst.kw_args.special_prong == .@"else") {
|
||||
return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (inst.kw_args.special_prong != .@"else") {
|
||||
return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{});
|
||||
}
|
||||
},
|
||||
.Bool => {
|
||||
var true_count: u8 = 0;
|
||||
var false_count: u8 = 0;
|
||||
for (inst.positionals.items) |item| {
|
||||
const resolved = try resolveInst(mod, scope, item);
|
||||
const casted = try mod.coerce(scope, Type.initTag(.bool), resolved);
|
||||
if ((try mod.resolveConstValue(scope, casted)).toBool()) {
|
||||
true_count += 1;
|
||||
} else {
|
||||
false_count += 1;
|
||||
}
|
||||
|
||||
if (true_count + false_count > 2) {
|
||||
return mod.fail(scope, item.src, "duplicate switch value", .{});
|
||||
}
|
||||
}
|
||||
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 + false_count == 2) and inst.kw_args.special_prong == .@"else") {
|
||||
return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{});
|
||||
}
|
||||
},
|
||||
.EnumLiteral, .Void, .Fn, .Pointer, .Type => {
|
||||
if (inst.kw_args.special_prong != .@"else") {
|
||||
return mod.fail(scope, inst.base.src, "else prong required when switching on type '{}'", .{target.ty});
|
||||
}
|
||||
|
||||
var seen_values = std.HashMap(Value, usize, Value.hash, Value.eql, std.hash_map.DefaultMaxLoadPercentage).init(mod.gpa);
|
||||
defer seen_values.deinit();
|
||||
|
||||
for (inst.positionals.items) |item| {
|
||||
const resolved = try resolveInst(mod, scope, item);
|
||||
const casted = try mod.coerce(scope, target.ty, resolved);
|
||||
const val = try mod.resolveConstValue(scope, casted);
|
||||
|
||||
if (try seen_values.fetchPut(val, item.src)) |prev| {
|
||||
return mod.fail(scope, item.src, "duplicate switch value", .{});
|
||||
// TODO notes "previous value here" prev.value
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
.ErrorUnion,
|
||||
.NoReturn,
|
||||
.Array,
|
||||
.Struct,
|
||||
.Undefined,
|
||||
.Null,
|
||||
.Optional,
|
||||
.BoundFn,
|
||||
.Opaque,
|
||||
.Vector,
|
||||
.Frame,
|
||||
.AnyFrame,
|
||||
.ComptimeFloat,
|
||||
.Float,
|
||||
=> {
|
||||
return mod.fail(scope, target.src, "invalid switch target type '{}'", .{target.ty});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn zirImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
@ -185,6 +185,52 @@ pub fn addCases(ctx: *TestContext) !void {
|
||||
\\}
|
||||
, "");
|
||||
}
|
||||
{
|
||||
var case = ctx.exeFromCompiledC("control flow", .{});
|
||||
|
||||
// Simple while loop
|
||||
case.addCompareOutput(
|
||||
\\export fn main() c_int {
|
||||
\\ var a: c_int = 0;
|
||||
\\ while (a < 5) : (a+=1) {}
|
||||
\\ return a - 5;
|
||||
\\}
|
||||
, "");
|
||||
case.addCompareOutput(
|
||||
\\export fn main() c_int {
|
||||
\\ var a = true;
|
||||
\\ while (!a) {}
|
||||
\\ return 0;
|
||||
\\}
|
||||
, "");
|
||||
|
||||
// If expression
|
||||
case.addCompareOutput(
|
||||
\\export fn main() c_int {
|
||||
\\ var cond: c_int = 0;
|
||||
\\ var a: c_int = @as(c_int, if (cond == 0)
|
||||
\\ 2
|
||||
\\ else
|
||||
\\ 3) + 9;
|
||||
\\ return a - 11;
|
||||
\\}
|
||||
, "");
|
||||
|
||||
// Switch expression
|
||||
case.addCompareOutput(
|
||||
\\export fn main() c_int {
|
||||
\\ var cond: c_int = 0;
|
||||
\\ var a: c_int = switch (cond) {
|
||||
\\ 1 => 1,
|
||||
\\ 2 => 2,
|
||||
\\ 99...300, 12 => 3,
|
||||
\\ 0 => 4,
|
||||
\\ else => 5,
|
||||
\\ };
|
||||
\\ return a - 4;
|
||||
\\}
|
||||
, "");
|
||||
}
|
||||
ctx.c("empty start function", linux_x64,
|
||||
\\export fn _start() noreturn {
|
||||
\\ unreachable;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user