Skip to content

Conversation

@junrushao
Copy link
Member

Summary

  • Add refl::enable_copy() to the C++ reflection API, which registers a __ffi_shallow_copy__ method using the copy constructor
  • Implement ObjectDeepCopier in src/ffi/extra/copy.cc that walks the object graph (DFS with explicit stack), then copies in topological order (leaves first) with field replacement
  • Wire up Python __copy__, __deepcopy__, and __replace__ for both @register_object and @c_class paths; types without enable_copy() raise TypeError

Test plan

17 new tests in tests/python/test_copy.py covering:

  • copy.copy(): basic fields, new object identity, mutable fields, mutation isolation, derived type preservation, unsupported type error
  • copy.deepcopy(): basic fields, nested object copying, shared reference preservation, mutation isolation, unsupported type error
  • __replace__(): single/multiple field replacement, no-kwargs copy, original unchanged, readonly field error, unsupported type error

Misc

  • Add --error-on-warning to the ty pre-commit hook

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @junrushao, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the interoperability between C++ FFI objects and Python by introducing robust copying mechanisms. It allows users to perform shallow and deep copies, as well as field-based replacements, on C++-backed objects directly from Python, aligning their behavior with standard Python objects. This change improves the usability and flexibility of FFI objects within Python workflows.

Highlights

  • C++ Reflection API Enhancement: Introduced refl::enable_copy() in the C++ reflection API to register a __ffi_shallow_copy__ method, allowing C++ objects to define their shallow copy behavior using their copy constructor.
  • Deep Copy Mechanism: Developed ObjectDeepCopier in C++ (src/ffi/extra/copy.cc) which performs a two-phase deep copy: first, it walks the object graph using DFS to collect objects in topological order, then it creates shallow copies and replaces fields with previously copied values.
  • Python Integration for Copy Operations: Wired up Python's __copy__, __deepcopy__, and __replace__ methods for FFI objects, ensuring proper copy semantics for both @register_object and @c_class types. Types without enable_copy() will now raise a TypeError when these methods are called.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • .pre-commit-config.yaml
    • Updated the ty check pre-commit hook to include --error-on-warning, enforcing stricter type checking.
  • CMakeLists.txt
    • Added src/ffi/extra/copy.cc to the build system, incorporating the new C++ copy utilities.
  • include/tvm/ffi/extra/copy.h
    • Introduced the DeepCopyValue function declaration, providing a C++ API for deep copying ffi::Any values.
  • include/tvm/ffi/reflection/registry.h
    • Defined refl::enable_copy helper struct to facilitate registration of __ffi_shallow_copy__ methods for C++ object types.
    • Extended ObjectDef to include a def(enable_copy) method, allowing types to easily opt-in to copy support.
  • python/tvm_ffi/dataclasses/c_class.py
    • Modified the @c_class decorator to call _setup_copy_methods, enabling Python copy methods for C++ classes.
  • python/tvm_ffi/registry.py
    • Implemented _setup_copy_methods to dynamically add __copy__, __deepcopy__, and __replace__ to FFI object types based on the presence of __ffi_shallow_copy__.
    • Added helper functions (_copy_supported, _deepcopy_supported, _replace_supported, and their _unsupported counterparts) to handle the Python-side implementation and error reporting for copy operations.
    • Introduced _get_deep_copy_func and _deep_copy_func_cache for efficient lookup of the C++ deep copy function.
  • src/ffi/extra/copy.cc
    • Implemented the ObjectDeepCopier class, which performs a two-phase deep copy: graph traversal (DFS) to collect objects in topological order, followed by shallow copying and field replacement.
    • Provided the DeepCopyValue function, exposed as ffi.DeepCopy to the FFI, which leverages ObjectDeepCopier.
  • src/ffi/testing/testing.cc
    • Updated test object definitions (TestIntPairObj, TestObjectBase, TestObjectDerived, SchemaAllTypesObj) to use refl::enable_copy(), making them copyable for testing purposes.
  • tests/python/test_copy.py
    • Added a new test file with 17 tests covering various aspects of copy.copy(), copy.deepcopy(), and __replace__ for FFI objects, including basic fields, new object identity, mutable fields, mutation isolation, derived type preservation, nested objects, shared references, and error handling for unsupported types.
Activity
  • A new C++ implementation for deep copying FFI objects has been introduced.
  • Python's copy module functionality (__copy__, __deepcopy__, __replace__) has been integrated with FFI objects.
  • Comprehensive test cases have been added to validate the new copy mechanisms across various scenarios.
  • The build system and pre-commit hooks were updated to support the new C++ files and enforce stricter code quality.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces __copy__, __deepcopy__, and __replace__ for FFI objects, which is a valuable addition for better Python integration. The implementation is well-designed, with a C++ ObjectDeepCopier that correctly handles complex object graphs using a two-phase, topological sort-based approach. The Python bindings correctly wire up the new functionality for both @register_object and @c_class paths, and the new tests are comprehensive.

I have one suggestion to improve the robustness of the C++ deep copy implementation by making container traversal iterative to prevent potential stack overflows with deeply nested structures.

@junrushao
Copy link
Member Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces __copy__, __deepcopy__, and __replace__ methods for FFI objects, which is a valuable addition for Python users. The implementation includes a C++ deep copier that correctly handles object graphs with cycles and shared references, and the Python-side wiring is well-executed. The new functionality is supported by a comprehensive test suite.

