From 5d705fc6e35e75a604d3dbbb377ab01bf2b2b575 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 18 Jun 2018 15:01:42 -0400 Subject: remove error set casting syntax. add `@errSetCast` See #1061 --- src/codegen.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/codegen.cpp') diff --git a/src/codegen.cpp b/src/codegen.cpp index 84335d4e06..585881a9a5 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -4727,6 +4727,7 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, case IrInstructionIdIntToFloat: case IrInstructionIdFloatToInt: case IrInstructionIdBoolToInt: + case IrInstructionIdErrSetCast: zig_unreachable(); case IrInstructionIdReturn: @@ -6356,6 +6357,7 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdErrorReturnTrace, "errorReturnTrace", 0); create_builtin_fn(g, BuiltinFnIdAtomicRmw, "atomicRmw", 5); create_builtin_fn(g, BuiltinFnIdAtomicLoad, "atomicLoad", 3); + create_builtin_fn(g, BuiltinFnIdErrSetCast, "errSetCast", 2); } static const char *bool_to_str(bool b) { -- cgit v1.2.3 From 1aafbae5be518309b4c2194cdc24e22642514519 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 18 Jun 2018 17:25:29 -0400 Subject: remove []u8 casting syntax. add `@bytesToSlice` and `@sliceToBytes` See #1061 --- doc/langref.html.in | 45 +++++++--- src/all_types.hpp | 28 +++++++ src/codegen.cpp | 4 + src/ir.cpp | 217 ++++++++++++++++++++++++++++++++++++------------ src/ir_print.cpp | 20 +++++ std/heap.zig | 2 +- std/macho.zig | 2 +- std/mem.zig | 12 +-- std/net.zig | 2 +- std/os/windows/util.zig | 2 +- test/cases/align.zig | 2 +- test/cases/cast.zig | 8 +- test/cases/misc.zig | 4 +- test/cases/struct.zig | 4 +- test/compile_errors.zig | 21 ++--- 15 files changed, 277 insertions(+), 96 deletions(-) (limited to 'src/codegen.cpp') diff --git a/doc/langref.html.in b/doc/langref.html.in index 48f525fedc..9a3d5e5f17 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -1456,8 +1456,7 @@ test "pointer array access" { // Taking an address of an individual element gives a // pointer to a single item. This kind of pointer // does not support pointer arithmetic. - - var array = []u8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + var array = []u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; const ptr = &array[2]; assert(@typeOf(ptr) == *u8); @@ -1469,7 +1468,7 @@ test "pointer array access" { test "pointer slicing" { // In Zig, we prefer using slices over null-terminated pointers. // You can turn an array into a slice using slice syntax: - var array = []u8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + var array = []u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; const slice = array[2..4]; assert(slice.len == 2); @@ -1541,13 +1540,13 @@ test "pointer casting" { // To convert one pointer type to another, use @ptrCast. This is an unsafe // operation that Zig cannot protect you against. Use @ptrCast only when other // conversions are not possible. - const bytes align(@alignOf(u32)) = []u8{0x12, 0x12, 0x12, 0x12}; + const bytes align(@alignOf(u32)) = []u8{ 0x12, 0x12, 0x12, 0x12 }; const u32_ptr = @ptrCast(*const u32, &bytes[0]); assert(u32_ptr.* == 0x12121212); // Even this example is contrived - there are better ways to do the above than // pointer casting. For example, using a slice narrowing cast: - const u32_value = ([]const u32)(bytes[0..])[0]; + const u32_value = @bytesToSlice(u32, bytes[0..])[0]; assert(u32_value == 0x12121212); // And even another way, the most straightforward way to do it: @@ -1630,13 +1629,13 @@ test "function alignment" { const assert = @import("std").debug.assert; test "pointer alignment safety" { - var array align(4) = []u32{0x11111111, 0x11111111}; - const bytes = ([]u8)(array[0..]); + var array align(4) = []u32{ 0x11111111, 0x11111111 }; + const bytes = @sliceToBytes(array[0..]); assert(foo(bytes) == 0x11111111); } fn foo(bytes: []u8) u32 { const slice4 = bytes[1..5]; - const int_slice = ([]u32)(@alignCast(4, slice4)); + const int_slice = @bytesToSlice(u32, @alignCast(4, slice4)); return int_slice[0]; } {#code_end#} @@ -1728,8 +1727,8 @@ test "slice pointer" { test "slice widening" { // Zig supports slice widening and slice narrowing. Cast a slice of u8 // to a slice of anything else, and Zig will perform the length conversion. - const array align(@alignOf(u32)) = []u8{0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13}; - const slice = ([]const u32)(array[0..]); + const array align(@alignOf(u32)) = []u8{ 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13 }; + const slice = @bytesToSlice(u32, array[0..]); assert(slice.len == 2); assert(slice[0] == 0x12121212); assert(slice[1] == 0x13131313); @@ -4651,6 +4650,18 @@ comptime {

