Binaryen 9.0版本编译的一个bug

WebAssembly简介

在互联网的高速发展过程中,Web协议族成为了互联网的主流协议,甚至于在某些语境中,Web已经等于互联网,长期以来,Web技术的管理组织W3C官方认证有三门语言:HTMLCSSJavaScript,其中HTMLCSS是标记语言,而JavaScript编程语言。而在2019年12月5日W3C宣布WebAssembly成为第四门Web语言,同时发布WebAssembly核心规格第一版 WebAssembly Core Specification

自从JavaScript诞生起到现在已经变成最流行的编程语言,这正是随着Web的飞速发展推动的,随着Web应用越来越复杂,使用JavaScript编写的程序也越来越复杂,在这个过程中逐渐暴露出JavaScript编程语言的各种问题,其中最突出的是性能问题,一方面是性能随着程序越来越复杂显著降低,另一方面是涌现出越来越多的要求高性能的需求,超出了目前在浏览器中运行JavaScript的性能。

于是WebAssembly诞生,WebAssembly,缩写为WASM,与之前的三门Web语言不同,HTMLCSSJavaScript都是面向人类读写的文本格式,尽管后来有各种压缩、加密、混淆等工具,但是仍旧兼容于人类读写的文本格式。而WebAssembly不同,从设计伊始就定位于基于栈的虚拟机的二进制指令格式,或者称为字节码。面向虚拟机的设计使得WebAssembly具有广泛的移植能力。同时,字节码也广泛适于将各种高级语言编译成WebAssembly,目前常见的编译成WebAssembly的高级语言包括CC++Rust。由于WebAssembly的速度显著快于JavaScript,使得对于对于性能有较高要求的应用得以有机会在浏览器中直接运行,类似的对于性能有较高要求的应用,也可以借助WebAssembly适应更广泛的场景。

内存安全的沙箱环境是虚拟机的基本特征,需要注意WebAssembly虚拟机是运行时环境级别的虚拟机,与模拟计算机硬件的虚拟化技术不同。模拟计算机硬件的虚拟化技术借助硬件可以达到几乎接近直接运行的运行效率。基于操作系统内核的实现也仅有少量的性能损失,此时,所运行的程序需要使用CPU指令集,也就局限于在CPU硬件上。而WebAssembly虚拟机需要借助软件将字节码翻译成CPU指令运行,相对来说,性能有明显损失,可能仅有50%左右,好处就是不局限于CPU硬件,抹平了不同CPU架构的指令集,可以运行在各种设备上。这个特性和Java.net的运行时环境虚拟机相同,也就具有广泛的可移植特征。

Java.net虚拟机是从头开始设计,因此和浏览器的结合并不紧密,例如Java通过JavaApplet技术嵌入到网页中,而WebAssembly的出发点即为基于目前的浏览器使用场景,因此WebAssembly的运行时环境可以在现有的JavaScript虚拟机中实现。同时,WebAssembly也遵循网页中JavaScript的各种既定规格要求,例如强制执行浏览器的同源和权限安全策略。目前,WebAssembly 1.0版本已经支持在四个主流浏览器上FirefoxChromeSafariEdge

因为WebAssembly已经受到主流浏览器的广泛支持,因此在需要提升浏览器性能的场景中,可以直接采用WebAssembly技术实现,基于WebAssembly的字节码设计,可以在浏览器中将页面中的虚拟指令集程序编译成本地指令集运行,实现贴近本地指令集的性能。类似于Java.net等其他字节码,WebAssembly编译后尺寸显著减小,在主要应用WebAssembly的浏览器场景中,有助于优化通过互联网分发时的加载时间,

同时,WebAssembly不像浏览器中运行JavaScript程序时需要有一个解释的过程,WebAssembly不需要解释过程,因此WebAssembly从网络下载到开始运行的速度比JavaScript有显著提升,这些都明显改进用户体验。这种优化方式并不像某些优化技术一样仅对遵循技术编写的全新程序有效,WebAssembly支持将现有代码编译为WebAssembly

从字节码标准的角度看,WebAssembly的生态可以分成两部分,一部分是把各种高级编程语言或者其他形态的程序编译成字节码,另一部分是运行字节码的执行环境。目前来说,运行字节码的环境主要是主流浏览器,浏览器厂商根据WebAssembly规范实现虚拟机,因此这部分通常不需要程序员关心。程序员需要关注第一部分,也就是编译成字节码的各种工具,工具执行环境通过规范解耦合,当工具只需要编译出符合规范要求的字节码,则不需要关心在执行环境中的具体细节。

