From 35d3444e2742faa3c2e805cdcbfeceaf0287eefc Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 9 Aug 2017 10:09:38 -0400 Subject: [PATCH] more intuitive left shift and right shift operators Before: * << is left shift, not allowed to shift 1 bits out * <<% is left shift, allowed to shift 1 bits out * >> is right shift, allowed to shift 1 bits out After: * << is left shift, allowed to shift 1 bits out * >> is right shift, allowed to shift 1 bits out * @shlExact is left shift, not allowed to shift 1 bits out * @shrExact is right shift, not allowed to shift 1 bits out Closes #413 --- src/all_types.hpp | 14 ++++---- src/ast_render.cpp | 2 -- src/bigint.cpp | 2 +- src/bigint.hpp | 2 +- src/codegen.cpp | 67 +++++++++++++++++++++++++++++-------- src/error.cpp | 1 + src/error.hpp | 1 + src/ir.cpp | 73 +++++++++++++++++++++++++++++++---------- src/ir_print.cpp | 10 +++--- src/parser.cpp | 2 -- src/tokenizer.cpp | 22 ------------- src/tokenizer.hpp | 2 -- src/zig_llvm.cpp | 15 ++++++++- src/zig_llvm.hpp | 4 +++ std/base64.zig | 22 ++++++------- std/math/exp2.zig | 2 +- std/math/expm1.zig | 4 +-- std/math/ilogb.zig | 4 +-- std/math/ln.zig | 4 +-- std/math/log10.zig | 6 ++-- std/math/log1p.zig | 4 +-- std/math/log2.zig | 6 ++-- std/math/modf.zig | 4 +-- std/rand.zig | 4 +-- std/special/builtin.zig | 32 +++++++++--------- test/cases/math.zig | 45 ++++++++++++++++++++----- test/compile_errors.zig | 14 ++++++++ test/debug_safety.zig | 34 +++++++++++++++++-- 28 files changed, 274 insertions(+), 128 deletions(-) diff --git a/src/all_types.hpp b/src/all_types.hpp index eb54eae539..88a42d3a25 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -493,7 +493,6 @@ enum BinOpType { BinOpTypeAssignMinus, BinOpTypeAssignMinusWrap, BinOpTypeAssignBitShiftLeft, - BinOpTypeAssignBitShiftLeftWrap, BinOpTypeAssignBitShiftRight, BinOpTypeAssignBitAnd, BinOpTypeAssignBitXor, @@ -512,7 +511,6 @@ enum BinOpType { BinOpTypeBinXor, BinOpTypeBinAnd, BinOpTypeBitShiftLeft, - BinOpTypeBitShiftLeftWrap, BinOpTypeBitShiftRight, BinOpTypeAdd, BinOpTypeAddWrap, @@ -1232,6 +1230,8 @@ enum BuiltinFnId { BuiltinFnIdOffsetOf, BuiltinFnIdInlineCall, BuiltinFnIdTypeId, + BuiltinFnIdShlExact, + BuiltinFnIdShrExact, }; struct BuiltinFnEntry { @@ -1248,7 +1248,8 @@ enum PanicMsgId { PanicMsgIdCastNegativeToUnsigned, PanicMsgIdCastTruncatedData, PanicMsgIdIntegerOverflow, - PanicMsgIdShiftOverflowedBits, + PanicMsgIdShlOverflowedBits, + PanicMsgIdShrOverflowedBits, PanicMsgIdDivisionByZero, PanicMsgIdRemainderDivisionByZero, PanicMsgIdExactDivisionRemainder, @@ -1930,9 +1931,10 @@ enum IrBinOp { IrBinOpBinOr, IrBinOpBinXor, IrBinOpBinAnd, - IrBinOpBitShiftLeft, - IrBinOpBitShiftLeftWrap, - IrBinOpBitShiftRight, + IrBinOpBitShiftLeftLossy, + IrBinOpBitShiftLeftExact, + IrBinOpBitShiftRightLossy, + IrBinOpBitShiftRightExact, IrBinOpAdd, IrBinOpAddWrap, IrBinOpSub, diff --git a/src/ast_render.cpp b/src/ast_render.cpp index 0b6e6c149b..5613347f7d 100644 --- a/src/ast_render.cpp +++ b/src/ast_render.cpp @@ -26,7 +26,6 @@ static const char *bin_op_str(BinOpType bin_op) { case BinOpTypeBinXor: return "^"; case BinOpTypeBinAnd: return "&"; case BinOpTypeBitShiftLeft: return "<<"; - case BinOpTypeBitShiftLeftWrap: return "<<%"; case BinOpTypeBitShiftRight: return ">>"; case BinOpTypeAdd: return "+"; case BinOpTypeAddWrap: return "+%"; @@ -46,7 +45,6 @@ static const char *bin_op_str(BinOpType bin_op) { case BinOpTypeAssignMinus: return "-="; case BinOpTypeAssignMinusWrap: return "-%="; case BinOpTypeAssignBitShiftLeft: return "<<="; - case BinOpTypeAssignBitShiftLeftWrap: return "<<%="; case BinOpTypeAssignBitShiftRight: return ">>="; case BinOpTypeAssignBitAnd: return "&="; case BinOpTypeAssignBitXor: return "^="; diff --git a/src/bigint.cpp b/src/bigint.cpp index 3dc0f7d08a..db55d25fc9 100644 --- a/src/bigint.cpp +++ b/src/bigint.cpp @@ -799,7 +799,7 @@ void bigint_shl(BigInt *dest, const BigInt *op1, const BigInt *op2) { bigint_normalize(dest); } -void bigint_shl_wrap(BigInt *dest, const BigInt *op1, const BigInt *op2, size_t bit_count, bool is_signed) { +void bigint_shl_trunc(BigInt *dest, const BigInt *op1, const BigInt *op2, size_t bit_count, bool is_signed) { BigInt unwrapped = {0}; bigint_shl(&unwrapped, op1, op2); bigint_truncate(dest, &unwrapped, bit_count, is_signed); diff --git a/src/bigint.hpp b/src/bigint.hpp index 3c29106365..fb1b62fb76 100644 --- a/src/bigint.hpp +++ b/src/bigint.hpp @@ -66,7 +66,7 @@ void bigint_and(BigInt *dest, const BigInt *op1, const BigInt *op2); void bigint_xor(BigInt *dest, const BigInt *op1, const BigInt *op2); void bigint_shl(BigInt *dest, const BigInt *op1, const BigInt *op2); -void bigint_shl_wrap(BigInt *dest, const BigInt *op1, const BigInt *op2, size_t bit_count, bool is_signed); +void bigint_shl_trunc(BigInt *dest, const BigInt *op1, const BigInt *op2, size_t bit_count, bool is_signed); void bigint_shr(BigInt *dest, const BigInt *op1, const BigInt *op2); void bigint_negate(BigInt *dest, const BigInt *op); diff --git a/src/codegen.cpp b/src/codegen.cpp index 2233b6da04..6d323a8e2c 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -694,8 +694,10 @@ static Buf *panic_msg_buf(PanicMsgId msg_id) { return buf_create_from_str("integer cast truncated bits"); case PanicMsgIdIntegerOverflow: return buf_create_from_str("integer overflow"); - case PanicMsgIdShiftOverflowedBits: + case PanicMsgIdShlOverflowedBits: return buf_create_from_str("left shift overflowed bits"); + case PanicMsgIdShrOverflowedBits: + return buf_create_from_str("right shift overflowed bits"); case PanicMsgIdDivisionByZero: return buf_create_from_str("division by zero"); case PanicMsgIdRemainderDivisionByZero: @@ -1153,7 +1155,7 @@ static LLVMValueRef ir_render_return(CodeGen *g, IrExecutable *executable, IrIns static LLVMValueRef gen_overflow_shl_op(CodeGen *g, TypeTableEntry *type_entry, LLVMValueRef val1, LLVMValueRef val2) { - // for unsigned left shifting, we do the wrapping shift, then logically shift + // for unsigned left shifting, we do the lossy shift, then logically shift // right the same number of bits // if the values don't match, we have an overflow // for signed left shifting we do the same except arithmetic shift right @@ -1174,7 +1176,32 @@ static LLVMValueRef gen_overflow_shl_op(CodeGen *g, TypeTableEntry *type_entry, LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); LLVMPositionBuilderAtEnd(g->builder, fail_block); - gen_debug_safety_crash(g, PanicMsgIdShiftOverflowedBits); + gen_debug_safety_crash(g, PanicMsgIdShlOverflowedBits); + + LLVMPositionBuilderAtEnd(g->builder, ok_block); + return result; +} + +static LLVMValueRef gen_overflow_shr_op(CodeGen *g, TypeTableEntry *type_entry, + LLVMValueRef val1, LLVMValueRef val2) +{ + assert(type_entry->id == TypeTableEntryIdInt); + + LLVMValueRef result; + if (type_entry->data.integral.is_signed) { + result = LLVMBuildAShr(g->builder, val1, val2, ""); + } else { + result = LLVMBuildLShr(g->builder, val1, val2, ""); + } + LLVMValueRef orig_val = LLVMBuildShl(g->builder, result, val2, ""); + LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, val1, orig_val, ""); + + LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "OverflowOk"); + LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "OverflowFail"); + LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); + + LLVMPositionBuilderAtEnd(g->builder, fail_block); + gen_debug_safety_crash(g, PanicMsgIdShrOverflowedBits); LLVMPositionBuilderAtEnd(g->builder, ok_block); return result; @@ -1496,12 +1523,12 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable, return LLVMBuildXor(g->builder, op1_value, op2_value, ""); case IrBinOpBinAnd: return LLVMBuildAnd(g->builder, op1_value, op2_value, ""); - case IrBinOpBitShiftLeft: - case IrBinOpBitShiftLeftWrap: + case IrBinOpBitShiftLeftLossy: + case IrBinOpBitShiftLeftExact: { assert(type_entry->id == TypeTableEntryIdInt); - bool is_wrapping = (op_id == IrBinOpBitShiftLeftWrap); - if (is_wrapping) { + bool is_sloppy = (op_id == IrBinOpBitShiftLeftLossy); + if (is_sloppy) { return LLVMBuildShl(g->builder, op1_value, op2_value, ""); } else if (want_debug_safety) { return gen_overflow_shl_op(g, type_entry, op1_value, op2_value); @@ -1511,12 +1538,24 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable, return ZigLLVMBuildNUWShl(g->builder, op1_value, op2_value, ""); } } - case IrBinOpBitShiftRight: - assert(type_entry->id == TypeTableEntryIdInt); - if (type_entry->data.integral.is_signed) { - return LLVMBuildAShr(g->builder, op1_value, op2_value, ""); - } else { - return LLVMBuildLShr(g->builder, op1_value, op2_value, ""); + case IrBinOpBitShiftRightLossy: + case IrBinOpBitShiftRightExact: + { + assert(type_entry->id == TypeTableEntryIdInt); + bool is_sloppy = (op_id == IrBinOpBitShiftRightLossy); + if (is_sloppy) { + if (type_entry->data.integral.is_signed) { + return LLVMBuildAShr(g->builder, op1_value, op2_value, ""); + } else { + return LLVMBuildLShr(g->builder, op1_value, op2_value, ""); + } + } else if (want_debug_safety) { + return gen_overflow_shr_op(g, type_entry, op1_value, op2_value); + } else if (type_entry->data.integral.is_signed) { + return ZigLLVMBuildAShrExact(g->builder, op1_value, op2_value, ""); + } else { + return ZigLLVMBuildLShrExact(g->builder, op1_value, op2_value, ""); + } } case IrBinOpSub: case IrBinOpSubWrap: @@ -4556,6 +4595,8 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdMod, "mod", 2); create_builtin_fn(g, BuiltinFnIdInlineCall, "inlineCall", SIZE_MAX); create_builtin_fn(g, BuiltinFnIdTypeId, "typeId", 1); + create_builtin_fn(g, BuiltinFnIdShlExact, "shlExact", 2); + create_builtin_fn(g, BuiltinFnIdShrExact, "shrExact", 2); } static const char *bool_to_str(bool b) { diff --git a/src/error.cpp b/src/error.cpp index 32d8601eda..a6953d4ab3 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -25,6 +25,7 @@ const char *err_str(int err) { case ErrorUnexpected: return "unexpected error"; case ErrorExactDivRemainder: return "exact division had a remainder"; case ErrorNegativeDenominator: return "negative denominator"; + case ErrorShiftedOutOneBits: return "exact shift shifted out one bits"; } return "(invalid error)"; } diff --git a/src/error.hpp b/src/error.hpp index 08b66d11f3..d7d9c45baf 100644 --- a/src/error.hpp +++ b/src/error.hpp @@ -25,6 +25,7 @@ enum Error { ErrorUnexpected, ErrorExactDivRemainder, ErrorNegativeDenominator, + ErrorShiftedOutOneBits, }; const char *err_str(int err); diff --git a/src/ir.cpp b/src/ir.cpp index f64bf1032e..875be38654 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -3625,11 +3625,9 @@ static IrInstruction *ir_gen_bin_op(IrBuilder *irb, Scope *scope, AstNode *node) case BinOpTypeAssignMinusWrap: return ir_gen_assign_op(irb, scope, node, IrBinOpSubWrap); case BinOpTypeAssignBitShiftLeft: - return ir_gen_assign_op(irb, scope, node, IrBinOpBitShiftLeft); - case BinOpTypeAssignBitShiftLeftWrap: - return ir_gen_assign_op(irb, scope, node, IrBinOpBitShiftLeftWrap); + return ir_gen_assign_op(irb, scope, node, IrBinOpBitShiftLeftLossy); case BinOpTypeAssignBitShiftRight: - return ir_gen_assign_op(irb, scope, node, IrBinOpBitShiftRight); + return ir_gen_assign_op(irb, scope, node, IrBinOpBitShiftRightLossy); case BinOpTypeAssignBitAnd: return ir_gen_assign_op(irb, scope, node, IrBinOpBinAnd); case BinOpTypeAssignBitXor: @@ -3663,11 +3661,9 @@ static IrInstruction *ir_gen_bin_op(IrBuilder *irb, Scope *scope, AstNode *node) case BinOpTypeBinAnd: return ir_gen_bin_op_id(irb, scope, node, IrBinOpBinAnd); case BinOpTypeBitShiftLeft: - return ir_gen_bin_op_id(irb, scope, node, IrBinOpBitShiftLeft); - case BinOpTypeBitShiftLeftWrap: - return ir_gen_bin_op_id(irb, scope, node, IrBinOpBitShiftLeftWrap); + return ir_gen_bin_op_id(irb, scope, node, IrBinOpBitShiftLeftLossy); case BinOpTypeBitShiftRight: - return ir_gen_bin_op_id(irb, scope, node, IrBinOpBitShiftRight); + return ir_gen_bin_op_id(irb, scope, node, IrBinOpBitShiftRightLossy); case BinOpTypeAdd: return ir_gen_bin_op_id(irb, scope, node, IrBinOpAdd); case BinOpTypeAddWrap: @@ -4457,6 +4453,34 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo return ir_build_type_id(irb, scope, node, arg0_value); } + case BuiltinFnIdShlExact: + { + 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; + + return ir_build_bin_op(irb, scope, node, IrBinOpBitShiftLeftExact, arg0_value, arg1_value, true); + } + case BuiltinFnIdShrExact: + { + 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; + + return ir_build_bin_op(irb, scope, node, IrBinOpBitShiftRightExact, arg0_value, arg1_value, true); + } } zig_unreachable(); } @@ -8362,16 +8386,27 @@ static int ir_eval_math_op(TypeTableEntry *type_entry, ConstExprValue *op1_val, assert(is_int); bigint_and(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint); break; - case IrBinOpBitShiftLeft: + case IrBinOpBitShiftLeftExact: assert(is_int); bigint_shl(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint); break; - case IrBinOpBitShiftLeftWrap: + case IrBinOpBitShiftLeftLossy: assert(type_entry->id == TypeTableEntryIdInt); - bigint_shl_wrap(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint, + bigint_shl_trunc(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint, type_entry->data.integral.bit_count, type_entry->data.integral.is_signed); break; - case IrBinOpBitShiftRight: + case IrBinOpBitShiftRightExact: + { + assert(is_int); + bigint_shr(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint); + BigInt orig_bigint; + bigint_shl(&orig_bigint, &out_val->data.x_bigint, &op2_val->data.x_bigint); + if (bigint_cmp(&op1_val->data.x_bigint, &orig_bigint) != CmpEQ) { + return ErrorShiftedOutOneBits; + } + break; + } + case IrBinOpBitShiftRightLossy: assert(is_int); bigint_shr(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint); break; @@ -8591,8 +8626,8 @@ static TypeTableEntry *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstructionBinOp } if (resolved_type->id == TypeTableEntryIdNumLitInt) { - if (op_id == IrBinOpBitShiftLeftWrap) { - op_id = IrBinOpBitShiftLeft; + if (op_id == IrBinOpBitShiftLeftLossy) { + op_id = IrBinOpBitShiftLeftExact; } else if (op_id == IrBinOpAddWrap) { op_id = IrBinOpAdd; } else if (op_id == IrBinOpSubWrap) { @@ -8631,6 +8666,9 @@ static TypeTableEntry *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstructionBinOp } else if (err == ErrorNegativeDenominator) { ir_add_error(ira, &bin_op_instruction->base, buf_sprintf("negative denominator")); return ira->codegen->builtin_types.entry_invalid; + } else if (err == ErrorShiftedOutOneBits) { + ir_add_error(ira, &bin_op_instruction->base, buf_sprintf("exact shift shifted out 1 bits")); + return ira->codegen->builtin_types.entry_invalid; } else { zig_unreachable(); } @@ -8857,9 +8895,10 @@ static TypeTableEntry *ir_analyze_instruction_bin_op(IrAnalyze *ira, IrInstructi case IrBinOpBinOr: case IrBinOpBinXor: case IrBinOpBinAnd: - case IrBinOpBitShiftLeft: - case IrBinOpBitShiftLeftWrap: - case IrBinOpBitShiftRight: + case IrBinOpBitShiftLeftLossy: + case IrBinOpBitShiftLeftExact: + case IrBinOpBitShiftRightLossy: + case IrBinOpBitShiftRightExact: case IrBinOpAdd: case IrBinOpAddWrap: case IrBinOpSub: diff --git a/src/ir_print.cpp b/src/ir_print.cpp index 97e4ae5fbc..3af16802ad 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -92,12 +92,14 @@ static const char *ir_bin_op_id_str(IrBinOp op_id) { return "^"; case IrBinOpBinAnd: return "&"; - case IrBinOpBitShiftLeft: + case IrBinOpBitShiftLeftLossy: return "<<"; - case IrBinOpBitShiftLeftWrap: - return "<<%"; - case IrBinOpBitShiftRight: + case IrBinOpBitShiftLeftExact: + return "@shlExact"; + case IrBinOpBitShiftRightLossy: return ">>"; + case IrBinOpBitShiftRightExact: + return "@shrExact"; case IrBinOpAdd: return "+"; case IrBinOpAddWrap: diff --git a/src/parser.cpp b/src/parser.cpp index ff2e76ac02..a967172b8e 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1131,7 +1131,6 @@ static AstNode *ast_parse_add_expr(ParseContext *pc, size_t *token_index, bool m static BinOpType tok_to_bit_shift_op(Token *token) { switch (token->id) { case TokenIdBitShiftLeft: return BinOpTypeBitShiftLeft; - case TokenIdBitShiftLeftPercent: return BinOpTypeBitShiftLeftWrap; case TokenIdBitShiftRight: return BinOpTypeBitShiftRight; default: return BinOpTypeInvalid; } @@ -1909,7 +1908,6 @@ static BinOpType tok_to_ass_op(Token *token) { case TokenIdMinusEq: return BinOpTypeAssignMinus; case TokenIdMinusPercentEq: return BinOpTypeAssignMinusWrap; case TokenIdBitShiftLeftEq: return BinOpTypeAssignBitShiftLeft; - case TokenIdBitShiftLeftPercentEq: return BinOpTypeAssignBitShiftLeftWrap; case TokenIdBitShiftRightEq: return BinOpTypeAssignBitShiftRight; case TokenIdBitAndEq: return BinOpTypeAssignBitAnd; case TokenIdBitXorEq: return BinOpTypeAssignBitXor; diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index 30bd68d102..a353e23df6 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -201,7 +201,6 @@ enum TokenizeState { TokenizeStateSawBang, TokenizeStateSawLessThan, TokenizeStateSawLessThanLessThan, - TokenizeStateSawShiftLeftPercent, TokenizeStateSawGreaterThan, TokenizeStateSawGreaterThanGreaterThan, TokenizeStateSawDot, @@ -673,24 +672,6 @@ void tokenize(Buf *buf, Tokenization *out) { end_token(&t); t.state = TokenizeStateStart; break; - case '%': - set_token_id(&t, t.cur_tok, TokenIdBitShiftLeftPercent); - t.state = TokenizeStateSawShiftLeftPercent; - break; - default: - t.pos -= 1; - end_token(&t); - t.state = TokenizeStateStart; - continue; - } - break; - case TokenizeStateSawShiftLeftPercent: - switch (c) { - case '=': - set_token_id(&t, t.cur_tok, TokenIdBitShiftLeftPercentEq); - end_token(&t); - t.state = TokenizeStateStart; - break; default: t.pos -= 1; end_token(&t); @@ -1410,7 +1391,6 @@ void tokenize(Buf *buf, Tokenization *out) { case TokenizeStateSawStarPercent: case TokenizeStateSawPlusPercent: case TokenizeStateSawMinusPercent: - case TokenizeStateSawShiftLeftPercent: case TokenizeStateLineString: case TokenizeStateLineStringEnd: end_token(&t); @@ -1451,8 +1431,6 @@ const char * token_name(TokenId id) { case TokenIdBitOrEq: return "|="; case TokenIdBitShiftLeft: return "<<"; case TokenIdBitShiftLeftEq: return "<<="; - case TokenIdBitShiftLeftPercent: return "<<%"; - case TokenIdBitShiftLeftPercentEq: return "<<%="; case TokenIdBitShiftRight: return ">>"; case TokenIdBitShiftRightEq: return ">>="; case TokenIdBitXorEq: return "^="; diff --git a/src/tokenizer.hpp b/src/tokenizer.hpp index b49cbcbd95..c113a152c0 100644 --- a/src/tokenizer.hpp +++ b/src/tokenizer.hpp @@ -23,8 +23,6 @@ enum TokenId { TokenIdBitOrEq, TokenIdBitShiftLeft, TokenIdBitShiftLeftEq, - TokenIdBitShiftLeftPercent, - TokenIdBitShiftLeftPercentEq, TokenIdBitShiftRight, TokenIdBitShiftRightEq, TokenIdBitXorEq, diff --git a/src/zig_llvm.cpp b/src/zig_llvm.cpp index 1eeac98421..d9a35e4c4a 100644 --- a/src/zig_llvm.cpp +++ b/src/zig_llvm.cpp @@ -754,9 +754,22 @@ LLVMValueRef ZigLLVMBuildNSWShl(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMVa LLVMValueRef ZigLLVMBuildNUWShl(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char *name) { - return wrap(unwrap(builder)->CreateShl(unwrap(LHS), unwrap(RHS), name, false, true)); + return wrap(unwrap(builder)->CreateShl(unwrap(LHS), unwrap(RHS), name, true, false)); } +LLVMValueRef ZigLLVMBuildLShrExact(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, + const char *name) +{ + return wrap(unwrap(builder)->CreateLShr(unwrap(LHS), unwrap(RHS), name, true)); +} + +LLVMValueRef ZigLLVMBuildAShrExact(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, + const char *name) +{ + return wrap(unwrap(builder)->CreateAShr(unwrap(LHS), unwrap(RHS), name, true)); +} + + #include "buffer.hpp" bool ZigLLDLink(ZigLLVM_ObjectFormatType oformat, const char **args, size_t arg_count, Buf *diag_buf) { diff --git a/src/zig_llvm.hpp b/src/zig_llvm.hpp index 413d158e6d..fe3468a9b8 100644 --- a/src/zig_llvm.hpp +++ b/src/zig_llvm.hpp @@ -48,6 +48,10 @@ LLVMValueRef ZigLLVMBuildNSWShl(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMVa const char *name); LLVMValueRef ZigLLVMBuildNUWShl(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, const char *name); +LLVMValueRef ZigLLVMBuildLShrExact(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, + const char *name); +LLVMValueRef ZigLLVMBuildAShrExact(LLVMBuilderRef builder, LLVMValueRef LHS, LLVMValueRef RHS, + const char *name); ZigLLVMDIType *ZigLLVMCreateDebugPointerType(ZigLLVMDIBuilder *dibuilder, ZigLLVMDIType *pointee_type, uint64_t size_in_bits, uint64_t align_in_bits, const char *name); diff --git a/std/base64.zig b/std/base64.zig index e495a5b636..863a0c856a 100644 --- a/std/base64.zig +++ b/std/base64.zig @@ -21,11 +21,11 @@ pub fn encodeWithAlphabet(dest: []u8, source: []const u8, alphabet: []const u8) dest[out_index] = alphabet[(source[i] >> 2) & 0x3f]; out_index += 1; - dest[out_index] = alphabet[((source[i] & 0x3) <<% 4) | + dest[out_index] = alphabet[((source[i] & 0x3) << 4) | ((source[i + 1] & 0xf0) >> 4)]; out_index += 1; - dest[out_index] = alphabet[((source[i + 1] & 0xf) <<% 2) | + dest[out_index] = alphabet[((source[i + 1] & 0xf) << 2) | ((source[i + 2] & 0xc0) >> 6)]; out_index += 1; @@ -38,17 +38,17 @@ pub fn encodeWithAlphabet(dest: []u8, source: []const u8, alphabet: []const u8) out_index += 1; if (i + 1 == source.len) { - dest[out_index] = alphabet[(source[i] & 0x3) <<% 4]; + dest[out_index] = alphabet[(source[i] & 0x3) << 4]; out_index += 1; dest[out_index] = alphabet[64]; out_index += 1; } else { - dest[out_index] = alphabet[((source[i] & 0x3) <<% 4) | + dest[out_index] = alphabet[((source[i] & 0x3) << 4) | ((source[i + 1] & 0xf0) >> 4)]; out_index += 1; - dest[out_index] = alphabet[(source[i + 1] & 0xf) <<% 2]; + dest[out_index] = alphabet[(source[i + 1] & 0xf) << 2]; out_index += 1; } @@ -83,15 +83,15 @@ pub fn decodeWithAscii6BitMap(dest: []u8, source: []const u8, ascii6: []const u8 } while (in_buf_len > 4) { - dest[dest_index] = ascii6[source[src_index + 0]] <<% 2 | + dest[dest_index] = ascii6[source[src_index + 0]] << 2 | ascii6[source[src_index + 1]] >> 4; dest_index += 1; - dest[dest_index] = ascii6[source[src_index + 1]] <<% 4 | + dest[dest_index] = ascii6[source[src_index + 1]] << 4 | ascii6[source[src_index + 2]] >> 2; dest_index += 1; - dest[dest_index] = ascii6[source[src_index + 2]] <<% 6 | + dest[dest_index] = ascii6[source[src_index + 2]] << 6 | ascii6[source[src_index + 3]]; dest_index += 1; @@ -100,17 +100,17 @@ pub fn decodeWithAscii6BitMap(dest: []u8, source: []const u8, ascii6: []const u8 } if (in_buf_len > 1) { - dest[dest_index] = ascii6[source[src_index + 0]] <<% 2 | + dest[dest_index] = ascii6[source[src_index + 0]] << 2 | ascii6[source[src_index + 1]] >> 4; dest_index += 1; } if (in_buf_len > 2) { - dest[dest_index] = ascii6[source[src_index + 1]] <<% 4 | + dest[dest_index] = ascii6[source[src_index + 1]] << 4 | ascii6[source[src_index + 2]] >> 2; dest_index += 1; } if (in_buf_len > 3) { - dest[dest_index] = ascii6[source[src_index + 2]] <<% 6 | + dest[dest_index] = ascii6[source[src_index + 2]] << 6 | ascii6[source[src_index + 3]]; dest_index += 1; } diff --git a/std/math/exp2.zig b/std/math/exp2.zig index da8168f95d..d04f72cbba 100644 --- a/std/math/exp2.zig +++ b/std/math/exp2.zig @@ -83,7 +83,7 @@ fn exp2_32(x: f32) -> f32 { const k = i0 / tblsiz; // NOTE: musl relies on undefined overflow shift behaviour. Appears that this produces the // intended result but should confirm how GCC/Clang handle this to ensure. - const uk = @bitCast(f64, u64(0x3FF + k) <<% 52); + const uk = @bitCast(f64, u64(0x3FF + k) << 52); i0 &= tblsiz - 1; uf -= redux; diff --git a/std/math/expm1.zig b/std/math/expm1.zig index 7ffc14b951..4343a92117 100644 --- a/std/math/expm1.zig +++ b/std/math/expm1.zig @@ -124,7 +124,7 @@ fn expm1_32(x_: f32) -> f32 { } } - const twopk = @bitCast(f32, u32((0x7F + k) <<% 23)); + const twopk = @bitCast(f32, u32((0x7F + k) << 23)); if (k < 0 or k > 56) { var y = x - e + 1.0; @@ -253,7 +253,7 @@ fn expm1_64(x_: f64) -> f64 { } } - const twopk = @bitCast(f64, u64(0x3FF + k) <<% 52); + const twopk = @bitCast(f64, u64(0x3FF + k) << 52); if (k < 0 or k > 56) { var y = x - e + 1.0; diff --git a/std/math/ilogb.zig b/std/math/ilogb.zig index 805a14d9ba..5bdbe5f254 100644 --- a/std/math/ilogb.zig +++ b/std/math/ilogb.zig @@ -49,7 +49,7 @@ fn ilogb32(x: f32) -> i32 { if (e == 0xFF) { math.raiseInvalid(); - if (u <<% 9 != 0) { + if (u << 9 != 0) { return fp_ilogbnan; } else { return @maxValue(i32); @@ -84,7 +84,7 @@ fn ilogb64(x: f64) -> i32 { if (e == 0x7FF) { math.raiseInvalid(); - if (u <<% 12 != 0) { + if (u << 12 != 0) { return fp_ilogbnan; } else { return @maxValue(i32); diff --git a/std/math/ln.zig b/std/math/ln.zig index 4d6d7216f0..51078f0ee2 100644 --- a/std/math/ln.zig +++ b/std/math/ln.zig @@ -36,7 +36,7 @@ fn lnf(x_: f32) -> f32 { // x < 2^(-126) if (ix < 0x00800000 or ix >> 31 != 0) { // log(+-0) = -inf - if (ix <<% 1 == 0) { + if (ix << 1 == 0) { return -math.inf(f32); } // log(-#) = nan @@ -91,7 +91,7 @@ fn lnd(x_: f64) -> f64 { if (hx < 0x00100000 or hx >> 31 != 0) { // log(+-0) = -inf - if (ix <<% 1 == 0) { + if (ix << 1 == 0) { return -math.inf(f64); } // log(-#) = nan diff --git a/std/math/log10.zig b/std/math/log10.zig index 144698b0cd..e9efc0c298 100644 --- a/std/math/log10.zig +++ b/std/math/log10.zig @@ -38,7 +38,7 @@ fn log10_32(x_: f32) -> f32 { // x < 2^(-126) if (ix < 0x00800000 or ix >> 31 != 0) { // log(+-0) = -inf - if (ix <<% 1 == 0) { + if (ix << 1 == 0) { return -math.inf(f32); } // log(-#) = nan @@ -100,7 +100,7 @@ fn log10_64(x_: f64) -> f64 { if (hx < 0x00100000 or hx >> 31 != 0) { // log(+-0) = -inf - if (ix <<% 1 == 0) { + if (ix << 1 == 0) { return -math.inf(f32); } // log(-#) = nan @@ -139,7 +139,7 @@ fn log10_64(x_: f64) -> f64 { // hi + lo = f - hfsq + s * (hfsq + R) ~ log(1 + f) var hi = f - hfsq; var hii = @bitCast(u64, hi); - hii &= u64(@maxValue(u64)) <<% 32; + hii &= u64(@maxValue(u64)) << 32; hi = @bitCast(f64, hii); const lo = f - hi - hfsq + s * (hfsq + R); diff --git a/std/math/log1p.zig b/std/math/log1p.zig index 6b8f9ba27d..a49b78d0aa 100644 --- a/std/math/log1p.zig +++ b/std/math/log1p.zig @@ -49,7 +49,7 @@ fn log1p_32(x: f32) -> f32 { } } // |x| < 2^(-24) - if ((ix <<% 1) < (0x33800000 << 1)) { + if ((ix << 1) < (0x33800000 << 1)) { // underflow if subnormal if (ix & 0x7F800000 == 0) { math.forceEval(x * x); @@ -128,7 +128,7 @@ fn log1p_64(x: f64) -> f64 { } } // |x| < 2^(-53) - if ((hx <<% 1) < (0x3CA00000 << 1)) { + if ((hx << 1) < (0x3CA00000 << 1)) { if ((hx & 0x7FF00000) == 0) { math.raiseUnderflow(); } diff --git a/std/math/log2.zig b/std/math/log2.zig index eade3d28a7..9468abdd4b 100644 --- a/std/math/log2.zig +++ b/std/math/log2.zig @@ -36,7 +36,7 @@ fn log2_32(x_: f32) -> f32 { // x < 2^(-126) if (ix < 0x00800000 or ix >> 31 != 0) { // log(+-0) = -inf - if (ix <<% 1 == 0) { + if (ix << 1 == 0) { return -math.inf(f32); } // log(-#) = nan @@ -94,7 +94,7 @@ fn log2_64(x_: f64) -> f64 { if (hx < 0x00100000 or hx >> 31 != 0) { // log(+-0) = -inf - if (ix <<% 1 == 0) { + if (ix << 1 == 0) { return -math.inf(f64); } // log(-#) = nan @@ -133,7 +133,7 @@ fn log2_64(x_: f64) -> f64 { // hi + lo = f - hfsq + s * (hfsq + R) ~ log(1 + f) var hi = f - hfsq; var hii = @bitCast(u64, hi); - hii &= u64(@maxValue(u64)) <<% 32; + hii &= u64(@maxValue(u64)) << 32; hi = @bitCast(f64, hii); const lo = f - hi - hfsq + s * (hfsq + R); diff --git a/std/math/modf.zig b/std/math/modf.zig index a10703e346..05d8174dd4 100644 --- a/std/math/modf.zig +++ b/std/math/modf.zig @@ -44,7 +44,7 @@ fn modf32(x: f32) -> modf32_result { // no fractional part if (e >= 23) { result.ipart = x; - if (e == 0x80 and u <<% 9 != 0) { // nan + if (e == 0x80 and u << 9 != 0) { // nan result.fpart = x; } else { result.fpart = @bitCast(f32, us); @@ -88,7 +88,7 @@ fn modf64(x: f64) -> modf64_result { // no fractional part if (e >= 52) { result.ipart = x; - if (e == 0x400 and u <<% 12 != 0) { // nan + if (e == 0x400 and u << 12 != 0) { // nan result.fpart = x; } else { result.fpart = @bitCast(f64, us); diff --git a/std/rand.zig b/std/rand.zig index 0a1f7b4715..8bc08aa5b0 100644 --- a/std/rand.zig +++ b/std/rand.zig @@ -182,8 +182,8 @@ fn MersenneTwister( mt.index += 1; x ^= ((x >> u) & d); - x ^= ((x <<% s) & b); - x ^= ((x <<% t) & c); + x ^= ((x << s) & b); + x ^= ((x << t) & c); x ^= (x >> l); return x; diff --git a/std/special/builtin.zig b/std/special/builtin.zig index 0c209267e4..9092d1e3f8 100644 --- a/std/special/builtin.zig +++ b/std/special/builtin.zig @@ -47,31 +47,31 @@ fn generic_fmod(comptime T: type, x: T, y: T) -> T { const sx = if (T == f32) u32(ux & 0x80000000) else i32(ux >> bits_minus_1); var i: uint = undefined; - if (uy <<% 1 == 0 or isNan(uint, uy) or ex == mask) + if (uy << 1 == 0 or isNan(uint, uy) or ex == mask) return (x * y) / (x * y); - if (ux <<% 1 <= uy <<% 1) { - if (ux <<% 1 == uy <<% 1) + if (ux << 1 <= uy << 1) { + if (ux << 1 == uy << 1) return 0 * x; return x; } // normalize x and y if (ex == 0) { - i = ux <<% exp_bits; - while (i >> bits_minus_1 == 0) : ({ex -= 1; i <<%= 1}) {} - ux <<%= @bitCast(u32, -ex + 1); + i = ux << exp_bits; + while (i >> bits_minus_1 == 0) : ({ex -= 1; i <<= 1}) {} + ux <<= @bitCast(u32, -ex + 1); } else { ux &= @maxValue(uint) >> exp_bits; - ux |= 1 <<% digits; + ux |= 1 << digits; } if (ey == 0) { - i = uy <<% exp_bits; - while (i >> bits_minus_1 == 0) : ({ey -= 1; i <<%= 1}) {} + i = uy << exp_bits; + while (i >> bits_minus_1 == 0) : ({ey -= 1; i <<= 1}) {} uy <<= @bitCast(u32, -ey + 1); } else { uy &= @maxValue(uint) >> exp_bits; - uy |= 1 <<% digits; + uy |= 1 << digits; } // x mod y @@ -82,7 +82,7 @@ fn generic_fmod(comptime T: type, x: T, y: T) -> T { return 0 * x; ux = i; } - ux <<%= 1; + ux <<= 1; } i = ux -% uy; if (i >> bits_minus_1 == 0) { @@ -90,19 +90,19 @@ fn generic_fmod(comptime T: type, x: T, y: T) -> T { return 0 * x; ux = i; } - while (ux >> digits == 0) : ({ux <<%= 1; ex -= 1}) {} + while (ux >> digits == 0) : ({ux <<= 1; ex -= 1}) {} // scale result up if (ex > 0) { - ux -%= 1 <<% digits; - ux |= @bitCast(u32, ex) <<% digits; + ux -%= 1 << digits; + ux |= @bitCast(u32, ex) << digits; } else { ux >>= @bitCast(u32, -ex + 1); } if (T == f32) { ux |= sx; } else { - ux |= uint(sx) <<% bits_minus_1; + ux |= uint(sx) << bits_minus_1; } return *@ptrCast(&const T, &ux); } @@ -111,7 +111,7 @@ fn isNan(comptime T: type, bits: T) -> bool { if (T == u32) { return (bits & 0x7fffffff) > 0x7f800000; } else if (T == u64) { - return (bits & (@maxValue(u64) >> 1)) > (u64(0x7ff) <<% 52); + return (bits & (@maxValue(u64) >> 1)) > (u64(0x7ff) << 52); } else { unreachable; } diff --git a/test/cases/math.zig b/test/cases/math.zig index 2bdeb3f3b8..1de3c12a25 100644 --- a/test/cases/math.zig +++ b/test/cases/math.zig @@ -168,15 +168,6 @@ fn testNegationWrappingEval(x: i16) { assert(neg == -32768); } -test "shift left wrapping" { - testShlWrappingEval(@maxValue(u16)); - comptime testShlWrappingEval(@maxValue(u16)); -} -fn testShlWrappingEval(x: u16) { - const shifted = x <<% 1; - assert(shifted == 65534); -} - test "unsigned 64-bit division" { test_u64_div(); comptime test_u64_div(); @@ -257,3 +248,39 @@ test "hex float literal within range" { const b = 0x0.1p1027; const c = 0x1.0p-1022; } + +test "truncating shift left" { + testShlTrunc(@maxValue(u16)); + comptime testShlTrunc(@maxValue(u16)); +} +fn testShlTrunc(x: u16) { + const shifted = x << 1; + assert(shifted == 65534); +} + +test "truncating shift right" { + testShrTrunc(@maxValue(u16)); + comptime testShrTrunc(@maxValue(u16)); +} +fn testShrTrunc(x: u16) { + const shifted = x >> 1; + assert(shifted == 32767); +} + +test "exact shift left" { + testShlExact(0b00110101); + comptime testShlExact(0b00110101); +} +fn testShlExact(x: u8) { + const shifted = @shlExact(x, 2); + assert(shifted == 0b11010100); +} + +test "exact shift right" { + testShrExact(0b10110100); + comptime testShrExact(0b10110100); +} +fn testShrExact(x: u8) { + const shifted = @shrExact(x, 2); + assert(shifted == 0b00101101); +} diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 07dafdbeb0..358569d0ef 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1959,4 +1959,18 @@ pub fn addCases(cases: &tests.CompileErrorContext) { \\} , ".tmp_source.zig:2:15: error: expected pointer, found 'i32'"); + + cases.add("@shlExact shifts out 1 bits", + \\comptime { + \\ const x = @shlExact(u8(0b01010101), 2); + \\} + , + ".tmp_source.zig:2:15: error: operation caused overflow"); + + cases.add("@shrExact shifts out 1 bits", + \\comptime { + \\ const x = @shrExact(u8(0b10101010), 2); + \\} + , + ".tmp_source.zig:2:15: error: exact shift shifted out 1 bits"); } diff --git a/test/debug_safety.zig b/test/debug_safety.zig index 3df411f7cd..60b92955a8 100644 --- a/test/debug_safety.zig +++ b/test/debug_safety.zig @@ -112,7 +112,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { \\ if (x == 0) return error.Whatever; \\} \\fn shl(a: i16, b: i16) -> i16 { - \\ a << b + \\ @shlExact(a, b) \\} ); @@ -127,7 +127,37 @@ pub fn addCases(cases: &tests.CompareOutputContext) { \\ if (x == 0) return error.Whatever; \\} \\fn shl(a: u16, b: u16) -> u16 { - \\ a << b + \\ @shlExact(a, b) + \\} + ); + + cases.addDebugSafety("signed shift right overflow", + \\pub fn panic(message: []const u8) -> noreturn { + \\ @breakpoint(); + \\ while (true) {} + \\} + \\error Whatever; + \\pub fn main() -> %void { + \\ const x = shr(-16385, 1); + \\ if (x == 0) return error.Whatever; + \\} + \\fn shr(a: i16, b: i16) -> i16 { + \\ @shrExact(a, b) + \\} + ); + + cases.addDebugSafety("unsigned shift right overflow", + \\pub fn panic(message: []const u8) -> noreturn { + \\ @breakpoint(); + \\ while (true) {} + \\} + \\error Whatever; + \\pub fn main() -> %void { + \\ const x = shr(0b0010111111111111, 3); + \\ if (x == 0) return error.Whatever; + \\} + \\fn shr(a: u16, b: u16) -> u16 { + \\ @shrExact(a, b) \\} );