
Binaryen은 C ++로 작성된 WebAssembly를위한 컴파일러 및 툴 체인 인프라 라이브러리입니다. webAssembly를 쉽고 빠르며 효과적으로 컴파일하는 것을 목표로합니다.
쉬운 : Binaryen은 단일 헤더에 간단한 C API를 가지고 있으며 JavaScript에서도 사용할 수 있습니다. webAssembly와 같은 형태의 입력을 받아들이고 그것을 선호하는 컴파일러에 대한 일반적인 제어 흐름 그래프도 허용합니다.
빠른 : Binaryen의 내부 IR은 소형 데이터 구조를 사용하며 사용 가능한 모든 CPU 코어를 사용하여 완전히 병렬 코드 겐 및 최적화를 위해 설계되었습니다. Binaryen의 IR은 본질적으로 WebAssembly의 하위 집합이기 때문에 WebAssembly에 매우 쉽고 빠르게 컴파일됩니다.
효과 : Binaryen의 Optimizer에는 코드 크기와 속도를 향상시킬 수있는 많은 패스 (나중에 개요 참조)가 있습니다. 이러한 최적화는 Binaryen을 그 자체로 컴파일러 백엔드로 사용하기에 충분히 강력하게 만드는 것을 목표로합니다. 특정 초점 영역 중 하나는 WebAssembly 별 최적화 (일반 목적 컴파일러가 할 수 없음)에 있으며, 이는 JavaScript, CSS 등에 대한 미니 화와 유사한 WASM 미니 화로 생각할 수 있으며, 모두 언어별로 다릅니다.
Binaryen을 구성 요소 로 사용하는 도구 체인 (일반적으로 wasm-opt 실행)은 다음을 포함합니다.
Emscripten (c/c ++)wasm-pack (녹)J2CL (Java; J2Wasm )Kotlin (Kotlin/Wasm)Dart (플러터)wasm_of_ocaml (OCAML)이러한 작동 방식에 대한 자세한 내용은 V8 WASMGC 포팅 블로그 포스트의 도구 체인 아키텍처 부분을 참조하십시오.
Binaryen을 라이브러리 로 사용하는 컴파일러는 다음과 같습니다.
AssemblyScriptwasm2js 는 webAsSembly를 JS로 컴파일합니다Asterius 는 haskell을 webassembly로 컴파일합니다GrainBinaryen은 또한 할 수있는 일련
참여에 관심이 있으시면 기고 지침을 참조하십시오.
Binaryen의 내부 IR은 설계되었습니다
Binaryen IR과 WebAssembly 언어에는 몇 가지 차이점이 있습니다.
catch 블록에서만 지원됨)은 pop 하위 표현으로 표시됩니다.--generate-stack-ir --print-stack-ir 수행 할 수 있습니다. 스택 IR을 인쇄하면 WASM 파서에 유효합니다.)ref.func 가 주소를 채취 한 함수가 테이블에 있거나 (elem declare func $..) 통해 선언해야합니다. Binaryen은 필요할 때 해당 데이터를 방출하지만 IR로 표시되지는 않습니다. 즉, IR은 기능 참조를 선언 할 필요없이 작업을 수행 할 수 있습니다.local.get 은 유효성을 유지하기 위해 local.set 에 의해 구조적으로 지배적이어야합니다 (NULL의 기본값을 읽지 않음). WASM 사양과 일치하더라도 다음과 같은 사소한 세부 사항이 있습니다.Block 검증을 방해하지 않습니다. 이름이없는 블록은 이진 형식으로 방출되지 않으므로 (우리는 단지 그들의 내용물을 방출합니다), 우리는 무화 할 수없는 지역 주민들의 목적으로 그것들을 무시합니다. 결과적으로 Binaryen이 방출 한 WASM 텍스트를 읽으면 사양에 따라 검증되지 않아야 할 코드가 무엇인지 알 수 있지만 (WASM 텍스트 파서에서 검증되지 않을 수도 있음) 이진 형식으로는 존재하지 않습니다 (Binaryen이 방출하는 Binaries는 항상 어디에서나 작동하지 않습니다).pass.h 및 LocalStructuralDominance 클래스의 requiresNonNullableLocalFixups() 을 참조하십시오.ref.func 의 IR 유형은 항상 특정 함수 유형이며 일반 funcref 아닙니다. 또한 무효화 할 수 없습니다.try_table 보내는 유형에도 사용됩니다 (우리가 지점이 보내지 않으면 null이 전송되지 않음), 즉 (참조 exn) (ref null exn)를 보냅니다 (ref exn). 두 경우 모두 GC가 활성화되지 않으면 이진에서 덜 정제 된 유형을 방출합니다. 이진을 읽을 때 IR을 구축 할 때보다 세련된 유형이 적용됩니다.br_if 출력 유형은 더 세련됩니다. 값이 흐를 때 값의 유형을 가지고 있습니다. WASM 사양에서는 분기 대상의 유형이 덜 개선 될 수 있습니다. 여기에서보다 세련된 유형을 사용하면 모든 유형 정보를 사용하여 가능한 최상의 방식으로 최적화 할 수 있지만 일부 왕복 작업이 조금 다르게 보일 수 있습니다. 특히, Binaryen IR에서 유형이 더 세련된 br_if 방출 할 때 우리는 그 직후에 캐스트를 방출하여 출력이 WASM 사양에 올바른 유형을 갖도록합니다. 드문 경우에 몇 바이트의 추가 크기가 발생할 수 있습니다 ( br_if 값이 사용되지 않는 공동 케이스에서는이 오버 헤드를 피합니다).stringview_wtf16 등)를 ref.cast 사용하여 캐스트 할 수 있습니다. 이렇게하면 ref.cast 항상 모든 장소에서 사용할 수있게되므로 IR을 단순화합니다 (최적화에서 가능한 경우 ref.as_non_null 로 낮아집니다). StringRef 사양은 이것을 허용하지 않는 것처럼 보이며 바이너리 라이터가 ref.as_non_null 로 무효화 할 수없는 유형으로 문자열보기를 캐스팅하는 ref.cast 대체 할 수 있습니다. No-OP 인 문자열보기의 ref.cast 완전히 건너 뜁니다.결과적으로 일부 코너 케이스에서 왕복 변환 (WASM => Binaryen ir => WASM)이 코드를 약간 변경한다는 것을 알 수 있습니다.
src/wasm-stack.h 참조). Stack IR을 사용하면 WebAssembly의 이진 형식의 스택 머신 형태에 맞게 조정 된 많은 최적화를 허용합니다 (그러나 스택 IR은 주요 Binaryen IR보다 일반 최적화에 덜 효율적입니다). 특히 잘 최적화 된 WASM 파일이있는 경우, Binaryen이 Binaryen IR의보다 체계적인 형식에 맞아 간단한 왕복 변환 (최적화없이 읽고 쓰기 만하면)이 더 눈에 띄는 차이를 유발할 수 있습니다. 왕복 변환 중에도 최적화되면 Stack IR Opts가 실행되고 최종 WASM이 더 잘 최적화됩니다.Binaryen IR과 함께 작업 할 때 :
Binaryen 내재 함수는 수입 통화 (예 :
( import " binaryen-intrinsics " " foo " ( func $foo ))그런 식으로 구현하면 다른 도구가 읽고 쓸 수 있으며 이진 형식 오류에 대한 혼란스러운 오류는 사용자 정의 바이너리 형식 확장 기능이있는 경우 해당 도구에서 발생할 수 있습니다.
본질적인 방법은 Optimizer에 의해 최적화 될 수 있습니다. 그렇지 않은 경우 WASM을 배송하기 전에 낮추어야 합니다. 그렇지 않으면 존재하지 않는 가져 오기 호출처럼 보일 것입니다 (VMS는 해당 가져 오기에 적절한 값을 갖지 않는 오류가 표시됩니다). 최종 하강은 자동으로 수행되지 않습니다 . 사용자가 여러 최적화 단계의 파이프 라인을 가질 수 있거나 로컬 실험을 수행하거나 퍼징/감소 등을 수행 할 수 있기 때문에 도구는 사용자가 최적화를 완료하려는시기를 알지 못하기 때문에 Intrinsics 사용자는 해당 패스를 명시 적으로 실행해야합니다. WASM이 "최종 최적화"가 발생하기 전에 최종 최적화가 발생할 때만 알 수 있습니다. 일반적으로 최종 하강 후에 일부 추가 최적화가 가능할 수 있으므로 유용한 패턴은 정상적으로 내재적으로 한 번 최적화 한 다음, 낮추고 그 후 최적화하는 것입니다.
wasm-opt input.wasm -o output.wasm -O --intrinsic-lowering -O각각의 고유 한 것은 시맨틱을 정의합니다. 여기에는 최적화가 허용되는 내용과 최종 하강이 무엇을 바꿀 것인지를 포함합니다. 자세한 정의는 Intrinsics.h를 참조하십시오. 간단한 요약이 여기에 나타납니다.
call.without.effects : call_ref 와 유사하게 매개 변수를 수신하고 호출 할 함수에 대한 참조 및 해당 매개 변수와 함께 해당 기능을 호출 할 수 있다는 점을 제외하고는 해당 매개 변수와 함께 호출이 부작용이 없다고 가정 할 수 있다는 점을 제외하고는 해당 매개 변수와 함께 해당 기능을 호출합니다 (일반적으로 사용되는 결과가없는 경우). 이 저장소에는 bin/ (건물 지침 참조)에서 다음 도구를 작성하는 코드가 포함되어 있습니다.
wasm-opt : WebAsSembly를로드하고 Binaryen IR 패스를 실행합니다.wasm-as : 텍스트 형식 (현재 S- 표현 형식)으로 webAsSembly를 이진 형식 (Binaryen IR을 통과)으로 조립합니다.wasm-dis : 이진 형식의 텍스트 형식으로 webAssembly를 조립하지 않습니다 (Binaryen IR을 통과).wasm2js : WebAssembly-to-JS 컴파일러. 이것은 eMScripten에서 webAsSembly의 대안으로 JavaScript를 생성하는 데 사용됩니다.wasm-reduce : WebAssembly 파일의 테스트 케이스 감소기. 어떤 이유로 든 흥미로운 WASM 파일이 주어지면 (예 : 특정 VM에 충돌) WASM-Reduce는 동일한 속성을 가진 더 작은 WASM 파일을 찾을 수 있으며, 이는 종종 디버그하기 쉬운 종종 더 쉽습니다. 자세한 내용은 문서를 참조하십시오.wasm-shell : WebAsSembly 코드를로드하고 해석 할 수있는 쉘. 사양 테스트 스위트도 실행할 수도 있습니다.wasm-emscripten-finalize : LLVM+LLD에 의해 생성 된 WASM 바이너리를 취하고 EMScripten- 특이 적 패스를 수행합니다.wasm-ctor-eval : 컴파일 시간에 함수 (또는 함수의 일부)를 실행할 수있는 도구.wasm-merge : 여러 WASM 파일을 단일 파일로 병합하여 해당 가져 오기를 내보내기에 연결합니다. JS를위한 번들처럼.wasm-metadce : 모듈 사용 방법에 따라 WASM 파일의 일부를 유연한 방식으로 제거하는 도구입니다.binaryen.js : WASM 모듈을 생성하고 최적화하기위한 Binaryen 방법을 노출시키는 독립형 JavaScript 라이브러리. 빌드에 대해서는 NPM의 Binaryen.js를 참조하십시오 (또는 Github 또는 UNPKG에서 직접 다운로드). 최소 요구 사항 : Node.js v15.8 또는 Chrome V75 또는 Firefox V78.모든 Binaryen 도구는 결정적입니다. 즉, 동일한 입력이 주어지면 항상 동일한 출력을 가져와야합니다. (그렇지 않으면 행동하는 사례가 보이면 문제를 제기하십시오.)
각각의 사용 지침은 다음과 같습니다.
Binaryen에는 WebAssembly가 더 작고 빠르게 만들기 위해 많은 최적화 패스가 포함되어 있습니다. wasm-opt 사용하여 Binaryen Optimizer를 실행할 수 있지만 wasm2js 및 wasm-metadce 와 같은 다른 도구를 사용하는 동안 실행할 수도 있습니다.
addDefaultFunctionOptimizationPasses 와 같은 기능으로 설정됩니다.wasm-opt --help 참조하십시오.무엇을하는지에 대한 자세한 내용은 각 최적화 패스를 참조하십시오. 그러나 여기에는 관련된 것들에 대한 간단한 개요가 있습니다.
if 2 개가 공유 지침이있는 경우).block 병합하여 숫자를 줄입니다.local.set 제거합니다. (Coalescelocals와의 겹치십시오. 이것은 다른 모든 작업 Coalescelocals없이 방금 언급 한 특정 작업을 달성하므로 최적화 파이프 라인의 다른 장소에서 유용합니다.)br 또는 br_table 제거 할 수있는 다양한 변환을 포함하여 "Minor Control Flow Optimizations"패스 (가능한 if 중간에 br 있는 block 돌리는 등)을 포함한 주요 "Minor Control Flow Optimizations"패스.local.get/set/tee "최적화 패스, 세트 교체 및 가능한 경우 세트의 가치를 Get (및 Tee)로 이동하는 등의 작업을 수행합니다. 또한 로컬을 사용하여 값을 전달하는 대신 block/if/loop 리턴 값을 만듭니다.if 팔을 제거하는 것, 부작용이없는 일정한 값, 단일 자녀가있는 block 등을 제거하는 것과 같은 작업을 수행하십시오.위의 "LTO"는 최적화가 여러 기능에 걸쳐 작동한다는 점에서 링크 시간 최적화와 유사하다는 것을 의미하지만, Binaryen은 일반적으로 최종 링크 된 WASM에서 실행되므로 항상 "lto"입니다.
Binaryen Optimizer의 고급 최적화 기술에는 SSAification, Flat IR 및 Stack/Poppy IR이 포함됩니다.
최적화를 효과적으로 사용하는 방법에 대한 자세한 내용은 Optimizer Cookbook Wiki 페이지를 참조하십시오.
Binaryen은 또한 JavaScript, Asyncify 등의 합법화와 같은 최적화 이외의 다른 일을하는 다양한 패스를 포함합니다.
Binaryen은 git submodules (GTEST를 위해서만 쓰기 시작)를 사용하므로 구축하기 전에 하위 모듈을 초기화해야합니다.
git submodule init
git submodule update그 후 CMAKE로 구축 할 수 있습니다.
cmake . && make C ++ 17 컴파일러가 필요합니다. MACOS에서는 예를 들어 brew install cmake cmake 해야합니다. ninja 발전기로 사용할 수도 있습니다 : cmake -G Ninja . && ninja .
GTEST 의존성을 피하려면 -DBUILD_TESTS=OFF CMAKE를 전달할 수 있습니다.
Binaryen.js는 SDK를 통해 설치할 수있는 EMScripten을 사용하여 구축 할 수 있습니다.
emcmake cmake . && emmake make binaryen_jsemcmake cmake -DBUILD_FOR_BROWSER=ON . && emmake makeMicrosoft Visual Studio 설치 프로그램을 사용하여 "CMAKE 용 Visual C ++ 도구"구성 요소를 설치하십시오.
프로젝트 생성 :
mkdir build
cd build
" %VISUAL_STUDIO_ROOT%Common7IDECommonExtensionsMicrosoftCMakeCMakebincmake.exe " ..Visual_studio_root를 비주얼 스튜디오 설치 경로로 대체하십시오. Visual Studio 빌드 도구를 사용하는 경우 경로는 "C : Program Files (x86) Microsoft Visual Studio 2017 BuildTools"입니다.
개발자 명령 프롬프트에서 원하는 프로젝트를 작성하십시오.
msbuild binaryen.vcxprojCMAKE는 모든 프로젝트를 편리하게 구축하기 위해 "All_Build.vcxProj"라는 프로젝트를 생성합니다.
빌드는 emscripten, wasm-pack 등 Binaryen을 사용하는 다양한 툴체인에 의해 배포됩니다. Github에는 공식 릴리스가 있습니다.
https://github.com/webassembly/binaryen/releases
현재 다음 플랫폼의 빌드가 포함되어 있습니다.
Linux-x86_64Linux-arm64MacOS-x86_64MacOS-arm64Windows-x86_64Node.js (실험) : JavaScript+WebAssembly에서 wasm-opt 의 포트. Node.js가 실행되는 모든 플랫폼에서 node wasm-opt.js wasm-opt 의 기본 빌드에 대한 드롭 인 교체로 실행합니다. Node.js 18+가 필요합니다 (WASM EH 및 WASM 스레드의 경우). (이 빌드는 Deno, Bun 또는 기타 JavaScript+WebAssembly 환경에서도 실행될 수 있지만 Node.js.에서만 테스트됩니다.) 달리다
bin/wasm-opt [.wasm or .wat file] [options] [passes, see --help] [--help]WASM Optimizer는 webAsSembly를 입력으로 수신하고 변환 전달을 실행할 수 있으며 (전 및/또는 변환 후) 인쇄 할 수 있습니다. 예를 들어 시도하십시오
bin/wasm-opt test/lit/passes/name-types.wast -all -S -o -테스트 스위트에서 테스트 사례 중 하나를 출력합니다. 변환 패스를 실행하려면 시도하십시오
bin/wasm-opt test/lit/passes/name-types.wast --name-types -all -S -o - name-types 패스는 각 유형에 이름이 있고 이름이 매우 긴 유형 이름을 확인합니다. 두 명령의 출력을 비교하여 변환 원인의 변경 사항을 확인할 수 있습니다.
자체 변환 패스를 쉘에 추가하고 .cpp 파일을 src/passes 에 추가하고 쉘을 재건 할 수 있습니다. 예를 들어 코드와 같은 name-types 패스를 살펴보십시오.
더 많은 메모 :
bin/wasm-opt --help 참조하십시오.--debug 일부 디버깅 정보를 방출합니다. 개별 디버그 채널 ( #define DEBUG_TYPE xxx 를 통해 소스 코드에 정의 됨)은 쉼표로 구분 된 문자열의 목록으로 전달하여 활성화 할 수 있습니다. 예를 들어 : bin/wasm-opt --debug=binary . 이 디버그 채널은 BINARYEN_DEBUG 환경 변수를 통해 활성화 될 수도 있습니다.달리다
bin/wasm2js [input.wasm file]이것은 자바 스크립트를 콘솔에 인쇄합니다.
예를 들어 시도하십시오
bin/wasm2js test/hello_world.wat그 출력에는 포함됩니다
function add ( x , y ) {
x = x | 0 ;
y = y | 0 ;
return x + y | 0 | 0 ;
}번역으로
( func $add (; 0 ;) ( type $0 ) ( param $x i32 ) ( param $y i32 ) ( result i32 )
( i32.add
( local.get $x )
( local.get $y )
)
)WASM2JS의 출력은 ES6 모듈 형식입니다. 기본적으로 WASM 모듈을 ES6 모듈로 변환합니다 (이전 브라우저 및 Node.js 버전에서 실행하여 Babel 등을 사용하여 ES5로 변환 할 수 있음). Hello World 와트를 부르는 전체 예를 살펴 보겠습니다. 먼저 기본 JS 파일을 만듭니다.
// main.mjs
import { add } from "./hello_world.mjs" ;
console . log ( 'the sum of 1 and 2 is:' , add ( 1 , 2 ) ) ;이 실행 (ES6 모듈 지원이 포함 된 충분한 Node.js가 필요합니다) :
$ bin/wasm2js test/hello_world.wat -o hello_world.mjs
$ node --experimental-modules main.mjs
the sum of 1 and 2 is: 3WASM2JS의 출력을 염두에 두어야합니다.
-O 또는 다른 최적화 수준을 사용하여 릴리스 빌드에 대한 최적화로 WASM2J를 실행해야합니다. 전체 파이프 라인 (WASM 및 JS)을 따라 최적화합니다. 그러나 whitespace를 최소화하는 것과 같이 JS 미니 퍼가 모든 것을 수행하지는 않으므로 나중에 정상적인 JS 미니퍼를 실행해야합니다. wasm-ctor-eval 컴파일 시간에 함수 또는 그 일부를 실행합니다. 그렇게 한 후 런타임 상태를 WASM으로 직렬화하는데, 이는 "스냅 샷"을 취하는 것과 같습니다. WASM이 나중에로드되어 VM에서 실행되면 이미 실행 된 작업을 다시 수행하지 않고 해당 시점부터 계속 실행됩니다.
예를 들어이 작은 프로그램을 고려하십시오.
( module
;; A global variable that begins at 0.
( global $global ( mut i32 ) ( i32.const 0 ))
( import " import " " import " ( func $import ))
( func " main "
;; Set the global to 1.
( global.set $global
( i32.const 1 ))
;; Call the imported function. This *cannot* be executed at
;; compile time.
( call $import )
;; We will never get to this point, since we stop at the
;; import.
( global.set $global
( i32.const 2 ))
)
)다음과 같이 컴파일 시간에 IT의 일부를 평가할 수 있습니다.
wasm-ctor-eval input.wat --ctors=main -S -o - 이것은 우리가 실행하려는 단일 함수가 있음을 알 수 있습니다 ( "CTOR"는 프로그램의 진입 지점 앞에 실행되는 코드에서 나오는 이름 인 "Global Constructor"의 경우 짧은 다음 stdout 에 텍스트로 인쇄합니다. 결과는 다음과 같습니다.
trying to eval main
...partial evalling successful, but stopping since could not eval: call import: import.import
...stopping
(module
(type $none_ = > _none (func))
(import " import " " import " (func $import ))
(global $global (mut i32) (i32.const 1))
(export " main " (func $0 _0))
(func $0 _0
(call $import )
(global.set $global
(i32.const 2)
)
)
) 로깅은 우리가 예상대로 main() 의 일부를 평가할 수있는 것을 보여줍니다. 예상대로 우리는 첫 번째 global.get 평가할 수 있지만, 가져 오기 기능으로 호출 할 때 중지합니다 (WASM이 실제로 VM에서 실행될 때 해당 함수가 무엇인지 알지 못하기 때문에). 출력에서 글로벌 값이 0에서 1으로 업데이트되었고 첫 번째 global.get 제거되었음을 주목하십시오. Get가 제거되었습니다. WASM은 이제 VM에서 실행할 때 wasm-ctor-eval 중지 된 지점에서 완벽하게 실행되는 상태에 있습니다.
이 작은 예에서 우리는 단지 소량의 작업을 저장했습니다. 저장 할 수있는 작업량은 프로그램에 따라 다릅니다. (순수한 계산을 전면으로 수행하고 가능한 한 늦게 수입으로 전화를 걸 수 있습니다.)
wasm-ctor-eval 의 이름은 앞에서 언급했듯이 글로벌 생성자 기능과 관련이 있지만 여기에서 실행할 수있는 내용에는 제한이 없습니다. 내용물이 적합한 경우 WASM의 모든 수출을 실행할 수 있습니다. 예를 들어, emscripten에서 wasm-ctor-eval 가능한 경우 main() 에서 실행됩니다.
wasm-merge WASM 파일을 결합합니다. 예를 들어, 여러 툴체인에서 WASM 파일을 사용하는 프로젝트가 있다고 상상해보십시오. 그런 다음 배송 전에 단일 WASM 파일로 병합하는 것이 도움이 될 수 있습니다. 단일 WASM 파일에서 모듈 간의 호출은 모듈 내부의 일반적인 통화가되어 인쇄되고 데드 코드가 제거 될 수 있으므로 속도와 크기를 개선 할 수 있습니다.
wasm-merge 정상 WASM 파일에서 작동합니다. WASM- wasm-ld Object 파일에서 작동하기 때문에 그 점에서 wasm-ld 와 다릅니다. wasm-merge 툴체인 중 하나 이상이 WASM 객체 파일을 사용하지 않는 다중 툴 체인 상황에서 도움이 될 수 있습니다.
예를 들어,이 두 WASM 파일이 있다고 상상해보십시오.
;; a.wasm
( module
( import " second " " bar " ( func $second.bar ))
( export " main " ( func $func ))
( func $func
( call $second.bar )
)
) ;; b.wasm
( module
( import " outside " " log " ( func $log ( param i32 )))
( export " bar " ( func $func ))
( func $func
( call $log
( i32.const 42 )
)
)
) 로컬 드라이브의 파일 이름은 a.wasm 및 b.wasm 이지만, 병합 / 번들 목적을 위해 첫 번째는 "first" 라고 말하고 두 번째는 "second" 라고 가정 해 봅시다. 즉, 첫 번째 모듈의 "second.bar" 가져 오기가 두 번째 모듈에서 함수 $func 호출하기를 원합니다. 다음은 WASM-MERGE 명령입니다.
wasm-merge a.wasm first b.wasm second -o output.wasm첫 번째 WASM 파일, 이름, 두 번째 WASM 파일 및 이름을 제공합니다. 병합 된 출력은 다음과 같습니다.
( module
( import " outside " " log " ( func $log ( param i32 )))
( export " main " ( func $func ))
( export " bar " ( func $func_2 ))
( func $func
( call $func_2 )
)
( func $func_2
( call $log
( i32.const 42 )
)
)
) wasm-merge 두 파일을 하나로 결합하여 기능, 가져 오기 등을 병합하면서 이름 충돌을 해결하고 해당 가져 오기를 내보내기에 연결했습니다. 특히 $func $func_2 호출하는 방법에 유의하십시오. 이것이 바로 우리가 원하는 것입니다. $func_2 두 번째 모듈의 기능입니다 (이름 충돌을 피하기 위해 이름이 바뀌 었음).
이 예제의 WASM 출력은 추가 최적화의 이점을 얻을 수 있습니다. 먼저, $func_2 에 대한 호출은 이제 쉽게 인쇄 될 수 있으므로 wasm-opt -O3 실행하여 우리를 위해 그렇게 할 수 있습니다. 또한 WASM-Metadce를 실행할 수있는 모든 수입 및 수출이 필요하지 않을 수 있습니다. 좋은 워크 플로우는 wasm-merge 실행 한 다음 wasm-metadce 실행 한 다음 wasm-opt 로 마무리하는 것입니다.
wasm-merge "JS Bundler"의 의미에서 WASM 파일의 번들과 비슷하지만 WASM의 경우입니다. 즉, 위의 WASM 파일을 사용하면이 JS 코드가 런타임에 인스턴스화되고 연결되어 있다고 상상해보십시오.
// Compile the first module.
var first = await fetch ( "a.wasm" ) ;
first = new WebAssembly . Module ( first ) ;
// Compile the first module.
var second = await fetch ( "b.wasm" ) ;
second = new WebAssembly . Module ( second ) ;
// Instantiate the second, with a JS import.
second = new WebAssembly . Instance ( second , {
outside : {
log : ( value ) => {
console . log ( 'value:' , value ) ;
}
}
} ) ;
// Instantiate the first, importing from the second.
first = new WebAssembly . Instance ( first , {
second : second . exports
} ) ;
// Call the main function.
first . exports . main ( ) ; wasm-merge 가하는 일은 기본적으로 JS가하는 일입니다. 수입을 내보내기에 연결하여 제공 한 모듈 이름을 사용하여 이름을 해결합니다. 즉, wasm-merge 실행함으로써 우리는 모듈을 런타임에서 컴파일 시간으로 연결하는 작업을 이동하고 있습니다. 결과적으로 wasm-merge 실행 한 후 동일한 결과를 얻으려면 JS가 훨씬 적습니다.
// Compile the single module.
var merged = await fetch ( "merged.wasm" ) ;
merged = new WebAssembly . Module ( merged ) ;
// Instantiate it with a JS import.
merged = new WebAssembly . Instance ( merged , {
outside : {
log : ( value ) => {
console . log ( 'value:' , value ) ;
}
}
} ) ;
// Call the main function.
merged . exports . main ( ) ;우리는 여전히 병합 된 WASM을 가져 와서 컴파일해야하며 JS 가져 오기를 제공해야하지만 두 개의 WASM 모듈을 연결하는 작업은 더 이상 필요하지 않습니다.
내보내기 이름이 겹치는 경우 기본적으로 wasm-merge 오류입니다. 즉, wasm-merge 겹치는 기능 이름 등을 자동으로 처리합니다. 왜냐하면 코드는 외부로 표시되지 않기 때문입니다 (코드는 여전히 동일하게 동일합니다). 그러나 우리가 내보내기로 바뀌면 외부는 새로운 내보내기 이름을 기대하도록 수정되어야합니다.
내보내기를 바꾸고 싶다면 --rename-export-conflicts 와 함께 wasm-merge 실행하십시오. 나중에 수출은 이전 수출과 겹치지 않도록 접미사가 추가됩니다. 접미사는 결정적이므로, 일단 그들이 무엇인지 보면 외부에서 호출 할 수 있습니다.
또 다른 옵션은 이름이 상충되는 나중에 수출을 건너 뛸 수있는 --skip-export-conflicts 사용하는 것입니다. 예를 들어, 이것은 첫 번째 모듈이 외부와 상호 작용하는 유일한 모듈 인 경우 첫 번째 모듈과 단지 첫 번째 모듈과 상호 작용하는 경우에 유용 할 수 있습니다.
wasm-merge 멀티 메모리 및 다중 테이블 기능을 사용합니다. 즉, 여러 입력 모듈에 각각 메모리가 있으면 출력 WASM에는 여러 메모리가 있으며 멀티 메모리 기능에 따라 다릅니다. 즉, 이전 WASM VM이 WASM을 실행하지 못할 수도 있습니다. (그러한 이전 VM의 해결 방법으로, 당신은 여러 기억을 하나의 기억으로 낮추기 위해 wasm-opt --multi-memory-lowering 실행할 수 있습니다.)
./check.py (또는 python check.py )는 test/ 의 테스트 케이스에서 wasm-shell , wasm-opt 등을 실행하고 출력을 확인합니다.
check.py 스크립트는 몇 가지 옵션을 지원합니다.
./check.py [--interpreter = /path/to/interpreter] [TEST1] [TEST2].../check.py --list-suites 실행하십시오.emcc 또는 nodejs 필요합니다. 도구를 찾을 수 없으면 실행되지 않으며 경고가 표시됩니다.tests/spec 업스트림에서 GIT 서브 모듈에서 테스트를 받았습니다. ./check.py 실행하면 업데이트해야합니다. 우리는 레거시 WASM-OPT 테스트를 점차적으로 포팅하여 조명을 수정할 때 lit 및 filecheck 사용하려고 노력하고 있습니다. 출력이 낭비되는 passes 테스트의 경우 scripts/port_passes_tests_to_lit.py 사용하여 자동으로 수행 할 수 있으며 출력이 낭비되는 비 passes 테스트의 경우 간단한 수동 포트를 수행하는 방법에 대한 예는 #4779를 참조하십시오.
조명 테스트의 경우 테스트 기대치 (체크 라인)를 Binaryen으로 변경하면 자동으로 업데이트 될 수 있습니다. scripts/update_lit_checks.py 참조하십시오.
비 조명 테스트는 대부분의 경우 자동으로 업데이트 될 수 있습니다. scripts/auto_update_tests.py 참조하십시오.
./third_party/setup.py [mozjs | v8 | wabt | all] (또는 python third_party/setup.py ) SpiderMonkey JS Shell, V8 JS Shell 및 WABT와 같은 필요한 종속성을 third_party/ 입니다. 다른 스크립트는 설치하면 자동으로이를 선택합니다.
pip3 install -r requirements-dev.txt 실행하여 lit 테스트에 대한 요구 사항을 얻으십시오. Linux, ~/.local/bin $PATH 에 위치 pip 설치가 필요합니다.
./scripts/fuzz_opt.py [--binaryen-bin = build/bin] (또는 python scripts/fuzz_opt.py )는 가능한 버그가 발견 될 때까지 임의의 패스가있는 임의의 입력에서 다양한 퍼지 모드를 실행합니다. 모든 세부 사항은 위키 페이지를 참조하십시오.
Binaryen can read and write source maps (see the -ism and -osm flags to wasm-opt ). It can also read and read source map annotations in the text format, that is,
;; @ src.cpp:100:33
( i32.const 42 ) That 42 constant is annotated as appearing in a file called src.cpp at line 100 and column 33 . Source maps and text format annotations are interchangeable, that is, they both lead to the same IR representation, so you can start with an annotated wat and have Binaryen write that to a binary + a source map file, or read a binary + source map file and print text which will contain those annotations.
The IR representation of source map info is simple: in each function we have a map of expressions to their locations. Optimization passes should update the map as relevant. Often this "just works" because the optimizer tries to reuse nodes when possible, so they keep the same debug info.
The text format annotations support a shorthand in which repeated annotations are not necessary. For example, children are tagged with the debug info of the parent, if they have no annotation of their own:
;; @ src.cpp:100:33
( i32.add
( i32.const 41 ) ;; This receives an annotation of src.cpp:100:33
;; @ src.cpp:111:44
( i32.const 1 )
)The first const will have debug info identical to the parent, because it has none specified, and generally such nesting indicates a "bundle" of instructions that all implement the same source code.
Note that text printing will not emit such repeated annotations, which can be confusing. To print out all the annotations, set BINARYEN_PRINT_FULL=1 in the environment. That will print this for the above add :
[i32] ;; @ src.cpp:100:33
( i32.add
[i32] ;; @ src.cpp:100:33
( i32.const 41 )
[i32] ;; @ src.cpp:111:44
( i32.const 1 )
) (full print mode also adds a [type] for each expression, right before the debug location).
The debug information is also propagated from an expression to its next sibling:
;; @ src.cpp:100:33
( local.set $x
( i32.const 0 )
)
( local.set $y ;; This receives an annotation of src.cpp:100:33
( i32.const 0 )
) You can prevent the propagation of debug info by explicitly mentioning that an expression has not debug info using the annotation ;;@ with nothing else:
;; @ src.cpp:100:33
( local.set $x
;; @
( i32.const 0 ) ;; This does not receive any annotation
)
;; @
( local.set $y ;; This does not receive any annotation
( i32.const 7 )
) This stops the propagatation to children and siblings as well. So, expression (i32.const 7) does not have any debug info either.
There is no shorthand in the binary format. That is, roundtripping (writing and reading) through a binary + source map should not change which expressions have debug info on them or the contents of that info.
The source maps format defines a mapping using segments , that is, if a segment starts at binary offset 10 then it applies to all instructions at that offset and until another segment begins (or the end of the input is reached). Binaryen's IR represents a mapping from expressions to locations, as mentioned, so we need to map to and from the segment-based format when writing and reading source maps.
That is mostly straightforward, but one thing we need to do is to handle the lack of debug info in between things that have it. If we have ABC where B lacks debug info, then just emitting a segment for A and C would lead A 's segment to also cover B , since in source maps segments do not have a size - rather they end when a new segment begins. To avoid B getting smeared in this manner, we emit a source maps entry to B of size 1, which just marks the binary offset it has, and without the later 3 fields of the source file, line number, and column. (This appears to be the intent of the source maps spec, and works in browsers and tools.)
Binaryen also has optional support for DWARF. This primarily just tracks the locations of expressions and rewrites the DWARF's locations accordingly; it does not handle things like re-indexing of locals, and so passes that might break DWARF are disabled by default. As a result, this mode is not suitable for a fully optimized release build, but it can be useful for local debugging.
Binaryen's name was inspired by Emscripten 's: Emscripten's name suggests it converts something into a script - specifically JavaScript - and Binaryen's suggests it converts something into a binary - specifically WebAssembly . Binaryen began as Emscripten's WebAssembly generation and optimization tool, so the name fit as it moved Emscripten from something that emitted the text-based format JavaScript (as it did from its early days) to the binary format WebAssembly (which it has done since WebAssembly launched).
"Binaryen" is pronounced in the same manner as "Targaryen".
Yes, it does. Here's a step-by-step tutorial on how to compile it under Windows 10 x64 with with CMake and Visual Studio 2015 . However, Visual Studio 2017 may now be required. Help would be appreciated on Windows and OS X as most of the core devs are on Linux.