4

以下调查的结果是:最近的 Node.js 不能移植到 AMD Geode(或其他非 SSE x86)处理器!!!

我深入研究了代码并陷入了 ia32-assembler 实现,它将 SSE/SSE2 指令深度集成到他们的代码中(宏、宏、宏......)。主要后果是,由于缺少更新的指令集扩展,您无法在 AMD geode 处理器上运行最新版本的 node.js。回退到 387 算法仅适用于 node.js 代码,但不适用于它所依赖的 javascript V8 编译器实现。调整 V8 以支持非 SSE x86 处理器是一件痛苦的事,而且需要付出很多努力。

如果有人提供相反的证据,我会很高兴听到 ;-)

调查历史

我有一个正在运行的 ALIX.2D13 ( https://www.pcengines.ch ),它有一个 AMD Geode LX 作为主处理器。它运行 voyage linux,这是一个基于 debian jessi 的资源受限嵌入式设备的发行版。

     root@voyage:~# cat /proc/cpuinfo 
     processor       : 0
     vendor_id       : AuthenticAMD
     cpu family      : 5
     model           : 10
     model name      : Geode(TM) Integrated Processor by AMD PCS
     stepping        : 2
     cpu MHz         : 498.004
     cache size      : 128 KB
     physical id     : 0
     siblings        : 1
     core id         : 0
     cpu cores       : 1
     apicid          : 0
     initial apicid  : 0
     fdiv_bug        : no
     f00f_bug        : no
     coma_bug        : no
     fpu             : yes
     fpu_exception   : yes
     cpuid level     : 1
     wp              : yes
     flags           : fpu de pse tsc msr cx8 sep pge cmov clflush mmx mmxext 3dnowext 3dnow 3dnowprefetch vmmcall
     bugs            : sysret_ss_attrs
     bogomips        : 996.00
     clflush size    : 32
     cache_alignment : 32
     address sizes   : 32 bits physical, 32 bits virtual

当我按照https://nodejs.org/en/download/package-manager/上的说明安装 nodejs 8.x 时,我得到一些“无效的机器指令”(不确定是否正确,但从德语错误输出翻译而来)。当我下载 32 位 x86 的二进制文件以及手动编译它时,也会发生这种情况。

deps/v8/gypfiles/toolchain.gypi在下面的答案之后,我通过删除-msse2和添加来更改编译器标志-march=geode -mtune=geode。现在我得到了同样的错误,但有一个堆栈跟踪:

root@voyage:~/GIT/node# ./node


#
# Fatal error in ../deps/v8/src/ia32/assembler-ia32.cc, line 109
# Check failed: cpu.has_sse2().
#

==== C stack trace ===============================

    ./node(v8::base::debug::StackTrace::StackTrace()+0x12) [0x908df36]
    ./node() [0x8f2b0c3]
    ./node(V8_Fatal+0x58) [0x908b559]
    ./node(v8::internal::CpuFeatures::ProbeImpl(bool)+0x19a) [0x8de6d08]
    ./node(v8::internal::V8::InitializeOncePerProcessImpl()+0x96) [0x8d8daf0]
    ./node(v8::base::CallOnceImpl(int*, void (*)(void*), void*)+0x35) [0x908bdf5]
    ./node(v8::internal::V8::Initialize()+0x21) [0x8d8db6d]
    ./node(v8::V8::Initialize()+0xb) [0x86700a1]
    ./node(node::Start(int, char**)+0xd3) [0x8e89f27]
    ./node(main+0x67) [0x846845c]
    /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0xb74fc723]
    ./node() [0x846a09c]
Ungültiger Maschinenbefehl
root@voyage:~/GIT/node#

如果你现在查看这个文件,你会发现以下内容

