SymbolicValue ConstExprFunctionState::computeConstantValueBuiltin(BuiltinInst *inst) { const BuiltinInfo &builtin = inst->getBuiltinInfo(); // Handle various cases in groups. auto unknownResult = [&]() -> SymbolicValue { return evaluator.getUnknown(SILValue(inst), UnknownReason::Default); }; // Unary operations. if (inst->getNumOperands() == 1) { auto operand = getConstantValue(inst->getOperand(0)); // TODO: Could add a "value used here" sort of diagnostic. if (!operand.isConstant()) return operand; // TODO: SUCheckedConversion/USCheckedConversion // Implement support for s_to_s_checked_trunc_Int2048_Int64 and other // checking integer truncates. These produce a tuple of the result value // and an overflow bit. // // TODO: We can/should diagnose statically detectable integer overflow // errors and subsume the ConstantFolding.cpp mandatory SIL pass. auto IntCheckedTruncFn = [&](bool srcSigned, bool dstSigned) -> SymbolicValue { if (operand.getKind() != SymbolicValue::Integer) return unknownResult(); auto operandVal = operand.getIntegerValue(); uint32_t srcBitWidth = operandVal.getBitWidth(); auto dstBitWidth = builtin.Types[1]->castTo<BuiltinIntegerType>()->getGreatestWidth(); APInt result = operandVal.trunc(dstBitWidth); // Compute the overflow by re-extending the value back to its source and // checking for loss of value. APInt reextended = dstSigned ? result.sext(srcBitWidth) : result.zext(srcBitWidth); bool overflowed = (operandVal != reextended); if (!srcSigned && dstSigned) overflowed |= result.isSignBitSet(); if (overflowed) return evaluator.getUnknown(SILValue(inst), UnknownReason::Overflow); auto &astContext = evaluator.getASTContext(); // Build the Symbolic value result for our truncated value. return SymbolicValue::getAggregate( {SymbolicValue::getInteger(result, astContext), SymbolicValue::getInteger(APInt(1, overflowed), astContext)}, astContext); }; switch (builtin.ID) { default: break; case BuiltinValueKind::SToSCheckedTrunc: return IntCheckedTruncFn(true, true); case BuiltinValueKind::UToSCheckedTrunc: return IntCheckedTruncFn(false, true); case BuiltinValueKind::SToUCheckedTrunc: return IntCheckedTruncFn(true, false); case BuiltinValueKind::UToUCheckedTrunc: return IntCheckedTruncFn(false, false); case BuiltinValueKind::Trunc: case BuiltinValueKind::TruncOrBitCast: case BuiltinValueKind::ZExt: case BuiltinValueKind::ZExtOrBitCast: case BuiltinValueKind::SExt: case BuiltinValueKind::SExtOrBitCast: { if (operand.getKind() != SymbolicValue::Integer) return unknownResult(); unsigned destBitWidth = inst->getType().castTo<BuiltinIntegerType>()->getGreatestWidth(); APInt result = operand.getIntegerValue(); if (result.getBitWidth() != destBitWidth) { switch (builtin.ID) { default: assert(0 && "Unknown case"); case BuiltinValueKind::Trunc: case BuiltinValueKind::TruncOrBitCast: result = result.trunc(destBitWidth); break; case BuiltinValueKind::ZExt: case BuiltinValueKind::ZExtOrBitCast: result = result.zext(destBitWidth); break; case BuiltinValueKind::SExt: case BuiltinValueKind::SExtOrBitCast: result = result.sext(destBitWidth); break; } } return SymbolicValue::getInteger(result, evaluator.getASTContext()); } } } // Binary operations. if (inst->getNumOperands() == 2) { auto operand0 = getConstantValue(inst->getOperand(0)); auto operand1 = getConstantValue(inst->getOperand(1)); if (!operand0.isConstant()) return operand0; if (!operand1.isConstant()) return operand1; auto constFoldIntCompare = [&](const std::function<bool(const APInt &, const APInt &)> &fn) -> SymbolicValue { if (operand0.getKind() != SymbolicValue::Integer || operand1.getKind() != SymbolicValue::Integer) return unknownResult(); auto result = fn(operand0.getIntegerValue(), operand1.getIntegerValue()); return SymbolicValue::getInteger(APInt(1, result), evaluator.getASTContext()); }; #define REQUIRE_KIND(KIND) \ if (operand0.getKind() != SymbolicValue::KIND || \ operand1.getKind() != SymbolicValue::KIND) \ return unknownResult(); switch (builtin.ID) { default: break; #define INT_BINOP(OPCODE, EXPR) \ case BuiltinValueKind::OPCODE: { \ REQUIRE_KIND(Integer) \ auto l = operand0.getIntegerValue(), r = operand1.getIntegerValue(); \ return SymbolicValue::getInteger((EXPR), evaluator.getASTContext()); \ } INT_BINOP(Add, l + r) INT_BINOP(And, l & r) INT_BINOP(AShr, l.ashr(r)) INT_BINOP(LShr, l.lshr(r)) INT_BINOP(Or, l | r) INT_BINOP(Mul, l * r) INT_BINOP(SDiv, l.sdiv(r)) INT_BINOP(Shl, l << r) INT_BINOP(SRem, l.srem(r)) INT_BINOP(Sub, l - r) INT_BINOP(UDiv, l.udiv(r)) INT_BINOP(URem, l.urem(r)) INT_BINOP(Xor, l ^ r) #undef INT_BINOP #define INT_COMPARE(OPCODE, EXPR) \ case BuiltinValueKind::OPCODE: \ REQUIRE_KIND(Integer) \ return constFoldIntCompare( \ [&](const APInt &l, const APInt &r) -> bool { return (EXPR); }) INT_COMPARE(ICMP_EQ, l == r); INT_COMPARE(ICMP_NE, l != r); INT_COMPARE(ICMP_SLT, l.slt(r)); INT_COMPARE(ICMP_SGT, l.sgt(r)); INT_COMPARE(ICMP_SLE, l.sle(r)); INT_COMPARE(ICMP_SGE, l.sge(r)); INT_COMPARE(ICMP_ULT, l.ult(r)); INT_COMPARE(ICMP_UGT, l.ugt(r)); INT_COMPARE(ICMP_ULE, l.ule(r)); INT_COMPARE(ICMP_UGE, l.uge(r)); #undef INT_COMPARE #undef REQUIRE_KIND } } // Three operand builtins. if (inst->getNumOperands() == 3) { auto operand0 = getConstantValue(inst->getOperand(0)); auto operand1 = getConstantValue(inst->getOperand(1)); auto operand2 = getConstantValue(inst->getOperand(2)); if (!operand0.isConstant()) return operand0; if (!operand1.isConstant()) return operand1; if (!operand2.isConstant()) return operand2; // Overflowing integer operations like sadd_with_overflow take three // operands: the last one is a "should report overflow" bit. auto constFoldIntOverflow = [&](const std::function<APInt(const APInt &, const APInt &, bool &)> &fn) -> SymbolicValue { if (operand0.getKind() != SymbolicValue::Integer || operand1.getKind() != SymbolicValue::Integer || operand2.getKind() != SymbolicValue::Integer) return unknownResult(); auto l = operand0.getIntegerValue(), r = operand1.getIntegerValue(); bool overflowed = false; auto result = fn(l, r, overflowed); // Return a statically diagnosed overflow if the operation is supposed to // trap on overflow. if (overflowed && !operand2.getIntegerValue().isNullValue()) return evaluator.getUnknown(SILValue(inst), UnknownReason::Overflow); auto &astContext = evaluator.getASTContext(); // Build the Symbolic value result for our normal and overflow bit. return SymbolicValue::getAggregate( {SymbolicValue::getInteger(result, astContext), SymbolicValue::getInteger(APInt(1, overflowed), astContext)}, astContext); }; switch (builtin.ID) { default: break; #define INT_OVERFLOW(OPCODE, METHOD) \ case BuiltinValueKind::OPCODE: \ return constFoldIntOverflow( \ [&](const APInt &l, const APInt &r, bool &overflowed) -> APInt { \ return l.METHOD(r, overflowed); \ }) INT_OVERFLOW(SAddOver, sadd_ov); INT_OVERFLOW(UAddOver, uadd_ov); INT_OVERFLOW(SSubOver, ssub_ov); INT_OVERFLOW(USubOver, usub_ov); INT_OVERFLOW(SMulOver, smul_ov); INT_OVERFLOW(UMulOver, umul_ov); #undef INT_OVERFLOW } } LLVM_DEBUG(llvm::dbgs() << "ConstExpr Unknown Builtin: " << *inst << "\n"); // Otherwise, we don't know how to handle this builtin. return unknownResult(); }