Commit 35d3444e27

Andrew Kelley <superjoe30@gmail.com>
2017-08-09 16:09:38
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
1 parent 54675b0
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,
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 "^=";
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);
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);
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) {
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)";
 }
src/error.hpp
@@ -25,6 +25,7 @@ enum Error {
     ErrorUnexpected,
     ErrorExactDivRemainder,
     ErrorNegativeDenominator,
+    ErrorShiftedOutOneBits,
 };
 
 const char *err_str(int err);
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:
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:
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;
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 "^=";
src/tokenizer.hpp
@@ -23,8 +23,6 @@ enum TokenId {
     TokenIdBitOrEq,
     TokenIdBitShiftLeft,
     TokenIdBitShiftLeftEq,
-    TokenIdBitShiftLeftPercent,
-    TokenIdBitShiftLeftPercentEq,
     TokenIdBitShiftRight,
     TokenIdBitShiftRightEq,
     TokenIdBitXorEq,
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) {
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);
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;
 
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;
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);
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
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);
 
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();
             }
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);
 
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);
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;
     }
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;
     }
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;
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);
+}
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");
 }
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)
         \\}
     );