diff --git a/build.zig b/build.zig index e4ed04ea16..a5e430fb01 100644 --- a/build.zig +++ b/build.zig @@ -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); diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 19b683b177..13c5a065e2 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -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); } } diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index 2365cb9b14..2a5153448c 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -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; } diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index d64c1824cf..7e71cc55db 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -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}), } } diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 53a73dbf6c..f501627691 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -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, diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index a3e1daa383..881602d76a 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -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 diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 1afa37ec28..d2df0afaf5 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -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).?; diff --git a/src/ir.cpp b/src/ir.cpp index 11ff7746e7..cf8558761f 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -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); } diff --git a/test/stage1/behavior/floatop.zig b/test/stage1/behavior/floatop.zig index 7dd96476e0..01ad68fe0c 100644 --- a/test/stage1/behavior/floatop.zig +++ b/test/stage1/behavior/floatop.zig @@ -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();