taichi-dev / taichi

Productive, portable, and performant GPU programming in Python.
https://taichi-lang.org
Apache License 2.0
25.34k stars 2.27k forks source link

Struct member function can not be called #7370

Open Enigmatisms opened 1 year ago

Enigmatisms commented 1 year ago

Calling nested struct member function Calling the function of a struct from the parent struct will fail and lead to "AttributeError: 'Structxx' object has no attribute 'xxx'"

To Reproduce First, I know this issue looks like #6917 or #6737 but it's different. I saw there was a pr in #6917. I ran through the code, and also found the updated version of struct initialization. Yet I got a exception. I create a small example snippet to reproduce:

import taichi as ti

ti.init()

@ti.dataclass
class Core:
    foo:  ti.i32

    @ti.func
    def positive(self):
        return self.foo >= 0

@ti.dataclass
class InnerWrapper:
    core: Core

    @ti.func
    def is_positive(self):
        return self.core.positive()

@ti.dataclass
class OuterWrapper:
    inner: InnerWrapper

test_field = OuterWrapper.field()
ti.root.bitmasked(ti.i, 16).place(test_field)

random_num = ti.field(ti.i32, 16)

for i in range(16):
    test_field[i] = OuterWrapper(inner = InnerWrapper(core = Core(foo = -1 if i & 1 else 1)))

@ti.kernel
def test():
    for i in test_field:
        # this call would fail
        random_num[i] = test_field[i].inner.is_positive()

test()

for i in range(16):
    print(random_num[i], end = ', ')

The code is run by (say we create a file called test.py): python3 test.py

Log/Screenshots The exception thrown:

[Taichi] version 1.4.1, llvm 15.0.1, commit e67c674e, win, python 3.7.1
[Taichi] Starting on arch=x64
Traceback (most recent call last):
  File ".\test.py", line 38, in <module>
    test()
  File "user\AppData\Local\Programs\Python\Python37\lib\site-packages\taichi\lang\kernel_impl.py", line 976, in wrapped
    raise type(e)('\n' + str(e)) from None
taichi.lang.exception.TaichiCompilationError:
File ".\test.py", line 36, in test:
        random_num[i] = test_field[i].inner.is_positive()
File ".\test.py", line 19, in is_positive:
        return self.core.positive()
Traceback (most recent call last):
  File "user\AppData\Local\Programs\Python\Python37\lib\site-packages\taichi\lang\ast\ast_transformer_utils.py", line 25, in __call__  
    return method(ctx, node)
  File "user\AppData\Local\Programs\Python\Python37\lib\site-packages\taichi\lang\ast\ast_transformer.py", line 862, in build_Attribute
    node.ptr = getattr(node.value.ptr, node.attr)
  File "user\AppData\Local\Programs\Python\Python37\lib\site-packages\taichi\lang\common_ops.py", line 27, in __getattr__
    f"'{type(self).__name__}' object has no attribute '{item}'")
AttributeError: 'Struct193' object has no attribute 'positive'

I have tested this behavior on Win11 / Ubuntu 20.04, with python 3.7.1 and 3.7.5, taichi 1.3.0 (after that fixing PR) and 1.4.1. It looks like on win11, llvm version is 15.0.1 while on linux it is 15.0.4. They all throw the same exception. I tried to print the __struct_method field when initializing the struct, everything seems fine to me. I just got no clue why this would happen.

Additional comments

chen-erqi commented 1 year ago

I meet this problem too T_T. May I ask if there are any ways to bypass this issue now? Can we write the member function outter and pass the struct into this function?

bobcao3 commented 1 year ago

I guess currently only way is to use procedural programming with first argument being the struct...

chen-erqi commented 1 year ago

I guess currently only way is to use procedural programming with first argument being the struct...

May i ask if this is data oriented programming? The struct is considered as a group of data without function.

Enigmatisms commented 1 year ago

It's been a long time. By the way I bypassed this issue by opting for newer python version.

So... what's the cause of this problem? As far as I can recall, it's caused by struct name aliasing? But I don't see anything related to this in my minimal reproduction code.

Judging from the discussion you have above, it seems that it is caused by struct nesting, and the member functions of the inner struct are stripped?

chen-erqi commented 1 year ago

It's been a long time. By the way I bypassed this issue by opting for newer python version.

So... what's the cause of this problem? As far as I can recall, it's caused by struct name aliasing? But I don't see anything related to this in my minimal reproduction code.

Judging from the discussion you have above, it seems that it is caused by struct nesting, and the member functions of the inner struct are stripped?

Could you please explain how you can bypass this issue by newer python version? Because I think maybe taichi is not related to the version of python? I think that it is caused by struct nesting, nesting in taichi and paralleling is complex.

Enigmatisms commented 1 year ago

Since I have two systems (one windows and one ubuntu) on my laptop, I found that the behavior of my code was different, even if I have the same version of Taichi. I did some experiments back then, and found that 3.8- (not all of them, 3.7.8 seemed fine, if I recall it correctly) would run into this issue. I tried again this code just now:

And..., is struct nesting such a big problem? I used a lot in my project, like: BSDF.py. The outtermost struct is BSDF, containing a struct Medium, which contains, further, a dataclass struct called PhaseFunction. The code works fine.

chen-erqi commented 1 year ago

thanks a lot!! My code was run on python 3.8.10 and it was bad. Could you please offer your exact python version?

Enigmatisms commented 1 year ago

Mine is 3.9.13, but my ubuntu is 3.8+. This is weird...

chen-erqi commented 1 year ago

Mine is 3.9.13, but my ubuntu is 3.8+. This is weird...

yes... My code will be pushed into some projects which require other envrioment... So i try to use procedural programming mentioned by @bobcao3. Thanks a lot for ur explaination and @bobcao3 !