经常用到的WebAssembly相关的编译工具有两个。

  • Emscripten,把CC++代码转换成wasmasm.js
  • Binaryen,把中间表示IR转换成wasm,并且提供wasm的编译时优化、wasm虚拟机、wasm压缩等功能。

Binaryen简介

Binaryen是一套全面的工具,从编译器架构的角度看,属于编译器的后端,也就是输出格式为WebAssembly,主要的技术特点是C API和一套自己的逻辑程序的中间表示IR,并在IR的基础上执行一些优化等操作。

2019年12月24日,Binaryen发布version_90版本,该版本也是WebAssembly称为W3C官方认证第四门语言后的第一次发布。发布内容包括各平台的预编译版本和源代码包。

Binaryen version_90 新增获取版本号参数功能的bug

version_90版本中新增了获取版本号的功能。相关源代码位置在binaryen/src/support/command-line.cpp#L56

  add("--version",
      "",
      "Output version information and exit",
      Arguments::Zero,
      [command](Options*, const std::string&) {
        std::cout << command << " " << BINARYEN_VERSION_INFO << "\n";
        exit(0);
      });

不过这里有一个bug,在某些情况下会导致不能得到预期的结果。由于Binaryen通常作为构建过程中调用的工具,当构建过程依赖获取版本号时,不能得到正确的版本号将导致构建过程失败。

使用Binaryen version_90预编译版本能正确获得版本号

下载Mac版本Binaryen预编译包,我的电脑是Mac,所以下载Mac版本Binaryen预编译包。

wget https://github.com/WebAssembly/binaryen/releases/download/version_90/binaryen-version_90-x86_64-apple-darwin.tar.gz

下载Mac版本Binaryen预编译包的校验文件。

wget https://github.com/WebAssembly/binaryen/releases/download/version_90/binaryen-version_90-x86_64-apple-darwin.tar.gz.sha256

校验下载的Mac版本Binaryen预编译包。

sha256sum --check binaryen-version_90-x86_64-apple-darwin.tar.gz.sha256

输出如下内容表示通过校验。

binaryen-version_90-x86_64-apple-darwin.tar.gz: OK

创建用于解压缩Mac版本Binaryen预编译包的文件夹。

mkdir binaryen-version_90-x86_64-apple-darwin

进入用于解压缩Mac版本Binaryen预编译包的文件夹。

cd binaryen-version_90-x86_64-apple-darwin

解压缩Mac版本Binaryen预编译包。

tar -xkzvf ../binaryen-version_90-x86_64-apple-darwin.tar.gz

查询Mac版本Binaryen预编译包中程序的版本。

./binaryen-version_90/wasm-opt --version

可以看到正确获取Binaryen版本号。

wasm-opt version_90

使用Binaryen version_90源代码包编译不能正确获得版本号

下载Binaryen version_90源代码包。

wget https://github.com/WebAssembly/binaryen/archive/version_90.tar.gz

创建用于解压缩Binaryen version_90源代码包的文件夹。

mkdir binaryen-version_90

进入用于解压缩Binaryen version_90源代码包的文件夹。

cd binaryen-version_90

解压缩Binaryen version_90源代码包。

tar -xkzvf ../binaryen-version_90.tar.gz

进入Binaryen version_90源代码文件夹。

cd binaryen-version_90

编译Binaryen version_90源代码。

cmake . && make

编译完成后查询Binaryen程序的版本。

./bin/wasm-opt --version

会发现没有获取Binaryen的版本号。

wasm-opt (unable to determine version)

使用Binaryen version_90源代码包编译不能正确获得版本号的原因分析

--version参数开始排查。

grep --binary-files=without-match --recursive --regexp='--version' .

结果为:

