From 54ffcf95a8596aa3adf57cddbe079a24f849f408 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Sat, 14 Mar 2020 13:23:41 +0100 Subject: [PATCH 01/10] ir: Support div/mod/rem on vector types Closes #4050 --- src/codegen.cpp | 140 +++++++++++----- src/ir.cpp | 286 +++++++++++++++++--------------- test/stage1/behavior/vector.zig | 75 +++++++++ 3 files changed, 327 insertions(+), 174 deletions(-) diff --git a/src/codegen.cpp b/src/codegen.cpp index 84168f509f..0fa181b32c 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2591,12 +2591,7 @@ static LLVMValueRef gen_overflow_shr_op(CodeGen *g, ZigType *type_entry, } static LLVMValueRef gen_float_op(CodeGen *g, LLVMValueRef val, ZigType *type_entry, BuiltinFnId op) { - if ((op == BuiltinFnIdCeil || - op == BuiltinFnIdFloor) && - type_entry->id == ZigTypeIdInt) - return val; - assert(type_entry->id == ZigTypeIdFloat); - + assert(type_entry->id == ZigTypeIdFloat || type_entry->id == ZigTypeIdVector); LLVMValueRef floor_fn = get_float_fn(g, type_entry, ZigLLVMFnIdFloatOp, op); return LLVMBuildCall(g->builder, floor_fn, &val, 1, ""); } @@ -2612,6 +2607,21 @@ static LLVMValueRef bigint_to_llvm_const(LLVMTypeRef type_ref, BigInt *bigint) { if (bigint->digit_count == 0) { return LLVMConstNull(type_ref); } + + if (LLVMGetTypeKind(type_ref) == LLVMVectorTypeKind) { + const unsigned vector_len = LLVMGetVectorSize(type_ref); + LLVMTypeRef elem_type = LLVMGetElementType(type_ref); + + LLVMValueRef *values = heap::c_allocator.allocate_nonzero(vector_len); + // Create a vector with all the elements having the same value + for (unsigned i = 0; i < vector_len; i++) { + values[i] = bigint_to_llvm_const(elem_type, bigint); + } + LLVMValueRef result = LLVMConstVector(values, vector_len); + heap::c_allocator.deallocate(values, vector_len); + return result; + } + LLVMValueRef unsigned_val; if (bigint->digit_count == 1) { unsigned_val = LLVMConstInt(type_ref, bigint_ptr(bigint)[0], false); @@ -2625,22 +2635,40 @@ static LLVMValueRef bigint_to_llvm_const(LLVMTypeRef type_ref, BigInt *bigint) { } } +// Collapses a vector into a single i1 whose value is 1 iff all the +// vector elements are 1 +static LLVMValueRef scalarize_cmp_result(CodeGen *g, LLVMValueRef val) { + assert(LLVMGetTypeKind(LLVMTypeOf(val)) == LLVMVectorTypeKind); + LLVMTypeRef scalar_type = LLVMIntType(LLVMGetVectorSize(LLVMTypeOf(val))); + LLVMValueRef all_ones = LLVMConstAllOnes(scalar_type); + LLVMValueRef casted = LLVMBuildBitCast(g->builder, val, scalar_type, ""); + return LLVMBuildICmp(g->builder, LLVMIntEQ, casted, all_ones, ""); +} + static LLVMValueRef gen_div(CodeGen *g, bool want_runtime_safety, bool want_fast_math, - LLVMValueRef val1, LLVMValueRef val2, - ZigType *type_entry, DivKind div_kind) + LLVMValueRef val1, LLVMValueRef val2, ZigType *operand_type, DivKind div_kind) { + ZigType *scalar_type = (operand_type->id == ZigTypeIdVector) ? + operand_type->data.vector.elem_type : operand_type; + ZigLLVMSetFastMath(g->builder, want_fast_math); - LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, type_entry)); - if (want_runtime_safety && (want_fast_math || type_entry->id != ZigTypeIdFloat)) { + LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, operand_type)); + if (want_runtime_safety && (want_fast_math || scalar_type->id != ZigTypeIdFloat)) { + // Safety check: divisor != 0 LLVMValueRef is_zero_bit; - if (type_entry->id == ZigTypeIdInt) { + if (scalar_type->id == ZigTypeIdInt) { is_zero_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, val2, zero, ""); - } else if (type_entry->id == ZigTypeIdFloat) { + } else if (scalar_type->id == ZigTypeIdFloat) { is_zero_bit = LLVMBuildFCmp(g->builder, LLVMRealOEQ, val2, zero, ""); } else { zig_unreachable(); } + + if (operand_type->id == ZigTypeIdVector) { + is_zero_bit = scalarize_cmp_result(g, is_zero_bit); + } + LLVMBasicBlockRef div_zero_fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivZeroFail"); LLVMBasicBlockRef div_zero_ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivZeroOk"); LLVMBuildCondBr(g->builder, is_zero_bit, div_zero_fail_block, div_zero_ok_block); @@ -2650,16 +2678,21 @@ static LLVMValueRef gen_div(CodeGen *g, bool want_runtime_safety, bool want_fast LLVMPositionBuilderAtEnd(g->builder, div_zero_ok_block); - if (type_entry->id == ZigTypeIdInt && type_entry->data.integral.is_signed) { - LLVMValueRef neg_1_value = LLVMConstInt(get_llvm_type(g, type_entry), -1, true); + // Safety check: check for overflow (dividend = minInt and divisor = -1) + if (scalar_type->id == ZigTypeIdInt && scalar_type->data.integral.is_signed) { + LLVMValueRef neg_1_value = LLVMConstAllOnes(get_llvm_type(g, operand_type)); BigInt int_min_bi = {0}; - eval_min_max_value_int(g, type_entry, &int_min_bi, false); - LLVMValueRef int_min_value = bigint_to_llvm_const(get_llvm_type(g, type_entry), &int_min_bi); + eval_min_max_value_int(g, scalar_type, &int_min_bi, false); + LLVMValueRef int_min_value = bigint_to_llvm_const(get_llvm_type(g, operand_type), &int_min_bi); + LLVMBasicBlockRef overflow_fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivOverflowFail"); LLVMBasicBlockRef overflow_ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivOverflowOk"); LLVMValueRef num_is_int_min = LLVMBuildICmp(g->builder, LLVMIntEQ, val1, int_min_value, ""); LLVMValueRef den_is_neg_1 = LLVMBuildICmp(g->builder, LLVMIntEQ, val2, neg_1_value, ""); LLVMValueRef overflow_fail_bit = LLVMBuildAnd(g->builder, num_is_int_min, den_is_neg_1, ""); + if (operand_type->id == ZigTypeIdVector) { + overflow_fail_bit = scalarize_cmp_result(g, overflow_fail_bit); + } LLVMBuildCondBr(g->builder, overflow_fail_bit, overflow_fail_block, overflow_ok_block); LLVMPositionBuilderAtEnd(g->builder, overflow_fail_block); @@ -2669,18 +2702,22 @@ static LLVMValueRef gen_div(CodeGen *g, bool want_runtime_safety, bool want_fast } } - if (type_entry->id == ZigTypeIdFloat) { + if (scalar_type->id == ZigTypeIdFloat) { LLVMValueRef result = LLVMBuildFDiv(g->builder, val1, val2, ""); switch (div_kind) { case DivKindFloat: return result; case DivKindExact: if (want_runtime_safety) { - LLVMValueRef floored = gen_float_op(g, result, type_entry, BuiltinFnIdFloor); + // Safety check: a / b == floor(a / b) + LLVMValueRef floored = gen_float_op(g, result, operand_type, BuiltinFnIdFloor); + LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivExactOk"); LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivExactFail"); LLVMValueRef ok_bit = LLVMBuildFCmp(g->builder, LLVMRealOEQ, floored, result, ""); - + if (operand_type->id == ZigTypeIdVector) { + ok_bit = scalarize_cmp_result(g, ok_bit); + } LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); LLVMPositionBuilderAtEnd(g->builder, fail_block); @@ -2695,54 +2732,61 @@ static LLVMValueRef gen_div(CodeGen *g, bool want_runtime_safety, bool want_fast LLVMBasicBlockRef gez_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivTruncGEZero"); LLVMBasicBlockRef end_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivTruncEnd"); LLVMValueRef ltz = LLVMBuildFCmp(g->builder, LLVMRealOLT, val1, zero, ""); + if (operand_type->id == ZigTypeIdVector) { + ltz = scalarize_cmp_result(g, ltz); + } LLVMBuildCondBr(g->builder, ltz, ltz_block, gez_block); LLVMPositionBuilderAtEnd(g->builder, ltz_block); - LLVMValueRef ceiled = gen_float_op(g, result, type_entry, BuiltinFnIdCeil); + LLVMValueRef ceiled = gen_float_op(g, result, operand_type, BuiltinFnIdCeil); LLVMBasicBlockRef ceiled_end_block = LLVMGetInsertBlock(g->builder); LLVMBuildBr(g->builder, end_block); LLVMPositionBuilderAtEnd(g->builder, gez_block); - LLVMValueRef floored = gen_float_op(g, result, type_entry, BuiltinFnIdFloor); + LLVMValueRef floored = gen_float_op(g, result, operand_type, BuiltinFnIdFloor); LLVMBasicBlockRef floored_end_block = LLVMGetInsertBlock(g->builder); LLVMBuildBr(g->builder, end_block); LLVMPositionBuilderAtEnd(g->builder, end_block); - LLVMValueRef phi = LLVMBuildPhi(g->builder, get_llvm_type(g, type_entry), ""); + LLVMValueRef phi = LLVMBuildPhi(g->builder, get_llvm_type(g, operand_type), ""); LLVMValueRef incoming_values[] = { ceiled, floored }; LLVMBasicBlockRef incoming_blocks[] = { ceiled_end_block, floored_end_block }; LLVMAddIncoming(phi, incoming_values, incoming_blocks, 2); return phi; } case DivKindFloor: - return gen_float_op(g, result, type_entry, BuiltinFnIdFloor); + return gen_float_op(g, result, operand_type, BuiltinFnIdFloor); } zig_unreachable(); } - assert(type_entry->id == ZigTypeIdInt); + assert(scalar_type->id == ZigTypeIdInt); switch (div_kind) { case DivKindFloat: zig_unreachable(); case DivKindTrunc: - if (type_entry->data.integral.is_signed) { + if (scalar_type->data.integral.is_signed) { return LLVMBuildSDiv(g->builder, val1, val2, ""); } else { return LLVMBuildUDiv(g->builder, val1, val2, ""); } case DivKindExact: if (want_runtime_safety) { + // Safety check: a % b == 0 LLVMValueRef remainder_val; - if (type_entry->data.integral.is_signed) { + if (scalar_type->data.integral.is_signed) { remainder_val = LLVMBuildSRem(g->builder, val1, val2, ""); } else { remainder_val = LLVMBuildURem(g->builder, val1, val2, ""); } - LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, remainder_val, zero, ""); LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivExactOk"); LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivExactFail"); + LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, remainder_val, zero, ""); + if (operand_type->id == ZigTypeIdVector) { + ok_bit = scalarize_cmp_result(g, ok_bit); + } LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); LLVMPositionBuilderAtEnd(g->builder, fail_block); @@ -2750,14 +2794,14 @@ static LLVMValueRef gen_div(CodeGen *g, bool want_runtime_safety, bool want_fast LLVMPositionBuilderAtEnd(g->builder, ok_block); } - if (type_entry->data.integral.is_signed) { + if (scalar_type->data.integral.is_signed) { return LLVMBuildExactSDiv(g->builder, val1, val2, ""); } else { return LLVMBuildExactUDiv(g->builder, val1, val2, ""); } case DivKindFloor: { - if (!type_entry->data.integral.is_signed) { + if (!scalar_type->data.integral.is_signed) { return LLVMBuildUDiv(g->builder, val1, val2, ""); } // const d = @divTrunc(a, b); @@ -2784,22 +2828,30 @@ enum RemKind { }; static LLVMValueRef gen_rem(CodeGen *g, bool want_runtime_safety, bool want_fast_math, - LLVMValueRef val1, LLVMValueRef val2, - ZigType *type_entry, RemKind rem_kind) + LLVMValueRef val1, LLVMValueRef val2, ZigType *operand_type, RemKind rem_kind) { + ZigType *scalar_type = (operand_type->id == ZigTypeIdVector) ? + operand_type->data.vector.elem_type : operand_type; + ZigLLVMSetFastMath(g->builder, want_fast_math); - LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, type_entry)); + LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, operand_type)); if (want_runtime_safety) { + // Safety check: divisor != 0 LLVMValueRef is_zero_bit; - if (type_entry->id == ZigTypeIdInt) { - LLVMIntPredicate pred = type_entry->data.integral.is_signed ? LLVMIntSLE : LLVMIntEQ; + if (scalar_type->id == ZigTypeIdInt) { + LLVMIntPredicate pred = scalar_type->data.integral.is_signed ? LLVMIntSLE : LLVMIntEQ; is_zero_bit = LLVMBuildICmp(g->builder, pred, val2, zero, ""); - } else if (type_entry->id == ZigTypeIdFloat) { + } else if (scalar_type->id == ZigTypeIdFloat) { is_zero_bit = LLVMBuildFCmp(g->builder, LLVMRealOEQ, val2, zero, ""); } else { zig_unreachable(); } + + if (operand_type->id == ZigTypeIdVector) { + is_zero_bit = scalarize_cmp_result(g, is_zero_bit); + } + LLVMBasicBlockRef rem_zero_ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "RemZeroOk"); LLVMBasicBlockRef rem_zero_fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "RemZeroFail"); LLVMBuildCondBr(g->builder, is_zero_bit, rem_zero_fail_block, rem_zero_ok_block); @@ -2810,7 +2862,7 @@ static LLVMValueRef gen_rem(CodeGen *g, bool want_runtime_safety, bool want_fast LLVMPositionBuilderAtEnd(g->builder, rem_zero_ok_block); } - if (type_entry->id == ZigTypeIdFloat) { + if (scalar_type->id == ZigTypeIdFloat) { if (rem_kind == RemKindRem) { return LLVMBuildFRem(g->builder, val1, val2, ""); } else { @@ -2821,8 +2873,8 @@ static LLVMValueRef gen_rem(CodeGen *g, bool want_runtime_safety, bool want_fast return LLVMBuildSelect(g->builder, ltz, c, a, ""); } } else { - assert(type_entry->id == ZigTypeIdInt); - if (type_entry->data.integral.is_signed) { + assert(scalar_type->id == ZigTypeIdInt); + if (scalar_type->data.integral.is_signed) { if (rem_kind == RemKindRem) { return LLVMBuildSRem(g->builder, val1, val2, ""); } else { @@ -3010,22 +3062,22 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutableGen *executable, } case IrBinOpDivUnspecified: return gen_div(g, want_runtime_safety, ir_want_fast_math(g, &bin_op_instruction->base), - op1_value, op2_value, scalar_type, DivKindFloat); + op1_value, op2_value, operand_type, DivKindFloat); case IrBinOpDivExact: return gen_div(g, want_runtime_safety, ir_want_fast_math(g, &bin_op_instruction->base), - op1_value, op2_value, scalar_type, DivKindExact); + op1_value, op2_value, operand_type, DivKindExact); case IrBinOpDivTrunc: return gen_div(g, want_runtime_safety, ir_want_fast_math(g, &bin_op_instruction->base), - op1_value, op2_value, scalar_type, DivKindTrunc); + op1_value, op2_value, operand_type, DivKindTrunc); case IrBinOpDivFloor: return gen_div(g, want_runtime_safety, ir_want_fast_math(g, &bin_op_instruction->base), - op1_value, op2_value, scalar_type, DivKindFloor); + op1_value, op2_value, operand_type, DivKindFloor); case IrBinOpRemRem: return gen_rem(g, want_runtime_safety, ir_want_fast_math(g, &bin_op_instruction->base), - op1_value, op2_value, scalar_type, RemKindRem); + op1_value, op2_value, operand_type, RemKindRem); case IrBinOpRemMod: return gen_rem(g, want_runtime_safety, ir_want_fast_math(g, &bin_op_instruction->base), - op1_value, op2_value, scalar_type, RemKindMod); + op1_value, op2_value, operand_type, RemKindMod); } zig_unreachable(); } diff --git a/src/ir.cpp b/src/ir.cpp index bc222a311b..04eaa217b9 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -16943,6 +16943,7 @@ static bool ok_float_op(IrBinOp op) { case IrBinOpDivExact: case IrBinOpRemRem: case IrBinOpRemMod: + case IrBinOpRemUnspecified: return true; case IrBinOpBoolOr: @@ -16963,7 +16964,6 @@ static bool ok_float_op(IrBinOp op) { case IrBinOpAddWrap: case IrBinOpSubWrap: case IrBinOpMultWrap: - case IrBinOpRemUnspecified: case IrBinOpArrayCat: case IrBinOpArrayMult: return false; @@ -16991,6 +16991,31 @@ static bool is_pointer_arithmetic_allowed(ZigType *lhs_type, IrBinOp op) { zig_unreachable(); } +static bool value_cmp_zero_any(ZigValue *value, Cmp predicate) { + assert(value->special == ConstValSpecialStatic); + + switch (value->type->id) { + case ZigTypeIdComptimeInt: + case ZigTypeIdInt: + return bigint_cmp_zero(&value->data.x_bigint) == predicate; + case ZigTypeIdComptimeFloat: + case ZigTypeIdFloat: + if (float_is_nan(value)) + return false; + return float_cmp_zero(value) == predicate; + case ZigTypeIdVector: { + for (size_t i = 0; i < value->type->data.vector.len; i++) { + ZigValue *scalar_val = &value->data.x_array.data.s_none.elements[i]; + if (!value_cmp_zero_any(scalar_val, predicate)) + return true; + } + return false; + } + default: + zig_unreachable(); + } +} + static IrInstGen *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstSrcBinOp *instruction) { Error err; @@ -17096,127 +17121,13 @@ static IrInstGen *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstSrcBinOp *instruc if (type_is_invalid(resolved_type)) return ira->codegen->invalid_inst_gen; - bool is_int = resolved_type->id == ZigTypeIdInt || resolved_type->id == ZigTypeIdComptimeInt; - bool is_float = resolved_type->id == ZigTypeIdFloat || resolved_type->id == ZigTypeIdComptimeFloat; - bool is_signed_div = ( - (resolved_type->id == ZigTypeIdInt && resolved_type->data.integral.is_signed) || - resolved_type->id == ZigTypeIdFloat || - (resolved_type->id == ZigTypeIdComptimeFloat && - ((bigfloat_cmp_zero(&op1->value->data.x_bigfloat) != CmpGT) != - (bigfloat_cmp_zero(&op2->value->data.x_bigfloat) != CmpGT))) || - (resolved_type->id == ZigTypeIdComptimeInt && - ((bigint_cmp_zero(&op1->value->data.x_bigint) != CmpGT) != - (bigint_cmp_zero(&op2->value->data.x_bigint) != CmpGT))) - ); - if (op_id == IrBinOpDivUnspecified && is_int) { - if (is_signed_div) { - bool ok = false; - if (instr_is_comptime(op1) && instr_is_comptime(op2)) { - ZigValue *op1_val = ir_resolve_const(ira, op1, UndefBad); - if (op1_val == nullptr) - return ira->codegen->invalid_inst_gen; + ZigType *scalar_type = (resolved_type->id == ZigTypeIdVector) ? + resolved_type->data.vector.elem_type : resolved_type; - ZigValue *op2_val = ir_resolve_const(ira, op2, UndefBad); - if (op2_val == nullptr) - return ira->codegen->invalid_inst_gen; + bool is_int = scalar_type->id == ZigTypeIdInt || scalar_type->id == ZigTypeIdComptimeInt; + bool is_float = scalar_type->id == ZigTypeIdFloat || scalar_type->id == ZigTypeIdComptimeFloat; - if (bigint_cmp_zero(&op2_val->data.x_bigint) == CmpEQ) { - // the division by zero error will be caught later, but we don't have a - // division function ambiguity problem. - op_id = IrBinOpDivTrunc; - ok = true; - } else { - BigInt trunc_result; - BigInt floor_result; - bigint_div_trunc(&trunc_result, &op1_val->data.x_bigint, &op2_val->data.x_bigint); - bigint_div_floor(&floor_result, &op1_val->data.x_bigint, &op2_val->data.x_bigint); - if (bigint_cmp(&trunc_result, &floor_result) == CmpEQ) { - ok = true; - op_id = IrBinOpDivTrunc; - } - } - } - if (!ok) { - ir_add_error(ira, &instruction->base.base, - buf_sprintf("division with '%s' and '%s': signed integers must use @divTrunc, @divFloor, or @divExact", - buf_ptr(&op1->value->type->name), - buf_ptr(&op2->value->type->name))); - return ira->codegen->invalid_inst_gen; - } - } else { - op_id = IrBinOpDivTrunc; - } - } else if (op_id == IrBinOpRemUnspecified) { - if (is_signed_div && (is_int || is_float)) { - bool ok = false; - if (instr_is_comptime(op1) && instr_is_comptime(op2)) { - ZigValue *op1_val = ir_resolve_const(ira, op1, UndefBad); - if (op1_val == nullptr) - return ira->codegen->invalid_inst_gen; - - if (is_int) { - ZigValue *op2_val = ir_resolve_const(ira, op2, UndefBad); - if (op2_val == nullptr) - return ira->codegen->invalid_inst_gen; - - if (bigint_cmp_zero(&op2->value->data.x_bigint) == CmpEQ) { - // the division by zero error will be caught later, but we don't - // have a remainder function ambiguity problem - ok = true; - } else { - BigInt rem_result; - BigInt mod_result; - bigint_rem(&rem_result, &op1_val->data.x_bigint, &op2_val->data.x_bigint); - bigint_mod(&mod_result, &op1_val->data.x_bigint, &op2_val->data.x_bigint); - ok = bigint_cmp(&rem_result, &mod_result) == CmpEQ; - } - } else { - IrInstGen *casted_op2 = ir_implicit_cast(ira, op2, resolved_type); - if (type_is_invalid(casted_op2->value->type)) - return ira->codegen->invalid_inst_gen; - - ZigValue *op2_val = ir_resolve_const(ira, casted_op2, UndefBad); - if (op2_val == nullptr) - return ira->codegen->invalid_inst_gen; - - if (float_cmp_zero(casted_op2->value) == CmpEQ) { - // the division by zero error will be caught later, but we don't - // have a remainder function ambiguity problem - ok = true; - } else { - ZigValue rem_result = {}; - ZigValue mod_result = {}; - float_rem(&rem_result, op1_val, op2_val); - float_mod(&mod_result, op1_val, op2_val); - ok = float_cmp(&rem_result, &mod_result) == CmpEQ; - } - } - } - if (!ok) { - ir_add_error(ira, &instruction->base.base, - buf_sprintf("remainder division with '%s' and '%s': signed integers and floats must use @rem or @mod", - buf_ptr(&op1->value->type->name), - buf_ptr(&op2->value->type->name))); - return ira->codegen->invalid_inst_gen; - } - } - op_id = IrBinOpRemRem; - } - - bool ok = false; - if (is_int) { - ok = true; - } else if (is_float && ok_float_op(op_id)) { - ok = true; - } else if (resolved_type->id == ZigTypeIdVector) { - ZigType *elem_type = resolved_type->data.vector.elem_type; - if (elem_type->id == ZigTypeIdInt || elem_type->id == ZigTypeIdComptimeInt) { - ok = true; - } else if ((elem_type->id == ZigTypeIdFloat || elem_type->id == ZigTypeIdComptimeFloat) && ok_float_op(op_id)) { - ok = true; - } - } - if (!ok) { + if (!is_int && !(is_float && ok_float_op(op_id))) { AstNode *source_node = instruction->base.base.source_node; ir_add_error_node(ira, source_node, buf_sprintf("invalid operands to binary expression: '%s' and '%s'", @@ -17225,16 +17136,6 @@ static IrInstGen *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstSrcBinOp *instruc return ira->codegen->invalid_inst_gen; } - if (resolved_type->id == ZigTypeIdComptimeInt) { - if (op_id == IrBinOpAddWrap) { - op_id = IrBinOpAdd; - } else if (op_id == IrBinOpSubWrap) { - op_id = IrBinOpSub; - } else if (op_id == IrBinOpMultWrap) { - op_id = IrBinOpMult; - } - } - IrInstGen *casted_op1 = ir_implicit_cast(ira, op1, resolved_type); if (type_is_invalid(casted_op1->value->type)) return ira->codegen->invalid_inst_gen; @@ -17243,17 +17144,142 @@ static IrInstGen *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstSrcBinOp *instruc if (type_is_invalid(casted_op2->value->type)) return ira->codegen->invalid_inst_gen; + // Comptime integers have no fixed size + if (scalar_type->id == ZigTypeIdComptimeInt) { + if (op_id == IrBinOpAddWrap) { + op_id = IrBinOpAdd; + } else if (op_id == IrBinOpSubWrap) { + op_id = IrBinOpSub; + } else if (op_id == IrBinOpMultWrap) { + op_id = IrBinOpMult; + } + } + if (instr_is_comptime(casted_op1) && instr_is_comptime(casted_op2)) { ZigValue *op1_val = ir_resolve_const(ira, casted_op1, UndefBad); if (op1_val == nullptr) return ira->codegen->invalid_inst_gen; + ZigValue *op2_val = ir_resolve_const(ira, casted_op2, UndefBad); if (op2_val == nullptr) return ira->codegen->invalid_inst_gen; + // Promote division with negative numbers to signed + bool is_signed_div = value_cmp_zero_any(op1_val, CmpLT) || + value_cmp_zero_any(op2_val, CmpLT); + + if (op_id == IrBinOpDivUnspecified && is_int) { + // Default to truncating division and check if it's valid for the + // given operands if signed + op_id = IrBinOpDivTrunc; + + if (is_signed_div) { + bool ok = false; + + if (value_cmp_zero_any(op2_val, CmpEQ)) { + // the division by zero error will be caught later, but we don't have a + // division function ambiguity problem. + ok = true; + } else { + IrInstGen *trunc_val = ir_analyze_math_op(ira, &instruction->base.base, resolved_type, + op1_val, IrBinOpDivTrunc, op2_val); + if (type_is_invalid(trunc_val->value->type)) + return ira->codegen->invalid_inst_gen; + + IrInstGen *floor_val = ir_analyze_math_op(ira, &instruction->base.base, resolved_type, + op1_val, IrBinOpDivFloor, op2_val); + if (type_is_invalid(floor_val->value->type)) + return ira->codegen->invalid_inst_gen; + + IrInstGen *cmp_val = ir_analyze_bin_op_cmp_numeric(ira, &instruction->base.base, + trunc_val, floor_val, IrBinOpCmpEq); + if (type_is_invalid(cmp_val->value->type)) + return ira->codegen->invalid_inst_gen; + + // We can "upgrade" the operator only if trunc(a/b) == floor(a/b) + if (!ir_resolve_bool(ira, cmp_val, &ok)) + return ira->codegen->invalid_inst_gen; + } + + if (!ok) { + ir_add_error(ira, &instruction->base.base, + buf_sprintf("division with '%s' and '%s': signed integers must use @divTrunc, @divFloor, or @divExact", + buf_ptr(&op1->value->type->name), + buf_ptr(&op2->value->type->name))); + return ira->codegen->invalid_inst_gen; + } + } + } else if (op_id == IrBinOpRemUnspecified) { + op_id = IrBinOpRemRem; + + if (is_signed_div) { + bool ok = false; + + if (value_cmp_zero_any(op2_val, CmpEQ)) { + // the division by zero error will be caught later, but we don't have a + // division function ambiguity problem. + ok = true; + } else { + IrInstGen *rem_val = ir_analyze_math_op(ira, &instruction->base.base, resolved_type, + op1_val, IrBinOpRemRem, op2_val); + if (type_is_invalid(rem_val->value->type)) + return ira->codegen->invalid_inst_gen; + + IrInstGen *mod_val = ir_analyze_math_op(ira, &instruction->base.base, resolved_type, + op1_val, IrBinOpRemMod, op2_val); + if (type_is_invalid(mod_val->value->type)) + return ira->codegen->invalid_inst_gen; + + IrInstGen *cmp_val = ir_analyze_bin_op_cmp_numeric(ira, &instruction->base.base, + rem_val, mod_val, IrBinOpCmpEq); + if (type_is_invalid(cmp_val->value->type)) + return ira->codegen->invalid_inst_gen; + + // We can "upgrade" the operator only if mod(a,b) == rem(a,b) + if (!ir_resolve_bool(ira, cmp_val, &ok)) + return ira->codegen->invalid_inst_gen; + } + + if (!ok) { + ir_add_error(ira, &instruction->base.base, + buf_sprintf("remainder division with '%s' and '%s': signed integers and floats must use @rem or @mod", + buf_ptr(&op1->value->type->name), + buf_ptr(&op2->value->type->name))); + return ira->codegen->invalid_inst_gen; + } + } + } + return ir_analyze_math_op(ira, &instruction->base.base, resolved_type, op1_val, op_id, op2_val); } + const bool is_signed_div = + (scalar_type->id == ZigTypeIdInt && scalar_type->data.integral.is_signed) || + scalar_type->id == ZigTypeIdFloat; + + // Warn the user to use the proper operators here + if (op_id == IrBinOpDivUnspecified && is_int) { + op_id = IrBinOpDivTrunc; + + if (is_signed_div) { + ir_add_error(ira, &instruction->base.base, + buf_sprintf("division with '%s' and '%s': signed integers must use @divTrunc, @divFloor, or @divExact", + buf_ptr(&op1->value->type->name), + buf_ptr(&op2->value->type->name))); + return ira->codegen->invalid_inst_gen; + } + } else if (op_id == IrBinOpRemUnspecified) { + op_id = IrBinOpRemRem; + + if (is_signed_div) { + ir_add_error(ira, &instruction->base.base, + buf_sprintf("remainder division with '%s' and '%s': signed integers and floats must use @rem or @mod", + buf_ptr(&op1->value->type->name), + buf_ptr(&op2->value->type->name))); + return ira->codegen->invalid_inst_gen; + } + } + return ir_build_bin_op_gen(ira, &instruction->base.base, resolved_type, op_id, casted_op1, casted_op2, instruction->safety_check_on); } diff --git a/test/stage1/behavior/vector.zig b/test/stage1/behavior/vector.zig index 01e5ac1fb8..6db695bfa3 100644 --- a/test/stage1/behavior/vector.zig +++ b/test/stage1/behavior/vector.zig @@ -276,3 +276,78 @@ test "vector comparison operators" { S.doTheTest(); comptime S.doTheTest(); } + +test "vector division operators" { + const S = struct { + fn doTheTestDiv(comptime T: type, x: @Vector(4, T), y: @Vector(4, T)) void { + if (!comptime std.meta.trait.isSignedInt(T)) { + const d0 = x / y; + for (@as([4]T, d0)) |v, i| { + expectEqual(x[i] / y[i], v); + } + } + const d1 = @divExact(x, y); + for (@as([4]T, d1)) |v, i| { + expectEqual(@divExact(x[i], y[i]), v); + } + const d2 = @divFloor(x, y); + for (@as([4]T, d2)) |v, i| { + expectEqual(@divFloor(x[i], y[i]), v); + } + const d3 = @divTrunc(x, y); + for (@as([4]T, d3)) |v, i| { + expectEqual(@divTrunc(x[i], y[i]), v); + } + } + + fn doTheTestMod(comptime T: type, x: @Vector(4, T), y: @Vector(4, T)) void { + if ((!comptime std.meta.trait.isSignedInt(T)) and @typeInfo(T) != .Float) { + const r0 = x % y; + for (@as([4]T, r0)) |v, i| { + expectEqual(x[i] % y[i], v); + } + } + const r1 = @mod(x, y); + for (@as([4]T, r1)) |v, i| { + expectEqual(@mod(x[i], y[i]), v); + } + const r2 = @rem(x, y); + for (@as([4]T, r2)) |v, i| { + expectEqual(@rem(x[i], y[i]), v); + } + } + + fn doTheTest() void { + doTheTestDiv(f16, [4]f16{ 4.0, -4.0, 4.0, -4.0 }, [4]f16{ 1.0, 2.0, -1.0, -2.0 }); + doTheTestDiv(f32, [4]f32{ 4.0, -4.0, 4.0, -4.0 }, [4]f32{ 1.0, 2.0, -1.0, -2.0 }); + doTheTestDiv(f64, [4]f64{ 4.0, -4.0, 4.0, -4.0 }, [4]f64{ 1.0, 2.0, -1.0, -2.0 }); + + doTheTestMod(f16, [4]f16{ 4.0, -4.0, 4.0, -4.0 }, [4]f16{ 1.0, 2.0, 0.5, 3.0 }); + doTheTestMod(f32, [4]f32{ 4.0, -4.0, 4.0, -4.0 }, [4]f32{ 1.0, 2.0, 0.5, 3.0 }); + doTheTestMod(f64, [4]f64{ 4.0, -4.0, 4.0, -4.0 }, [4]f64{ 1.0, 2.0, 0.5, 3.0 }); + + doTheTestDiv(i8, [4]i8{ 4, -4, 4, -4 }, [4]i8{ 1, 2, -1, -2 }); + doTheTestDiv(i16, [4]i16{ 4, -4, 4, -4 }, [4]i16{ 1, 2, -1, -2 }); + doTheTestDiv(i32, [4]i32{ 4, -4, 4, -4 }, [4]i32{ 1, 2, -1, -2 }); + doTheTestDiv(i64, [4]i64{ 4, -4, 4, -4 }, [4]i64{ 1, 2, -1, -2 }); + + doTheTestMod(i8, [4]i8{ 4, -4, 4, -4 }, [4]i8{ 1, 2, 4, 8 }); + doTheTestMod(i16, [4]i16{ 4, -4, 4, -4 }, [4]i16{ 1, 2, 4, 8 }); + doTheTestMod(i32, [4]i32{ 4, -4, 4, -4 }, [4]i32{ 1, 2, 4, 8 }); + doTheTestMod(i64, [4]i64{ 4, -4, 4, -4 }, [4]i64{ 1, 2, 4, 8 }); + + doTheTestDiv(u8, [4]u8{ 1, 2, 4, 8 }, [4]u8{ 1, 1, 2, 4 }); + doTheTestDiv(u16, [4]u16{ 1, 2, 4, 8 }, [4]u16{ 1, 1, 2, 4 }); + doTheTestDiv(u32, [4]u32{ 1, 2, 4, 8 }, [4]u32{ 1, 1, 2, 4 }); + doTheTestDiv(u64, [4]u64{ 1, 2, 4, 8 }, [4]u64{ 1, 1, 2, 4 }); + + doTheTestMod(u8, [4]u8{ 1, 2, 4, 8 }, [4]u8{ 1, 1, 2, 4 }); + doTheTestMod(u16, [4]u16{ 1, 2, 4, 8 }, [4]u16{ 1, 1, 2, 4 }); + doTheTestMod(u32, [4]u32{ 1, 2, 4, 8 }, [4]u32{ 1, 1, 2, 4 }); + doTheTestMod(u64, [4]u64{ 1, 2, 4, 8 }, [4]u64{ 1, 1, 2, 4 }); + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} From 2485f3004659723a1ccd2799a6e0bddb09e32d3b Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Sat, 14 Mar 2020 16:55:27 +0100 Subject: [PATCH 02/10] ir: Support bitwise not on vectors --- src/ir.cpp | 51 +++++++++++++++++++++++---------- test/stage1/behavior/vector.zig | 25 ++++++++++++++++ 2 files changed, 61 insertions(+), 15 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index 04eaa217b9..436db592f2 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -20363,24 +20363,45 @@ static IrInstGen *ir_analyze_bin_not(IrAnalyze *ira, IrInstSrcUnOp *instruction) if (type_is_invalid(expr_type)) return ira->codegen->invalid_inst_gen; - if (expr_type->id == ZigTypeIdInt) { - if (instr_is_comptime(value)) { - ZigValue *target_const_val = ir_resolve_const(ira, value, UndefBad); - if (target_const_val == nullptr) - return ira->codegen->invalid_inst_gen; + ZigType *scalar_type = (expr_type->id == ZigTypeIdVector) ? + expr_type->data.vector.elem_type : expr_type; - IrInstGen *result = ir_const(ira, &instruction->base.base, expr_type); - bigint_not(&result->value->data.x_bigint, &target_const_val->data.x_bigint, - expr_type->data.integral.bit_count, expr_type->data.integral.is_signed); - return result; - } - - return ir_build_binary_not(ira, &instruction->base.base, value, expr_type); + if (scalar_type->id != ZigTypeIdInt) { + ir_add_error(ira, &instruction->base.base, + buf_sprintf("unable to perform binary not operation on type '%s'", buf_ptr(&expr_type->name))); + return ira->codegen->invalid_inst_gen; } - ir_add_error(ira, &instruction->base.base, - buf_sprintf("unable to perform binary not operation on type '%s'", buf_ptr(&expr_type->name))); - return ira->codegen->invalid_inst_gen; + if (instr_is_comptime(value)) { + ZigValue *expr_val = ir_resolve_const(ira, value, UndefBad); + if (expr_val == nullptr) + return ira->codegen->invalid_inst_gen; + + IrInstGen *result = ir_const(ira, &instruction->base.base, expr_type); + + if (expr_type->id == ZigTypeIdVector) { + expand_undef_array(ira->codegen, expr_val); + result->value->special = ConstValSpecialUndef; + expand_undef_array(ira->codegen, result->value); + + for (size_t i = 0; i < expr_type->data.vector.len; i++) { + ZigValue *src_val = &expr_val->data.x_array.data.s_none.elements[i]; + ZigValue *dst_val = &result->value->data.x_array.data.s_none.elements[i]; + + dst_val->type = scalar_type; + dst_val->special = ConstValSpecialStatic; + bigint_not(&dst_val->data.x_bigint, &src_val->data.x_bigint, + scalar_type->data.integral.bit_count, scalar_type->data.integral.is_signed); + } + } else { + bigint_not(&result->value->data.x_bigint, &expr_val->data.x_bigint, + scalar_type->data.integral.bit_count, scalar_type->data.integral.is_signed); + } + + return result; + } + + return ir_build_binary_not(ira, &instruction->base.base, value, expr_type); } static IrInstGen *ir_analyze_instruction_un_op(IrAnalyze *ira, IrInstSrcUnOp *instruction) { diff --git a/test/stage1/behavior/vector.zig b/test/stage1/behavior/vector.zig index 6db695bfa3..f242aa0fbf 100644 --- a/test/stage1/behavior/vector.zig +++ b/test/stage1/behavior/vector.zig @@ -351,3 +351,28 @@ test "vector division operators" { S.doTheTest(); comptime S.doTheTest(); } + +test "vector bitwise not operator" { + const S = struct { + fn doTheTestNot(comptime T: type, x: @Vector(4, T)) void { + var y = ~x; + for (@as([4]T, y)) |v, i| { + expectEqual(~x[i], v); + } + } + fn doTheTest() void { + doTheTestNot(u8, [_]u8{ 0, 2, 4, 255 }); + doTheTestNot(u16, [_]u16{ 0, 2, 4, 255 }); + doTheTestNot(u32, [_]u32{ 0, 2, 4, 255 }); + doTheTestNot(u64, [_]u64{ 0, 2, 4, 255 }); + + doTheTestNot(u8, [_]u8{ 0, 2, 4, 255 }); + doTheTestNot(u16, [_]u16{ 0, 2, 4, 255 }); + doTheTestNot(u32, [_]u32{ 0, 2, 4, 255 }); + doTheTestNot(u64, [_]u64{ 0, 2, 4, 255 }); + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} From d2d97e55ccd2d7c992d01bd05ea52a52fe36776e Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Sat, 14 Mar 2020 20:01:28 +0100 Subject: [PATCH 03/10] ir: Support shift left/right on vectors --- src/codegen.cpp | 50 ++++++++++---- src/ir.cpp | 118 +++++++++++++++++++++++--------- test/stage1/behavior/vector.zig | 65 ++++++++++++++++++ 3 files changed, 184 insertions(+), 49 deletions(-) diff --git a/src/codegen.cpp b/src/codegen.cpp index 0fa181b32c..97d960b523 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -155,6 +155,7 @@ static LLVMValueRef gen_await_early_return(CodeGen *g, IrInstGen *source_instr, LLVMValueRef target_frame_ptr, ZigType *result_type, ZigType *ptr_result_type, LLVMValueRef result_loc, bool non_async); static Error get_tmp_filename(CodeGen *g, Buf *out, Buf *suffix); +static LLVMValueRef scalarize_cmp_result(CodeGen *g, LLVMValueRef val); static void addLLVMAttr(LLVMValueRef val, LLVMAttributeIndex attr_index, const char *attr_name) { unsigned kind_id = LLVMGetEnumAttributeKindForName(attr_name, strlen(attr_name)); @@ -2535,19 +2536,21 @@ static LLVMValueRef ir_render_return(CodeGen *g, IrExecutableGen *executable, Ir return nullptr; } -static LLVMValueRef gen_overflow_shl_op(CodeGen *g, ZigType *type_entry, - LLVMValueRef val1, LLVMValueRef val2) +static LLVMValueRef gen_overflow_shl_op(CodeGen *g, ZigType *operand_type, + LLVMValueRef val1, LLVMValueRef val2) { // 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 + ZigType *scalar_type = (operand_type->id == ZigTypeIdVector) ? + operand_type->data.vector.elem_type : operand_type; - assert(type_entry->id == ZigTypeIdInt); + assert(scalar_type->id == ZigTypeIdInt); LLVMValueRef result = LLVMBuildShl(g->builder, val1, val2, ""); LLVMValueRef orig_val; - if (type_entry->data.integral.is_signed) { + if (scalar_type->data.integral.is_signed) { orig_val = LLVMBuildAShr(g->builder, result, val2, ""); } else { orig_val = LLVMBuildLShr(g->builder, result, val2, ""); @@ -2556,6 +2559,9 @@ static LLVMValueRef gen_overflow_shl_op(CodeGen *g, ZigType *type_entry, LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "OverflowOk"); LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "OverflowFail"); + if (operand_type->id == ZigTypeIdVector) { + ok_bit = scalarize_cmp_result(g, ok_bit); + } LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); LLVMPositionBuilderAtEnd(g->builder, fail_block); @@ -2565,13 +2571,16 @@ static LLVMValueRef gen_overflow_shl_op(CodeGen *g, ZigType *type_entry, return result; } -static LLVMValueRef gen_overflow_shr_op(CodeGen *g, ZigType *type_entry, - LLVMValueRef val1, LLVMValueRef val2) +static LLVMValueRef gen_overflow_shr_op(CodeGen *g, ZigType *operand_type, + LLVMValueRef val1, LLVMValueRef val2) { - assert(type_entry->id == ZigTypeIdInt); + ZigType *scalar_type = (operand_type->id == ZigTypeIdVector) ? + operand_type->data.vector.elem_type : operand_type; + + assert(scalar_type->id == ZigTypeIdInt); LLVMValueRef result; - if (type_entry->data.integral.is_signed) { + if (scalar_type->data.integral.is_signed) { result = LLVMBuildAShr(g->builder, val1, val2, ""); } else { result = LLVMBuildLShr(g->builder, val1, val2, ""); @@ -2581,6 +2590,9 @@ static LLVMValueRef gen_overflow_shr_op(CodeGen *g, ZigType *type_entry, LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "OverflowOk"); LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "OverflowFail"); + if (operand_type->id == ZigTypeIdVector) { + ok_bit = scalarize_cmp_result(g, ok_bit); + } LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); LLVMPositionBuilderAtEnd(g->builder, fail_block); @@ -2897,11 +2909,17 @@ static void gen_shift_rhs_check(CodeGen *g, ZigType *lhs_type, ZigType *rhs_type // otherwise the check is useful as the allowed values are limited by the // operand type itself if (!is_power_of_2(lhs_type->data.integral.bit_count)) { - LLVMValueRef bit_count_value = LLVMConstInt(get_llvm_type(g, rhs_type), - lhs_type->data.integral.bit_count, false); - LLVMValueRef less_than_bit = LLVMBuildICmp(g->builder, LLVMIntULT, value, bit_count_value, ""); + BigInt bit_count_bi = {0}; + bigint_init_unsigned(&bit_count_bi, lhs_type->data.integral.bit_count); + LLVMValueRef bit_count_value = bigint_to_llvm_const(get_llvm_type(g, rhs_type), + &bit_count_bi); + LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "CheckFail"); LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "CheckOk"); + LLVMValueRef less_than_bit = LLVMBuildICmp(g->builder, LLVMIntULT, value, bit_count_value, ""); + if (rhs_type->id == ZigTypeIdVector) { + less_than_bit = scalarize_cmp_result(g, less_than_bit); + } LLVMBuildCondBr(g->builder, less_than_bit, ok_block, fail_block); LLVMPositionBuilderAtEnd(g->builder, fail_block); @@ -3018,7 +3036,8 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutableGen *executable, case IrBinOpBitShiftLeftExact: { assert(scalar_type->id == ZigTypeIdInt); - LLVMValueRef op2_casted = gen_widen_or_shorten(g, false, op2->value->type, scalar_type, op2_value); + LLVMValueRef op2_casted = LLVMBuildZExt(g->builder, op2_value, + LLVMTypeOf(op1_value), "");//gen_widen_or_shorten(g, false, op2->value->type, scalar_type, op2_value); if (want_runtime_safety) { gen_shift_rhs_check(g, scalar_type, op2->value->type, op2_value); @@ -3028,7 +3047,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutableGen *executable, if (is_sloppy) { return LLVMBuildShl(g->builder, op1_value, op2_casted, ""); } else if (want_runtime_safety) { - return gen_overflow_shl_op(g, scalar_type, op1_value, op2_casted); + return gen_overflow_shl_op(g, operand_type, op1_value, op2_casted); } else if (scalar_type->data.integral.is_signed) { return ZigLLVMBuildNSWShl(g->builder, op1_value, op2_casted, ""); } else { @@ -3039,7 +3058,8 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutableGen *executable, case IrBinOpBitShiftRightExact: { assert(scalar_type->id == ZigTypeIdInt); - LLVMValueRef op2_casted = gen_widen_or_shorten(g, false, op2->value->type, scalar_type, op2_value); + LLVMValueRef op2_casted = LLVMBuildZExt(g->builder, op2_value, + LLVMTypeOf(op1_value), "");//gen_widen_or_shorten(g, false, op2->value->type, scalar_type, op2_value); if (want_runtime_safety) { gen_shift_rhs_check(g, scalar_type, op2->value->type, op2_value); @@ -3053,7 +3073,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutableGen *executable, return LLVMBuildLShr(g->builder, op1_value, op2_casted, ""); } } else if (want_runtime_safety) { - return gen_overflow_shr_op(g, scalar_type, op1_value, op2_casted); + return gen_overflow_shr_op(g, operand_type, op1_value, op2_casted); } else if (scalar_type->data.integral.is_signed) { return ZigLLVMBuildAShrExact(g->builder, op1_value, op2_casted, ""); } else { diff --git a/src/ir.cpp b/src/ir.cpp index 436db592f2..6fed044c6c 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -283,6 +283,8 @@ static IrInstGen *ir_analyze_union_init(IrAnalyze *ira, IrInst* source_instructi IrInstGen *result_loc); static IrInstGen *ir_analyze_struct_value_field_value(IrAnalyze *ira, IrInst* source_instr, IrInstGen *struct_operand, TypeStructField *field); +static bool value_cmp_numeric_val_any(ZigValue *left, Cmp predicate, ZigValue *right); +static bool value_cmp_numeric_val_all(ZigValue *left, Cmp predicate, ZigValue *right); static void destroy_instruction_src(IrInstSrc *inst) { switch (inst->id) { @@ -16803,7 +16805,6 @@ static IrInstGen *ir_analyze_math_op(IrAnalyze *ira, IrInst* source_instr, ZigValue *scalar_op2_val = &op2_val->data.x_array.data.s_none.elements[i]; ZigValue *scalar_out_val = &out_val->data.x_array.data.s_none.elements[i]; assert(scalar_op1_val->type == scalar_type); - assert(scalar_op2_val->type == scalar_type); assert(scalar_out_val->type == scalar_type); ErrorMsg *msg = ir_eval_math_op_scalar(ira, source_instr, scalar_type, scalar_op1_val, op_id, scalar_op2_val, scalar_out_val); @@ -16828,27 +16829,49 @@ static IrInstGen *ir_analyze_bit_shift(IrAnalyze *ira, IrInstSrcBinOp *bin_op_in if (type_is_invalid(op1->value->type)) return ira->codegen->invalid_inst_gen; - if (op1->value->type->id != ZigTypeIdInt && op1->value->type->id != ZigTypeIdComptimeInt) { - ir_add_error(ira, &bin_op_instruction->op1->base, - buf_sprintf("bit shifting operation expected integer type, found '%s'", - buf_ptr(&op1->value->type->name))); - return ira->codegen->invalid_inst_gen; - } - IrInstGen *op2 = bin_op_instruction->op2->child; if (type_is_invalid(op2->value->type)) return ira->codegen->invalid_inst_gen; - if (op2->value->type->id != ZigTypeIdInt && op2->value->type->id != ZigTypeIdComptimeInt) { + ZigType *op1_type = op1->value->type; + ZigType *op2_type = op2->value->type; + + if (op1_type->id == ZigTypeIdVector && op2_type->id != ZigTypeIdVector) { + ir_add_error(ira, &bin_op_instruction->op1->base, + buf_sprintf("bit shifting operation expected vector type, found '%s'", + buf_ptr(&op2_type->name))); + return ira->codegen->invalid_inst_gen; + } + + if (op1_type->id != ZigTypeIdVector && op2_type->id == ZigTypeIdVector) { + ir_add_error(ira, &bin_op_instruction->op1->base, + buf_sprintf("bit shifting operation expected vector type, found '%s'", + buf_ptr(&op1_type->name))); + return ira->codegen->invalid_inst_gen; + } + + ZigType *op1_scalar_type = (op1_type->id == ZigTypeIdVector) ? + op1_type->data.vector.elem_type : op1_type; + ZigType *op2_scalar_type = (op2_type->id == ZigTypeIdVector) ? + op2_type->data.vector.elem_type : op2_type; + + if (op1_scalar_type->id != ZigTypeIdInt && op1_scalar_type->id != ZigTypeIdComptimeInt) { + ir_add_error(ira, &bin_op_instruction->op1->base, + buf_sprintf("bit shifting operation expected integer type, found '%s'", + buf_ptr(&op1_scalar_type->name))); + return ira->codegen->invalid_inst_gen; + } + + if (op2_scalar_type->id != ZigTypeIdInt && op2_scalar_type->id != ZigTypeIdComptimeInt) { ir_add_error(ira, &bin_op_instruction->op2->base, buf_sprintf("shift amount has to be an integer type, but found '%s'", - buf_ptr(&op2->value->type->name))); + buf_ptr(&op2_scalar_type->name))); return ira->codegen->invalid_inst_gen; } IrInstGen *casted_op2; IrBinOp op_id = bin_op_instruction->op_id; - if (op1->value->type->id == ZigTypeIdComptimeInt) { + if (op1_scalar_type->id == ZigTypeIdComptimeInt) { // comptime_int has no finite bit width casted_op2 = op2; @@ -16874,10 +16897,15 @@ static IrInstGen *ir_analyze_bit_shift(IrAnalyze *ira, IrInstSrcBinOp *bin_op_in return ira->codegen->invalid_inst_gen; } } else { - const unsigned bit_count = op1->value->type->data.integral.bit_count; + const unsigned bit_count = op1_scalar_type->data.integral.bit_count; ZigType *shift_amt_type = get_smallest_unsigned_int_type(ira->codegen, bit_count > 0 ? bit_count - 1 : 0); + if (op1_type->id == ZigTypeIdVector) { + shift_amt_type = get_vector_type(ira->codegen, op1_type->data.vector.len, + shift_amt_type); + } + casted_op2 = ir_implicit_cast(ira, op2, shift_amt_type); if (type_is_invalid(casted_op2->value->type)) return ira->codegen->invalid_inst_gen; @@ -16888,10 +16916,10 @@ static IrInstGen *ir_analyze_bit_shift(IrAnalyze *ira, IrInstSrcBinOp *bin_op_in if (op2_val == nullptr) return ira->codegen->invalid_inst_gen; - BigInt bit_count_value = {0}; - bigint_init_unsigned(&bit_count_value, bit_count); + ZigValue bit_count_value; + init_const_usize(ira->codegen, &bit_count_value, bit_count); - if (bigint_cmp(&op2_val->data.x_bigint, &bit_count_value) != CmpLT) { + if (!value_cmp_numeric_val_all(op2_val, CmpLT, &bit_count_value)) { ErrorMsg* msg = ir_add_error(ira, &bin_op_instruction->base.base, buf_sprintf("RHS of shift is too large for LHS type")); @@ -16910,7 +16938,7 @@ static IrInstGen *ir_analyze_bit_shift(IrAnalyze *ira, IrInstSrcBinOp *bin_op_in if (op2_val == nullptr) return ira->codegen->invalid_inst_gen; - if (bigint_cmp_zero(&op2_val->data.x_bigint) == CmpEQ) + if (value_cmp_numeric_val_all(op2_val, CmpEQ, nullptr)) return ir_analyze_cast(ira, &bin_op_instruction->base.base, op1->value->type, op1); } @@ -16923,7 +16951,7 @@ static IrInstGen *ir_analyze_bit_shift(IrAnalyze *ira, IrInstSrcBinOp *bin_op_in if (op2_val == nullptr) return ira->codegen->invalid_inst_gen; - return ir_analyze_math_op(ira, &bin_op_instruction->base.base, op1->value->type, op1_val, op_id, op2_val); + return ir_analyze_math_op(ira, &bin_op_instruction->base.base, op1_type, op1_val, op_id, op2_val); } return ir_build_bin_op_gen(ira, &bin_op_instruction->base.base, op1->value->type, @@ -16991,31 +17019,53 @@ static bool is_pointer_arithmetic_allowed(ZigType *lhs_type, IrBinOp op) { zig_unreachable(); } -static bool value_cmp_zero_any(ZigValue *value, Cmp predicate) { - assert(value->special == ConstValSpecialStatic); +static bool value_cmp_numeric_val(ZigValue *left, Cmp predicate, ZigValue *right, bool any) { + assert(left->special == ConstValSpecialStatic); + assert(right == nullptr || right->special == ConstValSpecialStatic); - switch (value->type->id) { + switch (left->type->id) { case ZigTypeIdComptimeInt: - case ZigTypeIdInt: - return bigint_cmp_zero(&value->data.x_bigint) == predicate; + case ZigTypeIdInt: { + const Cmp result = right ? + bigint_cmp(&left->data.x_bigint, &right->data.x_bigint) : + bigint_cmp_zero(&left->data.x_bigint); + return result == predicate; + } case ZigTypeIdComptimeFloat: - case ZigTypeIdFloat: - if (float_is_nan(value)) + case ZigTypeIdFloat: { + if (float_is_nan(left)) return false; - return float_cmp_zero(value) == predicate; + if (right != nullptr && float_is_nan(right)) + return false; + + const Cmp result = right ? float_cmp(left, right) : float_cmp_zero(left); + return result == predicate; + } case ZigTypeIdVector: { - for (size_t i = 0; i < value->type->data.vector.len; i++) { - ZigValue *scalar_val = &value->data.x_array.data.s_none.elements[i]; - if (!value_cmp_zero_any(scalar_val, predicate)) - return true; + for (size_t i = 0; i < left->type->data.vector.len; i++) { + ZigValue *scalar_val = &left->data.x_array.data.s_none.elements[i]; + const bool result = value_cmp_numeric_val(scalar_val, predicate, right, any); + + if (any && result) + return true; // This element satisfies the predicate + else if (!any && !result) + return false; // This element doesn't satisfy the predicate } - return false; + return any ? false : true; } default: zig_unreachable(); } } +static bool value_cmp_numeric_val_any(ZigValue *left, Cmp predicate, ZigValue *right) { + return value_cmp_numeric_val(left, predicate, right, true); +} + +static bool value_cmp_numeric_val_all(ZigValue *left, Cmp predicate, ZigValue *right) { + return value_cmp_numeric_val(left, predicate, right, false); +} + static IrInstGen *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstSrcBinOp *instruction) { Error err; @@ -17165,8 +17215,8 @@ static IrInstGen *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstSrcBinOp *instruc return ira->codegen->invalid_inst_gen; // Promote division with negative numbers to signed - bool is_signed_div = value_cmp_zero_any(op1_val, CmpLT) || - value_cmp_zero_any(op2_val, CmpLT); + bool is_signed_div = value_cmp_numeric_val_any(op1_val, CmpLT, nullptr) || + value_cmp_numeric_val_any(op2_val, CmpLT, nullptr); if (op_id == IrBinOpDivUnspecified && is_int) { // Default to truncating division and check if it's valid for the @@ -17176,7 +17226,7 @@ static IrInstGen *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstSrcBinOp *instruc if (is_signed_div) { bool ok = false; - if (value_cmp_zero_any(op2_val, CmpEQ)) { + if (value_cmp_numeric_val_any(op2_val, CmpEQ, nullptr)) { // the division by zero error will be caught later, but we don't have a // division function ambiguity problem. ok = true; @@ -17215,7 +17265,7 @@ static IrInstGen *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstSrcBinOp *instruc if (is_signed_div) { bool ok = false; - if (value_cmp_zero_any(op2_val, CmpEQ)) { + if (value_cmp_numeric_val_any(op2_val, CmpEQ, nullptr)) { // the division by zero error will be caught later, but we don't have a // division function ambiguity problem. ok = true; diff --git a/test/stage1/behavior/vector.zig b/test/stage1/behavior/vector.zig index f242aa0fbf..f3bc334b84 100644 --- a/test/stage1/behavior/vector.zig +++ b/test/stage1/behavior/vector.zig @@ -1,5 +1,6 @@ const std = @import("std"); const mem = std.mem; +const math = std.math; const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; @@ -376,3 +377,67 @@ test "vector bitwise not operator" { S.doTheTest(); comptime S.doTheTest(); } + +test "vector shift operators" { + const S = struct { + fn doTheTestShift(x: var, y: var) void { + const N = @typeInfo(@TypeOf(x)).Array.len; + const TX = @typeInfo(@TypeOf(x)).Array.child; + const TY = @typeInfo(@TypeOf(y)).Array.child; + + var xv = @as(@Vector(N, TX), x); + var yv = @as(@Vector(N, TY), y); + + var z0 = xv >> yv; + for (@as([N]TX, z0)) |v, i| { + expectEqual(x[i] >> y[i], v); + } + var z1 = xv << yv; + for (@as([N]TX, z1)) |v, i| { + expectEqual(x[i] << y[i], v); + } + } + fn doTheTestShiftExact(x: var, y: var, dir: enum { Left, Right }) void { + const N = @typeInfo(@TypeOf(x)).Array.len; + const TX = @typeInfo(@TypeOf(x)).Array.child; + const TY = @typeInfo(@TypeOf(y)).Array.child; + + var xv = @as(@Vector(N, TX), x); + var yv = @as(@Vector(N, TY), y); + + var z = if (dir == .Left) @shlExact(xv, yv) else @shrExact(xv, yv); + for (@as([N]TX, z)) |v, i| { + const check = if (dir == .Left) x[i] << y[i] else x[i] >> y[i]; + expectEqual(check, v); + } + } + fn doTheTest() void { + doTheTestShift([_]u8{ 0, 2, 4, math.maxInt(u8) }, [_]u3{ 2, 0, 2, 7 }); + doTheTestShift([_]u16{ 0, 2, 4, math.maxInt(u16) }, [_]u4{ 2, 0, 2, 15 }); + doTheTestShift([_]u24{ 0, 2, 4, math.maxInt(u24) }, [_]u5{ 2, 0, 2, 23 }); + doTheTestShift([_]u32{ 0, 2, 4, math.maxInt(u32) }, [_]u5{ 2, 0, 2, 31 }); + doTheTestShift([_]u64{ 0xfe, math.maxInt(u64) }, [_]u6{ 0, 63 }); + + doTheTestShift([_]i8{ 0, 2, 4, math.maxInt(i8) }, [_]u3{ 2, 0, 2, 7 }); + doTheTestShift([_]i16{ 0, 2, 4, math.maxInt(i16) }, [_]u4{ 2, 0, 2, 7 }); + doTheTestShift([_]i24{ 0, 2, 4, math.maxInt(i24) }, [_]u5{ 2, 0, 2, 7 }); + doTheTestShift([_]i32{ 0, 2, 4, math.maxInt(i32) }, [_]u5{ 2, 0, 2, 7 }); + doTheTestShift([_]i64{ 0xfe, math.maxInt(i64) }, [_]u6{ 0, 63 }); + + doTheTestShiftExact([_]u8{ 0, 1, 1 << 7, math.maxInt(u8) ^ 1 }, [_]u3{ 4, 0, 7, 1 }, .Right); + doTheTestShiftExact([_]u16{ 0, 1, 1 << 15, math.maxInt(u16) ^ 1 }, [_]u4{ 4, 0, 15, 1 }, .Right); + doTheTestShiftExact([_]u24{ 0, 1, 1 << 23, math.maxInt(u24) ^ 1 }, [_]u5{ 4, 0, 23, 1 }, .Right); + doTheTestShiftExact([_]u32{ 0, 1, 1 << 31, math.maxInt(u32) ^ 1 }, [_]u5{ 4, 0, 31, 1 }, .Right); + doTheTestShiftExact([_]u64{ 1 << 63, 1 }, [_]u6{ 63, 0 }, .Right); + + doTheTestShiftExact([_]u8{ 0, 1, 1, math.maxInt(u8) ^ (1 << 7) }, [_]u3{ 4, 0, 7, 1 }, .Left); + doTheTestShiftExact([_]u16{ 0, 1, 1, math.maxInt(u16) ^ (1 << 15) }, [_]u4{ 4, 0, 15, 1 }, .Left); + doTheTestShiftExact([_]u24{ 0, 1, 1, math.maxInt(u24) ^ (1 << 23) }, [_]u5{ 4, 0, 23, 1 }, .Left); + doTheTestShiftExact([_]u32{ 0, 1, 1, math.maxInt(u32) ^ (1 << 31) }, [_]u5{ 4, 0, 31, 1 }, .Left); + doTheTestShiftExact([_]u64{ 1 << 63, 1 }, [_]u6{ 0, 63 }, .Left); + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} From eff7555d5d62fc8c701529a9540fb296646d348e Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Sat, 14 Mar 2020 20:01:42 +0100 Subject: [PATCH 04/10] std: Delete a hack in the feature set code Now that bitwise not works on vectors we can simplify the code. --- lib/std/target.zig | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/std/target.zig b/lib/std/target.zig index 3f18791eb9..f920845d0d 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -501,11 +501,8 @@ pub const Target = struct { /// Removes the specified feature but not its dependents. pub fn removeFeatureSet(set: *Set, other_set: Set) void { - // TODO should be able to use binary not on @Vector type. - // https://github.com/ziglang/zig/issues/903 - for (set.ints) |*int, i| { - int.* &= ~other_set.ints[i]; - } + set.ints = @as(@Vector(usize_count, usize), set.ints) & + ~@as(@Vector(usize_count, usize), other_set.ints); } pub fn populateDependencies(set: *Set, all_features_list: []const Cpu.Feature) void { From fe77c38247a8869719579f911bf2775f00462330 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Tue, 24 Mar 2020 18:58:53 +0100 Subject: [PATCH 05/10] ir: Remove unused and commented out code --- src/codegen.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/codegen.cpp b/src/codegen.cpp index 97d960b523..e7fff882c3 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -3037,7 +3037,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutableGen *executable, { assert(scalar_type->id == ZigTypeIdInt); LLVMValueRef op2_casted = LLVMBuildZExt(g->builder, op2_value, - LLVMTypeOf(op1_value), "");//gen_widen_or_shorten(g, false, op2->value->type, scalar_type, op2_value); + LLVMTypeOf(op1_value), ""); if (want_runtime_safety) { gen_shift_rhs_check(g, scalar_type, op2->value->type, op2_value); @@ -3059,7 +3059,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutableGen *executable, { assert(scalar_type->id == ZigTypeIdInt); LLVMValueRef op2_casted = LLVMBuildZExt(g->builder, op2_value, - LLVMTypeOf(op1_value), "");//gen_widen_or_shorten(g, false, op2->value->type, scalar_type, op2_value); + LLVMTypeOf(op1_value), ""); if (want_runtime_safety) { gen_shift_rhs_check(g, scalar_type, op2->value->type, op2_value); From 91a8e3b47bef12727dc6d9e0dfe378f81375c794 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Tue, 24 Mar 2020 18:59:03 +0100 Subject: [PATCH 06/10] tests: Chop away some flaky tests --- test/stage1/behavior/vector.zig | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/test/stage1/behavior/vector.zig b/test/stage1/behavior/vector.zig index f3bc334b84..7e8eb6a42e 100644 --- a/test/stage1/behavior/vector.zig +++ b/test/stage1/behavior/vector.zig @@ -319,11 +319,13 @@ test "vector division operators" { } fn doTheTest() void { - doTheTestDiv(f16, [4]f16{ 4.0, -4.0, 4.0, -4.0 }, [4]f16{ 1.0, 2.0, -1.0, -2.0 }); + if (std.builtin.os.tag != .windows) + doTheTestDiv(f16, [4]f16{ 4.0, -4.0, 4.0, -4.0 }, [4]f16{ 1.0, 2.0, -1.0, -2.0 }); doTheTestDiv(f32, [4]f32{ 4.0, -4.0, 4.0, -4.0 }, [4]f32{ 1.0, 2.0, -1.0, -2.0 }); doTheTestDiv(f64, [4]f64{ 4.0, -4.0, 4.0, -4.0 }, [4]f64{ 1.0, 2.0, -1.0, -2.0 }); - doTheTestMod(f16, [4]f16{ 4.0, -4.0, 4.0, -4.0 }, [4]f16{ 1.0, 2.0, 0.5, 3.0 }); + if (std.builtin.os.tag != .windows) + doTheTestMod(f16, [4]f16{ 4.0, -4.0, 4.0, -4.0 }, [4]f16{ 1.0, 2.0, 0.5, 3.0 }); doTheTestMod(f32, [4]f32{ 4.0, -4.0, 4.0, -4.0 }, [4]f32{ 1.0, 2.0, 0.5, 3.0 }); doTheTestMod(f64, [4]f64{ 4.0, -4.0, 4.0, -4.0 }, [4]f64{ 1.0, 2.0, 0.5, 3.0 }); @@ -412,27 +414,37 @@ test "vector shift operators" { } } fn doTheTest() void { + const is32 = @sizeOf(usize) < 8; + doTheTestShift([_]u8{ 0, 2, 4, math.maxInt(u8) }, [_]u3{ 2, 0, 2, 7 }); doTheTestShift([_]u16{ 0, 2, 4, math.maxInt(u16) }, [_]u4{ 2, 0, 2, 15 }); - doTheTestShift([_]u24{ 0, 2, 4, math.maxInt(u24) }, [_]u5{ 2, 0, 2, 23 }); - doTheTestShift([_]u32{ 0, 2, 4, math.maxInt(u32) }, [_]u5{ 2, 0, 2, 31 }); + if (!is32) { + doTheTestShift([_]u24{ 0, 2, 4, math.maxInt(u24) }, [_]u5{ 2, 0, 2, 23 }); + doTheTestShift([_]u32{ 0, 2, 4, math.maxInt(u32) }, [_]u5{ 2, 0, 2, 31 }); + } doTheTestShift([_]u64{ 0xfe, math.maxInt(u64) }, [_]u6{ 0, 63 }); doTheTestShift([_]i8{ 0, 2, 4, math.maxInt(i8) }, [_]u3{ 2, 0, 2, 7 }); doTheTestShift([_]i16{ 0, 2, 4, math.maxInt(i16) }, [_]u4{ 2, 0, 2, 7 }); - doTheTestShift([_]i24{ 0, 2, 4, math.maxInt(i24) }, [_]u5{ 2, 0, 2, 7 }); - doTheTestShift([_]i32{ 0, 2, 4, math.maxInt(i32) }, [_]u5{ 2, 0, 2, 7 }); + if (!is32) { + doTheTestShift([_]i24{ 0, 2, 4, math.maxInt(i24) }, [_]u5{ 2, 0, 2, 7 }); + doTheTestShift([_]i32{ 0, 2, 4, math.maxInt(i32) }, [_]u5{ 2, 0, 2, 7 }); + } doTheTestShift([_]i64{ 0xfe, math.maxInt(i64) }, [_]u6{ 0, 63 }); doTheTestShiftExact([_]u8{ 0, 1, 1 << 7, math.maxInt(u8) ^ 1 }, [_]u3{ 4, 0, 7, 1 }, .Right); doTheTestShiftExact([_]u16{ 0, 1, 1 << 15, math.maxInt(u16) ^ 1 }, [_]u4{ 4, 0, 15, 1 }, .Right); - doTheTestShiftExact([_]u24{ 0, 1, 1 << 23, math.maxInt(u24) ^ 1 }, [_]u5{ 4, 0, 23, 1 }, .Right); + if (!is32) { + doTheTestShiftExact([_]u24{ 0, 1, 1 << 23, math.maxInt(u24) ^ 1 }, [_]u5{ 4, 0, 23, 1 }, .Right); + } doTheTestShiftExact([_]u32{ 0, 1, 1 << 31, math.maxInt(u32) ^ 1 }, [_]u5{ 4, 0, 31, 1 }, .Right); doTheTestShiftExact([_]u64{ 1 << 63, 1 }, [_]u6{ 63, 0 }, .Right); doTheTestShiftExact([_]u8{ 0, 1, 1, math.maxInt(u8) ^ (1 << 7) }, [_]u3{ 4, 0, 7, 1 }, .Left); doTheTestShiftExact([_]u16{ 0, 1, 1, math.maxInt(u16) ^ (1 << 15) }, [_]u4{ 4, 0, 15, 1 }, .Left); - doTheTestShiftExact([_]u24{ 0, 1, 1, math.maxInt(u24) ^ (1 << 23) }, [_]u5{ 4, 0, 23, 1 }, .Left); + if (!is32) { + doTheTestShiftExact([_]u24{ 0, 1, 1, math.maxInt(u24) ^ (1 << 23) }, [_]u5{ 4, 0, 23, 1 }, .Left); + } doTheTestShiftExact([_]u32{ 0, 1, 1, math.maxInt(u32) ^ (1 << 31) }, [_]u5{ 4, 0, 31, 1 }, .Left); doTheTestShiftExact([_]u64{ 1 << 63, 1 }, [_]u6{ 0, 63 }, .Left); } From 0f964e19109d44d039fbefa691657ca82c7bbe52 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Thu, 2 Apr 2020 10:25:08 +0200 Subject: [PATCH 07/10] I'm getting tired of this shit LLVM --- test/stage1/behavior/vector.zig | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/test/stage1/behavior/vector.zig b/test/stage1/behavior/vector.zig index 7e8eb6a42e..15ecd004e1 100644 --- a/test/stage1/behavior/vector.zig +++ b/test/stage1/behavior/vector.zig @@ -414,42 +414,36 @@ test "vector shift operators" { } } fn doTheTest() void { - const is32 = @sizeOf(usize) < 8; - doTheTestShift([_]u8{ 0, 2, 4, math.maxInt(u8) }, [_]u3{ 2, 0, 2, 7 }); doTheTestShift([_]u16{ 0, 2, 4, math.maxInt(u16) }, [_]u4{ 2, 0, 2, 15 }); - if (!is32) { - doTheTestShift([_]u24{ 0, 2, 4, math.maxInt(u24) }, [_]u5{ 2, 0, 2, 23 }); - doTheTestShift([_]u32{ 0, 2, 4, math.maxInt(u32) }, [_]u5{ 2, 0, 2, 31 }); - } + doTheTestShift([_]u24{ 0, 2, 4, math.maxInt(u24) }, [_]u5{ 2, 0, 2, 23 }); + doTheTestShift([_]u32{ 0, 2, 4, math.maxInt(u32) }, [_]u5{ 2, 0, 2, 31 }); doTheTestShift([_]u64{ 0xfe, math.maxInt(u64) }, [_]u6{ 0, 63 }); doTheTestShift([_]i8{ 0, 2, 4, math.maxInt(i8) }, [_]u3{ 2, 0, 2, 7 }); doTheTestShift([_]i16{ 0, 2, 4, math.maxInt(i16) }, [_]u4{ 2, 0, 2, 7 }); - if (!is32) { - doTheTestShift([_]i24{ 0, 2, 4, math.maxInt(i24) }, [_]u5{ 2, 0, 2, 7 }); - doTheTestShift([_]i32{ 0, 2, 4, math.maxInt(i32) }, [_]u5{ 2, 0, 2, 7 }); - } + doTheTestShift([_]i24{ 0, 2, 4, math.maxInt(i24) }, [_]u5{ 2, 0, 2, 7 }); + doTheTestShift([_]i32{ 0, 2, 4, math.maxInt(i32) }, [_]u5{ 2, 0, 2, 7 }); doTheTestShift([_]i64{ 0xfe, math.maxInt(i64) }, [_]u6{ 0, 63 }); doTheTestShiftExact([_]u8{ 0, 1, 1 << 7, math.maxInt(u8) ^ 1 }, [_]u3{ 4, 0, 7, 1 }, .Right); doTheTestShiftExact([_]u16{ 0, 1, 1 << 15, math.maxInt(u16) ^ 1 }, [_]u4{ 4, 0, 15, 1 }, .Right); - if (!is32) { - doTheTestShiftExact([_]u24{ 0, 1, 1 << 23, math.maxInt(u24) ^ 1 }, [_]u5{ 4, 0, 23, 1 }, .Right); - } + doTheTestShiftExact([_]u24{ 0, 1, 1 << 23, math.maxInt(u24) ^ 1 }, [_]u5{ 4, 0, 23, 1 }, .Right); doTheTestShiftExact([_]u32{ 0, 1, 1 << 31, math.maxInt(u32) ^ 1 }, [_]u5{ 4, 0, 31, 1 }, .Right); doTheTestShiftExact([_]u64{ 1 << 63, 1 }, [_]u6{ 63, 0 }, .Right); doTheTestShiftExact([_]u8{ 0, 1, 1, math.maxInt(u8) ^ (1 << 7) }, [_]u3{ 4, 0, 7, 1 }, .Left); doTheTestShiftExact([_]u16{ 0, 1, 1, math.maxInt(u16) ^ (1 << 15) }, [_]u4{ 4, 0, 15, 1 }, .Left); - if (!is32) { - doTheTestShiftExact([_]u24{ 0, 1, 1, math.maxInt(u24) ^ (1 << 23) }, [_]u5{ 4, 0, 23, 1 }, .Left); - } + doTheTestShiftExact([_]u24{ 0, 1, 1, math.maxInt(u24) ^ (1 << 23) }, [_]u5{ 4, 0, 23, 1 }, .Left); doTheTestShiftExact([_]u32{ 0, 1, 1, math.maxInt(u32) ^ (1 << 31) }, [_]u5{ 4, 0, 31, 1 }, .Left); doTheTestShiftExact([_]u64{ 1 << 63, 1 }, [_]u6{ 0, 63 }, .Left); } }; + // LLVM miscompiles pretty much every case on other architectures so don't + // even bother running this test + if (std.builtin.arch != .x86_64) return error.SkipZigTest; + S.doTheTest(); comptime S.doTheTest(); } From f6cdc94a50235eaf145f6c2c2ec257008d592494 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Sun, 5 Apr 2020 10:40:41 +0200 Subject: [PATCH 08/10] ir: Fix error checking for vector ops The extra logic that's needed was lost during a refactoring, now it should be fine. --- src/codegen.cpp | 59 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/src/codegen.cpp b/src/codegen.cpp index e7fff882c3..a2cd5fafc0 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -155,7 +155,6 @@ static LLVMValueRef gen_await_early_return(CodeGen *g, IrInstGen *source_instr, LLVMValueRef target_frame_ptr, ZigType *result_type, ZigType *ptr_result_type, LLVMValueRef result_loc, bool non_async); static Error get_tmp_filename(CodeGen *g, Buf *out, Buf *suffix); -static LLVMValueRef scalarize_cmp_result(CodeGen *g, LLVMValueRef val); static void addLLVMAttr(LLVMValueRef val, LLVMAttributeIndex attr_index, const char *attr_name) { unsigned kind_id = LLVMGetEnumAttributeKindForName(attr_name, strlen(attr_name)); @@ -2536,6 +2535,36 @@ static LLVMValueRef ir_render_return(CodeGen *g, IrExecutableGen *executable, Ir return nullptr; } +enum class ScalarizePredicate { + // Returns true iff all the elements in the vector are 1. + // Equivalent to folding all the bits with `and`. + All, + // Returns true iff there's at least one element in the vector that is 1. + // Equivalent to folding all the bits with `or`. + Any, +}; + +// Collapses a vector into a single i1 according to the given predicate +static LLVMValueRef scalarize_cmp_result(CodeGen *g, LLVMValueRef val, ScalarizePredicate predicate) { + assert(LLVMGetTypeKind(LLVMTypeOf(val)) == LLVMVectorTypeKind); + LLVMTypeRef scalar_type = LLVMIntType(LLVMGetVectorSize(LLVMTypeOf(val))); + LLVMValueRef casted = LLVMBuildBitCast(g->builder, val, scalar_type, ""); + + switch (predicate) { + case ScalarizePredicate::Any: { + LLVMValueRef all_zeros = LLVMConstNull(scalar_type); + return LLVMBuildICmp(g->builder, LLVMIntNE, casted, all_zeros, ""); + } + case ScalarizePredicate::All: { + LLVMValueRef all_ones = LLVMConstAllOnes(scalar_type); + return LLVMBuildICmp(g->builder, LLVMIntEQ, casted, all_ones, ""); + } + } + + zig_unreachable(); +} + + static LLVMValueRef gen_overflow_shl_op(CodeGen *g, ZigType *operand_type, LLVMValueRef val1, LLVMValueRef val2) { @@ -2560,7 +2589,7 @@ static LLVMValueRef gen_overflow_shl_op(CodeGen *g, ZigType *operand_type, LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "OverflowOk"); LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "OverflowFail"); if (operand_type->id == ZigTypeIdVector) { - ok_bit = scalarize_cmp_result(g, ok_bit); + ok_bit = scalarize_cmp_result(g, ok_bit, ScalarizePredicate::All); } LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); @@ -2591,7 +2620,7 @@ static LLVMValueRef gen_overflow_shr_op(CodeGen *g, ZigType *operand_type, LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "OverflowOk"); LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "OverflowFail"); if (operand_type->id == ZigTypeIdVector) { - ok_bit = scalarize_cmp_result(g, ok_bit); + ok_bit = scalarize_cmp_result(g, ok_bit, ScalarizePredicate::All); } LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); @@ -2647,16 +2676,6 @@ static LLVMValueRef bigint_to_llvm_const(LLVMTypeRef type_ref, BigInt *bigint) { } } -// Collapses a vector into a single i1 whose value is 1 iff all the -// vector elements are 1 -static LLVMValueRef scalarize_cmp_result(CodeGen *g, LLVMValueRef val) { - assert(LLVMGetTypeKind(LLVMTypeOf(val)) == LLVMVectorTypeKind); - LLVMTypeRef scalar_type = LLVMIntType(LLVMGetVectorSize(LLVMTypeOf(val))); - LLVMValueRef all_ones = LLVMConstAllOnes(scalar_type); - LLVMValueRef casted = LLVMBuildBitCast(g->builder, val, scalar_type, ""); - return LLVMBuildICmp(g->builder, LLVMIntEQ, casted, all_ones, ""); -} - static LLVMValueRef gen_div(CodeGen *g, bool want_runtime_safety, bool want_fast_math, LLVMValueRef val1, LLVMValueRef val2, ZigType *operand_type, DivKind div_kind) { @@ -2678,7 +2697,7 @@ static LLVMValueRef gen_div(CodeGen *g, bool want_runtime_safety, bool want_fast } if (operand_type->id == ZigTypeIdVector) { - is_zero_bit = scalarize_cmp_result(g, is_zero_bit); + is_zero_bit = scalarize_cmp_result(g, is_zero_bit, ScalarizePredicate::Any); } LLVMBasicBlockRef div_zero_fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivZeroFail"); @@ -2703,7 +2722,7 @@ static LLVMValueRef gen_div(CodeGen *g, bool want_runtime_safety, bool want_fast LLVMValueRef den_is_neg_1 = LLVMBuildICmp(g->builder, LLVMIntEQ, val2, neg_1_value, ""); LLVMValueRef overflow_fail_bit = LLVMBuildAnd(g->builder, num_is_int_min, den_is_neg_1, ""); if (operand_type->id == ZigTypeIdVector) { - overflow_fail_bit = scalarize_cmp_result(g, overflow_fail_bit); + overflow_fail_bit = scalarize_cmp_result(g, overflow_fail_bit, ScalarizePredicate::Any); } LLVMBuildCondBr(g->builder, overflow_fail_bit, overflow_fail_block, overflow_ok_block); @@ -2728,7 +2747,7 @@ static LLVMValueRef gen_div(CodeGen *g, bool want_runtime_safety, bool want_fast LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivExactFail"); LLVMValueRef ok_bit = LLVMBuildFCmp(g->builder, LLVMRealOEQ, floored, result, ""); if (operand_type->id == ZigTypeIdVector) { - ok_bit = scalarize_cmp_result(g, ok_bit); + ok_bit = scalarize_cmp_result(g, ok_bit, ScalarizePredicate::All); } LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); @@ -2745,7 +2764,7 @@ static LLVMValueRef gen_div(CodeGen *g, bool want_runtime_safety, bool want_fast LLVMBasicBlockRef end_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivTruncEnd"); LLVMValueRef ltz = LLVMBuildFCmp(g->builder, LLVMRealOLT, val1, zero, ""); if (operand_type->id == ZigTypeIdVector) { - ltz = scalarize_cmp_result(g, ltz); + ltz = scalarize_cmp_result(g, ltz, ScalarizePredicate::Any); } LLVMBuildCondBr(g->builder, ltz, ltz_block, gez_block); @@ -2797,7 +2816,7 @@ static LLVMValueRef gen_div(CodeGen *g, bool want_runtime_safety, bool want_fast LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "DivExactFail"); LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, remainder_val, zero, ""); if (operand_type->id == ZigTypeIdVector) { - ok_bit = scalarize_cmp_result(g, ok_bit); + ok_bit = scalarize_cmp_result(g, ok_bit, ScalarizePredicate::All); } LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); @@ -2861,7 +2880,7 @@ static LLVMValueRef gen_rem(CodeGen *g, bool want_runtime_safety, bool want_fast } if (operand_type->id == ZigTypeIdVector) { - is_zero_bit = scalarize_cmp_result(g, is_zero_bit); + is_zero_bit = scalarize_cmp_result(g, is_zero_bit, ScalarizePredicate::Any); } LLVMBasicBlockRef rem_zero_ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "RemZeroOk"); @@ -2918,7 +2937,7 @@ static void gen_shift_rhs_check(CodeGen *g, ZigType *lhs_type, ZigType *rhs_type LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "CheckOk"); LLVMValueRef less_than_bit = LLVMBuildICmp(g->builder, LLVMIntULT, value, bit_count_value, ""); if (rhs_type->id == ZigTypeIdVector) { - less_than_bit = scalarize_cmp_result(g, less_than_bit); + less_than_bit = scalarize_cmp_result(g, less_than_bit, ScalarizePredicate::Any); } LLVMBuildCondBr(g->builder, less_than_bit, ok_block, fail_block); From 5597b11a527b4495bcb3001211e6ee3923c00f87 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 5 Apr 2020 17:44:34 -0400 Subject: [PATCH 09/10] add runtime safety tests for SIMD integer division --- test/runtime_safety.zig | 43 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/test/runtime_safety.zig b/test/runtime_safety.zig index b8ab47ddac..f0c83049da 100644 --- a/test/runtime_safety.zig +++ b/test/runtime_safety.zig @@ -505,6 +505,21 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\} ); + cases.addRuntimeSafety("signed integer division overflow - vectors", + \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { + \\ @import("std").os.exit(126); + \\} + \\pub fn main() !void { + \\ var a: @Vector(4, i16) = [_]i16{ 1, 2, -32768, 4 }; + \\ var b: @Vector(4, i16) = [_]i16{ 1, 2, -1, 4 }; + \\ const x = div(a, b); + \\ if (x[2] == 32767) return error.Whatever; + \\} + \\fn div(a: @Vector(4, i16), b: @Vector(4, i16)) @Vector(4, i16) { + \\ return @divTrunc(a, b); + \\} + ); + cases.addRuntimeSafety("signed shift left overflow", \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); @@ -569,6 +584,20 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\} ); + cases.addRuntimeSafety("integer division by zero - vectors", + \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { + \\ @import("std").os.exit(126); + \\} + \\pub fn main() void { + \\ var a: @Vector(4, i32) = [4]i32{111, 222, 333, 444}; + \\ var b: @Vector(4, i32) = [4]i32{111, 0, 333, 444}; + \\ const x = div0(a, b); + \\} + \\fn div0(a: @Vector(4, i32), b: @Vector(4, i32)) @Vector(4, i32) { + \\ return @divTrunc(a, b); + \\} + ); + cases.addRuntimeSafety("exact division failure", \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); @@ -582,6 +611,20 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\} ); + cases.addRuntimeSafety("exact division failure - vectors", + \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { + \\ @import("std").os.exit(126); + \\} + \\pub fn main() !void { + \\ var a: @Vector(4, i32) = [4]i32{111, 222, 333, 444}; + \\ var b: @Vector(4, i32) = [4]i32{111, 222, 333, 441}; + \\ const x = divExact(a, b); + \\} + \\fn divExact(a: @Vector(4, i32), b: @Vector(4, i32)) @Vector(4, i32) { + \\ return @divExact(a, b); + \\} + ); + cases.addRuntimeSafety("cast []u8 to bigger slice of wrong size", \\const std = @import("std"); \\pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { From e84b9b70ff2814d6e50a851dc9f094b15399d2fe Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 5 Apr 2020 18:33:47 -0400 Subject: [PATCH 10/10] annotate disabled tests with github issue links --- test/stage1/behavior/vector.zig | 34 ++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/test/stage1/behavior/vector.zig b/test/stage1/behavior/vector.zig index 15ecd004e1..1d175de22f 100644 --- a/test/stage1/behavior/vector.zig +++ b/test/stage1/behavior/vector.zig @@ -319,13 +319,18 @@ test "vector division operators" { } fn doTheTest() void { - if (std.builtin.os.tag != .windows) + // https://github.com/ziglang/zig/issues/4952 + if (std.builtin.os.tag != .windows) { doTheTestDiv(f16, [4]f16{ 4.0, -4.0, 4.0, -4.0 }, [4]f16{ 1.0, 2.0, -1.0, -2.0 }); + } + doTheTestDiv(f32, [4]f32{ 4.0, -4.0, 4.0, -4.0 }, [4]f32{ 1.0, 2.0, -1.0, -2.0 }); doTheTestDiv(f64, [4]f64{ 4.0, -4.0, 4.0, -4.0 }, [4]f64{ 1.0, 2.0, -1.0, -2.0 }); - if (std.builtin.os.tag != .windows) + // https://github.com/ziglang/zig/issues/4952 + if (std.builtin.os.tag != .windows) { doTheTestMod(f16, [4]f16{ 4.0, -4.0, 4.0, -4.0 }, [4]f16{ 1.0, 2.0, 0.5, 3.0 }); + } doTheTestMod(f32, [4]f32{ 4.0, -4.0, 4.0, -4.0 }, [4]f32{ 1.0, 2.0, 0.5, 3.0 }); doTheTestMod(f64, [4]f64{ 4.0, -4.0, 4.0, -4.0 }, [4]f64{ 1.0, 2.0, 0.5, 3.0 }); @@ -440,9 +445,28 @@ test "vector shift operators" { } }; - // LLVM miscompiles pretty much every case on other architectures so don't - // even bother running this test - if (std.builtin.arch != .x86_64) return error.SkipZigTest; + switch (std.builtin.arch) { + .i386, + .aarch64, + .aarch64_be, + .aarch64_32, + .arm, + .armeb, + .thumb, + .thumbeb, + .mips, + .mipsel, + .mips64, + .mips64el, + .riscv64, + .sparcv9, + => { + // LLVM miscompiles on this architecture + // https://github.com/ziglang/zig/issues/4951 + return error.SkipZigTest; + }, + else => {}, + } S.doTheTest(); comptime S.doTheTest();