stage2: implement enough for assert() function to codegen

This commit is contained in:
Andrew Kelley
2020-07-14 02:24:12 -07:00
parent 135580c162
commit a92990f993
5 changed files with 192 additions and 36 deletions

View File

@@ -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.

View File

@@ -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}),

View File

@@ -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 }),
}
}

View File

@@ -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,
};
}

View File

@@ -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;
\\}
,
"",
);
}
}