From 5f38d6e2e97829ed74f06a96b5d07a2c68516063 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 21 Jun 2018 14:43:55 -0400 Subject: [PATCH] add casting docs, __extenddftf2, and __extendsftf2 --- CMakeLists.txt | 1 + doc/langref.html.in | 171 +++++++++++++++++-- src/ir.cpp | 6 +- std/special/compiler_rt/extendXfYf2.zig | 87 ++++++++++ std/special/compiler_rt/extendXfYf2_test.zig | 108 ++++++++++++ std/special/compiler_rt/index.zig | 2 + test/behavior.zig | 1 + test/cases/widening.zig | 26 +++ 8 files changed, 384 insertions(+), 18 deletions(-) create mode 100644 std/special/compiler_rt/extendXfYf2.zig create mode 100644 std/special/compiler_rt/extendXfYf2_test.zig create mode 100644 test/cases/widening.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index 030398d71..99de2328d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -558,6 +558,7 @@ set(ZIG_STD_FILES "special/compiler_rt/aullrem.zig" "special/compiler_rt/comparetf2.zig" "special/compiler_rt/divti3.zig" + "special/compiler_rt/extendXfYf2.zig" "special/compiler_rt/fixuint.zig" "special/compiler_rt/fixunsdfdi.zig" "special/compiler_rt/fixunsdfsi.zig" diff --git a/doc/langref.html.in b/doc/langref.html.in index bdc33cb80..b76fc6938 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -3573,14 +3573,161 @@ const optional_value: ?i32 = null; {#header_close#} {#header_close#} {#header_open|Casting#} -

TODO: explain implicit vs explicit casting

-

TODO: resolve peer types builtin

-

TODO: truncate builtin

-

TODO: bitcast builtin

-

TODO: int to ptr builtin

-

TODO: ptr to int builtin

-

TODO: ptrcast builtin

-

TODO: explain number literals vs concrete types

+

+ A type cast converts a value of one type to another. + Zig has {#link|Implicit Casts#} for conversions that are known to be completely safe and unambiguous, + and {#link|Explicit Casts#} for conversions that one would not want to happen on accident. + There is also a third kind of type conversion called {#link|Peer Type Resolution#} for + the case when a result type must be decided given multiple operand types. +

+ {#header_open|Implicit Casts#} +

+ An implicit cast occurs when one type is expected, but different type is provided: +

+ {#code_begin|test#} +test "implicit cast - variable declaration" { + var a: u8 = 1; + var b: u16 = a; +} + +test "implicit cast - function call" { + var a: u8 = 1; + foo(a); +} + +fn foo(b: u16) void {} + +test "implicit cast - invoke a type as a function" { + var a: u8 = 1; + var b = u16(a); +} + {#code_end#} + {#header_open|Implicit Cast: Stricter Qualification#} +

+ Values which have the same representation at runtime can be cast to increase the strictness + of the qualifiers, no matter how nested the qualifiers are: +

+ +

+ These casts are no-ops at runtime since the value representation does not change. +

+ {#code_begin|test#} +test "implicit cast - const qualification" { + var a: i32 = 1; + var b: *i32 = &a; + foo(b); +} + +fn foo(a: *const i32) void {} + {#code_end#} +

+ In addition, pointers implicitly cast to const optional pointers: +

+ {#code_begin|test#} +const std = @import("std"); +const assert = std.debug.assert; +const mem = std.mem; + +test "cast *[1][*]const u8 to [*]const ?[*]const u8" { + const window_name = [1][*]const u8{c"window name"}; + const x: [*]const ?[*]const u8 = &window_name; + assert(mem.eql(u8, std.cstr.toSliceConst(x[0].?), "window name")); +} + {#code_end#} + {#header_close#} + {#header_open|Implicit Cast: Integer and Float Widening#} +

+ {#link|Integers#} implicitly cast to integer types which can represent every value of the old type, and likewise + {#link|Floats#} implicitly cast to float types which can represent every value of the old type. +

+ {#code_begin|test#} +const std = @import("std"); +const assert = std.debug.assert; +const mem = std.mem; + +test "integer widening" { + var a: u8 = 250; + var b: u16 = a; + var c: u32 = b; + var d: u64 = c; + var e: u64 = d; + var f: u128 = e; + assert(f == a); +} + +test "implicit unsigned integer to signed integer" { + var a: u8 = 250; + var b: i16 = a; + assert(b == 250); +} + +test "float widening" { + var a: f32 = 12.34; + var b: f64 = a; + var c: f128 = b; + assert(c == a); +} + {#code_end#} + {#header_close#} + {#header_open|Implicit Cast: Arrays#} +

TODO: [N]T to []const T

+

TODO: *const [N]T to []const T

+

TODO: [N]T to *const []const T

+

TODO: [N]T to ?[]const T

+

TODO: *[N]T to []T

+

TODO: *[N]T to [*]T

+

TODO: *T to *[1]T

+

TODO: [N]T to E![]const T

+ {#header_close#} + {#header_open|Implicit Cast: Optionals#} +

TODO: T to ?T

+

TODO: T to E!?T

+

TODO: null to ?T

+ {#header_close#} + {#header_open|Implicit Cast: T to E!T#} +

TODO

+ {#header_close#} + {#header_open|Implicit Cast: E to E!T#} +

TODO

+ {#header_close#} + {#header_open|Implicit Cast: comptime_int to *const integer#} +

TODO

+ {#header_close#} + {#header_open|Implicit Cast: comptime_float to *const float#} +

TODO

+ {#header_close#} + {#header_open|Implicit Cast: compile-time known numbers#} +

TODO

+ {#header_close#} + {#header_open|Implicit Cast: union to enum#} +

TODO

+ {#header_close#} + {#header_open|Implicit Cast: enum to union#} +

TODO

+ {#header_close#} + {#header_open|Implicit Cast: T to *T when @sizeOf(T) == 0#} +

TODO

+ {#header_close#} + {#header_open|Implicit Cast: undefined#} +

TODO

+ {#header_close#} + {#header_open|Implicit Cast: T to *const T#} +

TODO

+ {#header_close#} + {#header_close#} + + {#header_open|Explicit Casts#} +

TODO

+ {#header_close#} + + {#header_open|Peer Type Resolution#} +

TODO

+ {#header_close#} {#header_close#} {#header_open|void#} @@ -5522,12 +5669,6 @@ pub const FloatMode = enum {

{#see_also|Compile Variables#} {#header_close#} - {#header_open|@setGlobalSection#} -
@setGlobalSection(global_variable_name, comptime section_name: []const u8) bool
-

- Puts the global variable in the specified section. -

- {#header_close#} {#header_open|@shlExact#}
@shlExact(value: T, shift_amt: Log2T) T

@@ -6928,7 +7069,7 @@ hljs.registerLanguage("zig", function(t) { a = t.IR + "\\s*\\(", c = { keyword: "const align var extern stdcallcc nakedcc volatile export pub noalias inline struct packed enum union break return try catch test continue unreachable comptime and or asm defer errdefer if else switch while for fn use bool f32 f64 void type noreturn error i8 u8 i16 u16 i32 u32 i64 u64 isize usize i8w u8w i16w i32w u32w i64w u64w isizew usizew c_short c_ushort c_int c_uint c_long c_ulong c_longlong c_ulonglong resume cancel await async orelse", - built_in: "atomicLoad breakpoint returnAddress frameAddress fieldParentPtr setFloatMode IntType OpaqueType compileError compileLog setCold setRuntimeSafety setEvalBranchQuota offsetOf memcpy inlineCall setGlobalLinkage setGlobalSection divTrunc divFloor enumTagName intToPtr ptrToInt panic ptrCast intCast floatCast intToFloat floatToInt boolToInt bytesToSlice sliceToBytes errSetCast bitCast rem mod memset sizeOf alignOf alignCast maxValue minValue memberCount memberName memberType typeOf addWithOverflow subWithOverflow mulWithOverflow shlWithOverflow shlExact shrExact cInclude cDefine cUndef ctz clz import cImport errorName embedFile cmpxchgStrong cmpxchgWeak fence divExact truncate atomicRmw sqrt field typeInfo typeName newStackCall errorToInt intToError enumToInt intToEnum", + built_in: "atomicLoad breakpoint returnAddress frameAddress fieldParentPtr setFloatMode IntType OpaqueType compileError compileLog setCold setRuntimeSafety setEvalBranchQuota offsetOf memcpy inlineCall setGlobalLinkage divTrunc divFloor enumTagName intToPtr ptrToInt panic ptrCast intCast floatCast intToFloat floatToInt boolToInt bytesToSlice sliceToBytes errSetCast bitCast rem mod memset sizeOf alignOf alignCast maxValue minValue memberCount memberName memberType typeOf addWithOverflow subWithOverflow mulWithOverflow shlWithOverflow shlExact shrExact cInclude cDefine cUndef ctz clz import cImport errorName embedFile cmpxchgStrong cmpxchgWeak fence divExact truncate atomicRmw sqrt field typeInfo typeName newStackCall errorToInt intToError enumToInt intToEnum", literal: "true false null undefined" }, n = [e, t.CLCM, t.CBCM, s, r]; diff --git a/src/ir.cpp b/src/ir.cpp index 950d05149..c6078e755 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -10092,7 +10092,7 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst } } - // cast from &const [N]T to []const T + // cast from *const [N]T to []const T if (is_slice(wanted_type) && actual_type->id == TypeTableEntryIdPointer && actual_type->data.pointer.is_const && @@ -10111,7 +10111,7 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst } } - // cast from [N]T to &const []const T + // cast from [N]T to *const []const T if (wanted_type->id == TypeTableEntryIdPointer && wanted_type->data.pointer.is_const && is_slice(wanted_type->data.pointer.child_type) && @@ -10136,7 +10136,7 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst } } - // cast from [N]T to ?[]const N + // cast from [N]T to ?[]const T if (wanted_type->id == TypeTableEntryIdOptional && is_slice(wanted_type->data.maybe.child_type) && actual_type->id == TypeTableEntryIdArray) diff --git a/std/special/compiler_rt/extendXfYf2.zig b/std/special/compiler_rt/extendXfYf2.zig new file mode 100644 index 000000000..6fa8cf465 --- /dev/null +++ b/std/special/compiler_rt/extendXfYf2.zig @@ -0,0 +1,87 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const is_test = builtin.is_test; + +pub extern fn __extenddftf2(a: f64) f128 { + return extendXfYf2(f128, f64, a); +} + +pub extern fn __extendsftf2(a: f32) f128 { + return extendXfYf2(f128, f32, a); +} + +const CHAR_BIT = 8; + +pub fn extendXfYf2(comptime dst_t: type, comptime src_t: type, a: src_t) dst_t { + const src_rep_t = @IntType(false, @typeInfo(src_t).Float.bits); + const dst_rep_t = @IntType(false, @typeInfo(dst_t).Float.bits); + const srcSigBits = std.math.floatMantissaBits(src_t); + const dstSigBits = std.math.floatMantissaBits(dst_t); + const SrcShift = std.math.Log2Int(src_rep_t); + const DstShift = std.math.Log2Int(dst_rep_t); + + // Various constants whose values follow from the type parameters. + // Any reasonable optimizer will fold and propagate all of these. + const srcBits: i32 = @sizeOf(src_t) * CHAR_BIT; + const srcExpBits: i32 = srcBits - srcSigBits - 1; + const srcInfExp: i32 = (1 << srcExpBits) - 1; + const srcExpBias: i32 = srcInfExp >> 1; + + const srcMinNormal: src_rep_t = src_rep_t(1) << srcSigBits; + const srcInfinity: src_rep_t = src_rep_t(@bitCast(u32, srcInfExp)) << srcSigBits; + const srcSignMask: src_rep_t = src_rep_t(1) << @intCast(SrcShift, srcSigBits +% srcExpBits); + const srcAbsMask: src_rep_t = srcSignMask -% 1; + const srcQNaN: src_rep_t = src_rep_t(1) << @intCast(SrcShift, srcSigBits -% 1); + const srcNaNCode: src_rep_t = srcQNaN -% 1; + + const dstBits: i32 = @sizeOf(dst_t) * CHAR_BIT; + const dstExpBits: i32 = dstBits - dstSigBits - 1; + const dstInfExp: i32 = (1 << dstExpBits) - 1; + const dstExpBias: i32 = dstInfExp >> 1; + + const dstMinNormal: dst_rep_t = dst_rep_t(1) << dstSigBits; + + // Break a into a sign and representation of the absolute value + const aRep: src_rep_t = @bitCast(src_rep_t, a); + const aAbs: src_rep_t = aRep & srcAbsMask; + const sign: src_rep_t = aRep & srcSignMask; + var absResult: dst_rep_t = undefined; + + // If @sizeOf(src_rep_t) < @sizeOf(int), the subtraction result is promoted + // to (signed) int. To avoid that, explicitly cast to src_rep_t. + if ((src_rep_t)(aAbs -% srcMinNormal) < srcInfinity -% srcMinNormal) { + // a is a normal number. + // Extend to the destination type by shifting the significand and + // exponent into the proper position and rebiasing the exponent. + absResult = dst_rep_t(aAbs) << (dstSigBits -% srcSigBits); + absResult += dst_rep_t(@bitCast(u32, dstExpBias -% srcExpBias)) << dstSigBits; + } else if (aAbs >= srcInfinity) { + // a is NaN or infinity. + // Conjure the result by beginning with infinity, then setting the qNaN + // bit (if needed) and right-aligning the rest of the trailing NaN + // payload field. + absResult = dst_rep_t(@bitCast(u32, dstInfExp)) << dstSigBits; + absResult |= (dst_rep_t)(aAbs & srcQNaN) << (dstSigBits - srcSigBits); + absResult |= (dst_rep_t)(aAbs & srcNaNCode) << (dstSigBits - srcSigBits); + } else if (aAbs != 0) { + // a is denormal. + // renormalize the significand and clear the leading bit, then insert + // the correct adjusted exponent in the destination type. + const scale: i32 = @clz(aAbs) - @clz(srcMinNormal); + absResult = dst_rep_t(aAbs) << @intCast(DstShift, dstSigBits - srcSigBits + scale); + absResult ^= dstMinNormal; + const resultExponent: i32 = dstExpBias - srcExpBias - scale + 1; + absResult |= dst_rep_t(@bitCast(u32, resultExponent)) << @intCast(DstShift, dstSigBits); + } else { + // a is zero. + absResult = 0; + } + + // Apply the signbit to (dst_t)abs(a). + const result: dst_rep_t align(@alignOf(dst_t)) = absResult | dst_rep_t(sign) << @intCast(DstShift, dstBits - srcBits); + return @bitCast(dst_t, result); +} + +test "import extendXfYf2" { + _ = @import("extendXfYf2_test.zig"); +} diff --git a/std/special/compiler_rt/extendXfYf2_test.zig b/std/special/compiler_rt/extendXfYf2_test.zig new file mode 100644 index 000000000..84fb410fb --- /dev/null +++ b/std/special/compiler_rt/extendXfYf2_test.zig @@ -0,0 +1,108 @@ +const __extenddftf2 = @import("extendXfYf2.zig").__extenddftf2; +const __extendsftf2 = @import("extendXfYf2.zig").__extendsftf2; +const assert = @import("std").debug.assert; + +fn test__extenddftf2(a: f64, expectedHi: u64, expectedLo: u64) void { + const x = __extenddftf2(a); + + const rep = @bitCast(u128, x); + const hi = @intCast(u64, rep >> 64); + const lo = @truncate(u64, rep); + + if (hi == expectedHi and lo == expectedLo) + return; + + // test other possible NaN representation(signal NaN) + if (expectedHi == 0x7fff800000000000 and expectedLo == 0x0) { + if ((hi & 0x7fff000000000000) == 0x7fff000000000000 and + ((hi & 0xffffffffffff) > 0 or lo > 0)) + { + return; + } + } + + @panic("__extenddftf2 test failure"); +} + +fn test__extendsftf2(a: f32, expectedHi: u64, expectedLo: u64) void { + const x = __extendsftf2(a); + + const rep = @bitCast(u128, x); + const hi = @intCast(u64, rep >> 64); + const lo = @truncate(u64, rep); + + if (hi == expectedHi and lo == expectedLo) + return; + + // test other possible NaN representation(signal NaN) + if (expectedHi == 0x7fff800000000000 and expectedLo == 0x0) { + if ((hi & 0x7fff000000000000) == 0x7fff000000000000 and + ((hi & 0xffffffffffff) > 0 or lo > 0)) + { + return; + } + } + + @panic("__extendsftf2 test failure"); +} + +test "extenddftf2" { + // qNaN + test__extenddftf2(makeQNaN64(), 0x7fff800000000000, 0x0); + + // NaN + test__extenddftf2(makeNaN64(0x7100000000000), 0x7fff710000000000, 0x0); + + // inf + test__extenddftf2(makeInf64(), 0x7fff000000000000, 0x0); + + // zero + test__extenddftf2(0.0, 0x0, 0x0); + + test__extenddftf2(0x1.23456789abcdefp+5, 0x400423456789abcd, 0xf000000000000000); + + test__extenddftf2(0x1.edcba987654321fp-9, 0x3ff6edcba9876543, 0x2000000000000000); + + test__extenddftf2(0x1.23456789abcdefp+45, 0x402c23456789abcd, 0xf000000000000000); + + test__extenddftf2(0x1.edcba987654321fp-45, 0x3fd2edcba9876543, 0x2000000000000000); +} + +test "extendsftf2" { + // qNaN + test__extendsftf2(makeQNaN32(), 0x7fff800000000000, 0x0); + // NaN + test__extendsftf2(makeNaN32(0x410000), 0x7fff820000000000, 0x0); + // inf + test__extendsftf2(makeInf32(), 0x7fff000000000000, 0x0); + // zero + test__extendsftf2(0.0, 0x0, 0x0); + test__extendsftf2(0x1.23456p+5, 0x4004234560000000, 0x0); + test__extendsftf2(0x1.edcbap-9, 0x3ff6edcba0000000, 0x0); + test__extendsftf2(0x1.23456p+45, 0x402c234560000000, 0x0); + test__extendsftf2(0x1.edcbap-45, 0x3fd2edcba0000000, 0x0); +} + +fn makeQNaN64() f64 { + return @bitCast(f64, u64(0x7ff8000000000000)); +} + +fn makeInf64() f64 { + return @bitCast(f64, u64(0x7ff0000000000000)); +} + +fn makeNaN64(rand: u64) f64 { + return @bitCast(f64, 0x7ff0000000000000 | (rand & 0xfffffffffffff)); +} + +fn makeQNaN32() f32 { + return @bitCast(f32, u32(0x7fc00000)); +} + +fn makeNaN32(rand: u32) f32 { + return @bitCast(f32, 0x7f800000 | (rand & 0x7fffff)); +} + +fn makeInf32() f32 { + return @bitCast(f32, u32(0x7f800000)); +} diff --git a/std/special/compiler_rt/index.zig b/std/special/compiler_rt/index.zig index 6ad7768cb..c96e1587f 100644 --- a/std/special/compiler_rt/index.zig +++ b/std/special/compiler_rt/index.zig @@ -20,6 +20,8 @@ comptime { @export("__unordtf2", @import("comparetf2.zig").__unordtf2, linkage); @export("__floatuntidf", @import("floatuntidf.zig").__floatuntidf, linkage); + @export("__extenddftf2", @import("extendXfYf2.zig").__extenddftf2, linkage); + @export("__extendsftf2", @import("extendXfYf2.zig").__extendsftf2, linkage); @export("__fixunssfsi", @import("fixunssfsi.zig").__fixunssfsi, linkage); @export("__fixunssfdi", @import("fixunssfdi.zig").__fixunssfdi, linkage); diff --git a/test/behavior.zig b/test/behavior.zig index b494c623e..3a2f706ad 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -59,4 +59,5 @@ comptime { _ = @import("cases/var_args.zig"); _ = @import("cases/void.zig"); _ = @import("cases/while.zig"); + _ = @import("cases/widening.zig"); } diff --git a/test/cases/widening.zig b/test/cases/widening.zig new file mode 100644 index 000000000..18c12806d --- /dev/null +++ b/test/cases/widening.zig @@ -0,0 +1,26 @@ +const std = @import("std"); +const assert = std.debug.assert; +const mem = std.mem; + +test "integer widening" { + var a: u8 = 250; + var b: u16 = a; + var c: u32 = b; + var d: u64 = c; + var e: u64 = d; + var f: u128 = e; + assert(f == a); +} + +test "implicit unsigned integer to signed integer" { + var a: u8 = 250; + var b: i16 = a; + assert(b == 250); +} + +test "float widening" { + var a: f32 = 12.34; + var b: f64 = a; + var c: f128 = b; + assert(c == a); +}