I've identified a critical issue in the C++ deep copy logic that could lead to errors when copying objects with read-only fields. I've also suggested adding documentation for a design limitation that requires object fields to be writable for deep copy to work. Apart from these points, the changes look solid.

Comment on lines +419 to +434
def _get_deep_copy_func() -> core.Function:
global _deep_copy_func_cache # noqa: PLW0603
if _deep_copy_func_cache is None:
_deep_copy_func_cache = get_global_func("ffi.DeepCopy")
return _deep_copy_func_cache


_deep_copy_func_cache: core.Function | None = None
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably don't need this - just call get_global_func every time

@junrushao
Copy link
Member Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces __copy__, __deepcopy__, and __replace__ functionality for FFI objects, which is a great feature for improving the Python ergonomics. The implementation is robust, featuring a C++ refl::enable_copy() helper for opting into copy support, a well-designed deep copy algorithm in C++ that correctly handles cycles and shared references, and the necessary Python bindings to integrate with the standard copy module. The new functionality is also well-covered by a comprehensive suite of new tests. The overall quality of the code is high. I have a couple of minor suggestions to improve the clarity of comments in the C++ deep copy implementation.

@junrushao
Copy link
Member Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces __copy__, __deepcopy__, and __replace__ functionality for FFI objects, which is a great feature for improving the ergonomics of working with these objects in Python. The implementation is robust, handling object graphs with shared references and cycles correctly through a multi-pass deep copy algorithm in C++. The Python-side integration is clean, dynamically adding the appropriate copy methods based on whether the C++ type has copy support enabled. The new tests are comprehensive and cover a wide range of scenarios, including basic copies, nested objects, cycles, and error conditions. I have one suggestion to improve the efficiency of the C++ type registration logic.

Comment on lines +655 to +667
TVM_FFI_INLINE ObjectDef& def([[maybe_unused]] enable_copy ec, Extra&&... extra) {
// Register __ffi_shallow_copy__ as an instance method
RegisterMethod(kShallowCopyMethodName, false, &enable_copy::template shallow_copy<Class>,
std::forward<Extra>(extra)...);
// Also register as a type attribute for generic deep copy lookup
Function copy_fn = GetMethod(std::string(type_key_) + "." + kShallowCopyMethodName,
&enable_copy::template shallow_copy<Class>);
TVMFFIByteArray attr_name = {kShallowCopyMethodName,
std::char_traits<char>::length(kShallowCopyMethodName)};
TVMFFIAny attr_value = AnyView(copy_fn).CopyToTVMFFIAny();
TVM_FFI_CHECK_SAFE_CALL(TVMFFITypeRegisterAttr(type_index_, &attr_name, &attr_value));
return *this;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation creates the Function object for the shallow copy method twice: once within RegisterMethod on line 657 and again explicitly via GetMethod on line 660. This is slightly inefficient as it involves redundant work during type registration.

You can avoid this by retrieving the method that was just registered using TVMFFITypeGetMethod and then using that to register the type attribute. This makes the code more efficient and avoids the redundant object creation.

  TVM_FFI_INLINE ObjectDef& def([[maybe_unused]] enable_copy ec, Extra&&... extra) {
    // Register __ffi_shallow_copy__ as an instance method
    RegisterMethod(kShallowCopyMethodName, false, &enable_copy::template shallow_copy<Class>,
                   std::forward<Extra>(extra)...);
    // Also register as a type attribute for generic deep copy lookup
    TVMFFIMethodInfo method_info;
    TVMFFIByteArray method_name = {kShallowCopyMethodName,
                                   std::char_traits<char>::length(kShallowCopyMethodName)};
    TVM_FFI_CHECK_SAFE_CALL(TVMFFITypeGetMethod(type_index_, &method_name, &method_info));
    TVM_FFI_CHECK_SAFE_CALL(TVMFFITypeRegisterAttr(type_index_, &method_name, &method_info.method));
    return *this;
  }

@junrushao junrushao force-pushed the 2026-02-08/copy branch 2 times, most recently from 0be24b8 to 6a51521 Compare February 9, 2026 07:26
* \brief Reflection-based object copy utilities
*/
#ifndef TVM_FFI_EXTRA_COPY_H_
#define TVM_FFI_EXTRA_COPY_H_
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deep_copy.h?

* \param value The value to deep copy.
* \return The deep copied value.
*/
TVM_FFI_EXTRA_CXX_API Any DeepCopyValue(const Any& value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel DeepCopy as an API name is fine here

*
* \note The object type must have a copy constructor.
*/
struct enable_copy {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should it be enable_deep_copy to be explicit?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it's for shallow copy

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, i understand, seems the confusion part is that it enables a shallow copy API that was required for deep copy?

*
* Algorithm:
* Phase 1: Walk the entire object graph (objects, arrays, maps) using BFS,
* collecting all reachable nodes in a vector. An unordered_set
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likely this algorithm can be simplified to a single pass algorithm with memo_ that maps previously mapped Object into a new one.

The RunCopy will internally look up the memo and return copied objects if found. If not, it will then attempt to run the deep copy recursively and store the result into the memo. There is no need to record things like node order, visited will be part of the memo_ map that dedup double visits

@junrushao junrushao force-pushed the 2026-02-08/copy branch 2 times, most recently from ccfc66c to abd9ddd Compare February 9, 2026 18:58
@junrushao junrushao marked this pull request as draft February 10, 2026 00:41
@junrushao
Copy link
Member Author

Still - implication of cyclic reference and CoW containers (Array, Map) makes it pretty challenging to have an absolutely correct implementation of __deepcopy__. Converting it to draft.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants