pytorch/torch/csrc/jit/python/python_ivalue.h
Edward Z. Yang 9465c0e0b5 Add a lint rule for torch/csrc/util/pybind.h include (#82552)
We define specializations for pybind11 defined templates
(in particular, PYBIND11_DECLARE_HOLDER_TYPE) and consequently
it is important that these specializations *always* be #include'd
when making use of pybind11 templates whose behavior depends on
these specializations, otherwise we can cause an ODR violation.

The easiest way to ensure that all the specializations are always
loaded is to designate a header (in this case, torch/csrc/util/pybind.h)
that ensures the specializations are defined, and then add a lint
to ensure this header is included whenever pybind11 headers are
included.

The existing grep linter didn't have enough knobs to do this
conveniently, so I added some features.  I'm open to suggestions
for how to structure the features better.  The main changes:

- Added an --allowlist-pattern flag, which turns off the grep lint
  if some other line exists.  This is used to stop the grep
  lint from complaining about pybind11 includes if the util
  include already exists.

- Added --match-first-only flag, which lets grep only match against
  the first matching line.  This is because, even if there are multiple
  includes that are problematic, I only need to fix one of them.
  We don't /really/ need this, but when I was running lintrunner -a
  to fixup the preexisting codebase it was annoying without this,
  as the lintrunner overall driver fails if there are multiple edits
  on the same file.

I excluded any files that didn't otherwise have a dependency on
torch/ATen, this was mostly caffe2 and the valgrind wrapper compat
bindings.

Note the grep replacement is kind of crappy, but clang-tidy lint
cleaned it up in most cases.

See also https://github.com/pybind/pybind11/issues/4099

Signed-off-by: Edward Z. Yang <ezyang@fb.com>
Pull Request resolved: https://github.com/pytorch/pytorch/pull/82552
Approved by: https://github.com/albanD
2022-08-01 17:16:58 +00:00

100 lines
3.2 KiB
C++

#pragma once
#include <ATen/core/ivalue.h>
#include <pybind11/pybind11.h>
#include <torch/csrc/jit/python/pybind_utils.h>
#include <torch/csrc/python_headers.h>
#include <torch/csrc/utils/pybind.h>
namespace py = pybind11;
namespace c10 {
namespace ivalue {
// concrete ivalue Holder that hold a py::object
struct C10_EXPORT ConcretePyObjectHolder final : PyObjectHolder {
public:
static c10::intrusive_ptr<PyObjectHolder> create(py::object py_obj) {
return c10::make_intrusive<ConcretePyObjectHolder>(std::move(py_obj));
}
static c10::intrusive_ptr<PyObjectHolder> create(const py::handle& handle) {
py::gil_scoped_acquire ag;
return c10::make_intrusive<ConcretePyObjectHolder>(
handle.cast<py::object>());
}
PyObject* getPyObject() override {
return py_obj_.ptr();
}
InferredType tryToInferType() override {
pybind11::gil_scoped_acquire ag;
return torch::jit::tryToInferType(py_obj_);
}
IValue toIValue(const TypePtr& type, c10::optional<int32_t> N = c10::nullopt)
override {
pybind11::gil_scoped_acquire ag;
return torch::jit::toIValue(py_obj_, type, N);
}
std::string toStr() override {
pybind11::gil_scoped_acquire ag;
return py::str(py_obj_);
}
std::vector<at::Tensor> extractTensors() override {
// We could implement this entirely in C++ via pybind11 but it turns out to
// be substantially slower. Namely, the total time taken by markCompleted on
// a CUDAFuture is 21.5us with this implementation, but goes up to 58.7us
// when using C++. The reason is unclear.
try {
pybind11::gil_scoped_acquire ag;
static py::object& extractorFn = *new py::object(
py::module::import("torch._jit_internal").attr("_extract_tensors"));
return extractorFn(py_obj_).cast<std::vector<at::Tensor>>();
} catch (py::error_already_set& e) {
auto err = std::runtime_error(
c10::str("Cannot extract tensors from value: ", e.what()));
{
pybind11::gil_scoped_acquire ag;
e.restore();
PyErr_Clear();
}
throw err;
}
}
// Note [Destructing py::object]
// ~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// (1) Why py_obj_ = py::none(); does not work. Because we also need to
// acquire GIL when destructing py::object of None that de-references None.
// https://docs.python.org/3/c-api/none.html#c.Py_RETURN_NONE
//
// https://stackoverflow.com/questions/15287590/why-should-py-increfpy-none-be-required-before-returning-py-none-in-c
//
// (2) Why we need to call dec_ref() explicitly. Because py::object of
// nullptr, on destruction, effectively does nothing because of it calls
// Py_XDECREF(NULL) underlying.
// https://docs.python.org/3/c-api/refcounting.html#c.Py_XDECREF
~ConcretePyObjectHolder() override {
pybind11::gil_scoped_acquire ag;
py_obj_.dec_ref();
// explicitly setting PyObject* to nullptr to prevent py::object's dtor to
// decref on the PyObject again.
py_obj_.ptr() = nullptr;
}
// explicit construction to avoid errornous implicit conversion and
// copy-initialization
explicit ConcretePyObjectHolder(py::object py_obj)
: py_obj_(std::move(py_obj)) {}
private:
py::object py_obj_;
};
} // namespace ivalue
} // namespace c10