diff --git a/src/all_types.hpp b/src/all_types.hpp index daf37e5956..f6342cb8a0 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1129,6 +1129,7 @@ enum BuiltinFnId { BuiltinFnIdCmpExchange, BuiltinFnIdFence, BuiltinFnIdDivExact, + BuiltinFnIdTruncate, }; struct BuiltinFnEntry { diff --git a/src/analyze.cpp b/src/analyze.cpp index 133cbf47fa..233bc4e7b2 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -4686,6 +4686,44 @@ static TypeTableEntry *analyze_div_exact(CodeGen *g, ImportTableEntry *import, } } +static TypeTableEntry *analyze_truncate(CodeGen *g, ImportTableEntry *import, + BlockContext *context, AstNode *node) +{ + assert(node->type == NodeTypeFnCallExpr); + + AstNode **op1 = &node->data.fn_call_expr.params.at(0); + AstNode **op2 = &node->data.fn_call_expr.params.at(1); + + TypeTableEntry *dest_type = analyze_type_expr(g, import, context, *op1); + TypeTableEntry *src_type = analyze_expression(g, import, context, nullptr, *op2); + + if (dest_type->id == TypeTableEntryIdInvalid || src_type->id == TypeTableEntryIdInvalid) { + return g->builtin_types.entry_invalid; + } else if (dest_type->id != TypeTableEntryIdInt) { + add_node_error(g, *op1, + buf_sprintf("expected integer type, got '%s'", buf_ptr(&dest_type->name))); + return g->builtin_types.entry_invalid; + } else if (src_type->id != TypeTableEntryIdInt) { + add_node_error(g, *op2, + buf_sprintf("expected integer type, got '%s'", buf_ptr(&src_type->name))); + return g->builtin_types.entry_invalid; + } else if (src_type->data.integral.is_signed != dest_type->data.integral.is_signed) { + const char *sign_str = dest_type->data.integral.is_signed ? "signed" : "unsigned"; + add_node_error(g, *op2, + buf_sprintf("expected %s integer type, got '%s'", sign_str, buf_ptr(&src_type->name))); + return g->builtin_types.entry_invalid; + } else if (src_type->data.integral.bit_count <= dest_type->data.integral.bit_count) { + add_node_error(g, *op2, + buf_sprintf("type '%s' has same or fewer bits than destination type '%s'", + buf_ptr(&src_type->name), buf_ptr(&dest_type->name))); + return g->builtin_types.entry_invalid; + } + + // TODO const expr eval + + return dest_type; +} + static TypeTableEntry *analyze_builtin_fn_call_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context, TypeTableEntry *expected_type, AstNode *node) { @@ -5028,6 +5066,8 @@ static TypeTableEntry *analyze_builtin_fn_call_expr(CodeGen *g, ImportTableEntry return analyze_fence(g, import, context, node); case BuiltinFnIdDivExact: return analyze_div_exact(g, import, context, node); + case BuiltinFnIdTruncate: + return analyze_truncate(g, import, context, node); } zig_unreachable(); } diff --git a/src/codegen.cpp b/src/codegen.cpp index 8163cf9fc9..13f0c9eee0 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -473,6 +473,18 @@ static LLVMValueRef gen_div_exact(CodeGen *g, AstNode *node) { return gen_div(g, node, op1_val, op2_val, get_expr_type(op1_node), true); } +static LLVMValueRef gen_truncate(CodeGen *g, AstNode *node) { + assert(node->type == NodeTypeFnCallExpr); + + TypeTableEntry *dest_type = get_type_for_type_node(node->data.fn_call_expr.params.at(0)); + AstNode *src_node = node->data.fn_call_expr.params.at(1); + + LLVMValueRef src_val = gen_expr(g, src_node); + + set_debug_source_node(g, node); + return LLVMBuildTrunc(g->builder, src_val, dest_type->type_ref, ""); +} + static LLVMValueRef gen_shl_with_overflow(CodeGen *g, AstNode *node) { assert(node->type == NodeTypeFnCallExpr); @@ -661,6 +673,8 @@ static LLVMValueRef gen_builtin_fn_call_expr(CodeGen *g, AstNode *node) { return gen_fence(g, node); case BuiltinFnIdDivExact: return gen_div_exact(g, node); + case BuiltinFnIdTruncate: + return gen_truncate(g, node); } zig_unreachable(); } @@ -729,6 +743,24 @@ static LLVMValueRef gen_widen_or_shorten(CodeGen *g, AstNode *source_node, TypeT zig_unreachable(); } + if (actual_bits >= wanted_bits && actual_type->id == TypeTableEntryIdInt && + !wanted_type->data.integral.is_signed && actual_type->data.integral.is_signed && + want_debug_safety(g, source_node)) + { + set_debug_source_node(g, source_node); + LLVMValueRef zero = LLVMConstNull(actual_type->type_ref); + LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntSGE, expr_val, zero, ""); + + LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "SignCastOk"); + LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "SignCastFail"); + LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); + + LLVMPositionBuilderAtEnd(g->builder, fail_block); + gen_debug_safety_crash(g); + + LLVMPositionBuilderAtEnd(g->builder, ok_block); + } + if (actual_bits == wanted_bits) { return expr_val; } else if (actual_bits < wanted_bits) { @@ -752,7 +784,26 @@ static LLVMValueRef gen_widen_or_shorten(CodeGen *g, AstNode *source_node, TypeT return LLVMBuildFPTrunc(g->builder, expr_val, wanted_type->type_ref, ""); } else if (actual_type->id == TypeTableEntryIdInt) { set_debug_source_node(g, source_node); - return LLVMBuildTrunc(g->builder, expr_val, wanted_type->type_ref, ""); + LLVMValueRef trunc_val = LLVMBuildTrunc(g->builder, expr_val, wanted_type->type_ref, ""); + if (!want_debug_safety(g, source_node)) { + return trunc_val; + } + LLVMValueRef orig_val; + if (actual_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, ""); + } + LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, expr_val, orig_val, ""); + LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "CastShortenOk"); + LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "CastShortenFail"); + LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); + + LLVMPositionBuilderAtEnd(g->builder, fail_block); + gen_debug_safety_crash(g); + + LLVMPositionBuilderAtEnd(g->builder, ok_block); + return trunc_val; } else { zig_unreachable(); } @@ -4568,6 +4619,7 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn_with_arg_count(g, BuiltinFnIdCmpExchange, "cmpxchg", 5); create_builtin_fn_with_arg_count(g, BuiltinFnIdFence, "fence", 1); create_builtin_fn_with_arg_count(g, BuiltinFnIdDivExact, "div_exact", 2); + create_builtin_fn_with_arg_count(g, BuiltinFnIdTruncate, "truncate", 2); } static void init(CodeGen *g, Buf *source_path) { diff --git a/src/eval.cpp b/src/eval.cpp index b31cc47daa..4499b8529f 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -827,6 +827,7 @@ static bool eval_fn_call_builtin(EvalFn *ef, AstNode *node, ConstExprValue *out_ case BuiltinFnIdErrName: case BuiltinFnIdEmbedFile: case BuiltinFnIdCmpExchange: + case BuiltinFnIdTruncate: zig_panic("TODO"); case BuiltinFnIdBreakpoint: case BuiltinFnIdInvalid: diff --git a/std/rand.zig b/std/rand.zig index b7bf8be04b..725863d244 100644 --- a/std/rand.zig +++ b/std/rand.zig @@ -15,7 +15,7 @@ pub struct Rand { var i : isize = 1; var prev_value: u64w = seed; while (i < ARRAY_SIZE; i += 1) { - r.array[i] = u32((prev_value ^ (prev_value << 30)) * 0x6c078965 + u64w(i)); + r.array[i] = @truncate(u32, (prev_value ^ (prev_value << 30)) * 0x6c078965 + u64w(i)); prev_value = r.array[i]; } return r; diff --git a/test/run_tests.cpp b/test/run_tests.cpp index f4f958b911..aee5fe17fe 100644 --- a/test/run_tests.cpp +++ b/test/run_tests.cpp @@ -1368,6 +1368,20 @@ fn add(x: i8w, y: i32) { } )SOURCE", 1, ".tmp_source.zig:3:17: error: incompatible types: 'i8w' and 'i32'"); + add_compile_fail_case("truncate sign mismatch", R"SOURCE( +fn f() { + const x: u32 = 10; + @truncate(i8, x); +} + )SOURCE", 1, ".tmp_source.zig:4:19: error: expected signed integer type, got 'u32'"); + + add_compile_fail_case("truncate same bit count", R"SOURCE( +fn f() { + const x: i8 = 10; + @truncate(i8, x); +} + )SOURCE", 1, ".tmp_source.zig:4:19: error: type 'i8' has same or fewer bits than destination type 'i8'"); + } ////////////////////////////////////////////////////////////////////////////// @@ -1476,6 +1490,26 @@ fn widen_slice(slice: []u8) -> []i32 { } )SOURCE"); + add_debug_safety_case("value does not fit in shortening cast", R"SOURCE( +pub fn main(args: [][]u8) -> %void { + shorten_cast(200); +} +#static_eval_enable(false) +fn shorten_cast(x: i32) -> i8 { + i8(x) +} + )SOURCE"); + + add_debug_safety_case("signed integer not fitting in cast to unsigned integer", R"SOURCE( +pub fn main(args: [][]u8) -> %void { + unsigned_cast(-10); +} +#static_eval_enable(false) +fn unsigned_cast(x: i32) -> u32 { + u32(x) +} + )SOURCE"); + } ////////////////////////////////////////////////////////////////////////////// diff --git a/test/self_hosted.zig b/test/self_hosted.zig index 99ae4800de..ab5fde33a9 100644 --- a/test/self_hosted.zig +++ b/test/self_hosted.zig @@ -1631,3 +1631,12 @@ struct SillyStruct { const here_is_a_null_literal = SillyStruct { .context = null, }; + +#attribute("test") +fn truncate() { + assert(test_truncate(0x10fd) == 0xfd); +} +#static_eval_enable(false) +fn test_truncate(x: u32) -> u8 { + @truncate(u8, x) +}