【问题标题】:Can I get the AST from cppyy我可以从 cppyy 获取 AST
【发布时间】:2020-04-17 00:45:02
【问题描述】:

我想在创建 python 绑定之前访问 cppyy 中的 AST。我想用它来生成其他类型的绑定。

我见过cppyy-generator,但它需要在机器上单独安装clang。由于 cppyy 可以在没有单独安装 clang 的情况下进行 JIT 编译,我必须相信 AST 可以从底层的 Cling 解释器中获得。有没有办法从 cppyy 获取这个 AST 信息?

示例:

import cppyy

cppyy.cppdef("""
namespace foo
{
class Bar
{
public:
    void DoSomething() {}
};
}

""")

cppyy 可以(惊人地)为我生成 cppyy.gbl.foo.Bar。这意味着它必须使用 Cling 来编译、获取 AST 并生成 python。如何查看 AST 数据?

谢谢!

编辑

我可以看到我需要的大部分信息都在 cppyy-backend capi 和 cpp_cppyy 文件中。但是,我的 CPython foo 不够强大,无法弄清楚这些是如何被调用的,以及我如何从 python 脚本中访问它们。

编辑2

目前我们正在使用 castxml 和 pygccxml 的组合来生成表示 AST 的 python 数据结构。我看到与 cppyy 有很多重叠,并希望仅减少对 cppyy 的依赖,因为我们已经将它用于其他事情并且它是独立的。

我们将 AST 数据用于多种用途。一个重要的问题是代码生成。所以我们想对 AST 进行多次迭代 like you can with pygccxml

