290 lines
9.2 KiB
C
290 lines
9.2 KiB
C
|
/* Rich information on why an optimization wasn't possible.
|
||
|
Copyright (C) 2018-2023 Free Software Foundation, Inc.
|
||
|
Contributed by David Malcolm <dmalcolm@redhat.com>.
|
||
|
|
||
|
This file is part of GCC.
|
||
|
|
||
|
GCC is free software; you can redistribute it and/or modify it under
|
||
|
the terms of the GNU General Public License as published by the Free
|
||
|
Software Foundation; either version 3, or (at your option) any later
|
||
|
version.
|
||
|
|
||
|
GCC is distributed in the hope that it will be useful, but WITHOUT ANY
|
||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||
|
for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with GCC; see the file COPYING3. If not see
|
||
|
<http://www.gnu.org/licenses/>. */
|
||
|
|
||
|
#ifndef GCC_OPT_PROBLEM_H
|
||
|
#define GCC_OPT_PROBLEM_H
|
||
|
|
||
|
#include "diagnostic-core.h" /* for ATTRIBUTE_GCC_DIAG. */
|
||
|
#include "optinfo.h" /* for optinfo. */
|
||
|
|
||
|
/* This header declares a family of wrapper classes for tracking a
|
||
|
success/failure value, while optionally supporting propagating an
|
||
|
opt_problem * describing any failure back up the call stack.
|
||
|
|
||
|
For instance, at the deepest point of the callstack where the failure
|
||
|
happens, rather than:
|
||
|
|
||
|
if (!check_something ())
|
||
|
{
|
||
|
if (dump_enabled_p ())
|
||
|
dump_printf_loc (MSG_MISSED_OPTIMIZATION, vect_location,
|
||
|
"foo is unsupported.\n");
|
||
|
return false;
|
||
|
}
|
||
|
// [...more checks...]
|
||
|
|
||
|
// All checks passed:
|
||
|
return true;
|
||
|
|
||
|
we can capture the cause of the failure via:
|
||
|
|
||
|
if (!check_something ())
|
||
|
return opt_result::failure_at (stmt, "foo is unsupported");
|
||
|
// [...more checks...]
|
||
|
|
||
|
// All checks passed:
|
||
|
return opt_result::success ();
|
||
|
|
||
|
which effectively returns true or false, whilst recording any problem.
|
||
|
|
||
|
opt_result::success and opt_result::failure return opt_result values
|
||
|
which "looks like" true/false respectively, via operator bool().
|
||
|
If dump_enabled_p, then opt_result::failure also creates an opt_problem *,
|
||
|
capturing the pertinent data (here, "foo is unsupported " and "stmt").
|
||
|
If dumps are disabled, then opt_problem instances aren't
|
||
|
created, and it's equivalent to just returning a bool.
|
||
|
|
||
|
The opt_problem can be propagated via opt_result values back up
|
||
|
the call stack to where it makes most sense to the user.
|
||
|
For instance, rather than:
|
||
|
|
||
|
bool ok = try_something_that_might_fail ();
|
||
|
if (!ok)
|
||
|
{
|
||
|
if (dump_enabled_p ())
|
||
|
dump_printf_loc (MSG_MISSED_OPTIMIZATION, vect_location,
|
||
|
"some message.\n");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
we can replace the bool with an opt_result, so if dump_enabled_p, we
|
||
|
assume that if try_something_that_might_fail, an opt_problem * will be
|
||
|
created, and we can propagate it up the call chain:
|
||
|
|
||
|
opt_result ok = try_something_that_might_fail ();
|
||
|
if (!ok)
|
||
|
{
|
||
|
if (dump_enabled_p ())
|
||
|
dump_printf_loc (MSG_MISSED_OPTIMIZATION, vect_location,
|
||
|
"some message.\n");
|
||
|
return ok; // propagating the opt_result
|
||
|
}
|
||
|
|
||
|
opt_result is an opt_wrapper<bool>, where opt_wrapper<T> is a base
|
||
|
class for wrapping a T, optionally propagating an opt_problem in
|
||
|
case of failure_at (when dumps are enabled). Similarly,
|
||
|
opt_pointer_wrapper<T> can be used to wrap pointer types (where non-NULL
|
||
|
signifies success, NULL signifies failure).
|
||
|
|
||
|
In all cases, opt_wrapper<T> acts as if the opt_problem were one of its
|
||
|
fields, but the opt_problem is actually stored in a global, so that when
|
||
|
compiled, an opt_wrapper<T> is effectively just a T, so that we're
|
||
|
still just passing e.g. a bool around; the opt_wrapper<T> classes
|
||
|
simply provide type-checking and an API to ensure that we provide
|
||
|
error-messages deep in the callstack at the places where problems
|
||
|
occur, and that we propagate them. This also avoids having
|
||
|
to manage the ownership of the opt_problem instances.
|
||
|
|
||
|
Using opt_result and opt_wrapper<T> documents the intent of the code
|
||
|
for the places where we represent success values, and allows the C++ type
|
||
|
system to track where the deepest points in the callstack are where we
|
||
|
need to emit the failure messages from. */
|
||
|
|
||
|
/* A bundle of information about why an optimization failed (e.g.
|
||
|
vectorization), and the location in both the user's code and
|
||
|
in GCC itself where the problem occurred.
|
||
|
|
||
|
Instances are created by static member functions in opt_wrapper
|
||
|
subclasses, such as opt_result::failure.
|
||
|
|
||
|
Instances are only created when dump_enabled_p (). */
|
||
|
|
||
|
class opt_problem
|
||
|
{
|
||
|
public:
|
||
|
static opt_problem *get_singleton () { return s_the_problem; }
|
||
|
|
||
|
opt_problem (const dump_location_t &loc,
|
||
|
const char *fmt, va_list *ap)
|
||
|
ATTRIBUTE_GCC_DUMP_PRINTF (3, 0);
|
||
|
|
||
|
const dump_location_t &
|
||
|
get_dump_location () const { return m_optinfo.get_dump_location (); }
|
||
|
|
||
|
const optinfo & get_optinfo () const { return m_optinfo; }
|
||
|
|
||
|
void emit_and_clear ();
|
||
|
|
||
|
private:
|
||
|
optinfo m_optinfo;
|
||
|
|
||
|
static opt_problem *s_the_problem;
|
||
|
};
|
||
|
|
||
|
/* A base class for wrapper classes that track a success/failure value, while
|
||
|
optionally supporting propagating an opt_problem * describing any
|
||
|
failure back up the call stack. */
|
||
|
|
||
|
template <typename T>
|
||
|
class opt_wrapper
|
||
|
{
|
||
|
public:
|
||
|
typedef T wrapped_t;
|
||
|
|
||
|
/* Be accessible as the wrapped type. */
|
||
|
operator wrapped_t () const { return m_result; }
|
||
|
|
||
|
/* No public ctor. */
|
||
|
|
||
|
wrapped_t get_result () const { return m_result; }
|
||
|
opt_problem *get_problem () const { return opt_problem::get_singleton (); }
|
||
|
|
||
|
protected:
|
||
|
opt_wrapper (wrapped_t result, opt_problem */*problem*/)
|
||
|
: m_result (result)
|
||
|
{
|
||
|
/* "problem" is ignored: although it looks like a field, we
|
||
|
actually just use the opt_problem singleton, so that
|
||
|
opt_wrapper<T> in memory is just a T. */
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
wrapped_t m_result;
|
||
|
};
|
||
|
|
||
|
/* Subclass of opt_wrapper<T> for bool, where
|
||
|
- true signifies "success", and
|
||
|
- false signifies "failure"
|
||
|
whilst effectively propagating an opt_problem * describing any failure
|
||
|
back up the call stack. */
|
||
|
|
||
|
class opt_result : public opt_wrapper <bool>
|
||
|
{
|
||
|
public:
|
||
|
/* Generate a "success" value: a wrapper around "true". */
|
||
|
|
||
|
static opt_result success () { return opt_result (true, NULL); }
|
||
|
|
||
|
/* Generate a "failure" value: a wrapper around "false", and,
|
||
|
if dump_enabled_p, an opt_problem. */
|
||
|
|
||
|
static opt_result failure_at (const dump_location_t &loc,
|
||
|
const char *fmt, ...)
|
||
|
ATTRIBUTE_GCC_DUMP_PRINTF (2, 3)
|
||
|
{
|
||
|
opt_problem *problem = NULL;
|
||
|
if (dump_enabled_p ())
|
||
|
{
|
||
|
va_list ap;
|
||
|
va_start (ap, fmt);
|
||
|
problem = new opt_problem (loc, fmt, &ap);
|
||
|
va_end (ap);
|
||
|
}
|
||
|
return opt_result (false, problem);
|
||
|
}
|
||
|
|
||
|
/* Given a failure wrapper of some other kind, make an opt_result failure
|
||
|
object, for propagating the opt_problem up the call stack. */
|
||
|
|
||
|
template <typename S>
|
||
|
static opt_result
|
||
|
propagate_failure (opt_wrapper <S> other)
|
||
|
{
|
||
|
return opt_result (false, other.get_problem ());
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
/* Private ctor. Instances should be created by the success and failure
|
||
|
static member functions. */
|
||
|
opt_result (wrapped_t result, opt_problem *problem)
|
||
|
: opt_wrapper <bool> (result, problem)
|
||
|
{}
|
||
|
};
|
||
|
|
||
|
/* Subclass of opt_wrapper<T> where T is a pointer type, for tracking
|
||
|
success/failure, where:
|
||
|
- a non-NULL value signifies "success", and
|
||
|
- a NULL value signifies "failure",
|
||
|
whilst effectively propagating an opt_problem * describing any failure
|
||
|
back up the call stack. */
|
||
|
|
||
|
template <typename PtrType_t>
|
||
|
class opt_pointer_wrapper : public opt_wrapper <PtrType_t>
|
||
|
{
|
||
|
public:
|
||
|
typedef PtrType_t wrapped_pointer_t;
|
||
|
|
||
|
/* Given a non-NULL pointer, make a success object wrapping it. */
|
||
|
|
||
|
static opt_pointer_wrapper <wrapped_pointer_t>
|
||
|
success (wrapped_pointer_t ptr)
|
||
|
{
|
||
|
return opt_pointer_wrapper <wrapped_pointer_t> (ptr, NULL);
|
||
|
}
|
||
|
|
||
|
/* Make a NULL pointer failure object, with the given message
|
||
|
(if dump_enabled_p). */
|
||
|
|
||
|
static opt_pointer_wrapper <wrapped_pointer_t>
|
||
|
failure_at (const dump_location_t &loc,
|
||
|
const char *fmt, ...)
|
||
|
ATTRIBUTE_GCC_DUMP_PRINTF (2, 3)
|
||
|
{
|
||
|
opt_problem *problem = NULL;
|
||
|
if (dump_enabled_p ())
|
||
|
{
|
||
|
va_list ap;
|
||
|
va_start (ap, fmt);
|
||
|
problem = new opt_problem (loc, fmt, &ap);
|
||
|
va_end (ap);
|
||
|
}
|
||
|
return opt_pointer_wrapper <wrapped_pointer_t> (NULL, problem);
|
||
|
}
|
||
|
|
||
|
/* Given a failure wrapper of some other kind, make a NULL pointer
|
||
|
failure object, propagating the problem. */
|
||
|
|
||
|
template <typename S>
|
||
|
static opt_pointer_wrapper <wrapped_pointer_t>
|
||
|
propagate_failure (opt_wrapper <S> other)
|
||
|
{
|
||
|
return opt_pointer_wrapper <wrapped_pointer_t> (NULL,
|
||
|
other.get_problem ());
|
||
|
}
|
||
|
|
||
|
/* Support accessing the underlying pointer via ->. */
|
||
|
|
||
|
wrapped_pointer_t operator-> () const { return this->get_result (); }
|
||
|
|
||
|
private:
|
||
|
/* Private ctor. Instances should be built using the static member
|
||
|
functions "success" and "failure". */
|
||
|
opt_pointer_wrapper (wrapped_pointer_t result, opt_problem *problem)
|
||
|
: opt_wrapper<PtrType_t> (result, problem)
|
||
|
{}
|
||
|
};
|
||
|
|
||
|
/* A typedef for wrapping "tree" so that NULL_TREE can carry an
|
||
|
opt_problem describing the failure (if dump_enabled_p). */
|
||
|
|
||
|
typedef opt_pointer_wrapper<tree> opt_tree;
|
||
|
|
||
|
#endif /* #ifndef GCC_OPT_PROBLEM_H */
|