./check.py:    print('[ checking --version ... ]\n')
./check.py:        print('.. %s --version' % e)
./check.py:        out, err = subprocess.Popen([e, '--version'],
./scripts/test/shared.py:        subprocess.check_call([NODEJS, '--version'],
./scripts/test/shared.py:        subprocess.check_call([MOZJS, '--version'],
./scripts/test/shared.py:        subprocess.check_call([EMCC, '--version'],
./scripts/test/shared.py:        [os.path.join(options.binaryen_test, 'emscripten', 'emcc'), '--version'],
./src/support/command-line.cpp:  add("--version",

可以看到--version参数在binaryen/src/support/command-line.cpp#L56中定义,查看相关代码。

grep --regexp='--version' --after=7 ./src/support/command-line.cpp

可以看到如下代码。

  add("--version",
      "",
      "Output version information and exit",
      Arguments::Zero,
      [command](Options*, const std::string&) {
        std::cout << command << " " << BINARYEN_VERSION_INFO << "\n";
        exit(0);
      });

继续排查BINARYEN_VERSION_INFO

grep --binary-files=without-match --recursive --regexp='BINARYEN_VERSION_INFO' .

结果为:

./CMakeLists.txt:  set(BINARYEN_VERSION_INFO "(unable to determine version)")
./CMakeLists.txt:  set(BINARYEN_VERSION_INFO "${GIT_HASH}")
./config.h.in:#cmakedefine BINARYEN_VERSION_INFO "${BINARYEN_VERSION_INFO}"
./src/support/command-line.cpp:        std::cout << command << " " << BINARYEN_VERSION_INFO << "\n";

可以看到BINARYEN_VERSION_INFO在文件binaryen/blob/version_90/CMakeLists.txt#L24中定义,查看相关代码。

grep --regexp='BINARYEN_VERSION_INFO' --context=10 ./CMakeLists.txt

可以看到如下代码。

find_package(Git QUIET REQUIRED)
execute_process(COMMAND
             "${GIT_EXECUTABLE}" --git-dir=${CMAKE_CURRENT_SOURCE_DIR}/.git describe --tags
         RESULT_VARIABLE
             GIT_HASH_RESULT
         OUTPUT_VARIABLE
             GIT_HASH
         OUTPUT_STRIP_TRAILING_WHITESPACE)
if(${GIT_HASH_RESULT})
  message(WARNING "Error running git describe to determine version")
  set(BINARYEN_VERSION_INFO "(unable to determine version)")
else()
--
else()
  set(BINARYEN_VERSION_INFO "${GIT_HASH}")
endif()
configure_file(config.h.in config.h)

从代码可以看到,BINARYEN_VERSION_INFO使用如下代码获取git仓库的tags得到版本号。

"${GIT_EXECUTABLE}" --git-dir=${CMAKE_CURRENT_SOURCE_DIR}/.git describe --tags

GitHub.com自动打包的源代码中不包含git相关信息,自然也就获取不到版本号。

使用Git获取Binaryen version_90源代码编译能正确获得版本号

使用ghq工具获取Binaryen源代码。

ghq get https://github.com/WebAssembly/binaryen

进入下载的文件夹。

cd $(ghq list --full-path https://github.com/WebAssembly/binaryen)

查看tag列表。

git tag

检出指定tag

git checkout version_90

编译Binaryen源代码。

cmake . && make

编译完成后查询Binaryen程序的版本。

./bin/wasm-opt --version

可以看到正确获取Binaryen版本号。

wasm-opt version_90

Bug已在Binaryen master分支中修正

下载Binaryen master源代码包。

wget https://github.com/WebAssembly/binaryen/archive/master.tar.gz

创建用于解压缩Binaryen master源代码包的文件夹。

mkdir binaryen-master

进入用于解压缩Binaryen master源代码包的文件夹。

cd binaryen-master

解压缩Binaryen master源代码包。

tar -xkzvf ../master.tar.gz

进入Binaryen master源代码文件夹。

cd binaryen-master

编译Binaryen master源代码。

cmake . && make

由于Binaryen master仍旧处于开发状态,因此编译经常失败,如果成功将能获得正确的版本信息。

对于使用Brew更新的建议

Mac上使用brew install binaryen安装Binaryen比较方便,可惜Brew中的预编译版本并非是Binaryen version_90的预编译包,而是基于Binaryen version_90源代码包的编译版本,因此Brew中的预编译版本或者源代码编译都没有版本信息,由于该Bug已修复,建议手工使用Binaryen version_90的预编译包覆盖Brew安装的版本,

由于该Bug已经修复,因此不必卸载已安装的Binaryen,可以查询Binaryen的安装位置。

brew info binaryen

安装位置为

/usr/local/Cellar/binaryen/90

Binaryen version_90预编译版本复制到Brew的安装位置。

sudo cp binaryen-version_90-x86_64-apple-darwin/binaryen-version_90/* /usr/local/Cellar/binaryen/90/bin

运行程序查询版本。

wasm-opt --version

可以看到正确获取Binaryen版本号。

wasm-opt version_90
发布了1058 篇原创文章 · 获赞 107 · 访问量 290万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 撸撸猫 设计师: 设计师小姐姐

分享到微信朋友圈

×

扫一扫,手机浏览