【问题讨论】:

    标签: python abstract-syntax-tree cppyy


    【解决方案1】:

    这里有一些歧义,b/c 相同的名称适用于不同的步骤和不同的地方。让我解释一下结构(和历史),它甚至可以回答你的问题。

    cppyy-generator 使用 Clang Python 绑定。因此,它访问的 AST 是 C++ 的,并且它完全(丑陋)的荣耀可用。您不需要 cppyy 的任何部分来使用 Clang Python 绑定。 cppyy-generator 服务于一个特定的用例,您希望将所有本地 C++ 实体预加载到 Python 模块中。由于 cppyy 利用惰性一切和自动加载,出于性能原因,“所有 C++ 实体”(本地或其他)的概念没有明确定义的含义。因此使用了 libclang,概念清晰。

    cppyy-backend capi(或 C-API)是一个 API,它是在 reductio 中开发的,用于服务于 cppyy 的 PyPy 实现。它是一个用于引导 cppy/C++ 的 C 风格 API。它被简化为编写 Python-C++ 绑定的本质,隐藏了 Clang AST 的许多不相关的细节(例如,模板可以在 Clang AST 中存在的 15 种左右的方式被简化为“IsTemplate”等)。后端 C-API 完全不依赖或使用 Python。

    后端 C-API 的实现相当不漂亮。部分是由于历史原因(一件坏事),部分是为了隐藏所有 Cling 和 Clang,以防止与可能正在使用 Clang 或 LLVM 的应用程序的其他部分发生冲突(一件好事;正在使用的 Clang 版本by Cling 是定制的,可能不适用于 Numba)。同样,所有这些都完全独立于 Python。

    然后,它在 Python 中的使用。有两种不同的实现:用于 CPython 的 CPyCppyy,用 C 实现,和 PyPy _cppyy 模块,用 RPython 实现。两者都通过 C-API 执行从 Python 跨入 C++ 的咒语。既不生成也不使用 Python AST:都直接生成和操作 Python 实体。这是懒惰地发生的。仔细考虑这些步骤:在上面的示例中,Python 用户将输入类似cppyy.gbl.foo.Bar().DoSomething() 的内容。在cppyy中,使用Python的__getattr__来截取名字,然后简单的通过后端向Cling询问是否知道fooBar等。例如C-APIGetScope("foo")将返回一个有效的标识符,因此 CPyCppyy/_cppyy 知道生成一个 Python 类来表示命名空间。然而,它在任何时候都不会完全扫描 AST 中的全局(甚至foo)命名空间来生成先验绑定。根据您的描述,CPyCppyy/_cppyy 中没有对您有用的内容。

    回到您的第一个语句,您想要生成其他类型的绑定。您没有说明什么类型的绑定,但选择 C-API 的主要原因是它位于 Cling 之上,而不是 Clang,因为 Clang AST 直接来自 C++ 或通过其 Python 绑定。 Cling 提供了对 JIT 的轻松访问,但您也可以直接从 Clang(它的库,而不是 AST)对其进行编程。作为这种 easyu 访问的示例,在后端 C-API 中,您只需将要 JITted 的 C++ 字符串转储到 compile 函数中(与您的示例中的 cppdef 完全相同)。 Cling 人员计划直接为来自 Cling 的动态语言提供更好的接口,但这是一项正在进行的工作,并且 (AFAIK) 尚不可用。

    最后,请注意 Cling 包含 Clang,所以如果你安装 Cling,你仍然会得到 Clang(和 LLVM),这可能是一个严重的依赖项。

    编辑:从根本上说,与其他工具相反,cppyy 不提供起点列表(例如“所有类”),也不提供完整/真实的 AST。您可以从后端复制cpp_cppyy.h 标头(否则它不是安装的一部分),只需包含它并使用它(所有符号都已导出),但您需要先验地知道类列表。示例:

    import cppyy
    
    cppyy.cppdef('#define RPY_EXPORTED extern')
    cppyy.include('cpp_cppyy.h')
    
    import cppyy
    
    cppyy.cppdef("""
    namespace foo {
    class Bar {
    public:
        void DoSomething() {}
    };
    }""")
    
    cpp = cppyy.gbl
    capi = cpp.Cppyy
    
    scope_id = capi.GetScope(cpp.foo.Bar.__cpp_name__) # need to know existence
    for i in range(capi.GetNumMethods(scope_id)):
         m = capi.GetMethod(scope_id, i)
         print(capi.GetMethodName(m))
    

    但正如您所见,它不提供与原始代码一对一的结果。例如,所有编译器生成的构造函数和析构函数都被列为方法。

    后端 API 中也没有任何东西,如您链接的 pygccxml 文档中的 run_functions = unittests.member_functions('run')。原因是这样在 cppyy 的上下文中没有任何意义。例如。如果另一个头文件加载了更多run 函数怎么办?如果它是一个模板函数并且弹出更多实例怎么办?如果using namespace ... 出现在后面的代码中,引入更多run 重载怎么办?

    cppyy 确实有 GetAllCppNames C-API 函数,但不能保证它是详尽无遗的。它的存在是为了代码编辑器中的制表符补全(在绑定范围的自定义__dir__ 函数中调用它)。事实上,正是因为不完整,cppyy-generator 使用了 libclang。

    您在 cmets 中提到了 gInterpreter,但这是我之前提到的历史的一部分:它是 libclang 提供的完整 AST 和 Python 所需的极简 AST(例如后端 C -API)。是的,您可以直接使用它(事实上,它仍然在后端 C-API 下使用),但它更笨重,没有什么好处。

    例如,要处理“获取所有'运行'方法”示例,您可以这样做:

    import cppyy
    
    cppyy.cppdef("""
    namespace foo {
    void run(int) {}
    void run(double) {}
    }""")
    
    cpp = cppyy.gbl
    
    # using the ROOT/meta interface
    cls = cpp.CppyyLegacy.TClass.GetClass(cpp.foo.__cpp_name__)
    print('num "run" overloads:"', cls.GetListOfMethodOverloads('run').GetSize())
    
    # directly through gInterpreter
    gInterp = cpp.gInterpreter
    
    cls = gInterp.ClassInfo_Factory(cpp.foo.__cpp_name__)
    v = cpp.std.vector['const void*']()
    gInterp.GetFunctionOverloads(cls, 'run', v)
    gInterp.ClassInfo_Delete(cls)
    
    print('num "run" overloads:"', len(v))
    

    但前一个界面(通过 CppyyLegacy.TClass)可能不会保留下来,而且 gInterpreter 真的很难看。

    我很确定你不会乐意尝试让 cppyy 取代 pygccxml 的使用,如果我是你,我会改用 Clang Python 绑定。

    【讨论】:

    • 感谢您的历史。 cppyy 包含 cling,其中包含 clang。我很惊讶要使用 cppyy-generator 我必须单独导入 clang 包并安装 LLVM/clang 以指向 libclang。我认为您是说 libclang 由于 cppyy-backend 安装而存在于 python 的某个地方,但这使得 cppyy-generator 无法找到自己的 libclang 依赖项更加令人困惑。
    • 参见 Edit2 了解动机。我想使用 capi 通过 cppyy 获取 AST 数据。我可以在cppyy.gbl.gInterpreter 上调用各种函数,但我正在努力弄清楚如何在 python 脚本中调用 capi。你能给我一个例子,说明我如何获得类似于 cppyy-generator 为 cppyy 类声明吐出的信息,就像我的例子一样?谢谢!
    • 对于您的第一条评论:使用 libclang 正是因为 Clang 在 Cling 中“过于隐藏”,因此几乎不可能用于其他目的。请注意,cppyy-generator 是贡献的。我很想将其更改为使用 Cling 的内部 clang,但我还没有时间这样做。我已经用一些代码示例更新了答案,但我不认为使用 cppyy 来替换 pygccxml 会让你的生活变得轻松。
    • 您是指生成(JITed)代码的速度吗?如果从 cppyy 使用,这几乎是相同的(vanilla cling 会进行指针检查以防止 nullptr 取消引用,这既昂贵又会扼杀许多优化)。您还可以设置自定义选项。或者你的意思是编译本身?在那里,Cling 明确删除了某些使用成本高但几乎没有运行时优势的优化通道。 Cling 现在也支持预编译模块,但是 cppyy 还没有使用它,因为它只在 Linux 上受支持。这些将编译速度提高了一个数量级。
    • 现在我不再 100% 确定我们在谈论同一件事。可以肯定的是:JIT 不编译任何 Python 代码,只编译 C++ 代码。是的,C++ 代码以本机速度运行。将 C++ 代码放入 cppyy.cppdef 然后调用它就像手动执行 Numba(如果这种比较有意义的话),速度相同。如果您需要 JIT Python 代码,请使用 PyPy(此时,如果您在 PyPy 中使用 cppyy,则涉及 两个 JIT,尽管如果库函数可用,cppyy 将绕过 clang JIT 和通过 cffi_backend 调用这些函数)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-04
    • 2013-06-27
    • 1970-01-01
    • 1970-01-01
    • 2021-06-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多