From 81c512f35b1926cf3fb6f29b97e68256aa164f68 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 26 Jan 2021 19:50:46 +0200 Subject: stage2 cbe: loop instruction --- src/codegen/c.zig | 99 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 58 insertions(+), 41 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index b26f753757..ccde36a10d 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -6,7 +6,8 @@ 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"); @@ -324,51 +325,13 @@ 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 writer.writeAll(" {"); + + 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"); } else if (tv.val.tag() == .extern_fn) { const writer = o.code.writer(); try writer.writeAll("ZIG_EXTERN_C "); @@ -410,6 +373,52 @@ pub fn genHeader(dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void { } } +pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!void { + const writer = o.code.writer(); + if (body.instructions.len == 0) { + try writer.writeAll(" {}"); + return; + } + + try writer.writeAll(" {"); + + try writer.writeAll("\n"); + for (body.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).?), + .loop => try genLoop(o, inst.castTag(.loop).?), + 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("}"); +} + fn genAlloc(o: *Object, alloc: *Inst.NoOp) !CValue { const writer = o.code.writer(); @@ -627,6 +636,14 @@ fn genUnreach(o: *Object, inst: *Inst.NoOp) !CValue { return CValue.none; } +fn genLoop(o: *Object, inst: *Inst.Loop) !CValue { + try o.indent(); + try o.code.writer().writeAll("while (true)"); + try genBody(o, inst.body); + try o.code.writer().writeAll("\n"); + return CValue.none; +} + fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { if (as.base.isUnused() and !as.is_volatile) return CValue.none; -- cgit v1.2.3 From 6ca0ff90b63cea79d8d63519a3c133cfde111884 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 26 Jan 2021 20:25:30 +0200 Subject: stage2 cbe: use AutoIndentingStream --- src/codegen/c.zig | 100 ++++++++++++++++++++++-------------------------------- src/link/C.zig | 2 ++ 2 files changed, 43 insertions(+), 59 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index ccde36a10d..9bccde5ffd 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1,7 +1,6 @@ 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"); @@ -42,6 +41,7 @@ pub const Object = struct { value_map: CValueMap, next_arg_index: usize = 0, next_local_index: usize = 0, + indent_writer: std.io.AutoIndentingStream(std.ArrayList(u8).Writer), fn resolveInst(o: *Object, inst: *Inst) !CValue { if (inst.value()) |_| { @@ -58,31 +58,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, @@ -98,15 +95,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); } }; @@ -127,7 +124,7 @@ pub const DeclGen = struct { fn renderValue( dg: *DeclGen, - writer: Writer, + writer: anytype, t: Type, val: Value, ) error{ OutOfMemory, AnalysisFail }!void { @@ -204,7 +201,7 @@ pub const DeclGen = struct { } } - 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 "); } @@ -228,7 +225,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"); @@ -325,20 +322,19 @@ pub fn genDecl(o: *Object) !void { try fwd_decl_writer.writeAll(";\n"); const func: *Module.Fn = func_payload.data; - const writer = o.code.writer(); - try writer.writeAll("\n"); - try o.dg.renderFunctionSignature(writer, is_global); + try o.indent_writer.insertNewline(); + try o.dg.renderFunctionSignature(o.writer(), is_global); try genBody(o, func.body); - 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 @@ -374,15 +370,15 @@ pub fn genHeader(dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void { } pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!void { - const writer = o.code.writer(); + const writer = o.writer(); if (body.instructions.len == 0) { try writer.writeAll(" {}"); return; } - try writer.writeAll(" {"); + try writer.writeAll(" {\n"); + o.indent_writer.pushIndent(); - try writer.writeAll("\n"); for (body.instructions) |inst| { const result_value = switch (inst.tag) { .add => try genBinOp(o, inst.castTag(.add).?, " + "), @@ -416,14 +412,14 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi } } + 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); @@ -439,15 +435,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| { @@ -467,8 +461,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"); @@ -481,8 +474,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); @@ -497,8 +489,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 }; @@ -525,8 +516,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(" = "); @@ -552,8 +542,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)", .{}); @@ -596,8 +585,7 @@ fn genBlock(o: *Object, inst: *Inst.Block) !CValue { 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(" = ("); @@ -611,7 +599,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); @@ -625,22 +612,19 @@ 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.indent(); - try o.code.writer().writeAll("while (true)"); + try o.writer().writeAll("while (true)"); try genBody(o, inst.body); - try o.code.writer().writeAll("\n"); + try o.indent_writer.insertNewline(); return CValue.none; } @@ -648,13 +632,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); @@ -665,7 +648,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) |_| { diff --git a/src/link/C.zig b/src/link/C.zig index a60d0efd8e..765249cd7d 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -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(); -- cgit v1.2.3 From bdfe3aeab8310a64cc9c2f5fac194a609aa0f13d Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 26 Jan 2021 21:09:40 +0200 Subject: stage2 cbe: condbr and breaks --- src/codegen/c.zig | 56 +++++++++++++++++++++++++++++++++++++++++++++++------ test/stage2/cbe.zig | 18 +++++++++++++++++ 2 files changed, 68 insertions(+), 6 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 9bccde5ffd..bf6a5aac1f 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -41,6 +41,7 @@ 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 { @@ -255,8 +256,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) { @@ -325,6 +326,7 @@ pub fn genDecl(o: *Object) !void { try o.indent_writer.insertNewline(); try o.dg.renderFunctionSignature(o.writer(), is_global); + try o.writer().writeByte(' '); try genBody(o, func.body); try o.indent_writer.insertNewline(); @@ -372,11 +374,11 @@ 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(" {}"); + try writer.writeAll("{}"); return; } - try writer.writeAll(" {\n"); + try writer.writeAll("{\n"); o.indent_writer.pushIndent(); for (body.instructions) |inst| { @@ -404,6 +406,9 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi .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).?), + .brvoid => try genBrVoid(o, inst.castTag(.brvoid).?.block), else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}), }; switch (result_value) { @@ -579,7 +584,31 @@ 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; + // abuse codegen.msv to store the block's id + inst.codegen.mcv.a = block_id; + try genBody(o, inst.body); + try o.indent_writer.insertNewline(); + // label must be followed by an expression, add an empty one. + try o.writer().print("zig_block_{d}:;\n", .{block_id}); + + // blocks in C cannot result in values + // TODO we need some other way to pass the result of the block + return CValue.none; +} + +fn genBr(o: *Object, inst: *Inst.Br) !CValue { + if (inst.operand.ty.tag() != .void) { + return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement block return values", .{}); + } + + return genBrVoid(o, inst.block); +} + +fn genBrVoid(o: *Object, block: *Inst.Block) !CValue { + try o.writer().print("goto zig_block_{d};\n", .{block.codegen.mcv.a}); + return CValue.none; } fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue { @@ -622,12 +651,27 @@ fn genUnreach(o: *Object, inst: *Inst.NoOp) !CValue { } fn genLoop(o: *Object, inst: *Inst.Loop) !CValue { - try o.writer().writeAll("while (true)"); + 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 genAsm(o: *Object, as: *Inst.Assembly) !CValue { if (as.base.isUnused() and !as.is_volatile) return CValue.none; diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 6d4e2062bf..c953d6077e 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -133,6 +133,24 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ , ""); + + // Simple while loop + case.addCompareOutput( + \\export fn main() c_int { + \\ var a: c_int = 0; + \\ while (a < 5) : (a+=1) {} + \\ exit(a - 5); + \\} + \\ + \\fn exit(code: usize) noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (code) + \\ ); + \\ unreachable; + \\} + , ""); } { -- cgit v1.2.3 From 258f3ec5ecf8d2a165382d5837bed0dac2e0375b Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 27 Jan 2021 11:05:22 +0200 Subject: stage2 cbe: block results --- src/codegen/c.zig | 38 +++++++++++++++++++++++++++----------- test/stage2/cbe.zig | 21 +++++++++++++++++++++ 2 files changed, 48 insertions(+), 11 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index bf6a5aac1f..e33f812f0b 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -325,7 +325,7 @@ pub fn genDecl(o: *Object) !void { const func: *Module.Fn = func_payload.data; try o.indent_writer.insertNewline(); try o.dg.renderFunctionSignature(o.writer(), is_global); - + try o.writer().writeByte(' '); try genBody(o, func.body); @@ -586,28 +586,44 @@ fn genDbgStmt(o: *Object, inst: *Inst.NoOp) !CValue { fn genBlock(o: *Object, inst: *Inst.Block) !CValue { const block_id: usize = o.next_block_index; o.next_block_index += 1; - // abuse codegen.msv to store the block's id - inst.codegen.mcv.a = block_id; + 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 o.writer().print("zig_block_{d}:;\n", .{block_id}); - - // blocks in C cannot result in values - // TODO we need some other way to pass the result of the block - return CValue.none; + try writer.print("zig_block_{d}:;\n", .{block_id}); + return result; } fn genBr(o: *Object, inst: *Inst.Br) !CValue { - if (inst.operand.ty.tag() != .void) { - return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement block return values", .{}); + 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.mcv.a}); + try o.writer().print("goto zig_block_{d};\n", .{block.codegen.relocs.capacity}); return CValue.none; } diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index c953d6077e..8a264f5ca6 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -151,6 +151,27 @@ pub fn addCases(ctx: *TestContext) !void { \\ unreachable; \\} , ""); + + // 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; + \\ exit(a - 11); + \\} + \\ + \\fn exit(code: usize) noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (code) + \\ ); + \\ unreachable; + \\} + , ""); } { -- cgit v1.2.3 From 106520329e5adc6cf5ef83595da6c9d5dd3c4b35 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 27 Jan 2021 11:40:34 +0200 Subject: stage2 cbe: implement switchbr --- src/Module.zig | 4 +-- src/codegen/c.zig | 35 ++++++++++++++++++++++++ src/ir.zig | 4 +-- test/stage2/cbe.zig | 78 ++++++++++++++++++++++++++--------------------------- 4 files changed, 78 insertions(+), 43 deletions(-) (limited to 'src/codegen') diff --git a/src/Module.zig b/src/Module.zig index b495afb336..46c3d513f1 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -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, }; diff --git a/src/codegen/c.zig b/src/codegen/c.zig index e33f812f0b..7fcbe44205 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -129,6 +129,9 @@ pub const DeclGen = struct { 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()) @@ -196,6 +199,7 @@ 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), }), @@ -409,6 +413,10 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi .condbr => try genCondBr(o, inst.castTag(.condbr).?), .br => try genBr(o, inst.castTag(.br).?), .brvoid => try genBrVoid(o, inst.castTag(.brvoid).?.block), + .switchbr => try genSwitchBr(o, inst.castTag(.switchbr).?), + // booland and boolor are non-short-circuit operations + .booland => try genBinOp(o, inst.castTag(.booland).?, " & "), + .boolor => try genBinOp(o, inst.castTag(.boolor).?, " | "), else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}), }; switch (result_value) { @@ -688,6 +696,33 @@ fn genCondBr(o: *Object, inst: *Inst.CondBr) !CValue { 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; +} + fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { if (as.base.isUnused() and !as.is_volatile) return CValue.none; diff --git a/src/ir.zig b/src/ir.zig index 408efc3bba..0e83dbfd56 100644 --- a/src/ir.zig +++ b/src/ir.zig @@ -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; diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 8a264f5ca6..aacb2b7077 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -133,45 +133,6 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ , ""); - - // Simple while loop - case.addCompareOutput( - \\export fn main() c_int { - \\ var a: c_int = 0; - \\ while (a < 5) : (a+=1) {} - \\ exit(a - 5); - \\} - \\ - \\fn exit(code: usize) noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (code) - \\ ); - \\ unreachable; - \\} - , ""); - - // 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; - \\ exit(a - 11); - \\} - \\ - \\fn exit(code: usize) noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (code) - \\ ); - \\ unreachable; - \\} - , ""); } { @@ -224,6 +185,45 @@ 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; + \\} + , ""); + + // 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; -- cgit v1.2.3 From 3ec5c9a3bcae09c01cbe4f0505e6ab03834bbb98 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 27 Jan 2021 12:22:38 +0200 Subject: stage2 cbe: implement not and some bitwise ops --- src/codegen/c.zig | 24 ++++++++++++++++++++++-- test/stage2/cbe.zig | 7 +++++++ 2 files changed, 29 insertions(+), 2 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 7fcbe44205..39fa80ea3d 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -293,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), }), @@ -387,6 +388,7 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi 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), @@ -415,8 +417,10 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi .brvoid => try genBrVoid(o, inst.castTag(.brvoid).?.block), .switchbr => try genSwitchBr(o, inst.castTag(.switchbr).?), // booland and boolor are non-short-circuit operations - .booland => try genBinOp(o, inst.castTag(.booland).?, " & "), - .boolor => try genBinOp(o, inst.castTag(.boolor).?, " | "), + .booland, .bitand => try genBinOp(o, inst.castTag(.booland).?, " & "), + .boolor, .bitor => try genBinOp(o, inst.castTag(.boolor).?, " | "), + .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) { @@ -541,6 +545,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| diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index aacb2b7077..0eb2cf68b4 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -196,6 +196,13 @@ pub fn addCases(ctx: *TestContext) !void { \\ return a - 5; \\} , ""); + case.addCompareOutput( + \\export fn main() c_int { + \\ var a = true; + \\ while (!a) {} + \\ return 0; + \\} + , ""); // If expression case.addCompareOutput( -- cgit v1.2.3 From 75acfcf0eaa306b3a8872e50cb735e1d5eb18c52 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 1 Feb 2021 15:45:11 +0200 Subject: stage2: reimplement switch --- src/astgen.zig | 313 +++++++++++++++++++++++++++++++++++++++++++++++++++++- src/codegen/c.zig | 10 +- src/zir.zig | 60 +++++++++++ src/zir_sema.zig | 228 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 606 insertions(+), 5 deletions(-) (limited to 'src/codegen') diff --git a/src/astgen.zig b/src/astgen.zig index dfc5f06ddc..ece16d70da 100644 --- a/src/astgen.zig +++ b/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; diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 39fa80ea3d..cb3271a57f 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -414,11 +414,13 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi .loop => try genLoop(o, inst.castTag(.loop).?), .condbr => try genCondBr(o, inst.castTag(.condbr).?), .br => try genBr(o, inst.castTag(.br).?), - .brvoid => try genBrVoid(o, inst.castTag(.brvoid).?.block), + .br_void => try genBrVoid(o, inst.castTag(.br_void).?.block), .switchbr => try genSwitchBr(o, inst.castTag(.switchbr).?), - // booland and boolor are non-short-circuit operations - .booland, .bitand => try genBinOp(o, inst.castTag(.booland).?, " & "), - .boolor, .bitor => try genBinOp(o, inst.castTag(.boolor).?, " | "), + // 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}), diff --git a/src/zir.zig b/src/zir.zig index 2559fcdc8e..30bfeead9b 100644 --- a/src/zir.zig +++ b/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)), } } diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 301b95ad97..f373d7174d 100644 --- a/src/zir_sema.zig +++ b/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(); -- cgit v1.2.3