【问题标题】:How does code from different languages get integrated in the same platform? [closed]不同语言的代码如何集成在同一个平台上? [关闭]
【发布时间】:2021-06-22 10:54:29
【问题描述】:

首先,我不是软件专家,我意识到这个问题可能很模糊(这只是一种好奇心),并准备好阅读我的一些野蛮猜测!

这个问题来自阅读一篇关于 Linux 开发人员如何在他们的操作系统中实现 Rust (https://www.zdnet.com/article/linus-torvalds-on-where-rust-will-fit-into-linux/) 的文章。

在他们的操作系统中实现 Rust 意味着什么?他们是否有一些用 C 编写的编译代码调用由 Rust 编写的编译代码?我不知道如何有效地完成此操作,因为您可能会有不同的编译器无法优化代码,因为在此过程中,它正在调用“外部”代码。我想如果您将 Python 或 Java 之类的未预编译的语言混入其中,情况会变得更糟。现在您将让 JVM 或 PVM 与已编译的代码一起运行,我想这将是非常不切实际的。我可以想到的一种方法是,如果您将所有这些事情视为单独的进程,并且您只需让一种语言的代码启动一个与另一种语言的代码相对应的进程,但我想这不会很有效...

再次,我意识到我本可以更直接,但我不是在寻找解决问题的具体答案,而是在寻找不同语言如何一起使用的一般见解。感谢理解!

【问题讨论】:

  • 就像en.wikipedia.org/wiki/Foreign_function_interface 一样。但总的来说,我不明白这个问题。为什么不同的语言不能互相调用?为什么不呢?I don't see how this can efficiently be done你问如何还是问效率。尽管如此,在 C 中,通常每个文件都是单独优化的,因此没有区别,而且 LTO 的运行成本太高。
  • @KamilCuk 我不是说他们不能,我是说我看不出他们怎么能。例如,如果 C 编译器有一种特定的方式来管理堆栈,这与 Rust 管理堆栈的方式完全不同,那么调用 Rust 代码可能会像从 C 代码中那样弄乱堆栈,因为它可以确保不再成立的假设。因为它们共享相同的进程,所以内存不受保护。在另一种情况下,每次需要运行一点点代码时,我们需要重新检查代码版本,看看它是否需要更新,然后在 VM 中运行。
  • if the C compiler has a specific way of managing the stack, that is completely different from how Rust manages stack 但它不是,它是一样的。其规则由 ABI 管理,即。 uclibc.org/docs/psABI-x86_64.pdf 。尽管如此,如果一种语言确实以不同的方式使用堆栈,那么“只是”专门处理这种情况。我对 Python/Java 的东西一无所知 - Python 是用 C 编写的,所以你用 Python 编写的任何东西都由 C 执行,而 C 调用其他 C 函数(为了简化很多)。与 JVM 相同。而且它不是“虚拟机”,它更像是一个解释器。
  • 另一方面,我认为除了研究什么是“ABI”之外,您还应该研究什么是“链接器”和“链接”,链接和共享库如何工作以及@是什么987654326@做。和ELF文件格式。我认为这些主题可能会让您更好地理解。
  • 我明白了,刚刚了解了 ABI。我看到文章中提到过,但没有太在意。我想这是有道理的。 @user253751 也给了我一个关于如何集成基于 VM 的语言的好主意。感谢您的澄清!

标签: c linux rust compiler-construction jit


【解决方案1】:

通常在编译源文件时,我们有一组编译器生成的输出选项,例如从 main 函数创建二进制应用程序、静态链接二进制文件或动态链接库,或者在其他情况下,源源转换。

内核是用 C 编写的,为了能够编译像内核这样的大型代码库,我们经常决定将每个源文件或一组源文件(正确的术语是翻译单元https://en.wikipedia.org/wiki/Translation_unit_(programming))静态编译成一个链接库或目标文件。一旦我们收集了所有目标文件和静态(或共享)链接库,我们就可以将它们链接在一起并生成最终的二进制文件/库。

当我们谈论将 Rust 代码集成到内核中时,我们谈论的是在 Rust 中使用 C 中的静态链接库,反之亦然。调用其他编译器或语言生成的代码的过程称为外部函数接口,或 FFI。

FFI 存在许多细节和挑战,包括 ABI 或名称修改等等。 ABI 或应用程序二进制接口是您的文章中提到的问题之一。与 C 不同,Rust 还没有稳定的 ABI,这意味着无法保证从 Rust 编译的静态库中的符号将来不会有不同的名称或数据布局。这意味着使用 Rust 编译器编译的代码可能与以前的 Rust 编译器输出不兼容,这需要每次 ABI 更改时更新 C 代码。

【讨论】:

  • 我明白了,看来我们可以无缝集成它们只是时间问题。我会读一些关于 FFI 的书,感觉就像我打开了一个全新的世界,但每次我问一个我不太熟悉的话题时,都会再次发生这种情况!感谢您的回复!
【解决方案2】:

C 程序的传统编译方式如下:

  • 每个源文件都是单独编译的! - 放入一个目标文件,其中包含机器代码和用于将所有目标文件连接在一起的占位符。
  • 所有的目标文件都连接在一起,占位符也填好了。

如果您可以使用 Rust 而不是 C 生成其中一些目标文件,那么 C 编译器将无法区分。和不同的C文件没什么区别!


您需要确保跨越语言障碍的函数必须是两种语言的有效函数。例如,如果这在 Rust 中无效(我实际上不知道),您可能无法将结构作为参数传递或返回结构 - 您可能只能使用原始值,如整数、浮点数和指针。如果 Rust 机器代码期望浮点变量位于某些寄存器中,但 C 机器代码将它们放在不同的寄存器中,则您可能无法传递浮点数。或者你可能需要给一个或另一个编译器一个特殊的提示,说“参数进入 this 寄存器,哑!”如果 Rust 编译器的人不与 C 编译器的人合作,你可能会遇到一些这样的问题,但如果运气好的话,它们都可以解决。

由于该函数需要在两种语言中都有效,因此您还需要编写一个 C 头文件。你不能#include 一个 Rust 文件——你可以,但它不起作用——所以你需要用 C 语法编写函数声明再次,这样 C 编译器才能理解它们是什么。


对于需要虚拟机的语言,它的复杂程度不同。

通常这些语言与 C 语言非常不同,以至于您不能只将机器代码链接在一起。相反,您必须使用 VM 的 API 来初始化 VM、加载代码并调用代码。像这样的:

    // not real code, just an illustration of how it could work

    // How C starts a JVM and calls a Java method (a Java function)
    void run_jvm() {
        jvm_t *jvm = create_jvm();
        jvm_class_t *main_class = jvm_load_class(jvm, "Main.class");
        jvm_method_t *main_method = jvm_get_method(jvm, main_class, "main", NULL);
        jvm_call(jvm, main_method, NULL, NULL);
        delete_jvm(jvm);
    }

    // how Java calls a C function
    void Java_HelloPrinter_printHello(jvm_t *jvm, jvm_object_t *this, jvm_args_t *args) {
        printf("Hello world! My argument is %d\n", jvm_args_get_int(args, 1));
    }

    // how the method is declared in Java
    public class HelloPrinter {
        public static native void printHello(int i);
        //            ^^^^^^
        // this means it's a C function
    }

在 Python 或 Lua 中,作为更动态的语言,解释器不会为你寻找东西。而是在运行任何 Python 代码之前将函数放入变量中。

    // not real code, just an illustration of how it could work

    // How Python calls a C function
    void printHelloFunction(pvm_t *pvm, pvm_args_t *args) {
        printf("Hello world! My argument is %d\n", pvm_args_get_int(args, 1));
    }

    // How C starts a PVM and calls a Python method
    void run_python() {
        pvm_t *pvm = create_pvm();
        pvm_variable_t *var = pvm_create_global_variable(pvm, "printHello");
        pvm_set_variable_as_c_function(pvm, var, &printHelloFunction);
        pvm_load_module_from_file(pvm, "main.py");
        delete_pvm(pvm);
    }

这些语言与 C 不同,但 Rust 是,因为 Rust 编译为机器代码。如果您可以在 VM 之外运行 Java 或 Python 代码,它们可能处于平等地位。曾经有一个名为gcj 的编译器可以将Java 编译成机器码,但它不再维护。有人可以编写一个,尽管它不能运行大多数 Java 程序,因为其中很多程序需要与 VM 一起做一些事情(比如反射)。

【讨论】:

  • 我知道编译器是如何工作的(嗯,至少是 C 编译器)。但是让我感到困惑的是来自多个编译器的代码一起使用,因为预计它们会以不同的假设以不同的方式管理事物。我刚刚了解了 ABI,语言拥有它确实很有意义,但我并没有意识到这一点。还要感谢您阐明如何集成 VM 语言,这是一个相当直观的解释!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-09
相关资源
最近更新 更多