diff --git a/include/pybind11/internal/builtins.h b/include/pybind11/internal/builtins.h new file mode 100644 index 00000000..2dadf0b1 --- /dev/null +++ b/include/pybind11/internal/builtins.h @@ -0,0 +1,117 @@ +#pragma once + +#include "types.h" + +namespace pybind11 { + inline void exec(const char* code, handle global = {}, handle local = {}) { + vm->py_exec(code, global.ptr(), local.ptr()); + } + + // wrapper for builtin functions in Python + inline bool hasattr(const handle& obj, const handle& name) { + auto& key = _builtin_cast(name); + return vm->getattr(obj.ptr(), key, false) != nullptr; + } + + inline bool hasattr(const handle& obj, const char* name) { + return vm->getattr(obj.ptr(), name, false) != nullptr; + } + + inline void delattr(const handle& obj, const handle& name) { + auto& key = _builtin_cast(name); + vm->delattr(obj.ptr(), key); + } + + inline void delattr(const handle& obj, const char* name) { vm->delattr(obj.ptr(), name); } + + inline object getattr(const handle& obj, const handle& name) { + auto& key = _builtin_cast(name); + return reinterpret_borrow(vm->getattr(obj.ptr(), key)); + } + + inline object getattr(const handle& obj, const char* name) { + return reinterpret_borrow(vm->getattr(obj.ptr(), name)); + } + + inline object getattr(const handle& obj, const handle& name, const handle& default_) { + if(!hasattr(obj, name)) { + return reinterpret_borrow(default_); + } + return getattr(obj, name); + } + + inline object getattr(const handle& obj, const char* name, const handle& default_) { + if(!hasattr(obj, name)) { + return reinterpret_borrow(default_); + } + return getattr(obj, name); + } + + inline void setattr(const handle& obj, const handle& name, const handle& value) { + auto& key = _builtin_cast(name); + vm->setattr(obj.ptr(), key, value.ptr()); + } + + inline void setattr(const handle& obj, const char* name, const handle& value) { + vm->setattr(obj.ptr(), name, value.ptr()); + } + + template + inline bool isinstance(const handle& obj) { + pkpy::Type cls = _builtin_cast(type::handle_of().ptr()); + return vm->isinstance(obj.ptr(), cls); + } + + template <> + inline bool isinstance(const handle&) = delete; + + template <> + inline bool isinstance(const handle& obj) { + return hasattr(obj, "__iter__"); + } + + template <> + inline bool isinstance(const handle& obj) { + return hasattr(obj, "__iter__") && hasattr(obj, "__next__"); + } + + inline bool isinstance(const handle& obj, const handle& type) { + return vm->isinstance(obj.ptr(), _builtin_cast(type)); + } + + inline int64_t hash(const handle& obj) { return vm->py_hash(obj.ptr()); } + + template + struct type_caster; + + template + handle _cast(T&& value, + return_value_policy policy = return_value_policy::automatic_reference, + handle parent = handle()) { + using U = std::remove_pointer_t>>; + return type_caster::cast(std::forward(value), policy, parent); + } + + template + object cast(T&& value, + return_value_policy policy = return_value_policy::automatic_reference, + handle parent = handle()) { + return reinterpret_borrow(_cast(std::forward(value), policy, parent)); + } + + template + T cast(handle obj, bool convert = false) { + using Caster = + type_caster>>>; + Caster caster; + + if(caster.load(obj, convert)) { + if constexpr(std::is_rvalue_reference_v) { + return std::move(caster.value); + } else { + return caster.value; + } + } + throw std::runtime_error("Unable to cast Python instance to C++ type"); + } +} // namespace pybind11 diff --git a/include/pybind11/internal/cast.h b/include/pybind11/internal/cast.h new file mode 100644 index 00000000..f6cdc320 --- /dev/null +++ b/include/pybind11/internal/cast.h @@ -0,0 +1,173 @@ +#pragma once + +#include "instance.h" +#include "builtins.h" +#include "type_traits.h" + +namespace pybind11 { + + using pkpy::is_floating_point_v; + using pkpy::is_integral_v; + + template + constexpr inline bool is_string_v = + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v; + + template + constexpr bool is_pyobject_v = std::is_base_of_v; + + template + struct type_caster; + + template <> + struct type_caster { + bool value; + + bool load(const handle& src, bool) { + if(isinstance(src)) { + value = pkpy::_py_cast(vm, src.ptr()); + return true; + } + + return false; + } + + static handle cast(bool src, return_value_policy, handle) { + return src ? vm->True : vm->False; + } + }; + + template + struct type_caster>> { + T value; + + bool load(const handle& src, bool convert) { + if(isinstance(src)) { + value = pkpy::_py_cast(vm, src.ptr()); + return true; + } + + return false; + } + + static handle cast(T src, return_value_policy, handle) { return pkpy::py_var(vm, src); } + }; + + template + struct type_caster>> { + T value; + + bool load(const handle& src, bool convert) { + if(isinstance(src)) { + value = pkpy::_py_cast(vm, src.ptr()); + return true; + } + + if(convert && isinstance(src)) { + value = pkpy::_py_cast(vm, src.ptr()); + return true; + } + + return false; + } + + static handle cast(T src, return_value_policy, handle) { return pkpy::py_var(vm, src); } + }; + + template + struct type_caster>> { + T value; + + bool load(const handle& src, bool) { + if(isinstance(src)) { + // FIXME: support other kinds of string + value = pkpy::_py_cast(vm, src.ptr()); + return true; + } + + return false; + } + + static handle cast(const std::string& src, return_value_policy, handle) { + return pkpy::py_var(vm, src); + } + }; + + template + struct type_caster>> { + T value; + + bool load(const handle& src, bool) { + if(isinstance(src)) { + value = reinterpret_borrow(src); + return true; + } + + return false; + } + + template + static handle cast(U&& src, return_value_policy, handle) { + return std::forward(src); + } + }; + + template + struct type_caster { + value_wrapper value; + + using underlying_type = std::remove_pointer_t; + + bool load(handle src, bool convert) { + if(isinstance(src)) { + auto& i = _builtin_cast(src); + value.pointer = &i.cast(); + return true; + } + + return false; + } + + template + static handle cast(U&& value, return_value_policy policy, const handle& parent = handle()) { + // TODO: support implicit cast + const auto& info = typeid(underlying_type); + bool existed = vm->_cxx_typeid_map.find(info) != vm->_cxx_typeid_map.end(); + if(existed) { + auto type = vm->_cxx_typeid_map[info]; + return instance::create(std::forward(value), type, policy, parent.ptr()); + } + vm->TypeError("type not registered"); + } + }; + + template + struct type_caster || std::is_reference_v>> { + using underlying = std::conditional_t, + std::remove_pointer_t, + std::remove_reference_t>; + + struct wrapper { + type_caster caster; + + operator T () { + if constexpr(std::is_pointer_v) { + return caster.value.pointer; + } else { + return caster.value; + } + } + }; + + wrapper value; + + bool load(const handle& src, bool convert) { return value.caster.load(src, convert); } + + template + static handle cast(U&& value, return_value_policy policy, const handle& parent) { + return type_caster::cast(std::forward(value), policy, parent); + } + }; +} // namespace pybind11 + diff --git a/include/pybind11/internal/class.h b/include/pybind11/internal/class.h new file mode 100644 index 00000000..6adb892c --- /dev/null +++ b/include/pybind11/internal/class.h @@ -0,0 +1,184 @@ +#pragma once + +#include "cpp_function.h" + +namespace pybind11 { + + class module : public object { + + public: + using object::object; + + static module import(const char* name) { + if(name == std::string_view{"__main__"}) { + return module{vm->_main, true}; + } else { + return module{vm->py_import(name, false), true}; + } + } + }; + + // TODO: + // 1. inheritance + // 2. virtual function + // 3. factory function + + template + class class_ : public type { + public: + using type::type; + + template + class_(const handle& scope, const char* name, Args&&... args) : + type(vm->new_type_object(scope.ptr(), + name, + vm->tp_object, + false, + pkpy::PyTypeInfo::Vt::get()), + true) { + pkpy::PyVar mod = scope.ptr(); + mod->attr().set(name, m_ptr); + vm->_cxx_typeid_map[typeid(T)] = _builtin_cast(m_ptr); + vm->bind_func(m_ptr, "__new__", -1, [](pkpy::VM* vm, pkpy::ArgsView args) { + auto cls = _builtin_cast(args[0]); + return instance::create(cls); + }); + } + + /// bind constructor + template + class_& def(init, const Extra&... extra) { + if constexpr(!std::is_constructible_v) { + static_assert(std::is_constructible_v, "Invalid constructor arguments"); + } else { + bind_function( + *this, + "__init__", + [](T* self, Args... args) { new (self) T(args...); }, + pkpy::BindType::DEFAULT, + extra...); + return *this; + } + } + + /// bind member function + template + class_& def(const char* name, Fn&& f, const Extra&... extra) { + using first = std::tuple_element_t<0, callable_args_t>>; + constexpr bool is_first_base_of_v = + std::is_reference_v && std::is_base_of_v>; + + if constexpr(!is_first_base_of_v) { + static_assert( + is_first_base_of_v, + "If you want to bind member function, the first argument must be the base class"); + } else { + bind_function(*this, name, std::forward(f), pkpy::BindType::DEFAULT, extra...); + } + + return *this; + } + + /// bind operators + template + class_& def(Operator op, const Extras&... extras) { + op.execute(*this, extras...); + return *this; + } + + // TODO: factory function + + /// bind static function + template + class_& def_static(const char* name, Fn&& f, const Extra&... extra) { + bind_function(*this, name, std::forward(f), pkpy::BindType::STATICMETHOD, extra...); + return *this; + } + + template + class_& def_readwrite(const char* name, MP mp, const Extras&... extras) { + if constexpr(!std::is_member_object_pointer_v) { + static_assert(std::is_member_object_pointer_v, + "def_readwrite only supports pointer to data member"); + } else { + bind_property(*this, name, mp, mp, extras...); + } + return *this; + } + + template + class_& def_readonly(const char* name, MP mp, const Extras&... extras) { + if constexpr(!std::is_member_object_pointer_v) { + static_assert(std::is_member_object_pointer_v, + "def_readonly only supports pointer to data member"); + } else { + bind_property(*this, name, mp, nullptr, extras...); + } + return *this; + } + + template + class_& def_property(const char* name, Getter&& g, Setter&& s, const Extras&... extras) { + bind_property(*this, name, std::forward(g), std::forward(s), extras...); + return *this; + } + + template + class_& def_property_readonly(const char* name, Getter&& mp, const Extras&... extras) { + bind_property(*this, name, std::forward(mp), nullptr, extras...); + return *this; + } + + template + class_& def_readwrite_static(const char* name, Var& mp, const Extras&... extras) { + static_assert( + dependent_false, + "define static properties requires metaclass. This is a complex feature with few use cases, so it may never be implemented."); + return *this; + } + + template + class_& def_readonly_static(const char* name, Var& mp, const Extras&... extras) { + static_assert( + dependent_false, + "define static properties requires metaclass. This is a complex feature with few use cases, so it may never be implemented."); + return *this; + } + + template + class_& + def_property_static(const char* name, Getter&& g, Setter&& s, const Extras&... extras) { + static_assert( + dependent_false, + "define static properties requires metaclass. This is a complex feature with few use cases, so it may never be implemented."); + return *this; + } + }; + + template + class enum_ : public class_ { + std::map m_values; + + public: + using class_::class_; + + template + enum_(const handle& scope, const char* name, Args&&... args) : + class_(scope, name, std::forward(args)...) {} + + enum_& value(const char* name, T value) { + handle var = type_caster::cast(value, return_value_policy::copy); + this->m_ptr->attr().set(name, var.ptr()); + m_values[name] = var.ptr(); + return *this; + } + + enum_& export_values() { + pkpy::PyVar mod = this->m_ptr->attr("__module__"); + for(auto& [name, value]: m_values) { + mod->attr().set(name, value); + } + return *this; + } + }; +} // namespace pybind11 diff --git a/include/pybind11/internal/cpp_function.h b/include/pybind11/internal/cpp_function.h new file mode 100644 index 00000000..2f073c42 --- /dev/null +++ b/include/pybind11/internal/cpp_function.h @@ -0,0 +1,383 @@ +#pragma once + +#include "cast.h" +#include + +namespace pybind11 { + + template + struct keep_alive {}; + + template + struct call_guard { + static_assert(std::is_default_constructible_v, + "call_guard must be default constructible"); + }; + + // append the overload to the beginning of the overload list + struct prepend {}; + + template + struct init {}; + + // TODO: support more customized tags + // struct kw_only {}; + // + // struct pos_only {}; + // + // struct default_arg {}; + // + // struct arg { + // const char* name; + // const char* description; + // }; + // + // struct default_arg { + // const char* name; + // const char* description; + // const char* value; + // }; + + template >, + typename IndexSequence = std::make_index_sequence>> + struct generator; + + class function_record { + union { + void* data; + char buffer[16]; + }; + + // TODO: optimize the function_record size to reduce memory usage + const char* name; + function_record* next; + void (*destructor)(function_record*); + return_value_policy policy = return_value_policy::automatic; + handle (*wrapper)(function_record&, pkpy::ArgsView, bool convert, handle parent); + + template + friend struct generator; + + public: + template + function_record(Fn&& f, const char* name, const Extras&... extras) : + name(name), next(nullptr) { + + if constexpr(sizeof(f) <= sizeof(buffer)) { + new (buffer) auto(std::forward(f)); + destructor = [](function_record* self) { + reinterpret_cast(self->buffer)->~Fn(); + }; + } else { + data = new auto(std::forward(f)); + destructor = [](function_record* self) { delete static_cast(self->data); }; + } + + using Generator = generator, std::tuple>; + Generator::initialize(*this, extras...); + wrapper = Generator::generate(); + } + + ~function_record() { destructor(this); } + + template + auto& cast() { + if constexpr(sizeof(Fn) <= sizeof(buffer)) { + return *reinterpret_cast(buffer); + } else { + return *static_cast(data); + } + } + + void append(function_record* record) { + function_record* p = this; + while(p->next != nullptr) { + p = p->next; + } + p->next = record; + } + + handle operator() (pkpy::ArgsView view) { + function_record* p = this; + // foreach function record and call the function with not convert + while(p != nullptr) { + handle result = p->wrapper(*this, view, false, {}); + if(result) { + return result; + } + p = p->next; + } + + p = this; + // foreach function record and call the function with convert + while(p != nullptr) { + handle result = p->wrapper(*this, view, true, {}); + if(result) { + return result; + } + p = p->next; + } + + vm->TypeError("no matching function found"); + } + }; + + template + handle invoke(Fn&& fn, + std::index_sequence, + std::tuple...>& casters, + return_value_policy policy, + handle parent) { + using underlying_type = std::decay_t; + using ret = callable_return_t; + + // if the return type is void, return None + if constexpr(std::is_void_v) { + // resolve the member function pointer + if constexpr(std::is_member_function_pointer_v) { + [&](class_type_t& self, auto&... args) { + (self.*fn)(args...); + }(std::get(casters).value...); + } else { + fn(std::get(casters).value...); + } + return vm->None; + } else { + // resolve the member function pointer + if constexpr(std::is_member_function_pointer_v>) { + return type_caster::cast( + [&](class_type_t& self, auto&... args) { + return (self.*fn)(args...); + }(std::get(casters).value...), + policy, + parent); + } else { + return type_caster::cast(fn(std::get(casters).value...), policy, parent); + } + } + } + + template + struct generator, std::tuple, std::index_sequence> { + static void initialize(function_record& record, const Extras&... extras) {} + + static auto generate() { + return +[](function_record& self, pkpy::ArgsView view, bool convert, handle parent) { + // FIXME: + // Temporarily, args and kwargs must be at the end of the arguments list + // Named arguments are not supported yet + constexpr bool has_args = types_count_v...> != 0; + constexpr bool has_kwargs = types_count_v...> != 0; + constexpr std::size_t count = sizeof...(Args) - has_args - has_kwargs; + + handle stack[sizeof...(Args)] = {}; + + // initialize the stack + + if(!has_args && (view.size() != count)) { + return handle(); + } + + if(has_args && (view.size() < count)) { + return handle(); + } + + for(std::size_t i = 0; i < count; ++i) { + stack[i] = view[i]; + } + + // pack the args and kwargs + if constexpr(has_args) { + const auto n = view.size() - count; + pkpy::PyVar var = vm->new_object(vm->tp_tuple, n); + auto& tuple = var.obj_get(); + for(std::size_t i = 0; i < n; ++i) { + tuple[i] = view[count + i]; + } + stack[count] = var; + } + + if constexpr(has_kwargs) { + const auto n = vm->s_data._sp - view.end(); + pkpy::PyVar var = vm->new_object(vm->tp_dict); + auto& dict = var.obj_get(); + + for(std::size_t i = 0; i < n; i += 2) { + pkpy::i64 index = pkpy::_py_cast(vm, view[count + i]); + pkpy::PyVar str = + vm->new_object(vm->tp_str, pkpy::StrName(index).sv()); + dict.set(vm, str, view[count + i + 1]); + } + + stack[count + 1] = var; + } + + // check if all the arguments are not valid + for(std::size_t i = 0; i < sizeof...(Args); ++i) { + if(!stack[i]) { + return handle(); + } + } + + // ok, all the arguments are valid, call the function + std::tuple...> casters; + + // check type compatibility + if(((std::get(casters).load(stack[Is], convert)) && ...)) { + return invoke(self.cast(), + std::index_sequence{}, + casters, + self.policy, + parent); + } + + return handle(); + }; + } + }; + + constexpr inline static auto _wrapper = +[](pkpy::VM*, pkpy::ArgsView view) { + auto& record = pkpy::lambda_get_userdata(view.begin()); + return record(view).ptr(); + }; + + class cpp_function : public function { + public: + template + cpp_function(Fn&& f, const Extras&... extras) { + pkpy::any userdata = function_record(std::forward(f), "anonymous", extras...); + m_ptr = vm->bind_func(nullptr, "", -1, _wrapper, std::move(userdata)); + inc_ref(); + } + }; + + template + handle bind_function(const handle& obj, + const char* name, + Fn&& fn, + pkpy::BindType type, + const Extras&... extras) { + // do not use cpp_function directly to avoid unnecessary reference count change + pkpy::PyVar var = obj.ptr(); + pkpy::PyVar callable = var->attr().try_get(name); + + // if the function is not bound yet, bind it + if(!callable) { + pkpy::any userdata = function_record(std::forward(fn), name, extras...); + callable = vm->bind_func(var, name, -1, _wrapper, std::move(userdata)); + } else { + auto& userdata = callable.obj_get()._userdata; + function_record* record = new function_record(std::forward(fn), name, extras...); + + constexpr bool is_prepend = (types_count_v != 0); + if constexpr(is_prepend) { + // if prepend is specified, append the new record to the beginning of the list + function_record* last = (function_record*)userdata.data; + userdata.data = record; + record->append(last); + } else { + // otherwise, append the new record to the end of the list + function_record* last = (function_record*)userdata.data; + last->append(record); + } + } + return callable; + } + + template + handle bind_property(const handle& obj, + const char* name, + Getter_&& getter_, + Setter_&& setter_, + const Extras&... extras) { + pkpy::PyVar var = obj.ptr(); + pkpy::PyVar getter = vm->None; + pkpy::PyVar setter = vm->None; + using Getter = std::decay_t; + using Setter = std::decay_t; + + getter = vm->new_object( + vm->tp_native_func, + [](pkpy::VM* vm, pkpy::ArgsView view) -> pkpy::PyVar { + auto& getter = pkpy::lambda_get_userdata(view.begin()); + + if constexpr(std::is_member_pointer_v) { + using Self = class_type_t; + auto& self = _builtin_cast(view[0]).cast(); + + if constexpr(std::is_member_object_pointer_v) { + return type_caster>::cast( + self.*getter, + return_value_policy::reference_internal, + view[0]) + .ptr(); + } else { + return type_caster>::cast( + (self.*getter)(), + return_value_policy::reference_internal, + view[0]) + .ptr(); + } + + } else { + using Self = std::tuple_element_t<0, callable_args_t>; + auto& self = _builtin_cast(view[0]).cast(); + + return type_caster>::cast( + getter(self), + return_value_policy::reference_internal, + view[0]) + .ptr(); + } + }, + 1, + std::forward(getter_)); + + if constexpr(!std::is_same_v) { + setter = vm->new_object( + vm->tp_native_func, + [](pkpy::VM* vm, pkpy::ArgsView view) -> pkpy::PyVar { + auto& setter = pkpy::lambda_get_userdata(view.begin()); + + if constexpr(std::is_member_pointer_v) { + using Self = class_type_t; + auto& self = _builtin_cast(view[0]).cast(); + + if constexpr(std::is_member_object_pointer_v) { + type_caster> caster; + if(caster.load(view[1], true)) { + self.*setter = caster.value; + return vm->None; + } + } else { + type_caster>> caster; + if(caster.load(view[1], true)) { + (self.*setter)(caster.value); + return vm->None; + } + } + } else { + using Self = std::tuple_element_t<0, callable_args_t>; + auto& self = _builtin_cast(view[0]).cast(); + + type_caster>> caster; + if(caster.load(view[1], true)) { + setter(self, caster.value); + return vm->None; + } + } + + vm->TypeError("invalid argument"); + }, + 2, + std::forward(setter_)); + } + + pkpy::PyVar property = vm->new_object(vm->tp_property, getter, setter); + var->attr().set(name, property); + return property; + } + +} // namespace pybind11 diff --git a/include/pybind11/internal/instance.h b/include/pybind11/internal/instance.h new file mode 100644 index 00000000..dae3baec --- /dev/null +++ b/include/pybind11/internal/instance.h @@ -0,0 +1,147 @@ +#pragma once + +#include "kernel.h" + +namespace pybind11 { + struct type_info { + const char* name; + std::size_t size; + std::size_t alignment; + void (*destructor)(void*); + void (*copy)(void*, const void*); + void (*move)(void*, void*); + const std::type_info* type; + + template + static type_info& of() { + static_assert(!std::is_reference_v && !std::is_const_v>, + "T must not be a reference type or const type."); + static type_info info = { + typeid(T).name(), + sizeof(T), + alignof(T), + [](void* ptr) { + ((T*)ptr)->~T(); + operator delete (ptr); + }, + [](void* dst, const void* src) { new (dst) T(*(const T*)src); }, + [](void* dst, void* src) { new (dst) T(std::move(*(T*)src)); }, + &typeid(T), + }; + return info; + } + }; + + // all registered C++ class will be ensured as instance type. + class instance { + public: + // use to record the type information of C++ class. + + private: + enum Flag { + None = 0, + Own = 1 << 0, // if the instance is owned by C++ side. + Ref = 1 << 1, // need to mark the parent object. + }; + + Flag flag; + void* data; + const type_info* type; + pkpy::PyVar parent; + // pkpy::PyVar + + public: + instance() noexcept : flag(Flag::None), data(nullptr), type(nullptr), parent(nullptr) {} + + instance(const instance&) = delete; + + instance(instance&& other) noexcept : + flag(other.flag), data(other.data), type(other.type), parent(other.parent) { + other.flag = Flag::None; + other.data = nullptr; + other.type = nullptr; + other.parent = nullptr; + } + + template + static pkpy::PyVar create(pkpy::Type type) { + instance instance; + instance.type = &type_info::of(); + instance.data = operator new (sizeof(T)); + instance.flag = Flag::Own; + return vm->new_object(type, std::move(instance)); + } + + template + static pkpy::PyVar + create(T&& value, + pkpy::Type type, + return_value_policy policy = return_value_policy::automatic_reference, + pkpy::PyVar parent = nullptr) noexcept { + using underlying_type = std::remove_cv_t>; + + // resolve for automatic policy. + if(policy == return_value_policy::automatic) { + policy = std::is_pointer_v ? return_value_policy::take_ownership + : std::is_lvalue_reference_v ? return_value_policy::copy + : return_value_policy::move; + } else if(policy == return_value_policy::automatic_reference) { + policy = std::is_pointer_v ? return_value_policy::reference + : std::is_lvalue_reference_v ? return_value_policy::copy + : return_value_policy::move; + } + + auto& _value = [&]() -> auto& { + /** + * note that, pybind11 will ignore the const qualifier. + * in fact, try to modify a const value will result in undefined behavior. + */ + if constexpr(std::is_pointer_v) { + return *reinterpret_cast(value); + } else { + return const_cast(value); + } + }(); + + instance instance; + instance.type = &type_info::of(); + + if(policy == return_value_policy::take_ownership) { + instance.data = &_value; + instance.flag = Flag::Own; + } else if(policy == return_value_policy::copy) { + instance.data = ::new auto(_value); + instance.flag = Flag::Own; + } else if(policy == return_value_policy::move) { + instance.data = ::new auto(std::move(_value)); + instance.flag = Flag::Own; + } else if(policy == return_value_policy::reference) { + instance.data = &_value; + instance.flag = Flag::None; + } else if(policy == return_value_policy::reference_internal) { + instance.data = &_value; + instance.flag = Flag::Ref; + instance.parent = parent; + } + + return vm->new_object(type, std::move(instance)); + } + + ~instance() { + if(flag & Flag::Own) { + type->destructor(data); + } + } + + void _gc_mark(pkpy::VM* vm) const noexcept { + if(parent && (flag & Flag::Ref)) { + PK_OBJ_MARK(parent); + } + } + + template + T& cast() noexcept { + return *static_cast(data); + } + }; +} // namespace pybind11 diff --git a/include/pybind11/internal/kernel.h b/include/pybind11/internal/kernel.h new file mode 100644 index 00000000..b84fc9c3 --- /dev/null +++ b/include/pybind11/internal/kernel.h @@ -0,0 +1,108 @@ +#pragma once + +#include + +namespace pybind11 +{ + inline pkpy::VM* vm = nullptr; + inline std::map* _ref_counts_map = nullptr; + + inline void initialize(bool enable_os = true) + { + vm = new pkpy::VM(enable_os); + _ref_counts_map = new std::map(); + + // use to keep alive PyObject, when the object is hold by C++ side. + vm->heap._gc_marker_ex = [](pkpy::VM* vm) + { + for(auto iter = _ref_counts_map->begin(); iter != _ref_counts_map->end();) + { + auto ref_count = iter->second; + if(*ref_count != 0) + { + // if ref count is not zero, then mark it. + PK_OBJ_MARK(iter->first); + ++iter; + } + else + { + // if ref count is zero, then delete it. + iter = _ref_counts_map->erase(iter); + delete ref_count; + } + } + }; + } + + inline void finalize() + { + delete _ref_counts_map; + delete vm; + } + + enum class return_value_policy : uint8_t + { + /** + * This is the default return value policy, which falls back to the policy + * return_value_policy::take_ownership when the return value is a pointer. + * Otherwise, it uses return_value::move or return_value::copy for rvalue + * and lvalue references, respectively. See below for a description of what + * all of these different policies do. + */ + automatic = 0, + + /** + * As above, but use policy return_value_policy::reference when the return + * value is a pointer. This is the default conversion policy for function + * arguments when calling Python functions manually from C++ code (i.e. via + * handle::operator()). You probably won't need to use this. + */ + automatic_reference, + + /** + * Reference an existing object (i.e. do not create a new copy) and take + * ownership. Python will call the destructor and delete operator when the + * object's reference count reaches zero. Undefined behavior ensues when + * the C++ side does the same.. + */ + take_ownership, + + /** + * Create a new copy of the returned object, which will be owned by + * Python. This policy is comparably safe because the lifetimes of the two + * instances are decoupled. + */ + copy, + + /** + * Use std::move to move the return value contents into a new instance + * that will be owned by Python. This policy is comparably safe because the + * lifetimes of the two instances (move source and destination) are + * decoupled. + */ + move, + + /** + * Reference an existing object, but do not take ownership. The C++ side + * is responsible for managing the object's lifetime and deallocating it + * when it is no longer used. Warning: undefined behavior will ensue when + * the C++ side deletes an object that is still referenced and used by + * Python. + */ + reference, + + /** + * This policy only applies to methods and properties. It references the + * object without taking ownership similar to the above + * return_value_policy::reference policy. In contrast to that policy, the + * function or property's implicit this argument (called the parent) is + * considered to be the the owner of the return value (the child). + * pybind11 then couples the lifetime of the parent to the child via a + * reference relationship that ensures that the parent cannot be garbage + * collected while Python is still using the child. More advanced + * variations of this scheme are also possible using combinations of + * return_value_policy::reference and the keep_alive call policy + */ + reference_internal + }; +} // namespace pybind11 diff --git a/include/pybind11/internal/object.h b/include/pybind11/internal/object.h new file mode 100644 index 00000000..ab9b7f0a --- /dev/null +++ b/include/pybind11/internal/object.h @@ -0,0 +1,256 @@ +#pragma once + +#include "kernel.h" + +namespace pybind11 { + class handle; + class object; + class attr_accessor; + class item_accessor; + class iterator; + class str; + class bytes; + class iterable; + class tuple; + class dict; + class list; + class set; + class function; + class module; + class type; + class bool_; + class int_; + class float_; + class str; + class bytes; + + template + T& _builtin_cast(const handle& obj); + + template + T reinterpret_borrow(const handle& h); + + template + T reinterpret_steal(const handle& h); + + class handle { + protected: + pkpy::PyVar m_ptr = nullptr; + mutable int* ref_count = nullptr; + + public: + handle() = default; + handle(const handle& h) = default; + handle& operator= (const handle& other) = default; + + handle(pkpy::PyVar ptr) : m_ptr(ptr) {} + + pkpy::PyVar ptr() const { return m_ptr; } + + int reference_count() const { return ref_count == nullptr ? 0 : *ref_count; } + + const handle& inc_ref() const { + PK_DEBUG_ASSERT(m_ptr != nullptr); + if(ref_count == nullptr) { + auto iter = _ref_counts_map->find(m_ptr); + if(iter == _ref_counts_map->end()) { + ref_count = ::new int(1); + _ref_counts_map->insert({m_ptr, ref_count}); + } else { + ref_count = iter->second; + *ref_count += 1; + } + } else { + *ref_count += 1; + } + return *this; + } + + const handle& dec_ref() const { + PK_DEBUG_ASSERT(m_ptr != nullptr); + PK_DEBUG_ASSERT(ref_count != nullptr); + + *ref_count -= 1; + try { + if(*ref_count == 0) { + _ref_counts_map->erase(m_ptr); + ::delete ref_count; + ref_count = nullptr; + } + } catch(std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; } + + return *this; + } + + public: + template + T cast() const; + + explicit operator bool () const { return m_ptr.operator bool (); } + + bool is(const handle& other) const { return m_ptr == other.m_ptr; } + + bool is_none() const { return m_ptr == vm->None; } + + bool in(const handle& other) const { + return pkpy::py_cast(vm, vm->call(vm->py_op("contains"), other.m_ptr, m_ptr)); + } + + bool contains(const handle& other) const { + return pkpy::py_cast(vm, vm->call(vm->py_op("contains"), m_ptr, other.m_ptr)); + } + + iterator begin() const; + iterator end() const; + + str doc() const; + + attr_accessor attr(const char* name) const; + attr_accessor attr(const handle& name) const; + attr_accessor attr(object&& name) const; + + item_accessor operator[] (int64_t key) const; + item_accessor operator[] (const char* key) const; + item_accessor operator[] (const handle& key) const; + item_accessor operator[] (object&& key) const; + + object operator- () const; + object operator~() const; + + template + object operator() (Args&&... args) const; + + private: + friend object operator+ (const handle& lhs, const handle& rhs); + friend object operator- (const handle& lhs, const handle& rhs); + friend object operator* (const handle& lhs, const handle& rhs); + friend object operator% (const handle& lhs, const handle& rhs); + friend object operator/ (const handle& lhs, const handle& rhs); + friend object operator| (const handle& lhs, const handle& rhs); + friend object operator& (const handle& lhs, const handle& rhs); + friend object operator^ (const handle& lhs, const handle& rhs); + friend object operator<< (const handle& lhs, const handle& rhs); + friend object operator>> (const handle& lhs, const handle& rhs); + + friend object operator+= (const handle& lhs, const handle& rhs); + friend object operator-= (const handle& lhs, const handle& rhs); + friend object operator*= (const handle& lhs, const handle& rhs); + friend object operator/= (const handle& lhs, const handle& rhs); + friend object operator%= (const handle& lhs, const handle& rhs); + friend object operator|= (const handle& lhs, const handle& rhs); + friend object operator&= (const handle& lhs, const handle& rhs); + friend object operator^= (const handle& lhs, const handle& rhs); + friend object operator<<= (const handle& lhs, const handle& rhs); + friend object operator>>= (const handle& lhs, const handle& rhs); + + friend object operator== (const handle& lhs, const handle& rhs); + friend object operator!= (const handle& lhs, const handle& rhs); + friend object operator< (const handle& lhs, const handle& rhs); + friend object operator> (const handle& lhs, const handle& rhs); + friend object operator<= (const handle& lhs, const handle& rhs); + friend object operator>= (const handle& lhs, const handle& rhs); + + template + friend T& _builtin_cast(const handle& obj) { + // FIXME: 2.0 does not use Py_ anymore + static_assert(!std::is_reference_v, "T must not be a reference type."); + return obj.ptr().obj_get(); + } + }; + + static_assert(std::is_trivially_copyable_v); + + class object : public handle { + public: + object(const object& other) : handle(other) { inc_ref(); } + + object(object&& other) noexcept : handle(other) { + other.m_ptr = nullptr; + other.ref_count = nullptr; + } + + object& operator= (const object& other) { + if(this != &other) { + dec_ref(); + m_ptr = other.m_ptr; + ref_count = other.ref_count; + inc_ref(); + } + return *this; + } + + object& operator= (object&& other) noexcept { + if(this != &other) { + dec_ref(); + m_ptr = other.m_ptr; + ref_count = other.ref_count; + other.m_ptr = nullptr; + other.ref_count = nullptr; + } + return *this; + } + + ~object() { + if(m_ptr != nullptr) { + dec_ref(); + } + } + + protected: + object(const handle& h, bool borrow) : handle(h) { + if(borrow) { + inc_ref(); + } + } + + template + friend T reinterpret_borrow(const handle& h) { + return {h, true}; + } + + template + friend T reinterpret_steal(const handle& h) { + return {h, false}; + } + }; + + inline void setattr(const handle& obj, const handle& name, const handle& value); + inline void setitem(const handle& obj, const handle& key, const handle& value); + +#define PYBIND11_BINARY_OPERATOR(OP, NAME) \ + inline object operator OP (const handle& lhs, const handle& rhs) { \ + return reinterpret_borrow(vm->call(vm->py_op(NAME), lhs.m_ptr, rhs.m_ptr)); \ + } + + PYBIND11_BINARY_OPERATOR(+, "add"); + PYBIND11_BINARY_OPERATOR(-, "sub"); + PYBIND11_BINARY_OPERATOR(*, "mul"); + PYBIND11_BINARY_OPERATOR(/, "truediv"); + PYBIND11_BINARY_OPERATOR(%, "mod"); + PYBIND11_BINARY_OPERATOR(|, "or_"); + PYBIND11_BINARY_OPERATOR(&, "and_"); + PYBIND11_BINARY_OPERATOR(^, "xor"); + PYBIND11_BINARY_OPERATOR(<<, "lshift"); + PYBIND11_BINARY_OPERATOR(>>, "rshift"); + + PYBIND11_BINARY_OPERATOR(+=, "iadd"); + PYBIND11_BINARY_OPERATOR(-=, "isub"); + PYBIND11_BINARY_OPERATOR(*=, "imul"); + PYBIND11_BINARY_OPERATOR(/=, "itruediv"); + PYBIND11_BINARY_OPERATOR(%=, "imod"); + PYBIND11_BINARY_OPERATOR(|=, "ior"); + PYBIND11_BINARY_OPERATOR(&=, "iand"); + PYBIND11_BINARY_OPERATOR(^=, "ixor"); + PYBIND11_BINARY_OPERATOR(<<=, "ilshift"); + PYBIND11_BINARY_OPERATOR(>>=, "irshift"); + + PYBIND11_BINARY_OPERATOR(==, "eq"); + PYBIND11_BINARY_OPERATOR(!=, "ne"); + PYBIND11_BINARY_OPERATOR(<, "lt"); + PYBIND11_BINARY_OPERATOR(>, "gt"); + PYBIND11_BINARY_OPERATOR(<=, "le"); + PYBIND11_BINARY_OPERATOR(>=, "ge"); + +#undef PYBIND11_BINARY_OPERATOR + +} // namespace pybind11 diff --git a/include/pybind11/internal/type_traits.h b/include/pybind11/internal/type_traits.h new file mode 100644 index 00000000..124d539d --- /dev/null +++ b/include/pybind11/internal/type_traits.h @@ -0,0 +1,157 @@ +#pragma once + +#include +#include + +namespace pybind11 { + template + constexpr bool dependent_false = false; + + template + struct tuple_push_front; + + template + struct tuple_push_front> { + using type = std::tuple; + }; + + template + using tuple_push_front_t = typename tuple_push_front::type; + + // traits for function types + template + struct function_traits { + static_assert(dependent_false, "unsupported function type"); + }; + +#define PYBIND11_FUNCTION_TRAITS_SPECIALIZE(qualifiers) \ + template \ + struct function_traits { \ + using return_type = R; \ + using args_type = std::tuple; \ + constexpr static std::size_t args_count = sizeof...(Args); \ + }; + + PYBIND11_FUNCTION_TRAITS_SPECIALIZE() + PYBIND11_FUNCTION_TRAITS_SPECIALIZE(&) + PYBIND11_FUNCTION_TRAITS_SPECIALIZE(const) + PYBIND11_FUNCTION_TRAITS_SPECIALIZE(const&) + PYBIND11_FUNCTION_TRAITS_SPECIALIZE(noexcept) + PYBIND11_FUNCTION_TRAITS_SPECIALIZE(& noexcept) + PYBIND11_FUNCTION_TRAITS_SPECIALIZE(const noexcept) + PYBIND11_FUNCTION_TRAITS_SPECIALIZE(const& noexcept) + +#undef PYBIND11_FUNCTION_TRAITS_SPECIALIZE + + template + using function_return_t = typename function_traits::return_type; + + template + using function_args_t = typename function_traits::args_type; + + template + constexpr std::size_t function_args_count = function_traits::args_count; + + // traits for member pointers + template + struct member_traits; + + template + struct member_traits { + using member_type = M; + using class_type = C; + }; + + template + using member_type_t = typename member_traits::member_type; + + template + using class_type_t = typename member_traits::class_type; + + // some traits for distinguishing between function pointers, member function pointers and + // functors + using std::is_member_function_pointer_v; + using std::is_member_object_pointer_v; + + template + constexpr inline bool is_function_pointer_v = std::is_function_v>; + + template + constexpr bool is_functor_v = false; + + template + constexpr inline bool is_functor_v> = true; + + template + struct callable_traits; + + template + struct callable_traits>> { + using args_type = tuple_push_front_t&, function_args_t>>; + using return_type = function_return_t>; + }; + + template + struct callable_traits>> { + using args_type = function_args_t>; + using return_type = function_return_t>; + }; + + template + struct callable_traits>> { + using args_type = function_args_t>; + using return_type = function_return_t>; + }; + + template + using callable_args_t = typename callable_traits::args_type; + + template + using callable_return_t = typename callable_traits::return_type; + + template + constexpr std::size_t callable_args_count_v = std::tuple_size_v>; + + template + struct type_identity { + using type = T; + }; + + template + using remove_cvref_t = std::remove_cv_t>; + + template + constexpr inline std::size_t types_count_v = (std::is_same_v + ...); + + template + constexpr inline std::size_t types_count_v = 0; + + template + struct value_wrapper { + T* pointer; + + operator T& () { return *pointer; } + }; + + template + struct value_wrapper { + T* pointer; + + operator T* () { return pointer; } + }; + + template + struct value_wrapper { + T* pointer; + + operator T& () { return *pointer; } + }; + + template + struct value_wrapper { + T* pointer; + + operator T&& () { return std::move(*pointer); } + }; + +} // namespace pybind11 diff --git a/include/pybind11/internal/types.h b/include/pybind11/internal/types.h new file mode 100644 index 00000000..d223ad8f --- /dev/null +++ b/include/pybind11/internal/types.h @@ -0,0 +1,218 @@ +#pragma once + +#include "object.h" + +namespace pybind11 { + class type : public object { + public: + using object::object; + template + static handle handle_of(); + }; + + class iterable : public object { + public: + using object::object; + iterable() = delete; + }; + + class iterator : public object { + public: + using object::object; + iterator() = delete; + }; + + class list : public object { + public: + using object::object; + + list() : object(vm->new_object(pkpy::VM::tp_list), true) {} + }; + + class tuple : public object { + public: + using object::object; + + tuple(int n) : object(vm->new_object(pkpy::VM::tp_tuple, n), true) {} + + //& operator[](int i){ return _args[i]; } + // PyVar operator[](int i) const { return _args[i]; } + }; + + class set : public object { + public: + using object::object; + // set() : object(vm->new_object(pkpy::VM::tp_set), true) {} + }; + + class dict : public object { + public: + using object::object; + + dict() : object(vm->new_object(pkpy::VM::tp_dict), true) {} + }; + + class str : public object { + + public: + using object::object; + str(const char* c, int len) : + object(vm->new_object(pkpy::VM::tp_str, c, len), true) { + + }; + + str(const char* c = "") : str(c, strlen(c)) {} + + str(const std::string& s) : str(s.data(), s.size()) {} + + str(std::string_view sv) : str(sv.data(), sv.size()) {} + + explicit str(const bytes& b); + explicit str(handle h); + operator std::string () const; + + template + str format(Args&&... args) const; + }; + + class int_ : public object { + public: + using object::object; + + int_(int64_t value) : object(pkpy::py_var(vm, value), true) {} + }; + + class float_ : public object { + public: + using object::object; + + float_(double value) : object(pkpy::py_var(vm, value), true) {} + }; + + class bool_ : public object { + public: + using object::object; + + bool_(bool value) : object(pkpy::py_var(vm, value), true) {} + }; + + class function : public object { + public: + using object::object; + }; + + class attr_accessor : public object { + private: + object key; + + public: + template + attr_accessor(const object& obj, T&& key) : object(obj), key(std::forward(key)){}; + + template + attr_accessor& operator= (T&& value) & { + static_assert(std::is_base_of_v>, + "T must be derived from object"); + m_ptr = std::forward(value); + return *this; + } + + template + attr_accessor& operator= (T&& value) && { + static_assert(std::is_base_of_v>, + "T must be derived from object"); + setattr(*this, key, std::forward(value)); + return *this; + } + }; + + inline attr_accessor handle::attr(const char* name) const { + return attr_accessor(reinterpret_borrow(*this), str(name)); + } + + inline attr_accessor handle::attr(const handle& name) const { + return attr_accessor(reinterpret_borrow(*this), reinterpret_borrow(name)); + } + + inline attr_accessor handle::attr(object&& name) const { + return attr_accessor(reinterpret_borrow(*this), std::move(name)); + } + + class item_accessor : public object { + public: + object key; + + public: + template + item_accessor(const object& obj, T&& key) : object(obj), key(std::forward(key)){}; + + template + item_accessor& operator= (T&& value) & { + static_assert(std::is_base_of_v>, + "T must be derived from object"); + m_ptr = std::forward(value); + } + + template + item_accessor& operator= (object&& value) && { + static_assert(std::is_base_of_v>, + "T must be derived from object"); + setitem(*this, key, std::forward(value)); + } + }; + + inline item_accessor handle::operator[] (int64_t key) const { + return item_accessor(reinterpret_borrow(*this), int_(key)); + } + + inline item_accessor handle::operator[] (const char* key) const { + return item_accessor(reinterpret_borrow(*this), str(key)); + } + + inline item_accessor handle::operator[] (const handle& key) const { + return item_accessor(reinterpret_borrow(*this), reinterpret_borrow(key)); + } + + inline item_accessor handle::operator[] (object&& key) const { + return item_accessor(reinterpret_borrow(*this), std::move(key)); + } + + class args : public tuple { + using tuple::tuple; + }; + + class kwargs : public dict { + using dict::dict; + }; + + template + handle type::handle_of() { + if constexpr(std::is_same_v) { + return vm->_t(vm->tp_object); + } +#define PYBIND11_TYPE_MAPPER(type, tp) \ + else if constexpr(std::is_same_v) { \ + return vm->_t(vm->tp); \ + } + PYBIND11_TYPE_MAPPER(type, tp_type) + PYBIND11_TYPE_MAPPER(str, tp_str) + PYBIND11_TYPE_MAPPER(int_, tp_int) + PYBIND11_TYPE_MAPPER(float_, tp_float) + PYBIND11_TYPE_MAPPER(bool_, tp_bool) + PYBIND11_TYPE_MAPPER(list, tp_list) + PYBIND11_TYPE_MAPPER(tuple, tp_tuple) + PYBIND11_TYPE_MAPPER(args, tp_tuple) + PYBIND11_TYPE_MAPPER(dict, tp_dict) + PYBIND11_TYPE_MAPPER(kwargs, tp_dict) +#undef PYBIND11_TYPE_MAPPER + else { + auto result = vm->_cxx_typeid_map.find(typeid(T)); + if(result != vm->_cxx_typeid_map.end()) { + return vm->_t(result->second); + } + + vm->TypeError("Type not registered"); + } + } + +} // namespace pybind11 diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h new file mode 100644 index 00000000..3f35334d --- /dev/null +++ b/include/pybind11/pybind11.h @@ -0,0 +1,3 @@ +#pragma once + +#include "internal/class.h" \ No newline at end of file