{#header_close#} + {#header_open|@bytesToSlice#} +
@bytesToSlice(comptime Element: type, bytes: []u8) []Element
+

+ Converts a slice of bytes or array of bytes into a slice of Element. + The resulting slice has the same {#link|pointer|Pointers#} properties as the parameter. +

+

+ Attempting to convert a number of bytes with a length that does not evenly divide into a slice of + elements results in {#link|Undefined Behavior#}. +

+ {#header_close#} + {#header_open|@cDefine#}
@cDefine(comptime name: []u8, value)

@@ -5467,8 +5478,9 @@ pub const FloatMode = enum {

{#see_also|@shlExact|@shlWithOverflow#} {#header_close#} + {#header_open|@sizeOf#} -
@sizeOf(comptime T: type) (number literal)
+
@sizeOf(comptime T: type) comptime_int

This function returns the number of bytes it takes to store T in memory.

@@ -5476,6 +5488,15 @@ pub const FloatMode = enum { The result is a target-specific compile time constant.

{#header_close#} + + {#header_open|@sliceToBytes#} +
@sliceToBytes(value: var) []u8
+

+ Converts a slice or array to a slice of u8. The resulting slice has the same + {#link|pointer|Pointers#} properties as the parameter. +

+ {#header_close#} + {#header_open|@sqrt#}
@sqrt(comptime T: type, value: T) T

@@ -6810,7 +6831,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 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", + 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", literal: "true false null undefined" }, n = [e, t.CLCM, t.CBCM, s, r]; diff --git a/src/all_types.hpp b/src/all_types.hpp index 732af239e2..d8432232c2 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -234,6 +234,16 @@ enum RuntimeHintPtr { RuntimeHintPtrNonStack, }; +enum RuntimeHintSliceId { + RuntimeHintSliceIdUnknown, + RuntimeHintSliceIdLen, +}; + +struct RuntimeHintSlice { + enum RuntimeHintSliceId id; + uint64_t len; +}; + struct ConstGlobalRefs { LLVMValueRef llvm_value; LLVMValueRef llvm_global; @@ -270,6 +280,7 @@ struct ConstExprValue { RuntimeHintErrorUnion rh_error_union; RuntimeHintOptional rh_maybe; RuntimeHintPtr rh_ptr; + RuntimeHintSlice rh_slice; } data; }; @@ -1360,6 +1371,8 @@ enum BuiltinFnId { BuiltinFnIdIntCast, BuiltinFnIdFloatCast, BuiltinFnIdErrSetCast, + BuiltinFnIdToBytes, + BuiltinFnIdFromBytes, BuiltinFnIdIntToFloat, BuiltinFnIdFloatToInt, BuiltinFnIdBoolToInt, @@ -2123,6 +2136,8 @@ enum IrInstructionId { IrInstructionIdMarkErrRetTracePtr, IrInstructionIdSqrt, IrInstructionIdErrSetCast, + IrInstructionIdToBytes, + IrInstructionIdFromBytes, }; struct IrInstruction { @@ -2665,6 +2680,19 @@ struct IrInstructionErrSetCast { IrInstruction *target; }; +struct IrInstructionToBytes { + IrInstruction base; + + IrInstruction *target; +}; + +struct IrInstructionFromBytes { + IrInstruction base; + + IrInstruction *dest_child_type; + IrInstruction *target; +}; + struct IrInstructionIntToFloat { IrInstruction base; diff --git a/src/codegen.cpp b/src/codegen.cpp index 585881a9a5..1bc9a17804 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -4728,6 +4728,8 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, case IrInstructionIdFloatToInt: case IrInstructionIdBoolToInt: case IrInstructionIdErrSetCast: + case IrInstructionIdFromBytes: + case IrInstructionIdToBytes: zig_unreachable(); case IrInstructionIdReturn: @@ -6358,6 +6360,8 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdAtomicRmw, "atomicRmw", 5); create_builtin_fn(g, BuiltinFnIdAtomicLoad, "atomicLoad", 3); create_builtin_fn(g, BuiltinFnIdErrSetCast, "errSetCast", 2); + create_builtin_fn(g, BuiltinFnIdToBytes, "sliceToBytes", 1); + create_builtin_fn(g, BuiltinFnIdFromBytes, "bytesToSlice", 2); } static const char *bool_to_str(bool b) { diff --git a/src/ir.cpp b/src/ir.cpp index e5ba4114cf..c5ca12af1e 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -472,6 +472,14 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionErrSetCast *) { return IrInstructionIdErrSetCast; } +static constexpr IrInstructionId ir_instruction_id(IrInstructionToBytes *) { + return IrInstructionIdToBytes; +} + +static constexpr IrInstructionId ir_instruction_id(IrInstructionFromBytes *) { + return IrInstructionIdFromBytes; +} + static constexpr IrInstructionId ir_instruction_id(IrInstructionIntToFloat *) { return IrInstructionIdIntToFloat; } @@ -1956,6 +1964,26 @@ static IrInstruction *ir_build_err_set_cast(IrBuilder *irb, Scope *scope, AstNod return &instruction->base; } +static IrInstruction *ir_build_to_bytes(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *target) { + IrInstructionToBytes *instruction = ir_build_instruction(irb, scope, source_node); + instruction->target = target; + + ir_ref_instruction(target, irb->current_basic_block); + + return &instruction->base; +} + +static IrInstruction *ir_build_from_bytes(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *dest_child_type, IrInstruction *target) { + IrInstructionFromBytes *instruction = ir_build_instruction(irb, scope, source_node); + instruction->dest_child_type = dest_child_type; + instruction->target = target; + + ir_ref_instruction(dest_child_type, irb->current_basic_block); + ir_ref_instruction(target, irb->current_basic_block); + + return &instruction->base; +} + static IrInstruction *ir_build_int_to_float(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *dest_type, IrInstruction *target) { IrInstructionIntToFloat *instruction = ir_build_instruction(irb, scope, source_node); instruction->dest_type = dest_type; @@ -4084,6 +4112,31 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo IrInstruction *result = ir_build_err_set_cast(irb, scope, node, arg0_value, arg1_value); return ir_lval_wrap(irb, scope, result, lval); } + case BuiltinFnIdFromBytes: + { + AstNode *arg0_node = node->data.fn_call_expr.params.at(0); + IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope); + if (arg0_value == irb->codegen->invalid_instruction) + return arg0_value; + + AstNode *arg1_node = node->data.fn_call_expr.params.at(1); + IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope); + if (arg1_value == irb->codegen->invalid_instruction) + return arg1_value; + + IrInstruction *result = ir_build_from_bytes(irb, scope, node, arg0_value, arg1_value); + return ir_lval_wrap(irb, scope, result, lval); + } + case BuiltinFnIdToBytes: + { + AstNode *arg0_node = node->data.fn_call_expr.params.at(0); + IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope); + if (arg0_value == irb->codegen->invalid_instruction) + return arg0_value; + + IrInstruction *result = ir_build_to_bytes(irb, scope, node, arg0_value); + return ir_lval_wrap(irb, scope, result, lval); + } case BuiltinFnIdIntToFloat: { AstNode *arg0_node = node->data.fn_call_expr.params.at(0); @@ -9103,11 +9156,6 @@ static bool is_container(TypeTableEntry *type) { type->id == TypeTableEntryIdUnion; } -static bool is_u8(TypeTableEntry *type) { - return type->id == TypeTableEntryIdInt && - !type->data.integral.is_signed && type->data.integral.bit_count == 8; -} - static IrBasicBlock *ir_get_new_bb(IrAnalyze *ira, IrBasicBlock *old_bb, IrInstruction *ref_old_instruction) { assert(old_bb); @@ -9661,6 +9709,8 @@ static IrInstruction *ir_analyze_array_to_slice(IrAnalyze *ira, IrInstruction *s IrInstruction *result = ir_build_slice(&ira->new_irb, source_instr->scope, source_instr->source_node, array_ptr, start, end, false); result->value.type = wanted_type; + result->value.data.rh_slice.id = RuntimeHintSliceIdLen; + result->value.data.rh_slice.len = array_type->data.array.len; ir_add_alloca(ira, result, result->value.type); return result; @@ -10103,7 +10153,7 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst return ira->codegen->invalid_instruction; } - // explicit match or non-const to const + // perfect match or non-const to const if (types_match_const_cast_only(ira, wanted_type, actual_type, source_node, false).id == ConstCastResultIdOk) { return ir_resolve_cast(ira, source_instr, value, wanted_type, CastOpNoop, false); } @@ -10214,52 +10264,6 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst } } - // explicit cast from []T to []u8 or []u8 to []T - if (is_slice(wanted_type) && is_slice(actual_type)) { - TypeTableEntry *wanted_ptr_type = wanted_type->data.structure.fields[slice_ptr_index].type_entry; - TypeTableEntry *actual_ptr_type = actual_type->data.structure.fields[slice_ptr_index].type_entry; - if ((is_u8(wanted_ptr_type->data.pointer.child_type) || is_u8(actual_ptr_type->data.pointer.child_type)) && - (wanted_ptr_type->data.pointer.is_const || !actual_ptr_type->data.pointer.is_const)) - { - uint32_t src_align_bytes = get_ptr_align(actual_ptr_type); - uint32_t dest_align_bytes = get_ptr_align(wanted_ptr_type); - - if (dest_align_bytes > src_align_bytes) { - ErrorMsg *msg = ir_add_error(ira, source_instr, - buf_sprintf("cast increases pointer alignment")); - add_error_note(ira->codegen, msg, source_instr->source_node, - buf_sprintf("'%s' has alignment %" PRIu32, buf_ptr(&actual_type->name), src_align_bytes)); - add_error_note(ira->codegen, msg, source_instr->source_node, - buf_sprintf("'%s' has alignment %" PRIu32, buf_ptr(&wanted_type->name), dest_align_bytes)); - return ira->codegen->invalid_instruction; - } - - if (!ir_emit_global_runtime_side_effect(ira, source_instr)) - return ira->codegen->invalid_instruction; - return ir_resolve_cast(ira, source_instr, value, wanted_type, CastOpResizeSlice, true); - } - } - - // explicit cast from [N]u8 to []const T - if (is_slice(wanted_type) && - wanted_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.is_const && - actual_type->id == TypeTableEntryIdArray && - is_u8(actual_type->data.array.child_type)) - { - if (!ir_emit_global_runtime_side_effect(ira, source_instr)) - return ira->codegen->invalid_instruction; - uint64_t child_type_size = type_size(ira->codegen, - wanted_type->data.structure.fields[slice_ptr_index].type_entry->data.pointer.child_type); - if (actual_type->data.array.len % child_type_size == 0) { - return ir_resolve_cast(ira, source_instr, value, wanted_type, CastOpBytesToSlice, true); - } else { - ir_add_error_node(ira, source_instr->source_node, - buf_sprintf("unable to convert %s to %s: size mismatch", - buf_ptr(&actual_type->name), buf_ptr(&wanted_type->name))); - return ira->codegen->invalid_instruction; - } - } - // explicit *[N]T to [*]T if (wanted_type->id == TypeTableEntryIdPointer && wanted_type->data.pointer.ptr_len == PtrLenUnknown && @@ -17644,6 +17648,109 @@ static TypeTableEntry *ir_analyze_instruction_err_set_cast(IrAnalyze *ira, IrIns return dest_type; } +static TypeTableEntry *ir_analyze_instruction_from_bytes(IrAnalyze *ira, IrInstructionFromBytes *instruction) { + TypeTableEntry *dest_child_type = ir_resolve_type(ira, instruction->dest_child_type->other); + if (type_is_invalid(dest_child_type)) + return ira->codegen->builtin_types.entry_invalid; + + IrInstruction *target = instruction->target->other; + if (type_is_invalid(target->value.type)) + return ira->codegen->builtin_types.entry_invalid; + + bool src_ptr_const; + bool src_ptr_volatile; + uint32_t src_ptr_align; + if (target->value.type->id == TypeTableEntryIdPointer) { + src_ptr_const = target->value.type->data.pointer.is_const; + src_ptr_volatile = target->value.type->data.pointer.is_volatile; + src_ptr_align = target->value.type->data.pointer.alignment; + } else if (is_slice(target->value.type)) { + TypeTableEntry *src_ptr_type = target->value.type->data.structure.fields[slice_ptr_index].type_entry; + src_ptr_const = src_ptr_type->data.pointer.is_const; + src_ptr_volatile = src_ptr_type->data.pointer.is_volatile; + src_ptr_align = src_ptr_type->data.pointer.alignment; + } else { + src_ptr_const = true; + src_ptr_volatile = false; + src_ptr_align = get_abi_alignment(ira->codegen, target->value.type); + } + + TypeTableEntry *dest_ptr_type = get_pointer_to_type_extra(ira->codegen, dest_child_type, + src_ptr_const, src_ptr_volatile, PtrLenUnknown, + src_ptr_align, 0, 0); + TypeTableEntry *dest_slice_type = get_slice_type(ira->codegen, dest_ptr_type); + + TypeTableEntry *u8_ptr = get_pointer_to_type_extra(ira->codegen, ira->codegen->builtin_types.entry_u8, + src_ptr_const, src_ptr_volatile, PtrLenUnknown, + src_ptr_align, 0, 0); + TypeTableEntry *u8_slice = get_slice_type(ira->codegen, u8_ptr); + + IrInstruction *casted_value = ir_implicit_cast(ira, target, u8_slice); + if (type_is_invalid(casted_value->value.type)) + return ira->codegen->builtin_types.entry_invalid; + + bool have_known_len = false; + uint64_t known_len; + + if (instr_is_comptime(casted_value)) { + ConstExprValue *val = ir_resolve_const(ira, casted_value, UndefBad); + if (!val) + return ira->codegen->builtin_types.entry_invalid; + + ConstExprValue *len_val = &val->data.x_struct.fields[slice_len_index]; + if (value_is_comptime(len_val)) { + known_len = bigint_as_unsigned(&len_val->data.x_bigint); + have_known_len = true; + } + } + + if (casted_value->value.data.rh_slice.id == RuntimeHintSliceIdLen) { + known_len = casted_value->value.data.rh_slice.len; + have_known_len = true; + } + + if (have_known_len) { + uint64_t child_type_size = type_size(ira->codegen, dest_child_type); + uint64_t remainder = known_len % child_type_size; + if (remainder != 0) { + ErrorMsg *msg = ir_add_error(ira, &instruction->base, + buf_sprintf("unable to convert [%" ZIG_PRI_u64 "]u8 to %s: size mismatch", + known_len, buf_ptr(&dest_slice_type->name))); + add_error_note(ira->codegen, msg, instruction->dest_child_type->source_node, + buf_sprintf("%s has size %" ZIG_PRI_u64 "; remaining bytes: %" ZIG_PRI_u64, + buf_ptr(&dest_child_type->name), child_type_size, remainder)); + return ira->codegen->builtin_types.entry_invalid; + } + } + + IrInstruction *result = ir_resolve_cast(ira, &instruction->base, casted_value, dest_slice_type, CastOpResizeSlice, true); + ir_link_new_instruction(result, &instruction->base); + return dest_slice_type; +} + +static TypeTableEntry *ir_analyze_instruction_to_bytes(IrAnalyze *ira, IrInstructionToBytes *instruction) { + IrInstruction *target = instruction->target->other; + if (type_is_invalid(target->value.type)) + return ira->codegen->builtin_types.entry_invalid; + + if (!is_slice(target->value.type)) { + ir_add_error(ira, instruction->target, + buf_sprintf("expected slice, found '%s'", buf_ptr(&target->value.type->name))); + return ira->codegen->builtin_types.entry_invalid; + } + + TypeTableEntry *src_ptr_type = target->value.type->data.structure.fields[slice_ptr_index].type_entry; + + TypeTableEntry *dest_ptr_type = get_pointer_to_type_extra(ira->codegen, ira->codegen->builtin_types.entry_u8, + src_ptr_type->data.pointer.is_const, src_ptr_type->data.pointer.is_volatile, PtrLenUnknown, + src_ptr_type->data.pointer.alignment, 0, 0); + TypeTableEntry *dest_slice_type = get_slice_type(ira->codegen, dest_ptr_type); + + IrInstruction *result = ir_resolve_cast(ira, &instruction->base, target, dest_slice_type, CastOpResizeSlice, true); + ir_link_new_instruction(result, &instruction->base); + return dest_slice_type; +} + static TypeTableEntry *ir_analyze_instruction_int_to_float(IrAnalyze *ira, IrInstructionIntToFloat *instruction) { TypeTableEntry *dest_type = ir_resolve_type(ira, instruction->dest_type->other); if (type_is_invalid(dest_type)) @@ -20246,6 +20353,10 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi return ir_analyze_instruction_float_cast(ira, (IrInstructionFloatCast *)instruction); case IrInstructionIdErrSetCast: return ir_analyze_instruction_err_set_cast(ira, (IrInstructionErrSetCast *)instruction); + case IrInstructionIdFromBytes: + return ir_analyze_instruction_from_bytes(ira, (IrInstructionFromBytes *)instruction); + case IrInstructionIdToBytes: + return ir_analyze_instruction_to_bytes(ira, (IrInstructionToBytes *)instruction); case IrInstructionIdIntToFloat: return ir_analyze_instruction_int_to_float(ira, (IrInstructionIntToFloat *)instruction); case IrInstructionIdFloatToInt: @@ -20601,6 +20712,8 @@ bool ir_has_side_effects(IrInstruction *instruction) { case IrInstructionIdIntToFloat: case IrInstructionIdFloatToInt: case IrInstructionIdBoolToInt: + case IrInstructionIdFromBytes: + case IrInstructionIdToBytes: return false; case IrInstructionIdAsm: diff --git a/src/ir_print.cpp b/src/ir_print.cpp index 2667c246a5..1b35ecf57f 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -672,6 +672,20 @@ static void ir_print_err_set_cast(IrPrint *irp, IrInstructionErrSetCast *instruc fprintf(irp->f, ")"); } +static void ir_print_from_bytes(IrPrint *irp, IrInstructionFromBytes *instruction) { + fprintf(irp->f, "@bytesToSlice("); + ir_print_other_instruction(irp, instruction->dest_child_type); + fprintf(irp->f, ", "); + ir_print_other_instruction(irp, instruction->target); + fprintf(irp->f, ")"); +} + +static void ir_print_to_bytes(IrPrint *irp, IrInstructionToBytes *instruction) { + fprintf(irp->f, "@sliceToBytes("); + ir_print_other_instruction(irp, instruction->target); + fprintf(irp->f, ")"); +} + static void ir_print_int_to_float(IrPrint *irp, IrInstructionIntToFloat *instruction) { fprintf(irp->f, "@intToFloat("); ir_print_other_instruction(irp, instruction->dest_type); @@ -1472,6 +1486,12 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { case IrInstructionIdErrSetCast: ir_print_err_set_cast(irp, (IrInstructionErrSetCast *)instruction); break; + case IrInstructionIdFromBytes: + ir_print_from_bytes(irp, (IrInstructionFromBytes *)instruction); + break; + case IrInstructionIdToBytes: + ir_print_to_bytes(irp, (IrInstructionToBytes *)instruction); + break; case IrInstructionIdIntToFloat: ir_print_int_to_float(irp, (IrInstructionIntToFloat *)instruction); break; diff --git a/std/heap.zig b/std/heap.zig index 2a2c8c0b59..c948818e3d 100644 --- a/std/heap.zig +++ b/std/heap.zig @@ -221,7 +221,7 @@ pub const ArenaAllocator = struct { if (len >= actual_min_size) break; } const buf = try self.child_allocator.alignedAlloc(u8, @alignOf(BufNode), len); - const buf_node_slice = ([]BufNode)(buf[0..@sizeOf(BufNode)]); + const buf_node_slice = @bytesToSlice(BufNode, buf[0..@sizeOf(BufNode)]); const buf_node = &buf_node_slice[0]; buf_node.* = BufNode{ .data = buf, diff --git a/std/macho.zig b/std/macho.zig index 64f78ae4a3..fe5409ad4d 100644 --- a/std/macho.zig +++ b/std/macho.zig @@ -161,7 +161,7 @@ pub fn loadSymbols(allocator: *mem.Allocator, in: *io.FileInStream) !SymbolTable } fn readNoEof(in: *io.FileInStream, comptime T: type, result: []T) !void { - return in.stream.readNoEof(([]u8)(result)); + return in.stream.readNoEof(@sliceToBytes(result)); } fn readOneNoEof(in: *io.FileInStream, comptime T: type, result: *T) !void { return readNoEof(in, T, (*[1]T)(result)[0..]); diff --git a/std/mem.zig b/std/mem.zig index b02589b0dd..55844b88db 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -70,7 +70,7 @@ pub const Allocator = struct { for (byte_slice) |*byte| { byte.* = undefined; } - return ([]align(alignment) T)(@alignCast(alignment, byte_slice)); + return @bytesToSlice(T, @alignCast(alignment, byte_slice)); } pub fn realloc(self: *Allocator, comptime T: type, old_mem: []T, n: usize) ![]T { @@ -86,7 +86,7 @@ pub const Allocator = struct { return ([*]align(alignment) T)(undefined)[0..0]; } - const old_byte_slice = ([]u8)(old_mem); + const old_byte_slice = @sliceToBytes(old_mem); const byte_count = math.mul(usize, @sizeOf(T), n) catch return Error.OutOfMemory; const byte_slice = try self.reallocFn(self, old_byte_slice, byte_count, alignment); assert(byte_slice.len == byte_count); @@ -96,7 +96,7 @@ pub const Allocator = struct { byte.* = undefined; } } - return ([]T)(@alignCast(alignment, byte_slice)); + return @bytesToSlice(T, @alignCast(alignment, byte_slice)); } /// Reallocate, but `n` must be less than or equal to `old_mem.len`. @@ -118,13 +118,13 @@ pub const Allocator = struct { // n <= old_mem.len and the multiplication didn't overflow for that operation. const byte_count = @sizeOf(T) * n; - const byte_slice = self.reallocFn(self, ([]u8)(old_mem), byte_count, alignment) catch unreachable; + const byte_slice = self.reallocFn(self, @sliceToBytes(old_mem), byte_count, alignment) catch unreachable; assert(byte_slice.len == byte_count); - return ([]align(alignment) T)(@alignCast(alignment, byte_slice)); + return @bytesToSlice(T, @alignCast(alignment, byte_slice)); } pub fn free(self: *Allocator, memory: var) void { - const bytes = ([]const u8)(memory); + const bytes = @sliceToBytes(memory); if (bytes.len == 0) return; const non_const_ptr = @intToPtr([*]u8, @ptrToInt(bytes.ptr)); self.freeFn(self, non_const_ptr[0..bytes.len]); diff --git a/std/net.zig b/std/net.zig index f21611ff91..8c1aeb92d7 100644 --- a/std/net.zig +++ b/std/net.zig @@ -68,7 +68,7 @@ pub const Address = struct { pub fn parseIp4(buf: []const u8) !u32 { var result: u32 = undefined; - const out_ptr = ([]u8)((*[1]u32)(&result)[0..]); + const out_ptr = @sliceToBytes((*[1]u32)(&result)[0..]); var x: u8 = 0; var index: u8 = 0; diff --git a/std/os/windows/util.zig b/std/os/windows/util.zig index cb4788ba17..45b205451d 100644 --- a/std/os/windows/util.zig +++ b/std/os/windows/util.zig @@ -79,7 +79,7 @@ pub fn windowsIsCygwinPty(handle: windows.HANDLE) bool { const name_info = @ptrCast(*const windows.FILE_NAME_INFO, &name_info_bytes[0]); const name_bytes = name_info_bytes[size .. size + usize(name_info.FileNameLength)]; - const name_wide = ([]u16)(name_bytes); + const name_wide = @bytesToSlice(u16, name_bytes); return mem.indexOf(u16, name_wide, []u16{ 'm', 's', 'y', 's', '-' }) != null or mem.indexOf(u16, name_wide, []u16{ '-', 'p', 't', 'y' }) != null; } diff --git a/test/cases/align.zig b/test/cases/align.zig index 682c185e86..64f0788efc 100644 --- a/test/cases/align.zig +++ b/test/cases/align.zig @@ -90,7 +90,7 @@ fn testBytesAlignSlice(b: u8) void { b, b, }; - const slice = ([]u32)(bytes[0..]); + const slice: []u32 = @bytesToSlice(u32, bytes[0..]); assert(slice[0] == 0x33333333); } diff --git a/test/cases/cast.zig b/test/cases/cast.zig index f1e49c6d1f..0b79f0df48 100644 --- a/test/cases/cast.zig +++ b/test/cases/cast.zig @@ -372,7 +372,7 @@ test "const slice widen cast" { 0x12, }; - const u32_value = ([]const u32)(bytes[0..])[0]; + const u32_value = @bytesToSlice(u32, bytes[0..])[0]; assert(u32_value == 0x12121212); assert(@bitCast(u32, bytes) == 0x12121212); @@ -420,3 +420,9 @@ test "comptime_int @intToFloat" { assert(@typeOf(result) == f32); assert(result == 1234.0); } + +test "@bytesToSlice keeps pointer alignment" { + var bytes = []u8{ 0x01, 0x02, 0x03, 0x04 }; + const numbers = @bytesToSlice(u32, bytes[0..]); + comptime assert(@typeOf(numbers) == []align(@alignOf(@typeOf(bytes))) u32); +} diff --git a/test/cases/misc.zig b/test/cases/misc.zig index beb0d6d456..89c441e7f9 100644 --- a/test/cases/misc.zig +++ b/test/cases/misc.zig @@ -422,14 +422,14 @@ test "cast slice to u8 slice" { 4, }; const big_thing_slice: []i32 = big_thing_array[0..]; - const bytes = ([]u8)(big_thing_slice); + const bytes = @sliceToBytes(big_thing_slice); assert(bytes.len == 4 * 4); bytes[4] = 0; bytes[5] = 0; bytes[6] = 0; bytes[7] = 0; assert(big_thing_slice[1] == 0); - const big_thing_again = ([]align(1) i32)(bytes); + const big_thing_again = @bytesToSlice(i32, bytes); assert(big_thing_again[2] == 3); big_thing_again[2] = -1; assert(bytes[8] == @maxValue(u8)); diff --git a/test/cases/struct.zig b/test/cases/struct.zig index 94a2ba6336..2941ecb56a 100644 --- a/test/cases/struct.zig +++ b/test/cases/struct.zig @@ -302,7 +302,7 @@ test "packed array 24bits" { var bytes = []u8{0} ** (@sizeOf(FooArray24Bits) + 1); bytes[bytes.len - 1] = 0xaa; - const ptr = &([]FooArray24Bits)(bytes[0 .. bytes.len - 1])[0]; + const ptr = &@bytesToSlice(FooArray24Bits, bytes[0 .. bytes.len - 1])[0]; assert(ptr.a == 0); assert(ptr.b[0].field == 0); assert(ptr.b[1].field == 0); @@ -351,7 +351,7 @@ test "aligned array of packed struct" { } var bytes = []u8{0xbb} ** @sizeOf(FooArrayOfAligned); - const ptr = &([]FooArrayOfAligned)(bytes[0..bytes.len])[0]; + const ptr = &@bytesToSlice(FooArrayOfAligned, bytes[0..bytes.len])[0]; assert(ptr.a[0].a == 0xbb); assert(ptr.a[0].b == 0xbb); diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 8c5abaaccc..2f4a22553b 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -404,10 +404,10 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\const Set2 = error {A, C}; \\comptime { \\ var x = Set1.B; - \\ var y = Set2(x); + \\ var y = @errSetCast(Set2, x); \\} , - ".tmp_source.zig:5:17: error: error.B not a member of error set 'Set2'", + ".tmp_source.zig:5:13: error: error.B not a member of error set 'Set2'", ); cases.add( @@ -2086,10 +2086,11 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { "convert fixed size array to slice with invalid size", \\export fn f() void { \\ var array: [5]u8 = undefined; - \\ var foo = ([]const u32)(array)[0]; + \\ var foo = @bytesToSlice(u32, array)[0]; \\} , - ".tmp_source.zig:3:28: error: unable to convert [5]u8 to []const u32: size mismatch", + ".tmp_source.zig:3:15: error: unable to convert [5]u8 to []align(1) const u32: size mismatch", + ".tmp_source.zig:3:29: note: u32 has size 4; remaining bytes: 1", ); cases.add( @@ -3239,18 +3240,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { ".tmp_source.zig:3:26: note: '*u32' has alignment 4", ); - cases.add( - "increase pointer alignment in slice resize", - \\export fn entry() u32 { - \\ var bytes = []u8{0x01, 0x02, 0x03, 0x04}; - \\ return ([]u32)(bytes[0..])[0]; - \\} - , - ".tmp_source.zig:3:19: error: cast increases pointer alignment", - ".tmp_source.zig:3:19: note: '[]u8' has alignment 1", - ".tmp_source.zig:3:19: note: '[]u32' has alignment 4", - ); - cases.add( "@alignCast expects pointer or slice", \\export fn entry() void { -- cgit v1.2.3 From 626b73e8beeaae1cab23f883f877d89d64bbfa39 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 18 Jun 2018 18:48:29 -0400 Subject: remove error to/from int casting syntax; add `@errorToInt`/`@intToError` See #1061 --- doc/langref.html.in | 48 +++++++++++++++++++++++++++---- src/all_types.hpp | 2 ++ src/codegen.cpp | 2 ++ src/ir.cpp | 74 +++++++++++++++++++++++++++++++++++++----------- std/os/child_process.zig | 4 +-- test/cases/cast.zig | 4 +-- test/cases/error.zig | 10 +++---- test/cases/type_info.zig | 2 +- test/compile_errors.zig | 38 ++++++++++++------------- test/runtime_safety.zig | 14 ++++----- 10 files changed, 138 insertions(+), 60 deletions(-) (limited to 'src/codegen.cpp') diff --git a/doc/langref.html.in b/doc/langref.html.in index 9a3d5e5f17..24bf6e1b16 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -4658,7 +4658,7 @@ comptime {

Attempting to convert a number of bytes with a length that does not evenly divide into a slice of - elements results in {#link|Undefined Behavior#}. + elements results in safety-protected {#link|Undefined Behavior#}.

{#header_close#} @@ -4935,7 +4935,7 @@ test "main" {
@errSetCast(comptime T: DestType, value: var) DestType

Converts an error value from one error set to another error set. Attempting to convert an error - which is not in the destination error set results in {#link|Undefined Behavior#}. + which is not in the destination error set results in safety-protected {#link|Undefined Behavior#}.

{#header_close#} @@ -4955,6 +4955,7 @@ test "main" { error name table will be generated.

{#header_close#} + {#header_open|@errorReturnTrace#}
@errorReturnTrace() ?*builtin.StackTrace

@@ -4964,6 +4965,25 @@ test "main" {

{#header_close#} + {#header_open|@errorToInt#} +
@errorToInt(err: var) @IntType(false, @sizeOf(error) * 8)
+

+ Supports the following types: +

+ +

+ Converts an error to the integer representation of an error. +

+

+ It is generally recommended to avoid this + cast, as the integer representation of an error is not stable across source code changes. +

+ {#see_also|@intToError#} + {#header_close#} + {#header_open|@export#}
@export(comptime name: []const u8, target: var, linkage: builtin.GlobalLinkage) []const u8

@@ -5071,8 +5091,24 @@ fn add(a: i32, b: i32) i32 { return a + b; }

Converts an integer to another integer while keeping the same numerical value. Attempting to convert a number which is out of range of the destination type results in - {#link|Undefined Behavior#}. + safety-protected {#link|Undefined Behavior#}. +

+ {#header_close#} + + {#header_open|@intToError#} +
@intToError(value: @IntType(false, @sizeOf(error) * 8)) error
+

+ Converts from the integer representation of an error into the global error set type. +

+

+ It is generally recommended to avoid this + cast, as the integer representation of an error is not stable across source code changes. +

+

+ Attempting to convert an integer that does not correspond to any error results in + safety-protected {#link|Undefined Behavior#}.

+ {#see_also|@errorToInt#} {#header_close#} {#header_open|@intToFloat#} @@ -6123,8 +6159,8 @@ fn getNumberOrFail() !i32 { {#code_begin|test_err|integer value 11 represents no error#} comptime { const err = error.AnError; - const number = u32(err) + 10; - const invalid_err = error(number); + const number = @errorToInt(err) + 10; + const invalid_err = @intToError(number); } {#code_end#}

At runtime crashes with the message invalid error code and a stack trace.

@@ -6831,7 +6867,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", + 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", literal: "true false null undefined" }, n = [e, t.CLCM, t.CBCM, s, r]; diff --git a/src/all_types.hpp b/src/all_types.hpp index d8432232c2..e1a4ed7510 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1376,6 +1376,8 @@ enum BuiltinFnId { BuiltinFnIdIntToFloat, BuiltinFnIdFloatToInt, BuiltinFnIdBoolToInt, + BuiltinFnIdErrToInt, + BuiltinFnIdIntToErr, BuiltinFnIdIntType, BuiltinFnIdSetCold, BuiltinFnIdSetRuntimeSafety, diff --git a/src/codegen.cpp b/src/codegen.cpp index 1bc9a17804..c9b4ade4c6 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -6323,6 +6323,8 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdIntToFloat, "intToFloat", 2); create_builtin_fn(g, BuiltinFnIdFloatToInt, "floatToInt", 2); create_builtin_fn(g, BuiltinFnIdBoolToInt, "boolToInt", 1); + create_builtin_fn(g, BuiltinFnIdErrToInt, "errorToInt", 1); + create_builtin_fn(g, BuiltinFnIdIntToErr, "intToError", 1); create_builtin_fn(g, BuiltinFnIdCompileErr, "compileError", 1); create_builtin_fn(g, BuiltinFnIdCompileLog, "compileLog", SIZE_MAX); create_builtin_fn(g, BuiltinFnIdIntType, "IntType", 2); // TODO rename to Int diff --git a/src/ir.cpp b/src/ir.cpp index 3ed9f66947..350c80017e 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -4167,6 +4167,26 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo IrInstruction *result = ir_build_float_to_int(irb, scope, node, arg0_value, arg1_value); return ir_lval_wrap(irb, scope, result, lval); } + case BuiltinFnIdErrToInt: + { + AstNode *arg0_node = node->data.fn_call_expr.params.at(0); + IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope); + if (arg0_value == irb->codegen->invalid_instruction) + return arg0_value; + + IrInstruction *result = ir_build_err_to_int(irb, scope, node, arg0_value); + return ir_lval_wrap(irb, scope, result, lval); + } + case BuiltinFnIdIntToErr: + { + AstNode *arg0_node = node->data.fn_call_expr.params.at(0); + IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope); + if (arg0_value == irb->codegen->invalid_instruction) + return arg0_value; + + IrInstruction *result = ir_build_int_to_err(irb, scope, node, arg0_value); + return ir_lval_wrap(irb, scope, result, lval); + } case BuiltinFnIdBoolToInt: { AstNode *arg0_node = node->data.fn_call_expr.params.at(0); @@ -10465,21 +10485,6 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst return ir_analyze_number_to_literal(ira, source_instr, value, wanted_type); } - // explicit cast from T!void to integer type which can fit it - bool actual_type_is_void_err = actual_type->id == TypeTableEntryIdErrorUnion && - !type_has_bits(actual_type->data.error_union.payload_type); - bool actual_type_is_err_set = actual_type->id == TypeTableEntryIdErrorSet; - if ((actual_type_is_void_err || actual_type_is_err_set) && wanted_type->id == TypeTableEntryIdInt) { - return ir_analyze_err_to_int(ira, source_instr, value, wanted_type); - } - - // explicit cast from integer to error set - if (wanted_type->id == TypeTableEntryIdErrorSet && actual_type->id == TypeTableEntryIdInt && - !actual_type->data.integral.is_signed) - { - return ir_analyze_int_to_err(ira, source_instr, value, wanted_type); - } - // explicit cast from integer to enum type with no payload if (actual_type->id == TypeTableEntryIdInt && wanted_type->id == TypeTableEntryIdEnum) { return ir_analyze_int_to_enum(ira, source_instr, value, wanted_type); @@ -17785,6 +17790,39 @@ static TypeTableEntry *ir_analyze_instruction_float_to_int(IrAnalyze *ira, IrIns return dest_type; } +static TypeTableEntry *ir_analyze_instruction_err_to_int(IrAnalyze *ira, IrInstructionErrToInt *instruction) { + IrInstruction *target = instruction->target->other; + if (type_is_invalid(target->value.type)) + return ira->codegen->builtin_types.entry_invalid; + + IrInstruction *casted_target; + if (target->value.type->id == TypeTableEntryIdErrorSet) { + casted_target = target; + } else { + casted_target = ir_implicit_cast(ira, target, ira->codegen->builtin_types.entry_global_error_set); + if (type_is_invalid(casted_target->value.type)) + return ira->codegen->builtin_types.entry_invalid; + } + + IrInstruction *result = ir_analyze_err_to_int(ira, &instruction->base, casted_target, ira->codegen->err_tag_type); + ir_link_new_instruction(result, &instruction->base); + return result->value.type; +} + +static TypeTableEntry *ir_analyze_instruction_int_to_err(IrAnalyze *ira, IrInstructionIntToErr *instruction) { + IrInstruction *target = instruction->target->other; + if (type_is_invalid(target->value.type)) + return ira->codegen->builtin_types.entry_invalid; + + IrInstruction *casted_target = ir_implicit_cast(ira, target, ira->codegen->err_tag_type); + if (type_is_invalid(casted_target->value.type)) + return ira->codegen->builtin_types.entry_invalid; + + IrInstruction *result = ir_analyze_int_to_err(ira, &instruction->base, casted_target, ira->codegen->builtin_types.entry_global_error_set); + ir_link_new_instruction(result, &instruction->base); + return result->value.type; +} + static TypeTableEntry *ir_analyze_instruction_bool_to_int(IrAnalyze *ira, IrInstructionBoolToInt *instruction) { IrInstruction *target = instruction->target->other; if (type_is_invalid(target->value.type)) @@ -20229,8 +20267,6 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi case IrInstructionIdInvalid: case IrInstructionIdWidenOrShorten: case IrInstructionIdIntToEnum: - case IrInstructionIdIntToErr: - case IrInstructionIdErrToInt: case IrInstructionIdStructInit: case IrInstructionIdUnionInit: case IrInstructionIdStructFieldPtr: @@ -20491,6 +20527,10 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi return ir_analyze_instruction_mark_err_ret_trace_ptr(ira, (IrInstructionMarkErrRetTracePtr *)instruction); case IrInstructionIdSqrt: return ir_analyze_instruction_sqrt(ira, (IrInstructionSqrt *)instruction); + case IrInstructionIdIntToErr: + return ir_analyze_instruction_int_to_err(ira, (IrInstructionIntToErr *)instruction); + case IrInstructionIdErrToInt: + return ir_analyze_instruction_err_to_int(ira, (IrInstructionErrToInt *)instruction); } zig_unreachable(); } diff --git a/std/os/child_process.zig b/std/os/child_process.zig index 3a0fa7f461..da5e708555 100644 --- a/std/os/child_process.zig +++ b/std/os/child_process.zig @@ -318,7 +318,7 @@ pub const ChildProcess = struct { // Here we potentially return the fork child's error // from the parent pid. if (err_int != @maxValue(ErrInt)) { - return SpawnError(err_int); + return @errSetCast(SpawnError, @intToError(err_int)); } return statusToTerm(status); @@ -756,7 +756,7 @@ fn destroyPipe(pipe: *const [2]i32) void { // Child of fork calls this to report an error to the fork parent. // Then the child exits. fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn { - _ = writeIntFd(fd, ErrInt(err)); + _ = writeIntFd(fd, ErrInt(@errorToInt(err))); posix.exit(1); } diff --git a/test/cases/cast.zig b/test/cases/cast.zig index 0b79f0df48..156835bcb3 100644 --- a/test/cases/cast.zig +++ b/test/cases/cast.zig @@ -140,8 +140,8 @@ test "explicit cast from integer to error type" { comptime testCastIntToErr(error.ItBroke); } fn testCastIntToErr(err: error) void { - const x = usize(err); - const y = error(x); + const x = @errorToInt(err); + const y = @intToError(x); assert(error.ItBroke == y); } diff --git a/test/cases/error.zig b/test/cases/error.zig index 95890d8384..45971fd40d 100644 --- a/test/cases/error.zig +++ b/test/cases/error.zig @@ -31,8 +31,8 @@ test "@errorName" { } test "error values" { - const a = i32(error.err1); - const b = i32(error.err2); + const a = @errorToInt(error.err1); + const b = @errorToInt(error.err2); assert(a != b); } @@ -147,14 +147,14 @@ test "syntax: optional operator in front of error union operator" { } test "comptime err to int of error set with only 1 possible value" { - testErrToIntWithOnePossibleValue(error.A, u32(error.A)); - comptime testErrToIntWithOnePossibleValue(error.A, u32(error.A)); + testErrToIntWithOnePossibleValue(error.A, @errorToInt(error.A)); + comptime testErrToIntWithOnePossibleValue(error.A, @errorToInt(error.A)); } fn testErrToIntWithOnePossibleValue( x: error{A}, comptime value: u32, ) void { - if (u32(x) != value) { + if (@errorToInt(x) != value) { @compileError("bad"); } } diff --git a/test/cases/type_info.zig b/test/cases/type_info.zig index 1bc58b14e1..46fb3852f3 100644 --- a/test/cases/type_info.zig +++ b/test/cases/type_info.zig @@ -130,7 +130,7 @@ fn testErrorSet() void { assert(TypeId(error_set_info) == TypeId.ErrorSet); assert(error_set_info.ErrorSet.errors.len == 3); assert(mem.eql(u8, error_set_info.ErrorSet.errors[0].name, "First")); - assert(error_set_info.ErrorSet.errors[2].value == usize(TestErrorSet.Third)); + assert(error_set_info.ErrorSet.errors[2].value == @errorToInt(TestErrorSet.Third)); const error_union_info = @typeInfo(TestErrorSet!usize); assert(TypeId(error_union_info) == TypeId.ErrorUnion); diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 2f4a22553b..866b303082 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -467,25 +467,34 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { cases.add( "int to err global invalid number", - \\const Set1 = error{A, B}; + \\const Set1 = error{ + \\ A, + \\ B, + \\}; \\comptime { - \\ var x: usize = 3; - \\ var y = error(x); + \\ var x: u16 = 3; + \\ var y = @intToError(x); \\} , - ".tmp_source.zig:4:18: error: integer value 3 represents no error", + ".tmp_source.zig:7:13: error: integer value 3 represents no error", ); cases.add( "int to err non global invalid number", - \\const Set1 = error{A, B}; - \\const Set2 = error{A, C}; + \\const Set1 = error{ + \\ A, + \\ B, + \\}; + \\const Set2 = error{ + \\ A, + \\ C, + \\}; \\comptime { - \\ var x = usize(Set1.B); - \\ var y = Set2(x); + \\ var x = @errorToInt(Set1.B); + \\ var y = @errSetCast(Set2, @intToError(x)); \\} , - ".tmp_source.zig:5:17: error: integer value 2 represents no error in 'Set2'", + ".tmp_source.zig:11:13: error: error.B not a member of error set 'Set2'", ); cases.add( @@ -2612,17 +2621,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { ".tmp_source.zig:2:21: error: expected pointer, found 'usize'", ); - cases.add( - "too many error values to cast to small integer", - \\const Error = error { A, B, C, D, E, F, G, H }; - \\fn foo(e: Error) u2 { - \\ return u2(e); - \\} - \\export fn entry() usize { return @sizeOf(@typeOf(foo)); } - , - ".tmp_source.zig:3:14: error: too many error values to fit in 'u2'", - ); - cases.add( "asm at compile time", \\comptime { diff --git a/test/runtime_safety.zig b/test/runtime_safety.zig index 96384066e5..ea5beafe8d 100644 --- a/test/runtime_safety.zig +++ b/test/runtime_safety.zig @@ -175,7 +175,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ if (x.len == 0) return error.Whatever; \\} \\fn widenSlice(slice: []align(1) const u8) []align(1) const i32 { - \\ return ([]align(1) const i32)(slice); + \\ return @bytesToSlice(i32, slice); \\} ); @@ -227,12 +227,12 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\pub fn main() void { \\ _ = bar(9999); \\} - \\fn bar(x: u32) error { - \\ return error(x); + \\fn bar(x: u16) error { + \\ return @intToError(x); \\} ); - cases.addRuntimeSafety("cast integer to non-global error set and no match", + cases.addRuntimeSafety("@errSetCast error not present in destination", \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); \\} @@ -242,7 +242,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ _ = foo(Set1.B); \\} \\fn foo(set1: Set1) Set2 { - \\ return Set2(set1); + \\ return @errSetCast(Set2, set1); \\} ); @@ -252,12 +252,12 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\} \\pub fn main() !void { \\ var array align(4) = []u32{0x11111111, 0x11111111}; - \\ const bytes = ([]u8)(array[0..]); + \\ const bytes = @sliceToBytes(array[0..]); \\ if (foo(bytes) != 0x11111111) return error.Wrong; \\} \\fn foo(bytes: []u8) u32 { \\ const slice4 = bytes[1..5]; - \\ const int_slice = ([]u32)(@alignCast(4, slice4)); + \\ const int_slice = @bytesToSlice(u32, @alignCast(4, slice4)); \\ return int_slice[0]; \\} ); -- cgit v1.2.3 From a3ddd0826bd9c799768c0c707de72c21befa742a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 19 Jun 2018 03:50:38 -0400 Subject: remove enum to/from int casting syntax; add `@enumToInt`/`@intToEnum` see #1061 --- doc/langref.html.in | 34 +++++++++++--- src/all_types.hpp | 10 ++++ src/codegen.cpp | 3 ++ src/ir.cpp | 118 ++++++++++++++++++++++++++++++++++++++++++------ src/ir_print.cpp | 14 ++++++ std/json.zig | 2 +- test/cases/enum.zig | 16 +++---- test/cases/union.zig | 4 +- test/compile_errors.zig | 24 ++-------- 9 files changed, 174 insertions(+), 51 deletions(-) (limited to 'src/codegen.cpp') diff --git a/doc/langref.html.in b/doc/langref.html.in index 24bf6e1b16..19f023f6e1 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -1900,9 +1900,9 @@ const Value = enum(u2) { // Now you can cast between u2 and Value. // The ordinal value starts from 0, counting up for each member. test "enum ordinal value" { - assert(u2(Value.Zero) == 0); - assert(u2(Value.One) == 1); - assert(u2(Value.Two) == 2); + assert(@enumToInt(Value.Zero) == 0); + assert(@enumToInt(Value.One) == 1); + assert(@enumToInt(Value.Two) == 2); } // You can override the ordinal value for an enum. @@ -1912,9 +1912,9 @@ const Value2 = enum(u32) { Million = 1000000, }; test "set enum ordinal value" { - assert(u32(Value2.Hundred) == 100); - assert(u32(Value2.Thousand) == 1000); - assert(u32(Value2.Million) == 1000000); + assert(@enumToInt(Value2.Hundred) == 100); + assert(@enumToInt(Value2.Thousand) == 1000); + assert(@enumToInt(Value2.Million) == 1000000); } // Enums can have methods, the same as structs and unions. @@ -4931,6 +4931,14 @@ test "main" { {#see_also|@import#} {#header_close#} + {#header_open|@enumToInt#} +
@enumToInt(enum_value: var) var
+

+ Converts an enumeration value into its integer tag type. +

+ {#see_also|@intToEnum#} + {#header_close#} + {#header_open|@errSetCast#}
@errSetCast(comptime T: DestType, value: var) DestType

@@ -5095,6 +5103,18 @@ fn add(a: i32, b: i32) i32 { return a + b; }

{#header_close#} + {#header_open|@intToEnum#} +
@intToEnum(comptime DestType: type, int_value: @TagType(DestType)) DestType
+

+ Converts an integer into an {#link|enum#} value. +

+

+ Attempting to convert an integer which represents no value in the chosen enum type invokes + safety-checked {#link|Undefined Behavior#}. +

+ {#see_also|@enumToInt#} + {#header_close#} + {#header_open|@intToError#}
@intToError(value: @IntType(false, @sizeOf(error) * 8)) error

@@ -6867,7 +6887,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", + 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", literal: "true false null undefined" }, n = [e, t.CLCM, t.CBCM, s, r]; diff --git a/src/all_types.hpp b/src/all_types.hpp index e1a4ed7510..41c9fe18c3 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1378,6 +1378,8 @@ enum BuiltinFnId { BuiltinFnIdBoolToInt, BuiltinFnIdErrToInt, BuiltinFnIdIntToErr, + BuiltinFnIdEnumToInt, + BuiltinFnIdIntToEnum, BuiltinFnIdIntType, BuiltinFnIdSetCold, BuiltinFnIdSetRuntimeSafety, @@ -2092,6 +2094,7 @@ enum IrInstructionId { IrInstructionIdIntToPtr, IrInstructionIdPtrToInt, IrInstructionIdIntToEnum, + IrInstructionIdEnumToInt, IrInstructionIdIntToErr, IrInstructionIdErrToInt, IrInstructionIdCheckSwitchProngs, @@ -2905,6 +2908,13 @@ struct IrInstructionIntToPtr { struct IrInstructionIntToEnum { IrInstruction base; + IrInstruction *dest_type; + IrInstruction *target; +}; + +struct IrInstructionEnumToInt { + IrInstruction base; + IrInstruction *target; }; diff --git a/src/codegen.cpp b/src/codegen.cpp index c9b4ade4c6..21520e0dd0 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -4730,6 +4730,7 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, case IrInstructionIdErrSetCast: case IrInstructionIdFromBytes: case IrInstructionIdToBytes: + case IrInstructionIdEnumToInt: zig_unreachable(); case IrInstructionIdReturn: @@ -6325,6 +6326,8 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdBoolToInt, "boolToInt", 1); create_builtin_fn(g, BuiltinFnIdErrToInt, "errorToInt", 1); create_builtin_fn(g, BuiltinFnIdIntToErr, "intToError", 1); + create_builtin_fn(g, BuiltinFnIdEnumToInt, "enumToInt", 1); + create_builtin_fn(g, BuiltinFnIdIntToEnum, "intToEnum", 2); create_builtin_fn(g, BuiltinFnIdCompileErr, "compileError", 1); create_builtin_fn(g, BuiltinFnIdCompileLog, "compileLog", SIZE_MAX); create_builtin_fn(g, BuiltinFnIdIntType, "IntType", 2); // TODO rename to Int diff --git a/src/ir.cpp b/src/ir.cpp index 350c80017e..a8599e7aae 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -600,6 +600,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionIntToEnum *) { return IrInstructionIdIntToEnum; } +static constexpr IrInstructionId ir_instruction_id(IrInstructionEnumToInt *) { + return IrInstructionIdEnumToInt; +} + static constexpr IrInstructionId ir_instruction_id(IrInstructionIntToErr *) { return IrInstructionIdIntToErr; } @@ -2378,10 +2382,26 @@ static IrInstruction *ir_build_ptr_to_int(IrBuilder *irb, Scope *scope, AstNode } static IrInstruction *ir_build_int_to_enum(IrBuilder *irb, Scope *scope, AstNode *source_node, - IrInstruction *target) + IrInstruction *dest_type, IrInstruction *target) { IrInstructionIntToEnum *instruction = ir_build_instruction( irb, scope, source_node); + instruction->dest_type = dest_type; + instruction->target = target; + + if (dest_type) ir_ref_instruction(dest_type, irb->current_basic_block); + ir_ref_instruction(target, irb->current_basic_block); + + return &instruction->base; +} + + + +static IrInstruction *ir_build_enum_to_int(IrBuilder *irb, Scope *scope, AstNode *source_node, + IrInstruction *target) +{ + IrInstructionEnumToInt *instruction = ir_build_instruction( + irb, scope, source_node); instruction->target = target; ir_ref_instruction(target, irb->current_basic_block); @@ -4708,6 +4728,31 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo // this value does not mean anything since we passed non-null values for other arg AtomicOrderMonotonic); } + case BuiltinFnIdIntToEnum: + { + AstNode *arg0_node = node->data.fn_call_expr.params.at(0); + IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope); + if (arg0_value == irb->codegen->invalid_instruction) + return arg0_value; + + AstNode *arg1_node = node->data.fn_call_expr.params.at(1); + IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope); + if (arg1_value == irb->codegen->invalid_instruction) + return arg1_value; + + IrInstruction *result = ir_build_int_to_enum(irb, scope, node, arg0_value, arg1_value); + return ir_lval_wrap(irb, scope, result, lval); + } + case BuiltinFnIdEnumToInt: + { + AstNode *arg0_node = node->data.fn_call_expr.params.at(0); + IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope); + if (arg0_value == irb->codegen->invalid_instruction) + return arg0_value; + + IrInstruction *result = ir_build_enum_to_int(irb, scope, node, arg0_value); + return ir_lval_wrap(irb, scope, result, lval); + } } zig_unreachable(); } @@ -9951,7 +9996,7 @@ static IrInstruction *ir_analyze_int_to_enum(IrAnalyze *ira, IrInstruction *sour } IrInstruction *result = ir_build_int_to_enum(&ira->new_irb, source_instr->scope, - source_instr->source_node, target); + source_instr->source_node, nullptr, target); result->value.type = wanted_type; return result; } @@ -10485,16 +10530,6 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst return ir_analyze_number_to_literal(ira, source_instr, value, wanted_type); } - // explicit cast from integer to enum type with no payload - if (actual_type->id == TypeTableEntryIdInt && wanted_type->id == TypeTableEntryIdEnum) { - return ir_analyze_int_to_enum(ira, source_instr, value, wanted_type); - } - - // explicit cast from enum type with no payload to integer - if (wanted_type->id == TypeTableEntryIdInt && actual_type->id == TypeTableEntryIdEnum) { - return ir_analyze_enum_to_int(ira, source_instr, value, wanted_type); - } - // explicit cast from union to the enum type of the union if (actual_type->id == TypeTableEntryIdUnion && wanted_type->id == TypeTableEntryIdEnum) { type_ensure_zero_bits_known(ira->codegen, actual_type); @@ -20262,11 +20297,63 @@ static TypeTableEntry *ir_analyze_instruction_sqrt(IrAnalyze *ira, IrInstruction return result->value.type; } +static TypeTableEntry *ir_analyze_instruction_enum_to_int(IrAnalyze *ira, IrInstructionEnumToInt *instruction) { + IrInstruction *target = instruction->target->other; + if (type_is_invalid(target->value.type)) + return ira->codegen->builtin_types.entry_invalid; + + if (target->value.type->id != TypeTableEntryIdEnum) { + ir_add_error(ira, instruction->target, + buf_sprintf("expected enum, found type '%s'", buf_ptr(&target->value.type->name))); + return ira->codegen->builtin_types.entry_invalid; + } + + type_ensure_zero_bits_known(ira->codegen, target->value.type); + if (type_is_invalid(target->value.type)) + return ira->codegen->builtin_types.entry_invalid; + + TypeTableEntry *tag_type = target->value.type->data.enumeration.tag_int_type; + + IrInstruction *result = ir_analyze_enum_to_int(ira, &instruction->base, target, tag_type); + ir_link_new_instruction(result, &instruction->base); + return result->value.type; +} + +static TypeTableEntry *ir_analyze_instruction_int_to_enum(IrAnalyze *ira, IrInstructionIntToEnum *instruction) { + IrInstruction *dest_type_value = instruction->dest_type->other; + TypeTableEntry *dest_type = ir_resolve_type(ira, dest_type_value); + if (type_is_invalid(dest_type)) + return ira->codegen->builtin_types.entry_invalid; + + if (dest_type->id != TypeTableEntryIdEnum) { + ir_add_error(ira, instruction->dest_type, + buf_sprintf("expected enum, found type '%s'", buf_ptr(&dest_type->name))); + return ira->codegen->builtin_types.entry_invalid; + } + + type_ensure_zero_bits_known(ira->codegen, dest_type); + if (type_is_invalid(dest_type)) + return ira->codegen->builtin_types.entry_invalid; + + TypeTableEntry *tag_type = dest_type->data.enumeration.tag_int_type; + + IrInstruction *target = instruction->target->other; + if (type_is_invalid(target->value.type)) + return ira->codegen->builtin_types.entry_invalid; + + IrInstruction *casted_target = ir_implicit_cast(ira, target, tag_type); + if (type_is_invalid(casted_target->value.type)) + return ira->codegen->builtin_types.entry_invalid; + + IrInstruction *result = ir_analyze_int_to_enum(ira, &instruction->base, casted_target, dest_type); + ir_link_new_instruction(result, &instruction->base); + return result->value.type; +} + static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstruction *instruction) { switch (instruction->id) { case IrInstructionIdInvalid: case IrInstructionIdWidenOrShorten: - case IrInstructionIdIntToEnum: case IrInstructionIdStructInit: case IrInstructionIdUnionInit: case IrInstructionIdStructFieldPtr: @@ -20531,6 +20618,10 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi return ir_analyze_instruction_int_to_err(ira, (IrInstructionIntToErr *)instruction); case IrInstructionIdErrToInt: return ir_analyze_instruction_err_to_int(ira, (IrInstructionErrToInt *)instruction); + case IrInstructionIdIntToEnum: + return ir_analyze_instruction_int_to_enum(ira, (IrInstructionIntToEnum *)instruction); + case IrInstructionIdEnumToInt: + return ir_analyze_instruction_enum_to_int(ira, (IrInstructionEnumToInt *)instruction); } zig_unreachable(); } @@ -20754,6 +20845,7 @@ bool ir_has_side_effects(IrInstruction *instruction) { case IrInstructionIdBoolToInt: case IrInstructionIdFromBytes: case IrInstructionIdToBytes: + case IrInstructionIdEnumToInt: return false; case IrInstructionIdAsm: diff --git a/src/ir_print.cpp b/src/ir_print.cpp index 1b35ecf57f..5e5a71382c 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -928,6 +928,17 @@ static void ir_print_int_to_ptr(IrPrint *irp, IrInstructionIntToPtr *instruction static void ir_print_int_to_enum(IrPrint *irp, IrInstructionIntToEnum *instruction) { fprintf(irp->f, "@intToEnum("); + if (instruction->dest_type == nullptr) { + fprintf(irp->f, "(null)"); + } else { + ir_print_other_instruction(irp, instruction->dest_type); + } + ir_print_other_instruction(irp, instruction->target); + fprintf(irp->f, ")"); +} + +static void ir_print_enum_to_int(IrPrint *irp, IrInstructionEnumToInt *instruction) { + fprintf(irp->f, "@enumToInt("); ir_print_other_instruction(irp, instruction->target); fprintf(irp->f, ")"); } @@ -1717,6 +1728,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { case IrInstructionIdAtomicLoad: ir_print_atomic_load(irp, (IrInstructionAtomicLoad *)instruction); break; + case IrInstructionIdEnumToInt: + ir_print_enum_to_int(irp, (IrInstructionEnumToInt *)instruction); + break; } fprintf(irp->f, "\n"); } diff --git a/std/json.zig b/std/json.zig index 2930cd21bb..8986034fb4 100644 --- a/std/json.zig +++ b/std/json.zig @@ -180,7 +180,7 @@ pub const StreamingParser = struct { pub fn fromInt(x: var) State { debug.assert(x == 0 or x == 1); const T = @TagType(State); - return State(@intCast(T, x)); + return @intToEnum(State, @intCast(T, x)); } }; diff --git a/test/cases/enum.zig b/test/cases/enum.zig index 6a02a47784..50edfda536 100644 --- a/test/cases/enum.zig +++ b/test/cases/enum.zig @@ -92,14 +92,14 @@ test "enum to int" { } fn shouldEqual(n: Number, expected: u3) void { - assert(u3(n) == expected); + assert(@enumToInt(n) == expected); } test "int to enum" { testIntToEnumEval(3); } fn testIntToEnumEval(x: i32) void { - assert(IntToEnumNumber(@intCast(u3, x)) == IntToEnumNumber.Three); + assert(@intToEnum(IntToEnumNumber, @intCast(u3, x)) == IntToEnumNumber.Three); } const IntToEnumNumber = enum { Zero, @@ -768,7 +768,7 @@ test "casting enum to its tag type" { } fn testCastEnumToTagType(value: Small2) void { - assert(u2(value) == 1); + assert(@enumToInt(value) == 1); } const MultipleChoice = enum(u32) { @@ -784,7 +784,7 @@ test "enum with specified tag values" { } fn testEnumWithSpecifiedTagValues(x: MultipleChoice) void { - assert(u32(x) == 60); + assert(@enumToInt(x) == 60); assert(1234 == switch (x) { MultipleChoice.A => 1, MultipleChoice.B => 2, @@ -811,7 +811,7 @@ test "enum with specified and unspecified tag values" { } fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: MultipleChoice2) void { - assert(u32(x) == 1000); + assert(@enumToInt(x) == 1000); assert(1234 == switch (x) { MultipleChoice2.A => 1, MultipleChoice2.B => 2, @@ -826,8 +826,8 @@ fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: MultipleChoice2) void { } test "cast integer literal to enum" { - assert(MultipleChoice2(0) == MultipleChoice2.Unspecified1); - assert(MultipleChoice2(40) == MultipleChoice2.B); + assert(@intToEnum(MultipleChoice2, 0) == MultipleChoice2.Unspecified1); + assert(@intToEnum(MultipleChoice2, 40) == MultipleChoice2.B); } const EnumWithOneMember = enum { @@ -865,7 +865,7 @@ const EnumWithTagValues = enum(u4) { D = 1 << 3, }; test "enum with tag values don't require parens" { - assert(u4(EnumWithTagValues.C) == 0b0100); + assert(@enumToInt(EnumWithTagValues.C) == 0b0100); } test "enum with 1 field but explicit tag type should still have the tag type" { diff --git a/test/cases/union.zig b/test/cases/union.zig index bdcbbdb452..78b2dc8dd7 100644 --- a/test/cases/union.zig +++ b/test/cases/union.zig @@ -126,7 +126,7 @@ const MultipleChoice = union(enum(u32)) { test "simple union(enum(u32))" { var x = MultipleChoice.C; assert(x == MultipleChoice.C); - assert(u32(@TagType(MultipleChoice)(x)) == 60); + assert(@enumToInt(@TagType(MultipleChoice)(x)) == 60); } const MultipleChoice2 = union(enum(u32)) { @@ -148,7 +148,7 @@ test "union(enum(u32)) with specified and unspecified tag values" { } fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: *const MultipleChoice2) void { - assert(u32(@TagType(MultipleChoice2)(x.*)) == 60); + assert(@enumToInt(@TagType(MultipleChoice2)(x.*)) == 60); assert(1123 == switch (x.*) { MultipleChoice2.A => 1, MultipleChoice2.B => 2, diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 866b303082..609e3f103f 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -3709,22 +3709,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { ".tmp_source.zig:9:22: error: expected type 'u2', found 'Small'", ); - cases.add( - "explicitly casting enum to non tag type", - \\const Small = enum(u2) { - \\ One, - \\ Two, - \\ Three, - \\ Four, - \\}; - \\ - \\export fn entry() void { - \\ var x = u3(Small.Two); - \\} - , - ".tmp_source.zig:9:15: error: enum to integer cast to 'u3' instead of its tag type, 'u2'", - ); - cases.add( "explicitly casting non tag type to enum", \\const Small = enum(u2) { @@ -3736,10 +3720,10 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\ \\export fn entry() void { \\ var y = u3(3); - \\ var x = Small(y); + \\ var x = @intToEnum(Small, y); \\} , - ".tmp_source.zig:10:18: error: integer to enum cast from 'u3' instead of its tag type, 'u2'", + ".tmp_source.zig:10:31: error: expected type 'u2', found 'u3'", ); cases.add( @@ -4020,10 +4004,10 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\ B = 11, \\}; \\export fn entry() void { - \\ var x = Foo(0); + \\ var x = @intToEnum(Foo, 0); \\} , - ".tmp_source.zig:6:16: error: enum 'Foo' has no tag matching integer value 0", + ".tmp_source.zig:6:13: error: enum 'Foo' has no tag matching integer value 0", ".tmp_source.zig:1:13: note: 'Foo' declared here", ); -- cgit v1.2.3 From 13923132363b26b9982951fd79f01dbab0046e81 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Tue, 19 Jun 2018 17:45:19 +0300 Subject: @typeInfo now uses optional types instead of @typeOf(undefined) --- doc/langref.html.in | 8 +++--- src/codegen.cpp | 10 +++---- src/ir.cpp | 75 +++++++++++++++++++++++++++++++----------------- test/cases/type_info.zig | 16 +++++------ 4 files changed, 65 insertions(+), 44 deletions(-) (limited to 'src/codegen.cpp') diff --git a/doc/langref.html.in b/doc/langref.html.in index f1ae2bafaa..551f3ff769 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -5684,20 +5684,20 @@ pub const TypeInfo = union(TypeId) { pub const FnArg = struct { is_generic: bool, is_noalias: bool, - arg_type: type, + arg_type: ?type, }; pub const Fn = struct { calling_convention: CallingConvention, is_generic: bool, is_var_args: bool, - return_type: type, - async_allocator_type: type, + return_type: ?type, + async_allocator_type: ?type, args: []FnArg, }; pub const Promise = struct { - child: type, + child: ?type, }; pub const Definition = struct { diff --git a/src/codegen.cpp b/src/codegen.cpp index 84335d4e06..dcb5e8cb46 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -6627,7 +6627,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) { "\n" " pub const Union = struct {\n" " layout: ContainerLayout,\n" - " tag_type: type,\n" + " tag_type: ?type,\n" " fields: []UnionField,\n" " defs: []Definition,\n" " };\n" @@ -6644,20 +6644,20 @@ Buf *codegen_generate_builtin_source(CodeGen *g) { " pub const FnArg = struct {\n" " is_generic: bool,\n" " is_noalias: bool,\n" - " arg_type: type,\n" + " arg_type: ?type,\n" " };\n" "\n" " pub const Fn = struct {\n" " calling_convention: CallingConvention,\n" " is_generic: bool,\n" " is_var_args: bool,\n" - " return_type: type,\n" - " async_allocator_type: type,\n" + " return_type: ?type,\n" + " async_allocator_type: ?type,\n" " args: []FnArg,\n" " };\n" "\n" " pub const Promise = struct {\n" - " child: type,\n" + " child: ?type,\n" " };\n" "\n" " pub const Definition = struct {\n" diff --git a/src/ir.cpp b/src/ir.cpp index c75a3ae7c1..433637513e 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -16722,16 +16722,20 @@ static ConstExprValue *ir_make_type_info_value(IrAnalyze *ira, TypeTableEntry *t ConstExprValue *fields = create_const_vals(1); result->data.x_struct.fields = fields; - // @TODO ?type instead of using @typeOf(undefined) when we have no type. - // child: type + // child: ?type ensure_field_index(result->type, "child", 0); fields[0].special = ConstValSpecialStatic; - fields[0].type = ira->codegen->builtin_types.entry_type; + fields[0].type = get_maybe_type(ira->codegen, ira->codegen->builtin_types.entry_type); if (type_entry->data.promise.result_type == nullptr) - fields[0].data.x_type = ira->codegen->builtin_types.entry_undef; - else - fields[0].data.x_type = type_entry->data.promise.result_type; + fields[0].data.x_optional = nullptr; + else { + ConstExprValue *child_type = create_const_vals(1); + child_type->special = ConstValSpecialStatic; + child_type->type = ira->codegen->builtin_types.entry_type; + child_type->data.x_type = type_entry->data.promise.result_type; + fields[0].data.x_optional = child_type; + } break; } @@ -16872,19 +16876,23 @@ static ConstExprValue *ir_make_type_info_value(IrAnalyze *ira, TypeTableEntry *t fields[0].special = ConstValSpecialStatic; fields[0].type = ir_type_info_get_type(ira, "ContainerLayout"); bigint_init_unsigned(&fields[0].data.x_enum_tag, type_entry->data.unionation.layout); - // tag_type: type + // tag_type: ?type ensure_field_index(result->type, "tag_type", 1); fields[1].special = ConstValSpecialStatic; - fields[1].type = ira->codegen->builtin_types.entry_type; - // @TODO ?type instead of using @typeOf(undefined) when we have no type. + fields[1].type = get_maybe_type(ira->codegen, ira->codegen->builtin_types.entry_type); + AstNode *union_decl_node = type_entry->data.unionation.decl_node; if (union_decl_node->data.container_decl.auto_enum || union_decl_node->data.container_decl.init_arg_expr != nullptr) { - fields[1].data.x_type = type_entry->data.unionation.tag_type; + ConstExprValue *tag_type = create_const_vals(1); + tag_type->special = ConstValSpecialStatic; + tag_type->type = ira->codegen->builtin_types.entry_type; + tag_type->data.x_type = type_entry->data.unionation.tag_type; + fields[1].data.x_optional = tag_type; } else - fields[1].data.x_type = ira->codegen->builtin_types.entry_undef; + fields[1].data.x_optional = nullptr; // fields: []TypeInfo.UnionField ensure_field_index(result->type, "fields", 2); @@ -16913,7 +16921,7 @@ static ConstExprValue *ir_make_type_info_value(IrAnalyze *ira, TypeTableEntry *t inner_fields[1].special = ConstValSpecialStatic; inner_fields[1].type = get_maybe_type(ira->codegen, type_info_enum_field_type); - if (fields[1].data.x_type == ira->codegen->builtin_types.entry_undef) { + if (fields[1].data.x_optional == nullptr) { inner_fields[1].data.x_optional = nullptr; } else { inner_fields[1].data.x_optional = create_const_vals(1); @@ -17022,8 +17030,6 @@ static ConstExprValue *ir_make_type_info_value(IrAnalyze *ira, TypeTableEntry *t ConstExprValue *fields = create_const_vals(6); result->data.x_struct.fields = fields; - // @TODO Fix type = undefined with ?type - // calling_convention: TypeInfo.CallingConvention ensure_field_index(result->type, "calling_convention", 0); fields[0].special = ConstValSpecialStatic; @@ -17041,22 +17047,32 @@ static ConstExprValue *ir_make_type_info_value(IrAnalyze *ira, TypeTableEntry *t fields[2].special = ConstValSpecialStatic; fields[2].type = ira->codegen->builtin_types.entry_bool; fields[2].data.x_bool = type_entry->data.fn.fn_type_id.is_var_args; - // return_type: type + // return_type: ?type ensure_field_index(result->type, "return_type", 3); fields[3].special = ConstValSpecialStatic; - fields[3].type = ira->codegen->builtin_types.entry_type; + fields[3].type = get_maybe_type(ira->codegen, ira->codegen->builtin_types.entry_type); if (type_entry->data.fn.fn_type_id.return_type == nullptr) - fields[3].data.x_type = ira->codegen->builtin_types.entry_undef; - else - fields[3].data.x_type = type_entry->data.fn.fn_type_id.return_type; + fields[3].data.x_optional = nullptr; + else { + ConstExprValue *return_type = create_const_vals(1); + return_type->special = ConstValSpecialStatic; + return_type->type = ira->codegen->builtin_types.entry_type; + return_type->data.x_type = type_entry->data.fn.fn_type_id.return_type; + fields[3].data.x_optional = return_type; + } // async_allocator_type: type ensure_field_index(result->type, "async_allocator_type", 4); fields[4].special = ConstValSpecialStatic; - fields[4].type = ira->codegen->builtin_types.entry_type; + fields[4].type = get_maybe_type(ira->codegen, ira->codegen->builtin_types.entry_type); if (type_entry->data.fn.fn_type_id.async_allocator_type == nullptr) - fields[4].data.x_type = ira->codegen->builtin_types.entry_undef; - else - fields[4].data.x_type = type_entry->data.fn.fn_type_id.async_allocator_type; + fields[4].data.x_optional = nullptr; + else { + ConstExprValue *async_alloc_type = create_const_vals(1); + async_alloc_type->special = ConstValSpecialStatic; + async_alloc_type->type = ira->codegen->builtin_types.entry_type; + async_alloc_type->data.x_type = type_entry->data.fn.fn_type_id.async_allocator_type; + fields[4].data.x_optional = async_alloc_type; + } // args: []TypeInfo.FnArg TypeTableEntry *type_info_fn_arg_type = ir_type_info_get_type(ira, "FnArg"); size_t fn_arg_count = type_entry->data.fn.fn_type_id.param_count - @@ -17090,12 +17106,17 @@ static ConstExprValue *ir_make_type_info_value(IrAnalyze *ira, TypeTableEntry *t inner_fields[1].type = ira->codegen->builtin_types.entry_bool; inner_fields[1].data.x_bool = fn_param_info->is_noalias; inner_fields[2].special = ConstValSpecialStatic; - inner_fields[2].type = ira->codegen->builtin_types.entry_type; + inner_fields[2].type = get_maybe_type(ira->codegen, ira->codegen->builtin_types.entry_type); if (arg_is_generic) - inner_fields[2].data.x_type = ira->codegen->builtin_types.entry_undef; - else - inner_fields[2].data.x_type = fn_param_info->type; + inner_fields[2].data.x_optional = nullptr; + else { + ConstExprValue *arg_type = create_const_vals(1); + arg_type->special = ConstValSpecialStatic; + arg_type->type = ira->codegen->builtin_types.entry_type; + arg_type->data.x_type = fn_param_info->type; + inner_fields[2].data.x_optional = arg_type; + } fn_arg_val->data.x_struct.fields = inner_fields; fn_arg_val->data.x_struct.parent.id = ConstParentIdArray; diff --git a/test/cases/type_info.zig b/test/cases/type_info.zig index 1bc58b14e1..ab4f3678e8 100644 --- a/test/cases/type_info.zig +++ b/test/cases/type_info.zig @@ -107,11 +107,11 @@ test "type info: promise info" { fn testPromise() void { const null_promise_info = @typeInfo(promise); assert(TypeId(null_promise_info) == TypeId.Promise); - assert(null_promise_info.Promise.child == @typeOf(undefined)); + assert(null_promise_info.Promise.child == null); const promise_info = @typeInfo(promise->usize); assert(TypeId(promise_info) == TypeId.Promise); - assert(promise_info.Promise.child == usize); + assert(promise_info.Promise.child.? == usize); } test "type info: error set, error union info" { @@ -165,7 +165,7 @@ fn testUnion() void { const typeinfo_info = @typeInfo(TypeInfo); assert(TypeId(typeinfo_info) == TypeId.Union); assert(typeinfo_info.Union.layout == TypeInfo.ContainerLayout.Auto); - assert(typeinfo_info.Union.tag_type == TypeId); + assert(typeinfo_info.Union.tag_type.? == TypeId); assert(typeinfo_info.Union.fields.len == 25); assert(typeinfo_info.Union.fields[4].enum_field != null); assert(typeinfo_info.Union.fields[4].enum_field.?.value == 4); @@ -179,7 +179,7 @@ fn testUnion() void { const notag_union_info = @typeInfo(TestNoTagUnion); assert(TypeId(notag_union_info) == TypeId.Union); - assert(notag_union_info.Union.tag_type == @typeOf(undefined)); + assert(notag_union_info.Union.tag_type == null); assert(notag_union_info.Union.layout == TypeInfo.ContainerLayout.Auto); assert(notag_union_info.Union.fields.len == 2); assert(notag_union_info.Union.fields[0].enum_field == null); @@ -191,7 +191,7 @@ fn testUnion() void { const extern_union_info = @typeInfo(TestExternUnion); assert(extern_union_info.Union.layout == TypeInfo.ContainerLayout.Extern); - assert(extern_union_info.Union.tag_type == @typeOf(undefined)); + assert(extern_union_info.Union.tag_type == null); assert(extern_union_info.Union.fields[0].enum_field == null); assert(extern_union_info.Union.fields[0].field_type == *c_void); } @@ -238,13 +238,13 @@ fn testFunction() void { assert(fn_info.Fn.is_generic); assert(fn_info.Fn.args.len == 2); assert(fn_info.Fn.is_var_args); - assert(fn_info.Fn.return_type == @typeOf(undefined)); - assert(fn_info.Fn.async_allocator_type == @typeOf(undefined)); + assert(fn_info.Fn.return_type == null); + assert(fn_info.Fn.async_allocator_type == null); const test_instance: TestStruct = undefined; const bound_fn_info = @typeInfo(@typeOf(test_instance.foo)); assert(TypeId(bound_fn_info) == TypeId.BoundFn); - assert(bound_fn_info.BoundFn.args[0].arg_type == *const TestStruct); + assert(bound_fn_info.BoundFn.args[0].arg_type.? == *const TestStruct); } fn foo(comptime a: usize, b: bool, args: ...) usize { -- cgit v1.2.3 From c7804277bf390eeba368e3565b2aff0cf96f86b0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 19 Jun 2018 16:06:10 -0400 Subject: `@floatToInt` now has safety-checked undefined behavior when the integer part does not fit in the destination integer type * Also fix incorrect safety triggered for integer casting an `i32` to a `u7`. closes #1138 * adds compiler-rt function: `__floatuntidf` --- CMakeLists.txt | 1 + doc/langref.html.in | 9 +++- src/all_types.hpp | 1 + src/codegen.cpp | 36 +++++++++++-- src/ir.cpp | 25 +++++++-- std/math/expm1.zig | 8 +++ std/special/compiler_rt/floatuntidf.zig | 60 +++++++++++++++++++++ std/special/compiler_rt/floatuntidf_test.zig | 81 ++++++++++++++++++++++++++++ std/special/compiler_rt/index.zig | 2 + test/cases/cast.zig | 21 +++++++- test/compile_errors.zig | 17 ++++++ test/runtime_safety.zig | 39 ++++++++++++++ 12 files changed, 291 insertions(+), 9 deletions(-) create mode 100644 std/special/compiler_rt/floatuntidf.zig create mode 100644 std/special/compiler_rt/floatuntidf_test.zig (limited to 'src/codegen.cpp') diff --git a/CMakeLists.txt b/CMakeLists.txt index e502901bd2..030398d71c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -568,6 +568,7 @@ set(ZIG_STD_FILES "special/compiler_rt/fixunstfdi.zig" "special/compiler_rt/fixunstfsi.zig" "special/compiler_rt/fixunstfti.zig" + "special/compiler_rt/floatuntidf.zig" "special/compiler_rt/muloti4.zig" "special/compiler_rt/index.zig" "special/compiler_rt/udivmod.zig" diff --git a/doc/langref.html.in b/doc/langref.html.in index 4070ef0ac0..1bd28f9c3e 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -5035,8 +5035,12 @@ test "main" {

@floatToInt(comptime DestType: type, float: var) DestType

Converts the integer part of a floating point number to the destination type. - To convert the other way, use {#link|@intToFloat#}. This cast is always safe.

+

+ If the integer part of the floating point number cannot fit in the destination type, + it invokes safety-checked {#link|Undefined Behavior#}. +

+ {#see_also|@intToFloat#} {#header_close#} {#header_open|@frameAddress#} @@ -6207,7 +6211,10 @@ comptime { {#header_close#} {#header_open|Wrong Union Field Access#}

TODO

+ {#header_close#} + {#header_open|Out of Bounds Float To Integer Cast#} +

TODO

{#header_close#} {#header_close#} diff --git a/src/all_types.hpp b/src/all_types.hpp index 41c9fe18c3..12e054cbeb 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1434,6 +1434,7 @@ enum PanicMsgId { PanicMsgIdIncorrectAlignment, PanicMsgIdBadUnionField, PanicMsgIdBadEnumValue, + PanicMsgIdFloatToInt, PanicMsgIdCount, }; diff --git a/src/codegen.cpp b/src/codegen.cpp index 5f7fd60392..20268d636a 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -865,6 +865,8 @@ static Buf *panic_msg_buf(PanicMsgId msg_id) { return buf_create_from_str("access of inactive union field"); case PanicMsgIdBadEnumValue: return buf_create_from_str("invalid enum value"); + case PanicMsgIdFloatToInt: + return buf_create_from_str("integer part of floating point value out of bounds"); } zig_unreachable(); } @@ -1671,7 +1673,7 @@ static LLVMValueRef gen_widen_or_shorten(CodeGen *g, bool want_runtime_safety, T return trunc_val; } LLVMValueRef orig_val; - if (actual_type->data.integral.is_signed) { + if (wanted_type->data.integral.is_signed) { orig_val = LLVMBuildSExt(g->builder, trunc_val, actual_type->type_ref, ""); } else { orig_val = LLVMBuildZExt(g->builder, trunc_val, actual_type->type_ref, ""); @@ -2546,15 +2548,41 @@ static LLVMValueRef ir_render_cast(CodeGen *g, IrExecutable *executable, } else { return LLVMBuildUIToFP(g->builder, expr_val, wanted_type->type_ref, ""); } - case CastOpFloatToInt: + case CastOpFloatToInt: { assert(wanted_type->id == TypeTableEntryIdInt); ZigLLVMSetFastMath(g->builder, ir_want_fast_math(g, &cast_instruction->base)); + + bool want_safety = ir_want_runtime_safety(g, &cast_instruction->base); + + LLVMValueRef result; if (wanted_type->data.integral.is_signed) { - return LLVMBuildFPToSI(g->builder, expr_val, wanted_type->type_ref, ""); + result = LLVMBuildFPToSI(g->builder, expr_val, wanted_type->type_ref, ""); } else { - return LLVMBuildFPToUI(g->builder, expr_val, wanted_type->type_ref, ""); + result = LLVMBuildFPToUI(g->builder, expr_val, wanted_type->type_ref, ""); } + if (want_safety) { + LLVMValueRef back_to_float; + if (wanted_type->data.integral.is_signed) { + back_to_float = LLVMBuildSIToFP(g->builder, result, LLVMTypeOf(expr_val), ""); + } else { + back_to_float = LLVMBuildUIToFP(g->builder, result, LLVMTypeOf(expr_val), ""); + } + LLVMValueRef difference = LLVMBuildFSub(g->builder, expr_val, back_to_float, ""); + LLVMValueRef one_pos = LLVMConstReal(LLVMTypeOf(expr_val), 1.0f); + LLVMValueRef one_neg = LLVMConstReal(LLVMTypeOf(expr_val), -1.0f); + LLVMValueRef ok_bit_pos = LLVMBuildFCmp(g->builder, LLVMRealOLT, difference, one_pos, ""); + LLVMValueRef ok_bit_neg = LLVMBuildFCmp(g->builder, LLVMRealOGT, difference, one_neg, ""); + LLVMValueRef ok_bit = LLVMBuildAnd(g->builder, ok_bit_pos, ok_bit_neg, ""); + LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "FloatCheckOk"); + LLVMBasicBlockRef bad_block = LLVMAppendBasicBlock(g->cur_fn_val, "FloatCheckFail"); + LLVMBuildCondBr(g->builder, ok_bit, ok_block, bad_block); + LLVMPositionBuilderAtEnd(g->builder, bad_block); + gen_safety_crash(g, PanicMsgIdFloatToInt); + LLVMPositionBuilderAtEnd(g->builder, ok_block); + } + return result; + } case CastOpBoolToInt: assert(wanted_type->id == TypeTableEntryIdInt); assert(actual_type->id == TypeTableEntryIdBool); diff --git a/src/ir.cpp b/src/ir.cpp index 55da8ad944..1fe078dd3f 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -9057,7 +9057,8 @@ static void copy_const_val(ConstExprValue *dest, ConstExprValue *src, bool same_ } } -static void eval_const_expr_implicit_cast(CastOp cast_op, +static bool eval_const_expr_implicit_cast(IrAnalyze *ira, IrInstruction *source_instr, + CastOp cast_op, ConstExprValue *other_val, TypeTableEntry *other_type, ConstExprValue *const_val, TypeTableEntry *new_type) { @@ -9129,6 +9130,20 @@ static void eval_const_expr_implicit_cast(CastOp cast_op, } case CastOpFloatToInt: float_init_bigint(&const_val->data.x_bigint, other_val); + if (new_type->id == TypeTableEntryIdInt) { + if (!bigint_fits_in_bits(&const_val->data.x_bigint, new_type->data.integral.bit_count, + new_type->data.integral.is_signed)) + { + Buf *int_buf = buf_alloc(); + bigint_append_buf(int_buf, &const_val->data.x_bigint, 10); + + ir_add_error(ira, source_instr, + buf_sprintf("integer value '%s' cannot be stored in type '%s'", + buf_ptr(int_buf), buf_ptr(&new_type->name))); + return false; + } + } + const_val->special = ConstValSpecialStatic; break; case CastOpBoolToInt: @@ -9136,6 +9151,7 @@ static void eval_const_expr_implicit_cast(CastOp cast_op, const_val->special = ConstValSpecialStatic; break; } + return true; } static IrInstruction *ir_resolve_cast(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *value, TypeTableEntry *wanted_type, CastOp cast_op, bool need_alloca) @@ -9145,8 +9161,11 @@ static IrInstruction *ir_resolve_cast(IrAnalyze *ira, IrInstruction *source_inst { IrInstruction *result = ir_create_const(&ira->new_irb, source_instr->scope, source_instr->source_node, wanted_type); - eval_const_expr_implicit_cast(cast_op, &value->value, value->value.type, - &result->value, wanted_type); + if (!eval_const_expr_implicit_cast(ira, source_instr, cast_op, &value->value, value->value.type, + &result->value, wanted_type)) + { + return ira->codegen->invalid_instruction; + } return result; } else { IrInstruction *result = ir_build_cast(&ira->new_irb, source_instr->scope, source_instr->source_node, wanted_type, value, cast_op); diff --git a/std/math/expm1.zig b/std/math/expm1.zig index 438e44ccce..6fa0194b32 100644 --- a/std/math/expm1.zig +++ b/std/math/expm1.zig @@ -20,6 +20,10 @@ pub fn expm1(x: var) @typeOf(x) { fn expm1_32(x_: f32) f32 { @setFloatMode(this, builtin.FloatMode.Strict); + + if (math.isNan(x_)) + return math.nan(f32); + const o_threshold: f32 = 8.8721679688e+01; const ln2_hi: f32 = 6.9313812256e-01; const ln2_lo: f32 = 9.0580006145e-06; @@ -146,6 +150,10 @@ fn expm1_32(x_: f32) f32 { fn expm1_64(x_: f64) f64 { @setFloatMode(this, builtin.FloatMode.Strict); + + if (math.isNan(x_)) + return math.nan(f64); + const o_threshold: f64 = 7.09782712893383973096e+02; const ln2_hi: f64 = 6.93147180369123816490e-01; const ln2_lo: f64 = 1.90821492927058770002e-10; diff --git a/std/special/compiler_rt/floatuntidf.zig b/std/special/compiler_rt/floatuntidf.zig new file mode 100644 index 0000000000..3aabcb7b8a --- /dev/null +++ b/std/special/compiler_rt/floatuntidf.zig @@ -0,0 +1,60 @@ +const builtin = @import("builtin"); +const is_test = builtin.is_test; + +const DBL_MANT_DIG = 53; + +pub extern fn __floatuntidf(arg: u128) f64 { + @setRuntimeSafety(is_test); + + if (arg == 0) + return 0.0; + + var a = arg; + const N: u32 = @sizeOf(u128) * 8; + const sd = @bitCast(i32, N -% @clz(a)); // number of significant digits + var e: i32 = sd -% 1; // exponent + if (sd > DBL_MANT_DIG) { + // start: 0000000000000000000001xxxxxxxxxxxxxxxxxxxxxxPQxxxxxxxxxxxxxxxxxx + // finish: 000000000000000000000000000000000000001xxxxxxxxxxxxxxxxxxxxxxPQR + // 12345678901234567890123456 + // 1 = msb 1 bit + // P = bit DBL_MANT_DIG-1 bits to the right of 1 + // Q = bit DBL_MANT_DIG bits to the right of 1 + // R = "or" of all bits to the right of Q + switch (sd) { + DBL_MANT_DIG + 1 => { + a <<= 1; + }, + DBL_MANT_DIG + 2 => {}, + else => { + const shift_amt = @bitCast(i32, N +% (DBL_MANT_DIG + 2)) -% sd; + const shift_amt_u7 = @intCast(u7, shift_amt); + a = (a >> @intCast(u7, sd -% (DBL_MANT_DIG + 2))) | + @boolToInt((a & (u128(@maxValue(u128)) >> shift_amt_u7)) != 0); + }, + } + // finish + a |= @boolToInt((a & 4) != 0); // Or P into R + a +%= 1; // round - this step may add a significant bit + a >>= 2; // dump Q and R + // a is now rounded to DBL_MANT_DIG or DBL_MANT_DIG+1 bits + if ((a & (u128(1) << DBL_MANT_DIG)) != 0) { + a >>= 1; + e +%= 1; + } + // a is now rounded to DBL_MANT_DIG bits + } else { + a <<= @intCast(u7, DBL_MANT_DIG -% sd); + // a is now rounded to DBL_MANT_DIG bits + } + + const high: u64 = @bitCast(u32, (e +% 1023) << 20) | // exponent + (@truncate(u32, a >> 32) & 0x000FFFFF); // mantissa-high + const low = @truncate(u32, a); // mantissa-low + + return @bitCast(f64, low | (high << 32)); +} + +test "import floatuntidf" { + _ = @import("floatuntidf_test.zig"); +} diff --git a/std/special/compiler_rt/floatuntidf_test.zig b/std/special/compiler_rt/floatuntidf_test.zig new file mode 100644 index 0000000000..e2c79378e2 --- /dev/null +++ b/std/special/compiler_rt/floatuntidf_test.zig @@ -0,0 +1,81 @@ +const __floatuntidf = @import("floatuntidf.zig").__floatuntidf; +const assert = @import("std").debug.assert; + +fn test__floatuntidf(a: u128, expected: f64) void { + const x = __floatuntidf(a); + assert(x == expected); +} + +test "floatuntidf" { + test__floatuntidf(0, 0.0); + + test__floatuntidf(1, 1.0); + test__floatuntidf(2, 2.0); + test__floatuntidf(20, 20.0); + + test__floatuntidf(0x7FFFFF8000000000, 0x1.FFFFFEp+62); + test__floatuntidf(0x7FFFFFFFFFFFF800, 0x1.FFFFFFFFFFFFEp+62); + test__floatuntidf(0x7FFFFF0000000000, 0x1.FFFFFCp+62); + test__floatuntidf(0x7FFFFFFFFFFFF000, 0x1.FFFFFFFFFFFFCp+62); + + test__floatuntidf(make_ti(0x8000008000000000, 0), 0x1.000001p+127); + test__floatuntidf(make_ti(0x8000000000000800, 0), 0x1.0000000000001p+127); + test__floatuntidf(make_ti(0x8000010000000000, 0), 0x1.000002p+127); + test__floatuntidf(make_ti(0x8000000000001000, 0), 0x1.0000000000002p+127); + + test__floatuntidf(make_ti(0x8000000000000000, 0), 0x1.000000p+127); + test__floatuntidf(make_ti(0x8000000000000001, 0), 0x1.0000000000000002p+127); + + test__floatuntidf(0x0007FB72E8000000, 0x1.FEDCBAp+50); + + test__floatuntidf(0x0007FB72EA000000, 0x1.FEDCBA8p+50); + test__floatuntidf(0x0007FB72EB000000, 0x1.FEDCBACp+50); + test__floatuntidf(0x0007FB72EBFFFFFF, 0x1.FEDCBAFFFFFFCp+50); + test__floatuntidf(0x0007FB72EC000000, 0x1.FEDCBBp+50); + test__floatuntidf(0x0007FB72E8000001, 0x1.FEDCBA0000004p+50); + + test__floatuntidf(0x0007FB72E6000000, 0x1.FEDCB98p+50); + test__floatuntidf(0x0007FB72E7000000, 0x1.FEDCB9Cp+50); + test__floatuntidf(0x0007FB72E7FFFFFF, 0x1.FEDCB9FFFFFFCp+50); + test__floatuntidf(0x0007FB72E4000001, 0x1.FEDCB90000004p+50); + test__floatuntidf(0x0007FB72E4000000, 0x1.FEDCB9p+50); + + test__floatuntidf(0x023479FD0E092DC0, 0x1.1A3CFE870496Ep+57); + test__floatuntidf(0x023479FD0E092DA1, 0x1.1A3CFE870496Dp+57); + test__floatuntidf(0x023479FD0E092DB0, 0x1.1A3CFE870496Ep+57); + test__floatuntidf(0x023479FD0E092DB8, 0x1.1A3CFE870496Ep+57); + test__floatuntidf(0x023479FD0E092DB6, 0x1.1A3CFE870496Ep+57); + test__floatuntidf(0x023479FD0E092DBF, 0x1.1A3CFE870496Ep+57); + test__floatuntidf(0x023479FD0E092DC1, 0x1.1A3CFE870496Ep+57); + test__floatuntidf(0x023479FD0E092DC7, 0x1.1A3CFE870496Ep+57); + test__floatuntidf(0x023479FD0E092DC8, 0x1.1A3CFE870496Ep+57); + test__floatuntidf(0x023479FD0E092DCF, 0x1.1A3CFE870496Ep+57); + test__floatuntidf(0x023479FD0E092DD0, 0x1.1A3CFE870496Ep+57); + test__floatuntidf(0x023479FD0E092DD1, 0x1.1A3CFE870496Fp+57); + test__floatuntidf(0x023479FD0E092DD8, 0x1.1A3CFE870496Fp+57); + test__floatuntidf(0x023479FD0E092DDF, 0x1.1A3CFE870496Fp+57); + test__floatuntidf(0x023479FD0E092DE0, 0x1.1A3CFE870496Fp+57); + + test__floatuntidf(make_ti(0x023479FD0E092DC0, 0), 0x1.1A3CFE870496Ep+121); + test__floatuntidf(make_ti(0x023479FD0E092DA1, 1), 0x1.1A3CFE870496Dp+121); + test__floatuntidf(make_ti(0x023479FD0E092DB0, 2), 0x1.1A3CFE870496Ep+121); + test__floatuntidf(make_ti(0x023479FD0E092DB8, 3), 0x1.1A3CFE870496Ep+121); + test__floatuntidf(make_ti(0x023479FD0E092DB6, 4), 0x1.1A3CFE870496Ep+121); + test__floatuntidf(make_ti(0x023479FD0E092DBF, 5), 0x1.1A3CFE870496Ep+121); + test__floatuntidf(make_ti(0x023479FD0E092DC1, 6), 0x1.1A3CFE870496Ep+121); + test__floatuntidf(make_ti(0x023479FD0E092DC7, 7), 0x1.1A3CFE870496Ep+121); + test__floatuntidf(make_ti(0x023479FD0E092DC8, 8), 0x1.1A3CFE870496Ep+121); + test__floatuntidf(make_ti(0x023479FD0E092DCF, 9), 0x1.1A3CFE870496Ep+121); + test__floatuntidf(make_ti(0x023479FD0E092DD0, 0), 0x1.1A3CFE870496Ep+121); + test__floatuntidf(make_ti(0x023479FD0E092DD1, 11), 0x1.1A3CFE870496Fp+121); + test__floatuntidf(make_ti(0x023479FD0E092DD8, 12), 0x1.1A3CFE870496Fp+121); + test__floatuntidf(make_ti(0x023479FD0E092DDF, 13), 0x1.1A3CFE870496Fp+121); + test__floatuntidf(make_ti(0x023479FD0E092DE0, 14), 0x1.1A3CFE870496Fp+121); +} + +fn make_ti(high: u64, low: u64) u128 { + var result: u128 = high; + result <<= 64; + result |= low; + return result; +} diff --git a/std/special/compiler_rt/index.zig b/std/special/compiler_rt/index.zig index dc95aa23f2..6ad7768cb2 100644 --- a/std/special/compiler_rt/index.zig +++ b/std/special/compiler_rt/index.zig @@ -19,6 +19,8 @@ comptime { @export("__unordtf2", @import("comparetf2.zig").__unordtf2, linkage); + @export("__floatuntidf", @import("floatuntidf.zig").__floatuntidf, linkage); + @export("__fixunssfsi", @import("fixunssfsi.zig").__fixunssfsi, linkage); @export("__fixunssfdi", @import("fixunssfdi.zig").__fixunssfdi, linkage); @export("__fixunssfti", @import("fixunssfti.zig").__fixunssfti, linkage); diff --git a/test/cases/cast.zig b/test/cases/cast.zig index 156835bcb3..7b36bcd04a 100644 --- a/test/cases/cast.zig +++ b/test/cases/cast.zig @@ -340,11 +340,23 @@ fn testPeerErrorAndArray2(x: u8) error![]const u8 { }; } -test "explicit cast float number literal to integer if no fraction component" { +test "@floatToInt" { + testFloatToInts(); + comptime testFloatToInts(); +} + +fn testFloatToInts() void { const x = i32(1e4); assert(x == 10000); const y = @floatToInt(i32, f32(1e4)); assert(y == 10000); + expectFloatToInt(u8, 255.1, 255); + expectFloatToInt(i8, 127.2, 127); + expectFloatToInt(i8, -128.2, -128); +} + +fn expectFloatToInt(comptime T: type, f: f32, i: T) void { + assert(@floatToInt(T, f) == i); } test "cast u128 to f128 and back" { @@ -426,3 +438,10 @@ test "@bytesToSlice keeps pointer alignment" { const numbers = @bytesToSlice(u32, bytes[0..]); comptime assert(@typeOf(numbers) == []align(@alignOf(@typeOf(bytes))) u32); } + +test "@intCast i32 to u7" { + var x: u128 = @maxValue(u128); + var y: i32 = 120; + var z = x >> @intCast(u7, y); + assert(z == 0xff); +} diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 609e3f103f..39107b4a21 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1,6 +1,23 @@ const tests = @import("tests.zig"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.add( + "@floatToInt comptime safety", + \\comptime { + \\ _ = @floatToInt(i8, f32(-129.1)); + \\} + \\comptime { + \\ _ = @floatToInt(u8, f32(-1.1)); + \\} + \\comptime { + \\ _ = @floatToInt(u8, f32(256.1)); + \\} + , + ".tmp_source.zig:2:9: error: integer value '-129' cannot be stored in type 'i8'", + ".tmp_source.zig:5:9: error: integer value '-1' cannot be stored in type 'u8'", + ".tmp_source.zig:8:9: error: integer value '256' cannot be stored in type 'u8'", + ); + cases.add( "use c_void as return type of fn ptr", \\export fn entry() void { diff --git a/test/runtime_safety.zig b/test/runtime_safety.zig index ea5beafe8d..a7e8d6dc0e 100644 --- a/test/runtime_safety.zig +++ b/test/runtime_safety.zig @@ -1,6 +1,45 @@ const tests = @import("tests.zig"); pub fn addCases(cases: *tests.CompareOutputContext) void { + cases.addRuntimeSafety("@floatToInt cannot fit - negative to unsigned", + \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { + \\ @import("std").os.exit(126); + \\} + \\pub fn main() void { + \\ baz(bar(-1.1)); + \\} + \\fn bar(a: f32) u8 { + \\ return @floatToInt(u8, a); + \\} + \\fn baz(a: u8) void { } + ); + + cases.addRuntimeSafety("@floatToInt cannot fit - negative out of range", + \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { + \\ @import("std").os.exit(126); + \\} + \\pub fn main() void { + \\ baz(bar(-129.1)); + \\} + \\fn bar(a: f32) i8 { + \\ return @floatToInt(i8, a); + \\} + \\fn baz(a: i8) void { } + ); + + cases.addRuntimeSafety("@floatToInt cannot fit - positive out of range", + \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { + \\ @import("std").os.exit(126); + \\} + \\pub fn main() void { + \\ baz(bar(256.2)); + \\} + \\fn bar(a: f32) u8 { + \\ return @floatToInt(u8, a); + \\} + \\fn baz(a: u8) void { } + ); + cases.addRuntimeSafety("calling panic", \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); -- cgit v1.2.3 From 55193cb13bbc69350474f6a66728319b41149274 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 20 Jun 2018 06:45:45 -0400 Subject: fix runtime fn ptr equality codegen closes #1140 --- src/codegen.cpp | 10 +++++----- test/cases/misc.zig | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) (limited to 'src/codegen.cpp') diff --git a/src/codegen.cpp b/src/codegen.cpp index 20268d636a..c2406f0838 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2246,12 +2246,12 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable, } else if (type_entry->id == TypeTableEntryIdInt) { LLVMIntPredicate pred = cmp_op_to_int_predicate(op_id, type_entry->data.integral.is_signed); return LLVMBuildICmp(g->builder, pred, op1_value, op2_value, ""); - } else if (type_entry->id == TypeTableEntryIdEnum) { - LLVMIntPredicate pred = cmp_op_to_int_predicate(op_id, false); - return LLVMBuildICmp(g->builder, pred, op1_value, op2_value, ""); - } else if (type_entry->id == TypeTableEntryIdErrorSet || + } else if (type_entry->id == TypeTableEntryIdEnum || + type_entry->id == TypeTableEntryIdErrorSet || type_entry->id == TypeTableEntryIdPointer || - type_entry->id == TypeTableEntryIdBool) + type_entry->id == TypeTableEntryIdBool || + type_entry->id == TypeTableEntryIdPromise || + type_entry->id == TypeTableEntryIdFn) { LLVMIntPredicate pred = cmp_op_to_int_predicate(op_id, false); return LLVMBuildICmp(g->builder, pred, op1_value, op2_value, ""); diff --git a/test/cases/misc.zig b/test/cases/misc.zig index 89c441e7f9..d539f79a57 100644 --- a/test/cases/misc.zig +++ b/test/cases/misc.zig @@ -701,3 +701,8 @@ test "comptime cast fn to ptr" { const addr2 = @ptrCast(*const u8, emptyFn); comptime assert(addr1 == addr2); } + +test "equality compare fn ptrs" { + var a = emptyFn; + assert(a == a); +} -- cgit v1.2.3 From 1f45075a0e1d86fa110011f6cedbef61a9f6f056 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Wed, 27 Jun 2018 16:20:04 +0200 Subject: dry floating-point type definitions --- src/codegen.cpp | 57 ++++++++++++++------------------------------------------- 1 file changed, 14 insertions(+), 43 deletions(-) (limited to 'src/codegen.cpp') diff --git a/src/codegen.cpp b/src/codegen.cpp index c2406f0838..abec5a8ec7 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -6177,58 +6177,29 @@ static void define_builtin_types(CodeGen *g) { g->builtin_types.entry_usize = entry; } } - { - TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdFloat); - entry->type_ref = LLVMFloatType(); - buf_init_from_str(&entry->name, "f32"); - entry->data.floating.bit_count = 32; - - uint64_t debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, entry->type_ref); - entry->di_type = ZigLLVMCreateDebugBasicType(g->dbuilder, buf_ptr(&entry->name), - debug_size_in_bits, - ZigLLVMEncoding_DW_ATE_float()); - g->builtin_types.entry_f32 = entry; - g->primitive_type_table.put(&entry->name, entry); - } - { - TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdFloat); - entry->type_ref = LLVMDoubleType(); - buf_init_from_str(&entry->name, "f64"); - entry->data.floating.bit_count = 64; - uint64_t debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, entry->type_ref); - entry->di_type = ZigLLVMCreateDebugBasicType(g->dbuilder, buf_ptr(&entry->name), - debug_size_in_bits, - ZigLLVMEncoding_DW_ATE_float()); - g->builtin_types.entry_f64 = entry; - g->primitive_type_table.put(&entry->name, entry); - } - { + auto add_fp_entry = [] (CodeGen *g, + const char *name, + uint32_t bit_count, + LLVMTypeRef type_ref, + TypeTableEntry **field) { TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdFloat); - entry->type_ref = LLVMFP128Type(); - buf_init_from_str(&entry->name, "f128"); - entry->data.floating.bit_count = 128; + entry->type_ref = type_ref; + buf_init_from_str(&entry->name, name); + entry->data.floating.bit_count = bit_count; uint64_t debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, entry->type_ref); entry->di_type = ZigLLVMCreateDebugBasicType(g->dbuilder, buf_ptr(&entry->name), debug_size_in_bits, ZigLLVMEncoding_DW_ATE_float()); - g->builtin_types.entry_f128 = entry; + *field = entry; g->primitive_type_table.put(&entry->name, entry); - } - { - TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdFloat); - entry->type_ref = LLVMX86FP80Type(); - buf_init_from_str(&entry->name, "c_longdouble"); - entry->data.floating.bit_count = 80; + }; + add_fp_entry(g, "f32", 32, LLVMFloatType(), &g->builtin_types.entry_f32); + add_fp_entry(g, "f64", 64, LLVMDoubleType(), &g->builtin_types.entry_f64); + add_fp_entry(g, "f128", 128, LLVMFP128Type(), &g->builtin_types.entry_f128); + add_fp_entry(g, "c_longdouble", 80, LLVMX86FP80Type(), &g->builtin_types.entry_c_longdouble); - uint64_t debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, entry->type_ref); - entry->di_type = ZigLLVMCreateDebugBasicType(g->dbuilder, buf_ptr(&entry->name), - debug_size_in_bits, - ZigLLVMEncoding_DW_ATE_float()); - g->builtin_types.entry_c_longdouble = entry; - g->primitive_type_table.put(&entry->name, entry); - } { TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdVoid); entry->type_ref = LLVMVoidType(); -- cgit v1.2.3 From fd75e73ee9818f12fd81d8fdb3cb949c492d664a Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Wed, 27 Jun 2018 16:20:04 +0200 Subject: add f16 type Add support for half-precision floating point operations. Introduce `__extendhfsf2` and `__truncsfhf2` in std/special/compiler_rt. Add `__gnu_h2f_ieee` and `__gnu_f2h_ieee` as aliases that are used in Windows builds. The logic in std/special/compiler_rt/extendXfYf2.zig has been reworked and can now operate on 16 bits floating point types. `extendXfYf2()` and `truncXfYf2()` are marked `inline` to work around a not entirely understood stack alignment issue on Windows when calling the f16 versions of the builtins. closes #1122 --- CMakeLists.txt | 16 +++ src/all_types.hpp | 2 + src/analyze.cpp | 15 +++ src/bigfloat.cpp | 8 ++ src/bigfloat.hpp | 2 + src/codegen.cpp | 4 + src/ir.cpp | 151 ++++++++++++++++++++++++++- src/util.hpp | 19 ++++ std/special/compiler_rt/extendXfYf2.zig | 56 +++++----- std/special/compiler_rt/extendXfYf2_test.zig | 46 ++++++++ std/special/compiler_rt/index.zig | 5 + std/special/compiler_rt/truncXfYf2.zig | 111 ++++++++++++++++++++ std/special/compiler_rt/truncXfYf2_test.zig | 64 ++++++++++++ test/cases/cast.zig | 28 ++++- test/cases/math.zig | 12 ++- test/cases/misc.zig | 1 + 16 files changed, 505 insertions(+), 35 deletions(-) create mode 100644 std/special/compiler_rt/truncXfYf2.zig create mode 100644 std/special/compiler_rt/truncXfYf2_test.zig (limited to 'src/codegen.cpp') diff --git a/CMakeLists.txt b/CMakeLists.txt index 99de2328d2..789da4a8a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -261,12 +261,15 @@ endif() set(EMBEDDED_SOFTFLOAT_SOURCES "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/8086/f128M_isSignalingNaN.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/8086/s_commonNaNToF128M.c" + "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/8086/s_commonNaNToF16UI.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/8086/s_commonNaNToF32UI.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/8086/s_commonNaNToF64UI.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/8086/s_f128MToCommonNaN.c" + "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/8086/s_f16UIToCommonNaN.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/8086/s_f32UIToCommonNaN.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/8086/s_f64UIToCommonNaN.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/8086/s_propagateNaNF128M.c" + "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/8086/s_propagateNaNF16UI.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/8086/softfloat_raiseFlags.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/f128M_add.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/f128M_div.c" @@ -293,8 +296,20 @@ set(EMBEDDED_SOFTFLOAT_SOURCES "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/f128M_to_ui32_r_minMag.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/f128M_to_ui64.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/f128M_to_ui64_r_minMag.c" + "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/f16_add.c" + "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/f16_div.c" + "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/f16_eq.c" + "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/f16_lt.c" + "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/f16_mul.c" + "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/f16_rem.c" + "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/f16_roundToInt.c" + "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/f16_sqrt.c" + "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/f16_sub.c" + "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/f16_to_f128M.c" + "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/f16_to_f64.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/f32_to_f128M.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/f64_to_f128M.c" + "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/f64_to_f16.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/s_add256M.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/s_addCarryM.c" "${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/s_addComplCarryM.c" @@ -572,6 +587,7 @@ set(ZIG_STD_FILES "special/compiler_rt/floatuntidf.zig" "special/compiler_rt/muloti4.zig" "special/compiler_rt/index.zig" + "special/compiler_rt/truncXfYf2.zig" "special/compiler_rt/udivmod.zig" "special/compiler_rt/udivmoddi4.zig" "special/compiler_rt/udivmodti4.zig" diff --git a/src/all_types.hpp b/src/all_types.hpp index 019dcb182e..5d449491c8 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -258,6 +258,7 @@ struct ConstExprValue { // populated if special == ConstValSpecialStatic BigInt x_bigint; BigFloat x_bigfloat; + float16_t x_f16; float x_f32; double x_f64; float128_t x_f128; @@ -1598,6 +1599,7 @@ struct CodeGen { TypeTableEntry *entry_i128; TypeTableEntry *entry_isize; TypeTableEntry *entry_usize; + TypeTableEntry *entry_f16; TypeTableEntry *entry_f32; TypeTableEntry *entry_f64; TypeTableEntry *entry_f128; diff --git a/src/analyze.cpp b/src/analyze.cpp index c018ee4e92..25cc1c79d0 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -4668,6 +4668,13 @@ static uint32_t hash_const_val(ConstExprValue *const_val) { } case TypeTableEntryIdFloat: switch (const_val->type->data.floating.bit_count) { + case 16: + { + uint16_t result; + static_assert(sizeof(result) == sizeof(const_val->data.x_f16), ""); + memcpy(&result, &const_val->data.x_f16, sizeof(result)); + return result * 65537u; + } case 32: { uint32_t result; @@ -5128,6 +5135,9 @@ void init_const_float(ConstExprValue *const_val, TypeTableEntry *type, double va bigfloat_init_64(&const_val->data.x_bigfloat, value); } else if (type->id == TypeTableEntryIdFloat) { switch (type->data.floating.bit_count) { + case 16: + const_val->data.x_f16 = zig_double_to_f16(value); + break; case 32: const_val->data.x_f32 = value; break; @@ -5441,6 +5451,8 @@ bool const_values_equal(ConstExprValue *a, ConstExprValue *b) { case TypeTableEntryIdFloat: assert(a->type->data.floating.bit_count == b->type->data.floating.bit_count); switch (a->type->data.floating.bit_count) { + case 16: + return f16_eq(a->data.x_f16, b->data.x_f16); case 32: return a->data.x_f32 == b->data.x_f32; case 64: @@ -5614,6 +5626,9 @@ void render_const_value(CodeGen *g, Buf *buf, ConstExprValue *const_val) { return; case TypeTableEntryIdFloat: switch (type_entry->data.floating.bit_count) { + case 16: + buf_appendf(buf, "%f", zig_f16_to_double(const_val->data.x_f16)); + return; case 32: buf_appendf(buf, "%f", const_val->data.x_f32); return; diff --git a/src/bigfloat.cpp b/src/bigfloat.cpp index dcb6db61db..cc442fa3b7 100644 --- a/src/bigfloat.cpp +++ b/src/bigfloat.cpp @@ -18,6 +18,10 @@ void bigfloat_init_128(BigFloat *dest, float128_t x) { dest->value = x; } +void bigfloat_init_16(BigFloat *dest, float16_t x) { + f16_to_f128M(x, &dest->value); +} + void bigfloat_init_32(BigFloat *dest, float x) { float32_t f32_val; memcpy(&f32_val, &x, sizeof(float)); @@ -146,6 +150,10 @@ Cmp bigfloat_cmp(const BigFloat *op1, const BigFloat *op2) { } } +float16_t bigfloat_to_f16(const BigFloat *bigfloat) { + return f128M_to_f16(&bigfloat->value); +} + float bigfloat_to_f32(const BigFloat *bigfloat) { float32_t f32_value = f128M_to_f32(&bigfloat->value); float result; diff --git a/src/bigfloat.hpp b/src/bigfloat.hpp index e212c30c87..c6ae567945 100644 --- a/src/bigfloat.hpp +++ b/src/bigfloat.hpp @@ -22,6 +22,7 @@ struct BigFloat { struct Buf; +void bigfloat_init_16(BigFloat *dest, float16_t x); void bigfloat_init_32(BigFloat *dest, float x); void bigfloat_init_64(BigFloat *dest, double x); void bigfloat_init_128(BigFloat *dest, float128_t x); @@ -29,6 +30,7 @@ void bigfloat_init_bigfloat(BigFloat *dest, const BigFloat *x); void bigfloat_init_bigint(BigFloat *dest, const BigInt *op); int bigfloat_init_buf_base10(BigFloat *dest, const uint8_t *buf_ptr, size_t buf_len); +float16_t bigfloat_to_f16(const BigFloat *bigfloat); float bigfloat_to_f32(const BigFloat *bigfloat); double bigfloat_to_f64(const BigFloat *bigfloat); float128_t bigfloat_to_f128(const BigFloat *bigfloat); diff --git a/src/codegen.cpp b/src/codegen.cpp index abec5a8ec7..4419f4fc84 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -17,6 +17,7 @@ #include "os.hpp" #include "translate_c.hpp" #include "target.hpp" +#include "util.hpp" #include "zig_llvm.h" #include @@ -5211,6 +5212,8 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val, const c const_val->data.x_err_set->value, false); case TypeTableEntryIdFloat: switch (type_entry->data.floating.bit_count) { + case 16: + return LLVMConstReal(type_entry->type_ref, zig_f16_to_double(const_val->data.x_f16)); case 32: return LLVMConstReal(type_entry->type_ref, const_val->data.x_f32); case 64: @@ -6195,6 +6198,7 @@ static void define_builtin_types(CodeGen *g) { *field = entry; g->primitive_type_table.put(&entry->name, entry); }; + add_fp_entry(g, "f16", 16, LLVMHalfType(), &g->builtin_types.entry_f16); add_fp_entry(g, "f32", 32, LLVMFloatType(), &g->builtin_types.entry_f32); add_fp_entry(g, "f64", 64, LLVMDoubleType(), &g->builtin_types.entry_f64); add_fp_entry(g, "f128", 128, LLVMFP128Type(), &g->builtin_types.entry_f128); diff --git a/src/ir.cpp b/src/ir.cpp index 76178f2437..694f912145 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -11,9 +11,10 @@ #include "ir.hpp" #include "ir_print.hpp" #include "os.hpp" -#include "translate_c.hpp" #include "range_set.hpp" #include "softfloat.hpp" +#include "translate_c.hpp" +#include "util.hpp" struct IrExecContext { ConstExprValue *mem_slot_list; @@ -7238,6 +7239,11 @@ static bool float_has_fraction(ConstExprValue *const_val) { return bigfloat_has_fraction(&const_val->data.x_bigfloat); } else if (const_val->type->id == TypeTableEntryIdFloat) { switch (const_val->type->data.floating.bit_count) { + case 16: + { + float16_t floored = f16_roundToInt(const_val->data.x_f16, softfloat_round_minMag, false); + return !f16_eq(floored, const_val->data.x_f16); + } case 32: return floorf(const_val->data.x_f32) != const_val->data.x_f32; case 64: @@ -7261,6 +7267,9 @@ static void float_append_buf(Buf *buf, ConstExprValue *const_val) { bigfloat_append_buf(buf, &const_val->data.x_bigfloat); } else if (const_val->type->id == TypeTableEntryIdFloat) { switch (const_val->type->data.floating.bit_count) { + case 16: + buf_appendf(buf, "%f", zig_f16_to_double(const_val->data.x_f16)); + break; case 32: buf_appendf(buf, "%f", const_val->data.x_f32); break; @@ -7296,6 +7305,17 @@ static void float_init_bigint(BigInt *bigint, ConstExprValue *const_val) { bigint_init_bigfloat(bigint, &const_val->data.x_bigfloat); } else if (const_val->type->id == TypeTableEntryIdFloat) { switch (const_val->type->data.floating.bit_count) { + case 16: + { + double x = zig_f16_to_double(const_val->data.x_f16); + if (x >= 0) { + bigint_init_unsigned(bigint, (uint64_t)x); + } else { + bigint_init_unsigned(bigint, (uint64_t)-x); + bigint->is_negative = true; + } + break; + } case 32: if (const_val->data.x_f32 >= 0) { bigint_init_unsigned(bigint, (uint64_t)(const_val->data.x_f32)); @@ -7332,6 +7352,9 @@ static void float_init_bigfloat(ConstExprValue *dest_val, BigFloat *bigfloat) { bigfloat_init_bigfloat(&dest_val->data.x_bigfloat, bigfloat); } else if (dest_val->type->id == TypeTableEntryIdFloat) { switch (dest_val->type->data.floating.bit_count) { + case 16: + dest_val->data.x_f16 = bigfloat_to_f16(bigfloat); + break; case 32: dest_val->data.x_f32 = bigfloat_to_f32(bigfloat); break; @@ -7349,11 +7372,39 @@ static void float_init_bigfloat(ConstExprValue *dest_val, BigFloat *bigfloat) { } } +static void float_init_f16(ConstExprValue *dest_val, float16_t x) { + if (dest_val->type->id == TypeTableEntryIdComptimeFloat) { + bigfloat_init_16(&dest_val->data.x_bigfloat, x); + } else if (dest_val->type->id == TypeTableEntryIdFloat) { + switch (dest_val->type->data.floating.bit_count) { + case 16: + dest_val->data.x_f16 = x; + break; + case 32: + dest_val->data.x_f32 = zig_f16_to_double(x); + break; + case 64: + dest_val->data.x_f64 = zig_f16_to_double(x); + break; + case 128: + f16_to_f128M(x, &dest_val->data.x_f128); + break; + default: + zig_unreachable(); + } + } else { + zig_unreachable(); + } +} + static void float_init_f32(ConstExprValue *dest_val, float x) { if (dest_val->type->id == TypeTableEntryIdComptimeFloat) { bigfloat_init_32(&dest_val->data.x_bigfloat, x); } else if (dest_val->type->id == TypeTableEntryIdFloat) { switch (dest_val->type->data.floating.bit_count) { + case 16: + dest_val->data.x_f16 = zig_double_to_f16(x); + break; case 32: dest_val->data.x_f32 = x; break; @@ -7380,6 +7431,9 @@ static void float_init_f64(ConstExprValue *dest_val, double x) { bigfloat_init_64(&dest_val->data.x_bigfloat, x); } else if (dest_val->type->id == TypeTableEntryIdFloat) { switch (dest_val->type->data.floating.bit_count) { + case 16: + dest_val->data.x_f16 = zig_double_to_f16(x); + break; case 32: dest_val->data.x_f32 = x; break; @@ -7406,6 +7460,9 @@ static void float_init_f128(ConstExprValue *dest_val, float128_t x) { bigfloat_init_128(&dest_val->data.x_bigfloat, x); } else if (dest_val->type->id == TypeTableEntryIdFloat) { switch (dest_val->type->data.floating.bit_count) { + case 16: + dest_val->data.x_f16 = f128M_to_f16(&x); + break; case 32: { float32_t f32_val = f128M_to_f32(&x); @@ -7436,6 +7493,9 @@ static void float_init_float(ConstExprValue *dest_val, ConstExprValue *src_val) float_init_bigfloat(dest_val, &src_val->data.x_bigfloat); } else if (src_val->type->id == TypeTableEntryIdFloat) { switch (src_val->type->data.floating.bit_count) { + case 16: + float_init_f16(dest_val, src_val->data.x_f16); + break; case 32: float_init_f32(dest_val, src_val->data.x_f32); break; @@ -7459,6 +7519,14 @@ static Cmp float_cmp(ConstExprValue *op1, ConstExprValue *op2) { return bigfloat_cmp(&op1->data.x_bigfloat, &op2->data.x_bigfloat); } else if (op1->type->id == TypeTableEntryIdFloat) { switch (op1->type->data.floating.bit_count) { + case 16: + if (f16_lt(op1->data.x_f16, op2->data.x_f16)) { + return CmpLT; + } else if (f16_lt(op2->data.x_f16, op1->data.x_f16)) { + return CmpGT; + } else { + return CmpEQ; + } case 32: if (op1->data.x_f32 > op2->data.x_f32) { return CmpGT; @@ -7496,6 +7564,17 @@ static Cmp float_cmp_zero(ConstExprValue *op) { return bigfloat_cmp_zero(&op->data.x_bigfloat); } else if (op->type->id == TypeTableEntryIdFloat) { switch (op->type->data.floating.bit_count) { + case 16: + { + const float16_t zero = zig_double_to_f16(0); + if (f16_lt(op->data.x_f16, zero)) { + return CmpLT; + } else if (f16_lt(zero, op->data.x_f16)) { + return CmpGT; + } else { + return CmpEQ; + } + } case 32: if (op->data.x_f32 < 0.0) { return CmpLT; @@ -7537,6 +7616,9 @@ static void float_add(ConstExprValue *out_val, ConstExprValue *op1, ConstExprVal bigfloat_add(&out_val->data.x_bigfloat, &op1->data.x_bigfloat, &op2->data.x_bigfloat); } else if (op1->type->id == TypeTableEntryIdFloat) { switch (op1->type->data.floating.bit_count) { + case 16: + out_val->data.x_f16 = f16_add(op1->data.x_f16, op2->data.x_f16); + return; case 32: out_val->data.x_f32 = op1->data.x_f32 + op2->data.x_f32; return; @@ -7561,6 +7643,9 @@ static void float_sub(ConstExprValue *out_val, ConstExprValue *op1, ConstExprVal bigfloat_sub(&out_val->data.x_bigfloat, &op1->data.x_bigfloat, &op2->data.x_bigfloat); } else if (op1->type->id == TypeTableEntryIdFloat) { switch (op1->type->data.floating.bit_count) { + case 16: + out_val->data.x_f16 = f16_sub(op1->data.x_f16, op2->data.x_f16); + return; case 32: out_val->data.x_f32 = op1->data.x_f32 - op2->data.x_f32; return; @@ -7585,6 +7670,9 @@ static void float_mul(ConstExprValue *out_val, ConstExprValue *op1, ConstExprVal bigfloat_mul(&out_val->data.x_bigfloat, &op1->data.x_bigfloat, &op2->data.x_bigfloat); } else if (op1->type->id == TypeTableEntryIdFloat) { switch (op1->type->data.floating.bit_count) { + case 16: + out_val->data.x_f16 = f16_mul(op1->data.x_f16, op2->data.x_f16); + return; case 32: out_val->data.x_f32 = op1->data.x_f32 * op2->data.x_f32; return; @@ -7609,6 +7697,9 @@ static void float_div(ConstExprValue *out_val, ConstExprValue *op1, ConstExprVal bigfloat_div(&out_val->data.x_bigfloat, &op1->data.x_bigfloat, &op2->data.x_bigfloat); } else if (op1->type->id == TypeTableEntryIdFloat) { switch (op1->type->data.floating.bit_count) { + case 16: + out_val->data.x_f16 = f16_div(op1->data.x_f16, op2->data.x_f16); + return; case 32: out_val->data.x_f32 = op1->data.x_f32 / op2->data.x_f32; return; @@ -7633,6 +7724,19 @@ static void float_div_trunc(ConstExprValue *out_val, ConstExprValue *op1, ConstE bigfloat_div_trunc(&out_val->data.x_bigfloat, &op1->data.x_bigfloat, &op2->data.x_bigfloat); } else if (op1->type->id == TypeTableEntryIdFloat) { switch (op1->type->data.floating.bit_count) { + case 16: + { + double a = zig_f16_to_double(op1->data.x_f16); + double b = zig_f16_to_double(op2->data.x_f16); + double c = a / b; + if (c >= 0.0) { + c = floor(c); + } else { + c = ceil(c); + } + out_val->data.x_f16 = zig_double_to_f16(c); + return; + } case 32: out_val->data.x_f32 = op1->data.x_f32 / op2->data.x_f32; if (out_val->data.x_f32 >= 0.0) { @@ -7668,6 +7772,10 @@ static void float_div_floor(ConstExprValue *out_val, ConstExprValue *op1, ConstE bigfloat_div_floor(&out_val->data.x_bigfloat, &op1->data.x_bigfloat, &op2->data.x_bigfloat); } else if (op1->type->id == TypeTableEntryIdFloat) { switch (op1->type->data.floating.bit_count) { + case 16: + out_val->data.x_f16 = f16_div(op1->data.x_f16, op2->data.x_f16); + out_val->data.x_f16 = f16_roundToInt(out_val->data.x_f16, softfloat_round_min, false); + return; case 32: out_val->data.x_f32 = floorf(op1->data.x_f32 / op2->data.x_f32); return; @@ -7693,6 +7801,9 @@ static void float_rem(ConstExprValue *out_val, ConstExprValue *op1, ConstExprVal bigfloat_rem(&out_val->data.x_bigfloat, &op1->data.x_bigfloat, &op2->data.x_bigfloat); } else if (op1->type->id == TypeTableEntryIdFloat) { switch (op1->type->data.floating.bit_count) { + case 16: + out_val->data.x_f16 = f16_rem(op1->data.x_f16, op2->data.x_f16); + return; case 32: out_val->data.x_f32 = fmodf(op1->data.x_f32, op2->data.x_f32); return; @@ -7710,6 +7821,16 @@ static void float_rem(ConstExprValue *out_val, ConstExprValue *op1, ConstExprVal } } +// c = a - b * trunc(a / b) +static float16_t zig_f16_mod(float16_t a, float16_t b) { + float16_t c; + c = f16_div(a, b); + c = f16_roundToInt(c, softfloat_round_min, true); + c = f16_mul(b, c); + c = f16_sub(a, c); + return c; +} + // c = a - b * trunc(a / b) static void zig_f128M_mod(const float128_t* a, const float128_t* b, float128_t* c) { f128M_div(a, b, c); @@ -7725,6 +7846,9 @@ static void float_mod(ConstExprValue *out_val, ConstExprValue *op1, ConstExprVal bigfloat_mod(&out_val->data.x_bigfloat, &op1->data.x_bigfloat, &op2->data.x_bigfloat); } else if (op1->type->id == TypeTableEntryIdFloat) { switch (op1->type->data.floating.bit_count) { + case 16: + out_val->data.x_f16 = zig_f16_mod(op1->data.x_f16, op2->data.x_f16); + return; case 32: out_val->data.x_f32 = fmodf(fmodf(op1->data.x_f32, op2->data.x_f32) + op2->data.x_f32, op2->data.x_f32); return; @@ -7748,6 +7872,12 @@ static void float_negate(ConstExprValue *out_val, ConstExprValue *op) { bigfloat_negate(&out_val->data.x_bigfloat, &op->data.x_bigfloat); } else if (op->type->id == TypeTableEntryIdFloat) { switch (op->type->data.floating.bit_count) { + case 16: + { + const float16_t zero = zig_double_to_f16(0); + out_val->data.x_f16 = f16_sub(zero, op->data.x_f16); + return; + } case 32: out_val->data.x_f32 = -op->data.x_f32; return; @@ -7770,6 +7900,9 @@ static void float_negate(ConstExprValue *out_val, ConstExprValue *op) { void float_write_ieee597(ConstExprValue *op, uint8_t *buf, bool is_big_endian) { if (op->type->id == TypeTableEntryIdFloat) { switch (op->type->data.floating.bit_count) { + case 16: + memcpy(buf, &op->data.x_f16, 2); // TODO wrong when compiler is big endian + return; case 32: memcpy(buf, &op->data.x_f32, 4); // TODO wrong when compiler is big endian return; @@ -7790,6 +7923,9 @@ void float_write_ieee597(ConstExprValue *op, uint8_t *buf, bool is_big_endian) { void float_read_ieee597(ConstExprValue *val, uint8_t *buf, bool is_big_endian) { if (val->type->id == TypeTableEntryIdFloat) { switch (val->type->data.floating.bit_count) { + case 16: + memcpy(&val->data.x_f16, buf, 2); // TODO wrong when compiler is big endian + return; case 32: memcpy(&val->data.x_f32, buf, 4); // TODO wrong when compiler is big endian return; @@ -8817,6 +8953,9 @@ static bool eval_const_expr_implicit_cast(IrAnalyze *ira, IrInstruction *source_ if (other_val->type->id == TypeTableEntryIdComptimeFloat) { assert(new_type->id == TypeTableEntryIdFloat); switch (new_type->data.floating.bit_count) { + case 16: + const_val->data.x_f16 = bigfloat_to_f16(&other_val->data.x_bigfloat); + break; case 32: const_val->data.x_f32 = bigfloat_to_f32(&other_val->data.x_bigfloat); break; @@ -8847,6 +8986,9 @@ static bool eval_const_expr_implicit_cast(IrAnalyze *ira, IrInstruction *source_ BigFloat bigfloat; bigfloat_init_bigint(&bigfloat, &other_val->data.x_bigint); switch (new_type->data.floating.bit_count) { + case 16: + const_val->data.x_f16 = bigfloat_to_f16(&bigfloat); + break; case 32: const_val->data.x_f32 = bigfloat_to_f32(&bigfloat); break; @@ -20104,6 +20246,9 @@ static TypeTableEntry *ir_analyze_instruction_sqrt(IrAnalyze *ira, IrInstruction bigfloat_sqrt(&out_val->data.x_bigfloat, &val->data.x_bigfloat); } else if (float_type->id == TypeTableEntryIdFloat) { switch (float_type->data.floating.bit_count) { + case 16: + out_val->data.x_f16 = f16_sqrt(val->data.x_f16); + break; case 32: out_val->data.x_f32 = sqrtf(val->data.x_f32); break; @@ -20124,7 +20269,9 @@ static TypeTableEntry *ir_analyze_instruction_sqrt(IrAnalyze *ira, IrInstruction } assert(float_type->id == TypeTableEntryIdFloat); - if (float_type->data.floating.bit_count != 32 && float_type->data.floating.bit_count != 64) { + if (float_type->data.floating.bit_count != 16 && + float_type->data.floating.bit_count != 32 && + float_type->data.floating.bit_count != 64) { ir_add_error(ira, instruction->type, buf_sprintf("compiler TODO: add implementation of sqrt for '%s'", buf_ptr(&float_type->name))); return ira->codegen->builtin_types.entry_invalid; } diff --git a/src/util.hpp b/src/util.hpp index 52baab7ace..b0402137bd 100644 --- a/src/util.hpp +++ b/src/util.hpp @@ -31,6 +31,8 @@ #endif +#include "softfloat.hpp" + #define BREAKPOINT __asm("int $0x03") ATTRIBUTE_COLD @@ -165,4 +167,21 @@ static inline uint8_t log2_u64(uint64_t x) { return (63 - clzll(x)); } +static inline float16_t zig_double_to_f16(double x) { + float64_t y; + static_assert(sizeof(x) == sizeof(y), ""); + memcpy(&y, &x, sizeof(x)); + return f64_to_f16(y); +} + + +// Return value is safe to coerce to float even when |x| is NaN or Infinity. +static inline double zig_f16_to_double(float16_t x) { + float64_t y = f16_to_f64(x); + double z; + static_assert(sizeof(y) == sizeof(z), ""); + memcpy(&z, &y, sizeof(y)); + return z; +} + #endif diff --git a/std/special/compiler_rt/extendXfYf2.zig b/std/special/compiler_rt/extendXfYf2.zig index 6fa8cf4654..099e27b74a 100644 --- a/std/special/compiler_rt/extendXfYf2.zig +++ b/std/special/compiler_rt/extendXfYf2.zig @@ -10,9 +10,13 @@ pub extern fn __extendsftf2(a: f32) f128 { return extendXfYf2(f128, f32, a); } +pub extern fn __extendhfsf2(a: u16) f32 { + return extendXfYf2(f32, f16, @bitCast(f16, a)); +} + const CHAR_BIT = 8; -pub fn extendXfYf2(comptime dst_t: type, comptime src_t: type, a: src_t) dst_t { +inline 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); @@ -22,22 +26,22 @@ pub fn extendXfYf2(comptime dst_t: type, comptime src_t: type, a: src_t) dst_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 srcBits = @sizeOf(src_t) * CHAR_BIT; + const srcExpBits = srcBits - srcSigBits - 1; + const srcInfExp = (1 << srcExpBits) - 1; + const srcExpBias = 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 srcMinNormal = 1 << srcSigBits; + const srcInfinity = srcInfExp << srcSigBits; + const srcSignMask = 1 << (srcSigBits + srcExpBits); + const srcAbsMask = srcSignMask - 1; + const srcQNaN = 1 << (srcSigBits - 1); + const srcNaNCode = 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 dstBits = @sizeOf(dst_t) * CHAR_BIT; + const dstExpBits = dstBits - dstSigBits - 1; + const dstInfExp = (1 << dstExpBits) - 1; + const dstExpBias = dstInfExp >> 1; const dstMinNormal: dst_rep_t = dst_rep_t(1) << dstSigBits; @@ -47,38 +51,36 @@ pub fn extendXfYf2(comptime dst_t: type, comptime src_t: type, a: src_t) dst_t { 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) { + if (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; + absResult = dst_rep_t(aAbs) << (dstSigBits - srcSigBits); + absResult += (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); + absResult = 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); + const scale: u32 = @clz(aAbs) - @clz(src_rep_t(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); + const resultExponent: u32 = dstExpBias - srcExpBias - scale + 1; + absResult |= @intCast(dst_rep_t, resultExponent) << 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); + const result: dst_rep_t align(@alignOf(dst_t)) = absResult | dst_rep_t(sign) << (dstBits - srcBits); return @bitCast(dst_t, result); } diff --git a/std/special/compiler_rt/extendXfYf2_test.zig b/std/special/compiler_rt/extendXfYf2_test.zig index 84fb410fbb..0168de12a5 100644 --- a/std/special/compiler_rt/extendXfYf2_test.zig +++ b/std/special/compiler_rt/extendXfYf2_test.zig @@ -1,4 +1,5 @@ const __extenddftf2 = @import("extendXfYf2.zig").__extenddftf2; +const __extendhfsf2 = @import("extendXfYf2.zig").__extendhfsf2; const __extendsftf2 = @import("extendXfYf2.zig").__extendsftf2; const assert = @import("std").debug.assert; @@ -24,6 +25,22 @@ fn test__extenddftf2(a: f64, expectedHi: u64, expectedLo: u64) void { @panic("__extenddftf2 test failure"); } +fn test__extendhfsf2(a: u16, expected: u32) void { + const x = __extendhfsf2(a); + const rep = @bitCast(u32, x); + + if (rep == expected) { + if (rep & 0x7fffffff > 0x7f800000) { + return; // NaN is always unequal. + } + if (x == @bitCast(f32, expected)) { + return; + } + } + + @panic("__extendhfsf2 test failure"); +} + fn test__extendsftf2(a: f32, expectedHi: u64, expectedLo: u64) void { const x = __extendsftf2(a); @@ -68,6 +85,35 @@ test "extenddftf2" { test__extenddftf2(0x1.edcba987654321fp-45, 0x3fd2edcba9876543, 0x2000000000000000); } +test "extendhfsf2" { + test__extendhfsf2(0x7e00, 0x7fc00000); // qNaN + test__extendhfsf2(0x7f00, 0x7fe00000); // sNaN + + test__extendhfsf2(0, 0); // 0 + test__extendhfsf2(0x8000, 0x80000000); // -0 + + test__extendhfsf2(0x7c00, 0x7f800000); // inf + test__extendhfsf2(0xfc00, 0xff800000); // -inf + + test__extendhfsf2(0x0001, 0x33800000); // denormal (min), 2**-24 + test__extendhfsf2(0x8001, 0xb3800000); // denormal (min), -2**-24 + + test__extendhfsf2(0x03ff, 0x387fc000); // denormal (max), 2**-14 - 2**-24 + test__extendhfsf2(0x83ff, 0xb87fc000); // denormal (max), -2**-14 + 2**-24 + + test__extendhfsf2(0x0400, 0x38800000); // normal (min), 2**-14 + test__extendhfsf2(0x8400, 0xb8800000); // normal (min), -2**-14 + + test__extendhfsf2(0x7bff, 0x477fe000); // normal (max), 65504 + test__extendhfsf2(0xfbff, 0xc77fe000); // normal (max), -65504 + + test__extendhfsf2(0x3c01, 0x3f802000); // normal, 1 + 2**-10 + test__extendhfsf2(0xbc01, 0xbf802000); // normal, -1 - 2**-10 + + test__extendhfsf2(0x3555, 0x3eaaa000); // normal, approx. 1/3 + test__extendhfsf2(0xb555, 0xbeaaa000); // normal, approx. -1/3 +} + test "extendsftf2" { // qNaN test__extendsftf2(makeQNaN32(), 0x7fff800000000000, 0x0); diff --git a/std/special/compiler_rt/index.zig b/std/special/compiler_rt/index.zig index c96e1587f8..fda8d9d8af 100644 --- a/std/special/compiler_rt/index.zig +++ b/std/special/compiler_rt/index.zig @@ -15,6 +15,8 @@ comptime { @export("__lttf2", @import("comparetf2.zig").__letf2, linkage); @export("__netf2", @import("comparetf2.zig").__letf2, linkage); @export("__gttf2", @import("comparetf2.zig").__getf2, linkage); + @export("__gnu_h2f_ieee", @import("extendXfYf2.zig").__extendhfsf2, linkage); + @export("__gnu_f2h_ieee", @import("truncXfYf2.zig").__truncsfhf2, linkage); } @export("__unordtf2", @import("comparetf2.zig").__unordtf2, linkage); @@ -22,6 +24,9 @@ comptime { @export("__floatuntidf", @import("floatuntidf.zig").__floatuntidf, linkage); @export("__extenddftf2", @import("extendXfYf2.zig").__extenddftf2, linkage); @export("__extendsftf2", @import("extendXfYf2.zig").__extendsftf2, linkage); + @export("__extendhfsf2", @import("extendXfYf2.zig").__extendhfsf2, linkage); + + @export("__truncsfhf2", @import("truncXfYf2.zig").__truncsfhf2, linkage); @export("__fixunssfsi", @import("fixunssfsi.zig").__fixunssfsi, linkage); @export("__fixunssfdi", @import("fixunssfdi.zig").__fixunssfdi, linkage); diff --git a/std/special/compiler_rt/truncXfYf2.zig b/std/special/compiler_rt/truncXfYf2.zig new file mode 100644 index 0000000000..f08c6ae34f --- /dev/null +++ b/std/special/compiler_rt/truncXfYf2.zig @@ -0,0 +1,111 @@ +const std = @import("std"); + +pub extern fn __truncsfhf2(a: f32) u16 { + return @bitCast(u16, truncXfYf2(f16, f32, a)); +} + +const CHAR_BIT = 8; + +inline fn truncXfYf2(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 = @sizeOf(src_t) * CHAR_BIT; + const srcExpBits = srcBits - srcSigBits - 1; + const srcInfExp = (1 << srcExpBits) - 1; + const srcExpBias = srcInfExp >> 1; + + const srcMinNormal = 1 << srcSigBits; + const srcSignificandMask = srcMinNormal - 1; + const srcInfinity = srcInfExp << srcSigBits; + const srcSignMask = 1 << (srcSigBits + srcExpBits); + const srcAbsMask = srcSignMask - 1; + const roundMask = (1 << (srcSigBits - dstSigBits)) - 1; + const halfway = 1 << (srcSigBits - dstSigBits - 1); + const srcQNaN = 1 << (srcSigBits - 1); + const srcNaNCode = srcQNaN - 1; + + const dstBits = @sizeOf(dst_t) * CHAR_BIT; + const dstExpBits = dstBits - dstSigBits - 1; + const dstInfExp = (1 << dstExpBits) - 1; + const dstExpBias = dstInfExp >> 1; + + const underflowExponent = srcExpBias + 1 - dstExpBias; + const overflowExponent = srcExpBias + dstInfExp - dstExpBias; + const underflow = underflowExponent << srcSigBits; + const overflow = overflowExponent << srcSigBits; + + const dstQNaN = 1 << (dstSigBits - 1); + const dstNaNCode = dstQNaN - 1; + + // 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 (aAbs -% underflow < aAbs -% overflow) { + // The exponent of a is within the range of normal numbers in the + // destination format. We can convert by simply right-shifting with + // rounding and adjusting the exponent. + absResult = @truncate(dst_rep_t, aAbs >> (srcSigBits - dstSigBits)); + absResult -%= dst_rep_t(srcExpBias - dstExpBias) << dstSigBits; + + const roundBits: src_rep_t = aAbs & roundMask; + if (roundBits > halfway) { + // Round to nearest + absResult += 1; + } else if (roundBits == halfway) { + // Ties to even + absResult += absResult & 1; + } + } else if (aAbs > srcInfinity) { + // a is NaN. + // Conjure the result by beginning with infinity, setting the qNaN + // bit and inserting the (truncated) trailing NaN field. + absResult = @intCast(dst_rep_t, dstInfExp) << dstSigBits; + absResult |= dstQNaN; + absResult |= @intCast(dst_rep_t, ((aAbs & srcNaNCode) >> (srcSigBits - dstSigBits)) & dstNaNCode); + } else if (aAbs >= overflow) { + // a overflows to infinity. + absResult = @intCast(dst_rep_t, dstInfExp) << dstSigBits; + } else { + // a underflows on conversion to the destination type or is an exact + // zero. The result may be a denormal or zero. Extract the exponent + // to get the shift amount for the denormalization. + const aExp: u32 = aAbs >> srcSigBits; + const shift: u32 = srcExpBias - dstExpBias - aExp + 1; + + const significand: src_rep_t = (aRep & srcSignificandMask) | srcMinNormal; + + // Right shift by the denormalization amount with sticky. + if (shift > srcSigBits) { + absResult = 0; + } else { + const sticky: src_rep_t = significand << @intCast(SrcShift, srcBits - shift); + const denormalizedSignificand: src_rep_t = significand >> @intCast(SrcShift, shift) | sticky; + absResult = @intCast(dst_rep_t, denormalizedSignificand >> (srcSigBits - dstSigBits)); + const roundBits: src_rep_t = denormalizedSignificand & roundMask; + if (roundBits > halfway) { + // Round to nearest + absResult += 1; + } else if (roundBits == halfway) { + // Ties to even + absResult += absResult & 1; + } + } + } + + const result: dst_rep_t align(@alignOf(dst_t)) = absResult | @truncate(dst_rep_t, sign >> @intCast(SrcShift, srcBits - dstBits)); + return @bitCast(dst_t, result); +} + +test "import truncXfYf2" { + _ = @import("truncXfYf2_test.zig"); +} diff --git a/std/special/compiler_rt/truncXfYf2_test.zig b/std/special/compiler_rt/truncXfYf2_test.zig new file mode 100644 index 0000000000..e4dae7b5b0 --- /dev/null +++ b/std/special/compiler_rt/truncXfYf2_test.zig @@ -0,0 +1,64 @@ +const __truncsfhf2 = @import("truncXfYf2.zig").__truncsfhf2; + +fn test__truncsfhf2(a: u32, expected: u16) void { + const actual = __truncsfhf2(@bitCast(f32, a)); + + if (actual == expected) { + return; + } + + @panic("__truncsfhf2 test failure"); +} + +test "truncsfhf2" { + test__truncsfhf2(0x7fc00000, 0x7e00); // qNaN + test__truncsfhf2(0x7fe00000, 0x7f00); // sNaN + + test__truncsfhf2(0, 0); // 0 + test__truncsfhf2(0x80000000, 0x8000); // -0 + + test__truncsfhf2(0x7f800000, 0x7c00); // inf + test__truncsfhf2(0xff800000, 0xfc00); // -inf + + test__truncsfhf2(0x477ff000, 0x7c00); // 65520 -> inf + test__truncsfhf2(0xc77ff000, 0xfc00); // -65520 -> -inf + + test__truncsfhf2(0x71cc3892, 0x7c00); // 0x1.987124876876324p+100 -> inf + test__truncsfhf2(0xf1cc3892, 0xfc00); // -0x1.987124876876324p+100 -> -inf + + test__truncsfhf2(0x38800000, 0x0400); // normal (min), 2**-14 + test__truncsfhf2(0xb8800000, 0x8400); // normal (min), -2**-14 + + test__truncsfhf2(0x477fe000, 0x7bff); // normal (max), 65504 + test__truncsfhf2(0xc77fe000, 0xfbff); // normal (max), -65504 + + test__truncsfhf2(0x477fe100, 0x7bff); // normal, 65505 -> 65504 + test__truncsfhf2(0xc77fe100, 0xfbff); // normal, -65505 -> -65504 + + test__truncsfhf2(0x477fef00, 0x7bff); // normal, 65519 -> 65504 + test__truncsfhf2(0xc77fef00, 0xfbff); // normal, -65519 -> -65504 + + test__truncsfhf2(0x3f802000, 0x3c01); // normal, 1 + 2**-10 + test__truncsfhf2(0xbf802000, 0xbc01); // normal, -1 - 2**-10 + + test__truncsfhf2(0x3eaaa000, 0x3555); // normal, approx. 1/3 + test__truncsfhf2(0xbeaaa000, 0xb555); // normal, approx. -1/3 + + test__truncsfhf2(0x40490fdb, 0x4248); // normal, 3.1415926535 + test__truncsfhf2(0xc0490fdb, 0xc248); // normal, -3.1415926535 + + test__truncsfhf2(0x45cc3892, 0x6e62); // normal, 0x1.987124876876324p+12 + + test__truncsfhf2(0x3f800000, 0x3c00); // normal, 1 + test__truncsfhf2(0x38800000, 0x0400); // normal, 0x1.0p-14 + + test__truncsfhf2(0x33800000, 0x0001); // denormal (min), 2**-24 + test__truncsfhf2(0xb3800000, 0x8001); // denormal (min), -2**-24 + + test__truncsfhf2(0x387fc000, 0x03ff); // denormal (max), 2**-14 - 2**-24 + test__truncsfhf2(0xb87fc000, 0x83ff); // denormal (max), -2**-14 + 2**-24 + + test__truncsfhf2(0x35800000, 0x0010); // denormal, 0x1.0p-20 + test__truncsfhf2(0x33280000, 0x0001); // denormal, 0x1.5p-25 -> 0x1.0p-24 + test__truncsfhf2(0x33000000, 0x0000); // 0x1.0p-25 -> zero +} diff --git a/test/cases/cast.zig b/test/cases/cast.zig index 4209d87c1a..5688d90e11 100644 --- a/test/cases/cast.zig +++ b/test/cases/cast.zig @@ -350,13 +350,16 @@ fn testFloatToInts() void { assert(x == 10000); const y = @floatToInt(i32, f32(1e4)); assert(y == 10000); - expectFloatToInt(u8, 255.1, 255); - expectFloatToInt(i8, 127.2, 127); - expectFloatToInt(i8, -128.2, -128); + expectFloatToInt(f16, 255.1, u8, 255); + expectFloatToInt(f16, 127.2, i8, 127); + expectFloatToInt(f16, -128.2, i8, -128); + expectFloatToInt(f32, 255.1, u8, 255); + expectFloatToInt(f32, 127.2, i8, 127); + expectFloatToInt(f32, -128.2, i8, -128); } -fn expectFloatToInt(comptime T: type, f: f32, i: T) void { - assert(@floatToInt(T, f) == i); +fn expectFloatToInt(comptime F: type, f: F, comptime I: type, i: I) void { + assert(@floatToInt(I, f) == i); } test "cast u128 to f128 and back" { @@ -418,6 +421,16 @@ test "@intCast comptime_int" { } test "@floatCast comptime_int and comptime_float" { + { + const result = @floatCast(f16, 1234); + assert(@typeOf(result) == f16); + assert(result == 1234.0); + } + { + const result = @floatCast(f16, 1234.0); + assert(@typeOf(result) == f16); + assert(result == 1234.0); + } { const result = @floatCast(f32, 1234); assert(@typeOf(result) == f32); @@ -431,6 +444,11 @@ test "@floatCast comptime_int and comptime_float" { } test "comptime_int @intToFloat" { + { + const result = @intToFloat(f16, 1234); + assert(@typeOf(result) == f16); + assert(result == 1234.0); + } { const result = @intToFloat(f32, 1234); assert(@typeOf(result) == f32); diff --git a/test/cases/math.zig b/test/cases/math.zig index 08388d3df8..1807e5a1b0 100644 --- a/test/cases/math.zig +++ b/test/cases/math.zig @@ -6,15 +6,20 @@ test "division" { } fn testDivision() void { assert(div(u32, 13, 3) == 4); + assert(div(f16, 1.0, 2.0) == 0.5); assert(div(f32, 1.0, 2.0) == 0.5); assert(divExact(u32, 55, 11) == 5); assert(divExact(i32, -55, 11) == -5); + assert(divExact(f16, 55.0, 11.0) == 5.0); + assert(divExact(f16, -55.0, 11.0) == -5.0); assert(divExact(f32, 55.0, 11.0) == 5.0); assert(divExact(f32, -55.0, 11.0) == -5.0); assert(divFloor(i32, 5, 3) == 1); assert(divFloor(i32, -5, 3) == -2); + assert(divFloor(f16, 5.0, 3.0) == 1.0); + assert(divFloor(f16, -5.0, 3.0) == -2.0); assert(divFloor(f32, 5.0, 3.0) == 1.0); assert(divFloor(f32, -5.0, 3.0) == -2.0); assert(divFloor(i32, -0x80000000, -2) == 0x40000000); @@ -24,6 +29,8 @@ fn testDivision() void { assert(divTrunc(i32, 5, 3) == 1); assert(divTrunc(i32, -5, 3) == -1); + assert(divTrunc(f16, 5.0, 3.0) == 1.0); + assert(divTrunc(f16, -5.0, 3.0) == -1.0); assert(divTrunc(f32, 5.0, 3.0) == 1.0); assert(divTrunc(f32, -5.0, 3.0) == -1.0); @@ -435,10 +442,11 @@ test "comptime float rem int" { } test "remainder division" { + comptime remdiv(f16); comptime remdiv(f32); comptime remdiv(f64); comptime remdiv(f128); - remdiv(f32); + remdiv(f16); remdiv(f64); remdiv(f128); } @@ -453,6 +461,8 @@ test "@sqrt" { comptime testSqrt(f64, 12.0); testSqrt(f32, 13.0); comptime testSqrt(f32, 13.0); + testSqrt(f16, 13.0); + comptime testSqrt(f16, 13.0); const x = 14.0; const y = x * x; diff --git a/test/cases/misc.zig b/test/cases/misc.zig index d539f79a57..0f181a7b4e 100644 --- a/test/cases/misc.zig +++ b/test/cases/misc.zig @@ -53,6 +53,7 @@ test "@IntType builtin" { } test "floating point primitive bit counts" { + assert(f16.bit_count == 16); assert(f32.bit_count == 32); assert(f64.bit_count == 64); } -- cgit v1.2.3 From 35463526cceb91243410bdab4d74e2d5b3c60f66 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 2 Jul 2018 15:49:49 -0400 Subject: add runtime safety for `@intToEnum`; add docs for runtime safety See #367 --- doc/langref.html.in | 225 ++++++++++++++++++++++++++++++++++++++++++------ src/codegen.cpp | 19 +++- test/runtime_safety.zig | 18 ++++ 3 files changed, 233 insertions(+), 29 deletions(-) (limited to 'src/codegen.cpp') diff --git a/doc/langref.html.in b/doc/langref.html.in index 15e04459bd..1da4205b89 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -6144,7 +6144,14 @@ fn assert(ok: bool) void { if (!ok) unreachable; // assertion failure } {#code_end#} -

At runtime crashes with the message reached unreachable code and a stack trace.

+

At runtime:

+ {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + std.debug.assert(false); +} + {#code_end#} {#header_close#} {#header_open|Index out of Bounds#}

At compile-time:

@@ -6154,7 +6161,16 @@ comptime { const garbage = array[5]; } {#code_end#} -

At runtime crashes with the message index out of bounds and a stack trace.

+

At runtime:

+ {#code_begin|exe_err#} +pub fn main() void { + var x = foo("hello"); +} + +fn foo(x: []const u8) u8 { + return x[5]; +} + {#code_end#} {#header_close#} {#header_open|Cast Negative Number to Unsigned Integer#}

At compile-time:

@@ -6164,10 +6180,18 @@ comptime { const unsigned = @intCast(u32, value); } {#code_end#} -

At runtime crashes with the message attempt to cast negative value to unsigned integer and a stack trace.

+

At runtime:

+ {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + var value: i32 = -1; + var unsigned = @intCast(u32, value); + std.debug.warn("value: {}\n", unsigned); +} + {#code_end#}

- If you are trying to obtain the maximum value of an unsigned integer, use @maxValue(T), - where T is the integer type, such as u32. + To obtain the maximum value of an unsigned integer, use {#link|@maxValue#}.

{#header_close#} {#header_open|Cast Truncates Data#} @@ -6178,11 +6202,18 @@ comptime { const byte = @intCast(u8, spartan_count); } {#code_end#} -

At runtime crashes with the message integer cast truncated bits and a stack trace.

+

At runtime:

+ {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + var spartan_count: u16 = 300; + const byte = @intCast(u8, spartan_count); + std.debug.warn("value: {}\n", byte); +} + {#code_end#}

- If you are trying to truncate bits, use @truncate(T, value), - where T is the integer type, such as u32, and value - is the value you want to truncate. + To truncate bits, use {#link|@truncate#}.

{#header_close#} {#header_open|Integer Overflow#} @@ -6194,9 +6225,9 @@ comptime {
  • - (negation)
  • * (multiplication)
  • / (division)
  • -
  • @divTrunc (division)
  • -
  • @divFloor (division)
  • -
  • @divExact (division)
  • +
  • {#link|@divTrunc#} (division)
  • +
  • {#link|@divFloor#} (division)
  • +
  • {#link|@divExact#} (division)
  • Example with addition at compile-time:

    {#code_begin|test_err|operation caused overflow#} @@ -6205,7 +6236,16 @@ comptime { byte += 1; } {#code_end#} -

    At runtime crashes with the message integer overflow and a stack trace.

    +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + var byte: u8 = 255; + byte += 1; + std.debug.warn("value: {}\n", byte); +} + {#code_end#} {#header_close#} {#header_open|Standard Library Math Functions#}

    These functions provided by the standard library return possible errors.

    @@ -6240,13 +6280,13 @@ pub fn main() !void { occurred, as well as returning the overflowed bits:

      -
    • @addWithOverflow
    • -
    • @subWithOverflow
    • -
    • @mulWithOverflow
    • -
    • @shlWithOverflow
    • +
    • {#link|@addWithOverflow#}
    • +
    • {#link|@subWithOverflow#}
    • +
    • {#link|@mulWithOverflow#}
    • +
    • {#link|@shlWithOverflow#}

    - Example of @addWithOverflow: + Example of {#link|@addWithOverflow#}:

    {#code_begin|exe#} const warn = @import("std").debug.warn; @@ -6292,7 +6332,16 @@ comptime { const x = @shlExact(u8(0b01010101), 2); } {#code_end#} -

    At runtime crashes with the message left shift overflowed bits and a stack trace.

    +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + var x: u8 = 0b01010101; + var y = @shlExact(x, 2); + std.debug.warn("value: {}\n", y); +} + {#code_end#} {#header_close#} {#header_open|Exact Right Shift Overflow#}

    At compile-time:

    @@ -6301,7 +6350,16 @@ comptime { const x = @shrExact(u8(0b10101010), 2); } {#code_end#} -

    At runtime crashes with the message right shift overflowed bits and a stack trace.

    +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + var x: u8 = 0b10101010; + var y = @shrExact(x, 2); + std.debug.warn("value: {}\n", y); +} + {#code_end#} {#header_close#} {#header_open|Division by Zero#}

    At compile-time:

    @@ -6312,8 +6370,17 @@ comptime { const c = a / b; } {#code_end#} -

    At runtime crashes with the message division by zero and a stack trace.

    +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); +pub fn main() void { + var a: u32 = 1; + var b: u32 = 0; + var c = a / b; + std.debug.warn("value: {}\n", c); +} + {#code_end#} {#header_close#} {#header_open|Remainder Division by Zero#}

    At compile-time:

    @@ -6324,14 +6391,57 @@ comptime { const c = a % b; } {#code_end#} -

    At runtime crashes with the message remainder division by zero and a stack trace.

    +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); +pub fn main() void { + var a: u32 = 10; + var b: u32 = 0; + var c = a % b; + std.debug.warn("value: {}\n", c); +} + {#code_end#} {#header_close#} {#header_open|Exact Division Remainder#} -

    TODO

    +

    At compile-time:

    + {#code_begin|test_err|exact division had a remainder#} +comptime { + const a: u32 = 10; + const b: u32 = 3; + const c = @divExact(a, b); +} + {#code_end#} +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + var a: u32 = 10; + var b: u32 = 3; + var c = @divExact(a, b); + std.debug.warn("value: {}\n", c); +} + {#code_end#} {#header_close#} {#header_open|Slice Widen Remainder#} -

    TODO

    +

    At compile-time:

    + {#code_begin|test_err|unable to convert#} +comptime { + var bytes = [5]u8{ 1, 2, 3, 4, 5 }; + var slice = @bytesToSlice(u32, bytes); +} + {#code_end#} +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + var bytes = [5]u8{ 1, 2, 3, 4, 5 }; + var slice = @bytesToSlice(u32, bytes[0..]); + std.debug.warn("value: {}\n", slice[0]); +} + {#code_end#} {#header_close#} {#header_open|Attempt to Unwrap Null#}

    At compile-time:

    @@ -6341,7 +6451,16 @@ comptime { const number = optional_number.?; } {#code_end#} -

    At runtime crashes with the message attempt to unwrap null and a stack trace.

    +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + var optional_number: ?i32 = null; + var number = optional_number.?; + std.debug.warn("value: {}\n", number); +} + {#code_end#}

    One way to avoid this crash is to test for null instead of assuming non-null, with the if expression:

    {#code_begin|exe|test#} @@ -6356,6 +6475,7 @@ pub fn main() void { } } {#code_end#} + {#see_also|Optionals#} {#header_close#} {#header_open|Attempt to Unwrap Error#}

    At compile-time:

    @@ -6368,7 +6488,19 @@ fn getNumberOrFail() !i32 { return error.UnableToReturnNumber; } {#code_end#} -

    At runtime crashes with the message attempt to unwrap error: ErrorCode and a stack trace.

    +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + const number = getNumberOrFail() catch unreachable; + std.debug.warn("value: {}\n", number); +} + +fn getNumberOrFail() !i32 { + return error.UnableToReturnNumber; +} + {#code_end#}

    One way to avoid this crash is to test for an error instead of assuming a successful result, with the if expression:

    {#code_begin|exe#} @@ -6388,6 +6520,7 @@ fn getNumberOrFail() !i32 { return error.UnableToReturnNumber; } {#code_end#} + {#see_also|Errors#} {#header_close#} {#header_open|Invalid Error Code#}

    At compile-time:

    @@ -6398,11 +6531,47 @@ comptime { const invalid_err = @intToError(number); } {#code_end#} -

    At runtime crashes with the message invalid error code and a stack trace.

    +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + var err = error.AnError; + var number = @errorToInt(err) + 500; + var invalid_err = @intToError(number); + std.debug.warn("value: {}\n", number); +} + {#code_end#} {#header_close#} {#header_open|Invalid Enum Cast#} -

    TODO

    +

    At compile-time:

    + {#code_begin|test_err|has no tag matching integer value 3#} +const Foo = enum { + A, + B, + C, +}; +comptime { + const a: u2 = 3; + const b = @intToEnum(Foo, a); +} + {#code_end#} +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); + +const Foo = enum { + A, + B, + C, +}; +pub fn main() void { + var a: u2 = 3; + var b = @intToEnum(Foo, a); + std.debug.warn("value: {}\n", @tagName(b)); +} + {#code_end#} {#header_close#} {#header_open|Invalid Error Set Cast#} diff --git a/src/codegen.cpp b/src/codegen.cpp index 4419f4fc84..9c37c174d6 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2673,8 +2673,25 @@ static LLVMValueRef ir_render_int_to_enum(CodeGen *g, IrExecutable *executable, TypeTableEntry *tag_int_type = wanted_type->data.enumeration.tag_int_type; LLVMValueRef target_val = ir_llvm_value(g, instruction->target); - return gen_widen_or_shorten(g, ir_want_runtime_safety(g, &instruction->base), + LLVMValueRef tag_int_value = gen_widen_or_shorten(g, ir_want_runtime_safety(g, &instruction->base), instruction->target->value.type, tag_int_type, target_val); + + if (ir_want_runtime_safety(g, &instruction->base)) { + LLVMBasicBlockRef bad_value_block = LLVMAppendBasicBlock(g->cur_fn_val, "BadValue"); + LLVMBasicBlockRef ok_value_block = LLVMAppendBasicBlock(g->cur_fn_val, "OkValue"); + size_t field_count = wanted_type->data.enumeration.src_field_count; + LLVMValueRef switch_instr = LLVMBuildSwitch(g->builder, tag_int_value, bad_value_block, field_count); + for (size_t field_i = 0; field_i < field_count; field_i += 1) { + LLVMValueRef this_tag_int_value = bigint_to_llvm_const(tag_int_type->type_ref, + &wanted_type->data.enumeration.fields[field_i].value); + LLVMAddCase(switch_instr, this_tag_int_value, ok_value_block); + } + LLVMPositionBuilderAtEnd(g->builder, bad_value_block); + gen_safety_crash(g, PanicMsgIdBadEnumValue); + + LLVMPositionBuilderAtEnd(g->builder, ok_value_block); + } + return tag_int_value; } static LLVMValueRef ir_render_int_to_err(CodeGen *g, IrExecutable *executable, IrInstructionIntToErr *instruction) { diff --git a/test/runtime_safety.zig b/test/runtime_safety.zig index a7e8d6dc0e..3d58dfe748 100644 --- a/test/runtime_safety.zig +++ b/test/runtime_safety.zig @@ -1,6 +1,24 @@ const tests = @import("tests.zig"); pub fn addCases(cases: *tests.CompareOutputContext) void { + cases.addRuntimeSafety("@intToEnum - no matching tag value", + \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { + \\ @import("std").os.exit(126); + \\} + \\const Foo = enum { + \\ A, + \\ B, + \\ C, + \\}; + \\pub fn main() void { + \\ baz(bar(3)); + \\} + \\fn bar(a: u2) Foo { + \\ return @intToEnum(Foo, a); + \\} + \\fn baz(a: Foo) void {} + ); + cases.addRuntimeSafety("@floatToInt cannot fit - negative to unsigned", \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); -- cgit v1.2.3