Merge pull request #5905 from Vexu/stage2-float

Stage2: floats
master
Andrew Kelley 2020-07-21 22:35:05 +00:00 committed by GitHub
commit 25a01a16e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 461 additions and 92 deletions

View File

@ -13,6 +13,7 @@ const InstallDirectoryOptions = std.build.InstallDirectoryOptions;
pub fn build(b: *Builder) !void {
b.setPreferredReleaseMode(.ReleaseFast);
const mode = b.standardReleaseOptions();
const target = b.standardTargetOptions(.{});
var docgen_exe = b.addExecutable("docgen", "doc/docgen.zig");
@ -54,6 +55,7 @@ pub fn build(b: *Builder) !void {
if (!only_install_lib_files) {
var exe = b.addExecutable("zig", "src-self-hosted/main.zig");
exe.setBuildMode(mode);
exe.setTarget(target);
test_step.dependOn(&exe.step);
b.default_step.dependOn(&exe.step);

View File

@ -2352,6 +2352,7 @@ fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In
.fntype => return self.analyzeInstFnType(scope, old_inst.castTag(.fntype).?),
.intcast => return self.analyzeInstIntCast(scope, old_inst.castTag(.intcast).?),
.bitcast => return self.analyzeInstBitCast(scope, old_inst.castTag(.bitcast).?),
.floatcast => return self.analyzeInstFloatCast(scope, old_inst.castTag(.floatcast).?),
.elemptr => return self.analyzeInstElemPtr(scope, old_inst.castTag(.elemptr).?),
.add => return self.analyzeInstAdd(scope, old_inst.castTag(.add).?),
.sub => return self.analyzeInstSub(scope, old_inst.castTag(.sub).?),
@ -2796,16 +2797,16 @@ fn analyzeInstFieldPtr(self: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPt
}
}
fn analyzeInstIntCast(self: *Module, scope: *Scope, intcast: *zir.Inst.IntCast) InnerError!*Inst {
const dest_type = try self.resolveType(scope, intcast.positionals.dest_type);
const new_inst = try self.resolveInst(scope, intcast.positionals.value);
fn analyzeInstIntCast(self: *Module, scope: *Scope, inst: *zir.Inst.IntCast) InnerError!*Inst {
const dest_type = try self.resolveType(scope, inst.positionals.dest_type);
const operand = try self.resolveInst(scope, inst.positionals.operand);
const dest_is_comptime_int = switch (dest_type.zigTypeTag()) {
.ComptimeInt => true,
.Int => false,
else => return self.fail(
scope,
intcast.positionals.dest_type.src,
inst.positionals.dest_type.src,
"expected integer type, found '{}'",
.{
dest_type,
@ -2813,21 +2814,23 @@ fn analyzeInstIntCast(self: *Module, scope: *Scope, intcast: *zir.Inst.IntCast)
),
};
switch (new_inst.ty.zigTypeTag()) {
switch (operand.ty.zigTypeTag()) {
.ComptimeInt, .Int => {},
else => return self.fail(
scope,
intcast.positionals.value.src,
inst.positionals.operand.src,
"expected integer type, found '{}'",
.{new_inst.ty},
.{operand.ty},
),
}
if (dest_is_comptime_int or new_inst.value() != null) {
return self.coerce(scope, dest_type, new_inst);
if (operand.value() != null) {
return self.coerce(scope, dest_type, operand);
} else if (dest_is_comptime_int) {
return self.fail(scope, inst.base.src, "unable to cast runtime value to 'comptime_int'", .{});
}
return self.fail(scope, intcast.base.src, "TODO implement analyze widen or shorten int", .{});
return self.fail(scope, inst.base.src, "TODO implement analyze widen or shorten int", .{});
}
fn analyzeInstBitCast(self: *Module, scope: *Scope, inst: *zir.Inst.BitCast) InnerError!*Inst {
@ -2836,6 +2839,42 @@ fn analyzeInstBitCast(self: *Module, scope: *Scope, inst: *zir.Inst.BitCast) Inn
return self.bitcast(scope, dest_type, operand);
}
fn analyzeInstFloatCast(self: *Module, scope: *Scope, inst: *zir.Inst.FloatCast) InnerError!*Inst {
const dest_type = try self.resolveType(scope, inst.positionals.dest_type);
const operand = try self.resolveInst(scope, inst.positionals.operand);
const dest_is_comptime_float = switch (dest_type.zigTypeTag()) {
.ComptimeFloat => true,
.Float => false,
else => return self.fail(
scope,
inst.positionals.dest_type.src,
"expected float type, found '{}'",
.{
dest_type,
},
),
};
switch (operand.ty.zigTypeTag()) {
.ComptimeFloat, .Float, .ComptimeInt => {},
else => return self.fail(
scope,
inst.positionals.operand.src,
"expected float type, found '{}'",
.{operand.ty},
),
}
if (operand.value() != null) {
return self.coerce(scope, dest_type, operand);
} else if (dest_is_comptime_float) {
return self.fail(scope, inst.base.src, "unable to cast runtime value to 'comptime_float'", .{});
}
return self.fail(scope, inst.base.src, "TODO implement analyze widen or shorten float", .{});
}
fn analyzeInstElemPtr(self: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) InnerError!*Inst {
const array_ptr = try self.resolveInst(scope, inst.positionals.array_ptr);
const uncasted_index = try self.resolveInst(scope, inst.positionals.index);
@ -3358,6 +3397,14 @@ fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
return self.bitcast(scope, dest_type, inst);
}
// undefined to anything
if (inst.value()) |val| {
if (val.isUndef() or inst.ty.zigTypeTag() == .Undefined) {
return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
}
}
assert(inst.ty.zigTypeTag() != .Undefined);
// *[N]T to []T
if (inst.ty.isSinglePointer() and dest_type.isSlice() and
(!inst.ty.pointerIsConst() or dest_type.pointerIsConst()))
@ -3371,31 +3418,65 @@ fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
}
}
// comptime_int to fixed-width integer
if (inst.ty.zigTypeTag() == .ComptimeInt and dest_type.zigTypeTag() == .Int) {
// The representation is already correct; we only need to make sure it fits in the destination type.
const val = inst.value().?; // comptime_int always has comptime known value
if (!val.intFitsInType(dest_type, self.target())) {
return self.fail(scope, inst.src, "type {} cannot represent integer value {}", .{ inst.ty, val });
// comptime known number to other number
if (inst.value()) |val| {
const src_zig_tag = inst.ty.zigTypeTag();
const dst_zig_tag = dest_type.zigTypeTag();
if (dst_zig_tag == .ComptimeInt or dst_zig_tag == .Int) {
if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) {
if (val.floatHasFraction()) {
return self.fail(scope, inst.src, "fractional component prevents float value {} from being casted to type '{}'", .{ val, inst.ty });
}
return self.fail(scope, inst.src, "TODO float to int", .{});
} else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) {
if (!val.intFitsInType(dest_type, self.target())) {
return self.fail(scope, inst.src, "type {} cannot represent integer value {}", .{ inst.ty, val });
}
return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
}
} else if (dst_zig_tag == .ComptimeFloat or dst_zig_tag == .Float) {
if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) {
const res = val.floatCast(scope.arena(), dest_type, self.target()) catch |err| switch (err) {
error.Overflow => return self.fail(
scope,
inst.src,
"cast of value {} to type '{}' loses information",
.{ val, dest_type },
),
error.OutOfMemory => return error.OutOfMemory,
};
return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = res });
} else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) {
return self.fail(scope, inst.src, "TODO int to float", .{});
}
}
return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
}
// integer widening
if (inst.ty.zigTypeTag() == .Int and dest_type.zigTypeTag() == .Int) {
assert(inst.value() == null); // handled above
const src_info = inst.ty.intInfo(self.target());
const dst_info = dest_type.intInfo(self.target());
if (src_info.signed == dst_info.signed and dst_info.bits >= src_info.bits) {
if (inst.value()) |val| {
return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
} else {
return self.fail(scope, inst.src, "TODO implement runtime integer widening ({} to {})", .{
inst.ty,
dest_type,
});
}
} else {
return self.fail(scope, inst.src, "TODO implement more int widening {} to {}", .{ inst.ty, dest_type });
if ((src_info.signed == dst_info.signed and dst_info.bits >= src_info.bits) or
// small enough unsigned ints can get casted to large enough signed ints
(src_info.signed and !dst_info.signed and dst_info.bits > src_info.bits))
{
const b = try self.requireRuntimeBlock(scope, inst.src);
return self.addUnOp(b, inst.src, dest_type, .intcast, inst);
}
}
// float widening
if (inst.ty.zigTypeTag() == .Float and dest_type.zigTypeTag() == .Float) {
assert(inst.value() == null); // handled above
const src_bits = inst.ty.floatBits(self.target());
const dst_bits = dest_type.floatBits(self.target());
if (dst_bits >= src_bits) {
const b = try self.requireRuntimeBlock(scope, inst.src);
return self.addUnOp(b, inst.src, dest_type, .floatcast, inst);
}
}

View File

@ -38,6 +38,8 @@ pub fn expr(mod: *Module, scope: *Scope, node: *ast.Node) InnerError!*zir.Inst {
.Period => return field(mod, scope, node.castTag(.Period).?),
.Deref => return deref(mod, scope, node.castTag(.Deref).?),
.BoolNot => return boolNot(mod, scope, node.castTag(.BoolNot).?),
.FloatLiteral => return floatLiteral(mod, scope, node.castTag(.FloatLiteral).?),
.UndefinedLiteral, .BoolLiteral, .NullLiteral => return primitiveLiteral(mod, scope, node),
else => return mod.failNode(scope, node, "TODO implement astgen.Expr for {}", .{@tagName(node.tag)}),
}
}
@ -323,7 +325,14 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.Identifier) InnerErr
16 => if (is_signed) Value.initTag(.i16_type) else Value.initTag(.u16_type),
32 => if (is_signed) Value.initTag(.i32_type) else Value.initTag(.u32_type),
64 => if (is_signed) Value.initTag(.i64_type) else Value.initTag(.u64_type),
else => return mod.failNode(scope, &ident.base, "TODO implement arbitrary integer bitwidth types", .{}),
else => {
const int_type_payload = try scope.arena().create(Value.Payload.IntType);
int_type_payload.* = .{ .signed = is_signed, .bits = bit_count };
return mod.addZIRInstConst(scope, src, .{
.ty = Type.initTag(.comptime_int),
.val = Value.initPayload(&int_type_payload.base),
});
},
};
return mod.addZIRInstConst(scope, src, .{
.ty = Type.initTag(.type),
@ -405,6 +414,52 @@ fn integerLiteral(mod: *Module, scope: *Scope, int_lit: *ast.Node.IntegerLiteral
}
}
fn floatLiteral(mod: *Module, scope: *Scope, float_lit: *ast.Node.FloatLiteral) InnerError!*zir.Inst {
const arena = scope.arena();
const tree = scope.tree();
const bytes = tree.tokenSlice(float_lit.token);
if (bytes.len > 2 and bytes[1] == 'x') {
return mod.failTok(scope, float_lit.token, "TODO hex floats", .{});
}
const val = std.fmt.parseFloat(f128, bytes) catch |e| switch (e) {
error.InvalidCharacter => unreachable, // validated by tokenizer
};
const float_payload = try arena.create(Value.Payload.Float_128);
float_payload.* = .{ .val = val };
const src = tree.token_locs[float_lit.token].start;
return mod.addZIRInstConst(scope, src, .{
.ty = Type.initTag(.comptime_float),
.val = Value.initPayload(&float_payload.base),
});
}
fn primitiveLiteral(mod: *Module, scope: *Scope, node: *ast.Node) InnerError!*zir.Inst {
const arena = scope.arena();
const tree = scope.tree();
const src = tree.token_locs[node.firstToken()].start;
if (node.cast(ast.Node.BoolLiteral)) |bool_node| {
return mod.addZIRInstConst(scope, src, .{
.ty = Type.initTag(.bool),
.val = if (tree.token_ids[bool_node.token] == .Keyword_true)
Value.initTag(.bool_true)
else
Value.initTag(.bool_false),
});
} else if (node.tag == .UndefinedLiteral) {
return mod.addZIRInstConst(scope, src, .{
.ty = Type.initTag(.@"undefined"),
.val = Value.initTag(.undef),
});
} else if (node.tag == .NullLiteral) {
return mod.addZIRInstConst(scope, src, .{
.ty = Type.initTag(.@"null"),
.val = Value.initTag(.null_value),
});
} else unreachable;
}
fn assembly(mod: *Module, scope: *Scope, asm_node: *ast.Node.Asm) InnerError!*zir.Inst {
if (asm_node.outputs.len != 0) {
return mod.failNode(scope, &asm_node.base, "TODO implement asm with an output", .{});
@ -534,30 +589,6 @@ fn getSimplePrimitiveValue(name: []const u8) ?TypedValue {
.val = Value.initTag(tag),
};
}
if (mem.eql(u8, name, "null")) {
return TypedValue{
.ty = Type.initTag(.@"null"),
.val = Value.initTag(.null_value),
};
}
if (mem.eql(u8, name, "undefined")) {
return TypedValue{
.ty = Type.initTag(.@"undefined"),
.val = Value.initTag(.undef),
};
}
if (mem.eql(u8, name, "true")) {
return TypedValue{
.ty = Type.initTag(.bool),
.val = Value.initTag(.bool_true),
};
}
if (mem.eql(u8, name, "false")) {
return TypedValue{
.ty = Type.initTag(.bool),
.val = Value.initTag(.bool_false),
};
}
return null;
}

View File

@ -459,6 +459,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.sub => return self.genSub(inst.castTag(.sub).?),
.unreach => return MCValue{ .unreach = {} },
.not => return self.genNot(inst.castTag(.not).?),
.floatcast => return self.genFloatCast(inst.castTag(.floatcast).?),
.intcast => return self.genIntCast(inst.castTag(.intcast).?),
}
}
fn genFloatCast(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
// No side effects, so if it's unreferenced, do nothing.
if (inst.base.isUnused())
return MCValue.dead;
switch (arch) {
else => return self.fail(inst.base.src, "TODO implement floatCast for {}", .{self.target.cpu.arch}),
}
}
fn genIntCast(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
// No side effects, so if it's unreferenced, do nothing.
if (inst.base.isUnused())
return MCValue.dead;
switch (arch) {
else => return self.fail(inst.base.src, "TODO implement intCast for {}", .{self.target.cpu.arch}),
}
}