... [line 107-110]
void CpuFeatures::ProbeImpl(bool cross_compile) {
base::CPU cpu;
CHECK(cpu.has_sse2());  // SSE2 support is mandatory.
CHECK(cpu.has_cmov());  // CMOV support is mandatory.
...

我评论了这条线,但仍然是“Ungültiger Maschinenbefehl”(无效的机器指令)。

这是gdb ./node显示(执行run):

root@voyage:~/GIT/node# gdb ./node
GNU gdb (Debian 7.7.1+dfsg-5) 7.7.1
[...]
This GDB was configured as "i586-linux-gnu".
[...]
Reading symbols from ./node...done.
(gdb) run
Starting program: /root/GIT/node/node 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
[New Thread 0xb7ce2b40 (LWP 29876)]
[New Thread 0xb74e2b40 (LWP 29877)]
[New Thread 0xb6ce2b40 (LWP 29878)]
[New Thread 0xb64e2b40 (LWP 29879)]

Program received signal SIGILL, Illegal instruction.
0x287a23c0 in ?? ()
(gdb) 

我认为,有必要用调试符号编译......

make clean
make CFLAGS="-g"

没有机会解决所有 SSE/SSE2 问题……放弃!看我最上面的部分

4

1 回答 1

2

结论:node.js + V8 在 x86 上运行时通常需要 SSE2。

V8 端口页面上x87(官方不支持)

如果需要,请联系/抄送 CL 中的 x87 团队。为此,请使用邮件列表 v8-x87-ports.at.googlegroups.com。

Javascript 通常需要浮点数(每个数字变量都是浮点数,使用整数数学只是一种优化),因此可能很难避免 V8 实际发出 FP 数学指令。

V8 目前被设计为始终JIT,而不是解释。当它仍在分析时,或者当它遇到使其“去优化”的东西时,它开始/回退到 JITing 未优化的机器代码。

正在努力向 V8 添加解释器,但这可能无济于事,因为解释器本身将使用 TurboFan JIT 后端编写。它的目的不是让 V8 可移植到它目前不知道如何 JIT 的架构。


疯狂的想法:node.js在软件仿真层(如Intel 的 SDE或者可能是qemu-user)之上运行,该层可以在仅支持 x87​​ 的 x86 CPU 上使用 SSE/SSE2 模拟 x86。他们使用动态翻译,因此对于不使用任何 SSE 指令的代码可能会以接近本机的速度运行。

这可能很疯狂,因为 node.js + V8 可能是一些可能混淆仿真层的虚拟内存技巧。不过,我想这qemu应该足够强大。


下面留下的原始答案作为调查其他程序此类问题的通用指南。(提示: grep Makefiles 等等 for -msseor -msse2,或者在pgrep -a gcc构建时检查编译器命令行)。


cpuinfo说它有 CMOV,这是一个 686 (ppro / p6) 功能。 这表示Geode 支持 i686。与“普通” CPU 相比,缺少的是 SSE2,它在最近的一些编译器版本中默认启用-m32(32 位模式)。

无论如何,你应该做的是用 编译-march=geode -O3,所以 gcc 或 clang 将使用你的 CPU 支持的一切,但仅此而已。

-O3 -msse2 -march=geode会告诉 gcc 它可以使用 Geode 支持的所有东西以及SSE2,所以你需要删除任何-msse-msse2选项,或者-mno-sse在它们之后添加。 在 node.js 中,deps/v8/gypfiles/toolchain.gypi正在设置-msse2.


使用-march=geode暗示-mtune=geode,这会影响不涉及使用新指令的代码生成选择,因此幸运的是,您的二进制文件将比您仅用于-mno-sse控制指令集内容而不覆盖-mtune=generic. (如果你在 geode 上构建,你可以使用-march=native,这应该与 using 相同-march=geode。)


另一种可能性是问题说明存在于 JIT 编译的 Javascript 函数中。

node.js 使用 V8。我做了一个快速的谷歌搜索,但没有找到任何关于告诉 V8 不要假设 SSE/SSE2 的信息。如果它没有用于浮点的后备代码生成策略(x87 指令),那么您可能必须完全禁用 JIT 并使其在解释器模式下运行。(速度较慢,所以这可能是个问题。)

但希望 V8 表现良好,并在 JITing 之前检查支持哪些指令集。


你应该通过运行来检查 gdb /usr/bin/node,看看哪里出了问题。 在 GDB 命令行上键入run my_program.js以启动程序。(当您第一次启动 gdb 时,您不能将 args 传递给 node.js。您必须在 gdb 中指定 args 时run。)

如果引发 SIGILL 的指令的地址位于映射到文件的内存区域中(/proc/pid/maps如果 gdb 没有告诉您,请查看),这会告诉您哪个提前编译的可执行文件或库负责。重新编译它-march=geode

如果它在匿名内存中,则很可能是 JIT 编译器输出。

当程序接收到 SIGILL 时 GDB 会在停止时打印指令地址。您还可以查看(32 位模式指令指针)print $ip的当前值。EIP

于 2017-07-16T02:12:26.450 回答