文章目录
- WebAssembly简介
- Binaryen简介
- Binaryen [version_90](https://github.com/WebAssembly/binaryen/tree/version_90) 新增获取版本号参数功能的`bug`
- 使用[Binaryen](https://github.com/WebAssembly/binaryen) [version_90](https://github.com/WebAssembly/binaryen/tree/version_90)预编译版本能正确获得版本号
- 使用[Binaryen](https://github.com/WebAssembly/binaryen) [version_90](https://github.com/WebAssembly/binaryen/tree/version_90)源代码包编译不能正确获得版本号
- 使用[Binaryen](https://github.com/WebAssembly/binaryen) [version_90](https://github.com/WebAssembly/binaryen/tree/version_90)源代码包编译不能正确获得版本号的原因分析
- 使用[Git](https://git-scm.com)获取[Binaryen](https://github.com/WebAssembly/binaryen) [version_90](https://github.com/WebAssembly/binaryen/tree/version_90)源代码编译能正确获得版本号
- 该`Bug`已在[Binaryen](https://github.com/WebAssembly/binaryen) [master](https://github.com/WebAssembly/binaryen/tree/master)分支中修正
- 对于使用[Brew](https://brew.sh)更新的建议
WebAssembly简介
在互联网的高速发展过程中,Web
协议族成为了互联网的主流协议,甚至于在某些语境中,Web
已经等于互联网,长期以来,Web
技术的管理组织W3C官方认证有三门语言:HTML、CSS、JavaScript,其中HTML和CSS是标记语言,而JavaScript编程语言。而在2019年12月5日,W3C宣布WebAssembly成为第四门Web
语言,同时发布WebAssembly核心规格第一版 WebAssembly Core Specification。
自从JavaScript诞生起到现在已经变成最流行的编程语言,这正是随着Web
的飞速发展推动的,随着Web
应用越来越复杂,使用JavaScript编写的程序也越来越复杂,在这个过程中逐渐暴露出JavaScript编程语言的各种问题,其中最突出的是性能问题,一方面是性能随着程序越来越复杂显著降低,另一方面是涌现出越来越多的要求高性能的需求,超出了目前在浏览器中运行JavaScript的性能。
于是WebAssembly诞生,WebAssembly,缩写为WASM
,与之前的三门Web
语言不同,HTML、CSS和JavaScript都是面向人类读写的文本格式,尽管后来有各种压缩、加密、混淆等工具,但是仍旧兼容于人类读写的文本格式。而WebAssembly不同,从设计伊始就定位于基于栈的虚拟机的二进制指令格式,或者称为字节码。面向虚拟机的设计使得WebAssembly具有广泛的移植能力。同时,字节码也广泛适于将各种高级语言编译成WebAssembly,目前常见的编译成WebAssembly的高级语言包括C
、C++
、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
版本已经支持在四个主流浏览器上Firefox、Chrome、Safari、Edge。
因为WebAssembly已经受到主流浏览器的广泛支持,因此在需要提升浏览器性能的场景中,可以直接采用WebAssembly技术实现,基于WebAssembly的字节码设计,可以在浏览器中将页面中的虚拟指令集程序编译成本地指令集运行,实现贴近本地指令集的性能。类似于Java、.net等其他字节码,WebAssembly编译后尺寸显著减小,在主要应用WebAssembly的浏览器场景中,有助于优化通过互联网分发时的加载时间,
同时,WebAssembly不像浏览器中运行JavaScript程序时需要有一个解释的过程,WebAssembly不需要解释过程,因此WebAssembly从网络下载到开始运行的速度比JavaScript有显著提升,这些都明显改进用户体验。这种优化方式并不像某些优化技术一样仅对遵循技术编写的全新程序有效,WebAssembly支持将现有代码编译为WebAssembly。
从字节码标准的角度看,WebAssembly的生态可以分成两部分,一部分是把各种高级编程语言或者其他形态的程序编译成字节码,另一部分是运行字节码的执行环境。目前来说,运行字节码的环境主要是主流浏览器,浏览器厂商根据WebAssembly规范实现虚拟机,因此这部分通常不需要程序员关心。程序员需要关注第一部分,也就是编译成字节码的各种工具,工具执行环境通过规范解耦合,当工具只需要编译出符合规范要求的字节码,则不需要关心在执行环境中的具体细节。
经常用到的WebAssembly相关的编译工具有两个。
- Emscripten,把
C
、C++
代码转换成wasm
、asm.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分支中修正
wget https://github.com/WebAssembly/binaryen/archive/master.tar.gz
创建用于解压缩Binaryen master源代码包的文件夹。
mkdir binaryen-master
进入用于解压缩Binaryen master源代码包的文件夹。
cd binaryen-master
tar -xkzvf ../master.tar.gz
cd 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