View File

@ -71,6 +71,8 @@ pub const Inst = struct {
sub,
unreach,
not,
floatcast,
intcast,
/// There is one-to-one correspondence between tag and type for now,
/// but this will not always be the case. For example, binary operations
@ -89,6 +91,8 @@ pub const Inst = struct {
.isnonnull,
.isnull,
.ptrtoint,
.floatcast,
.intcast,
=> UnOp,
.add,

View File

@ -70,6 +70,7 @@ pub const Value = extern union {
// After this, the tag requires a payload.
ty,
int_type,
int_u64,
int_i64,
int_big_positive,
@ -80,6 +81,10 @@ pub const Value = extern union {
elem_ptr,
bytes,
repeated, // the value is a value repeated some number of times
float_16,
float_32,
float_64,
float_128,
pub const last_no_payload_tag = Tag.bool_false;
pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
@ -174,6 +179,7 @@ pub const Value = extern union {
};
return Value{ .ptr_otherwise = &new_payload.base };
},
.int_type => return self.copyPayloadShallow(allocator, Payload.IntType),
.int_u64 => return self.copyPayloadShallow(allocator, Payload.Int_u64),
.int_i64 => return self.copyPayloadShallow(allocator, Payload.Int_i64),
.int_big_positive => {
@ -213,6 +219,10 @@ pub const Value = extern union {
};
return Value{ .ptr_otherwise = &new_payload.base };
},
.float_16 => return self.copyPayloadShallow(allocator, Payload.Float_16),
.float_32 => return self.copyPayloadShallow(allocator, Payload.Float_32),
.float_64 => return self.copyPayloadShallow(allocator, Payload.Float_64),
.float_128 => return self.copyPayloadShallow(allocator, Payload.Float_128),
}
}
@ -279,6 +289,13 @@ pub const Value = extern union {
.bool_true => return out_stream.writeAll("true"),
.bool_false => return out_stream.writeAll("false"),
.ty => return val.cast(Payload.Ty).?.ty.format("", options, out_stream),
.int_type => {
const int_type = val.cast(Payload.IntType).?;
return out_stream.print("{}{}", .{
if (int_type.signed) "s" else "u",
int_type.bits,
});
},
.int_u64 => return std.fmt.formatIntValue(val.cast(Payload.Int_u64).?.int, "", options, out_stream),
.int_i64 => return std.fmt.formatIntValue(val.cast(Payload.Int_i64).?.int, "", options, out_stream),
.int_big_positive => return out_stream.print("{}", .{val.cast(Payload.IntBigPositive).?.asBigInt()}),
@ -300,6 +317,10 @@ pub const Value = extern union {
try out_stream.writeAll("(repeated) ");
val = val.cast(Payload.Repeated).?.val;
},
.float_16 => return out_stream.print("{}", .{val.cast(Payload.Float_16).?.val}),
.float_32 => return out_stream.print("{}", .{val.cast(Payload.Float_32).?.val}),
.float_64 => return out_stream.print("{}", .{val.cast(Payload.Float_64).?.val}),
.float_128 => return out_stream.print("{}", .{val.cast(Payload.Float_128).?.val}),
};
}
@ -323,6 +344,7 @@ pub const Value = extern union {
pub fn toType(self: Value) Type {
return switch (self.tag()) {
.ty => self.cast(Payload.Ty).?.ty,
.int_type => @panic("TODO int type to type"),
.u8_type => Type.initTag(.u8),
.i8_type => Type.initTag(.i8),
@ -380,6 +402,10 @@ pub const Value = extern union {
.elem_ptr,
.bytes,
.repeated,
.float_16,
.float_32,
.float_64,
.float_128,
=> unreachable,
};
}
@ -388,6 +414,7 @@ pub const Value = extern union {
pub fn toBigInt(self: Value, space: *BigIntSpace) BigIntConst {
switch (self.tag()) {
.ty,
.int_type,
.u8_type,
.i8_type,
.u16_type,
@ -435,6 +462,10 @@ pub const Value = extern union {
.bytes,
.undef,
.repeated,
.float_16,
.float_32,
.float_64,
.float_128,
=> unreachable,
.the_one_possible_value, // An integer with one possible value is always zero.
@ -455,6 +486,7 @@ pub const Value = extern union {
pub fn toUnsignedInt(self: Value) u64 {
switch (self.tag()) {
.ty,
.int_type,
.u8_type,
.i8_type,
.u16_type,
@ -502,6 +534,10 @@ pub const Value = extern union {
.bytes,
.undef,
.repeated,
.float_16,
.float_32,
.float_64,
.float_128,
=> unreachable,
.zero,
@ -518,11 +554,38 @@ pub const Value = extern union {
}
}
pub fn toBool(self: Value) bool {
return switch (self.tag()) {
.bool_true => true,
.bool_false, .zero => false,
else => unreachable,
};
}
/// Asserts that the value is a float or an integer.
pub fn toFloat(self: Value, comptime T: type) T {
return switch (self.tag()) {
.float_16 => @panic("TODO soft float"),
.float_32 => @floatCast(T, self.cast(Payload.Float_32).?.val),
.float_64 => @floatCast(T, self.cast(Payload.Float_64).?.val),
.float_128 => @floatCast(T, self.cast(Payload.Float_128).?.val),
.zero, .the_one_possible_value => 0,
.int_u64 => @intToFloat(T, self.cast(Payload.Int_u64).?.int),
// .int_i64 => @intToFloat(f128, self.cast(Payload.Int_i64).?.int),
.int_i64 => @panic("TODO lld: error: undefined symbol: __floatditf"),
.int_big_positive, .int_big_negative => @panic("big int to f128"),
else => unreachable,
};
}
/// Asserts the value is an integer and not undefined.
/// Returns the number of bits the value requires to represent stored in twos complement form.
pub fn intBitCountTwosComp(self: Value) usize {
switch (self.tag()) {
.ty,
.int_type,
.u8_type,
.i8_type,
.u16_type,
@ -570,6 +633,10 @@ pub const Value = extern union {
.bytes,
.undef,
.repeated,
.float_16,
.float_32,
.float_64,
.float_128,
=> unreachable,
.the_one_possible_value, // an integer with one possible value is always zero
@ -596,6 +663,7 @@ pub const Value = extern union {
pub fn intFitsInType(self: Value, ty: Type, target: Target) bool {
switch (self.tag()) {
.ty,
.int_type,
.u8_type,
.i8_type,
.u16_type,
@ -642,6 +710,10 @@ pub const Value = extern union {
.elem_ptr,
.bytes,
.repeated,
.float_16,
.float_32,
.float_64,
.float_128,
=> unreachable,
.zero,
@ -701,10 +773,55 @@ pub const Value = extern union {
}
}
/// Converts an integer or a float to a float.
/// Returns `error.Overflow` if the value does not fit in the new type.
pub fn floatCast(self: Value, allocator: *Allocator, ty: Type, target: Target) !Value {
const dest_bit_count = switch (ty.tag()) {
.comptime_float => 128,
else => ty.floatBits(target),
};
switch (dest_bit_count) {
16, 32, 64, 128 => {},
else => std.debug.panic("TODO float cast bit count {}\n", .{dest_bit_count}),
}
if (ty.isInt()) {
@panic("TODO int to float");
}
switch (dest_bit_count) {
16 => {
@panic("TODO soft float");
// var res_payload = Value.Payload.Float_16{.val = self.toFloat(f16)};
// if (!self.eql(Value.initPayload(&res_payload.base)))
// return error.Overflow;
// return Value.initPayload(&res_payload.base).copy(allocator);
},
32 => {
var res_payload = Value.Payload.Float_32{.val = self.toFloat(f32)};
if (!self.eql(Value.initPayload(&res_payload.base)))
return error.Overflow;
return Value.initPayload(&res_payload.base).copy(allocator);
},
64 => {
var res_payload = Value.Payload.Float_64{.val = self.toFloat(f64)};
if (!self.eql(Value.initPayload(&res_payload.base)))
return error.Overflow;
return Value.initPayload(&res_payload.base).copy(allocator);
},
128 => {
const float_payload = try allocator.create(Value.Payload.Float_128);
float_payload.* = .{ .val = self.toFloat(f128) };
return Value.initPayload(&float_payload.base);
},
else => unreachable,
}
}
/// Asserts the value is a float
pub fn floatHasFraction(self: Value) bool {
return switch (self.tag()) {
.ty,
.int_type,
.u8_type,
.i8_type,
.u16_type,
@ -762,12 +879,19 @@ pub const Value = extern union {
=> unreachable,
.zero => false,
.float_16 => @rem(self.cast(Payload.Float_16).?.val, 1) != 0,
.float_32 => @rem(self.cast(Payload.Float_32).?.val, 1) != 0,
.float_64 => @rem(self.cast(Payload.Float_64).?.val, 1) != 0,
// .float_128 => @rem(self.cast(Payload.Float_128).?.val, 1) != 0,
.float_128 => @panic("TODO lld: error: undefined symbol: fmodl"),
};
}
pub fn orderAgainstZero(lhs: Value) std.math.Order {
switch (lhs.tag()) {
return switch (lhs.tag()) {
.ty,
.int_type,
.u8_type,
.i8_type,
.u16_type,
@ -820,27 +944,49 @@ pub const Value = extern union {
.zero,
.the_one_possible_value, // an integer with one possible value is always zero
.bool_false,
=> return .eq,
=> .eq,
.bool_true => return .gt,
.bool_true => .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),
.int_big_negative => return lhs.cast(Payload.IntBigNegative).?.asBigInt().orderAgainstScalar(0),
}
.int_u64 => std.math.order(lhs.cast(Payload.Int_u64).?.int, 0),
.int_i64 => std.math.order(lhs.cast(Payload.Int_i64).?.int, 0),
.int_big_positive => lhs.cast(Payload.IntBigPositive).?.asBigInt().orderAgainstScalar(0),
.int_big_negative => lhs.cast(Payload.IntBigNegative).?.asBigInt().orderAgainstScalar(0),
.float_16 => std.math.order(lhs.cast(Payload.Float_16).?.val, 0),
.float_32 => std.math.order(lhs.cast(Payload.Float_32).?.val, 0),
.float_64 => std.math.order(lhs.cast(Payload.Float_64).?.val, 0),
.float_128 => std.math.order(lhs.cast(Payload.Float_128).?.val, 0),
};
}
/// Asserts the value is comparable.
pub fn order(lhs: Value, rhs: Value) std.math.Order {
const lhs_tag = lhs.tag();
const rhs_tag = lhs.tag();
const rhs_tag = rhs.tag();
const lhs_is_zero = lhs_tag == .zero or lhs_tag == .the_one_possible_value;
const rhs_is_zero = rhs_tag == .zero or rhs_tag == .the_one_possible_value;
if (lhs_is_zero) return rhs.orderAgainstZero().invert();
if (rhs_is_zero) return lhs.orderAgainstZero();
// TODO floats
const lhs_float = lhs.isFloat();
const rhs_float = rhs.isFloat();
if (lhs_float and rhs_float) {
if (lhs_tag == rhs_tag) {
return switch (lhs.tag()) {
.float_16 => return std.math.order(lhs.cast(Payload.Float_16).?.val, rhs.cast(Payload.Float_16).?.val),
.float_32 => return std.math.order(lhs.cast(Payload.Float_32).?.val, rhs.cast(Payload.Float_32).?.val),
.float_64 => return std.math.order(lhs.cast(Payload.Float_64).?.val, rhs.cast(Payload.Float_64).?.val),
.float_128 => return std.math.order(lhs.cast(Payload.Float_128).?.val, rhs.cast(Payload.Float_128).?.val),
else => unreachable,
};
}
}
if (lhs_float or rhs_float) {
const lhs_f128 = lhs.toFloat(f128);
const rhs_f128 = rhs.toFloat(f128);
return std.math.order(lhs_f128, rhs_f128);
}
var lhs_bigint_space: BigIntSpace = undefined;
var rhs_bigint_space: BigIntSpace = undefined;
@ -864,19 +1010,12 @@ pub const Value = extern union {
return compare(a, .eq, b);
}
pub fn toBool(self: Value) bool {
return switch (self.tag()) {
.bool_true => true,
.bool_false, .zero => false,
else => unreachable,
};
}
/// Asserts the value is a pointer and dereferences it.
/// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis.
pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value {
return switch (self.tag()) {
.ty,
.int_type,
.u8_type,
.i8_type,
.u16_type,
@ -928,6 +1067,10 @@ pub const Value = extern union {
.bytes,
.undef,
.repeated,
.float_16,
.float_32,
.float_64,
.float_128,
=> unreachable,
.the_one_possible_value => Value.initTag(.the_one_possible_value),
@ -946,6 +1089,7 @@ pub const Value = extern union {
pub fn elemValue(self: Value, allocator: *Allocator, index: usize) error{OutOfMemory}!Value {
switch (self.tag()) {
.ty,
.int_type,
.u8_type,
.i8_type,
.u16_type,
@ -999,6 +1143,10 @@ pub const Value = extern union {
.elem_ptr,
.ref_val,
.decl_ref,
.float_16,
.float_32,
.float_64,
.float_128,
=> unreachable,
.bytes => {
@ -1032,6 +1180,7 @@ pub const Value = extern union {
pub fn isNull(self: Value) bool {
return switch (self.tag()) {
.ty,
.int_type,
.u8_type,
.i8_type,
.u16_type,
@ -1085,6 +1234,10 @@ pub const Value = extern union {
.elem_ptr,
.bytes,
.repeated,
.float_16,
.float_32,
.float_64,
.float_128,
=> false,
.undef => unreachable,
@ -1092,6 +1245,20 @@ pub const Value = extern union {
};
}
/// Valid for all types. Asserts the value is not undefined.
pub fn isFloat(self: Value) bool {
return switch (self.tag()) {
.undef => unreachable,
.float_16,
.float_32,
.float_64,
.float_128,
=> true,
else => false,
};
}
/// This type is not copyable since it may contain pointers to its inner data.
pub const Payload = struct {
tag: Tag,
@ -1162,12 +1329,38 @@ pub const Value = extern union {
ty: Type,
};
pub const IntType = struct {
base: Payload = Payload{ .tag = .int_type },
bits: u16,
signed: bool,
};
pub const Repeated = struct {
base: Payload = Payload{ .tag = .ty },
/// This value is repeated some number of times. The amount of times to repeat
/// is stored externally.
val: Value,
};
pub const Float_16 = struct {
base: Payload = .{ .tag = .float_16 },
val: f16,
};
pub const Float_32 = struct {
base: Payload = .{ .tag = .float_32 },
val: f32,
};
pub const Float_64 = struct {
base: Payload = .{ .tag = .float_64 },
val: f64,
};
pub const Float_128 = struct {
base: Payload = .{ .tag = .float_128 },
val: f128,
};
};
/// Big enough to fit any non-BigInt value

View File

@ -76,6 +76,7 @@ pub const Inst = struct {
primitive,
intcast,
bitcast,
floatcast,
elemptr,
add,
sub,
@ -137,6 +138,7 @@ pub const Inst = struct {
.fntype => FnType,
.intcast => IntCast,
.bitcast => BitCast,
.floatcast => FloatCast,
.elemptr => ElemPtr,
.condbr => CondBr,
};
@ -169,6 +171,7 @@ pub const Inst = struct {
.primitive,
.intcast,
.bitcast,
.floatcast,
.elemptr,
.add,
.sub,
@ -556,19 +559,33 @@ pub const Inst = struct {
};
};
pub const IntCast = struct {
pub const base_tag = Tag.intcast;
pub const FloatCast = struct {
pub const base_tag = Tag.floatcast;
pub const builtin_name = "@floatCast";
base: Inst,
positionals: struct {
dest_type: *Inst,
value: *Inst,
operand: *Inst,
},
kw_args: struct {},
};
pub const IntCast = struct {
pub const base_tag = Tag.intcast;
pub const builtin_name = "@intCast";
base: Inst,
positionals: struct {
dest_type: *Inst,
operand: *Inst,
},
kw_args: struct {},
};
pub const BitCast = struct {
pub const base_tag = Tag.bitcast;
pub const builtin_name = "@bitCast";
base: Inst,
positionals: struct {
@ -1618,6 +1635,28 @@ const EmitZIR = struct {
return &new_inst.base;
}
fn emitCast(
self: *EmitZIR,
src: usize,
new_body: ZirBody,
old_inst: *ir.Inst.UnOp,
comptime I: type,
) Allocator.Error!*Inst {
const new_inst = try self.arena.allocator.create(I);
new_inst.* = .{
.base = .{
.src = src,
.tag = I.base_tag,
},
.positionals = .{
.dest_type = (try self.emitType(src, old_inst.base.ty)).inst,
.operand = try self.resolveInst(new_body, old_inst.operand),
},
.kw_args = .{},
};
return &new_inst.base;
}
fn emitBody(
self: *EmitZIR,
body: ir.Body,
@ -1652,22 +1691,9 @@ const EmitZIR = struct {
.cmp_gt => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_gt).?, .cmp_gt),
.cmp_neq => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_neq).?, .cmp_neq),
.bitcast => blk: {
const old_inst = inst.castTag(.bitcast).?;
const new_inst = try self.arena.allocator.create(Inst.BitCast);
new_inst.* = .{
.base = .{
.src = inst.src,
.tag = Inst.BitCast.base_tag,
},
.positionals = .{
.dest_type = (try self.emitType(inst.src, inst.ty)).inst,
.operand = try self.resolveInst(new_body, old_inst.operand),
},
.kw_args = .{},
};
break :blk &new_inst.base;
},
.bitcast => try self.emitCast(inst.src, new_body, inst.castTag(.bitcast).?, Inst.BitCast),
.intcast => try self.emitCast(inst.src, new_body, inst.castTag(.intcast).?, Inst.IntCast),
.floatcast => try self.emitCast(inst.src, new_body, inst.castTag(.floatcast).?, Inst.FloatCast),
.block => blk: {
const old_inst = inst.castTag(.block).?;

View File

@ -288,6 +288,7 @@ static IrInstGen *ir_analyze_struct_value_field_value(IrAnalyze *ira, IrInst* so
static bool value_cmp_numeric_val_any(ZigValue *left, Cmp predicate, ZigValue *right);
static bool value_cmp_numeric_val_all(ZigValue *left, Cmp predicate, ZigValue *right);
static void memoize_field_init_val(CodeGen *codegen, ZigType *container_type, TypeStructField *field);
static void value_to_bigfloat(BigFloat *out, ZigValue *val);
#define ir_assert(OK, SOURCE_INSTRUCTION) ir_assert_impl((OK), (SOURCE_INSTRUCTION), __FILE__, __LINE__)
#define ir_assert_gen(OK, SOURCE_INSTRUCTION) ir_assert_gen_impl((OK), (SOURCE_INSTRUCTION), __FILE__, __LINE__)
@ -10930,8 +10931,8 @@ static Cmp float_cmp(ZigValue *op1, ZigValue *op2) {
}
BigFloat op1_big;
BigFloat op2_big;
float_init_bigfloat(op1, &op1_big);
float_init_bigfloat(op2, &op2_big);
value_to_bigfloat(&op1_big, op1);
value_to_bigfloat(&op2_big, op2);
return bigfloat_cmp(&op1_big, &op2_big);
}

View File

@ -434,6 +434,17 @@ fn testFloatComparisons() void {
}
}
test "different sized float comparisons" {
testDifferentSizedFloatComparisons();
comptime testDifferentSizedFloatComparisons();
}
fn testDifferentSizedFloatComparisons() void {
var a: f16 = 1;
var b: f64 = 2;
expect(a < b);
}
// TODO This is waiting on library support for the Windows build (not sure why the other's don't need it)
//test "@nearbyint" {
// comptime testNearbyInt();