1 Star 0 Fork 3

xiongying/Halide

forked from Gitee 极速下载/Halide 
加入 Gitee
与超过 1400万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
Simplify_Call.cpp 32.14 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801
#include "Simplify_Internal.h"
#include "FindIntrinsics.h"
#include "Simplify.h"
#ifdef _MSC_VER
#include <intrin.h>
#endif
#include <cfenv>
#include <functional>
#include <unordered_map>
namespace Halide {
namespace Internal {
using std::string;
using std::vector;
namespace {
// Rewrite name(broadcast(args)) to broadcast(name(args)).
// Assumes that scalars are implicitly broadcast.
Expr lift_elementwise_broadcasts(Type type, const std::string &name, std::vector<Expr> args, Call::CallType call_type) {
if (type.lanes() == 1) {
return Expr();
}
int lanes = 0;
for (Expr &i : args) {
if (const Broadcast *b = i.as<Broadcast>()) {
i = b->value;
if (lanes == 0) {
lanes = i.type().lanes();
} else if (lanes != i.type().lanes()) {
// This is a broadcast of another vector, and does not match another vector argument.
return Expr();
}
} else if (!i.type().is_scalar()) {
// This is not a scalar or broadcast scalar, we can't lift broadcasts.
return Expr();
}
}
if (lanes != type.lanes()) {
return Broadcast::make(Call::make(type.with_lanes(lanes), name, args, call_type), type.lanes() / lanes);
} else {
return Expr();
}
}
} // namespace
Expr Simplify::visit(const Call *op, ExprInfo *bounds) {
// Calls implicitly depend on host, dev, mins, and strides of the buffer referenced
if (op->call_type == Call::Image || op->call_type == Call::Halide) {
found_buffer_reference(op->name, op->args.size());
}
if (op->is_intrinsic(Call::unreachable)) {
in_unreachable = true;
return op;
} else if (op->is_intrinsic(Call::strict_float)) {
if (Call::as_intrinsic(op->args[0], {Call::strict_float})) {
// Always simplify strict_float(strict_float(x)) -> strict_float(x).
Expr arg = mutate(op->args[0], nullptr);
return arg.same_as(op->args[0]) ? op->args[0] : arg;
} else {
ScopedValue<bool> save_no_float_simplify(no_float_simplify, true);
Expr arg = mutate(op->args[0], nullptr);
if (arg.same_as(op->args[0])) {
return op;
} else {
return strict_float(arg);
}
}
} else if (op->is_intrinsic(Call::popcount) ||
op->is_intrinsic(Call::count_leading_zeros) ||
op->is_intrinsic(Call::count_trailing_zeros)) {
Expr a = mutate(op->args[0], nullptr);
Expr unbroadcast = lift_elementwise_broadcasts(op->type, op->name, {a}, op->call_type);
if (unbroadcast.defined()) {
return mutate(unbroadcast, bounds);
}
uint64_t ua = 0;
if (const_int(a, (int64_t *)(&ua)) || const_uint(a, &ua)) {
const int bits = op->type.bits();
const uint64_t mask = std::numeric_limits<uint64_t>::max() >> (64 - bits);
ua &= mask;
static_assert(sizeof(unsigned long long) >= sizeof(uint64_t), "");
int r = 0;
if (op->is_intrinsic(Call::popcount)) {
// popcount *is* well-defined for ua = 0
r = popcount64(ua);
} else if (op->is_intrinsic(Call::count_leading_zeros)) {
// clz64() is undefined for 0, but Halide's count_leading_zeros defines clz(0) = bits
r = ua == 0 ? bits : (clz64(ua) - (64 - bits));
} else /* if (op->is_intrinsic(Call::count_trailing_zeros)) */ {
// ctz64() is undefined for 0, but Halide's count_trailing_zeros defines clz(0) = bits
r = ua == 0 ? bits : (ctz64(ua));
}
return make_const(op->type, r);
}
if (a.same_as(op->args[0])) {
return op;
} else {
return Call::make(op->type, op->name, {std::move(a)}, Internal::Call::PureIntrinsic);
}
} else if (op->is_intrinsic(Call::shift_left) ||
op->is_intrinsic(Call::shift_right)) {
Expr a = mutate(op->args[0], nullptr);
// TODO: When simplifying b, it would be nice to specify the min/max useful bounds, so
// stronger simplifications could occur. For example, x >> min(-i8, 0) should be simplified
// to x >> -max(i8, 0) and then x << max(i8, 0). This isn't safe because -i8 can overflow.
ExprInfo b_info;
Expr b = mutate(op->args[1], &b_info);
if (is_const_zero(b)) {
return a;
}
Expr unbroadcast = lift_elementwise_broadcasts(op->type, op->name, {a, b}, op->call_type);
if (unbroadcast.defined()) {
return mutate(unbroadcast, bounds);
}
const Type t = op->type;
// We might swap from a right to left shift or the reverse.
std::string result_op = op->name;
// If we know the sign of this shift, change it to an unsigned shift.
if (b_info.min_defined && b_info.min >= 0) {
b = mutate(cast(b.type().with_code(halide_type_uint), b), nullptr);
} else if (b.type().is_int() && b_info.max_defined && b_info.max <= 0) {
result_op = Call::get_intrinsic_name(op->is_intrinsic(Call::shift_right) ? Call::shift_left : Call::shift_right);
b = mutate(cast(b.type().with_code(halide_type_uint), -b), nullptr);
}
// If the shift is by a constant, it should now be unsigned.
uint64_t ub = 0;
if (const_uint(b, &ub)) {
// LLVM shl and shr instructions produce poison for
// shifts >= typesize, so we will follow suit in our simplifier.
if (ub >= (uint64_t)(t.bits())) {
clear_bounds_info(bounds);
return make_signed_integer_overflow(t);
}
if (a.type().is_uint() || ub < ((uint64_t)t.bits() - 1)) {
b = make_const(t, ((int64_t)1LL) << ub);
if (result_op == Call::get_intrinsic_name(Call::shift_left)) {
return mutate(Mul::make(a, b), bounds);
} else {
return mutate(Div::make(a, b), bounds);
}
} else {
// For signed types, (1 << (t.bits() - 1)) will overflow into the sign bit while
// (-32768 >> (t.bits() - 1)) propagates the sign bit, making decomposition
// into mul or div problematic, so just special-case them here.
if (result_op == Call::get_intrinsic_name(Call::shift_left)) {
return mutate(select((a & 1) != 0, make_const(t, ((int64_t)1LL) << ub), make_zero(t)), bounds);
} else {
return mutate(select(a < 0, make_const(t, -1), make_zero(t)), bounds);
}
}
}
// Rewrite shifts with signed negated RHSes as shifts of the other direction.
if (b.type().is_int()) {
if (const Sub *sub = b.as<Sub>()) {
if (is_const_zero(sub->a)) {
result_op = Call::get_intrinsic_name(op->is_intrinsic(Call::shift_right) ? Call::shift_left : Call::shift_right);
b = sub->b;
return mutate(Call::make(op->type, result_op, {a, b}, Call::PureIntrinsic), bounds);
}
}
}
if (a.same_as(op->args[0]) && b.same_as(op->args[1])) {
internal_assert(result_op == op->name);
return op;
} else {
return Call::make(op->type, result_op, {a, b}, Call::PureIntrinsic);
}
} else if (op->is_intrinsic(Call::bitwise_and)) {
Expr a = mutate(op->args[0], nullptr);
Expr b = mutate(op->args[1], nullptr);
Expr unbroadcast = lift_elementwise_broadcasts(op->type, op->name, {a, b}, op->call_type);
if (unbroadcast.defined()) {
return mutate(unbroadcast, bounds);
}
int64_t ia, ib = 0;
uint64_t ua, ub = 0;
int bits;
if (const_int(a, &ia) &&
const_int(b, &ib)) {
return make_const(op->type, ia & ib);
} else if (const_uint(a, &ua) &&
const_uint(b, &ub)) {
return make_const(op->type, ua & ub);
} else if (const_int(b, &ib) &&
!b.type().is_max(ib) &&
is_const_power_of_two_integer(make_const(a.type(), ib + 1), &bits)) {
return Mod::make(a, make_const(a.type(), ib + 1));
} else if (const_uint(b, &ub) &&
b.type().is_max(ub)) {
return a;
} else if (const_int(b, &ib) &&
ib == -1) {
return a;
} else if (const_uint(b, &ub) &&
is_const_power_of_two_integer(make_const(a.type(), ub + 1), &bits)) {
return Mod::make(a, make_const(a.type(), ub + 1));
} else if (a.same_as(op->args[0]) && b.same_as(op->args[1])) {
return op;
} else {
return a & b;
}
} else if (op->is_intrinsic(Call::bitwise_or)) {
Expr a = mutate(op->args[0], nullptr);
Expr b = mutate(op->args[1], nullptr);
Expr unbroadcast = lift_elementwise_broadcasts(op->type, op->name, {a, b}, op->call_type);
if (unbroadcast.defined()) {
return mutate(unbroadcast, bounds);
}
int64_t ia, ib;
uint64_t ua, ub;
if (const_int(a, &ia) &&
const_int(b, &ib)) {
return make_const(op->type, ia | ib);
} else if (const_uint(a, &ua) &&
const_uint(b, &ub)) {
return make_const(op->type, ua | ub);
} else if (a.same_as(op->args[0]) && b.same_as(op->args[1])) {
return op;
} else {
return a | b;
}
} else if (op->is_intrinsic(Call::bitwise_not)) {
Expr a = mutate(op->args[0], nullptr);
Expr unbroadcast = lift_elementwise_broadcasts(op->type, op->name, {a}, op->call_type);
if (unbroadcast.defined()) {
return mutate(unbroadcast, bounds);
}
int64_t ia;
uint64_t ua;
if (const_int(a, &ia)) {
return make_const(op->type, ~ia);
} else if (const_uint(a, &ua)) {
return make_const(op->type, ~ua);
} else if (a.same_as(op->args[0])) {
return op;
} else {
return ~a;
}
} else if (op->is_intrinsic(Call::bitwise_xor)) {
Expr a = mutate(op->args[0], nullptr);
Expr b = mutate(op->args[1], nullptr);
Expr unbroadcast = lift_elementwise_broadcasts(op->type, op->name, {a, b}, op->call_type);
if (unbroadcast.defined()) {
return mutate(unbroadcast, bounds);
}
int64_t ia, ib;
uint64_t ua, ub;
if (const_int(a, &ia) &&
const_int(b, &ib)) {
return make_const(op->type, ia ^ ib);
} else if (const_uint(a, &ua) &&
const_uint(b, &ub)) {
return make_const(op->type, ua ^ ub);
} else if (a.same_as(op->args[0]) && b.same_as(op->args[1])) {
return op;
} else {
return a ^ b;
}
} else if (op->is_intrinsic(Call::abs)) {
// Constant evaluate abs(x).
ExprInfo a_bounds;
Expr a = mutate(op->args[0], &a_bounds);
Expr unbroadcast = lift_elementwise_broadcasts(op->type, op->name, {a}, op->call_type);
if (unbroadcast.defined()) {
return mutate(unbroadcast, bounds);
}
Type ta = a.type();
int64_t ia = 0;
double fa = 0;
if (ta.is_int() && const_int(a, &ia)) {
if (ia < 0 && !(Int(64).is_min(ia))) {
ia = -ia;
}
return make_const(op->type, ia);
} else if (ta.is_uint()) {
// abs(uint) is a no-op.
return a;
} else if (const_float(a, &fa)) {
if (fa < 0) {
fa = -fa;
}
return make_const(a.type(), fa);
} else if (a.type().is_int() && a_bounds.min_defined && a_bounds.min >= 0) {
return cast(op->type, a);
} else if (a.type().is_int() && a_bounds.max_defined && a_bounds.max <= 0) {
return cast(op->type, -a);
} else if (a.same_as(op->args[0])) {
return op;
} else {
return abs(a);
}
} else if (op->is_intrinsic(Call::absd)) {
// Constant evaluate absd(a, b).
ExprInfo a_bounds, b_bounds;
Expr a = mutate(op->args[0], &a_bounds);
Expr b = mutate(op->args[1], &b_bounds);
Expr unbroadcast = lift_elementwise_broadcasts(op->type, op->name, {a, b}, op->call_type);
if (unbroadcast.defined()) {
return mutate(unbroadcast, bounds);
}
Type ta = a.type();
// absd() should enforce identical types for a and b when the node is created
internal_assert(ta == b.type());
int64_t ia = 0, ib = 0;
uint64_t ua = 0, ub = 0;
double fa = 0, fb = 0;
if (ta.is_int() && const_int(a, &ia) && const_int(b, &ib)) {
// Note that absd(int, int) always produces a uint result
internal_assert(op->type.is_uint());
const uint64_t d = ia > ib ? (uint64_t)(ia - ib) : (uint64_t)(ib - ia);
return make_const(op->type, d);
} else if (ta.is_uint() && const_uint(a, &ua) && const_uint(b, &ub)) {
const uint64_t d = ua > ub ? ua - ub : ub - ua;
return make_const(op->type, d);
} else if (const_float(a, &fa) && const_float(b, &fb)) {
const double d = fa > fb ? fa - fb : fb - fa;
return make_const(op->type, d);
} else if (a.same_as(op->args[0]) && b.same_as(op->args[1])) {
return op;
} else {
return absd(a, b);
}
} else if (op->is_intrinsic(Call::saturating_cast)) {
internal_assert(op->args.size() == 1);
ExprInfo a_bounds;
Expr a = mutate(op->args[0], &a_bounds);
// TODO(rootjalex): We could be intelligent about using a_bounds to remove saturating_casts;
if (is_const(a)) {
a = lower_saturating_cast(op->type, a);
return mutate(a, bounds);
} else if (!a.same_as(op->args[0])) {
return saturating_cast(op->type, a);
} else {
return op;
}
} else if (op->is_intrinsic(Call::stringify)) {
// Eagerly concat constant arguments to a stringify.
bool changed = false;
vector<Expr> new_args;
const StringImm *last = nullptr;
for (const auto &a : op->args) {
Expr arg = mutate(a, nullptr);
if (!arg.same_as(a)) {
changed = true;
}
const StringImm *string_imm = arg.as<StringImm>();
const IntImm *int_imm = arg.as<IntImm>();
const FloatImm *float_imm = arg.as<FloatImm>();
// We use snprintf here rather than stringstreams,
// because the runtime's float printing is guaranteed
// to match snprintf.
char buf[64]; // Large enough to hold the biggest float literal.
if (last && string_imm) {
new_args.back() = last->value + string_imm->value;
changed = true;
} else if (int_imm) {
snprintf(buf, sizeof(buf), "%lld", (long long)int_imm->value);
if (last) {
new_args.back() = last->value + buf;
} else {
new_args.emplace_back(string(buf));
}
changed = true;
} else if (last && float_imm) {
snprintf(buf, sizeof(buf), "%f", float_imm->value);
if (last) {
new_args.back() = last->value + buf;
} else {
new_args.emplace_back(string(buf));
}
changed = true;
} else {
new_args.push_back(arg);
}
last = new_args.back().as<StringImm>();
}
if (new_args.size() == 1 && new_args[0].as<StringImm>()) {
// stringify of a string constant is just the string constant
return new_args[0];
} else if (changed) {
return Call::make(op->type, op->name, new_args, op->call_type);
} else {
return op;
}
} else if (op->is_intrinsic(Call::prefetch)) {
// Collapse the prefetched region into lower dimension whenever is possible.
// TODO(psuriana): Deal with negative strides and overlaps.
internal_assert(op->args.size() % 2 == 0); // Prefetch: {base, offset, extent0, stride0, ...}
auto [args, changed] = mutate_with_changes(op->args, nullptr);
// The {extent, stride} args in the prefetch call are sorted
// based on the storage dimension in ascending order (i.e. innermost
// first and outermost last), so, it is enough to check for the upper
// triangular pairs to see if any contiguous addresses exist.
for (size_t i = 2; i < args.size(); i += 2) {
Expr extent_0 = args[i];
Expr stride_0 = args[i + 1];
for (size_t j = i + 2; j < args.size(); j += 2) {
Expr extent_1 = args[j];
Expr stride_1 = args[j + 1];
if (is_const_one(mutate(extent_0 * stride_0 == stride_1, nullptr))) {
Expr new_extent = mutate(extent_0 * extent_1, nullptr);
args.erase(args.begin() + j, args.begin() + j + 2);
args[i] = new_extent;
args[i + 1] = stride_0;
i -= 2;
break;
}
}
}
internal_assert(args.size() <= op->args.size());
if (changed || (args.size() != op->args.size())) {
return Call::make(op->type, Call::prefetch, args, Call::Intrinsic);
} else {
return op;
}
} else if (op->is_intrinsic(Call::require)) {
Expr cond = mutate(op->args[0], nullptr);
// likely(const-bool) is deliberately not reduced
// by the simplify(), but for our purposes here, we want
// to ignore the likely() wrapper. (Note that this is
// equivalent to calling can_prove() without needing to
// create a new Simplifier instance.)
if (const Call *c = cond.as<Call>()) {
if (c->is_intrinsic(Call::likely)) {
cond = c->args[0];
}
}
if (is_const_zero(cond)) {
// (We could simplify this to avoid evaluating the provably-false
// expression, but since this is a degenerate condition, don't bother.)
user_warning << "This pipeline is guaranteed to fail a require() expression at runtime: \n"
<< Expr(op) << "\n";
}
Expr result;
{
// Can assume the condition is true when evaluating the value.
auto t = scoped_truth(cond);
result = mutate(op->args[1], bounds);
}
if (is_const_one(cond)) {
return result;
}
Expr message = mutate(op->args[2], nullptr);
if (cond.same_as(op->args[0]) &&
result.same_as(op->args[1]) &&
message.same_as(op->args[2])) {
return op;
} else {
return Internal::Call::make(op->type,
Internal::Call::require,
{std::move(cond), std::move(result), std::move(message)},
Internal::Call::PureIntrinsic);
}
} else if (op->is_intrinsic(Call::promise_clamped) ||
op->is_intrinsic(Call::unsafe_promise_clamped)) {
// If the simplifier can infer that the clamp is unnecessary,
// we should be good to discard the promise.
internal_assert(op->args.size() == 3);
ExprInfo arg_info, lower_info, upper_info;
Expr arg = mutate(op->args[0], &arg_info);
Expr lower = mutate(op->args[1], &lower_info);
Expr upper = mutate(op->args[2], &upper_info);
const Broadcast *b_arg = arg.as<Broadcast>();
const Broadcast *b_lower = lower.as<Broadcast>();
const Broadcast *b_upper = upper.as<Broadcast>();
if (arg_info.min_defined &&
arg_info.max_defined &&
lower_info.max_defined &&
upper_info.min_defined &&
arg_info.min >= lower_info.max &&
arg_info.max <= upper_info.min) {
return arg;
} else if (b_arg && b_lower && b_upper) {
// Move broadcasts outwards
return Broadcast::make(Call::make(b_arg->value.type(), op->name,
{b_arg->value, b_lower->value, b_upper->value},
Call::Intrinsic),
b_arg->lanes);
} else if (arg.same_as(op->args[0]) &&
lower.same_as(op->args[1]) &&
upper.same_as(op->args[2])) {
return op;
} else {
return Call::make(op->type, op->name,
{arg, lower, upper},
Call::Intrinsic);
}
} else if (Call::as_tag(op)) {
// The bounds of the result are the bounds of the arg
internal_assert(op->args.size() == 1);
Expr arg = mutate(op->args[0], bounds);
if (arg.same_as(op->args[0])) {
return op;
} else {
return Call::make(op->type, op->name, {arg}, op->call_type);
}
} else if (op->is_intrinsic(Call::if_then_else)) {
// Note that this call promises to evaluate exactly one of the conditions,
// so this optimization should be safe.
internal_assert(op->args.size() == 2 || op->args.size() == 3);
Expr cond_value = mutate(op->args[0], nullptr);
// Ignore tags for our purposes here
Expr cond = unwrap_tags(cond_value);
if (in_unreachable) {
return op;
}
if (is_const_one(cond)) {
return mutate(op->args[1], bounds);
} else if (is_const_zero(cond)) {
if (op->args.size() == 3) {
return mutate(op->args[2], bounds);
} else {
return mutate(make_zero(op->type), bounds);
}
} else {
Expr true_value = mutate(op->args[1], nullptr);
bool true_unreachable = in_unreachable;
in_unreachable = false;
Expr false_value = op->args.size() == 3 ? mutate(op->args[2], nullptr) : Expr();
bool false_unreachable = in_unreachable;
if (true_unreachable && false_unreachable) {
return true_value;
}
in_unreachable = false;
if (true_unreachable) {
return false_value;
} else if (false_unreachable) {
return true_value;
}
if (cond_value.same_as(op->args[0]) &&
true_value.same_as(op->args[1]) &&
(op->args.size() == 2 || false_value.same_as(op->args[2]))) {
return op;
} else {
vector<Expr> args = {std::move(cond_value), std::move(true_value)};
if (op->args.size() == 3) {
args.push_back(std::move(false_value));
}
return Internal::Call::make(op->type, Call::if_then_else, args, op->call_type);
}
}
} else if (op->is_intrinsic(Call::mux)) {
internal_assert(op->args.size() >= 2);
int num_values = (int)op->args.size() - 1;
if (num_values == 1) {
// Mux of a single value
return mutate(op->args[1], bounds);
}
ExprInfo index_info;
Expr index = mutate(op->args[0], &index_info);
// Check if the mux has statically resolved
if (index_info.min_defined &&
index_info.max_defined &&
index_info.min == index_info.max) {
if (index_info.min >= 0 && index_info.min < num_values) {
// In-range, return the (simplified) corresponding value.
return mutate(op->args[index_info.min + 1], bounds);
} else {
// It's out-of-range, so return the last value.
return mutate(op->args.back(), bounds);
}
}
// The logic above could be extended to also truncate the
// range of values in the case where the mux index has a
// constant bound. This seems unlikely to ever come up though.
bool unchanged = index.same_as(op->args[0]);
vector<Expr> mutated_args(op->args.size());
mutated_args[0] = index;
for (size_t i = 1; i < op->args.size(); ++i) {
mutated_args[i] = mutate(op->args[i], nullptr);
unchanged &= mutated_args[i].same_as(op->args[i]);
}
if (unchanged) {
return op;
} else {
return Call::make(op->type, Call::mux, mutated_args, Call::PureIntrinsic);
}
} else if (op->call_type == Call::PureExtern || op->call_type == Call::PureIntrinsic) {
// TODO: This could probably be simplified into a single map-lookup
// with a bit more cleverness; not sure if the reduced lookup time
// would pay for itself (in comparison with the possible lost code clarity).
// Handle all the PureExtern cases of float -> bool
{
using FnType = bool (*)(double);
// Some GCC versions are unable to resolve std::isnan (etc) directly, so
// wrap them in lambdas.
const FnType is_finite = [](double a) -> bool { return std::isfinite(a); };
const FnType is_inf = [](double a) -> bool { return std::isinf(a); };
const FnType is_nan = [](double a) -> bool { return std::isnan(a); };
static const std::unordered_map<std::string, FnType>
pure_externs_f1b = {
{"is_finite_f16", is_finite},
{"is_finite_f32", is_finite},
{"is_finite_f64", is_finite},
{"is_inf_f16", is_inf},
{"is_inf_f32", is_inf},
{"is_inf_f64", is_inf},
{"is_nan_f16", is_nan},
{"is_nan_f32", is_nan},
{"is_nan_f64", is_nan},
};
auto it = pure_externs_f1b.find(op->name);
if (it != pure_externs_f1b.end()) {
Expr arg = mutate(op->args[0], nullptr);
double f = 0.0;
if (const_float(arg, &f)) {
auto fn = it->second;
return make_bool(fn(f));
} else if (arg.same_as(op->args[0])) {
return op;
} else {
return Call::make(op->type, op->name, {arg}, op->call_type);
}
}
// else fall thru
}
// Handle all the PureExtern cases of float -> float
// TODO: should we handle the f16 and f64 cases here? (We never did before.)
// TODO: should we handle fast_inverse and/or fast_inverse_sqrt here?
{
using FnType = double (*)(double);
static const std::unordered_map<std::string, FnType>
pure_externs_f1 = {
{"acos_f32", std::acos},
{"acosh_f32", std::acosh},
{"asin_f32", std::asin},
{"asinh_f32", std::asinh},
{"atan_f32", std::atan},
{"atanh_f32", std::atanh},
{"cos_f32", std::cos},
{"cosh_f32", std::cosh},
{"exp_f32", std::exp},
{"log_f32", std::log},
{"sin_f32", std::sin},
{"sinh_f32", std::sinh},
{"sqrt_f32", std::sqrt},
{"tan_f32", std::tan},
{"tanh_f32", std::tanh},
};
auto it = pure_externs_f1.find(op->name);
if (it != pure_externs_f1.end()) {
Expr arg = mutate(op->args[0], nullptr);
if (const double *f = as_const_float(arg)) {
auto fn = it->second;
return make_const(arg.type(), fn(*f));
} else if (arg.same_as(op->args[0])) {
return op;
} else {
return Call::make(op->type, op->name, {arg}, op->call_type);
}
}
// else fall thru
}
// Handle all the PureExtern/PureIntrinsic cases of float -> integerized-float
{
using FnType = double (*)(double);
static const std::unordered_map<std::string, FnType>
pure_externs_truncation = {
{"ceil_f32", std::ceil},
{"floor_f32", std::floor},
{Call::get_intrinsic_name(Call::round), [](double a) -> double {
std::fesetround(FE_TONEAREST);
a = std::nearbyint(a);
return a;
}},
{"trunc_f32", std::trunc},
};
auto it = pure_externs_truncation.find(op->name);
if (it != pure_externs_truncation.end()) {
internal_assert(op->args.size() == 1);
Expr arg = mutate(op->args[0], nullptr);
const Call *call = arg.as<Call>();
if (const double *f = as_const_float(arg)) {
auto fn = it->second;
return make_const(arg.type(), fn(*f));
} else if (call && (call->call_type == Call::PureExtern || call->call_type == Call::PureIntrinsic) &&
(it = pure_externs_truncation.find(call->name)) != pure_externs_truncation.end()) {
// For any combination of these integer-valued functions, we can
// discard the outer function. For example, floor(ceil(x)) == ceil(x).
return call;
} else if (!arg.same_as(op->args[0])) {
return Call::make(op->type, op->name, {arg}, op->call_type);
} else {
return op;
}
}
// else fall thru
}
// Handle all the PureExtern cases of (float, float) -> integerized-float
{
using FnType = double (*)(double, double);
static const std::unordered_map<std::string, FnType>
pure_externs_f2 = {
{"atan2_f32", std::atan2},
{"pow_f32", std::pow},
};
auto it = pure_externs_f2.find(op->name);
if (it != pure_externs_f2.end()) {
Expr arg0 = mutate(op->args[0], nullptr);
Expr arg1 = mutate(op->args[1], nullptr);
const double *f0 = as_const_float(arg0);
const double *f1 = as_const_float(arg1);
if (f0 && f1) {
auto fn = it->second;
return make_const(arg0.type(), fn(*f0, *f1));
} else if (!arg0.same_as(op->args[0]) || !arg1.same_as(op->args[1])) {
return Call::make(op->type, op->name, {arg0, arg1}, op->call_type);
} else {
return op;
}
}
// else fall thru
}
// There are other PureExterns we don't bother with (e.g. fast_inverse_f32)...
// just fall thru and take the general case.
debug(2) << "Simplifier: unhandled PureExtern: " << op->name;
} else if (op->is_intrinsic(Call::signed_integer_overflow)) {
clear_bounds_info(bounds);
} else if (op->is_intrinsic(Call::concat_bits) && op->args.size() == 1) {
return mutate(op->args[0], bounds);
}
// No else: we want to fall thru from the PureExtern clause.
{
auto [new_args, changed] = mutate_with_changes(op->args, nullptr);
if (!changed) {
return op;
} else {
return Call::make(op->type, op->name, new_args, op->call_type,
op->func, op->value_index, op->image, op->param);
}
}
}
} // namespace Internal
} // namespace Halide
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
C/C++
1
https://gitee.com/VisionDeveloper/Halide.git
git@gitee.com:VisionDeveloper/Halide.git
VisionDeveloper
Halide
Halide
main

搜索帮助