diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 1ed655a055..de9e1f2386 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1358,17 +1358,19 @@ fn astGenInfixOp(self: *Module, scope: *Scope, infix_node: *ast.Node.InfixOp) In const tree = scope.tree(); const src = tree.token_locs[infix_node.op_token].start; + const op: std.math.CompareOperator = switch (infix_node.op) { + .BangEqual => .neq, + .EqualEqual => .eq, + .GreaterThan => .gt, + .GreaterOrEqual => .gte, + .LessThan => .lt, + .LessOrEqual => .lte, + else => unreachable, + }; + return self.addZIRInst(scope, src, zir.Inst.Cmp, .{ .lhs = lhs, - .op = @as(std.math.CompareOperator, switch (infix_node.op) { - .BangEqual => .neq, - .EqualEqual => .eq, - .GreaterThan => .gt, - .GreaterOrEqual => .gte, - .LessThan => .lt, - .LessOrEqual => .lte, - else => unreachable, - }), + .op = op, .rhs = rhs, }, .{}); }, @@ -1415,11 +1417,13 @@ fn astGenIf(self: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir defer then_scope.instructions.deinit(self.gpa); const then_result = try self.astGenExpr(&then_scope.base, if_node.body); - const then_src = tree.token_locs[if_node.body.lastToken()].start; - _ = try self.addZIRInst(&then_scope.base, then_src, zir.Inst.Break, .{ - .block = block, - .operand = then_result, - }, .{}); + if (!then_result.tag.isNoReturn()) { + const then_src = tree.token_locs[if_node.body.lastToken()].start; + _ = try self.addZIRInst(&then_scope.base, then_src, zir.Inst.Break, .{ + .block = block, + .operand = then_result, + }, .{}); + } condbr.positionals.true_body = .{ .instructions = try then_scope.arena.dupe(*zir.Inst, then_scope.instructions.items), }; @@ -1433,11 +1437,13 @@ fn astGenIf(self: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir if (if_node.@"else") |else_node| { const else_result = try self.astGenExpr(&else_scope.base, else_node.body); - const else_src = tree.token_locs[else_node.body.lastToken()].start; - _ = try self.addZIRInst(&else_scope.base, else_src, zir.Inst.Break, .{ - .block = block, - .operand = else_result, - }, .{}); + if (!else_result.tag.isNoReturn()) { + const else_src = tree.token_locs[else_node.body.lastToken()].start; + _ = try self.addZIRInst(&else_scope.base, else_src, zir.Inst.Break, .{ + .block = block, + .operand = else_result, + }, .{}); + } } else { // TODO Optimization opportunity: we can avoid an allocation and a memcpy here // by directly allocating the body for this one instruction. diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 5753559ce1..314d497808 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -415,7 +415,46 @@ const Function = struct { // No side effects, so if it's unreferenced, do nothing. if (inst.base.isUnused()) return MCValue.dead; + const operand = try self.resolveInst(inst.args.operand); + switch (operand) { + .dead => unreachable, + .unreach => unreachable, + .compare_flags_unsigned => |op| return MCValue{ + .compare_flags_unsigned = switch (op) { + .gte => .lt, + .gt => .lte, + .neq => .eq, + .lt => .gte, + .lte => .gt, + .eq => .neq, + }, + }, + .compare_flags_signed => |op| return MCValue{ + .compare_flags_signed = switch (op) { + .gte => .lt, + .gt => .lte, + .neq => .eq, + .lt => .gte, + .lte => .gt, + .eq => .neq, + }, + }, + else => {}, + } + switch (arch) { + .x86_64 => { + var imm = ir.Inst.Constant{ + .base = .{ + .tag = .constant, + .deaths = 0, + .ty = inst.args.operand.ty, + .src = inst.args.operand.src, + }, + .val = Value.initTag(.bool_true), + }; + return try self.genX8664BinMath(&inst.base, inst.args.operand, &imm.base, 6, 0x30); + }, else => return self.fail(inst.base.src, "TODO implement NOT for {}", .{self.target.cpu.arch}), } } @@ -444,7 +483,7 @@ const Function = struct { } } - /// ADD, SUB + /// ADD, SUB, XOR, OR, AND fn genX8664BinMath(self: *Function, inst: *ir.Inst, op_lhs: *ir.Inst, op_rhs: *ir.Inst, opx: u8, mr: u8) !MCValue { try self.code.ensureCapacity(self.code.items.len + 8); @@ -705,7 +744,7 @@ const Function = struct { fn genCondBr(self: *Function, inst: *ir.Inst.CondBr, comptime arch: std.Target.Cpu.Arch) !MCValue { switch (arch) { - .i386, .x86_64 => { + .x86_64 => { try self.code.ensureCapacity(self.code.items.len + 6); const cond = try self.resolveInst(inst.args.condition); @@ -734,7 +773,20 @@ const Function = struct { }; return self.genX86CondBr(inst, opcode, arch); }, - else => return self.fail(inst.base.src, "TODO implement condbr {} when condition not already in the compare flags", .{self.target.cpu.arch}), + .register => |reg_usize| { + const reg = @intToEnum(Reg(arch), @intCast(u8, reg_usize)); + // test reg, 1 + // TODO detect al, ax, eax + try self.code.ensureCapacity(self.code.items.len + 4); + self.rex(.{ .b = reg.isExtended(), .w = reg.size() == 64 }); + self.code.appendSliceAssumeCapacity(&[_]u8{ + 0xf6, + @as(u8, 0xC0) | (0 << 3) | @truncate(u3, reg.id()), + 0x01, + }); + return self.genX86CondBr(inst, 0x84, arch); + }, + else => return self.fail(inst.base.src, "TODO implement condbr {} when condition is {}", .{ self.target.cpu.arch, @tagName(cond) }), } }, else => return self.fail(inst.base.src, "TODO implement condbr for {}", .{self.target.cpu.arch}), @@ -892,7 +944,18 @@ const Function = struct { .none => unreachable, .unreach => unreachable, .compare_flags_unsigned => |op| { - return self.fail(src, "TODO set register with compare flags value (unsigned)", .{}); + try self.code.ensureCapacity(self.code.items.len + 3); + self.rex(.{ .b = reg.isExtended(), .w = reg.size() == 64 }); + const opcode: u8 = switch (op) { + .gte => 0x93, + .gt => 0x97, + .neq => 0x95, + .lt => 0x92, + .lte => 0x96, + .eq => 0x94, + }; + const id = @as(u8, reg.id() & 0b111); + self.code.appendSliceAssumeCapacity(&[_]u8{ 0x0f, opcode, 0xC0 | id }); }, .compare_flags_signed => |op| { return self.fail(src, "TODO set register with compare flags value (signed)", .{}); @@ -1147,6 +1210,9 @@ const Function = struct { } return MCValue{ .immediate = typed_value.val.toUnsignedInt() }; }, + .Bool => { + return MCValue{ .immediate = @boolToInt(typed_value.val.toBool()) }; + }, .ComptimeInt => unreachable, // semantic analysis prevents this .ComptimeFloat => unreachable, // semantic analysis prevents this else => return self.fail(src, "TODO implement const of type '{}'", .{typed_value.ty}), diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index 512dd631a7..1cc1b690f3 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -163,6 +163,22 @@ pub const Type = extern union { return sentinel_b == null; } }, + .Fn => { + if (!a.fnReturnType().eql(b.fnReturnType())) + return false; + if (a.fnCallingConvention() != b.fnCallingConvention()) + return false; + const a_param_len = a.fnParamLen(); + const b_param_len = b.fnParamLen(); + if (a_param_len != b_param_len) + return false; + var i: usize = 0; + while (i < a_param_len) : (i += 1) { + if (!a.fnParamType(i).eql(b.fnParamType(i))) + return false; + } + return true; + }, .Float, .Struct, .Optional, @@ -170,14 +186,13 @@ pub const Type = extern union { .ErrorSet, .Enum, .Union, - .Fn, .BoundFn, .Opaque, .Frame, .AnyFrame, .Vector, .EnumLiteral, - => @panic("TODO implement more Type equality comparison"), + => std.debug.panic("TODO implement Type equality comparison of {} and {}", .{ a, b }), } } diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index c1e9a38bd1..a3e1daa383 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -427,8 +427,6 @@ pub const Value = extern union { .fn_ccc_void_no_args_type, .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, - .bool_true, - .bool_false, .null_value, .function, .ref_val, @@ -441,8 +439,11 @@ pub const Value = extern union { .the_one_possible_value, // An integer with one possible value is always zero. .zero, + .bool_false, => return BigIntMutable.init(&space.limbs, 0).toConst(), + .bool_true => return BigIntMutable.init(&space.limbs, 1).toConst(), + .int_u64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_u64).?.int).toConst(), .int_i64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_i64).?.int).toConst(), .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt(), @@ -493,8 +494,6 @@ pub const Value = extern union { .fn_ccc_void_no_args_type, .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, - .bool_true, - .bool_false, .null_value, .function, .ref_val, @@ -507,8 +506,11 @@ pub const Value = extern union { .zero, .the_one_possible_value, // an integer with one possible value is always zero + .bool_false, => return 0, + .bool_true => return 1, + .int_u64 => return self.cast(Payload.Int_u64).?.int, .int_i64 => return @intCast(u64, self.cast(Payload.Int_u64).?.int), .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt().to(u64) catch unreachable, @@ -560,8 +562,6 @@ pub const Value = extern union { .fn_ccc_void_no_args_type, .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, - .bool_true, - .bool_false, .null_value, .function, .ref_val, @@ -574,8 +574,11 @@ pub const Value = extern union { .the_one_possible_value, // an integer with one possible value is always zero .zero, + .bool_false, => return 0, + .bool_true => return 1, + .int_u64 => { const x = self.cast(Payload.Int_u64).?.int; if (x == 0) return 0; @@ -632,8 +635,6 @@ pub const Value = extern union { .fn_ccc_void_no_args_type, .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, - .bool_true, - .bool_false, .null_value, .function, .ref_val, @@ -646,8 +647,18 @@ pub const Value = extern union { .zero, .undef, .the_one_possible_value, // an integer with one possible value is always zero + .bool_false, => return true, + .bool_true => { + const info = ty.intInfo(target); + if (info.signed) { + return info.bits >= 2; + } else { + return info.bits >= 1; + } + }, + .int_u64 => switch (ty.zigTypeTag()) { .Int => { const x = self.cast(Payload.Int_u64).?.int; @@ -796,8 +807,6 @@ pub const Value = extern union { .fn_ccc_void_no_args_type, .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, - .bool_true, - .bool_false, .null_value, .function, .ref_val, @@ -810,8 +819,11 @@ pub const Value = extern union { .zero, .the_one_possible_value, // an integer with one possible value is always zero + .bool_false, => return .eq, + .bool_true => return .gt, + .int_u64 => return std.math.order(lhs.cast(Payload.Int_u64).?.int, 0), .int_i64 => return std.math.order(lhs.cast(Payload.Int_i64).?.int, 0), .int_big_positive => return lhs.cast(Payload.IntBigPositive).?.asBigInt().orderAgainstScalar(0), @@ -855,7 +867,7 @@ pub const Value = extern union { pub fn toBool(self: Value) bool { return switch (self.tag()) { .bool_true => true, - .bool_false => false, + .bool_false, .zero => false, else => unreachable, }; } diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index 27c9f48d72..c4f85bfba4 100644 --- a/test/stage2/compare_output.zig +++ b/test/stage2/compare_output.zig @@ -170,4 +170,61 @@ pub fn addCases(ctx: *TestContext) !void { "", ); } + { + var case = ctx.exe("assert function", linux_x64); + case.addCompareOutput( + \\export fn _start() noreturn { + \\ add(3, 4); + \\ + \\ exit(); + \\} + \\ + \\fn add(a: u32, b: u32) void { + \\ assert(a + b == 7); + \\} + \\ + \\pub fn assert(ok: bool) void { + \\ if (!ok) unreachable; // assertion failure + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "", + ); + case.addCompareOutput( + \\export fn _start() noreturn { + \\ add(100, 200); + \\ + \\ exit(); + \\} + \\ + \\fn add(a: u32, b: u32) void { + \\ assert(a + b == 300); + \\} + \\ + \\pub fn assert(ok: bool) void { + \\ if (!ok) unreachable; // assertion failure + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "", + ); + } }